Last active
November 10, 2024 21:29
-
-
Save zerkalica/be78b8c7d4c23835049a0f29621b1f4c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace $ { | |
export type $mol_file_type = 'file' | 'dir' | 'link' | |
export interface $mol_file_stat { | |
type: $mol_file_type | |
size: number | |
atime: Date | |
mtime: Date | |
ctime: Date | |
} | |
// export class $mol_file_not_found extends Error {} | |
export class $mol_file extends $mol_object { | |
@ $mol_mem_key | |
static absolute( path : string ) { | |
return this.make({ | |
path : $mol_const( path ) | |
}) | |
} | |
static relative( path : string ) : $mol_file { | |
throw new Error( 'Not implemented yet' ) | |
} | |
static base = '' | |
path() { | |
return '.' | |
} | |
parent() { | |
return this.resolve( '..' ) | |
} | |
@ $mol_mem | |
stat(next? : $mol_file_stat | null, virt?: 'virt') { | |
const path = this.path() | |
const parent = this.parent() | |
parent.watcher() | |
// Отслеживать проверку наличия родительской папки не стоит до корня диска | |
// Лучше ограничить mam-ом | |
const root = this.$.$mol_file.watch_root ?? this | |
if ( this !== root ) { | |
// Если родитель удалился, надо ресетнуть все дочерние на любой глубине | |
// Родитель может удалиться, потом создасться, а дочерняя папка только удалиться. | |
// Поэтому parent.exists() не запустит перевычисления | |
// parent.version() меняется не только при удалении, будет ложное срабатывание | |
// события вотчера addDir сбрасывает только parent.sub(), а parent.version() может остаться та же | |
// тогда дочерний не перзапустится | |
// Если addDir будет сбрасывать parent.version(), то будет лишний раз перевычислен parent, хоть и он сам не поменялся | |
parent.version() | |
// parent.sub_version() | |
} | |
if( virt ) return next ?? null | |
return next ?? this.info(path) | |
} | |
protected static changed = new Set<$mol_file> | |
protected static added = new Set<$mol_file> | |
protected static frame = null as null | $mol_after_timeout | |
protected static changed_add(type: 'addDir' | 'unlinkDir' | 'add' | 'change' | 'unlink', path: string) { | |
const file = this.$.$mol_file.relative( path.at(-1) === '/' ? path.slice(0, -1) : path ) | |
if (type === 'add') { | |
// добавился файл - у parent надо обновить список sub, если он был заюзан | |
this.added.add(file) | |
} | |
if (type === 'change' || type === 'unlink') { | |
// обновился или удалился файл - ресетим | |
this.changed.add(file) | |
} | |
if ( type === 'addDir' ) { | |
// добавилась папка, у parent обновляем список директорий в sub | |
// дочерние ресетим | |
this.added.add(file) | |
this.changed.add(file) | |
} | |
if ( type === 'unlinkDir') { | |
// удалилась папка, ресетим ее | |
// stat у всех дочерних обновится сам, т.к. связан с parent.version() | |
this.changed.add(file) | |
} | |
if (this.side_effect) return | |
this.frame?.destructor() | |
this.frame = new this.$.$mol_after_timeout(500, () => { | |
if (this.side_effect) return | |
this.side_effect = () => {} | |
$mol_wire_async(this).flush() | |
} ) | |
} | |
static flush() { | |
// this.flush_counter() | |
// Пока flush работает, вотчер сюда не заходит, но может добавлять новые изменения | |
// на каждом перезапуске они применятся | |
// Пока run выполняется, изменения накапливаются, в конце run вызывается flush | |
// Пока применяются изменения, run должен ожидать конца flush | |
for (const file of this.added) { | |
const parent = file.parent() | |
if ($mol_wire_probe(() => parent.sub())) parent.sub(null) | |
} | |
for (const file of this.changed) { | |
file.reset() | |
} | |
this.added.clear() | |
this.changed.clear() | |
// Выставляем обратно в null, что б watch мог зайти сюда | |
this.side_effect = null | |
return null | |
} | |
static side_effect = null as null | (() => unknown) | |
@ $mol_mem | |
static side_effect_ver(reset?: null): number { | |
return 1 + ( $mol_wire_probe(() => this.side_effect_ver()) ?? 0 ) | |
} | |
@ $mol_mem | |
static job() { | |
try { | |
this.side_effect_ver() | |
const result = this.side_effect?.() | |
this.flush() | |
return result | |
} catch (e) { | |
if ( ! $mol_promise_like(e) ) this.flush() | |
$mol_fail_hidden(e) | |
} | |
} | |
@ $mol_action | |
static job_prev() { | |
try { | |
return this.job() | |
} catch (error) { | |
if ($mol_fail_catch(error)) return null | |
} | |
} | |
@ $mol_action | |
static job_post() { return this.job() } | |
// @ $mol_action | |
static watch_off<Result>(cb: () => Result, path: string) { | |
this.job_prev() | |
this.side_effect_ver(null) | |
this.side_effect = cb | |
// watch запаздывает и событие может прилететь через 3 сек после окончания side_effect | |
this.changed.add(this.$.$mol_file.absolute(path)) | |
return this.job_post() as Result | |
} | |
reset() { | |
this.stat( null ) | |
} | |
@ $mol_mem | |
modified() { return this.stat()?.mtime ?? null } | |
@ $mol_mem | |
version() { | |
return this.stat()?.mtime.getTime().toString( 36 ).toUpperCase() ?? '' | |
} | |
protected info( path: string ) { return null as null | $mol_file_stat } | |
protected ensure() {} | |
protected drop() {} | |
protected copy(to: string) {} | |
@ $mol_mem_key | |
clone(to: string) { | |
if (! this.exists() ) return null | |
const target = this.$.$mol_file.absolute(to) | |
try { | |
this.version() | |
target.parent().exists(true) | |
this.copy(to) | |
target.reset() | |
return target | |
} catch (error) { | |
if ( $mol_fail_catch(error)) { | |
console.error(error) | |
} | |
} | |
return null | |
} | |
protected static watch_root = null as null | $mol_file | |
static root( path: string) { | |
this.watch_root = this.absolute( path ) | |
return this.watch_root | |
} | |
watcher() { | |
console.warn('$mol_file_web.watcher() not implemented') | |
return { | |
destructor() {} | |
} | |
} | |
@ $mol_mem | |
exists( next? : boolean ) { | |
const exists = Boolean( this.stat() ) | |
if( next === undefined ) return exists | |
if( next === exists ) return exists | |
if( next ) { | |
this.parent().exists( true ) | |
this.ensure() | |
this.reset() | |
return next | |
} | |
this.drop() | |
// удалили директорию, все дочерние потеряли актуальность | |
this.reset() | |
return next | |
} | |
@ $mol_mem | |
type() { | |
return this.stat()?.type ?? '' | |
} | |
name() { | |
return this.path().replace( /^.*\//, '' ) | |
} | |
ext() { | |
const match = /((?:\.\w+)+)$/.exec( this.path() ) | |
return match ? match[ 1 ].substring( 1 ) : '' | |
} | |
@ $mol_mem | |
buffer( next? : Uint8Array ) { return next ?? new Uint8Array } | |
text(next?: string, virt?: 'virt') { | |
if (next !== undefined) this.version() | |
return this.text_int(next, virt) | |
} | |
@ $mol_mem | |
text_int(next?: string, virt?: 'virt') { | |
if( virt ) { | |
const now = new Date | |
this.stat( { | |
type: 'file', | |
size: 0, | |
atime: now, | |
mtime: now, | |
ctime: now, | |
}, 'virt' ) | |
return next! | |
} | |
if( next === undefined ) { | |
return $mol_charset_decode( this.buffer( ) ) | |
} else { | |
const buffer = $mol_charset_encode( next ) | |
this.buffer( buffer ) | |
return next | |
} | |
} | |
@ $mol_mem | |
sub(reset?: null) { | |
if (! this.exists() ) return [] | |
if ( this.type() !== 'dir') return [] | |
this.stat() | |
// Если дочерний file удалился, список надо обновить | |
return this.kids().filter(file => file.exists()) | |
} | |
protected kids() { | |
return [] as readonly $mol_file[] | |
} | |
resolve(path: string): $mol_file { | |
throw new Error('implement') | |
} | |
relate( base?: $mol_file ): string { | |
throw new Error('implement') | |
} | |
protected append( next : Uint8Array | string ) {} | |
find( | |
include? : RegExp , | |
exclude? : RegExp | |
) { | |
const found = [] as $mol_file[] | |
const sub = this.sub() | |
for (const child of sub) { | |
const child_path = child.path() | |
if( exclude && child_path.match( exclude ) ) continue | |
if( !include || child_path.match( include ) ) found.push( child ) | |
if( child.type() === 'dir' ) { | |
const sub_child = child.find( include , exclude ) | |
for (const child of sub_child) found.push(child) | |
} | |
} | |
return found | |
} | |
@ $mol_mem | |
size() { | |
switch( this.type() ) { | |
case 'file': return this.stat()?.size ?? 0 | |
default: return 0 | |
} | |
} | |
open( ... modes: readonly ( 'create' | 'exists_truncate' | 'exists_fail' | 'read_only' | 'write_only' | 'read_write' | 'append' )[] ) { | |
return 0 | |
} | |
toJSON() { | |
return this.path() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment