(
backend: ISyncBackend,
bookId: string,
filePath: string,
onProgress?: (progress: { downloaded: number; total: number }) => void,
)
| 1236 | * pushed (and therefore not yet migrated) can still serve the book. |
| 1237 | */ |
| 1238 | export async function downloadBookFile( |
| 1239 | backend: ISyncBackend, |
| 1240 | bookId: string, |
| 1241 | filePath: string, |
| 1242 | onProgress?: (progress: { downloaded: number; total: number }) => void, |
| 1243 | ): Promise<DownloadBookOutcome> { |
| 1244 | const adapter = getSyncAdapter(); |
| 1245 | const { setBookSyncStatus } = await import("../db/database"); |
| 1246 | |
| 1247 | try { |
| 1248 | const ext = getExt(filePath) || "epub"; |
| 1249 | |
| 1250 | // Resolve book title for new-path computation. |
| 1251 | const db = await getDB(); |
| 1252 | const rows = await db.select<{ id: string; title: string }>( |
| 1253 | "SELECT id, title FROM books WHERE id = ?", |
| 1254 | [bookId], |
| 1255 | ); |
| 1256 | const book = rows[0] ?? null; |
| 1257 | |
| 1258 | const newPath = book ? buildBookRemoteFile(book, ext) : ""; |
| 1259 | const legacyPath = `${REMOTE_FILES}/${bookId}.${ext}`; |
| 1260 | |
| 1261 | onProgress?.({ downloaded: 0, total: 100 }); |
| 1262 | |
| 1263 | // Track whether *every* failure we saw was a 404. If so, the remote |
| 1264 | // genuinely doesn't have the file. If at least one attempt failed with |
| 1265 | // some other error, it's an "error" outcome — the caller can retry. |
| 1266 | let sawNon404Error = false; |
| 1267 | const noteFailure = (e: unknown) => { |
| 1268 | const msg = (e as { message?: string })?.message ?? ""; |
| 1269 | if (!/404|not found/i.test(msg)) sawNon404Error = true; |
| 1270 | }; |
| 1271 | |
| 1272 | const appDataDir = await adapter.getAppDataDir(); |
| 1273 | const localPath = isAbsoluteOrProtocolPath(filePath) |
| 1274 | ? filePath |
| 1275 | : adapter.joinPath(appDataDir, filePath); |
| 1276 | const reportDownloadProgress = (loaded: number, total: number) => { |
| 1277 | if (total > 0) onProgress?.({ downloaded: loaded, total }); |
| 1278 | }; |
| 1279 | let downloaded = false; |
| 1280 | if (newPath) { |
| 1281 | try { |
| 1282 | await downloadRemoteFileToPath(backend, newPath, localPath, reportDownloadProgress); |
| 1283 | downloaded = true; |
| 1284 | console.log(`[Sync] Downloaded ${newPath} (new layout)`); |
| 1285 | } catch (e) { |
| 1286 | noteFailure(e); |
| 1287 | const msg = (e as { message?: string })?.message ?? ""; |
| 1288 | if (!/404|not found/i.test(msg)) { |
| 1289 | console.warn(`[Sync] New-layout fetch failed (${msg}); trying legacy path`); |
| 1290 | } |
| 1291 | } |
| 1292 | } |
| 1293 | if (!downloaded) { |
| 1294 | try { |
| 1295 | await downloadRemoteFileToPath(backend, legacyPath, localPath, reportDownloadProgress); |
no test coverage detected