code-server/packages/vscode/src/fill/storageDatabase.ts
2019-02-21 14:11:49 -06:00

125 lines
3.4 KiB
TypeScript

import { readFile, writeFile, mkdir } from "fs";
import * as path from "path";
import { promisify } from "util";
import { IDisposable } from "@coder/disposable";
import { Event } from "vs/base/common/event";
import * as workspaceStorage from "vs/base/node/storage";
import * as globalStorage from "vs/platform/storage/node/storageIpc";
import * as paths from "./paths";
import { logger, field } from "@coder/logger";
import { client } from "@coder/vscode/src/client";
import { IStorageService, WillSaveStateReason } from "vs/platform/storage/common/storage";
class StorageDatabase implements workspaceStorage.IStorageDatabase {
public readonly onDidChangeItemsExternal = Event.None;
private readonly items = new Map<string, string>();
private fetched: boolean = false;
private readonly path: string;
public constructor(path: string) {
this.path = path.replace(/\.vscdb$/, ".json");
logger.debug("Setting up storage", field("path", this.path));
window.addEventListener("unload", () => {
if (!navigator.sendBeacon) {
throw new Error("cannot save state");
}
this.triggerFlush(WillSaveStateReason.SHUTDOWN);
navigator.sendBeacon(`/resource${this.path}`, this.content);
});
}
public async getItems(): Promise<Map<string, string>> {
if (this.fetched) {
return this.items;
}
try {
const contents = await promisify(readFile)(this.path, "utf8");
const json = JSON.parse(contents);
Object.keys(json).forEach((key) => {
this.items.set(key, json[key]);
});
} catch (error) {
if (error.code !== "ENOENT") {
throw error;
}
}
this.fetched = true;
return this.items;
}
public updateItems(request: workspaceStorage.IUpdateRequest): Promise<void> {
if (request.insert) {
request.insert.forEach((value, key) => this.items.set(key, value));
}
if (request.delete) {
request.delete.forEach(key => this.items.delete(key));
}
return this.save();
}
public close(): Promise<void> {
return Promise.resolve();
}
public checkIntegrity(): Promise<string> {
return Promise.resolve("ok");
}
private async save(): Promise<void> {
try {
await promisify(mkdir)(path.dirname(this.path));
} catch (ex) {}
return promisify(writeFile)(this.path, this.content);
}
private triggerFlush(reason: WillSaveStateReason = WillSaveStateReason.NONE): boolean {
// tslint:disable-next-line:no-any
const storageService = client.serviceCollection.get<IStorageService>(IStorageService) as any;
if (reason === WillSaveStateReason.SHUTDOWN && storageService.close) {
storageService.close();
return true;
}
if (storageService._onWillSaveState) {
storageService._onWillSaveState.fire({ reason });
return true;
}
return false;
}
private get content(): string {
const json: { [key: string]: string } = {};
this.items.forEach((value, key) => {
json[key] = value;
});
return JSON.stringify(json);
}
}
class GlobalStorageDatabase extends StorageDatabase implements IDisposable {
public constructor() {
super(path.join(paths.getAppDataPath(), "globalStorage", "state.vscdb"));
}
public dispose(): void {
// Nothing to do.
}
}
const workspaceTarget = workspaceStorage as typeof workspaceStorage;
// @ts-ignore TODO: don't ignore it.
workspaceTarget.SQLiteStorageDatabase = StorageDatabase;
const globalTarget = globalStorage as typeof globalStorage;
// @ts-ignore TODO: don't ignore it.
globalTarget.GlobalStorageDatabaseChannelClient = GlobalStorageDatabase;