1/*
2 * Copyright (c) 2023-2024 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 type { BusinessError } from '@ohos.base';
17import fs from '@ohos.file.fs';
18import fileuri from '@ohos.file.fileuri';
19import type uri from '@ohos.uri';
20import Logger from '../log/Logger';
21
22export class FsUtil {
23  static readonly TAG: string = 'FsUtil';
24
25  public static async stat(file: string | number): Promise<fs.Stat | BusinessError> {
26    try {
27      return await fs.stat(file);
28    } catch (error) {
29      Logger.i(FsUtil.TAG, 'fs stat error = ' + JSON.stringify(error));
30      return error;
31    }
32  }
33
34  public static statSync(file: string | number): fs.Stat | BusinessError {
35    try {
36      return fs.statSync(file);
37    } catch (error) {
38      Logger.i(FsUtil.TAG, 'fs statSync error = ' + JSON.stringify(error));
39      return error;
40    }
41  }
42
43  public static async access(path: string): Promise<boolean | BusinessError> {
44    try {
45      return await fs.access(path);
46    } catch (error) {
47      Logger.i(FsUtil.TAG, 'fs access error = ' + JSON.stringify(error));
48      return error;
49    }
50  }
51
52  public static accessSync(path: string): boolean {
53    try {
54      return fs.accessSync(path);
55    } catch (error) {
56      Logger.i(FsUtil.TAG, 'fs accessSync error = ' + JSON.stringify(error));
57      return false;
58    }
59  }
60
61  public static openSync(path: string, mode?: number): fs.File | BusinessError {
62    try {
63      return fs.openSync(path, mode);
64    } catch (error) {
65      Logger.i(FsUtil.TAG, 'fs openSync error = ' + JSON.stringify(error));
66      return error;
67    }
68  }
69
70  public static async close(file: number | fs.File): Promise<void | BusinessError> {
71    try {
72      return await fs.close(file);
73    } catch (error) {
74      Logger.i(FsUtil.TAG, 'fs close error = ' + JSON.stringify(error));
75      return error;
76    }
77  }
78
79  public static closeSync(file: number | fs.File): void | BusinessError {
80    try {
81      return fs.closeSync(file);
82    } catch (error) {
83      Logger.i(FsUtil.TAG, 'fs closeSync error = ' + JSON.stringify(error));
84      return error;
85    }
86  }
87
88  public static async mkdir(path: string, recursion: boolean = false): Promise<void | BusinessError> {
89    try {
90      return await fs.mkdir(path, recursion);
91    } catch (error) {
92      Logger.i(FsUtil.TAG, 'fs mkdir error = ' + JSON.stringify(error));
93      return error;
94    }
95  }
96
97  public static mkdirSync(path: string, recursion: boolean = false): void | BusinessError {
98    try {
99      return fs.mkdirSync(path, recursion);
100    } catch (error) {
101      Logger.i(FsUtil.TAG, 'fs mkdirSync error = ' + JSON.stringify(error));
102      return error;
103    }
104  }
105
106  public static async rmdir(path: string): Promise<void | BusinessError> {
107    try {
108      return await fs.rmdir(path);
109    } catch (error) {
110      Logger.i(FsUtil.TAG, 'fs rmdir error = ' + JSON.stringify(error));
111      return error;
112    }
113  }
114
115  public static rmdirSync(path: string): void | BusinessError {
116    try {
117      return fs.rmdirSync(path);
118    } catch (error) {
119      Logger.i(FsUtil.TAG, 'fs rmdirSync error = ' + JSON.stringify(error));
120      return error;
121    }
122  }
123
124  public static async moveFile(src: string, dest: string, mode?: number): Promise<void | BusinessError> {
125    try {
126      return await fs.moveFile(src, dest, mode);
127    } catch (error) {
128      Logger.i(FsUtil.TAG, 'fs moveFile error = ' + JSON.stringify(error));
129      return error;
130    }
131  }
132
133  public static async moveDir(src: string, dest: string, mode?: number): Promise<void | BusinessError> {
134    try {
135      return await fs.moveDir(src, dest, mode);
136    } catch (error) {
137      Logger.i(FsUtil.TAG, 'fs moveDir error = ' + JSON.stringify(error));
138      return error;
139    }
140  }
141
142  public static moveFileSync(src: string, dest: string, mode?: number): void | BusinessError {
143    try {
144      return fs.moveFileSync(src, dest, mode);
145    } catch (error) {
146      Logger.i(FsUtil.TAG, 'fs moveFileSync error = ' + JSON.stringify(error));
147      return error;
148    }
149  }
150
151  public static moveDirSync(src: string, dest: string, mode?: number): void | BusinessError {
152    try {
153      return fs.moveDirSync(src, dest, mode);
154    } catch (error) {
155      Logger.i(FsUtil.TAG, 'fs moveDirSync error = ' + JSON.stringify(error));
156      return error;
157    }
158  }
159
160  public static async rename(oldPath: string, newPath: string): Promise<void | BusinessError> {
161    try {
162      return await fs.rename(oldPath, newPath);
163    } catch (error) {
164      Logger.i(FsUtil.TAG, 'fs rename error = ' + JSON.stringify(error));
165      return error;
166    }
167  }
168
169  public static renameSync(oldPath: string, newPath: string): void | BusinessError {
170    try {
171      return fs.renameSync(oldPath, newPath);
172    } catch (error) {
173      Logger.i(FsUtil.TAG, 'fs renameSync error = ' + JSON.stringify(error));
174      return error;
175    }
176  }
177
178  public static async unlink(path: string): Promise<void | BusinessError> {
179    try {
180       return await fs.unlink(path);
181    } catch (error) {
182      Logger.i(FsUtil.TAG, 'fs unlink error = ' + JSON.stringify(error));
183      return error;
184    }
185  }
186
187  public static unlinkSync(path: string): void | BusinessError {
188    try {
189      return fs.unlinkSync(path);
190    } catch (error) {
191      Logger.i(FsUtil.TAG, 'fs unlinkSync error = ' + JSON.stringify(error));
192      return error;
193    }
194  }
195
196  // @ts-ignore
197  public static async write(fd: number, buffer: ArrayBuffer | string, options?: fs.WriteOptions): Promise<number | BusinessError> {
198    try {
199      return await fs.write(fd, buffer, options);
200    } catch (error) {
201      Logger.i(FsUtil.TAG, 'fs write error = ' + JSON.stringify(error));
202      return error;
203    }
204  }
205
206  // @ts-ignore
207  public static writeSync(fd: number, buffer: ArrayBuffer | string, options?: fs.WriteOptions): number | BusinessError {
208    try {
209      return fs.writeSync(fd, buffer, options);
210    } catch (error) {
211      Logger.i(FsUtil.TAG, 'fs writeSync error = ' + JSON.stringify(error));
212      return error;
213    }
214  }
215
216  // @ts-ignore
217  public static async read(fd: number, buffer: ArrayBuffer, options?: fs.ReadOptions): Promise<number | BusinessError> {
218    try {
219      return await fs.read(fd, buffer, options);
220    } catch (error) {
221      Logger.i(FsUtil.TAG, 'fs read error = ' + JSON.stringify(error));
222      return error;
223    }
224  }
225
226  // @ts-ignore
227  public static readSync(fd: number, buffer: ArrayBuffer, options?: fs.ReadOptions): number | BusinessError {
228    try {
229      return fs.readSync(fd, buffer, options);
230    } catch (error) {
231      Logger.i(FsUtil.TAG, 'fs readSync error = ' + JSON.stringify(error));
232      return error;
233    }
234  }
235
236  public static readTextSync(path: string): string | BusinessError {
237    try {
238      return fs.readTextSync(path);
239    } catch (error) {
240      Logger.i(FsUtil.TAG, `fs readTextSync error =  ${JSON.stringify(error)}`);
241      return error;
242    }
243  }
244
245  // @ts-ignore
246  public static listFileSync(path: string, options?: fs.ListFileOptions): string[] | BusinessError {
247    try {
248      let res = fs.listFileSync(path, options);
249      return res;
250    } catch (error) {
251      Logger.i(FsUtil.TAG, 'fs listFileSync error = ' + JSON.stringify(error));
252      return error;
253    }
254  }
255
256  public static async fsync(fd: number): Promise<void | BusinessError> {
257    try {
258      return await fs.fsync(fd);
259    } catch (error) {
260      Logger.i(FsUtil.TAG, 'fs fsync error = ' + JSON.stringify(error));
261      return error;
262    }
263  }
264
265  public static fsyncSync(fd: number): void | BusinessError {
266    try {
267      return fs.fsyncSync(fd);
268    } catch (error) {
269      Logger.i(FsUtil.TAG, 'fs fsync error = ' + JSON.stringify(error));
270      return error;
271    }
272  }
273
274  /**
275   * 强制删除文件
276   * @param uri 删除文件的uri
277   */
278  public static forceDelete(uri: string): number | BusinessError {
279    try {
280      let fileUri: fileuri.FileUri = new fileuri.FileUri(uri);
281      let filePath: string = fileUri.path;
282      if (fs.statSync(filePath).isDirectory()) {
283        fs.rmdirSync(filePath);
284      } else {
285        fs.unlinkSync(filePath);
286      }
287      return 0;
288    } catch (error) {
289      Logger.i(FsUtil.TAG, 'force delete file error: ' + JSON.stringify(error));
290      return error;
291    }
292  }
293
294  /**
295   * 文件夹判空
296   * @param path 文件夹的uri
297   */
298  public static isFolderEmpty(path: string): boolean | BusinessError {
299    try {
300      let fileList = fs.listFileSync(path, { listNum: 1 });
301      return fileList.length === 0;
302    } catch (error) {
303      Logger.i(FsUtil.TAG, 'isFolderEmpty error: ' + JSON.stringify(error));
304      return error;
305    }
306  }
307  /**
308   * 判断文件是否为文件夹(目前暂不支持应用沙箱目录)
309   * @param path 文件
310   * @returns
311   */
312  public static isFolder(path: string): boolean | BusinessError {
313    try {
314      let stat = fs.statSync(path);
315      return stat?.isDirectory();
316    } catch (error) {
317      Logger.i(FsUtil.TAG, path + ' isFolder error: ' + JSON.stringify(error));
318      return error;
319    }
320  }
321
322  /**
323   * 判断文件是否存在
324   * @param uri 文件uri
325   * @returns 判断结果
326   */
327  public static isFileExist(uri: string): boolean {
328    let isExist: boolean = false;
329    try {
330      Logger.i(FsUtil.TAG, 'open start');
331      let fileFd: fs.File = fs.openSync(uri, fs.OpenMode.READ_ONLY);
332      fs.closeSync(fileFd);
333      isExist = true;
334    } catch (error) {
335      Logger.i(FsUtil.TAG, 'openSync fail: ' + JSON.stringify(error));
336    }
337    return isExist;
338  }
339
340  /**
341   * 判断文件是否被删除(包括软删除和硬删除)
342   * @param uri 文件uri
343   * @returns 判断结果
344   */
345  public static isFileDeleted(uri: string): boolean {
346    try {
347      Logger.i(FsUtil.TAG, 'open start');
348      let fileFd: fs.File = fs.openSync(uri, fs.OpenMode.READ_ONLY); // 此处报错说明被硬删除了
349      const path = fileFd.path;
350      fs.closeSync(fileFd);
351      let stat = fs.statSync(path);
352      if (stat.ctime === 0 && stat.mtime === 0) { // 说明被软删除了
353        return true;
354      }
355      return false;
356    } catch (error) {
357      Logger.i(FsUtil.TAG, 'openSync fail: ' + JSON.stringify(error));
358      return true;
359    }
360  }
361
362  /**
363   * 判断目录下是否存在同名文件
364   * @param destUri:目录uri
365   * @param fileName:待判断的文件名
366   * @returns 判断结果
367   */
368  public static isExistDupName(destUri: string, fileName: string): boolean {
369    let isExistDupName: boolean = false;
370    try {
371      let destFileInfo: uri.URI = new fileuri.FileUri(destUri);
372      let newFilePath: string = destFileInfo.path + '/' + fileName;
373      isExistDupName = fs.accessSync(newFilePath);
374    } catch (err) {
375      Logger.i(FsUtil.TAG, 'isExistDupName err: ' + JSON.stringify(err));
376    }
377    return isExistDupName;
378  }
379
380  public static getInoByUri(uri: string): string {
381    try {
382      let fileUri: fileuri.FileUri = new fileuri.FileUri(uri);
383      let stat = fs.statSync(fileUri.path);
384      return stat.ino.toString();
385    } catch (error) {
386      Logger.i(FsUtil.TAG, `get ino failed, error message : ${error?.message}, error code : ${error?.code}`);
387      return '';
388    }
389  }
390
391  /**
392   * 文件拷贝同步接口,适合十几兆的小文件拷贝
393   * @param srcPath 源文件
394   * @param destinationPath 目标文件
395   * @param mode 拷贝模式
396   * @returns true:拷贝成功,目标文件已经存在
397   */
398  public static copyFileSyncByPath(srcPath: string, destinationPath: string, mode?: number): boolean {
399    try {
400      fs.copyFileSync(srcPath, destinationPath, mode);
401      return FsUtil.isExistSyncByPath(destinationPath);
402    } catch (error) {
403      Logger.i(FsUtil.TAG, 'copyFileSyncByPath err: ' + JSON.stringify(error));
404      return false;
405    }
406  }
407
408  /**
409   * 判断文件是否存在
410   *
411   * @param path 文件全目录
412   * @returns true:文件存在
413   */
414  public static isExistSyncByPath(path: string): boolean {
415    try {
416      return fs.accessSync(path);
417    } catch (error) {
418      Logger.i(FsUtil.TAG, 'isExistSyncByPath error = ' + JSON.stringify(error));
419      return false;
420    }
421  }
422
423  /**
424   * 重命名异步接口
425   * @param oldPath 即将要重命名的文件全路径
426   * @param newPath 重命名之后的文件全路径
427   * @returns true:命名成功
428   */
429  public static renameSyncByPath(oldPath: string, newPath: string): boolean {
430    try {
431      fs.renameSync(oldPath, newPath);
432      return FsUtil.isExistSyncByPath(newPath);
433    } catch (error) {
434      Logger.i(FsUtil.TAG, 'fs renameSync error = ' + JSON.stringify(error));
435      return false;
436    }
437  }
438
439  /**
440   * 同步获取文件大小
441   * @param path 文件全路径
442   * @returns 文件夹大小
443   */
444  public static getFileSizeSyncByPath(path: string): number {
445    let size = 0;
446    try {
447      let stat = fs.statSync(path);
448      size = stat.size;
449    } catch (error) {
450      Logger.i(FsUtil.TAG, 'getFileInfoByFs fail, error:' + JSON.stringify(error));
451    }
452    return size;
453  }
454}