ImagePickerUtil.ets 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. import { photoAccessHelper } from '@kit.MediaLibraryKit';
  2. import { fileIo } from '@kit.CoreFileKit';
  3. import { FileUtil } from '@pura/harmony-utils';
  4. import Logger from './Logger';
  5. import { Context } from '@kit.AbilityKit';
  6. import { fileUri } from '@kit.CoreFileKit';
  7. const TAG = 'ImagePickerUtil';
  8. /**
  9. * 图片选择工具类
  10. */
  11. export class ImagePickerUtil {
  12. /**
  13. * 选择图片并返回复制到应用私有目录的路径
  14. * @param context 应用上下文
  15. * @param maxSelectCount 最大选择数量,默认1张
  16. * @returns 返回选择的图片路径数组
  17. */
  18. static async selectImages(context: Context, maxSelectCount: number = 1): Promise<string[]> {
  19. try {
  20. Logger.info(TAG, '开始选择图片');
  21. const photoSelectOptions: photoAccessHelper.PhotoSelectOptions = {
  22. MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
  23. maxSelectNumber: maxSelectCount
  24. };
  25. const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
  26. const photoSelectResult = await photoViewPicker.select(photoSelectOptions);
  27. Logger.info(TAG, `选择了 ${photoSelectResult.photoUris.length} 张图片`);
  28. if (photoSelectResult.photoUris.length === 0) {
  29. return [];
  30. }
  31. // 将选中的图片复制到应用私有目录
  32. const copiedPaths: string[] = [];
  33. for (const uri of photoSelectResult.photoUris) {
  34. try {
  35. const copiedPath = await ImagePickerUtil.copyImageToAppDir(context, uri);
  36. if (copiedPath) {
  37. copiedPaths.push(copiedPath);
  38. }
  39. } catch (error) {
  40. Logger.error(TAG, `复制图片失败: ${(error as Error).message}`);
  41. }
  42. }
  43. return copiedPaths;
  44. } catch (error) {
  45. Logger.error(TAG, `选择图片失败: ${(error as Error).message}`);
  46. return [];
  47. }
  48. }
  49. /**
  50. * 选择单张图片
  51. * @param context 应用上下文
  52. * @returns 返回选择的图片路径,如果取消选择返回null
  53. */
  54. static async selectSingleImage(context: Context): Promise<string | null> {
  55. const paths = await ImagePickerUtil.selectImages(context, 1);
  56. return paths.length > 0 ? paths[0] : null;
  57. }
  58. /**
  59. * 将图片复制到应用私有目录
  60. * @param context 应用上下文
  61. * @param imageUri 图片URI
  62. * @param type 图片类型,默认为playlist,可选webdav
  63. * @returns 返回复制后的图片路径
  64. */
  65. private static async copyImageToAppDir(context: Context, imageUri: string, type: string = 'playlist'): Promise<string> {
  66. try {
  67. // 生成唯一文件名
  68. const timestamp = Date.now();
  69. const randomSuffix = Math.random().toString(36).substring(2, 8);
  70. const fileName = `${type}_cover_${timestamp}_${randomSuffix}.jpg`;
  71. // 目标路径
  72. const targetDir = context.filesDir + FileUtil.separator + `${type}_covers`;
  73. const targetPath = targetDir + FileUtil.separator + fileName;
  74. // 确保目录存在
  75. try {
  76. fileIo.mkdirSync(targetDir);
  77. } catch (error) {
  78. // 目录可能已存在,忽略错误
  79. }
  80. // 打开源文件和目标文件
  81. const sourceFile = fileIo.openSync(imageUri, fileIo.OpenMode.READ_ONLY);
  82. const targetFile = fileIo.openSync(targetPath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
  83. // 复制文件内容
  84. const buffer = new ArrayBuffer(4096);
  85. let readSize = 0;
  86. do {
  87. readSize = fileIo.readSync(sourceFile.fd, buffer);
  88. if (readSize > 0) {
  89. fileIo.writeSync(targetFile.fd, buffer, { length: readSize });
  90. }
  91. } while (readSize > 0);
  92. // 关闭文件
  93. fileIo.closeSync(sourceFile.fd);
  94. fileIo.closeSync(targetFile.fd);
  95. // 转换为URI格式(关键步骤!)
  96. const uriPath = fileUri.getUriFromPath(targetPath);
  97. Logger.info(TAG, `图片复制成功: ${uriPath}`);
  98. return uriPath;
  99. } catch (error) {
  100. Logger.error(TAG, `复制图片失败: ${(error as Error).message}`);
  101. throw new Error(`复制图片失败: ${(error as Error).message}`);
  102. }
  103. }
  104. /**
  105. * 选择单张图片(WebDAV账户封面)
  106. * @param context 应用上下文
  107. * @returns 返回选择的图片路径,如果取消选择返回null
  108. */
  109. static async selectSingleWebDavCover(context: Context): Promise<string | null> {
  110. try {
  111. Logger.info(TAG, '开始选择WebDAV账户封面');
  112. const photoSelectOptions: photoAccessHelper.PhotoSelectOptions = {
  113. MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
  114. maxSelectNumber: 1
  115. };
  116. const photoViewPicker = new photoAccessHelper.PhotoViewPicker();
  117. const photoSelectResult = await photoViewPicker.select(photoSelectOptions);
  118. Logger.info(TAG, `选择了 ${photoSelectResult.photoUris.length} 张图片`);
  119. if (photoSelectResult.photoUris.length === 0) {
  120. return null;
  121. }
  122. // 将选中的图片复制到应用私有目录
  123. const uri = photoSelectResult.photoUris[0];
  124. const copiedPath = await ImagePickerUtil.copyImageToAppDir(context, uri, 'webdav');
  125. return copiedPath;
  126. } catch (error) {
  127. Logger.error(TAG, `选择WebDAV账户封面失败: ${(error as Error).message}`);
  128. return null;
  129. }
  130. }
  131. /**
  132. * 删除歌单封面图片
  133. * @param imagePath 图片路径
  134. */
  135. static deleteImage(imagePath: string): void {
  136. try {
  137. if (imagePath && ImagePickerUtil.checkFileExists(imagePath)) {
  138. fileIo.unlinkSync(imagePath);
  139. Logger.info(TAG, `删除图片成功: ${imagePath}`);
  140. }
  141. } catch (error) {
  142. Logger.error(TAG, `删除图片失败: ${(error as Error).message}`);
  143. }
  144. }
  145. /**
  146. * 检查文件是否存在
  147. * @param filePath 文件路径
  148. * @returns 是否存在
  149. */
  150. private static checkFileExists(filePath: string): boolean {
  151. try {
  152. fileIo.accessSync(filePath);
  153. return true;
  154. } catch (error) {
  155. return false;
  156. }
  157. }
  158. /**
  159. * 检查图片文件是否存在
  160. * @param imagePath 图片路径
  161. * @returns 是否存在
  162. */
  163. static imageExists(imagePath: string): boolean {
  164. if (!imagePath) {
  165. Logger.info(TAG, '图片路径为空');
  166. return false;
  167. }
  168. const exists = ImagePickerUtil.checkFileExists(imagePath);
  169. Logger.info(TAG, `检查图片是否存在: ${imagePath}, 结果: ${exists}`);
  170. return exists;
  171. }
  172. /**
  173. * 获取歌单封面目录
  174. * @param context 应用上下文
  175. * @returns 歌单封面目录路径
  176. */
  177. static getPlaylistCoverDir(context: Context): string {
  178. return context.filesDir + FileUtil.separator + 'playlist_covers';
  179. }
  180. /**
  181. * 清理未使用的封面图片
  182. * @param context 应用上下文
  183. * @param usedPaths 正在使用的图片路径列表
  184. */
  185. static async cleanupUnusedCovers(context: Context, usedPaths: string[]): Promise<void> {
  186. try {
  187. const coverDir = ImagePickerUtil.getPlaylistCoverDir(context);
  188. // 检查目录是否存在
  189. if (!ImagePickerUtil.checkFileExists(coverDir)) {
  190. return;
  191. }
  192. // 读取目录中的所有文件
  193. const files = fileIo.listFileSync(coverDir);
  194. const usedFileNames = new Set<string>();
  195. // 将使用中的路径转换为文件名
  196. for (const path of usedPaths) {
  197. if (path && path.includes('playlist_covers' + FileUtil.separator)) {
  198. const fileName = path.substring(path.lastIndexOf(FileUtil.separator) + 1);
  199. usedFileNames.add(fileName);
  200. }
  201. }
  202. // 删除未使用的文件
  203. for (const fileName of files) {
  204. if (!usedFileNames.has(fileName)) {
  205. const filePath = coverDir + FileUtil.separator + fileName;
  206. try {
  207. fileIo.unlinkSync(filePath);
  208. Logger.info(TAG, `清理未使用的封面: ${filePath}`);
  209. } catch (error) {
  210. Logger.error(TAG, `清理封面失败: ${filePath}, 错误: ${(error as Error).message}`);
  211. }
  212. }
  213. }
  214. Logger.info(TAG, '封面清理完成');
  215. } catch (error) {
  216. Logger.error(TAG, `清理封面失败: ${(error as Error).message}`);
  217. }
  218. }
  219. }
  220. export default ImagePickerUtil;