Files
qris_bank_jatim/composables/useFileSync.ts
bagus-arie05 783f143902 final update
2025-11-11 14:23:30 +07:00

216 lines
7.4 KiB
TypeScript

import localforage from "localforage"
import type { FileInfo } from '~/types/serverFiles'
export function useFileSync() {
const fileServer = ref<FileInfo[]>([])
const fileLocal = ref<{ name: string; blob: Blob; type: string; url: string }[]>([])
const errorMsg = ref<string | null>(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<FileInfo[]>('/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<Blob>(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<Blob>(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,
}
}