src/fs/bare/tree.ts
import type { CID } from "multiformats/cid"
import * as check from "../types/check.js"
import * as protocol from "../protocol/index.js"
import * as ipfs from "../../ipfs/index.js"
import * as link from "../link.js"
import { AddResult, FileContent } from "../../ipfs/index.js"
import { HardLinks, BaseLinks, Tree, File, Puttable, UpdateCallback } from "../types.js"
import { Maybe, decodeCID } from "../../common/index.js"
import { Path } from "../../path.js"
import BareFile from "../bare/file.js"
import BaseTree from "../base/tree.js"
class BareTree extends BaseTree {
links: HardLinks
children: { [name: string]: Tree | File }
constructor(links: HardLinks) {
super()
this.links = links
this.children = {}
}
static async empty(): Promise<BareTree> {
return new BareTree({})
}
static async fromCID(cid: CID): Promise<BareTree> {
const links = link.arrToMap(
(await ipfs.ls(cid)).map(link.fromFSFile)
)
return new BareTree(links)
}
static fromLinks(links: HardLinks): BareTree {
return new BareTree(links)
}
async createChildTree(name: string, onUpdate: Maybe<UpdateCallback>): Promise<Tree> {
const child = await BareTree.empty()
const existing = this.children[name]
if (existing) {
if (check.isFile(existing)) {
throw new Error(`There is a file at the given path: ${name}`)
}
return existing
}
await this.updateDirectChild(child, name, onUpdate)
return child
}
async createOrUpdateChildFile(content: FileContent, name: string, onUpdate: Maybe<UpdateCallback>): Promise<BareFile> {
const existing = await this.getDirectChild(name)
let file: BareFile
if (existing === null) {
file = await BareFile.create(content)
} else if (BareFile.instanceOf(existing)) {
file = await existing.updateContent(content)
} else {
throw new Error(`There is already a directory with that name: ${name}`)
}
await this.updateDirectChild(file, name, onUpdate)
return file
}
async putDetailed(): Promise<AddResult> {
return protocol.basic.putLinks(this.links)
}
async putAndUpdateLink(child: Puttable, name: string, onUpdate: Maybe<UpdateCallback>): Promise<this> {
const details = await child.putDetailed()
this.updateLink(name, details)
onUpdate && await onUpdate()
return this
}
async updateDirectChild(child: Tree | File, name: string, onUpdate: Maybe<UpdateCallback>): Promise<this> {
this.children[name] = child
return this.putAndUpdateLink(child, name, onUpdate)
}
removeDirectChild(name: string): this {
delete this.links[name]
if (this.children[name]) {
delete this.children[name]
}
return this
}
async getDirectChild(name: string): Promise<Tree | File | null> {
if (this.children[name]) {
return this.children[name]
}
const link = this.links[name] || null
if (link === null) return null
const cid = decodeCID(link.cid)
const child = link.isFile
? await BareFile.fromCID(cid)
: await BareTree.fromCID(cid)
// check that the child wasn't added while retrieving the content from the network
if (this.children[name]) {
return this.children[name]
}
this.children[name] = child
return child
}
// TODO
async get(path: Path): Promise<Tree | File | null> {
const [ head, ...nextPath ] = path
if (!head) return this
const nextTree = await this.getDirectChild(head)
if (!nextPath.length) {
return nextTree
} else if (nextTree === null || check.isFile(nextTree)) {
return null
}
return nextTree.get(nextPath)
}
updateLink(name: string, result: AddResult): this {
const { cid, size, isFile } = result
this.links[name] = link.make(name, cid, isFile, size)
return this
}
getLinks(): BaseLinks {
return this.links
}
}
export default BareTree