216 lines
7.4 KiB
TypeScript
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,
|
|
|
|
}
|
|
}
|