You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
maestro/app/classes/logical/UniversalPath.js

168 lines
4.7 KiB

const { Injectable } = require('flitter-di')
const moment = require('moment')
class UniversalPath extends Injectable {
static get services() {
return [...super.services, 'hosts']
}
static PATH_TYPE_FILE = Symbol('file')
static PATH_TYPE_DIRECTORY = Symbol('directory')
static PATH_TYPE_UNKNOWN = Symbol('unknown')
_directory_classify_command = 'test -d'
_file_classify_command = 'test -f'
_datetime_format = 'Y-MM-DD_HH-mm-ss-SSSS'
static fromString(path_string) {
const parts = path_string.split('::')
const hostname = parts.length > 1 ? parts[0] : 'localhost'
const path = parts.length > 1 ? parts[1] : parts[0]
const host = this.prototype.hosts.get(hostname)
return new this(host, path)
}
constructor(host, host_path, type) {
super()
this._host = host
this._path = host_path
this._type = type ? type : this.constructor.PATH_TYPE_UNKNOWN
}
get host() {
return this._host
}
get path() {
return this._path
}
get type() {
return this._type
}
async resolve() {
return this._host.resolve_path(this._path)
}
is_directory() {
return this.type === this.constructor.PATH_TYPE_DIRECTORY
}
is_file() {
return this.type === this.constructor.PATH_TYPE_FILE
}
is_valid() {
return this.is_file() || this.is_directory()
}
async classify() {
const dir_result = await this._host.execute(`${this._directory_classify_command} ${this._path}`)
if ( dir_result.exit_code === 0 ) {
this._type = this.constructor.PATH_TYPE_DIRECTORY
return
}
const file_result = await this._host.execute(`${this._file_classify_command} ${this._path}`)
if ( file_result.exit_code === 0 ) {
this._type = this.constructor.PATH_TYPE_FILE
return
}
this._type = this.constructor.PATH_TYPE_UNKNOWN
}
async open_read_stream() {
await this.classify()
if ( this._type === this.constructor.PATH_TYPE_FILE ) {
return this._host.open_file_read_stream(this._path)
} else {
throw new Error('Maestro does not support generation of read streams for this type of path: '+this._type.description)
}
}
async open_write_stream() {
await this.classify()
if (
[this.constructor.PATH_TYPE_FILE, this.constructor.PATH_TYPE_UNKNOWN].includes(this._type)
) {
return this._host.open_file_write_stream(this._path)
} else {
throw new Error('Maestro does not support generation of write streams for this type of path: '+this._type.description)
}
}
async copy_from(other_path) {
const read = await other_path.open_read_stream()
const write = await this.open_write_stream()
await read.pipe(write)
}
async copy_to(other_path) {
const read = await this.open_read_stream()
const write = await other_path.open_write_stream()
await read.pipe(write)
}
async unlink() {
await this.host.delete_path(this._path)
}
async move_from(other_path) {
await this.copy_from(other_path)
await other_path.unlink()
}
async move_to(other_path) {
await this.copy_to(other_path)
await this.unlink()
}
async touch(directory = false) {
await this.classify()
if ( !directory ) {
const base_dir = this._path.split('/').slice(0, -1).join('/')
await this._host.run(`mkdir -p ${base_dir}`)
await this._host.run(`touch ${this._path}`)
} else {
await this._host.run(`mkdir -p ${this._path}`)
}
}
async echo(text) {
await this.classify()
if ( this.is_directory() ) {
throw new Error('Cannot echo to a directory.')
}
if ( !this.is_valid() ) {
await this.touch()
}
const ws = await this.open_write_stream()
ws.write(text)
return new Promise(resolve => {
ws.on('finish', () => resolve())
ws.end()
})
}
async hash() {
const line = await this._host.run_line_result(`sha256sum ${this._path}`)
return line.split(' ')[0]
}
async backup() {
const now = moment().format(this._datetime_format)
const backup_path = `${this}.maestro-backup-${now}`
const backup_target = this.constructor.fromString(backup_path)
await this.copy_to(backup_target)
}
toString() {
return `${this._host.name}::${this._path}`
}
}
module.exports = exports = UniversalPath