49278723b1
- Use Node filesystem APIs instead of shell commands for temp cleanup - Add absolute path helpers for cross-platform compatibility - Generate both main.js and dist/main.js artifacts - Improve error handling in document downloader - Track additional document metadata in sync tracker - Replace Unix-specific `find` with recursive directory traversal The build system now generates two output files (main.js and dist/main.js) for better compatibility with Obsidian's plugin loading. Path handling has been centralized in new utilities to ensure cross-platform behavior, replacing shell commands that had Windows compatibility issues.
112 lines
3.5 KiB
TypeScript
112 lines
3.5 KiB
TypeScript
import { RmapiNode } from "../types";
|
|
import RemarkablePlugin from "../main";
|
|
import { Notice } from "obsidian";
|
|
import { vaultPathToAbsolute, vaultRelativePath } from "../utils/paths";
|
|
|
|
function sanitizeFileName(name: string): string {
|
|
return name.replace(/[\\/:*?"<>|]/g, "_");
|
|
}
|
|
|
|
function sanitizeRemotePath(remotePath: string): string[] {
|
|
return remotePath.split("/").filter(Boolean).map(sanitizeFileName);
|
|
}
|
|
|
|
interface DocumentEntry {
|
|
node: RmapiNode;
|
|
remotePath: string;
|
|
}
|
|
|
|
export class DocumentDownloader {
|
|
plugin: RemarkablePlugin;
|
|
|
|
constructor(plugin: RemarkablePlugin) {
|
|
this.plugin = plugin;
|
|
}
|
|
|
|
async syncAll(): Promise<void> {
|
|
try {
|
|
new Notice("Syncing from reMarkable...");
|
|
const allDocs = await this.listAllDocuments("/");
|
|
|
|
for (const entry of allDocs) {
|
|
await this.downloadDocument(entry);
|
|
}
|
|
|
|
new Notice(`Sync completed! ${allDocs.length} document(s) synced.`);
|
|
} catch (e) {
|
|
new Notice(`Sync failed: ${e}`);
|
|
console.error("Sync error:", e);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
async listAllDocuments(path: string): Promise<DocumentEntry[]> {
|
|
const nodes = await this.plugin.rmapi.list(path);
|
|
let docs: DocumentEntry[] = nodes
|
|
.filter((n) => n.type === "DocumentType")
|
|
.map((n) => ({
|
|
node: n,
|
|
remotePath: path === "/" ? `/${n.name}` : `${path}/${n.name}`,
|
|
}));
|
|
|
|
for (const col of nodes.filter((n) => n.type === "CollectionType")) {
|
|
const subPath = path === "/" ? `/${col.name}` : `${path}/${col.name}`;
|
|
const subDocs = await this.listAllDocuments(subPath);
|
|
docs = docs.concat(subDocs);
|
|
}
|
|
|
|
return docs;
|
|
}
|
|
|
|
async downloadDocument(entry: DocumentEntry): Promise<void> {
|
|
const { node: doc, remotePath } = entry;
|
|
|
|
const synced = await this.plugin.tracker.getSyncedDocument(doc.id);
|
|
if (synced && synced.remoteVersion === doc.version && synced.remoteModified === doc.modifiedClient) {
|
|
return;
|
|
}
|
|
|
|
const pathSegments = sanitizeRemotePath(remotePath);
|
|
const safeName = pathSegments.pop() || sanitizeFileName(doc.name);
|
|
const localDir = vaultRelativePath(this.plugin.settings.downloadPath, ...pathSegments);
|
|
const localPath = vaultRelativePath(localDir, `${safeName}.rm`);
|
|
const absoluteLocalPath = vaultPathToAbsolute(this.plugin, localPath);
|
|
|
|
await this.ensureVaultFolder(localDir);
|
|
|
|
await this.plugin.rmapi.downloadFile(remotePath, absoluteLocalPath);
|
|
|
|
let hasPdf = false;
|
|
if (this.plugin.settings.convertToPdf) {
|
|
const pdfPath = vaultRelativePath(localDir, `${safeName}.pdf`);
|
|
await this.plugin.rmapi.downloadAnnotatedPdf(remotePath, vaultPathToAbsolute(this.plugin, pdfPath));
|
|
hasPdf = true;
|
|
}
|
|
|
|
await this.plugin.tracker.trackDocument(doc, localPath, hasPdf, false);
|
|
|
|
if (this.plugin.settings.enableHandwritingMd) {
|
|
const mdPath = vaultRelativePath(localDir, `${safeName}.md`);
|
|
const mdResult = await this.plugin.ocrPipeline.processDocument(localPath, mdPath);
|
|
if (mdResult) {
|
|
await this.plugin.tracker.trackDocument(doc, localPath, hasPdf, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async ensureVaultFolder(path: string): Promise<void> {
|
|
const segments = path.split("/").filter(Boolean);
|
|
let current = "";
|
|
for (const segment of segments) {
|
|
current = current ? vaultRelativePath(current, segment) : segment;
|
|
try {
|
|
await this.plugin.app.vault.adapter.mkdir(current);
|
|
} catch (err) {
|
|
if (!(await this.plugin.app.vault.adapter.exists(current))) {
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|