* Local filesystem backend for the LAN server. * Serves files from the device's own data directory for sync.
| 28 | * Serves files from the device's own data directory for sync. |
| 29 | */ |
| 30 | class LocalFsBackend implements ISyncBackend { |
| 31 | readonly type = "lan" as const; |
| 32 | |
| 33 | private async getCurrentDeviceSnapshot(): Promise<DeviceSyncPayload> { |
| 34 | return collectChanges(0); |
| 35 | } |
| 36 | |
| 37 | private async getDataDir(): Promise<string> { |
| 38 | const adapter = getSyncAdapter(); |
| 39 | return await adapter.getAppDataDir(); |
| 40 | } |
| 41 | |
| 42 | private async mapVirtualPath(path: string): Promise<string> { |
| 43 | const adapter = getSyncAdapter(); |
| 44 | const dataDir = await this.getDataDir(); |
| 45 | |
| 46 | // Remote structure mapping to local storage: |
| 47 | // /readany/data/readany.db -> local DB file |
| 48 | // /readany/data/books/{title-id}/{title}.ext -> local books/{id}.ext or covers/{id}.ext |
| 49 | // /readany/data/file/* -> local books/* (legacy layout) |
| 50 | // /readany/data/cover/* -> local covers/* (legacy layout) |
| 51 | |
| 52 | if (path === "/readany/data/readany.db") { |
| 53 | return await adapter.getDatabasePath(); |
| 54 | } |
| 55 | |
| 56 | if (path.startsWith(`${REMOTE_BOOKS_ROOT}/`)) { |
| 57 | // /readany/data/books/{title-id}/{filename} |
| 58 | const rest = path.substring(REMOTE_BOOKS_ROOT.length + 1); |
| 59 | const slashIdx = rest.indexOf("/"); |
| 60 | if (slashIdx === -1) { |
| 61 | // Caller asked to map the dir itself — return books root local equivalent (best-effort). |
| 62 | return adapter.joinPath(dataDir, "books"); |
| 63 | } |
| 64 | const folderName = rest.substring(0, slashIdx); |
| 65 | const fileName = rest.substring(slashIdx + 1); |
| 66 | const bookId = parseBookFolderName(folderName) ?? folderName.split("-").pop() ?? folderName; |
| 67 | const ext = fileName.includes(".") ? fileName.split(".").pop()! : ""; |
| 68 | const targetSubdir = isCoverFileName(fileName) ? "covers" : "books"; |
| 69 | return adapter.joinPath(dataDir, targetSubdir, ext ? `${bookId}.${ext}` : bookId); |
| 70 | } |
| 71 | |
| 72 | if (path.startsWith("/readany/data/file")) { |
| 73 | const subPath = path.substring("/readany/data/file".length); |
| 74 | return adapter.joinPath(dataDir, "books", subPath); |
| 75 | } |
| 76 | |
| 77 | if (path.startsWith("/readany/data/cover")) { |
| 78 | const subPath = path.substring("/readany/data/cover".length); |
| 79 | return adapter.joinPath(dataDir, "covers", subPath); |
| 80 | } |
| 81 | |
| 82 | if (path.startsWith("/readany/data/")) { |
| 83 | const subPath = path.substring("/readany/data/".length); |
| 84 | return adapter.joinPath(dataDir, subPath); |
| 85 | } |
| 86 | |
| 87 | return adapter.joinPath(dataDir, path); |
nothing calls this directly
no outgoing calls
no test coverage detected