From a5f3a90592a9ecbced7cc72555fee61daaa50a5b Mon Sep 17 00:00:00 2001 From: azmy60 Date: Thu, 1 Feb 2024 02:53:02 +0700 Subject: [PATCH] dont use reactivity on config --- apps/studio/src/App.vue | 2 +- apps/studio/src/background.ts | 1 - apps/studio/src/background/config_manager.ts | 148 ------------- apps/studio/src/common/AppEvent.ts | 3 - apps/studio/src/components/TabQueryEditor.vue | 4 +- .../src/components/data/DataManager.vue | 2 +- .../src/components/tableview/TableTable.vue | 9 +- apps/studio/src/main.ts | 1 - apps/studio/src/plugins/ConfigPlugin.js | 7 - apps/studio/src/plugins/ConfigPlugin.ts | 205 ++++++++++++++++++ apps/studio/src/store/index.ts | 16 +- .../src/store/modules/ConfigsStoreModule.ts | 102 --------- .../default.config.ini => default.config.ini | 0 .../local.config.ini => local.config.ini | 0 14 files changed, 216 insertions(+), 284 deletions(-) delete mode 100644 apps/studio/src/background/config_manager.ts delete mode 100644 apps/studio/src/plugins/ConfigPlugin.js create mode 100644 apps/studio/src/plugins/ConfigPlugin.ts delete mode 100644 apps/studio/src/store/modules/ConfigsStoreModule.ts rename apps/studio/default.config.ini => default.config.ini (100%) rename apps/studio/local.config.ini => local.config.ini (100%) diff --git a/apps/studio/src/App.vue b/apps/studio/src/App.vue index 9ec8afd8ba..216aee0351 100644 --- a/apps/studio/src/App.vue +++ b/apps/studio/src/App.vue @@ -62,7 +62,7 @@ export default Vue.extend({ return this.$store.state.connection }, - ...mapGetters(['storeInitialized']), + ...mapState(['storeInitialized']), ...mapGetters({ 'themeValue': 'settings/themeValue', 'menuStyle': 'settings/menuStyle' diff --git a/apps/studio/src/background.ts b/apps/studio/src/background.ts index bfaa1eab65..9a2b4d241b 100644 --- a/apps/studio/src/background.ts +++ b/apps/studio/src/background.ts @@ -74,7 +74,6 @@ async function initBasics() { log.debug("Building the window") log.debug("managing updates") manageUpdates() - manageConfigs() ipcMain.on(AppEvent.openExternally, (_e: electron.IpcMainEvent, args: any[]) => { const url = args[0] if (!url) return diff --git a/apps/studio/src/background/config_manager.ts b/apps/studio/src/background/config_manager.ts deleted file mode 100644 index 3ac0f3c0fd..0000000000 --- a/apps/studio/src/background/config_manager.ts +++ /dev/null @@ -1,148 +0,0 @@ -import * as path from "path"; -import { homedir } from "os"; -import ini from "ini"; -import rawLog from "electron-log"; -import { existsSync, readFileSync, watch, writeFileSync } from "fs"; -import platformInfo from "../common/platform_info"; -import { AppEvent } from "@/common/AppEvent"; -import { getActiveWindows } from "./WindowBuilder"; -import { ipcMain } from "electron"; - -export type ConfigType = "default" | "dev" | "user"; - -const log = rawLog.scope("config_manager"); - -const DEFAULT_CONFIG_FILENAME = "default.config.ini"; -const DEV_CONFIG_FILENAME = "local.config.ini"; -const USER_CONFIG_FILENAME = "user.config.ini"; - -function resolveConfigType(type: ConfigType) { - let location: string; - - if (platformInfo.isDevelopment) { - location = require("path").resolve("./"); - } else { - location = homedir(); - } - - if (type === "default") { - return path.join(location, DEFAULT_CONFIG_FILENAME); - } else if (type === "dev") { - return path.join(location, DEV_CONFIG_FILENAME); - } else { - return path.join(location, USER_CONFIG_FILENAME); - } -} - -async function readConfigFile(type: ConfigType) { - return new Promise((resolve, reject) => { - const filepath = resolveConfigType(type); - - log.debug("Reading user config", filepath); - - if (!existsSync(filepath)) { - // Default config must exist - if (type === "default") { - reject(new Error(`Config file ${filepath} does not exist.`)); - } else { - resolve({}); - } - return; - } - - try { - const parsed = ini.parse(readFileSync(filepath, "utf-8")); - log.debug(`Successfully read config ${filepath}`, parsed); - resolve(parsed); - } catch (error) { - log.debug(`Failed to read config ${filepath}`, error); - reject(error); - } - }); -} - -async function writeConfigFile( - type: Exclude, - config: unknown -) { - return new Promise((resolve, reject) => { - try { - log.debug("Writing user config", config); - writeFileSync(resolveConfigType(type), ini.stringify(config)); - log.info("Successfully wrote user config"); - resolve(); - } catch (error) { - log.debug(`Failed to write user config`, error); - reject(error); - } - }); -} - -function watchConfigFile(options: { - type: ConfigType; - callback: (newConfig: unknown) => void; - errorCallback?: (error: Error) => void; -}) { - const { type, callback, errorCallback } = options; - - const watcher = watch(resolveConfigType(type)); - - watcher.on("change", async () => { - callback(await readConfigFile(type)); - }); - - watcher.on("error", (error) => { - log.error(error); - if (errorCallback) { - errorCallback(error); - } - }); - - return () => { - watcher.close(); - }; -} - -export function manageConfigs() { - ipcMain.on(AppEvent.configStateManagerReady, async (event) => { - try { - const defaultConfig = await readConfigFile("default"); - event.sender.send(AppEvent.configChanged, "default", defaultConfig); - - if (platformInfo.isDevelopment) { - const devConfig = await readConfigFile("dev"); - event.sender.send(AppEvent.configChanged, "dev", devConfig); - } else { - const userConfig = await readConfigFile("user"); - event.sender.send(AppEvent.configChanged, "user", userConfig); - } - } catch (error) { - log.error(error); - } - }); - - ipcMain.on(AppEvent.saveUserConfig, (_, userConfig) => { - const type = platformInfo.isDevelopment ? "dev" : "user"; - writeConfigFile(type, userConfig); - }); - - if (platformInfo.isDevelopment) { - watchConfigFile({ - type: "default", - callback: (newConfig) => { - getActiveWindows().forEach((beeWin) => - beeWin.send(AppEvent.configChanged, "default", newConfig) - ); - }, - }); - - watchConfigFile({ - type: "dev", - callback: (newConfig) => { - getActiveWindows().forEach((beeWin) => - beeWin.send(AppEvent.configChanged, "dev", newConfig) - ); - }, - }); - } -} diff --git a/apps/studio/src/common/AppEvent.ts b/apps/studio/src/common/AppEvent.ts index d2a3bb5a33..0cbe602e9e 100644 --- a/apps/studio/src/common/AppEvent.ts +++ b/apps/studio/src/common/AppEvent.ts @@ -34,9 +34,6 @@ export enum AppEvent { togglePinTableList = 'togglePinTableList', dropzoneEnter = 'dropzoneEnter', dropzoneDrop = 'dropzoneDrop', - configChanged = 'configChanged', - configStateManagerReady = 'configStateManagerReady', - saveUserConfig = 'updateUserConfig', } export interface RootBinding { diff --git a/apps/studio/src/components/TabQueryEditor.vue b/apps/studio/src/components/TabQueryEditor.vue index 61504f9f08..2a640d6f0a 100644 --- a/apps/studio/src/components/TabQueryEditor.vue +++ b/apps/studio/src/components/TabQueryEditor.vue @@ -385,8 +385,8 @@ } }, computed: { - ...mapGetters(['dialect', 'dialectData', 'defaultSchema', 'storeInitialized']), - ...mapState(['usedConfig', 'connection', 'database', 'tables']), + ...mapGetters(['dialect', 'dialectData', 'defaultSchema']), + ...mapState(['usedConfig', 'connection', 'database', 'tables', 'storeInitialized']), ...mapState('data/queries', {'savedQueries': 'items'}), ...mapState('settings', ['settings']), ...mapState('tabs', { 'activeTab': 'active' }), diff --git a/apps/studio/src/components/data/DataManager.vue b/apps/studio/src/components/data/DataManager.vue index 07fc7223a4..5f2f85174a 100644 --- a/apps/studio/src/components/data/DataManager.vue +++ b/apps/studio/src/components/data/DataManager.vue @@ -15,7 +15,7 @@ export default Vue.extend({ }), mounted() { this.mountAndRefresh() - this.$store.commit('dataManagerInitialized') + this.$store.commit('storeInitialized', true) this.interval = setInterval(this.poll, globals.dataCheckInterval) }, beforeDestroy() { diff --git a/apps/studio/src/components/tableview/TableTable.vue b/apps/studio/src/components/tableview/TableTable.vue index 95cb9b8277..6fc1968b8a 100644 --- a/apps/studio/src/components/tableview/TableTable.vue +++ b/apps/studio/src/components/tableview/TableTable.vue @@ -307,9 +307,9 @@ export default Vue.extend({ }, computed: { ...mapState(['tables', 'tablesInitialLoaded', 'usedConfig', 'database', 'workspaceId']), - ...mapGetters(['dialectData', 'dialect', 'config']), + ...mapGetters(['dialectData', 'dialect']), limit() { - return Number(this.config.ui.tableTable.pageSize) + return Number(this.$bkConfig.ui.tableTable.pageSize) }, columnsWithFilterAndOrder() { if (!this.tabulator || !this.table) return [] @@ -692,10 +692,7 @@ export default Vue.extend({ }, pendingChangesCount() { this.tab.unsavedChanges = this.pendingChangesCount > 0 - }, - limit() { - this.tabulator.setPageSize(this.limit) - }, + } }, beforeDestroy() { if(this.interval) clearInterval(this.interval) diff --git a/apps/studio/src/main.ts b/apps/studio/src/main.ts index deb8d44fb6..59446b3dca 100644 --- a/apps/studio/src/main.ts +++ b/apps/studio/src/main.ts @@ -172,7 +172,6 @@ import { HeaderSortTabulatorModule } from './plugins/HeaderSortTabulatorModule' store, }) await app.$store.dispatch('settings/initializeSettings') - await app.$store.dispatch('configs/initialize') const handler = new AppEventHandler(ipcRenderer, app) handler.registerCallbacks() app.$mount('#app') diff --git a/apps/studio/src/plugins/ConfigPlugin.js b/apps/studio/src/plugins/ConfigPlugin.js deleted file mode 100644 index d5faa29d33..0000000000 --- a/apps/studio/src/plugins/ConfigPlugin.js +++ /dev/null @@ -1,7 +0,0 @@ -import config from '../config' - -export default { - install(Vue) { - Vue.prototype.$config = config - } -} diff --git a/apps/studio/src/plugins/ConfigPlugin.ts b/apps/studio/src/plugins/ConfigPlugin.ts new file mode 100644 index 0000000000..88d59bbff9 --- /dev/null +++ b/apps/studio/src/plugins/ConfigPlugin.ts @@ -0,0 +1,205 @@ +import config from "../config"; +import _ from "lodash"; +import * as path from "path"; +import ini from "ini"; +import rawLog from "electron-log"; +import { existsSync, readFileSync, watch, writeFileSync } from "fs"; +import platformInfo from "../common/platform_info"; +import { AppEvent } from "@/common/AppEvent"; +import { ipcRenderer } from "electron"; + +export type ConfigType = "default" | "user"; + +const log = rawLog.scope("config_manager"); + +const DEFAULT_CONFIG_FILENAME = "default.config.ini"; +const DEV_CONFIG_FILENAME = "local.config.ini"; +const USER_CONFIG_FILENAME = "user.config.ini"; + +function resolveConfigType(type: ConfigType) { + let configPath: string; + + if (platformInfo.isDevelopment) { + configPath = path.resolve(__dirname).split("/node_modules")[0]; + } else { + configPath = platformInfo.userDirectory; + } + + if (type === "default") { + return path.join(configPath, DEFAULT_CONFIG_FILENAME); + } else if (platformInfo.isDevelopment) { + return path.join(configPath, DEV_CONFIG_FILENAME); + } else { + return path.join(configPath, USER_CONFIG_FILENAME); + } +} + +async function readConfigFile(type: ConfigType) { + return new Promise((resolve, reject) => { + const filepath = resolveConfigType(type); + + log.debug("Reading user config", filepath); + + if (!existsSync(filepath)) { + // Default config must exist + if (type === "default") { + reject(new Error(`Config file ${filepath} does not exist.`)); + } else { + resolve({}); + } + return; + } + + try { + const parsed = ini.parse(readFileSync(filepath, "utf-8")); + log.debug(`Successfully read config ${filepath}`, parsed); + resolve(parsed); + } catch (error) { + log.debug(`Failed to read config ${filepath}`, error); + reject(error); + } + }); +} + +async function writeUserConfigFile() { + return new Promise((resolve, reject) => { + try { + log.debug("Writing user config", BkConfigStore.userConfig); + writeFileSync( + resolveConfigType("user"), + ini.stringify(BkConfigStore.userConfig) + ); + log.info("Successfully wrote user config"); + resolve(); + } catch (error) { + log.debug(`Failed to write user config`, error); + reject(error); + } + }); +} + +function watchConfigFile(options: { + type: ConfigType; + callback: () => void; + errorCallback?: (error: Error) => void; +}) { + const { type, callback, errorCallback } = options; + + const watcher = watch(resolveConfigType(type)); + + watcher.on("change", async () => { + callback(); + }); + + watcher.on("error", (error) => { + log.error(error); + if (errorCallback) { + errorCallback(error); + } + }); + + return () => { + watcher.close(); + }; +} + +const BkConfigStore = { + defaultConfig: {}, + userConfig: {}, + userConfigWarnings: [], +}; + +function configTester(path: string) { + return !_.isNil(_.at(BkConfigStore.userConfig, path)); +} + +function configGetter(path: string) { + return ( + _.get(BkConfigStore.userConfig, path) ?? + _.get(BkConfigStore.defaultConfig, path) + ); +} + +function configSetter(path: string, value: unknown) { + _.set(BkConfigStore.userConfig, path, value); + writeUserConfigFile().catch(log.error); +} + +function checkUserConfigWarnings() { + const warnings: { + type: "section" | "key"; + key: string; + }[] = []; + for (const section in BkConfigStore.userConfig) { + const hasSection = Object.prototype.hasOwnProperty.call( + BkConfigStore.defaultConfig, + section + ); + + if (!hasSection) { + warnings.push({ type: "section", key: section }); + continue; + } + + for (const key in BkConfigStore.userConfig[section]) { + const hasKey = Object.prototype.hasOwnProperty.call( + BkConfigStore.defaultConfig[section], + key + ); + + if (!hasKey) { + warnings.push({ type: "key", key: `${section}.${key}` }); + } + } + } + return warnings; +} + +const BkConfigHandler = new Proxy(BkConfigStore, { + get: function (_, prop) { + if (prop === "has") { + return configTester; + } + if (prop === "get") { + return configGetter; + } + if (prop === "set") { + return configSetter; + } + return configGetter(prop.toString()); + }, + set: function () { + throw new Error( + "Cannot set properties on BkConfigHandler. Please use .set instead." + ); + }, +}); + +export { BkConfigHandler as BkConfig }; + +export default { + async install(Vue: any) { + BkConfigStore.defaultConfig = await readConfigFile("default"); + BkConfigStore.userConfig = await readConfigFile("user"); + BkConfigStore.userConfigWarnings = checkUserConfigWarnings(); + + if (platformInfo.isDevelopment) { + watchConfigFile({ + type: "default", + callback: async () => { + ipcRenderer.send(AppEvent.menuClick, "reload"); + }, + }); + + watchConfigFile({ + type: "user", + callback: async () => { + ipcRenderer.send(AppEvent.menuClick, "reload"); + }, + }); + } + + Vue.prototype.$bkConfig = BkConfigHandler; + Vue.prototype.$config = config; + }, +}; diff --git a/apps/studio/src/store/index.ts b/apps/studio/src/store/index.ts index f6e7b335de..535a3a69c8 100644 --- a/apps/studio/src/store/index.ts +++ b/apps/studio/src/store/index.ts @@ -9,7 +9,6 @@ import { SavedConnection } from '../common/appdb/models/saved_connection' import ConnectionProvider from '../lib/connection-provider' import ExportStoreModule from './modules/exports/ExportStoreModule' import SettingStoreModule from './modules/settings/SettingStoreModule' -import ConfigsStoreModule from './modules/ConfigsStoreModule' import { DBConnection } from '../lib/db/client' import { Routine, TableOrView } from "../lib/db/models" import { IDbConnectionPublicServer } from '../lib/db/server' @@ -57,8 +56,8 @@ export interface State { activeTab: Nullable, selectedSidebarItem: Nullable, workspaceId: number, + storeInitialized: boolean, windowTitle: string, - dataManagerInitialized: boolean } Vue.use(Vuex) @@ -68,7 +67,6 @@ const store = new Vuex.Store({ modules: { exports: ExportStoreModule, settings: SettingStoreModule, - configs: ConfigsStoreModule, pins: PinModule, tabs: TabModule, search: SearchModule, @@ -100,17 +98,11 @@ const store = new Vuex.Store({ activeTab: null, selectedSidebarItem: null, workspaceId: LocalWorkspace.id, + storeInitialized: false, windowTitle: 'Beekeeper Studio', - dataManagerInitialized: false, }, getters: { - storeInitialized(state) { - return state.dataManagerInitialized && state['configs'].initialized - }, - config(_, getters) { - return getters['configs/config'] - }, defaultSchema(state: State) { return state.connection.defaultSchema ? state.connection.defaultSchema() : @@ -198,8 +190,8 @@ const store = new Vuex.Store({ } }, mutations: { - dataManagerInitialized(state) { - state.dataManagerInitialized = true + storeInitialized(state, b: boolean) { + state.storeInitialized = b }, workspaceId(state, id: number) { state.workspaceId = id diff --git a/apps/studio/src/store/modules/ConfigsStoreModule.ts b/apps/studio/src/store/modules/ConfigsStoreModule.ts deleted file mode 100644 index 2fde3cd474..0000000000 --- a/apps/studio/src/store/modules/ConfigsStoreModule.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { ConfigType } from "@/background/config_manager"; -import { AppEvent } from "@/common/AppEvent"; -import platformInfo from "@/common/platform_info"; -import { ipcRenderer } from "electron"; -import _ from "lodash"; -import { Module } from "vuex"; - -interface State { - defaultConfig: any; - userConfig: any; - devConfig: any; - initialized: boolean; -} - -const ConfigsStoreModule: Module = { - namespaced: true, - state: () => ({ - defaultConfig: {}, - userConfig: {}, - devConfig: {}, - initialized: false, - }), - mutations: { - setInitialized(state) { - state.initialized = true; - }, - setConfig(state, { type, config }: { type: ConfigType; config: unknown }) { - if (type === "default") { - state.defaultConfig = config; - } else if (type === "dev") { - state.devConfig = config; - } else if (type === "user") { - state.userConfig = config; - } - }, - }, - actions: { - async initialize(context) { - ipcRenderer.on( - AppEvent.configChanged, - (_, type: ConfigType, config: unknown) => { - context.commit("setConfig", { type, config }); - context.commit("setInitialized"); - } - ); - ipcRenderer.send(AppEvent.configStateManagerReady); - }, - async setKeyValue(context, { key, value }) { - context.commit("setConfig", key, value); - ipcRenderer.send(AppEvent.saveUserConfig, context.state.userConfig); - }, - }, - getters: { - config(state) { - if (platformInfo.isDevelopment) { - return _.merge({}, state.defaultConfig, state.devConfig); - } else { - return _.merge({}, state.defaultConfig, state.userConfig); - } - }, - warnings(state) { - const warnings: { - type: "section" | "key"; - key: string; - }[] = []; - - let matchConfig: any; - if (platformInfo.isDevelopment) { - matchConfig = state.devConfig; - } else { - matchConfig = state.userConfig; - } - - for (const section in matchConfig) { - const hasSection = Object.prototype.hasOwnProperty.call( - state.defaultConfig, - section - ); - - if (!hasSection) { - warnings.push({ type: "section", key: section }); - continue; - } - - for (const key in matchConfig[section]) { - const hasKey = Object.prototype.hasOwnProperty.call( - state.defaultConfig[section], - key - ); - - if (!hasKey) { - warnings.push({ type: "key", key: `${section}.${key}` }); - } - } - } - - return warnings; - }, - }, -}; - -export default ConfigsStoreModule; diff --git a/apps/studio/default.config.ini b/default.config.ini similarity index 100% rename from apps/studio/default.config.ini rename to default.config.ini diff --git a/apps/studio/local.config.ini b/local.config.ini similarity index 100% rename from apps/studio/local.config.ini rename to local.config.ini