125 lines
3.4 KiB
TypeScript
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;
|