From a66c3bfdb60b7eebc3762a6d23be3c6df1431854 Mon Sep 17 00:00:00 2001 From: mboehmlaender Date: Mon, 23 Mar 2026 08:02:29 +0000 Subject: [PATCH] 0.12.0-11 Drive Fix --- .claude/settings.json | 3 +- backend/package-lock.json | 4 +- backend/package.json | 2 +- frontend/package-lock.json | 4 +- frontend/package.json | 2 +- frontend/src/App.jsx | 1 + frontend/src/components/JobDetailDialog.jsx | 12 +++-- frontend/src/pages/DashboardPage.jsx | 49 +++++++++++++++++---- package-lock.json | 4 +- package.json | 2 +- 10 files changed, 61 insertions(+), 22 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 0ba7fca..1fec878 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -23,7 +23,8 @@ "Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); [print\\(e\\) for e in d.get\\(''''blockdevices'''',[]\\)]\")", "Bash(lsblk -o NAME,TYPE,MODEL)", "Bash(node --check /home/michael/ripster/backend/src/routes/pipelineRoutes.js)", - "Bash(grep -n \"inferMediaProfile\" /home/michael/ripster/backend/src/services/*.js)" + "Bash(grep -n \"inferMediaProfile\" /home/michael/ripster/backend/src/services/*.js)", + "Bash(grep -rn \"PROCESS_LOG\\\\|LOG_UPDATE\\\\|processLog\" /home/michael/ripster/backend/src --include=*.js)" ] } } diff --git a/backend/package-lock.json b/backend/package-lock.json index 5a3ff9d..8bb0216 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-backend", - "version": "0.12.0-10", + "version": "0.12.0-11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-backend", - "version": "0.12.0-10", + "version": "0.12.0-11", "dependencies": { "archiver": "^7.0.1", "cors": "^2.8.5", diff --git a/backend/package.json b/backend/package.json index d7c5b63..277d0fd 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-backend", - "version": "0.12.0-10", + "version": "0.12.0-11", "private": true, "type": "commonjs", "scripts": { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5dc8ed8..bfbf49e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-frontend", - "version": "0.12.0-10", + "version": "0.12.0-11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-frontend", - "version": "0.12.0-10", + "version": "0.12.0-11", "dependencies": { "primeicons": "^7.0.0", "primereact": "^10.9.2", diff --git a/frontend/package.json b/frontend/package.json index e8e9e65..3f64087 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-frontend", - "version": "0.12.0-10", + "version": "0.12.0-11", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4dfccfb..0f7c95c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -455,6 +455,7 @@ function App() { if (message.type === 'DISC_DETECTED') { setLastDiscEvent(message.payload?.device || null); + refreshPipeline().catch(() => null); } if (message.type === 'DISC_REMOVED') { diff --git a/frontend/src/components/JobDetailDialog.jsx b/frontend/src/components/JobDetailDialog.jsx index 0cb735b..7929eda 100644 --- a/frontend/src/components/JobDetailDialog.jsx +++ b/frontend/src/components/JobDetailDialog.jsx @@ -605,6 +605,10 @@ export default function JobDetailDialog({ downloadFolderBusyPath = null }) { const mkDone = Boolean(job?.ripSuccessful) || !job?.makemkvInfo || job?.makemkvInfo?.status === 'SUCCESS'; + // RAW-Import aus Datenbank, der noch nie encodiert wurde → nur "Neu einlesen" erlaubt + const isUnencodedOrphanImport = Boolean( + job?.makemkvInfo?.source === 'orphan_raw_import' && !job?.handbrakeInfo + ); const running = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING', 'CD_RIPPING', 'CD_ENCODING'].includes(job?.status); const showFinalLog = !running; const mediaType = resolveMediaType(job); @@ -1340,7 +1344,7 @@ export default function JobDetailDialog({ size="small" onClick={() => onRestartReview?.(job)} loading={actionBusy} - disabled={!canRestartReview} + disabled={!canRestartReview || isUnencodedOrphanImport} /> {isAudiobook ? 'Öffnet die Kapitel-Auswahl erneut, um Kapitel anzupassen bevor der Encode startet.' @@ -1354,9 +1358,9 @@ export default function JobDetailDialog({ icon="pi pi-sync" severity="info" size="small" - onClick={() => onReencode?.(job)} - loading={reencodeBusy} - disabled={!canReencode || typeof onReencode !== 'function'} + onClick={() => isUnencodedOrphanImport ? onRestartReview?.(job) : onReencode?.(job)} + loading={isUnencodedOrphanImport ? actionBusy : reencodeBusy} + disabled={isUnencodedOrphanImport ? !canRestartReview : (!canReencode || typeof onReencode !== 'function')} /> {isAudiobook ? 'Liest die AAX-Datei neu ein und öffnet danach die Kapitel-Auswahl. Sinnvoll wenn sich die Quelldatei geändert hat.' diff --git a/frontend/src/pages/DashboardPage.jsx b/frontend/src/pages/DashboardPage.jsx index 1165e37..99df44a 100644 --- a/frontend/src/pages/DashboardPage.jsx +++ b/frontend/src/pages/DashboardPage.jsx @@ -960,6 +960,19 @@ export default function DashboardPage({ const state = String(pipeline?.state || 'IDLE').trim().toUpperCase(); const currentPipelineJobId = normalizeJobId(pipeline?.activeJobId || pipeline?.context?.jobId); const isProcessing = processingStates.includes(state); + // When the pipeline transitions to METADATA_SELECTION for a new job while another job + // is still encoding in the background, isProcessing becomes false and the log clears. + // Track the background encoding job so its live log keeps polling. + const backgroundProcessingJobId = useMemo(() => { + if (isProcessing) return null; + const bg = dashboardJobs.find((job) => { + const s = normalizeStatus(job?.status); + return processingStates.includes(s); + }); + return bg ? normalizeJobId(bg.id) : null; + }, [isProcessing, dashboardJobs]); + const liveLogJobId = backgroundProcessingJobId || currentPipelineJobId; + const liveLogActive = isProcessing || Boolean(backgroundProcessingJobId); const monitoringState = useMemo( () => normalizeHardwareMonitoringPayload(hardwareMonitoring), [hardwareMonitoring] @@ -1295,7 +1308,7 @@ export default function DashboardPage({ }, [audiobookUploadPhase]); useEffect(() => { - if (!currentPipelineJobId || !isProcessing) { + if (!liveLogJobId || !liveLogActive) { setLiveJobLog(''); return undefined; } @@ -1303,7 +1316,7 @@ export default function DashboardPage({ let cancelled = false; const refreshLiveLog = async () => { try { - const response = await api.getJob(currentPipelineJobId, { includeLiveLog: true, lite: true }); + const response = await api.getJob(liveLogJobId, { includeLiveLog: true, lite: true }); if (!cancelled) { setLiveJobLog(response?.job?.log || ''); } @@ -1318,7 +1331,7 @@ export default function DashboardPage({ cancelled = true; clearInterval(interval); }; - }, [currentPipelineJobId, isProcessing]); + }, [liveLogJobId, liveLogActive]); const pipelineByJobId = useMemo(() => { const map = new Map(); @@ -1435,7 +1448,7 @@ export default function DashboardPage({ await refreshPipeline(); await loadDashboardJobs(); const analyzedJobId = normalizeJobId(response?.result?.jobId); - if (analyzedJobId && state === 'ENCODING') { + if (analyzedJobId) { setMetadataDialogContext({ jobId: analyzedJobId, detectedTitle: response?.result?.detectedTitle || '', @@ -1465,9 +1478,26 @@ export default function DashboardPage({ const handleAnalyzeForDrive = async (devicePath) => { setDriveAnalyzeBusy((prev) => new Set([...prev, devicePath])); try { - await api.analyzeDisc(devicePath); + const response = await api.analyzeDisc(devicePath); await refreshPipeline(); await loadDashboardJobs(); + const analyzedJobId = normalizeJobId(response?.result?.jobId); + if (analyzedJobId) { + setMetadataDialogContext({ + jobId: analyzedJobId, + detectedTitle: response?.result?.detectedTitle || '', + selectedMetadata: { + title: response?.result?.detectedTitle || '', + year: null, + imdbId: null, + poster: null + }, + omdbCandidates: Array.isArray(response?.result?.omdbCandidates) + ? response.result.omdbCandidates + : [] + }); + setMetadataDialogVisible(true); + } } catch (error) { showError(error); } finally { @@ -2284,7 +2314,10 @@ export default function DashboardPage({ } if (device?.path) { const base = driveMap.get(device.path) || { path: device.path, model: device.model }; - driveMap.set(device.path, { ...base, pipelineDevice: device, pipelineState: state }); + // When device comes from a recent DISC_DETECTED event, use 'DISC_DETECTED' as the + // effective state for that drive even if the global pipeline state is ENCODING etc. + const effectivePipelineState = lastNonCdDiscEvent ? 'DISC_DETECTED' : state; + driveMap.set(device.path, { ...base, pipelineDevice: device, pipelineState: effectivePipelineState }); } // For drives that the detection service found but aren't tracked by cdDrives or the global // state machine (e.g. a second non-CD disc), show DISC_DETECTED using detectedDiscs. @@ -2297,7 +2330,7 @@ export default function DashboardPage({ return Array.from(driveMap.values()) .filter((drv) => !String(drv.path || '').startsWith('__virtual__')) .sort((a, b) => (a.path || '').localeCompare(b.path || '')); - }, [knownDrives, pipeline?.cdDrives, pipeline?.detectedDiscs, device, state]); + }, [knownDrives, pipeline?.cdDrives, pipeline?.detectedDiscs, device, state, lastNonCdDiscEvent]); const isDriveActive = driveActiveStates.includes(state); const canRescan = !isDriveActive; const canOpenMetadataModal = Boolean(defaultMetadataDialogContext?.jobId); @@ -2801,7 +2834,7 @@ export default function DashboardPage({ const statusBadgeValue = getStatusLabel(job?.status, { queued: isQueued }); const statusBadgeSeverity = getStatusSeverity(normalizedStatus, { queued: isQueued }); const isExpanded = normalizeJobId(expandedJobId) === jobId; - const isCurrentSession = currentPipelineJobId === jobId && state !== 'IDLE'; + const isCurrentSession = liveLogJobId === jobId && (state !== 'IDLE' || liveLogActive); const reviewConfirmed = Boolean(Number(job?.encode_review_confirmed || 0)); const pipelineForJob = pipelineByJobId.get(jobId) || pipeline; const jobTitle = job?.title || job?.detected_title || `Job #${jobId}`; diff --git a/package-lock.json b/package-lock.json index 376f61e..78158c7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster", - "version": "0.12.0-10", + "version": "0.12.0-11", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster", - "version": "0.12.0-10", + "version": "0.12.0-11", "devDependencies": { "concurrently": "^9.1.2" } diff --git a/package.json b/package.json index 7f95767..cacd137 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ripster", "private": true, - "version": "0.12.0-10", + "version": "0.12.0-11", "scripts": { "dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"", "dev:backend": "npm run dev --prefix backend",