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}