import fs from "node:fs/promises"; import { createWriteStream } from "node:fs"; import stream from "node:stream/promises"; import { Zip, ZipPassThrough } from "fflate"; import { Database } from "./database"; export type Book = { id: number; platform: "dmm-books" | "google-play-books"; readerUrl: string; title: string; authors: Array; }; export function createLibrary(db: Database) { return { async add(readerUrlOrBook: string | Book) { const platform = "dmm-books"; if (typeof readerUrlOrBook === "string") { await db.run( `insert into books(platform_id, reader_url) values((select id from platforms where name = ?), ?)`, platform, readerUrlOrBook, ); return; } await db.run( `\ insert into books( platform_id, reader_url, title, authors) values((select id from platforms where name = ?), ?, ?, ?) on conflict(reader_url) do update set title = excluded.title, authors = excluded.authors `, platform, readerUrlOrBook.readerUrl, readerUrlOrBook.title, JSON.stringify(readerUrlOrBook.authors), ); }, async delete(id: number) { await db.run(`delete from books where id = ?`, id); }, async get(id: number): Promise { const row = await db.get( `select books.id, platforms.name as platform, books.reader_url as readerUrl, books.title, books.authors from books left join platforms on books.platform_id = platforms.id where books.id = ?`, id, ); const book: Book | undefined = row && { ...row, authors: JSON.parse(row.authors), }; return book; }, async getBooks(): Promise> { const rows = await db.all( `select books.id, platforms.name as platform, books.reader_url as readerUrl, books.title, books.authors from books left join platforms on books.platform_id = platforms.id`, ); const books: Array = rows.map((row) => ({ ...row, authors: JSON.parse(row.authors), })); return books; }, async archive(dir: string) { const bookDirs = await fs.readdir(dir, { withFileTypes: true }); for (const bookDir of bookDirs) { if (!bookDir.isDirectory()) { continue; } const path = `${bookDir.path}/${bookDir.name}`; const out = createWriteStream(`${path}.cbz`); const zip = new Zip(function cb(err, data, final) { if (err) { out.destroy(err); return; } out[final ? "end" : "write"](data); }); const files = await fs.readdir(path); for (const file of files) { const data = new ZipPassThrough(file); zip.add(data); const buffer = await fs.readFile(`${path}/${file}`); data.push(buffer, true); } zip.end(); await stream.finished(out); await fs.rm(path, { recursive: true }); } }, }; }