mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-04 21:30:11 +00:00
Add Transmission/NZBGet and per-client paths and much more
Extend multi-download-client support to include Transmission and NZBGet and introduce per-client custom download paths. Adds protocol mapping and new client types, Transmission/NZBGet integration services, API CRUD and validation changes, UI components/modal updates and live path previews, and manager routing by protocol. Includes DB migrations (download_path on download_history, interactive_search_access on users), schema updates, and related processor/service fixes and tests to ensure backward compatibility and proper path resolution.
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
} from './chapter-merger';
|
||||
import { prisma } from '../db';
|
||||
import { substituteTemplate, type TemplateVariables } from './path-template.util';
|
||||
import { AUDIO_EXTENSIONS } from '../constants/audio-formats';
|
||||
|
||||
export interface AudiobookMetadata {
|
||||
title: string;
|
||||
@@ -362,6 +363,34 @@ export class FileOrganizer {
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
|
||||
await logger?.error(`Failed to copy ${filename}: ${errorMsg}`);
|
||||
|
||||
// If the tagged temp file failed to copy, clean it up and try the original untagged file
|
||||
if (taggedFilePath) {
|
||||
// Clean up the tagged temp file that failed to copy
|
||||
try {
|
||||
await fs.unlink(taggedFilePath);
|
||||
await logger?.info(`Cleaned up temp file after copy failure: ${path.basename(taggedFilePath)}`);
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
|
||||
// Fallback: attempt to copy the original untagged file instead
|
||||
await logger?.info(`Attempting fallback copy of original (untagged) file: ${filename}`);
|
||||
try {
|
||||
await fs.access(originalSourcePath, fs.constants.R_OK);
|
||||
await fs.copyFile(originalSourcePath, targetFilePath);
|
||||
await fs.chmod(targetFilePath, 0o644);
|
||||
result.audioFiles.push(targetFilePath);
|
||||
result.filesMovedCount++;
|
||||
await logger?.info(`Fallback copy succeeded (without metadata tags): ${filename}`);
|
||||
result.errors.push(`Tagged copy failed for ${filename}, copied original without metadata tags`);
|
||||
continue;
|
||||
} catch (fallbackError) {
|
||||
const fallbackMsg = fallbackError instanceof Error ? fallbackError.message : 'Unknown error';
|
||||
await logger?.error(`Fallback copy of original file also failed: ${fallbackMsg}`);
|
||||
}
|
||||
}
|
||||
|
||||
result.errors.push(`Failed to copy ${audioFile}: ${errorMsg}`);
|
||||
// Continue with other files instead of throwing
|
||||
}
|
||||
@@ -411,7 +440,15 @@ export class FileOrganizer {
|
||||
// This replaces the old inline ebook sidecar download that happened here.
|
||||
|
||||
result.targetPath = targetPath;
|
||||
result.success = true;
|
||||
|
||||
// Only mark as success if at least one audio file was placed in the target directory
|
||||
// (either freshly copied or already existed from a previous attempt)
|
||||
if (result.audioFiles.length > 0) {
|
||||
result.success = true;
|
||||
} else {
|
||||
result.errors.push('No audio files were successfully copied to the target directory');
|
||||
await logger?.error(`Organization failed: no audio files copied despite ${audioFiles.length} file(s) found`);
|
||||
}
|
||||
|
||||
// DO NOT clean up download directory - files needed for seeding
|
||||
// Cleanup will be handled by the seeding cleanup job after seeding requirements are met
|
||||
@@ -431,7 +468,7 @@ export class FileOrganizer {
|
||||
private async findAudiobookFiles(
|
||||
downloadPath: string
|
||||
): Promise<{ audioFiles: string[]; coverFile?: string; isFile: boolean }> {
|
||||
const audioExtensions = ['.m4b', '.m4a', '.mp3', '.mp4', '.aa', '.aax'];
|
||||
const audioExtensions: readonly string[] = AUDIO_EXTENSIONS;
|
||||
const coverPatterns = [
|
||||
/cover\.(jpg|jpeg|png)$/i,
|
||||
/folder\.(jpg|jpeg|png)$/i,
|
||||
|
||||
Reference in New Issue
Block a user