import { photoAccessHelper } from '@kit.MediaLibraryKit'; import { fileIo } from '@kit.CoreFileKit'; import { FileUtil } from '@pura/harmony-utils'; import Logger from './Logger'; import { Context } from '@kit.AbilityKit'; import { fileUri } from '@kit.CoreFileKit'; const TAG = 'ImagePickerUtil'; /** * 图片选择工具类 */ export class ImagePickerUtil { /** * 选择图片并返回复制到应用私有目录的路径 * @param context 应用上下文 * @param maxSelectCount 最大选择数量,默认1张 * @returns 返回选择的图片路径数组 */ static async selectImages(context: Context, maxSelectCount: number = 1): Promise { try { Logger.info(TAG, '开始选择图片'); const photoSelectOptions: photoAccessHelper.PhotoSelectOptions = { MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE, maxSelectNumber: maxSelectCount }; const photoViewPicker = new photoAccessHelper.PhotoViewPicker(); const photoSelectResult = await photoViewPicker.select(photoSelectOptions); Logger.info(TAG, `选择了 ${photoSelectResult.photoUris.length} 张图片`); if (photoSelectResult.photoUris.length === 0) { return []; } // 将选中的图片复制到应用私有目录 const copiedPaths: string[] = []; for (const uri of photoSelectResult.photoUris) { try { const copiedPath = await ImagePickerUtil.copyImageToAppDir(context, uri); if (copiedPath) { copiedPaths.push(copiedPath); } } catch (error) { Logger.error(TAG, `复制图片失败: ${(error as Error).message}`); } } return copiedPaths; } catch (error) { Logger.error(TAG, `选择图片失败: ${(error as Error).message}`); return []; } } /** * 选择单张图片 * @param context 应用上下文 * @returns 返回选择的图片路径,如果取消选择返回null */ static async selectSingleImage(context: Context): Promise { const paths = await ImagePickerUtil.selectImages(context, 1); return paths.length > 0 ? paths[0] : null; } /** * 将图片复制到应用私有目录 * @param context 应用上下文 * @param imageUri 图片URI * @param type 图片类型,默认为playlist,可选webdav * @returns 返回复制后的图片路径 */ private static async copyImageToAppDir(context: Context, imageUri: string, type: string = 'playlist'): Promise { try { // 生成唯一文件名 const timestamp = Date.now(); const randomSuffix = Math.random().toString(36).substring(2, 8); const fileName = `${type}_cover_${timestamp}_${randomSuffix}.jpg`; // 目标路径 const targetDir = context.filesDir + FileUtil.separator + `${type}_covers`; const targetPath = targetDir + FileUtil.separator + fileName; // 确保目录存在 try { fileIo.mkdirSync(targetDir); } catch (error) { // 目录可能已存在,忽略错误 } // 打开源文件和目标文件 const sourceFile = fileIo.openSync(imageUri, fileIo.OpenMode.READ_ONLY); const targetFile = fileIo.openSync(targetPath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE); // 复制文件内容 const buffer = new ArrayBuffer(4096); let readSize = 0; do { readSize = fileIo.readSync(sourceFile.fd, buffer); if (readSize > 0) { fileIo.writeSync(targetFile.fd, buffer, { length: readSize }); } } while (readSize > 0); // 关闭文件 fileIo.closeSync(sourceFile.fd); fileIo.closeSync(targetFile.fd); // 转换为URI格式(关键步骤!) const uriPath = fileUri.getUriFromPath(targetPath); Logger.info(TAG, `图片复制成功: ${uriPath}`); return uriPath; } catch (error) { Logger.error(TAG, `复制图片失败: ${(error as Error).message}`); throw new Error(`复制图片失败: ${(error as Error).message}`); } } /** * 选择单张图片(WebDAV账户封面) * @param context 应用上下文 * @returns 返回选择的图片路径,如果取消选择返回null */ static async selectSingleWebDavCover(context: Context): Promise { try { Logger.info(TAG, '开始选择WebDAV账户封面'); const photoSelectOptions: photoAccessHelper.PhotoSelectOptions = { MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE, maxSelectNumber: 1 }; const photoViewPicker = new photoAccessHelper.PhotoViewPicker(); const photoSelectResult = await photoViewPicker.select(photoSelectOptions); Logger.info(TAG, `选择了 ${photoSelectResult.photoUris.length} 张图片`); if (photoSelectResult.photoUris.length === 0) { return null; } // 将选中的图片复制到应用私有目录 const uri = photoSelectResult.photoUris[0]; const copiedPath = await ImagePickerUtil.copyImageToAppDir(context, uri, 'webdav'); return copiedPath; } catch (error) { Logger.error(TAG, `选择WebDAV账户封面失败: ${(error as Error).message}`); return null; } } /** * 删除歌单封面图片 * @param imagePath 图片路径 */ static deleteImage(imagePath: string): void { try { if (imagePath && ImagePickerUtil.checkFileExists(imagePath)) { fileIo.unlinkSync(imagePath); Logger.info(TAG, `删除图片成功: ${imagePath}`); } } catch (error) { Logger.error(TAG, `删除图片失败: ${(error as Error).message}`); } } /** * 检查文件是否存在 * @param filePath 文件路径 * @returns 是否存在 */ private static checkFileExists(filePath: string): boolean { try { fileIo.accessSync(filePath); return true; } catch (error) { return false; } } /** * 检查图片文件是否存在 * @param imagePath 图片路径 * @returns 是否存在 */ static imageExists(imagePath: string): boolean { if (!imagePath) { Logger.info(TAG, '图片路径为空'); return false; } const exists = ImagePickerUtil.checkFileExists(imagePath); Logger.info(TAG, `检查图片是否存在: ${imagePath}, 结果: ${exists}`); return exists; } /** * 获取歌单封面目录 * @param context 应用上下文 * @returns 歌单封面目录路径 */ static getPlaylistCoverDir(context: Context): string { return context.filesDir + FileUtil.separator + 'playlist_covers'; } /** * 清理未使用的封面图片 * @param context 应用上下文 * @param usedPaths 正在使用的图片路径列表 */ static async cleanupUnusedCovers(context: Context, usedPaths: string[]): Promise { try { const coverDir = ImagePickerUtil.getPlaylistCoverDir(context); // 检查目录是否存在 if (!ImagePickerUtil.checkFileExists(coverDir)) { return; } // 读取目录中的所有文件 const files = fileIo.listFileSync(coverDir); const usedFileNames = new Set(); // 将使用中的路径转换为文件名 for (const path of usedPaths) { if (path && path.includes('playlist_covers' + FileUtil.separator)) { const fileName = path.substring(path.lastIndexOf(FileUtil.separator) + 1); usedFileNames.add(fileName); } } // 删除未使用的文件 for (const fileName of files) { if (!usedFileNames.has(fileName)) { const filePath = coverDir + FileUtil.separator + fileName; try { fileIo.unlinkSync(filePath); Logger.info(TAG, `清理未使用的封面: ${filePath}`); } catch (error) { Logger.error(TAG, `清理封面失败: ${filePath}, 错误: ${(error as Error).message}`); } } } Logger.info(TAG, '封面清理完成'); } catch (error) { Logger.error(TAG, `清理封面失败: ${(error as Error).message}`); } } } export default ImagePickerUtil;