1
This commit is contained in:
commit
466d2fc789
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
/node_modules
|
||||
/oh_modules
|
||||
/local.properties
|
||||
/.idea
|
||||
**/build
|
||||
/.hvigor
|
||||
.cxx
|
||||
/.clangd
|
||||
/.clang-format
|
||||
/.clang-tidy
|
||||
**/.test
|
||||
/.appanalyzer
|
10
AppScope/app.json5
Normal file
10
AppScope/app.json5
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
8
AppScope/resources/base/element/string.json
Normal file
8
AppScope/resources/base/element/string.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "app_name",
|
||||
"value": "DataMigration"
|
||||
}
|
||||
]
|
||||
}
|
BIN
AppScope/resources/base/media/app_icon.png
Normal file
BIN
AppScope/resources/base/media/app_icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
46
build-profile.json5
Normal file
46
build-profile.json5
Normal file
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
BIN
dependencies/hvigor-4.3.0.tgz
vendored
Normal file
BIN
dependencies/hvigor-4.3.0.tgz
vendored
Normal file
Binary file not shown.
BIN
dependencies/hvigor-ohos-arkui-x-plugin-3.2.0.tgz
vendored
Normal file
BIN
dependencies/hvigor-ohos-arkui-x-plugin-3.2.0.tgz
vendored
Normal file
Binary file not shown.
BIN
dependencies/hvigor-ohos-plugin-4.3.0.tgz
vendored
Normal file
BIN
dependencies/hvigor-ohos-plugin-4.3.0.tgz
vendored
Normal file
Binary file not shown.
BIN
dependencies/rollup.tgz
vendored
Normal file
BIN
dependencies/rollup.tgz
vendored
Normal file
Binary file not shown.
6
entry/.gitignore
vendored
Normal file
6
entry/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
/node_modules
|
||||
/oh_modules
|
||||
/.preview
|
||||
/build
|
||||
/.cxx
|
||||
/.test
|
28
entry/build-profile.json5
Normal file
28
entry/build-profile.json5
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"apiType": "stageMode",
|
||||
"buildOption": {
|
||||
},
|
||||
"buildOptionSet": [
|
||||
{
|
||||
"name": "release",
|
||||
"arkOptions": {
|
||||
"obfuscation": {
|
||||
"ruleOptions": {
|
||||
"enable": true,
|
||||
"files": [
|
||||
"./obfuscation-rules.txt"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
"targets": [
|
||||
{
|
||||
"name": "default"
|
||||
},
|
||||
{
|
||||
"name": "ohosTest",
|
||||
}
|
||||
]
|
||||
}
|
6
entry/hvigorfile.ts
Normal file
6
entry/hvigorfile.ts
Normal file
|
@ -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. */
|
||||
}
|
18
entry/obfuscation-rules.txt
Normal file
18
entry/obfuscation-rules.txt
Normal file
|
@ -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
|
11
entry/oh-package.json5
Normal file
11
entry/oh-package.json5
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "entry",
|
||||
"version": "1.0.0",
|
||||
"description": "Please describe the basic information.",
|
||||
"main": "",
|
||||
"author": "",
|
||||
"license": "",
|
||||
"dependencies": {
|
||||
}
|
||||
}
|
||||
|
123
entry/src/main/ets/backupExtension/BackupExtension.ets
Normal file
123
entry/src/main/ets/backupExtension/BackupExtension.ets
Normal file
|
@ -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<void> {
|
||||
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')
|
||||
// }
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import { bundleManager } from '@kit.AbilityKit'
|
||||
|
||||
export abstract class BaseTransferManager {
|
||||
protected BundleName = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT).name
|
||||
|
||||
}
|
123
entry/src/main/ets/backupExtension/DbTransferManager.ets
Normal file
123
entry/src/main/ets/backupExtension/DbTransferManager.ets
Normal file
|
@ -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<boolean> {
|
||||
// 已经初始化后,不必重新初始化
|
||||
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<boolean> {
|
||||
// 已经初始化后,不必重新初始化
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
60
entry/src/main/ets/backupExtension/FileTransferManager.ets
Normal file
60
entry/src/main/ets/backupExtension/FileTransferManager.ets
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
164
entry/src/main/ets/backupExtension/MmkvTransferManager.ets
Normal file
164
entry/src/main/ets/backupExtension/MmkvTransferManager.ets
Normal file
|
@ -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> | string | boolean;
|
||||
|
||||
|
||||
export class MmkvTransferManager extends BaseTransferManager {
|
||||
/**
|
||||
* 移动双框架mmkv文件到单框架指定目录
|
||||
* 文件参考@link FileTransferManager.moveFiles
|
||||
*
|
||||
* @param context context上下文
|
||||
* @return 成功返回true, 否则false
|
||||
*/
|
||||
private async moveMmkvFile(context: common.Context): Promise<boolean> {
|
||||
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<string>类型
|
||||
* 单的mmkv文件名为:target_mmkv
|
||||
*
|
||||
* @param context context上下文
|
||||
* @return map 包含了双mmkv中的数据
|
||||
*/
|
||||
private parseSourceMmkvInfo(context: common.Context): Map<string, MmkvValueType> {
|
||||
let dataMap = new Map<string, MmkvValueType>();
|
||||
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<string>()));
|
||||
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<string, MmkvValueType>): 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<string>);
|
||||
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<string, MmkvValueType> {
|
||||
let dataMap = new Map<string, MmkvValueType>();
|
||||
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<string>()));
|
||||
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;
|
||||
}
|
||||
}
|
307
entry/src/main/ets/backupExtension/SpTransferManager.ets
Normal file
307
entry/src/main/ets/backupExtension/SpTransferManager.ets
Normal file
|
@ -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<string, preferences.ValueType> {
|
||||
let dataMap = new Map<string, preferences.ValueType>();
|
||||
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是标签中的值,如:<stringname="stringData">stringDataValue</string>
|
||||
if (item._elements && item._elements.length > 0) {
|
||||
dataMap.set(key, item._elements[0]._text as string || '');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'set': {
|
||||
// 双中的set,格式如下:
|
||||
/*
|
||||
<set name='setData'>
|
||||
<string>setDataValue1</string>
|
||||
<string>setDataValue2</string>
|
||||
</set>
|
||||
*/
|
||||
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': {
|
||||
// 其他类型的值,如:(具体根据实际情况来定)
|
||||
// <int name="intData" value="18" />
|
||||
// <float name="floatData" value="99.5" />
|
||||
// <long name="longData" value="1713168913" />
|
||||
// <boolean name="boolData" value="false" />
|
||||
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<string,
|
||||
preferences.ValueType>): Promise<boolean> {
|
||||
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<Map<string, preferences.ValueType>> {
|
||||
let dataMap = new Map<string, preferences.ValueType>();
|
||||
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
|
||||
}
|
||||
}
|
41
entry/src/main/ets/entryability/EntryAbility.ets
Normal file
41
entry/src/main/ets/entryability/EntryAbility.ets
Normal file
|
@ -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');
|
||||
}
|
||||
}
|
20
entry/src/main/ets/pages/Index.ets
Normal file
20
entry/src/main/ets/pages/Index.ets
Normal file
|
@ -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%')
|
||||
}
|
||||
}
|
57
entry/src/main/module.json5
Normal file
57
entry/src/main/module.json5
Normal file
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
8
entry/src/main/resources/base/element/color.json
Normal file
8
entry/src/main/resources/base/element/color.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"color": [
|
||||
{
|
||||
"name": "start_window_background",
|
||||
"value": "#FFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
16
entry/src/main/resources/base/element/string.json
Normal file
16
entry/src/main/resources/base/element/string.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_desc",
|
||||
"value": "module description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
}
|
||||
]
|
||||
}
|
BIN
entry/src/main/resources/base/media/background.png
Normal file
BIN
entry/src/main/resources/base/media/background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
BIN
entry/src/main/resources/base/media/foreground.png
Normal file
BIN
entry/src/main/resources/base/media/foreground.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
7
entry/src/main/resources/base/media/layered_image.json
Normal file
7
entry/src/main/resources/base/media/layered_image.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"layered-image":
|
||||
{
|
||||
"background" : "$media:background",
|
||||
"foreground" : "$media:foreground"
|
||||
}
|
||||
}
|
BIN
entry/src/main/resources/base/media/startIcon.png
Normal file
BIN
entry/src/main/resources/base/media/startIcon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
3
entry/src/main/resources/base/profile/backup_config.json
Normal file
3
entry/src/main/resources/base/profile/backup_config.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"allowToBackupRestore": true
|
||||
}
|
5
entry/src/main/resources/base/profile/main_pages.json
Normal file
5
entry/src/main/resources/base/profile/main_pages.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"src": [
|
||||
"pages/Index"
|
||||
]
|
||||
}
|
16
entry/src/main/resources/en_US/element/string.json
Normal file
16
entry/src/main/resources/en_US/element/string.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_desc",
|
||||
"value": "module description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
}
|
||||
]
|
||||
}
|
16
entry/src/main/resources/zh_CN/element/string.json
Normal file
16
entry/src/main/resources/zh_CN/element/string.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "module_desc",
|
||||
"value": "模块描述"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_desc",
|
||||
"value": "description"
|
||||
},
|
||||
{
|
||||
"name": "EntryAbility_label",
|
||||
"value": "label"
|
||||
}
|
||||
]
|
||||
}
|
2
entry/src/mock/mock-config.json5
Normal file
2
entry/src/mock/mock-config.json5
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
35
entry/src/ohosTest/ets/test/Ability.test.ets
Normal file
35
entry/src/ohosTest/ets/test/Ability.test.ets
Normal file
|
@ -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);
|
||||
})
|
||||
})
|
||||
}
|
5
entry/src/ohosTest/ets/test/List.test.ets
Normal file
5
entry/src/ohosTest/ets/test/List.test.ets
Normal file
|
@ -0,0 +1,5 @@
|
|||
import abilityTest from './Ability.test';
|
||||
|
||||
export default function testsuite() {
|
||||
abilityTest();
|
||||
}
|
47
entry/src/ohosTest/ets/testability/TestAbility.ets
Normal file
47
entry/src/ohosTest/ets/testability/TestAbility.ets
Normal file
|
@ -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');
|
||||
}
|
||||
}
|
17
entry/src/ohosTest/ets/testability/pages/Index.ets
Normal file
17
entry/src/ohosTest/ets/testability/pages/Index.ets
Normal file
|
@ -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%')
|
||||
}
|
||||
}
|
90
entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets
Normal file
90
entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets
Normal file
|
@ -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<string, string> = 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<string, Object> = JSON.parse(jsonStr);
|
||||
let map: Map<string, object> = new Map<string, object>(Object.entries(jsonObj));
|
||||
let mockList: Record<string, string> = {};
|
||||
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;
|
||||
}
|
38
entry/src/ohosTest/module.json5
Normal file
38
entry/src/ohosTest/module.json5
Normal file
|
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
8
entry/src/ohosTest/resources/base/element/color.json
Normal file
8
entry/src/ohosTest/resources/base/element/color.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"color": [
|
||||
{
|
||||
"name": "start_window_background",
|
||||
"value": "#FFFFFF"
|
||||
}
|
||||
]
|
||||
}
|
16
entry/src/ohosTest/resources/base/element/string.json
Normal file
16
entry/src/ohosTest/resources/base/element/string.json
Normal file
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
BIN
entry/src/ohosTest/resources/base/media/icon.png
Normal file
BIN
entry/src/ohosTest/resources/base/media/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"src": [
|
||||
"testability/pages/Index"
|
||||
]
|
||||
}
|
5
entry/src/test/List.test.ets
Normal file
5
entry/src/test/List.test.ets
Normal file
|
@ -0,0 +1,5 @@
|
|||
import localUnitTest from './LocalUnit.test';
|
||||
|
||||
export default function testsuite() {
|
||||
localUnitTest();
|
||||
}
|
33
entry/src/test/LocalUnit.test.ets
Normal file
33
entry/src/test/LocalUnit.test.ets
Normal file
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
5
hvigor/hvigor-config.json5
Normal file
5
hvigor/hvigor-config.json5
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"modelVersion": "5.0.0",
|
||||
"dependencies": {
|
||||
}
|
||||
}
|
6
hvigorfile.ts
Normal file
6
hvigorfile.ts
Normal file
|
@ -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. */
|
||||
}
|
55
oh-package-lock.json5
Normal file
55
oh-package-lock.json5
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
17
oh-package.json5
Normal file
17
oh-package.json5
Normal file
|
@ -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": {}
|
||||
}
|
Loading…
Reference in New Issue
Block a user