(
backend: ISyncBackend,
onProgress?: (progress: SyncProgress) => void,
options: SyncFilesOptions = {},
)
| 312 | * Sync book files and covers between local and remote. |
| 313 | */ |
| 314 | export async function syncFiles( |
| 315 | backend: ISyncBackend, |
| 316 | onProgress?: (progress: SyncProgress) => void, |
| 317 | options: SyncFilesOptions = {}, |
| 318 | ): Promise<{ |
| 319 | filesUploaded: number; |
| 320 | filesDownloaded: number; |
| 321 | filesUploadFailed: number; |
| 322 | filesDownloadFailed: number; |
| 323 | }> { |
| 324 | const syncFilesStart = Date.now(); |
| 325 | console.log("[Sync] 📁 Starting file sync..."); |
| 326 | |
| 327 | const adapter = getSyncAdapter(); |
| 328 | const db = await getDB(); |
| 329 | const { setBookSyncStatus } = await import("../db/database"); |
| 330 | const { |
| 331 | forceUploadAll = false, |
| 332 | forceDownloadAll = false, |
| 333 | downloadRemoteBooks = false, |
| 334 | disableUploads = false, |
| 335 | disableRemoteDeletes = false, |
| 336 | } = options; |
| 337 | let filesUploaded = 0; |
| 338 | let filesDownloaded = 0; |
| 339 | let filesUploadFailed = 0; |
| 340 | let filesDownloadFailed = 0; |
| 341 | |
| 342 | const books = await db.select<BookRow>( |
| 343 | "SELECT id, file_path, file_hash, cover_url, title FROM books WHERE deleted_at IS NULL", |
| 344 | [], |
| 345 | ); |
| 346 | |
| 347 | const appDataDir = await adapter.getAppDataDir(); |
| 348 | const currentBookIds = new Set(books.map((b) => b.id)); |
| 349 | |
| 350 | // --- Compute per-book info --- |
| 351 | const bookInfos: BookInfo[] = books.map((book) => { |
| 352 | const fileExt = book.file_path ? getExt(book.file_path) || "epub" : ""; |
| 353 | const coverExt = book.cover_url ? getExt(book.cover_url) || "jpg" : ""; |
| 354 | const localFilePath = book.file_path |
| 355 | ? isAbsoluteOrProtocolPath(book.file_path) |
| 356 | ? book.file_path |
| 357 | : adapter.joinPath(appDataDir, book.file_path) |
| 358 | : ""; |
| 359 | const localCoverPath = book.cover_url |
| 360 | ? isAbsoluteOrProtocolPath(book.cover_url) |
| 361 | ? book.cover_url |
| 362 | : adapter.joinPath(appDataDir, book.cover_url) |
| 363 | : ""; |
| 364 | return { |
| 365 | book, |
| 366 | fileExt, |
| 367 | coverExt, |
| 368 | localFilePath, |
| 369 | localCoverPath, |
| 370 | remoteDir: buildBookRemoteDir(book), |
| 371 | expectedFolderName: buildBookFolderName(book), |
no test coverage detected