diff options
-rw-r--r-- | packages/main/package.json | 1 | ||||
-rw-r--r-- | packages/main/src/controllers/config.ts | 32 | ||||
-rw-r--r-- | packages/main/src/controllers/nativeTheme.ts | 10 | ||||
-rw-r--r-- | packages/main/src/services/ConfigPersistenceService.ts | 39 | ||||
-rw-r--r-- | packages/main/src/utils/logging.ts | 31 | ||||
-rw-r--r-- | packages/renderer/src/index.tsx | 1 | ||||
-rw-r--r-- | yarn.lock | 8 |
7 files changed, 79 insertions, 43 deletions
diff --git a/packages/main/package.json b/packages/main/package.json index 76eff8e..a1af489 100644 --- a/packages/main/package.json +++ b/packages/main/package.json | |||
@@ -11,6 +11,7 @@ | |||
11 | "dependencies": { | 11 | "dependencies": { |
12 | "@sophie/service-shared": "workspace:*", | 12 | "@sophie/service-shared": "workspace:*", |
13 | "@sophie/shared": "workspace:*", | 13 | "@sophie/shared": "workspace:*", |
14 | "chalk": "^5.0.0", | ||
14 | "electron": "16.0.5", | 15 | "electron": "16.0.5", |
15 | "json5": "^2.2.0", | 16 | "json5": "^2.2.0", |
16 | "lodash-es": "^4.17.21", | 17 | "lodash-es": "^4.17.21", |
diff --git a/packages/main/src/controllers/config.ts b/packages/main/src/controllers/config.ts index f2467c7..600a142 100644 --- a/packages/main/src/controllers/config.ts +++ b/packages/main/src/controllers/config.ts | |||
@@ -28,17 +28,16 @@ import { Disposer, getLogger } from '../utils'; | |||
28 | 28 | ||
29 | const DEFAULT_CONFIG_DEBOUNCE_TIME = ms('1s'); | 29 | const DEFAULT_CONFIG_DEBOUNCE_TIME = ms('1s'); |
30 | 30 | ||
31 | const logger = getLogger('controller:config'); | 31 | const log = getLogger('config'); |
32 | 32 | ||
33 | export async function initConfig( | 33 | export async function initConfig( |
34 | config: Config, | 34 | config: Config, |
35 | persistenceService: ConfigPersistenceService, | 35 | persistenceService: ConfigPersistenceService, |
36 | debounceTime: number = DEFAULT_CONFIG_DEBOUNCE_TIME, | 36 | debounceTime: number = DEFAULT_CONFIG_DEBOUNCE_TIME, |
37 | ): Promise<Disposer> { | 37 | ): Promise<Disposer> { |
38 | logger.debug('Initializing controller'); | 38 | log.trace('Initializing config controller'); |
39 | 39 | ||
40 | let lastSnapshotOnDisk: ConfigSnapshotOut | null = null; | 40 | let lastSnapshotOnDisk: ConfigSnapshotOut | null = null; |
41 | let writingConfig: boolean = false; | ||
42 | 41 | ||
43 | async function readConfig(): Promise<boolean> { | 42 | async function readConfig(): Promise<boolean> { |
44 | const result = await persistenceService.readConfig(); | 43 | const result = await persistenceService.readConfig(); |
@@ -46,9 +45,8 @@ export async function initConfig( | |||
46 | try { | 45 | try { |
47 | applySnapshot(config, result.data); | 46 | applySnapshot(config, result.data); |
48 | lastSnapshotOnDisk = getSnapshot(config); | 47 | lastSnapshotOnDisk = getSnapshot(config); |
49 | logger.debug('Loaded config'); | ||
50 | } catch (err) { | 48 | } catch (err) { |
51 | logger.error('Failed to read config', result.data, err); | 49 | log.error('Failed to apply config snapshot', result.data, err); |
52 | } | 50 | } |
53 | } | 51 | } |
54 | return result.found; | 52 | return result.found; |
@@ -56,23 +54,17 @@ export async function initConfig( | |||
56 | 54 | ||
57 | async function writeConfig(): Promise<void> { | 55 | async function writeConfig(): Promise<void> { |
58 | const snapshot = getSnapshot(config); | 56 | const snapshot = getSnapshot(config); |
59 | writingConfig = true; | 57 | await persistenceService.writeConfig(snapshot); |
60 | try { | 58 | lastSnapshotOnDisk = snapshot; |
61 | await persistenceService.writeConfig(snapshot); | ||
62 | lastSnapshotOnDisk = snapshot; | ||
63 | logger.debug('Wrote config'); | ||
64 | } finally { | ||
65 | writingConfig = false; | ||
66 | } | ||
67 | } | 59 | } |
68 | 60 | ||
69 | if (!await readConfig()) { | 61 | if (!await readConfig()) { |
70 | logger.info('Config file was not found'); | 62 | log.info('Config file was not found'); |
71 | try { | 63 | try { |
72 | await writeConfig(); | 64 | await writeConfig(); |
73 | logger.info('Created config file'); | 65 | log.info('Created config file'); |
74 | } catch (err) { | 66 | } catch (err) { |
75 | logger.error('Failed to initialize config'); | 67 | log.error('Failed to initialize config', err); |
76 | } | 68 | } |
77 | } | 69 | } |
78 | 70 | ||
@@ -80,19 +72,17 @@ export async function initConfig( | |||
80 | // We can compare snapshots by reference, since it is only recreated on store changes. | 72 | // We can compare snapshots by reference, since it is only recreated on store changes. |
81 | if (lastSnapshotOnDisk !== snapshot) { | 73 | if (lastSnapshotOnDisk !== snapshot) { |
82 | writeConfig().catch((err) => { | 74 | writeConfig().catch((err) => { |
83 | logger.error('Failed to write config on config change', err); | 75 | log.error('Failed to write config on config change', err); |
84 | }) | 76 | }) |
85 | } | 77 | } |
86 | }, debounceTime)); | 78 | }, debounceTime)); |
87 | 79 | ||
88 | const disposeWatcher = persistenceService.watchConfig(async () => { | 80 | const disposeWatcher = persistenceService.watchConfig(async () => { |
89 | if (!writingConfig) { | 81 | await readConfig(); |
90 | await readConfig(); | ||
91 | } | ||
92 | }, debounceTime); | 82 | }, debounceTime); |
93 | 83 | ||
94 | return () => { | 84 | return () => { |
95 | logger.debug('Disposing controller'); | 85 | log.trace('Disposing config controller'); |
96 | disposeWatcher(); | 86 | disposeWatcher(); |
97 | disposeOnSnapshot(); | 87 | disposeOnSnapshot(); |
98 | }; | 88 | }; |
diff --git a/packages/main/src/controllers/nativeTheme.ts b/packages/main/src/controllers/nativeTheme.ts index 9f9bc21..9edb4e8 100644 --- a/packages/main/src/controllers/nativeTheme.ts +++ b/packages/main/src/controllers/nativeTheme.ts | |||
@@ -24,25 +24,25 @@ import { autorun } from 'mobx'; | |||
24 | import type { MainStore } from '../stores/MainStore'; | 24 | import type { MainStore } from '../stores/MainStore'; |
25 | import { Disposer, getLogger } from '../utils'; | 25 | import { Disposer, getLogger } from '../utils'; |
26 | 26 | ||
27 | const logger = getLogger('controller:nativeTheme'); | 27 | const log = getLogger('nativeTheme'); |
28 | 28 | ||
29 | export function initNativeTheme(store: MainStore): Disposer { | 29 | export function initNativeTheme(store: MainStore): Disposer { |
30 | logger.debug('Initializing controller'); | 30 | log.trace('Initializing nativeTheme controller'); |
31 | 31 | ||
32 | const disposeThemeSourceReaction = autorun(() => { | 32 | const disposeThemeSourceReaction = autorun(() => { |
33 | nativeTheme.themeSource = store.config.themeSource; | 33 | nativeTheme.themeSource = store.config.themeSource; |
34 | logger.debug('Set theme source:', store.config.themeSource); | 34 | log.debug('Set theme source:', store.config.themeSource); |
35 | }); | 35 | }); |
36 | 36 | ||
37 | store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors); | 37 | store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors); |
38 | const shouldUseDarkColorsListener = () => { | 38 | const shouldUseDarkColorsListener = () => { |
39 | store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors); | 39 | store.setShouldUseDarkColors(nativeTheme.shouldUseDarkColors); |
40 | logger.debug('Set should use dark colors:', nativeTheme.shouldUseDarkColors); | 40 | log.debug('Set should use dark colors:', nativeTheme.shouldUseDarkColors); |
41 | }; | 41 | }; |
42 | nativeTheme.on('updated', shouldUseDarkColorsListener); | 42 | nativeTheme.on('updated', shouldUseDarkColorsListener); |
43 | 43 | ||
44 | return () => { | 44 | return () => { |
45 | logger.debug('Disposing controller'); | 45 | log.trace('Disposing nativeTheme controller'); |
46 | nativeTheme.off('updated', shouldUseDarkColorsListener); | 46 | nativeTheme.off('updated', shouldUseDarkColorsListener); |
47 | disposeThemeSourceReaction(); | 47 | disposeThemeSourceReaction(); |
48 | }; | 48 | }; |
diff --git a/packages/main/src/services/ConfigPersistenceService.ts b/packages/main/src/services/ConfigPersistenceService.ts index ee5eb9f..b2109f6 100644 --- a/packages/main/src/services/ConfigPersistenceService.ts +++ b/packages/main/src/services/ConfigPersistenceService.ts | |||
@@ -24,13 +24,17 @@ import throttle from 'lodash-es/throttle'; | |||
24 | import { join } from 'path'; | 24 | import { join } from 'path'; |
25 | 25 | ||
26 | import type { ConfigSnapshotOut } from '../stores/Config'; | 26 | import type { ConfigSnapshotOut } from '../stores/Config'; |
27 | import { Disposer } from '../utils'; | 27 | import { Disposer, getLogger } from '../utils'; |
28 | |||
29 | const log = getLogger('configPersistence'); | ||
28 | 30 | ||
29 | export type ReadConfigResult = { found: true; data: unknown; } | { found: false; }; | 31 | export type ReadConfigResult = { found: true; data: unknown; } | { found: false; }; |
30 | 32 | ||
31 | export class ConfigPersistenceService { | 33 | export class ConfigPersistenceService { |
32 | private readonly configFilePath: string; | 34 | private readonly configFilePath: string; |
33 | 35 | ||
36 | private writingConfig = false; | ||
37 | |||
34 | private timeLastWritten: Date | null = null; | 38 | private timeLastWritten: Date | null = null; |
35 | 39 | ||
36 | constructor( | 40 | constructor( |
@@ -47,10 +51,12 @@ export class ConfigPersistenceService { | |||
47 | configStr = await readFile(this.configFilePath, 'utf8'); | 51 | configStr = await readFile(this.configFilePath, 'utf8'); |
48 | } catch (err) { | 52 | } catch (err) { |
49 | if ((err as NodeJS.ErrnoException).code === 'ENOENT') { | 53 | if ((err as NodeJS.ErrnoException).code === 'ENOENT') { |
54 | log.debug('Config file', this.configFilePath, 'was not found'); | ||
50 | return { found: false }; | 55 | return { found: false }; |
51 | } | 56 | } |
52 | throw err; | 57 | throw err; |
53 | } | 58 | } |
59 | log.info('Read config file', this.configFilePath); | ||
54 | return { | 60 | return { |
55 | found: true, | 61 | found: true, |
56 | data: JSON5.parse(configStr), | 62 | data: JSON5.parse(configStr), |
@@ -61,24 +67,42 @@ export class ConfigPersistenceService { | |||
61 | const configJson = JSON5.stringify(configSnapshot, { | 67 | const configJson = JSON5.stringify(configSnapshot, { |
62 | space: 2, | 68 | space: 2, |
63 | }); | 69 | }); |
64 | await writeFile(this.configFilePath, configJson, 'utf8'); | 70 | this.writingConfig = true; |
65 | const stats = await stat(this.configFilePath); | 71 | try { |
66 | this.timeLastWritten = stats.mtime; | 72 | await writeFile(this.configFilePath, configJson, 'utf8'); |
73 | const { mtime } = await stat(this.configFilePath); | ||
74 | log.trace('Config file', this.configFilePath, 'last written at', mtime); | ||
75 | this.timeLastWritten = mtime; | ||
76 | } finally { | ||
77 | this.writingConfig = false; | ||
78 | } | ||
79 | log.info('Wrote config file', this.configFilePath); | ||
67 | } | 80 | } |
68 | 81 | ||
69 | watchConfig(callback: () => Promise<void>, throttleMs: number): Disposer { | 82 | watchConfig(callback: () => Promise<void>, throttleMs: number): Disposer { |
83 | log.debug('Installing watcher for', this.userDataDir); | ||
84 | |||
70 | const configChanged = throttle(async () => { | 85 | const configChanged = throttle(async () => { |
71 | let mtime: Date; | 86 | let mtime: Date; |
72 | try { | 87 | try { |
73 | const stats = await stat(this.configFilePath); | 88 | const stats = await stat(this.configFilePath); |
74 | mtime = stats.mtime; | 89 | mtime = stats.mtime; |
90 | log.trace('Config file last modified at', mtime); | ||
75 | } catch (err) { | 91 | } catch (err) { |
76 | if ((err as NodeJS.ErrnoException).code === 'ENOENT') { | 92 | if ((err as NodeJS.ErrnoException).code === 'ENOENT') { |
93 | log.debug('Config file', this.configFilePath, 'was deleted after being changed'); | ||
77 | return; | 94 | return; |
78 | } | 95 | } |
79 | throw err; | 96 | throw err; |
80 | } | 97 | } |
81 | if (this.timeLastWritten === null || mtime > this.timeLastWritten) { | 98 | if (!this.writingConfig |
99 | && (this.timeLastWritten === null || mtime > this.timeLastWritten)) { | ||
100 | log.debug( | ||
101 | 'Found a config file modified at', | ||
102 | mtime, | ||
103 | 'whish is newer than last written', | ||
104 | this.timeLastWritten, | ||
105 | ); | ||
82 | return callback(); | 106 | return callback(); |
83 | } | 107 | } |
84 | }, throttleMs); | 108 | }, throttleMs); |
@@ -96,6 +120,9 @@ export class ConfigPersistenceService { | |||
96 | } | 120 | } |
97 | }); | 121 | }); |
98 | 122 | ||
99 | return () => watcher.close(); | 123 | return () => { |
124 | log.trace('Removing watcher for', this.configFilePath); | ||
125 | watcher.close(); | ||
126 | }; | ||
100 | } | 127 | } |
101 | } | 128 | } |
diff --git a/packages/main/src/utils/logging.ts b/packages/main/src/utils/logging.ts index 5cb5d21..6c4cba2 100644 --- a/packages/main/src/utils/logging.ts +++ b/packages/main/src/utils/logging.ts | |||
@@ -18,28 +18,37 @@ | |||
18 | * SPDX-License-Identifier: AGPL-3.0-only | 18 | * SPDX-License-Identifier: AGPL-3.0-only |
19 | */ | 19 | */ |
20 | 20 | ||
21 | import chalk, { ChalkInstance } from 'chalk'; | ||
21 | import loglevel, { Logger } from 'loglevel'; | 22 | import loglevel, { Logger } from 'loglevel'; |
22 | import prefix from 'loglevel-plugin-prefix'; | 23 | import prefix from 'loglevel-plugin-prefix'; |
23 | 24 | ||
24 | const isDevelopment = import.meta.env.MODE === 'development'; | 25 | if (import.meta.env.DEV) { |
26 | loglevel.setLevel('debug'); | ||
27 | } else { | ||
28 | loglevel.setLevel('info'); | ||
29 | } | ||
30 | |||
31 | const COLORS: Partial<Record<string, ChalkInstance>> = { | ||
32 | TRACE: chalk.magenta, | ||
33 | DEBUG: chalk.cyan, | ||
34 | INFO: chalk.blue, | ||
35 | WARN: chalk.yellow, | ||
36 | ERROR: chalk.red, | ||
37 | CRITICAL: chalk.red, | ||
38 | }; | ||
25 | 39 | ||
26 | if (isDevelopment) { | 40 | function getColor(level: string): ChalkInstance { |
27 | loglevel.enableAll(); | 41 | return COLORS[level] ?? chalk.gray; |
28 | } | 42 | } |
29 | 43 | ||
30 | prefix.reg(loglevel); | 44 | prefix.reg(loglevel); |
31 | prefix.apply(loglevel, { | 45 | prefix.apply(loglevel, { |
32 | format(level, name, timestamp) { | 46 | format(level, name, timestamp) { |
33 | let shortName = 'global'; | 47 | const levelColor = getColor(level); |
34 | if (name !== undefined) { | 48 | return `${chalk.gray(`[${timestamp}]`)} ${levelColor(level)} ${chalk.green(`${name}:`)}`; |
35 | const nameSegments = name.split(':'); | ||
36 | const lastSegment = nameSegments.pop(); | ||
37 | shortName = [...nameSegments.map((segment) => segment[0]), lastSegment].join(':'); | ||
38 | } | ||
39 | return `[${timestamp}] ${level} (${shortName})`; | ||
40 | }, | 49 | }, |
41 | }); | 50 | }); |
42 | 51 | ||
43 | export function getLogger(loggerName: string): Logger { | 52 | export function getLogger(loggerName: string): Logger { |
44 | return loglevel.getLogger(`sophie:${loggerName}`); | 53 | return loglevel.getLogger(loggerName); |
45 | } | 54 | } |
diff --git a/packages/renderer/src/index.tsx b/packages/renderer/src/index.tsx index befb823..5e943b3 100644 --- a/packages/renderer/src/index.tsx +++ b/packages/renderer/src/index.tsx | |||
@@ -36,6 +36,7 @@ const isDevelopment = import.meta.env.MODE === 'development'; | |||
36 | 36 | ||
37 | if (isDevelopment) { | 37 | if (isDevelopment) { |
38 | hotReloadServices(); | 38 | hotReloadServices(); |
39 | document.title = `[dev] ${document.title}`; | ||
39 | } | 40 | } |
40 | 41 | ||
41 | const store = createAndConnectRendererStore(window.sophieRenderer); | 42 | const store = createAndConnectRendererStore(window.sophieRenderer); |
@@ -1203,6 +1203,7 @@ __metadata: | |||
1203 | "@types/lodash-es": ^4.17.5 | 1203 | "@types/lodash-es": ^4.17.5 |
1204 | "@types/ms": ^0.7.31 | 1204 | "@types/ms": ^0.7.31 |
1205 | "@types/node": ^17.0.5 | 1205 | "@types/node": ^17.0.5 |
1206 | chalk: ^5.0.0 | ||
1206 | electron: 16.0.5 | 1207 | electron: 16.0.5 |
1207 | electron-devtools-installer: ^3.2.0 | 1208 | electron-devtools-installer: ^3.2.0 |
1208 | esbuild: ^0.14.9 | 1209 | esbuild: ^0.14.9 |
@@ -2353,6 +2354,13 @@ __metadata: | |||
2353 | languageName: node | 2354 | languageName: node |
2354 | linkType: hard | 2355 | linkType: hard |
2355 | 2356 | ||
2357 | "chalk@npm:^5.0.0": | ||
2358 | version: 5.0.0 | ||
2359 | resolution: "chalk@npm:5.0.0" | ||
2360 | checksum: 6eba7c518b9aa5fe882ae6d14a1ffa58c418d72a3faa7f72af56641f1bbef51b645fca1d6e05d42357b7d3c846cd504c0b7b64d12309cdd07867e3b4411e0d01 | ||
2361 | languageName: node | ||
2362 | linkType: hard | ||
2363 | |||
2356 | "char-regex@npm:^1.0.2": | 2364 | "char-regex@npm:^1.0.2": |
2357 | version: 1.0.2 | 2365 | version: 1.0.2 |
2358 | resolution: "char-regex@npm:1.0.2" | 2366 | resolution: "char-regex@npm:1.0.2" |