0.13.1-8 Fix Analysis
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ripster-backend",
|
||||
"version": "0.13.1-7",
|
||||
"version": "0.13.1-8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ripster-backend",
|
||||
"version": "0.13.1-7",
|
||||
"version": "0.13.1-8",
|
||||
"dependencies": {
|
||||
"archiver": "^7.0.1",
|
||||
"cors": "^2.8.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripster-backend",
|
||||
"version": "0.13.1-7",
|
||||
"version": "0.13.1-8",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
|
||||
@@ -425,6 +425,17 @@ router.post(
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/jobs/:jobId/raw-decision',
|
||||
asyncHandler(async (req, res) => {
|
||||
const jobId = Number(req.params.jobId);
|
||||
const { decision } = req.body;
|
||||
logger.info('post:raw-decision', { reqId: req.reqId, jobId, decision });
|
||||
const result = await pipelineService.submitRawDecision(jobId, decision);
|
||||
res.json({ result });
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/start/:jobId',
|
||||
asyncHandler(async (req, res) => {
|
||||
|
||||
@@ -13692,6 +13692,83 @@ class PipelineService extends EventEmitter {
|
||||
return this.runMediainfoReviewForJob(jobId, rawPath, options);
|
||||
}
|
||||
|
||||
|
||||
async submitRawDecision(jobId, decision) {
|
||||
const historyService = require('./historyService'); // already required at top? wait, pipelineService requires historyService. We can just use historyService directly.
|
||||
const job = await historyService.getJobById(jobId);
|
||||
if (!job) throw new Error('Job nicht gefunden.');
|
||||
|
||||
const isWaiting = (
|
||||
String(job.status || '').trim().toUpperCase() === 'WAITING_FOR_USER_DECISION' ||
|
||||
String(job.last_state || '').trim().toUpperCase() === 'WAITING_FOR_USER_DECISION'
|
||||
);
|
||||
if (!isWaiting) {
|
||||
throw new Error(`Job ist nicht im Status WAITING_FOR_USER_DECISION (${job.status})`);
|
||||
}
|
||||
|
||||
if (decision === 'delete') {
|
||||
const fsOptions = { recursive: true, force: true };
|
||||
if (job.raw_path && require('fs').existsSync(job.raw_path)) {
|
||||
await require('fs').promises.rm(job.raw_path, fsOptions);
|
||||
await historyService.appendLog(jobId, 'SYSTEM', `RAW-Verzeichnis wurde auf Benutzerwunsch gelöscht: ${job.raw_path}`);
|
||||
}
|
||||
} else if (decision === 'continue') {
|
||||
await historyService.appendLog(jobId, 'SYSTEM', `Benutzer hat "Mit vorhandenem RAW weiter machen" gewählt.`);
|
||||
} else if (decision === 'cancel') {
|
||||
await historyService.appendLog(jobId, 'SYSTEM', `Benutzer hat auf Grund vorhandenem RAW abgebrochen.`);
|
||||
await this.cancelJob(jobId);
|
||||
return historyService.getJobById(jobId);
|
||||
} else {
|
||||
throw new Error(`Unbekannte Entscheidung: ${decision}`);
|
||||
}
|
||||
|
||||
const mkInfo = this.safeParseJson(job.makemkv_info_json);
|
||||
const currentAnalyzeContext = mkInfo?.analyzeContext || {};
|
||||
const playlistDecisionRequired = Boolean(currentAnalyzeContext.playlistDecisionRequired);
|
||||
const selectedTitleId = currentAnalyzeContext.selectedTitleId ?? null;
|
||||
const requiresManualPlaylistSelection = Boolean(playlistDecisionRequired && selectedTitleId === null);
|
||||
|
||||
const nextStatus = requiresManualPlaylistSelection ? 'WAITING_FOR_USER_DECISION' : 'READY_TO_START';
|
||||
|
||||
await this.setState(nextStatus, {
|
||||
activeJobId: jobId,
|
||||
progress: 0,
|
||||
eta: null,
|
||||
statusText: requiresManualPlaylistSelection
|
||||
? 'waiting_for_manual_playlist_selection'
|
||||
: (decision === 'continue'
|
||||
? 'Metadaten übernommen - vorhandenes RAW erkannt'
|
||||
: 'Metadaten übernommen - bereit zum Start'),
|
||||
context: {
|
||||
...(this.snapshot.context || {}),
|
||||
waitingForRawDecision: false,
|
||||
manualDecisionState: requiresManualPlaylistSelection
|
||||
? 'waiting_for_manual_playlist_selection'
|
||||
: null
|
||||
}
|
||||
});
|
||||
|
||||
if (requiresManualPlaylistSelection) {
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'SYSTEM',
|
||||
'Bitte selected_playlist setzen (z.B. 00800 oder 00800.mpls), bevor Backup/Encoding gestartet wird.'
|
||||
);
|
||||
return historyService.getJobById(jobId);
|
||||
}
|
||||
|
||||
if (decision === 'continue') {
|
||||
await historyService.appendLog(jobId, 'SYSTEM', `Starte automatische Spur-Ermittlung (Mediainfo) mit vorhandenem RAW: ${job.raw_path}`);
|
||||
await this.startPreparedJob(jobId);
|
||||
return historyService.getJobById(jobId);
|
||||
}
|
||||
|
||||
await historyService.appendLog(jobId, 'SYSTEM', 'RAW gelöscht oder ignoriert. Starte Backup/Rip automatisch.');
|
||||
await this.startPreparedJob(jobId);
|
||||
return historyService.getJobById(jobId);
|
||||
}
|
||||
|
||||
|
||||
async selectMetadata({
|
||||
jobId,
|
||||
title,
|
||||
@@ -14515,11 +14592,12 @@ class PipelineService extends EventEmitter {
|
||||
recommendation: null
|
||||
}
|
||||
: basePlaylistDecision;
|
||||
const isRawImportMetadataJob = String(mkInfo?.source || '').trim().toLowerCase() === 'orphan_raw_import';
|
||||
const requiresManualPlaylistSelection = Boolean(
|
||||
playlistDecision.playlistDecisionRequired && playlistDecision.selectedTitleId === null
|
||||
);
|
||||
const nextStatus = requiresManualPlaylistSelection ? 'WAITING_FOR_USER_DECISION' : 'READY_TO_START';
|
||||
const isRawImportMetadataJob = String(mkInfo?.source || '').trim().toLowerCase() === 'orphan_raw_import';
|
||||
const requiresRawDecision = shouldAutoReviewFromRaw && !isRawImportMetadataJob;
|
||||
const nextStatus = (requiresManualPlaylistSelection || requiresRawDecision) ? 'WAITING_FOR_USER_DECISION' : 'READY_TO_START';
|
||||
|
||||
const updatedMakemkvInfo = this.withAnalyzeContextMediaProfile({
|
||||
...mkInfo,
|
||||
@@ -14605,11 +14683,13 @@ class PipelineService extends EventEmitter {
|
||||
activeJobId: jobId,
|
||||
progress: 0,
|
||||
eta: null,
|
||||
statusText: requiresManualPlaylistSelection
|
||||
statusText: requiresRawDecision
|
||||
? 'waiting_for_raw_decision'
|
||||
: (requiresManualPlaylistSelection
|
||||
? 'waiting_for_manual_playlist_selection'
|
||||
: (shouldAutoReviewFromRaw
|
||||
? 'Metadaten übernommen - vorhandenes RAW erkannt'
|
||||
: 'Metadaten übernommen - bereit zum Start'),
|
||||
: 'Metadaten übernommen - bereit zum Start')),
|
||||
context: {
|
||||
...(this.snapshot.context || {}),
|
||||
jobId,
|
||||
@@ -14622,9 +14702,12 @@ class PipelineService extends EventEmitter {
|
||||
selectedPlaylist: playlistDecision.selectedPlaylist || null,
|
||||
selectedTitleId: playlistDecision.selectedTitleId ?? null,
|
||||
waitingForManualPlaylistSelection: requiresManualPlaylistSelection,
|
||||
manualDecisionState: requiresManualPlaylistSelection
|
||||
waitingForRawDecision: requiresRawDecision,
|
||||
manualDecisionState: requiresRawDecision
|
||||
? 'waiting_for_raw_decision'
|
||||
: (requiresManualPlaylistSelection
|
||||
? 'waiting_for_manual_playlist_selection'
|
||||
: null
|
||||
: null)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -14636,6 +14719,15 @@ class PipelineService extends EventEmitter {
|
||||
);
|
||||
}
|
||||
|
||||
if (requiresRawDecision) {
|
||||
await historyService.appendLog(
|
||||
jobId,
|
||||
'SYSTEM',
|
||||
`Vorhandenes RAW zur Disk gefunden: ${updatedRawPath}. Warte auf Entscheidung.`
|
||||
);
|
||||
return historyService.getJobById(jobId);
|
||||
}
|
||||
|
||||
if (requiresManualPlaylistSelection) {
|
||||
const playlistFiles = playlistDecision.candidatePlaylists
|
||||
.map((item) => item.playlistFile)
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.13.1-7",
|
||||
"version": "0.13.1-8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.13.1-7",
|
||||
"version": "0.13.1-8",
|
||||
"dependencies": {
|
||||
"primeicons": "^7.0.0",
|
||||
"primereact": "^10.9.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripster-frontend",
|
||||
"version": "0.13.1-7",
|
||||
"version": "0.13.1-8",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -625,6 +625,14 @@ export const api = {
|
||||
afterMutationInvalidate(['/history', '/pipeline/queue']);
|
||||
return result;
|
||||
},
|
||||
async submitRawDecision(jobId, decision) {
|
||||
const result = await request(`/pipeline/jobs/${jobId}/raw-decision`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ decision })
|
||||
});
|
||||
afterMutationInvalidate(['/history', '/pipeline/queue']);
|
||||
return result;
|
||||
},
|
||||
async startJob(jobId) {
|
||||
const result = await request(`/pipeline/start/${jobId}`, {
|
||||
method: 'POST'
|
||||
|
||||
@@ -1126,6 +1126,7 @@ export default function PipelineStatusCard({
|
||||
onConfirmReview,
|
||||
onSelectPlaylist,
|
||||
onSelectHandBrakeTitle,
|
||||
onSubmitRawDecision,
|
||||
onCancel,
|
||||
onRetry,
|
||||
onDeleteJob,
|
||||
@@ -2131,7 +2132,9 @@ export default function PipelineStatusCard({
|
||||
};
|
||||
}, [running, retryJobId]);
|
||||
|
||||
const playlistDecisionRequiredBeforeStart = state === 'WAITING_FOR_USER_DECISION' && !handBrakeTitleDecisionRequired;
|
||||
const manualDecisionState = pipeline?.context?.manualDecisionState || null;
|
||||
const rawDecisionRequiredBeforeStart = state === 'WAITING_FOR_USER_DECISION' && manualDecisionState === 'waiting_for_raw_decision';
|
||||
const playlistDecisionRequiredBeforeStart = state === 'WAITING_FOR_USER_DECISION' && !handBrakeTitleDecisionRequired && !rawDecisionRequiredBeforeStart;
|
||||
const isSeriesDvdReview = useMemo(() => {
|
||||
if (jobMediaProfile !== 'dvd') {
|
||||
return false;
|
||||
@@ -3103,6 +3106,40 @@ export default function PipelineStatusCard({
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{rawDecisionRequiredBeforeStart && !queueLocked ? (
|
||||
<div className="playlist-decision-block">
|
||||
<h3>RAW Entscheidung erforderlich</h3>
|
||||
<small>
|
||||
Vorhandenes Raw zur Disk gefunden. Wie soll weiter verfahren werden?
|
||||
</small>
|
||||
<div className="actions-row mt-3">
|
||||
<Button
|
||||
label="mit RAW weiter machen"
|
||||
icon="pi pi-check"
|
||||
severity="success"
|
||||
onClick={() => onSubmitRawDecision?.(retryJobId, 'continue')}
|
||||
loading={busy}
|
||||
/>
|
||||
<Button
|
||||
label="RAW löschen und Rip erneut starten"
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
outlined
|
||||
onClick={() => onSubmitRawDecision?.(retryJobId, 'delete')}
|
||||
loading={busy}
|
||||
/>
|
||||
<Button
|
||||
label="abbrechen"
|
||||
icon="pi pi-times"
|
||||
severity="secondary"
|
||||
outlined
|
||||
onClick={() => onSubmitRawDecision?.(retryJobId, 'cancel')}
|
||||
loading={busy}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{playlistDecisionRequiredBeforeStart && !queueLocked ? (
|
||||
<div className="playlist-decision-block">
|
||||
<h3>Playlist-Auswahl erforderlich</h3>
|
||||
|
||||
@@ -1906,6 +1906,21 @@ export default function RipperPage({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmitRawDecision = async (jobId, decision) => {
|
||||
const normalizedJobId = normalizeJobId(jobId);
|
||||
if (normalizedJobId) setJobBusy(normalizedJobId, true);
|
||||
try {
|
||||
await api.submitRawDecision(jobId, decision);
|
||||
await refreshPipeline();
|
||||
await loadRipperJobs();
|
||||
setExpandedJobId(normalizedJobId);
|
||||
} catch (error) {
|
||||
showError(error);
|
||||
} finally {
|
||||
if (normalizedJobId) setJobBusy(normalizedJobId, false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRetry = async (jobId) => {
|
||||
const normalizedJobId = normalizeJobId(jobId);
|
||||
if (normalizedJobId) setJobBusy(normalizedJobId, true);
|
||||
@@ -3097,6 +3112,7 @@ export default function RipperPage({
|
||||
onConfirmReview={handleConfirmReview}
|
||||
onSelectPlaylist={handleSelectPlaylist}
|
||||
onSelectHandBrakeTitle={handleSelectHandBrakeTitle}
|
||||
onSubmitRawDecision={handleSubmitRawDecision}
|
||||
onCancel={handleCancel}
|
||||
onRetry={handleRetry}
|
||||
onDeleteJob={handleDeleteRipperJob}
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ripster",
|
||||
"version": "0.13.1-7",
|
||||
"version": "0.13.1-8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ripster",
|
||||
"version": "0.13.1-7",
|
||||
"version": "0.13.1-8",
|
||||
"devDependencies": {
|
||||
"concurrently": "^9.1.2"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "ripster",
|
||||
"private": true,
|
||||
"version": "0.13.1-7",
|
||||
"version": "0.13.1-8",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
|
||||
"dev:backend": "npm run dev --prefix backend",
|
||||
|
||||
Reference in New Issue
Block a user