import localforage from "localforage" import type { FileInfo } from '~/types/serverFiles' export function useFileSync() { const fileServer = ref([]) const fileLocal = ref<{ name: string; blob: Blob; type: string; url: string }[]>([]) const errorMsg = ref(null) const isSyncing = ref(false) // buat instance localforage langsung di sini const localStore = localforage.createInstance({ name: 'nuxt-offline', storeName: 'file-store', }) // cek file server const fetchServerFiles = async () => { try { const { data, error } = await useFetch('/api/serverFile/files') if (error.value) throw error.value const sortedFiles = data.value?.sort((a, b) => a.name.localeCompare(b.name)) || [] fileServer.value = sortedFiles return fileServer.value } catch (err: any) { errorMsg.value = err.message || 'Gagal mengambil data file' return [] } } //cek indexedDB const getLocalMeta = async (name: string) => { return await localStore.getItem<{ modified: string, size: number }>(`${name}-meta`) } //Simpan ke IndexedDB const saveToIndexedDB = async (file: FileInfo, blob: Blob) => { console.log('Downloaded file to save:', blob); await localStore.setItem(file.name, blob) await localStore.setItem(`${file.name}-meta`, { modified: file.modified, size: file.size, typeFile: file.ext }) } const downloadFile = async (file: FileInfo) => { const response = await fetch(`/api/serverFile/${file.name}`) if (!response.ok) throw new Error(`Gagal download ${file.name}`) const blob = await response.blob() // console.log('blob server', blob ,file.name,file.ext) await saveToIndexedDB(file, blob) } const removeFiles = async (prefix: string) => { const IndexedDBKeys: string[] = [] // Ambil semua key dulu await localStore.iterate((_value, key) => { IndexedDBKeys.push(key) }) // console.log('Removing files with prefix:', IndexedDBKeys) for (const key of IndexedDBKeys) { const baseName = key.split('-')[0].split('.')[0] // console.log('Checking key:', key, 'Base name:', baseName) if (baseName.startsWith(prefix)) { await localStore.removeItem(key) console.log('Removed:', key) } } } // Helper MIME const mimeFromExt = (ext: string) => { const e = ext.replace(/^\./, '').toLowerCase() switch (e) { case 'png': return 'image/png' case 'jpg': case 'jpeg': return 'image/jpeg' case 'gif': return 'image/gif' case 'svg': return 'image/svg+xml' case 'mp4': return 'video/mp4' case 'mkv': return 'video/x-matroska' default: return 'application/octet-stream' } } const syncFiles = async () => { isSyncing.value = true try { const serverFiles = await fetchServerFiles() // console.log('file server :', serverFiles) for (const dataFileServer of serverFiles) { const localMeta = await getLocalMeta(dataFileServer.name) // console.log('fileServer', dataFileServer.name ,'local', localMeta) // console.log('localMeta', localMeta) const mime = mimeFromExt(dataFileServer.ext) // Jika ekstensi tidak dalam mimeFromExt if (mime !== 'application/octet-stream') { const firstNamePart = dataFileServer.name.split('.')[0] const isNew = !localMeta const isUpdated = localMeta && new Date(dataFileServer.modified) > new Date(localMeta.modified) //jika data baru atau di update if (isNew || isUpdated) { // console.log('Download file: ', firstNamePart) await removeFiles(firstNamePart) await downloadFile(dataFileServer) } } } } catch (err: any) { errorMsg.value = err.message || 'Gagal sinkronisasi file' } } // const tampilFileBrowser = async () => { // await syncFiles(); // if (isSyncing.value) { // try { // const localStoreKeys = await localStore.keys() // // console.log('localStore Keys:', localStoreKeys) // const files: { name: string; blob: Blob; type: string; url: string }[] = [] // for (const key of localStoreKeys) { // if (!key.endsWith('-meta')) { // const fileBlob = await localStore.getItem(key) // if (fileBlob instanceof Blob) { // const url = URL.createObjectURL(fileBlob) // files.push({ name: key, blob: fileBlob, type: fileBlob.type, url: url }) // } // } // } // // console.log('Files in IndexedDB:',fileLocal.value) // return fileLocal.value = files // } catch (err) { // console.error('Gagal memuat file dari IndexedDB:', err) // } finally { // isSyncing.value = false // } // } // } const tampilFileBrowser = async () => { await syncFiles(); if (isSyncing.value) { try { const localStoreKeys = await localStore.keys() // console.log('localStore Keys:', localStoreKeys) const files: { name: string; blob: Blob; type: string; url: string }[] = [] for (const key of localStoreKeys) { if (!key.endsWith('-meta')) { const fileBlob = await localStore.getItem(key) if (fileBlob instanceof Blob) { const url = URL.createObjectURL(fileBlob) files.push({ name: key, blob: fileBlob, type: fileBlob.type, url: url }) } } } // console.log('Files in IndexedDB:',files) // console.log('File Server:', fileServer.value) const fileTidaksama = files.filter(local => !fileServer.value.some(fs => fs.name === local.name)) // console.log('File tidak sama dengan server:', fileTidaksama) for (const namaFile of fileTidaksama) { const firstNamePart = namaFile.name.split('.')[0]; console.log('file lokal yang tidak ada di server:', firstNamePart); await removeFiles(firstNamePart) } return fileLocal.value = files } catch (err) { console.error('Gagal memuat file dari IndexedDB:', err) } finally { isSyncing.value = false } } } // Bersihkan object URL biar gak bocor memori onBeforeUnmount(() => { fileLocal.value.forEach(f => URL.revokeObjectURL(f.url)) }) return { fileServer, errorMsg, tampilFileBrowser, fileLocal, } }