import { useState, useEffect, useCallback, useRef } from 'react'; import { objectsApi } from '@/lib/api'; import type { S3Object, UploadTask } from '@/types'; import { toast } from 'sonner'; export function useBucketObjects(bucketName: string | null, currentPath: string = '') { const [objects, setObjects] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [isNavigating, setIsNavigating] = useState(false); const [error, setError] = useState(null); const [isTruncated, setIsTruncated] = useState(false); const [nextContinuationToken, setNextContinuationToken] = useState(undefined); const [itemsPerPage, setItemsPerPage] = useState(25); const [currentContinuationToken, setCurrentContinuationToken] = useState(undefined); const previousPathRef = useRef(currentPath); const [uploadTasks, setUploadTasks] = useState([]); const clearTasksTimerRef = useRef | null>(null); const fetchObjects = useCallback(async (continuationToken?: string, isRefresh = false, isNav = false) => { if (!bucketName) return; try { if (isRefresh) { setIsRefreshing(true); } else if (isNav) { setIsNavigating(true); } else { setIsLoading(true); } setError(null); const response = await objectsApi.list(bucketName, currentPath, itemsPerPage, continuationToken); setObjects(response.objects); setIsTruncated(response.isTruncated); setNextContinuationToken(response.nextContinuationToken); setCurrentContinuationToken(continuationToken); } catch (err) { setError(err as Error); console.error('Failed to fetch objects:', err); } finally { setIsLoading(false); setIsRefreshing(false); setIsNavigating(false); } }, [bucketName, currentPath, itemsPerPage]); useEffect(() => { if (!bucketName) return; const isPathChange = previousPathRef.current !== currentPath && objects.length > 0; previousPathRef.current = currentPath; fetchObjects(undefined, false, isPathChange); // eslint-disable-next-line react-hooks/exhaustive-deps }, [bucketName, currentPath, itemsPerPage]); useEffect(() => { return () => { if (clearTasksTimerRef.current) clearTimeout(clearTasksTimerRef.current); }; }, []); const uploadFiles = useCallback(async (files: File[]) => { if (!bucketName) return false; const hasRelativePaths = files.some((file) => !!file.webkitRelativePath); const folders = new Set(); files.forEach((file) => { if (file.webkitRelativePath) { const parts = file.webkitRelativePath.split('/'); if (parts.length > 1) { folders.add(parts[0]); } } }); const tasks: UploadTask[] = files.map((file, index) => { const relativePath = file.webkitRelativePath || file.name; const key = currentPath ? `${currentPath}${relativePath}` : relativePath; return { id: `${Date.now()}-${index}`, file, key, bucket: bucketName, progress: 0, status: 'pending' as const, }; }); setUploadTasks(tasks); let successCount = 0; let errorCount = 0; for (const task of tasks) { try { setUploadTasks(prev => prev.map(t => t.id === task.id ? { ...t, status: 'uploading' as const } : t )); await objectsApi.upload(bucketName, task.key, task.file, (progress) => { setUploadTasks(prev => prev.map(t => { if (t.id !== task.id || t.progress === progress) return t; return { ...t, progress }; })); }); setUploadTasks(prev => prev.map(t => t.id === task.id ? { ...t, status: 'completed' as const, progress: 100 } : t )); successCount++; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Upload failed'; setUploadTasks(prev => prev.map(t => t.id === task.id ? { ...t, status: 'error' as const, error: errorMessage } : t )); errorCount++; console.error(`Failed to upload ${task.key}:`, error); } } if (errorCount === 0) { if (hasRelativePaths && folders.size > 0) { const folderNames = Array.from(folders).join(', '); toast.success(`Successfully uploaded ${successCount} file${successCount > 1 ? 's' : ''} from ${folders.size} folder${folders.size > 1 ? 's' : ''} (${folderNames})`); } else { toast.success(`Successfully uploaded ${successCount} file${successCount > 1 ? 's' : ''}`); } } else if (successCount > 0) { toast.warning(`Uploaded ${successCount} file${successCount > 1 ? 's' : ''}, ${errorCount} failed`); } else { toast.error(`Failed to upload ${errorCount} file${errorCount > 1 ? 's' : ''}`); } if (clearTasksTimerRef.current) clearTimeout(clearTasksTimerRef.current); clearTasksTimerRef.current = setTimeout(() => { setUploadTasks([]); clearTasksTimerRef.current = null; }, 3000); await fetchObjects(currentContinuationToken, true); return successCount > 0; }, [bucketName, currentPath, currentContinuationToken, fetchObjects]); const deleteObject = useCallback(async (key: string) => { if (!bucketName) return false; try { setObjects(prev => prev.filter(obj => obj.key !== key)); await objectsApi.delete(bucketName, key); toast.success(`Object "${key}" deleted successfully`); await fetchObjects(currentContinuationToken, true); return true; } catch (error) { console.error('Delete object error:', error); await fetchObjects(currentContinuationToken, true); return false; } }, [bucketName, currentContinuationToken, fetchObjects]); const deleteMultipleObjects = useCallback(async (keys: string[]) => { if (!bucketName || keys.length === 0) return false; try { setObjects(prev => prev.filter(obj => !keys.includes(obj.key))); await objectsApi.deleteMultiple(bucketName, keys, currentPath || undefined); toast.success(`Successfully deleted ${keys.length} file${keys.length > 1 ? 's' : ''}`); await fetchObjects(currentContinuationToken, true); return true; } catch (error) { console.error('Bulk delete error:', error); await fetchObjects(currentContinuationToken, true); return false; } }, [bucketName, currentPath, currentContinuationToken, fetchObjects]); const createDirectory = useCallback(async (dirName: string) => { if (!bucketName) return false; try { const dirKey = currentPath ? `${currentPath}${dirName}/` : `${dirName}/`; await objectsApi.upload(bucketName, dirKey, new File([], '.keep')); toast.success(`Directory "${dirName}" created successfully`); await fetchObjects(currentContinuationToken, true); return true; } catch (error) { console.error('Create directory error:', error); return false; } }, [bucketName, currentPath, currentContinuationToken, fetchObjects]); return { objects, isLoading, isRefreshing, isNavigating, error, isTruncated, nextContinuationToken, currentContinuationToken, itemsPerPage, setItemsPerPage, fetchObjects, uploadFiles, uploadTasks, deleteObject, deleteMultipleObjects, createDirectory, }; }