diff --git a/README.en.md b/README.en.md index 5bd4195be9e6293a6d696f37aa04da566dea7c02..034bdc909ff0310789d1a3a7a609ca2213ca80f9 100644 --- a/README.en.md +++ b/README.en.md @@ -48,7 +48,6 @@ How to Use ### How to Implement 1. Use the [Remote Communication Kit](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/remote-communication-rcp) capabilities. -2. Reference the link: [RCP-based File Upload and Download](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-rcp-file-transfer). ### Permissions diff --git a/README.md b/README.md index 367a87f61c6905bbf11e513af96bdb1276fbacbe..de0ee0f7837935de4d387023d966fb410a55fc06 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ ### 具体实现 1. 使用[Remote Communication Kit](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/remote-communication-rcp)相关能力。 -2. 参考链接:[基于RCP的文件上传下载开发实践](https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-rcp-file-transfer) ### 相关权限 diff --git a/entry/src/main/ets/pages/BackgroundFileTransfer.ets b/entry/src/main/ets/pages/BackgroundFileTransfer.ets index 02651d6832b9f15b54e2f73909bef9a5e2511ec9..09e6ea32036e073169ff776547ee92dcc6c686c6 100644 --- a/entry/src/main/ets/pages/BackgroundFileTransfer.ets +++ b/entry/src/main/ets/pages/BackgroundFileTransfer.ets @@ -27,8 +27,10 @@ import { copyFileSync, getAlbumAsset, selectImagesFromAlbum } from '../utils/Loc import { ProgressBtn } from '../components/ProgressButton'; import { getProgressPercent, showErrorMessage, showSuccessMessage } from '../utils/CommonUtil'; import { Logger } from '../utils/Logger'; + const uiContext: UIContext | undefined = AppStorage.get('uiContext'); let context = uiContext!.getHostContext()!; + @Component export struct BackgroundFileTransfer { @Consume navPageInfos: NavPathStack; @@ -48,11 +50,9 @@ export struct BackgroundFileTransfer { onUploadProgress: rcp.OnUploadProgress = (totalSize, uploadedSize) => { this.uploadProgress = getProgressPercent(totalSize, uploadedSize); } - onDownloadProgress: rcp.OnDownloadProgress = (totalSize, downloadedSize) => { this.downloadProgress = getProgressPercent(totalSize, downloadedSize); } - downloadToFile: () => Promise = async () => { const image = this.imageList.find(image => image.id === this.selectedRowId); if (!image) return; @@ -74,12 +74,13 @@ export struct BackgroundFileTransfer { this.downloadProgress = 0; } } - upload: () => Promise = async () => { try { const sandboxPaths = await selectImagesFromAlbum(); const filePath = sandboxPaths[0]; - if (!filePath) return; + if (!filePath) { + return; + } const httpEventsHandler: rcp.HttpEventsHandler = { onUploadProgress: this.onUploadProgress, }; @@ -112,19 +113,22 @@ export struct BackgroundFileTransfer { async startBackgroundTask(task: () => Promise) { try { const wantAgentObj = await wantAgent.getWantAgent(this.getWantAgentInfo()); - await backgroundTaskManager.startBackgroundRunning( - context, - backgroundTaskManager.BackgroundMode.DATA_TRANSFER, - wantAgentObj - ); - task().finally(() => { - backgroundTaskManager.stopBackgroundRunning(context); - }); + if (canIUse('SystemCapability.ResourceSchedule.BackgroundTaskManager.Core')) { + await backgroundTaskManager.startBackgroundRunning( + context, + backgroundTaskManager.BackgroundMode.DATA_TRANSFER, + wantAgentObj + ); + task().finally(() => { + backgroundTaskManager.stopBackgroundRunning(context); + }); + } } catch (err) { Logger.error('Failed to start background task', JSON.stringify(err)); showErrorMessage('A background task is running'); } } + // [End back_task] getCustomListData(): CustomListItem[] { @@ -148,6 +152,7 @@ export struct BackgroundFileTransfer { }) } .layoutWeight(1) + if (this.downloadProgress) { ProgressBtn({ progress: this.downloadProgress, diff --git a/entry/src/main/ets/pages/ChunkedFileTransfer.ets b/entry/src/main/ets/pages/ChunkedFileTransfer.ets index 5d86444625974a7236b9a8fc3ad90a214c5834a8..6781bf741f978d5f0962f061384df8221e0f77e9 100644 --- a/entry/src/main/ets/pages/ChunkedFileTransfer.ets +++ b/entry/src/main/ets/pages/ChunkedFileTransfer.ets @@ -15,6 +15,7 @@ import { rcp } from '@kit.RemoteCommunicationKit'; import { fileIo } from '@kit.CoreFileKit'; +import { BusinessError } from '@kit.BasicServicesKit'; import { CustomListItem } from '../components/CustomList'; import { SelectionList } from '../components/SelectionList'; import { chunkedDownload, getFileList } from '../service/FileRequest'; @@ -24,6 +25,8 @@ import { ProgressBtn } from '../components/ProgressButton'; import { getProgressPercent, showErrorMessage, showSuccessMessage } from '../utils/CommonUtil'; import { Logger } from '../utils/Logger'; +const TAG = 'ChunkedFileTransfer'; + @Component export struct ChunkedFileTransfer { @Consume navPageInfos: NavPathStack; @@ -45,7 +48,9 @@ export struct ChunkedFileTransfer { async downloadToFile() { const file = this.fileList.find(image => image.id === this.selectedRowId); - if (!file) return; + if (!file) { + return; + } let filePath = ''; try { filePath = await chunkedDownload(file.name, this.onDownloadProgress); @@ -55,7 +60,9 @@ export struct ChunkedFileTransfer { showErrorMessage('Failed to save to album'); Logger.error(err.message); } finally { - fileIo.unlink(filePath); + fileIo.unlink(filePath).catch((err: BusinessError) => { + Logger.error(TAG, `unlink failed: code: ${err.code}, message: ${err.message}`); + }); this.selectedRowId = ''; this.downloadProgress = 0; } @@ -82,6 +89,7 @@ export struct ChunkedFileTransfer { }) } .layoutWeight(1) + ProgressBtn({ progress: this.downloadProgress, text: $r('app.string.button_text_download_chunked') diff --git a/entry/src/main/ets/pages/ResumableFileTransfer.ets b/entry/src/main/ets/pages/ResumableFileTransfer.ets index 422331363b952029d7b7eee177d8c8938fde3d99..4f9077b88d206520cc698408df3bfefb820a1139 100644 --- a/entry/src/main/ets/pages/ResumableFileTransfer.ets +++ b/entry/src/main/ets/pages/ResumableFileTransfer.ets @@ -15,7 +15,7 @@ import { rcp } from '@kit.RemoteCommunicationKit'; import { fileIo } from '@kit.CoreFileKit'; - +import { BusinessError } from '@kit.BasicServicesKit'; import { CustomListItem } from '../components/CustomList'; import { SelectionList } from '../components/SelectionList'; import { getFileList, ResumableDownloadManager } from '../service/FileRequest'; @@ -23,9 +23,12 @@ import { File } from '../service/Model'; import { getAlbumAsset } from '../utils/LocalFileUtil'; import { ProgressBtn } from '../components/ProgressButton'; import { getProgressPercent, showSuccessMessage } from '../utils/CommonUtil'; +import { Logger } from '../utils/Logger'; +const TAG = 'ResumableFileTransfer'; const uiContext: UIContext | undefined = AppStorage.get('uiContext'); let promptAction = uiContext!.getPromptAction()!; + @Component export struct ResumableFileTransfer { @Consume navPageInfos: NavPathStack; @@ -51,7 +54,9 @@ export struct ResumableFileTransfer { async startDownload() { const file = this.fileList.find(file => file.id === this.selectedRowId); - if (!file) return; + if (!file) { + return; + } this.selectedFile = file; const fileNameExtension = file.name.split('.').pop() || 'png'; this.filePath = await getAlbumAsset(fileNameExtension); @@ -79,7 +84,9 @@ export struct ResumableFileTransfer { this.selectedRowId = ''; this.filePath = ''; this.selectedFile = undefined; - fileIo.unlink(this.filePath); + fileIo.unlink(this.filePath).catch((err: BusinessError) => { + Logger.error(TAG, `unlink failed: code: ${err.code}, message: ${err.message}`); + }); } getCustomListData(): CustomListItem[] { @@ -94,21 +101,29 @@ export struct ResumableFileTransfer { } beforeRowChange: (row: CustomListItem) => Promise = async () => { - if (!this.resumableDownloadManager) return true; - const res = await promptAction.showDialog({ - message: 'Downloading file...Cancel?', - buttons: [ - { - text: 'cancel', - color: '#000000' - }, - { - text: 'confirm', - color: '#0A59F7' - } - ] - }); - if (res.index === 0) return false; + if (!this.resumableDownloadManager) { + return true; + } + try { + const res = await promptAction.showDialog({ + message: 'Downloading file...Cancel?', + buttons: [ + { + text: 'cancel', + color: '#000000' + }, + { + text: 'confirm', + color: '#0A59F7' + } + ] + }); + if (res.index === 0) { + return false; + } + } catch (exception) { + Logger.error(TAG, `unlink failed: code: ${exception.code}, message: ${exception.message}`); + } this.pauseDownload(); this.resetStates(); return true; @@ -125,6 +140,7 @@ export struct ResumableFileTransfer { }) } .layoutWeight(1) + if (this.progress) { ProgressBtn({ progress: this.progress, diff --git a/entry/src/main/ets/service/FileRequest.ets b/entry/src/main/ets/service/FileRequest.ets index f4861c40ba38ea6ba8c4056fc70162d9539f9b1d..fbd266ba2fc2a34c5b6e94c2ae0942787e668d88 100644 --- a/entry/src/main/ets/service/FileRequest.ets +++ b/entry/src/main/ets/service/FileRequest.ets @@ -17,6 +17,7 @@ */ import { rcp } from '@kit.RemoteCommunicationKit'; import { fileIo } from '@kit.CoreFileKit'; +import { BusinessError } from '@kit.BasicServicesKit'; import { BASE_URL } from './Env'; import { File, FileListResponse } from './Model'; import { fileListFormatter } from './Formatter'; @@ -24,6 +25,9 @@ import { StatusCodeInterceptor } from './Interceptor'; import { getChunkRanges } from '../utils/CommonUtil'; import { getSandboxPath } from '../utils/LocalFileUtil'; import { imageExtensions, videoExtensions } from '../constants/Constants'; +import { Logger } from '../utils/Logger'; + +const TAG = 'FileRequest'; // [Start config] function genSessionConfig(httpEventsHandler?: rcp.HttpEventsHandler) { @@ -42,62 +46,88 @@ function genSessionConfig(httpEventsHandler?: rcp.HttpEventsHandler) { }; return config; } + // [End config] export function getFileList(): Promise { - const session = rcp.createSession(genSessionConfig()); - return session.get('~/api/get_file_list').then((res: rcp.Response) => { - const fileListRes: FileListResponse = res.toJSON() as FileListResponse; - const fileList: File[] = fileListFormatter(fileListRes); - return fileList.filter(file => { - const extension = file.name.split('.').pop() || ''; - return imageExtensions.includes(extension) || videoExtensions.includes(extension); + try { + const session = rcp.createSession(genSessionConfig()); + return session.get('~/api/get_file_list').then((res: rcp.Response) => { + const fileListRes: FileListResponse = res.toJSON() as FileListResponse; + const fileList: File[] = fileListFormatter(fileListRes); + return fileList.filter(file => { + const extension = file.name.split('.').pop() || ''; + return imageExtensions.includes(extension) || videoExtensions.includes(extension); + }); + }).finally(() => { + session.close(); }); - }).finally(() => { - session.close(); - }); + } catch (exception) { + Logger.error(TAG, `getFileList failed. Code:${exception.code}, message:${exception.message}`); + return Promise.resolve([]); + } } // [Start get_file] export function getFileSize(fileName: string): Promise { - const session = rcp.createSession(genSessionConfig()); - return session.head(`/${fileName}`).then(res => { - const contentLength = res.headers['content-length']; - return contentLength ? Number(contentLength): 0; - }).finally(() => { - session.close(); - }); + try { + const session = rcp.createSession(genSessionConfig()); + return session.head(`/${fileName}`).then(res => { + const contentLength = res.headers['content-length']; + return contentLength ? Number(contentLength) : 0; + }).finally(() => { + session.close(); + }); + } catch (exception) { + Logger.error(TAG, `getFileSize failed. Code:${exception.code}, message:${exception.message}`); + return Promise.resolve(0); + } } + // [End get_file] // [Start download] export function download(fileName: string, httpEventsHandler: rcp.HttpEventsHandler) { - const destPath = getSandboxPath(fileName); - const rcpSession = rcp.createSession(genSessionConfig(httpEventsHandler)); - const downloadTo: rcp.DownloadToFile = { - kind: 'file', - file: destPath - }; - return rcpSession.downloadToFile(`/${fileName}`, downloadTo) - .then(() => destPath) - .finally(() => { - rcpSession.close(); - }); + try { + const destPath = getSandboxPath(fileName); + const rcpSession = rcp.createSession(genSessionConfig(httpEventsHandler)); + const downloadTo: rcp.DownloadToFile = { + kind: 'file', + file: destPath + }; + return rcpSession.downloadToFile(`/${fileName}`, downloadTo) + .then(() => destPath) + .finally(() => { + rcpSession.close(); + }); + } catch (exception) { + Logger.error(TAG, `download failed. Code:${exception.code}, message:${exception.message}`); + return Promise.resolve(''); + } } + // [End download] // [Start upload_rcp] export function upload(srcPath: string, httpEventsHandler: rcp.HttpEventsHandler) { - const session = rcp.createSession(genSessionConfig(httpEventsHandler)); - const formData = new rcp.MultipartForm({ - file: { - contentOrPath: srcPath - } - }); - return session.post('/', formData).finally(() => { - session.close(); - }); + try { + const session = rcp.createSession(genSessionConfig(httpEventsHandler)); + const formData = new rcp.MultipartForm({ + file: { + contentOrPath: srcPath + } + }); + return session.post('/', formData).catch((err: BusinessError) => { + Logger.error(TAG, `post failed: code: ${err.code}, message: ${err.message}`); + }).finally(() => { + session.close(); + }); + } catch (exception) { + Logger.error(TAG, `upload failed. Code:${exception.code}, message:${exception.message}`); + return; + } } + // [End upload_rcp] export async function chunkedDownload( @@ -106,21 +136,24 @@ export async function chunkedDownload( chunkSize: number = 1024 * 60 ) { const filePath = getSandboxPath(fileName); - const file = fileIo.openSync(filePath, fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE); - const totalSize = await getFileSize(fileName); - const session = rcp.createSession(genSessionConfig()); - let downloaded: number = 0; - for (const range of getChunkRanges(totalSize, chunkSize)) { - const request = new rcp.Request(`/${fileName}`); - const from = range[0]; - const to = range[1]; - request.transferRange = { from, to }; - const response = await session.fetch(request); - fileIo.writeSync(file.fd, response.body, { offset: from }); - downloaded += (to - from + 1); - onProgress(totalSize, downloaded); + try { + const file = fileIo.openSync(filePath, fileIo.OpenMode.WRITE_ONLY | fileIo.OpenMode.CREATE); + const totalSize = await getFileSize(fileName); + const session = rcp.createSession(genSessionConfig()); + let downloaded: number = 0; + for (const range of getChunkRanges(totalSize, chunkSize)) { + const request = new rcp.Request(`/${fileName}`); + const from = range[0]; + const to = range[1]; + request.transferRange = { from, to }; + const response = await session.fetch(request); + fileIo.writeSync(file.fd, response.body, { offset: from }); + downloaded += (to - from + 1); + onProgress(totalSize, downloaded); + } + } catch (exception) { + Logger.error(TAG, `closeSync failed: code: ${exception.code}, message: ${exception.message}`); } - fileIo.closeSync(file); return filePath; } @@ -156,14 +189,19 @@ export class ResumableDownloadManager { this.currentRequest = request; return this.session.fetch(request).then(() => { this.session.close(); - fileIo.close(this.file) + fileIo.close(this.file); + }).catch((err: BusinessError) => { + Logger.error(TAG, + `fetch failed. Code:${err.code}, message:${err.message}`); }); } + // [End start] // [Start pause] pause() { this.session.cancel(this.currentRequest); } + // [End pause] } \ No newline at end of file diff --git a/entry/src/main/ets/utils/CommonUtil.ets b/entry/src/main/ets/utils/CommonUtil.ets index c890022d9269b9d134400a5e7e2e2b7e41051959..c6c44d3c2f56cb7d551120854a52d59999141a18 100644 --- a/entry/src/main/ets/utils/CommonUtil.ets +++ b/entry/src/main/ets/utils/CommonUtil.ets @@ -12,9 +12,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { Logger } from '../utils/Logger'; +const TAG = 'CommonUtil'; const uiContext: UIContext | undefined = AppStorage.get('uiContext'); -let promptAction = uiContext!.getPromptAction()!; export function getChunkRanges(totalSize: number, chunkSize: number): number[][] { const chunkCount = Math.ceil(totalSize / chunkSize); @@ -30,17 +31,25 @@ export function getProgressPercent(totalSize: number, transferredSize: number) { } export function showSuccessMessage(message: string) { - uiContext?.getPromptAction().showToast({ - message: message, - duration: 2000 - }); + try { + uiContext?.getPromptAction().showToast({ + message: message, + duration: 2000 + }); + } catch (exception) { + Logger.error(TAG, `showSuccessMessage failed. Code:${exception.code}, message:${exception.message}`); + } } export function showErrorMessage(errMessage: string) { - uiContext?.getPromptAction().showToast({ - message: errMessage, - backgroundColor: 'rgba(232, 64, 38, 0.15)', - textColor: '#E84026', - duration: 3000 - }); + try { + uiContext?.getPromptAction().showToast({ + message: errMessage, + backgroundColor: 'rgba(232, 64, 38, 0.15)', + textColor: '#E84026', + duration: 3000 + }); + } catch (exception) { + Logger.error(TAG, `showErrorMessage failed. Code:${exception.code}, message:${exception.message}`); + } } \ No newline at end of file diff --git a/entry/src/main/ets/utils/LocalFileUtil.ets b/entry/src/main/ets/utils/LocalFileUtil.ets index eb4c0821e24ea5062d3c79dda30f71f11e51e17c..a6773d1335f02aa2c8b508b7e06e7fa5b6a618cd 100644 --- a/entry/src/main/ets/utils/LocalFileUtil.ets +++ b/entry/src/main/ets/utils/LocalFileUtil.ets @@ -17,7 +17,11 @@ */ import { fileIo, fileUri } from '@kit.CoreFileKit'; import { photoAccessHelper } from '@kit.MediaLibraryKit'; +import { BusinessError } from '@kit.BasicServicesKit'; import { imageExtensions } from '../constants/Constants'; +import { Logger } from '../utils/Logger'; + +const TAG = 'LocalFileUtil'; const uiContext: UIContext | undefined = AppStorage.get('uiContext'); let context = uiContext!.getHostContext()!; @@ -26,22 +30,31 @@ export function getSandboxPath(path: string) { } export function copyFileSync(srcPath: string, destPath: string) { - const srcFile = fileIo.openSync(srcPath, fileIo.OpenMode.READ_ONLY); - const destFile = fileIo.openSync(destPath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE); - fileIo.copyFileSync(srcFile.fd, destFile.fd); - fileIo.closeSync(srcFile); - fileIo.closeSync(destFile); + try { + const srcFile = fileIo.openSync(srcPath, fileIo.OpenMode.READ_ONLY); + const destFile = fileIo.openSync(destPath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE); + fileIo.copyFileSync(srcFile.fd, destFile.fd); + fileIo.closeSync(srcFile); + fileIo.closeSync(destFile); + } catch (exception) { + Logger.error(TAG, `createAsset failed. Code:${exception.code}, message:${exception.message}`); + } } function getPhotoType(fileNameExtension: string) { return imageExtensions.includes(fileNameExtension) ? - photoAccessHelper.PhotoType.IMAGE : - photoAccessHelper.PhotoType.VIDEO; + photoAccessHelper.PhotoType.IMAGE : + photoAccessHelper.PhotoType.VIDEO; } export async function getAlbumAsset(fileNameExtension: string) { const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context); - return await phAccessHelper.createAsset(getPhotoType(fileNameExtension), fileNameExtension); + try { + return await phAccessHelper.createAsset(getPhotoType(fileNameExtension), fileNameExtension); + } catch (exception) { + Logger.error(TAG, `createAsset failed. Code:${exception.code}, message:${exception.message}`); + return ''; + } } // [Start album] @@ -53,7 +66,12 @@ export async function saveImageToAlbum(sandboxPath: string) { photoType: getPhotoType(fileNameExtension), }; const uri: string = fileUri.getUriFromPath(sandboxPath); - const desFileUris: string[] = await phAccessHelper.showAssetsCreationDialog([uri], [photoCreationConfig]); + let desFileUris: string[] = []; + try { + desFileUris = await phAccessHelper.showAssetsCreationDialog([uri], [photoCreationConfig]); + } catch (exception) { + Logger.error(TAG, `showAssetsCreationDialog failed. Code:${exception.code}, message:${exception.message}`); + } const filePath = desFileUris[0]; if (!filePath) { throw new Error('photo assets permission denied'); @@ -61,6 +79,7 @@ export async function saveImageToAlbum(sandboxPath: string) { copyFileSync(sandboxPath, filePath); return filePath; } + // [End album] // [Start from_album] @@ -78,6 +97,10 @@ export async function selectImagesFromAlbum(maxNumber: number = 1): Promise { + Logger.error(TAG, `selectImagesFromAlbum failed. Code:${err.code}, message:${err.message}`); + return []; }); } + // [End from_album]