From f7be917bf412f382a92a0e10b890e9a6e1629159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=81=E6=89=AC?= Date: Thu, 12 Sep 2024 17:39:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=86=E5=B8=83=E5=BC=8F=E9=82=AE=E4=BB=B6?= =?UTF-8?q?=E6=97=A0=E9=9C=80=E7=94=B3=E8=AF=B7datasync=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 +- .../main/ets/entryability/EntryAbility.ets | 228 ++++++++++-------- entry/src/main/ets/pages/MailHomePage.ets | 40 +-- entry/src/main/ets/viewmodel/MailInfo.ets | 73 +++++- entry/src/main/module.json5 | 12 - oh-package.json5 | 1 - 6 files changed, 211 insertions(+), 151 deletions(-) diff --git a/README.md b/README.md index 913d681..9032f09 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 分布式邮件 +# 数据和文件-分布式邮件 ### 简介 @@ -22,9 +22,9 @@ ### 约束与限制 1. 本示例仅支持标准系统上运行,支持设备:华为手机。 -2. HarmonyOS系统:HarmonyOS NEXT Developer Beta1及以上。 -3. DevEco Studio版本:DevEco Studio NEXT Developer Beta1及以上。 -4. HarmonyOS SDK版本:HarmonyOS NEXT Developer Beta1 SDK及以上。 +2. HarmonyOS系统:HarmonyOS NEXT Developer Beta5及以上。 +3. DevEco Studio版本:DevEco Studio NEXT Developer Beta5及以上。 +4. HarmonyOS SDK版本:HarmonyOS NEXT Developer Beta5 SDK及以上。 4. 双端设备需要登录同一华为账号。 5. 双端设备需要打开Wi-Fi和蓝牙开关。条件允许时,建议双端设备接入同一个局域网,可提升数据传输的速度。 6. 应用接续只能在同应用(UIAbility)之间触发,双端设备都需要有该应用。 diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets index 7b7dec4..5d26644 100644 --- a/entry/src/main/ets/entryability/EntryAbility.ets +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -1,122 +1,171 @@ /* - * Copyright (c) 2023 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { abilityAccessCtrl, AbilityConstant, bundleManager, Permissions, UIAbility, Want } from '@kit.AbilityKit'; +* Copyright (c) 2023 Huawei Device Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; import { BusinessError } from '@kit.BasicServicesKit'; -import { distributedDataObject } from '@kit.ArkData'; +import { commonType, distributedDataObject } from '@kit.ArkData'; import { window } from '@kit.ArkUI'; import Logger from '../common/utils/Logger'; import { CommonConstants } from '../common/constants/CommonConstants'; import { AppendixBean } from '../viewmodel/AppendixItem'; import { MailInfo } from '../viewmodel/MailInfo'; +import { fileIo, fileUri } from '@kit.CoreFileKit'; +import { JSON } from '@kit.ArkTS'; export default class EntryAbility extends UIAbility { - private localObject: distributedDataObject.DataObject | undefined = undefined; - private targetDeviceId: string | undefined = undefined; - private changeCall: (sessionId: string, fields: Array) => void = (sessionId: string, fields: Array) => { - if (fields != null && fields != undefined && this.localObject != undefined) { - for (let index: number = 0; index < fields.length; index++) { - if (fields[index] === 'recipient') { - AppStorage.setOrCreate('recipient', this.localObject['recipient'] as string); - } else if (fields[index] === 'sender') { - AppStorage.setOrCreate('sender', this.localObject['sender'] as string); - } else if (fields[index] === 'subject') { - AppStorage.setOrCreate('subject', this.localObject['subject'] as string); - } else if (fields[index] === 'emailContent') { - AppStorage.setOrCreate('emailContent', this.localObject['emailContent'] as string); - } - } - } - } + private distributedObject: distributedDataObject.DataObject | undefined = undefined; onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { Logger.info('EntryAbility', 'Ability onCreate'); - this.restoringData(want, launchParam); + this.restoreDistributedObject(want, launchParam); } onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void { Logger.info('EntryAbility', 'Ability onNewWant'); - this.restoringData(want, launchParam); + this.restoreDistributedObject(want, launchParam); } - async restoringData(want: Want, launchParam: AbilityConstant.LaunchParam): Promise { - this.checkPermissions(); - // Recovering migrated data from want. - if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) { - // get user data from want params. - AppStorage.setOrCreate('isContinuation', CommonConstants.CAN_CONTINUATION); - AppStorage.setOrCreate>('appendix', JSON.parse(want.parameters?.appendix as string) as Array); - let sessionId : string = want.parameters?.distributedSessionId as string; - if (!this.localObject) { - let mailInfo: MailInfo = new MailInfo(undefined, undefined, undefined, undefined); - this.localObject = distributedDataObject.create(this.context, mailInfo); - this.localObject.on('change', this.changeCall); - } - if (sessionId && this.localObject) { - await this.localObject.setSessionId(sessionId); - AppStorage.setOrCreate('recipient', this.localObject['recipient']); - AppStorage.setOrCreate('sender', this.localObject['sender']); - AppStorage.setOrCreate('subject', this.localObject['subject']); - AppStorage.setOrCreate('emailContent', this.localObject['emailContent']); - } - this.context.restoreWindowStage(new LocalStorage()); + /** + * The peer device receives data. + * @param want + * @param launchParam + * @returns + */ + async restoreDistributedObject(want: Want, launchParam: AbilityConstant.LaunchParam): Promise { + if (launchParam.launchReason !== AbilityConstant.LaunchReason.CONTINUATION) { + return; } + AppStorage.setOrCreate('isContinuation', CommonConstants.CAN_CONTINUATION); + AppStorage.setOrCreate>('appendix', JSON.parse(want.parameters?.appendix as string) as Array); + // Obtain the session ID of a distributed data object. + + let mailInfo: MailInfo = new MailInfo(undefined, undefined, undefined, undefined, undefined); + this.distributedObject = distributedDataObject.create(this.context, mailInfo); + // Add a data restored listener. + this.distributedObject.on('status', (sessionId: string, networkId: string, status: 'online' | 'offline' | 'restored') => { + Logger.info('EntryAbility', 'status changed ' + sessionId + ' ' + status + ' ' + networkId); + if (status === 'restored') { + if (!this.distributedObject) { + return; + } + AppStorage.setOrCreate('recipient', this.distributedObject['recipient']); + AppStorage.setOrCreate('sender', this.distributedObject['sender']); + AppStorage.setOrCreate('subject', this.distributedObject['subject']); + AppStorage.setOrCreate('emailContent', this.distributedObject['emailContent']); + AppStorage.setOrCreate('attachments', this.distributedObject['attachments']); + let attachments = this.distributedObject['attachments'] as commonType.Assets; + Logger.info('this.distributedObject[attachments] ' + JSON.stringify(this.distributedObject['attachments'])) + for (const attachment of attachments) { + this.fileCopy(attachment); + } + } + }); + let sessionId : string = want.parameters?.distributedSessionId as string; + Logger.info('sessionId' + sessionId); + this.distributedObject.setSessionId(sessionId); + this.context.restoreWindowStage(new LocalStorage()); } - onContinue(wantParam: Record): AbilityConstant.OnContinueResult { - // The data to be migrated is stored in wantParam. + async onContinue(wantParam: Record): Promise { wantParam.appendix = JSON.stringify(AppStorage.get>('appendix')); try { + // Generate the session ID of the distributed data object. let sessionId: string = distributedDataObject.genSessionId(); - if (this.localObject) { - this.localObject.setSessionId(sessionId); - this.localObject['recipient'] = AppStorage.get('recipient'); - this.localObject['sender'] = AppStorage.get('sender'); - this.localObject['subject'] = AppStorage.get('subject'); - this.localObject['emailContent'] = AppStorage.get('emailContent'); - this.targetDeviceId = wantParam.targetDevice as string; - this.localObject.save(wantParam.targetDevice as string).then(() => { - Logger.info('onContinue localObject save success'); - }).catch((err: BusinessError) => { - Logger.error(`Failed to save. Code:${err.code},message:${err.message}`); - }); - } wantParam.distributedSessionId = sessionId; + + let appendix = AppStorage.get>('appendix'); + let assets: commonType.Assets = []; + if (appendix) { + for (let i = 0; i < appendix.length; i++) { + let append = appendix[i]; + let attachment: commonType.Asset = this.getAssetInfo(append); + assets.push(attachment); + } + } + + let mailInfo: MailInfo = new MailInfo(AppStorage.get('recipient'), AppStorage.get('sender'), + AppStorage.get('subject'), AppStorage.get('emailContent'), assets); + let source = mailInfo.flatAssets(); + this.distributedObject = distributedDataObject.create(this.context, source); + this.distributedObject.setSessionId(sessionId); + await this.distributedObject.save(wantParam.targetDevice as string).then(() => { + Logger.info('onContinue distributedObject save success'); + }).catch((err: BusinessError) => { + Logger.error(`Failed to save. Code:${err.code},message:${err.message}`); + }); } catch (error) { Logger.error('EntryAbility', 'distributedDataObject failed', `code ${(error as BusinessError).code}`); } return AbilityConstant.OnContinueResult.AGREE; } - async checkPermissions(): Promise { - const permissions: Array = ["ohos.permission.DISTRIBUTED_DATASYNC"]; - const accessManager = abilityAccessCtrl.createAtManager(); + /** + * Copy distributed files. + * @param attachmentRecord + * @param key + */ + private fileCopy(attachment: commonType.Asset) { + let filePath: string = this.context.distributedFilesDir + '/' + attachment.name; + let savePath: string = this.context.filesDir + '/' + attachment.name; try { - const bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION; - const bundleInfo = await bundleManager.getBundleInfoForSelf(bundleFlags); - const grantStatus = await accessManager.checkAccessToken(bundleInfo.appInfo.accessTokenId, permissions[0]); - - if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_DENIED) { - accessManager.requestPermissionsFromUser(this.context, permissions); + if (fileIo.accessSync(filePath)) { + let saveFile = fileIo.openSync(savePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE); + let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE); + let buf: ArrayBuffer = new ArrayBuffer(CommonConstants.FILE_BUFFER_SIZE); + let readSize = 0; + let readLen = fileIo.readSync(file.fd, buf, { + offset: readSize + }); + while (readLen > 0) { + readSize += readLen; + fileIo.writeSync(saveFile.fd, buf); + readLen = fileIo.readSync(file.fd, buf, { + offset: readSize + }); + } + fileIo.closeSync(file); + fileIo.closeSync(saveFile); + Logger.info('EntryAbility', attachment.name + 'synchronized successfully.'); } - } catch (err) { - Logger.error('EntryAbility', 'checkPermissions', `Catch err: ${err}`); - return; + } catch (error) { + let err: BusinessError = error as BusinessError; + Logger.error(`DocumentViewPicker failed with err: ${JSON.stringify(err)}`); } } + /** + * Obtain distributed file asset information. + * @param append + * @returns + */ + private getAssetInfo(append: AppendixBean) { + let filePath = getContext().distributedFilesDir + '/' + append.fileName; + fileIo.statSync(filePath); + let uri: string = fileUri.getUriFromPath(filePath); + let stat = fileIo.statSync(filePath); + let attachment: commonType.Asset = { + name: append.fileName, + uri: uri, + path: filePath, + createTime: stat.ctime.toString(), + modifyTime: stat.ctime.toString(), + size: stat.size.toString() + }; + return attachment; + } + onWindowStageCreate(windowStage: window.WindowStage) { // Main window is created, set main page for this ability. Logger.info('EntryAbility', 'Ability onWindowStageCreate'); @@ -129,10 +178,6 @@ export default class EntryAbility extends UIAbility { } Logger.info('EntryAbility', 'Succeeded in loading the content, ', `Data: ${data}`); }); - if (!this.localObject) { - let mailInfo: MailInfo = new MailInfo(undefined, undefined, undefined, undefined); - this.localObject = distributedDataObject.create(this.context, mailInfo); - } } onWindowStageDestroy() { @@ -147,14 +192,7 @@ export default class EntryAbility extends UIAbility { Logger.info('EntryAbility', 'Ability onBackground'); } - async onDestroy() { + onDestroy() { Logger.info('EntryAbility', 'Ability onDestroy'); - if (this.localObject && this.targetDeviceId) { - await this.localObject.save(this.targetDeviceId).then(() => { - Logger.info('onDestroy localObject save success'); - }).catch((err: BusinessError) => { - Logger.error(`Failed to save. Code:${err.code},message:${err.message}`); - }); - } } } diff --git a/entry/src/main/ets/pages/MailHomePage.ets b/entry/src/main/ets/pages/MailHomePage.ets index 3fae487..5bb5994 100644 --- a/entry/src/main/ets/pages/MailHomePage.ets +++ b/entry/src/main/ets/pages/MailHomePage.ets @@ -33,15 +33,10 @@ struct MailHomePage { @StorageLink('isContinuation') isContinuation: string = CommonConstants.NO_CONTINUATION; @State crossEndPicture: PixelMap | undefined = undefined; @StorageLink('appendix') appendix: Array = []; + @StorageLink('attachments') attachments: Object = []; private appContext: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; private distributedPath: string = CommonConstants.MAIL_DISTRIBUTED_PATH; - aboutToAppear() { - if ((this.isContinuation === CommonConstants.CAN_CONTINUATION) && (this.appendix.length >= 1)) { - this.readFile(); - } - } - build() { Column() { this.NavigationTitle() @@ -302,36 +297,6 @@ struct MailHomePage { }) } - /** - * Reading file from distributed file systems. - */ - readFile(): void { - this.appendix.forEach((item: AppendixBean) => { - let filePath: string = this.distributedPath + item.fileName; - let savePath: string = getContext().filesDir + '/' + item.fileName; - try { - while (fileIo.accessSync(filePath)) { - let saveFile = fileIo.openSync(savePath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE); - let file = fileIo.openSync(filePath, fileIo.OpenMode.READ_WRITE); - let buf: ArrayBuffer = new ArrayBuffer(CommonConstants.FILE_BUFFER_SIZE); - let readSize = 0; - let readLen = fileIo.readSync(file.fd, buf, { offset: readSize }); - while (readLen > 0) { - readSize += readLen; - fileIo.writeSync(saveFile.fd, buf); - readLen = fileIo.readSync(file.fd, buf, { offset: readSize }); - } - fileIo.closeSync(file); - fileIo.closeSync(saveFile); - break; - } - } catch (error) { - let err: BusinessError = error as BusinessError; - Logger.error(`DocumentViewPicker failed with err: ${JSON.stringify(err)}`); - } - }); - } - /** * Add appendix from file manager. * @@ -350,7 +315,7 @@ struct MailHomePage { // File name is not supported chinese name. let fileName = file.name; if (!fileName.endsWith(imageIndex[fileType].fileType) || - new RegExp("\[\\u4E00-\\u9FA5]|[\\uFE30-\\uFFA0]", "gi").test(fileName)) { + new RegExp("\[\\u4E00-\\u9FA5]|[\\uFE30-\\uFFA0]", "gi").test(fileName)) { promptAction.showToast({ message: $r('app.string.alert_message_chinese') }) @@ -364,7 +329,6 @@ struct MailHomePage { readSize += readLen; fileIo.writeSync(destination.fd, buf); fileIo.writeSync(destinationDistribute.fd, buf); - console.info(destinationDistribute.path); readLen = fileIo.readSync(file.fd, buf, { offset: readSize }); } fileIo.closeSync(file); diff --git a/entry/src/main/ets/viewmodel/MailInfo.ets b/entry/src/main/ets/viewmodel/MailInfo.ets index 2517b8e..38bbdd1 100644 --- a/entry/src/main/ets/viewmodel/MailInfo.ets +++ b/entry/src/main/ets/viewmodel/MailInfo.ets @@ -1,13 +1,84 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License,Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { commonType } from '@kit.ArkData'; + export class MailInfo { private recipient: string | undefined; private sender: string | undefined; private subject: string | undefined; private emailContent: string | undefined; + private attachments: commonType.Assets | undefined; - constructor(recipient: string | undefined, sender: string | undefined, subject: string | undefined, emailContent: string | undefined) { + constructor(recipient: string | undefined, sender: string | undefined, subject: string | undefined, + emailContent: string | undefined, attachments: commonType.Assets | undefined) { this.recipient = recipient; this.sender = sender; this.subject = subject; this.emailContent = emailContent; + this.attachments = attachments; + } + + flatAssets(): object { + let obj: object = this; + if (!this.attachments) { + return obj; + } + for (let i = 0; i < this.attachments.length; i++) { + obj[`attachments${i}`] = this.attachments[i]; + } + return obj; + } + + getRecipient(): string | undefined { + return this.recipient; + } + + setRecipient(value: string | undefined) { + this.recipient = value; + } + + getSender(): string | undefined { + return this.sender; + } + + setSender(value: string | undefined) { + this.sender = value; + } + + getSubject(): string | undefined { + return this.subject; + } + + setSubject(value: string | undefined) { + this.subject = value; + } + + getEmailContent(): string | undefined { + return this.emailContent; + } + + setEmailContent(value: string | undefined) { + this.emailContent = value; + } + + getAttachments(): commonType.Assets | undefined { + return this.attachments; + } + + setAttachments(value: commonType.Assets) { + this.attachments = value; } } \ No newline at end of file diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 index c6d1813..635c303 100644 --- a/entry/src/main/module.json5 +++ b/entry/src/main/module.json5 @@ -35,18 +35,6 @@ "continuable": true, "launchType": "singleton" } - ], - "requestPermissions": [ - { - "name": "ohos.permission.DISTRIBUTED_DATASYNC", - "reason": "$string:distributed_data_sync", - "usedScene": { - "abilities": [ - "EntryAbility" - ], - "when": "inuse" - } - } ] } } \ No newline at end of file diff --git a/oh-package.json5 b/oh-package.json5 index a3cb131..0cd9ae6 100644 --- a/oh-package.json5 +++ b/oh-package.json5 @@ -2,7 +2,6 @@ "modelVersion": "5.0.0", "license": "", "devDependencies": { - "@ohos/hypium": "1.0.15" }, "author": "", "name": "distributedmail", -- Gitee