1/* 2 * Copyright (c) 2021 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 16class PersistentStorage implements IMultiPropertiesChangeSubscriber { 17 private static Storage_: IStorage; 18 private static Instance_: PersistentStorage = undefined; 19 20 private id_: number; 21 private links_: Map<string, ObservedPropertyAbstract<any>>; 22 23 /** 24 * 25 * @param storage method to be used by the framework to set the backend 26 * this is to be done during startup 27 */ 28 public static ConfigureBackend(storage: IStorage): void { 29 PersistentStorage.Storage_ = storage; 30 } 31 32 private static GetOrCreate(): PersistentStorage { 33 if (PersistentStorage.Instance_) { 34 // already initialized 35 return PersistentStorage.Instance_; 36 } 37 38 PersistentStorage.Instance_ = new PersistentStorage(); 39 return PersistentStorage.Instance_; 40 } 41 42 public static AboutToBeDeleted(): void { 43 if (!PersistentStorage.Instance_) { 44 return; 45 } 46 47 PersistentStorage.GetOrCreate().aboutToBeDeleted(); 48 PersistentStorage.Instance_ = undefined; 49 } 50 51 public static PersistProp<T>(key: string, defaultValue: T): void { 52 PersistentStorage.GetOrCreate().persistProp(key, defaultValue); 53 } 54 55 public static DeleteProp(key: string): void { 56 PersistentStorage.GetOrCreate().deleteProp(key); 57 } 58 59 public static PersistProps(properties: { 60 key: string, 61 defaultValue: any 62 }[]): void { 63 PersistentStorage.GetOrCreate().persistProps(properties); 64 } 65 66 public static Keys(): Array<string> { 67 let result = []; 68 const it = PersistentStorage.GetOrCreate().keys(); 69 let val = it.next(); 70 71 while (!val.done) { 72 result.push(val.value); 73 val = it.next(); 74 } 75 76 return result; 77 } 78 79 private constructor() { 80 this.links_ = new Map<string, ObservedPropertyAbstract<any>>(); 81 this.id_ = SubscriberManager.Get().MakeId(); 82 SubscriberManager.Get().add(this); 83 } 84 85 private keys(): IterableIterator<string> { 86 return this.links_.keys(); 87 } 88 89 private persistProp<T>(propName: string, defaultValue: T): void { 90 if (this.persistProp1(propName, defaultValue)) { 91 // persist new prop 92 console.debug(`PersistentStorage: writing '${propName}' - '${this.links_.get(propName)}' to storage`); 93 PersistentStorage.Storage_.set(propName, JSON.stringify(this.links_.get(propName).get())); 94 } 95 } 96 97 98 // helper function to persist a property 99 // does everything except writing prop to disk 100 private persistProp1<T>(propName: string, defaultValue: T): boolean { 101 if (defaultValue == null || defaultValue == undefined) { 102 console.error(`PersistentStorage: persistProp for ${propName} called with 'null' or 'undefined' default value!`); 103 return false; 104 } 105 106 if (this.links_.get(propName)) { 107 console.warn(`PersistentStorage: persistProp: ${propName} is already persisted`); 108 return false; 109 } 110 111 let link = AppStorage.GetOrCreate().link(propName, this); 112 if (link) { 113 console.debug(`PersistentStorage: persistProp ${propName} in AppStorage, using that`); 114 this.links_.set(propName, link); 115 } else { 116 let newValue: string = PersistentStorage.Storage_.get(propName); 117 let returnValue: T; 118 if (!newValue || newValue == "") { 119 console.debug(`PersistentStorage: no entry for ${propName}, will initialize with default value`); 120 returnValue = defaultValue; 121 } 122 try { 123 returnValue = JSON.parse(newValue); 124 } catch (error) { 125 console.error(`PersistentStorage: convert for ${propName} has error: ` + error.toString()); 126 } 127 link = AppStorage.GetOrCreate().setAndLink(propName, returnValue, this); 128 this.links_.set(propName, link); 129 console.debug(`PersistentStorage: created new persistent prop for ${propName}`); 130 } 131 return true; 132 } 133 134 private persistProps(properties: { 135 key: string, 136 defaultValue: any 137 }[]): void { 138 properties.forEach(property => this.persistProp1(property.key, property.defaultValue)); 139 this.write(); 140 } 141 142 private deleteProp(propName: string): void { 143 let link = this.links_.get(propName); 144 if (link) { 145 link.aboutToBeDeleted(); 146 this.links_.delete(propName); 147 PersistentStorage.Storage_.delete(propName); 148 console.debug(`PersistentStorage: deleteProp: no longer persisting '${propName}'.`); 149 } else { 150 console.warn(`PersistentStorage: '${propName}' is not a persisted property warning.`); 151 } 152 } 153 154 private write(): void { 155 this.links_.forEach((link, propName, map) => { 156 console.debug(`PersistentStorage: writing ${propName} to storage`); 157 PersistentStorage.Storage_.set(propName, JSON.stringify(link.get())); 158 }); 159 } 160 161 public propertyHasChanged(info?: PropertyInfo): void { 162 console.debug("PersistentStorage: property changed"); 163 this.write(); 164 } 165 166 // public required by the interface, use the static method instead! 167 public aboutToBeDeleted(): void { 168 console.debug("PersistentStorage: about to be deleted"); 169 this.links_.forEach((val, key, map) => { 170 console.debug(`PersistentStorage: removing ${key}`); 171 val.aboutToBeDeleted(); 172 }); 173 174 this.links_.clear(); 175 SubscriberManager.Get().delete(this.id__()); 176 PersistentStorage.Storage_.clear(); 177 } 178 179 public id__(): number { 180 return this.id_; 181 } 182 183 /** 184 * This methid offers a way to force writing the property value with given 185 * key to persistent storage. 186 * In the general case this is unnecessary as the framework observed changes 187 * and triggers writing to disk by itself. For nested objects (e.g. array of 188 * objects) however changes of a property of a property as not observed. This 189 * is the case where the application needs to signal to the framework. 190 * @param key property that has changed 191 */ 192 public static NotifyHasChanged(propName: string) { 193 console.debug(`PersistentStorage: force writing '${propName}'- 194 '${PersistentStorage.GetOrCreate().links_.get(propName)}' to storage`); 195 PersistentStorage.Storage_.set(propName, 196 JSON.stringify(PersistentStorage.GetOrCreate().links_.get(propName).get())); 197 } 198}; 199