154 lines
5.0 KiB
TypeScript
154 lines
5.0 KiB
TypeScript
import { createContext, useContext } from "react";
|
|
import type { SummaryObject, CallbackObject, TrackCallbackObject, AlbumCallbackObject, PlaylistCallbackObject, ProcessingCallbackObject } from "@/types/callbacks";
|
|
|
|
export type DownloadType = "track" | "album" | "playlist";
|
|
|
|
// Type guards for callback objects
|
|
const isProcessingCallback = (obj: CallbackObject): obj is ProcessingCallbackObject => {
|
|
return "status" in obj && typeof obj.status === "string";
|
|
};
|
|
|
|
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;
|
|
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;
|
|
cancelItem: (id: string) => void;
|
|
toggleVisibility: () => void;
|
|
clearCompleted: () => void;
|
|
cancelAll: () => void;
|
|
loadMoreTasks: () => void;
|
|
}
|
|
|
|
export const QueueContext = createContext<QueueContextType | undefined>(undefined);
|
|
|
|
export function useQueue() {
|
|
const context = useContext(QueueContext);
|
|
if (context === undefined) {
|
|
throw new Error("useQueue must be used within a QueueProvider");
|
|
}
|
|
return context;
|
|
}
|