146 lines
3.7 KiB
TypeScript
146 lines
3.7 KiB
TypeScript
import { EventEmitter } from 'node:events'
|
|
import type { DownloadItem, DownloadOptions } from '../../shared/types'
|
|
|
|
interface QueueItem {
|
|
id: string
|
|
options: DownloadOptions
|
|
item: DownloadItem
|
|
}
|
|
|
|
export class DownloadQueue extends EventEmitter {
|
|
private queue: QueueItem[] = []
|
|
private activeDownloads: Map<string, QueueItem> = new Map()
|
|
private completedDownloads: Map<string, QueueItem> = new Map()
|
|
private maxConcurrent: number
|
|
|
|
constructor(maxConcurrent: number = 5) {
|
|
super()
|
|
this.maxConcurrent = maxConcurrent
|
|
}
|
|
|
|
setMaxConcurrent(max: number): void {
|
|
this.maxConcurrent = max
|
|
this.processQueue()
|
|
}
|
|
|
|
add(id: string, options: DownloadOptions, item: DownloadItem): void {
|
|
this.queue.push({ id, options, item })
|
|
this.emit('queue-updated', this.getQueueStatus())
|
|
this.processQueue()
|
|
}
|
|
|
|
remove(id: string): boolean {
|
|
// Remove from queue
|
|
const queueIndex = this.queue.findIndex((item) => item.id === id)
|
|
if (queueIndex !== -1) {
|
|
this.queue.splice(queueIndex, 1)
|
|
this.emit('queue-updated', this.getQueueStatus())
|
|
return true
|
|
}
|
|
|
|
// Remove from active downloads
|
|
if (this.activeDownloads.has(id)) {
|
|
this.activeDownloads.delete(id)
|
|
this.emit('queue-updated', this.getQueueStatus())
|
|
this.processQueue()
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
downloadCompleted(id: string): void {
|
|
const activeDownload = this.activeDownloads.get(id)
|
|
if (activeDownload) {
|
|
this.completedDownloads.set(id, activeDownload)
|
|
this.activeDownloads.delete(id)
|
|
}
|
|
this.emit('queue-updated', this.getQueueStatus())
|
|
this.processQueue()
|
|
}
|
|
|
|
private processQueue(): void {
|
|
while (this.activeDownloads.size < this.maxConcurrent && this.queue.length > 0) {
|
|
const item = this.queue.shift()
|
|
if (item) {
|
|
this.activeDownloads.set(item.id, item)
|
|
this.emit('start-download', item)
|
|
}
|
|
}
|
|
this.emit('queue-updated', this.getQueueStatus())
|
|
}
|
|
|
|
getQueueStatus(): {
|
|
queued: number
|
|
active: number
|
|
activeIds: string[]
|
|
} {
|
|
return {
|
|
queued: this.queue.length,
|
|
active: this.activeDownloads.size,
|
|
activeIds: Array.from(this.activeDownloads.keys())
|
|
}
|
|
}
|
|
|
|
isDownloading(id: string): boolean {
|
|
return this.activeDownloads.has(id)
|
|
}
|
|
|
|
getCompletedDownload(id: string): QueueItem | undefined {
|
|
return this.completedDownloads.get(id)
|
|
}
|
|
|
|
getItemDetails(id: string): { options: DownloadOptions; item: DownloadItem } | undefined {
|
|
const inActive = this.activeDownloads.get(id)
|
|
if (inActive) {
|
|
return {
|
|
options: inActive.options,
|
|
item: { ...inActive.item }
|
|
}
|
|
}
|
|
|
|
const inQueue = this.queue.find((entry) => entry.id === id)
|
|
if (inQueue) {
|
|
return {
|
|
options: inQueue.options,
|
|
item: { ...inQueue.item }
|
|
}
|
|
}
|
|
|
|
const completed = this.completedDownloads.get(id)
|
|
if (completed) {
|
|
return {
|
|
options: completed.options,
|
|
item: { ...completed.item }
|
|
}
|
|
}
|
|
|
|
return undefined
|
|
}
|
|
|
|
updateItemInfo(id: string, updates: Partial<DownloadItem>): void {
|
|
// Update in queue
|
|
const queueItem = this.queue.find((item) => item.id === id)
|
|
if (queueItem) {
|
|
Object.assign(queueItem.item, updates)
|
|
}
|
|
|
|
// Update in active downloads
|
|
const activeItem = this.activeDownloads.get(id)
|
|
if (activeItem) {
|
|
Object.assign(activeItem.item, updates)
|
|
}
|
|
|
|
// Update in completed downloads
|
|
const completedItem = this.completedDownloads.get(id)
|
|
if (completedItem) {
|
|
Object.assign(completedItem.item, updates)
|
|
}
|
|
}
|
|
|
|
clear(): void {
|
|
this.queue = []
|
|
this.emit('queue-updated', this.getQueueStatus())
|
|
}
|
|
}
|