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