0.13.1-8 Fix Analysis
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "0.13.1-7",
|
"version": "0.13.1-8",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "0.13.1-7",
|
"version": "0.13.1-8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "0.13.1-7",
|
"version": "0.13.1-8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"scripts": {
|
"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(
|
router.post(
|
||||||
'/start/:jobId',
|
'/start/:jobId',
|
||||||
asyncHandler(async (req, res) => {
|
asyncHandler(async (req, res) => {
|
||||||
|
|||||||
@@ -13692,6 +13692,83 @@ class PipelineService extends EventEmitter {
|
|||||||
return this.runMediainfoReviewForJob(jobId, rawPath, options);
|
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({
|
async selectMetadata({
|
||||||
jobId,
|
jobId,
|
||||||
title,
|
title,
|
||||||
@@ -14515,11 +14592,12 @@ class PipelineService extends EventEmitter {
|
|||||||
recommendation: null
|
recommendation: null
|
||||||
}
|
}
|
||||||
: basePlaylistDecision;
|
: basePlaylistDecision;
|
||||||
|
const isRawImportMetadataJob = String(mkInfo?.source || '').trim().toLowerCase() === 'orphan_raw_import';
|
||||||
const requiresManualPlaylistSelection = Boolean(
|
const requiresManualPlaylistSelection = Boolean(
|
||||||
playlistDecision.playlistDecisionRequired && playlistDecision.selectedTitleId === null
|
playlistDecision.playlistDecisionRequired && playlistDecision.selectedTitleId === null
|
||||||
);
|
);
|
||||||
const nextStatus = requiresManualPlaylistSelection ? 'WAITING_FOR_USER_DECISION' : 'READY_TO_START';
|
const requiresRawDecision = shouldAutoReviewFromRaw && !isRawImportMetadataJob;
|
||||||
const isRawImportMetadataJob = String(mkInfo?.source || '').trim().toLowerCase() === 'orphan_raw_import';
|
const nextStatus = (requiresManualPlaylistSelection || requiresRawDecision) ? 'WAITING_FOR_USER_DECISION' : 'READY_TO_START';
|
||||||
|
|
||||||
const updatedMakemkvInfo = this.withAnalyzeContextMediaProfile({
|
const updatedMakemkvInfo = this.withAnalyzeContextMediaProfile({
|
||||||
...mkInfo,
|
...mkInfo,
|
||||||
@@ -14605,11 +14683,13 @@ class PipelineService extends EventEmitter {
|
|||||||
activeJobId: jobId,
|
activeJobId: jobId,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
eta: null,
|
eta: null,
|
||||||
statusText: requiresManualPlaylistSelection
|
statusText: requiresRawDecision
|
||||||
|
? 'waiting_for_raw_decision'
|
||||||
|
: (requiresManualPlaylistSelection
|
||||||
? 'waiting_for_manual_playlist_selection'
|
? 'waiting_for_manual_playlist_selection'
|
||||||
: (shouldAutoReviewFromRaw
|
: (shouldAutoReviewFromRaw
|
||||||
? 'Metadaten übernommen - vorhandenes RAW erkannt'
|
? 'Metadaten übernommen - vorhandenes RAW erkannt'
|
||||||
: 'Metadaten übernommen - bereit zum Start'),
|
: 'Metadaten übernommen - bereit zum Start')),
|
||||||
context: {
|
context: {
|
||||||
...(this.snapshot.context || {}),
|
...(this.snapshot.context || {}),
|
||||||
jobId,
|
jobId,
|
||||||
@@ -14622,9 +14702,12 @@ class PipelineService extends EventEmitter {
|
|||||||
selectedPlaylist: playlistDecision.selectedPlaylist || null,
|
selectedPlaylist: playlistDecision.selectedPlaylist || null,
|
||||||
selectedTitleId: playlistDecision.selectedTitleId ?? null,
|
selectedTitleId: playlistDecision.selectedTitleId ?? null,
|
||||||
waitingForManualPlaylistSelection: requiresManualPlaylistSelection,
|
waitingForManualPlaylistSelection: requiresManualPlaylistSelection,
|
||||||
manualDecisionState: requiresManualPlaylistSelection
|
waitingForRawDecision: requiresRawDecision,
|
||||||
|
manualDecisionState: requiresRawDecision
|
||||||
|
? 'waiting_for_raw_decision'
|
||||||
|
: (requiresManualPlaylistSelection
|
||||||
? 'waiting_for_manual_playlist_selection'
|
? 'waiting_for_manual_playlist_selection'
|
||||||
: null
|
: null)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} 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) {
|
if (requiresManualPlaylistSelection) {
|
||||||
const playlistFiles = playlistDecision.candidatePlaylists
|
const playlistFiles = playlistDecision.candidatePlaylists
|
||||||
.map((item) => item.playlistFile)
|
.map((item) => item.playlistFile)
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "0.13.1-7",
|
"version": "0.13.1-8",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "0.13.1-7",
|
"version": "0.13.1-8",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primereact": "^10.9.2",
|
"primereact": "^10.9.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "0.13.1-7",
|
"version": "0.13.1-8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -625,6 +625,14 @@ export const api = {
|
|||||||
afterMutationInvalidate(['/history', '/pipeline/queue']);
|
afterMutationInvalidate(['/history', '/pipeline/queue']);
|
||||||
return result;
|
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) {
|
async startJob(jobId) {
|
||||||
const result = await request(`/pipeline/start/${jobId}`, {
|
const result = await request(`/pipeline/start/${jobId}`, {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
|
|||||||
@@ -1126,6 +1126,7 @@ export default function PipelineStatusCard({
|
|||||||
onConfirmReview,
|
onConfirmReview,
|
||||||
onSelectPlaylist,
|
onSelectPlaylist,
|
||||||
onSelectHandBrakeTitle,
|
onSelectHandBrakeTitle,
|
||||||
|
onSubmitRawDecision,
|
||||||
onCancel,
|
onCancel,
|
||||||
onRetry,
|
onRetry,
|
||||||
onDeleteJob,
|
onDeleteJob,
|
||||||
@@ -2131,7 +2132,9 @@ export default function PipelineStatusCard({
|
|||||||
};
|
};
|
||||||
}, [running, retryJobId]);
|
}, [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(() => {
|
const isSeriesDvdReview = useMemo(() => {
|
||||||
if (jobMediaProfile !== 'dvd') {
|
if (jobMediaProfile !== 'dvd') {
|
||||||
return false;
|
return false;
|
||||||
@@ -3103,6 +3106,40 @@ export default function PipelineStatusCard({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : 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 ? (
|
{playlistDecisionRequiredBeforeStart && !queueLocked ? (
|
||||||
<div className="playlist-decision-block">
|
<div className="playlist-decision-block">
|
||||||
<h3>Playlist-Auswahl erforderlich</h3>
|
<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 handleRetry = async (jobId) => {
|
||||||
const normalizedJobId = normalizeJobId(jobId);
|
const normalizedJobId = normalizeJobId(jobId);
|
||||||
if (normalizedJobId) setJobBusy(normalizedJobId, true);
|
if (normalizedJobId) setJobBusy(normalizedJobId, true);
|
||||||
@@ -3097,6 +3112,7 @@ export default function RipperPage({
|
|||||||
onConfirmReview={handleConfirmReview}
|
onConfirmReview={handleConfirmReview}
|
||||||
onSelectPlaylist={handleSelectPlaylist}
|
onSelectPlaylist={handleSelectPlaylist}
|
||||||
onSelectHandBrakeTitle={handleSelectHandBrakeTitle}
|
onSelectHandBrakeTitle={handleSelectHandBrakeTitle}
|
||||||
|
onSubmitRawDecision={handleSubmitRawDecision}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
onRetry={handleRetry}
|
onRetry={handleRetry}
|
||||||
onDeleteJob={handleDeleteRipperJob}
|
onDeleteJob={handleDeleteRipperJob}
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "0.13.1-7",
|
"version": "0.13.1-8",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "0.13.1-7",
|
"version": "0.13.1-8",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^9.1.2"
|
"concurrently": "^9.1.2"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.13.1-7",
|
"version": "0.13.1-8",
|
||||||
"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",
|
||||||
|
|||||||
Reference in New Issue
Block a user