Remove boy scout formatting changes

This commit is contained in:
Rob Walsh
2026-02-27 16:08:34 -07:00
parent 41d45d1210
commit 3861d07cf4
4 changed files with 190 additions and 463 deletions
+123 -265
View File
@@ -63,8 +63,8 @@ export interface MonitorDownloadPayload extends JobPayload {
downloadHistoryId: string;
downloadClientId: string;
downloadClient: DownloadClientType;
lastProgress?: number; // Previous poll's progress (0-100) for stall detection
stallCount?: number; // Consecutive polls with no progress change (drives backoff)
lastProgress?: number; // Previous poll's progress (0-100) for stall detection
stallCount?: number; // Consecutive polls with no progress change (drives backoff)
pathWaitCount?: number; // Consecutive polls waiting for content_path to relocate to save_path
}
@@ -227,15 +227,13 @@ export class JobQueueService {
'failed',
null,
error.message,
error.stack,
error.stack
);
// Handle permanent failures for specific job types after all retries exhausted
if (job.name === 'monitor_download' && job.data) {
const payload = job.data as MonitorDownloadPayload;
logger.error(
`MonitorDownload job permanently failed for request ${payload.requestId} after ${job.attemptsMade} attempts`,
);
logger.error(`MonitorDownload job permanently failed for request ${payload.requestId} after ${job.attemptsMade} attempts`);
// Update request status to failed (only happens after all retries exhausted)
try {
@@ -243,9 +241,7 @@ export class JobQueueService {
where: { id: payload.requestId },
data: {
status: 'failed',
errorMessage:
error.message ||
'Failed to monitor download after multiple retries',
errorMessage: error.message || 'Failed to monitor download after multiple retries',
updatedAt: new Date(),
},
});
@@ -261,12 +257,7 @@ export class JobQueueService {
});
}
} catch (updateError) {
logger.error('Failed to update request/download status', {
error:
updateError instanceof Error
? updateError.message
: String(updateError),
});
logger.error('Failed to update request/download status', { error: updateError instanceof Error ? updateError.message : String(updateError) });
}
}
});
@@ -290,211 +281,106 @@ export class JobQueueService {
*/
private startProcessors(): void {
// Search indexers processor
this.queue.process(
'search_indexers',
2,
async (job: BullJob<SearchIndexersPayload>) => {
const { processSearchIndexers } =
await import('../processors/search-indexers.processor');
return await processSearchIndexers(job.data);
},
);
this.queue.process('search_indexers', 2, async (job: BullJob<SearchIndexersPayload>) => {
const { processSearchIndexers } = await import('../processors/search-indexers.processor');
return await processSearchIndexers(job.data);
});
// Download torrent processor
this.queue.process(
'download_torrent',
2,
async (job: BullJob<DownloadTorrentPayload>) => {
const { processDownloadTorrent } =
await import('../processors/download-torrent.processor');
return await processDownloadTorrent(job.data);
},
);
this.queue.process('download_torrent', 2, async (job: BullJob<DownloadTorrentPayload>) => {
const { processDownloadTorrent } = await import('../processors/download-torrent.processor');
return await processDownloadTorrent(job.data);
});
// Monitor download processor
this.queue.process(
'monitor_download',
2,
async (job: BullJob<MonitorDownloadPayload>) => {
const { processMonitorDownload } =
await import('../processors/monitor-download.processor');
return await processMonitorDownload(job.data);
},
);
this.queue.process('monitor_download', 2, async (job: BullJob<MonitorDownloadPayload>) => {
const { processMonitorDownload } = await import('../processors/monitor-download.processor');
return await processMonitorDownload(job.data);
});
// Organize files processor
this.queue.process(
'organize_files',
2,
async (job: BullJob<OrganizeFilesPayload>) => {
const { processOrganizeFiles } =
await import('../processors/organize-files.processor');
return await processOrganizeFiles(job.data);
},
);
this.queue.process('organize_files', 2, async (job: BullJob<OrganizeFilesPayload>) => {
const { processOrganizeFiles } = await import('../processors/organize-files.processor');
return await processOrganizeFiles(job.data);
});
// Scan Plex processor
this.queue.process(
'scan_plex',
1,
async (job: BullJob<ScanPlexPayload>) => {
const { processScanPlex } =
await import('../processors/scan-plex.processor');
return await processScanPlex(job.data);
},
);
this.queue.process('scan_plex', 1, async (job: BullJob<ScanPlexPayload>) => {
const { processScanPlex } = await import('../processors/scan-plex.processor');
return await processScanPlex(job.data);
});
// Scheduled job processors
this.queue.process('plex_library_scan', 1, async (job: BullJob) => {
// plex_library_scan is just an alias for scan_plex
const { processScanPlex } =
await import('../processors/scan-plex.processor');
const payloadWithJobId = await this.ensureJobRecord(
job,
'plex_library_scan',
);
const { processScanPlex } = await import('../processors/scan-plex.processor');
const payloadWithJobId = await this.ensureJobRecord(job, 'plex_library_scan');
return await processScanPlex(payloadWithJobId);
});
this.queue.process(
'plex_recently_added_check',
1,
async (job: BullJob<PlexRecentlyAddedPayload>) => {
const { processPlexRecentlyAddedCheck } =
await import('../processors/plex-recently-added.processor');
const payloadWithJobId = await this.ensureJobRecord(
job,
'plex_recently_added_check',
);
return await processPlexRecentlyAddedCheck(payloadWithJobId);
},
);
this.queue.process('plex_recently_added_check', 1, async (job: BullJob<PlexRecentlyAddedPayload>) => {
const { processPlexRecentlyAddedCheck } = await import('../processors/plex-recently-added.processor');
const payloadWithJobId = await this.ensureJobRecord(job, 'plex_recently_added_check');
return await processPlexRecentlyAddedCheck(payloadWithJobId);
});
this.queue.process(
'monitor_rss_feeds',
1,
async (job: BullJob<MonitorRssFeedsPayload>) => {
const { processMonitorRssFeeds } =
await import('../processors/monitor-rss-feeds.processor');
const payloadWithJobId = await this.ensureJobRecord(
job,
'monitor_rss_feeds',
);
return await processMonitorRssFeeds(payloadWithJobId);
},
);
this.queue.process('monitor_rss_feeds', 1, async (job: BullJob<MonitorRssFeedsPayload>) => {
const { processMonitorRssFeeds } = await import('../processors/monitor-rss-feeds.processor');
const payloadWithJobId = await this.ensureJobRecord(job, 'monitor_rss_feeds');
return await processMonitorRssFeeds(payloadWithJobId);
});
this.queue.process(
'audible_refresh',
1,
async (job: BullJob<AudibleRefreshPayload>) => {
const { processAudibleRefresh } =
await import('../processors/audible-refresh.processor');
const payloadWithJobId = await this.ensureJobRecord(
job,
'audible_refresh',
);
return await processAudibleRefresh(payloadWithJobId);
},
);
this.queue.process('audible_refresh', 1, async (job: BullJob<AudibleRefreshPayload>) => {
const { processAudibleRefresh } = await import('../processors/audible-refresh.processor');
const payloadWithJobId = await this.ensureJobRecord(job, 'audible_refresh');
return await processAudibleRefresh(payloadWithJobId);
});
this.queue.process(
'retry_missing_torrents',
1,
async (job: BullJob<RetryMissingTorrentsPayload>) => {
const { processRetryMissingTorrents } =
await import('../processors/retry-missing-torrents.processor');
const payloadWithJobId = await this.ensureJobRecord(
job,
'retry_missing_torrents',
);
return await processRetryMissingTorrents(payloadWithJobId);
},
);
this.queue.process('retry_missing_torrents', 1, async (job: BullJob<RetryMissingTorrentsPayload>) => {
const { processRetryMissingTorrents } = await import('../processors/retry-missing-torrents.processor');
const payloadWithJobId = await this.ensureJobRecord(job, 'retry_missing_torrents');
return await processRetryMissingTorrents(payloadWithJobId);
});
this.queue.process(
'retry_failed_imports',
1,
async (job: BullJob<RetryFailedImportsPayload>) => {
const { processRetryFailedImports } =
await import('../processors/retry-failed-imports.processor');
const payloadWithJobId = await this.ensureJobRecord(
job,
'retry_failed_imports',
);
return await processRetryFailedImports(payloadWithJobId);
},
);
this.queue.process('retry_failed_imports', 1, async (job: BullJob<RetryFailedImportsPayload>) => {
const { processRetryFailedImports } = await import('../processors/retry-failed-imports.processor');
const payloadWithJobId = await this.ensureJobRecord(job, 'retry_failed_imports');
return await processRetryFailedImports(payloadWithJobId);
});
this.queue.process(
'cleanup_seeded_torrents',
1,
async (job: BullJob<CleanupSeededTorrentsPayload>) => {
const { processCleanupSeededTorrents } =
await import('../processors/cleanup-seeded-torrents.processor');
const payloadWithJobId = await this.ensureJobRecord(
job,
'cleanup_seeded_torrents',
);
return await processCleanupSeededTorrents(payloadWithJobId);
},
);
this.queue.process('cleanup_seeded_torrents', 1, async (job: BullJob<CleanupSeededTorrentsPayload>) => {
const { processCleanupSeededTorrents } = await import('../processors/cleanup-seeded-torrents.processor');
const payloadWithJobId = await this.ensureJobRecord(job, 'cleanup_seeded_torrents');
return await processCleanupSeededTorrents(payloadWithJobId);
});
this.queue.process(
'sync_reading_shelves',
1,
async (job: BullJob<SyncShelvesPayload>) => {
const { processSyncShelves } =
await import('../processors/sync-shelves.processor');
const payloadWithJobId = await this.ensureJobRecord(
job,
'sync_reading_shelves',
);
return await processSyncShelves(payloadWithJobId);
},
);
this.queue.process('sync_reading_shelves', 1, async (job: BullJob<SyncShelvesPayload>) => {
const { processSyncShelves } = await import('../processors/sync-shelves.processor');
const payloadWithJobId = await this.ensureJobRecord(job, 'sync_reading_shelves');
return await processSyncShelves(payloadWithJobId);
});
// Send notification processor
this.queue.process(
'send_notification',
2,
async (job: BullJob<SendNotificationPayload>) => {
const { processSendNotification } =
await import('../processors/send-notification.processor');
return await processSendNotification(job.data);
},
);
this.queue.process('send_notification', 2, async (job: BullJob<SendNotificationPayload>) => {
const { processSendNotification } = await import('../processors/send-notification.processor');
return await processSendNotification(job.data);
});
// Ebook-specific processors
this.queue.process(
'search_ebook',
2,
async (job: BullJob<SearchEbookPayload>) => {
const { processSearchEbook } =
await import('../processors/search-ebook.processor');
return await processSearchEbook(job.data);
},
);
this.queue.process('search_ebook', 2, async (job: BullJob<SearchEbookPayload>) => {
const { processSearchEbook } = await import('../processors/search-ebook.processor');
return await processSearchEbook(job.data);
});
this.queue.process(
'start_direct_download',
2,
async (job: BullJob<StartDirectDownloadPayload>) => {
const { processStartDirectDownload } =
await import('../processors/direct-download.processor');
return await processStartDirectDownload(job.data);
},
);
this.queue.process('start_direct_download', 2, async (job: BullJob<StartDirectDownloadPayload>) => {
const { processStartDirectDownload } = await import('../processors/direct-download.processor');
return await processStartDirectDownload(job.data);
});
this.queue.process(
'monitor_direct_download',
2,
async (job: BullJob<MonitorDirectDownloadPayload>) => {
const { processMonitorDirectDownload } =
await import('../processors/direct-download.processor');
return await processMonitorDirectDownload(job.data);
},
);
this.queue.process('monitor_direct_download', 2, async (job: BullJob<MonitorDirectDownloadPayload>) => {
const { processMonitorDirectDownload } = await import('../processors/direct-download.processor');
return await processMonitorDirectDownload(job.data);
});
}
/**
@@ -519,17 +405,12 @@ export class JobQueueService {
if (existingJob) {
// Update lastRun for the scheduled job if this is a timer-triggered job
if (payload.scheduledJobId) {
await prisma.scheduledJob
.update({
where: { id: payload.scheduledJobId },
data: { lastRun: new Date() },
})
.catch((err) => {
logger.error(
`Failed to update lastRun for scheduled job ${payload.scheduledJobId}`,
{ error: err instanceof Error ? err.message : String(err) },
);
});
await prisma.scheduledJob.update({
where: { id: payload.scheduledJobId },
data: { lastRun: new Date() },
}).catch(err => {
logger.error(`Failed to update lastRun for scheduled job ${payload.scheduledJobId}`, { error: err instanceof Error ? err.message : String(err) });
});
}
return { ...payload, jobId: existingJob.id };
}
@@ -549,17 +430,12 @@ export class JobQueueService {
// Update lastRun for the scheduled job if this is a timer-triggered job
if (payload.scheduledJobId) {
await prisma.scheduledJob
.update({
where: { id: payload.scheduledJobId },
data: { lastRun: new Date() },
})
.catch((err) => {
logger.error(
`Failed to update lastRun for scheduled job ${payload.scheduledJobId}`,
{ error: err instanceof Error ? err.message : String(err) },
);
});
await prisma.scheduledJob.update({
where: { id: payload.scheduledJobId },
data: { lastRun: new Date() },
}).catch(err => {
logger.error(`Failed to update lastRun for scheduled job ${payload.scheduledJobId}`, { error: err instanceof Error ? err.message : String(err) });
});
}
return { ...payload, jobId: dbJob.id };
@@ -573,7 +449,7 @@ export class JobQueueService {
status: string,
result?: any,
errorMessage?: string,
stackTrace?: string,
stackTrace?: string
): Promise<void> {
try {
const updateData: any = {
@@ -606,9 +482,7 @@ export class JobQueueService {
data: updateData,
});
} catch (error) {
logger.error('Failed to update job in database', {
error: error instanceof Error ? error.message : String(error),
});
logger.error('Failed to update job in database', { error: error instanceof Error ? error.message : String(error) });
}
}
@@ -618,7 +492,7 @@ export class JobQueueService {
private async addJob(
type: JobType,
payload: JobPayload,
options?: JobOptions,
options?: JobOptions
): Promise<string> {
// First create the database job record
const dbJob = await prisma.job.create({
@@ -651,10 +525,7 @@ export class JobQueueService {
/**
* Add search indexers job
*/
async addSearchJob(
requestId: string,
audiobook: { id: string; title: string; author: string; asin?: string },
): Promise<string> {
async addSearchJob(requestId: string, audiobook: { id: string; title: string; author: string; asin?: string }): Promise<string> {
return await this.addJob(
'search_indexers',
{
@@ -663,7 +534,7 @@ export class JobQueueService {
} as SearchIndexersPayload,
{
priority: 10, // High priority for user-initiated requests
},
}
);
}
@@ -673,7 +544,7 @@ export class JobQueueService {
async addDownloadJob(
requestId: string,
audiobook: { id: string; title: string; author: string },
torrent: TorrentResult,
torrent: TorrentResult
): Promise<string> {
return await this.addJob(
'download_torrent',
@@ -684,7 +555,7 @@ export class JobQueueService {
} as DownloadTorrentPayload,
{
priority: 9, // High priority - download selected torrent
},
}
);
}
@@ -699,7 +570,7 @@ export class JobQueueService {
delaySeconds: number = 0,
lastProgress?: number,
stallCount?: number,
pathWaitCount?: number,
pathWaitCount?: number
): Promise<string> {
return await this.addJob(
'monitor_download',
@@ -715,7 +586,7 @@ export class JobQueueService {
{
priority: 5, // Medium priority
delay: delaySeconds * 1000, // Convert seconds to milliseconds
},
}
);
}
@@ -727,7 +598,7 @@ export class JobQueueService {
requestId: string,
audiobookId: string,
downloadPath: string,
targetPath?: string,
targetPath?: string
): Promise<string> {
return await this.addJob(
'organize_files',
@@ -739,18 +610,14 @@ export class JobQueueService {
} as OrganizeFilesPayload,
{
priority: 8,
},
}
);
}
/**
* Add Plex scan job
*/
async addPlexScanJob(
libraryId: string,
partial?: boolean,
path?: string,
): Promise<string> {
async addPlexScanJob(libraryId: string, partial?: boolean, path?: string): Promise<string> {
return await this.addJob(
'scan_plex',
{
@@ -760,7 +627,7 @@ export class JobQueueService {
} as ScanPlexPayload,
{
priority: 7,
},
}
);
}
@@ -775,7 +642,7 @@ export class JobQueueService {
} as PlexRecentlyAddedPayload,
{
priority: 8,
},
}
);
}
@@ -790,7 +657,7 @@ export class JobQueueService {
} as MonitorRssFeedsPayload,
{
priority: 8,
},
}
);
}
@@ -805,7 +672,7 @@ export class JobQueueService {
} as AudibleRefreshPayload,
{
priority: 9,
},
}
);
}
@@ -820,7 +687,7 @@ export class JobQueueService {
} as RetryMissingTorrentsPayload,
{
priority: 7,
},
}
);
}
@@ -835,7 +702,7 @@ export class JobQueueService {
} as RetryFailedImportsPayload,
{
priority: 7,
},
}
);
}
@@ -850,19 +717,14 @@ export class JobQueueService {
} as CleanupSeededTorrentsPayload,
{
priority: 10,
},
}
);
}
/**
* Add sync reading shelves job
*/
async addSyncShelvesJob(
scheduledJobId?: string,
shelfId?: string,
shelfType?: 'goodreads' | 'hardcover',
maxLookupsPerShelf?: number,
): Promise<string> {
async addSyncShelvesJob(scheduledJobId?: string, shelfId?: string, shelfType?: 'goodreads' | 'hardcover', maxLookupsPerShelf?: number): Promise<string> {
return await this.addJob(
'sync_reading_shelves',
{
@@ -873,7 +735,7 @@ export class JobQueueService {
} as SyncShelvesPayload,
{
priority: 7,
},
}
);
}
@@ -887,7 +749,7 @@ export class JobQueueService {
async addSearchEbookJob(
requestId: string,
audiobook: { id: string; title: string; author: string; asin?: string },
preferredFormat?: string,
preferredFormat?: string
): Promise<string> {
return await this.addJob(
'search_ebook',
@@ -898,7 +760,7 @@ export class JobQueueService {
} as SearchEbookPayload,
{
priority: 10, // High priority for user-initiated requests
},
}
);
}
@@ -910,7 +772,7 @@ export class JobQueueService {
downloadHistoryId: string,
downloadUrl: string,
targetFilename: string,
expectedSize?: number,
expectedSize?: number
): Promise<string> {
return await this.addJob(
'start_direct_download',
@@ -923,7 +785,7 @@ export class JobQueueService {
} as StartDirectDownloadPayload,
{
priority: 9, // High priority - download selected ebook
},
}
);
}
@@ -936,7 +798,7 @@ export class JobQueueService {
downloadId: string,
targetPath: string,
expectedSize?: number,
delaySeconds: number = 0,
delaySeconds: number = 0
): Promise<string> {
return await this.addJob(
'monitor_direct_download',
@@ -950,7 +812,7 @@ export class JobQueueService {
{
priority: 5, // Medium priority
delay: delaySeconds * 1000,
},
}
);
}
@@ -1099,13 +961,9 @@ export class JobQueueService {
author: string,
userName: string,
message?: string,
requestType?: string,
requestType?: string
): Promise<string> {
logger.info(`Queueing notification: ${event}`, {
requestId,
title,
userName,
});
logger.info(`Queueing notification: ${event}`, { requestId, title, userName });
return await this.addJob(
'send_notification',
{
@@ -1125,7 +983,7 @@ export class JobQueueService {
} as SendNotificationPayload,
{
priority: 5, // Medium priority
},
}
);
}
@@ -1136,7 +994,7 @@ export class JobQueueService {
jobType: string,
payload: JobPayload,
cronExpression: string,
jobId: string,
jobId: string
): Promise<void> {
await this.queue.add(jobType, payload, {
repeat: {
@@ -1153,7 +1011,7 @@ export class JobQueueService {
async removeRepeatableJob(
jobType: string,
cronExpression: string,
jobId: string,
jobId: string
): Promise<void> {
await this.queue.removeRepeatable(jobType, {
cron: cronExpression,