insomnia/packages/insomnia-app/app/sync/store/drivers/file-system-driver.ts
Opender Singh 6a1abbf3db
Migrate collections into their remote space (#3876)
* initial implementation

* take vcs as a parameter (so it's easier to mock and test)

* update console log and exit early if not logged in

* add unit tests for migration

* add async filter and replace vcs find by root document

* lint fix

* fix memory driver and add test for hasProjectForRootDocument

* setup check on interval

* move migration logic and interval from main process to render process, because session state and a VCS instance dont work in the main process yet

* update test

* replace constructor with static class creator

* replace interval with login logout hook

* remove effect helpers

* empty commit to trigger CI
2021-08-10 23:35:05 +00:00

157 lines
3.8 KiB
TypeScript

import fs from 'fs';
import mkdirp from 'mkdirp';
import path from 'path';
import type { BaseDriver } from './base';
export default class FileSystemDriver implements BaseDriver {
_directory: string;
private constructor(config: { directory: string }) {
this._directory = config.directory;
console.log(`[FileSystemDriver] Initialized in "${this._directory}"`);
}
static create(dataDirectory: string) {
const directory = path.join(dataDirectory, 'version-control');
return new FileSystemDriver({ directory });
}
async hasItem(key: string) {
return new Promise<boolean>((resolve, reject) => {
fs.stat(this._getKeyPath(key), err => {
if (err && err.code === 'ENOENT') {
resolve(false);
} else if (err) {
reject(err);
} else {
resolve(true);
}
});
});
}
setItem(key: string, value: Buffer) {
return new Promise<void>((resolve, reject) => {
const finalPath = this._getKeyPath(key);
// Temp path contains randomness to avoid race-condition collisions. This
// doesn't actually avoid race conditions but at least it won't fail.
const tmpPath = `${finalPath}.${Math.random()}.tmp`;
// This method implements atomic writes by first writing to a temporary
// file (non-atomic) then renaming the file to the final value (atomic)
fs.writeFile(tmpPath, value, 'utf8', err => {
if (err) {
return reject(err);
}
fs.rename(tmpPath, finalPath, err => {
if (err) {
return reject(err);
}
resolve();
});
});
});
}
getItem(key: string) {
return new Promise<Buffer | null>((resolve, reject) => {
fs.readFile(this._getKeyPath(key), (err, data) => {
if (err && err.code === 'ENOENT') {
resolve(null);
} else if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
removeItem(key: string) {
return new Promise<void>((resolve, reject) => {
fs.unlink(this._getKeyPath(key), err => {
if (err && err.code === 'ENOENT') {
resolve();
} else if (err) {
reject(err);
} else {
resolve();
}
});
});
}
clear() {
return new Promise<void>((resolve, reject) => {
fs.readdir(this._directory, (err, names) => {
if (err) {
return reject(err);
}
for (const name of names) {
fs.unlinkSync(this._getKeyPath(name));
}
resolve();
});
});
}
async keys(prefix: string, recursive: boolean) {
const next = dir => {
return new Promise<string[]>(async (resolve, reject) => {
let keys: string[] = [];
let names: string[] = [];
try {
names = fs.readdirSync(dir);
} catch (err) {
if (err.code !== 'ENOENT') {
reject(err);
}
}
for (const name of names) {
if (name.indexOf('.') === 0) {
// Skip any non-vcs files
continue;
}
const p = path.join(dir, name);
const isDir = fs.statSync(p).isDirectory();
if (isDir && recursive) {
const more = await next(p);
keys = [...keys, ...more];
} else if (!recursive && isDir) {
keys.push(p);
} else if (!isDir) {
keys.push(p);
}
}
resolve(keys);
});
};
const rawKeys = await next(this._getKeyPath(prefix));
const keys: string[] = [];
for (const rawKey of rawKeys) {
keys.push(rawKey.substring(this._directory.length));
}
return keys;
}
_getKeyPath(key: string) {
const p = path.join(this._directory, key);
// Create base directory
mkdirp.sync(path.dirname(p));
return p;
}
}