Compare commits
8 Commits
frontend-n
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| ba91f83722 | |||
| 466e7a7a3d | |||
| e67c0d316d | |||
| 1da5ee3e34 | |||
| 4d377f3eb4 | |||
| df708485b5 | |||
| b6cac5efb4 | |||
| f38081649f |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -81,3 +81,6 @@ Thumbs.db
|
|||||||
# ----------------------------
|
# ----------------------------
|
||||||
deploy-ripster.sh
|
deploy-ripster.sh
|
||||||
build-handbrake-nvdec.sh
|
build-handbrake-nvdec.sh
|
||||||
|
gitea-setup.sh
|
||||||
|
gitea_install.sh
|
||||||
|
/scripts/
|
||||||
|
|||||||
4
backend/package-lock.json
generated
4
backend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "1.0.0",
|
"version": "0.9.0-1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "1.0.0",
|
"version": "0.9.0-1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "1.0.0",
|
"version": "0.9.0-1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -855,6 +855,41 @@ async function migrateSettingsSchemaMetadata(db) {
|
|||||||
logger.info('migrate:settings-schema-category-moved', { key: move.key, category: move.category });
|
logger.info('migrate:settings-schema-category-moved', { key: move.key, category: move.category });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rawDirCdLabel = 'CD RAW-Ordner';
|
||||||
|
const rawDirCdDescription = 'Basisordner für rohe CD-WAV-Dateien (cdparanoia-Output). Leer = Standardpfad (data/output/cd).';
|
||||||
|
const rawDirCdResult = await db.run(
|
||||||
|
`UPDATE settings_schema
|
||||||
|
SET label = ?, description = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE key = 'raw_dir_cd' AND (label != ? OR description != ?)`,
|
||||||
|
[rawDirCdLabel, rawDirCdDescription, rawDirCdLabel, rawDirCdDescription]
|
||||||
|
);
|
||||||
|
if (rawDirCdResult?.changes > 0) {
|
||||||
|
logger.info('migrate:settings-schema-cd-raw-updated', {
|
||||||
|
key: 'raw_dir_cd',
|
||||||
|
label: rawDirCdLabel
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate raw_dir_cd_owner label
|
||||||
|
await db.run(
|
||||||
|
`UPDATE settings_schema SET label = 'Eigentümer CD RAW-Ordner', updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE key = 'raw_dir_cd_owner' AND label != 'Eigentümer CD RAW-Ordner'`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add movie_dir_cd if not already present
|
||||||
|
await db.run(
|
||||||
|
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||||
|
VALUES ('movie_dir_cd', 'Pfade', 'CD Output-Ordner', 'path', 0, 'Zielordner für encodierte CD-Ausgaben (FLAC, MP3 usw.). Leer = gleicher Ordner wie CD RAW-Ordner.', NULL, '[]', '{}', 114)`
|
||||||
|
);
|
||||||
|
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('movie_dir_cd', NULL)`);
|
||||||
|
|
||||||
|
// Add movie_dir_cd_owner if not already present
|
||||||
|
await db.run(
|
||||||
|
`INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||||
|
VALUES ('movie_dir_cd_owner', 'Pfade', 'Eigentümer CD Output-Ordner', 'string', 0, 'Eigentümer der encodierten CD-Ausgaben im Format user:gruppe. Leer = Standardbenutzer des Dienstes.', NULL, '[]', '{}', 1145)`
|
||||||
|
);
|
||||||
|
await db.run(`INSERT OR IGNORE INTO settings_values (key, value) VALUES ('movie_dir_cd_owner', NULL)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDb() {
|
async function getDb() {
|
||||||
|
|||||||
@@ -321,6 +321,20 @@ function formatCommandLine(cmd, args = []) {
|
|||||||
return [quoteShellArg(cmd), ...normalizedArgs.map((arg) => quoteShellArg(arg))].join(' ');
|
return [quoteShellArg(cmd), ...normalizedArgs.map((arg) => quoteShellArg(arg))].join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyFilePreservingRaw(sourcePath, targetPath) {
|
||||||
|
const rawSource = String(sourcePath || '').trim();
|
||||||
|
const rawTarget = String(targetPath || '').trim();
|
||||||
|
if (!rawSource || !rawTarget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const source = path.resolve(rawSource);
|
||||||
|
const target = path.resolve(rawTarget);
|
||||||
|
if (source === target) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fs.copyFileSync(source, target);
|
||||||
|
}
|
||||||
|
|
||||||
async function runProcessTracked({
|
async function runProcessTracked({
|
||||||
cmd,
|
cmd,
|
||||||
args,
|
args,
|
||||||
@@ -492,7 +506,7 @@ async function ripAndEncode(options) {
|
|||||||
|
|
||||||
// ── Phase 2: Encode WAVs to target format ─────────────────────────────────
|
// ── Phase 2: Encode WAVs to target format ─────────────────────────────────
|
||||||
if (format === 'wav') {
|
if (format === 'wav') {
|
||||||
// Just move WAV files to output dir with proper names
|
// Keep RAW WAVs in place and copy them to the final output structure.
|
||||||
for (let i = 0; i < tracksToRip.length; i++) {
|
for (let i = 0; i < tracksToRip.length; i++) {
|
||||||
assertNotCancelled(isCancelled);
|
assertNotCancelled(isCancelled);
|
||||||
const track = tracksToRip[i];
|
const track = tracksToRip[i];
|
||||||
@@ -508,8 +522,8 @@ async function ripAndEncode(options) {
|
|||||||
percent: 50 + ((i / tracksToRip.length) * 50)
|
percent: 50 + ((i / tracksToRip.length) * 50)
|
||||||
});
|
});
|
||||||
ensureDir(path.dirname(outFile));
|
ensureDir(path.dirname(outFile));
|
||||||
log('info', `Promptkette [Move ${i + 1}/${tracksToRip.length}]: mv ${quoteShellArg(wavFile)} ${quoteShellArg(outFile)}`);
|
log('info', `Promptkette [Copy ${i + 1}/${tracksToRip.length}]: cp ${quoteShellArg(wavFile)} ${quoteShellArg(outFile)}`);
|
||||||
fs.renameSync(wavFile, outFile);
|
copyFilePreservingRaw(wavFile, outFile);
|
||||||
onProgress && onProgress({
|
onProgress && onProgress({
|
||||||
phase: 'encode',
|
phase: 'encode',
|
||||||
trackEvent: 'complete',
|
trackEvent: 'complete',
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ class HardwareMonitorService {
|
|||||||
} else {
|
} else {
|
||||||
addPath('raw_dir', 'RAW-Verzeichnis', blurayRawPath || dvdRawPath || sourceMap.raw_dir);
|
addPath('raw_dir', 'RAW-Verzeichnis', blurayRawPath || dvdRawPath || sourceMap.raw_dir);
|
||||||
}
|
}
|
||||||
addPath('raw_dir_cd', 'CD-Verzeichnis', cdRawPath || sourceMap.raw_dir_cd);
|
addPath('raw_dir_cd', 'CD RAW-Ordner', cdRawPath || sourceMap.raw_dir_cd);
|
||||||
|
|
||||||
if (blurayMoviePath && dvdMoviePath && blurayMoviePath !== dvdMoviePath) {
|
if (blurayMoviePath && dvdMoviePath && blurayMoviePath !== dvdMoviePath) {
|
||||||
addPath('movie_dir_bluray', 'Movie-Verzeichnis (Blu-ray)', blurayMoviePath);
|
addPath('movie_dir_bluray', 'Movie-Verzeichnis (Blu-ray)', blurayMoviePath);
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ function parseJsonSafe(raw, fallback = null) {
|
|||||||
|
|
||||||
const PROCESS_LOG_TAIL_MAX_BYTES = 1024 * 1024;
|
const PROCESS_LOG_TAIL_MAX_BYTES = 1024 * 1024;
|
||||||
const processLogStreams = new Map();
|
const processLogStreams = new Map();
|
||||||
const PROFILE_PATH_SUFFIXES = ['bluray', 'dvd', 'other'];
|
const PROFILE_PATH_SUFFIXES = ['bluray', 'dvd', 'cd', 'other'];
|
||||||
const RAW_INCOMPLETE_PREFIX = 'Incomplete_';
|
const RAW_INCOMPLETE_PREFIX = 'Incomplete_';
|
||||||
const RAW_RIP_COMPLETE_PREFIX = 'Rip_Complete_';
|
const RAW_RIP_COMPLETE_PREFIX = 'Rip_Complete_';
|
||||||
|
|
||||||
@@ -161,6 +161,41 @@ function hasBlurayStructure(rawPath) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasCdStructure(rawPath) {
|
||||||
|
const basePath = String(rawPath || '').trim();
|
||||||
|
if (!basePath) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(basePath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const stat = fs.statSync(basePath);
|
||||||
|
if (!stat.isDirectory()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const entries = fs.readdirSync(basePath);
|
||||||
|
const audioExtensions = new Set(['.flac', '.wav', '.mp3', '.opus', '.ogg', '.aiff', '.aif']);
|
||||||
|
return entries.some((entry) => audioExtensions.has(path.extname(entry).toLowerCase()));
|
||||||
|
} catch (_error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function detectOrphanMediaType(rawPath) {
|
||||||
|
if (hasBlurayStructure(rawPath)) {
|
||||||
|
return 'bluray';
|
||||||
|
}
|
||||||
|
if (hasDvdStructure(rawPath)) {
|
||||||
|
return 'dvd';
|
||||||
|
}
|
||||||
|
if (hasCdStructure(rawPath)) {
|
||||||
|
return 'cd';
|
||||||
|
}
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
|
||||||
function hasDvdStructure(rawPath) {
|
function hasDvdStructure(rawPath) {
|
||||||
const basePath = String(rawPath || '').trim();
|
const basePath = String(rawPath || '').trim();
|
||||||
if (!basePath) {
|
if (!basePath) {
|
||||||
@@ -356,12 +391,46 @@ function toProcessLogStreamKey(jobId) {
|
|||||||
return String(Math.trunc(normalizedId));
|
return String(Math.trunc(normalizedId));
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveEffectiveRawPath(storedPath, rawDir) {
|
function resolveEffectiveRawPath(storedPath, rawDir, extraDirs = []) {
|
||||||
const stored = String(storedPath || '').trim();
|
const stored = String(storedPath || '').trim();
|
||||||
if (!stored || !rawDir) return stored;
|
if (!stored) return stored;
|
||||||
const folderName = path.basename(stored);
|
const folderName = path.basename(stored);
|
||||||
if (!folderName) return stored;
|
if (!folderName) return stored;
|
||||||
return path.join(String(rawDir).trim(), folderName);
|
|
||||||
|
const candidates = [];
|
||||||
|
const seen = new Set();
|
||||||
|
const pushCandidate = (candidatePath) => {
|
||||||
|
const normalized = String(candidatePath || '').trim();
|
||||||
|
if (!normalized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const comparable = normalizeComparablePath(normalized);
|
||||||
|
if (!comparable || seen.has(comparable)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
seen.add(comparable);
|
||||||
|
candidates.push(normalized);
|
||||||
|
};
|
||||||
|
|
||||||
|
pushCandidate(stored);
|
||||||
|
if (rawDir) {
|
||||||
|
pushCandidate(path.join(String(rawDir).trim(), folderName));
|
||||||
|
}
|
||||||
|
for (const extraDir of Array.isArray(extraDirs) ? extraDirs : []) {
|
||||||
|
pushCandidate(path.join(String(extraDir || '').trim(), folderName));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
} catch (_error) {
|
||||||
|
// ignore fs errors and continue with fallbacks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawDir ? path.join(String(rawDir).trim(), folderName) : stored;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveEffectiveOutputPath(storedPath, movieDir) {
|
function resolveEffectiveOutputPath(storedPath, movieDir) {
|
||||||
@@ -405,17 +474,16 @@ function resolveEffectiveStoragePathsForJob(settings = null, job = {}, parsed =
|
|||||||
const effectiveSettings = settingsService.resolveEffectiveToolSettings(settings || {}, mediaType);
|
const effectiveSettings = settingsService.resolveEffectiveToolSettings(settings || {}, mediaType);
|
||||||
const rawDir = String(effectiveSettings?.raw_dir || '').trim();
|
const rawDir = String(effectiveSettings?.raw_dir || '').trim();
|
||||||
const configuredMovieDir = String(effectiveSettings?.movie_dir || '').trim();
|
const configuredMovieDir = String(effectiveSettings?.movie_dir || '').trim();
|
||||||
const movieDir = mediaType === 'cd' ? rawDir : configuredMovieDir;
|
const movieDir = configuredMovieDir || rawDir;
|
||||||
const effectiveRawPath = mediaType === 'cd'
|
const rawLookupDirs = getConfiguredMediaPathList(settings || {}, 'raw_dir')
|
||||||
? (job?.raw_path || null)
|
.filter((candidate) => normalizeComparablePath(candidate) !== normalizeComparablePath(rawDir));
|
||||||
: (rawDir && job?.raw_path
|
const effectiveRawPath = job?.raw_path
|
||||||
? resolveEffectiveRawPath(job.raw_path, rawDir)
|
? resolveEffectiveRawPath(job.raw_path, rawDir, rawLookupDirs)
|
||||||
: (job?.raw_path || null));
|
: (job?.raw_path || null);
|
||||||
const effectiveOutputPath = mediaType === 'cd'
|
// For CD, output_path is a directory (album folder) — skip path-relocation heuristic
|
||||||
? (job?.output_path || null)
|
const effectiveOutputPath = (mediaType !== 'cd' && configuredMovieDir && job?.output_path)
|
||||||
: (configuredMovieDir && job?.output_path
|
|
||||||
? resolveEffectiveOutputPath(job.output_path, configuredMovieDir)
|
? resolveEffectiveOutputPath(job.output_path, configuredMovieDir)
|
||||||
: (job?.output_path || null));
|
: (job?.output_path || null);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mediaType,
|
mediaType,
|
||||||
@@ -1364,6 +1432,7 @@ class HistoryService {
|
|||||||
|
|
||||||
const stat = fs.statSync(rawPath);
|
const stat = fs.statSync(rawPath);
|
||||||
const metadata = parseRawFolderMetadata(entry.name);
|
const metadata = parseRawFolderMetadata(entry.name);
|
||||||
|
const detectedMediaType = detectOrphanMediaType(rawPath);
|
||||||
orphanRows.push({
|
orphanRows.push({
|
||||||
rawPath,
|
rawPath,
|
||||||
folderName: entry.name,
|
folderName: entry.name,
|
||||||
@@ -1372,7 +1441,10 @@ class HistoryService {
|
|||||||
imdbId: metadata.imdbId,
|
imdbId: metadata.imdbId,
|
||||||
folderJobId: metadata.folderJobId,
|
folderJobId: metadata.folderJobId,
|
||||||
entryCount: Number(dirInfo.entryCount || 0),
|
entryCount: Number(dirInfo.entryCount || 0),
|
||||||
hasBlurayStructure: fs.existsSync(path.join(rawPath, 'BDMV', 'STREAM')),
|
detectedMediaType,
|
||||||
|
hasBlurayStructure: detectedMediaType === 'bluray',
|
||||||
|
hasDvdStructure: detectedMediaType === 'dvd',
|
||||||
|
hasCdStructure: detectedMediaType === 'cd',
|
||||||
lastModifiedAt: stat.mtime.toISOString()
|
lastModifiedAt: stat.mtime.toISOString()
|
||||||
});
|
});
|
||||||
seenOrphanPaths.add(normalizedPath);
|
seenOrphanPaths.add(normalizedPath);
|
||||||
@@ -1509,6 +1581,7 @@ class HistoryService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const detectedMediaType = detectOrphanMediaType(finalRawPath);
|
||||||
const orphanPosterUrl = omdbById?.poster || null;
|
const orphanPosterUrl = omdbById?.poster || null;
|
||||||
await this.updateJob(created.id, {
|
await this.updateJob(created.id, {
|
||||||
status: 'FINISHED',
|
status: 'FINISHED',
|
||||||
@@ -1533,7 +1606,11 @@ class HistoryService {
|
|||||||
status: 'SUCCESS',
|
status: 'SUCCESS',
|
||||||
source: 'orphan_raw_import',
|
source: 'orphan_raw_import',
|
||||||
importedAt,
|
importedAt,
|
||||||
rawPath: finalRawPath
|
rawPath: finalRawPath,
|
||||||
|
mediaProfile: detectedMediaType,
|
||||||
|
analyzeContext: {
|
||||||
|
mediaProfile: detectedMediaType
|
||||||
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1551,8 +1628,8 @@ class HistoryService {
|
|||||||
created.id,
|
created.id,
|
||||||
'SYSTEM',
|
'SYSTEM',
|
||||||
renameSteps.length > 0
|
renameSteps.length > 0
|
||||||
? `Historieneintrag aus RAW erstellt. Ordner umbenannt: ${renameSteps.map((step) => `${step.from} -> ${step.to}`).join(' | ')}`
|
? `Historieneintrag aus RAW erstellt (Medientyp: ${detectedMediaType}). Ordner umbenannt: ${renameSteps.map((step) => `${step.from} -> ${step.to}`).join(' | ')}`
|
||||||
: `Historieneintrag aus bestehendem RAW-Ordner erstellt: ${finalRawPath}`
|
: `Historieneintrag aus bestehendem RAW-Ordner erstellt: ${finalRawPath} (Medientyp: ${detectedMediaType})`
|
||||||
);
|
);
|
||||||
if (metadata.imdbId) {
|
if (metadata.imdbId) {
|
||||||
await this.appendLog(
|
await this.appendLog(
|
||||||
@@ -1566,7 +1643,8 @@ class HistoryService {
|
|||||||
|
|
||||||
logger.info('job:import-orphan-raw', {
|
logger.info('job:import-orphan-raw', {
|
||||||
jobId: created.id,
|
jobId: created.id,
|
||||||
rawPath: absRawPath
|
rawPath: absRawPath,
|
||||||
|
detectedMediaType
|
||||||
});
|
});
|
||||||
|
|
||||||
const imported = await this.getJobById(created.id);
|
const imported = await this.getJobById(created.id);
|
||||||
|
|||||||
@@ -3694,6 +3694,46 @@ class PipelineService extends EventEmitter {
|
|||||||
return existingDirectories[0];
|
return existingDirectories[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildRawPathLookupConfig(settingsMap = {}, mediaProfile = null) {
|
||||||
|
const sourceMap = settingsMap && typeof settingsMap === 'object' ? settingsMap : {};
|
||||||
|
const normalizedMediaProfile = normalizeMediaProfile(mediaProfile);
|
||||||
|
const effectiveSettings = settingsService.resolveEffectiveToolSettings(sourceMap, normalizedMediaProfile);
|
||||||
|
const preferredDefaultRawDir = normalizedMediaProfile === 'cd'
|
||||||
|
? settingsService.DEFAULT_CD_DIR
|
||||||
|
: settingsService.DEFAULT_RAW_DIR;
|
||||||
|
const uniqueRawDirs = Array.from(
|
||||||
|
new Set(
|
||||||
|
[
|
||||||
|
effectiveSettings?.raw_dir,
|
||||||
|
sourceMap?.raw_dir,
|
||||||
|
sourceMap?.raw_dir_bluray,
|
||||||
|
sourceMap?.raw_dir_dvd,
|
||||||
|
sourceMap?.raw_dir_cd,
|
||||||
|
preferredDefaultRawDir,
|
||||||
|
settingsService.DEFAULT_RAW_DIR,
|
||||||
|
settingsService.DEFAULT_CD_DIR
|
||||||
|
]
|
||||||
|
.map((item) => String(item || '').trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
effectiveSettings,
|
||||||
|
rawBaseDir: uniqueRawDirs[0] || String(preferredDefaultRawDir || '').trim() || null,
|
||||||
|
rawExtraDirs: uniqueRawDirs.slice(1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveCurrentRawPathForSettings(settingsMap = {}, mediaProfile = null, storedRawPath = null) {
|
||||||
|
const stored = String(storedRawPath || '').trim();
|
||||||
|
if (!stored) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { rawBaseDir, rawExtraDirs } = this.buildRawPathLookupConfig(settingsMap, mediaProfile);
|
||||||
|
return this.resolveCurrentRawPath(rawBaseDir, stored, rawExtraDirs);
|
||||||
|
}
|
||||||
|
|
||||||
async migrateRawFolderNamingOnStartup(db) {
|
async migrateRawFolderNamingOnStartup(db) {
|
||||||
const settings = await settingsService.getSettingsMap();
|
const settings = await settingsService.getSettingsMap();
|
||||||
const rawBaseDir = String(settings?.raw_dir || settingsService.DEFAULT_RAW_DIR || '').trim();
|
const rawBaseDir = String(settings?.raw_dir || settingsService.DEFAULT_RAW_DIR || '').trim();
|
||||||
@@ -5385,15 +5425,17 @@ class PipelineService extends EventEmitter {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const existingPlan = this.safeParseJson(job.encode_plan_json);
|
||||||
const refreshSettings = await settingsService.getSettingsMap();
|
const refreshSettings = await settingsService.getSettingsMap();
|
||||||
const refreshRawBaseDir = settingsService.DEFAULT_RAW_DIR;
|
const refreshMediaProfile = this.resolveMediaProfileForJob(job, {
|
||||||
const refreshRawExtraDirs = [
|
encodePlan: existingPlan,
|
||||||
refreshSettings?.raw_dir_bluray,
|
rawPath: job.raw_path
|
||||||
refreshSettings?.raw_dir_dvd
|
});
|
||||||
].map((d) => String(d || '').trim()).filter(Boolean);
|
const resolvedRefreshRawPath = this.resolveCurrentRawPathForSettings(
|
||||||
const resolvedRefreshRawPath = job.raw_path
|
refreshSettings,
|
||||||
? this.resolveCurrentRawPath(refreshRawBaseDir, job.raw_path, refreshRawExtraDirs)
|
refreshMediaProfile,
|
||||||
: null;
|
job.raw_path
|
||||||
|
);
|
||||||
|
|
||||||
if (!resolvedRefreshRawPath) {
|
if (!resolvedRefreshRawPath) {
|
||||||
return {
|
return {
|
||||||
@@ -5409,7 +5451,6 @@ class PipelineService extends EventEmitter {
|
|||||||
await historyService.updateJob(activeJobId, { raw_path: resolvedRefreshRawPath });
|
await historyService.updateJob(activeJobId, { raw_path: resolvedRefreshRawPath });
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingPlan = this.safeParseJson(job.encode_plan_json);
|
|
||||||
const mode = existingPlan?.mode || this.snapshot.context?.mode || 'rip';
|
const mode = existingPlan?.mode || this.snapshot.context?.mode || 'rip';
|
||||||
const sourceJobId = existingPlan?.sourceJobId || this.snapshot.context?.sourceJobId || null;
|
const sourceJobId = existingPlan?.sourceJobId || this.snapshot.context?.sourceJobId || null;
|
||||||
|
|
||||||
@@ -7339,14 +7380,11 @@ class PipelineService extends EventEmitter {
|
|||||||
encodePlan: confirmedPlan
|
encodePlan: confirmedPlan
|
||||||
});
|
});
|
||||||
const confirmSettings = await settingsService.getEffectiveSettingsMap(readyMediaProfile);
|
const confirmSettings = await settingsService.getEffectiveSettingsMap(readyMediaProfile);
|
||||||
const confirmRawBaseDir = String(confirmSettings?.raw_dir || '').trim();
|
const resolvedConfirmRawPath = this.resolveCurrentRawPathForSettings(
|
||||||
const confirmRawExtraDirs = [
|
confirmSettings,
|
||||||
confirmSettings?.raw_dir_bluray,
|
readyMediaProfile,
|
||||||
confirmSettings?.raw_dir_dvd
|
job.raw_path
|
||||||
].map((d) => String(d || '').trim()).filter(Boolean);
|
);
|
||||||
const resolvedConfirmRawPath = job.raw_path
|
|
||||||
? this.resolveCurrentRawPath(confirmRawBaseDir, job.raw_path, confirmRawExtraDirs)
|
|
||||||
: null;
|
|
||||||
const activeConfirmRawPath = resolvedConfirmRawPath || String(job.raw_path || '').trim() || null;
|
const activeConfirmRawPath = resolvedConfirmRawPath || String(job.raw_path || '').trim() || null;
|
||||||
|
|
||||||
let inputPath = isPreRipMode
|
let inputPath = isPreRipMode
|
||||||
@@ -7460,13 +7498,16 @@ class PipelineService extends EventEmitter {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reencodeMediaProfile = this.resolveMediaProfileForJob(sourceJob, {
|
||||||
|
makemkvInfo: mkInfo,
|
||||||
|
rawPath: sourceJob.raw_path
|
||||||
|
});
|
||||||
const reencodeSettings = await settingsService.getSettingsMap();
|
const reencodeSettings = await settingsService.getSettingsMap();
|
||||||
const reencodeRawBaseDir = settingsService.DEFAULT_RAW_DIR;
|
const resolvedReencodeRawPath = this.resolveCurrentRawPathForSettings(
|
||||||
const reencodeRawExtraDirs = [
|
reencodeSettings,
|
||||||
reencodeSettings?.raw_dir_bluray,
|
reencodeMediaProfile,
|
||||||
reencodeSettings?.raw_dir_dvd
|
sourceJob.raw_path
|
||||||
].map((d) => String(d || '').trim()).filter(Boolean);
|
);
|
||||||
const resolvedReencodeRawPath = this.resolveCurrentRawPath(reencodeRawBaseDir, sourceJob.raw_path, reencodeRawExtraDirs);
|
|
||||||
if (!resolvedReencodeRawPath) {
|
if (!resolvedReencodeRawPath) {
|
||||||
const error = new Error(`Re-Encode nicht möglich: RAW-Pfad existiert nicht (${sourceJob.raw_path}).`);
|
const error = new Error(`Re-Encode nicht möglich: RAW-Pfad existiert nicht (${sourceJob.raw_path}).`);
|
||||||
error.statusCode = 400;
|
error.statusCode = 400;
|
||||||
@@ -8339,14 +8380,7 @@ class PipelineService extends EventEmitter {
|
|||||||
rawPath: job.raw_path
|
rawPath: job.raw_path
|
||||||
});
|
});
|
||||||
const settings = await settingsService.getEffectiveSettingsMap(mediaProfile);
|
const settings = await settingsService.getEffectiveSettingsMap(mediaProfile);
|
||||||
const rawBaseDir = String(settings.raw_dir || '').trim();
|
const resolvedRawPath = this.resolveCurrentRawPathForSettings(settings, mediaProfile, job.raw_path);
|
||||||
const rawExtraDirs = [
|
|
||||||
settings.raw_dir_bluray,
|
|
||||||
settings.raw_dir_dvd
|
|
||||||
].map((item) => String(item || '').trim()).filter(Boolean);
|
|
||||||
const resolvedRawPath = job.raw_path
|
|
||||||
? this.resolveCurrentRawPath(rawBaseDir, job.raw_path, rawExtraDirs)
|
|
||||||
: null;
|
|
||||||
const activeRawPath = resolvedRawPath || String(job.raw_path || '').trim() || null;
|
const activeRawPath = resolvedRawPath || String(job.raw_path || '').trim() || null;
|
||||||
if (activeRawPath && normalizeComparablePath(activeRawPath) !== normalizeComparablePath(job.raw_path)) {
|
if (activeRawPath && normalizeComparablePath(activeRawPath) !== normalizeComparablePath(job.raw_path)) {
|
||||||
await historyService.updateJob(jobId, { raw_path: activeRawPath });
|
await historyService.updateJob(jobId, { raw_path: activeRawPath });
|
||||||
@@ -9251,14 +9285,15 @@ class PipelineService extends EventEmitter {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const retrySettings = await settingsService.getEffectiveSettingsMap(mediaProfile);
|
const retrySettings = await settingsService.getEffectiveSettingsMap(mediaProfile);
|
||||||
const retryRawBaseDir = String(retrySettings?.raw_dir || '').trim();
|
const { rawBaseDir: retryRawBaseDir, rawExtraDirs: retryRawExtraDirs } = this.buildRawPathLookupConfig(
|
||||||
const retryRawExtraDirs = [
|
retrySettings,
|
||||||
retrySettings?.raw_dir_bluray,
|
mediaProfile
|
||||||
retrySettings?.raw_dir_dvd
|
);
|
||||||
].map((dirPath) => String(dirPath || '').trim()).filter(Boolean);
|
const resolvedOldRawPath = this.resolveCurrentRawPathForSettings(
|
||||||
const resolvedOldRawPath = sourceJob.raw_path
|
retrySettings,
|
||||||
? this.resolveCurrentRawPath(retryRawBaseDir, sourceJob.raw_path, retryRawExtraDirs)
|
mediaProfile,
|
||||||
: null;
|
sourceJob.raw_path
|
||||||
|
);
|
||||||
|
|
||||||
if (resolvedOldRawPath) {
|
if (resolvedOldRawPath) {
|
||||||
const oldRawFolderName = path.basename(resolvedOldRawPath);
|
const oldRawFolderName = path.basename(resolvedOldRawPath);
|
||||||
@@ -9419,15 +9454,13 @@ class PipelineService extends EventEmitter {
|
|||||||
const mode = String(encodePlan?.mode || 'rip').trim().toLowerCase();
|
const mode = String(encodePlan?.mode || 'rip').trim().toLowerCase();
|
||||||
const isPreRipMode = mode === 'pre_rip' || Boolean(encodePlan?.preRip);
|
const isPreRipMode = mode === 'pre_rip' || Boolean(encodePlan?.preRip);
|
||||||
const reviewConfirmed = Boolean(Number(job.encode_review_confirmed || 0) || encodePlan?.reviewConfirmed);
|
const reviewConfirmed = Boolean(Number(job.encode_review_confirmed || 0) || encodePlan?.reviewConfirmed);
|
||||||
const resumeSettings = await settingsService.getEffectiveSettingsMap(this.resolveMediaProfileForJob(job, { encodePlan }));
|
const readyMediaProfile = this.resolveMediaProfileForJob(job, { encodePlan });
|
||||||
const resumeRawBaseDir = String(resumeSettings?.raw_dir || '').trim();
|
const resumeSettings = await settingsService.getEffectiveSettingsMap(readyMediaProfile);
|
||||||
const resumeRawExtraDirs = [
|
const resolvedResumeRawPath = this.resolveCurrentRawPathForSettings(
|
||||||
resumeSettings?.raw_dir_bluray,
|
resumeSettings,
|
||||||
resumeSettings?.raw_dir_dvd
|
readyMediaProfile,
|
||||||
].map((d) => String(d || '').trim()).filter(Boolean);
|
job.raw_path
|
||||||
const resolvedResumeRawPath = job.raw_path
|
);
|
||||||
? this.resolveCurrentRawPath(resumeRawBaseDir, job.raw_path, resumeRawExtraDirs)
|
|
||||||
: null;
|
|
||||||
const activeResumeRawPath = resolvedResumeRawPath || String(job.raw_path || '').trim() || null;
|
const activeResumeRawPath = resolvedResumeRawPath || String(job.raw_path || '').trim() || null;
|
||||||
|
|
||||||
let inputPath = isPreRipMode
|
let inputPath = isPreRipMode
|
||||||
@@ -9463,10 +9496,6 @@ class PipelineService extends EventEmitter {
|
|||||||
imdbId: job.imdb_id || null,
|
imdbId: job.imdb_id || null,
|
||||||
poster: job.poster_url || null
|
poster: job.poster_url || null
|
||||||
};
|
};
|
||||||
const readyMediaProfile = this.resolveMediaProfileForJob(job, {
|
|
||||||
encodePlan
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.setState('READY_TO_ENCODE', {
|
await this.setState('READY_TO_ENCODE', {
|
||||||
activeJobId: jobId,
|
activeJobId: jobId,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
@@ -9613,14 +9642,11 @@ class PipelineService extends EventEmitter {
|
|||||||
encodePlan: restartPlan
|
encodePlan: restartPlan
|
||||||
});
|
});
|
||||||
const restartSettings = await settingsService.getEffectiveSettingsMap(readyMediaProfile);
|
const restartSettings = await settingsService.getEffectiveSettingsMap(readyMediaProfile);
|
||||||
const restartRawBaseDir = String(restartSettings?.raw_dir || '').trim();
|
const resolvedRestartRawPath = this.resolveCurrentRawPathForSettings(
|
||||||
const restartRawExtraDirs = [
|
restartSettings,
|
||||||
restartSettings?.raw_dir_bluray,
|
readyMediaProfile,
|
||||||
restartSettings?.raw_dir_dvd
|
job.raw_path
|
||||||
].map((d) => String(d || '').trim()).filter(Boolean);
|
);
|
||||||
const resolvedRestartRawPath = job.raw_path
|
|
||||||
? this.resolveCurrentRawPath(restartRawBaseDir, job.raw_path, restartRawExtraDirs)
|
|
||||||
: null;
|
|
||||||
const activeRestartRawPath = resolvedRestartRawPath || String(job.raw_path || '').trim() || null;
|
const activeRestartRawPath = resolvedRestartRawPath || String(job.raw_path || '').trim() || null;
|
||||||
|
|
||||||
let inputPath = isPreRipMode
|
let inputPath = isPreRipMode
|
||||||
@@ -9761,13 +9787,19 @@ class PipelineService extends EventEmitter {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const reviewMakemkvInfo = this.safeParseJson(sourceJob.makemkv_info_json);
|
||||||
|
const reviewEncodePlan = this.safeParseJson(sourceJob.encode_plan_json);
|
||||||
|
const reviewMediaProfile = this.resolveMediaProfileForJob(sourceJob, {
|
||||||
|
makemkvInfo: reviewMakemkvInfo,
|
||||||
|
encodePlan: reviewEncodePlan,
|
||||||
|
rawPath: sourceJob.raw_path
|
||||||
|
});
|
||||||
const reviewSettings = await settingsService.getSettingsMap();
|
const reviewSettings = await settingsService.getSettingsMap();
|
||||||
const reviewRawBaseDir = settingsService.DEFAULT_RAW_DIR;
|
const resolvedReviewRawPath = this.resolveCurrentRawPathForSettings(
|
||||||
const reviewRawExtraDirs = [
|
reviewSettings,
|
||||||
reviewSettings?.raw_dir_bluray,
|
reviewMediaProfile,
|
||||||
reviewSettings?.raw_dir_dvd
|
sourceJob.raw_path
|
||||||
].map((d) => String(d || '').trim()).filter(Boolean);
|
);
|
||||||
const resolvedReviewRawPath = this.resolveCurrentRawPath(reviewRawBaseDir, sourceJob.raw_path, reviewRawExtraDirs);
|
|
||||||
if (!resolvedReviewRawPath) {
|
if (!resolvedReviewRawPath) {
|
||||||
const error = new Error(`Review-Neustart nicht möglich: RAW-Pfad existiert nicht (${sourceJob.raw_path}).`);
|
const error = new Error(`Review-Neustart nicht möglich: RAW-Pfad existiert nicht (${sourceJob.raw_path}).`);
|
||||||
error.statusCode = 400;
|
error.statusCode = 400;
|
||||||
@@ -9855,11 +9887,9 @@ class PipelineService extends EventEmitter {
|
|||||||
encode_plan_json: null,
|
encode_plan_json: null,
|
||||||
encode_input_path: normalizedReviewInputPath || null,
|
encode_input_path: normalizedReviewInputPath || null,
|
||||||
encode_review_confirmed: 0,
|
encode_review_confirmed: 0,
|
||||||
makemkv_info_json: nextMakemkvInfoJson
|
makemkv_info_json: nextMakemkvInfoJson,
|
||||||
|
raw_path: resolvedReviewRawPath
|
||||||
};
|
};
|
||||||
if (resolvedReviewRawPath !== sourceJob.raw_path) {
|
|
||||||
jobUpdatePayload.raw_path = resolvedReviewRawPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
const replacementJob = await historyService.createJob({
|
const replacementJob = await historyService.createJob({
|
||||||
discDevice: sourceJob.disc_device || null,
|
discDevice: sourceJob.disc_device || null,
|
||||||
@@ -10345,10 +10375,10 @@ class PipelineService extends EventEmitter {
|
|||||||
logger.error('command:failed', { jobId, stage, source, error: errorToMeta(error) });
|
logger.error('command:failed', { jobId, stage, source, error: errorToMeta(error) });
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
await historyService.closeProcessLog(jobId);
|
|
||||||
this.activeProcesses.delete(Number(normalizedJobId));
|
this.activeProcesses.delete(Number(normalizedJobId));
|
||||||
this.syncPrimaryActiveProcess();
|
|
||||||
this.cancelRequestedByJob.delete(Number(normalizedJobId));
|
this.cancelRequestedByJob.delete(Number(normalizedJobId));
|
||||||
|
this.syncPrimaryActiveProcess();
|
||||||
|
await historyService.closeProcessLog(jobId);
|
||||||
await this.emitQueueChanged();
|
await this.emitQueueChanged();
|
||||||
void this.pumpQueue();
|
void this.pumpQueue();
|
||||||
}
|
}
|
||||||
@@ -10398,16 +10428,16 @@ class PipelineService extends EventEmitter {
|
|||||||
hasRawPath = false;
|
hasRawPath = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedStage === 'ENCODING' && hasConfirmedPlan) {
|
if (normalizedStage === 'ENCODING' && hasConfirmedPlan && !isCancelled) {
|
||||||
try {
|
try {
|
||||||
await historyService.appendLog(
|
await historyService.appendLog(
|
||||||
jobId,
|
jobId,
|
||||||
'SYSTEM',
|
'SYSTEM',
|
||||||
`${isCancelled ? 'Abbruch' : 'Fehler'} in ${stage}: ${message}. Letzte Encode-Auswahl wird zur direkten Anpassung geladen.`
|
`Fehler in ${stage}: ${message}. Letzte Encode-Auswahl wird zur direkten Anpassung geladen.`
|
||||||
);
|
);
|
||||||
await this.restartEncodeWithLastSettings(jobId, {
|
await this.restartEncodeWithLastSettings(jobId, {
|
||||||
immediate: true,
|
immediate: true,
|
||||||
triggerReason: isCancelled ? 'cancelled_encode' : 'failed_encode'
|
triggerReason: 'failed_encode'
|
||||||
});
|
});
|
||||||
this.cancelRequestedByJob.delete(Number(jobId));
|
this.cancelRequestedByJob.delete(Number(jobId));
|
||||||
return;
|
return;
|
||||||
@@ -10960,20 +10990,22 @@ class PipelineService extends EventEmitter {
|
|||||||
const cdOutputTemplate = String(
|
const cdOutputTemplate = String(
|
||||||
settings.cd_output_template || cdRipService.DEFAULT_CD_OUTPUT_TEMPLATE
|
settings.cd_output_template || cdRipService.DEFAULT_CD_OUTPUT_TEMPLATE
|
||||||
).trim() || cdRipService.DEFAULT_CD_OUTPUT_TEMPLATE;
|
).trim() || cdRipService.DEFAULT_CD_OUTPUT_TEMPLATE;
|
||||||
const cdBaseDir = String(settings.raw_dir || '').trim() || settingsService.DEFAULT_CD_DIR;
|
const cdRawBaseDir = String(settings.raw_dir || '').trim() || settingsService.DEFAULT_CD_DIR;
|
||||||
const cdOutputOwner = String(settings.raw_dir_owner || '').trim();
|
const cdOutputBaseDir = String(settings.movie_dir || '').trim() || cdRawBaseDir;
|
||||||
|
const cdRawOwner = String(settings.raw_dir_owner || '').trim();
|
||||||
|
const cdOutputOwner = String(settings.movie_dir_owner || settings.raw_dir_owner || '').trim();
|
||||||
const cdMetadataBase = buildRawMetadataBase({
|
const cdMetadataBase = buildRawMetadataBase({
|
||||||
title: effectiveSelectedMeta?.album || effectiveSelectedMeta?.title || null,
|
title: effectiveSelectedMeta?.album || effectiveSelectedMeta?.title || null,
|
||||||
year: effectiveSelectedMeta?.year || null
|
year: effectiveSelectedMeta?.year || null
|
||||||
}, activeJobId);
|
}, activeJobId);
|
||||||
const rawDirName = buildRawDirName(cdMetadataBase, activeJobId, { state: RAW_FOLDER_STATES.INCOMPLETE });
|
const rawDirName = buildRawDirName(cdMetadataBase, activeJobId, { state: RAW_FOLDER_STATES.INCOMPLETE });
|
||||||
const rawJobDir = path.join(cdBaseDir, rawDirName);
|
const rawJobDir = path.join(cdRawBaseDir, rawDirName);
|
||||||
const rawWavDir = rawJobDir;
|
const rawWavDir = rawJobDir;
|
||||||
const outputDir = cdRipService.buildOutputDir(effectiveSelectedMeta, cdBaseDir, cdOutputTemplate);
|
const outputDir = cdRipService.buildOutputDir(effectiveSelectedMeta, cdOutputBaseDir, cdOutputTemplate);
|
||||||
ensureDir(cdBaseDir);
|
ensureDir(cdRawBaseDir);
|
||||||
ensureDir(rawJobDir);
|
ensureDir(rawJobDir);
|
||||||
ensureDir(outputDir);
|
ensureDir(outputDir);
|
||||||
chownRecursive(rawJobDir, cdOutputOwner);
|
chownRecursive(rawJobDir, cdRawOwner);
|
||||||
chownRecursive(outputDir, cdOutputOwner);
|
chownRecursive(outputDir, cdOutputOwner);
|
||||||
const previewTrackPos = effectiveSelectedTrackPositions[0] || mergedTracks[0]?.position || 1;
|
const previewTrackPos = effectiveSelectedTrackPositions[0] || mergedTracks[0]?.position || 1;
|
||||||
const previewWavPath = path.join(rawWavDir, `track${String(previewTrackPos).padStart(2, '0')}.cdda.wav`);
|
const previewWavPath = path.join(rawWavDir, `track${String(previewTrackPos).padStart(2, '0')}.cdda.wav`);
|
||||||
@@ -11076,12 +11108,13 @@ class PipelineService extends EventEmitter {
|
|||||||
devicePath,
|
devicePath,
|
||||||
cdparanoiaCmd,
|
cdparanoiaCmd,
|
||||||
rawWavDir,
|
rawWavDir,
|
||||||
rawBaseDir: cdBaseDir,
|
rawBaseDir: cdRawBaseDir,
|
||||||
cdMetadataBase,
|
cdMetadataBase,
|
||||||
outputDir,
|
outputDir,
|
||||||
format,
|
format,
|
||||||
formatOptions,
|
formatOptions,
|
||||||
outputTemplate: cdOutputTemplate,
|
outputTemplate: cdOutputTemplate,
|
||||||
|
rawOwner: cdRawOwner,
|
||||||
outputOwner: cdOutputOwner,
|
outputOwner: cdOutputOwner,
|
||||||
selectedTrackPositions: effectiveSelectedTrackPositions,
|
selectedTrackPositions: effectiveSelectedTrackPositions,
|
||||||
tocTracks: mergedTracks,
|
tocTracks: mergedTracks,
|
||||||
@@ -11110,6 +11143,7 @@ class PipelineService extends EventEmitter {
|
|||||||
format,
|
format,
|
||||||
formatOptions,
|
formatOptions,
|
||||||
outputTemplate,
|
outputTemplate,
|
||||||
|
rawOwner,
|
||||||
outputOwner,
|
outputOwner,
|
||||||
selectedTrackPositions,
|
selectedTrackPositions,
|
||||||
tocTracks,
|
tocTracks,
|
||||||
@@ -11365,7 +11399,7 @@ class PipelineService extends EventEmitter {
|
|||||||
await historyService.updateJob(jobId, { poster_url: cdPromotedUrl }).catch(() => {});
|
await historyService.updateJob(jobId, { poster_url: cdPromotedUrl }).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
chownRecursive(activeRawDir, outputOwner);
|
chownRecursive(activeRawDir, rawOwner || outputOwner);
|
||||||
chownRecursive(outputDir, outputOwner);
|
chownRecursive(outputDir, outputOwner);
|
||||||
await historyService.appendLog(jobId, 'SYSTEM', `CD-Rip abgeschlossen. Ausgabe: ${outputDir}`);
|
await historyService.appendLog(jobId, 'SYSTEM', `CD-Rip abgeschlossen. Ausgabe: ${outputDir}`);
|
||||||
const finishedStatusText = postEncodeScriptsSummary.failed > 0
|
const finishedStatusText = postEncodeScriptsSummary.failed > 0
|
||||||
|
|||||||
@@ -52,11 +52,13 @@ const PROFILED_SETTINGS = {
|
|||||||
},
|
},
|
||||||
movie_dir: {
|
movie_dir: {
|
||||||
bluray: 'movie_dir_bluray',
|
bluray: 'movie_dir_bluray',
|
||||||
dvd: 'movie_dir_dvd'
|
dvd: 'movie_dir_dvd',
|
||||||
|
cd: 'movie_dir_cd'
|
||||||
},
|
},
|
||||||
movie_dir_owner: {
|
movie_dir_owner: {
|
||||||
bluray: 'movie_dir_bluray_owner',
|
bluray: 'movie_dir_bluray_owner',
|
||||||
dvd: 'movie_dir_dvd_owner'
|
dvd: 'movie_dir_dvd_owner',
|
||||||
|
cd: 'movie_dir_cd_owner'
|
||||||
},
|
},
|
||||||
mediainfo_extra_args: {
|
mediainfo_extra_args: {
|
||||||
bluray: 'mediainfo_extra_args_bluray',
|
bluray: 'mediainfo_extra_args_bluray',
|
||||||
@@ -690,7 +692,7 @@ class SettingsService {
|
|||||||
if (legacyKey === 'raw_dir') {
|
if (legacyKey === 'raw_dir') {
|
||||||
resolvedValue = normalizedRequestedProfile === 'cd' ? DEFAULT_CD_DIR : DEFAULT_RAW_DIR;
|
resolvedValue = normalizedRequestedProfile === 'cd' ? DEFAULT_CD_DIR : DEFAULT_RAW_DIR;
|
||||||
} else if (legacyKey === 'movie_dir') {
|
} else if (legacyKey === 'movie_dir') {
|
||||||
resolvedValue = DEFAULT_MOVIE_DIR;
|
resolvedValue = normalizedRequestedProfile === 'cd' ? DEFAULT_CD_DIR : DEFAULT_MOVIE_DIR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
effective[legacyKey] = resolvedValue;
|
effective[legacyKey] = resolvedValue;
|
||||||
@@ -725,7 +727,7 @@ class SettingsService {
|
|||||||
return {
|
return {
|
||||||
bluray: { raw: bluray.raw_dir, movies: bluray.movie_dir },
|
bluray: { raw: bluray.raw_dir, movies: bluray.movie_dir },
|
||||||
dvd: { raw: dvd.raw_dir, movies: dvd.movie_dir },
|
dvd: { raw: dvd.raw_dir, movies: dvd.movie_dir },
|
||||||
cd: { raw: cd.raw_dir },
|
cd: { raw: cd.raw_dir, movies: cd.movie_dir },
|
||||||
defaults: {
|
defaults: {
|
||||||
raw: DEFAULT_RAW_DIR,
|
raw: DEFAULT_RAW_DIR,
|
||||||
movies: DEFAULT_MOVIE_DIR,
|
movies: DEFAULT_MOVIE_DIR,
|
||||||
|
|||||||
@@ -373,13 +373,21 @@ VALUES ('cd_output_template', '{artist} - {album} ({year})/{trackNr} {artist} -
|
|||||||
|
|
||||||
-- Pfade – CD
|
-- Pfade – CD
|
||||||
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||||
VALUES ('raw_dir_cd', 'Pfade', 'CD RAW-Ordner', 'path', 0, 'Basisordner für CD-Rips. Enthält die WAV-Rohdaten (RAW) sowie den encodierten Audio-Output. Leer = Standardpfad (data/output/cd).', NULL, '[]', '{}', 104);
|
VALUES ('raw_dir_cd', 'Pfade', 'CD RAW-Ordner', 'path', 0, 'Basisordner für rohe CD-WAV-Dateien (cdparanoia-Output). Leer = Standardpfad (data/output/cd).', NULL, '[]', '{}', 104);
|
||||||
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('raw_dir_cd', NULL);
|
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('raw_dir_cd', NULL);
|
||||||
|
|
||||||
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||||
VALUES ('raw_dir_cd_owner', 'Pfade', 'Eigentümer CD-Ordner', 'string', 0, 'Eigentümer der Dateien im Format user:gruppe. Nur aktiv wenn ein Pfad gesetzt ist. Leer = Standardbenutzer des Dienstes.', NULL, '[]', '{}', 1045);
|
VALUES ('raw_dir_cd_owner', 'Pfade', 'Eigentümer CD RAW-Ordner', 'string', 0, 'Eigentümer der Dateien im Format user:gruppe. Nur aktiv wenn ein Pfad gesetzt ist. Leer = Standardbenutzer des Dienstes.', NULL, '[]', '{}', 1045);
|
||||||
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('raw_dir_cd_owner', NULL);
|
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('raw_dir_cd_owner', NULL);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||||
|
VALUES ('movie_dir_cd', 'Pfade', 'CD Output-Ordner', 'path', 0, 'Zielordner für encodierte CD-Ausgaben (FLAC, MP3 usw.). Leer = gleicher Ordner wie CD RAW-Ordner.', NULL, '[]', '{}', 114);
|
||||||
|
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('movie_dir_cd', NULL);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||||
|
VALUES ('movie_dir_cd_owner', 'Pfade', 'Eigentümer CD Output-Ordner', 'string', 0, 'Eigentümer der encodierten CD-Ausgaben im Format user:gruppe. Leer = Standardbenutzer des Dienstes.', NULL, '[]', '{}', 1145);
|
||||||
|
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('movie_dir_cd_owner', NULL);
|
||||||
|
|
||||||
-- Metadaten
|
-- Metadaten
|
||||||
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||||
VALUES ('omdb_api_key', 'Metadaten', 'OMDb API Key', 'string', 0, 'API Key für Metadatensuche.', NULL, '[]', '{}', 400);
|
VALUES ('omdb_api_key', 'Metadaten', 'OMDb API Key', 'string', 0, 'API Key für Metadatensuche.', NULL, '[]', '{}', 400);
|
||||||
|
|||||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "1.0.0",
|
"version": "0.9.0-1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "1.0.0",
|
"version": "0.9.0-1",
|
||||||
"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": "1.0.0",
|
"version": "0.9.0-1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import HistoryPage from './pages/HistoryPage';
|
|||||||
import DatabasePage from './pages/DatabasePage';
|
import DatabasePage from './pages/DatabasePage';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const appVersion = __APP_VERSION__;
|
||||||
const [pipeline, setPipeline] = useState({ state: 'IDLE', progress: 0, context: {} });
|
const [pipeline, setPipeline] = useState({ state: 'IDLE', progress: 0, context: {} });
|
||||||
const [hardwareMonitoring, setHardwareMonitoring] = useState(null);
|
const [hardwareMonitoring, setHardwareMonitoring] = useState(null);
|
||||||
const [lastDiscEvent, setLastDiscEvent] = useState(null);
|
const [lastDiscEvent, setLastDiscEvent] = useState(null);
|
||||||
@@ -115,7 +116,12 @@ function App() {
|
|||||||
<img src="/logo.png" alt="Ripster Logo" className="brand-logo" />
|
<img src="/logo.png" alt="Ripster Logo" className="brand-logo" />
|
||||||
<div className="brand-copy">
|
<div className="brand-copy">
|
||||||
<h1>Ripster</h1>
|
<h1>Ripster</h1>
|
||||||
|
<div className="brand-meta">
|
||||||
<p>Disc Ripping Control Center</p>
|
<p>Disc Ripping Control Center</p>
|
||||||
|
<span className="app-version" aria-label={`Version ${appVersion}`}>
|
||||||
|
v{appVersion}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="nav-buttons">
|
<div className="nav-buttons">
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export default function CdMetadataDialog({
|
|||||||
}
|
}
|
||||||
setSelected(null);
|
setSelected(null);
|
||||||
setQuery('');
|
setQuery('');
|
||||||
setManualTitle(context?.detectedTitle || '');
|
setManualTitle('');
|
||||||
setManualArtist('');
|
setManualArtist('');
|
||||||
setManualYear(null);
|
setManualYear(null);
|
||||||
setResults([]);
|
setResults([]);
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ function buildToolSections(settings) {
|
|||||||
// Path keys per medium — _owner keys are rendered inline
|
// Path keys per medium — _owner keys are rendered inline
|
||||||
const BLURAY_PATH_KEYS = ['raw_dir_bluray', 'movie_dir_bluray', 'output_template_bluray'];
|
const BLURAY_PATH_KEYS = ['raw_dir_bluray', 'movie_dir_bluray', 'output_template_bluray'];
|
||||||
const DVD_PATH_KEYS = ['raw_dir_dvd', 'movie_dir_dvd', 'output_template_dvd'];
|
const DVD_PATH_KEYS = ['raw_dir_dvd', 'movie_dir_dvd', 'output_template_dvd'];
|
||||||
const CD_PATH_KEYS = ['raw_dir_cd', 'cd_output_template'];
|
const CD_PATH_KEYS = ['raw_dir_cd', 'movie_dir_cd', 'cd_output_template'];
|
||||||
const LOG_PATH_KEYS = ['log_dir'];
|
const LOG_PATH_KEYS = ['log_dir'];
|
||||||
|
|
||||||
function buildSectionsForCategory(categoryName, settings) {
|
function buildSectionsForCategory(categoryName, settings) {
|
||||||
@@ -380,7 +380,8 @@ function PathCategoryTab({ settings, values, errors, dirtyKeys, onChange, effect
|
|||||||
const blurayMovies = ep.bluray?.movies || defaultMovies;
|
const blurayMovies = ep.bluray?.movies || defaultMovies;
|
||||||
const dvdRaw = ep.dvd?.raw || defaultRaw;
|
const dvdRaw = ep.dvd?.raw || defaultRaw;
|
||||||
const dvdMovies = ep.dvd?.movies || defaultMovies;
|
const dvdMovies = ep.dvd?.movies || defaultMovies;
|
||||||
const cdOutput = ep.cd?.raw || defaultCd;
|
const cdRaw = ep.cd?.raw || defaultCd;
|
||||||
|
const cdMovies = ep.cd?.movies || cdRaw;
|
||||||
|
|
||||||
const isDefault = (path, def) => path === def;
|
const isDefault = (path, def) => path === def;
|
||||||
|
|
||||||
@@ -425,9 +426,13 @@ function PathCategoryTab({ settings, values, errors, dirtyKeys, onChange, effect
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>CD / Audio</strong></td>
|
<td><strong>CD / Audio</strong></td>
|
||||||
<td colSpan={2}>
|
<td>
|
||||||
<code>{cdOutput}</code>
|
<code>{cdRaw}</code>
|
||||||
{isDefault(cdOutput, defaultCd) && <span className="path-default-badge">Standard</span>}
|
{isDefault(cdRaw, defaultCd) && <span className="path-default-badge">Standard</span>}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<code>{cdMovies}</code>
|
||||||
|
{isDefault(cdMovies, cdRaw) && <span className="path-default-badge">Standard</span>}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ function resolveMediaType(row) {
|
|||||||
if (['dvd', 'disc', 'dvdvideo', 'dvd-video', 'dvdrom', 'dvd-rom', 'video_ts', 'iso9660'].includes(raw)) {
|
if (['dvd', 'disc', 'dvdvideo', 'dvd-video', 'dvdrom', 'dvd-rom', 'video_ts', 'iso9660'].includes(raw)) {
|
||||||
return 'dvd';
|
return 'dvd';
|
||||||
}
|
}
|
||||||
|
if (['cd', 'audio_cd'].includes(raw)) {
|
||||||
|
return 'cd';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 'other';
|
return 'other';
|
||||||
}
|
}
|
||||||
@@ -467,7 +470,7 @@ export default function DatabasePage() {
|
|||||||
|
|
||||||
const handleImportOrphanRaw = async (row) => {
|
const handleImportOrphanRaw = async (row) => {
|
||||||
const target = row?.rawPath || row?.folderName || '-';
|
const target = row?.rawPath || row?.folderName || '-';
|
||||||
const confirmed = window.confirm(`Für RAW-Ordner "${target}" einen neuen Historienjob anlegen?`);
|
const confirmed = window.confirm(`Für RAW-Ordner "${target}" einen neuen Historienjob anlegen und direkt scannen?`);
|
||||||
if (!confirmed) {
|
if (!confirmed) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -475,12 +478,32 @@ export default function DatabasePage() {
|
|||||||
setOrphanImportBusyPath(row.rawPath);
|
setOrphanImportBusyPath(row.rawPath);
|
||||||
try {
|
try {
|
||||||
const response = await api.importOrphanRawFolder(row.rawPath);
|
const response = await api.importOrphanRawFolder(row.rawPath);
|
||||||
|
const newJobId = response?.job?.id;
|
||||||
|
if (newJobId) {
|
||||||
|
try {
|
||||||
|
await api.reencodeJob(newJobId);
|
||||||
|
toastRef.current?.show({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Job angelegt & Scan gestartet',
|
||||||
|
detail: `Historieneintrag #${newJobId} erstellt, Mediainfo-Scan läuft.`,
|
||||||
|
life: 4000
|
||||||
|
});
|
||||||
|
} catch (scanError) {
|
||||||
|
toastRef.current?.show({
|
||||||
|
severity: 'info',
|
||||||
|
summary: 'Job angelegt',
|
||||||
|
detail: `Historieneintrag #${newJobId} erstellt. Scan konnte nicht automatisch gestartet werden: ${scanError.message}`,
|
||||||
|
life: 6000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
toastRef.current?.show({
|
toastRef.current?.show({
|
||||||
severity: 'success',
|
severity: 'success',
|
||||||
summary: 'Job angelegt',
|
summary: 'Job angelegt',
|
||||||
detail: `Historieneintrag #${response?.job?.id || '-'} wurde erstellt.`,
|
detail: `Historieneintrag wurde erstellt.`,
|
||||||
life: 3500
|
life: 3500
|
||||||
});
|
});
|
||||||
|
}
|
||||||
await load();
|
await load();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toastRef.current?.show({
|
toastRef.current?.show({
|
||||||
|
|||||||
@@ -142,6 +142,28 @@ body {
|
|||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.brand-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-version {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 1.35rem;
|
||||||
|
padding: 0.1rem 0.45rem;
|
||||||
|
border: 1px solid rgba(58, 29, 18, 0.18);
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 250, 240, 0.45);
|
||||||
|
color: rgba(58, 29, 18, 0.72);
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-buttons {
|
.nav-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
@@ -2530,6 +2552,10 @@ body {
|
|||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-version {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
}
|
||||||
|
|
||||||
.metadata-grid,
|
.metadata-grid,
|
||||||
.device-meta,
|
.device-meta,
|
||||||
.hardware-monitor-grid,
|
.hardware-monitor-grid,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { readFileSync } from 'node:fs';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
const appPackage = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
||||||
const publicOrigin = (process.env.VITE_PUBLIC_ORIGIN || '').trim();
|
const publicOrigin = (process.env.VITE_PUBLIC_ORIGIN || '').trim();
|
||||||
const parsedAllowedHosts = (process.env.VITE_ALLOWED_HOSTS || '')
|
const parsedAllowedHosts = (process.env.VITE_ALLOWED_HOSTS || '')
|
||||||
.split(',')
|
.split(',')
|
||||||
@@ -24,6 +26,9 @@ if (publicOrigin) {
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
define: {
|
||||||
|
__APP_VERSION__: JSON.stringify(appPackage.version)
|
||||||
|
},
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
port: 5173,
|
port: 5173,
|
||||||
|
|||||||
152
gitea_setup.sh
Executable file
152
gitea_setup.sh
Executable file
@@ -0,0 +1,152 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
GITEA_BASE="https://git.d-razz.de"
|
||||||
|
REPO_OWNER="michael"
|
||||||
|
REPO_NAME="ripster"
|
||||||
|
BRANCHES_API_URL="${GITEA_BASE}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/branches?limit=50"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<'EOF'
|
||||||
|
Verwendung:
|
||||||
|
bash setup.sh [Optionen]
|
||||||
|
|
||||||
|
Optionen (wie install.sh):
|
||||||
|
--branch <branch> Branch direkt setzen (ohne Auswahlmenue)
|
||||||
|
--dir <pfad> Installationsverzeichnis
|
||||||
|
--user <benutzer> Systembenutzer fuer den Dienst
|
||||||
|
--port <port> Backend-Port
|
||||||
|
--host <hostname> Hostname/IP fuer die Weboberflaeche
|
||||||
|
--no-makemkv MakeMKV-Installation ueberspringen
|
||||||
|
--no-handbrake HandBrake-Installation ueberspringen
|
||||||
|
--no-nginx Nginx-Einrichtung ueberspringen
|
||||||
|
--reinstall Vorhandene Installation aktualisieren
|
||||||
|
-h, --help Hilfe anzeigen
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
SELECTED_BRANCH=""
|
||||||
|
FORWARDED_ARGS=()
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--branch)
|
||||||
|
[[ $# -ge 2 ]] || { echo "Fehlender Wert fuer --branch" >&2; exit 1; }
|
||||||
|
SELECTED_BRANCH="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dir|--user|--port|--host)
|
||||||
|
[[ $# -ge 2 ]] || { echo "Fehlender Wert fuer $1" >&2; exit 1; }
|
||||||
|
FORWARDED_ARGS+=("$1" "$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--no-makemkv|--no-handbrake|--no-nginx|--reinstall)
|
||||||
|
FORWARDED_ARGS+=("$1")
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unbekannter Parameter: $1" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
fetch_url() {
|
||||||
|
local url="$1"
|
||||||
|
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
curl -fsSL "$url"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v wget >/dev/null 2>&1; then
|
||||||
|
wget -qO- "$url"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Weder curl noch wget gefunden. Bitte eines davon installieren." >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
download_file() {
|
||||||
|
local url="$1"
|
||||||
|
local target="$2"
|
||||||
|
fetch_url "$url" > "$target"
|
||||||
|
}
|
||||||
|
|
||||||
|
select_branch() {
|
||||||
|
local branches_json
|
||||||
|
local -a branches
|
||||||
|
local selection
|
||||||
|
|
||||||
|
branches_json="$(fetch_url "$BRANCHES_API_URL")"
|
||||||
|
mapfile -t branches < <(
|
||||||
|
printf '%s\n' "$branches_json" \
|
||||||
|
| grep -oE '"name"[[:space:]]*:[[:space:]]*"[^"]+"' \
|
||||||
|
| sed -E 's/"name"[[:space:]]*:[[:space:]]*"([^"]+)"/\1/'
|
||||||
|
)
|
||||||
|
|
||||||
|
if [[ ${#branches[@]} -eq 0 ]]; then
|
||||||
|
echo "Keine Branches gefunden oder API-Antwort ungültig." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$SELECTED_BRANCH" ]]; then
|
||||||
|
local found=false
|
||||||
|
for branch in "${branches[@]}"; do
|
||||||
|
if [[ "$branch" == "$SELECTED_BRANCH" ]]; then
|
||||||
|
found=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [[ "$found" == false ]]; then
|
||||||
|
echo "Branch '$SELECTED_BRANCH' nicht gefunden." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -t 0 ]]; then
|
||||||
|
echo "Kein interaktives Terminal für die Branch-Auswahl verfügbar." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Verfügbare Branches:"
|
||||||
|
for i in "${!branches[@]}"; do
|
||||||
|
printf " %2d) %s\n" "$((i + 1))" "${branches[$i]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
read -r -p "Bitte Branch auswählen [1-${#branches[@]}]: " selection
|
||||||
|
if [[ "$selection" =~ ^[0-9]+$ ]] && (( selection >= 1 && selection <= ${#branches[@]} )); then
|
||||||
|
SELECTED_BRANCH="${branches[$((selection - 1))]}"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
echo "Ungültige Auswahl."
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
select_branch
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd -P)"
|
||||||
|
INSTALL_SCRIPT="${SCRIPT_DIR}/gitea_install.sh"
|
||||||
|
|
||||||
|
if [[ ! -f "$INSTALL_SCRIPT" ]]; then
|
||||||
|
echo "gitea_install.sh nicht gefunden in $SCRIPT_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $EUID -eq 0 ]]; then
|
||||||
|
bash "$INSTALL_SCRIPT" --branch "$SELECTED_BRANCH" "${FORWARDED_ARGS[@]}"
|
||||||
|
else
|
||||||
|
if ! command -v sudo >/dev/null 2>&1; then
|
||||||
|
echo "sudo nicht gefunden. Bitte als root ausführen." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sudo bash "$INSTALL_SCRIPT" --branch "$SELECTED_BRANCH" "${FORWARDED_ARGS[@]}"
|
||||||
|
fi
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "1.0.0",
|
"version": "0.9.0-1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "1.0.0",
|
"version": "0.9.0-1",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^9.1.2"
|
"concurrently": "^9.1.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "0.9.0-1",
|
||||||
"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",
|
||||||
"dev:frontend": "npm run dev --prefix frontend",
|
"dev:frontend": "npm run dev --prefix frontend",
|
||||||
"start": "npm run start --prefix backend",
|
"start": "npm run start --prefix backend",
|
||||||
"build:frontend": "npm run build --prefix frontend"
|
"build:frontend": "npm run build --prefix frontend",
|
||||||
|
"release:interactive": "bash ./scripts/release.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^9.1.2"
|
"concurrently": "^9.1.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user