keeweb/app/scripts/storage/impl/storage-file.js

187 lines
5.7 KiB
JavaScript

import { Launcher } from 'comp/launcher';
import { StorageBase } from 'storage/storage-base';
const fileWatchers = {};
class StorageFile extends StorageBase {
name = 'file';
icon = 'hdd-o';
enabled = !!Launcher;
system = true;
backup = true;
load(path, opts, callback) {
this.logger.debug('Load', path);
const ts = this.logger.ts();
const onError = (e) => {
this.logger.error('Error reading local file', path, e);
if (callback) {
callback(e, null);
}
};
Launcher.readFile(path, undefined, (data, err) => {
if (err) {
return onError(err);
}
Launcher.statFile(path, (stat, err) => {
if (err) {
return onError(err);
}
const rev = stat.mtime.getTime().toString();
this.logger.debug('Loaded', path, rev, this.logger.ts(ts));
if (callback) {
callback(null, data.buffer, { rev });
}
});
});
}
stat(path, opts, callback) {
this.logger.debug('Stat', path);
const ts = this.logger.ts();
Launcher.statFile(path, (stat, err) => {
if (err) {
this.logger.error('Error stat local file', path, err);
if (err.code === 'ENOENT') {
err.notFound = true;
}
return callback && callback(err, null);
}
this.logger.debug('Stat done', path, this.logger.ts(ts));
if (callback) {
const fileRev = stat.mtime.getTime().toString();
callback(null, { rev: fileRev });
}
});
}
save(path, opts, data, callback, rev) {
this.logger.debug('Save', path, rev);
const ts = this.logger.ts();
const onError = (e) => {
if (Object.prototype.hasOwnProperty.call(e, 'code') && e.code === 'EISDIR') {
e.isDir = true;
}
this.logger.error('Error writing local file', path, e);
if (callback) {
callback(e);
}
};
const write = () => {
Launcher.writeFile(path, data, (err) => {
if (err) {
return onError(err);
}
Launcher.statFile(path, (stat, err) => {
if (err) {
return onError(err);
}
const newRev = stat.mtime.getTime().toString();
this.logger.debug('Saved', path, this.logger.ts(ts));
if (callback) {
callback(undefined, { rev: newRev });
}
});
});
};
if (rev) {
Launcher.statFile(path, (stat, err) => {
if (err) {
return write();
}
const fileRev = stat.mtime.getTime().toString();
if (fileRev !== rev) {
this.logger.debug('Save mtime differs', rev, fileRev);
return callback && callback({ revConflict: true }, { rev: fileRev });
}
write();
});
} else {
write();
}
}
mkdir(path, callback) {
this.logger.debug('Make dir', path);
const ts = this.logger.ts();
Launcher.mkdir(path, (err) => {
if (err) {
this.logger.error('Error making local dir', path, err);
if (callback) {
callback('Error making local dir');
}
} else {
this.logger.debug('Made dir', path, this.logger.ts(ts));
if (callback) {
callback();
}
}
});
}
watch(path, callback) {
const names = Launcher.parsePath(path);
if (!fileWatchers[names.dir] && !names.dir.startsWith('\\')) {
this.logger.debug('Watch dir', names.dir);
let fsWatcher;
try {
fsWatcher = Launcher.createFsWatcher(names.dir);
} catch (e) {
this.logger.warn('Error watching dir', e);
}
if (fsWatcher) {
fsWatcher.on('change', this.fsWatcherChange.bind(this, names.dir));
fileWatchers[names.dir] = {
fsWatcher,
callbacks: []
};
}
}
const fsWatcher = fileWatchers[names.dir];
if (fsWatcher) {
fsWatcher.callbacks.push({
file: names.file,
callback
});
}
}
unwatch(path) {
const names = Launcher.parsePath(path);
const watcher = fileWatchers[names.dir];
if (watcher) {
const ix = watcher.callbacks.findIndex((cb) => cb.file === names.file);
if (ix >= 0) {
watcher.callbacks.splice(ix, 1);
}
if (!watcher.callbacks.length) {
this.logger.debug('Stop watch dir', names.dir);
watcher.fsWatcher.close();
delete fileWatchers[names.dir];
}
}
}
fsWatcherChange(dirname, evt, fileName) {
const watcher = fileWatchers[dirname];
if (watcher) {
watcher.callbacks.forEach((cb) => {
if (cb.file === fileName && typeof cb.callback === 'function') {
this.logger.debug('File changed', dirname, evt, fileName);
cb.callback();
}
});
}
}
}
export { StorageFile };