* Copyright (c) Huawei Device Co., Ltd. 2024-2025. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { LogDomain, LogHelper, SingleContext } from '@ohos/basicutils';
import { localEventManager } from '@ohos/frameworkwrapper';
import { DockItemInfo } from '../bean/DockItemInfo';
import GridLayoutItemInfo from '../bean/GridLayoutItemInfo';
import { CommonConstants } from '../constants/CommonConstants';
import { EventConstants } from '../constants/EventConstants';
import GridLayoutUtil from '../utils/GridLayoutUtil';
import { LaunchLayoutCacheManager } from './layout/LaunchLayoutCacheManager';
import { ResidentLayoutCacheMgr } from '../TsIndex';
const TAG = 'Cache2RdbHelper';
const log: LogHelper = LogHelper.getLogHelper(LogDomain.HOME, TAG);
export class Cache2RdbHelper {
private static _instance: Cache2RdbHelper | null = null;
private eventMap: Map<string, Cache2RdbEvent> = new Map();
public static getInstance(): Cache2RdbHelper {
if (!Cache2RdbHelper._instance) {
Cache2RdbHelper._instance = new Cache2RdbHelper();
}
return Cache2RdbHelper._instance;
}
public createEvent(eventId: string): boolean {
if (this.eventMap.has(eventId)) {
log.showInfo(`event:${eventId} exist`);
return false;
}
this.eventMap.set(eventId, new Cache2RdbEvent(eventId));
log.showInfo(`create new Cache2RdbEvent ${eventId}`);
return true;
}
public addItem(eventId: string, item: GridLayoutItemInfo): boolean {
const event = this.eventMap.get(eventId);
if (!event) {
return false;
}
event.addItem(item);
return true;
}
public setChangedPage(eventId: string, changedPageSet: Set<number>): boolean {
const event = this.eventMap.get(eventId);
if (!event) {
return false;
}
event.setChangedPage(changedPageSet);
return true;
}
public deleteItem(eventId: string, item: GridLayoutItemInfo, msg: string): boolean {
const event = this.eventMap.get(eventId);
if (!event) {
return false;
}
return event.deleteItem(item, msg);
}
public addLock(eventId: string, msg: string): (() => void) | undefined {
const event = this.eventMap.get(eventId);
if (!event) {
return undefined;
}
return event.addLock(msg);
}
public async executeEvent(eventId: string, isOuter?: boolean, ctx?: SingleContext): Promise<boolean> {
const event = this.eventMap.get(eventId);
if (!event) {
return false;
}
let res: boolean = false;
try {
res = await event.execute(isOuter, ctx);
} catch (error) {
log.error('executeEvent error', error);
}
event.release();
this.eventMap.delete(eventId);
return res;
}
}
class Cache2RdbEvent {
private itemSet: Set<string> = new Set();
private folderOrStackTypeId = [CommonConstants.TYPE_FOLDER, CommonConstants.TYPE_FORM_STACK];
private lockList: Map<Promise<void>, string> = new Map();
private isExecuted: boolean = false;
private changedPage: Set<number> = new Set();
constructor(eventName: string) {
this._unique = eventName;
}
private _unique: string = '';
public get unique(): string {
return this._unique;
}
public addItem(item: GridLayoutItemInfo): void {
const uniqueKey: string = GridLayoutUtil.generateUniqueKey(item);
if (!uniqueKey) {
log.showWarn('add invalid item: %{public}s', item.keyName);
return;
}
this.itemSet.add(uniqueKey);
}
public setChangedPage(changedPage: Set<number>): void {
changedPage.forEach(item => this.changedPage.add(item));
}
public deleteItem(item: GridLayoutItemInfo, msg: string): boolean {
const uniqueKey: string = GridLayoutUtil.generateUniqueKey(item);
if (uniqueKey) {
log.showWarn(`delete item:${item.keyName} by:${msg}`);
return this.itemSet.delete(uniqueKey);
}
return false;
}
public addLock(msg: string): (() => void) | undefined {
if (this.isExecuted) {
log.showInfo(`can't add lock to executed event; event:${this.unique} lock:${msg}`);
return undefined;
}
let lockResolve: (() => void) | undefined;
const lock = new Promise<void>((resolve) => {
lockResolve = resolve;
});
this.lockList.set(lock, msg);
log.showInfo(`add lock success; event:${this.unique} lock:${msg} size:${this.lockList.size}`);
return lockResolve;
}
public async execute(isOuter?: boolean, ctx?: SingleContext): Promise<boolean> {
if (this.isExecuted) {
log.showInfo(`can't execute again. event:${this.unique}`);
return false;
}
log.showInfo(`event execute; event:${this.unique}`);
this.isExecuted = true;
await this.waitForUnLock();
const cacheMgr = LaunchLayoutCacheManager.getInstance();
const beforeCount: number = cacheMgr.selectPageCount();
log.showInfo(`before excute update rdb after drag, page count is ${beforeCount}`);
const extraUpdateItems: GridLayoutItemInfo[] = cacheMgr.deleteBlankPageByCache(this.changedPage, isOuter);
log.showInfo(`need updateItems's length is ${extraUpdateItems.length}`);
extraUpdateItems.forEach((item: GridLayoutItemInfo) => this.addItem(item));
const afterCount: number = cacheMgr.selectPageCount();
log.showInfo(`after excute update rdb after drag, page count is ${afterCount}`);
if (extraUpdateItems.length > 0 || beforeCount !== afterCount) {
log.showInfo(`deleted blankPage; event:${this.unique}`);
localEventManager.sendLocalEventSticky(EventConstants.EVENT_REQUEST_PAGEDESK_LIGHT_REFRESH, null);
}
if (this.itemSet.size === 0) {
log.showWarn(`execute interrupt! no changed cacheItem; event:${this.unique}`);
return false;
}
const updatedCacheItemList: GridLayoutItemInfo[] = [];
const desktopGridItemList: GridLayoutItemInfo[] =
cacheMgr.getAllGridLayoutItemList(`Cache2RdbEvent:${this.unique}`);
this.updateCacheList(desktopGridItemList, updatedCacheItemList);
log.showInfo(`traversal desktop finish; event:${this.unique}`);
const dockGridItemList: GridLayoutItemInfo[] = [];
const residentList: DockItemInfo[] = ResidentLayoutCacheMgr.getInstance().getAllDockItems();
residentList.forEach((item: DockItemInfo) => dockGridItemList.push(GridLayoutUtil.dockItemToGridLayout(item)));
this.updateCacheList(dockGridItemList, updatedCacheItemList);
log.showInfo(`traversal dock finish; event:${this.unique}`);
this.printChangedItem(updatedCacheItemList);
cacheMgr.updateInfoToRdb(updatedCacheItemList, this.unique, ctx);
return true;
}
public release(): void {
if (this.itemSet.size !== 0) {
this.itemSet.forEach((key: string) => {
log.showWarn('cannot update item: %{public}s', key);
});
}
this.itemSet.clear();
this.lockList.clear();
}
private async waitForUnLock(): Promise<void> {
if (this.lockList.size === 0) {
log.showInfo(`lock list is empty; event:${this.unique}`);
return;
}
log.showInfo(`start waitForLock event:${this.unique} lockList:${Array.from(this.lockList.values()).toString()}`);
const overTimeHandler: Promise<void> = new Promise((resolve, reject) => {
setTimeout(() => {
reject();
}, 500);
});
const lockList = Array.from(this.lockList.keys());
const waitLock = Promise.all(lockList.map(lock => {
return lock.then(
() => log.showInfo(`lock release lock:${this.lockList.get(lock)}`),
() => log.showInfo(`lock reject lock:${this.lockList.get(lock)}`)
).finally(() => this.lockList.delete(lock));
}));
await Promise.race([overTimeHandler, waitLock])
.then(() => log.showWarn(`all lock release; event:${this.unique}`))
.catch(() => log.showWarn(`some lock overTime :${Array.from(this.lockList.values()).toString()}`));
this.lockList.clear();
log.showInfo(`event:${this.unique} all lock release or overTime`);
return;
}
private printChangedItem(itemList: GridLayoutItemInfo[], limit: number = 10): void {
let count = 0;
while (count < itemList.length) {
let arr = itemList.slice(count, count + limit);
count += limit;
log.showInfo(`changed cacheItems: ${
arr.map(item =>`${item.bundleName}_${item.row}_${item.column}_${item.page}_${item.container}`).toString()
}`);
}
}
private updateCacheList(layoutInfo: GridLayoutItemInfo[], updatedCacheItemList: GridLayoutItemInfo[]): void {
layoutInfo.forEach((item: GridLayoutItemInfo) => {
const uniqueKey = GridLayoutUtil.generateUniqueKey(item);
if (this.itemSet.has(uniqueKey)) {
updatedCacheItemList.push(item);
this.itemSet.delete(uniqueKey);
}
if (item.typeId !== undefined && this.folderOrStackTypeId.includes(item.typeId)) {
this.updateCacheList(item.layoutInfo?.flat() ?? [], updatedCacheItemList);
}
});
}
}