diff --git a/README.en.md b/README.en.md index f92fdd91f470978cde71b9f4e107c8931450884a..337ccac4523c33bd255fb053f8b01048756d5f92 100644 --- a/README.en.md +++ b/README.en.md @@ -54,7 +54,7 @@ This example demonstrates how to access and copy files across multiple devices. ## Specific Implementation -1. Obtain the sandbox path of the current application's distributed shared directory using [getUIContext()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-custom-component-api#getuicontext)Then obtain [getHostContext()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-arkui-uicontext#gethostcontext12) +1. Obtain the sandbox path of the current application's distributed shared directory using [getUIContext()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-custom-component-api#getuicontext)Then obtain [getHostContext()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-uicontext-uicontext#gethostcontext12) Subsequently, access the distributedFilesDir distributed shared directory. 2. Create files through [fs.openSync()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsopensync)Open files or directories using synchronization method, and then use [fs.riteSync()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fswritesync )Write data to a file using a synchronous method, and finally use [fs.closeSync()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsclosesync)Close the file or directory using the synchronization method to create it successfully. 3. Access local files through [fs.listFile()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fslistfile)List all files in the local filesDir sandbox path directory. @@ -75,8 +75,8 @@ This example demonstrates how to access and copy files across multiple devices. ## Constraints and limitations 1. This example only supports running on standard systems and supports devices such as Huawei phones. -2. HarmonyOS system: HarmonyOS 5.1.0 Release and above. -3. DevEco Studio version: DevEco Studio 5.1.0 Release and above. -4. HarmonyOS SDK version: HarmonyOS 5.1.0 Release SDK or above. +2. HarmonyOS system: HarmonyOS 5.1.1 Release and above. +3. DevEco Studio version: DevEco Studio 5.1.1 Release and above. +4. HarmonyOS SDK version: HarmonyOS 5.1.1 Release SDK or above. 5. Dual end devices need to log in to the same Huawei account. 6. Dual end devices require Wi Fi and Bluetooth switches to be turned on. When conditions permit, it is recommended that both devices be connected to the same local area network to improve data transmission speed. \ No newline at end of file diff --git a/README.md b/README.md index a0a38298b9c2da99471bdedc2803e80bac14a2ff..c813808aa9b30377abb4a568e0c5143d94b3fe12 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,14 @@ ## 使用说明 -1. 首页填写文件名和文件内容,点击“确定”按钮创建文件,若需要修改填写内容,点击“清空”按钮。 +1. 首页填写文件名和文件内容,点击“确定”按钮创建文件,若需要修改填写内容,点击“清除”按钮。 2. 创建成功后,点击“远端文件”按钮,查看已创建好的文件。 -3. 远端文件列表列表: +3. 远端文件列表: - 右上角打开编辑状态,选择某一项文件,可在底部进行删除,或拷贝复制操作。左上角可取消编辑状态。 - 点击任意一项文件,进入预览界面。 -4. 本地文件列表列表:页面功能和远端文件列表大致相同,区别在于: +4. 本地文件列表:页面功能和远端文件列表大致相同,区别在于: - 本地文件需要在远端文件列表中进行拷贝复制到本地。 - - 编辑状态下无拷贝按钮。 + - 编辑状态下无复制按钮。 5. 预览界面: - 点击编辑区域,自动进入编辑状态。 - 右上角对钩为编辑后保存按钮。 @@ -51,7 +51,7 @@ ## 具体实现 -1. 获取当前应用分布式共享目录沙箱路径,通过[getUIContext()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-custom-component-api#getuicontext)然后获取[getHostContext()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-arkui-uicontext#gethostcontext12) +1. 获取当前应用分布式共享目录沙箱路径,通过[getUIContext()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-custom-component-api#getuicontext)然后获取[getHostContext()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-uicontext-uicontext#gethostcontext12) ,随后访问distributedFilesDir分布式共享目录。 2. 创建文件通过[fs.openSync()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsopensync)以同步方法打开文件或目录,然后通过[fs.writeSync()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fswritesync)以同步方法将数据写入文件,最后通过[fs.closeSync()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fsclosesync)以同步方法关闭文件或目录,即可创建成功。 3. 访问本地文件,通过[fs.listFile()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-file-fs#fslistfile)列出本地filesDir沙箱路径目录下所有文件。 @@ -72,8 +72,8 @@ ## 约束与限制 1. 本示例仅支持标准系统上运行,支持设备:华为手机。 -2. HarmonyOS系统:HarmonyOS 5.1.0 Release及以上。 -3. DevEco Studio版本:DevEco Studio 5.1.0 Release及以上。 -4. HarmonyOS SDK版本:HarmonyOS 5.1.0 Release SDK及以上。 +2. HarmonyOS系统:HarmonyOS 5.1.1 Release及以上。 +3. DevEco Studio版本:DevEco Studio 5.1.1 Release及以上。 +4. HarmonyOS SDK版本:HarmonyOS 5.1.1 Release SDK及以上。 5. 双端设备需要登录同一华为账号。 6. 双端设备需要打开Wi-Fi和蓝牙开关。条件允许时,建议双端设备接入同一个局域网,可提升数据传输的速度。 diff --git a/entry/src/main/ets/components/DeleteDialog.ets b/entry/src/main/ets/components/DeleteDialog.ets index b3269a5ba3ebe746d6a56285451ca15b3ba29676..f3d527b2fb3564b9ba3c24ba4bbaed05837a7f68 100644 --- a/entry/src/main/ets/components/DeleteDialog.ets +++ b/entry/src/main/ets/components/DeleteDialog.ets @@ -1,13 +1,13 @@ /* * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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. diff --git a/entry/src/main/ets/components/EditTitleBar.ets b/entry/src/main/ets/components/EditTitleBar.ets index 2c0e06a2f1b1e49f9b298967ce3258ba02eba85a..8276ec0716770f9dd689cff7d5de1a8b2a095cc7 100644 --- a/entry/src/main/ets/components/EditTitleBar.ets +++ b/entry/src/main/ets/components/EditTitleBar.ets @@ -1,13 +1,13 @@ /* * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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. diff --git a/entry/src/main/ets/components/ListComp.ets b/entry/src/main/ets/components/ListComp.ets index d3b9b70a9f5e294a1a603953ed6c392716bf2b30..5aadf243c4b0642a9d90f004a129ad9e45801a58 100644 --- a/entry/src/main/ets/components/ListComp.ets +++ b/entry/src/main/ets/components/ListComp.ets @@ -1,13 +1,13 @@ /* * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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. diff --git a/entry/src/main/ets/components/Loading.ets b/entry/src/main/ets/components/Loading.ets index f5aef628433442295bc6f58eba8c2f0cc59aad6a..d5f7bbc30826a714a9ca19669632de9716e54b3c 100644 --- a/entry/src/main/ets/components/Loading.ets +++ b/entry/src/main/ets/components/Loading.ets @@ -1,13 +1,13 @@ /* * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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. diff --git a/entry/src/main/ets/components/ProgressDialog.ets b/entry/src/main/ets/components/ProgressDialog.ets index 5357e9ef8176c4f97e3bdda26d4ce3c7a99e18c8..cadbbd8ed2832a161be8f5d76a281e68f046c416 100644 --- a/entry/src/main/ets/components/ProgressDialog.ets +++ b/entry/src/main/ets/components/ProgressDialog.ets @@ -1,13 +1,13 @@ /* * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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. diff --git a/entry/src/main/ets/components/TitleBar.ets b/entry/src/main/ets/components/TitleBar.ets index 97817a784630bab7b38e4e9b5e10893995ca673c..421b9af0c19fed4b74768059dbc0517b3617b44a 100644 --- a/entry/src/main/ets/components/TitleBar.ets +++ b/entry/src/main/ets/components/TitleBar.ets @@ -1,13 +1,13 @@ /* * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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. @@ -15,10 +15,13 @@ import { fileIo as fs, WriteOptions } from '@kit.CoreFileKit'; import CommonUtils from '../utils/CommonUtils'; import { TitleBarDeleteDialog } from './TitleBarDeleteDialog'; +import { util } from '@kit.ArkTS'; import { hilog } from '@kit.PerformanceAnalysisKit'; +// [Start save_file] @Component export struct TitleBar { + // [StartExclude save_file] @State uiContext: UIContext | undefined = AppStorage.get('uiContext'); @Consume('pageInfo') pageStack: NavPathStack; @Link isEdit: boolean; @@ -50,21 +53,26 @@ export struct TitleBar { this.oldFileContent = this.fileContent; } + // [EndExclude save_file] + saveFile() { try { const file = fs.openSync(this.filePath + `/${this.fileTile}`, - fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC); + fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC); // Open files in read-write mode const writeOptions: WriteOptions = { offset: 0, // Key point: Reset write position - length: this.fileContent.length }; - let writeLen = fs.writeSync(file.fd, this.fileContent, writeOptions); + const textEncoder = new util.TextEncoder(); // Create an instance of TextEncoder and specify UTF-8 encoding + const encodedData = textEncoder.encodeInto(this.fileContent); // Convert a string to Uint8Array in UTF-8 format + let writeLen = fs.writeSync(file.fd, encodedData.buffer, writeOptions); // write file fs.fsyncSync(file.fd); if (writeLen) { - fs.closeSync(file); + fs.closeSync(file); // close file CommonUtils.showToast($r('app.string.successfully_saved'), this.uiContext!); - this.pageStack.pop(); + this.pageStack.pop(); // Back to previous level + // [StartExclude save_file] this.isDeleteChange = !this.isDeleteChange; + // [EndExclude save_file] } } catch (error) { hilog.error(0xFF00, 'error', '%{public}s', 'have errors', `${JSON.stringify(error)}`); @@ -72,6 +80,7 @@ export struct TitleBar { } build() { + // [StartExclude save_file] Row() { Row() { Row() { @@ -146,5 +155,9 @@ export struct TitleBar { } .width('100%') .justifyContent(FlexAlign.SpaceBetween) + + // [EndExclude save_file] } -} \ No newline at end of file +} + +// [End save_file] \ No newline at end of file diff --git a/entry/src/main/ets/components/TitleBarDeleteDialog.ets b/entry/src/main/ets/components/TitleBarDeleteDialog.ets index 9c2b4ba857bdff6c395bff8ed7cddc1c26d37f76..784bb62c1abd8c842fb88820ad328a7240803c2b 100644 --- a/entry/src/main/ets/components/TitleBarDeleteDialog.ets +++ b/entry/src/main/ets/components/TitleBarDeleteDialog.ets @@ -1,13 +1,13 @@ /* * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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. diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets index 6da9648479ca325a1afc48c710e792507abc6f45..8f6c3a6a26a8a9237abd970a1a451d4bbe22ac2a 100644 --- a/entry/src/main/ets/entryability/EntryAbility.ets +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -1,19 +1,19 @@ /* * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { window } from '@kit.ArkUI'; @@ -22,7 +22,6 @@ let uiContext: UIContext | undefined = undefined; export default class EntryAbility extends UIAbility { onCreate(_want: Want, _launchParam: AbilityConstant.LaunchParam): void { - this.permissions(); try { this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); @@ -32,10 +31,6 @@ export default class EntryAbility extends UIAbility { } - onNewWant(_want: Want, _launchParam: AbilityConstant.LaunchParam): void { - this.permissions(); - } - onDestroy(): void { hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); } @@ -69,27 +64,4 @@ export default class EntryAbility extends UIAbility { // Ability has back to background hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); } - - /** - * Apply for the permission to exchange data between different devices. - */ - async permissions() { - try { - let atManager = abilityAccessCtrl.createAtManager(); - const result = await atManager.requestPermissionsFromUser(this.context, ['ohos.permission.DISTRIBUTED_DATASYNC']); - - if (result.authResults[0] !== -1) { - uiContext!.getPromptAction().showToast({ message: $r('app.string.authorization_successful') }); - } - - const resultAgain = - await atManager.requestPermissionOnSetting(this.context, ['ohos.permission.DISTRIBUTED_DATASYNC']); - if (resultAgain[0] === 0) { - uiContext!.getPromptAction().showToast({ message: $r('app.string.authorization_successful') }); - } - uiContext!.getPromptAction().showToast({ message: $r('app.string.reauthorization') }); - } catch (error) { - hilog.error(0xFF00, 'error', '%{public}s', 'have errors', `${JSON.stringify(error)}`); - } - } } \ No newline at end of file diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index 08408bdc2949354609110acc2888da5d28dc51e0..7b70e34f3add83d4b9920ffd77426ac66b884800 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -12,28 +12,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { common } from '@kit.AbilityKit'; +// [Start import_common] +import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit'; + +// [StartExclude import_common] +// [Start import_file] import { fileIo as fs } from '@kit.CoreFileKit'; +// [StartExclude import_file] import CommonUtils from '../utils/CommonUtils'; import { hilog } from '@kit.PerformanceAnalysisKit'; +// [EndExclude import_common] +// [EndExclude import_file] + +// [Start create_file] @Entry @Component struct Index { + // [StartExclude import_common] + // [StartExclude create_file] + // [StartExclude import_file] @Provide('pageInfo') pageStack: NavPathStack = new NavPathStack(); @Provide('isDeleteChange') isDeleteChange: boolean = false; + @State atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); @State uiContext: UIContext | undefined = AppStorage.get('uiContext'); + // [EndExclude import_common] @State context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext; + // [StartExclude import_common] @State fileTile: string = ''; @State fileContent: string = ''; + // [EndExclude import_file] + // [EndExclude create_file] + createFile() { + // [StartExclude import_file] + // [StartExclude create_file] if (this.fileTile === '' && this.fileContent === '') { CommonUtils.showToast($r('app.string.file_name_and_file_content_cannot_be_empty'), this.uiContext!); return; } + // [EndExclude import_file] // Get the file path of the distributed directory let pathDir: string = this.context.distributedFilesDir; + // [EndExclude create_file] + // [StartExclude import_file] let filePath: string = pathDir + `/${this.fileTile}.txt`; try { // Create files in a distributed directory @@ -43,20 +66,76 @@ struct Index { fs.writeSync(file.fd, this.fileContent); // close file fs.closeSync(file.fd); + // [StartExclude create_file] CommonUtils.showToast($r('app.string.created_successfully'), this.uiContext!); this.fileTile = ''; this.fileContent = ''; + // [EndExclude create_file] } catch (error) { hilog.error(0x0000, 'Index', '%{public}s', 'Failed to openSync / writeSync / closeSync. Code: $\{error.code}, message: $\{error.message}'); } + // [EndExclude import_file] } + // [StartExclude create_file] + // [StartExclude import_file] clear() { this.fileTile = ''; this.fileContent = ''; } + onPageShow(): void { + const permissionResult = this.checkPermissions(['ohos.permission.DISTRIBUTED_DATASYNC']); + if (permissionResult) { + return; + } else { + this.permissions(); + } + } + + /** + * Apply for the permission to exchange data between different devices. + */ + async permissions() { + try { + const result = + await this.atManager.requestPermissionsFromUser(this.context, ['ohos.permission.DISTRIBUTED_DATASYNC']); + + if (result.authResults[0] !== -1) { + this.uiContext!.getPromptAction().showToast({ message: $r('app.string.authorization_successful') }); + } + + const resultAgain = + await this.atManager.requestPermissionOnSetting(this.context, ['ohos.permission.DISTRIBUTED_DATASYNC']); + if (resultAgain[0] === 0) { + this.uiContext!.getPromptAction().showToast({ message: $r('app.string.authorization_successful') }); + } else { + this.uiContext!.getPromptAction().showToast({ message: $r('app.string.reauthorization') }); + } + } catch (error) { + hilog.error(0xFF00, 'error', '%{public}s', 'have errors', `${JSON.stringify(error)}`); + } + } + + /** + * Check if authorized + * @param permissions Permission array + * @returns result + */ + checkPermissions(permissions: Permissions[]) { + // Get bundle information + let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION; + + const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags); + // Extract tokenID identifier + const tokenID = bundleInfo.appInfo.accessTokenId; + // Verify if the application has been granted permission + const authResults = permissions.map((item) => this.atManager.checkAccessTokenSync(tokenID, item)); + // Return whether the result has been authorized or not + return authResults.every(v => v === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED); + } + build() { Navigation(this.pageStack) { Column({ space: 12 }) { @@ -173,4 +252,12 @@ struct Index { .height('100%') .width('100%') } -} \ No newline at end of file + + // [EndExclude import_common] + // [EndExclude import_file] + // [EndExclude create_file] +} + +// [End import_common] +// [End import_file] +// [End create_file] \ No newline at end of file diff --git a/entry/src/main/ets/utils/CommonUtils.ets b/entry/src/main/ets/utils/CommonUtils.ets index 701165020496688ce037dd38a92caa196f815a4f..f0e23b4c5c798281f0fb0d036d6e41956d185c16 100644 --- a/entry/src/main/ets/utils/CommonUtils.ets +++ b/entry/src/main/ets/utils/CommonUtils.ets @@ -1,22 +1,24 @@ /* * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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 { hilog } from "@kit.PerformanceAnalysisKit"; +import { hilog } from '@kit.PerformanceAnalysisKit'; import { fileIo as fs } from '@kit.CoreFileKit'; -import { BusinessError } from "@kit.BasicServicesKit"; +import { BusinessError } from '@kit.BasicServicesKit'; +// [Start delete_file] class CommonUtils { + // [StartExclude delete_file] public showToast(message: ResourceStr, uiContext: UIContext) { try { uiContext.getPromptAction().showToast({ message }); @@ -25,12 +27,14 @@ class CommonUtils { } }; + // [EndExclude delete_file] + public delete(selectedValue: string, pathDir: string, uiContext: UIContext, callback: (result: boolean) => void) { - const srcUri = pathDir + `/${selectedValue}`; + const srcUri = pathDir + `/${selectedValue}`; // Splicing the sandbox path of the required file fs.unlink(srcUri, (err: BusinessError) => { if (err) { hilog.error(0x0000, 'RemoteFile', '%{public}s', - ("remove file failed with error message: " + err.message + ", error code: " + err.code)); + ('remove file failed with error message: ' + err.message + ', error code: ' + err.code)); callback(false); } else { this.showToast($r('app.string.delete_successfully'), uiContext!); @@ -41,3 +45,5 @@ class CommonUtils { } export default new CommonUtils(); + +// [End delete_file] diff --git a/entry/src/main/ets/view/LocalFileList.ets b/entry/src/main/ets/view/LocalFileList.ets index da5cf050c09b36274bfa4f23281c5643a834f966..2e98ea28ef87d2fe8a8f0de1403b7d47537921f3 100644 --- a/entry/src/main/ets/view/LocalFileList.ets +++ b/entry/src/main/ets/view/LocalFileList.ets @@ -1,13 +1,13 @@ /* * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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. @@ -15,7 +15,6 @@ import { fileIo as fs } from '@kit.CoreFileKit'; import { common } from '@kit.AbilityKit'; import CommonUtils from '../utils/CommonUtils'; -import { BusinessError } from '@kit.BasicServicesKit'; import { ListComp } from '../components/ListComp'; import { Loading } from '../components/Loading'; import { EditTitleBar } from '../components/EditTitleBar'; @@ -62,6 +61,7 @@ export struct LocalFileList { this.getFileList(); } + // [Start get_file] async getFileList() { this.isShowDialog = true; // Get the file path of the local directory @@ -72,14 +72,18 @@ export struct LocalFileList { } catch (error) { hilog.error(0xFF00, 'error', '%{public}s', 'have errors', `${JSON.stringify(error)}`); } + // [StartExclude get_file] if (this.localFileListArr.length > 0) { this.isShowDialog = false; } else { this.isEdit = false; this.isShowDialog = false; } + // [EndExclude get_file] } + // [End get_file] + delete() { if (!this.selectedValue) { CommonUtils.showToast($r('app.string.Please_select_a_file'), this.uiContext!); diff --git a/entry/src/main/ets/view/Preview.ets b/entry/src/main/ets/view/Preview.ets index 0a7c814ca8324d5563537273c4734487d04667cd..badb16d59c87dfd1a2b083be6720134b138a7ae9 100644 --- a/entry/src/main/ets/view/Preview.ets +++ b/entry/src/main/ets/view/Preview.ets @@ -1,13 +1,13 @@ /* * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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. @@ -27,8 +27,10 @@ export function PreviewBuild() { } +// [Start preview] @Component export struct Preview { + // [StartExclude preview] @State uiContext: UIContext | undefined = AppStorage.get('uiContext'); @Consume('pageInfo') pageStack: NavPathStack; controller: TextInputController = new TextInputController(); @@ -39,23 +41,29 @@ export struct Preview { @State isEdit: boolean = false; @Consume('isDeleteChange') isDeleteChange: boolean; + // [EndExclude preview] + aboutToAppear(): void { const param = this.pageStack.getParamByName('Preview')[0] as Param; - this.fileTile = param.fileName; - this.filePath = param.path; - this.targetPath = this.filePath + `/${this.fileTile}`; + this.fileTile = param.fileName; // Incoming file name + this.filePath = param.path; // The corresponding file sandbox path passed in + this.targetPath = this.filePath + `/${this.fileTile}`; //Splicing into the file path to be read try { - this.fileContent = fs.readTextSync(this.targetPath); + this.fileContent = fs.readTextSync(this.targetPath); //Read the file, fileContent is the content of the read file } catch (error) { hilog.error(0xFF00, 'error', '%{public}s', 'have errors', `${JSON.stringify(error)}`); } } + // [StartExclude preview] stopEdit() { this.controller.stopEditing(); } + // [EndExclude preview] + build() { + // [StartExclude preview] NavDestination() { Column({ space: 12 }) { TitleBar({ @@ -109,5 +117,9 @@ export struct Preview { .justifyContent(FlexAlign.Start) } .hideTitleBar(true) + + // [EndExclude preview] } } + +// [End preview] diff --git a/entry/src/main/ets/view/RemoteFileList.ets b/entry/src/main/ets/view/RemoteFileList.ets index aaa0ba907d8e21050f4063eced0f38367fdb266f..85f299a93ee3d6357386fe2daf5a977e4bc0f8af 100644 --- a/entry/src/main/ets/view/RemoteFileList.ets +++ b/entry/src/main/ets/view/RemoteFileList.ets @@ -1,28 +1,28 @@ /* * Copyright (c) 2025 Huawei Device Co., Ltd. - * Licensed under the Apache License, Version 2.0 (the "License"); + * 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, + * 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 { distributedDeviceManager } from "@kit.DistributedServiceKit"; -import { fileIo as fs, fileUri } from '@kit.CoreFileKit'; -import { common } from "@kit.AbilityKit"; -import { BusinessError } from "@kit.BasicServicesKit"; +import { distributedDeviceManager } from '@kit.DistributedServiceKit'; +import { fileUri, fileIo as fs } from '@kit.CoreFileKit'; +import { common } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; import CommonUtils from '../utils/CommonUtils'; -import { ProgressDialog } from "../components/ProgressDialog"; -import { ListComp } from "../components/ListComp"; -import { Loading } from "../components/Loading"; -import { EditTitleBar } from "../components/EditTitleBar"; -import { DeleteDialog } from "../components/DeleteDialog"; -import { hilog } from "@kit.PerformanceAnalysisKit"; +import { ProgressDialog } from '../components/ProgressDialog'; +import { ListComp } from '../components/ListComp'; +import { Loading } from '../components/Loading'; +import { EditTitleBar } from '../components/EditTitleBar'; +import { DeleteDialog } from '../components/DeleteDialog'; +import { hilog } from '@kit.PerformanceAnalysisKit'; @Builder @@ -30,8 +30,12 @@ export function RemoteFileListBuild() { RemoteFileList(); } +// [Start getRemoteFileList] +// [Start copy_file] @Component export struct RemoteFileList { + // [StartExclude getRemoteFileList] + // [StartExclude copy_file] @Consume('pageInfo') pageStack: NavPathStack; @State uiContext: UIContext | undefined = AppStorage.get('uiContext'); @State context: Context = this.getUIContext().getHostContext() as common.UIAbilityContext; @@ -44,6 +48,12 @@ export struct RemoteFileList { @State value: number = 0; @State total: number = 0; @State networkId: string = ''; + @State connectedNetworkIds: string[] = []; + listeners: fs.DfsListeners = { + onStatus: (_networkId: string, _status: number): void => { + hilog.info(0x0000, 'RemoteFile', '%{public}s', 'Failed to access public directory'); + } + }; ProgressDialog: CustomDialogController = new CustomDialogController({ builder: ProgressDialog({ value: this.value, @@ -77,47 +87,86 @@ export struct RemoteFileList { this.getRemoteFileList(); } - aboutToDisappear(): void { - fs.disconnectDfs(this.networkId).then(() => { - hilog.info(0x0000, 'RemoteFile', '%{public}s', 'Success to disconnect dfs'); + syncDevice(deviceInfoList: Array) { + // Create connection promises for each device information + const connectPromises = deviceInfoList.map(deviceInfo => { + return fs.connectDfs(deviceInfo.networkId, this.listeners) + .then(() => { + if (!this.connectedNetworkIds.includes(deviceInfo.networkId!)) { + this.connectedNetworkIds.push(deviceInfo.networkId!); + } + hilog.info(0x0000, 'RemoteFile', '%{public}s', + `Success to connect dfs for network ${deviceInfo.networkId}`); + }) + .catch((error: BusinessError) => { + hilog.error(0x0000, 'RemoteFile', '%{public}s', + `Failed to connect dfs for network ${deviceInfo.networkId}. Code: ${error.code}, message: ${error.message}`); + }); + }); + + // Wait for all connection operations to complete + Promise.all(connectPromises).then(async () => { + // Process data after all connection operations are completed + this.pathDir = this.context.distributedFilesDir; + this.remoteFileListArr = await fs.listFile(this.pathDir); + + if (this.remoteFileListArr.length > 0) { + this.isShowDialog = false; + } else { + this.isShowDialog = false; + this.isEdit = false; + } }).catch((error: BusinessError) => { - let err: BusinessError = error as BusinessError; hilog.error(0x0000, 'RemoteFile', '%{public}s', - `Failed to disconnect dfs. Code: ${err.code}, message: ${err.message}`); + `Error in connection process. Code: ${error.code}, message: ${error.message}`); }); } + async disconnectAllDfs() { + // If there are no connected devices, return directly + if (this.connectedNetworkIds.length === 0) { + hilog.info(0x0000, 'RemoteFile', '%{public}s', 'No connected devices to disconnect'); + return; + } + + // Perform a disconnection operation on each connected device and generate a disconnection Promise array + const disconnectPromises = this.connectedNetworkIds.map(networkId => { + return fs.disconnectDfs(networkId) + .then(() => { + hilog.info(0x0000, 'RemoteFile', '%{public}s', + `Success to disconnect dfs for network ${networkId}`); + }) + .catch((error: BusinessError) => { + const err: BusinessError = error as BusinessError; + hilog.error(0x0000, 'RemoteFile', '%{public}s', + `Failed to disconnect dfs for network ${networkId}. Code: ${err.code}, message: ${err.message}`); + }); + }); + + await Promise.all(disconnectPromises); + + // Clear the list of connected devices + this.connectedNetworkIds = []; + hilog.info(0x0000, 'RemoteFile', '%{public}s', 'All connected devices disconnect process completed'); + } + + aboutToDisappear(): void { + this.disconnectAllDfs(); + } + + // [EndExclude getRemoteFileList] + // Retrieve the file list in the remote distributed directory async getRemoteFileList() { this.isShowDialog = true; - let dmInstance = distributedDeviceManager.createDeviceManager("com.example.crossDeviceFileOperation"); + let dmInstance = distributedDeviceManager.createDeviceManager('com.example.crossDeviceFileOperation'); try { let deviceInfoList: Array = dmInstance.getAvailableDeviceListSync(); if (deviceInfoList && deviceInfoList.length > 0) { this.networkId = deviceInfoList[0].networkId!; - // Define callbacks for accessing public file directories - let listeners: fs.DfsListeners = { - onStatus: (_networkId: string, _status: number): void => { - hilog.info(0x0000, 'RemoteFile', '%{public}s', 'Failed to access public directory'); - } - }; // Start cross device file access - await fs.connectDfs(this.networkId, listeners).then(async () => { - this.pathDir = this.context.distributedFilesDir; - this.remoteFileListArr = await fs.listFile(this.pathDir); - - if (this.remoteFileListArr.length > 0) { - this.isShowDialog = false; - } else { - this.isShowDialog = false; - this.isEdit = false; - } - }).catch((error: BusinessError) => { - let err: BusinessError = error as BusinessError; - hilog.error(0x0000, 'RemoteFile', '%{public}s', - `Failed to connect dfs. Code: ${err.code}, message: ${err.message}`); - }); + this.syncDevice(deviceInfoList); } else { CommonUtils.showToast($r('app.string.no_devices_currently_available'), this.uiContext!); this.isShowDialog = false; @@ -127,18 +176,25 @@ export struct RemoteFileList { } } + // [EndExclude copy_file] + + // [StartExclude getRemoteFileList] // Copy files copyFile() { if (!this.selectedValue) { CommonUtils.showToast($r('app.string.Please_select_a_file'), this.uiContext!); } else { this.ProgressDialog.open(); - const srcUri = fileUri.getUriFromPath(this.pathDir + `/${this.selectedValue}`); - const destUri = fileUri.getUriFromPath(this.context.filesDir + `/${this.selectedValue}`); - + const srcUri = fileUri.getUriFromPath(this.pathDir + `/${this.selectedValue}`); // To copy file path + const destUri = fileUri.getUriFromPath(this.context.filesDir + `/${this.selectedValue}`); // Target file path + const existingPath = this.context.filesDir + `/${this.selectedValue}`; + let res = fs.accessSync(existingPath); + if (res) { + fs.unlinkSync(existingPath); + } let progressListener: fs.ProgressListener = (progress: fs.Progress) => { - this.value = progress.processedSize; - this.total = progress.totalSize; + this.value = progress.processedSize; // Copy file size + this.total = progress.totalSize; // Total size of copied files if ((this.value / this.total * 100) > 99) { CommonUtils.showToast($r('app.string.Success_copy'), this.uiContext!); this.ProgressDialog.close(); @@ -146,22 +202,23 @@ export struct RemoteFileList { } }; let copyOption: fs.CopyOptions = { - "progressListener": progressListener + 'progressListener': progressListener }; try { fs.copy(srcUri, destUri, copyOption).then(() => { hilog.info(0x0000, 'RemoteFile', '%{public}s', 'Success to copy.'); }).catch((err: BusinessError) => { - hilog.error(0x0000, 'RemoteFile', '%{public}s', `Failed to copy. Code: ${err.code}, message: ${err.message}`); + hilog.error(0x0000, 'RemoteFile', `Failed to copy.Code:${err.code},message: ${err.message}`); }); } catch (err) { hilog.error(0x0000, 'RemoteFile', '%{public}s', `Failed to copy.Code try: ${err.code}, message: ${err.message}`); - } } } + // [StartExclude copy_file] + delete() { if (!this.selectedValue) { CommonUtils.showToast($r('app.string.Please_select_a_file'), this.uiContext!); @@ -177,7 +234,11 @@ export struct RemoteFileList { } } + // [EndExclude getRemoteFileList] + // [EndExclude copy_file] build() { + // [StartExclude getRemoteFileList] + // [StartExclude copy_file] NavDestination() { Column({ space: 12 }) { EditTitleBar({ @@ -315,5 +376,11 @@ export struct RemoteFileList { .systemTransition(NavigationSystemTransitionType.SLIDE_RIGHT) .title(this.isEdit ? $r('app.string.edit') : $r('app.string.remote_file')) .hideTitleBar(true) + + // [EndExclude getRemoteFileList] + // [EndExclude copy_file] } -} \ No newline at end of file +} + +// [End getRemoteFileList] +// [End copy_file] \ No newline at end of file