diff --git a/example/lib/data.dart b/example/lib/data.dart index 45f266f7b5fc26fbe6d62f9aa3e10e829d763452..b03976fa796b97a221b9f09889ffc365f0b7d9eb 100644 --- a/example/lib/data.dart +++ b/example/lib/data.dart @@ -3,7 +3,8 @@ class DownloadItems { DownloadItem( name: 'Android Programming Cookbook', url: - 'http://enos.itcollege.ee/~jpoial/allalaadimised/reading/Android-Programming-Cookbook.pdf', + // 'http://enos.itcollege.ee/~jpoial/allalaadimised/reading/Android-Programming-Cookbook.pdf', + 'http://englishonlineclub.com/pdf/iOS%20Programming%20-%20The%20Big%20Nerd%20Ranch%20Guide%20(6th%20Edition)%20[EnglishOnlineClub.com].pdf', ), DownloadItem( name: 'iOS Programming Guide', @@ -16,22 +17,26 @@ class DownloadItems { DownloadItem( name: 'Arches National Park', url: - 'https://upload.wikimedia.org/wikipedia/commons/6/60/The_Organ_at_Arches_National_Park_Utah_Corrected.jpg', + // 'https://upload.wikimedia.org/wikipedia/commons/6/60/The_Organ_at_Arches_National_Park_Utah_Corrected.jpg', + 'https://images.pexels.com/photos/26690031/pexels-photo-26690031.jpeg', ), DownloadItem( name: 'Canyonlands National Park', url: - 'https://upload.wikimedia.org/wikipedia/commons/7/78/Canyonlands_National_Park%E2%80%A6Needles_area_%286294480744%29.jpg', + // 'https://upload.wikimedia.org/wikipedia/commons/7/78/Canyonlands_National_Park%E2%80%A6Needles_area_%286294480744%29.jpg', + 'https://images.pexels.com/photos/23495788/pexels-photo-23495788.jpeg', ), DownloadItem( name: 'Death Valley National Park', url: - 'https://upload.wikimedia.org/wikipedia/commons/b/b2/Sand_Dunes_in_Death_Valley_National_Park.jpg', + // 'https://upload.wikimedia.org/wikipedia/commons/b/b2/Sand_Dunes_in_Death_Valley_National_Park.jpg', + 'https://images.pexels.com/photos/27675493/pexels-photo-27675493.jpeg', ), DownloadItem( name: 'Gates of the Arctic National Park and Preserve', url: - 'https://upload.wikimedia.org/wikipedia/commons/e/e4/GatesofArctic.jpg', + // 'https://upload.wikimedia.org/wikipedia/commons/e/e4/GatesofArctic.jpg', + 'https://youimg1.c-ctrip.com/target/010571200084z6xhs48A5_D_750_420.jpg' ), ]; @@ -39,12 +44,14 @@ class DownloadItems { DownloadItem( name: 'Big Buck Bunny', url: - 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', + // 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', + 'https://videos.pexels.com/video-files/17880140/17880140-uhd_1440_2560_60fps.mp4', ), DownloadItem( name: 'Elephant Dream', url: - 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4', + // 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4', + 'https://videos.pexels.com/video-files/20500768/20500768-uhd_1440_2560_30fps.mp4', ), ]; diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart index 01716b4c0c2db35257f04f5c217517601e373951..33293adabd14f20fd84c80d9f311ef00197a2058 100644 --- a/example/lib/home_page.dart +++ b/example/lib/home_page.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'dart:isolate'; import 'dart:ui'; +import 'package:android_path_provider/android_path_provider.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; @@ -65,7 +66,6 @@ class _MyHomePageState extends State { final taskId = (data as List)[0] as String; final status = DownloadTaskStatus.fromInt(data[1] as int); final progress = data[2] as int; - print( 'Callback on UI isolate: ' 'task ($taskId) is in status ($status) and process ($progress)', @@ -138,7 +138,6 @@ class _MyHomePageState extends State { data: item, onTap: (task) async { final scaffoldMessenger = ScaffoldMessenger.of(context); - final success = await _openDownloadedFile(task); if (!success) { scaffoldMessenger.showSnackBar( @@ -273,6 +272,10 @@ class _MyHomePageState extends State { return result == PermissionStatus.granted; } + if (Platform.operatingSystem == 'ohos') { + return true; + } + throw StateError('unknown platform'); } @@ -356,17 +359,30 @@ class _MyHomePageState extends State { Future _prepareSaveDir() async { _localPath = (await _getSavedDir())!; - final savedDir = Directory(_localPath); - if (!savedDir.existsSync()) { - await savedDir.create(); - } + // final savedDir = Directory(_localPath); + // if (!savedDir.existsSync()) { + // await savedDir.create(); + // } } Future _getSavedDir() async { String? externalStorageDirPath; - externalStorageDirPath = - (await getApplicationDocumentsDirectory()).absolute.path; + if (Platform.isAndroid) { + try { + externalStorageDirPath = await AndroidPathProvider.downloadsPath; + } catch (err, st) { + print('failed to get downloads path: $err, $st'); + + final directory = await getExternalStorageDirectory(); + externalStorageDirPath = directory?.path; + } + } else if (Platform.isIOS) { + externalStorageDirPath = + (await getApplicationDocumentsDirectory()).absolute.path; + } else if (Platform.operatingSystem == 'ohos') { + externalStorageDirPath = 'Image'; + } return externalStorageDirPath; } @@ -401,7 +417,6 @@ class _MyHomePageState extends State { if (!_showContent) { return const Center(child: CircularProgressIndicator()); } - return _permissionReady ? _buildDownloadList() : _buildNoPermissionWarning(); diff --git a/example/ohos/.gitignore b/example/ohos/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..940b8265564d187f21566b25654fb8608808c6a4 --- /dev/null +++ b/example/ohos/.gitignore @@ -0,0 +1,18 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +entry/libs/arm64-v8a/libapp.so +entry/libs/arm64-v8a/libflutter.so +entry/libs/arm64-v8a/libvmservice_snapshot.so +entry/src/main/resources/rawfile/flutter_assets/ +har/*.har +oh-package-lock.json5 +dta \ No newline at end of file diff --git a/example/ohos/AppScope/app.json5 b/example/ohos/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..725ea69ff8b4c94b71f06e355558014e638c923a --- /dev/null +++ b/example/ohos/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.flutter_downloader", + "vendor": "example", + "versionCode": 1, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} \ No newline at end of file diff --git a/example/ohos/AppScope/resources/base/element/string.json b/example/ohos/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..810f4a362c1d177309eec4f2efe5cac2f4558c28 --- /dev/null +++ b/example/ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "example" + } + ] +} diff --git a/example/ohos/AppScope/resources/base/media/app_icon.png b/example/ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/example/ohos/AppScope/resources/base/media/app_icon.png differ diff --git a/example/ohos/build-profile.json5 b/example/ohos/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..1d12140d202702d7c73d64f1b291fe5c45a660ce --- /dev/null +++ b/example/ohos/build-profile.json5 @@ -0,0 +1,27 @@ +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.0.0(12)", + "runtimeOS": "HarmonyOS" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/.gitignore b/example/ohos/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..91f7d8e4f9dc4864ded3eb8d12bab119a5d082ab --- /dev/null +++ b/example/ohos/entry/.gitignore @@ -0,0 +1,10 @@ + +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test +/har +/libs +oh-package-lock.json5 \ No newline at end of file diff --git a/example/ohos/entry/build-profile.json5 b/example/ohos/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..633d360fbc91a3186a23b66ab71b27e5618944cb --- /dev/null +++ b/example/ohos/entry/build-profile.json5 @@ -0,0 +1,29 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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. +*/ + +{ + "apiType": 'stageMode', + "buildOption": { + }, + "targets": [ + { + "name": "default", + "runtimeOS": "HarmonyOS" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/hvigorfile.ts b/example/ohos/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..5bda56eeac3f79703639db986e2faaa433b0e48c --- /dev/null +++ b/example/ohos/entry/hvigorfile.ts @@ -0,0 +1,17 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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. +*/ + +// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently. +export { hapTasks } from '@ohos/hvigor-ohos-plugin'; diff --git a/example/ohos/entry/oh-package-lock.json5 b/example/ohos/entry/oh-package-lock.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7fa7bb99a53af95cd00c886fea39a2e201a46379 --- /dev/null +++ b/example/ohos/entry/oh-package-lock.json5 @@ -0,0 +1,28 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/flutter_ohos@../har/flutter.har": "@ohos/flutter_ohos@../har/flutter.har", + "flutter_downloader@../har/flutter_downloader.har": "flutter_downloader@../har/flutter_downloader.har" + }, + "packages": { + "@ohos/flutter_ohos@../har/flutter.har": { + "name": "@ohos/flutter_ohos", + "version": "1.0.0-83a49bb1f6", + "resolved": "../har/flutter.har", + "registryType": "local" + }, + "flutter_downloader@../har/flutter_downloader.har": { + "name": "flutter_downloader", + "version": "1.0.0", + "resolved": "../har/flutter_downloader.har", + "registryType": "local", + "dependencies": { + "@ohos/flutter_ohos": "file:../har/flutter.har" + } + } + } +} \ No newline at end of file diff --git a/example/ohos/entry/oh-package.json5 b/example/ohos/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..27fda28ce5f3357b42849b72f85f1344db9048ed --- /dev/null +++ b/example/ohos/entry/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "flutter_downloader": "file:../har/flutter_downloader.har" + } +} \ No newline at end of file diff --git a/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets b/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..b5888ed9002dd328f9abd3141ee3e63e88b40d0f --- /dev/null +++ b/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,24 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos'; +import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant'; + +export default class EntryAbility extends FlutterAbility { + configureFlutterEngine(flutterEngine: FlutterEngine) { + super.configureFlutterEngine(flutterEngine) + GeneratedPluginRegistrant.registerWith(flutterEngine) + } +} diff --git a/example/ohos/entry/src/main/ets/pages/Index.ets b/example/ohos/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..5d9647743f4651f3dab7aa3076ed0707b207b436 --- /dev/null +++ b/example/ohos/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 common from '@ohos.app.ability.common'; +import { FlutterPage } from '@ohos/flutter_ohos' + +let storage = LocalStorage.getShared() +const EVENT_BACK_PRESS = 'EVENT_BACK_PRESS' + +@Entry(storage) +@Component +struct Index { + private context = getContext(this) as common.UIAbilityContext + @LocalStorageLink('viewId') viewId: string = ""; + + build() { + Column() { + FlutterPage({ viewId: this.viewId }) + } + } + + onBackPress(): boolean { + this.context.eventHub.emit(EVENT_BACK_PRESS) + return true + } +} \ No newline at end of file diff --git a/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets b/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets new file mode 100644 index 0000000000000000000000000000000000000000..8f8a03c72908ca125ef9d429f995e7d53fbfa138 --- /dev/null +++ b/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 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 { FlutterEngine, Log } from '@ohos/flutter_ohos'; +import FlutterDownloaderPlugin from 'flutter_downloader'; + +/** + * Generated file. Do not edit. + * This file is generated by the Flutter tool based on the + * plugins that support the Ohos platform. + */ + +const TAG = "GeneratedPluginRegistrant"; + +export class GeneratedPluginRegistrant { + + static registerWith(flutterEngine: FlutterEngine) { + try { + flutterEngine.getPlugins()?.add(new FlutterDownloaderPlugin()); + } catch (e) { + Log.e( + TAG, + "Tried to register plugins with FlutterEngine (" + + flutterEngine + + ") failed."); + Log.e(TAG, "Received exception while registering", e); + } + } +} diff --git a/example/ohos/entry/src/main/module.json5 b/example/ohos/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..abc5a9f7eee29394fdd7bde8b2998133b3abb2a4 --- /dev/null +++ b/example/ohos/entry/src/main/module.json5 @@ -0,0 +1,53 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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. +*/ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:icon", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "requestPermissions": [ + {"name" : "ohos.permission.INTERNET"} + ] + } +} \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/base/element/color.json b/example/ohos/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/example/ohos/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/base/element/string.json b/example/ohos/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..67e0f4ff4ac762d1714f6e215c6636a4ad3d620e --- /dev/null +++ b/example/ohos/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "example" + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/base/media/icon.png b/example/ohos/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/example/ohos/entry/src/main/resources/base/media/icon.png differ diff --git a/example/ohos/entry/src/main/resources/base/profile/main_pages.json b/example/ohos/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce --- /dev/null +++ b/example/ohos/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/example/ohos/entry/src/main/resources/en_US/element/string.json b/example/ohos/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..67e0f4ff4ac762d1714f6e215c6636a4ad3d620e --- /dev/null +++ b/example/ohos/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "example" + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/rawfile/flutter_assets/AssetManifest.bin b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/AssetManifest.bin new file mode 100644 index 0000000000000000000000000000000000000000..86d111f09a93cccfa0011858c519a823e7dafef7 Binary files /dev/null and b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/AssetManifest.bin differ diff --git a/example/ohos/entry/src/main/resources/rawfile/flutter_assets/AssetManifest.json b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/AssetManifest.json new file mode 100644 index 0000000000000000000000000000000000000000..9e26dfeeb6e641a33dae4961196235bdb965b21b --- /dev/null +++ b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/AssetManifest.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/rawfile/flutter_assets/FontManifest.json b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/FontManifest.json new file mode 100644 index 0000000000000000000000000000000000000000..3abf18c41c58c933308c244a875bf383856e103e --- /dev/null +++ b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/FontManifest.json @@ -0,0 +1 @@ +[{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]}] \ No newline at end of file diff --git a/example/ohos/entry/src/main/resources/rawfile/flutter_assets/NOTICES.Z b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/NOTICES.Z new file mode 100644 index 0000000000000000000000000000000000000000..8bcd6d31065d73f6cb631a01ad809b9153995b17 Binary files /dev/null and b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/NOTICES.Z differ diff --git a/example/ohos/entry/src/main/resources/rawfile/flutter_assets/fonts/MaterialIcons-Regular.otf b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/fonts/MaterialIcons-Regular.otf new file mode 100644 index 0000000000000000000000000000000000000000..8c99266130a89547b4344f47e08aacad473b14e0 Binary files /dev/null and b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/fonts/MaterialIcons-Regular.otf differ diff --git a/example/ohos/entry/src/main/resources/rawfile/flutter_assets/isolate_snapshot_data b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/isolate_snapshot_data new file mode 100644 index 0000000000000000000000000000000000000000..3dd3d4255920b800d1c1e967074f1f8f9a3da676 Binary files /dev/null and b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/isolate_snapshot_data differ diff --git a/example/ohos/entry/src/main/resources/rawfile/flutter_assets/kernel_blob.bin b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/kernel_blob.bin new file mode 100644 index 0000000000000000000000000000000000000000..26d366be953b02b7115eff5d75a3ecc9fa64df9d Binary files /dev/null and b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/kernel_blob.bin differ diff --git a/example/ohos/entry/src/main/resources/rawfile/flutter_assets/shaders/ink_sparkle.frag b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/shaders/ink_sparkle.frag new file mode 100644 index 0000000000000000000000000000000000000000..3e070c02d07c31fc79fb6091d23669e166d71f1e Binary files /dev/null and b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/shaders/ink_sparkle.frag differ diff --git a/example/ohos/entry/src/main/resources/rawfile/flutter_assets/vm_snapshot_data b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/vm_snapshot_data new file mode 100644 index 0000000000000000000000000000000000000000..dc84dc3f25c003b17d77d9fcfda81dbca4face0c Binary files /dev/null and b/example/ohos/entry/src/main/resources/rawfile/flutter_assets/vm_snapshot_data differ diff --git a/example/ohos/entry/src/main/resources/zh_CN/element/string.json b/example/ohos/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..601e2b5a1c273aa04920b126e3ab715a4450e58f --- /dev/null +++ b/example/ohos/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "example" + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets b/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bdb2d4b3479e7c4b3d4ffb539abc734470c57f32 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,50 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 hilog from '@ohos.hilog'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' + +export default function abilityTest() { + describe('ActsAbilityTest', function () { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(function () { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(function () { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(function () { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(function () { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain',0, function () { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc' + let b = 'b' + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b) + expect(a).assertEqual(a) + }) + }) +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/test/List.test.ets b/example/ohos/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..5ed99383f79bb67de7472f356de5b0421fa26e71 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 abilityTest from './Ability.test' + +export default function testsuite() { + abilityTest() +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets b/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..465211ee2e66f8b16d8c28881acc0f534df700e5 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 UIAbility from '@ohos.app.ability.UIAbility'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import hilog from '@ohos.hilog'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; +import window from '@ohos.window'; + +export default class TestAbility extends UIAbility { + onCreate(want, launchParam) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); + hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); + hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:'+ JSON.stringify(launchParam) ?? ''); + var abilityDelegator: any + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var abilityDelegatorArguments: any + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) + } + + onDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); + windowStage.loadContent('testability/pages/Index', (err, data) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets b/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..bdf1e5f905bcacd6a73af7709c41d4a79d41b170 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 hilog from '@ohos.hilog'; + +@Entry +@Component +struct Index { + aboutToAppear() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility index aboutToAppear'); + } + @State message: string = 'Hello World' + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + Button() { + Text('next page') + .fontSize(20) + .fontWeight(FontWeight.Bold) + }.type(ButtonType.Capsule) + .margin({ + top: 20 + }) + .backgroundColor('#0D9FFB') + .width('35%') + .height('5%') + .onClick(()=>{ + }) + } + .width('100%') + } + .height('100%') + } + } \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 0000000000000000000000000000000000000000..58d9c312f08e7c9ac01e4d6f2d0a33ddc6188ed9 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 hilog from '@ohos.hilog'; +import TestRunner from '@ohos.application.testRunner'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +var abilityDelegator = undefined +var abilityDelegatorArguments = undefined + +async function onAbilityCreateCallback() { + hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback'); +} + +async function addAbilityMonitorCallback(err: any) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare '); + } + + async onRun() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility' + let lMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName + var debug = abilityDelegatorArguments.parameters['-D'] + if (debug == 'true') + { + cmd += ' -D' + } + hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd); + abilityDelegator.executeShellCommand(cmd, + (err: any, d: any) => { + hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? ''); + hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? ''); + }) + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/module.json5 b/example/ohos/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..fab77ce2e0c61e3ad010bab5b27ccbd15f9a8c96 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/module.json5 @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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. +*/ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/example/ohos/entry/src/ohosTest/resources/base/element/color.json b/example/ohos/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/example/ohos/entry/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/resources/base/element/string.json b/example/ohos/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..65d8fa5a7cf54aa3943dcd0214f58d1771bc1f6c --- /dev/null +++ b/example/ohos/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/example/ohos/entry/src/ohosTest/resources/base/media/icon.png b/example/ohos/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/example/ohos/entry/src/ohosTest/resources/base/media/icon.png differ diff --git a/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json b/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..b7e7343cacb32ce982a45e76daad86e435e054fe --- /dev/null +++ b/example/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "testability/pages/Index" + ] +} diff --git a/example/ohos/hvigor/hvigor-config.json5 b/example/ohos/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..3bcb859e88b72cc2dcc6ec1bc89d26a6411cb2d8 --- /dev/null +++ b/example/ohos/hvigor/hvigor-config.json5 @@ -0,0 +1,23 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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. +*/ + +{ + "modelVersion": "5.0.0", + "dependencies": { + }, + "properties": { + "ohos.nativeResolver": false + } +} \ No newline at end of file diff --git a/example/ohos/hvigorfile.ts b/example/ohos/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..38626e385a5b47dd3cba0e1e83c614f091b7cc9e --- /dev/null +++ b/example/ohos/hvigorfile.ts @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/example/ohos/oh-package-lock.json5 b/example/ohos/oh-package-lock.json5 new file mode 100644 index 0000000000000000000000000000000000000000..5403ef66b9f4525e133a7b23f1538ea3295aba54 --- /dev/null +++ b/example/ohos/oh-package-lock.json5 @@ -0,0 +1,27 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/flutter_ohos@har/flutter.har": "@ohos/flutter_ohos@har/flutter.har", + "@ohos/hypium@1.0.6": "@ohos/hypium@1.0.6" + }, + "packages": { + "@ohos/flutter_ohos@har/flutter.har": { + "name": "@ohos/flutter_ohos", + "version": "1.0.0-83a49bb1f6", + "resolved": "har/flutter.har", + "registryType": "local" + }, + "@ohos/hypium@1.0.6": { + "name": "@ohos/hypium", + "version": "1.0.6", + "integrity": "sha512-bb3DWeWhYrFqj9mPFV3yZQpkm36kbcK+YYaeY9g292QKSjOdmhEIQR2ULPvyMsgSR4usOBf5nnYrDmaCCXirgQ==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.6.tgz", + "shasum": "3f5fed65372633233264b3447705b0831dfe7ea1", + "registryType": "ohpm" + } + } +} \ No newline at end of file diff --git a/example/ohos/oh-package.json5 b/example/ohos/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..eceba12a95b34e122eb70ba92d9dd7afc1f328ef --- /dev/null +++ b/example/ohos/oh-package.json5 @@ -0,0 +1,20 @@ +{ + "modelVersion": "5.0.0", + "name": "example", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "@ohos/flutter_ohos": "file:./har/flutter.har" + }, + "devDependencies": { + "@ohos/hypium": "1.0.6" + }, + "overrides": { + "@ohos/flutter_ohos": "file:./har/flutter.har", + "flutter_downloader": "file:./har/flutter_downloader.har", + "@ohos/flutter_module": "file:./entry" + } +} \ No newline at end of file diff --git a/example/pubspec.yaml b/example/pubspec.yaml index a81cae347eb2a3146e2402393c11ee2b2f01f451..42f43dbcf86d592c5abd35fe40f3b0222aa82071 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -8,6 +8,7 @@ environment: flutter: ">=3.19.0" dependencies: + android_path_provider: ^0.3.0 device_info_plus: ^10.1.0 flutter: sdk: flutter diff --git a/lib/src/downloader.dart b/lib/src/downloader.dart index adbe45044ec621993724a84c0aea403915e47423..d36d1243667ddbb183a5c3566a4a7cbd46bc233e 100644 --- a/lib/src/downloader.dart +++ b/lib/src/downloader.dart @@ -104,7 +104,7 @@ class FlutterDownloader { int timeout = 15000, }) async { assert(_initialized, 'plugin flutter_downloader is not initialized'); - assert(Directory(savedDir).existsSync(), 'savedDir does not exist'); + // assert(Directory(savedDir).existsSync(), 'savedDir does not exist'); try { final taskId = await _channel.invokeMethod('enqueue', { diff --git a/ohos/.gitignore b/ohos/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/ohos/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/ohos/BuildProfile.ets b/ohos/BuildProfile.ets new file mode 100644 index 0000000000000000000000000000000000000000..3a501e5ddee8ea6d28961648fc7dd314a5304bd4 --- /dev/null +++ b/ohos/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/ohos/Index.ets b/ohos/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..5aaa61da8ee2c937722b9db8b244301d7cde4220 --- /dev/null +++ b/ohos/Index.ets @@ -0,0 +1,17 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 { FlutterDownloaderPlugin } from './src/main/ets/components/FlutterDownloaderPlugin'; +export default FlutterDownloaderPlugin; \ No newline at end of file diff --git a/ohos/build-profile.json5 b/ohos/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..bfd753ac3c70d1afd9ba0a7955a7938d12375a62 --- /dev/null +++ b/ohos/build-profile.json5 @@ -0,0 +1,31 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} \ No newline at end of file diff --git a/ohos/consumer-rules.txt b/ohos/consumer-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ohos/hvigorfile.ts b/ohos/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..d3c13e1193d9899a3e95a527fcd50034a649877c --- /dev/null +++ b/ohos/hvigorfile.ts @@ -0,0 +1,16 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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. +*/ + +export { harTasks } from '@ohos/hvigor-ohos-plugin'; diff --git a/ohos/obfuscation-rules.txt b/ohos/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/ohos/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/ohos/oh-package-lock.json5 b/ohos/oh-package-lock.json5 new file mode 100644 index 0000000000000000000000000000000000000000..8faf075d9a6ffb43ed2ba33a635fd8216af17bc7 --- /dev/null +++ b/ohos/oh-package-lock.json5 @@ -0,0 +1,18 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/flutter_ohos@../example/ohos/har/flutter.har": "@ohos/flutter_ohos@../example/ohos/har/flutter.har" + }, + "packages": { + "@ohos/flutter_ohos@../example/ohos/har/flutter.har": { + "name": "@ohos/flutter_ohos", + "version": "1.0.0-83a49bb1f6", + "resolved": "../example/ohos/har/flutter.har", + "registryType": "local" + } + } +} \ No newline at end of file diff --git a/ohos/oh-package.json5 b/ohos/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..9e6f3a270ab467e0a1d5efd594aa945577246aae --- /dev/null +++ b/ohos/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "name": "flutter_downloader", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@ohos/flutter_ohos": "file:../har/flutter.har" + } +} diff --git a/ohos/src/main/ets/components/DownloadStatus.ets b/ohos/src/main/ets/components/DownloadStatus.ets new file mode 100644 index 0000000000000000000000000000000000000000..1d56ac604e304543cf66b7fdf7ef3a451f020fd0 --- /dev/null +++ b/ohos/src/main/ets/components/DownloadStatus.ets @@ -0,0 +1,24 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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. +*/ + +export enum DownloadStatus { + UNDEFINED, + ENQUEUED, + RUNNING, + COMPLETE, + FAILED, + CANCELED, + PAUSED +} \ No newline at end of file diff --git a/ohos/src/main/ets/components/DownloadTask.ets b/ohos/src/main/ets/components/DownloadTask.ets new file mode 100644 index 0000000000000000000000000000000000000000..93e0d51724676092b13bec0de1719620c34e1f9a --- /dev/null +++ b/ohos/src/main/ets/components/DownloadTask.ets @@ -0,0 +1,33 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 { DownloadStatus } from './DownloadStatus'; + +export interface DownloadTask { + primaryId: number; + taskId: string; + status: DownloadStatus; + progress: number; + url: string; + filename: string; + savedDir: string; + headers: string; + mimeType: string; + resumable: boolean; + showNotification: boolean; + openFileFromNotification: boolean; + timeCreated: number; + allowCellular: boolean; +} \ No newline at end of file diff --git a/ohos/src/main/ets/components/FileUtils.ets b/ohos/src/main/ets/components/FileUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..56ec32bf3299d6b71db52cc2714821c288e4d756 --- /dev/null +++ b/ohos/src/main/ets/components/FileUtils.ets @@ -0,0 +1,109 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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. +*/ + +const mimeTypeMap: Map = new Map([ + ["aac", "audio/aac"], + ["abw", "application/x-abiword"], + ["apng", "image/apng"], + ["arc", "application/x-freearc"], + ["avif", "image/avif"], + ["avi", "video/x-msvideo"], + ["azw", "application/vnd.amazon.ebook"], + ["bin", "application/octet-stream"], + ["bmp", "image/bmp"], + ["bz", "application/x-bzip"], + ["bz2", "application/x-bzip2"], + ["cda", "application/x-cdf"], + ["csh", "application/x-csh"], + ["css", "text/css"], + ["csv", "text/csv"], + ["doc", "application/msword"], + ["docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"], + ["eot", "application/vnd.ms-fontobject"], + ["epub", "application/epub+zip"], + ["gz", "application/gzip"], + ["gif", "image/gif"], + ["htm", "text/html"], + ["html", "text/html"], + ["ico", "image/vnd.microsoft.icon"], + ["ics", "text/calendar"], + ["jar", "application/java-archive"], + ["jpeg", "image/jpeg"], + ["jpg", "image/jpeg"], + ["js", "text/javascript"], + ["json", "application/json"], + ["jsonld", "application/ld+json"], + ["mid", "audio/midi"], + ["midi", "audio/x-midi"], + ["mjs", "text/javascript"], + ["mp3", "audio/mpeg"], + ["mp4", "video/mp4"], + ["mpeg", "video/mpeg"], + ["mpkg", "application/vnd.apple.installer+xml"], + ["odp", "application/vnd.oasis.opendocument.presentation"], + ["ods", "application/vnd.oasis.opendocument.spreadsheet"], + ["odt", "application/vnd.oasis.opendocument.text"], + ["oga", "audio/ogg"], + ["ogv", "video/ogg"], + ["ogx", "application/ogg"], + ["opus", "audio/opus"], + ["otf", "font/otf"], + ["png", "image/png"], + ["pdf", "application/pdf"], + ["php", "application/x-httpd-php"], + ["ppt", "application/vnd.ms-powerpoint"], + ["pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"], + ["rar", "application/vnd.rar"], + ["rtf", "application/rtf"], + ["sh", "application/x-sh"], + ["svg", "image/svg+xml"], + ["tar", "application/x-tar"], + ["tif", "image/tiff"], + ["tiff", "image/tiff"], + ["ts", "video/mp2t"], + ["ttf", "font/ttf"], + ["txt", "text/plain"], + ["vsd", "application/vnd.visio"], + ["wav", "audio/wav"], + ["weba", "audio/webm"], + ["webm", "video/webm"], + ["webp", "image/webp"], + ["woff", "font/woff"], + ["woff2", "font/woff2"], + ["xhtml", "application/xhtml+xml"], + ["xls", "application/vnd.ms-excel"], + ["xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"], + ["xml", "application/xml"], + ["xul", "application/vnd.mozilla.xul+xml"], + ["zip", "application/zip"], + ["3gp", "video/3gpp"], + ["3g2", "video/3gpp2"], + ["7z", "application/x-7z-compressed"], +]); + +export function getMimeType(filename: string): string { + let newFilename = filename; + let index = newFilename.lastIndexOf('/'); + if (index !== -1) { + newFilename = newFilename.substring(index + 1, newFilename.length); + } + index = newFilename.lastIndexOf('.'); + if (index == -1) { + return ""; + } + let fileExtension = newFilename.substring(index + 1, newFilename.length); + let mimeType = mimeTypeMap.get(fileExtension); + return mimeType == undefined ? "" : mimeType; +} \ No newline at end of file diff --git a/ohos/src/main/ets/components/FlutterDownloaderPlugin.ets b/ohos/src/main/ets/components/FlutterDownloaderPlugin.ets new file mode 100644 index 0000000000000000000000000000000000000000..0df80a8da2e258ba37a4df8a91bd451c831b20e9 --- /dev/null +++ b/ohos/src/main/ets/components/FlutterDownloaderPlugin.ets @@ -0,0 +1,630 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 { + AbilityAware, + AbilityPluginBinding, + Any, + DartExecutor, + FlutterCallbackInformation, + FlutterEngine, + FlutterInjector, + FlutterPlugin, + FlutterPluginBinding, + MethodCall, + MethodCallHandler +} from '@ohos/flutter_ohos'; +import MethodChannel, { MethodResult } from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel'; +import preferences from '@ohos.data.preferences'; +import fs from '@ohos.file.fs'; +import { TaskDao } from './TaskDao'; +import { TaskDbHelper } from './TaskDbHelper'; +import { DownloadStatus } from './DownloadStatus'; +import { ValuesBucket } from '@kit.ArkData'; +import { TaskEntry } from './TaskEntry'; +import { fileUri, picker } from '@kit.CoreFileKit'; +import { common, Want, wantConstant } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { request } from '@kit.BasicServicesKit'; +import Logger from './Logger'; +import { DartCallback } from '@ohos/flutter_ohos/src/main/ets/embedding/engine/dart/DartExecutor'; + +const invalidTaskId = "invalid_task_id"; +const invalidStatus = "invalid_status"; +const invalidData = "invalid_data"; +const TAG = 'FlutterDownloaderPlugin'; +const CHANNEL = "vn.hunghd/downloader"; + +export class FlutterDownloaderPlugin implements FlutterPlugin, MethodCallHandler, AbilityAware { + public static readonly SHARED_PREFERENCES_KEY = 'vn.hunghd.downloader.pref'; + public static readonly CALLBACK_DISPATCHER_HANDLE_KEY = "callback_dispatcher_handle_key"; + private flutterChannel: MethodChannel | null = null; + private backgroundChannel: MethodChannel | null = null; + private flutterPluginBinding: FlutterPluginBinding | null = null; + private taskDao: TaskDao | null = null; + private context: common.UIAbilityContext | null = null; + private callbackHandle: number = 0; + private backgroundFlutterEngine: FlutterEngine | null = null; + private debugMode: number = 0; + private tasks: Set = new Set(); + private isNeedProgressMap: Map = new Map(); + private lastProgressMap: Map = new Map(); + + onAttachedToEngine(binding: FlutterPluginBinding): void { + this.flutterPluginBinding = binding; + } + + onDetachedFromEngine(binding: FlutterPluginBinding): void { + this.onDetachedFromAbility(); + this.context = null + this.flutterPluginBinding = null; + } + + onAttachedToAbility(binding: AbilityPluginBinding): void { + if (this.flutterChannel != null) { + return; + } + this.context = binding.getAbility().context; + this.flutterChannel = + new MethodChannel(this.flutterPluginBinding!.getBinaryMessenger(), CHANNEL); + this.flutterChannel.setMethodCallHandler(this); + this.taskDao = new TaskDao(TaskDbHelper.getInstance(this.context)) + } + + onDetachedFromAbility(): void { + if (this.flutterChannel != null) { + this.flutterChannel.setMethodCallHandler(null); + this.flutterChannel = null; + } + if (this.backgroundChannel != null) { + this.backgroundChannel.setMethodCallHandler(null); + this.backgroundChannel = null; + } + } + + getUniqueClassName(): string { + return TAG; + } + + onMethodCall(call: MethodCall, result: MethodResult): void { + Logger.info(TAG, `method: ${call.method}`); + switch (call.method) { + case 'initialize': + this.initialize(call, result); + break; + case 'registerCallback': + this.registerCallback(call, result); + break; + case 'enqueue': + this.enqueue(call, result); + break; + case 'loadTasks': + this.loadTasks(call, result); + break; + case 'loadTasksWithRawQuery': + this.loadTasksWithRawQuery(call, result); + break; + case 'cancel': + this.cancel(call, result); + break; + case 'cancelAll': + this.cancelAll(call, result); + break; + case 'pause': + this.pause(call, result); + break; + case 'resume': + this.resume(call, result); + break; + case 'retry': + this.retry(call, result); + break; + case 'open': + this.open(call, result); + break; + case 'remove': + this.remove(call, result); + break; + default: + result.notImplemented(); + break; + } + } + + private remove(call: MethodCall, result: MethodResult) { + const taskId = this.requireArgument(call, "task_id"); + this.isNeedProgressMap.delete(taskId); + const shouldDeleteContent = this.requireArgument(call, "should_delete_content"); + const task = this.taskDao!.loadTask(taskId); + if (task != null) { + if (task.status == DownloadStatus.ENQUEUED || task.status == DownloadStatus.RUNNING) { + request.agent.remove(taskId, (err: BusinessError) => { + if (err) { + Logger.error(TAG, `Failed to removing a download task, Code: ${err.code}, message: ${err.message}`); + return; + } + Logger.info(TAG, `Succeeded in removing a download task.`); + }); + } + if (shouldDeleteContent) { + const saveFilePath = task.savedDir + '/' + task.filename; + if (fs.accessSync(saveFilePath)) { + fs.unlink(saveFilePath).then(() => { + Logger.info(TAG, "remove file succeed"); + }).catch((err: BusinessError) => { + Logger.error(TAG, "remove file failed with error message: " + err.message + ", error code: " + err.code); + }); + } + } + this.taskDao!.deleteTask(taskId) + result.success(null); + } else { + result.error(invalidTaskId, "not found task corresponding to given task id", null); + } + } + + private open(call: MethodCall, result: MethodResult) { + const taskId = this.requireArgument(call, "task_id"); + const task = this.taskDao!.loadTask(taskId); + if (task == null) { + result.error(invalidTaskId, "not found task with id $taskId", null); + return; + } + + if (task.status != DownloadStatus.COMPLETE) { + result.error(invalidStatus, "only completed tasks can be opened", null); + return; + } + this.openFile(`${task.savedDir}/${task.filename}`, task.mimeType).then(openResult => { + result.success(openResult); + }).catch((e: BusinessError) => { + result.success(false); + }); + } + + private async retry(call: MethodCall, result: MethodResult): Promise { + const taskId = this.requireArgument(call, "task_id"); + const task = this.taskDao!.loadTask(taskId); + if (task != null) { + if (task.status == DownloadStatus.FAILED || task.status == DownloadStatus.CANCELED) { + const newTaskId = + await this.downloadFile(task.url, task.headers, task.filename!, task.savedDir, task.allowCellular, + task.showNotification, task.openFileFromNotification); + result.success(newTaskId); + this.sendUpdateProgress(newTaskId, DownloadStatus.ENQUEUED, task.progress); + const values: ValuesBucket = {}; + values[TaskEntry.COLUMN_NAME_TASK_ID] = newTaskId; + values[TaskEntry.COLUMN_NAME_STATUS] = DownloadStatus.ENQUEUED; + values[TaskEntry.COLUMN_NAME_PROGRESS] = task.progress; + values[TaskEntry.COLUMN_NAME_RESUMABLE] = 0; + this.taskDao!.updateTask(taskId, values); + } else { + result.error(invalidStatus, "only failed and canceled task can be retried", null); + } + } else { + result.error(invalidTaskId, "not found task corresponding to given task id", null); + } + } + + private resume(call: MethodCall, result: MethodResult) { + const taskId = this.requireArgument(call, "task_id"); + this.isNeedProgressMap.set(taskId, true); + const task = this.taskDao!.loadTask(taskId); + if (task != null) { + if (task.status == DownloadStatus.PAUSED) { + request.agent.getTask(this.context!, taskId).then((downloadTask: request.agent.Task) => { + downloadTask.resume((err: BusinessError) => { + if (err) { + Logger.error(TAG, `Failed to resume the download task, Code: ${err.code}, message: ${err.message}`); + result.error( + invalidData, + `Failed to resume the download task, Code: ${err.code}, message: ${err.message}`, + null + ) + return; + } + Logger.info(TAG, `Succeeded in resuming a download task. `); + result.success(taskId); + }) + }) + } else { + Logger.error(TAG, "only paused task can be resumed"); + result.error(invalidStatus, "only paused task can be resumed", null); + } + } else { + Logger.error(TAG, "not found task corresponding to given task id"); + result.error(invalidTaskId, "not found task corresponding to given task id", null); + } + } + + private pause(call: MethodCall, result: MethodResult) { + const taskId = this.requireArgument(call, "task_id"); + this.isNeedProgressMap.set(taskId, false); + request.agent.getTask(this.context!, taskId).then((task: request.agent.Task) => { + task.pause((err: BusinessError) => { + if (err) { + Logger.error(TAG, `Failed to pause the download task, Code: ${err.code}, message: ${err.message}`); + return; + } + Logger.info(TAG, `Succeeded in pausing a download task. `); + }) + }) + result.success(null); + } + + private cancel(call: MethodCall, result: MethodResult) { + const taskId = this.requireArgument(call, "task_id"); + this.cancelTask(taskId); + result.success(null); + } + + private cancelAll(call: MethodCall, result: MethodResult) { + for (const taskId of this.tasks) { + this.cancelTask(taskId); + } + this.isNeedProgressMap.clear(); + result.success(null); + } + + private loadTasksWithRawQuery(call: MethodCall, result: MethodResult) { + const query = this.requireArgument(call, "query"); + const tasks = this.taskDao!.loadTasksWithRawQuery(query); + const array: Array> = new Array(); + for (const task of tasks) { + const item: Record = { + "task_id": task.taskId, + "status": task.status, + "progress": task.progress, + "url": task.url, + "file_name": task.filename, + "saved_dir": task.savedDir, + "time_created": task.timeCreated, + "allow_cellular": task.allowCellular + } + array.push(item); + } + result.success(array); + } + + private async loadTasks(call: MethodCall, result: MethodResult) { + const tasks = await this.taskDao!.loadAllTasks(); + const array: Array> = new Array(); + for (const task of tasks) { + const item: Record = { + "task_id": task.taskId, + "status": task.status, + "progress": task.progress, + "url": task.url, + "file_name": task.filename, + "saved_dir": task.savedDir, + "time_created": task.timeCreated, + "allow_cellular": task.allowCellular + } + array.push(item); + this.tasks.add(task.taskId); + } + result.success(array); + } + + private requireArgument(call: MethodCall, key: string): T { + const value: Any = call.argument(key); + if (value === null || value === undefined) { + throw new Error(`Required key '${key}' was null`); + } + return value as T; // 使用类型断言返回值 + } + + private async enqueue(call: MethodCall, result: MethodResult): Promise { + const url = this.requireArgument(call, 'url'); + const savedDir = this.requireArgument(call, 'saved_dir'); + const filename: string = call.argument('file_name') ?? url.substring(url.lastIndexOf("/") + 1, url.length); + const headers = this.requireArgument(call, 'headers'); + const showNotification = this.requireArgument(call, 'show_notification'); + const openFileFromNotification = this.requireArgument(call, 'open_file_from_notification'); + const allowCellular = this.requireArgument(call, 'allow_cellular'); + const taskId = + await this.downloadFile(url, headers, filename, savedDir, allowCellular, showNotification, + openFileFromNotification); + if (this.isNeedProgressMap.has(taskId)) { + this.isNeedProgressMap.delete(taskId); + this.isNeedProgressMap.set(taskId, true); + } else { + this.isNeedProgressMap.set(taskId, true); + } + result.success(taskId); + this.sendUpdateProgress(taskId, DownloadStatus.ENQUEUED, 0); + this.taskDao!.insertOrUpdateNewTask( + taskId, + url, + DownloadStatus.ENQUEUED, + 0, + filename, + savedDir, + headers, + showNotification, + openFileFromNotification, + allowCellular + ); + return Promise.resolve(); + } + + private registerCallback(call: + MethodCall, result: + MethodResult + ): void { + const args: number[] = call.args as number[]; + this.callbackHandle = args[0]; + result.success(null); + } + + private initialize(call: + MethodCall, result: + MethodResult + ): void { + const args: number[] = call.args as number[]; + this.callbackHandle = args[0]; + this.debugMode = args[1]; + preferences.getPreferencesSync(this.context, { name: FlutterDownloaderPlugin.SHARED_PREFERENCES_KEY }) + .putSync(FlutterDownloaderPlugin.CALLBACK_DISPATCHER_HANDLE_KEY, this.callbackHandle); + result.success(null) + + this.startBackgroundIsolate(this.callbackHandle); + } + + private sendUpdateProgress(id: string, status: DownloadStatus, progress: number): void { + const args: Map = new Map(); + args["task_id"] = id + args["status"] = status + args["progress"] = progress + this.flutterChannel!.invokeMethod("updateProgress", args) + } + + private async openFile(savedFile: string, mimeType: string): Promise { + let uri = fileUri.getUriFromPath(savedFile); + const want: Want = { + action: 'ohos.want.action.viewData', + flags: wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION, + uri: uri, + type: mimeType + } + try { + await this.context!.startAbility(want); + return Promise.resolve(true); + } catch (err) { + // 处理入参错误异常 + let code = (err as BusinessError).code; + let message = (err as BusinessError).message; + Logger.error(TAG, `startAbility failed, code is ${code}, message is ${message}`); + return Promise.resolve(false); + } + } + + private cancelTask(taskId: string): void { + request.agent.getTask(this.context!, taskId).then((task: request.agent.Task) => { + task.stop((err: BusinessError) => { + if (err) { + Logger.error(TAG, `Failed to stop the download task, Code: ${err.code}, message: ${err.message}`); + return; + } + Logger.info(TAG, `Succeeded in stopping a download task. `); + const values: ValuesBucket = {}; + values[TaskEntry.COLUMN_NAME_STATUS] = DownloadStatus.CANCELED; + values[TaskEntry.COLUMN_NAME_PROGRESS] = -1 + this.taskDao!.updateTask(taskId, values); + request.agent.remove(taskId, (err: BusinessError) => { + if (err) { + Logger.error(TAG, `Failed to removing a download task, Code: ${err.code}, message: ${err.message}`); + return; + } + Logger.info(TAG, `Succeeded in removing a download task. taskId: ${taskId}`); + }); + }); + }); + } + + private callback(taskId: string, filename: string, status: DownloadStatus, + progress: request.agent.Progress, isResume?: number): void { + const task = this.taskDao!.loadTask(taskId); + if (task != null) { + let lastProgress = 0; + if (status == DownloadStatus.COMPLETE) { + lastProgress = 100; + } else if (status == DownloadStatus.FAILED) { + lastProgress = -1; + } else { + let tempProgress: number = progress.processed; + if (tempProgress == 0) { + if (this.lastProgressMap.has(taskId)) { + tempProgress = this.lastProgressMap.get(taskId) as number; + } + } + lastProgress = Math.trunc(tempProgress * 100 / progress.sizes[0]); + const args: Map = new Map(); + args["task_id"] = taskId + args["status"] = status + args["progress"] = lastProgress + this.backgroundChannel?.invokeMethod('', [this.callbackHandle, taskId, status, lastProgress]) + } + const values: ValuesBucket = {}; + values[TaskEntry.COLUMN_NAME_FILE_NAME] = filename; + values[TaskEntry.COLUMN_NAME_STATUS] = status; + values[TaskEntry.COLUMN_NAME_PROGRESS] = lastProgress; + if (!isResume) { + values[TaskEntry.COLUMN_NAME_RESUMABLE] = isResume!; + } + this.taskDao!.updateTask(taskId, values); + } + } + + private renameSavedFile(filename: string): string { + // 匹配文件名结尾的 "(数字)" + const regex = /\((\d+)\)$/; + + // 找到最后一个点的位置 + const lastDotIndex = filename.lastIndexOf('.'); + + const baseName = lastDotIndex === -1 ? filename : filename.substring(0, lastDotIndex); // 文件名 + const extension = lastDotIndex === -1 ? '' : filename.substring(lastDotIndex); // 包含点的扩展名 + + // 如果基本名称的结尾匹配到 "(数字)",则替换 + if (regex.test(baseName)) { + return baseName.replace(regex, (match, number: string) => { + const newNumber = parseInt(number, 10) + 1; // 数字加 1 + return `(${newNumber})`; // 返回新的 "(新数字)" + }) + extension; // 返回新的文件名 + } else { + // 如果没有匹配到 "(数字)",则在扩展名之前添加 "(1)" + return `${baseName}(1)${extension}`; + } + } + + private async downloadFile( + url: string, + headers: string, + filename: string, + savedDir: string, + allowCellular: boolean, + showNotification: boolean, + openFileFromNotification: boolean + ): Promise { + const tempDir = this.context!.getApplicationContext().tempDir; + const tempFile = `${tempDir}/${filename}`; + const documentViewPicker = new picker.DocumentViewPicker(this.context!); + const documentSaveOptions = new picker.DocumentSaveOptions(); + documentSaveOptions.pickerMode = picker.DocumentPickerMode.DOWNLOAD; + const documentSaveResult = await documentViewPicker.save(documentSaveOptions) + const downloadDirUri = documentSaveResult[0]; + const downloadDir = new fileUri.FileUri(downloadDirUri).path; + const saveFileDir = `${downloadDir}/${savedDir}`; + if (!fs.accessSync(saveFileDir)) { + fs.mkdirSync(`${saveFileDir}`, true); + } + let savedFile = `${saveFileDir}/${filename}`; + while (fs.accessSync(savedFile)) { + filename = this.renameSavedFile(filename); + savedFile = `${saveFileDir}/${filename}`; + } + const requestConfig: request.agent.Config = { + action: request.agent.Action.DOWNLOAD, + url: url, + saveas: tempFile, + metered: allowCellular, + overwrite: true, + gauge: showNotification + }; + + const downloadTask = await request.agent.create(this.context!, requestConfig); + const taskId = downloadTask.tid; + downloadTask.start((err: BusinessError) => { + if (err) { + Logger.error(TAG, `Failed to start the download task, Code: ${err.code}, message: ${err.message}`); + return; + } + Logger.info(TAG, `Succeeded in starting a download task.`); + this.tasks.add(taskId); + }); + + downloadTask.on('progress', (progress) => { + Logger.info(TAG, `downloader running`); + if (this.isNeedProgressMap.get(taskId)) { + this.callback(taskId, filename, DownloadStatus.RUNNING, progress); + } + if (this.lastProgressMap.has(taskId)) { + this.lastProgressMap.delete(taskId); + if (progress.processed != 0) { + this.lastProgressMap.set(taskId, progress.processed); + } + } else { + if (progress.processed != 0) { + this.lastProgressMap.set(taskId, progress.processed); + } + } + }); + + downloadTask.on('pause', (progress) => { + Logger.info(TAG, `downloader paused`); + this.callback(taskId, filename, DownloadStatus.PAUSED, progress, 1); + }); + + downloadTask.on('resume', (progress) => { + Logger.info(TAG, `downloader resumed`); + this.lastProgressMap.delete(taskId) + this.callback(taskId, filename, DownloadStatus.RUNNING, progress, 0); + }); + + downloadTask.on('failed', (progress) => { + Logger.info(TAG, `downloader failed`); + this.callback(taskId, filename, DownloadStatus.FAILED, progress); + }); + + downloadTask.on('remove', () => { + this.tasks.delete(taskId); + }); + + downloadTask.on('completed', () => { + //删除两个map中的taskId + this.isNeedProgressMap.delete(taskId); + this.lastProgressMap.delete(taskId); + Logger.info(TAG, `Request download completed`); + this.backgroundChannel?.invokeMethod('', [this.callbackHandle, taskId, DownloadStatus.COMPLETE, 100]) + fs.moveFileSync(tempFile, savedFile, 0); + const task = this.taskDao!.loadTask(taskId); + if (task != null) { + if (openFileFromNotification) { + const mimeType = task.mimeType; + this.openFile(savedFile, mimeType); + } + const values: ValuesBucket = {}; + values[TaskEntry.COLUMN_NAME_SAVED_DIR] = saveFileDir; + values[TaskEntry.COLUMN_NAME_STATUS] = DownloadStatus.COMPLETE; + values[TaskEntry.COLUMN_NAME_PROGRESS] = 100; + this.taskDao!.updateTask(taskId, values); + } + + request.agent.remove(taskId, (err: BusinessError) => { + if (err) { + Logger.error(TAG, `Failed to removing a download task, Code: ${err.code}, message: ${err.message}`); + return; + } + Logger.info(TAG, `Succeeded in removing a download task.`); + }) + }) + + return Promise.resolve(taskId); + } + + private async startBackgroundIsolate(handle: number) { + if (this.backgroundFlutterEngine == null) { + let context = this.context ?? getContext(this); + this.backgroundFlutterEngine = new FlutterEngine(context, null, null, null); + await this.backgroundFlutterEngine.init(context, null, true); + + let flutterCallback = FlutterCallbackInformation.lookupCallbackInformation(handle); + if (flutterCallback == null) { + Logger.error(TAG, `Fatal: failed to find callback".`); + return + } + let resourceManager = context.getApplicationContext().resourceManager; + let appBundlePath = FlutterInjector.getInstance().getFlutterLoader().findAppBundlePath(); + this.backgroundFlutterEngine?.dartExecutor.executeDartCallback( + new DartCallback(resourceManager, appBundlePath, flutterCallback) + ) + } + this.backgroundChannel = + new MethodChannel(this.backgroundFlutterEngine.getDartExecutor().getBinaryMessenger(), + "vn.hunghd/downloader_background"); + this.backgroundChannel.setMethodCallHandler(this); + } +} \ No newline at end of file diff --git a/ohos/src/main/ets/components/Logger.ts b/ohos/src/main/ets/components/Logger.ts new file mode 100644 index 0000000000000000000000000000000000000000..a5d2b4d0e45b1de036fd731b324b7eba5ee99dd0 --- /dev/null +++ b/ohos/src/main/ets/components/Logger.ts @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2022 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 hilog from '@ohos.hilog'; + +class Logger { + private domain: number; + private prefix: string; + private format: string = "%{public}s, %{public}s"; + constructor(prefix: string) { + this.prefix = prefix; + this.domain = 0xFF00; + } + debug(...args: string[]) { + hilog.debug(this.domain, this.prefix, this.format, args); + } + info(...args: string[]) { + hilog.info(this.domain, this.prefix, this.format, args); + } + warn(...args: string[]) { + hilog.warn(this.domain, this.prefix, this.format, args); + } + error(...args: string[]) { + hilog.error(this.domain, this.prefix, this.format, args); + } +} +export default new Logger('[Flutter_Downloader]'); \ No newline at end of file diff --git a/ohos/src/main/ets/components/TaskDao.ets b/ohos/src/main/ets/components/TaskDao.ets new file mode 100644 index 0000000000000000000000000000000000000000..3696d6c2bc6b7c1b03a4ed30c18cceef894374e5 --- /dev/null +++ b/ohos/src/main/ets/components/TaskDao.ets @@ -0,0 +1,235 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 { DownloadStatus } from './DownloadStatus'; +import { TaskDbHelper } from './TaskDbHelper'; +import { TaskEntry } from './TaskEntry'; +import { relationalStore, ValuesBucket } from '@kit.ArkData'; +import { BusinessError } from '@kit.BasicServicesKit'; +import Logger from './Logger'; +import { DownloadTask } from './DownloadTask'; +import { getMimeType } from './FileUtils'; + +const TAG = 'TaskDao'; + +export class TaskDao { + private dbHelper: TaskDbHelper | null = null; + private projection: string[] = [ + TaskEntry.COLUMN_NAME_ID, + TaskEntry.COLUMN_NAME_TASK_ID, + TaskEntry.COLUMN_NAME_PROGRESS, + TaskEntry.COLUMN_NAME_STATUS, + TaskEntry.COLUMN_NAME_URL, + TaskEntry.COLUMN_NAME_FILE_NAME, + TaskEntry.COLUMN_NAME_SAVED_DIR, + TaskEntry.COLUMN_NAME_HEADERS, + TaskEntry.COLUMN_NAME_MIME_TYPE, + TaskEntry.COLUMN_NAME_RESUMABLE, + TaskEntry.COLUMN_NAME_OPEN_FILE_FROM_NOTIFICATION, + TaskEntry.COLUMN_NAME_SHOW_NOTIFICATION, + TaskEntry.COLUMN_NAME_TIME_CREATED, + TaskEntry.COLUMN_ALLOW_CELLULAR, + ]; + + constructor(dbHelper: TaskDbHelper) { + this.dbHelper = dbHelper; // 将 dbHelper 作为实例变量保存 + } + + async insertOrUpdateNewTask( + taskId: string, + url: string, + status: DownloadStatus, + progress: number, + filename: string, + savedDir: string, + headers: string, + showNotification: boolean, + openFileFromNotification: boolean, + allowCellular: boolean, + ): Promise { + let db = this.dbHelper!.getRdbStore(); + if (db == null) { + return; + } + let values: ValuesBucket = {}; + values[TaskEntry.COLUMN_NAME_TASK_ID] = taskId; + values[TaskEntry.COLUMN_NAME_URL] = url; + values[TaskEntry.COLUMN_NAME_STATUS] = status; + values[TaskEntry.COLUMN_NAME_PROGRESS] = progress; + values[TaskEntry.COLUMN_NAME_FILE_NAME] = filename; + values[TaskEntry.COLUMN_NAME_SAVED_DIR] = savedDir; + values[TaskEntry.COLUMN_NAME_HEADERS] = headers; + values[TaskEntry.COLUMN_NAME_MIME_TYPE] = getMimeType(filename); + values[TaskEntry.COLUMN_NAME_SHOW_NOTIFICATION] = showNotification ? 1 : 0; + values[TaskEntry.COLUMN_NAME_OPEN_FILE_FROM_NOTIFICATION] = openFileFromNotification ? 1 : 0; + values[TaskEntry.COLUMN_NAME_RESUMABLE] = 0; + values[TaskEntry.COLUMN_NAME_TIME_CREATED] = Date.now(); + values[TaskEntry.COLUMN_ALLOW_CELLULAR] = allowCellular ? 1 : 0; + db.beginTransaction(); + try { + let rows = await db.insert(TaskEntry.TABLE_NAME, values, + relationalStore.ConflictResolution.ON_CONFLICT_REPLACE); + Logger.info(TAG, `insertOrUpdateNewTask row count: ${rows}`); + db.commit(); + } catch (e) { + let err: BusinessError = e as BusinessError + Logger.error(TAG, `insertOrUpdateNewTask failed, err.code: ${err.code}, err.message: ${err.message}`); + return Promise.reject(e); + } + return Promise.resolve(); + } + + async loadAllTasks(): Promise> { + let db = this.dbHelper!.getRdbStore(); + let result = new Array(); + if (db != null) { + try { + let predicates = new relationalStore.RdbPredicates(TaskEntry.TABLE_NAME); + let resultSet = await db.query(predicates, this.projection); + Logger.info(TAG, `ResultSet row count: ${resultSet.rowCount}`); + while (resultSet.goToNextRow()) { + result.push(this.parseResultSet(resultSet)); + } + // 释放数据集的内存 + resultSet.close(); + } catch (e) { + let err: BusinessError = e as BusinessError + Logger.error(TAG, `loadAllTasks failed, err.code: ${err.code}, err.message: ${err.message}`); + Promise.reject(e); + } + } + Logger.info(TAG, `ResultSet: ${JSON.stringify(result)}`); + return Promise.resolve(result); + } + + loadTasksWithRawQuery(query: string): Array { + let db = this.dbHelper!.getRdbStore(); + let result = new Array(); + if (db != null) { + try { + let resultSet = db.querySqlSync(query); + Logger.info(TAG, `ResultSet row count: ${resultSet.rowCount}`); + while (resultSet.goToNextRow()) { + result.push(this.parseResultSet(resultSet)); + } + // 释放数据集的内存 + resultSet.close(); + } catch (e) { + let err: BusinessError = e as BusinessError; + Logger.error(TAG, `loadTasksWithRawQuery failed, err.code: ${err.code}, err.message: ${err.message}`); + } + } + return result; + } + + loadTask(taskId: string): DownloadTask | null { + let db = this.dbHelper!.getRdbStore(); + let result: DownloadTask | null = null; + if (db != null) { + try { + let predicates = new relationalStore.RdbPredicates(TaskEntry.TABLE_NAME); + predicates.equalTo(TaskEntry.COLUMN_NAME_TASK_ID, taskId); + predicates.orderByDesc(TaskEntry.COLUMN_NAME_ID); + predicates.limitAs(1); + let resultSet = db.querySync(predicates); + while (resultSet.goToNextRow()) { + result = this.parseResultSet(resultSet); + } + // 释放数据集的内存 + resultSet.close(); + } catch (e) { + let err: BusinessError = e as BusinessError; + Logger.error(TAG, `loadTasksWithRawQuery failed, err.code: ${err.code}, err.message: ${err.message}`); + } + } + return result; + } + + async updateTask(taskId: string, values: ValuesBucket): Promise { + let db = this.dbHelper!.getRdbStore(); + if (db == null) { + return; + } + db.beginTransaction(); + try { + let predicates = new relationalStore.RdbPredicates(TaskEntry.TABLE_NAME); + predicates.equalTo(TaskEntry.COLUMN_NAME_TASK_ID, taskId); + let rows = await db.update(values, predicates); + Logger.info(TAG, `Updated row count: ${rows}`); + db.commit(); + } catch (e) { + let err: BusinessError = e as BusinessError + Logger.error(TAG, `updateTask err, err.code: ${err.code}, err.message: ${err.message}`); + return Promise.reject(`updateTask failed`); + } + return Promise.resolve(); + } + + async deleteTask(taskId: string): Promise { + let db = this.dbHelper!.getRdbStore(); + if (db == null) { + return; + } + db.beginTransaction(); + try { + let predicates = new relationalStore.RdbPredicates(TaskEntry.TABLE_NAME); + predicates.equalTo(TaskEntry.COLUMN_NAME_TASK_ID, taskId); + let rows = await db.delete(predicates); + Logger.info(TAG, `Deleted row count: ${rows}`); + db.commit(); + } catch (e) { + let err: BusinessError = e as BusinessError + Logger.error(TAG, `deleteTask err, err.code: ${err.code}, err.message: ${err.message}`); + return Promise.reject(`deleteTask failed`); + } + return Promise.resolve(); + } + + private parseResultSet(resultSet: relationalStore.ResultSet): DownloadTask { + const primaryId = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_ID)); + const task_id = resultSet.getString(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_TASK_ID)); + const status = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_STATUS)); + const progress = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_PROGRESS)); + const url = resultSet.getString(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_URL)); + const filename = resultSet.getString(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_FILE_NAME)); + const savedDir = resultSet.getString(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_SAVED_DIR)); + const headers = resultSet.getString(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_HEADERS)); + const mimeType = resultSet.getString(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_MIME_TYPE)); + const resumable = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_RESUMABLE)); + const showNotification = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_SHOW_NOTIFICATION)); + const clickToOpenDownloadedFile = + resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_OPEN_FILE_FROM_NOTIFICATION)); + const timeCreated = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_NAME_TIME_CREATED)); + const allowCellular = resultSet.getLong(resultSet.getColumnIndex(TaskEntry.COLUMN_ALLOW_CELLULAR)); + + let task: DownloadTask = { + primaryId: primaryId, + taskId: task_id, + status: status, + progress: progress, + url: url, + filename: filename, + savedDir: savedDir, + headers: headers, + mimeType: mimeType, + resumable: resumable == 1, + showNotification: showNotification == 1, + openFileFromNotification: clickToOpenDownloadedFile == 1, + timeCreated: timeCreated, + allowCellular: allowCellular == 1 + } + return task; + } +} \ No newline at end of file diff --git a/ohos/src/main/ets/components/TaskDbHelper.ets b/ohos/src/main/ets/components/TaskDbHelper.ets new file mode 100644 index 0000000000000000000000000000000000000000..0dc6d37ecb08dd307af927e6b411aa298c6ae3fb --- /dev/null +++ b/ohos/src/main/ets/components/TaskDbHelper.ets @@ -0,0 +1,69 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 relationalStore from '@ohos.data.relationalStore'; +import { Context } from '@kit.AbilityKit'; +import { TaskEntry } from './TaskEntry'; + +export class TaskDbHelper { + private static instance: TaskDbHelper; + private static readonly DATABASE_VERSION = 4; + private static readonly DATABASE_NAME = "download_tasks.db"; + private static readonly SQL_CREATE_ENTRIES = + "CREATE TABLE IF NOT EXISTS " + TaskEntry.TABLE_NAME + " (" + + TaskEntry.COLUMN_NAME_ID + " INTEGER PRIMARY KEY," + + TaskEntry.COLUMN_NAME_TASK_ID + " VARCHAR(256), " + + TaskEntry.COLUMN_NAME_URL + " TEXT, " + + TaskEntry.COLUMN_NAME_STATUS + " INTEGER DEFAULT 0, " + + TaskEntry.COLUMN_NAME_PROGRESS + " INTEGER DEFAULT 0, " + + TaskEntry.COLUMN_NAME_FILE_NAME + " TEXT, " + + TaskEntry.COLUMN_NAME_SAVED_DIR + " TEXT, " + + TaskEntry.COLUMN_NAME_HEADERS + " TEXT, " + + TaskEntry.COLUMN_NAME_MIME_TYPE + " VARCHAR(128), " + + TaskEntry.COLUMN_NAME_RESUMABLE + " TINYINT DEFAULT 0, " + + TaskEntry.COLUMN_NAME_SHOW_NOTIFICATION + " TINYINT DEFAULT 0, " + + TaskEntry.COLUMN_NAME_OPEN_FILE_FROM_NOTIFICATION + " TINYINT DEFAULT 0, " + + TaskEntry.COLUMN_NAME_TIME_CREATED + " INTEGER DEFAULT 0, " + + TaskEntry.COLUMN_ALLOW_CELLULAR + " TINYINT DEFAULT 1" + + ")"; + private static readonly SQL_DELETE_ENTRIES = `DROP TABLE IF EXISTS ${TaskEntry.TABLE_NAME}`; + private rdbStore: relationalStore.RdbStore | null = null; + + constructor(context: Context) { + if (TaskDbHelper.instance) { + return TaskDbHelper.instance; // 单例模式 + } + relationalStore.getRdbStore(context, { + name: TaskDbHelper.DATABASE_NAME, + securityLevel: relationalStore.SecurityLevel.S1 + }).then(rdbStore => { + this.rdbStore = rdbStore; + this.rdbStore.version = TaskDbHelper.DATABASE_VERSION; + this.rdbStore.executeSql(TaskDbHelper.SQL_CREATE_ENTRIES); + TaskDbHelper.instance = this; // 设置单例 + }) + } + + static getInstance(context?: Context): TaskDbHelper { + if (!TaskDbHelper.instance) { + TaskDbHelper.instance = new TaskDbHelper(context!); + } + return TaskDbHelper.instance; + } + + getRdbStore(): relationalStore.RdbStore | null { + return this.rdbStore; + } +} \ No newline at end of file diff --git a/ohos/src/main/ets/components/TaskEntry.ets b/ohos/src/main/ets/components/TaskEntry.ets new file mode 100644 index 0000000000000000000000000000000000000000..56aa130cbf15b78027921c72351cb2e4752bd739 --- /dev/null +++ b/ohos/src/main/ets/components/TaskEntry.ets @@ -0,0 +1,32 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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. +*/ + +export class TaskEntry { + public static readonly TABLE_NAME = "task"; + public static readonly COLUMN_NAME_ID = "_id"; + public static readonly COLUMN_NAME_TASK_ID = "task_id"; + public static readonly COLUMN_NAME_STATUS = "status"; + public static readonly COLUMN_NAME_PROGRESS = "progress"; + public static readonly COLUMN_NAME_URL = "url"; + public static readonly COLUMN_NAME_SAVED_DIR = "saved_dir"; + public static readonly COLUMN_NAME_FILE_NAME = "file_name"; + public static readonly COLUMN_NAME_MIME_TYPE = "mime_type"; + public static readonly COLUMN_NAME_RESUMABLE = "resumable"; + public static readonly COLUMN_NAME_HEADERS = "headers"; + public static readonly COLUMN_NAME_SHOW_NOTIFICATION = "show_notification"; + public static readonly COLUMN_NAME_OPEN_FILE_FROM_NOTIFICATION = "open_file_from_notification"; + public static readonly COLUMN_NAME_TIME_CREATED = "time_created"; + public static readonly COLUMN_ALLOW_CELLULAR = "allow_cellular"; +} \ No newline at end of file diff --git a/ohos/src/main/module.json5 b/ohos/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..d8ff7a7effa1344e6d1d57cd99a3f5e0ff1f33ca --- /dev/null +++ b/ohos/src/main/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "flutter_downloader", + "type": "har", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ] + } +} diff --git a/ohos/src/test/List.test.ets b/ohos/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..e3f1904373eb8182a07290c5b9a1287d1a9dd219 --- /dev/null +++ b/ohos/src/test/List.test.ets @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/ohos/src/test/LocalUnit.test.ets b/ohos/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..13b2128b8428b9368a257451baf665e38434bbac --- /dev/null +++ b/ohos/src/test/LocalUnit.test.ets @@ -0,0 +1,48 @@ +/* +* Copyright (c) 2024 SwanLink (Jiangsu) Technology Development 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 92291843c9af32d71362a96274b400807e905453..022a184a7527df5875b990730c0dc51d5ac5c5d9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,8 @@ flutter: pluginClass: FlutterDownloaderPlugin ios: pluginClass: FlutterDownloaderPlugin + ohos: + pluginClass: FlutterDownloaderPlugin environment: sdk: ">=3.3.0 <4.0.0"