react-native-update 10.37.20 → 10.38.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README-CN.md +4 -4
- package/README.md +2 -12
- package/android/bin/.settings/org.eclipse.buildship.core.prefs +13 -0
- package/android/build.gradle +4 -0
- package/android/jni/Android.mk +14 -1
- package/android/jni/Application.mk +5 -2
- package/android/lib/arm64-v8a/librnupdate.so +0 -0
- package/android/lib/armeabi-v7a/librnupdate.so +0 -0
- package/android/lib/x86/librnupdate.so +0 -0
- package/android/lib/x86_64/librnupdate.so +0 -0
- package/android/src/main/java/cn/reactnative/modules/update/ArchivePatchPlanResult.java +6 -0
- package/android/src/main/java/cn/reactnative/modules/update/CopyGroupResult.java +6 -0
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +136 -136
- package/android/src/main/java/cn/reactnative/modules/update/NativeUpdateCore.java +34 -0
- package/android/src/main/java/cn/reactnative/modules/update/StateCoreResult.java +16 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +131 -48
- package/cpp/patch_core/archive_patch_core.cpp +125 -0
- package/cpp/patch_core/archive_patch_core.h +59 -0
- package/cpp/patch_core/patch_core.cpp +533 -0
- package/cpp/patch_core/patch_core.h +68 -0
- package/cpp/patch_core/patch_core_android.cpp +112 -0
- package/cpp/patch_core/state_core.cpp +110 -0
- package/cpp/patch_core/state_core.h +58 -0
- package/cpp/patch_core/tests/patch_core_test.cpp +473 -0
- package/cpp/patch_core/update_core_android.cpp +469 -0
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy.mm +233 -143
- package/package.json +17 -15
- package/react-native-update.podspec +3 -0
- package/scripts/build-harmony-har.js +12 -0
- package/scripts/prepublish.ts +49 -3
- package/scripts/prune-host-stl.sh +6 -0
- package/scripts/test-patch-core.sh +39 -0
- package/src/client.ts +129 -76
- package/src/core.ts +2 -1
- package/src/endpoint.ts +171 -0
- package/src/utils.ts +40 -27
- package/android/jni/lzma/DOC/7zC.txt +0 -187
- package/android/jni/lzma/DOC/7zFormat.txt +0 -469
- package/android/jni/lzma/DOC/Methods.txt +0 -173
- package/android/jni/lzma/DOC/installer.txt +0 -166
- package/android/jni/lzma/DOC/lzma-history.txt +0 -446
- package/android/jni/lzma/DOC/lzma-sdk.txt +0 -357
- package/android/jni/lzma/DOC/lzma-specification.txt +0 -1176
- package/android/jni/lzma/DOC/lzma.txt +0 -328
- package/android/jni/lzma/bin/7zS2.sfx +0 -0
- package/android/jni/lzma/bin/7zS2con.sfx +0 -0
- package/android/jni/lzma/bin/7zSD.sfx +0 -0
- package/android/jni/lzma/bin/7zdec.exe +0 -0
- package/android/jni/lzma/bin/7zr.exe +0 -0
- package/android/jni/lzma/bin/installer/config.txt +0 -5
- package/android/jni/lzma/bin/installer/cr.bat +0 -5
- package/android/jni/lzma/bin/lzma.exe +0 -0
- package/android/jni/lzma/bin/x64/7zr.exe +0 -0
- package/error.js +0 -1609
- package/harmony/har-wrapper/AppScope/app.json5 +0 -8
- package/harmony/har-wrapper/build-profile.json5 +0 -35
- package/harmony/har-wrapper/hvigor/hvigor-config.json5 +0 -5
- package/harmony/har-wrapper/hvigorfile.ts +0 -6
- package/harmony/har-wrapper/oh-package.json5 +0 -4
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.cmake/api/v1/reply/cache-v2-77b153ce45aba0ed28ef.json +0 -1415
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.cmake/api/v1/reply/cmakeFiles-v1-b65a07793384e0ce3e08.json +0 -809
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.cmake/api/v1/reply/codemodel-v2-ce0e89410afd8bf3a057.json +0 -60
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.cmake/api/v1/reply/directory-.-Release-f5ebdc15457944623624.json +0 -14
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.cmake/api/v1/reply/index-2026-03-18T12-50-36-0050.json +0 -89
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.cmake/api/v1/reply/target-rnupdate-Release-267153624504c9c3ffdd.json +0 -222
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.ninja_deps +0 -0
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/.ninja_log +0 -8
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeCache.txt +0 -415
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CMakeCCompiler.cmake +0 -74
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CMakeCXXCompiler.cmake +0 -85
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CMakeDetermineCompilerABI_C.bin +0 -0
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CMakeDetermineCompilerABI_CXX.bin +0 -0
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CMakeSystem.cmake +0 -15
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CompilerIdC/CMakeCCompilerId.c +0 -880
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CompilerIdC/CMakeCCompilerId.o +0 -0
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CompilerIdCXX/CMakeCXXCompilerId.cpp +0 -869
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/3.28.2/CompilerIdCXX/CMakeCXXCompilerId.o +0 -0
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/CMakeConfigureLog.yaml +0 -388
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/TargetDirectories.txt +0 -3
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/cmake.check_cache +0 -1
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rnupdate.dir/__w/react-native-update/react-native-update/android/jni/HDiffPatch/file_for_patch.c.o +0 -0
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rnupdate.dir/__w/react-native-update/react-native-update/android/jni/HDiffPatch/libHDiffPatch/HPatch/patch.c.o +0 -0
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rnupdate.dir/__w/react-native-update/react-native-update/android/jni/hpatch.c.o +0 -0
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rnupdate.dir/__w/react-native-update/react-native-update/android/jni/lzma/C/Lzma2Dec.c.o +0 -0
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rnupdate.dir/__w/react-native-update/react-native-update/android/jni/lzma/C/LzmaDec.c.o +0 -0
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rnupdate.dir/pushy.c.o +0 -0
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/CMakeFiles/rules.ninja +0 -64
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/additional_project_files.txt +0 -0
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/build.ninja +0 -206
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/build_file_index.txt +0 -1
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/cmake_install.cmake +0 -54
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/compile_commands.json +0 -38
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/configure_fingerprint.json +0 -1
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/hvigor_native_config.json +0 -1
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/metadata_generation_command.txt +0 -17
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/native_work_dir.txt +0 -1
- package/harmony/pushy/.cxx/default/default/release/arm64-v8a/output.log +0 -14
- package/harmony/pushy/.cxx/default/default/release/hvigor/arm64-v8a/summary.cmake +0 -0
- package/harmony/pushy/BuildProfile.ets +0 -17
- package/harmony/pushy/OAT.xml +0 -38
- package/harmony/pushy/README.md +0 -0
- package/harmony/pushy/build-profile.json5 +0 -15
- package/harmony/pushy/hvigor-plugin.ts +0 -34
- package/harmony/pushy/hvigorfile.ts +0 -1
- package/harmony/pushy/index.ets +0 -2
- package/harmony/pushy/oh-package-lock.json5 +0 -20
- package/harmony/pushy/oh-package.json5 +0 -13
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +0 -51
- package/harmony/pushy/src/main/cpp/PushyPackage.h +0 -55
- package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +0 -142
- package/harmony/pushy/src/main/cpp/PushyTurboModule.h +0 -38
- package/harmony/pushy/src/main/cpp/pushy.c +0 -117
- package/harmony/pushy/src/main/cpp/pushy.h +0 -8
- package/harmony/pushy/src/main/ets/DownloadTask.ts +0 -570
- package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +0 -19
- package/harmony/pushy/src/main/ets/EventHub.ts +0 -39
- package/harmony/pushy/src/main/ets/Logger.ts +0 -52
- package/harmony/pushy/src/main/ets/PushyFileJSBundleProvider.ets +0 -50
- package/harmony/pushy/src/main/ets/PushyPackage.ts +0 -22
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +0 -171
- package/harmony/pushy/src/main/ets/SaveFile.ts +0 -34
- package/harmony/pushy/src/main/ets/UpdateContext.ts +0 -262
- package/harmony/pushy/src/main/ets/UpdateModuleImpl.ts +0 -123
- package/harmony/pushy/src/main/module.json5 +0 -7
- package/harmony/pushy/src/main/resources/base/element/string.json +0 -8
- package/harmony/pushy/src/main/resources/en_US/element/string.json +0 -8
- package/harmony/pushy/src/main/resources/zh_CN/element/string.json +0 -8
- package/harmony/pushy/ts.ts +0 -3
- package/src/__tests__/core.test.ts +0 -103
- package/src/__tests__/utils.test.ts +0 -36
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
#include "patch_core.h"
|
|
2
|
+
|
|
3
|
+
#include <cerrno>
|
|
4
|
+
#include <cstdio>
|
|
5
|
+
#include <cstring>
|
|
6
|
+
#include <dirent.h>
|
|
7
|
+
#include <sys/stat.h>
|
|
8
|
+
#include <sys/types.h>
|
|
9
|
+
#include <unistd.h>
|
|
10
|
+
|
|
11
|
+
#include <set>
|
|
12
|
+
#include <sstream>
|
|
13
|
+
#include <vector>
|
|
14
|
+
|
|
15
|
+
extern "C" {
|
|
16
|
+
#include "hpatch.h"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
namespace pushy {
|
|
20
|
+
namespace patch {
|
|
21
|
+
namespace {
|
|
22
|
+
|
|
23
|
+
constexpr size_t kCopyBufferSize = 16 * 1024;
|
|
24
|
+
|
|
25
|
+
class HdiffBundlePatcher final : public BundlePatcher {
|
|
26
|
+
public:
|
|
27
|
+
Status Apply(
|
|
28
|
+
const std::string& origin_bundle_path,
|
|
29
|
+
const std::string& bundle_patch_path,
|
|
30
|
+
const std::string& destination_bundle_path) const override;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
Status MakeErrnoStatus(const std::string& message, int err = errno) {
|
|
34
|
+
std::ostringstream stream;
|
|
35
|
+
stream << message;
|
|
36
|
+
if (err != 0) {
|
|
37
|
+
stream << ": " << std::strerror(err);
|
|
38
|
+
}
|
|
39
|
+
return Status::Error(stream.str());
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
bool EndsWithSlash(const std::string& path) {
|
|
43
|
+
return !path.empty() && path[path.size() - 1] == '/';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
std::string TrimTrailingSlash(const std::string& path) {
|
|
47
|
+
if (path.empty()) {
|
|
48
|
+
return path;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
size_t end = path.size();
|
|
52
|
+
while (end > 0 && path[end - 1] == '/') {
|
|
53
|
+
--end;
|
|
54
|
+
}
|
|
55
|
+
return path.substr(0, end);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
std::vector<std::string> SplitPath(const std::string& path) {
|
|
59
|
+
std::vector<std::string> segments;
|
|
60
|
+
std::string current;
|
|
61
|
+
|
|
62
|
+
for (char ch : path) {
|
|
63
|
+
if (ch == '/') {
|
|
64
|
+
segments.push_back(current);
|
|
65
|
+
current.clear();
|
|
66
|
+
} else {
|
|
67
|
+
current.push_back(ch);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
segments.push_back(current);
|
|
71
|
+
return segments;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
std::string JoinPath(const std::string& base, const std::string& relative) {
|
|
75
|
+
if (base.empty()) {
|
|
76
|
+
return relative;
|
|
77
|
+
}
|
|
78
|
+
if (relative.empty()) {
|
|
79
|
+
return base;
|
|
80
|
+
}
|
|
81
|
+
if (base[base.size() - 1] == '/') {
|
|
82
|
+
return base + relative;
|
|
83
|
+
}
|
|
84
|
+
return base + "/" + relative;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
std::string Dirname(const std::string& path) {
|
|
88
|
+
size_t slash = path.find_last_of('/');
|
|
89
|
+
if (slash == std::string::npos) {
|
|
90
|
+
return std::string();
|
|
91
|
+
}
|
|
92
|
+
return path.substr(0, slash);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
bool PathExists(const std::string& path) {
|
|
96
|
+
struct stat st;
|
|
97
|
+
return stat(path.c_str(), &st) == 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
bool IsDirectory(const std::string& path) {
|
|
101
|
+
struct stat st;
|
|
102
|
+
if (stat(path.c_str(), &st) != 0) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return S_ISDIR(st.st_mode);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
Status EnsureDirectory(const std::string& path) {
|
|
109
|
+
if (path.empty()) {
|
|
110
|
+
return Status::Ok();
|
|
111
|
+
}
|
|
112
|
+
if (PathExists(path)) {
|
|
113
|
+
if (IsDirectory(path)) {
|
|
114
|
+
return Status::Ok();
|
|
115
|
+
}
|
|
116
|
+
return Status::Error("Expected directory path: " + path);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const std::string parent = Dirname(path);
|
|
120
|
+
if (!parent.empty()) {
|
|
121
|
+
Status parent_status = EnsureDirectory(parent);
|
|
122
|
+
if (!parent_status) {
|
|
123
|
+
return parent_status;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (mkdir(path.c_str(), 0755) != 0 && errno != EEXIST) {
|
|
128
|
+
return MakeErrnoStatus("Failed to create directory " + path);
|
|
129
|
+
}
|
|
130
|
+
return Status::Ok();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Status RemovePathRecursively(const std::string& path) {
|
|
134
|
+
struct stat st;
|
|
135
|
+
if (lstat(path.c_str(), &st) != 0) {
|
|
136
|
+
if (errno == ENOENT) {
|
|
137
|
+
return Status::Ok();
|
|
138
|
+
}
|
|
139
|
+
return MakeErrnoStatus("Failed to stat path " + path);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (S_ISDIR(st.st_mode)) {
|
|
143
|
+
DIR* dir = opendir(path.c_str());
|
|
144
|
+
if (!dir) {
|
|
145
|
+
return MakeErrnoStatus("Failed to open directory " + path);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
struct dirent* entry = nullptr;
|
|
149
|
+
while ((entry = readdir(dir)) != nullptr) {
|
|
150
|
+
const std::string name = entry->d_name;
|
|
151
|
+
if (name == "." || name == "..") {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
Status remove_status = RemovePathRecursively(JoinPath(path, name));
|
|
155
|
+
if (!remove_status) {
|
|
156
|
+
closedir(dir);
|
|
157
|
+
return remove_status;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
closedir(dir);
|
|
161
|
+
|
|
162
|
+
if (rmdir(path.c_str()) != 0) {
|
|
163
|
+
return MakeErrnoStatus("Failed to remove directory " + path);
|
|
164
|
+
}
|
|
165
|
+
return Status::Ok();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (unlink(path.c_str()) != 0) {
|
|
169
|
+
return MakeErrnoStatus("Failed to remove file " + path);
|
|
170
|
+
}
|
|
171
|
+
return Status::Ok();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
Status CopyFile(const std::string& from, const std::string& to, bool overwrite) {
|
|
175
|
+
struct stat st;
|
|
176
|
+
if (stat(from.c_str(), &st) != 0) {
|
|
177
|
+
return MakeErrnoStatus("Failed to stat source file " + from);
|
|
178
|
+
}
|
|
179
|
+
if (!S_ISREG(st.st_mode)) {
|
|
180
|
+
return Status::Error("Source is not a regular file: " + from);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const std::string parent = Dirname(to);
|
|
184
|
+
Status dir_status = EnsureDirectory(parent);
|
|
185
|
+
if (!dir_status) {
|
|
186
|
+
return dir_status;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (PathExists(to)) {
|
|
190
|
+
if (!overwrite) {
|
|
191
|
+
return Status::Ok();
|
|
192
|
+
}
|
|
193
|
+
Status remove_status = RemovePathRecursively(to);
|
|
194
|
+
if (!remove_status) {
|
|
195
|
+
return remove_status;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
FILE* source = std::fopen(from.c_str(), "rb");
|
|
200
|
+
if (!source) {
|
|
201
|
+
return MakeErrnoStatus("Failed to open source file " + from);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
FILE* destination = std::fopen(to.c_str(), "wb");
|
|
205
|
+
if (!destination) {
|
|
206
|
+
std::fclose(source);
|
|
207
|
+
return MakeErrnoStatus("Failed to open destination file " + to);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
std::vector<unsigned char> buffer(kCopyBufferSize);
|
|
211
|
+
while (true) {
|
|
212
|
+
size_t bytes_read = std::fread(buffer.data(), 1, buffer.size(), source);
|
|
213
|
+
if (bytes_read > 0) {
|
|
214
|
+
size_t bytes_written = std::fwrite(buffer.data(), 1, bytes_read, destination);
|
|
215
|
+
if (bytes_written != bytes_read) {
|
|
216
|
+
std::fclose(source);
|
|
217
|
+
std::fclose(destination);
|
|
218
|
+
return MakeErrnoStatus("Failed to write destination file " + to);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (bytes_read < buffer.size()) {
|
|
223
|
+
if (std::ferror(source)) {
|
|
224
|
+
std::fclose(source);
|
|
225
|
+
std::fclose(destination);
|
|
226
|
+
return MakeErrnoStatus("Failed to read source file " + from);
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
std::fclose(source);
|
|
233
|
+
if (std::fclose(destination) != 0) {
|
|
234
|
+
return MakeErrnoStatus("Failed to close destination file " + to);
|
|
235
|
+
}
|
|
236
|
+
return Status::Ok();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
struct DeleteRule {
|
|
240
|
+
std::string path;
|
|
241
|
+
bool directory_hint;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
class DeleteMatcher {
|
|
245
|
+
public:
|
|
246
|
+
explicit DeleteMatcher(const std::vector<std::string>& deletes) {
|
|
247
|
+
for (const std::string& raw : deletes) {
|
|
248
|
+
rules_.push_back(DeleteRule{TrimTrailingSlash(raw), EndsWithSlash(raw)});
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
bool Matches(const std::string& relative_path) const {
|
|
253
|
+
const std::string trimmed = TrimTrailingSlash(relative_path);
|
|
254
|
+
for (const DeleteRule& rule : rules_) {
|
|
255
|
+
if (rule.path.empty()) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (trimmed == rule.path) {
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
if (rule.directory_hint &&
|
|
262
|
+
trimmed.size() > rule.path.size() &&
|
|
263
|
+
trimmed.compare(0, rule.path.size(), rule.path) == 0 &&
|
|
264
|
+
trimmed[rule.path.size()] == '/') {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private:
|
|
272
|
+
std::vector<DeleteRule> rules_;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
Status ValidateManifestImpl(const PatchManifest& manifest) {
|
|
276
|
+
for (const CopyOperation& copy : manifest.copies) {
|
|
277
|
+
if (!IsSafeRelativePath(copy.from)) {
|
|
278
|
+
return Status::Error("Unsafe copy source path: " + copy.from);
|
|
279
|
+
}
|
|
280
|
+
if (!IsSafeRelativePath(copy.to)) {
|
|
281
|
+
return Status::Error("Unsafe copy target path: " + copy.to);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
for (const std::string& deleted : manifest.deletes) {
|
|
286
|
+
const std::string trimmed = TrimTrailingSlash(deleted);
|
|
287
|
+
if (trimmed.empty() || !IsSafeRelativePath(trimmed)) {
|
|
288
|
+
return Status::Error("Unsafe deleted path: " + deleted);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return Status::Ok();
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
Status MergeDirectoryRecursively(
|
|
296
|
+
const std::string& source_root,
|
|
297
|
+
const std::string& target_root,
|
|
298
|
+
const std::string& relative_root,
|
|
299
|
+
const DeleteMatcher& deletes) {
|
|
300
|
+
DIR* dir = opendir(source_root.c_str());
|
|
301
|
+
if (!dir) {
|
|
302
|
+
if (errno == ENOENT) {
|
|
303
|
+
return Status::Ok();
|
|
304
|
+
}
|
|
305
|
+
return MakeErrnoStatus("Failed to open source directory " + source_root);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
struct dirent* entry = nullptr;
|
|
309
|
+
while ((entry = readdir(dir)) != nullptr) {
|
|
310
|
+
const std::string name = entry->d_name;
|
|
311
|
+
if (name == "." || name == "..") {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const std::string source_path = JoinPath(source_root, name);
|
|
316
|
+
const std::string relative_path =
|
|
317
|
+
relative_root.empty() ? name : JoinPath(relative_root, name);
|
|
318
|
+
|
|
319
|
+
if (deletes.Matches(relative_path)) {
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
struct stat st;
|
|
324
|
+
if (stat(source_path.c_str(), &st) != 0) {
|
|
325
|
+
closedir(dir);
|
|
326
|
+
return MakeErrnoStatus("Failed to stat source path " + source_path);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const std::string target_path = JoinPath(target_root, name);
|
|
330
|
+
if (S_ISDIR(st.st_mode)) {
|
|
331
|
+
Status dir_status = EnsureDirectory(target_path);
|
|
332
|
+
if (!dir_status) {
|
|
333
|
+
closedir(dir);
|
|
334
|
+
return dir_status;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
Status merge_status =
|
|
338
|
+
MergeDirectoryRecursively(source_path, target_path, relative_path, deletes);
|
|
339
|
+
if (!merge_status) {
|
|
340
|
+
closedir(dir);
|
|
341
|
+
return merge_status;
|
|
342
|
+
}
|
|
343
|
+
} else if (S_ISREG(st.st_mode)) {
|
|
344
|
+
Status copy_status = CopyFile(source_path, target_path, false);
|
|
345
|
+
if (!copy_status) {
|
|
346
|
+
closedir(dir);
|
|
347
|
+
return copy_status;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
closedir(dir);
|
|
353
|
+
return Status::Ok();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
} // namespace
|
|
357
|
+
|
|
358
|
+
Status Status::Ok() {
|
|
359
|
+
return Status{true, std::string()};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
Status Status::Error(const std::string& message) {
|
|
363
|
+
return Status{false, message};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
Status ValidateManifest(const PatchManifest& manifest) {
|
|
367
|
+
return ValidateManifestImpl(manifest);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const BundlePatcher& DefaultBundlePatcher() {
|
|
371
|
+
static const HdiffBundlePatcher kPatcher;
|
|
372
|
+
return kPatcher;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
Status ApplyPatchFromFileSource(
|
|
376
|
+
const FileSourcePatchOptions& options,
|
|
377
|
+
const BundlePatcher& bundle_patcher) {
|
|
378
|
+
Status manifest_status = ValidateManifest(options.manifest);
|
|
379
|
+
if (!manifest_status) {
|
|
380
|
+
return manifest_status;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
Status bundle_status = bundle_patcher.Apply(
|
|
384
|
+
options.origin_bundle_path,
|
|
385
|
+
options.bundle_patch_path,
|
|
386
|
+
options.bundle_output_path);
|
|
387
|
+
if (!bundle_status) {
|
|
388
|
+
return bundle_status;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
for (const CopyOperation& copy : options.manifest.copies) {
|
|
392
|
+
Status copy_status = CopyFile(
|
|
393
|
+
JoinPath(options.source_root, copy.from),
|
|
394
|
+
JoinPath(options.target_root, copy.to),
|
|
395
|
+
true);
|
|
396
|
+
if (!copy_status) {
|
|
397
|
+
return copy_status;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (!options.enable_merge) {
|
|
402
|
+
return Status::Ok();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const std::string normalized_merge_subdir =
|
|
406
|
+
TrimTrailingSlash(options.merge_source_subdir);
|
|
407
|
+
const std::string merge_source_root =
|
|
408
|
+
normalized_merge_subdir.empty()
|
|
409
|
+
? options.source_root
|
|
410
|
+
: JoinPath(options.source_root, normalized_merge_subdir);
|
|
411
|
+
const std::string merge_target_root =
|
|
412
|
+
normalized_merge_subdir.empty()
|
|
413
|
+
? options.target_root
|
|
414
|
+
: JoinPath(options.target_root, normalized_merge_subdir);
|
|
415
|
+
|
|
416
|
+
DeleteMatcher deletes(options.manifest.deletes);
|
|
417
|
+
Status dir_status = EnsureDirectory(merge_target_root);
|
|
418
|
+
if (!dir_status) {
|
|
419
|
+
return dir_status;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return MergeDirectoryRecursively(
|
|
423
|
+
merge_source_root,
|
|
424
|
+
merge_target_root,
|
|
425
|
+
normalized_merge_subdir,
|
|
426
|
+
deletes);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
Status CleanupOldEntries(
|
|
430
|
+
const std::string& root_dir,
|
|
431
|
+
const std::string& keep_current,
|
|
432
|
+
const std::string& keep_previous,
|
|
433
|
+
int max_age_days,
|
|
434
|
+
std::time_t now) {
|
|
435
|
+
DIR* dir = opendir(root_dir.c_str());
|
|
436
|
+
if (!dir) {
|
|
437
|
+
if (errno == ENOENT) {
|
|
438
|
+
return Status::Ok();
|
|
439
|
+
}
|
|
440
|
+
return MakeErrnoStatus("Failed to open cleanup directory " + root_dir);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const std::time_t effective_now = now > 0 ? now : std::time(nullptr);
|
|
444
|
+
const std::time_t max_age_seconds =
|
|
445
|
+
static_cast<std::time_t>(max_age_days) * 24 * 60 * 60;
|
|
446
|
+
|
|
447
|
+
struct dirent* entry = nullptr;
|
|
448
|
+
while ((entry = readdir(dir)) != nullptr) {
|
|
449
|
+
const std::string name = entry->d_name;
|
|
450
|
+
if (name == "." || name == ".." || (!name.empty() && name[0] == '.')) {
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (name == keep_current || name == keep_previous) {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const std::string entry_path = JoinPath(root_dir, name);
|
|
458
|
+
struct stat st;
|
|
459
|
+
if (stat(entry_path.c_str(), &st) != 0) {
|
|
460
|
+
closedir(dir);
|
|
461
|
+
return MakeErrnoStatus("Failed to stat cleanup path " + entry_path);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (effective_now - st.st_mtime < max_age_seconds) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
Status remove_status = RemovePathRecursively(entry_path);
|
|
469
|
+
if (!remove_status) {
|
|
470
|
+
closedir(dir);
|
|
471
|
+
return remove_status;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
closedir(dir);
|
|
476
|
+
return Status::Ok();
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
bool IsSafeRelativePath(const std::string& path) {
|
|
480
|
+
if (path.empty()) {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
if (path[0] == '/' || path.find('\\') != std::string::npos) {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const std::vector<std::string> segments = SplitPath(path);
|
|
488
|
+
for (const std::string& segment : segments) {
|
|
489
|
+
if (segment.empty() || segment == "." || segment == "..") {
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
Status HdiffBundlePatcher::Apply(
|
|
497
|
+
const std::string& origin_bundle_path,
|
|
498
|
+
const std::string& bundle_patch_path,
|
|
499
|
+
const std::string& destination_bundle_path) const {
|
|
500
|
+
if (!PathExists(origin_bundle_path)) {
|
|
501
|
+
return Status::Error("Origin bundle not found: " + origin_bundle_path);
|
|
502
|
+
}
|
|
503
|
+
if (!PathExists(bundle_patch_path)) {
|
|
504
|
+
return Status::Error("Bundle patch not found: " + bundle_patch_path);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const std::string parent = Dirname(destination_bundle_path);
|
|
508
|
+
Status dir_status = EnsureDirectory(parent);
|
|
509
|
+
if (!dir_status) {
|
|
510
|
+
return dir_status;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (PathExists(destination_bundle_path)) {
|
|
514
|
+
Status remove_status = RemovePathRecursively(destination_bundle_path);
|
|
515
|
+
if (!remove_status) {
|
|
516
|
+
return remove_status;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
int result = hpatch_by_file(
|
|
521
|
+
origin_bundle_path.c_str(),
|
|
522
|
+
destination_bundle_path.c_str(),
|
|
523
|
+
bundle_patch_path.c_str());
|
|
524
|
+
if (result != 0) {
|
|
525
|
+
std::ostringstream stream;
|
|
526
|
+
stream << "Failed to apply bundle patch, hpatch error " << result;
|
|
527
|
+
return Status::Error(stream.str());
|
|
528
|
+
}
|
|
529
|
+
return Status::Ok();
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
} // namespace patch
|
|
533
|
+
} // namespace pushy
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <ctime>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <vector>
|
|
6
|
+
|
|
7
|
+
namespace pushy {
|
|
8
|
+
namespace patch {
|
|
9
|
+
|
|
10
|
+
struct Status {
|
|
11
|
+
bool ok;
|
|
12
|
+
std::string message;
|
|
13
|
+
|
|
14
|
+
explicit operator bool() const { return ok; }
|
|
15
|
+
|
|
16
|
+
static Status Ok();
|
|
17
|
+
static Status Error(const std::string& message);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
struct CopyOperation {
|
|
21
|
+
std::string from;
|
|
22
|
+
std::string to;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
struct PatchManifest {
|
|
26
|
+
std::vector<CopyOperation> copies;
|
|
27
|
+
std::vector<std::string> deletes;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
struct FileSourcePatchOptions {
|
|
31
|
+
PatchManifest manifest;
|
|
32
|
+
std::string source_root;
|
|
33
|
+
std::string target_root;
|
|
34
|
+
std::string origin_bundle_path;
|
|
35
|
+
std::string bundle_patch_path;
|
|
36
|
+
std::string bundle_output_path;
|
|
37
|
+
std::string merge_source_subdir;
|
|
38
|
+
bool enable_merge = true;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
class BundlePatcher {
|
|
42
|
+
public:
|
|
43
|
+
virtual ~BundlePatcher() = default;
|
|
44
|
+
virtual Status Apply(
|
|
45
|
+
const std::string& origin_bundle_path,
|
|
46
|
+
const std::string& bundle_patch_path,
|
|
47
|
+
const std::string& destination_bundle_path) const = 0;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const BundlePatcher& DefaultBundlePatcher();
|
|
51
|
+
|
|
52
|
+
Status ValidateManifest(const PatchManifest& manifest);
|
|
53
|
+
|
|
54
|
+
Status ApplyPatchFromFileSource(
|
|
55
|
+
const FileSourcePatchOptions& options,
|
|
56
|
+
const BundlePatcher& bundle_patcher = DefaultBundlePatcher());
|
|
57
|
+
|
|
58
|
+
Status CleanupOldEntries(
|
|
59
|
+
const std::string& root_dir,
|
|
60
|
+
const std::string& keep_current,
|
|
61
|
+
const std::string& keep_previous,
|
|
62
|
+
int max_age_days,
|
|
63
|
+
std::time_t now = 0);
|
|
64
|
+
|
|
65
|
+
bool IsSafeRelativePath(const std::string& path);
|
|
66
|
+
|
|
67
|
+
} // namespace patch
|
|
68
|
+
} // namespace pushy
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#include <jni.h>
|
|
2
|
+
|
|
3
|
+
#include <string>
|
|
4
|
+
#include <vector>
|
|
5
|
+
|
|
6
|
+
#include "patch_core.h"
|
|
7
|
+
|
|
8
|
+
namespace {
|
|
9
|
+
|
|
10
|
+
std::string JStringToString(JNIEnv* env, jstring value) {
|
|
11
|
+
if (value == nullptr) {
|
|
12
|
+
return std::string();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const char* chars = env->GetStringUTFChars(value, nullptr);
|
|
16
|
+
if (chars == nullptr) {
|
|
17
|
+
return std::string();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
std::string result(chars);
|
|
21
|
+
env->ReleaseStringUTFChars(value, chars);
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
std::vector<std::string> JArrayToVector(JNIEnv* env, jobjectArray values) {
|
|
26
|
+
std::vector<std::string> result;
|
|
27
|
+
if (values == nullptr) {
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const jsize size = env->GetArrayLength(values);
|
|
32
|
+
result.reserve(static_cast<size_t>(size));
|
|
33
|
+
for (jsize i = 0; i < size; ++i) {
|
|
34
|
+
auto* item = static_cast<jstring>(env->GetObjectArrayElement(values, i));
|
|
35
|
+
result.push_back(JStringToString(env, item));
|
|
36
|
+
env->DeleteLocalRef(item);
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
void ThrowRuntimeException(JNIEnv* env, const std::string& message) {
|
|
42
|
+
jclass exception = env->FindClass("java/lang/RuntimeException");
|
|
43
|
+
if (exception != nullptr) {
|
|
44
|
+
env->ThrowNew(exception, message.c_str());
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
} // namespace
|
|
49
|
+
|
|
50
|
+
extern "C" JNIEXPORT void JNICALL
|
|
51
|
+
Java_cn_reactnative_modules_update_DownloadTask_applyPatchFromFileSource(
|
|
52
|
+
JNIEnv* env,
|
|
53
|
+
jclass,
|
|
54
|
+
jstring source_root,
|
|
55
|
+
jstring target_root,
|
|
56
|
+
jstring origin_bundle_path,
|
|
57
|
+
jstring bundle_patch_path,
|
|
58
|
+
jstring bundle_output_path,
|
|
59
|
+
jstring merge_source_subdir,
|
|
60
|
+
jboolean enable_merge,
|
|
61
|
+
jobjectArray copy_froms,
|
|
62
|
+
jobjectArray copy_tos,
|
|
63
|
+
jobjectArray deletes) {
|
|
64
|
+
const std::vector<std::string> from_values = JArrayToVector(env, copy_froms);
|
|
65
|
+
const std::vector<std::string> to_values = JArrayToVector(env, copy_tos);
|
|
66
|
+
|
|
67
|
+
if (from_values.size() != to_values.size()) {
|
|
68
|
+
ThrowRuntimeException(env, "copy_froms and copy_tos length mismatch");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pushy::patch::FileSourcePatchOptions options;
|
|
73
|
+
options.source_root = JStringToString(env, source_root);
|
|
74
|
+
options.target_root = JStringToString(env, target_root);
|
|
75
|
+
options.origin_bundle_path = JStringToString(env, origin_bundle_path);
|
|
76
|
+
options.bundle_patch_path = JStringToString(env, bundle_patch_path);
|
|
77
|
+
options.bundle_output_path = JStringToString(env, bundle_output_path);
|
|
78
|
+
options.merge_source_subdir = JStringToString(env, merge_source_subdir);
|
|
79
|
+
options.enable_merge = enable_merge == JNI_TRUE;
|
|
80
|
+
|
|
81
|
+
for (size_t index = 0; index < from_values.size(); ++index) {
|
|
82
|
+
options.manifest.copies.push_back(pushy::patch::CopyOperation{
|
|
83
|
+
from_values[index],
|
|
84
|
+
to_values[index],
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
options.manifest.deletes = JArrayToVector(env, deletes);
|
|
88
|
+
|
|
89
|
+
const pushy::patch::Status status =
|
|
90
|
+
pushy::patch::ApplyPatchFromFileSource(options);
|
|
91
|
+
if (!status.ok) {
|
|
92
|
+
ThrowRuntimeException(env, status.message);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
extern "C" JNIEXPORT void JNICALL
|
|
97
|
+
Java_cn_reactnative_modules_update_DownloadTask_cleanupOldEntries(
|
|
98
|
+
JNIEnv* env,
|
|
99
|
+
jclass,
|
|
100
|
+
jstring root_dir,
|
|
101
|
+
jstring keep_current,
|
|
102
|
+
jstring keep_previous,
|
|
103
|
+
jint max_age_days) {
|
|
104
|
+
const pushy::patch::Status status = pushy::patch::CleanupOldEntries(
|
|
105
|
+
JStringToString(env, root_dir),
|
|
106
|
+
JStringToString(env, keep_current),
|
|
107
|
+
JStringToString(env, keep_previous),
|
|
108
|
+
static_cast<int>(max_age_days));
|
|
109
|
+
if (!status.ok) {
|
|
110
|
+
ThrowRuntimeException(env, status.message);
|
|
111
|
+
}
|
|
112
|
+
}
|