0.13.1-7 Fix Series
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 MiB |
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "0.13.1-6",
|
"version": "0.13.1-7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "0.13.1-6",
|
"version": "0.13.1-7",
|
||||||
"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-6",
|
"version": "0.13.1-7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -2356,6 +2356,14 @@ function normalizeJobIdValue(value) {
|
|||||||
return Math.trunc(parsed);
|
return Math.trunc(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isSeriesContainerRow(row) {
|
||||||
|
return String(row?.job_kind || '').trim().toLowerCase() === 'dvd_series_container';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSeriesChildRow(row) {
|
||||||
|
return String(row?.job_kind || '').trim().toLowerCase() === 'dvd_series_child';
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeArchiveTarget(value) {
|
function normalizeArchiveTarget(value) {
|
||||||
const raw = String(value || '').trim().toLowerCase();
|
const raw = String(value || '').trim().toLowerCase();
|
||||||
if (raw === 'raw') {
|
if (raw === 'raw') {
|
||||||
@@ -4771,6 +4779,11 @@ class HistoryService {
|
|||||||
if (!entry.isDirectory() || isHiddenDirectoryName(entry.name)) {
|
if (!entry.isDirectory() || isHiddenDirectoryName(entry.name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (/^incomplete_/i.test(String(entry.name || '').trim())) {
|
||||||
|
// Incomplete RAW folders represent unfinished rips and are intentionally
|
||||||
|
// not re-importable from /database.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const rawPath = path.join(rawDir, entry.name);
|
const rawPath = path.join(rawDir, entry.name);
|
||||||
const normalizedPath = normalizeComparablePath(rawPath);
|
const normalizedPath = normalizeComparablePath(rawPath);
|
||||||
@@ -5765,6 +5778,12 @@ class HistoryService {
|
|||||||
|
|
||||||
const pending = [normalizedJobId];
|
const pending = [normalizedJobId];
|
||||||
const visited = new Set();
|
const visited = new Set();
|
||||||
|
const parentRow = normalizeJobIdValue(primary?.parent_job_id) !== null
|
||||||
|
? byId.get(normalizeJobIdValue(primary?.parent_job_id))
|
||||||
|
: null;
|
||||||
|
const isPrimarySeriesChild = isSeriesChildRow(primary)
|
||||||
|
|| (parentRow && isSeriesContainerRow(parentRow));
|
||||||
|
const isPrimarySeriesContainer = isSeriesContainerRow(primary);
|
||||||
const enqueue = (value) => {
|
const enqueue = (value) => {
|
||||||
const id = normalizeJobIdValue(value);
|
const id = normalizeJobIdValue(value);
|
||||||
if (!id || visited.has(id)) {
|
if (!id || visited.has(id)) {
|
||||||
@@ -5785,8 +5804,10 @@ class HistoryService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(isPrimarySeriesChild || isPrimarySeriesContainer)) {
|
||||||
enqueue(row.parent_job_id);
|
enqueue(row.parent_job_id);
|
||||||
enqueue(parseSourceJobIdFromPlan(row.encode_plan_json));
|
enqueue(parseSourceJobIdFromPlan(row.encode_plan_json));
|
||||||
|
}
|
||||||
|
|
||||||
for (const childId of (childrenByParent.get(currentId) || [])) {
|
for (const childId of (childrenByParent.get(currentId) || [])) {
|
||||||
enqueue(childId);
|
enqueue(childId);
|
||||||
@@ -5795,7 +5816,7 @@ class HistoryService {
|
|||||||
enqueue(childId);
|
enqueue(childId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeLogLinks) {
|
if (includeLogLinks && !(isPrimarySeriesChild || isPrimarySeriesContainer)) {
|
||||||
try {
|
try {
|
||||||
const processLog = await this.readProcessLogLines(currentId, { includeAll: true });
|
const processLog = await this.readProcessLogLines(currentId, { includeAll: true });
|
||||||
const linkedJobIds = parseRetryLinkedJobIdsFromLogLines(processLog.lines);
|
const linkedJobIds = parseRetryLinkedJobIdsFromLogLines(processLog.lines);
|
||||||
@@ -5808,6 +5829,25 @@ class HistoryService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isPrimarySeriesChild && parentRow && isSeriesContainerRow(parentRow)) {
|
||||||
|
const parentId = normalizeJobIdValue(parentRow?.id);
|
||||||
|
if (parentId) {
|
||||||
|
const remainingChildren = rows.filter((row) => {
|
||||||
|
const rowId = normalizeJobIdValue(row?.id);
|
||||||
|
if (!rowId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (normalizeJobIdValue(row?.parent_job_id) !== parentId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !visited.has(rowId);
|
||||||
|
});
|
||||||
|
if (remainingChildren.length === 0) {
|
||||||
|
visited.add(parentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Array.from(visited)
|
return Array.from(visited)
|
||||||
.map((id) => byId.get(id))
|
.map((id) => byId.get(id))
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@@ -6849,16 +6889,23 @@ class HistoryService {
|
|||||||
|
|
||||||
const includeRelated = Boolean(options?.includeRelated);
|
const includeRelated = Boolean(options?.includeRelated);
|
||||||
if (!includeRelated) {
|
if (!includeRelated) {
|
||||||
const existing = await this.getJobById(jobId);
|
const resolved = await this.getJobByIdOrReplacement(jobId);
|
||||||
|
const existing = resolved?.job || null;
|
||||||
|
const normalizedJobId = normalizeJobIdValue(resolved?.resolvedJobId || jobId);
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
const error = new Error('Job nicht gefunden.');
|
const error = new Error('Job nicht gefunden.');
|
||||||
error.statusCode = 404;
|
error.statusCode = 404;
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
if (!normalizedJobId) {
|
||||||
|
const error = new Error('Ungültige Job-ID.');
|
||||||
|
error.statusCode = 400;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
let fileSummary = null;
|
let fileSummary = null;
|
||||||
if (fileTarget !== 'none') {
|
if (fileTarget !== 'none') {
|
||||||
const preview = await this.getJobDeletePreview(jobId, { includeRelated: false });
|
const preview = await this.getJobDeletePreview(normalizedJobId, { includeRelated: false });
|
||||||
fileSummary = this._deletePathsFromPreview(preview, fileTarget, {
|
fileSummary = this._deletePathsFromPreview(preview, fileTarget, {
|
||||||
selectedMoviePaths: options?.selectedMoviePaths
|
selectedMoviePaths: options?.selectedMoviePaths
|
||||||
});
|
});
|
||||||
@@ -6869,7 +6916,7 @@ class HistoryService {
|
|||||||
'SELECT state, active_job_id FROM pipeline_state WHERE id = 1'
|
'SELECT state, active_job_id FROM pipeline_state WHERE id = 1'
|
||||||
);
|
);
|
||||||
|
|
||||||
const isActivePipelineJob = Number(pipelineRow?.active_job_id || 0) === Number(jobId);
|
const isActivePipelineJob = Number(pipelineRow?.active_job_id || 0) === Number(normalizedJobId);
|
||||||
const runningStates = new Set(['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING', 'CD_ANALYZING', 'CD_RIPPING', 'CD_ENCODING']);
|
const runningStates = new Set(['ANALYZING', 'RIPPING', 'MEDIAINFO_CHECK', 'ENCODING', 'CD_ANALYZING', 'CD_RIPPING', 'CD_ENCODING']);
|
||||||
|
|
||||||
if (isActivePipelineJob && runningStates.has(String(pipelineRow?.state || ''))) {
|
if (isActivePipelineJob && runningStates.has(String(pipelineRow?.state || ''))) {
|
||||||
@@ -6904,23 +6951,23 @@ class HistoryService {
|
|||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = 1 AND active_job_id = ?
|
WHERE id = 1 AND active_job_id = ?
|
||||||
`,
|
`,
|
||||||
[jobId]
|
[normalizedJobId]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await db.run('DELETE FROM jobs WHERE id = ?', [jobId]);
|
await db.run('DELETE FROM jobs WHERE id = ?', [normalizedJobId]);
|
||||||
await db.exec('COMMIT');
|
await db.exec('COMMIT');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await db.exec('ROLLBACK');
|
await db.exec('ROLLBACK');
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.closeProcessLog(jobId);
|
await this.closeProcessLog(normalizedJobId);
|
||||||
this._deleteProcessLogFile(jobId);
|
this._deleteProcessLogFile(normalizedJobId);
|
||||||
thumbnailService.deleteThumbnail(jobId);
|
thumbnailService.deleteThumbnail(normalizedJobId);
|
||||||
|
|
||||||
logger.warn('job:deleted', {
|
logger.warn('job:deleted', {
|
||||||
jobId,
|
jobId: normalizedJobId,
|
||||||
fileTarget,
|
fileTarget,
|
||||||
includeRelated: false,
|
includeRelated: false,
|
||||||
pipelineStateReset: isActivePipelineJob,
|
pipelineStateReset: isActivePipelineJob,
|
||||||
@@ -6934,15 +6981,16 @@ class HistoryService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
deleted: true,
|
deleted: true,
|
||||||
jobId,
|
jobId: normalizedJobId,
|
||||||
fileTarget,
|
fileTarget,
|
||||||
includeRelated: false,
|
includeRelated: false,
|
||||||
deletedJobIds: [Number(jobId)],
|
deletedJobIds: [Number(normalizedJobId)],
|
||||||
fileSummary
|
fileSummary
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizedJobId = normalizeJobIdValue(jobId);
|
const resolved = await this.getJobByIdOrReplacement(jobId);
|
||||||
|
const normalizedJobId = normalizeJobIdValue(resolved?.resolvedJobId || jobId);
|
||||||
const preview = await this.getJobDeletePreview(normalizedJobId, { includeRelated: true });
|
const preview = await this.getJobDeletePreview(normalizedJobId, { includeRelated: true });
|
||||||
const deleteJobIds = Array.isArray(preview?.relatedJobs)
|
const deleteJobIds = Array.isArray(preview?.relatedJobs)
|
||||||
? preview.relatedJobs
|
? preview.relatedJobs
|
||||||
|
|||||||
@@ -20984,7 +20984,7 @@ class PipelineService extends EventEmitter {
|
|||||||
|
|
||||||
const shouldAutoRecoverEncode = normalizedStage === 'ENCODING'
|
const shouldAutoRecoverEncode = normalizedStage === 'ENCODING'
|
||||||
&& hasConfirmedPlan
|
&& hasConfirmedPlan
|
||||||
&& (!isCancelled || resolvedMediaProfile === 'converter');
|
&& resolvedMediaProfile !== 'audiobook';
|
||||||
if (shouldAutoRecoverEncode) {
|
if (shouldAutoRecoverEncode) {
|
||||||
try {
|
try {
|
||||||
const recoveryReasonLabel = isCancelled ? 'Abbruch' : 'Fehler';
|
const recoveryReasonLabel = isCancelled ? 'Abbruch' : 'Fehler';
|
||||||
@@ -22782,7 +22782,13 @@ class PipelineService extends EventEmitter {
|
|||||||
);
|
);
|
||||||
newRawPath = nextRaw.rawDir;
|
newRawPath = nextRaw.rawDir;
|
||||||
} else {
|
} else {
|
||||||
const rawBaseDir = path.dirname(currentRawPath);
|
const rawStorage = resolveSeriesAwareRawStorage(
|
||||||
|
settings || {},
|
||||||
|
mediaProfile,
|
||||||
|
selectedMetadata,
|
||||||
|
mkInfo?.analyzeContext || null
|
||||||
|
);
|
||||||
|
const rawBaseDir = String(rawStorage?.rawBaseDir || '').trim() || path.dirname(currentRawPath);
|
||||||
const newMetadataBase = buildRawMetadataBase({
|
const newMetadataBase = buildRawMetadataBase({
|
||||||
title: job.title || job.detected_title || null,
|
title: job.title || job.detected_title || null,
|
||||||
year: job.year || null,
|
year: job.year || null,
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "0.13.1-6",
|
"version": "0.13.1-7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "0.13.1-6",
|
"version": "0.13.1-7",
|
||||||
"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-6",
|
"version": "0.13.1-7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -940,9 +940,9 @@ function InlineConfig({ job, plan, onStarted, onDeleted, onCancelled, onInputsCh
|
|||||||
{isVideo && (
|
{isVideo && (
|
||||||
<div className="converter-job-card-actions" style={{ marginTop: 0 }}>
|
<div className="converter-job-card-actions" style={{ marginTop: 0 }}>
|
||||||
<Button
|
<Button
|
||||||
label="OMDb neu zuordnen"
|
label="Metadaten neu zuweisen"
|
||||||
icon="pi pi-search"
|
icon="pi pi-search"
|
||||||
severity="secondary"
|
severity="info"
|
||||||
outlined
|
outlined
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => setMetadataDialogVisible(true)}
|
onClick={() => setMetadataDialogVisible(true)}
|
||||||
|
|||||||
@@ -1130,6 +1130,7 @@ export default function PipelineStatusCard({
|
|||||||
onRetry,
|
onRetry,
|
||||||
onDeleteJob,
|
onDeleteJob,
|
||||||
onInputsChanged,
|
onInputsChanged,
|
||||||
|
jobRow,
|
||||||
isQueued = false,
|
isQueued = false,
|
||||||
busy
|
busy
|
||||||
}) {
|
}) {
|
||||||
@@ -1141,6 +1142,21 @@ export default function PipelineStatusCard({
|
|||||||
const contextJobId = normalizeJobId(pipeline?.context?.jobId);
|
const contextJobId = normalizeJobId(pipeline?.context?.jobId);
|
||||||
const retryJobId = explicitJobId || contextJobId;
|
const retryJobId = explicitJobId || contextJobId;
|
||||||
const queueLocked = Boolean(isQueued && retryJobId);
|
const queueLocked = Boolean(isQueued && retryJobId);
|
||||||
|
const orphanSource = String(
|
||||||
|
jobRow?.makemkvInfo?.source
|
||||||
|
|| pipeline?.context?.source
|
||||||
|
|| pipeline?.context?.importSource
|
||||||
|
|| ''
|
||||||
|
).trim().toLowerCase();
|
||||||
|
const isOrphanImport = orphanSource === 'orphan_raw_import';
|
||||||
|
const isOrphanCancelled = isOrphanImport && (state === 'ERROR' || state === 'CANCELLED');
|
||||||
|
const ripCompleted = Boolean(
|
||||||
|
Number(jobRow?.rip_successful || jobRow?.ripSuccessful || pipeline?.context?.ripSuccessful || 0) === 1
|
||||||
|
|| jobRow?.raw_path
|
||||||
|
|| pipeline?.context?.rawPath
|
||||||
|
|| jobRow?.makemkvInfo?.rawPath
|
||||||
|
|| String(jobRow?.makemkvInfo?.status || '').trim().toUpperCase() === 'SUCCESS'
|
||||||
|
);
|
||||||
const selectedMetadata = pipeline?.context?.selectedMetadata || null;
|
const selectedMetadata = pipeline?.context?.selectedMetadata || null;
|
||||||
const mediaInfoReview = pipeline?.context?.mediaInfoReview || null;
|
const mediaInfoReview = pipeline?.context?.mediaInfoReview || null;
|
||||||
const playlistAnalysis = pipeline?.context?.playlistAnalysis || null;
|
const playlistAnalysis = pipeline?.context?.playlistAnalysis || null;
|
||||||
@@ -1789,6 +1805,11 @@ export default function PipelineStatusCard({
|
|||||||
&& retryJobId
|
&& retryJobId
|
||||||
&& pipeline?.context?.canRestartEncodeFromLastSettings
|
&& pipeline?.context?.canRestartEncodeFromLastSettings
|
||||||
);
|
);
|
||||||
|
const canCancelReview = Boolean(
|
||||||
|
!running
|
||||||
|
&& retryJobId
|
||||||
|
&& (state === 'READY_TO_ENCODE' || state === 'WAITING_FOR_USER_DECISION')
|
||||||
|
);
|
||||||
const canRestartReviewFromRaw = Boolean(
|
const canRestartReviewFromRaw = Boolean(
|
||||||
retryJobId
|
retryJobId
|
||||||
&& !running
|
&& !running
|
||||||
@@ -2852,7 +2873,7 @@ export default function PipelineStatusCard({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(state === 'METADATA_SELECTION' || state === 'WAITING_FOR_USER_DECISION')
|
{(state === 'METADATA_SELECTION' || state === 'WAITING_FOR_USER_DECISION' || isOrphanCancelled)
|
||||||
&& retryJobId
|
&& retryJobId
|
||||||
&& typeof onOpenMetadata === 'function'
|
&& typeof onOpenMetadata === 'function'
|
||||||
&& !(jobMediaProfile === 'converter' && !selectedMetadata?.imdbId) ? (
|
&& !(jobMediaProfile === 'converter' && !selectedMetadata?.imdbId) ? (
|
||||||
@@ -2882,11 +2903,12 @@ export default function PipelineStatusCard({
|
|||||||
&& state !== 'DISC_DETECTED'
|
&& state !== 'DISC_DETECTED'
|
||||||
&& retryJobId
|
&& retryJobId
|
||||||
&& typeof onReassignOmdb === 'function'
|
&& typeof onReassignOmdb === 'function'
|
||||||
|
&& !isOrphanCancelled
|
||||||
&& !(jobMediaProfile === 'converter' && state === 'READY_TO_START' && !selectedMetadata?.imdbId) ? (
|
&& !(jobMediaProfile === 'converter' && state === 'READY_TO_START' && !selectedMetadata?.imdbId) ? (
|
||||||
<Button
|
<Button
|
||||||
label="OMDb neu zuordnen"
|
label="Metadaten neu zuweisen"
|
||||||
icon="pi pi-search"
|
icon="pi pi-search"
|
||||||
severity="secondary"
|
severity="info"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => onReassignOmdb?.(retryJobId)}
|
onClick={() => onReassignOmdb?.(retryJobId)}
|
||||||
loading={busy}
|
loading={busy}
|
||||||
@@ -2986,7 +3008,7 @@ export default function PipelineStatusCard({
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{running && (
|
{(running || canCancelReview) && (
|
||||||
<Button
|
<Button
|
||||||
label="Abbrechen"
|
label="Abbrechen"
|
||||||
icon="pi pi-stop"
|
icon="pi pi-stop"
|
||||||
@@ -3041,7 +3063,7 @@ export default function PipelineStatusCard({
|
|||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{(state === 'ERROR' || state === 'CANCELLED') && retryJobId && (
|
{(state === 'ERROR' || state === 'CANCELLED') && retryJobId && !isOrphanCancelled && !ripCompleted && (
|
||||||
<Button
|
<Button
|
||||||
label="Retry Rippen"
|
label="Retry Rippen"
|
||||||
icon="pi pi-refresh"
|
icon="pi pi-refresh"
|
||||||
@@ -3051,7 +3073,7 @@ export default function PipelineStatusCard({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(state === 'ERROR' || state === 'CANCELLED') ? (
|
{(state === 'ERROR' || state === 'CANCELLED') && !isOrphanCancelled && !ripCompleted ? (
|
||||||
<Button
|
<Button
|
||||||
label="Disk-Analyse neu starten"
|
label="Disk-Analyse neu starten"
|
||||||
icon="pi pi-search"
|
icon="pi pi-search"
|
||||||
@@ -3062,7 +3084,7 @@ export default function PipelineStatusCard({
|
|||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{retryJobId && typeof onDeleteJob === 'function' ? (
|
{!running && retryJobId && typeof onDeleteJob === 'function' ? (
|
||||||
<Button
|
<Button
|
||||||
label="Job löschen"
|
label="Job löschen"
|
||||||
icon="pi pi-trash"
|
icon="pi pi-trash"
|
||||||
|
|||||||
@@ -1279,6 +1279,7 @@ function ConverterVideoJobCard({
|
|||||||
|
|
||||||
<PipelineStatusCard
|
<PipelineStatusCard
|
||||||
jobId={jobId}
|
jobId={jobId}
|
||||||
|
jobRow={job}
|
||||||
pipeline={pipeline}
|
pipeline={pipeline}
|
||||||
onOpenMetadata={onOpenMetadata}
|
onOpenMetadata={onOpenMetadata}
|
||||||
onReassignOmdb={onReassignOmdb}
|
onReassignOmdb={onReassignOmdb}
|
||||||
|
|||||||
@@ -76,10 +76,9 @@ export default function DatabasePage() {
|
|||||||
: (newJobId ? `Historieneintrag #${newJobId} erstellt.` : 'Historieneintrag wurde erstellt.'),
|
: (newJobId ? `Historieneintrag #${newJobId} erstellt.` : 'Historieneintrag wurde erstellt.'),
|
||||||
life: 3500
|
life: 3500
|
||||||
});
|
});
|
||||||
await loadOrphans();
|
// Jump to the Ripper view immediately after creating a job from /database.
|
||||||
if (activationStarted) {
|
|
||||||
navigate('/ripper');
|
navigate('/ripper');
|
||||||
}
|
await loadOrphans();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toastRef.current?.show({
|
toastRef.current?.show({
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
|
|||||||
@@ -64,6 +64,20 @@ function normalizeDrivePath(value) {
|
|||||||
return String(value || '').trim();
|
return String(value || '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPathBaseName(value) {
|
||||||
|
const normalized = String(value || '').trim().replace(/[\\]+/g, '/');
|
||||||
|
if (!normalized) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const segments = normalized.split('/').filter(Boolean);
|
||||||
|
return segments[segments.length - 1] || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function isIncompleteRawPath(rawPath) {
|
||||||
|
const baseName = getPathBaseName(rawPath);
|
||||||
|
return /^incomplete_/i.test(baseName);
|
||||||
|
}
|
||||||
|
|
||||||
function formatPercent(value, digits = 1) {
|
function formatPercent(value, digits = 1) {
|
||||||
const parsed = Number(value);
|
const parsed = Number(value);
|
||||||
if (!Number.isFinite(parsed)) {
|
if (!Number.isFinite(parsed)) {
|
||||||
@@ -1643,27 +1657,7 @@ export default function RipperPage({
|
|||||||
latestCancelledJob = await fetchLatestCancelledJob();
|
latestCancelledJob = await fetchLatestCancelledJob();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const latestStatus = String(
|
// No post-cancel cleanup dialog here; encode/rip cleanup is handled by settings or retry flows.
|
||||||
latestCancelledJob?.status
|
|
||||||
|| latestCancelledJob?.last_state
|
|
||||||
|| ''
|
|
||||||
).trim().toUpperCase();
|
|
||||||
const autoPreparedForRestart = cancelledState === 'ENCODING' && latestStatus === 'READY_TO_ENCODE';
|
|
||||||
if (cancelledState === 'ENCODING' && cancelledJobId && !autoPreparedForRestart) {
|
|
||||||
setCancelCleanupDialog({
|
|
||||||
visible: true,
|
|
||||||
jobId: cancelledJobId,
|
|
||||||
target: 'movie',
|
|
||||||
path: latestCancelledJob?.output_path || cancelledJob?.output_path || null
|
|
||||||
});
|
|
||||||
} else if (cancelledState === 'RIPPING' && cancelledJobId) {
|
|
||||||
setCancelCleanupDialog({
|
|
||||||
visible: true,
|
|
||||||
jobId: cancelledJobId,
|
|
||||||
target: 'raw',
|
|
||||||
path: latestCancelledJob?.raw_path || cancelledJob?.raw_path || null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error);
|
showError(error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -1939,13 +1933,20 @@ export default function RipperPage({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const job = ripperJobs.find((item) => normalizeJobId(item?.id) === normalizedJobId) || null;
|
const job = ripperJobs.find((item) => normalizeJobId(item?.id) === normalizedJobId) || null;
|
||||||
|
const ripSuccessful = Number(job?.rip_successful || 0) === 1;
|
||||||
|
const deleteIncompleteRaw = !ripSuccessful && isIncompleteRawPath(job?.raw_path);
|
||||||
|
const deleteTarget = deleteIncompleteRaw ? 'raw' : 'none';
|
||||||
const title = String(job?.title || job?.detected_title || `Job #${normalizedJobId}`).trim();
|
const title = String(job?.title || job?.detected_title || `Job #${normalizedJobId}`).trim();
|
||||||
const confirmed = await confirmModal({
|
const confirmed = await confirmModal({
|
||||||
header: 'Job löschen',
|
header: 'Job löschen',
|
||||||
message:
|
message:
|
||||||
`Job #${normalizedJobId} wirklich löschen?\n` +
|
`Job #${normalizedJobId} wirklich löschen?\n` +
|
||||||
`${title}\n\n` +
|
`${title}\n\n` +
|
||||||
'Hinweis: Dateien bleiben erhalten. Das zugeordnete Laufwerk wird freigegeben und auf Leerzustand zurückgesetzt.',
|
(deleteIncompleteRaw
|
||||||
|
? 'Hinweis: Unvollständiges RAW wird gelöscht. Für einen neuen Versuch bitte das Laufwerk erneut analysieren.'
|
||||||
|
: 'Hinweis: Dateien bleiben erhalten. Vollständiges RAW ist anschließend über /database wiederherstellbar.') +
|
||||||
|
'\nZugehörige Einträge (z.B. Serien-Container oder Folgejobs) werden ebenfalls entfernt.' +
|
||||||
|
'\nDas zugeordnete Laufwerk wird freigegeben und auf Leerzustand zurückgesetzt.',
|
||||||
acceptLabel: 'Löschen',
|
acceptLabel: 'Löschen',
|
||||||
rejectLabel: 'Abbrechen',
|
rejectLabel: 'Abbrechen',
|
||||||
danger: true
|
danger: true
|
||||||
@@ -1969,8 +1970,8 @@ export default function RipperPage({
|
|||||||
await wait(250);
|
await wait(250);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await api.deleteJobEntry(normalizedJobId, 'none', {
|
await api.deleteJobEntry(normalizedJobId, deleteTarget, {
|
||||||
includeRelated: false,
|
includeRelated: true,
|
||||||
resetDriveState: true,
|
resetDriveState: true,
|
||||||
keepDetectedDevice: false
|
keepDetectedDevice: false
|
||||||
});
|
});
|
||||||
@@ -2207,7 +2208,40 @@ export default function RipperPage({
|
|||||||
setBusy(true);
|
setBusy(true);
|
||||||
try {
|
try {
|
||||||
if (metadataDialogReassignMode) {
|
if (metadataDialogReassignMode) {
|
||||||
|
const providerRaw = String(payload?.metadataProvider || '').trim().toLowerCase();
|
||||||
|
const nextWorkflowKind = String(
|
||||||
|
payload?.workflowKind
|
||||||
|
|| (providerRaw === 'tmdb' ? 'series' : (providerRaw === 'omdb' ? 'film' : ''))
|
||||||
|
|| ''
|
||||||
|
).trim().toLowerCase();
|
||||||
|
const reassignJob = ripperJobs.find((item) => normalizeJobId(item?.id) === normalizeJobId(payload?.jobId)) || null;
|
||||||
|
const currentWorkflowKind = String(
|
||||||
|
reassignJob?.makemkvInfo?.analyzeContext?.selectedMetadata?.workflowKind
|
||||||
|
|| reassignJob?.makemkvInfo?.analyzeContext?.workflowKind
|
||||||
|
|| reassignJob?.makemkvInfo?.selectedMetadata?.workflowKind
|
||||||
|
|| effectiveMetadataDialogContext?.selectedMetadata?.workflowKind
|
||||||
|
|| ''
|
||||||
|
).trim().toLowerCase();
|
||||||
|
const currentProvider = String(
|
||||||
|
reassignJob?.makemkvInfo?.analyzeContext?.metadataProvider
|
||||||
|
|| reassignJob?.makemkvInfo?.selectedMetadata?.metadataProvider
|
||||||
|
|| effectiveMetadataDialogContext?.metadataProvider
|
||||||
|
|| ''
|
||||||
|
).trim().toLowerCase();
|
||||||
|
const shouldRestartReview = Boolean(
|
||||||
|
nextWorkflowKind
|
||||||
|
&& currentWorkflowKind
|
||||||
|
&& nextWorkflowKind !== currentWorkflowKind
|
||||||
|
) || Boolean(
|
||||||
|
currentProvider
|
||||||
|
&& providerRaw
|
||||||
|
&& currentProvider !== providerRaw
|
||||||
|
);
|
||||||
|
if (shouldRestartReview) {
|
||||||
|
await api.selectMetadata(payload);
|
||||||
|
} else {
|
||||||
await api.assignJobOmdb(payload.jobId, payload);
|
await api.assignJobOmdb(payload.jobId, payload);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
await api.selectMetadata(payload);
|
await api.selectMetadata(payload);
|
||||||
}
|
}
|
||||||
@@ -3051,6 +3085,7 @@ export default function RipperPage({
|
|||||||
{!isCdJob && !isAudiobookJob ? (
|
{!isCdJob && !isAudiobookJob ? (
|
||||||
<PipelineStatusCard
|
<PipelineStatusCard
|
||||||
jobId={jobId}
|
jobId={jobId}
|
||||||
|
jobRow={job}
|
||||||
pipeline={pipelineForJob}
|
pipeline={pipelineForJob}
|
||||||
onAnalyze={handleAnalyze}
|
onAnalyze={handleAnalyze}
|
||||||
onReanalyze={handleReanalyze}
|
onReanalyze={handleReanalyze}
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "0.13.1-6",
|
"version": "0.13.1-7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "0.13.1-6",
|
"version": "0.13.1-7",
|
||||||
"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-6",
|
"version": "0.13.1-7",
|
||||||
"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