From e140a9fa8cc251b4d3669cd7e56684f7f316e530 Mon Sep 17 00:00:00 2001 From: mboehmlaender Date: Sat, 14 Mar 2026 08:57:25 +0000 Subject: [PATCH] 0.9.1 Fix Restart --- backend/package-lock.json | 4 +- backend/package.json | 2 +- backend/src/routes/historyRoutes.js | 19 +++++ backend/src/services/historyService.js | 79 ++++++++++++++++++ frontend/package-lock.json | 4 +- frontend/package.json | 2 +- frontend/src/api/client.js | 8 ++ frontend/src/components/JobDetailDialog.jsx | 14 +++- frontend/src/pages/DatabasePage.jsx | 90 +++++++++++++++++++++ package-lock.json | 4 +- package.json | 2 +- 11 files changed, 218 insertions(+), 10 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 4d24aab..9448ca7 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-backend", - "version": "0.9.0-1", + "version": "0.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-backend", - "version": "0.9.0-1", + "version": "0.9.1", "dependencies": { "cors": "^2.8.5", "dotenv": "^16.4.7", diff --git a/backend/package.json b/backend/package.json index b127d8d..f218b2a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-backend", - "version": "0.9.0-1", + "version": "0.9.1", "private": true, "type": "commonjs", "scripts": { diff --git a/backend/src/routes/historyRoutes.js b/backend/src/routes/historyRoutes.js index 46e7089..2179ec6 100644 --- a/backend/src/routes/historyRoutes.js +++ b/backend/src/routes/historyRoutes.js @@ -95,6 +95,25 @@ router.post( }) ); +router.post( + '/:id/cd/assign', + asyncHandler(async (req, res) => { + const id = Number(req.params.id); + const payload = req.body || {}; + logger.info('post:job:cd:assign', { + reqId: req.reqId, + id, + mbId: payload?.mbId || null, + hasTitle: Boolean(payload?.title), + hasArtist: Boolean(payload?.artist), + trackCount: Array.isArray(payload?.tracks) ? payload.tracks.length : 0 + }); + + const job = await historyService.assignCdMetadata(id, payload); + res.json({ job }); + }) +); + router.post( '/:id/delete-files', asyncHandler(async (req, res) => { diff --git a/backend/src/services/historyService.js b/backend/src/services/historyService.js index c4c78b7..2f7300e 100644 --- a/backend/src/services/historyService.js +++ b/backend/src/services/historyService.js @@ -1718,6 +1718,85 @@ class HistoryService { return enrichJobRow(updated, settings); } + async assignCdMetadata(jobId, payload = {}) { + const job = await this.getJobById(jobId); + if (!job) { + const error = new Error('Job nicht gefunden.'); + error.statusCode = 404; + throw error; + } + + const title = String(payload.title || '').trim() || null; + const artist = String(payload.artist || '').trim() || null; + const yearRaw = Number(payload.year); + const year = Number.isFinite(yearRaw) && yearRaw > 0 ? Math.trunc(yearRaw) : null; + const mbId = String(payload.mbId || '').trim() || null; + const coverUrl = String(payload.coverUrl || '').trim() || null; + const selectedTracks = Array.isArray(payload.tracks) ? payload.tracks : null; + + if (!title && !artist && !mbId) { + const error = new Error('Keine CD-Metadaten zum Aktualisieren angegeben.'); + error.statusCode = 400; + throw error; + } + + const cdInfo = parseJsonSafe(job.makemkv_info_json, {}); + const tocTracks = Array.isArray(cdInfo.tracks) ? cdInfo.tracks : []; + + let mergedTracks = tocTracks; + if (selectedTracks && tocTracks.length > 0) { + mergedTracks = tocTracks.map((t) => { + const selected = selectedTracks.find((st) => Number(st.position) === Number(t.position)); + const resolvedTitle = String(selected?.title || t.title || `Track ${t.position}`).replace(/\s+/g, ' ').trim(); + const resolvedArtist = String(selected?.artist || t.artist || artist || '').replace(/\s+/g, ' ').trim() || null; + return { + ...t, + title: resolvedTitle, + artist: resolvedArtist, + selected: selected ? Boolean(selected.selected) : true + }; + }); + } + + const prevSelected = cdInfo.selectedMetadata && typeof cdInfo.selectedMetadata === 'object' ? cdInfo.selectedMetadata : {}; + const updatedCdInfo = { + ...cdInfo, + tracks: mergedTracks, + selectedMetadata: { + ...prevSelected, + title: title || prevSelected.title || null, + artist: artist || prevSelected.artist || null, + year: year !== null ? year : (prevSelected.year || null), + mbId: mbId || prevSelected.mbId || null, + coverUrl: coverUrl || prevSelected.coverUrl || null + } + }; + + await this.updateJob(jobId, { + title: title || null, + year: year || null, + imdb_id: mbId || null, + poster_url: coverUrl || null, + makemkv_info_json: JSON.stringify(updatedCdInfo) + }); + + if (coverUrl && !thumbnailService.isLocalUrl(coverUrl)) { + thumbnailService.cacheJobThumbnail(jobId, coverUrl).catch(() => {}); + } + + await this.appendLog( + jobId, + 'USER_ACTION', + `CD-Metadaten aktualisiert: album="${title || '-'}", artist="${artist || '-'}", year="${year || '-'}", mbId="${mbId || '-'}"` + ); + + const [updated, settings] = await Promise.all([ + this.getJobById(jobId), + settingsService.getSettingsMap() + ]); + return enrichJobRow(updated, settings); + } + async _resolveRelatedJobsForDeletion(jobId, options = {}) { const includeRelated = options?.includeRelated !== false; const normalizedJobId = normalizeJobIdValue(jobId); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e34ced8..8f06b96 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "ripster-frontend", - "version": "0.9.0-1", + "version": "0.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ripster-frontend", - "version": "0.9.0-1", + "version": "0.9.1", "dependencies": { "primeicons": "^7.0.0", "primereact": "^10.9.2", diff --git a/frontend/package.json b/frontend/package.json index 3c73d16..ee85e8f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "ripster-frontend", - "version": "0.9.0-1", + "version": "0.9.1", "private": true, "type": "module", "scripts": { diff --git a/frontend/src/api/client.js b/frontend/src/api/client.js index 3e37302..213a6e0 100644 --- a/frontend/src/api/client.js +++ b/frontend/src/api/client.js @@ -435,6 +435,14 @@ export const api = { afterMutationInvalidate(['/history']); return result; }, + async assignJobCdMetadata(jobId, payload = {}) { + const result = await request(`/history/${jobId}/cd/assign`, { + method: 'POST', + body: JSON.stringify(payload || {}) + }); + afterMutationInvalidate(['/history']); + return result; + }, async deleteJobFiles(jobId, target = 'both') { const result = await request(`/history/${jobId}/delete-files`, { method: 'POST', diff --git a/frontend/src/components/JobDetailDialog.jsx b/frontend/src/components/JobDetailDialog.jsx index 56d7eb1..1cfc7bd 100644 --- a/frontend/src/components/JobDetailDialog.jsx +++ b/frontend/src/components/JobDetailDialog.jsx @@ -379,6 +379,7 @@ export default function JobDetailDialog({ onLoadLog, logLoadingMode = null, onAssignOmdb, + onAssignCdMetadata, onResumeReady, onRestartEncode, onRestartReview, @@ -389,6 +390,7 @@ export default function JobDetailDialog({ onRemoveFromQueue, isQueued = false, omdbAssignBusy = false, + cdMetadataAssignBusy = false, actionBusy = false, reencodeBusy = false, deleteEntryBusy = false @@ -748,7 +750,17 @@ export default function JobDetailDialog({ loading={omdbAssignBusy} disabled={running || typeof onAssignOmdb !== 'function'} /> - ) : null} + ) : ( +