import { basename, dirname } from "node:path"; import { createContentFile } from "../content/mod.ts"; import type { Document, DocumentAccessor } from "../model/mod.ts"; import { ContentList } from "./content_list.ts"; import type { IDiffWatcher } from "./watcher.ts"; // refactoring needed. export class ContentDiffHandler { /** content file list waiting to add */ waiting_list: ContentList; /** deleted contents */ tombstone: Map; // hash, contentfile doc_cntr: DocumentAccessor; /** content type of handle */ content_type: string; constructor(cntr: DocumentAccessor, content_type: string) { this.waiting_list = new ContentList(); this.tombstone = new Map(); this.doc_cntr = cntr; this.content_type = content_type; } async setup() { const deleted = await this.doc_cntr.findDeleted(this.content_type); for (const it of deleted) { if (it.deleted_at === null) { // it should not be happened. but when it happens, ignore it. console.error("It should not happen"); continue; } this.tombstone.set(it.content_hash, it); } } register(diff: IDiffWatcher) { diff .on("create", (path) => this.OnCreated(path)) .on("delete", (path) => this.OnDeleted(path)) .on("change", (prev, cur) => this.OnChanged(prev, cur)); } private async OnDeleted(cpath: string) { const basepath = dirname(cpath); const filename = basename(cpath); console.log("deleted ", cpath); // if it wait to add, delete it from waiting list. if (this.waiting_list.hasByPath(cpath)) { this.waiting_list.deleteByPath(cpath); return; } const dbc = await this.doc_cntr.findByPath(basepath, filename); // when there is no related content in db, ignore. if (dbc.length === 0) { console.log("its not in waiting_list and db!!!: ", cpath); return; } const content_hash = dbc[0].content_hash; // When a path is changed, it takes into account when the // creation event occurs first and the deletion occurs, not // the change event. const cf = this.waiting_list.getByHash(content_hash); if (cf) { // if a path is changed, update the changed path. console.log("update path from", cpath, "to", cf.path); const newFilename = basename(cf.path); const newBasepath = dirname(cf.path); this.waiting_list.deleteByHash(content_hash); await this.doc_cntr.update({ id: dbc[0].id, deleted_at: null, filename: newFilename, basepath: newBasepath, }); return; } // invalidate db and add it to tombstone. await this.doc_cntr.update({ id: dbc[0].id, deleted_at: Date.now(), }); this.tombstone.set(content_hash, dbc[0]); } private async OnCreated(cpath: string) { const basepath = dirname(cpath); const filename = basename(cpath); console.log("createContentFile", cpath); const content = createContentFile(this.content_type, cpath); const hash = await content.getHash(); const c = this.tombstone.get(hash); if (c !== undefined) { await this.doc_cntr.update({ id: c.id, deleted_at: null, filename: filename, basepath: basepath, }); } if (this.waiting_list.hasByHash(hash)) { console.log("Hash Conflict!!!"); } this.waiting_list.set(content); } private async OnChanged(prev_path: string, cur_path: string) { const prev_basepath = dirname(prev_path); const prev_filename = basename(prev_path); const cur_basepath = dirname(cur_path); const cur_filename = basename(cur_path); console.log("modify", cur_path, "from", prev_path); const c = this.waiting_list.getByPath(prev_path); if (c !== undefined) { await this.waiting_list.delete(c); const content = createContentFile(this.content_type, cur_path); await this.waiting_list.set(content); return; } const doc = await this.doc_cntr.findByPath(prev_basepath, prev_filename); if (doc.length === 0) { await this.OnCreated(cur_path); return; } await this.doc_cntr.update({ ...doc[0], basepath: cur_basepath, filename: cur_filename, }); } }