174 lines
4.9 KiB
TypeScript
174 lines
4.9 KiB
TypeScript
import { useCallback, useEffect, useRef } from 'react';
|
|
import { useDownloadStore } from '../stores/downloadStore';
|
|
import { downloadService, type DownloadSession } from '../services/download/downloadService';
|
|
import type { Movie, Torrent } from '../types';
|
|
import { logger } from '../utils/logger';
|
|
|
|
/**
|
|
* Hook to manage downloads with actual torrent downloading
|
|
* This combines the download store state with the download service
|
|
*/
|
|
export function useDownloadManager() {
|
|
const {
|
|
items,
|
|
addDownload: addToStore,
|
|
updateDownload,
|
|
removeDownload: removeFromStore,
|
|
pauseDownload: pauseInStore,
|
|
resumeDownload: resumeInStore,
|
|
getDownload,
|
|
getDownloadByMovieId,
|
|
clearCompleted,
|
|
} = useDownloadStore();
|
|
|
|
// Track active download sessions
|
|
const activeSessionsRef = useRef<Map<string, string>>(new Map());
|
|
|
|
/**
|
|
* Start a new download
|
|
*/
|
|
const startDownload = useCallback(async (movie: Movie, torrent: Torrent): Promise<string> => {
|
|
// Add to store first to get the ID
|
|
const id = addToStore(movie, torrent);
|
|
|
|
try {
|
|
// Update status to downloading
|
|
updateDownload(id, { status: 'downloading' });
|
|
|
|
// Start the actual download
|
|
const result = await downloadService.startDownload(
|
|
id,
|
|
movie,
|
|
torrent,
|
|
(data: DownloadSession) => {
|
|
// Update progress in store
|
|
updateDownload(id, {
|
|
progress: data.progress,
|
|
downloadSpeed: data.downloadSpeed,
|
|
uploadSpeed: data.uploadSpeed,
|
|
peers: data.peers,
|
|
status: data.status === 'ready'
|
|
? 'completed'
|
|
: data.status === 'error'
|
|
? 'error'
|
|
: 'downloading',
|
|
});
|
|
|
|
// Log progress periodically
|
|
if (Math.floor(data.progress * 100) % 10 === 0) {
|
|
logger.debug('Download progress', {
|
|
id,
|
|
progress: `${(data.progress * 100).toFixed(1)}%`,
|
|
speed: `${(data.downloadSpeed / 1024 / 1024).toFixed(2)} MB/s`
|
|
});
|
|
}
|
|
}
|
|
);
|
|
|
|
// Store the session ID
|
|
activeSessionsRef.current.set(id, result.sessionId);
|
|
|
|
logger.info('Download started successfully', { id, movie: movie.title });
|
|
return id;
|
|
} catch (error) {
|
|
// Update status to error
|
|
updateDownload(id, {
|
|
status: 'error',
|
|
});
|
|
logger.error('Failed to start download', error);
|
|
throw error;
|
|
}
|
|
}, [addToStore, updateDownload]);
|
|
|
|
/**
|
|
* Stop and remove a download
|
|
*/
|
|
const removeDownload = useCallback(async (id: string): Promise<void> => {
|
|
try {
|
|
// Stop the download on the server
|
|
await downloadService.stopDownload(id);
|
|
activeSessionsRef.current.delete(id);
|
|
} catch (error) {
|
|
logger.error('Failed to stop download', error);
|
|
}
|
|
|
|
// Remove from store
|
|
removeFromStore(id);
|
|
}, [removeFromStore]);
|
|
|
|
/**
|
|
* Pause a download
|
|
*/
|
|
const pauseDownload = useCallback(async (id: string): Promise<void> => {
|
|
// For now, pausing just updates the UI state
|
|
// A full implementation would need server support for pausing torrents
|
|
pauseInStore(id);
|
|
logger.info('Download paused', { id });
|
|
}, [pauseInStore]);
|
|
|
|
/**
|
|
* Resume a download
|
|
*/
|
|
const resumeDownload = useCallback(async (id: string): Promise<void> => {
|
|
const download = getDownload(id);
|
|
if (!download) return;
|
|
|
|
// If the download was never started or needs to be restarted
|
|
if (!activeSessionsRef.current.has(id)) {
|
|
try {
|
|
resumeInStore(id);
|
|
|
|
// Restart the download
|
|
await downloadService.startDownload(
|
|
id,
|
|
download.movie,
|
|
download.torrent,
|
|
(data: DownloadSession) => {
|
|
updateDownload(id, {
|
|
progress: data.progress,
|
|
downloadSpeed: data.downloadSpeed,
|
|
uploadSpeed: data.uploadSpeed,
|
|
peers: data.peers,
|
|
status: data.status === 'ready'
|
|
? 'completed'
|
|
: data.status === 'error'
|
|
? 'error'
|
|
: 'downloading',
|
|
});
|
|
}
|
|
);
|
|
} catch (error) {
|
|
updateDownload(id, { status: 'error' });
|
|
logger.error('Failed to resume download', error);
|
|
}
|
|
} else {
|
|
resumeInStore(id);
|
|
}
|
|
}, [getDownload, resumeInStore, updateDownload]);
|
|
|
|
/**
|
|
* Cleanup on unmount
|
|
*/
|
|
useEffect(() => {
|
|
return () => {
|
|
// Don't disconnect on unmount - downloads should continue in background
|
|
// downloadService.disconnect();
|
|
};
|
|
}, []);
|
|
|
|
return {
|
|
downloads: items,
|
|
startDownload,
|
|
removeDownload,
|
|
pauseDownload,
|
|
resumeDownload,
|
|
getDownload,
|
|
getDownloadByMovieId,
|
|
clearCompleted,
|
|
activeCount: items.filter(d => d.status === 'downloading').length,
|
|
completedCount: items.filter(d => d.status === 'completed').length,
|
|
};
|
|
}
|
|
|
|
export default useDownloadManager;
|