1/*
2 * Copyright (c) 2021-2023 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
16import fileExtensionInfo from '@ohos.file.fileExtensionInfo';
17import fileAccess from '@ohos.file.fileAccess';
18import ObjectUtil from './ObjectUtil';
19import Logger from '../log/Logger';
20import StringUtil from './StringUtil';
21import { FILENAME_MAX_LENGTH, RENAME_CONNECT_CHARACTER } from '../constants/Constant';
22import fs from '@ohos.file.fs';
23import FileUri from '@ohos.file.fileuri';
24import { photoAccessHelper } from '@kit.MediaLibraryKit';
25
26const TAG = 'FileUtil';
27
28export class FileUtil {
29  /**
30   * uri 格式开头
31   */
32  static readonly URI_START = 'file://';
33
34  /**
35   * 根据fileAccess.FileInfo中的mode匹配是否是文件夹
36   * @param mode number
37   * @returns boolean
38   */
39  public static isFolder(mode: number): boolean {
40    return (mode & fileExtensionInfo.DocumentFlag.REPRESENTS_DIR) === fileExtensionInfo.DocumentFlag.REPRESENTS_DIR;
41  }
42
43  /**
44   * 计算文件夹子文件个数
45   * @param fileIterator fileAccess.FileIterator
46   * @returns number
47   */
48  public static getChildCountOfFolder(fileIterator: fileAccess.FileIterator): number {
49    let count = 0;
50    if (ObjectUtil.isNullOrUndefined(fileIterator)) {
51      return count;
52    }
53    let isDone: boolean = false;
54    while (!isDone) {
55      let currItem = fileIterator.next();
56      isDone = currItem.done;
57      if (isDone) {
58        break;
59      }
60      count++;
61    }
62    return count;
63  }
64
65  /**
66   * 获取文件信息
67   * @param uri 文件uri
68   * @param fileAccessHelper fileAccess.FileAccessHelper
69   * @returns fileAccess.FileInfo
70   */
71  public static async getFileInfoByUri(uri: string,
72    fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileInfo> {
73    try {
74      return await fileAccessHelper.getFileInfoFromUri(uri);
75    } catch (err) {
76      Logger.e(TAG, 'getFileInfoByUri err: ' + JSON.stringify(err));
77    }
78    return null;
79  }
80
81  /**
82   * 获取文件信息
83   * @param relativePath 文件relativePath
84   * @param fileAccessHelper fileAccess.FileAccessHelper
85   * @returns fileAccess.FileInfo
86   */
87  public static async getFileInfoByRelativePath(relativePath: string,
88    fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileInfo> {
89    try {
90      return await fileAccessHelper.getFileInfoFromRelativePath(relativePath);
91    } catch (err) {
92      Logger.e(TAG, 'getFileInfoByRelativePath err: ' + JSON.stringify(err));
93    }
94    return null;
95  }
96
97  /**
98   * 根据uri获取文件夹子文件列表Iterator
99   * @param uri
100   * @param fileAccessHelper
101   * @returns FileIterator
102   */
103  public static async getFileIteratorByUri(uri: string,
104    fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileIterator> {
105    try {
106      let fileInfo = await fileAccessHelper.getFileInfoFromUri(uri);
107      return fileInfo.listFile();
108    } catch (err) {
109      Logger.e(TAG, 'getFileIteratorByUri err: ' + JSON.stringify(err));
110    }
111    return null;
112  }
113
114  public static getFileAccessHelper(context, wants): fileAccess.FileAccessHelper {
115    try {
116      return fileAccess.createFileAccessHelper(context, wants);
117    } catch (err) {
118      Logger.i(TAG, 'getFileAccessHelper err: ' + JSON.stringify(err));
119    }
120    return null;
121  }
122
123  public static async getFileAccessHelperAsync(context): Promise<fileAccess.FileAccessHelper> {
124    try {
125      let wants = await fileAccess.getFileAccessAbilityInfo();
126      return fileAccess.createFileAccessHelper(context, wants);
127    } catch (err) {
128      Logger.i(TAG, 'getFileAccessHelperAsync err: ' + JSON.stringify(err));
129    }
130    return null;
131  }
132
133  public static getParentRelativePath(relativePath: string): string {
134    let curPath = relativePath;
135    if (StringUtil.isEmpty(relativePath)) {
136      return '';
137    }
138
139    let index: number = curPath.lastIndexOf('/');
140    // 去掉最后一个'/'
141    if (index === curPath.length - 1) {
142      curPath = curPath.substr(0, index);
143    }
144    index = curPath.lastIndexOf('/');
145    if (index <= 0) {
146      return '';
147    }
148    return curPath.substr(0, index + 1);
149  }
150
151  public static getUsageHabitsKey(prefix: string, suffix: string): string {
152    return prefix + suffix.charAt(0).toLocaleUpperCase() + suffix.substring(1);
153  }
154
155  /**
156   * 是否是uri路径
157   * @param path 路径
158   * @returns 结果
159   */
160  public static isUriPath(path: string): boolean {
161    if (ObjectUtil.isNullOrUndefined(path)) {
162      return false;
163    }
164    return path.startsWith(this.URI_START);
165  }
166
167  /**
168   * 从目录下获取某个文件名的文件
169   * @param foldrUri 目录uri
170   * @param fileName 文件名
171   * return 结果
172   */
173  public static async getFileFromFolder(foldrUri: string, fileName,
174    fileAccessHelper: fileAccess.FileAccessHelper): Promise<fileAccess.FileInfo> {
175    // 先将目录的信息查询出来
176    let fileInfo: fileAccess.FileInfo = await this.getFileInfoByUri(foldrUri, fileAccessHelper);
177    if (ObjectUtil.isNullOrUndefined(fileInfo)) {
178      return null;
179    }
180    // 构建目标目录下的同名文件的相对路径
181    const destFileRelativePath = fileInfo.relativePath + fileInfo.fileName + '/' + fileName;
182    // 根据相对路径查询相应的文件
183    return await this.getFileInfoByRelativePath(destFileRelativePath, fileAccessHelper);
184  }
185
186  /**
187   * 根据FileInfo获取当前文件的文件夹
188   *
189   * @param fileInfo 文件对象
190   * @returns 返回当前文件的文件夹
191   */
192  public static getCurrentFolderByFileInfo(fileInfo: fileAccess.FileInfo): string {
193    if (fileInfo !== null) {
194      let path = fileInfo.relativePath;
195      return FileUtil.getCurrentDir(path, FileUtil.isFolder(fileInfo.mode));
196    }
197    return "";
198  }
199
200  public static async createFolder(fileAccessHelper: fileAccess.FileAccessHelper, parentUri: string,
201    name: string): Promise<{
202    code,
203    uri
204  }> {
205    let uri: string = '';
206    let code: any;
207    try {
208      uri = await fileAccessHelper.mkDir(parentUri, name);
209    } catch (error) {
210      code = error.code;
211      Logger.e(TAG, 'createFolder error occurred:' + error.code + ', ' + error.message);
212    }
213    return { code: code, uri: uri };
214  }
215
216  public static async hardDelete(uri: string): Promise<boolean> {
217    try {
218      await photoAccessHelper.MediaAssetChangeRequest.deleteAssets(globalThis.abilityContext , [uri]);
219      return true;
220    } catch (e) {
221      Logger.e(TAG, 'hardDelete error: ' + JSON.stringify(e));
222    }
223    return false;
224  }
225
226  /**
227   * 重命名
228   * @param fileAccessHelper FileAccessHelper
229   * @param oldUri oldUri
230   * @param newName newName
231   * @returns {err, uri}
232   */
233  public static async rename(fileAccessHelper: fileAccess.FileAccessHelper, oldUri: string, newName: string): Promise<{
234    err,
235    uri
236  }> {
237    let uri: string = '';
238    let err: any;
239    try {
240      uri = await fileAccessHelper.rename(oldUri, newName);
241    } catch (error) {
242      err = { code: error.code, message: error.message };
243      Logger.e(TAG, 'rename error occurred:' + error.code + ', ' + error.message);
244    }
245    return { err: err, uri: uri };
246  }
247
248  public static async createFile(fileAccessHelper: fileAccess.FileAccessHelper, parentUri: string,
249    fileName: string): Promise<{
250    err,
251    uri
252  }> {
253    let retUri: string = '';
254    let err: any;
255    try {
256      Logger.i(TAG, 'createFile ' + fileAccessHelper + '; ' + parentUri + " ; " + fileName);
257      retUri = await fileAccessHelper.createFile(parentUri, fileName);
258    } catch (e) {
259      Logger.e(TAG, 'createFile error: ' + e.code + ', ' + e.message);
260      err = { code: e.code, message: e.message };
261    }
262    return { err: err, uri: retUri };
263  }
264
265  public static hasSubFolder(loadPath: string, curFolderPath: string): boolean {
266    if (!StringUtil.isEmpty(loadPath)) {
267      if (!StringUtil.isEmpty(curFolderPath)) {
268        loadPath = FileUtil.getPathWithFileSplit(loadPath);
269        curFolderPath = FileUtil.getPathWithFileSplit(curFolderPath);
270        if (loadPath.startsWith(curFolderPath)) {
271          return true;
272        }
273      }
274    }
275    return false;
276  }
277
278  public static getPathWithFileSplit(path: string): string {
279    let fileSplit: string = '/';
280    if (path && !path.endsWith(fileSplit)) {
281      path = path + fileSplit;
282    }
283    return path;
284  }
285
286  public static loadSubFinish(loadPath: string, curFolderPath: string, maxLevel: number): boolean {
287    let fileSplit: string = '/';
288    if (!StringUtil.isEmpty(loadPath)) {
289      if (!loadPath.endsWith(fileSplit)) {
290        loadPath = loadPath + fileSplit;
291      }
292
293      let folders = curFolderPath.split(fileSplit);
294
295      if ((curFolderPath + fileSplit) === loadPath || folders.length >= maxLevel) {
296        return true;
297      }
298    }
299    return false;
300  }
301
302  public static renameFile(fileName: string, renameCount: number, suffix: string): string {
303    if (ObjectUtil.isNullOrUndefined(fileName)) {
304      return fileName;
305    }
306    let newName = fileName;
307    if (renameCount > 0) {
308      newName = fileName + RENAME_CONNECT_CHARACTER + renameCount;
309      let strLen = newName.length + suffix.length;
310      // 字符长度大于最大长度
311      if (strLen > FILENAME_MAX_LENGTH) {
312        // 计算需要裁剪的长度
313        let subLen = strLen - FILENAME_MAX_LENGTH + 1;
314        newName = fileName.substring(0, fileName.length - subLen) + RENAME_CONNECT_CHARACTER + renameCount;
315      }
316    }
317    return newName + suffix;
318  }
319
320  public static getFileNameReName(fileName: string): string[] {
321    if (StringUtil.isEmpty(fileName)) {
322      return null;
323    }
324    let index = fileName.lastIndexOf(RENAME_CONNECT_CHARACTER);
325    if (index === -1) {
326      return null;
327    }
328    let str = fileName.substring(index + 1, fileName.length);
329    let name = fileName.substring(0, index);
330    return [name, str];
331  }
332
333  public static getCurrentDir(path: string, isFolder: boolean): string {
334    if (isFolder) {
335      return path;
336    }
337    if (path) {
338      let index: number = path.lastIndexOf('/');
339      let len: number = path.length;
340      if (len > 1 && index > 1) {
341        return path.substring(0, index);
342      }
343    }
344    return path;
345  }
346
347  public static getUriPath(path: string): string {
348    if (path && FileUtil.isUriPath(path)) {
349      return path;
350    }
351    return null;
352  }
353
354  /**
355   * 根据文件的沙箱路径获取文件uri
356   * @param path 文件的沙箱路径
357   * @returns 文件的uri
358   */
359  public static getUriFromPath(path: string): string {
360    let uri = '';
361    try {
362      // 该接口如果以’/'结尾,返回的uri会以‘/'结尾
363      uri = FileUri.getUriFromPath(path);
364    } catch (error) {
365      Logger.e(TAG, 'getUriFromPath fail, error:' + JSON.stringify(error));
366    }
367    return uri;
368  }
369
370  /**
371   * 将文件uri转换成FileUri对象
372   */
373  public static getFileUriObjectFromUri(uri: string): FileUri.FileUri | undefined {
374    let fileUriObject: FileUri.FileUri | undefined;
375    try {
376      fileUriObject = new FileUri.FileUri(uri);
377    } catch (error) {
378      Logger.e(TAG, 'getFileUriObjectFromUri fail, error:' + JSON.stringify(error));
379    }
380    return fileUriObject;
381  }
382
383  /**
384   * 通过将文件uri转换成FileUri对象获取文件的沙箱路径
385   * @param uri 文件uri
386   * @returns 文件的沙箱路径
387   */
388  public static getPathFromUri(uri: string): string {
389    let path = '';
390    const fileUriObj = FileUtil.getFileUriObjectFromUri(uri);
391    if (!!fileUriObj) {
392      path = fileUriObj.path;
393    }
394    return path;
395  }
396
397  /**
398   * 创建文件夹
399   * @param parentFolderUri 父目录uri
400   * @param newFolderName 新文件夹名
401   * @returns 新文件夹uri
402   */
403  public static createFolderByFs(parentFolderUri: string, newFolderName: string): string {
404    try {
405      const parentFolderPath = FileUtil.getPathFromUri(parentFolderUri);
406      const newFolderPath = parentFolderPath + '/' + newFolderName;
407      fs.mkdirSync(newFolderPath);
408      return FileUtil.getUriFromPath(newFolderPath);
409    } catch (error) {
410      Logger.e(TAG, 'createFolderByFs fail, error:' + JSON.stringify(error));
411      throw error as Error;
412    }
413  }
414}