* Bring the remote state for one book up to the current layout: * - If a folder exists under a stale name (title changed) → rename folder contents. * - If only legacy `/file/{id}.ext` / `/cover/{id}.ext` exist → move them into the per-book folder. * - Otherwise inspect the existing folder t
( backend: ISyncBackend, info: BookInfo, listings: RemoteListings, )
| 829 | * Returns whether file/cover are now present at their *new* canonical paths. |
| 830 | */ |
| 831 | async function migrateBookRemoteState( |
| 832 | backend: ISyncBackend, |
| 833 | info: BookInfo, |
| 834 | listings: RemoteListings, |
| 835 | ): Promise<MigrationResult> { |
| 836 | const { book } = info; |
| 837 | const existingFolderName = listings.bookDirByBookId.get(book.id); |
| 838 | let fileAtNew = false; |
| 839 | let coverAtNew = false; |
| 840 | let fileSize = listings.fileSizeByBookId.get(book.id); |
| 841 | let coverSize = listings.coverSizeByBookId.get(book.id); |
| 842 | |
| 843 | if (existingFolderName) { |
| 844 | if (existingFolderName !== info.expectedFolderName) { |
| 845 | // Title changed (or sanitized form changed): rename folder by moving every child. |
| 846 | const oldDir = `${REMOTE_BOOKS_ROOT}/${existingFolderName}`; |
| 847 | let oldFiles: RemoteFile[] = []; |
| 848 | try { |
| 849 | oldFiles = await backend.listDir(oldDir); |
| 850 | } catch (e) { |
| 851 | console.warn(`[Sync] Failed to list old book folder ${oldDir}:`, e); |
| 852 | } |
| 853 | for (const f of oldFiles) { |
| 854 | if (f.isDirectory) continue; |
| 855 | const ext = getExt(f.name); |
| 856 | if (!ext) continue; |
| 857 | const cover = isCoverFileName(f.name); |
| 858 | const target = cover ? buildBookRemoteCover(book, ext) : buildBookRemoteFile(book, ext); |
| 859 | try { |
| 860 | await backend.move(`${oldDir}/${f.name}`, target); |
| 861 | if (cover) { |
| 862 | coverAtNew = true; |
| 863 | if (isPositiveFiniteNumber(f.size)) coverSize = f.size; |
| 864 | } else { |
| 865 | fileAtNew = true; |
| 866 | if (isPositiveFiniteNumber(f.size)) fileSize = f.size; |
| 867 | } |
| 868 | } catch (e) { |
| 869 | console.warn(`[Sync] Folder-rename MOVE failed for ${book.id} (${f.name}):`, e); |
| 870 | if (await backend.exists(target)) { |
| 871 | try { |
| 872 | await backend.delete(`${oldDir}/${f.name}`); |
| 873 | } catch {} |
| 874 | if (cover) { |
| 875 | coverAtNew = true; |
| 876 | if (isPositiveFiniteNumber(f.size)) coverSize = f.size; |
| 877 | } else { |
| 878 | fileAtNew = true; |
| 879 | if (isPositiveFiniteNumber(f.size)) fileSize = f.size; |
| 880 | } |
| 881 | } |
| 882 | } |
| 883 | } |
| 884 | try { |
| 885 | await backend.delete(oldDir); |
| 886 | } catch {} |
| 887 | // Update in-memory map so orphan cleanup downstream doesn't see this as unknown. |
| 888 | listings.bookDirByBookId.delete(book.id); |
no test coverage detected