import { runCommand } from "./process"; /** * Extract a .rm file (which is a zip archive) to a temporary directory. * Returns the path to the extraction directory. */ export async function extractRmFile(rmPath: string, outputDir: string): Promise { // .rm files are zip archives const { code, stderr } = await runCommand("unzip", ["-o", rmPath, "-d", outputDir]); if (code !== 0) { throw new Error(`Failed to extract .rm file: ${stderr}`); } return outputDir; } /** * List the contents of the extracted .rm archive. */ export async function listRmContents(extractedDir: string): Promise { const { stdout, code, stderr } = await runCommand("find", [extractedDir, "-type", "f"]); if (code !== 0) { throw new Error(`Failed to list .rm contents: ${stderr}`); } return stdout.trim().split("\n").filter(Boolean); } /** * Check if a document is a notebook (handwritten) or a PDF/ebook. * On reMarkable v3.x, notebooks have .rm page files and no content.pdf. * Falls back to rendering a test page if ambiguous. */ export async function isNotebook(extractedDir: string): Promise { const contents = await listRmContents(extractedDir); // Notebooks have .rm page files and no embedded PDF content const hasRmPages = contents.some((f) => f.endsWith(".rm") && !f.endsWith(".zip")); const hasContentPdf = contents.some((f) => f.endsWith("content.pdf") || f.endsWith(".pdf")); // Clear case if (hasRmPages && !hasContentPdf) return true; if (!hasRmPages || hasContentPdf) return false; // Ambiguous: try to render a page const pageFiles = contents.filter((f) => f.endsWith(".rm") && !f.endsWith(".zip")); if (pageFiles.length === 0) return false; try { // Try rendering the first page const { code } = await runCommand("file", [pageFiles[0]]); // If file command succeeds and identifies it as data, assume notebook return code === 0; } catch { return false; } } /** * Get the list of page .rm files from an extracted notebook. * Returns basenames only. * Recursively searches for .rm files (not just root). */ export async function getPageFiles(extractedDir: string): Promise { const contents = await listRmContents(extractedDir); return contents.filter((f) => f.endsWith(".rm") && !f.endsWith(".zip")).sort(); }