UNPKG

@oddjs/odd

Version:
240 lines (183 loc) 6.72 kB
import type { CID } from "multiformats/cid" import * as Check from "../types/check.js" import * as Pathing from "../../path/index.js" import { Maybe } from "../../common/index.js" import { Segments as Path } from "../../path/index.js" import { PutResult } from "../../components/depot/implementation.js" import { Tree, File, UnixTree, Links, UpdateCallback } from "../types.js" abstract class BaseTree implements Tree, UnixTree { readOnly: boolean constructor() { this.readOnly = false } async put(): Promise<CID> { const { cid } = await this.putDetailed() return cid } async ls(path: Path): Promise<Links> { const dir = await this.get(path) if (dir === null) { throw new Error("Path does not exist") } else if (Check.isFile(dir)) { throw new Error("Can not `ls` a file") } return dir.getLinks() } async cat(path: Path): Promise<Uint8Array> { const file = await this.get(path) if (file === null) { throw new Error("Path does not exist") } else if (!Check.isFile(file)) { throw new Error("Can not `cat` a directory") } return file.content } async mkdir(path: Path): Promise<this> { return this.mkdirRecurse(path, () => this.put()) } async mkdirRecurse(path: Path, onUpdate: Maybe<UpdateCallback>): Promise<this> { const [ head, ...nextPath ] = path if (!head) { throw new Error("Invalid path: empty") } const child = await this.getOrCreateDirectChild(head, onUpdate) if (Check.isFile(child)) { throw new Error(`There is a file along the given path: ${Pathing.log(path)}`) } if (nextPath.length) { await child.mkdirRecurse(nextPath, () => this.updateDirectChild(child, head, onUpdate)) } return this } async add(path: Path, content: Uint8Array): Promise<this> { await this.addRecurse(path, content, () => this.put()) return this } async addRecurse(path: Path, content: Uint8Array, onUpdate: Maybe<UpdateCallback>): Promise<this> { const [ head, ...nextPath ] = path if (!head) { throw new Error("Invalid path: empty") } if (nextPath.length === 0) { await this.createOrUpdateChildFile(content, head, onUpdate) } else { const child = await this.getOrCreateDirectChild(head, onUpdate) if (Check.isFile(child)) { throw new Error(`There is a file along the given path: ${Pathing.log(path)}`) } await child.addRecurse(nextPath, content, async () => { await this.updateDirectChild(child, head, onUpdate) }) } return this } async rm(path: Path): Promise<this> { await this.rmRecurse(path, () => this.put()) return this } async rmRecurse(path: Path, onUpdate: Maybe<UpdateCallback>): Promise<this> { const [ head, ...nextPath ] = path if (!head) { throw new Error("Invalid path: empty") } if (nextPath.length === 0) { this.removeDirectChild(head) onUpdate && await onUpdate() } else { const child = await this.getDirectChild(head) if (child === null) { throw new Error("Invalid path: does not exist") } else if (Check.isFile(child)) { throw new Error(`There is a file along the given path: ${Pathing.log(path)}`) } await child.rmRecurse(nextPath, async () => { await this.updateDirectChild(child, head, onUpdate) }) } return this } async mv(from: Path, to: Path): Promise<this> { const node = await this.get(from) if (node === null) { throw new Error(`Path does not exist: ${Pathing.log(from)}`) } if (to.length < 1) { throw new Error(`Path does not exist: ${Pathing.log(to)}`) } const parentPath = to.slice(0, -1) let parent = await this.get(parentPath) if (!parent) { await this.mkdir(parentPath) parent = await this.get(parentPath) } else if (Check.isFile(parent)) { throw new Error(`Can not \`mv\` to a file: ${Pathing.log(parentPath)}`) } await this.rm(from) await [ ...to ].reverse().reduce((acc, part, idx) => { return acc.then(async child => { const childParentParts = to.slice(0, -(idx + 1)) const tree = childParentParts.length ? await this.get(childParentParts) : this if (tree && !Check.isFile(tree)) { await tree.updateDirectChild(child, part, null) return tree } else { throw new Error("Failed to update tree while moving node") } }) }, Promise.resolve(node)) return this } async exists(path: Path): Promise<boolean> { const node = await this.get(path) return node !== null } read(path: Path): Promise<Tree | File | null> { return this.get(path) } write(path: Path, content: Uint8Array): Promise<this> { return this.add(path, content) } async getOrCreateDirectChild(name: string, onUpdate: Maybe<UpdateCallback>): Promise<Tree | File> { const node = await this.getDirectChild(name) return node !== null ? node : this.createChildTree(name, onUpdate) } /** * `put` is called on child (result of promise) in `updateDirectChild` * Then for the outermost parent, `put` should be called manually. */ async updateChild(child: Tree | File, path: Path): Promise<this> { const chain: [ string, Tree ][] = [] await path.reduce(async (promise: Promise<Tree>, p, idx) => { const parent = await promise chain.push([ p, parent ]) if (idx + 1 === path.length) { return parent } const c = await parent.getDirectChild(p) if (!Check.isTree(c)) { const pathSoFar = path.slice(idx + 1) throw new Error(`Expected a tree at the given path: ${Pathing.log(pathSoFar)}`) } return c }, Promise.resolve(this)) await chain.reverse().reduce(async (promise, [ name, parent ]) => { await parent.updateDirectChild(await promise, name, null) return parent }, Promise.resolve(child)) return this } abstract createChildTree(name: string, onUpdate: Maybe<UpdateCallback>): Promise<Tree> abstract createOrUpdateChildFile(content: Uint8Array, name: string, onUpdate: Maybe<UpdateCallback>): Promise<File> abstract putDetailed(): Promise<PutResult> abstract updateDirectChild(child: Tree | File, name: string, onUpdate: Maybe<UpdateCallback>): Promise<this> abstract removeDirectChild(name: string): this abstract getDirectChild(name: string): Promise<Tree | File | null> abstract get(path: Path): Promise<Tree | File | null> abstract updateLink(name: string, result: PutResult): this abstract getLinks(): Links } export default BaseTree