// Copyright (C) 2020 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import {getDefaultRecordingTargets, RecordingTarget} from '../common/state'; import { createEmptyRecordConfig, NamedRecordConfig, namedRecordConfigValidator, RecordConfig, recordConfigValidator, } from '../controller/record_config_types'; import {runValidator, ValidationResult} from '../controller/validators'; const LOCAL_STORAGE_RECORD_CONFIGS_KEY = 'recordConfigs'; const LOCAL_STORAGE_AUTOSAVE_CONFIG_KEY = 'autosaveConfig'; const LOCAL_STORAGE_RECORD_TARGET_OS_KEY = 'recordTargetOS'; export class RecordConfigStore { recordConfigs: Array>; recordConfigNames: Set; constructor() { this.recordConfigs = []; this.recordConfigNames = new Set(); this.reloadFromLocalStorage(); } private _save() { window.localStorage.setItem( LOCAL_STORAGE_RECORD_CONFIGS_KEY, JSON.stringify(this.recordConfigs.map((x) => x.result))); } save(recordConfig: RecordConfig, title?: string): void { // We reload from local storage in case of concurrent // modifications of local storage from a different tab. this.reloadFromLocalStorage(); const savedTitle = title ? title : new Date().toJSON(); const config: NamedRecordConfig = { title: savedTitle, config: recordConfig, key: new Date().toJSON(), }; this.recordConfigs.push({result: config, invalidKeys: [], extraKeys: []}); this.recordConfigNames.add(savedTitle); this._save(); } overwrite(recordConfig: RecordConfig, key: string) { // We reload from local storage in case of concurrent // modifications of local storage from a different tab. this.reloadFromLocalStorage(); const found = this.recordConfigs.find((e) => e.result.key === key); if (found === undefined) { throw new Error('trying to overwrite non-existing config'); } found.result.config = recordConfig; this._save(); } delete(key: string): void { // We reload from local storage in case of concurrent // modifications of local storage from a different tab. this.reloadFromLocalStorage(); let idx = -1; for (let i = 0; i < this.recordConfigs.length; ++i) { if (this.recordConfigs[i].result.key === key) { idx = i; break; } } if (idx !== -1) { this.recordConfigNames.delete(this.recordConfigs[idx].result.title); this.recordConfigs.splice(idx, 1); this._save(); } else { // TODO(bsebastien): Show a warning message to the user in the UI. console.warn('The config selected doesn\'t exist any more'); } } private clearRecordConfigs(): void { this.recordConfigs = []; this.recordConfigNames.clear(); this._save(); } reloadFromLocalStorage(): void { const configsLocalStorage = window.localStorage.getItem(LOCAL_STORAGE_RECORD_CONFIGS_KEY); if (configsLocalStorage) { this.recordConfigNames.clear(); try { const validConfigLocalStorage: Array> = []; const parsedConfigsLocalStorage = JSON.parse(configsLocalStorage); // Check if it's an array. if (!Array.isArray(parsedConfigsLocalStorage)) { this.clearRecordConfigs(); return; } for (let i = 0; i < parsedConfigsLocalStorage.length; ++i) { try { validConfigLocalStorage.push(runValidator( namedRecordConfigValidator, parsedConfigsLocalStorage[i])); } catch { // Parsing failed with unrecoverable error (e.g. title or key are // missing), ignore the result. console.log( 'Validation of saved record config has failed: ' + JSON.stringify(parsedConfigsLocalStorage[i])); } } this.recordConfigs = validConfigLocalStorage; this._save(); } catch (e) { this.clearRecordConfigs(); } } else { this.clearRecordConfigs(); } } canSave(title: string) { return !this.recordConfigNames.has(title); } } // This class is a singleton to avoid many instances // conflicting as they attempt to edit localStorage. export const recordConfigStore = new RecordConfigStore(); export class AutosaveConfigStore { config: RecordConfig; // Whether the current config is a default one or has been saved before. // Used to determine whether the button to load "last started config" should // be present in the recording profiles list. hasSavedConfig: boolean; constructor() { this.hasSavedConfig = false; this.config = createEmptyRecordConfig(); const savedItem = window.localStorage.getItem(LOCAL_STORAGE_AUTOSAVE_CONFIG_KEY); if (savedItem === null) { return; } const parsed = JSON.parse(savedItem); if (parsed !== null && typeof parsed === 'object') { this.config = runValidator(recordConfigValidator, parsed).result; this.hasSavedConfig = true; } } get(): RecordConfig { return this.config; } save(newConfig: RecordConfig) { window.localStorage.setItem( LOCAL_STORAGE_AUTOSAVE_CONFIG_KEY, JSON.stringify(newConfig)); this.config = newConfig; this.hasSavedConfig = true; } } export const autosaveConfigStore = new AutosaveConfigStore(); export class RecordTargetStore { recordTargetOS: string|null; constructor() { this.recordTargetOS = window.localStorage.getItem(LOCAL_STORAGE_RECORD_TARGET_OS_KEY); } get(): string|null { return this.recordTargetOS; } getValidTarget(): RecordingTarget { const validTargets = getDefaultRecordingTargets(); const savedOS = this.get(); const validSavedTarget = validTargets.find((el) => el.os === savedOS); return validSavedTarget || validTargets[0]; } save(newTargetOS: string) { window.localStorage.setItem( LOCAL_STORAGE_RECORD_TARGET_OS_KEY, newTargetOS); this.recordTargetOS = newTargetOS; } } export const recordTargetStore = new RecordTargetStore();