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 { SYSTEM_BAR_COLOR } from '../base/constants/UiConstant'; 22import StringUtil from '../base/utils/StringUtil'; 23import { FileUtil } 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'; 31 32const TAG = 'PathSelector'; 33let storage = LocalStorage.getShared(); 34 35@Entry(storage) 36@Component 37struct PathSelector { 38 private startModeOptions: StartModeOptions = FilePickerUtil.getStartOptionsFromStorage(); 39 @State createResultType: number = ErrorCodeConst.PICKER.NORMAL; 40 41 aboutToAppear() { 42 UiUtil.setWindowBackground(SYSTEM_BAR_COLOR.LIGHT_GRAY); 43 } 44 45 async saveFileCallback(res, startModeOptions: StartModeOptions): Promise<void> { 46 if (res?.cancel) { 47 AbilityCommonUtil.terminatePathPicker([], ResultCodePicker.CANCEL, startModeOptions); 48 return; 49 } else { 50 let fileNameList = this.startModeOptions.newFileNames; 51 // 保存单个文件时文件名可修改,需使用修改后的文件名来创建文件 52 if (fileNameList.length <= 1) { 53 fileNameList = [res.fileName]; 54 } 55 this.saveFiles(res.selectUri, fileNameList).then((createdFileList) => { 56 AbilityCommonUtil.terminatePathPicker(createdFileList, ResultCodePicker.SUCCESS, startModeOptions); 57 }).catch((err) => { 58 let errorMessage = ''; 59 let errorCode = 0; 60 Logger.e(TAG, JSON.stringify(err)); 61 if (err.code) { 62 if (err.code === ErrorCodeConst.FILE_ACCESS.FILE_NAME_EXIST) { 63 errorMessage = 'Same name file already exists'; 64 errorCode = ErrorCodeConst.PICKER.FILE_NAME_EXIST; 65 this.createResultType = errorCode; 66 const pathName = startModeOptions.newFileNames; 67 let listLength: number = pathName.length; 68 if (listLength == 1) { 69 return; 70 } 71 } else if (err.code === ErrorCodeConst.FILE_ACCESS.FILE_NAME_INVALID) { 72 errorMessage = 'Invalid display name'; 73 errorCode = ErrorCodeConst.PICKER.FILE_NAME_INVALID; 74 } else { 75 errorMessage = 'File create failed'; 76 errorCode = ErrorCodeConst.PICKER.OTHER_ERROR; 77 } 78 } else { 79 errorMessage = err.message ? err.message : err; 80 errorCode = ErrorCodeConst.PICKER.OTHER_ERROR; 81 } 82 AbilityCommonUtil.terminatePathPicker([], errorCode, startModeOptions); 83 toast($r('app.string.save_file_fail')); 84 Logger.e(TAG, `path select error, errorCode: ${errorCode}, errorMessage: ${errorMessage}`); 85 }) 86 } 87 } 88 89 /** 90 * PathPicker保存文件 91 * @param data SaveFilesParam 92 */ 93 async saveFiles(path: string, nameList: string[]): Promise<string[]> { 94 return new Promise(async (resolve, reject) => { 95 let fileAccessHelper = await FileUtil.getFileAccessHelperAsync(globalThis.abilityContext); 96 let dirPath = path; 97 if (StringUtil.isEmpty(dirPath)) { 98 dirPath = (await FileUtil.getFileInfoByRelativePath('Documents/', fileAccessHelper)).uri; 99 } 100 let fileNameArr = nameList; 101 let successArr: string[] = []; 102 let resultErr: any; 103 let len: number = fileNameArr.length; 104 let fileNameList: string[] = []; 105 if (len > 1) { 106 fileNameList = await this.getPickPathListFiles(dirPath, fileAccessHelper); 107 } 108 Logger.i(TAG, 'saveFiles createName: ' + JSON.stringify(fileNameArr) + ' ; '); 109 Logger.i(TAG, 'saveFiles subList: ' + JSON.stringify(fileNameList) + ' ; '); 110 for (let i = 0; i < len; i++) { 111 const currName = fileNameArr[i]; 112 let result; 113 if (len === 1) { 114 result = await FileUtil.createFile(fileAccessHelper, dirPath, currName); 115 } else { 116 result = await this.tryRenameFileOperate(fileAccessHelper, currName, dirPath, 0, fileNameList); 117 } 118 if (ObjectUtil.isUndefined(result.err)) { 119 Logger.i(TAG, 'saveFiles createOK: ' + result.uri); 120 successArr.push(result.uri); 121 continue; 122 } 123 Logger.i(TAG, 'saveFiles err: ' + result.err.code); 124 // 失败 125 resultErr = { code: result.err.code, message: result.err.message }; 126 let photoManageHelper: photoAccessHelper.PhotoAccessHelper = AbilityCommonUtil.getPhotoManageHelper(); 127 if (ObjectUtil.isNullOrUndefined(photoManageHelper)) { 128 break; 129 } 130 for (let i = 0; i < successArr.length; i++) { 131 await FileUtil.hardDelete(successArr[i]); 132 } 133 try { 134 photoManageHelper.release(); 135 } catch (e) { 136 Logger.e(TAG, 'mediaLibrary close error'); 137 } 138 successArr = []; 139 break; 140 } 141 142 Logger.i(TAG, 'saveFiles end: ' + JSON.stringify(successArr)); 143 if (!ArrayUtil.isEmpty(successArr)) { 144 resolve(successArr); 145 } else { 146 reject(resultErr); 147 } 148 }) 149 } 150 151 private async getPickPathListFiles(dirUri: string, fileAccessHelper: fileAccess.FileAccessHelper): Promise<string[]> { 152 let fileInfo: fileAccess.FileInfo = await FileUtil.getFileInfoByUri(dirUri, fileAccessHelper); 153 if (ObjectUtil.isNullOrUndefined(fileInfo) || !FileUtil.isFolder(fileInfo.mode)) { 154 return []; 155 } 156 return this.getFilesByIterator(fileInfo.listFile()); 157 } 158 159 private getFilesByIterator(fileIterator: fileAccess.FileIterator): string[] { 160 if (ObjectUtil.isNull(fileIterator)) { 161 return null; 162 } 163 let result: string[] = []; 164 let isDone = false; 165 while (!isDone) { 166 try { 167 let nextFileInfo = fileIterator.next(); 168 isDone = nextFileInfo.done; 169 if (isDone) { 170 break; 171 } 172 let currFile = nextFileInfo.value; 173 if (!FileUtil.isFolder(currFile.mode)) { 174 result.push(currFile.fileName); 175 } 176 } catch (err) { 177 Logger.e(TAG, 'current File err: ' + JSON.stringify(err) + ', ' + err.toString()); 178 } 179 } 180 return result; 181 } 182 183 private async tryRenameFileOperate(fileAccessHelper: fileAccess.FileAccessHelper, fileName: string, 184 dirUri: string, renameCount: number, fileNameList: string[] = []): Promise<{ 185 err, 186 uri 187 }> { 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) => { 256 this.saveFileCallback(e, this.startModeOptions); 257 } 258 }) 259 } 260 } 261} 262