From 391b0ac7ab2dda1ec0ce0224e1b864743a6761c3 Mon Sep 17 00:00:00 2001 From: Tamius Han Date: Tue, 22 Apr 2025 02:42:33 +0200 Subject: [PATCH] Settings snapshots --- src/ext/conf/ExtConfPatches.ts | 4 +- src/ext/conf/ExtensionConf.ts | 44 ++++++++ src/ext/lib/Settings.ts | 53 +++++++-- .../lib/settings/SettingsSnapshotManager.ts | 106 ++++++++++++++++++ 4 files changed, 199 insertions(+), 8 deletions(-) create mode 100644 src/ext/lib/settings/SettingsSnapshotManager.ts diff --git a/src/ext/conf/ExtConfPatches.ts b/src/ext/conf/ExtConfPatches.ts index 7226420..fd45942 100644 --- a/src/ext/conf/ExtConfPatches.ts +++ b/src/ext/conf/ExtConfPatches.ts @@ -21,7 +21,6 @@ const ExtensionConfPatch = [ normal: ExtensionMode.Default, } } - const uiEnabled = userOptions.sites['@global'].enableUI = { fullscreen: userOptions.ui.inPlayer.enabled ? ExtensionMode.Enabled : ExtensionMode.Disabled, theater: ExtensionMode.Enabled, @@ -84,6 +83,9 @@ const ExtensionConfPatch = [ delete (userOptions as any).actions; + userOptions.dev = { + loadFromSnapshot: false + }; userOptions.ui.dev = { aardDebugOverlay: { showOnStartup: false, diff --git a/src/ext/conf/ExtensionConf.ts b/src/ext/conf/ExtensionConf.ts index 054d816..3258463 100644 --- a/src/ext/conf/ExtensionConf.ts +++ b/src/ext/conf/ExtensionConf.ts @@ -14,6 +14,10 @@ if(Debug.debug) console.log("Loading: ExtensionConf.js"); const ExtensionConf: SettingsInterface = { + dev: { + loadFromSnapshot: false, + }, + arDetect: { aardType: 'auto', @@ -728,6 +732,46 @@ const ExtensionConf: SettingsInterface = { } } }, + "www.youtube-nocookie.com": { + enable: { + fullscreen: ExtensionMode.Enabled, + theater: ExtensionMode.Enabled, + normal: ExtensionMode.Enabled, + }, + enableAard: { + fullscreen: ExtensionMode.Enabled, + theater: ExtensionMode.Enabled, + normal: ExtensionMode.Enabled, + }, + enableKeyboard: { + fullscreen: ExtensionMode.Enabled, + theater: ExtensionMode.Enabled, + normal: ExtensionMode.Enabled + }, + enableUI: { + fullscreen: ExtensionMode.Enabled, + theater: ExtensionMode.Enabled, + normal: ExtensionMode.Disabled + }, + + override: false, // ignore value localStorage in favour of this + type: 'official', // is officially supported? (Alternatives are 'community' and 'user-defined') + defaultType: 'official', // if user mucks around with settings, type changes to 'user-defined'. + // We still want to know what the original type was, hence defaultType + + activeDOMConfig: 'official', + DOMConfig: { + 'official': { + type: 'official', + elements: { + player: { + manual: true, + querySelectors: "#movie_player, #player, #c4-player", + } + } + } + } + }, "www.netflix.com" : { enable: { fullscreen: ExtensionMode.Enabled, diff --git a/src/ext/lib/Settings.ts b/src/ext/lib/Settings.ts index 055c734..9a4ff2c 100644 --- a/src/ext/lib/Settings.ts +++ b/src/ext/lib/Settings.ts @@ -12,11 +12,15 @@ import Logger from './Logger'; import SettingsInterface from '../../common/interfaces/SettingsInterface'; import AspectRatioType from '../../common/enums/AspectRatioType.enum'; import { SiteSettings } from './settings/SiteSettings'; +import { SettingsSnapshotManager } from './settings/SettingsSnapshotManager'; if(process.env.CHANNEL !== 'stable'){ console.info("Loading Settings"); } +interface SetSettingsOptions { + forcePreserveVersion?: boolean, +} class Settings { //#region flags @@ -41,6 +45,17 @@ class Settings { afterSettingsChangedCallbacks: (() => void)[] = []; private sortedPatches: any[]; + + + public snapshotManager: SettingsSnapshotManager; + + private _migrationReport: string = ''; + private set migrationReport(report: string) { + this._migrationReport = report; + } + public get migrationReport(): string { + return this._migrationReport; + } //#endregion constructor(options) { @@ -50,11 +65,14 @@ class Settings { this.afterSettingsSaved = options?.afterSettingsSaved; this.active = options?.activeSettings ?? undefined; this.default = ExtensionConf; + this.snapshotManager = new SettingsSnapshotManager(); + this.default['version'] = this.getExtensionVersion(); chrome.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)}); this.sortedPatches = this.sortConfPatches(ExtensionConfPatch); + } private storageChangeListener(changes, area) { @@ -186,6 +204,18 @@ class Settings { return; } + // save current settings object + const currentSettings = this.active; + + this.snapshotManager.createSnapshot( + JSON.parse(JSON.stringify(currentSettings)), + { + label: 'Pre-migration snapshot', + isAutomatic: true + } + ); + + // apply all remaining patches this.logger?.log('info', 'settings', `[Settings::applySettingsPatches] There are ${this.sortedPatches.length - index} settings patches to apply`); while (index < this.sortedPatches.length) { @@ -209,7 +239,16 @@ class Settings { } async init() { - const settings = await this.get(); + let settings = await this.get(); + + if (settings?.dev?.loadFromSnapshot) { + this.logger?.log('info', 'settings', '[Settings::init] Dev mode is enabled, Loading settings from snapshot:', settings.dev.loadFromSnapshot); + const snapshot = await this.snapshotManager.getSnapshot(); + if (snapshot) { + settings = snapshot.settings; + } + } + this.version = this.getExtensionVersion(); // |—> on first setup, settings is undefined & settings.version is haram @@ -282,7 +321,7 @@ class Settings { return this.active; } - async get() { + async get(): Promise { let ret; ret = await chrome.storage.local.get('uwSettings'); @@ -290,13 +329,13 @@ class Settings { this.logger?.log('info', 'settings', 'Got settings:', ret && ret.uwSettings && JSON.parse(ret.uwSettings)); try { - return JSON.parse(ret.uwSettings); + return JSON.parse(ret.uwSettings) as SettingsInterface; } catch(e) { return undefined; } } - async set(extensionConf, options?) { + async set(extensionConf, options?: SetSettingsOptions) { if (!options || !options.forcePreserveVersion) { extensionConf.version = this.version; } @@ -344,7 +383,7 @@ class Settings { } } - async save(options?) { + async save(options?: SetSettingsOptions) { if (Debug.debug && Debug.storage) { console.log("[Settings::save] Saving active settings:", this.active); } @@ -354,10 +393,10 @@ class Settings { } - async saveWithoutReload() { + async saveWithoutReload(options?: SetSettingsOptions) { this.active.preventReload = true; this.active.lastModified = new Date(); - await this.set(this.active); + await this.set(this.active, options); } async rollback() { diff --git a/src/ext/lib/settings/SettingsSnapshotManager.ts b/src/ext/lib/settings/SettingsSnapshotManager.ts new file mode 100644 index 0000000..8ebca74 --- /dev/null +++ b/src/ext/lib/settings/SettingsSnapshotManager.ts @@ -0,0 +1,106 @@ +import { settings } from 'cluster' +import SettingsInterface from '@src/common/interfaces/SettingsInterface'; + + +export interface SettingsSnapshot { + isAutomatic?: boolean; + isProtected?: boolean; + isDefault?: boolean; + forVersion: string; + label: string; + settings: SettingsInterface; + createdAt: Date; +} + +export interface SettingsSnapshotOptions { + isAutomatic?: boolean, + isProtected?: boolean, + isDefault?: boolean, + label?: string, + forVersion?: string +} + +export class SettingsSnapshotManager { + private MAX_AUTOMATIC_SNAPSHOTS = 5; + + async getSnapshot(index?: number) { + const snapshots = await this.listSnapshots(); + + if (!index) { + return snapshots.find(x => x.isDefault); + } else { + if (index < 0 || index >= snapshots.length) { + throw new Error('Invalid index'); + } + return snapshots[index]; + } + } + + async createSnapshot(settings: SettingsInterface, options?: SettingsSnapshotOptions) { + const snapshot = { + ...options, + label: options.label ?? 'Automatic snapshot', + forVersion: options.forVersion || settings.version, + settings: JSON.parse(JSON.stringify(settings)), + createdAt: new Date(), + }; + + const snapshots = await this.listSnapshots(); + const automaticSnapshots = snapshots.filter((s) => s.isAutomatic && !s.isProtected); + + if (options.isAutomatic && automaticSnapshots.length >= this.MAX_AUTOMATIC_SNAPSHOTS) { + const firstAutomaticIndex = snapshots.findIndex((s) => s.isAutomatic && !s.isProtected); + snapshots.splice(firstAutomaticIndex, 1); + } + + snapshots.push(snapshot); + this.set(snapshots); + } + + async setDefaultSnapshot(index: number, isDefault: boolean) { + const snapshots = await this.listSnapshots(); + if (index < 0 || index >= snapshots.length) { + throw new Error('Invalid index'); + } + if (isDefault) { + for (const snapshot of snapshots) { + snapshot.isDefault = false; + } + } + snapshots[index].isDefault = isDefault; + this.set(snapshots); + } + + async markSnapshotAsProtected(index: number, isProtected: boolean) { + const snapshots = await this.listSnapshots(); + if (index < 0 || index >= snapshots.length) { + throw new Error('Invalid index'); + } + snapshots[index].isProtected = isProtected; + this.set(snapshots); + } + + async deleteSnapshot(index: number) { + const snapshots = await this.listSnapshots(); + if (index < 0 || index >= snapshots.length) { + throw new Error('Invalid index'); + } + snapshots.splice(index, 1); + this.set(snapshots); + } + + async listSnapshots(): Promise { + const ret = await chrome.storage.local.get('uwSettings-snapshots'); + try { + JSON.parse(ret['uwSettings-snapshots']) as SettingsSnapshot[]; + } catch (e) { + return [] as SettingsSnapshot[]; + } + } + + private async set(snapshots: SettingsSnapshot[]) { + await chrome.storage.local.set({ + 'uwSettings-snapshots': JSON.stringify(snapshots), + }); + } +}