diff --git a/qbittorrent_performer_sorter.html b/qbittorrent_performer_sorter.html
index 1649584..a15bb4c 100644
--- a/qbittorrent_performer_sorter.html
+++ b/qbittorrent_performer_sorter.html
@@ -2627,19 +2627,20 @@
qbTabBadge.textContent = qbState.torrents.length;
renderTable();
updateStats();
+ showToast(`Loaded ${qbState.torrents.length} torrents (${cachedLoadedCount} restored from SQLite cache)`, 'success');
- // Auto Stash lookup
+ // Auto Stash lookup in background (non-blocking)
if (stashAutoLookupSelect && stashAutoLookupSelect.value === 'true') {
const unanalyzed = qbState.torrents.filter(t => !t.analysis && t.moveStatus !== 'moved');
if (unanalyzed.length > 0) {
- const stashMatches = await matchItemsWithStash(unanalyzed, 'qb');
- if (stashMatches > 0) {
- showToast(`⚡ Stash matched ${stashMatches} / ${unanalyzed.length} torrents!`, 'success');
- }
+ setTimeout(async () => {
+ const stashMatches = await matchItemsWithStash(unanalyzed, 'qb');
+ if (stashMatches > 0) {
+ showToast(`⚡ Stash matched ${stashMatches} / ${unanalyzed.length} torrents!`, 'success');
+ }
+ }, 50);
}
}
-
- showToast(`Loaded ${qbState.torrents.length} torrents (${cachedLoadedCount} restored from SQLite cache)`, 'success');
} catch (err) {
showToast(err.message, 'error');
} finally {
@@ -3321,19 +3322,20 @@ Return ONLY valid JSON matching:
smbTabBadge.textContent = smbState.files.length;
renderSmbTable();
updateSmbStats();
+ showToast(`Scanned ${smbState.files.length} SMB video files (${cachedLoadedCount} restored from SQLite cache)`, 'success');
- // Auto Stash lookup
+ // Auto Stash lookup in background (non-blocking)
if (stashAutoLookupSelect && stashAutoLookupSelect.value === 'true') {
const unanalyzed = smbState.files.filter(f => !f.analysis && f.moveStatus !== 'moved');
if (unanalyzed.length > 0) {
- const stashMatches = await matchItemsWithStash(unanalyzed, 'smb');
- if (stashMatches > 0) {
- showToast(`⚡ Stash matched ${stashMatches} / ${unanalyzed.length} video files!`, 'success');
- }
+ setTimeout(async () => {
+ const stashMatches = await matchItemsWithStash(unanalyzed, 'smb');
+ if (stashMatches > 0) {
+ showToast(`⚡ Stash matched ${stashMatches} / ${unanalyzed.length} video files!`, 'success');
+ }
+ }, 50);
}
}
-
- showToast(`Scanned ${smbState.files.length} SMB video files (${cachedLoadedCount} restored from SQLite cache)`, 'success');
} catch (err) {
showToast(`SMB scan failed: ${err.message}`, 'error');
} finally {
diff --git a/server.py b/server.py
index cc21fdc..5b14274 100644
--- a/server.py
+++ b/server.py
@@ -322,40 +322,64 @@ def query_stash_batch(items, stash_url=None):
if not items:
return []
- queries = []
- for idx, item in enumerate(items):
- file_path = item.get('path') or item.get('name')
- basename = os.path.basename(file_path)
- p = f'/data/videodownloader/{basename}'
- queries.append(f'item_{idx}: findScenes(scene_filter: {{path: {{value: {json.dumps(p)}, modifier: EQUALS}}}}) {{ scenes {{ id title performers {{ name gender }} studio {{ name }} }} }}')
-
- batch_q = '{\n' + '\n'.join(queries) + '\n}'
results = [None] * len(items)
unmatched_indices = []
- try:
- res = execute_stash_graphql(batch_q, stash_url, timeout=6)
- data = res.get('data', {})
- for idx in range(len(items)):
- scenes = data.get(f'item_{idx}', {}).get('scenes', [])
- if scenes and scenes[0].get('performers'):
- parsed = parse_stash_scene_data(scenes[0], 'exact_path')
- if parsed:
- results[idx] = parsed
- if not results[idx]:
- unmatched_indices.append(idx)
- except Exception as e:
- print(f"Batch exact Stash query failed: {e}")
- unmatched_indices = list(range(len(items)))
+ # 1. First pass: Batch exact path queries in chunks of 30
+ chunk_size = 30
+ for c_start in range(0, len(items), chunk_size):
+ c_items = items[c_start:c_start + chunk_size]
+ queries = []
+ for i_idx, item in enumerate(c_items):
+ file_path = item.get('path') or item.get('name') or ''
+ basename = os.path.basename(file_path)
+ p = f'/data/videodownloader/{basename}'
+ queries.append(f'item_{i_idx}: findScenes(scene_filter: {{path: {{value: {json.dumps(p)}, modifier: EQUALS}}}}) {{ scenes {{ id title performers {{ id name gender image_path }} studio {{ name }} }} }}')
+
+ batch_q = '{\n' + '\n'.join(queries) + '\n}'
+ try:
+ res = execute_stash_graphql(batch_q, stash_url, timeout=4)
+ data = res.get('data', {})
+ for i_idx in range(len(c_items)):
+ global_idx = c_start + i_idx
+ scenes = data.get(f'item_{i_idx}', {}).get('scenes', [])
+ if scenes and scenes[0].get('performers'):
+ parsed = parse_stash_scene_data(scenes[0], 'exact_path')
+ if parsed:
+ results[global_idx] = parsed
+ if not results[global_idx]:
+ unmatched_indices.append(global_idx)
+ except Exception as e:
+ print(f"Batch exact Stash query failed: {e}")
+ for i_idx in range(len(c_items)):
+ unmatched_indices.append(c_start + i_idx)
- # Second pass for unmatched items
- for idx in unmatched_indices:
- item = items[idx]
- file_name = item.get('name', '')
- file_path = item.get('path', '')
- item_type = item.get('item_type', 'smb')
- match = query_stash_single(file_name, file_path, item_type, stash_url)
- results[idx] = match
+ # 2. Second pass: Batch cleaned title queries for all unmatched items in chunks of 30
+ if unmatched_indices:
+ for c_start in range(0, len(unmatched_indices), chunk_size):
+ c_indices = unmatched_indices[c_start:c_start + chunk_size]
+ queries = []
+ for sub_idx, g_idx in enumerate(c_indices):
+ item = items[g_idx]
+ raw_name = item.get('name') or os.path.basename(item.get('path') or '')
+ cleaned_title = clean_torrent_title_for_stash(raw_name)
+ queries.append(f'title_{sub_idx}: findScenes(filter: {{q: {json.dumps(cleaned_title[:35])}, per_page: 2}}) {{ scenes {{ id title performers {{ id name gender image_path }} studio {{ name }} }} }}')
+
+ batch_q = '{\n' + '\n'.join(queries) + '\n}'
+ try:
+ res = execute_stash_graphql(batch_q, stash_url, timeout=4)
+ data = res.get('data', {})
+ for sub_idx, g_idx in enumerate(c_indices):
+ scenes = data.get(f'title_{sub_idx}', {}).get('scenes', [])
+ if scenes:
+ for s in scenes:
+ if s.get('performers'):
+ parsed = parse_stash_scene_data(s, 'title_match')
+ if parsed:
+ results[g_idx] = parsed
+ break
+ except Exception as e:
+ print(f"Batch title Stash query failed: {e}")
return results