import { SNUserPrefs, PrefKey, PrefValue, UserPrefsMutator, ItemContent, FillItemContent } from '@standardnotes/models' import { ItemManager } from '../Items/ItemManager' import { SingletonManager } from '../Singleton/SingletonManager' import { SyncService } from '../Sync/SyncService' import { AbstractService, InternalEventBusInterface, SyncEvent, ApplicationStage, PreferenceServiceInterface, PreferencesServiceEvent, MutatorClientInterface, InternalEventHandlerInterface, InternalEventInterface, ApplicationEvent, ApplicationStageChangedEventPayload, } from '@standardnotes/services' import { ContentType } from '@standardnotes/domain-core' export class PreferencesService extends AbstractService implements PreferenceServiceInterface, InternalEventHandlerInterface { private shouldReload = true private reloading = false private preferences?: SNUserPrefs private removeItemObserver?: () => void private removeSyncObserver?: () => void constructor( private singletons: SingletonManager, items: ItemManager, private mutator: MutatorClientInterface, private sync: SyncService, protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) this.removeItemObserver = items.addObserver(ContentType.TYPES.UserPrefs, () => { this.shouldReload = true }) this.removeSyncObserver = sync.addEventObserver((event) => { if (event === SyncEvent.SyncCompletedWithAllItemsUploaded || event === SyncEvent.LocalDataIncrementalLoad) { void this.reload() } }) } override deinit(): void { this.removeItemObserver?.() this.removeSyncObserver?.() ;(this.singletons as unknown) = undefined ;(this.mutator as unknown) = undefined super.deinit() } async handleEvent(event: InternalEventInterface): Promise { if (event.type === ApplicationEvent.ApplicationStageChanged) { const stage = (event.payload as ApplicationStageChangedEventPayload).stage if (stage === ApplicationStage.LoadedDatabase_12) { /** Try to read preferences singleton from storage */ this.preferences = this.singletons.findSingleton( ContentType.TYPES.UserPrefs, SNUserPrefs.singletonPredicate, ) if (this.preferences) { void this.notifyEvent(PreferencesServiceEvent.PreferencesChanged) } } } } getValue(key: K, defaultValue: PrefValue[K] | undefined): PrefValue[K] | undefined getValue(key: K, defaultValue: PrefValue[K]): PrefValue[K] getValue(key: K, defaultValue?: PrefValue[K]): PrefValue[K] | undefined { return this.preferences?.getPref(key) ?? defaultValue } async setValue(key: K, value: PrefValue[K]): Promise { await this.setValueDetached(key, value) void this.notifyEvent(PreferencesServiceEvent.PreferencesChanged) void this.sync.sync({ sourceDescription: 'PreferencesService.setValue' }) } async setValueDetached(key: K, value: PrefValue[K]): Promise { if (!this.preferences) { return } this.preferences = (await this.mutator.changeItem(this.preferences, (m) => { m.setPref(key, value) })) as SNUserPrefs } private async reload() { if (!this.shouldReload || this.reloading) { return } this.reloading = true try { const previousRef = this.preferences this.preferences = await this.singletons.findOrCreateContentTypeSingleton( ContentType.TYPES.UserPrefs, FillItemContent({}), ) if ( previousRef?.uuid !== this.preferences.uuid || this.preferences.userModifiedDate > previousRef.userModifiedDate ) { void this.notifyEvent(PreferencesServiceEvent.PreferencesChanged) } this.shouldReload = false } finally { this.reloading = false } } }