All files / src/fs/base tree.ts

0% Statements 0/88
0% Branches 0/48
0% Functions 0/23
0% Lines 0/84

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196                                                                                                                                                                                                                                                                                                                                                                                                       
/** @internal */
 
/** @internal */
import * as pathUtil from '../path'
import { Tree, File, UnixTree, BaseLinks, UpdateCallback } from '../types'
import { SemVer } from '../semver'
import * as check from '../types/check'
import { AddResult, CID, FileContent } from '../../ipfs'
import { Maybe } from '../../common'
 
 
abstract class BaseTree implements Tree, UnixTree {
 
  version: SemVer
 
  constructor(version: SemVer) {
    this.version = version
  }
 
  async put(): Promise<CID> {
    const { cid } = await this.putDetailed()
    return cid
  }
 
  async ls(path: string): Promise<BaseLinks> {
    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: string): Promise<FileContent> {
    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: string): Promise<this> {
    return this.mkdirRecurse(path, () => this.put())
  }
 
  async mkdirRecurse(path: string, onUpdate: Maybe<UpdateCallback>): Promise<this> {
    const { head, nextPath } = pathUtil.takeHead(path)
    if(head === null){
      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: ${path}`)
    }
    if(nextPath !== null){
      await child.mkdirRecurse(nextPath, () => this.updateDirectChild(child, head, onUpdate) )
    }
    return this
  }
 
  async add(path: string, content: FileContent): Promise<this> {
    await this.addRecurse(path, content, () => this.put())
    return this
  }
 
  async addRecurse(path: string, content: FileContent, onUpdate: Maybe<UpdateCallback>): Promise<this> {
    const { head, nextPath } = pathUtil.takeHead(path)
    if(head === null){
      throw new Error("Invalid path: empty")
    }
    if(nextPath === null) {
      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: ${path}`)
      }
      await child.addRecurse(nextPath, content, async () => {
        await this.updateDirectChild(child, head, onUpdate)
      })
    }
    return this
  }
 
  async rm(path: string): Promise<this> {
    await this.rmRecurse(path, () => this.put())
    return this
  }
 
  async rmRecurse(path: string, onUpdate: Maybe<UpdateCallback>): Promise<this> {
    const { head, nextPath } = pathUtil.takeHead(path)
    if(head === null){
      throw new Error("Invalid path: empty")
    }
    if(nextPath === null) {
      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: ${path}`)
      }
      await child.rmRecurse(nextPath, async () => {
        await this.updateDirectChild(child, head, onUpdate)
      })
    }
    return this
  }
 
  async mv(from: string, to: string): Promise<this> {
    const node = await this.get(from)
    if (node === null) {
      throw new Error(`Path does not exist: ${from}`)
    }
 
    let { tail, parentPath } = pathUtil.takeTail(to)
    parentPath = parentPath || ''
 
    if (tail === null) {
      throw new Error(`Path does not exist: ${to}`)
    }
 
    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: ${parentPath}`)
    }
 
    const toParts = pathUtil.splitParts(to)
 
    await this.rm(from)
    await [...toParts].reverse().reduce((acc, part, idx) => {
      return acc.then(async child => {
        const childParentParts = toParts.slice(0, -(idx + 1))
        const tree = childParentParts.length
          ? await this.get(pathUtil.join(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: string): Promise<boolean> {
    const node = await this.get(path)
    return node !== null
  }
 
  read(path: string): Promise<Tree | File | null> {
    return this.get(path)
  }
 
  write(path: string, content: FileContent): 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)
  }
 
  abstract createChildTree(name: string, onUpdate: Maybe<UpdateCallback>): Promise<Tree>
  abstract createOrUpdateChildFile(content: FileContent, name: string, onUpdate: Maybe<UpdateCallback>): Promise<File>
 
  abstract putDetailed(): Promise<AddResult>
 
  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: string): Promise<Tree | File | null>
 
  abstract updateLink(name: string, result: AddResult): this
  abstract getLinks(): BaseLinks
}
 
 
export default BaseTree