()
| 27 | * Custom hook for importing files from remote URLs |
| 28 | */ |
| 29 | export default function useRemoteFileImport() { |
| 30 | const [isImporting, setIsImporting] = useState(false); |
| 31 | const [importProgress, setImportProgress] = useState(0); |
| 32 | const [importStatus, setImportStatus] = useState(''); |
| 33 | const [error, setError] = useState<string | null>(null); |
| 34 | |
| 35 | // DuckDB store access |
| 36 | const duckDB = useDuckDBStore(); |
| 37 | |
| 38 | // CSV parser |
| 39 | const csvParser = useStreamingCSVParser(); |
| 40 | |
| 41 | /** |
| 42 | * Fetch a remote file with progress tracking |
| 43 | */ |
| 44 | const fetchWithProgress = useCallback(async ( |
| 45 | url: string, |
| 46 | options: { |
| 47 | onProgress?: (bytesReceived: number, totalBytes?: number) => void; |
| 48 | headers?: HeadersInit; |
| 49 | timeout?: number; |
| 50 | } = {} |
| 51 | ): Promise<{ |
| 52 | stream: ReadableStream<Uint8Array>; |
| 53 | fileName: string; |
| 54 | fileSize?: number; |
| 55 | fileType?: string; |
| 56 | }> => { |
| 57 | const { onProgress, headers = {}, timeout = 30000 } = options; |
| 58 | |
| 59 | // Set up a timeout controller |
| 60 | const controller = new AbortController(); |
| 61 | const timeoutId = setTimeout(() => controller.abort(), timeout); |
| 62 | |
| 63 | try { |
| 64 | // Make initial HEAD request to get metadata |
| 65 | const headResponse = await fetch(url, { |
| 66 | method: 'HEAD', |
| 67 | headers, |
| 68 | signal: AbortSignal.timeout(5000) // Shorter timeout for HEAD |
| 69 | }).catch(() => null); // Ignore HEAD failures, some servers don't support it |
| 70 | |
| 71 | // Get content length and filename from headers if available |
| 72 | const contentLength = headResponse?.headers.get('content-length'); |
| 73 | const contentDisposition = headResponse?.headers.get('content-disposition'); |
| 74 | const contentType = headResponse?.headers.get('content-type'); |
| 75 | |
| 76 | let fileName = ''; |
| 77 | |
| 78 | // Try to get filename from content-disposition header |
| 79 | if (contentDisposition) { |
| 80 | const filenameMatch = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition); |
| 81 | if (filenameMatch && filenameMatch[1]) { |
| 82 | fileName = filenameMatch[1].replace(/['"]/g, '').trim(); |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | // If no filename from header, extract from URL |
nothing calls this directly
no test coverage detected