1/* 2 * Copyright (c) 2024 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 16const enum PersistError { 17 Quota = 'quota', 18 Serialization = 'serialization', 19 Unknown = 'unknown' 20}; 21 22type PersistErrorType = PersistError.Quota | PersistError.Serialization | PersistError.Unknown; 23type PersistErrorCallback = ((key: string, reason: PersistErrorType, message: string) => void) | undefined; 24type StorageDefaultCreator<T> = () => T; 25 26interface TypeConstructorWithArgs<T> { 27 new(...args: any): T; 28} 29 30class ConnectOptions<T extends object> { 31 type: TypeConstructorWithArgs<T>; 32 key?: string; 33 defaultCreator?: StorageDefaultCreator<T>; 34 areaMode?: number; 35} 36 37const enum AreaMode { 38 EL1 = 0, 39 EL2 = 1, 40 EL3 = 2, 41 EL4 = 3, 42 EL5 = 4 43} 44 45const enum MapType { 46 NOT_IN_MAP = -1, 47 MODULE_MAP = 0, 48 GLOBAL_MAP = 1 49} 50 51class StorageHelper { 52 protected static readonly INVALID_DEFAULT_VALUE: string = 'The default creator should be function when first connect'; 53 protected static readonly DELETE_NOT_EXIST_KEY: string = 'The key to be deleted does not exist'; 54 protected static readonly INVALID_TYPE: string = 'The type should have function constructor signature when use storage'; 55 protected static readonly EMPTY_STRING_KEY: string = 'Cannot use empty string as the key'; 56 protected static readonly INVALID_LENGTH_KEY: string = 'Cannot use the key! The length of key should be 2 to 255'; 57 protected static readonly INVALID_CHARACTER_KEY: string = 'Cannot use the key! The value of key can only consist of letters, digits and underscores'; 58 protected static readonly NULL_OR_UNDEFINED_KEY: string = `The parameter cannot be null or undefined`; 59 60 // sotre old type name to check the type matches or not 61 protected oldTypeValues_: Map<string, string>; 62 63 constructor() { 64 this.oldTypeValues_ = new Map<string, string>(); 65 } 66 67 protected getConnectedKey<T>(type: TypeConstructorWithArgs<T>, 68 keyOrDefaultCreator?: string | StorageDefaultCreator<T>): string | undefined { 69 if (keyOrDefaultCreator === null || keyOrDefaultCreator === undefined) { 70 stateMgmtConsole.applicationWarn(StorageHelper.NULL_OR_UNDEFINED_KEY + ', try to use the type name as key'); 71 } 72 73 if (typeof keyOrDefaultCreator === 'string') { 74 return keyOrDefaultCreator; 75 } 76 77 return this.getTypeName(type); 78 } 79 80 protected getKeyOrTypeName<T>(keyOrType: string | TypeConstructorWithArgs<T>): string | undefined { 81 if (typeof keyOrType === 'function') { 82 keyOrType = this.getTypeName(keyOrType); 83 } 84 return keyOrType; 85 } 86 87 protected checkTypeByName<T>(key: string, type: TypeConstructorWithArgs<T>, oldType: string): void { 88 if (this.getTypeName(type) !== oldType) { 89 throw new Error(`The type mismatches when use the key '${key}' in storage`); 90 } 91 } 92 93 protected checkTypeByInstanceOf<T>(key: string, type: TypeConstructorWithArgs<T>, ins: T): void { 94 this.checkTypeIsFunc(type); 95 96 if (!(ins instanceof type)) { 97 throw new Error(`The type mismatches when use the key '${key}' in storage`); 98 } 99 } 100 101 protected getTypeName<T>(type: TypeConstructorWithArgs<T>): string | undefined { 102 this.checkTypeIsFunc(type); 103 104 let name: string | undefined = type.name; 105 while (name === undefined) { 106 type = Object.getPrototypeOf(type); 107 if (!type) { 108 break; 109 } 110 name = type.name; 111 } 112 return name; 113 } 114 115 protected isKeyValid(key: string | null | undefined): boolean { 116 if (typeof key !== 'string') { 117 throw new Error(StorageHelper.INVALID_TYPE); 118 } 119 120 // The key string is empty 121 if (key === '') { 122 stateMgmtConsole.applicationError(StorageHelper.EMPTY_STRING_KEY); 123 return false; 124 } 125 126 const len: number = key.length; 127 // The key string length should shorter than 1024 128 if (len >= 1024) { 129 stateMgmtConsole.applicationError(StorageHelper.INVALID_LENGTH_KEY); 130 return false; 131 } 132 133 if (len < 2 || len > 255) { 134 stateMgmtConsole.applicationWarn(StorageHelper.INVALID_LENGTH_KEY); 135 } 136 137 if (!/^[0-9a-zA-Z_]+$/.test(key)) { 138 stateMgmtConsole.applicationWarn(StorageHelper.INVALID_CHARACTER_KEY); 139 } 140 141 return true; 142 } 143 144 private checkTypeIsFunc<T>(type: TypeConstructorWithArgs<T>): void { 145 if (typeof type !== 'function') { 146 throw new Error(StorageHelper.INVALID_TYPE); 147 } 148 } 149} 150 151class AppStorageV2Impl extends StorageHelper { 152 private static instance_: AppStorageV2Impl = undefined; 153 154 private memorizedValues_: Map<string, any>; 155 156 constructor() { 157 super(); 158 this.memorizedValues_ = new Map<string, any>(); 159 } 160 161 public static instance(): AppStorageV2Impl { 162 if (AppStorageV2Impl.instance_) { 163 return AppStorageV2Impl.instance_; 164 } 165 AppStorageV2Impl.instance_ = new AppStorageV2Impl(); 166 return AppStorageV2Impl.instance_; 167 } 168 169 public connect<T extends object>(type: TypeConstructorWithArgs<T>, keyOrDefaultCreator?: string | StorageDefaultCreator<T>, 170 defaultCreator?: StorageDefaultCreator<T>): T | undefined { 171 const key: string = this.getConnectedKey(type, keyOrDefaultCreator); 172 173 if (!this.isKeyValid(key)) { 174 return undefined; 175 } 176 177 if (typeof keyOrDefaultCreator === 'function') { 178 defaultCreator = keyOrDefaultCreator; 179 } 180 181 if (!this.memorizedValues_.has(key)) { 182 if (typeof defaultCreator !== 'function') { 183 throw new Error(AppStorageV2Impl.INVALID_DEFAULT_VALUE); 184 } 185 186 const defaultValue: T = defaultCreator(); 187 188 this.checkTypeByInstanceOf(key, type, defaultValue); 189 190 this.memorizedValues_.set(key, defaultValue); 191 this.oldTypeValues_.set(key, this.getTypeName(type)); 192 return defaultValue; 193 } 194 195 this.checkTypeByName(key, type, this.oldTypeValues_.get(key)); 196 197 const existedValue: T = this.memorizedValues_.get(key); 198 return existedValue; 199 } 200 201 public remove<T>(keyOrType: string | TypeConstructorWithArgs<T>): void { 202 if (keyOrType === null || keyOrType === undefined) { 203 stateMgmtConsole.applicationWarn(AppStorageV2Impl.NULL_OR_UNDEFINED_KEY); 204 return; 205 } 206 207 const key: string = this.getKeyOrTypeName(keyOrType); 208 209 if (!this.isKeyValid(key)) { 210 return; 211 } 212 213 this.removeFromMemory(key); 214 } 215 216 public getMemoryKeys(): Array<string> { 217 return Array.from(this.memorizedValues_.keys()); 218 } 219 220 private removeFromMemory(key: string): void { 221 const isDeleted: boolean = this.memorizedValues_.delete(key); 222 223 if (!isDeleted) { 224 stateMgmtConsole.applicationWarn(AppStorageV2Impl.DELETE_NOT_EXIST_KEY); 225 } else { 226 this.oldTypeValues_.delete(key); 227 } 228 } 229} 230 231class PersistenceV2Impl extends StorageHelper { 232 public static readonly MIN_PERSISTENCE_ID = 0x1010000000000; 233 public static nextPersistId_ = PersistenceV2Impl.MIN_PERSISTENCE_ID; 234 235 private static readonly NOT_SUPPORT_TYPE_MESSAGE_: string = 'Not support! Can only use the class object in Persistence'; 236 private static readonly NOT_SUPPORT_AREAMODE_MESSAGE_: string = 'AreaMode Value Error! value range can only in EL1-EL5'; 237 private static readonly KEYS_DUPLICATE_: string = 'ERROR, Duplicate key used when connect'; 238 private static readonly KEYS_ARR_: string = '___keys_arr'; 239 private static readonly MAX_DATA_LENGTH_: number = 8000; 240 private static readonly NOT_SUPPORT_TYPES_: Array<any> = 241 [Array, Set, Map, WeakSet, WeakMap, Date, Boolean, Number, String, Symbol, BigInt, RegExp, Function, Promise, ArrayBuffer]; 242 private static storage_: IStorage; 243 private static instance_: PersistenceV2Impl = undefined; 244 245 // the map is used to store the persisted value in memory, can reuse when re-connect if the key exists 246 private map_: Map<string, any>; 247 private globalMap_: Map<string, any>; 248 private globalMapAreaMode_: Map<string, number>; 249 private keysArr_: Set<string>; 250 private globalKeysArr_: Array<Set<string>>; 251 private cb_: PersistErrorCallback = undefined; 252 private idToKey_: Map<number, string>; 253 254 constructor() { 255 super(); 256 this.map_ = new Proxy(new Map<string, any>(), new SetMapProxyHandler()); 257 this.globalMap_ = new Proxy(new Map<string, any>(), new SetMapProxyHandler()); 258 this.globalMapAreaMode_ = new Map<string, number>(); 259 this.keysArr_ = new Set<string>(); 260 this.globalKeysArr_ = [new Set(), new Set(), new Set(), new Set(), new Set()]; 261 this.idToKey_ = new Map<number, string>(); 262 } 263 264 public static instance(): PersistenceV2Impl { 265 if (PersistenceV2Impl.instance_) { 266 return PersistenceV2Impl.instance_; 267 } 268 PersistenceV2Impl.instance_ = new PersistenceV2Impl(); 269 return PersistenceV2Impl.instance_; 270 } 271 272 public static configureBackend(storage: IStorage): void { 273 PersistenceV2Impl.storage_ = storage; 274 } 275 276 public connect<T extends object>(type: TypeConstructorWithArgs<T>, keyOrDefaultCreator?: string | StorageDefaultCreator<T>, 277 defaultCreator?: StorageDefaultCreator<T>): T | undefined { 278 this.checkTypeIsClassObject(type); 279 280 const key: string | undefined = this.getRightKey(type, keyOrDefaultCreator); 281 if (!this.isKeyValid(key)) { 282 return undefined; 283 } 284 285 if (typeof keyOrDefaultCreator === 'function') { 286 defaultCreator = keyOrDefaultCreator; 287 } 288 289 // In memory 290 if (this.globalMap_.has(key)) { 291 throw new Error(PersistenceV2Impl.KEYS_DUPLICATE_); 292 } 293 if (this.map_.has(key)) { 294 const existedValue: T = this.map_.get(key); 295 this.checkTypeByName(key, type, this.oldTypeValues_.get(key)); 296 return existedValue; 297 } 298 299 const observedValue: T | undefined = this.getConnectDefaultValue(key, type, defaultCreator); 300 if (!observedValue) { 301 return undefined; 302 } 303 304 const id: number = ++PersistenceV2Impl.nextPersistId_; 305 this.idToKey_.set(id, key); 306 307 // Not in memory, but in disk 308 if (PersistenceV2Impl.storage_.has(key)) { 309 return this.getValueFromDisk(key, id, observedValue, type); 310 } 311 312 // Neither in memory or in disk 313 return this.setValueToDisk(key, id, observedValue, type); 314 } 315 316 public globalConnect<T extends object>(connectOptions: ConnectOptions<T>): T | undefined { 317 this.checkTypeIsClassObject(connectOptions.type); 318 319 const key: string | undefined = this.getRightGlobalKey(connectOptions.type, connectOptions.key); 320 if (!this.isKeyValid(key)) { 321 return undefined; 322 } 323 324 // In memory, do duplicate key check 325 if (this.map_.has(key)) { 326 throw new Error(PersistenceV2Impl.KEYS_DUPLICATE_); 327 } 328 // In memory, return if globalMap_ exist 329 if (this.globalMap_.has(key)) { 330 const existedValue: T = this.globalMap_.get(key); 331 this.checkTypeByName(key, connectOptions.type, this.oldTypeValues_.get(key)); 332 return existedValue; 333 } 334 335 const observedValue: T | undefined = this.getConnectDefaultValue(key, 336 connectOptions.type, connectOptions.defaultCreator); 337 if (!observedValue) { 338 return undefined; 339 } 340 341 const id: number = ++PersistenceV2Impl.nextPersistId_; 342 this.idToKey_.set(id, key); 343 344 // Not in memory, but in disk 345 const areaMode: number = this.getAreaMode(connectOptions.areaMode); 346 this.globalMapAreaMode_.set(key, areaMode); 347 if (PersistenceV2Impl.storage_.has(key, areaMode)) { 348 return this.getValueFromDisk(key, id, observedValue, connectOptions.type, areaMode); 349 } 350 351 // Neither in memory or in disk 352 return this.setValueToDisk(key, id, observedValue, connectOptions.type, areaMode); 353 } 354 355 public keys(): Array<string> { 356 const allKeys: Set<string> = new Set<string>(); 357 try { 358 // add module path key 359 if (!this.keysArr_.size) { 360 this.keysArr_ = this.getKeysArrFromStorage(); 361 } 362 for (const key of this.keysArr_) { 363 allKeys.add(key); 364 } 365 // add global path key 366 for (let i = 0; i < this.globalKeysArr_.length; i++) { 367 if (!this.globalKeysArr_[i].size) { 368 this.globalKeysArr_[i] = this.getKeysArrFromStorage(i); 369 } 370 for (const key of this.globalKeysArr_[i]) { 371 allKeys.add(key); 372 } 373 } 374 } catch (err) { 375 if (this.cb_ && typeof this.cb_ === 'function') { 376 this.cb_('', PersistError.Unknown, `fail to get all persisted keys`); 377 return []; 378 } 379 throw new Error(err); 380 } 381 382 return Array.from(allKeys); 383 } 384 385 public remove<T>(keyOrType: string | TypeConstructorWithArgs<T>): void { 386 if (keyOrType === null || keyOrType === undefined) { 387 stateMgmtConsole.applicationWarn(PersistenceV2Impl.NULL_OR_UNDEFINED_KEY); 388 return; 389 } 390 391 this.checkTypeIsClassObject(keyOrType); 392 393 const key: string = this.getKeyOrTypeName(keyOrType); 394 395 if (!this.isKeyValid(key)) { 396 return; 397 } 398 399 this.disconnectValue(key); 400 } 401 402 public save<T>(keyOrType: string | TypeConstructorWithArgs<T>): void { 403 if (keyOrType === null || keyOrType === undefined) { 404 stateMgmtConsole.applicationWarn(PersistenceV2Impl.NULL_OR_UNDEFINED_KEY); 405 return; 406 } 407 408 this.checkTypeIsClassObject(keyOrType); 409 410 const key: string = this.getKeyOrTypeName(keyOrType); 411 412 if (!this.isKeyValid(key)) { 413 return; 414 } 415 416 if (this.globalMap_.has(key)) { 417 // find in global path 418 const areaMode = this.globalMapAreaMode_.get(key); 419 try { 420 PersistenceV2Impl.storage_.set(key, JSONCoder.stringify(this.globalMap_.get(key)), areaMode); 421 } catch (err) { 422 this.errorHelper(key, PersistError.Serialization, err); 423 } 424 } else if (this.map_.has(key)) { 425 // find in module path 426 try { 427 PersistenceV2Impl.storage_.set(key, JSONCoder.stringify(this.map_.get(key))); 428 } catch (err) { 429 this.errorHelper(key, PersistError.Serialization, err); 430 } 431 } else { 432 stateMgmtConsole.applicationWarn(`Cannot save the key '${key}'! The key is disconnected`); 433 } 434 } 435 436 public notifyOnError(cb: PersistErrorCallback): void { 437 this.cb_ = cb; 438 } 439 440 public onChangeObserved(persistKeys: Array<number>): void { 441 this.writeAllChangedToFile(persistKeys); 442 } 443 444 private connectNewValue(key: string, newValue: any, typeName: string, areaMode?: number | undefined): void { 445 if (typeof areaMode === 'number') { 446 this.globalMap_.set(key, newValue); 447 } else { 448 this.map_.set(key, newValue); 449 } 450 this.oldTypeValues_.set(key, typeName); 451 452 this.storeKeyToPersistenceV2(key, areaMode); 453 } 454 455 private disconnectValue(key: string): void { 456 const keyType: MapType = this.getKeyMapType(key); 457 let areaMode: number | undefined = undefined; 458 if (keyType === MapType.GLOBAL_MAP) { 459 this.globalMap_.delete(key); 460 areaMode = this.globalMapAreaMode_.get(key); 461 this.globalMapAreaMode_.delete(key); 462 } else if (keyType === MapType.MODULE_MAP) { 463 this.map_.delete(key); 464 } 465 466 this.oldTypeValues_.delete(key); 467 this.removeFromPersistenceV2(key, areaMode); 468 } 469 470 private checkTypeIsClassObject<T>(keyOrType: string | TypeConstructorWithArgs<T>) { 471 if ((typeof keyOrType !== 'string' && typeof keyOrType !== 'function') || 472 PersistenceV2Impl.NOT_SUPPORT_TYPES_.includes(keyOrType as any)) { 473 throw new Error(PersistenceV2Impl.NOT_SUPPORT_TYPE_MESSAGE_); 474 } 475 } 476 477 private getRightKey<T extends object>(type: TypeConstructorWithArgs<T>, 478 keyOrDefaultCreator?: string | StorageDefaultCreator<T>) { 479 const key: string = this.getConnectedKey(type, keyOrDefaultCreator); 480 481 if (key === undefined) { 482 throw new Error(PersistenceV2Impl.NOT_SUPPORT_TYPE_MESSAGE_); 483 } 484 485 if (key === PersistenceV2Impl.KEYS_ARR_) { 486 this.errorHelper(key, PersistError.Quota, `The key '${key}' cannot be used`); 487 return undefined; 488 } 489 490 return key; 491 } 492 493 private getRightGlobalKey<T extends object>(type: TypeConstructorWithArgs<T>, 494 key: string | undefined): string { 495 if (key === undefined || key === null) { 496 stateMgmtConsole.applicationWarn(StorageHelper.NULL_OR_UNDEFINED_KEY + ', try to use the type name as key'); 497 key = this.getTypeName(type); 498 if (key === undefined) { 499 throw new Error(PersistenceV2Impl.NOT_SUPPORT_TYPE_MESSAGE_); 500 } 501 } 502 503 if (key === PersistenceV2Impl.KEYS_ARR_) { 504 this.errorHelper(key, PersistError.Quota, `The key '${key}' cannot be used`); 505 return undefined; 506 } 507 return key; 508 } 509 510 private getAreaMode(areaMode: number | undefined): number { 511 if (typeof areaMode === 'undefined') { 512 return AreaMode.EL2; 513 } 514 else if (areaMode >= AreaMode.EL1 && areaMode <= AreaMode.EL5) { 515 return areaMode; 516 } else { 517 throw new Error(PersistenceV2Impl.NOT_SUPPORT_AREAMODE_MESSAGE_); 518 } 519 } 520 521 private getKeyMapType(key: string): MapType { 522 if (this.globalMap_.has(key)) { 523 return MapType.GLOBAL_MAP; 524 } else if (this.map_.has(key)) { 525 return MapType.MODULE_MAP; 526 } else { 527 return MapType.NOT_IN_MAP; 528 } 529 } 530 531 private getConnectDefaultValue<T extends object>(key: string, type: TypeConstructorWithArgs<T>, 532 defaultCreator?: StorageDefaultCreator<T>): T | undefined { 533 if (!PersistenceV2Impl.storage_) { 534 this.errorHelper(key, PersistError.Unknown, `The storage is null`); 535 return undefined; 536 } 537 538 if (typeof defaultCreator !== 'function') { 539 throw new Error(PersistenceV2Impl.INVALID_DEFAULT_VALUE); 540 } 541 542 const observedValue: T = defaultCreator(); 543 544 this.checkTypeByInstanceOf(key, type, observedValue); 545 546 if (this.isNotClassObject(observedValue)) { 547 throw new Error(PersistenceV2Impl.NOT_SUPPORT_TYPE_MESSAGE_); 548 } 549 550 return observedValue; 551 } 552 553 private getValueFromDisk<T extends object>(key: string, id: number, observedValue: T, 554 type: TypeConstructorWithArgs<T>, areaMode?: number | undefined): T | undefined { 555 let newObservedValue: T; 556 557 try { 558 const json: string = PersistenceV2Impl.storage_.get(key, areaMode); 559 560 // Adding ref for persistence 561 ObserveV2.getObserve().startRecordDependencies(this, id); 562 newObservedValue = JSONCoder.parseTo(observedValue, json) as T; 563 ObserveV2.getObserve().stopRecordDependencies(); 564 } catch (err) { 565 this.errorHelper(key, PersistError.Serialization, err); 566 } 567 568 this.checkTypeByInstanceOf(key, type, newObservedValue); 569 570 this.connectNewValue(key, newObservedValue, this.getTypeName(type), areaMode); 571 return newObservedValue; 572 } 573 574 private setValueToDisk<T extends object>(key: string, id: number, observedValue: T, 575 type: TypeConstructorWithArgs<T>, areaMode?: number | undefined): T | undefined { 576 ObserveV2.getObserve().startRecordDependencies(this, id); 577 // Map is a proxy object. When it is connected for the first time, map.has is used to add dependencies, 578 // and map.set is used to trigger writing to disk. 579 const keyType: MapType = this.getKeyMapType(key); 580 ObserveV2.getObserve().stopRecordDependencies(); 581 582 // Writing to persistence by ref 583 if (keyType === MapType.NOT_IN_MAP) { 584 if (typeof areaMode === 'number') { 585 this.connectNewValue(key, observedValue, this.getTypeName(type), areaMode); 586 } else { 587 this.connectNewValue(key, observedValue, this.getTypeName(type)); 588 } 589 } 590 return observedValue; 591 } 592 593 private writeAllChangedToFile(persistKeys: Array<number>): void { 594 for (let i = 0; i < persistKeys.length; ++i) { 595 const id: number = persistKeys[i]; 596 const key: string = this.idToKey_.get(id); 597 598 try { 599 const keyType: MapType = this.getKeyMapType(key); 600 601 if (keyType !== MapType.NOT_IN_MAP) { 602 const value: object = keyType === MapType.GLOBAL_MAP ? this.globalMap_.get(key) : this.map_.get(key); 603 604 ObserveV2.getObserve().startRecordDependencies(this, id); 605 const json: string = JSONCoder.stringify(value); 606 ObserveV2.getObserve().stopRecordDependencies(); 607 608 if (this.isOverSizeLimit(json)) { 609 stateMgmtConsole.applicationError( 610 `Cannot store the key '${key}'! The length of data must be less than ${PersistenceV2Impl.MAX_DATA_LENGTH_}`); 611 } else { 612 if (keyType === MapType.GLOBAL_MAP) { 613 const areaMode = this.globalMapAreaMode_.get(key); 614 PersistenceV2Impl.storage_.set(key, json, areaMode); 615 } else { 616 PersistenceV2Impl.storage_.set(key, json); 617 } 618 } 619 } 620 } catch (err) { 621 if (this.cb_ && typeof this.cb_ === 'function') { 622 this.cb_(key, PersistError.Serialization, err); 623 continue; 624 } 625 626 stateMgmtConsole.applicationError(`For '${key}' key: ` + err); 627 } 628 } 629 } 630 631 private isOverSizeLimit(json: string): boolean { 632 if (typeof json !== 'string') { 633 return false; 634 } 635 636 return json.length >= PersistenceV2Impl.MAX_DATA_LENGTH_; 637 } 638 639 private isNotClassObject(value: object): boolean { 640 return Array.isArray(value) || this.isNotSupportType(value); 641 } 642 643 private isNotSupportType(value: object): boolean { 644 for (let i = 0; i < PersistenceV2Impl.NOT_SUPPORT_TYPES_.length; ++i) { 645 if (value instanceof PersistenceV2Impl.NOT_SUPPORT_TYPES_[i]) { 646 return true; 647 } 648 } 649 return false; 650 } 651 652 private storeKeyToPersistenceV2(key: string, areaMode?: number | undefined): void { 653 try { 654 if (typeof areaMode === 'number') { 655 if (this.globalKeysArr_[areaMode].has(key)) { 656 return; 657 } 658 659 // Initializing the keys arr in memory 660 if (!this.globalKeysArr_[areaMode].size) { 661 this.globalKeysArr_[areaMode] = this.getKeysArrFromStorage(areaMode); 662 } 663 664 this.globalKeysArr_[areaMode].add(key); 665 666 // Updating the keys arr in disk 667 this.storeKeysArrToStorage(this.globalKeysArr_[areaMode], areaMode); 668 } else { 669 if (this.keysArr_.has(key)) { 670 return; 671 } 672 673 // Initializing the keys arr in memory 674 if (!this.keysArr_.size) { 675 this.keysArr_ = this.getKeysArrFromStorage(); 676 } 677 678 this.keysArr_.add(key); 679 680 // Updating the keys arr in disk 681 this.storeKeysArrToStorage(this.keysArr_); 682 } 683 } catch (err) { 684 this.errorHelper(key, PersistError.Unknown, `fail to store the key '${key}'`); 685 } 686 } 687 688 private removeForModulePath(key: string): void { 689 PersistenceV2Impl.storage_.delete(key); 690 // The first call for module path 691 if (!this.keysArr_.has(key)) { 692 this.keysArr_ = this.getKeysArrFromStorage(); 693 } 694 695 this.keysArr_.delete(key); 696 this.storeKeysArrToStorage(this.keysArr_); 697 } 698 699 private getRemoveFlagForGlobalPath(key: string): boolean { 700 let removeFlag = false; 701 // first call for global path 702 for (let i = 0; i < this.globalKeysArr_.length; i++) { 703 if (PersistenceV2Impl.storage_.has(key, i)) { 704 removeFlag = true; 705 PersistenceV2Impl.storage_.delete(key, i); 706 this.globalKeysArr_[i] = this.getKeysArrFromStorage(i); 707 this.globalKeysArr_[i].delete(key); 708 this.storeKeysArrToStorage(this.globalKeysArr_[i], i); 709 } 710 } 711 return removeFlag; 712 } 713 714 private removeFromPersistenceV2(key: string, areaMode: number | undefined): void { 715 try { 716 // check for global path 717 if (typeof areaMode === 'number') { 718 PersistenceV2Impl.storage_.delete(key, areaMode); 719 this.globalKeysArr_[areaMode].delete(key); 720 this.storeKeysArrToStorage(this.globalKeysArr_[areaMode], areaMode); 721 } else { 722 let removeFlag: boolean = false; 723 // check for module path 724 if (PersistenceV2Impl.storage_.has(key)) { 725 removeFlag = true; 726 this.removeForModulePath(key); 727 } else { 728 removeFlag = this.getRemoveFlagForGlobalPath(key); 729 } 730 if (!removeFlag) { 731 stateMgmtConsole.applicationWarn(PersistenceV2Impl.DELETE_NOT_EXIST_KEY + `keys:${key}`); 732 } 733 } 734 } catch (err) { 735 this.errorHelper(key, PersistError.Unknown, `fail to remove the key '${key}'`); 736 } 737 } 738 739 private getKeysArrFromStorage(areaMode?: number | undefined): Set<string> { 740 if (typeof areaMode === 'number' && !PersistenceV2Impl.storage_.has(PersistenceV2Impl.KEYS_ARR_, areaMode)) { 741 return this.globalKeysArr_[areaMode]; 742 } 743 else if (typeof areaMode === 'undefined' && !PersistenceV2Impl.storage_.has(PersistenceV2Impl.KEYS_ARR_)) { 744 return this.keysArr_; 745 } 746 const jsonKeysArr: string = PersistenceV2Impl.storage_.get(PersistenceV2Impl.KEYS_ARR_, areaMode); 747 return new Set(JSON.parse(jsonKeysArr)); 748 } 749 750 private storeKeysArrToStorage(keysArr: Set<string>, areaMode?: number | undefined): void { 751 PersistenceV2Impl.storage_.set(PersistenceV2Impl.KEYS_ARR_, JSON.stringify(Array.from(keysArr)), areaMode); 752 } 753 754 private errorHelper(key: string, reason: PersistError, message: string) { 755 if (this.cb_ && typeof this.cb_ === 'function') { 756 this.cb_(key, reason, message); 757 return; 758 } 759 760 if (!key) { 761 key = 'unknown'; 762 } 763 throw new Error(`For '${key}' key: ` + message); 764 } 765} 766