• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021-2023 Huawei Device Co., Ltd.
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 */
15
16/**
17 * file version
18 *
19 * To indicate the file formate
20 *
21 */
22enum ObjectVersion {
23  NewVersion,
24  CompatibleVersion,
25  Default,
26}
27
28/**
29 * MapInfo
30 *
31 * Helper class to persist Map in Persistent storage
32 *
33 */
34type MapItem<K, V> = { key: K, value: V };
35class MapInfo<K, V> {
36  static readonly replacer: string = '_____map_replacer__';
37  static readonly replacerCompatible: string = 'ace_engine_state_mgmt_map_replacer';
38  public keys: K[];
39  public values: V[];
40
41  constructor(
42    public mapReplacer: string,
43    public keyToValue: MapItem<K, V>[]
44  ) { }
45
46  // Check if the given object is of type MapInfo
47  static isObject<K, V>(obj: unknown): ObjectVersion {
48    const typedObject = obj as MapInfo<K, V>;
49    if ('mapReplacer' in typedObject && typedObject.mapReplacer === MapInfo.replacer) {
50      return ObjectVersion.NewVersion;
51    }
52    if ('mapReplacer' in typedObject && typedObject.mapReplacer === MapInfo.replacerCompatible) {
53      return ObjectVersion.CompatibleVersion;
54    }
55    return ObjectVersion.Default;
56  }
57
58  // Convert Map to Object
59  static toObject<K, V>(map: Map<K, V>): MapInfo<K, V> {
60    let mapItems: MapItem<K, V>[] = [];
61    map.forEach((val: V, key: K) => {
62      mapItems.push({ key: key, value: val })
63    })
64    return new MapInfo(MapInfo.replacer, mapItems);
65  }
66
67  // Convert Object to Map
68  static toMap<K, V>(obj: MapInfo<K, V>): Map<K, V> {
69    return new Map<K, V>(obj.keyToValue.map((item: MapItem<K, V>) => [item.key, item.value]));
70  }
71
72  static toMapCompatible<K, V>(obj: MapInfo<K, V>): Map<K, V> {
73    return new Map<K, V>(obj.keys.map((key, i) => [key, obj.values[i]]));
74  }
75}
76
77/**
78 * SetInfo
79 *
80 * Helper class to persist Set in Persistent storage
81 *
82 */
83class SetInfo<V> {
84  static readonly replacer: string = '_____set_replacer__';
85  static readonly replacerCompatible: string = "ace_engine_state_mgmt_set_replacer";
86
87  constructor(
88    public setReplacer: string,
89    public values: V[]
90  ) { }
91
92  // Check if the given object is of type SetInfo
93  static isObject<V>(obj: unknown): obj is SetInfo<V> {
94    const typedObject = obj as SetInfo<V>;
95    if ('setReplacer' in typedObject &&
96      (typedObject.setReplacer === SetInfo.replacer || typedObject.setReplacer === SetInfo.replacerCompatible)) {
97      return true;
98    }
99    return false;
100  }
101
102  // Convert Set to Object
103  static toObject<V>(set: Set<V>): SetInfo<V> {
104    const values: V[] = Array.from(set.values());
105    return new SetInfo(SetInfo.replacer, values);
106  }
107
108  // Convert Object to Set
109  static toSet<V>(obj: SetInfo<V>): Set<V> {
110    return new Set<V>(obj.values);
111  }
112}
113
114/**
115 * DateInfo
116 *
117 * Helper class to persist Date in Persistent storage
118 *
119 */
120class DateInfo {
121  static readonly replacer: string = '_____date_replacer__';
122  static readonly replacerCompatible: string = "ace_engine_state_mgmt_date_replacer";
123
124  constructor(
125    public dateReplacer: string,
126    public date: string
127  ) { }
128
129  // Check if the given object is of type DateInfo
130  static isObject(obj: unknown): obj is DateInfo {
131    const typedObject = obj as DateInfo;
132    if ('dateReplacer' in typedObject &&
133      (typedObject.dateReplacer === DateInfo.replacer || typedObject.dateReplacer === DateInfo.replacerCompatible)) {
134      return true;
135    }
136    return false;
137  }
138
139  // Convert Date to Object
140  static toObject(date: Date): DateInfo {
141    return new DateInfo(DateInfo.replacer, date.toISOString());
142  }
143
144  // Convert Object to Date
145  static toDate(obj: DateInfo): Date {
146    return new Date(obj.date);
147  }
148}
149
150/**
151 * PersistentStorage
152 *
153 * Keeps current values of select AppStorage property properties persisted to file.
154 *
155 * since 9
156 */
157
158class PersistentStorage implements IMultiPropertiesChangeSubscriber {
159  private static storage_: IStorage;
160  private static instance_: PersistentStorage = undefined;
161
162  private id_: number;
163  private links_: Map<string, SubscribedAbstractProperty<any>>;
164
165  /**
166   *
167   * @param storage method to be used by the framework to set the backend
168   * this is to be done during startup
169   *
170   * internal function, not part of the SDK
171   *
172   */
173  public static configureBackend(storage: IStorage): void {
174    PersistentStorage.storage_ = storage;
175  }
176
177  /**
178   * private, use static functions!
179   */
180  private static getOrCreate(): PersistentStorage {
181    if (PersistentStorage.instance_) {
182      // already initialized
183      return PersistentStorage.instance_;
184    }
185
186    PersistentStorage.instance_ = new PersistentStorage();
187    return PersistentStorage.instance_;
188  }
189
190  /**
191   *
192   * internal function, not part of the SDK
193   */
194  public static aboutToBeDeleted(): void {
195    if (!PersistentStorage.instance_) {
196      return;
197    }
198
199    PersistentStorage.getOrCreate().aboutToBeDeleted();
200    PersistentStorage.instance_ = undefined;
201  }
202
203
204  /**
205   * Add property 'key' to AppStorage properties whose current value will be
206   * persistent.
207   * If AppStorage does not include this property it will be added and initializes
208   * with given value
209   *
210   * @since 10
211   *
212   * @param key property name
213   * @param defaultValue If AppStorage does not include this property it will be initialized with this value
214   *
215   */
216  public static persistProp<T>(key: string, defaultValue: T): void {
217    PersistentStorage.getOrCreate().persistProp(key, defaultValue);
218  }
219
220  /**
221   * @see persistProp
222   * @deprecated
223   */
224  public static PersistProp<T>(key: string, defaultValue: T): void {
225    PersistentStorage.getOrCreate().persistProp(key, defaultValue);
226  }
227
228
229  /**
230   * Reverse of @see persistProp
231   * @param key no longer persist the property named key
232   *
233   * @since 10
234   */
235  public static deleteProp(key: string): void {
236    PersistentStorage.getOrCreate().deleteProp(key);
237  }
238
239  /**
240   * @see deleteProp
241   * @deprecated
242   */
243  public static DeleteProp(key: string): void {
244    PersistentStorage.getOrCreate().deleteProp(key);
245  }
246
247  /**
248   * Persist given AppStorage properties with given names.
249   * If a property does not exist in AppStorage, add it and initialize it with given value
250   * works as @see persistProp for multiple properties.
251   *
252   * @param properties
253   *
254   * @since 10
255   *
256   */
257  public static persistProps(properties: {
258    key: string,
259    defaultValue: any
260  }[]): void {
261    PersistentStorage.getOrCreate().persistProps(properties);
262  }
263
264  /**
265   * @see persistProps
266   * @deprecated
267   */
268  public static PersistProps(properties: {
269    key: string,
270    defaultValue: any
271  }[]): void {
272    PersistentStorage.getOrCreate().persistProps(properties);
273  }
274
275  /**
276   * Inform persisted AppStorage property names
277   * @returns array of AppStorage keys
278   *
279   * @since 10
280   */
281  public static keys(): Array<string> {
282    let result = [];
283    const it = PersistentStorage.getOrCreate().keys();
284    let val = it.next();
285
286    while (!val.done) {
287      result.push(val.value);
288      val = it.next();
289    }
290
291    return result;
292  }
293
294  /**
295   * @see keys
296   * @deprecated
297   */
298  public static Keys(): Array<string> {
299    return PersistentStorage.keys();
300  }
301
302/**
303  * This methid offers a way to force writing the property value with given
304  * key to persistent storage.
305  * In the general case this is unnecessary as the framework observed changes
306  * and triggers writing to disk by itself. For nested objects (e.g. array of
307  * objects) however changes of a property of a property as not observed. This
308  * is the case where the application needs to signal to the framework.
309  *
310  * @param key property that has changed
311  *
312  * @since 10
313  *
314  */
315  public static notifyHasChanged(propName: string) {
316  stateMgmtConsole.debug(`PersistentStorage: force writing '${propName}'-
317      '${PersistentStorage.getOrCreate().links_.get(propName)}' to storage`);
318  PersistentStorage.getOrCreate().writeToPersistentStorage(propName,
319    PersistentStorage.getOrCreate().links_.get(propName).get());
320  }
321
322 /**
323  * @see notifyHasChanged
324  * @deprecated
325  */
326  public static NotifyHasChanged(propName: string) {
327    stateMgmtConsole.debug(`PersistentStorage: force writing '${propName}'-
328        '${PersistentStorage.getOrCreate().links_.get(propName)}' to storage`);
329    PersistentStorage.getOrCreate().writeToPersistentStorage(propName,
330      PersistentStorage.getOrCreate().links_.get(propName).get());
331  }
332
333  /**
334   * all following methods are framework internal
335   */
336
337  private constructor() {
338    this.links_ = new Map<string, SubscribedAbstractProperty<any>>();
339    this.id_ = SubscriberManager.MakeId();
340    SubscriberManager.Add(this);
341  }
342
343  private keys(): IterableIterator<string> {
344    return this.links_.keys();
345  }
346
347  private persistProp<T>(propName: string, defaultValue: T): void {
348    if (this.persistProp1(propName, defaultValue)) {
349      // persist new prop
350      stateMgmtConsole.debug(`PersistentStorage: writing '${propName}' - '${this.links_.get(propName)}' to storage`);
351      this.writeToPersistentStorage(propName, this.links_.get(propName).get());
352    }
353  }
354
355
356  // helper function to persist a property
357  // does everything except writing prop to disk
358  private persistProp1<T>(propName: string, defaultValue: T): boolean {
359    stateMgmtConsole.debug(`PersistentStorage: persistProp1 ${propName} ${defaultValue}`);
360    if (defaultValue == null && !Utils.isApiVersionEQAbove(12)) {
361      stateMgmtConsole.error(`PersistentStorage: persistProp for ${propName} called with 'null' or 'undefined' default value!`);
362      return false;
363    }
364
365    if (this.links_.get(propName)) {
366      stateMgmtConsole.warn(`PersistentStorage: persistProp: ${propName} is already persisted`);
367      return false;
368    }
369
370    let link = AppStorage.link(propName, this);
371    if (link) {
372      stateMgmtConsole.debug(`PersistentStorage: persistProp ${propName} in AppStorage, using that`);
373      this.links_.set(propName, link);
374    } else {
375      let returnValue: T;
376      if (!PersistentStorage.storage_.has(propName)) {
377        stateMgmtConsole.debug(`PersistentStorage: no entry for ${propName}, will initialize with default value`);
378        returnValue = defaultValue;
379      }
380      else {
381        returnValue = this.readFromPersistentStorage(propName);
382      }
383      link = AppStorage.setAndLink(propName, returnValue, this);
384      if (link === undefined) {
385        stateMgmtConsole.debug(`PersistentStorage: failed to set and link app storage property ${propName}`);
386        return false;
387      }
388      this.links_.set(propName, link);
389      stateMgmtConsole.debug(`PersistentStorage: created new persistent prop for ${propName}`);
390    }
391    return true;
392  }
393
394  private persistProps(properties: {
395    key: string,
396    defaultValue: any
397  }[]): void {
398    properties.forEach(property => this.persistProp1(property.key, property.defaultValue));
399    this.write();
400  }
401
402  private deleteProp(propName: string): void {
403    let link = this.links_.get(propName);
404    if (link) {
405      link.aboutToBeDeleted();
406      this.links_.delete(propName);
407      PersistentStorage.storage_.delete(propName);
408      stateMgmtConsole.debug(`PersistentStorage: deleteProp: no longer persisting '${propName}'.`);
409    } else {
410      stateMgmtConsole.warn(`PersistentStorage: '${propName}' is not a persisted property warning.`);
411    }
412  }
413
414  private write(): void {
415    this.links_.forEach((link, propName, map) => {
416      stateMgmtConsole.debug(`PersistentStorage: writing ${propName} to storage`);
417      this.writeToPersistentStorage(propName, link.get());
418    });
419  }
420
421  // helper function to write to the persistent storage
422  // any additional check and formatting can to be done here
423  private writeToPersistentStorage<T>(propName: string, value: T): void {
424    if (value instanceof Map) {
425      value = MapInfo.toObject(value) as unknown as T;
426    } else if (value instanceof Set) {
427      value = SetInfo.toObject(value) as unknown as T;
428    } else if (value instanceof Date) {
429      value = DateInfo.toObject(value) as unknown as T;
430    }
431
432    PersistentStorage.storage_.set(propName, value);
433  }
434
435  // helper function to read from the persistent storage
436  // any additional check and formatting can to be done here
437  private readFromPersistentStorage<T>(propName: string): T {
438    let newValue: T = PersistentStorage.storage_.get(propName);
439    if (newValue instanceof Object) {
440      if (MapInfo.isObject(newValue) === ObjectVersion.NewVersion) {
441        newValue = MapInfo.toMap(newValue as unknown as MapInfo<any, any>) as unknown as T;
442      } else if (MapInfo.isObject(newValue) === ObjectVersion.CompatibleVersion) {
443        newValue = MapInfo.toMapCompatible(newValue as unknown as MapInfo<any, any>) as unknown as T;
444      } else if (SetInfo.isObject(newValue)) {
445        newValue = SetInfo.toSet(newValue) as unknown as T;
446      } else if (DateInfo.isObject(newValue)) {
447        newValue = DateInfo.toDate(newValue) as unknown as T;
448      }
449    }
450
451    return newValue;
452  }
453
454  // FU code path method
455  public propertyHasChanged(info?: PropertyInfo): void {
456    stateMgmtConsole.debug('PersistentStorage: property changed');
457    this.write();
458  }
459
460  // PU code path method
461  public syncPeerHasChanged(eventSource: ObservedPropertyAbstractPU<any>) {
462    stateMgmtConsole.debug(`PersistentStorage: sync peer ${eventSource.info()} has changed`);
463    this.write();
464  }
465
466  // public required by the interface, use the static method instead!
467  public aboutToBeDeleted(): void {
468    stateMgmtConsole.debug('PersistentStorage: about to be deleted');
469    this.links_.forEach((val, key, map) => {
470      stateMgmtConsole.debug(`PersistentStorage: removing ${key}`);
471      val.aboutToBeDeleted();
472    });
473
474    this.links_.clear();
475    SubscriberManager.Delete(this.id__());
476    PersistentStorage.storage_.clear();
477  }
478
479  public id__(): number {
480    return this.id_;
481  }
482};
483
484