mirror of
https://gitcode.com/gh_mirrors/re/react-native-pushy.git
synced 2025-10-07 12:25:13 +08:00
update pushy reference method (#499)
* update pushy reference method * update
This commit is contained in:
17
harmony/pushy/BuildProfile.ets
Normal file
17
harmony/pushy/BuildProfile.ets
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Use these variables when you tailor your ArkTS code. They must be of the const type.
|
||||
*/
|
||||
export const HAR_VERSION = '3.1.0-0.0.7';
|
||||
export const BUILD_MODE_NAME = 'debug';
|
||||
export const DEBUG = true;
|
||||
export const TARGET_NAME = 'default';
|
||||
|
||||
/**
|
||||
* BuildProfile Class is used only for compatibility purposes.
|
||||
*/
|
||||
export default class BuildProfile {
|
||||
static readonly HAR_VERSION = HAR_VERSION;
|
||||
static readonly BUILD_MODE_NAME = BUILD_MODE_NAME;
|
||||
static readonly DEBUG = DEBUG;
|
||||
static readonly TARGET_NAME = TARGET_NAME;
|
||||
}
|
38
harmony/pushy/OAT.xml
Normal file
38
harmony/pushy/OAT.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<oatconfig>
|
||||
<licensefile>LICENSE</licensefile>
|
||||
<filefilterlist>
|
||||
<filefilter name="copyrightPolicyFilter" desc="Filters for compatibility,license header policies">
|
||||
<filteritem type="filename" name="hvigorfile.ts" desc="hvigor构建脚本,DevEco Studio自动生成,不需要添加版权头"/>
|
||||
<filteritem type="filename" name="*.json5" desc="hvigor工程配置文件,DevEco Studio自动生成,不需要添加版权头"/>
|
||||
<filteritem type="filename" name="*.proto" desc="资源文件,不需要添加版权头"/>
|
||||
<filteritem type="filename" name="*.json" desc="资源文件,不需要添加版权头"/>
|
||||
<filteritem type="filepath" name="hvigorw" desc="工程模板,不修改版权头,以防有修改版权风险"/>
|
||||
<filteritem type="filepath" name="hvigorw.bat" desc="工程模板,不修改版权头,以防有修改版权风险"/>
|
||||
<filteritem type="filepath" name="hvigor/hvigor-wrapper.js" desc="工程模板,不修改版权头,以防有修改版权风险"/>
|
||||
<filteritem type="filename" name="LICENSE" desc="工程文件,不修改版权头"/>
|
||||
</filefilter>
|
||||
<filefilter name="defaultPolicyFilter" desc="Filters for compatibility,license header policies">
|
||||
<filteritem type="filename" name="hvigorfile.ts" desc="hvigor构建脚本,DevEco Studio自动生成,不需要添加许可证头"/>
|
||||
<filteritem type="filename" name="*.json5" desc="hvigor工程配置文件,DevEco Studio自动生成,不需要添加许可证头"/>
|
||||
<filteritem type="filename" name="LICENSE" desc="原三方库证书文件无需更改,因此添加过滤"/>
|
||||
<filteritem type="filename" name="*.proto" desc="资源文件,不需要添加许可证头"/>
|
||||
<filteritem type="filename" name="*.json" desc="资源文件,不需要添加许可证头"/>
|
||||
<filteritem type="filepath" name="hvigorw" desc="工程模板,不修改版权头,以防有修改版权风险"/>
|
||||
<filteritem type="filepath" name="hvigorw.bat" desc="工程模板,不修改版权头,以防有修改版权风险"/>
|
||||
<filteritem type="filepath" name="hvigor/hvigor-wrapper.js" desc="工程模板,不修改版权头,以防有修改版权风险"/>
|
||||
</filefilter>
|
||||
<filefilter name="binaryFileTypePolicyFilter" desc="Filters for resources files policies">
|
||||
<filteritem type="filename" name="icon.png" desc="应用图标"/>
|
||||
<filteritem type="filename" name="app_icon.png" desc="应用图标"/>
|
||||
<filteritem type="filename" name="warn.png" desc="页面展示图标"/>
|
||||
</filefilter>
|
||||
</filefilterlist>
|
||||
<policylist>
|
||||
<policy name="projectPolicy" desc="">
|
||||
<policyitem type="license" name="MIT" path="*.*" desc="license under the MIT"/>
|
||||
</policy>
|
||||
</policylist>
|
||||
</oatconfig>
|
||||
</configuration>
|
0
harmony/pushy/README.md
Normal file
0
harmony/pushy/README.md
Normal file
8
harmony/pushy/build-profile.json5
Normal file
8
harmony/pushy/build-profile.json5
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"apiType": "stageMode",
|
||||
"targets": [
|
||||
{
|
||||
"name": "default",
|
||||
}
|
||||
]
|
||||
}
|
1
harmony/pushy/hvigorfile.ts
Normal file
1
harmony/pushy/hvigorfile.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { harTasks } from '@ohos/hvigor-ohos-plugin';
|
2
harmony/pushy/index.ets
Normal file
2
harmony/pushy/index.ets
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
export * from './ts'
|
19
harmony/pushy/oh-package-lock.json5
Normal file
19
harmony/pushy/oh-package-lock.json5
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"meta": {
|
||||
"stableOrder": true
|
||||
},
|
||||
"lockfileVersion": 3,
|
||||
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
|
||||
"specifiers": {
|
||||
"@rnoh/react-native-openharmony@0.72.38": "@rnoh/react-native-openharmony@0.72.38"
|
||||
},
|
||||
"packages": {
|
||||
"@rnoh/react-native-openharmony@0.72.38": {
|
||||
"name": "@rnoh/react-native-openharmony",
|
||||
"version": "0.72.38",
|
||||
"integrity": "sha512-br5SIrbB0OarSLirenleE7eTOX1lNccMJ7nb/G7qWTyJ7kW4DalmTXVKYpoT2qaOLls1uEE7McD1OjbZZM9jug==",
|
||||
"resolved": "https://ohpm.openharmony.cn/ohpm/@rnoh/react-native-openharmony/-/react-native-openharmony-0.72.38.har",
|
||||
"registryType": "ohpm"
|
||||
}
|
||||
}
|
||||
}
|
12
harmony/pushy/oh-package.json5
Normal file
12
harmony/pushy/oh-package.json5
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"license": "ISC",
|
||||
"types": "",
|
||||
"devDependencies": {},
|
||||
"name": "pushy",
|
||||
"description": "",
|
||||
"main": "index.ets",
|
||||
"version": "3.1.0-0.0.7",
|
||||
"dependencies": {
|
||||
"@rnoh/react-native-openharmony":"^0.72.38"
|
||||
}
|
||||
}
|
36
harmony/pushy/src/main/cpp/CMakeLists.txt
Normal file
36
harmony/pushy/src/main/cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,36 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
project(rnupdate)
|
||||
|
||||
set(HDIFFPATCH_DIR ${CMAKE_CURRENT_SOURCE_DIR}/HDiffPatch)
|
||||
set(LZMA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lzma)
|
||||
set(HDP_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/pushy.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/hpatch.c
|
||||
${HDIFFPATCH_DIR}/libHDiffPatch/HPatch/patch.c
|
||||
${HDIFFPATCH_DIR}/file_for_patch.c
|
||||
${LZMA_DIR}/C/LzmaDec.c
|
||||
${LZMA_DIR}/C/Lzma2Dec.c
|
||||
)
|
||||
set(CMAKE_VERBOSE_MAKEFILE on)
|
||||
|
||||
|
||||
add_library(rnupdate SHARED
|
||||
${HDP_SOURCES}
|
||||
)
|
||||
|
||||
target_include_directories(rnupdate PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${HDIFFPATCH_DIR}
|
||||
${HDIFFPATCH_DIR}/libHDiffPatch/HPatch
|
||||
${LZMA_DIR}/C
|
||||
)
|
||||
|
||||
target_link_libraries(rnupdate PUBLIC
|
||||
libace_napi.z.so
|
||||
)
|
||||
|
||||
file(GLOB rnoh_pushy_SRC CONFIGURE_DEPENDS *.cpp)
|
||||
add_library(rnoh_pushy SHARED ${rnoh_pushy_SRC})
|
||||
target_include_directories(rnoh_pushy PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(rnoh_pushy PUBLIC rnoh)
|
||||
|
55
harmony/pushy/src/main/cpp/PushyPackage.h
Normal file
55
harmony/pushy/src/main/cpp/PushyPackage.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (C) 2023 Huawei Device Co., Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef GEO_LOCATION_PACKAGE_H
|
||||
#define GEO_LOCATION_PACKAGE_H
|
||||
|
||||
#include "RNOH/Package.h"
|
||||
#include "PushyTurboModule.h"
|
||||
|
||||
using namespace rnoh;
|
||||
using namespace facebook;
|
||||
|
||||
class PushyTurboModuleFactoryDelegate : public TurboModuleFactoryDelegate {
|
||||
public:
|
||||
SharedTurboModule createTurboModule(Context ctx, const std::string &name) const override
|
||||
{
|
||||
if (name == "Pushy") {
|
||||
return std::make_shared<PushyTurboModule>(ctx, name);
|
||||
}
|
||||
return nullptr;
|
||||
};
|
||||
};
|
||||
|
||||
namespace rnoh {
|
||||
class PushyPackage : public Package {
|
||||
public:
|
||||
PushyPackage(Package::Context ctx) : Package(ctx){}
|
||||
std::unique_ptr<TurboModuleFactoryDelegate> createTurboModuleFactoryDelegate() override
|
||||
{
|
||||
return std::make_unique<PushyTurboModuleFactoryDelegate>();
|
||||
}
|
||||
};
|
||||
} // namespace rnoh
|
||||
#endif
|
142
harmony/pushy/src/main/cpp/PushyTurboModule.cpp
Normal file
142
harmony/pushy/src/main/cpp/PushyTurboModule.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
|
||||
#include "PushyTurboModule.h"
|
||||
|
||||
using namespace rnoh;
|
||||
using namespace facebook;
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_getConstants(
|
||||
jsi::Runtime &rt,
|
||||
react::TurboModule & turboModule,
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"getConstants", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_setLocalHashInfo(
|
||||
jsi::Runtime &rt,
|
||||
react::TurboModule & turboModule,
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"setLocalHashInfo", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_getLocalHashInfo(
|
||||
jsi::Runtime &rt,
|
||||
react::TurboModule & turboModule,
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"getLocalHashInfo", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_setUuid(
|
||||
jsi::Runtime &rt,
|
||||
react::TurboModule & turboModule,
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"setUuid", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_reloadUpdate(
|
||||
jsi::Runtime &rt,
|
||||
react::TurboModule & turboModule,
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"reloadUpdate", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_setNeedUpdate(
|
||||
jsi::Runtime &rt,
|
||||
react::TurboModule & turboModule,
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"setNeedUpdate", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_markSuccess(
|
||||
jsi::Runtime &rt,
|
||||
react::TurboModule & turboModule,
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"markSuccess", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_downloadPatchFromPpk(
|
||||
jsi::Runtime &rt,
|
||||
react::TurboModule & turboModule,
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"downloadPatchFromPpk", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_downloadPatchFromPackage(
|
||||
jsi::Runtime &rt,
|
||||
react::TurboModule & turboModule,
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"downloadPatchFromPackage", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_downloadFullUpdate(
|
||||
jsi::Runtime &rt,
|
||||
react::TurboModule & turboModule,
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"downloadFullUpdate", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_downloadAndInstallApk(
|
||||
jsi::Runtime &rt,
|
||||
react::TurboModule & turboModule,
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"downloadAndInstallApk", args, count));
|
||||
}
|
||||
|
||||
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_addListener(
|
||||
jsi::Runtime &rt,
|
||||
react::TurboModule & turboModule,
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"addListener", args, count));
|
||||
}
|
||||
|
||||
static jsi::Value _hostFunction_PushyTurboModule_removeListeners(
|
||||
jsi::Runtime &rt,
|
||||
react::TurboModule & turboModule,
|
||||
const jsi::Value* args,
|
||||
size_t count)
|
||||
{
|
||||
return jsi::Value(static_cast<ArkTSTurboModule &> (turboModule).call(rt,"removeListeners", args, count));
|
||||
}
|
||||
|
||||
PushyTurboModule::PushyTurboModule(const ArkTSTurboModule::Context ctx, const std::string name)
|
||||
: ArkTSTurboModule(ctx,name)
|
||||
{
|
||||
methodMap_["getConstants"]= MethodMetadata{1, _hostFunction_PushyTurboModule_getConstants};
|
||||
methodMap_["setLocalHashInfo"]= MethodMetadata{2, _hostFunction_PushyTurboModule_setLocalHashInfo};
|
||||
methodMap_["getLocalHashInfo"]= MethodMetadata{3, _hostFunction_PushyTurboModule_getLocalHashInfo};
|
||||
methodMap_["setUuid"]= MethodMetadata{1, _hostFunction_PushyTurboModule_setUuid};
|
||||
methodMap_["reloadUpdate"]= MethodMetadata{0, _hostFunction_PushyTurboModule_reloadUpdate};
|
||||
methodMap_["setNeedUpdate"]= MethodMetadata{0, _hostFunction_PushyTurboModule_setNeedUpdate};
|
||||
methodMap_["markSuccess"]= MethodMetadata{0, _hostFunction_PushyTurboModule_markSuccess};
|
||||
methodMap_["downloadPatchFromPpk"]= MethodMetadata{0, _hostFunction_PushyTurboModule_downloadPatchFromPpk};
|
||||
methodMap_["downloadPatchFromPackage"]= MethodMetadata{0, _hostFunction_PushyTurboModule_downloadPatchFromPackage};
|
||||
methodMap_["downloadFullUpdate"]= MethodMetadata{0, _hostFunction_PushyTurboModule_downloadFullUpdate};
|
||||
methodMap_["downloadAndInstallApk"]= MethodMetadata{0, _hostFunction_PushyTurboModule_downloadAndInstallApk};
|
||||
methodMap_["addListener"]= MethodMetadata{1, _hostFunction_PushyTurboModule_addListener};
|
||||
methodMap_["removeListeners"]= MethodMetadata{1, _hostFunction_PushyTurboModule_removeListeners};
|
||||
}
|
38
harmony/pushy/src/main/cpp/PushyTurboModule.h
Normal file
38
harmony/pushy/src/main/cpp/PushyTurboModule.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (C) 2023 Huawei Device Co., Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef GEO_LOCATION_TURBOMODULE_H
|
||||
#define GEO_LOCATION_TURBOMODULE_H
|
||||
|
||||
#include <ReactCommon/TurboModule.h>
|
||||
#include "RNOH/ArkTSTurboModule.h"
|
||||
|
||||
namespace rnoh {
|
||||
class JSI_EXPORT PushyTurboModule : public ArkTSTurboModule {
|
||||
public:
|
||||
PushyTurboModule(const ArkTSTurboModule::Context ctx, const std::string name);
|
||||
};
|
||||
} // namespace rnoh
|
||||
|
||||
#endif
|
137
harmony/pushy/src/main/cpp/hpatch.c
Normal file
137
harmony/pushy/src/main/cpp/hpatch.c
Normal file
@@ -0,0 +1,137 @@
|
||||
// hpatch.c
|
||||
// Copyright 2021 housisong, All rights reserved
|
||||
#include "hpatch.h"
|
||||
#include "HDiffPatch/libHDiffPatch/HPatch/patch.h"
|
||||
#include "HDiffPatch/file_for_patch.h"
|
||||
|
||||
//#define _CompressPlugin_zlib
|
||||
//#define _CompressPlugin_bz2
|
||||
#define _CompressPlugin_lzma
|
||||
#define _CompressPlugin_lzma2
|
||||
#define _IsNeedIncludeDefaultCompressHead 0
|
||||
#include "lzma/C/LzmaDec.h"
|
||||
#include "lzma/C/Lzma2Dec.h"
|
||||
#include "HDiffPatch/decompress_plugin_demo.h"
|
||||
|
||||
#define kMaxLoadMemOldSize ((1<<20)*8)
|
||||
|
||||
#define _check(v,errorType) do{ \
|
||||
if (!(v)){ if (result==kHPatch_ok) result=errorType; if (!_isInClear){ goto _clear; }; } }while(0)
|
||||
|
||||
int hpatch_getInfo_by_mem(hpatch_singleCompressedDiffInfo* out_patinfo,
|
||||
const uint8_t* pat,size_t patsize){
|
||||
hpatch_TStreamInput patStream;
|
||||
mem_as_hStreamInput(&patStream,pat,pat+patsize);
|
||||
if (!getSingleCompressedDiffInfo(out_patinfo,&patStream,0))
|
||||
return kHPatch_error_info;//data error;
|
||||
return kHPatch_ok; //ok
|
||||
}
|
||||
|
||||
static hpatch_TDecompress* getDecompressPlugin(const char* compressType){
|
||||
#ifdef _CompressPlugin_zlib
|
||||
if (zlibDecompressPlugin.is_can_open(compressType))
|
||||
return &zlibDecompressPlugin;
|
||||
#endif
|
||||
#ifdef _CompressPlugin_bz2
|
||||
if (bz2DecompressPlugin.is_can_open(compressType))
|
||||
return &bz2DecompressPlugin;
|
||||
#endif
|
||||
#ifdef _CompressPlugin_lzma
|
||||
if (lzmaDecompressPlugin.is_can_open(compressType))
|
||||
return &lzmaDecompressPlugin;
|
||||
#endif
|
||||
#ifdef _CompressPlugin_lzma2
|
||||
if (lzma2DecompressPlugin.is_can_open(compressType))
|
||||
return &lzma2DecompressPlugin;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
static int hpatch_by_stream(const hpatch_TStreamInput* old,hpatch_BOOL isLoadOldAllToMem,const hpatch_TStreamInput* pat,
|
||||
hpatch_TStreamOutput* out_new,const hpatch_singleCompressedDiffInfo* patInfo){
|
||||
int result=kHPatch_ok;
|
||||
int _isInClear=hpatch_FALSE;
|
||||
hpatch_TDecompress* decompressPlugin=0;
|
||||
uint8_t* temp_cache=0;
|
||||
size_t temp_cache_size;
|
||||
hpatch_singleCompressedDiffInfo _patinfo;
|
||||
hpatch_TStreamInput _old;
|
||||
{// info
|
||||
if (!patInfo){
|
||||
_check(getSingleCompressedDiffInfo(&_patinfo,pat,0),kHPatch_error_info);
|
||||
patInfo=&_patinfo;
|
||||
}
|
||||
_check(old->streamSize==patInfo->oldDataSize,kHPatch_error_old_size);
|
||||
_check(out_new->streamSize>=patInfo->newDataSize,kHPatch_error_new_size);
|
||||
out_new->streamSize=patInfo->newDataSize;
|
||||
if (strlen(patInfo->compressType)>0){
|
||||
decompressPlugin=getDecompressPlugin(patInfo->compressType);
|
||||
_check(decompressPlugin,kHPatch_error_compressType);
|
||||
}
|
||||
}
|
||||
{// mem
|
||||
size_t mem_size;
|
||||
size_t oldSize=(size_t)old->streamSize;
|
||||
isLoadOldAllToMem=isLoadOldAllToMem&&(old->streamSize<=kMaxLoadMemOldSize);
|
||||
temp_cache_size=patInfo->stepMemSize+hpatch_kFileIOBufBetterSize*3;
|
||||
mem_size=temp_cache_size+(isLoadOldAllToMem?oldSize:0);
|
||||
temp_cache=malloc(mem_size);
|
||||
_check(temp_cache,kHPatch_error_malloc);
|
||||
if (isLoadOldAllToMem){//load old to mem
|
||||
uint8_t* oldMem=temp_cache+temp_cache_size;
|
||||
_check(old->read(old,0,oldMem,oldMem+oldSize),kHPatch_error_old_fread);
|
||||
mem_as_hStreamInput(&_old,oldMem,oldMem+oldSize);
|
||||
old=&_old;
|
||||
}
|
||||
}
|
||||
|
||||
_check(patch_single_compressed_diff(out_new,old,pat,patInfo->diffDataPos,
|
||||
patInfo->uncompressedSize,decompressPlugin,patInfo->coverCount,
|
||||
patInfo->stepMemSize,temp_cache,temp_cache+temp_cache_size),kHPatch_error_patch);
|
||||
|
||||
_clear:
|
||||
_isInClear=hpatch_TRUE;
|
||||
if (temp_cache){ free(temp_cache); temp_cache=0; }
|
||||
return result;
|
||||
}
|
||||
|
||||
int hpatch_by_mem(const uint8_t* old,size_t oldsize,uint8_t* newBuf,size_t newsize,
|
||||
const uint8_t* pat,size_t patsize,const hpatch_singleCompressedDiffInfo* patInfo){
|
||||
hpatch_TStreamInput oldStream;
|
||||
hpatch_TStreamInput patStream;
|
||||
hpatch_TStreamOutput newStream;
|
||||
mem_as_hStreamInput(&oldStream,old,old+oldsize);
|
||||
mem_as_hStreamInput(&patStream,pat,pat+patsize);
|
||||
mem_as_hStreamOutput(&newStream,newBuf,newBuf+newsize);
|
||||
return hpatch_by_stream(&oldStream,hpatch_FALSE,&patStream,&newStream,patInfo);
|
||||
}
|
||||
|
||||
int hpatch_by_file(const char* oldfile, const char* newfile, const char* patchfile){
|
||||
int result=kHPatch_ok;
|
||||
int _isInClear=hpatch_FALSE;
|
||||
int patch_result;
|
||||
hpatch_TFileStreamInput oldStream;
|
||||
hpatch_TFileStreamInput patStream;
|
||||
hpatch_TFileStreamOutput newStream;
|
||||
hpatch_TFileStreamInput_init(&oldStream);
|
||||
hpatch_TFileStreamInput_init(&patStream);
|
||||
hpatch_TFileStreamOutput_init(&newStream);
|
||||
|
||||
_check(hpatch_TFileStreamInput_open(&oldStream,oldfile),kHPatch_error_old_fopen);
|
||||
_check(hpatch_TFileStreamInput_open(&patStream,patchfile),kHPatch_error_pat_fopen);
|
||||
_check(hpatch_TFileStreamOutput_open(&newStream,newfile,~(hpatch_StreamPos_t)0),kHPatch_error_new_fopen);
|
||||
|
||||
patch_result=hpatch_by_stream(&oldStream.base,hpatch_TRUE,&patStream.base,&newStream.base,0);
|
||||
if (patch_result!=kHPatch_ok){
|
||||
_check(!oldStream.fileError,kHPatch_error_old_fread);
|
||||
_check(!patStream.fileError,kHPatch_error_pat_fread);
|
||||
_check(!newStream.fileError,kHPatch_error_new_fwrite);
|
||||
_check(hpatch_FALSE,patch_result);
|
||||
}
|
||||
|
||||
_clear:
|
||||
_isInClear=hpatch_TRUE;
|
||||
_check(hpatch_TFileStreamInput_close(&oldStream),kHPatch_error_old_fclose);
|
||||
_check(hpatch_TFileStreamInput_close(&patStream),kHPatch_error_pat_fclose);
|
||||
_check(hpatch_TFileStreamOutput_close(&newStream),kHPatch_error_new_fclose);
|
||||
return result;
|
||||
}
|
44
harmony/pushy/src/main/cpp/hpatch.h
Normal file
44
harmony/pushy/src/main/cpp/hpatch.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// hpatch.h
|
||||
// import HDiffPatch, support patchData created by "hdiffz -SD -c-lzma2 oldfile newfile patchfile"
|
||||
// Copyright 2021 housisong, All rights reserved
|
||||
|
||||
#ifndef HDIFFPATCH_PATCH_H
|
||||
#define HDIFFPATCH_PATCH_H
|
||||
# include <stdint.h> //for uint8_t
|
||||
#include "HDiffPatch/libHDiffPatch/HPatch/patch_types.h" //for hpatch_singleCompressedDiffInfo
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//result
|
||||
enum {
|
||||
kHPatch_ok = 0,
|
||||
kHPatch_error_malloc =-1,
|
||||
kHPatch_error_info =-2,
|
||||
kHPatch_error_compressType =-3,
|
||||
kHPatch_error_patch =-4,
|
||||
kHPatch_error_old_fopen =-5,
|
||||
kHPatch_error_old_fread =-6,
|
||||
kHPatch_error_old_fclose =-7,
|
||||
kHPatch_error_pat_fopen =-8,
|
||||
kHPatch_error_pat_fread =-9,
|
||||
kHPatch_error_pat_fclose =-10,
|
||||
kHPatch_error_new_fopen =-11,
|
||||
kHPatch_error_new_fwrite =-12,
|
||||
kHPatch_error_new_fclose =-13,
|
||||
kHPatch_error_old_size =-14,
|
||||
kHPatch_error_new_size =-15,
|
||||
};
|
||||
|
||||
int hpatch_getInfo_by_mem(hpatch_singleCompressedDiffInfo* out_patinfo,
|
||||
const uint8_t* pat,size_t patsize);
|
||||
|
||||
//patInfo can NULL
|
||||
int hpatch_by_mem(const uint8_t* old,size_t oldsize, uint8_t* newBuf,size_t newsize,
|
||||
const uint8_t* pat,size_t patsize,const hpatch_singleCompressedDiffInfo* patInfo);
|
||||
int hpatch_by_file(const char* oldfile, const char* newfile, const char* patchfile);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif //HDIFFPATCH_PATCH_H
|
117
harmony/pushy/src/main/cpp/pushy.c
Normal file
117
harmony/pushy/src/main/cpp/pushy.c
Normal file
@@ -0,0 +1,117 @@
|
||||
#include "pushy.h"
|
||||
#include "hpatch.h"
|
||||
#include <napi/native_api.h>
|
||||
#include <js_native_api.h>
|
||||
#include <js_native_api_types.h>
|
||||
|
||||
#define _check(v,errInfo) do { \
|
||||
if (!(v)) { \
|
||||
_isError = hpatch_TRUE; \
|
||||
_errInfo = errInfo; \
|
||||
goto _clear; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
napi_value HdiffPatch(napi_env env, napi_callback_info info) {
|
||||
napi_status status;
|
||||
size_t argc = 2;
|
||||
napi_value args[2];
|
||||
hpatch_BOOL _isError = hpatch_FALSE;
|
||||
const char* _errInfo = "";
|
||||
|
||||
// 获取参数
|
||||
status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
||||
if (status != napi_ok || argc < 2) {
|
||||
napi_throw_error(env, NULL, "Wrong number of arguments");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 获取origin buffer
|
||||
bool isTypedArray;
|
||||
status = napi_is_typedarray(env, args[0], &isTypedArray);
|
||||
if (status != napi_ok || !isTypedArray) {
|
||||
napi_throw_error(env, NULL, "First argument must be a TypedArray");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t* originPtr;
|
||||
size_t originLength;
|
||||
status = napi_get_typedarray_info(env, args[0], NULL, &originLength,
|
||||
(void**)&originPtr, NULL, NULL);
|
||||
if (status != napi_ok) {
|
||||
napi_throw_error(env, NULL, "Failed to get origin buffer");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 获取patch buffer
|
||||
status = napi_is_typedarray(env, args[1], &isTypedArray);
|
||||
if (status != napi_ok || !isTypedArray) {
|
||||
napi_throw_error(env, NULL, "Second argument must be a TypedArray");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t* patchPtr;
|
||||
size_t patchLength;
|
||||
status = napi_get_typedarray_info(env, args[1], NULL, &patchLength,
|
||||
(void**)&patchPtr, NULL, NULL);
|
||||
if (status != napi_ok) {
|
||||
napi_throw_error(env, NULL, "Failed to get patch buffer");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 执行patch操作
|
||||
hpatch_singleCompressedDiffInfo patInfo;
|
||||
|
||||
_check(((originLength==0)||originPtr) && patchPtr && (patchLength>0), "Corrupt patch");
|
||||
_check(kHPatch_ok==hpatch_getInfo_by_mem(&patInfo, patchPtr, patchLength), "Error info in hpatch");
|
||||
_check(originLength==patInfo.oldDataSize, "Error oldDataSize in hpatch");
|
||||
size_t newsize = (size_t)patInfo.newDataSize;
|
||||
if (sizeof(size_t)!=sizeof(hpatch_StreamPos_t))
|
||||
_check(newsize==patInfo.newDataSize, "Error newDataSize in hpatch");
|
||||
|
||||
// 创建结果buffer
|
||||
napi_value resultBuffer;
|
||||
uint8_t* outPtr;
|
||||
void* data;
|
||||
|
||||
status = napi_create_arraybuffer(env, newsize, &data, &resultBuffer);
|
||||
if (status != napi_ok) {
|
||||
napi_throw_error(env, NULL, "Failed to create result buffer");
|
||||
return NULL;
|
||||
}
|
||||
outPtr = (uint8_t*)data;
|
||||
|
||||
// 执行patch
|
||||
_check(kHPatch_ok==hpatch_by_mem(originPtr, originLength, outPtr, newsize,
|
||||
patchPtr, patchLength, &patInfo), "hpatch");
|
||||
return resultBuffer;
|
||||
|
||||
_clear:
|
||||
if (_isError) {
|
||||
napi_throw_error(env, NULL, _errInfo);
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 模块初始化
|
||||
napi_value Init(napi_env env, napi_value exports) {
|
||||
napi_status status;
|
||||
napi_value fn;
|
||||
|
||||
status = napi_create_function(env, NULL, 0, HdiffPatch, NULL, &fn);
|
||||
if (status != napi_ok) {
|
||||
napi_throw_error(env, NULL, "Unable to wrap native function");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
status = napi_set_named_property(env, exports, "hdiffPatch", fn);
|
||||
if (status != napi_ok) {
|
||||
napi_throw_error(env, NULL, "Unable to populate exports");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|
8
harmony/pushy/src/main/cpp/pushy.h
Normal file
8
harmony/pushy/src/main/cpp/pushy.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef _DOWNLOAD_TASK_H_
|
||||
#define _DOWNLOAD_TASK_H_
|
||||
|
||||
#include <napi/native_api.h>
|
||||
|
||||
napi_value HdiffPatch(napi_env env, napi_callback_info info);
|
||||
|
||||
#endif // _DOWNLOAD_TASK_H_
|
501
harmony/pushy/src/main/ets/DownloadTask.ts
Normal file
501
harmony/pushy/src/main/ets/DownloadTask.ts
Normal file
@@ -0,0 +1,501 @@
|
||||
import http from '@ohos.net.http';
|
||||
import fileIo from '@ohos.file.fs';
|
||||
import util from '@ohos.util';
|
||||
import common from '@ohos.app.ability.common';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { buffer } from '@kit.ArkTS';
|
||||
import zip from '@ohos.zlib';
|
||||
import { EventHub } from './EventHub';
|
||||
import { DownloadTaskParams } from './DownloadTaskParams';
|
||||
import Pushy from 'librnupdate.so';
|
||||
|
||||
interface ZipEntry {
|
||||
filename: string;
|
||||
content: ArrayBuffer;
|
||||
}
|
||||
|
||||
interface ZipFile {
|
||||
entries: ZipEntry[];
|
||||
}
|
||||
|
||||
export class DownloadTask {
|
||||
private context: common.Context;
|
||||
private hash: string;
|
||||
private readonly DOWNLOAD_CHUNK_SIZE = 4096;
|
||||
private eventHub: EventHub;
|
||||
|
||||
constructor(context: common.Context) {
|
||||
this.context = context;
|
||||
this.eventHub = EventHub.getInstance();
|
||||
}
|
||||
|
||||
private async removeDirectory(path: string): Promise<void> {
|
||||
try {
|
||||
const res = fileIo.accessSync(path);
|
||||
if (res) {
|
||||
const stat = await fileIo.stat(path);
|
||||
if (stat.isDirectory()) {
|
||||
const files = await fileIo.listFile(path);
|
||||
for (const file of files) {
|
||||
if (file === '.' || file === '..') continue;
|
||||
await this.removeDirectory(`${path}/${file}`);
|
||||
}
|
||||
await fileIo.rmdir(path);
|
||||
} else {
|
||||
await fileIo.unlink(path);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to delete directory:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async downloadFile(params: DownloadTaskParams): Promise<void> {
|
||||
const httpRequest = http.createHttp();
|
||||
this.hash = params.hash;
|
||||
|
||||
try {
|
||||
try {
|
||||
const exists = fileIo.accessSync(params.targetFile);
|
||||
if (exists) {
|
||||
await fileIo.unlink(params.targetFile);
|
||||
}else{
|
||||
const targetDir = params.targetFile.substring(
|
||||
0,
|
||||
params.targetFile.lastIndexOf('/'),
|
||||
);
|
||||
const exists = fileIo.accessSync(targetDir);
|
||||
if(!exists){
|
||||
await fileIo.mkdir(targetDir);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const response = await httpRequest.request(params.url, {
|
||||
method: http.RequestMethod.GET,
|
||||
readTimeout: 60000,
|
||||
connectTimeout: 60000,
|
||||
header: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
},
|
||||
});
|
||||
if (response.responseCode > 299) {
|
||||
throw new Error(`Server error: ${response.responseCode}`);
|
||||
}
|
||||
|
||||
const contentLength = parseInt(response.header['content-length'] || '0');
|
||||
const writer = await fileIo.open(
|
||||
params.targetFile,
|
||||
fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE,
|
||||
);
|
||||
let received = 0;
|
||||
const data = response.result as ArrayBuffer;
|
||||
const chunks = Math.ceil(data.byteLength / this.DOWNLOAD_CHUNK_SIZE);
|
||||
for (let i = 0; i < chunks; i++) {
|
||||
const start = i * this.DOWNLOAD_CHUNK_SIZE;
|
||||
const end = Math.min(start + this.DOWNLOAD_CHUNK_SIZE, data.byteLength);
|
||||
const chunk = data.slice(start, end);
|
||||
|
||||
await fileIo.write(writer.fd, chunk);
|
||||
received += chunk.byteLength;
|
||||
|
||||
this.onProgressUpdate(received, contentLength);
|
||||
}
|
||||
await fileIo.close(writer);
|
||||
const stats = await fileIo.stat(params.targetFile);
|
||||
const fileSize = stats.size;
|
||||
if (fileSize !== contentLength) {
|
||||
throw new Error(`Download incomplete: expected ${contentLength} bytes but got ${stats.size} bytes`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Download failed:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
httpRequest.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
private onProgressUpdate(received: number, total: number): void {
|
||||
this.eventHub.emit('RCTPushyDownloadProgress', {
|
||||
received,
|
||||
total,
|
||||
hash: this.hash,
|
||||
});
|
||||
}
|
||||
|
||||
private async copyFile(from: string, to: string): Promise<void> {
|
||||
let reader;
|
||||
let writer;
|
||||
try {
|
||||
reader = fileIo.openSync(from, fileIo.OpenMode.READ_ONLY);
|
||||
writer = fileIo.openSync(
|
||||
to,
|
||||
fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY,
|
||||
);
|
||||
const arrayBuffer = new ArrayBuffer(4096);
|
||||
let bytesRead: number;
|
||||
do {
|
||||
bytesRead = await fileIo
|
||||
.read(reader.fd, arrayBuffer)
|
||||
.catch((err: BusinessError) => {
|
||||
throw new Error(
|
||||
`Error reading file: ${err.message}, code: ${err.code}`,
|
||||
);
|
||||
});
|
||||
if (bytesRead > 0) {
|
||||
const buf = buffer.from(arrayBuffer, 0, bytesRead);
|
||||
await fileIo
|
||||
.write(writer.fd, buf.buffer, {
|
||||
offset: 0,
|
||||
length: bytesRead,
|
||||
})
|
||||
.catch((err: BusinessError) => {
|
||||
throw new Error(
|
||||
`Error writing file: ${err.message}, code: ${err.code}`,
|
||||
);
|
||||
});
|
||||
}
|
||||
} while (bytesRead > 0);
|
||||
console.info('File copied successfully');
|
||||
} catch (error) {
|
||||
console.error('Copy file failed:', error);
|
||||
throw error;
|
||||
} finally {
|
||||
if (reader !== undefined) {
|
||||
fileIo.closeSync(reader);
|
||||
}
|
||||
if (writer !== undefined) {
|
||||
fileIo.closeSync(writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async doFullPatch(params: DownloadTaskParams): Promise<void> {
|
||||
await this.downloadFile(params);
|
||||
await this.removeDirectory(params.unzipDirectory);
|
||||
await fileIo.mkdir(params.unzipDirectory);
|
||||
|
||||
try {
|
||||
await zip.decompressFile(params.targetFile, params.unzipDirectory);
|
||||
} catch (error) {
|
||||
console.error('Unzip failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async processUnzippedFiles(directory: string): Promise<ZipFile> {
|
||||
const entries: ZipEntry[] = [];
|
||||
try {
|
||||
const files = await fileIo.listFile(directory);
|
||||
for (const file of files) {
|
||||
if (file === '.' || file === '..') continue;
|
||||
|
||||
const filePath = `${directory}/${file}`;
|
||||
const stat = await fileIo.stat(filePath);
|
||||
|
||||
if (!stat.isDirectory()) {
|
||||
const reader = await fileIo.open(filePath, fileIo.OpenMode.READ_ONLY);
|
||||
const fileSize = stat.size;
|
||||
const content = new ArrayBuffer(fileSize);
|
||||
|
||||
try {
|
||||
await fileIo.read(reader.fd, content);
|
||||
entries.push({
|
||||
filename: file,
|
||||
content: content,
|
||||
});
|
||||
} finally {
|
||||
await fileIo.close(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { entries };
|
||||
} catch (error) {
|
||||
console.error('Failed to process unzipped files:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async doPatchFromApp(params: DownloadTaskParams): Promise<void> {
|
||||
await this.downloadFile(params);
|
||||
await this.removeDirectory(params.unzipDirectory);
|
||||
await fileIo.mkdir(params.unzipDirectory);
|
||||
|
||||
let foundDiff = false;
|
||||
let foundBundlePatch = false;
|
||||
const copyList: Map<string, Array<any>> = new Map();
|
||||
await zip.decompressFile(params.targetFile, params.unzipDirectory);
|
||||
const zipFile = await this.processUnzippedFiles(params.unzipDirectory);
|
||||
for (const entry of zipFile.entries) {
|
||||
const fn = entry.filename;
|
||||
|
||||
if (fn === '__diff.json') {
|
||||
foundDiff = true;
|
||||
let jsonContent = '';
|
||||
const bufferArray = new Uint8Array(entry.content);
|
||||
for (let i = 0; i < bufferArray.length; i++) {
|
||||
jsonContent += String.fromCharCode(bufferArray[i]);
|
||||
}
|
||||
const obj = JSON.parse(jsonContent);
|
||||
|
||||
const copies = obj.copies;
|
||||
for (const to in copies) {
|
||||
let from = copies[to];
|
||||
if (from === '') {
|
||||
from = to;
|
||||
}
|
||||
|
||||
if (!copyList.has(from)) {
|
||||
copyList.set(from, []);
|
||||
}
|
||||
|
||||
const target = copyList.get(from);
|
||||
if (target) {
|
||||
const toFile = `${params.unzipDirectory}/${to}`;
|
||||
target.push(toFile);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (fn === 'bundle.harmony.js.patch') {
|
||||
foundBundlePatch = true;
|
||||
try {
|
||||
const resourceManager = this.context.resourceManager;
|
||||
const originContent = await resourceManager.getRawFileContent(
|
||||
'bundle.harmony.js',
|
||||
);
|
||||
const patched = await Pushy.hdiffPatch(
|
||||
new Uint8Array(originContent.buffer),
|
||||
new Uint8Array(entry.content),
|
||||
);
|
||||
const outputFile = `${params.unzipDirectory}/bundle.harmony.js`;
|
||||
const writer = await fileIo.open(
|
||||
outputFile,
|
||||
fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY,
|
||||
);
|
||||
const chunkSize = 4096;
|
||||
let bytesWritten = 0;
|
||||
const totalLength = patched.byteLength;
|
||||
|
||||
while (bytesWritten < totalLength) {
|
||||
const chunk = patched.slice(bytesWritten, bytesWritten + chunkSize);
|
||||
await fileIo.write(writer.fd, chunk);
|
||||
bytesWritten += chunk.byteLength;
|
||||
}
|
||||
await fileIo.close(writer);
|
||||
continue;
|
||||
} catch (error) {
|
||||
console.error('Failed to process bundle patch:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if(fn !== '.DS_Store'){
|
||||
await zip.decompressFile(fn, params.unzipDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundDiff) {
|
||||
throw new Error('diff.json not found');
|
||||
}
|
||||
if (!foundBundlePatch) {
|
||||
throw new Error('bundle patch not found');
|
||||
}
|
||||
await this.copyFromResource(copyList);
|
||||
}
|
||||
|
||||
private async doPatchFromPpk(params: DownloadTaskParams): Promise<void> {
|
||||
await this.downloadFile(params);
|
||||
await this.removeDirectory(params.unzipDirectory);
|
||||
await fileIo.mkdir(params.unzipDirectory);
|
||||
|
||||
let foundDiff = false;
|
||||
let foundBundlePatch = false;
|
||||
const copyList: Map<string, Array<any>> = new Map();
|
||||
await zip.decompressFile(params.targetFile, params.unzipDirectory);
|
||||
const zipFile = await this.processUnzippedFiles(params.unzipDirectory);
|
||||
for (const entry of zipFile.entries) {
|
||||
const fn = entry.filename;
|
||||
|
||||
if (fn === '__diff.json') {
|
||||
foundDiff = true;
|
||||
let jsonContent = '';
|
||||
const bufferArray = new Uint8Array(entry.content);
|
||||
for (let i = 0; i < bufferArray.length; i++) {
|
||||
jsonContent += String.fromCharCode(bufferArray[i]);
|
||||
}
|
||||
const obj = JSON.parse(jsonContent);
|
||||
|
||||
const copies = obj.copies;
|
||||
for (const to in copies) {
|
||||
let from = copies[to];
|
||||
if (from === '') {
|
||||
from = to;
|
||||
}
|
||||
|
||||
if (!copyList.has(from)) {
|
||||
copyList.set(from, []);
|
||||
}
|
||||
|
||||
const target = copyList.get(from);
|
||||
if (target) {
|
||||
const toFile = `${params.unzipDirectory}/${to}`;
|
||||
target.push(toFile);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (fn === 'bundle.harmony.js.patch') {
|
||||
foundBundlePatch = true;
|
||||
const filePath = params.originDirectory + '/bundle.harmony.js';
|
||||
const res = fileIo.accessSync(filePath);
|
||||
if (res) {
|
||||
const stat = await fileIo.stat(filePath);
|
||||
const reader = await fileIo.open(filePath, fileIo.OpenMode.READ_ONLY);
|
||||
const fileSize = stat.size;
|
||||
const originContent = new ArrayBuffer(fileSize);
|
||||
try {
|
||||
await fileIo.read(reader.fd, originContent);
|
||||
const patched = await Pushy.hdiffPatch(
|
||||
new Uint8Array(originContent),
|
||||
new Uint8Array(entry.content),
|
||||
);
|
||||
const outputFile = `${params.unzipDirectory}/bundle.harmony.js`;
|
||||
const writer = await fileIo.open(outputFile, fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY);
|
||||
const chunkSize = 4096;
|
||||
let bytesWritten = 0;
|
||||
const totalLength = patched.byteLength;
|
||||
while (bytesWritten < totalLength) {
|
||||
const chunk = patched.slice(bytesWritten, bytesWritten + chunkSize);
|
||||
await fileIo.write(writer.fd, chunk);
|
||||
bytesWritten += chunk.byteLength;
|
||||
}
|
||||
await fileIo.close(writer);
|
||||
continue;
|
||||
} finally {
|
||||
await fileIo.close(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await zip.decompressFile(entry.filename, params.unzipDirectory);
|
||||
}
|
||||
|
||||
if (!foundDiff) {
|
||||
throw new Error('diff.json not found');
|
||||
}
|
||||
if (!foundBundlePatch) {
|
||||
throw new Error('bundle patch not found');
|
||||
}
|
||||
console.info('Patch from PPK completed');
|
||||
}
|
||||
|
||||
private async copyFromResource(
|
||||
copyList: Map<string, Array<string>>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const bundlePath = this.context.bundleCodeDir;
|
||||
|
||||
const files = await fileIo.listFile(bundlePath);
|
||||
for (const file of files) {
|
||||
if (file === '.' || file === '..') continue;
|
||||
|
||||
const targets = copyList.get(file);
|
||||
if (targets) {
|
||||
let lastTarget: string | undefined;
|
||||
|
||||
for (const target of targets) {
|
||||
console.info(`Copying from resource ${file} to ${target}`);
|
||||
|
||||
if (lastTarget) {
|
||||
await this.copyFile(lastTarget, target);
|
||||
} else {
|
||||
const sourcePath = `${bundlePath}/${file}`;
|
||||
await this.copyFile(sourcePath, target);
|
||||
lastTarget = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Copy from resource failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async doCleanUp(params: DownloadTaskParams): Promise<void> {
|
||||
const DAYS_TO_KEEP = 7;
|
||||
const now = Date.now();
|
||||
const maxAge = DAYS_TO_KEEP * 24 * 60 * 60 * 1000;
|
||||
|
||||
try {
|
||||
const files = await fileIo.listFile(params.unzipDirectory);
|
||||
for (const file of files) {
|
||||
if (file.startsWith('.')) continue;
|
||||
|
||||
const filePath = `${params.unzipDirectory}/${file}`;
|
||||
const stat = await fileIo.stat(filePath);
|
||||
|
||||
if (
|
||||
now - stat.mtime > maxAge &&
|
||||
file !== params.hash &&
|
||||
file !== params.originHash
|
||||
) {
|
||||
if (stat.isDirectory()) {
|
||||
await this.removeDirectory(filePath);
|
||||
} else {
|
||||
await fileIo.unlink(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Cleanup failed:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
public async execute(params: DownloadTaskParams): Promise<void> {
|
||||
try {
|
||||
switch (params.type) {
|
||||
case DownloadTaskParams.TASK_TYPE_PATCH_FULL:
|
||||
await this.doFullPatch(params);
|
||||
break;
|
||||
case DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP:
|
||||
await this.doPatchFromApp(params);
|
||||
break;
|
||||
case DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK:
|
||||
await this.doPatchFromPpk(params);
|
||||
break;
|
||||
case DownloadTaskParams.TASK_TYPE_CLEANUP:
|
||||
await this.doCleanUp(params);
|
||||
break;
|
||||
case DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD:
|
||||
await this.downloadFile(params);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown task type: ${params.type}`);
|
||||
}
|
||||
|
||||
params.listener?.onDownloadCompleted(params);
|
||||
} catch (error) {
|
||||
console.error('Task execution failed:', error);
|
||||
if (params.type !== DownloadTaskParams.TASK_TYPE_CLEANUP) {
|
||||
try {
|
||||
if (params.type === DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD) {
|
||||
await fileIo.unlink(params.targetFile);
|
||||
} else {
|
||||
await this.removeDirectory(params.unzipDirectory);
|
||||
}
|
||||
} catch (cleanupError) {
|
||||
console.error('Cleanup after error failed:', cleanupError);
|
||||
}
|
||||
}
|
||||
params.listener?.onDownloadFailed(error);
|
||||
}
|
||||
}
|
||||
}
|
25
harmony/pushy/src/main/ets/DownloadTaskParams.ts
Normal file
25
harmony/pushy/src/main/ets/DownloadTaskParams.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export interface DownloadTaskListener {
|
||||
onDownloadCompleted(params: DownloadTaskParams): void;
|
||||
onDownloadFailed(error: Error): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载任务参数类
|
||||
*/
|
||||
export class DownloadTaskParams {
|
||||
// 任务类型常量
|
||||
static readonly TASK_TYPE_CLEANUP: number = 0; // 保留hash和originHash
|
||||
static readonly TASK_TYPE_PATCH_FULL: number = 1; // 全量补丁
|
||||
static readonly TASK_TYPE_PATCH_FROM_APP: number = 2; // 从APP补丁
|
||||
static readonly TASK_TYPE_PATCH_FROM_PPK: number = 3; // 从PPK补丁
|
||||
static readonly TASK_TYPE_PLAIN_DOWNLOAD: number = 4; // 普通下载
|
||||
|
||||
type: number; // 任务类型
|
||||
url: string; // 下载URL
|
||||
hash: string; // 文件哈希值
|
||||
originHash: string; // 原始文件哈希值
|
||||
targetFile: string; // 目标文件路径
|
||||
unzipDirectory: string; // 解压目录路径
|
||||
originDirectory: string; // 原始文件目录路径
|
||||
listener: DownloadTaskListener; // 下载监听器
|
||||
}
|
39
harmony/pushy/src/main/ets/EventHub.ts
Normal file
39
harmony/pushy/src/main/ets/EventHub.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
type EventCallback = (data: any) => void;
|
||||
|
||||
export class EventHub {
|
||||
private static instance: EventHub;
|
||||
private listeners: Map<string, Set<EventCallback>>;
|
||||
private rnInstance: any;
|
||||
|
||||
private constructor() {
|
||||
this.listeners = new Map();
|
||||
}
|
||||
|
||||
public static getInstance(): EventHub {
|
||||
if (!EventHub.instance) {
|
||||
EventHub.instance = new EventHub();
|
||||
}
|
||||
return EventHub.instance;
|
||||
}
|
||||
|
||||
public on(event: string, callback: EventCallback): void {
|
||||
if (!this.listeners.has(event)) {
|
||||
this.listeners.set(event, new Set());
|
||||
}
|
||||
this.listeners.get(event)?.add(callback);
|
||||
}
|
||||
|
||||
public off(event: string, callback: EventCallback): void {
|
||||
this.listeners.get(event)?.delete(callback);
|
||||
}
|
||||
|
||||
public emit(event: string, data: any): void {
|
||||
if (this.rnInstance) {
|
||||
this.rnInstance.emitDeviceEvent(event, data);
|
||||
}
|
||||
}
|
||||
|
||||
setRNInstance(instance: any) {
|
||||
this.rnInstance = instance;
|
||||
}
|
||||
}
|
35
harmony/pushy/src/main/ets/Logger.ts
Normal file
35
harmony/pushy/src/main/ets/Logger.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
import hilog from '@ohos.hilog';
|
||||
|
||||
class Logger {
|
||||
private domain: number;
|
||||
private prefix: string;
|
||||
private format: string = '%{public}s,%{public}s';
|
||||
private isDebug: boolean;
|
||||
|
||||
constructor(prefix: string = 'MyApp', domain: number = 0xFF00, isDebug = false) {
|
||||
this.prefix = prefix;
|
||||
this.domain = domain;
|
||||
this.isDebug = isDebug;
|
||||
}
|
||||
|
||||
debug(...args: string[]): void {
|
||||
if (this.isDebug) {
|
||||
hilog.debug(this.domain, this.prefix, this.format, args);
|
||||
}
|
||||
}
|
||||
|
||||
info(...args: string[]): void {
|
||||
hilog.info(this.domain, this.prefix, this.format, args);
|
||||
}
|
||||
|
||||
warn(...args: string[]): void {
|
||||
hilog.warn(this.domain, this.prefix, this.format, args);
|
||||
}
|
||||
|
||||
error(...args: string[]): void {
|
||||
hilog.error(this.domain, this.prefix, this.format, args);
|
||||
}
|
||||
}
|
||||
|
||||
export default new Logger('geolocation', 0xFF00, false)
|
54
harmony/pushy/src/main/ets/PushyFileJSBundleProvider.ets
Normal file
54
harmony/pushy/src/main/ets/PushyFileJSBundleProvider.ets
Normal file
@@ -0,0 +1,54 @@
|
||||
import { HotReloadConfig, JSBundleProvider, JSBundleProviderError, JSPackagerClientConfig } from '@rnoh/react-native-openharmony';
|
||||
import fileIo from '@ohos.file.fs';
|
||||
import common from '@ohos.app.ability.common';
|
||||
import { UpdateContext } from './UpdateContext';
|
||||
|
||||
export class PushyFileJSBundleProvider extends JSBundleProvider {
|
||||
private updateContext: UpdateContext;
|
||||
private filePath: string = ''
|
||||
|
||||
constructor(context: common.UIAbilityContext) {
|
||||
super();
|
||||
this.updateContext = new UpdateContext(context);
|
||||
}
|
||||
getURL(): string {
|
||||
return this.updateContext.getBundleUrl();
|
||||
}
|
||||
|
||||
async getBundle(): Promise<ArrayBuffer> {
|
||||
try {
|
||||
this.filePath = this.updateContext.getBundleUrl();
|
||||
const res = fileIo.accessSync(this.filePath);
|
||||
if (res) {
|
||||
const file = fileIo.openSync(this.filePath, fileIo.OpenMode.READ_ONLY);
|
||||
try {
|
||||
const stat = await fileIo.stat(this.filePath);
|
||||
const fileSize = stat.size;
|
||||
const buffer = new ArrayBuffer(fileSize);
|
||||
const bytesRead = fileIo.readSync(file.fd, buffer, {
|
||||
offset: 0,
|
||||
length: fileSize
|
||||
});
|
||||
|
||||
if (bytesRead !== fileSize) {
|
||||
throw new Error(`Failed to read entire file: read ${bytesRead} of ${fileSize} bytes`);
|
||||
}
|
||||
return buffer;
|
||||
} finally {
|
||||
fileIo.closeSync(file.fd);
|
||||
}
|
||||
}
|
||||
throw new Error('Update bundle not found');
|
||||
} catch (error) {
|
||||
throw new JSBundleProviderError({
|
||||
whatHappened: `Couldn't load JSBundle from ${this.filePath}`,
|
||||
extraData: error,
|
||||
howCanItBeFixed: [`Check if a bundle exists at "${this.filePath}" on your device.`]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
getAppKeys(): string[] {
|
||||
return [];
|
||||
}
|
||||
}
|
22
harmony/pushy/src/main/ets/PushyPackage.ts
Normal file
22
harmony/pushy/src/main/ets/PushyPackage.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { RNPackage, TurboModulesFactory } from '@rnoh/react-native-openharmony/ts';
|
||||
import type { TurboModule, TurboModuleContext } from '@rnoh/react-native-openharmony/ts';
|
||||
import { PushyTurboModule } from './PushyTurboModule';
|
||||
|
||||
class PushyTurboModulesFactory extends TurboModulesFactory {
|
||||
createTurboModule(name: string): TurboModule | null {
|
||||
if (name === 'Pushy') {
|
||||
return new PushyTurboModule(this.ctx);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
hasTurboModule(name: string): boolean {
|
||||
return name === 'Pushy';
|
||||
}
|
||||
}
|
||||
|
||||
export class PushyPackage extends RNPackage {
|
||||
createTurboModulesFactory(ctx: TurboModuleContext): TurboModulesFactory {
|
||||
return new PushyTurboModulesFactory(ctx);
|
||||
}
|
||||
}
|
123
harmony/pushy/src/main/ets/PushyTurboModule.ts
Normal file
123
harmony/pushy/src/main/ets/PushyTurboModule.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { TurboModule, TurboModuleContext } from '@rnoh/react-native-openharmony/ts';
|
||||
import common from '@ohos.app.ability.common';
|
||||
import dataPreferences from '@ohos.data.preferences';
|
||||
import { bundleManager } from '@kit.AbilityKit';
|
||||
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
|
||||
import { BusinessError } from '@ohos.base';
|
||||
import logger from './Logger';
|
||||
import { UpdateModuleImpl } from './UpdateModuleImpl';
|
||||
import { UpdateContext } from './UpdateContext';
|
||||
import { EventHub } from './EventHub';
|
||||
|
||||
const TAG = "PushyTurboModule"
|
||||
|
||||
export class PushyTurboModule extends TurboModule {
|
||||
mUiCtx: common.UIAbilityContext
|
||||
context: UpdateContext
|
||||
|
||||
constructor(protected ctx: TurboModuleContext) {
|
||||
super(ctx);
|
||||
logger.debug(TAG, ",PushyTurboModule constructor");
|
||||
this.mUiCtx = ctx.uiAbilityContext
|
||||
this.context = new UpdateContext(this.mUiCtx)
|
||||
EventHub.getInstance().setRNInstance(ctx.rnInstance)
|
||||
}
|
||||
|
||||
|
||||
getConstants(): Object {
|
||||
logger.debug(TAG, ",call getConstants");
|
||||
const context = this.mUiCtx;
|
||||
const preferencesManager = dataPreferences.getPreferencesSync(context,{ name: 'update' });
|
||||
const isFirstTime = preferencesManager.getSync("isFirstTime", false) as boolean;
|
||||
const rolledBackVersion = preferencesManager.getSync("rolledBackVersion", "") as string;
|
||||
const uuid = preferencesManager.getSync("uuid", "") as string;
|
||||
const currentVersion = preferencesManager.getSync("currentVersion", "") as string;
|
||||
const buildTime = preferencesManager.getSync("buildTime", "") as string;
|
||||
const isUsingBundleUrl = this.context.getIsUsingBundleUrl();
|
||||
let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
|
||||
let packageVersion = '';
|
||||
try {
|
||||
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags);
|
||||
packageVersion = bundleInfo?.versionName || "Unknown"
|
||||
} catch (error) {
|
||||
console.error("Failed to get bundle info:", error);
|
||||
}
|
||||
|
||||
if (isFirstTime) {
|
||||
preferencesManager.deleteSync("isFirstTime");
|
||||
}
|
||||
|
||||
if (rolledBackVersion) {
|
||||
preferencesManager.deleteSync("rolledBackVersion");
|
||||
}
|
||||
|
||||
return {
|
||||
downloadRootDir: `${context.filesDir}/_update`,
|
||||
packageVersion,
|
||||
currentVersion,
|
||||
buildTime,
|
||||
isUsingBundleUrl,
|
||||
isFirstTime,
|
||||
rolledBackVersion,
|
||||
uuid,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async setLocalHashInfo(hash: string, info: string): Promise<boolean> {
|
||||
logger.debug(TAG, ",call setLocalHashInfo");
|
||||
return UpdateModuleImpl.setLocalHashInfo(this.context,hash,info);
|
||||
}
|
||||
|
||||
async getLocalHashInfo(hash: string): Promise<string> {
|
||||
return UpdateModuleImpl.getLocalHashInfo(this.context,hash);
|
||||
}
|
||||
|
||||
async setUuid(uuid: string): Promise<boolean> {
|
||||
logger.debug(TAG, `,call setUuid`);
|
||||
return UpdateModuleImpl.setUuid(this.context,uuid);
|
||||
}
|
||||
|
||||
async reloadUpdate(options: { hash: string }): Promise<void> {
|
||||
logger.debug(TAG, `,call reloadUpdate`);
|
||||
return UpdateModuleImpl.reloadUpdate(this.context, this.mUiCtx, options);
|
||||
}
|
||||
|
||||
async setNeedUpdate(options: { hash: string }): Promise<boolean> {
|
||||
logger.debug(TAG, `,call setNeedUpdate`);
|
||||
return UpdateModuleImpl.setNeedUpdate(this.context, options);
|
||||
}
|
||||
|
||||
async markSuccess(): Promise<boolean> {
|
||||
logger.debug(TAG, `,call markSuccess`);
|
||||
return UpdateModuleImpl.markSuccess(this.context);
|
||||
}
|
||||
|
||||
async downloadPatchFromPpk(options: { updateUrl: string; hash: string; originHash: string }): Promise<void> {
|
||||
logger.debug(TAG, `,call downloadPatchFromPpk`);
|
||||
return UpdateModuleImpl.downloadPatchFromPpk(this.context, options);
|
||||
}
|
||||
|
||||
async downloadPatchFromPackage(options: { updateUrl: string; hash: string }): Promise<void> {
|
||||
logger.debug(TAG, `,call downloadPatchFromPackage`);
|
||||
return UpdateModuleImpl.downloadPatchFromPackage(this.context, options);
|
||||
}
|
||||
|
||||
async downloadFullUpdate(options: { updateUrl: string; hash: string }): Promise<void> {
|
||||
logger.debug(TAG, `,call downloadFullUpdate`);
|
||||
return UpdateModuleImpl.downloadFullUpdate(this.context, options);
|
||||
}
|
||||
|
||||
async downloadAndInstallApk(options: { url: string; target: string; hash: string }): Promise<void> {
|
||||
logger.debug(TAG, `,call downloadAndInstallApk`);
|
||||
return UpdateModuleImpl.downloadAndInstallApk(this.mUiCtx, options);
|
||||
}
|
||||
|
||||
addListener(eventName: string): void {
|
||||
logger.debug(TAG, `,call addListener`);
|
||||
}
|
||||
|
||||
removeListeners(count: number): void {
|
||||
logger.debug(TAG, `,call removeListeners`);
|
||||
}
|
||||
}
|
254
harmony/pushy/src/main/ets/UpdateContext.ts
Normal file
254
harmony/pushy/src/main/ets/UpdateContext.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
import preferences from '@ohos.data.preferences';
|
||||
import bundleManager from '@ohos.bundle.bundleManager';
|
||||
import fileIo from '@ohos.file.fs';
|
||||
import { DownloadTask } from './DownloadTask';
|
||||
import common from '@ohos.app.ability.common';
|
||||
import { DownloadTaskParams } from './DownloadTaskParams';
|
||||
|
||||
export class UpdateContext {
|
||||
private context: common.UIAbilityContext;
|
||||
private rootDir: string;
|
||||
private preferences: preferences.Preferences;
|
||||
private static DEBUG: boolean = false;
|
||||
private static isUsingBundleUrl: boolean = false;
|
||||
|
||||
constructor(context: common.UIAbilityContext) {
|
||||
this.context = context;
|
||||
this.rootDir = context.filesDir + '/_update';
|
||||
|
||||
try {
|
||||
if (!fileIo.accessSync(this.rootDir)) {
|
||||
fileIo.mkdirSync(this.rootDir);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to create root directory:', e);
|
||||
}
|
||||
this.initPreferences();
|
||||
}
|
||||
|
||||
private initPreferences() {
|
||||
try {
|
||||
this.preferences = preferences.getPreferencesSync(this.context, {name:'update'});
|
||||
const packageVersion = this.getPackageVersion();
|
||||
const storedVersion = this.preferences.getSync('packageVersion', '');
|
||||
if(!storedVersion){
|
||||
this.preferences.putSync('packageVersion', packageVersion);
|
||||
this.preferences.flush();
|
||||
} else if (storedVersion && packageVersion !== storedVersion) {
|
||||
this.preferences.clear();
|
||||
this.preferences.putSync('packageVersion', packageVersion);
|
||||
this.preferences.flush();
|
||||
this.cleanUp();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to init preferences:', e);
|
||||
}
|
||||
}
|
||||
|
||||
public setKv(key: string, value: string): void {
|
||||
this.preferences.putSync(key, value);
|
||||
this.preferences.flush();
|
||||
}
|
||||
|
||||
public getKv(key: string): string {
|
||||
return this.preferences.getSync(key, '') as string;
|
||||
}
|
||||
|
||||
public isFirstTime(): boolean {
|
||||
return this.preferences.getSync('firstTime', false) as boolean;
|
||||
}
|
||||
|
||||
public rolledBackVersion(): string {
|
||||
return this.preferences.getSync('rolledBackVersion', '') as string;
|
||||
}
|
||||
|
||||
public markSuccess(): void {
|
||||
this.preferences.putSync('firstTimeOk', true);
|
||||
const lastVersion = this.preferences.getSync('lastVersion', '') as string;
|
||||
const curVersion = this.preferences.getSync('currentVersion', '') as string;
|
||||
|
||||
if (lastVersion && lastVersion !== curVersion) {
|
||||
this.preferences.deleteSync('lastVersion');
|
||||
this.preferences.deleteSync(`hash_${lastVersion}`);
|
||||
}
|
||||
this.preferences.flush();
|
||||
this.cleanUp();
|
||||
}
|
||||
|
||||
public clearFirstTime(): void {
|
||||
this.preferences.putSync('firstTime', false);
|
||||
this.preferences.flush();
|
||||
this.cleanUp();
|
||||
}
|
||||
|
||||
public clearRollbackMark(): void {
|
||||
this.preferences.putSync('rolledBackVersion', null);
|
||||
this.preferences.flush();
|
||||
this.cleanUp();
|
||||
}
|
||||
|
||||
public async downloadFullUpdate(url: string, hash: string, listener: DownloadFileListener): Promise<void> {
|
||||
try {
|
||||
const params = new DownloadTaskParams();
|
||||
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FULL;
|
||||
params.url = url;
|
||||
params.hash = hash;
|
||||
params.listener = listener;
|
||||
params.targetFile = `${this.rootDir}/${hash}.ppk`;
|
||||
const downloadTask = new DownloadTask(this.context);
|
||||
await downloadTask.execute(params);
|
||||
} catch (e) {
|
||||
console.error('Failed to download full update:', e);
|
||||
}
|
||||
}
|
||||
|
||||
public async downloadFile(url: string, hash: string, fileName: string, listener: DownloadFileListener): Promise<void> {
|
||||
const params = new DownloadTaskParams();
|
||||
params.type = DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD;
|
||||
params.url = url;
|
||||
params.hash = hash;
|
||||
params.listener = listener;
|
||||
params.targetFile = this.rootDir + '/' + fileName;
|
||||
|
||||
const downloadTask = new DownloadTask(this.context);
|
||||
await downloadTask.execute(params);
|
||||
}
|
||||
|
||||
public async downloadPatchFromPpk(url: string, hash: string, originHash: string, listener: DownloadFileListener): Promise<void> {
|
||||
const params = new DownloadTaskParams();
|
||||
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK;
|
||||
params.url = url;
|
||||
params.hash = hash;
|
||||
params.originHash = originHash;
|
||||
params.listener = listener;
|
||||
params.targetFile = `${this.rootDir}/${originHash}_${hash}.ppk.patch`;
|
||||
params.unzipDirectory = `${this.rootDir}/${hash}`;
|
||||
params.originDirectory = `${this.rootDir}/${params.originHash}`;
|
||||
|
||||
const downloadTask = new DownloadTask(this.context);
|
||||
await downloadTask.execute(params);
|
||||
}
|
||||
|
||||
public async downloadPatchFromPackage(url: string, hash: string, listener: DownloadFileListener): Promise<void> {
|
||||
try {
|
||||
const params = new DownloadTaskParams();
|
||||
params.type = DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP;
|
||||
params.url = url;
|
||||
params.hash = hash;
|
||||
params.listener = listener;
|
||||
params.targetFile = `${this.rootDir}/${hash}.app.patch`;
|
||||
params.unzipDirectory = `${this.rootDir}/${hash}`;
|
||||
|
||||
const downloadTask = new DownloadTask(this.context);
|
||||
return await downloadTask.execute(params);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
console.error('Failed to download APK patch:', e);
|
||||
}
|
||||
}
|
||||
|
||||
public switchVersion(hash: string): void {
|
||||
try {
|
||||
const bundlePath = `${this.rootDir}/${hash}/bundle.harmony.js`;
|
||||
if (!fileIo.accessSync(bundlePath)) {
|
||||
throw new Error(`Bundle version ${hash} not found.`);
|
||||
}
|
||||
|
||||
const lastVersion = this.getKv('currentVersion');
|
||||
this.setKv('currentVersion', hash);
|
||||
if (lastVersion && lastVersion !== hash) {
|
||||
this.setKv('lastVersion', lastVersion);
|
||||
}
|
||||
|
||||
this.setKv('firstTime', 'true');
|
||||
this.setKv('firstTimeOk', 'false');
|
||||
this.setKv('rolledBackVersion', "");
|
||||
} catch (e) {
|
||||
console.error('Failed to switch version:', e);
|
||||
}
|
||||
}
|
||||
|
||||
public static getBundleUrl(context: common.UIAbilityContext, defaultAssetsUrl?: string): string {
|
||||
return new UpdateContext(context).getBundleUrl(defaultAssetsUrl);
|
||||
}
|
||||
|
||||
public getBundleUrl(defaultAssetsUrl?: string): string {
|
||||
UpdateContext.isUsingBundleUrl = true;
|
||||
const currentVersion = this.getCurrentVersion();
|
||||
if (!currentVersion) {
|
||||
return defaultAssetsUrl;
|
||||
}
|
||||
if (!this.isFirstTime()) {
|
||||
if (!this.preferences.getSync('firstTimeOk', true)) {
|
||||
return this.rollBack();
|
||||
}
|
||||
}
|
||||
let version = currentVersion;
|
||||
while (version) {
|
||||
const bundleFile = `${this.rootDir}/${version}/bundle.harmony.js`;
|
||||
try {
|
||||
if (!fileIo.accessSync(bundleFile)) {
|
||||
console.error(`Bundle version ${version} not found.`);
|
||||
version = this.rollBack();
|
||||
continue;
|
||||
}
|
||||
return bundleFile;
|
||||
} catch (e) {
|
||||
console.error('Failed to access bundle file:', e);
|
||||
version = this.rollBack();
|
||||
}
|
||||
}
|
||||
return defaultAssetsUrl;
|
||||
}
|
||||
|
||||
getPackageVersion(): string {
|
||||
let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
|
||||
let packageVersion = '';
|
||||
try {
|
||||
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleFlags);
|
||||
packageVersion = bundleInfo?.versionName || "Unknown";
|
||||
} catch (error) {
|
||||
console.error("获取包信息失败:", error);
|
||||
}
|
||||
return packageVersion;
|
||||
}
|
||||
|
||||
public getCurrentVersion() : string {
|
||||
const currentVersion = this.getKv('currentVersion');
|
||||
return currentVersion;
|
||||
}
|
||||
|
||||
private rollBack(): string {
|
||||
const lastVersion = this.preferences.getSync('lastVersion', '') as string;
|
||||
const currentVersion = this.preferences.getSync('currentVersion', '') as string;
|
||||
if (!lastVersion) {
|
||||
this.preferences.deleteSync('currentVersion');
|
||||
} else {
|
||||
this.preferences.putSync('currentVersion', lastVersion);
|
||||
}
|
||||
this.preferences.putSync('firstTimeOk', true);
|
||||
this.preferences.putSync('firstTime', false);
|
||||
this.preferences.putSync('rolledBackVersion', currentVersion);
|
||||
this.preferences.flush();
|
||||
return lastVersion;
|
||||
}
|
||||
|
||||
private cleanUp(): void {
|
||||
const params = new DownloadTaskParams();
|
||||
params.type = DownloadTaskParams.TASK_TYPE_CLEANUP;
|
||||
params.hash = this.preferences.getSync('currentVersion', '') as string;
|
||||
params.originHash = this.preferences.getSync('lastVersion', '') as string;
|
||||
params.unzipDirectory = this.rootDir;
|
||||
const downloadTask = new DownloadTask(this.context);
|
||||
downloadTask.execute(params);
|
||||
}
|
||||
|
||||
public getIsUsingBundleUrl(): boolean {
|
||||
return UpdateContext.isUsingBundleUrl;
|
||||
}
|
||||
}
|
||||
|
||||
export interface DownloadFileListener {
|
||||
onDownloadCompleted(params: DownloadTaskParams): void;
|
||||
onDownloadFailed(error: Error): void;
|
||||
}
|
197
harmony/pushy/src/main/ets/UpdateModuleImpl.ts
Normal file
197
harmony/pushy/src/main/ets/UpdateModuleImpl.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import bundleManager from '@ohos.bundle.bundleManager';
|
||||
import common from '@ohos.app.ability.common';
|
||||
import { UpdateContext } from './UpdateContext';
|
||||
import { DownloadTaskParams } from './DownloadTaskParams';
|
||||
import logger from './Logger';
|
||||
|
||||
const TAG = "UpdateModuleImpl";
|
||||
|
||||
export class UpdateModuleImpl {
|
||||
static readonly NAME = "Pushy";
|
||||
|
||||
static async downloadFullUpdate(
|
||||
updateContext: UpdateContext,
|
||||
options: { updateUrl: string; hash: string }
|
||||
): Promise<void> {
|
||||
try {
|
||||
await updateContext.downloadFullUpdate(options.updateUrl, options.hash, {
|
||||
onDownloadCompleted: (params: DownloadTaskParams) => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
onDownloadFailed: (error: Error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(TAG, `downloadFullUpdate failed: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async downloadAndInstallApk(
|
||||
context: common.UIAbilityContext,
|
||||
options: { url: string; hash: string; target: string }
|
||||
): Promise<void> {
|
||||
try {
|
||||
const want = {
|
||||
action: 'action.system.home',
|
||||
parameters: {
|
||||
uri: 'appmarket://details'
|
||||
}
|
||||
};
|
||||
|
||||
if (!context) {
|
||||
throw new Error('获取context失败');
|
||||
}
|
||||
|
||||
await context.startAbility(want);
|
||||
} catch (error) {
|
||||
logger.error(TAG, `installApk failed: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async downloadPatchFromPackage(
|
||||
updateContext: UpdateContext,
|
||||
options: { updateUrl: string; hash: string }
|
||||
): Promise<void> {
|
||||
try {
|
||||
return await updateContext.downloadPatchFromPackage(options.updateUrl, options.hash, {
|
||||
onDownloadCompleted: (params: DownloadTaskParams) => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
onDownloadFailed: (error: Error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(TAG, `downloadPatchFromPackage failed: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async downloadPatchFromPpk(
|
||||
updateContext: UpdateContext,
|
||||
options: { updateUrl: string; hash: string; originHash: string }
|
||||
): Promise<void> {
|
||||
try {
|
||||
await updateContext.downloadPatchFromPpk(
|
||||
options.updateUrl,
|
||||
options.hash,
|
||||
options.originHash,
|
||||
{
|
||||
onDownloadCompleted: (params: DownloadTaskParams) => {
|
||||
return Promise.resolve();
|
||||
},
|
||||
onDownloadFailed: (error: Error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(TAG, `downloadPatchFromPpk failed: ${error}`);
|
||||
throw new Error(`执行报错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
static async reloadUpdate(
|
||||
updateContext: UpdateContext,
|
||||
context: common.UIAbilityContext,
|
||||
options: { hash: string }
|
||||
): Promise<void> {
|
||||
const hash = options.hash;
|
||||
if (!hash) {
|
||||
throw new Error('hash不能为空');
|
||||
}
|
||||
|
||||
try {
|
||||
await updateContext.switchVersion(hash);
|
||||
const bundleInfo = await bundleManager.getBundleInfoForSelf(
|
||||
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION
|
||||
);
|
||||
await context.terminateSelf();
|
||||
const want = {
|
||||
bundleName: bundleInfo.name,
|
||||
abilityName: context.abilityInfo?.name
|
||||
};
|
||||
await context.startAbility(want);
|
||||
} catch (error) {
|
||||
logger.error(TAG, `reloadUpdate failed: ${error}`);
|
||||
throw new Error(`pushy:switchVersion failed ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
static async setNeedUpdate(
|
||||
updateContext: UpdateContext,
|
||||
options: { hash: string }
|
||||
): Promise<boolean> {
|
||||
const hash = options.hash;
|
||||
if (!hash) {
|
||||
throw new Error('hash不能为空');
|
||||
}
|
||||
|
||||
try {
|
||||
await updateContext.switchVersion(hash);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(TAG, `setNeedUpdate failed: ${error}`);
|
||||
throw new Error(`switchVersionLater failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
static async markSuccess(updateContext: UpdateContext): Promise<boolean> {
|
||||
try {
|
||||
await updateContext.markSuccess();
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(TAG, `markSuccess failed: ${error}`);
|
||||
throw new Error(`执行报错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
static async setUuid(
|
||||
updateContext: UpdateContext,
|
||||
uuid: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await updateContext.setKv('uuid', uuid);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(TAG, `setUuid failed: ${error}`);
|
||||
throw new Error(`执行报错: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
static checkJson(json: string): boolean {
|
||||
try {
|
||||
JSON.parse(json);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static async setLocalHashInfo(
|
||||
updateContext: UpdateContext,
|
||||
hash: string,
|
||||
info: string
|
||||
): Promise<boolean> {
|
||||
if (!this.checkJson(info)) {
|
||||
await updateContext.setKv(`hash_${hash}`, info);
|
||||
throw new Error('校验报错:json字符串格式错误');
|
||||
}
|
||||
await updateContext.setKv(`hash_${hash}`, info);
|
||||
return true;
|
||||
}
|
||||
|
||||
static async getLocalHashInfo(
|
||||
updateContext: UpdateContext,
|
||||
hash: string
|
||||
): Promise<string> {
|
||||
const value = await updateContext.getKv(`hash_${hash}`);
|
||||
if (!this.checkJson(value)) {
|
||||
throw new Error('校验报错:json字符串格式错误');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
7
harmony/pushy/src/main/module.json5
Normal file
7
harmony/pushy/src/main/module.json5
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"module": {
|
||||
"name": "pushy",
|
||||
"type": "har",
|
||||
"deviceTypes": ['default'],
|
||||
},
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "page_show",
|
||||
"value": "page from npm package"
|
||||
}
|
||||
]
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "page_show",
|
||||
"value": "page from npm package"
|
||||
}
|
||||
]
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"string": [
|
||||
{
|
||||
"name": "page_show",
|
||||
"value": "page from npm package"
|
||||
}
|
||||
]
|
||||
}
|
3
harmony/pushy/ts.ts
Normal file
3
harmony/pushy/ts.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
export * from './src/main/ets/PushyPackage'
|
||||
export * from './src/main/ets/PushyTurboModule'
|
Reference in New Issue
Block a user