This commit is contained in:
yangfan 2024-05-27 10:33:10 +08:00
commit 466d2fc789
50 changed files with 1501 additions and 0 deletions

12
.gitignore vendored Normal file
View 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
View 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"
}
}

View File

@ -0,0 +1,8 @@
{
"string": [
{
"name": "app_name",
"value": "DataMigration"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

46
build-profile.json5 Normal file
View 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

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
dependencies/rollup.tgz vendored Normal file

Binary file not shown.

6
entry/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
/node_modules
/oh_modules
/.preview
/build
/.cxx
/.test

28
entry/build-profile.json5 Normal file
View 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
View 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. */
}

View 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
View File

@ -0,0 +1,11 @@
{
"name": "entry",
"version": "1.0.0",
"description": "Please describe the basic information.",
"main": "",
"author": "",
"license": "",
"dependencies": {
}
}

View 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')
// }
}

View File

@ -0,0 +1,6 @@
import { bundleManager } from '@kit.AbilityKit'
export abstract class BaseTransferManager {
protected BundleName = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT).name
}

View 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;
}
}
}

View 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
}
}
}

View 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;
}
}

View 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
}
}

View 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');
}
}

View 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%')
}
}

View 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"
}
]
}
]
}
}

View File

@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}

View File

@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,7 @@
{
"layered-image":
{
"background" : "$media:background",
"foreground" : "$media:foreground"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,3 @@
{
"allowToBackupRestore": true
}

View File

@ -0,0 +1,5 @@
{
"src": [
"pages/Index"
]
}

View File

@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_desc",
"value": "module description"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
}
]
}

View File

@ -0,0 +1,16 @@
{
"string": [
{
"name": "module_desc",
"value": "模块描述"
},
{
"name": "EntryAbility_desc",
"value": "description"
},
{
"name": "EntryAbility_label",
"value": "label"
}
]
}

View File

@ -0,0 +1,2 @@
{
}

View 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);
})
})
}

View File

@ -0,0 +1,5 @@
import abilityTest from './Ability.test';
export default function testsuite() {
abilityTest();
}

View 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');
}
}

View 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%')
}
}

View 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;
}

View 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"
]
}
]
}
]
}
}

View File

@ -0,0 +1,8 @@
{
"color": [
{
"name": "start_window_background",
"value": "#FFFFFF"
}
]
}

View 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"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,5 @@
{
"src": [
"testability/pages/Index"
]
}

View File

@ -0,0 +1,5 @@
import localUnitTest from './LocalUnit.test';
export default function testsuite() {
localUnitTest();
}

View 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);
});
});
}

View File

@ -0,0 +1,5 @@
{
"modelVersion": "5.0.0",
"dependencies": {
}
}

6
hvigorfile.ts Normal file
View 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
View 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
View 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": {}
}