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 * PersistentStorage 18 * 19 * Keeps current values of select AppStorage property properties persisted to file. 20 * 21 * since 9 22 */ 23 24class PersistentStorage implements IMultiPropertiesChangeSubscriber { 25 private static storage_: IStorage; 26 private static instance_: PersistentStorage = undefined; 27 28 private id_: number; 29 private links_: Map<string, SubscribedAbstractProperty<any>>; 30 31 /** 32 * 33 * @param storage method to be used by the framework to set the backend 34 * this is to be done during startup 35 * 36 * internal function, not part of the SDK 37 * 38 */ 39 public static configureBackend(storage: IStorage): void { 40 PersistentStorage.storage_ = storage; 41 } 42 43 /** 44 * private, use static functions! 45 */ 46 private static getOrCreate(): PersistentStorage { 47 if (PersistentStorage.instance_) { 48 // already initialized 49 return PersistentStorage.instance_; 50 } 51 52 PersistentStorage.instance_ = new PersistentStorage(); 53 return PersistentStorage.instance_; 54 } 55 56 /** 57 * 58 * internal function, not part of the SDK 59 */ 60 public static aboutToBeDeleted(): void { 61 if (!PersistentStorage.instance_) { 62 return; 63 } 64 65 PersistentStorage.getOrCreate().aboutToBeDeleted(); 66 PersistentStorage.instance_ = undefined; 67 } 68 69 70 /** 71 * Add property 'key' to AppStorage properties whose current value will be 72 * persistent. 73 * If AppStorage does not include this property it will be added and initializes 74 * with given value 75 * 76 * @since 10 77 * 78 * @param key property name 79 * @param defaultValue If AppStorage does not include this property it will be initialized with this value 80 * 81 */ 82 public static persistProp<T>(key: string, defaultValue: T): void { 83 PersistentStorage.getOrCreate().persistProp(key, defaultValue); 84 } 85 86 /** 87 * @see persistProp 88 * @deprecated 89 */ 90 public static PersistProp<T>(key: string, defaultValue: T): void { 91 PersistentStorage.getOrCreate().persistProp(key, defaultValue); 92 } 93 94 95 /** 96 * Reverse of @see persistProp 97 * @param key no longer persist the property named key 98 * 99 * @since 10 100 */ 101 public static deleteProp(key: string): void { 102 PersistentStorage.getOrCreate().deleteProp(key); 103 } 104 105 /** 106 * @see deleteProp 107 * @deprecated 108 */ 109 public static DeleteProp(key: string): void { 110 PersistentStorage.getOrCreate().deleteProp(key); 111 } 112 113 /** 114 * Persist given AppStorage properties with given names. 115 * If a property does not exist in AppStorage, add it and initialize it with given value 116 * works as @see persistProp for multiple properties. 117 * 118 * @param properties 119 * 120 * @since 10 121 * 122 */ 123 public static persistProps(properties: { 124 key: string, 125 defaultValue: any 126 }[]): void { 127 PersistentStorage.getOrCreate().persistProps(properties); 128 } 129 130 /** 131 * @see persistProps 132 * @deprecated 133 */ 134 public static PersistProps(properties: { 135 key: string, 136 defaultValue: any 137 }[]): void { 138 PersistentStorage.getOrCreate().persistProps(properties); 139 } 140 141 /** 142 * Inform persisted AppStorage property names 143 * @returns array of AppStorage keys 144 * 145 * @since 10 146 */ 147 public static keys(): Array<string> { 148 let result = []; 149 const it = PersistentStorage.getOrCreate().keys(); 150 let val = it.next(); 151 152 while (!val.done) { 153 result.push(val.value); 154 val = it.next(); 155 } 156 157 return result; 158 } 159 160 /** 161 * @see keys 162 * @deprecated 163 */ 164 public static Keys(): Array<string> { 165 return PersistentStorage.keys(); 166 } 167 168/** 169 * This methid offers a way to force writing the property value with given 170 * key to persistent storage. 171 * In the general case this is unnecessary as the framework observed changes 172 * and triggers writing to disk by itself. For nested objects (e.g. array of 173 * objects) however changes of a property of a property as not observed. This 174 * is the case where the application needs to signal to the framework. 175 * 176 * @param key property that has changed 177 * 178 * @since 10 179 * 180 */ 181 public static notifyHasChanged(propName: string) { 182 stateMgmtConsole.debug(`PersistentStorage: force writing '${propName}'- 183 '${PersistentStorage.getOrCreate().links_.get(propName)}' to storage`); 184 PersistentStorage.storage_.set(propName, 185 PersistentStorage.getOrCreate().links_.get(propName).get()); 186 } 187 188 /** 189 * @see notifyHasChanged 190 * @deprecated 191 */ 192 public static NotifyHasChanged(propName: string) { 193 stateMgmtConsole.debug(`PersistentStorage: force writing '${propName}'- 194 '${PersistentStorage.getOrCreate().links_.get(propName)}' to storage`); 195 PersistentStorage.storage_.set(propName, 196 PersistentStorage.getOrCreate().links_.get(propName).get()); 197 } 198 199 /** 200 * all following methods are framework internal 201 */ 202 203 private constructor() { 204 this.links_ = new Map<string, SubscribedAbstractProperty<any>>(); 205 this.id_ = SubscriberManager.MakeId(); 206 SubscriberManager.Add(this); 207 } 208 209 private keys(): IterableIterator<string> { 210 return this.links_.keys(); 211 } 212 213 private persistProp<T>(propName: string, defaultValue: T): void { 214 if (this.persistProp1(propName, defaultValue)) { 215 // persist new prop 216 stateMgmtConsole.debug(`PersistentStorage: writing '${propName}' - '${this.links_.get(propName)}' to storage`); 217 PersistentStorage.storage_.set(propName, this.links_.get(propName).get()); 218 } 219 } 220 221 222 // helper function to persist a property 223 // does everything except writing prop to disk 224 private persistProp1<T>(propName: string, defaultValue: T): boolean { 225 stateMgmtConsole.debug(`PersistentStorage: persistProp1 ${propName} ${defaultValue}`); 226 if (defaultValue == null || defaultValue == undefined) { 227 stateMgmtConsole.error(`PersistentStorage: persistProp for ${propName} called with 'null' or 'undefined' default value!`); 228 return false; 229 } 230 231 if (this.links_.get(propName)) { 232 stateMgmtConsole.warn(`PersistentStorage: persistProp: ${propName} is already persisted`); 233 return false; 234 } 235 236 let link = AppStorage.link(propName, this); 237 if (link) { 238 stateMgmtConsole.debug(`PersistentStorage: persistProp ${propName} in AppStorage, using that`); 239 this.links_.set(propName, link); 240 } else { 241 let newValue: T = PersistentStorage.storage_.get(propName); 242 let returnValue: T; 243 if (newValue == undefined || newValue == null) { 244 stateMgmtConsole.debug(`PersistentStorage: no entry for ${propName}, will initialize with default value`); 245 returnValue = defaultValue; 246 } 247 else { 248 returnValue = newValue; 249 } 250 link = AppStorage.setAndLink(propName, returnValue, this); 251 this.links_.set(propName, link); 252 stateMgmtConsole.debug(`PersistentStorage: created new persistent prop for ${propName}`); 253 } 254 return true; 255 } 256 257 private persistProps(properties: { 258 key: string, 259 defaultValue: any 260 }[]): void { 261 properties.forEach(property => this.persistProp1(property.key, property.defaultValue)); 262 this.write(); 263 } 264 265 private deleteProp(propName: string): void { 266 let link = this.links_.get(propName); 267 if (link) { 268 link.aboutToBeDeleted(); 269 this.links_.delete(propName); 270 PersistentStorage.storage_.delete(propName); 271 stateMgmtConsole.debug(`PersistentStorage: deleteProp: no longer persisting '${propName}'.`); 272 } else { 273 stateMgmtConsole.warn(`PersistentStorage: '${propName}' is not a persisted property warning.`); 274 } 275 } 276 277 private write(): void { 278 this.links_.forEach((link, propName, map) => { 279 stateMgmtConsole.debug(`PersistentStorage: writing ${propName} to storage`); 280 PersistentStorage.storage_.set(propName, link.get()); 281 }); 282 } 283 284 public propertyHasChanged(info?: PropertyInfo): void { 285 stateMgmtConsole.debug("PersistentStorage: property changed"); 286 this.write(); 287 } 288 289 public syncPeerHasChanged(eventSource: ObservedPropertyAbstractPU<any>) { 290 stateMgmtConsole.debug(`PersistentStorage: sync peer ${eventSource.info()} has changed`); 291 this.write(); 292 } 293 294 // public required by the interface, use the static method instead! 295 public aboutToBeDeleted(): void { 296 stateMgmtConsole.debug("PersistentStorage: about to be deleted"); 297 this.links_.forEach((val, key, map) => { 298 stateMgmtConsole.debug(`PersistentStorage: removing ${key}`); 299 val.aboutToBeDeleted(); 300 }); 301 302 this.links_.clear(); 303 SubscriberManager.Delete(this.id__()); 304 PersistentStorage.storage_.clear(); 305 } 306 307 public id__(): number { 308 return this.id_; 309 } 310}; 311 312