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 { fileTree } from './component/dialog/FileMoveDialog'; 17import Logger from '../base/log/Logger'; 18import ErrorCodeConst from '../base//constants/ErrorCodeConst'; 19import { toast } from '../base/utils/Common'; 20import AbilityCommonUtil, { ResultCodePicker } from '../base/utils/AbilityCommonUtil'; 21import { SystemBarColor } from '../base/constants/UiConstant'; 22import StringUtil from '../base/utils/StringUtil'; 23import { FileUtil, ErrUri, ErrCodeMessage } from '../base/utils/FileUtil'; 24import ObjectUtil from '../base/utils/ObjectUtil'; 25import fileAccess from '@ohos.file.fileAccess'; 26import { ArrayUtil } from '../base/utils/ArrayUtil'; 27import { UiUtil } from '../base/utils/UiUtil'; 28import { StartModeOptions } from '../base/model/StartModeOptions'; 29import { FilePickerUtil } from '../base/utils/FilePickerUtil'; 30import { photoAccessHelper } from '@kit.MediaLibraryKit'; 31import { BusinessError } from '@ohos.base'; 32import { MoveCallBackParams } from '../databases/model/FileData' 33 34const TAG = 'PathSelector'; 35let storage = LocalStorage.getShared(); 36 37@Entry(storage) 38@Component 39struct PathSelector { 40 private startModeOptions: StartModeOptions = FilePickerUtil.getStartOptionsFromStorage(); 41 @State createResultType: number = ErrorCodeConst.PICKER.NORMAL; 42 43 aboutToAppear() { 44 UiUtil.setWindowBackground(SystemBarColor.LIGHT_GRAY); 45 } 46 47 async saveFileCallback(res: MoveCallBackParams, startModeOptions: StartModeOptions): Promise<void> { 48 if (res?.cancel) { 49 AbilityCommonUtil.terminatePathPicker([], ResultCodePicker.CANCEL, startModeOptions); 50 return; 51 } else { 52 let fileNameList = this.startModeOptions.newFileNames; 53 // 保存单个文件时文件名可修改,需使用修改后的文件名来创建文件 54 if (fileNameList.length <= 1) { 55 fileNameList = [res.fileName]; 56 } 57 this.saveFiles(res.selectUri, fileNameList).then((createdFileList) => { 58 AbilityCommonUtil.terminatePathPicker(createdFileList, ResultCodePicker.SUCCESS, startModeOptions); 59 }).catch((err: BusinessError) => { 60 let errorMessage = ''; 61 let errorCode = 0; 62 Logger.e(TAG, JSON.stringify(err)); 63 if (err.code) { 64 if (err.code === ErrorCodeConst.FILE_ACCESS.FILE_NAME_EXIST) { 65 errorMessage = 'Same name file already exists'; 66 errorCode = ErrorCodeConst.PICKER.FILE_NAME_EXIST; 67 this.createResultType = errorCode; 68 const pathName = startModeOptions.newFileNames; 69 let listLength: number = pathName.length; 70 if (listLength == 1) { 71 return; 72 } 73 } else if (err.code === ErrorCodeConst.FILE_ACCESS.FILE_NAME_INVALID) { 74 errorMessage = 'Invalid display name'; 75 errorCode = ErrorCodeConst.PICKER.FILE_NAME_INVALID; 76 } else { 77 errorMessage = 'File create failed'; 78 errorCode = ErrorCodeConst.PICKER.OTHER_ERROR; 79 } 80 } else { 81 errorMessage = err.message ? err.message : ''; 82 errorCode = ErrorCodeConst.PICKER.OTHER_ERROR; 83 } 84 AbilityCommonUtil.terminatePathPicker([], errorCode, startModeOptions); 85 toast($r('app.string.save_file_fail')); 86 Logger.e(TAG, `path select error, errorCode: ${errorCode}, errorMessage: ${errorMessage}`); 87 }) 88 } 89 } 90 91 /** 92 * PathPicker保存文件 93 * @param data SaveFilesParam 94 */ 95 async saveFiles(path: string, nameList: string[]): Promise<string[]> { 96 return new Promise(async (resolve, reject) => { 97 let fileAccessHelper = await FileUtil.getFileAccessHelperAsync(globalThis.abilityContext); 98 let dirPath = path; 99 if (StringUtil.isEmpty(dirPath)) { 100 dirPath = (await FileUtil.getFileInfoByRelativePath('Documents/', fileAccessHelper)).uri; 101 } 102 let fileNameArr = nameList; 103 let successArr: string[] = []; 104 let resultErr: ErrCodeMessage = new ErrCodeMessage(); 105 let len: number = fileNameArr.length; 106 let fileNameList: string[] = []; 107 if (len > 1) { 108 fileNameList = await this.getPickPathListFiles(dirPath, fileAccessHelper); 109 } 110 Logger.i(TAG, 'saveFiles createName: ' + JSON.stringify(fileNameArr) + ' ; '); 111 Logger.i(TAG, 'saveFiles subList: ' + JSON.stringify(fileNameList) + ' ; '); 112 for (let i = 0; i < len; i++) { 113 const currName = fileNameArr[i]; 114 let result: ErrUri = new ErrUri(); 115 if (len === 1) { 116 result = await FileUtil.createFile(fileAccessHelper, dirPath, currName); 117 } else { 118 result = await this.tryRenameFileOperate(fileAccessHelper, currName, dirPath, 0, fileNameList); 119 } 120 if (ObjectUtil.isUndefined(result.err)) { 121 Logger.i(TAG, 'saveFiles createOK: ' + result.uri); 122 successArr.push(result.uri); 123 continue; 124 } 125 Logger.i(TAG, 'saveFiles err: ' + result.err.code); 126 // 失败 127 resultErr.code = result.err.code; 128 resultErr.message = result.err.message; 129 let photoManageHelper: photoAccessHelper.PhotoAccessHelper = AbilityCommonUtil.getPhotoManageHelper(); 130 if (ObjectUtil.isNullOrUndefined(photoManageHelper)) { 131 break; 132 } 133 for (let i = 0; i < successArr.length; i++) { 134 await FileUtil.hardDelete(successArr[i]); 135 } 136 try { 137 photoManageHelper.release(); 138 } catch (e) { 139 Logger.e(TAG, 'mediaLibrary close error'); 140 } 141 successArr = []; 142 break; 143 } 144 145 Logger.i(TAG, 'saveFiles end: ' + JSON.stringify(successArr)); 146 if (!ArrayUtil.isEmpty(successArr)) { 147 resolve(successArr); 148 } else { 149 reject(resultErr); 150 } 151 }) 152 } 153 154 private async getPickPathListFiles(dirUri: string, fileAccessHelper: fileAccess.FileAccessHelper): Promise<string[]> { 155 let fileInfo: fileAccess.FileInfo = await FileUtil.getFileInfoByUri(dirUri, fileAccessHelper); 156 if (ObjectUtil.isNullOrUndefined(fileInfo) || !FileUtil.isFolder(fileInfo.mode)) { 157 return []; 158 } 159 return this.getFilesByIterator(fileInfo.listFile()); 160 } 161 162 private getFilesByIterator(fileIterator: fileAccess.FileIterator): string[] { 163 if (ObjectUtil.isNull(fileIterator)) { 164 return []; 165 } 166 let result: string[] = []; 167 let isDone = false; 168 while (!isDone) { 169 try { 170 let nextFileInfo = fileIterator.next(); 171 isDone = nextFileInfo.done; 172 if (isDone) { 173 break; 174 } 175 let currFile = nextFileInfo.value; 176 if (!FileUtil.isFolder(currFile.mode)) { 177 result.push(currFile.fileName); 178 } 179 } catch (err) { 180 Logger.e(TAG, 'current File err: ' + JSON.stringify(err) + ', ' + err.toString()); 181 } 182 } 183 return result; 184 } 185 186 private async tryRenameFileOperate(fileAccessHelper: fileAccess.FileAccessHelper, fileName: string, 187 dirUri: string, renameCount: number, fileNameList: string[] = []): Promise<ErrUri> { 188 let index = fileName.lastIndexOf('.'); 189 let name = fileName; 190 let suffix = ''; 191 if (index !== -1) { 192 suffix = fileName.substring(index, fileName.length); 193 name = fileName.substring(0, index); 194 } 195 let hasReNameCount = FileUtil.getFileNameReName(name); 196 if (!ObjectUtil.isNullOrUndefined(hasReNameCount)) { 197 let num = Number(hasReNameCount[1]); 198 if (!isNaN(num)) { 199 name = hasReNameCount[0]; 200 renameCount = num; 201 } 202 } 203 204 let newName = fileName; 205 while (true) { 206 newName = FileUtil.renameFile(name, renameCount++, suffix); 207 let index = this.getIndex(newName, fileNameList); 208 Logger.i(TAG, 'tryRenameFileOperate : ' + newName + ' ; index = ' + index); 209 if (index === -1) { 210 const result = await FileUtil.createFile(fileAccessHelper, dirUri, newName); 211 if (ObjectUtil.isUndefined(result.err)) { 212 Logger.i(TAG, 'tryRenameFileOperate createOK: ' + result.uri); 213 return result; 214 } else { 215 Logger.i(TAG, 'tryRenameFileOperate createFail: ' + JSON.stringify(result) + ' ; ' + newName); 216 if (result.err.code === ErrorCodeConst.FILE_ACCESS.FILE_NAME_EXIST) { 217 fileNameList.push(newName); 218 } else { 219 return result; 220 } 221 } 222 } 223 } 224 } 225 226 private getIndex(fileName: string, fileNameList: string[] = []) { 227 return fileNameList.findIndex(value => value === fileName); 228 } 229 230 build() { 231 if (this.startModeOptions.isUxt()) { 232 Column() { 233 }.bindSheet(true, this.mainContent(), { 234 height: '95%', 235 dragBar: false, 236 showClose: false, 237 preferType: SheetType.CENTER, 238 onAppear: () => { 239 }, 240 shouldDismiss: () => { 241 this.startModeOptions.session.terminateSelf(); 242 } 243 }) 244 } else { 245 this.mainContent() 246 } 247 } 248 249 @Builder 250 mainContent() { 251 Row() { 252 fileTree({ 253 startModeOptions: this.startModeOptions, 254 createFileFailType: $createResultType, 255 moveCallback: (e: MoveCallBackParams) => { 256 this.saveFileCallback(e, this.startModeOptions); 257 } 258 }) 259 } 260 } 261} 262