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 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 * persistemt. 73 * If AppStorage does not include this property it will be added and initializes 74 * with given value 75 * 76 * @since 9 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 * Reverse of @see PersistProp 88 * @param key no longer persist the property named key 89 * 90 * @since 9 91 */ 92 public static DeleteProp(key: string): void { 93 PersistentStorage.GetOrCreate().deleteProp(key); 94 } 95 96 /** 97 * Persist given AppStorage properties with given names. 98 * If a property does not exist in AppStorage, add it and initialize it with given value 99 * works as @see PersistProp for multiple properties. 100 * 101 * @param properties 102 * 103 * @since 9 104 * 105 */ 106 public static PersistProps(properties: { 107 key: string, 108 defaultValue: any 109 }[]): void { 110 PersistentStorage.GetOrCreate().persistProps(properties); 111 } 112 113 /** 114 * Inform persisted AppStorage property names 115 * @returns array of AppStorage keys 116 * 117 * @since 9 118 */ 119 public static Keys(): Array<string> { 120 let result = []; 121 const it = PersistentStorage.GetOrCreate().keys(); 122 let val = it.next(); 123 124 while (!val.done) { 125 result.push(val.value); 126 val = it.next(); 127 } 128 129 return result; 130 } 131 132/** 133 * This methid offers a way to force writing the property value with given 134 * key to persistent storage. 135 * In the general case this is unnecessary as the framework observed changes 136 * and triggers writing to disk by itself. For nested objects (e.g. array of 137 * objects) however changes of a property of a property as not observed. This 138 * is the case where the application needs to signal to the framework. 139 * 140 * @param key property that has changed 141 * 142 * @since 9 143 * 144 */ 145 public static NotifyHasChanged(propName: string) { 146 stateMgmtConsole.debug(`PersistentStorage: force writing '${propName}'- 147 '${PersistentStorage.GetOrCreate().links_.get(propName)}' to storage`); 148 PersistentStorage.Storage_.set(propName, 149 PersistentStorage.GetOrCreate().links_.get(propName).get()); 150} 151 /** 152 * all following methods are framework internal 153 */ 154 155 private constructor() { 156 this.links_ = new Map<string, SubscribedAbstractProperty<any>>(); 157 this.id_ = SubscriberManager.MakeId(); 158 SubscriberManager.Add(this); 159 } 160 161 private keys(): IterableIterator<string> { 162 return this.links_.keys(); 163 } 164 165 private persistProp<T>(propName: string, defaultValue: T): void { 166 if (this.persistProp1(propName, defaultValue)) { 167 // persist new prop 168 stateMgmtConsole.debug(`PersistentStorage: writing '${propName}' - '${this.links_.get(propName)}' to storage`); 169 PersistentStorage.Storage_.set(propName, this.links_.get(propName).get()); 170 } 171 } 172 173 174 // helper function to persist a property 175 // does everything except writing prop to disk 176 private persistProp1<T>(propName: string, defaultValue: T): boolean { 177 if (defaultValue == null || defaultValue == undefined) { 178 stateMgmtConsole.error(`PersistentStorage: persistProp for ${propName} called with 'null' or 'undefined' default value!`); 179 return false; 180 } 181 182 if (this.links_.get(propName)) { 183 stateMgmtConsole.warn(`PersistentStorage: persistProp: ${propName} is already persisted`); 184 return false; 185 } 186 187 let link = AppStorage.Link(propName, this); 188 if (link) { 189 stateMgmtConsole.debug(`PersistentStorage: persistProp ${propName} in AppStorage, using that`); 190 this.links_.set(propName, link); 191 } else { 192 let newValue: T = PersistentStorage.Storage_.get(propName); 193 let returnValue: T; 194 if (!newValue) { 195 stateMgmtConsole.debug(`PersistentStorage: no entry for ${propName}, will initialize with default value`); 196 returnValue = defaultValue; 197 } 198 else { 199 returnValue = newValue; 200 } 201 link = AppStorage.SetAndLink(propName, returnValue, this); 202 this.links_.set(propName, link); 203 stateMgmtConsole.debug(`PersistentStorage: created new persistent prop for ${propName}`); 204 } 205 return true; 206 } 207 208 private persistProps(properties: { 209 key: string, 210 defaultValue: any 211 }[]): void { 212 properties.forEach(property => this.persistProp1(property.key, property.defaultValue)); 213 this.write(); 214 } 215 216 private deleteProp(propName: string): void { 217 let link = this.links_.get(propName); 218 if (link) { 219 link.aboutToBeDeleted(); 220 this.links_.delete(propName); 221 PersistentStorage.Storage_.delete(propName); 222 stateMgmtConsole.debug(`PersistentStorage: deleteProp: no longer persisting '${propName}'.`); 223 } else { 224 stateMgmtConsole.warn(`PersistentStorage: '${propName}' is not a persisted property warning.`); 225 } 226 } 227 228 private write(): void { 229 this.links_.forEach((link, propName, map) => { 230 stateMgmtConsole.debug(`PersistentStorage: writing ${propName} to storage`); 231 PersistentStorage.Storage_.set(propName, link.get()); 232 }); 233 } 234 235 public propertyHasChanged(info?: PropertyInfo): void { 236 stateMgmtConsole.debug("PersistentStorage: property changed"); 237 this.write(); 238 } 239 240 // public required by the interface, use the static method instead! 241 public aboutToBeDeleted(): void { 242 stateMgmtConsole.debug("PersistentStorage: about to be deleted"); 243 this.links_.forEach((val, key, map) => { 244 stateMgmtConsole.debug(`PersistentStorage: removing ${key}`); 245 val.aboutToBeDeleted(); 246 }); 247 248 this.links_.clear(); 249 SubscriberManager.Delete(this.id__()); 250 PersistentStorage.Storage_.clear(); 251 } 252 253 public id__(): number { 254 return this.id_; 255 } 256}; 257 258