UNPKG

@react-native-ohos/react-native-document-picker

Version:

A react native interface to access documents from dropbox, google drive, iCloud...

199 lines (181 loc) 6.52 kB
/* * Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved * Use of this source code is governed by a MIT license that can be * found in the LICENSE file */ import { TM } from './generated/ts'; import { RNOHLogger, TurboModule } from '@rnoh/react-native-openharmony/ts'; import { PickerOptions } from './FileTypes'; import picker from '@ohos.file.picker'; import fileuri from '@ohos.file.fileuri'; import fs, { ReadOptions } from '@ohos.file.fs'; import { BusinessError } from '@kit.BasicServicesKit'; import util from '@ohos.util'; import mime from "mime"; const CANCELED_ERROR_CODE = 'DOCUMENT_PICKER_CANCELED'; const LOGGER_NAME = 'DocumentPicker'; export class DocumentPickerTurboModule extends TurboModule implements TM.RNDocumentPicker.Spec { private documentPicker: picker.DocumentViewPicker = new picker.DocumentViewPicker(); public logger: RNOHLogger; constructor(ctx) { super(ctx); this.logger = this.ctx.logger.clone(LOGGER_NAME) } /* * 获取常量,暂未用到 */ getConstants(): Object { return {}; } /* * 获取要选择文件的后缀 * * @param types 文件后缀列表 eg: [ '.jpeg .jpg .png', '.txt', '.zip .gz' ] * @return 文件后缀列表 [ '.jpeg', '.jpg', '.png', '.txt', '.zip', '.gz' ] */ private getPickerFileSuffix(types: PickerOptions['type']): string[] { let suffixList: string[] = []; if (Array.isArray(types)) { for (const suffix of types) { suffixList.push(...suffix.split(' ')); } } return suffixList; } /* * 获取文件大小 * * @param uri 文件路径 * @return 返回文件大小(byte) */ private async getFileSize(uri: string): Promise<number> { const stat = await fs.stat(uri); return stat.size; } /* * 有传入的copyTo选项,每次会新建UUID目录且文件拷贝到该目录下, dir参数指定是在哪个目录下。 * * @param sourceUri 源文件uri * @param dir 目标文件夹 */ private async copyFileToLocalStorage(sourceUri: fileuri.FileUri, dir: PickerOptions['copyTo']): Promise<string> { const dirPath = dir === 'cachesDirectory' ? this.ctx.uiAbilityContext.cacheDir : this.ctx.uiAbilityContext.filesDir; const destUUIdDir = dirPath + '/' + util.generateRandomUUID(); const destFilePath = `${destUUIdDir}/${sourceUri.name ?? new Date().getTime()}`; await fs.mkdir(destUUIdDir, true); await copyFile(sourceUri.path, destFilePath); return destFilePath; } /* * 根据picker拿到的uri获取文件更多信息:文件大小、文件名、 mimetype等 */ private async parseFileByFileUri(uri: string, copyToDir?: PickerOptions['copyTo']): Promise<TM.RNDocumentPicker.DocumentPickerResponse> { const fUri: fileuri.FileUri = new fileuri.FileUri(uri); const fileSize = await this.getFileSize(fUri.path); const filename = fUri.name; const fileExt = filename.substring(filename.lastIndexOf('.')); const fileMimeType = mime.getType(fileExt); let result: TM.RNDocumentPicker.DocumentPickerResponse; result = { uri: fUri.path, type: fileMimeType, name: filename, size: fileSize, fileCopyUri: null, }; try { if (copyToDir) { result.fileCopyUri = await this.copyFileToLocalStorage(fUri, copyToDir); }; } catch (err) { let e: BusinessError = err; result.copyError = `${e.code} ${e.message}`; } return result; } /* * 根据传入的选择参数调用 documentPicker */ async pick(options: PickerOptions): Promise<TM.RNDocumentPicker.DocumentPickerResponse[]> { try { const pickerOpt = new picker.DocumentSelectOptions(); if (canIUse('SystemCapability.FileManagement.UserFileService.FolderSelection')) { pickerOpt.selectMode = picker.DocumentSelectMode.FILE; pickerOpt.fileSuffixFilters = this.getPickerFileSuffix(options.type); }; // 单选 if (!options.allowMultiSelection) { pickerOpt.maxSelectNumber = 1; }; const pickerRes = await this.documentPicker.select(pickerOpt); // 选择为空:用户取消了 if (!pickerRes.length) { throw new PickCancelError() }; const parseRes = await Promise.allSettled( pickerRes.map(uri => this.parseFileByFileUri(uri, options.copyTo)) ); return parseRes.map(v => v.status === 'fulfilled' && v.value); } catch (err) { this.logger.info(`${err.code} ${err.message}`); if (err instanceof PickCancelError) { console.log(JSON.stringify(err)); throw err; } } } async releaseSecureAccess(_uris: string[]): Promise<void> { return; } async pickDirectory(): Promise<TM.RNDocumentPicker.DirectoryPickerResponse> { const pickerOpt = new picker.DocumentSelectOptions(); if (canIUse('SystemCapability.FileManagement.UserFileService.FolderSelection')) { pickerOpt.selectMode = picker.DocumentSelectMode.FOLDER; }; const pickerRes = await this.documentPicker.select(pickerOpt); return { uri: pickerRes[0] } } public __onDestroy__(): void { this.logger.info('RNDocumentPick destroy!'); } } /* * 流式读写 使用系统fs.copyFile会报错 * * @param source 原文件沙箱路径 * @param dest 目标沙箱路径 */ async function copyFile(source: string, dest: string): Promise<void> { // 打开文件流 let inputStream = fs.createStreamSync(source, 'r+'); let outputStream = fs.createStreamSync(dest, "w+"); // 以流的形式读取源文件内容并写入目的文件 let bufSize = 4096; let readSize = 0; let buf = new ArrayBuffer(bufSize); let readOptions: ReadOptions = { offset: readSize, length: bufSize }; let readLen = await inputStream.read(buf, readOptions); readSize += readLen; while (readLen > 0) { await outputStream.write(buf); readOptions.offset = readSize; readLen = await inputStream.read(buf, readOptions); readSize += readLen; } // 关闭文件流 inputStream.closeSync(); outputStream.closeSync(); } /* * 取消选择抛出错误, 目前rn框架不支持抛出 message以外的属性 */ class PickCancelError extends Error { // code: string = CANCELED_ERROR_CODE; message: string = CANCELED_ERROR_CODE }