From 5712e88785d600a63d59cb583f045375c8c16255 Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Tue, 28 Dec 2021 13:51:16 +0100 Subject: refactor: Functional design for controllers --- packages/main/src/CompositionRoot.ts | 37 -------- packages/main/src/compositionRoot.ts | 38 +++++++++ packages/main/src/controllers/ConfigController.ts | 99 ---------------------- .../main/src/controllers/NativeThemeController.ts | 42 --------- packages/main/src/controllers/config.ts | 93 ++++++++++++++++++++ packages/main/src/controllers/nativeTheme.ts | 42 +++++++++ packages/main/src/index.ts | 10 +-- .../main/src/services/ConfigPersistenceService.ts | 2 +- packages/main/src/utils.ts | 27 +----- 9 files changed, 180 insertions(+), 210 deletions(-) delete mode 100644 packages/main/src/CompositionRoot.ts create mode 100644 packages/main/src/compositionRoot.ts delete mode 100644 packages/main/src/controllers/ConfigController.ts delete mode 100644 packages/main/src/controllers/NativeThemeController.ts create mode 100644 packages/main/src/controllers/config.ts create mode 100644 packages/main/src/controllers/nativeTheme.ts (limited to 'packages/main') diff --git a/packages/main/src/CompositionRoot.ts b/packages/main/src/CompositionRoot.ts deleted file mode 100644 index affb186..0000000 --- a/packages/main/src/CompositionRoot.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2021-2022 Kristóf Marussy - * - * This file is part of Sophie. - * - * Sophie is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { app } from 'electron'; - -import { ConfigController } from './controllers/ConfigController'; -import { NativeThemeController } from './controllers/NativeThemeController'; -import { ConfigPersistenceService } from './services/ConfigPersistenceService'; -import { MainStore } from './stores/MainStore'; -import { DisposeHelper } from './utils'; - -export class CompositionRoot extends DisposeHelper { - async init(store: MainStore): Promise { - const configPersistenceService = new ConfigPersistenceService(app.getPath('userData')); - await this.registerDisposable(new ConfigController( - configPersistenceService, - )).connect(store.config); - this.registerDisposable(new NativeThemeController()).connect(store); - } -} diff --git a/packages/main/src/compositionRoot.ts b/packages/main/src/compositionRoot.ts new file mode 100644 index 0000000..eb6f50f --- /dev/null +++ b/packages/main/src/compositionRoot.ts @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021-2022 Kristóf Marussy + * + * This file is part of Sophie. + * + * Sophie is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { app } from 'electron'; + +import { initConfig } from './controllers/config'; +import { initNativeTheme } from './controllers/nativeTheme'; +import { ConfigPersistenceService } from './services/ConfigPersistenceService'; +import { MainStore } from './stores/MainStore'; +import { Disposer } from './utils'; + +export async function init(store: MainStore): Promise { + const configPersistenceService = new ConfigPersistenceService(app.getPath('userData')); + const disposeConfigController = await initConfig(store.config, configPersistenceService); + const disposeNativeThemeController = initNativeTheme(store); + + return () => { + disposeNativeThemeController(); + disposeConfigController(); + }; +} diff --git a/packages/main/src/controllers/ConfigController.ts b/packages/main/src/controllers/ConfigController.ts deleted file mode 100644 index 318506f..0000000 --- a/packages/main/src/controllers/ConfigController.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2021-2022 Kristóf Marussy - * - * This file is part of Sophie. - * - * Sophie is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { debounce } from 'lodash'; -import ms from 'ms'; -import { applySnapshot, getSnapshot, onSnapshot } from 'mobx-state-tree'; - -import type { ConfigPersistenceService } from '../services/ConfigPersistenceService'; -import type { Config, ConfigSnapshotOut } from '../stores/Config'; -import { DisposeHelper } from '../utils'; - -const DEFAULT_CONFIG_DEBOUNCE_TIME = ms('1s'); - -export class ConfigController extends DisposeHelper { - private config: Config | null = null; - - private lastSnapshotOnDisk: ConfigSnapshotOut | null = null; - - private writingConfig: boolean = false; - - constructor( - private readonly persistenceService: ConfigPersistenceService, - private readonly debounceTime: number = DEFAULT_CONFIG_DEBOUNCE_TIME, - ) { - super(); - } - - async connect(config: Config): Promise { - this.config = config; - - const foundConfig: boolean = await this.readConfig(); - if (!foundConfig) { - console.log('Creating new config file'); - try { - await this.writeConfig(); - } catch (err) { - console.error('Failed to initialize config'); - } - } - - this.registerDisposable(onSnapshot(this.config, debounce((snapshot) => { - // We can compare snapshots by reference, since it is only recreated on store changes. - if (this.lastSnapshotOnDisk !== snapshot) { - this.writeConfig().catch((err) => { - console.log('Failed to write config on config change', err); - }) - } - }, this.debounceTime))); - - this.registerDisposable(this.persistenceService.watchConfig(async () => { - if (!this.writingConfig) { - await this.readConfig(); - } - }, this.debounceTime)); - } - - private async readConfig(): Promise { - const result = await this.persistenceService.readConfig(); - if (result.found) { - try { - applySnapshot(this.config!, result.data); - this.lastSnapshotOnDisk = getSnapshot(this.config!); - console.log('Loaded config'); - } catch (err) { - console.error('Failed to read config', result.data, err); - } - } - return result.found; - } - - private async writeConfig(): Promise { - const snapshot = getSnapshot(this.config!); - this.writingConfig = true; - try { - await this.persistenceService.writeConfig(snapshot); - this.lastSnapshotOnDisk = snapshot; - console.log('Wrote config'); - } finally { - this.writingConfig = false; - } - } -} diff --git a/packages/main/src/controllers/NativeThemeController.ts b/packages/main/src/controllers/NativeThemeController.ts deleted file mode 100644 index 931660c..0000000 --- a/packages/main/src/controllers/NativeThemeController.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2021-2022 Kristóf Marussy - * - * This file is part of Sophie. - * - * Sophie is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, version 3. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { nativeTheme } from 'electron'; -import { autorun } from 'mobx'; - -import type { MainStore } from '../stores/MainStore'; -import { DisposeHelper } from '../utils'; - -export class NativeThemeController extends DisposeHelper { - connect(store: MainStore): void { - this.registerDisposable(autorun(() => { - nativeTheme.themeSource = store.config.themeSource; - })); - - store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors); - const shouldUseDarkColorsListener = () => { - store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors); - }; - nativeTheme.on('updated', shouldUseDarkColorsListener); - this.registerDisposable(() => { - nativeTheme.off('updated', shouldUseDarkColorsListener); - }); - } -} diff --git a/packages/main/src/controllers/config.ts b/packages/main/src/controllers/config.ts new file mode 100644 index 0000000..c7b027d --- /dev/null +++ b/packages/main/src/controllers/config.ts @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021-2022 Kristóf Marussy + * + * This file is part of Sophie. + * + * Sophie is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { debounce } from 'lodash'; +import ms from 'ms'; +import { applySnapshot, getSnapshot, onSnapshot } from 'mobx-state-tree'; + +import type { ConfigPersistenceService } from '../services/ConfigPersistenceService'; +import type { Config, ConfigSnapshotOut } from '../stores/Config'; +import { Disposer } from '../utils'; + +const DEFAULT_CONFIG_DEBOUNCE_TIME = ms('1s'); + +export async function initConfig( + config: Config, + persistenceService: ConfigPersistenceService, + debounceTime: number = DEFAULT_CONFIG_DEBOUNCE_TIME, +): Promise { + let lastSnapshotOnDisk: ConfigSnapshotOut | null = null; + let writingConfig: boolean = false; + + async function readConfig(): Promise { + const result = await persistenceService.readConfig(); + if (result.found) { + try { + applySnapshot(config, result.data); + lastSnapshotOnDisk = getSnapshot(config); + console.log('Loaded config'); + } catch (err) { + console.error('Failed to read config', result.data, err); + } + } + return result.found; + } + + async function writeConfig(): Promise { + const snapshot = getSnapshot(config); + writingConfig = true; + try { + await persistenceService.writeConfig(snapshot); + lastSnapshotOnDisk = snapshot; + console.log('Wrote config'); + } finally { + writingConfig = false; + } + } + + if (!await readConfig()) { + console.log('Creating new config file'); + try { + await writeConfig(); + } catch (err) { + console.error('Failed to initialize config'); + } + } + + const disposeOnSnapshot = onSnapshot(config, debounce((snapshot) => { + // We can compare snapshots by reference, since it is only recreated on store changes. + if (lastSnapshotOnDisk !== snapshot) { + writeConfig().catch((err) => { + console.log('Failed to write config on config change', err); + }) + } + }, debounceTime)); + + const disposeWatcher = persistenceService.watchConfig(async () => { + if (!writingConfig) { + await readConfig(); + } + }, debounceTime); + + return () => { + disposeWatcher(); + disposeOnSnapshot(); + }; +} diff --git a/packages/main/src/controllers/nativeTheme.ts b/packages/main/src/controllers/nativeTheme.ts new file mode 100644 index 0000000..e4390a8 --- /dev/null +++ b/packages/main/src/controllers/nativeTheme.ts @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2021-2022 Kristóf Marussy + * + * This file is part of Sophie. + * + * Sophie is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { nativeTheme } from 'electron'; +import { autorun } from 'mobx'; + +import type { MainStore } from '../stores/MainStore'; +import { Disposer } from '../utils'; + +export function initNativeTheme(store: MainStore): Disposer { + const disposeThemeSourceReaction = autorun(() => { + nativeTheme.themeSource = store.config.themeSource; + }); + + store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors); + const shouldUseDarkColorsListener = () => { + store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors); + }; + nativeTheme.on('updated', shouldUseDarkColorsListener); + + return () => { + nativeTheme.off('updated', shouldUseDarkColorsListener); + disposeThemeSourceReaction(); + }; +} diff --git a/packages/main/src/index.ts b/packages/main/src/index.ts index 3e9e338..7c7be35 100644 --- a/packages/main/src/index.ts +++ b/packages/main/src/index.ts @@ -41,7 +41,7 @@ import { } from '@sophie/shared'; import { URL } from 'url'; -import { CompositionRoot } from './CompositionRoot'; +import { init } from './compositionRoot'; import { installDevToolsExtensions, openDevToolsWhenReady, @@ -104,13 +104,11 @@ if (isDevelopment) { let mainWindow: BrowserWindow | null = null; const store = createMainStore(); -const compositionRoot = new CompositionRoot(); -compositionRoot.init(store).catch((err) => { +init(store).then((disposeCompositionRoot) => { + app.on('will-quit', disposeCompositionRoot); +}).catch((err) => { console.log('Failed to initialize application', err); }); -app.on('will-quit', () => { - compositionRoot.dispose(); -}); const rendererBaseUrl = getResourceUrl('../renderer/'); function shouldCancelMainWindowRequest(url: string, method: string): boolean { diff --git a/packages/main/src/services/ConfigPersistenceService.ts b/packages/main/src/services/ConfigPersistenceService.ts index 34d0e3e..1c0315f 100644 --- a/packages/main/src/services/ConfigPersistenceService.ts +++ b/packages/main/src/services/ConfigPersistenceService.ts @@ -24,7 +24,7 @@ import { throttle } from 'lodash'; import { join } from 'path'; import type { ConfigSnapshotOut } from '../stores/Config'; -import type { Disposer } from '../utils'; +import { Disposer } from '../utils'; export type ReadConfigResult = { found: true; data: unknown; } | { found: false; }; diff --git a/packages/main/src/utils.ts b/packages/main/src/utils.ts index 11c78e9..0d469dd 100644 --- a/packages/main/src/utils.ts +++ b/packages/main/src/utils.ts @@ -18,29 +18,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -export type Disposable = Disposer | DisposableObject; +import { IDisposer } from 'mobx-state-tree'; -export type Disposer = () => void; - -export interface DisposableObject { - dispose(): void; -} - -export class DisposeHelper implements DisposableObject { - private readonly disposers: Disposer[] = []; - - protected registerDisposable(disposable: T): T { - if (typeof disposable === 'object') { - this.disposers.push(() => disposable.dispose()); - } else { - this.disposers.push(disposable); - } - return disposable; - } - - dispose(): void { - for (let i = this.disposers.length - 1; i >= 0; i -= 1) { - this.disposers[i](); - } - } -} +export type Disposer = IDisposer; -- cgit v1.2.3-54-g00ecf