Skip to content

Migrate database to LevelDB. #810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions app/main/datastore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { app, dialog, BrowserWindow } from 'electron';

import path = require('path');
import fs = require('fs');
import LevelDB = require('./leveldb');
import Logger = require('../renderer/js/utils/logger-util');

const logger = new Logger({
file: 'datastore.log',
timestamp: true
});

class DataStore {
settingsDB: any;
settings: any;
domainsDB: any;
domains: Domain[];
constructor() {
this.settings = {};
this.domains = [];

this.settingsDB = LevelDB.settings.db;
this.domainsDB = LevelDB.domains.db;

this.loadSettings();
this.loadDomains();
}

async convertDB(databasePath: string): Promise<void> {
let legacyDB = null;
try {
const file = fs.readFileSync(databasePath, 'utf8');
// necessary to catch errors in JSON
legacyDB = JSON.parse(file);
} catch (err) {
if (fs.existsSync(databasePath)) {
fs.unlinkSync(databasePath);
dialog.showErrorBox(
'Error saving new organization',
'There seems to be error while saving new organization, ' +
'you may have to re-add your previous organizations back.'
);
logger.error('Error while JSON parsing domain.json: ');
logger.error(err);
logger.reportSentry(err);
}
return;
}
if (legacyDB) {
if (databasePath.includes('domain')) {
await this.domainsDB.put('domains', legacyDB.domains);
this.domains = legacyDB.domains;
} else {
let batch = this.settingsDB.batch();
for (const key in legacyDB) {
if (legacyDB.hasOwnProperty(key)) {
batch = batch.put(key, legacyDB[key]);
}
}
await batch.write();
this.settings = legacyDB;
}
}
if (fs.existsSync(databasePath)) {
// delete legacy database to complete migration.
fs.unlinkSync(databasePath);
}
}

loadSettings(): void {
const databasePath = path.join(app.getPath('userData'), 'config/settings.json');
if (fs.existsSync(databasePath)) {
this.convertDB(databasePath);
return;
}
this.settingsDB.createReadStream().on('data', (configItem: any) => {
if (configItem.value === '__null__') {
configItem.value = null;
}
this.settings[configItem.key] = configItem.value;
this.updateUtil('config-update');
}).on('error', (err: Error) => {
logger.error(err);
});
}

loadDomains(): void {
const databasePath = path.join(app.getPath('userData'), 'config/domain.json');
if (fs.existsSync(databasePath)) {
this.convertDB(databasePath);
return;
}
this.domainsDB.createReadStream().on('data', (domains: any) => {
this.domains = domains.value;
this.updateUtil('domain-update');
}).on('error', (err: Error) => {
logger.error(err);
});
}

updateUtil(message: string): void {
const win = BrowserWindow.getAllWindows()[0];
if (process.platform === 'darwin') {
win.restore();
}
if (process.type === 'browser') {
win.webContents.send(message, this.settings);
} else {
win.webContents.send('forward-message', message, this.settings);
}
}
}

export = new DataStore();
27 changes: 27 additions & 0 deletions app/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import AppMenu = require('./menu');
import BadgeSettings = require('../renderer/js/pages/preference/badge-settings');
import ConfigUtil = require('../renderer/js/utils/config-util');
import ProxyUtil = require('../renderer/js/utils/proxy-util');
import leveldb = require('./leveldb');
import DataStore = require('./datastore');

interface PatchedGlobal extends NodeJS.Global {
mainWindowState: windowStateKeeper.State;
Expand Down Expand Up @@ -362,6 +364,31 @@ app.on('ready', () => {
ipcMain.on('save-last-tab', (_event: Electron.IpcMessageEvent, index: number) => {
ConfigUtil.setConfigItem('lastActiveTab', index);
});

ipcMain.on('db-set-item', (_event: Electron.IpcMessageEvent, key: string, value: any) => {
DataStore.settings[key] = value;
leveldb.settings.setItem(key, value);
});

ipcMain.on('db-delete-item', (_event: Electron.IpcMessageEvent, key: string) => {
delete DataStore.settings.key;
leveldb.settings.deleteItem(key);
});

ipcMain.on('get-settings', (_event: Electron.IpcMessageEvent) => {
_event.returnValue = DataStore.settings;
});

ipcMain.on('get-domains', (_event: Electron.IpcMessageEvent) => {
_event.returnValue = DataStore.domains;
});

ipcMain.on('db-update-domains', async (_event: Electron.IpcMessageEvent, domains: Domain[]) => {
DataStore.domains = domains;
await leveldb.domains.deleteItem('domains');
await leveldb.domains.setItem('domains', domains);
_event.returnValue = true;
});
});

app.on('before-quit', () => {
Expand Down
74 changes: 74 additions & 0 deletions app/main/leveldb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { app } from 'electron';
import level from 'level';

import path = require('path');
import Logger = require('../renderer/js/utils/logger-util');

const logger = new Logger({
file: 'leveldb.log',
timestamp: true
});

const settingsJsonPath = path.join(app.getPath('userData'), 'config/settings');
const domainsJsonPath = path.join(app.getPath('userData'), 'config/domains');

class LevelDB {
// TODO: change this to a proper type
db: any;
constructor(databasePath: string) {
this.reloadDB(databasePath);
}

reloadDB(databasePath: string): void {
try {
this.db = level(databasePath, { valueEncoding: 'json' });
} catch (err) {
logger.error(err);
logger.reportSentry(err.toString());
}
}

async setItem(key: string, value: any): Promise<void> {
try {
if (value === null || value === undefined) {
// sentinel value needed because leveldb doesn't allow
// null or undefined values in database.
value = '__null__';
}
await this.db.put(key, value);
} catch (err) {
logger.error(err);
logger.reportSentry(err.toString());
}
}

async doesItemExist(key: string): Promise<boolean> {
try {
await this.db.get(key);
// if control reaches here, key is present and accessible
return true;
} catch (err) {
return false;
}
}

async deleteItem(key: string): Promise<boolean> {
try {
return await this.db.del(key);
} catch (err) {
if (err instanceof level.errors.NotFoundError) {
// key does not exist in database
// no need to report this to Sentry
return false;
}
logger.error(err);
logger.reportSentry(err.toString());
return false;
}
}
}

export = {
settings: new LevelDB(settingsJsonPath),
domains: new LevelDB(domainsJsonPath)
};
1 change: 1 addition & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"escape-html": "1.0.3",
"i18n": "0.8.3",
"is-online": "7.0.0",
"level": "5.0.1",
"node-json-db": "0.9.2",
"request": "2.85.0",
"semver": "5.4.1",
Expand Down
12 changes: 10 additions & 2 deletions app/renderer/js/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,14 @@ class ServerManagerView {
});
}

ipcRenderer.on('config-update', (event: Event, settings: any) => {
ConfigUtil.updateSettings(settings);
});

ipcRenderer.on('domain-update', (event: Event, domains: Domain[]) => {
DomainUtil.updateDomainUtil(domains);
});

ipcRenderer.on('open-settings', (event: Event, settingNav: string) => {
this.openSettings(settingNav);
});
Expand Down Expand Up @@ -872,7 +880,7 @@ class ServerManagerView {
this.tabs[index].webview.props.name = realmName;

domain.alias = escape(realmName);
DomainUtil.db.push(`/domains[${index}]`, domain, true);
DomainUtil.updateDomain(index, domain);
DomainUtil.reloadDB();
// Update the realm name also on the Window menu
ipcRenderer.send('update-menu', {
Expand All @@ -893,7 +901,7 @@ class ServerManagerView {
serverImgs[index].src = localIconUrl;

domain.icon = localIconUrl;
DomainUtil.db.push(`/domains[${index}]`, domain, true);
DomainUtil.updateDomain(index, domain);
DomainUtil.reloadDB();
});
}
Expand Down
2 changes: 1 addition & 1 deletion app/renderer/js/pages/preference/base-section.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class BaseSection extends BaseComponent {
}

reloadApp(): void {
ipcRenderer.send('forward-message', 'reload-viewer');
ipcRenderer.send('reload-full-app');
}
}

Expand Down
Loading