0.12.0-11 Drive Fix

This commit is contained in:
2026-03-23 08:02:29 +00:00
parent 6911892b89
commit a66c3bfdb6
10 changed files with 61 additions and 22 deletions
+2 -1
View File
@@ -23,7 +23,8 @@
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); [print\\(e\\) for e in d.get\\(''''blockdevices'''',[]\\)]\")", "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(lsblk -o NAME,TYPE,MODEL)",
"Bash(node --check /home/michael/ripster/backend/src/routes/pipelineRoutes.js)", "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)"
] ]
} }
} }
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.12.0-10", "version": "0.12.0-11",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.12.0-10", "version": "0.12.0-11",
"dependencies": { "dependencies": {
"archiver": "^7.0.1", "archiver": "^7.0.1",
"cors": "^2.8.5", "cors": "^2.8.5",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ripster-backend", "name": "ripster-backend",
"version": "0.12.0-10", "version": "0.12.0-11",
"private": true, "private": true,
"type": "commonjs", "type": "commonjs",
"scripts": { "scripts": {
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.12.0-10", "version": "0.12.0-11",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.12.0-10", "version": "0.12.0-11",
"dependencies": { "dependencies": {
"primeicons": "^7.0.0", "primeicons": "^7.0.0",
"primereact": "^10.9.2", "primereact": "^10.9.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ripster-frontend", "name": "ripster-frontend",
"version": "0.12.0-10", "version": "0.12.0-11",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+1
View File
@@ -455,6 +455,7 @@ function App() {
if (message.type === 'DISC_DETECTED') { if (message.type === 'DISC_DETECTED') {
setLastDiscEvent(message.payload?.device || null); setLastDiscEvent(message.payload?.device || null);
refreshPipeline().catch(() => null);
} }
if (message.type === 'DISC_REMOVED') { if (message.type === 'DISC_REMOVED') {
+8 -4
View File
@@ -605,6 +605,10 @@ export default function JobDetailDialog({
downloadFolderBusyPath = null downloadFolderBusyPath = null
}) { }) {
const mkDone = Boolean(job?.ripSuccessful) || !job?.makemkvInfo || job?.makemkvInfo?.status === 'SUCCESS'; 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 running = ['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING', 'CD_RIPPING', 'CD_ENCODING'].includes(job?.status);
const showFinalLog = !running; const showFinalLog = !running;
const mediaType = resolveMediaType(job); const mediaType = resolveMediaType(job);
@@ -1340,7 +1344,7 @@ export default function JobDetailDialog({
size="small" size="small"
onClick={() => onRestartReview?.(job)} onClick={() => onRestartReview?.(job)}
loading={actionBusy} loading={actionBusy}
disabled={!canRestartReview} disabled={!canRestartReview || isUnencodedOrphanImport}
/> />
<span className="action-desc">{isAudiobook <span className="action-desc">{isAudiobook
? 'Öffnet die Kapitel-Auswahl erneut, um Kapitel anzupassen bevor der Encode startet.' ? 'Öffnet die Kapitel-Auswahl erneut, um Kapitel anzupassen bevor der Encode startet.'
@@ -1354,9 +1358,9 @@ export default function JobDetailDialog({
icon="pi pi-sync" icon="pi pi-sync"
severity="info" severity="info"
size="small" size="small"
onClick={() => onReencode?.(job)} onClick={() => isUnencodedOrphanImport ? onRestartReview?.(job) : onReencode?.(job)}
loading={reencodeBusy} loading={isUnencodedOrphanImport ? actionBusy : reencodeBusy}
disabled={!canReencode || typeof onReencode !== 'function'} disabled={isUnencodedOrphanImport ? !canRestartReview : (!canReencode || typeof onReencode !== 'function')}
/> />
<span className="action-desc">{isAudiobook <span className="action-desc">{isAudiobook
? 'Liest die AAX-Datei neu ein und öffnet danach die Kapitel-Auswahl. Sinnvoll wenn sich die Quelldatei geändert hat.' ? 'Liest die AAX-Datei neu ein und öffnet danach die Kapitel-Auswahl. Sinnvoll wenn sich die Quelldatei geändert hat.'
+41 -8
View File
@@ -960,6 +960,19 @@ export default function DashboardPage({
const state = String(pipeline?.state || 'IDLE').trim().toUpperCase(); const state = String(pipeline?.state || 'IDLE').trim().toUpperCase();
const currentPipelineJobId = normalizeJobId(pipeline?.activeJobId || pipeline?.context?.jobId); const currentPipelineJobId = normalizeJobId(pipeline?.activeJobId || pipeline?.context?.jobId);
const isProcessing = processingStates.includes(state); 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( const monitoringState = useMemo(
() => normalizeHardwareMonitoringPayload(hardwareMonitoring), () => normalizeHardwareMonitoringPayload(hardwareMonitoring),
[hardwareMonitoring] [hardwareMonitoring]
@@ -1295,7 +1308,7 @@ export default function DashboardPage({
}, [audiobookUploadPhase]); }, [audiobookUploadPhase]);
useEffect(() => { useEffect(() => {
if (!currentPipelineJobId || !isProcessing) { if (!liveLogJobId || !liveLogActive) {
setLiveJobLog(''); setLiveJobLog('');
return undefined; return undefined;
} }
@@ -1303,7 +1316,7 @@ export default function DashboardPage({
let cancelled = false; let cancelled = false;
const refreshLiveLog = async () => { const refreshLiveLog = async () => {
try { try {
const response = await api.getJob(currentPipelineJobId, { includeLiveLog: true, lite: true }); const response = await api.getJob(liveLogJobId, { includeLiveLog: true, lite: true });
if (!cancelled) { if (!cancelled) {
setLiveJobLog(response?.job?.log || ''); setLiveJobLog(response?.job?.log || '');
} }
@@ -1318,7 +1331,7 @@ export default function DashboardPage({
cancelled = true; cancelled = true;
clearInterval(interval); clearInterval(interval);
}; };
}, [currentPipelineJobId, isProcessing]); }, [liveLogJobId, liveLogActive]);
const pipelineByJobId = useMemo(() => { const pipelineByJobId = useMemo(() => {
const map = new Map(); const map = new Map();
@@ -1435,7 +1448,7 @@ export default function DashboardPage({
await refreshPipeline(); await refreshPipeline();
await loadDashboardJobs(); await loadDashboardJobs();
const analyzedJobId = normalizeJobId(response?.result?.jobId); const analyzedJobId = normalizeJobId(response?.result?.jobId);
if (analyzedJobId && state === 'ENCODING') { if (analyzedJobId) {
setMetadataDialogContext({ setMetadataDialogContext({
jobId: analyzedJobId, jobId: analyzedJobId,
detectedTitle: response?.result?.detectedTitle || '', detectedTitle: response?.result?.detectedTitle || '',
@@ -1465,9 +1478,26 @@ export default function DashboardPage({
const handleAnalyzeForDrive = async (devicePath) => { const handleAnalyzeForDrive = async (devicePath) => {
setDriveAnalyzeBusy((prev) => new Set([...prev, devicePath])); setDriveAnalyzeBusy((prev) => new Set([...prev, devicePath]));
try { try {
await api.analyzeDisc(devicePath); const response = await api.analyzeDisc(devicePath);
await refreshPipeline(); await refreshPipeline();
await loadDashboardJobs(); 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) { } catch (error) {
showError(error); showError(error);
} finally { } finally {
@@ -2284,7 +2314,10 @@ export default function DashboardPage({
} }
if (device?.path) { if (device?.path) {
const base = driveMap.get(device.path) || { path: device.path, model: device.model }; 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 // 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. // 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()) return Array.from(driveMap.values())
.filter((drv) => !String(drv.path || '').startsWith('__virtual__')) .filter((drv) => !String(drv.path || '').startsWith('__virtual__'))
.sort((a, b) => (a.path || '').localeCompare(b.path || '')); .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 isDriveActive = driveActiveStates.includes(state);
const canRescan = !isDriveActive; const canRescan = !isDriveActive;
const canOpenMetadataModal = Boolean(defaultMetadataDialogContext?.jobId); const canOpenMetadataModal = Boolean(defaultMetadataDialogContext?.jobId);
@@ -2801,7 +2834,7 @@ export default function DashboardPage({
const statusBadgeValue = getStatusLabel(job?.status, { queued: isQueued }); const statusBadgeValue = getStatusLabel(job?.status, { queued: isQueued });
const statusBadgeSeverity = getStatusSeverity(normalizedStatus, { queued: isQueued }); const statusBadgeSeverity = getStatusSeverity(normalizedStatus, { queued: isQueued });
const isExpanded = normalizeJobId(expandedJobId) === jobId; 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 reviewConfirmed = Boolean(Number(job?.encode_review_confirmed || 0));
const pipelineForJob = pipelineByJobId.get(jobId) || pipeline; const pipelineForJob = pipelineByJobId.get(jobId) || pipeline;
const jobTitle = job?.title || job?.detected_title || `Job #${jobId}`; const jobTitle = job?.title || job?.detected_title || `Job #${jobId}`;
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "ripster", "name": "ripster",
"version": "0.12.0-10", "version": "0.12.0-11",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "ripster", "name": "ripster",
"version": "0.12.0-10", "version": "0.12.0-11",
"devDependencies": { "devDependencies": {
"concurrently": "^9.1.2" "concurrently": "^9.1.2"
} }
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "ripster", "name": "ripster",
"private": true, "private": true,
"version": "0.12.0-10", "version": "0.12.0-11",
"scripts": { "scripts": {
"dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"", "dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
"dev:backend": "npm run dev --prefix backend", "dev:backend": "npm run dev --prefix backend",