You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-07-03 23:50:33 +02:00
92 lines
2.5 KiB
TypeScript
92 lines
2.5 KiB
TypeScript
import { GlobOptionsWithFileTypesFalse, sync } from 'glob';
|
|
import { stat, utimes } from 'fs/promises';
|
|
import { ensureFile, removeSync } from 'fs-extra';
|
|
import { Second } from './time';
|
|
|
|
// Wraps glob.sync but with good default options so that it works across
|
|
// platforms and with consistent sorting.
|
|
export const globSync = (pattern: string | string[], options: GlobOptionsWithFileTypesFalse) => {
|
|
let output = sync(pattern, options);
|
|
output = output.map(f => f.replace(/\\/g, '/'));
|
|
output.sort();
|
|
return output;
|
|
};
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// This is a relatively crude system for "locking" files. It does so by regularly updating the
|
|
// timestamp of a file. If the file hasn't been updated for more than x seconds, it means the lock
|
|
// is stale and the file can be considered unlocked.
|
|
//
|
|
// This is good enough for our use case, to detect if a profile is already being used by a running
|
|
// instance of Joplin.
|
|
// ------------------------------------------------------------------------------------------------
|
|
|
|
interface FileLockerOptions {
|
|
interval?: number;
|
|
}
|
|
|
|
export class FileLocker {
|
|
|
|
private filePath_ = '';
|
|
private interval_: ReturnType<typeof setInterval> | null = null;
|
|
private options_: FileLockerOptions;
|
|
|
|
public constructor(filePath: string, options: FileLockerOptions|null = null) {
|
|
this.options_ = {
|
|
interval: 10 * Second,
|
|
...options,
|
|
};
|
|
|
|
this.filePath_ = filePath;
|
|
}
|
|
|
|
public get options() {
|
|
return this.options_;
|
|
}
|
|
|
|
public async lock() {
|
|
if (!(await this.canLock())) return false;
|
|
|
|
await this.updateLock();
|
|
|
|
this.interval_ = setInterval(() => {
|
|
void this.updateLock();
|
|
}, this.options_.interval);
|
|
|
|
return true;
|
|
}
|
|
|
|
private async canLock() {
|
|
try {
|
|
const s = await stat(this.filePath_);
|
|
return Date.now() - s.mtime.getTime() > (this.options_.interval as number);
|
|
} catch (error) {
|
|
const e = error as NodeJS.ErrnoException;
|
|
if (e.code === 'ENOENT') return true;
|
|
e.message = `Could not find out if this file can be locked: ${this.filePath_}`;
|
|
return e;
|
|
}
|
|
}
|
|
|
|
// We want the unlock operation to be synchronous because it may be performed when the app
|
|
// is closing.
|
|
public unlockSync() {
|
|
this.stopMonitoring_();
|
|
removeSync(this.filePath_);
|
|
}
|
|
|
|
private async updateLock() {
|
|
await ensureFile(this.filePath_);
|
|
const now = new Date();
|
|
await utimes(this.filePath_, now, now);
|
|
}
|
|
|
|
public stopMonitoring_() {
|
|
if (this.interval_) {
|
|
clearInterval(this.interval_);
|
|
this.interval_ = null;
|
|
}
|
|
}
|
|
|
|
}
|