• 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 {runValidator, ValidationResult} from '../base/validators';
16import {getDefaultRecordingTargets, RecordingTarget} from '../common/state';
17import {
18  createEmptyRecordConfig,
19  NamedRecordConfig,
20  namedRecordConfigValidator,
21  RecordConfig,
22  recordConfigValidator,
23} from '../controller/record_config_types';
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
46  save(recordConfig: RecordConfig, title?: string): void {
47    // We reload from local storage in case of concurrent
48    // modifications of local storage from a different tab.
49    this.reloadFromLocalStorage();
50
51    const savedTitle = title ? title : new Date().toJSON();
52    const config: NamedRecordConfig = {
53      title: savedTitle,
54      config: recordConfig,
55      key: new Date().toJSON(),
56    };
57
58    this.recordConfigs.push({result: config, invalidKeys: [], extraKeys: []});
59    this.recordConfigNames.add(savedTitle);
60
61    this._save();
62  }
63
64  overwrite(recordConfig: RecordConfig, key: string) {
65    // We reload from local storage in case of concurrent
66    // modifications of local storage from a different tab.
67    this.reloadFromLocalStorage();
68
69    const found = this.recordConfigs.find((e) => e.result.key === key);
70    if (found === undefined) {
71      throw new Error('trying to overwrite non-existing config');
72    }
73
74    found.result.config = recordConfig;
75
76    this._save();
77  }
78
79  delete(key: string): void {
80    // We reload from local storage in case of concurrent
81    // modifications of local storage from a different tab.
82    this.reloadFromLocalStorage();
83
84    let idx = -1;
85    for (let i = 0; i < this.recordConfigs.length; ++i) {
86      if (this.recordConfigs[i].result.key === key) {
87        idx = i;
88        break;
89      }
90    }
91
92    if (idx !== -1) {
93      this.recordConfigNames.delete(this.recordConfigs[idx].result.title);
94      this.recordConfigs.splice(idx, 1);
95      this._save();
96    } else {
97      // TODO(bsebastien): Show a warning message to the user in the UI.
98      console.warn("The config selected doesn't exist any more");
99    }
100  }
101
102  private clearRecordConfigs(): void {
103    this.recordConfigs = [];
104    this.recordConfigNames.clear();
105    this._save();
106  }
107
108  reloadFromLocalStorage(): void {
109    const configsLocalStorage = window.localStorage.getItem(
110      LOCAL_STORAGE_RECORD_CONFIGS_KEY,
111    );
112
113    if (configsLocalStorage) {
114      this.recordConfigNames.clear();
115
116      try {
117        const validConfigLocalStorage: Array<
118          ValidationResult<NamedRecordConfig>
119        > = [];
120        const parsedConfigsLocalStorage = JSON.parse(configsLocalStorage);
121
122        // Check if it's an array.
123        if (!Array.isArray(parsedConfigsLocalStorage)) {
124          this.clearRecordConfigs();
125          return;
126        }
127
128        for (let i = 0; i < parsedConfigsLocalStorage.length; ++i) {
129          try {
130            validConfigLocalStorage.push(
131              runValidator(
132                namedRecordConfigValidator,
133                parsedConfigsLocalStorage[i],
134              ),
135            );
136          } catch {
137            // Parsing failed with unrecoverable error (e.g. title or key are
138            // missing), ignore the result.
139            console.log(
140              'Validation of saved record config has failed: ' +
141                JSON.stringify(parsedConfigsLocalStorage[i]),
142            );
143          }
144        }
145
146        this.recordConfigs = validConfigLocalStorage;
147        this._save();
148      } catch (e) {
149        this.clearRecordConfigs();
150      }
151    } else {
152      this.clearRecordConfigs();
153    }
154  }
155
156  canSave(title: string) {
157    return !this.recordConfigNames.has(title);
158  }
159}
160
161// This class is a singleton to avoid many instances
162// conflicting as they attempt to edit localStorage.
163export const recordConfigStore = new RecordConfigStore();
164
165export class AutosaveConfigStore {
166  config: RecordConfig;
167
168  // Whether the current config is a default one or has been saved before.
169  // Used to determine whether the button to load "last started config" should
170  // be present in the recording profiles list.
171  hasSavedConfig: boolean;
172
173  constructor() {
174    this.hasSavedConfig = false;
175    this.config = createEmptyRecordConfig();
176    const savedItem = window.localStorage.getItem(
177      LOCAL_STORAGE_AUTOSAVE_CONFIG_KEY,
178    );
179    if (savedItem === null) {
180      return;
181    }
182    const parsed = JSON.parse(savedItem);
183    if (parsed !== null && typeof parsed === 'object') {
184      this.config = runValidator(recordConfigValidator, parsed).result;
185      this.hasSavedConfig = true;
186    }
187  }
188
189  get(): RecordConfig {
190    return this.config;
191  }
192
193  save(newConfig: RecordConfig) {
194    window.localStorage.setItem(
195      LOCAL_STORAGE_AUTOSAVE_CONFIG_KEY,
196      JSON.stringify(newConfig),
197    );
198    this.config = newConfig;
199    this.hasSavedConfig = true;
200  }
201}
202
203export const autosaveConfigStore = new AutosaveConfigStore();
204
205export class RecordTargetStore {
206  recordTargetOS: string | null;
207
208  constructor() {
209    this.recordTargetOS = window.localStorage.getItem(
210      LOCAL_STORAGE_RECORD_TARGET_OS_KEY,
211    );
212  }
213
214  get(): string | null {
215    return this.recordTargetOS;
216  }
217
218  getValidTarget(): RecordingTarget {
219    const validTargets = getDefaultRecordingTargets();
220    const savedOS = this.get();
221
222    const validSavedTarget = validTargets.find((el) => el.os === savedOS);
223    return validSavedTarget || validTargets[0];
224  }
225
226  save(newTargetOS: string) {
227    window.localStorage.setItem(
228      LOCAL_STORAGE_RECORD_TARGET_OS_KEY,
229      newTargetOS,
230    );
231    this.recordTargetOS = newTargetOS;
232  }
233}
234
235export const recordTargetStore = new RecordTargetStore();
236