diff --git a/qbittorrent_performer_sorter.html b/qbittorrent_performer_sorter.html index af286d2..c4d33d5 100644 --- a/qbittorrent_performer_sorter.html +++ b/qbittorrent_performer_sorter.html @@ -8027,7 +8027,7 @@ Return ONLY a valid JSON object with an array named "results": } } - // Batch Replace Action + // Batch Replace Action with Live Per-Item Progress const btnBatchReplaceTranscodes = document.getElementById('btn-batch-replace-transcodes'); if (btnBatchReplaceTranscodes) { btnBatchReplaceTranscodes.addEventListener('click', async () => { @@ -8043,35 +8043,64 @@ Return ONLY a valid JSON object with an array named "results": return; } - try { - btnBatchReplaceTranscodes.disabled = true; - btnBatchReplaceTranscodes.textContent = `Replacing ${selectedItems.length}...`; + const deleteCache = document.getElementById('transcode-opt-delete-cache')?.checked; + const triggerRescan = document.getElementById('transcode-opt-trigger-rescan')?.checked; - const deleteCache = document.getElementById('transcode-opt-delete-cache')?.checked; - const triggerRescan = document.getElementById('transcode-opt-trigger-rescan')?.checked; + let successCount = 0; + let totalSavedBytes = 0; + btnBatchReplaceTranscodes.disabled = true; - const res = await fetch('/api/stash/transcodes/batch-replace', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - items: selectedItems, - delete_transcode: deleteCache, - trigger_rescan: triggerRescan - }) - }); - const data = await res.json(); - if (data.success) { - showToast(`✓ Batch replaced ${data.replaced_count} files (Saved ${(data.total_saved_bytes/(1024*1024)).toFixed(1)} MB)!`, 'success'); - await scanStashTranscodes(); - } else { - showToast(`Batch error: ${data.error}`, 'error'); + for (let i = 0; i < selectedItems.length; i++) { + const item = selectedItems[i]; + btnBatchReplaceTranscodes.textContent = `Replacing ${i + 1} / ${selectedItems.length}...`; + + const row = document.getElementById(`transcode-row-${item.oshash}`); + const replaceBtn = row?.querySelector('.btn-transcode-replace-single'); + if (replaceBtn) { + replaceBtn.disabled = true; + replaceBtn.textContent = '⏳ Copying...'; + } + + try { + const res = await fetch('/api/stash/transcodes/replace', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + orig_local_path: item.orig_local_path, + transcode_path: item.transcode_path, + delete_transcode: deleteCache, + trigger_rescan: (i === selectedItems.length - 1) ? triggerRescan : false + }) + }); + const data = await res.json(); + if (data.success) { + successCount++; + totalSavedBytes += (data.saved_bytes || 0); + if (row) { + row.style.opacity = '0.4'; + row.style.background = 'rgba(16, 185, 129, 0.1)'; + if (replaceBtn) replaceBtn.textContent = '✅ Replaced'; + } + } else { + if (replaceBtn) { + replaceBtn.disabled = false; + replaceBtn.textContent = '❌ Failed'; + replaceBtn.title = data.error || 'Failed'; + } + } + } catch (err) { + if (replaceBtn) { + replaceBtn.disabled = false; + replaceBtn.textContent = '❌ Error'; + replaceBtn.title = err.message; + } } - } catch (err) { - showToast(`Batch execution error: ${err.message}`, 'error'); - } finally { - btnBatchReplaceTranscodes.disabled = false; - btnBatchReplaceTranscodes.textContent = `⚡ Batch Replace Selected (${selectedTranscodeHashes.size})`; } + + const savedMb = (totalSavedBytes / (1024 * 1024)).toFixed(1); + showToast(`✓ Batch completed: Replaced ${successCount} / ${selectedItems.length} files (Saved ${savedMb} MB)!`, 'success'); + btnBatchReplaceTranscodes.disabled = false; + btnBatchReplaceTranscodes.textContent = `⚡ Batch Replace Selected (${selectedTranscodeHashes.size})`; }); } diff --git a/server.py b/server.py index a8bf5df..d457720 100644 --- a/server.py +++ b/server.py @@ -2889,14 +2889,18 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler): saved_bytes = orig_size - transcode_size # Step 5: Record to Relocation History audit log - record_relocation_history( - source_path=transcode_path, - target_path=orig_local_path, - performer_name="Transcode Optimization", - status="completed", - size_bytes=transcode_size, - action_type="transcode_replace" - ) + try: + record_relocation_history( + source_type="stash_transcode", + item_key=orig_local_path, + item_name=os.path.basename(orig_local_path), + source_path=transcode_path, + target_path=orig_local_path, + performer="Transcode Optimization", + file_size=transcode_size + ) + except Exception as e: + print(f"Warning: History record error: {e}") # Step 6: Trigger Stash metadata scan on parent folder if requested if trigger_rescan: @@ -2967,6 +2971,19 @@ class RobustProxyHandler(http.server.SimpleHTTPRequestHandler): saved = orig_size - transcode_size total_saved_bytes += saved rescanned_dirs.add(os.path.dirname(orig_local_path)) + + try: + record_relocation_history( + source_type="stash_transcode", + item_key=orig_local_path, + item_name=os.path.basename(orig_local_path), + source_path=transcode_path, + target_path=orig_local_path, + performer="Transcode Optimization", + file_size=transcode_size + ) + except Exception: + pass except Exception as e: errors.append({"path": orig_local_path, "error": str(e)})