mirror of
https://github.com/Crosstalk-Solutions/project-nomad.git
synced 2026-03-28 03:29:25 +01:00
Three bugs caused downloads to hang, disappear, or leave stuck spinners: 1. Wikipedia downloads that failed never updated the DB status from 'downloading', leaving the spinner stuck forever. Now the worker's failed handler marks them as failed. 2. No stall detection on streaming downloads - if data stopped flowing mid-download, the job hung indefinitely. Added a 5-minute stall timer that triggers retry. 3. Failed jobs were invisible to users since only waiting/active/delayed states were queried. Now failed jobs appear with error indicators in the download list. Closes #364, closes #216 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
54 lines
2.2 KiB
TypeScript
54 lines
2.2 KiB
TypeScript
import { inject } from '@adonisjs/core'
|
|
import { QueueService } from './queue_service.js'
|
|
import { RunDownloadJob } from '#jobs/run_download_job'
|
|
import { DownloadModelJob } from '#jobs/download_model_job'
|
|
import { DownloadJobWithProgress } from '../../types/downloads.js'
|
|
import { normalize } from 'path'
|
|
|
|
@inject()
|
|
export class DownloadService {
|
|
constructor(private queueService: QueueService) {}
|
|
|
|
async listDownloadJobs(filetype?: string): Promise<DownloadJobWithProgress[]> {
|
|
// Get regular file download jobs (zim, map, etc.)
|
|
const queue = this.queueService.getQueue(RunDownloadJob.queue)
|
|
const fileJobs = await queue.getJobs(['waiting', 'active', 'delayed', 'failed'])
|
|
|
|
const fileDownloads = fileJobs.map((job) => ({
|
|
jobId: job.id!.toString(),
|
|
url: job.data.url,
|
|
progress: parseInt(job.progress.toString(), 10),
|
|
filepath: normalize(job.data.filepath),
|
|
filetype: job.data.filetype,
|
|
status: (job.failedReason ? 'failed' : 'active') as 'active' | 'failed',
|
|
failedReason: job.failedReason || undefined,
|
|
}))
|
|
|
|
// Get Ollama model download jobs
|
|
const modelQueue = this.queueService.getQueue(DownloadModelJob.queue)
|
|
const modelJobs = await modelQueue.getJobs(['waiting', 'active', 'delayed', 'failed'])
|
|
|
|
const modelDownloads = modelJobs.map((job) => ({
|
|
jobId: job.id!.toString(),
|
|
url: job.data.modelName || 'Unknown Model', // Use model name as url
|
|
progress: parseInt(job.progress.toString(), 10),
|
|
filepath: job.data.modelName || 'Unknown Model', // Use model name as filepath
|
|
filetype: 'model',
|
|
status: (job.failedReason ? 'failed' : 'active') as 'active' | 'failed',
|
|
failedReason: job.failedReason || undefined,
|
|
}))
|
|
|
|
const allDownloads = [...fileDownloads, ...modelDownloads]
|
|
|
|
// Filter by filetype if specified
|
|
const filtered = allDownloads.filter((job) => !filetype || job.filetype === filetype)
|
|
|
|
// Sort: active downloads first (by progress desc), then failed at the bottom
|
|
return filtered.sort((a, b) => {
|
|
if (a.status === 'failed' && b.status !== 'failed') return 1
|
|
if (a.status !== 'failed' && b.status === 'failed') return -1
|
|
return b.progress - a.progress
|
|
})
|
|
}
|
|
}
|