Fix file copy location to respect configured media directory

Previously, files were always being copied to /media/audiobooks regardless
of the configured media directory in settings. This was caused by:

1. FileOrganizer singleton reading from MEDIA_DIR env var (never set)
   instead of database config 'media_dir'
2. Hardcoded /media/audiobooks fallback being used when env var not found
3. Three locations passing hardcoded paths to addOrganizeJob (unused)

Changes:
- Modified getFileOrganizer() to read media_dir from database config
- Made targetPath parameter optional in addOrganizeJob (not used by processor)
- Removed hardcoded /media/audiobooks paths from all addOrganizeJob calls
- Updated organize-files processor to await getFileOrganizer()
- Updated documentation to reflect configuration behavior

Files now correctly copy to the directory configured in setup wizard or
settings page, with /media/audiobooks only as fallback if not configured.

Fixes: User-reported issue where configured media directory was ignored
This commit is contained in:
Claude
2025-12-22 00:05:19 +00:00
committed by kikootwo
parent a59bbedd00
commit ef98dcf438
7 changed files with 40 additions and 28 deletions
+1 -2
View File
@@ -194,8 +194,7 @@ export async function PATCH(
await jobQueue.addOrganizeJob(
id,
requestWithData.audiobook.id,
downloadPath,
`/media/audiobooks/${requestWithData.audiobook.author}/${requestWithData.audiobook.title}`
downloadPath
);
updated = await prisma.request.update({
@@ -117,13 +117,12 @@ export async function processMonitorDownload(payload: MonitorDownloadPayload): P
throw new Error('Request or audiobook not found');
}
// Trigger organize files job
// Trigger organize files job (target path determined by database config)
const jobQueue = getJobQueueService();
await jobQueue.addOrganizeJob(
requestId,
request.audiobook.id,
`${downloadPath}/${torrent.name}`,
`/media/audiobooks/${request.audiobook.author}/${request.audiobook.title}`
`${downloadPath}/${torrent.name}`
);
await logger?.info(`Triggered organize_files job for request ${requestId}`);
@@ -43,8 +43,8 @@ export async function processOrganizeFiles(payload: OrganizeFilesPayload): Promi
await logger?.info(`Organizing: ${audiobook.title} by ${audiobook.author}`);
// Get file organizer
const organizer = getFileOrganizer();
// Get file organizer (reads media_dir from database config)
const organizer = await getFileOrganizer();
// Organize files (pass logger to file organizer)
const result = await organizer.organize(
@@ -72,8 +72,7 @@ export async function processRetryFailedImports(payload: RetryFailedImportsPaylo
await jobQueue.addOrganizeJob(
request.id,
request.audiobook.id,
downloadPath,
`/media/audiobooks/${request.audiobook.author}/${request.audiobook.title}`
downloadPath
);
triggered++;
await logger?.info(`Triggered organize job for request ${request.id}: ${request.audiobook.title}`);
+4 -3
View File
@@ -58,7 +58,7 @@ export interface OrganizeFilesPayload extends JobPayload {
requestId: string;
audiobookId: string;
downloadPath: string;
targetPath: string;
targetPath?: string; // Optional - not used by processor (reads from database config)
}
export interface ScanPlexPayload extends JobPayload {
@@ -499,12 +499,13 @@ export class JobQueueService {
/**
* Add organize files job
* Note: targetPath parameter is deprecated and unused (reads from database config instead)
*/
async addOrganizeJob(
requestId: string,
audiobookId: string,
downloadPath: string,
targetPath: string
targetPath?: string
): Promise<string> {
return await this.addJob(
'organize_files',
@@ -512,7 +513,7 @@ export class JobQueueService {
requestId,
audiobookId,
downloadPath,
targetPath,
targetPath, // Not used by processor
} as OrganizeFilesPayload,
{
priority: 8,
+12 -10
View File
@@ -464,16 +464,18 @@ export class FileOrganizer {
}
}
// Singleton instance
let fileOrganizer: FileOrganizer | null = null;
/**
* Get FileOrganizer instance configured from database settings
* Reads media_dir from database configuration, falls back to /media/audiobooks if not configured
*/
export async function getFileOrganizer(): Promise<FileOrganizer> {
// Read media_dir from database config
const config = await prisma.configuration.findUnique({
where: { key: 'media_dir' },
});
export function getFileOrganizer(): FileOrganizer {
if (!fileOrganizer) {
const mediaDir = process.env.MEDIA_DIR || '/media/audiobooks';
const tempDir = process.env.TEMP_DIR || '/tmp/readmeabook';
const mediaDir = config?.value || process.env.MEDIA_DIR || '/media/audiobooks';
const tempDir = process.env.TEMP_DIR || '/tmp/readmeabook';
fileOrganizer = new FileOrganizer(mediaDir, tempDir);
}
return fileOrganizer;
return new FileOrganizer(mediaDir, tempDir);
}