|
|
@@ -54,6 +54,14 @@ interface ImageCacheItem {
|
|
|
fileSize: number;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * 图片下载结果接口
|
|
|
+ */
|
|
|
+interface ImageDownloadResult {
|
|
|
+ fileName: string;
|
|
|
+ fd?: number;
|
|
|
+}
|
|
|
+
|
|
|
/**
|
|
|
* 包含图片文件描述符的卡片数据接口
|
|
|
*/
|
|
|
@@ -109,10 +117,10 @@ export class EnhancedFormUpdateService {
|
|
|
|
|
|
// 网络图片缓存
|
|
|
private imageCache: Map<string, ImageCacheItem> = new Map();
|
|
|
- private downloadingImages: Map<string, Promise<string | null>> = new Map();
|
|
|
+ private downloadingImages: Map<string, Promise<ImageDownloadResult | null>> = new Map();
|
|
|
private readonly imageCacheExpiry: number = 24 * 60 * 60 * 1000; // 24小时
|
|
|
private readonly maxCacheSize: number = 50; // 最多缓存50张图片
|
|
|
-
|
|
|
+
|
|
|
// 本地图片处理缓存
|
|
|
private localImageCache: Map<string, ProcessedImageInfo> = new Map();
|
|
|
private processingLocalImages: Map<string, Promise<ProcessedImageInfo | null>> = new Map();
|
|
|
@@ -141,7 +149,7 @@ export class EnhancedFormUpdateService {
|
|
|
*/
|
|
|
public setAppContext(context: Context): void {
|
|
|
this.appContext = context;
|
|
|
-
|
|
|
+
|
|
|
this.initializeImageCache();
|
|
|
}
|
|
|
|
|
|
@@ -151,10 +159,10 @@ export class EnhancedFormUpdateService {
|
|
|
public async updateAllForms(data: WidgetData): Promise<BatchUpdateStats> {
|
|
|
const batchStartTime = Date.now();
|
|
|
console.log("Heanup updateAllForms data:"+JSON.stringify(data))
|
|
|
-
|
|
|
+
|
|
|
// 预处理图片路径 - 转换本地文件URI为可用格式
|
|
|
const processedData = await this.preprocessImageData(data);
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
if (!this.appContext) {
|
|
|
hilog.error(0x0000, TAG, '❌ App context not set, cannot update forms');
|
|
|
@@ -164,53 +172,53 @@ export class EnhancedFormUpdateService {
|
|
|
// 防抖检查
|
|
|
const now = Date.now();
|
|
|
if (now - this.lastUpdateTime < this.updateDebounceDelay) {
|
|
|
-
|
|
|
+
|
|
|
return this.createEmptyStats();
|
|
|
}
|
|
|
|
|
|
// 防止并发更新
|
|
|
if (this.isUpdating) {
|
|
|
-
|
|
|
+
|
|
|
return this.createEmptyStats();
|
|
|
}
|
|
|
|
|
|
this.isUpdating = true;
|
|
|
this.lastUpdateTime = now;
|
|
|
|
|
|
-
|
|
|
+
|
|
|
|
|
|
// 获取所有持久化的 Form ID(使用原有方法)
|
|
|
const prefs = await this.preferencesUtil.getPreferences(this.appContext);
|
|
|
const formIds = await this.preferencesUtil.getFormIds(prefs);
|
|
|
|
|
|
if (formIds.length === 0) {
|
|
|
-
|
|
|
+
|
|
|
return this.createEmptyStats();
|
|
|
}
|
|
|
|
|
|
-
|
|
|
+
|
|
|
|
|
|
// 验证活跃卡片与持久化卡片的一致性
|
|
|
await this.validateActiveWidgets(formIds, prefs);
|
|
|
-
|
|
|
+
|
|
|
// 预先验证卡片ID的有效性,移除无效的ID
|
|
|
const validFormIds = await this.preValidateFormIds(formIds, prefs);
|
|
|
-
|
|
|
+
|
|
|
if (validFormIds.length === 0) {
|
|
|
hilog.warn(0x0000, TAG, '📋 No valid form IDs found after validation');
|
|
|
return this.createEmptyStats();
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
hilog.info(0x0000, TAG, `📋 Valid forms: ${validFormIds.length}/${formIds.length}`);
|
|
|
|
|
|
// 并行更新所有卡片
|
|
|
const updatePromises = validFormIds.map((formId, index) => {
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
return this.updateSingleFormWithRetry(formId, processedData, prefs);
|
|
|
});
|
|
|
|
|
|
-
|
|
|
+
|
|
|
|
|
|
// 等待所有更新完成
|
|
|
const results = await Promise.allSettled(updatePromises);
|
|
|
@@ -249,10 +257,10 @@ export class EnhancedFormUpdateService {
|
|
|
// }
|
|
|
|
|
|
await this.updateSingleForm(formId, data, prefs);
|
|
|
-
|
|
|
+
|
|
|
const updateTime = Date.now() - startTime;
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
return {
|
|
|
formId,
|
|
|
success: true,
|
|
|
@@ -263,13 +271,13 @@ export class EnhancedFormUpdateService {
|
|
|
} catch (error) {
|
|
|
lastError = error as Error;
|
|
|
const errorStr :string= error.toString();
|
|
|
-
|
|
|
+
|
|
|
// 如果是无效卡片ID错误,立即停止重试并清理
|
|
|
- if (errorStr.includes('form not exist') ||
|
|
|
- errorStr.includes('16501001') ||
|
|
|
- errorStr.includes('The ID of the form to be operated does not exist')) {
|
|
|
+ if (errorStr.includes('form not exist') ||
|
|
|
+ errorStr.includes('16501001') ||
|
|
|
+ errorStr.includes('The ID of the form to be operated does not exist')) {
|
|
|
hilog.warn(0x0000, TAG, `🗑️ [${formId}] Invalid form ID detected, cleaning up immediately`);
|
|
|
-
|
|
|
+
|
|
|
// 立即清理无效ID
|
|
|
try {
|
|
|
await this.preferencesUtil.removeFormId(prefs, formId);
|
|
|
@@ -278,7 +286,7 @@ export class EnhancedFormUpdateService {
|
|
|
} catch (cleanupError) {
|
|
|
hilog.error(0x0000, TAG, `❌ [${formId}] Failed to cleanup invalid form ID: ${cleanupError}`);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 立即返回失败结果,不再重试
|
|
|
const updateTime = Date.now() - startTime;
|
|
|
return {
|
|
|
@@ -289,14 +297,14 @@ export class EnhancedFormUpdateService {
|
|
|
retryCount: attempt
|
|
|
};
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const updateTime = Date.now() - startTime;
|
|
|
hilog.error(0x0000, TAG, `❌ [${formId}] All ${this.maxRetryCount + 1} attempts failed (${updateTime}ms total)`);
|
|
|
-
|
|
|
+
|
|
|
return {
|
|
|
formId,
|
|
|
success: false,
|
|
|
@@ -311,20 +319,20 @@ export class EnhancedFormUpdateService {
|
|
|
*/
|
|
|
private async updateSingleForm(formId: string, data: WidgetData, prefs: preferences.Preferences): Promise<void> {
|
|
|
try {
|
|
|
-
|
|
|
+
|
|
|
|
|
|
// 获取卡片的当前状态
|
|
|
const formState = await this.preferencesUtil.getFormState(prefs, formId);
|
|
|
const widgetSizeStr = (formState?.size as string) || 'medium';
|
|
|
const widgetSize: WidgetSize = widgetSizeStr as WidgetSize;
|
|
|
|
|
|
-
|
|
|
+
|
|
|
|
|
|
// 适配数据到卡片尺寸
|
|
|
const adaptedData = this.layoutManager.adaptDataForSize(data, widgetSize);
|
|
|
|
|
|
- // 处理网络图片
|
|
|
- await this.handleNetworkImage(adaptedData);
|
|
|
+ // 处理网络图片(传递原始数据以便设置文件描述符)
|
|
|
+ await this.handleNetworkImage(adaptedData, data);
|
|
|
|
|
|
// 转换为 FormattedWidgetData
|
|
|
const formattedData: FormattedWidgetData = {
|
|
|
@@ -347,7 +355,7 @@ export class EnhancedFormUpdateService {
|
|
|
imgName: adaptedData.imgName || ''
|
|
|
};
|
|
|
|
|
|
-
|
|
|
+
|
|
|
|
|
|
// 处理图片文件描述符(如果有本地图片)
|
|
|
let formData: formBindingData.FormBindingData;
|
|
|
@@ -373,12 +381,15 @@ export class EnhancedFormUpdateService {
|
|
|
imgName: formattedData.imgName,
|
|
|
formImages: {} as Record<string, number>
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
// 设置图片文件描述符
|
|
|
- if (dataWithImages.formImages) {
|
|
|
+ // 根据官方文档:imgName 必须和 formImages 中的 key 相同
|
|
|
+ if (dataWithImages.formImages && data.imageFileInfo.fileName) {
|
|
|
dataWithImages.formImages[data.imageFileInfo.fileName] = data.imageFileInfo.fd;
|
|
|
+ // 确保 imgName 与 formImages 的 key 一致
|
|
|
+ dataWithImages.imgName = data.imageFileInfo.fileName;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
formData = formBindingData.createFormBindingData(dataWithImages);
|
|
|
hilog.info(0x0000, TAG, `📷 [${formId}] Created form data with image fd: ${data.imageFileInfo.fd} for ${data.imageFileInfo.fileName}`);
|
|
|
} else {
|
|
|
@@ -388,7 +399,20 @@ export class EnhancedFormUpdateService {
|
|
|
// 更新卡片
|
|
|
await formProvider.updateForm(formId, formData);
|
|
|
|
|
|
-
|
|
|
+ // 根据官方文档:在formProvider.updateForm执行完毕后,关闭文件描述符
|
|
|
+ if (data.imageFileInfo && data.imageFileInfo.fd !== undefined && data.imageFileInfo.fd > 0) {
|
|
|
+ try {
|
|
|
+ // 修复:直接传递文件描述符数值
|
|
|
+ fileIo.closeSync(data.imageFileInfo.fd);
|
|
|
+ hilog.info(0x0000, TAG, `📷 [${formId}] Closed file descriptor: ${data.imageFileInfo.fd} for ${data.imageFileInfo.fileName}`);
|
|
|
+ // 清除文件描述符避免重复关闭
|
|
|
+ data.imageFileInfo.fd = -1;
|
|
|
+ } catch (closeError) {
|
|
|
+ hilog.warn(0x0000, TAG, `⚠️ [${formId}] Failed to close file descriptor: ${closeError}`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
|
|
|
// 保存增强状态信息
|
|
|
await this.saveEnhancedFormState(prefs, formId, widgetSizeStr, data);
|
|
|
@@ -407,7 +431,7 @@ export class EnhancedFormUpdateService {
|
|
|
// 修改点1: 显式声明processedData的类型为WidgetData
|
|
|
let processedData: WidgetData = data;
|
|
|
const coverImagePath = data.currentSong.coverImagePath;
|
|
|
-
|
|
|
+
|
|
|
if (!coverImagePath || coverImagePath.trim() === '') {
|
|
|
hilog.info(0x0000, TAG, '📷 No cover image path, skipping preprocessing');
|
|
|
return data;
|
|
|
@@ -418,14 +442,14 @@ export class EnhancedFormUpdateService {
|
|
|
// 处理本地文件URI
|
|
|
if (this.isLocalFileUri(coverImagePath)) {
|
|
|
hilog.info(0x0000, TAG, `📷 Detected local file URI: ${coverImagePath}`);
|
|
|
-
|
|
|
+
|
|
|
const processedImageInfo = await this.processLocalImageFile(coverImagePath);
|
|
|
-
|
|
|
+
|
|
|
if (processedImageInfo) {
|
|
|
// 创建新的数据对象,包含处理后的图片信息
|
|
|
processedData.currentSong.coverImagePath = processedImageInfo.memoryUri;
|
|
|
processedData.imageFileInfo = processedImageInfo as ImageFileInfo;
|
|
|
-
|
|
|
+
|
|
|
hilog.info(0x0000, TAG, `📷 Local image processed successfully: ${processedImageInfo.fileName} -> ${processedImageInfo.memoryUri}`);
|
|
|
return processedData;
|
|
|
} else {
|
|
|
@@ -444,7 +468,7 @@ export class EnhancedFormUpdateService {
|
|
|
hilog.info(0x0000, TAG, `📷 Using image path as-is: ${coverImagePath}`);
|
|
|
return data;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
} catch (error) {
|
|
|
hilog.error(0x0000, TAG, `❌ Error preprocessing image data: ${error}`);
|
|
|
return data; // 出错时返回原始数据
|
|
|
@@ -457,10 +481,10 @@ export class EnhancedFormUpdateService {
|
|
|
private async processLocalImageFile(fileUri: string): Promise<ProcessedImageInfo | null> {
|
|
|
try {
|
|
|
hilog.info(0x0000, TAG, `📷 Processing local image file: ${fileUri}`);
|
|
|
-
|
|
|
+
|
|
|
// 生成缓存键(基于URI和时间戳)
|
|
|
const cacheKey = this.generateLocalImageCacheKey(fileUri);
|
|
|
-
|
|
|
+
|
|
|
// 检查本地图片缓存
|
|
|
const cachedInfo = this.localImageCache.get(cacheKey);
|
|
|
if (cachedInfo) {
|
|
|
@@ -475,30 +499,30 @@ export class EnhancedFormUpdateService {
|
|
|
hilog.warn(0x0000, TAG, `📷 Cached file not found, will reprocess: ${cachedInfo.fileName}`);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 检查是否正在处理中
|
|
|
if (this.processingLocalImages.has(cacheKey)) {
|
|
|
hilog.info(0x0000, TAG, `📷 Image already being processed, waiting: ${fileUri}`);
|
|
|
return await this.processingLocalImages.get(cacheKey)!;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 创建处理Promise
|
|
|
const processingPromise = this.performLocalImageProcessing(fileUri, cacheKey);
|
|
|
this.processingLocalImages.set(cacheKey, processingPromise);
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
const result = await processingPromise;
|
|
|
return result;
|
|
|
} finally {
|
|
|
this.processingLocalImages.delete(cacheKey);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
} catch (error) {
|
|
|
hilog.error(0x0000, TAG, `❌ Failed to process local image: ${error}`);
|
|
|
return null;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 生成本地图片缓存键
|
|
|
*/
|
|
|
@@ -509,7 +533,7 @@ export class EnhancedFormUpdateService {
|
|
|
// 使用文件名作为缓存键(因为日志显示相同的文件被重复处理)
|
|
|
return `local_${fileName}`;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 执行本地图片处理
|
|
|
*/
|
|
|
@@ -522,7 +546,7 @@ export class EnhancedFormUpdateService {
|
|
|
// file://com.xgplayer.ttmusic.hm/data/storage/el2/base/haps/entry/files/xxx.jpg
|
|
|
// 转换为 /data/storage/el2/base/haps/entry/files/xxx.jpg
|
|
|
let realPath = fileUri.replace('file://', '');
|
|
|
-
|
|
|
+
|
|
|
// 如果路径包含应用包名,需要移除它并构建正确的绝对路径
|
|
|
if (realPath.startsWith('com.xgplayer.ttmusic.hm/')) {
|
|
|
// 移除包名前缀,获取相对路径
|
|
|
@@ -534,13 +558,13 @@ export class EnhancedFormUpdateService {
|
|
|
// 如果不是绝对路径,添加根路径
|
|
|
realPath = `/${realPath}`;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
hilog.info(0x0000, TAG, `📷 Final resolved path: ${realPath}`);
|
|
|
-
|
|
|
+
|
|
|
// 检查源文件是否存在
|
|
|
if (!fileIo.accessSync(realPath)) {
|
|
|
hilog.error(0x0000, TAG, `📷 Source image file does not exist: ${realPath}`);
|
|
|
-
|
|
|
+
|
|
|
// 尝试备用路径查找
|
|
|
const fileName = realPath.split('/').pop();
|
|
|
const alternativePaths = [
|
|
|
@@ -548,7 +572,7 @@ export class EnhancedFormUpdateService {
|
|
|
`${this.appContext!.cacheDir}/${fileName}`,
|
|
|
`${this.appContext!.tempDir}/${fileName}`
|
|
|
];
|
|
|
-
|
|
|
+
|
|
|
let foundPath: string | null = null;
|
|
|
for (const altPath of alternativePaths) {
|
|
|
if (fileIo.accessSync(altPath)) {
|
|
|
@@ -557,19 +581,19 @@ export class EnhancedFormUpdateService {
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (!foundPath) {
|
|
|
hilog.error(0x0000, TAG, `📷 File not found in any location: ${fileName}`);
|
|
|
return null;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
realPath = foundPath;
|
|
|
}
|
|
|
|
|
|
// 生成目标文件名(确保每次都不同,符合官方文档要求)
|
|
|
const fileExtension = this.getFileExtension(realPath) || 'jpg';
|
|
|
const fileName = `widget_local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.${fileExtension}`;
|
|
|
-
|
|
|
+
|
|
|
// 使用 FormExtensionAbility 的 tempDir(官方文档要求)
|
|
|
const formTempDir = this.appContext!.getApplicationContext().tempDir;
|
|
|
const tempFilePath = `${formTempDir}/${fileName}`;
|
|
|
@@ -584,20 +608,20 @@ export class EnhancedFormUpdateService {
|
|
|
const fd = file.fd;
|
|
|
|
|
|
const memoryUri = `memory://${fileName}`;
|
|
|
-
|
|
|
+
|
|
|
hilog.info(0x0000, TAG, `📷 Local image copied successfully: ${realPath} -> ${tempFilePath}, fd: ${fd}`);
|
|
|
-
|
|
|
+
|
|
|
const result: ProcessedImageInfo = {
|
|
|
fileName,
|
|
|
memoryUri,
|
|
|
fd,
|
|
|
sourceHash: cacheKey
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
// 添加到缓存
|
|
|
this.localImageCache.set(cacheKey, result);
|
|
|
hilog.info(0x0000, TAG, `📷 Added to local image cache: ${cacheKey}`);
|
|
|
-
|
|
|
+
|
|
|
return result;
|
|
|
|
|
|
} catch (error) {
|
|
|
@@ -618,28 +642,28 @@ export class EnhancedFormUpdateService {
|
|
|
*/
|
|
|
private cleanupLocalImageCache(): void {
|
|
|
const now = Date.now();
|
|
|
-
|
|
|
+
|
|
|
// 每5分钟清理一次
|
|
|
if (now - this.lastLocalCacheCleanup < 5 * 60 * 1000) {
|
|
|
return;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
this.lastLocalCacheCleanup = now;
|
|
|
-
|
|
|
+
|
|
|
// 清理过期的缓存项
|
|
|
const keysToDelete: string[] = [];
|
|
|
const tempDir = this.appContext!.getApplicationContext().tempDir;
|
|
|
-
|
|
|
+
|
|
|
this.localImageCache.forEach((info, key) => {
|
|
|
const tempFilePath = `${tempDir}/${info.fileName}`;
|
|
|
-
|
|
|
+
|
|
|
// 检查文件是否存在
|
|
|
if (!fileIo.accessSync(tempFilePath)) {
|
|
|
keysToDelete.push(key);
|
|
|
hilog.info(0x0000, TAG, `📷 Removing cache entry for missing file: ${info.fileName}`);
|
|
|
}
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 如果缓存过大,删除最旧的项
|
|
|
if (this.localImageCache.size > 20) {
|
|
|
const sortedEntries = Array.from(this.localImageCache.entries())
|
|
|
@@ -649,7 +673,7 @@ export class EnhancedFormUpdateService {
|
|
|
const timeB = parseInt(b[1].fileName.split('_')[2] || '0');
|
|
|
return timeA - timeB;
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
const entriesToDelete = sortedEntries.slice(0, this.localImageCache.size - 15);
|
|
|
entriesToDelete.forEach((entry) => {
|
|
|
const key = entry[0];
|
|
|
@@ -659,7 +683,7 @@ export class EnhancedFormUpdateService {
|
|
|
try {
|
|
|
const filePath = `${tempDir}/${info.fileName}`;
|
|
|
if (fileIo.accessSync(filePath)) {
|
|
|
- fileIo.closeSync(info.fd);
|
|
|
+ // 不要关闭可能已经无效的文件描述符,直接删除文件
|
|
|
fileIo.unlinkSync(filePath);
|
|
|
hilog.info(0x0000, TAG, `📷 Deleted old cached file: ${info.fileName}`);
|
|
|
}
|
|
|
@@ -668,17 +692,17 @@ export class EnhancedFormUpdateService {
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 删除缓存项
|
|
|
keysToDelete.forEach(key => {
|
|
|
this.localImageCache.delete(key);
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
if (keysToDelete.length > 0) {
|
|
|
hilog.info(0x0000, TAG, `📷 Cleaned up ${keysToDelete.length} local image cache entries`);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 获取文件扩展名
|
|
|
*/
|
|
|
@@ -693,7 +717,7 @@ export class EnhancedFormUpdateService {
|
|
|
/**
|
|
|
* 处理网络图片(缓存 + 下载)
|
|
|
*/
|
|
|
- private async handleNetworkImage(adaptedData: FormattedWidgetData): Promise<void> {
|
|
|
+ private async handleNetworkImage(adaptedData: FormattedWidgetData, originalData: WidgetData): Promise<void> {
|
|
|
if (!adaptedData.coverImage) {
|
|
|
return;
|
|
|
}
|
|
|
@@ -703,7 +727,7 @@ export class EnhancedFormUpdateService {
|
|
|
if (adaptedData.coverImage.startsWith('memory://')) {
|
|
|
const fileName = adaptedData.coverImage.replace('memory://', '');
|
|
|
const tempFilePath = `${this.appContext!.getApplicationContext().tempDir}/${fileName}`;
|
|
|
-
|
|
|
+
|
|
|
// 验证文件是否存在
|
|
|
if (fileIo.accessSync(tempFilePath)) {
|
|
|
hilog.info(0x0000, TAG, `📷 Using existing memory URI: ${adaptedData.coverImage}`);
|
|
|
@@ -722,28 +746,58 @@ export class EnhancedFormUpdateService {
|
|
|
}
|
|
|
|
|
|
const imageUrl = adaptedData.coverImage;
|
|
|
-
|
|
|
+
|
|
|
|
|
|
// 检查缓存
|
|
|
const cachedItem = this.imageCache.get(imageUrl);
|
|
|
if (cachedItem && cachedItem.expiry > Date.now()) {
|
|
|
-
|
|
|
- adaptedData.coverImage = `memory://${cachedItem.fileName}`;
|
|
|
- adaptedData.imgName = cachedItem.fileName;
|
|
|
- return;
|
|
|
+
|
|
|
+ // 尝试打开缓存的文件获取文件描述符
|
|
|
+ try {
|
|
|
+ const cachedFilePath = `${this.appContext!.getApplicationContext().tempDir}/${cachedItem.fileName}`;
|
|
|
+ const fileForWidget = fileIo.openSync(cachedFilePath, fileIo.OpenMode.READ_ONLY);
|
|
|
+ const fd = fileForWidget.fd;
|
|
|
+
|
|
|
+ adaptedData.coverImage = `memory://${cachedItem.fileName}`;
|
|
|
+ adaptedData.imgName = cachedItem.fileName;
|
|
|
+
|
|
|
+ // 设置文件描述符信息到原始数据
|
|
|
+ originalData.imageFileInfo = {
|
|
|
+ fileName: cachedItem.fileName,
|
|
|
+ memoryUri: `memory://${cachedItem.fileName}`,
|
|
|
+ fd: fd
|
|
|
+ };
|
|
|
+
|
|
|
+ hilog.info(0x0000, TAG, `📷 Using cached image with fd: ${fd}, fileName: ${cachedItem.fileName}`);
|
|
|
+ // 注意:不要在这里关闭文件,文件描述符需要传递给卡片
|
|
|
+ return;
|
|
|
+ } catch (fdError) {
|
|
|
+ hilog.warn(0x0000, TAG, `📷 Failed to open cached file for fd: ${fdError}, will redownload`);
|
|
|
+ // 如果无法打开文件,继续下载新的
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 下载图片
|
|
|
- const fileName = await this.downloadAndCacheImage(imageUrl);
|
|
|
- if (fileName) {
|
|
|
- adaptedData.coverImage = `memory://${fileName}`;
|
|
|
- adaptedData.imgName = fileName;
|
|
|
-
|
|
|
+ const downloadResult = await this.downloadAndCacheImage(imageUrl);
|
|
|
+ if (downloadResult) {
|
|
|
+ adaptedData.coverImage = `memory://${downloadResult.fileName}`;
|
|
|
+ adaptedData.imgName = downloadResult.fileName;
|
|
|
+
|
|
|
+ // 设置文件描述符信息到原始数据(memory协议需要)
|
|
|
+ if (downloadResult.fd !== undefined) {
|
|
|
+ originalData.imageFileInfo = {
|
|
|
+ fileName: downloadResult.fileName,
|
|
|
+ memoryUri: `memory://${downloadResult.fileName}`,
|
|
|
+ fd: downloadResult.fd
|
|
|
+ };
|
|
|
+ hilog.info(0x0000, TAG, `📷 Network image downloaded with fd: ${downloadResult.fd}, fileName: ${downloadResult.fileName}`);
|
|
|
+ }
|
|
|
+
|
|
|
} else {
|
|
|
// 下载失败,清除图片
|
|
|
adaptedData.coverImage = '';
|
|
|
adaptedData.imgName = '';
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
@@ -756,10 +810,10 @@ export class EnhancedFormUpdateService {
|
|
|
/**
|
|
|
* 下载并缓存网络图片
|
|
|
*/
|
|
|
- private async downloadAndCacheImage(imageUrl: string): Promise<string | null> {
|
|
|
+ private async downloadAndCacheImage(imageUrl: string): Promise<ImageDownloadResult | null> {
|
|
|
// 检查是否正在下载
|
|
|
if (this.downloadingImages.has(imageUrl)) {
|
|
|
-
|
|
|
+
|
|
|
return await this.downloadingImages.get(imageUrl)!;
|
|
|
}
|
|
|
|
|
|
@@ -778,10 +832,10 @@ export class EnhancedFormUpdateService {
|
|
|
/**
|
|
|
* 执行图片下载
|
|
|
*/
|
|
|
- private async performImageDownload(imageUrl: string): Promise<string | null> {
|
|
|
+ private async performImageDownload(imageUrl: string): Promise<ImageDownloadResult | null> {
|
|
|
try {
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
const httpRequest = http.createHttp();
|
|
|
const response = await httpRequest.request(imageUrl, {
|
|
|
method: http.RequestMethod.GET,
|
|
|
@@ -795,15 +849,28 @@ export class EnhancedFormUpdateService {
|
|
|
|
|
|
// 生成文件名
|
|
|
const fileName = `widget_cover_${Date.now()}_${Math.random().toString(36).substr(2, 9)}.jpg`;
|
|
|
-
|
|
|
- // 保存到缓存目录
|
|
|
- const filePath = `${this.appContext!.cacheDir}/${fileName}`;
|
|
|
- const file = fileIo.openSync(filePath, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
|
|
|
+
|
|
|
+ // 保存到临时目录(卡片要求使用tempDir)
|
|
|
+ const filePath = `${this.appContext!.getApplicationContext().tempDir}/${fileName}`;
|
|
|
|
|
|
// 修改点2: 显式声明buffer的类型为ArrayBuffer
|
|
|
const buffer: ArrayBuffer = response.result as ArrayBuffer;
|
|
|
- fileIo.writeSync(file.fd, new Uint8Array(buffer));
|
|
|
- fileIo.closeSync(file);
|
|
|
+
|
|
|
+ // 根据官方文档:先创建文件并写入数据
|
|
|
+ const tempFile = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
|
|
|
+ try {
|
|
|
+ // 使用 write 方法写入数据
|
|
|
+ const writeLen = await fileIo.write(tempFile.fd, buffer);
|
|
|
+ hilog.info(0x0000, TAG, `📷 Write data to file succeed and size is: ${writeLen}`);
|
|
|
+ } catch (writeError) {
|
|
|
+ hilog.error(0x0000, TAG, `❌ Write data to file failed: ${writeError}`);
|
|
|
+ fileIo.closeSync(tempFile);
|
|
|
+ httpRequest.destroy();
|
|
|
+ return null;
|
|
|
+ } finally {
|
|
|
+ // 关闭临时文件句柄
|
|
|
+ fileIo.closeSync(tempFile);
|
|
|
+ }
|
|
|
|
|
|
// 添加到缓存
|
|
|
const cacheItem: ImageCacheItem = {
|
|
|
@@ -812,14 +879,29 @@ export class EnhancedFormUpdateService {
|
|
|
expiry: Date.now() + this.imageCacheExpiry,
|
|
|
fileSize: buffer.byteLength
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
this.imageCache.set(imageUrl, cacheItem);
|
|
|
this.cleanupImageCache();
|
|
|
|
|
|
-
|
|
|
-
|
|
|
- httpRequest.destroy();
|
|
|
- return fileName;
|
|
|
+ // 根据官方文档:重新打开文件获取文件描述符用于卡片显示
|
|
|
+ try {
|
|
|
+ const fileForWidget = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
|
|
|
+ const fd = fileForWidget.fd;
|
|
|
+
|
|
|
+ hilog.info(0x0000, TAG, `📷 Image downloaded and opened for widget: ${fileName}, fd: ${fd}`);
|
|
|
+
|
|
|
+ // 注意:根据官方文档,不要在这里关闭文件,文件描述符需要传递给卡片
|
|
|
+ // fileIo.closeSync(fileForWidget); // 等待卡片更新完成后再关闭
|
|
|
+
|
|
|
+ httpRequest.destroy();
|
|
|
+ const result: ImageDownloadResult = { fileName: fileName, fd: fd };
|
|
|
+ return result;
|
|
|
+ } catch (fdError) {
|
|
|
+ hilog.error(0x0000, TAG, `❌ Failed to open file for widget fd: ${fdError}`);
|
|
|
+ httpRequest.destroy();
|
|
|
+ const result: ImageDownloadResult = { fileName: fileName };
|
|
|
+ return result; // 返回不包含fd的结果
|
|
|
+ }
|
|
|
|
|
|
} catch (error) {
|
|
|
hilog.error(0x0000, TAG, `❌ Failed to download image: ${error}`);
|
|
|
@@ -840,7 +922,7 @@ export class EnhancedFormUpdateService {
|
|
|
.sort((a, b) => a[1].downloadTime - b[1].downloadTime);
|
|
|
|
|
|
const itemsToDelete = sortedItems.slice(0, this.imageCache.size - this.maxCacheSize);
|
|
|
-
|
|
|
+
|
|
|
for (let i = 0; i < itemsToDelete.length; i++) {
|
|
|
try {
|
|
|
const entry = itemsToDelete[i];
|
|
|
@@ -851,9 +933,9 @@ export class EnhancedFormUpdateService {
|
|
|
fileIo.unlinkSync(filePath);
|
|
|
}
|
|
|
this.imageCache.delete(url);
|
|
|
-
|
|
|
+
|
|
|
} catch (error) {
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -866,21 +948,21 @@ export class EnhancedFormUpdateService {
|
|
|
try {
|
|
|
const cacheDir = this.appContext!.cacheDir;
|
|
|
const files = fileIo.listFileSync(cacheDir);
|
|
|
-
|
|
|
+
|
|
|
for (const file of files) {
|
|
|
if (file.startsWith('widget_cover_')) {
|
|
|
const filePath = `${cacheDir}/${file}`;
|
|
|
const stat = fileIo.statSync(filePath);
|
|
|
-
|
|
|
+
|
|
|
// 删除超过24小时的文件
|
|
|
if (Date.now() - stat.mtime > this.imageCacheExpiry) {
|
|
|
fileIo.unlinkSync(filePath);
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
} catch (error) {
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -890,26 +972,26 @@ export class EnhancedFormUpdateService {
|
|
|
private async preValidateFormIds(formIds: string[], prefs: preferences.Preferences): Promise<string[]> {
|
|
|
const validFormIds: string[] = [];
|
|
|
const invalidFormIds: string[] = [];
|
|
|
-
|
|
|
+
|
|
|
hilog.info(0x0000, TAG, `🔍 Pre-validating ${formIds.length} form IDs...`);
|
|
|
-
|
|
|
+
|
|
|
for (const formId of formIds) {
|
|
|
try {
|
|
|
// 尝试使用一个简单的测试数据来验证卡片ID
|
|
|
const testData = formBindingData.createFormBindingData({
|
|
|
test: 'validation'
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
// 尝试更新卡片,如果失败说明卡片ID无效
|
|
|
await formProvider.updateForm(formId, testData);
|
|
|
validFormIds.push(formId);
|
|
|
hilog.debug(0x0000, TAG, `✅ [${formId}] Valid form ID`);
|
|
|
-
|
|
|
+
|
|
|
} catch (error) {
|
|
|
const errorStr :string= error.toString();
|
|
|
- if (errorStr.includes('form not exist') ||
|
|
|
- errorStr.includes('16501001') ||
|
|
|
- errorStr.includes('The ID of the form to be operated does not exist')) {
|
|
|
+ if (errorStr.includes('form not exist') ||
|
|
|
+ errorStr.includes('16501001') ||
|
|
|
+ errorStr.includes('The ID of the form to be operated does not exist')) {
|
|
|
hilog.warn(0x0000, TAG, `❌ [${formId}] Invalid form ID detected during validation`);
|
|
|
invalidFormIds.push(formId);
|
|
|
} else {
|
|
|
@@ -919,7 +1001,7 @@ export class EnhancedFormUpdateService {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// 清理无效的卡片ID
|
|
|
if (invalidFormIds.length > 0) {
|
|
|
hilog.info(0x0000, TAG, `🗑️ Cleaning up ${invalidFormIds.length} invalid form IDs`);
|
|
|
@@ -933,7 +1015,7 @@ export class EnhancedFormUpdateService {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
hilog.info(0x0000, TAG, `🔍 Validation complete: ${validFormIds.length} valid, ${invalidFormIds.length} invalid`);
|
|
|
return validFormIds;
|
|
|
}
|
|
|
@@ -945,23 +1027,23 @@ export class EnhancedFormUpdateService {
|
|
|
const activeWidgets = this.globalWidgetManager.getActiveWidgets();
|
|
|
const activeFormIds = Array.from(activeWidgets.keys());
|
|
|
|
|
|
-
|
|
|
+
|
|
|
|
|
|
// 检查持久化但不活跃的卡片
|
|
|
const persistentOnlyIds = formIds.filter(id => !activeWidgets.has(id));
|
|
|
if (persistentOnlyIds.length > 0) {
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
|
|
|
// 检查活跃但未持久化的卡片
|
|
|
const activeOnlyIds = activeFormIds.filter(id => !formIds.includes(id));
|
|
|
if (activeOnlyIds.length > 0) {
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
// 将活跃的卡片添加到持久化存储
|
|
|
for (const formId of activeOnlyIds) {
|
|
|
await this.preferencesUtil.addFormId(prefs, formId);
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -983,9 +1065,9 @@ export class EnhancedFormUpdateService {
|
|
|
};
|
|
|
|
|
|
await this.preferencesUtil.saveFormState(prefs, formId, stateData);
|
|
|
-
|
|
|
+
|
|
|
} catch (error) {
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1000,13 +1082,13 @@ export class EnhancedFormUpdateService {
|
|
|
|
|
|
results.forEach((result, index) => {
|
|
|
const formId = formIds[index];
|
|
|
-
|
|
|
+
|
|
|
if (result.status === 'fulfilled') {
|
|
|
const updateResult = result.value;
|
|
|
if (updateResult.success) {
|
|
|
successCount++;
|
|
|
totalUpdateTime += updateResult.updateTime;
|
|
|
-
|
|
|
+
|
|
|
} else {
|
|
|
failedCount++;
|
|
|
hilog.error(0x0000, TAG, `❌ Form ${index + 1}/${formIds.length} (${formId}) failed: ${updateResult.error?.message}`);
|
|
|
@@ -1040,29 +1122,29 @@ export class EnhancedFormUpdateService {
|
|
|
const error = result.value.error!;
|
|
|
const errorStr = error.toString();
|
|
|
const formId = formIds[index];
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- if (errorStr.includes('form not exist') ||
|
|
|
- errorStr.includes('16501001') ||
|
|
|
- errorStr.includes('FormProvider') ||
|
|
|
- errorStr.includes('invalid form')) {
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ if (errorStr.includes('form not exist') ||
|
|
|
+ errorStr.includes('16501001') ||
|
|
|
+ errorStr.includes('FormProvider') ||
|
|
|
+ errorStr.includes('invalid form')) {
|
|
|
+
|
|
|
invalidFormIds.push(formId);
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
if (invalidFormIds.length > 0) {
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
for (const invalidFormId of invalidFormIds) {
|
|
|
await this.preferencesUtil.removeFormId(prefs, invalidFormId);
|
|
|
this.globalWidgetManager.unregisterWidget(invalidFormId);
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
}
|
|
|
} catch (error) {
|
|
|
hilog.error(0x0000, TAG, `❌ Failed to cleanup invalid forms: ${error}`);
|
|
|
@@ -1093,9 +1175,9 @@ export class EnhancedFormUpdateService {
|
|
|
failedUpdates: 0,
|
|
|
averageUpdateTime: 0
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
/**
|
|
|
* 清理所有缓存
|
|
|
*/
|
|
|
@@ -1105,7 +1187,7 @@ export class EnhancedFormUpdateService {
|
|
|
try {
|
|
|
const tempFilePath = `${this.appContext!.getApplicationContext().tempDir}/${info.fileName}`;
|
|
|
if (fileIo.accessSync(tempFilePath)) {
|
|
|
- fileIo.closeSync(info.fd);
|
|
|
+ // 不要关闭可能已经无效的文件描述符,直接删除文件
|
|
|
fileIo.unlinkSync(tempFilePath);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
@@ -1114,11 +1196,11 @@ export class EnhancedFormUpdateService {
|
|
|
});
|
|
|
this.localImageCache.clear();
|
|
|
this.processingLocalImages.clear();
|
|
|
-
|
|
|
+
|
|
|
// 清理网络图片缓存
|
|
|
this.imageCache.clear();
|
|
|
this.downloadingImages.clear();
|
|
|
-
|
|
|
+
|
|
|
hilog.info(0x0000, TAG, '🧹 All caches cleared');
|
|
|
}
|
|
|
|