mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
* feat: update filegraph on touch command * feat: update filegraph on mkdir * feat: add different parsing opts for each command * refactor: replace axios with fetch. put handleCommand to a new file * feat: add config for mounting cli in dev stage * refactor: clean up unused validators * feat: add command handler for copy files. reuse resolvePath function * feat: add rm handler, support specific files deletion. Glob not supported yet * feat: handle discover cases that the folder is actually empty * feat: handle discover empty folder better and support rmdir * feat: handle mv command * refactor: remove unused console logs
162 lines
4.7 KiB
TypeScript
162 lines
4.7 KiB
TypeScript
import File, { FileObject } from './file.js'
|
|
import { ParentNotExistsError, FileNotExistsError } from './errors.js'
|
|
import { dirname } from './utils.js'
|
|
|
|
export interface IFileNode {
|
|
path: string
|
|
type: 'file' | 'directory'
|
|
discovered: boolean
|
|
children?: IFileNode[]
|
|
}
|
|
|
|
export default class FileNode extends File implements IFileNode {
|
|
children: FileNode[]
|
|
|
|
constructor(file: FileObject | IFileNode) {
|
|
super(file)
|
|
this.children = []
|
|
if ((<FileNode>file).children)
|
|
this.children = (<FileNode>file).children.map((f) => new FileNode(f))
|
|
}
|
|
|
|
addChild(
|
|
fileInput: FileObject,
|
|
{ makeParents }: { makeParents?: boolean } = {}
|
|
) {
|
|
const file = new FileNode(fileInput)
|
|
if (file.path === this.path) {
|
|
if (!this.discovered) {
|
|
this.discovered = file.discovered
|
|
}
|
|
return
|
|
}
|
|
|
|
let existing = false
|
|
for (const child of this.children) {
|
|
if (child.path === file.path) {
|
|
existing = true
|
|
if (!child.discovered) {
|
|
child.discovered = file.discovered
|
|
}
|
|
break
|
|
}
|
|
}
|
|
if (existing) {
|
|
return
|
|
}
|
|
|
|
const parentPath = dirname(file.path)
|
|
if (parentPath === this.path) {
|
|
// Direct child of this node
|
|
this.children.push(file)
|
|
} else {
|
|
// Recursively call addChild on the correct child node
|
|
const childNode = this.children.find((child) => child.contains(file))
|
|
if (childNode) {
|
|
childNode.addChild(file, { makeParents })
|
|
} else if (makeParents) {
|
|
const parent = new FileNode({
|
|
path: parentPath,
|
|
type: 'directory',
|
|
discovered: false,
|
|
children: [file],
|
|
})
|
|
this.addChild(parent, { makeParents })
|
|
} else {
|
|
throw new ParentNotExistsError(
|
|
`Parent path not found in the tree, ${file.path}, ${this.path}`
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
removeChild(fileToRemove: FileObject) {
|
|
const file = new FileNode(fileToRemove)
|
|
const parentPath = dirname(file.path)
|
|
|
|
if (parentPath === this.path) {
|
|
// Direct child of this node
|
|
this.children = this.children.filter((child) => child.path !== file.path)
|
|
} else {
|
|
// Recursively call removeChild on the correct child node
|
|
const childNode = this.children.find((child) => child.contains(file))
|
|
if (childNode) {
|
|
childNode.removeChild(file)
|
|
} else {
|
|
throw new FileNotExistsError(
|
|
`File ${file.path} does not exist in the tree: ${this.toString()}`
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
merge(fileNode: FileNode) {
|
|
// Merge the children of the provided file node into this node
|
|
// If a child already exists, merge the children of that child
|
|
// If a child does not exist, add it to this node
|
|
|
|
// If this is the same node, merge the children
|
|
if (fileNode.path === this.path) {
|
|
// Update the state of this node
|
|
this.discovered = this.discovered || fileNode.discovered
|
|
|
|
// the provided directory is empty, remove all children
|
|
if (fileNode.empty) {
|
|
this.children = []
|
|
return
|
|
}
|
|
|
|
fileNode.children.forEach((child) => {
|
|
const existingChild = this.children.find((c) => c.path === child.path)
|
|
if (existingChild) {
|
|
existingChild.merge(child)
|
|
} else {
|
|
this.children.push(new FileNode(child))
|
|
}
|
|
})
|
|
|
|
// If a child exists in this node but not in the provided node, remove it
|
|
if (fileNode.children.length > 0) {
|
|
this.children = this.children.filter((child) =>
|
|
fileNode.children.some((c) => c.path === child.path)
|
|
)
|
|
}
|
|
}
|
|
// If this contains the parent node
|
|
else if (this.contains(fileNode)) {
|
|
const childNode = this.children.find((child) => child.contains(fileNode))
|
|
// If a child node exists, merge the provided node into the child node
|
|
if (childNode) {
|
|
childNode.merge(fileNode)
|
|
}
|
|
// If a child node does not exist, add the provided node as a child
|
|
else {
|
|
this.addChild(fileNode, { makeParents: true })
|
|
}
|
|
} else {
|
|
throw new Error(
|
|
`Cannot merge file nodes with different paths: ${this.path}, ${fileNode.path}`
|
|
)
|
|
}
|
|
}
|
|
|
|
toString(level = 0): string {
|
|
let { name } = this
|
|
const indent = ' '.repeat(level)
|
|
name = `${indent}${name}`
|
|
if (this.isDirectory()) name += '/'
|
|
if (!this.discovered) name += `*`
|
|
const childrenString = this.children
|
|
.map((child) => child.toString(level + 1))
|
|
.join('')
|
|
return `${name}\n${childrenString}`
|
|
}
|
|
|
|
include(filePath: string, type?: 'file' | 'directory'): boolean {
|
|
if (this.path === filePath) {
|
|
return type ? this.type === type : true
|
|
}
|
|
return this.children.some((child) => child.include(filePath, type))
|
|
}
|
|
}
|