Improved SSE and queue handling. Updated to use callbacks.ts
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,113 +1,145 @@
|
||||
import { createContext, useContext } from "react";
|
||||
import type { SummaryObject } from "@/types/callbacks";
|
||||
import type { SummaryObject, CallbackObject, TrackCallbackObject, AlbumCallbackObject, PlaylistCallbackObject, ProcessingCallbackObject } from "@/types/callbacks";
|
||||
|
||||
export type DownloadType = "track" | "album" | "artist" | "playlist";
|
||||
export type QueueStatus =
|
||||
| "initializing"
|
||||
| "pending"
|
||||
| "downloading"
|
||||
| "processing"
|
||||
| "completed"
|
||||
| "error"
|
||||
| "skipped"
|
||||
| "cancelled"
|
||||
| "done"
|
||||
| "queued"
|
||||
| "retrying"
|
||||
| "real-time"
|
||||
| "progress"
|
||||
| "track_progress";
|
||||
export type DownloadType = "track" | "album" | "playlist";
|
||||
|
||||
// Active task statuses - tasks that are currently working/processing or queued
|
||||
// This matches the ACTIVE_TASK_STATES constant in the backend plus queued tasks
|
||||
export const ACTIVE_TASK_STATUSES: Set<QueueStatus> = new Set([
|
||||
"initializing", // task is starting up
|
||||
"processing", // task is being processed
|
||||
"downloading", // actively downloading
|
||||
"progress", // album/playlist progress updates
|
||||
"track_progress", // real-time track progress
|
||||
"real-time", // real-time download progress
|
||||
"retrying", // task is retrying after error
|
||||
"queued", // task is queued and waiting to start
|
||||
]);
|
||||
// Type guards for callback objects
|
||||
const isProcessingCallback = (obj: CallbackObject): obj is ProcessingCallbackObject => {
|
||||
return "status" in obj && typeof obj.status === "string";
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine if a task status represents an active (working/processing) task
|
||||
*/
|
||||
export function isActiveTaskStatus(status: string): boolean {
|
||||
return ACTIVE_TASK_STATUSES.has(status as QueueStatus);
|
||||
}
|
||||
const isTrackCallback = (obj: CallbackObject): obj is TrackCallbackObject => {
|
||||
return "track" in obj && "status_info" in obj;
|
||||
};
|
||||
|
||||
const isAlbumCallback = (obj: CallbackObject): obj is AlbumCallbackObject => {
|
||||
return "album" in obj && "status_info" in obj;
|
||||
};
|
||||
|
||||
const isPlaylistCallback = (obj: CallbackObject): obj is PlaylistCallbackObject => {
|
||||
return "playlist" in obj && "status_info" in obj;
|
||||
};
|
||||
|
||||
// Simplified queue item that works directly with callback objects
|
||||
export interface QueueItem {
|
||||
id: string;
|
||||
name: string;
|
||||
type: DownloadType;
|
||||
spotifyId: string;
|
||||
|
||||
// Display Info
|
||||
artist?: string;
|
||||
albumName?: string;
|
||||
playlistOwner?: string;
|
||||
currentTrackTitle?: string;
|
||||
|
||||
// Status and Progress
|
||||
status: QueueStatus;
|
||||
taskId?: string;
|
||||
error?: string;
|
||||
canRetry?: boolean;
|
||||
progress?: number;
|
||||
speed?: string;
|
||||
size?: string;
|
||||
eta?: string;
|
||||
currentTrackNumber?: number;
|
||||
totalTracks?: number;
|
||||
summary?: SummaryObject;
|
||||
|
||||
// Real-time download data
|
||||
last_line?: {
|
||||
// Direct status and error fields
|
||||
status?: string;
|
||||
error?: string;
|
||||
id?: number;
|
||||
timestamp?: number;
|
||||
|
||||
// Album/playlist progress fields
|
||||
current_track?: number;
|
||||
total_tracks?: number;
|
||||
parent?: any; // Parent album/playlist information
|
||||
|
||||
// Real-time progress data (when status is "real-time")
|
||||
status_info?: {
|
||||
progress?: number;
|
||||
status?: string;
|
||||
time_elapsed?: number;
|
||||
error?: string;
|
||||
timestamp?: number;
|
||||
ids?: {
|
||||
isrc?: string;
|
||||
spotify?: string;
|
||||
};
|
||||
};
|
||||
track?: any; // Contains detailed track information
|
||||
};
|
||||
id: string;
|
||||
taskId?: string;
|
||||
downloadType: DownloadType;
|
||||
spotifyId: string;
|
||||
|
||||
// Current callback data - this is the source of truth
|
||||
lastCallback?: CallbackObject;
|
||||
|
||||
// Derived display properties (computed from callback)
|
||||
name: string;
|
||||
artist: string;
|
||||
|
||||
// Summary data for completed downloads
|
||||
summary?: SummaryObject;
|
||||
|
||||
// Error state
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// Status extraction utilities
|
||||
export const getStatus = (item: QueueItem): string => {
|
||||
if (!item.lastCallback) {
|
||||
// Only log if this seems problematic (task has been around for a while)
|
||||
return "initializing";
|
||||
}
|
||||
|
||||
if (isProcessingCallback(item.lastCallback)) {
|
||||
return item.lastCallback.status;
|
||||
}
|
||||
|
||||
if (isTrackCallback(item.lastCallback)) {
|
||||
// For parent downloads, if we're getting track callbacks, the parent is "downloading"
|
||||
if (item.downloadType === "album" || item.downloadType === "playlist") {
|
||||
return item.lastCallback.status_info.status === "done" ? "downloading" : "downloading";
|
||||
}
|
||||
return item.lastCallback.status_info.status;
|
||||
}
|
||||
|
||||
if (isAlbumCallback(item.lastCallback)) {
|
||||
return item.lastCallback.status_info.status;
|
||||
}
|
||||
|
||||
if (isPlaylistCallback(item.lastCallback)) {
|
||||
return item.lastCallback.status_info.status;
|
||||
}
|
||||
|
||||
console.warn(`getStatus: Unknown callback type for item ${item.id}:`, item.lastCallback);
|
||||
return "unknown";
|
||||
};
|
||||
|
||||
export const isActiveStatus = (status: string): boolean => {
|
||||
return ["initializing", "processing", "downloading", "real-time", "progress", "track_progress", "retrying", "queued"].includes(status);
|
||||
};
|
||||
|
||||
export const isTerminalStatus = (status: string): boolean => {
|
||||
// Handle both "complete" (backend) and "completed" (frontend) for compatibility
|
||||
return ["completed", "complete", "done", "error", "cancelled", "skipped"].includes(status);
|
||||
};
|
||||
|
||||
// Progress calculation utilities
|
||||
export const getProgress = (item: QueueItem): number | undefined => {
|
||||
if (!item.lastCallback) return undefined;
|
||||
|
||||
// For individual tracks
|
||||
if (item.downloadType === "track" && isTrackCallback(item.lastCallback)) {
|
||||
if (item.lastCallback.status_info.status === "real-time" && "progress" in item.lastCallback.status_info) {
|
||||
return item.lastCallback.status_info.progress;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// For parent downloads (albums/playlists) - calculate based on track callbacks
|
||||
if ((item.downloadType === "album" || item.downloadType === "playlist") && isTrackCallback(item.lastCallback)) {
|
||||
const callback = item.lastCallback;
|
||||
const currentTrack = callback.current_track || 1;
|
||||
const totalTracks = callback.total_tracks || 1;
|
||||
const trackProgress = (callback.status_info.status === "real-time" && "progress" in callback.status_info)
|
||||
? callback.status_info.progress : 0;
|
||||
|
||||
// Formula: ((completed tracks) + (current track progress / 100)) / total tracks * 100
|
||||
const completedTracks = currentTrack - 1;
|
||||
return ((completedTracks + (trackProgress / 100)) / totalTracks) * 100;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// Display info extraction
|
||||
export const getCurrentTrackInfo = (item: QueueItem): { current?: number; total?: number; title?: string } => {
|
||||
if (!item.lastCallback) return {};
|
||||
|
||||
if (isTrackCallback(item.lastCallback)) {
|
||||
return {
|
||||
current: item.lastCallback.current_track,
|
||||
total: item.lastCallback.total_tracks,
|
||||
title: item.lastCallback.track.title
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
export interface QueueContextType {
|
||||
items: QueueItem[];
|
||||
isVisible: boolean;
|
||||
activeCount: number;
|
||||
totalTasks: number;
|
||||
hasMore: boolean;
|
||||
isLoadingMore: boolean;
|
||||
|
||||
// Actions
|
||||
addItem: (item: { name: string; type: DownloadType; spotifyId: string; artist?: string }) => void;
|
||||
removeItem: (id: string) => void;
|
||||
retryItem: (id: string) => void;
|
||||
cancelItem: (id: string) => void;
|
||||
toggleVisibility: () => void;
|
||||
clearCompleted: () => void;
|
||||
cancelAll: () => void;
|
||||
cancelItem: (id: string) => void;
|
||||
// Pagination
|
||||
hasMore: boolean;
|
||||
isLoadingMore: boolean;
|
||||
loadMoreTasks: () => void;
|
||||
totalTasks: number;
|
||||
}
|
||||
|
||||
export const QueueContext = createContext<QueueContextType | undefined>(undefined);
|
||||
|
||||
Reference in New Issue
Block a user