• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (C) 2020 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {getDefaultRecordingTargets, RecordingTarget} from '../common/state';
16import {
17  createEmptyRecordConfig,
18  NamedRecordConfig,
19  namedRecordConfigValidator,
20  RecordConfig,
21  recordConfigValidator
22} from '../controller/record_config_types';
23import {runValidator, ValidationResult} from '../controller/validators';
24
25const LOCAL_STORAGE_RECORD_CONFIGS_KEY = 'recordConfigs';
26const LOCAL_STORAGE_AUTOSAVE_CONFIG_KEY = 'autosaveConfig';
27const LOCAL_STORAGE_RECORD_TARGET_OS_KEY = 'recordTargetOS';
28
29export class RecordConfigStore {
30  recordConfigs: Array<ValidationResult<NamedRecordConfig>>;
31  recordConfigNames: Set<string>;
32
33  constructor() {
34    this.recordConfigs = [];
35    this.recordConfigNames = new Set();
36    this.reloadFromLocalStorage();
37  }
38
39  private _save() {
40    window.localStorage.setItem(
41        LOCAL_STORAGE_RECORD_CONFIGS_KEY,
42        JSON.stringify(this.recordConfigs.map((x) => x.result)));
43  }
44
45  save(recordConfig: RecordConfig, title?: string): void {
46    // We reload from local storage in case of concurrent
47    // modifications of local storage from a different tab.
48    this.reloadFromLocalStorage();
49
50    const savedTitle = title ? title : new Date().toJSON();
51    const config: NamedRecordConfig = {
52      title: savedTitle,
53      config: recordConfig,
54      key: new Date().toJSON()
55    };
56
57    this.recordConfigs.push({result: config, invalidKeys: [], extraKeys: []});
58    this.recordConfigNames.add(savedTitle);
59
60    this._save();
61  }
62
63  overwrite(recordConfig: RecordConfig, key: string) {
64    // We reload from local storage in case of concurrent
65    // modifications of local storage from a different tab.
66    this.reloadFromLocalStorage();
67
68    const found = this.recordConfigs.find((e) => e.result.key === key);
69    if (found === undefined) {
70      throw new Error('trying to overwrite non-existing config');
71    }
72
73    found.result.config = recordConfig;
74
75    this._save();
76  }
77
78  delete(key: string): void {
79    // We reload from local storage in case of concurrent
80    // modifications of local storage from a different tab.
81    this.reloadFromLocalStorage();
82
83    let idx = -1;
84    for (let i = 0; i < this.recordConfigs.length; ++i) {
85      if (this.recordConfigs[i].result.key === key) {
86        idx = i;
87        break;
88      }
89    }
90
91    if (idx !== -1) {
92      this.recordConfigNames.delete(this.recordConfigs[idx].result.title);
93      this.recordConfigs.splice(idx, 1);
94      this._save();
95    } else {
96      // TODO(bsebastien): Show a warning message to the user in the UI.
97      console.warn('The config selected doesn\'t exist any more');
98    }
99  }
100
101  private clearRecordConfigs(): void {
102    this.recordConfigs = [];
103    this.recordConfigNames.clear();
104    this._save();
105  }
106
107  reloadFromLocalStorage(): void {
108    const configsLocalStorage =
109        window.localStorage.getItem(LOCAL_STORAGE_RECORD_CONFIGS_KEY);
110
111    if (configsLocalStorage) {
112      this.recordConfigNames.clear();
113
114      try {
115        const validConfigLocalStorage:
116            Array<ValidationResult<NamedRecordConfig>> = [];
117        const parsedConfigsLocalStorage = JSON.parse(configsLocalStorage);
118
119        // Check if it's an array.
120        if (!Array.isArray(parsedConfigsLocalStorage)) {
121          this.clearRecordConfigs();
122          return;
123        }
124
125        for (let i = 0; i < parsedConfigsLocalStorage.length; ++i) {
126          try {
127            validConfigLocalStorage.push(runValidator(
128                namedRecordConfigValidator, parsedConfigsLocalStorage[i]));
129          } catch {
130            // Parsing failed with unrecoverable error (e.g. title or key are
131            // missing), ignore the result.
132            console.log(
133                'Validation of saved record config has failed: ' +
134                JSON.stringify(parsedConfigsLocalStorage[i]));
135          }
136        }
137
138        this.recordConfigs = validConfigLocalStorage;
139        this._save();
140      } catch (e) {
141        this.clearRecordConfigs();
142      }
143    } else {
144      this.clearRecordConfigs();
145    }
146  }
147
148  canSave(title: string) {
149    return !this.recordConfigNames.has(title);
150  }
151}
152
153// This class is a singleton to avoid many instances
154// conflicting as they attempt to edit localStorage.
155export const recordConfigStore = new RecordConfigStore();
156
157export class AutosaveConfigStore {
158  config: RecordConfig;
159
160  // Whether the current config is a default one or has been saved before.
161  // Used to determine whether the button to load "last started config" should
162  // be present in the recording profiles list.
163  hasSavedConfig: boolean;
164
165  constructor() {
166    this.hasSavedConfig = false;
167    this.config = createEmptyRecordConfig();
168    const savedItem =
169        window.localStorage.getItem(LOCAL_STORAGE_AUTOSAVE_CONFIG_KEY);
170    if (savedItem === null) {
171      return;
172    }
173    const parsed = JSON.parse(savedItem);
174    if (parsed !== null && typeof parsed === 'object') {
175      this.config = runValidator(recordConfigValidator, parsed).result;
176      this.hasSavedConfig = true;
177    }
178  }
179
180  get(): RecordConfig {
181    return this.config;
182  }
183
184  save(newConfig: RecordConfig) {
185    window.localStorage.setItem(
186        LOCAL_STORAGE_AUTOSAVE_CONFIG_KEY, JSON.stringify(newConfig));
187    this.config = newConfig;
188    this.hasSavedConfig = true;
189  }
190}
191
192export const autosaveConfigStore = new AutosaveConfigStore();
193
194export class RecordTargetStore {
195  recordTargetOS: string|null;
196
197  constructor() {
198    this.recordTargetOS =
199        window.localStorage.getItem(LOCAL_STORAGE_RECORD_TARGET_OS_KEY);
200  }
201
202  get(): string|null {
203    return this.recordTargetOS;
204  }
205
206  getValidTarget(): RecordingTarget {
207    const validTargets = getDefaultRecordingTargets();
208    const savedOS = this.get();
209
210    const validSavedTarget = validTargets.find((el) => el.os === savedOS);
211    return validSavedTarget || validTargets[0];
212  }
213
214  save(newTargetOS: string) {
215    window.localStorage.setItem(
216        LOCAL_STORAGE_RECORD_TARGET_OS_KEY, newTargetOS);
217    this.recordTargetOS = newTargetOS;
218  }
219}
220
221export const recordTargetStore = new RecordTargetStore();
222