aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/stores
diff options
context:
space:
mode:
authorLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-26 19:17:23 +0100
committerLibravatar Kristóf Marussy <kristof@marussy.com>2021-12-26 19:17:23 +0100
commite56cdad02c00adf3b779d9de62d460e78be204a6 (patch)
tree638d8ac94380b3d675f13c6eabc1e5ee51126342 /packages/main/src/stores
parentrefactor: Config persistence architecture (diff)
downloadsophie-e56cdad02c00adf3b779d9de62d460e78be204a6.tar.gz
sophie-e56cdad02c00adf3b779d9de62d460e78be204a6.tar.zst
sophie-e56cdad02c00adf3b779d9de62d460e78be204a6.zip
refactor: Clarify main process architecture
* stores: reactive data structures to hold application state * controllers: subscribe to store changes and call store actions in response to external events from services * services: integrate with the nodejs and electron environment (should be mocked for unit testing)
Diffstat (limited to 'packages/main/src/stores')
-rw-r--r--packages/main/src/stores/Config.ts87
-rw-r--r--packages/main/src/stores/MainStore.ts16
2 files changed, 8 insertions, 95 deletions
diff --git a/packages/main/src/stores/Config.ts b/packages/main/src/stores/Config.ts
index eb53635..7d1168f 100644
--- a/packages/main/src/stores/Config.ts
+++ b/packages/main/src/stores/Config.ts
@@ -18,15 +18,7 @@
18 * SPDX-License-Identifier: AGPL-3.0-only 18 * SPDX-License-Identifier: AGPL-3.0-only
19 */ 19 */
20 20
21import { debounce } from 'lodash'; 21import { Instance } from 'mobx-state-tree';
22import {
23 applySnapshot,
24 flow,
25 getSnapshot,
26 IDisposer,
27 Instance,
28 onSnapshot,
29} from 'mobx-state-tree';
30import { 22import {
31 config as originalConfig, 23 config as originalConfig,
32 ConfigSnapshotIn, 24 ConfigSnapshotIn,
@@ -34,86 +26,11 @@ import {
34 ThemeSource, 26 ThemeSource,
35} from '@sophie/shared'; 27} from '@sophie/shared';
36 28
37import { CONFIG_DEBOUNCE_TIME, ReadConfigResult } from '../services/ConfigPersistence';
38import { getEnv } from '../services/MainEnv';
39
40export const config = originalConfig.actions((self) => ({ 29export const config = originalConfig.actions((self) => ({
41 setThemeSource(mode: ThemeSource) { 30 setThemeSource(mode: ThemeSource) {
42 self.themeSource = mode; 31 self.themeSource = mode;
43 }, 32 },
44})).actions((self) => { 33}));
45 let lastSnapshotOnDisk: ConfigSnapshotOut | null = null;
46 let writingConfig = false;
47 let configMtime: Date | null = null;
48 let onSnapshotDisposer: IDisposer | null = null;
49 let watcherDisposer: IDisposer | null = null;
50
51 function dispose() {
52 onSnapshotDisposer?.();
53 watcherDisposer?.();
54 }
55
56 const actions: {
57 beforeDetach(): void,
58 readConfig(): Promise<boolean>;
59 writeConfig(): Promise<void>;
60 initConfig(): Promise<void>;
61 } = {
62 beforeDetach() {
63 dispose();
64 },
65 readConfig: flow(function*() {
66 const result: ReadConfigResult = yield getEnv(self).configPersistence.readConfig();
67 if (result.found) {
68 try {
69 applySnapshot(self, result.data);
70 lastSnapshotOnDisk = getSnapshot(self);
71 console.log('Loaded config');
72 } catch (err) {
73 console.error('Failed to read config', result.data, err);
74 }
75 }
76 return result.found;
77 }),
78 writeConfig: flow(function*() {
79 const snapshot = getSnapshot(self);
80 writingConfig = true;
81 try {
82 configMtime = yield getEnv(self).configPersistence.writeConfig(snapshot);
83 lastSnapshotOnDisk = snapshot;
84 console.log('Wrote config');
85 } finally {
86 writingConfig = false;
87 }
88 }),
89 initConfig: flow(function*() {
90 dispose();
91 const foundConfig: boolean = yield actions.readConfig();
92 if (!foundConfig) {
93 console.log('Creating new config file');
94 try {
95 yield actions.writeConfig();
96 } catch (err) {
97 console.error('Failed to initialize config');
98 }
99 }
100 onSnapshotDisposer = onSnapshot(self, debounce((snapshot) => {
101 // We can compare snapshots by reference, since it is only recreated on store changes.
102 if (lastSnapshotOnDisk !== snapshot) {
103 actions.writeConfig().catch((err) => {
104 console.log('Failed to write config on config change', err);
105 })
106 }
107 }, CONFIG_DEBOUNCE_TIME));
108 watcherDisposer = getEnv(self).configPersistence.watchConfig(async (mtime) => {
109 if (!writingConfig && (configMtime === null || mtime > configMtime)) {
110 await actions.readConfig();
111 }
112 });
113 }),
114 };
115 return actions;
116});
117 34
118export interface Config extends Instance<typeof config> {} 35export interface Config extends Instance<typeof config> {}
119 36
diff --git a/packages/main/src/stores/MainStore.ts b/packages/main/src/stores/MainStore.ts
index ee215a7..4b85c22 100644
--- a/packages/main/src/stores/MainStore.ts
+++ b/packages/main/src/stores/MainStore.ts
@@ -22,7 +22,6 @@ import { applySnapshot, Instance, types } from 'mobx-state-tree';
22import { BrowserViewBounds, emptySharedStore } from '@sophie/shared'; 22import { BrowserViewBounds, emptySharedStore } from '@sophie/shared';
23 23
24import type { Config } from './Config'; 24import type { Config } from './Config';
25import { MainEnv } from '../services/MainEnv';
26import { sharedStore } from './SharedStore'; 25import { sharedStore } from './SharedStore';
27 26
28export const mainStore = types.model('MainStore', { 27export const mainStore = types.model('MainStore', {
@@ -46,14 +45,11 @@ export const mainStore = types.model('MainStore', {
46 } 45 }
47})); 46}));
48 47
49export interface RootStore extends Instance<typeof mainStore> {} 48export interface MainStore extends Instance<typeof mainStore> {}
50 49
51export function createMainStore(env: MainEnv): RootStore { 50export function createMainStore(): MainStore {
52 return mainStore.create( 51 return mainStore.create({
53 { 52 browserViewBounds: {},
54 browserViewBounds: {}, 53 shared: emptySharedStore,
55 shared: emptySharedStore, 54 });
56 },
57 env,
58 );
59} 55}