commit 466d2fc789dfa245e54d8dd7abddab7066685b90 Author: yangfan Date: Mon May 27 10:33:10 2024 +0800 1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2ff201 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/AppScope/app.json5 b/AppScope/app.json5 new file mode 100644 index 0000000..c34a0f7 --- /dev/null +++ b/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.datamigration", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/AppScope/resources/base/element/string.json b/AppScope/resources/base/element/string.json new file mode 100644 index 0000000..fad3621 --- /dev/null +++ b/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "DataMigration" + } + ] +} diff --git a/AppScope/resources/base/media/app_icon.png b/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000..a39445d Binary files /dev/null and b/AppScope/resources/base/media/app_icon.png differ diff --git a/build-profile.json5 b/build-profile.json5 new file mode 100644 index 0000000..2a22fd2 --- /dev/null +++ b/build-profile.json5 @@ -0,0 +1,46 @@ +{ + "app": { + "signingConfigs": [ + { + "name": "default", + "type": "HarmonyOS", + "material": { + "certpath": "C:\\Users\\wps\\.ohos\\config\\default_DataMigration_nuljKEfmRXHB8PLGEfuYN4YV1lGSGbA9v0wpbuwfmqk=.cer", + "storePassword": "0000001B5E9448F6AD9BADDA4C83D493A5E5F48217711679002A2C5CD29AC48C63630FE6471EB67921842A", + "keyAlias": "debugKey", + "keyPassword": "0000001B0EAED5D90BF4B127B068A11A8BABCB79E7B088CF931A3B521BC9C308CE7E18162699E1E1A760DC", + "profile": "C:\\Users\\wps\\.ohos\\config\\default_DataMigration_nuljKEfmRXHB8PLGEfuYN4YV1lGSGbA9v0wpbuwfmqk=.p7b", + "signAlg": "SHA256withECDSA", + "storeFile": "C:\\Users\\wps\\.ohos\\config\\default_DataMigration_nuljKEfmRXHB8PLGEfuYN4YV1lGSGbA9v0wpbuwfmqk=.p12" + } + } + ], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.0.0(12)", + "runtimeOS": "HarmonyOS", + } + ], + "buildModeSet": [ + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/dependencies/hvigor-4.3.0.tgz b/dependencies/hvigor-4.3.0.tgz new file mode 100644 index 0000000..4331aad Binary files /dev/null and b/dependencies/hvigor-4.3.0.tgz differ diff --git a/dependencies/hvigor-ohos-arkui-x-plugin-3.2.0.tgz b/dependencies/hvigor-ohos-arkui-x-plugin-3.2.0.tgz new file mode 100644 index 0000000..2cb6ae9 Binary files /dev/null and b/dependencies/hvigor-ohos-arkui-x-plugin-3.2.0.tgz differ diff --git a/dependencies/hvigor-ohos-plugin-4.3.0.tgz b/dependencies/hvigor-ohos-plugin-4.3.0.tgz new file mode 100644 index 0000000..6fb89f7 Binary files /dev/null and b/dependencies/hvigor-ohos-plugin-4.3.0.tgz differ diff --git a/dependencies/rollup.tgz b/dependencies/rollup.tgz new file mode 100644 index 0000000..b224a37 Binary files /dev/null and b/dependencies/rollup.tgz differ diff --git a/entry/.gitignore b/entry/.gitignore new file mode 100644 index 0000000..e2713a2 --- /dev/null +++ b/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/entry/build-profile.json5 b/entry/build-profile.json5 new file mode 100644 index 0000000..b695582 --- /dev/null +++ b/entry/build-profile.json5 @@ -0,0 +1,28 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/entry/hvigorfile.ts b/entry/hvigorfile.ts new file mode 100644 index 0000000..c6edcd9 --- /dev/null +++ b/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/entry/obfuscation-rules.txt b/entry/obfuscation-rules.txt new file mode 100644 index 0000000..985b2ae --- /dev/null +++ b/entry/obfuscation-rules.txt @@ -0,0 +1,18 @@ +# 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://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md + +# 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 \ No newline at end of file diff --git a/entry/oh-package.json5 b/entry/oh-package.json5 new file mode 100644 index 0000000..b8cb704 --- /dev/null +++ b/entry/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + } +} + diff --git a/entry/src/main/ets/backupExtension/BackupExtension.ets b/entry/src/main/ets/backupExtension/BackupExtension.ets new file mode 100644 index 0000000..706bdad --- /dev/null +++ b/entry/src/main/ets/backupExtension/BackupExtension.ets @@ -0,0 +1,123 @@ +import { BackupExtensionAbility, BundleVersion, fileUri } from '@kit.CoreFileKit'; +import fs from '@ohos.file.fs'; +import { preferences, relationalStore } from '@kit.ArkData'; +import { bundleManager } from '@kit.AbilityKit'; +import { JSON, util } from '@kit.ArkTS'; +import File from '@system.file'; + +const TAG = `BackupExtensionAbility1` +const BundleName = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT).name +const DatabaseDir = `/data/app/el2/100/database/${BundleName}/rdb` +const DocumentDir = `/data/app/el2/100/base/${BundleName}/files/document` +const PreferenceDir = `/data/app/el2/100/base/${BundleName}/preferences` +const backupDir = `/data/storage/el2/backup/restore/${BundleName}` + +export default class BackupExtension extends BackupExtensionAbility { + onBackup(): void { + console.log(TAG, `HarmonyOS to HarmonyOS NEXT scenario`); + } + + async onRestore(bundleVersion: BundleVersion): Promise { + if (bundleVersion.name.startsWith("0.0.0.0")) { + // 在此处实现终端设备从HarmonyOS 4.0升级到HarmonyOS NEXT后,应用数据的转换和迁移 + // 涉及异步操作请进行同步等待 + this.restoreData() + console.log(TAG, `HarmonyOS to HarmonyOS NEXT scenario`); + } else { + // 在此处实现从HarmonyOS NEXT设备迁移到HarmonyOS NEXT设备后,应用数据的处理。无特殊要求,可以空实现 + // 涉及异步操作请进行同步等待 + console.log(TAG, `Other scenario`); + } + } + + async restoreData() { + const databasePath = `/data/storage/el2/backup/restore/${BundleName}` + '/ce/databases/qingsdk2.db' + console.log(TAG, '1' + fs.statSync(databasePath).size) + if (!fs.accessSync(DatabaseDir)) { + fs.mkdirSync(DatabaseDir, true) + } + console.log(TAG, '2' + fs.accessSync(DatabaseDir)) + const dbFile = fs.openSync(databasePath, fs.OpenMode.READ_ONLY) + // fs.copyFileSync(dbFile.fd, DatabaseDir + '/qingsdk2.db') + // fs.closeSync(dbFile.fd) + console.log(TAG, '3' + fs.accessSync(DatabaseDir + '/qingsdk2.db')) + const rdbStore = await relationalStore.getRdbStore(this.context.getApplicationContext(), { + name: 'qingsdk2.db', + securityLevel: relationalStore.SecurityLevel.S1, + customDir: 'qingsdk2.db' + }) + const pre = new relationalStore.RdbPredicates('roaming_list') + pre.equalTo('userid', '1510872069') + rdbStore.query(pre, (err, res) => { + console.log(TAG, JSON.stringify(res)) + }) + // const fileRoot = backupDir + '/A/data/.Cloud/cn/' + // const userIds = fs.listFileSync(fileRoot) + // const docDir = this.context.filesDir + '/document' + // if (!fs.accessSync(docDir)) { + // fs.mkdirSync(docDir) + // } + // userIds.forEach((userId) => { + // const singleUserFile = fileRoot + userId + "/f" + // const guids = fs.listFileSync(singleUserFile, { + // recursion: true + // }) + // + // + // // const filepath = guids + 'test.txt' + // + // console.log(TAG, JSON.stringify(guids)) + // guids.forEach((subPath, index) => { + // const file = singleUserFile + subPath + // console.log(TAG, subPath.split('/').pop()) + // console.log(TAG, decodeURI(subPath.split('/').pop()!)) + // + // // const stat = fs.lstatSync(file) + // // console.log(TAG, file + ": "+ JSON.stringify(stat)) + // console.log(TAG, file) + // try { + // const f = fs.openSync(file) + // const name = f.name + // } catch (e) { + // console.error(TAG, JSON.stringify(e)) + // } + // // const fileName = fs.listFileSync(singleUserFile + guid)[0] + // // console.log(TAG, fileName) + // // const a = fs.openSync(singleUserFile + guid + '/' + fileName) + // // console.log(TAG, fileName) + // // const b = fs.openSync(docDir + '/' + fileName) + // // console.log(TAG, singleUserFile + guid + '/' + fileName + "|" + docDir + '/' + fileName) + // + // // console.log(TAG, "2" + fs.accessSync(docDir + '/' + name)) + // + // // fs.copyFileSync(singleUserFile + guid + '/' + fileName, docDir + '/' + fileName) + // }) + // }) + } + + async transferDb(){ + + } + + + // /** + // * 迁移本地文件 + // */ + // restoreCache() { + // const fileRoot = backupDir + '/A/data/.Cloud/cn/' + // const userIds = fs.listFileSync(fileRoot) + // userIds.forEach((userId) => { + // const singleUserFile = fileRoot + userId + /f/ + // const guids = fs.listFileSync(singleUserFile) + // guids.forEach((guid) => { + // + // }) + // }) + // console.log(TAG, JSON.stringify(fs.listFileSync(fileRoot))) + // } + // + // getFileId() { + // const databasePath = backupDir + '/ce/databases/qingsdk2.db' + // fs.copyFileSync(databasePath, DatabaseDir + '/qingsdk2.db') + // } +} \ No newline at end of file diff --git a/entry/src/main/ets/backupExtension/BaseTransferManager.ets b/entry/src/main/ets/backupExtension/BaseTransferManager.ets new file mode 100644 index 0000000..ff2a5cc --- /dev/null +++ b/entry/src/main/ets/backupExtension/BaseTransferManager.ets @@ -0,0 +1,6 @@ +import { bundleManager } from '@kit.AbilityKit' + +export abstract class BaseTransferManager { + protected BundleName = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT).name + +} \ No newline at end of file diff --git a/entry/src/main/ets/backupExtension/DbTransferManager.ets b/entry/src/main/ets/backupExtension/DbTransferManager.ets new file mode 100644 index 0000000..bd14fd7 --- /dev/null +++ b/entry/src/main/ets/backupExtension/DbTransferManager.ets @@ -0,0 +1,123 @@ +import { BaseTransferManager } from './BaseTransferManager'; +import { common } from '@kit.AbilityKit'; +import fs from '@ohos.file.fs'; +import { relationalStore } from '@kit.ArkData'; + +export default class DbTransferManager extends BaseTransferManager { + private sourceRdbStore?: relationalStore.RdbStore; + private targetRdbStore?: relationalStore.RdbStore; + + async transfer(context: common.Context) { + const result = this.moveDbFile(context) + if (!result) { + return + } + + + } + + + /** + * 初始化双框架db的rdbStore + * + * @param context context上下文 + * @return 成功返回true, 否则false + */ + private async initSourceDb(context: common.Context): Promise { + // 已经初始化后,不必重新初始化 + if (this.sourceRdbStore) { + return true; + } + try { + const SOURCE_CONFIG: relationalStore.StoreConfig = { + // 双框架数据库名,根据实际名称修改 + name: 'source_db.db', + // 数据库的安全级别, S1表示低级别,根据实际情况指定 + securityLevel: relationalStore.SecurityLevel.S1 + }; + this.sourceRdbStore = await relationalStore.getRdbStore(context, SOURCE_CONFIG); + return true; + } catch (error) { + // 异常处理 + return false; + } + } + + + /** + * 初始化单框架db的rdbStore + * + * @param context context上下文 + * @return 成功返回true, 否则false + */ + private async initTargetDb(context: common.Context): Promise { + // 已经初始化后,不必重新初始化 + if (this.targetRdbStore) { + return true; + } + try { + const TARGET_CONFIG: relationalStore.StoreConfig = { + // 单框架数据库名,根据实际名称修改 + name: 'target_db.db', + // 数据库的安全级别, S1表示低级别,根据实际情况指定 + securityLevel: relationalStore.SecurityLevel.S1 + }; + this.targetRdbStore = await relationalStore.getRdbStore(context, TARGET_CONFIG); + // 创建表,根据实际需求创建 + let createSql = `CREATE TABLE IF NOT EXISTS t_user ( + id integer PRIMARY KEY AUTOINCREMENT NOT NULL, + source_id integer, + name varchar(128) + )`; + await this.targetRdbStore.executeSql(createSql); + return true; + } catch (error) { + // 异常处理 + return false; + } + } + + + /** + * 移动双框架数据库文件到单框架 + * 文件参考@link FileTransferManager.moveFiles + * + * @param context context上下文 + * @return 成功返回true, 否则false + */ + private moveDbFile(context: common.Context): boolean { + try { + // 双升单数据迁移后,数据库默认在databases目录下 + let sourceDbPath: string = `/data/storage/el2/backup/restore/${this.BundleName}/ce/databases`; + if (!fs.accessSync(sourceDbPath)) { + return false; + } + // 得到源路径下面所有的文件和目录 + let listFileNames: string[] = fs.listFileSync(sourceDbPath); + if (!listFileNames || listFileNames.length === 0) { + return false; + } + // 单rdb默认路径 + let targetPath: string = context.databaseDir + '/rdb'; + // 目标目录不存在,则需要创建 + if (!fs.accessSync(targetPath)) { + fs.mkdirSync(targetPath); + } + + // 遍历源路径下面的文件或目录,进行移动 + listFileNames.forEach((fileName: string) => { + let srcPath = `${sourceDbPath}/${fileName}`; + let destPath = `${targetPath}/${fileName}`; + if (fs.statSync(srcPath).isFile()) { + fs.moveFileSync(srcPath, destPath); + } else { + fs.moveDirSync(srcPath, targetPath); + } + }); + return true; + } catch (error) { + // 异常处理 + return false; + } + } +} \ No newline at end of file diff --git a/entry/src/main/ets/backupExtension/FileTransferManager.ets b/entry/src/main/ets/backupExtension/FileTransferManager.ets new file mode 100644 index 0000000..b2c8fbf --- /dev/null +++ b/entry/src/main/ets/backupExtension/FileTransferManager.ets @@ -0,0 +1,60 @@ +import { common } from '@kit.AbilityKit'; +import fs from '@ohos.file.fs'; +import { BaseTransferManager } from './BaseTransferManager'; + +export class FileTransferManager extends BaseTransferManager { + /** + * 双框架文件移动到单框架指定目录 + * + * @param context context上下文 + */ + transfer(context: common.Context): void { + // move源文件到目标路径 + if (this.moveFiles(context)) { + // 文件move成功的处理 + return; + } + // 文件move失败的处理 + } + + + private moveFiles(context: common.Context): boolean { + // 文件移动逻辑 + try { + // ce路径前缀,数据迁移时会将双框架/data/data/${双包名}下的数据迁移至此处 + let cePathPrefix: string = `/data/storage/el2/backup/restore/${this.BundleName}/ce` + // 需要移动的源文件路径,根据实际路径来指定,如这里移动files/sourceFiles下面的所有文件 + let sourceFilePath: string = cePathPrefix + '/files/sourceFiles' + if (!fs.accessSync(sourceFilePath)) { + return false + } + // 得到源路径下面所有的文件和目录 + let listFileNames: string[] = fs.listFileSync(sourceFilePath) + if (!listFileNames || listFileNames.length === 0) { + return false + } + + // 目标路径根据实际情况来指定, 如这里是沙箱路径files下面的targetFiles目录 + let targetPath: string = context.filesDir + '/targetFiles' + // 目标目录不存在,则需要创建 + if (!fs.accessSync(targetPath)) { + fs.mkdirSync(targetPath); + } + + // 遍历源路径下面的文件或目录,进行移动 + listFileNames.forEach((fileName: string) => { + let srcPath: string = `${sourceFilePath}/${fileName}` + let destPath: string = `${targetPath}/${fileName}` + if (fs.statSync(srcPath).isFile()) { + fs.moveFileSync(srcPath, destPath) + } else { + fs.moveDirSync(srcPath, targetPath) + } + }) + return true + } catch (error) { + // 异常自行处理 + return false + } + } +} diff --git a/entry/src/main/ets/backupExtension/MmkvTransferManager.ets b/entry/src/main/ets/backupExtension/MmkvTransferManager.ets new file mode 100644 index 0000000..6bba519 --- /dev/null +++ b/entry/src/main/ets/backupExtension/MmkvTransferManager.ets @@ -0,0 +1,164 @@ +import { BaseTransferManager } from './BaseTransferManager'; +import common from '@ohos.app.ability.common'; +import fs from '@ohos.file.fs'; +import { MMKV } from '@ohos/MMKV'; + +// mmkv值类型定义 +type MmkvValueType = number | Set | string | boolean; + + +export class MmkvTransferManager extends BaseTransferManager { + /** + * 移动双框架mmkv文件到单框架指定目录 + * 文件参考@link FileTransferManager.moveFiles + * + * @param context context上下文 + * @return 成功返回true, 否则false + */ + private async moveMmkvFile(context: common.Context): Promise { + try { + // 双升单数据迁移后,mmkv默认在files/mmkv目录下 + let sourceMmkvPath: string = `/data/storage/el2/backup/restore/${this.BundleName}/ce/files/mmkv`; + if (!fs.accessSync(sourceMmkvPath)) { + return false; + } + // 得到源路径下面所有的文件和目录 + let listFileNames: string[] = fs.listFileSync(sourceMmkvPath); + if (!listFileNames || listFileNames.length === 0) { + return false; + } + + // 单mmkv路径, 根据实际路径指定 + let targetPath: string = context.filesDir + '/mmkv'; + // 目标目录不存在,则需要创建 + if (!fs.accessSync(targetPath)) { + fs.mkdirSync(targetPath); + } + + // 遍历源路径下面的文件或目录,进行移动 + listFileNames.forEach((fileName: string) => { + let srcPath: string = `${sourceMmkvPath}/${fileName}`; + let destPath: string = `${targetPath}/${fileName}`; + if (fs.statSync(srcPath).isFile()) { + fs.moveFileSync(srcPath, destPath); + } else { + fs.moveDirSync(srcPath, targetPath); + } + }); + return true; + } catch (error) { // 异常处理逻辑,自行实现 + return false; + } + } + + + /** + * 解析双框架mmkv文件 + * 当前例子中: + * 双的mmkv文件名为:source_mmkv, source_mmkv.crc + * 其中设置的属性有:intData 整型, boolData bool型, stringData 字符串型,floatData 浮点型, + doubleData 双精度浮点型, setData Set类型 + * 单的mmkv文件名为:target_mmkv + * + * @param context context上下文 + * @return map 包含了双mmkv中的数据 + */ + private parseSourceMmkvInfo(context: common.Context): Map { + let dataMap = new Map(); + let mmkv: MMKV | null = null; + try { + // 初始化双的mmkv, mmkv文件移动到context.filesDir + '/mmkv' 目录下了 + MMKV.initialize(context.filesDir + '/mmkv', context.cacheDir + '/mmkv'); + // mmkv文件名 + let mmapID: string = 'source_mmkv'; + // 秘钥,根据实际情况指定秘钥, 当前未设置秘钥 + let crpKey: string = ''; + mmkv = MMKV.getBackedUpMMKVWithID(mmapID, MMKV.SINGLE_PROCESS_MODE, crpKey, ''); + + // 注:当前mmkv对于双框架mmkv设置的int long float的类型暂不支持,待后续更新 + // dataMap.set('intData', mmkv.decodeNumber('intData', 0)); + // dataMap.set('longData', mmkv.decodeNumber('longData', 0)); + // dataMap.set('floatData', mmkv.decodeNumber('floatData', 0)); + dataMap.set('doubleData', mmkv.decodeNumber('doubleData', 0)); + dataMap.set('setData', mmkv.decodeSet('setData', new Set())); + dataMap.set('boolData', mmkv.decodeBool('boolData', true)); + dataMap.set('stringData', mmkv.decodeString('stringData', '')); + } catch (error) { + // 异常处理逻辑,自行实现 + } finally { + mmkv?.close(); + } + return dataMap; + } + + /** + * 插入数据到单的mmkv中 + * + * @param context context上下文 + * @param dataMap 待插入的数据 + * @return 成功返回true, 否则false + */ + private insertToTargetMmkv(context: Context, dataMap: Map): boolean { + let mmkv: MMKV | null = null; + try { + // 初始化单框架mmkv, 获取mmkv的实例可以根据实际情况自行封装成工具类调用 + MMKV.initialize(context.filesDir + '/mmkv', context.cacheDir + '/mmkv'); + // mmkv文件名 + let mmapID: string = 'target_mmkv'; + // 秘钥,根据实际情况指定秘钥, 当前未设置秘钥 + let crpKey: string = ''; + mmkv = MMKV.getMMKVWithMMapID(mmapID, MMKV.SINGLE_PROCESS_MODE, crpKey, ''); + + // 设置请求参数中的值 + mmkv.encodeNumber('doubleData', dataMap.get('doubleData') as number); + mmkv.encodeSet('setData', dataMap.get('setData') as Set); + mmkv.encodeBool('boolData', dataMap.get('boolData') as boolean); + mmkv.encodeString('stringData', dataMap.get('stringData') as string); + + // 设置其它值 + mmkv.encodeNumber('intData', 1); + mmkv.encodeNumber('floatData', 1); + mmkv.encodeNumber('longData', new Date().getTime()); + } catch (error) { + // 异常处理逻辑,自行实现 + return false; + } finally { + mmkv?.close(); + } + return true; + } + + /** + * 获取单框架中mmkv中的数据 + * + * @param context context上下文 + * @return map 包含单框架mmkv中的数据 + */ + private getDataFromTarget(context: common.Context): Map { + let dataMap = new Map(); + let mmkv: MMKV | null = null; + try { + // 初始化单框架mmkv + MMKV.initialize(context.filesDir + '/mmkv', context.cacheDir + '/mmkv'); + // mmkv文件名 + let mmapID: string = 'target_mmkv'; + // 秘钥,根据实际情况指定秘钥, 当前未设置秘钥 + let crpKey: string = ''; + mmkv = MMKV.getMMKVWithMMapID(mmapID, MMKV.SINGLE_PROCESS_MODE, crpKey, ''); + + // 取值,根据mmkv中实际存在的key来取值 + dataMap.set('doubleData', mmkv.decodeNumber('doubleData', 0)); + dataMap.set('setData', mmkv.decodeSet('setData', new Set())); + dataMap.set('boolData', mmkv.decodeBool('boolData', true)); + dataMap.set('stringData', mmkv.decodeString('stringData', '')); + dataMap.set('intData', mmkv.decodeNumber('intData', 0)); + dataMap.set('floatData', mmkv.decodeNumber('floatData', 0)); + dataMap.set('longData', mmkv.decodeNumber('longData', 0)); + } catch (error) { + // 异常处理逻辑,自行实现 + } finally { + mmkv?.close(); + } + return dataMap; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/backupExtension/SpTransferManager.ets b/entry/src/main/ets/backupExtension/SpTransferManager.ets new file mode 100644 index 0000000..f6858bf --- /dev/null +++ b/entry/src/main/ets/backupExtension/SpTransferManager.ets @@ -0,0 +1,307 @@ +import { BaseTransferManager } from './BaseTransferManager'; +import { common } from '@kit.AbilityKit'; +import convertxml from '@ohos.convertxml'; +import preferences from '@ohos.data.preferences'; +import fs from '@ohos.file.fs'; +import util from '@ohos.util'; +import { Context } from '@ohos.arkui.UIContext'; + +// xml转json对象类型定义 +interface XmlAttributes { + name?: string; + value?: string; +} + +interface XmlElement { + _elements?: XmlElement[]; + _name?: string; + _type?: string; + _text?: string; + _attributes?: XmlAttributes +} + +export class SpTransferManager extends BaseTransferManager { + /** + * 双框架sp文件移动到单框架 + * 文件迁移参考@link FileTransferManager.moveFiles + * + * @param context context上下文 + * @return 成功返回true, 否则false + */ + private moveSpFile(context: common.Context): boolean { + try { + // 双升单数据迁移后,sp文件默认在shared_prefs目录下 + let sourceSpPath = `/data/storage/el2/backup/restore/${this.BundleName}/ce/shared_prefs`; + if (!fs.accessSync(sourceSpPath)) { + return false; + } + // 得到源路径下面所有的文件和目录 + let listFileNames: string[] = fs.listFileSync(sourceSpPath); + if (!listFileNames || listFileNames.length === 0) { + return false; + } + + // 单sp默认路径 + let targetPath: string = context.preferencesDir; + // 遍历源路径下面的文件或目录,进行移动 + listFileNames.forEach((fileName: string) => { + let srcPath = `${sourceSpPath}/${fileName}`; + let destPath = `${targetPath}/${fileName}`; + if (fs.statSync(srcPath).isFile()) { + fs.moveFileSync(srcPath, destPath); + } else { + fs.moveDirSync(srcPath, targetPath); + } + }); + return true; + } catch (error) { + // 异常处理 + return false; + } + } + + + /** + * 读取双的sp xml文件的文本内容 + * 当前例子中: + * 双的sp文件名为:source_sp.xml + * + * @param context context上下文 + * @return 双sp xml文件的文本内容 + */ + private getSourceXmlStr(context: common.Context, sourceName: string): string { + let xmlContent: string = ''; + try { + // 双sp经过moveSpFile后的文件路径 + let sourceSpFilePath: string = `${context.preferencesDir}/${sourceName}`; + // 得到双sp xml文本内容 + let fileStream = fs.createStreamSync(sourceSpFilePath, 'r'); + if (!fileStream) { + return xmlContent; + } + + let bufArray: ArrayBuffer[] = []; + let totalLength = 0; + const READ_DATA_SIZE = 1024 * 1024; + let buffer = new ArrayBuffer(READ_DATA_SIZE); + let len = fileStream.readSync(buffer); + while (len !== 0) { + totalLength += len; + if (len < READ_DATA_SIZE) { + buffer = buffer.slice(0, len); + bufArray.push(buffer); + break; + } + bufArray.push(buffer); + buffer = new ArrayBuffer(READ_DATA_SIZE); + len = fs.readSync(Number(fileStream), buffer); + } + let contentBuffer = new Uint8Array(totalLength); + let offset = 0; + bufArray.forEach(bufferArr => { + let uInt8Arr = new Uint8Array(bufferArr); + contentBuffer.set(uInt8Arr, offset); + offset += uInt8Arr.byteLength; + }); + let textDecoder = util.TextDecoder.create('utf-8', { + ignoreBOM: false + }) + xmlContent = textDecoder.decodeWithStream(new Uint8Array(contentBuffer), { + stream: false + }); + } catch (error) { + // 异常处理 + } + return xmlContent; + } + + /** + * 转换双框架sp xml的文本内容为XmlElement对象 + * 详情请参考:https://docs.openharmony.cn/pages/v4.0/zh-cn/application-dev/arkts-utils/xmlconversion.md + * + * @param xmlContent xml文本内容 + * @return XmlElement xml对应的对象 + */ + private transferSourceXmlToXmlElement(xmlContent: string): XmlElement { + let result: XmlElement = {}; + if (!xmlContent) { + return result; + } + try { + let conv = new convertxml.ConvertXML(); + let options: convertxml.ConvertOptions = { + trim: false, + declarationKey: '_declaration', // 转换后文件声明使用_declaration来标识 + instructionKey: '_instruction', // 转换后指令使用_instruction标识 + attributesKey: '_attributes', // 转换后属性使用_attributes标识 + textKey: '_text', // 转换后标签值使用_text标识 + cdataKey: '_cdata', // 转换后未解析数据使用_cdata标识 + doctypeKey: '_doctype', // 转换后文档类型使用_doctype标识 + commentKey: '_comment', // 转换后注释使用_comment标识 + parentKey: '_parent', // 转换后父类使用_parent标识 + typeKey: '_type', // 转换后元素类型使用_type标识 + nameKey: '_name', // 转换后标签名称使用_name标识 + elementsKey: '_elements' // 转换后元素使用_elements标识 + }; + // 将双sp 内容转成xml对应的json对象 + let xmlJsonStr = JSON.stringify(conv.convertToJSObject(xmlContent, options)); + result = JSON.parse(xmlJsonStr) as XmlElement; + } catch (error) { + // 异常处理 + } + return result; + } + + /** + * 解析双的sp xml文件对应的XmlElement对象 + * + * @param xmlElement xmlElement对象,包含了双sp中的数据 + * @return map 存放双sp中的数据 + */ + private parseXmlElement(xmlElement: XmlElement): Map { + let dataMap = new Map(); + if (!xmlElement._elements || xmlElement._elements.length == 0) { + return dataMap; + } + let rootNode = xmlElement._elements[0]; + if (!rootNode || !rootNode._elements) { + return dataMap; + } + + for (let i = 0; i < rootNode._elements.length; i++) { + let item = rootNode._elements[i]; + let key = item._attributes?.name; + if (!key) { + continue; + } + let valueType = item._name; + switch (valueType) { + case 'string': { + // 双的sp中对应的string标签,value是标签中的值,如:stringDataValue + if (item._elements && item._elements.length > 0) { + dataMap.set(key, item._elements[0]._text as string || ''); + } + break; + } + case 'set': { + // 双中的set,格式如下: + /* + + setDataValue1 + setDataValue2 + + */ + if (item._elements) { + let strArr: string[] = []; + item._elements.forEach((setValue) => { + if (setValue._elements && setValue._elements.length > 0) { + let item = setValue._elements[0]._text as string || ''; + strArr.push(item); + } + }) + dataMap.set(key, strArr); + } + break; + } + case 'int': + case 'float': + case 'long': + case 'boolean': { + // 其他类型的值,如:(具体根据实际情况来定) + // + // + // + // + if (item._attributes) { + dataMap.set(key, item._attributes.value as preferences.ValueType); + break; + } + } + default: { + break; + } + + } + } + return dataMap; + } + + /** + * 将数据插入到单的sp中 + * 当前例子中,单的sp文件名为:target_sp.xml + * + * @param context context上下文 + * @param dataMap 待插入的数据 + * @return boolean 成功返回true, 否则false + */ + private async insertDataToTargetSp(context: Context, dataMap: Map): Promise { + try { + // 获取单框架sp (可以自行提供工具类获取sp对象用于初始化,进行读、写),当前例子中单的sp名为: + // target_sp.xml,没有则创建;以实际为准 + let targetSp = await preferences.getPreferences(context, 'target_sp.xml'); + dataMap.forEach((value, key) => { + targetSp.put(key, value); + }) + + // 插入sp后需要调用flush,保存到文件 + targetSp.flush(() => { + // flush 成功的回调逻辑,自行实现 + }); + return true; + } catch (error) { + // 异常处理 + return false; + } + } + + /** + * 从单的sp中读取数据 + * 当前例子中,包含的内容如下面取值,实际取值根据实际数据决定 + * 拥有的key为:intData, floatData, setData, boolData, stringData, longData + * + * @param context context上下文 + * @return map 包含单框架sp中的数据 + */ + private async getDataFromTargetSp(context: Context): Promise> { + let dataMap = new Map(); + try { + // 获取单框架sp,当前例子中单的sp名为:target_sp.xml,没有则创建;以实际为准 + let targetSp = await preferences.getPreferences(context, 'target_sp.xml'); + + dataMap.set('intData', targetSp.getSync('intData', 0)); + dataMap.set('floatData', targetSp.getSync('floatData', 0.0)); + dataMap.set('setData', targetSp.getSync('setData', [])); + dataMap.set('boolData', targetSp.getSync('boolData', true)); + dataMap.set('stringData', targetSp.getSync('stringData', '')); + dataMap.set('longData', targetSp.getSync('longData', 0)); + } catch (error) { + // 异常处理 + } + return dataMap; + } + + + private async isCloudSync(context: Context) { + // todo: 获取userId + const userId = 'xxx' + const xmlStr = this.getSourceXmlStr(context, 'auto_upload_switch.xml') + const xmlEle = this.transferSourceXmlToXmlElement(xmlStr) + const preMap = this.parseXmlElement(xmlEle) + const isCloudSync = preMap.get(userId) as boolean + if (isCloudSync) { + return true + } else { + return false + } + } + + private getAndroidId(context: Context) { + const key = "CHECK_DEVICEID" + const xmlStr = this.getSourceXmlStr(context, 'AppPersistenceCompat.xml') + const xmlEle = this.transferSourceXmlToXmlElement(xmlStr) + const preMap = this.parseXmlElement(xmlEle) + const androidId = preMap.get(key) as string + return androidId + } +} \ No newline at end of file diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000..8335b76 --- /dev/null +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,41 @@ +import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + 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.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); + } +} diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000..4b30ae3 --- /dev/null +++ b/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,20 @@ +@Entry +@Component +struct Index { + @State message: string = 'Data Migration'; + + build() { + RelativeContainer() { + Text(this.message) + .id('HelloWorld') + .fontSize(50) + .fontWeight(FontWeight.Bold) + .alignRules({ + center: { anchor: '__container__', align: VerticalAlign.Center }, + middle: { anchor: '__container__', align: HorizontalAlign.Center } + }) + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 new file mode 100644 index 0000000..4d8798e --- /dev/null +++ b/entry/src/main/module.json5 @@ -0,0 +1,57 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "description": "DemoBackupExtension", + // "icon": "$media:icon", + "name": "BackupExtensionAbility", + "srcEntry": "./ets/backupExtension/BackupExtension.ets", + // 对应BackupExtension.ets在代码仓中的位置 + "type": "backup", + // 类型需要选择backup + "exported": true, + "metadata": [ + // 对应注册的元数据资源 + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/element/color.json b/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000..3c71296 --- /dev/null +++ b/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/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000..f945955 --- /dev/null +++ b/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": "label" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/background.png b/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000..f939c9f Binary files /dev/null and b/entry/src/main/resources/base/media/background.png differ diff --git a/entry/src/main/resources/base/media/foreground.png b/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000..4483dda Binary files /dev/null and b/entry/src/main/resources/base/media/foreground.png differ diff --git a/entry/src/main/resources/base/media/layered_image.json b/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000..fb49920 --- /dev/null +++ b/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/entry/src/main/resources/base/media/startIcon.png b/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000..205ad8b Binary files /dev/null and b/entry/src/main/resources/base/media/startIcon.png differ diff --git a/entry/src/main/resources/base/profile/backup_config.json b/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000..78f40ae --- /dev/null +++ b/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000..1898d94 --- /dev/null +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/entry/src/main/resources/en_US/element/string.json b/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000..f945955 --- /dev/null +++ b/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": "label" + } + ] +} \ No newline at end of file diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000..597ecf9 --- /dev/null +++ b/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": "label" + } + ] +} \ No newline at end of file diff --git a/entry/src/mock/mock-config.json5 b/entry/src/mock/mock-config.json5 new file mode 100644 index 0000000..7a73a41 --- /dev/null +++ b/entry/src/mock/mock-config.json5 @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/Ability.test.ets b/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000..85c78f6 --- /dev/null +++ b/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // 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. + 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/entry/src/ohosTest/ets/test/List.test.ets b/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000..794c7dc --- /dev/null +++ b/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/testability/TestAbility.ets b/entry/src/ohosTest/ets/testability/TestAbility.ets new file mode 100644 index 0000000..3e04349 --- /dev/null +++ b/entry/src/ohosTest/ets/testability/TestAbility.ets @@ -0,0 +1,47 @@ +import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { abilityDelegatorRegistry } from '@kit.TestKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; + +export default class TestAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.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) ?? ''); + let abilityDelegator: abilityDelegatorRegistry.AbilityDelegator; + abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); + let abilityDelegatorArguments: abilityDelegatorRegistry.AbilityDelegatorArgs; + 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) => { + 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.'); + }); + } + + 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/entry/src/ohosTest/ets/testability/pages/Index.ets b/entry/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 0000000..423b427 --- /dev/null +++ b/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,17 @@ +@Entry +@Component +struct Index { + @State message: string = 'Hello World'; + + build() { + Row() { + Column() { + Text(this.message) + .fontSize(50) + .fontWeight(FontWeight.Bold) + } + .width('100%') + } + .height('100%') + } +} \ No newline at end of file diff --git a/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets b/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets new file mode 100644 index 0000000..713592f --- /dev/null +++ b/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets @@ -0,0 +1,90 @@ +import { abilityDelegatorRegistry, TestRunner } from '@kit.TestKit'; +import { UIAbility, Want } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { resourceManager } from '@kit.LocalizationKit'; +import { util } from '@kit.ArkTS'; + +let abilityDelegator: abilityDelegatorRegistry.AbilityDelegator; +let abilityDelegatorArguments: abilityDelegatorRegistry.AbilityDelegatorArgs; +let jsonPath: string = 'mock/mock-config.json'; +let tag: string = 'testTag'; + +async function onAbilityCreateCallback(data: UIAbility) { + hilog.info(0x0000, 'testTag', 'onAbilityCreateCallback, data: ${}', JSON.stringify(data)); +} + +async function addAbilityMonitorCallback(err: BusinessError) { + 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() { + let tag = 'testTag'; + hilog.info(0x0000, tag, '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = abilityDelegatorRegistry.getArguments() + abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator() + let moduleName = abilityDelegatorArguments.parameters['-m']; + let context = abilityDelegator.getAppContext().getApplicationContext().createModuleContext(moduleName); + let mResourceManager = context.resourceManager; + await checkMock(abilityDelegator, mResourceManager); + const bundleName = abilityDelegatorArguments.bundleName; + const testAbilityName: string = 'TestAbility'; + let lMonitor: abilityDelegatorRegistry.AbilityMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + moduleName: moduleName + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + const want: Want = { + bundleName: bundleName, + abilityName: testAbilityName, + moduleName: moduleName + }; + abilityDelegator.startAbility(want, (err: BusinessError, data: void) => { + hilog.info(0x0000, tag, 'startAbility : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, tag, 'startAbility : data : %{public}s', JSON.stringify(data) ?? ''); + }) + hilog.info(0x0000, tag, '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} + +async function checkMock(abilityDelegator: abilityDelegatorRegistry.AbilityDelegator, resourceManager: resourceManager.ResourceManager) { + let rawFile: Uint8Array; + try { + rawFile = resourceManager.getRawFileContentSync(jsonPath); + hilog.info(0x0000, tag, 'MockList file exists'); + let mockStr: string = util.TextDecoder.create('utf-8', { ignoreBOM: true }).decodeWithStream(rawFile); + let mockMap: Record = getMockList(mockStr); + try { + abilityDelegator.setMockList(mockMap) + } catch (error) { + let code = (error as BusinessError).code; + let message = (error as BusinessError).message; + hilog.error(0x0000, tag, `abilityDelegator.setMockList failed, error code: ${code}, message: ${message}.`); + } + } catch (error) { + let code = (error as BusinessError).code; + let message = (error as BusinessError).message; + hilog.error(0x0000, tag, `ResourceManager:callback getRawFileContent failed, error code: ${code}, message: ${message}.`); + } +} + +function getMockList(jsonStr: string) { + let jsonObj: Record = JSON.parse(jsonStr); + let map: Map = new Map(Object.entries(jsonObj)); + let mockList: Record = {}; + map.forEach((value: object, key: string) => { + let realValue: string = value['source'].toString(); + mockList[key] = realValue; + }); + hilog.info(0x0000, tag, '%{public}s', 'mock-json value:' + JSON.stringify(mockList) ?? ''); + return mockList; +} \ No newline at end of file diff --git a/entry/src/ohosTest/module.json5 b/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000..d0dc5d5 --- /dev/null +++ b/entry/src/ohosTest/module.json5 @@ -0,0 +1,38 @@ +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "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/entry/src/ohosTest/resources/base/element/color.json b/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 0000000..3c71296 --- /dev/null +++ b/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/entry/src/ohosTest/resources/base/element/string.json b/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 0000000..65d8fa5 --- /dev/null +++ b/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/entry/src/ohosTest/resources/base/media/icon.png b/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000..a39445d Binary files /dev/null and b/entry/src/ohosTest/resources/base/media/icon.png differ diff --git a/entry/src/ohosTest/resources/base/profile/test_pages.json b/entry/src/ohosTest/resources/base/profile/test_pages.json new file mode 100644 index 0000000..b7e7343 --- /dev/null +++ b/entry/src/ohosTest/resources/base/profile/test_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "testability/pages/Index" + ] +} diff --git a/entry/src/test/List.test.ets b/entry/src/test/List.test.ets new file mode 100644 index 0000000..bb5b5c3 --- /dev/null +++ b/entry/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/entry/src/test/LocalUnit.test.ets b/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000..165fc16 --- /dev/null +++ b/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +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/hvigor/hvigor-config.json5 b/hvigor/hvigor-config.json5 new file mode 100644 index 0000000..f70ecd4 --- /dev/null +++ b/hvigor/hvigor-config.json5 @@ -0,0 +1,5 @@ +{ + "modelVersion": "5.0.0", + "dependencies": { + } +} \ No newline at end of file diff --git a/hvigorfile.ts b/hvigorfile.ts new file mode 100644 index 0000000..f3cb9f1 --- /dev/null +++ b/hvigorfile.ts @@ -0,0 +1,6 @@ +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. */ +} diff --git a/oh-package-lock.json5 b/oh-package-lock.json5 new file mode 100644 index 0000000..015ce0f --- /dev/null +++ b/oh-package-lock.json5 @@ -0,0 +1,55 @@ +{ + "meta": { + "stableOrder": true + }, + "lockfileVersion": 3, + "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", + "specifiers": { + "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0", + "@ohos/hypium@1.0.17": "@ohos/hypium@1.0.17", + "@ohos/mmkv@^2.0.3": "@ohos/mmkv@2.0.3", + "libmmkv.so@oh_modules/.ohpm/@ohos+mmkv@2.0.3/oh_modules/@ohos/mmkv/src/main/cpp/types/libmmkv": "libmmkv.so@oh_modules/.ohpm/@ohos+mmkv@2.0.3/oh_modules/@ohos/mmkv/src/main/cpp/types/libmmkv", + "reflect-metadata@^0.1.13": "reflect-metadata@0.1.13" + }, + "packages": { + "@ohos/hamock@1.0.0": { + "name": "@ohos/hamock", + "version": "1.0.0", + "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har", + "registryType": "ohpm" + }, + "@ohos/hypium@1.0.17": { + "name": "@ohos/hypium", + "version": "1.0.17", + "integrity": "sha512-niGSBi8S8izx+TVSe9qKjry1HloeFVdOsBii24W5ApiAohqQIu0K/BVMavDL8YXFrqqEFsHYKcBLSDvLqmtbtQ==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.17.har", + "registryType": "ohpm" + }, + "@ohos/mmkv@2.0.3": { + "name": "@ohos/mmkv", + "version": "2.0.3", + "integrity": "sha512-SKZdSAfha39uW3O2xQfdRgPsRNwi6AHuNZ113OCukdodmUoNItXSELSOxi7zNXO3kOLQQ9FAjk4gL++MGyZthg==", + "resolved": "https://repo.harmonyos.com/ohpm/@ohos/mmkv/-/mmkv-2.0.3.har", + "registryType": "ohpm", + "dependencies": { + "reflect-metadata": "^0.1.13", + "libmmkv.so": "file:./src/main/cpp/types/libmmkv" + } + }, + "libmmkv.so@oh_modules/.ohpm/@ohos+mmkv@2.0.3/oh_modules/@ohos/mmkv/src/main/cpp/types/libmmkv": { + "name": "libmmkv.so", + "version": "0.0.0", + "resolved": "oh_modules/.ohpm/@ohos+mmkv@2.0.3/oh_modules/@ohos/mmkv/src/main/cpp/types/libmmkv", + "registryType": "local" + }, + "reflect-metadata@0.1.13": { + "name": "reflect-metadata", + "version": "0.1.13", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", + "resolved": "https://repo.harmonyos.com/ohpm/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "shasum": "67ae3ca57c972a2aa1642b10fe363fe32d49dc08", + "registryType": "ohpm" + } + } +} \ No newline at end of file diff --git a/oh-package.json5 b/oh-package.json5 new file mode 100644 index 0000000..688e9d6 --- /dev/null +++ b/oh-package.json5 @@ -0,0 +1,17 @@ +{ + "modelVersion": "5.0.0", + "name": "datamigration", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "@ohos/mmkv": "^2.0.3" + }, + "devDependencies": { + "@ohos/hypium": "1.0.17", + "@ohos/hamock": "1.0.0" + }, + "dynamicDependencies": {} +} \ No newline at end of file