Queue and UI fixes
This commit is contained in:
@@ -95,11 +95,13 @@ router.post(
|
||||
const selectedEncodeTitleId = req.body?.selectedEncodeTitleId ?? null;
|
||||
const selectedTrackSelection = req.body?.selectedTrackSelection ?? null;
|
||||
const selectedPostEncodeScriptIds = req.body?.selectedPostEncodeScriptIds;
|
||||
const skipPipelineStateUpdate = Boolean(req.body?.skipPipelineStateUpdate);
|
||||
logger.info('post:confirm-encode', {
|
||||
reqId: req.reqId,
|
||||
jobId,
|
||||
selectedEncodeTitleId,
|
||||
selectedTrackSelectionProvided: Boolean(selectedTrackSelection),
|
||||
skipPipelineStateUpdate,
|
||||
selectedPostEncodeScriptIdsCount: Array.isArray(selectedPostEncodeScriptIds)
|
||||
? selectedPostEncodeScriptIds.length
|
||||
: 0
|
||||
@@ -107,7 +109,8 @@ router.post(
|
||||
const job = await pipelineService.confirmEncodeReview(jobId, {
|
||||
selectedEncodeTitleId,
|
||||
selectedTrackSelection,
|
||||
selectedPostEncodeScriptIds
|
||||
selectedPostEncodeScriptIds,
|
||||
skipPipelineStateUpdate
|
||||
});
|
||||
res.json({ job });
|
||||
})
|
||||
@@ -116,9 +119,13 @@ router.post(
|
||||
router.post(
|
||||
'/cancel',
|
||||
asyncHandler(async (req, res) => {
|
||||
logger.warn('post:cancel', { reqId: req.reqId });
|
||||
await pipelineService.cancel();
|
||||
res.json({ ok: true });
|
||||
const rawJobId = req.body?.jobId;
|
||||
const jobId = rawJobId === null || rawJobId === undefined || String(rawJobId).trim() === ''
|
||||
? null
|
||||
: Number(rawJobId);
|
||||
logger.warn('post:cancel', { reqId: req.reqId, jobId });
|
||||
const result = await pipelineService.cancel(jobId);
|
||||
res.json({ result });
|
||||
})
|
||||
);
|
||||
|
||||
@@ -127,8 +134,8 @@ router.post(
|
||||
asyncHandler(async (req, res) => {
|
||||
const jobId = Number(req.params.jobId);
|
||||
logger.info('post:retry', { reqId: req.reqId, jobId });
|
||||
await pipelineService.retry(jobId);
|
||||
res.json({ ok: true });
|
||||
const result = await pipelineService.retry(jobId);
|
||||
res.json({ result });
|
||||
})
|
||||
);
|
||||
|
||||
@@ -152,6 +159,16 @@ router.post(
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/restart-review/:jobId',
|
||||
asyncHandler(async (req, res) => {
|
||||
const jobId = Number(req.params.jobId);
|
||||
logger.info('post:restart-review', { reqId: req.reqId, jobId });
|
||||
const result = await pipelineService.restartReviewFromRaw(jobId);
|
||||
res.json({ result });
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/restart-encode/:jobId',
|
||||
asyncHandler(async (req, res) => {
|
||||
@@ -162,4 +179,23 @@ router.post(
|
||||
})
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/queue',
|
||||
asyncHandler(async (req, res) => {
|
||||
logger.debug('get:queue', { reqId: req.reqId });
|
||||
const queue = await pipelineService.getQueueSnapshot();
|
||||
res.json({ queue });
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/queue/reorder',
|
||||
asyncHandler(async (req, res) => {
|
||||
const orderedJobIds = Array.isArray(req.body?.orderedJobIds) ? req.body.orderedJobIds : [];
|
||||
logger.info('post:queue:reorder', { reqId: req.reqId, orderedJobIds });
|
||||
const queue = await pipelineService.reorderQueue(orderedJobIds);
|
||||
res.json({ queue });
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
@@ -564,6 +564,49 @@ class HistoryService {
|
||||
}));
|
||||
}
|
||||
|
||||
async getJobsByIds(jobIds = []) {
|
||||
const ids = Array.isArray(jobIds)
|
||||
? jobIds
|
||||
.map((value) => Number(value))
|
||||
.filter((value) => Number.isFinite(value) && value > 0)
|
||||
.map((value) => Math.trunc(value))
|
||||
: [];
|
||||
if (ids.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const db = await getDb();
|
||||
const placeholders = ids.map(() => '?').join(', ');
|
||||
const rows = await db.all(
|
||||
`SELECT * FROM jobs WHERE id IN (${placeholders})`,
|
||||
ids
|
||||
);
|
||||
const byId = new Map(rows.map((row) => [Number(row.id), row]));
|
||||
return ids
|
||||
.map((id) => byId.get(id))
|
||||
.filter(Boolean)
|
||||
.map((job) => ({
|
||||
...enrichJobRow(job),
|
||||
log_count: hasProcessLogFile(job.id) ? 1 : 0
|
||||
}));
|
||||
}
|
||||
|
||||
async getRunningJobs() {
|
||||
const db = await getDb();
|
||||
const rows = await db.all(
|
||||
`
|
||||
SELECT *
|
||||
FROM jobs
|
||||
WHERE status IN ('RIPPING', 'ENCODING')
|
||||
ORDER BY updated_at ASC, id ASC
|
||||
`
|
||||
);
|
||||
return rows.map((job) => ({
|
||||
...enrichJobRow(job),
|
||||
log_count: hasProcessLogFile(job.id) ? 1 : 0
|
||||
}));
|
||||
}
|
||||
|
||||
async getJobWithLogs(jobId, options = {}) {
|
||||
const db = await getDb();
|
||||
const job = await db.get('SELECT * FROM jobs WHERE id = ?', [jobId]);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -71,18 +71,23 @@ function spawnTrackedProcess({
|
||||
});
|
||||
});
|
||||
|
||||
let cancelCalled = false;
|
||||
const cancel = () => {
|
||||
if (child.killed) {
|
||||
if (cancelCalled) {
|
||||
return;
|
||||
}
|
||||
cancelCalled = true;
|
||||
|
||||
logger.warn('spawn:cancel:requested', { cmd, args, context, pid: child.pid });
|
||||
child.kill('SIGINT');
|
||||
|
||||
setTimeout(() => {
|
||||
if (!child.killed) {
|
||||
try {
|
||||
process.kill(child.pid, 0);
|
||||
logger.warn('spawn:cancel:force-kill', { cmd, args, context, pid: child.pid });
|
||||
child.kill('SIGKILL');
|
||||
} catch (_e) {
|
||||
// Process already terminated
|
||||
}
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
@@ -70,6 +70,20 @@ function normalizeTrackIds(rawList) {
|
||||
return output;
|
||||
}
|
||||
|
||||
function normalizeNonNegativeInteger(rawValue) {
|
||||
if (rawValue === null || rawValue === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (typeof rawValue === 'string' && rawValue.trim() === '') {
|
||||
return null;
|
||||
}
|
||||
const value = Number(rawValue);
|
||||
if (!Number.isFinite(value) || value < 0) {
|
||||
return null;
|
||||
}
|
||||
return Math.trunc(value);
|
||||
}
|
||||
|
||||
function removeSelectionArgs(extraArgs) {
|
||||
const args = Array.isArray(extraArgs) ? extraArgs : [];
|
||||
const filtered = [];
|
||||
@@ -554,7 +568,7 @@ class SettingsService {
|
||||
? 'backup'
|
||||
: 'mkv';
|
||||
const sourceArg = this.resolveSourceArg(map, deviceInfo);
|
||||
const rawSelectedTitleId = Number(options?.selectedTitleId);
|
||||
const rawSelectedTitleId = normalizeNonNegativeInteger(options?.selectedTitleId);
|
||||
const parsedExtra = splitArgs(map.makemkv_rip_extra_args);
|
||||
let extra = [];
|
||||
let baseArgs = [];
|
||||
@@ -574,7 +588,7 @@ class SettingsService {
|
||||
} else {
|
||||
extra = parsedExtra;
|
||||
const minLength = Number(map.makemkv_min_length_minutes || 60);
|
||||
const hasExplicitTitle = Number.isFinite(rawSelectedTitleId) && rawSelectedTitleId >= 0;
|
||||
const hasExplicitTitle = rawSelectedTitleId !== null;
|
||||
const targetTitle = hasExplicitTitle ? String(Math.trunc(rawSelectedTitleId)) : 'all';
|
||||
if (hasExplicitTitle) {
|
||||
baseArgs = [
|
||||
|
||||
@@ -524,13 +524,15 @@ function analyzePlaylistObfuscation(lines, minLengthMinutes = 60, options = {})
|
||||
|
||||
const similarityGroups = buildSimilarityGroups(candidates, durationSimilaritySeconds);
|
||||
const obfuscationDetected = similarityGroups.length > 0;
|
||||
const primaryGroup = similarityGroups[0] || null;
|
||||
const evaluatedCandidates = primaryGroup ? scoreCandidates(primaryGroup.titles) : [];
|
||||
const multipleCandidatesDetected = candidates.length > 1;
|
||||
const manualDecisionRequired = multipleCandidatesDetected;
|
||||
const decisionPool = manualDecisionRequired ? candidates : [];
|
||||
const evaluatedCandidates = decisionPool.length > 0 ? scoreCandidates(decisionPool) : [];
|
||||
const recommendation = evaluatedCandidates[0] || null;
|
||||
const candidatePlaylists = primaryGroup
|
||||
? uniqueOrdered(primaryGroup.titles.map((item) => item.playlistId).filter(Boolean))
|
||||
const candidatePlaylists = manualDecisionRequired
|
||||
? uniqueOrdered(decisionPool.map((item) => item.playlistId).filter(Boolean))
|
||||
: [];
|
||||
const playlistSegments = buildPlaylistSegmentMap(primaryGroup ? primaryGroup.titles : []);
|
||||
const playlistSegments = buildPlaylistSegmentMap(decisionPool);
|
||||
const playlistToTitleId = buildPlaylistToTitleIdMap(parsedTitles);
|
||||
|
||||
return {
|
||||
@@ -542,7 +544,10 @@ function analyzePlaylistObfuscation(lines, minLengthMinutes = 60, options = {})
|
||||
candidates,
|
||||
duplicateDurationGroups: similarityGroups,
|
||||
obfuscationDetected,
|
||||
manualDecisionRequired: obfuscationDetected,
|
||||
manualDecisionRequired,
|
||||
manualDecisionReason: manualDecisionRequired
|
||||
? (obfuscationDetected ? 'multiple_similar_candidates' : 'multiple_candidates_after_min_length')
|
||||
: null,
|
||||
candidatePlaylists,
|
||||
candidatePlaylistFiles: candidatePlaylists.map((item) => `${item}.mpls`),
|
||||
playlistToTitleId,
|
||||
|
||||
Reference in New Issue
Block a user