diff --git a/build/build.node.ts b/build/build.node.ts index 67a05f033b..30d31f6bc8 100644 --- a/build/build.node.ts +++ b/build/build.node.ts @@ -6,7 +6,7 @@ namespace $ { @ $mol_mem_key static root( [ root, paths ] : [root: string, paths: readonly string[] ] ) { - this.$.$mol_file.watch_root = root + this.$.$mol_file.base = root return this.make({ root : ()=> this.$.$mol_file.absolute( root ) , diff --git a/file/base/base.ts b/file/base/base.ts index e201588767..c25481b98f 100644 --- a/file/base/base.ts +++ b/file/base/base.ts @@ -26,6 +26,14 @@ namespace $ { @ $mol_action exists_cut() { return this.exists() } + protected root() { + const path = this.path() + const base = (this.constructor as typeof $mol_file_base).base + + // Если путь выше или равен base - считаем это корнем + return base.startsWith(path) + } + @ $mol_mem protected stat(next? : $mol_file_stat | null, virt?: 'virt') { @@ -34,29 +42,20 @@ namespace $ { // Отслеживать проверку наличия родительской папки не стоит до корня диска // Лучше ограничить mam-ом - const root = (this.constructor as typeof $mol_file_base).watch_root - if ( path !== root && path !== parent.path() ) { + if ( ! this.root() ) { /* - Если папка удалилась, надо ресетнуть все объекты в ней на любой глубине. - rm -rf с последующим git pull: parent папка может удалиться, потом создасться, а текущая папка только удалиться. - Поэтому parent.exists() не запустит перевычисления + Если parent папка удалилась, надо ресетнуть все объекты в ней на любой глубине. + Например, rm -rf с последующим git pull: parent папка может удалиться, потом создасться, + а текущая папка успеет только удалиться до момента выполнения stat. + Поэтому parent.exists() не запустит перевычисления, нужна именно parent.version() - parent.version() меняется не только при удалении, будет ложное срабатывание - Если addDir будет сбрасывать parent.version(), то будет лишний раз перевычислен parent, хоть и он сам не поменялся + Однако, parent.version() меняется не только при удалении, будет ложное срабатывание + С этим придется мириться, красивого решения пока нет. */ parent.version() - - // родительской папки может не быть, например, из foo/bar/baz на диске есть только foo - // baz.stat запустит bar.watcher и node.fs.watch упадет с ошибкой, т.к. папки bar нет - if (parent.exists()) parent.watcher() - } else { - // watch_root - это корень mam или диска, считаем, что он всегда существует - // если тут делать exists проверку, как строчками выше, - // то будет выполнен stat во всех верхних папках до корня диска, - // что делать не стоит из-за прав доступа, к примеру - parent.watcher() } + parent.watcher() if( virt ) return next ?? null @@ -241,9 +240,6 @@ namespace $ { // Если файл записали, потом отключили вотчер, кто-то из вне его поменял, потом включили вотчер, снова записали тот же буфер, // то буфер не запишется на диск, т.к. кэш не консистентен с диском. - // Также это не поможет, т.к. всякие генерации view.tree используют не идемпотентные id-ки - // При первом старте, даже если есть уже сбилженый view.tree.d.ts, он будет перезаписан. - // watcher триггернется и снова запишет с новыми id и зациклится if (! changed && this.exists()) return prev this.parent().exists( true ) @@ -287,7 +283,7 @@ namespace $ { return null } - static watch_root = '' + // static watch_root = '' // static watcher_warned = false watcher() { @@ -314,12 +310,10 @@ namespace $ { if( next ) { this.parent().exists( true ) this.ensure() - this.reset() - return next + } else { + this.drop() } - this.drop() - // удалили директорию, все дочерние потеряли актуальность this.reset() return next diff --git a/file/file.node.ts b/file/file.node.ts index 4f4c091b99..0d502ff366 100644 --- a/file/file.node.ts +++ b/file/file.node.ts @@ -50,48 +50,58 @@ namespace $ { return this.absolute( $node.path.resolve( this.base, path ).replace( /\\/g , '/' ) ) } - @ $mol_mem_key - static watcher(path: string) { - const watcher = $node.fs.watch( path, (type, name) => { + @ $mol_mem + override watcher(reset?: null) { + const path = this.path() + const root = this.root() + // Если папки/файла нет, watch упадет с ошибкой + // exists обратится к parent.version и parent.watcher + // Поэтому у root-папки и выше не надо вызывать exists, иначе поднимется выше base до корня диска + // exists вызывать надо, что б пересоздавать вотчер при появлении папки или файла + if (! root && ! this.exists() ) return super.watcher() + + let watcher + + try { + // Между exists и watch файл может удалиться, в любом случае надо обрабатывать ENOENT + watcher = $node.fs.watch( path ) + } catch (error: any) { + if ( ! (error instanceof Error) ) error = new Error('Unknown watch error', {cause: error}) + error.message += '\n' + path + + if ( root || error.code !== 'ENOENT' ) { + this.$.$mol_fail_log(error) + } + + // Если файла нет - вотчер не создается, создастся потом, когда exists поменяется на true. + // Если создание упало с другой ошибкой - не ломаем работу mol_file, деградируем до не реактивной fs. + + return super.watcher() + } + + watcher.on('change', (type: 'change' | 'rename', name) => { if (! name) return - this.changed_add(type, $node.path.join( path, name )) + const path = $node.path.join( this.path(), name.toString() ) + ;(this.constructor as typeof $mol_file_base).changed_add(type, path) }) watcher.on('error', e => this.$.$mol_fail_log(e) ) + let destructed = false + + watcher.on('close', () => { + // Если в процессе работы вотчер сам закрылся, надо его переоткрыть + if (! destructed) setTimeout(() => $mol_wire_async(this).watcher(null), 500) + }) + return { destructor() { + destructed = true watcher.close() } } - - // const watcher = $node.chokidar.watch( path , { - // persistent : true , - // ignored: path => /([\/\\]\.|___$)/.test( path ), - // depth : 0 , - // ignoreInitial : true , - // awaitWriteFinish: { - // stabilityThreshold: 100, - // }, - // } ) - - // watcher - // .on( 'all' , (type: 'add' | 'change' | 'unlink' | 'addDir' | 'unlinkDir', path) => { - // const normalized = type === 'unlink' || type === 'unlinkDir' ? 'rename' : 'change' - // this.changed_add(normalized, path) - // } ) - // .on( 'error' , e => this.$.$mol_fail_log(e) ) - - // return { - // destructor() { - // watcher.close() - // } - // } - } - override watcher() { return this.$.$mol_file_node.watcher(this.path()) } - @ $mol_action protected override info( path: string ) { try {