UNPKG

imdone-core

Version:
246 lines (224 loc) 7.28 kB
import { fileSystemStoreMixin } from './repo-fs-store.js' import debug from 'debug' const log = debug('imdone-mixins:repo-watched-fs-store') import { File } from '../file.js' import { constants } from '../constants.js' import chokidar from 'chokidar' import realFs from 'fs' import { logger } from '../adapters/logger.js' // BACKLOG Only emit file.update if the file checksum has changed // <!-- // order:-650 // --> export function watchedFileSystemStoreMixin(repo, fs = realFs) { repo = fileSystemStoreMixin(repo, fs) const _refresh = repo.refresh const _destroy = repo.destroy const _init = repo.init let initializing = false let refreshing = false let watcherPaused = false function ready() { return !initializing && !refreshing } async function waitForReady() { while (!ready()) { logger.log('Waiting for repo to be ready:', repo.path) await new Promise(resolve => setTimeout(resolve, 500)) } } // Add function to pause the watcher repo.pauseWatcher = async function() { if (repo.watcher && !watcherPaused) { logger.info('Pausing file watcher for:', repo.path) await repo.watcher.close() watcherPaused = true repo.emit('watcher.paused') return true } return false } // Add function to unpause the watcher repo.unpauseWatcher = async function() { if (watcherPaused) { logger.info('Unpausing file watcher for:', repo.path) await repo.initWatcher() watcherPaused = false repo.emit('watcher.resumed') return true } return false } // READY Refactor init to async/await // #esm-migration #important // <!-- // order:-310 // --> repo.init = async function () { if (initializing) { throw new Error("Can't initialize repo while it's initializing.") } log('Initializing repo:', repo.path) initializing = true try { const files = await _init.call(repo) await repo.initWatcher() repo.emit('watching', { ok: true, lists: repo.getLists() }) initializing = false return files } catch (err) { initializing = false throw new Error(`Error initializing repo: ${repo.path}`, { cause: err }) } } repo.destroy = async function () { if (repo.watcher) await repo.watcher.close() await _destroy.apply(repo) } // READY Refactor refresh to async/await // #esm-migration #important // <!-- // order:-330 // --> repo.refresh = async function () { await waitForReady() log('Refreshing repo:', repo.path) refreshing = true try { const files = await _refresh.call(repo) // Only initialize watcher if it's not paused if (!watcherPaused) { await repo.initWatcher() } refreshing = false return files } catch (err) { refreshing = false throw new Error(`Error refreshing repo: ${repo.path}`, { cause: err }) } } function _isImdoneConfig(path) { return path === constants.CONFIG_FILE_YML } function _isImdoneIgnore(path) { return path === constants.IGNORE_FILE } async function onConfigChange(file) { logger.info('Loading config. Observed a change to:', file) const label = `Refresh repo: ${repo.path}` logger.time(label) try { await repo.refresh() repo.emitConfigUpdate(file, repo.config) } finally { logger.timeEnd(label) } } const onIgnoreChange = onConfigChange // READY Refactor initWatcher to async/await // #esm-migration #important // <!-- // order:-320 // defer:2025-03-18 // --> repo.initWatcher = async () => { // Close existing watcher if it exists if (repo.watcher) { await repo.watcher.close() } return new Promise(async (resolve, reject) => { logger.log('initializing watcher for:', repo.path) repo.watcher = chokidar.watch(repo.path, { persistent: true, usePolling: false, alwaysStat: true, awaitWriteFinish: true, atomic: true, ignoreInitial: true, ignored: (file) => { if (file === repo.path) return false const relPath = repo.getRelativePath(file) const ignored = !repo.shouldInclude(relPath) if (ignored) logger.log('Ignoring file:', relPath) return ignored } }).on('error', (err) => { logger.error('Error in watcher') logger.error(err) repo.emit('watch.error', err) reject(err) }) .on('ready', () => { logger.log('watcher ready for:', repo.path) initializing = false resolve() }) .on('add', async function (path, stat) { logger.log('Watcher received add event for file: ' + path) if (stat.isDirectory()) return let file = repo.getFile(path) if (file === undefined) { log('Creating file: ' + path) file = new File({ repoId: repo.getId(), filePath: path, languages: repo.languages, project: repo.project, }) } stat = await repo.fileOK() log('fileOK returned: ' + JSON.stringify(stat)) if (stat.mtime <= file.getModifiedTime()) { return log('File update already applied. Change ignored for file: ' + path) } log('Reading file: ' + path) file = await repo.readFile(file) repo.emit('watch.add', file) repo.emitFileUpdate(file) }) .on('change', async function (path, stat) { logger.log( `Watcher received change event for file: ${path} repoPath: ${repo.path}`, stat.mtime ) if (!path || stat.isDirectory()) return const relPath = repo.getRelativePath(path) if (!initializing && _isImdoneConfig(relPath)) onConfigChange(relPath) if (!initializing && _isImdoneIgnore(relPath)) onIgnoreChange(relPath) var file = repo.getFile(path) || path const isFile = File.isFile(file) // BACKLOG ## Handle the case where the file is not in the repo // #urgent #1.12.0 // <!-- // order:-660 // --> if (isFile && stat.mtime <= file.getModifiedTime()) { // logger.log("File update already applied. Change ignored.") return } const ok = await repo.fileOK((file && file.path) || path) if (!ok) return log('Reading file: ' + path) if (!isFile) file = repo.getFile(path) file = await repo.readFile(file) repo.emit('watch.change', file) repo.emitFileUpdate(file) }) .on('delete', async function (path) { log('Watcher received unlink event for file: ' + path) repo.removeFilePath(path) var file = new File({ repoId: repo.getId(), filePath: path, languages: repo.languages, project: repo.project, }) log('Removing file: ' + path) repo.removeFile(file) repo.emit('watch.delete', file) repo.emitFileUpdate(file, true) }) }); } return repo }