react-native-update 10.43.3 → 10.44.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/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +24 -12
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +23 -8
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +9 -1
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +64 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +2 -2
- package/android/src/main/java/cn/reactnative/modules/update/UpdateModuleImpl.java +8 -8
- package/cpp/patch_core/archive_patch_core.cpp +16 -0
- package/cpp/patch_core/archive_patch_core.h +5 -0
- package/cpp/patch_core/jni_util.h +56 -0
- package/cpp/patch_core/patch_core_android.cpp +4 -37
- package/cpp/patch_core/state_ops.h +24 -0
- package/cpp/patch_core/tests/patch_core_test.cpp +32 -2
- package/cpp/patch_core/update_core_android.cpp +43 -69
- package/harmony/pushy/src/main/cpp/pushy.cpp +162 -35
- package/harmony/pushy/src/main/ets/DownloadTask.ts +59 -9
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +2 -2
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +10 -10
- package/harmony/pushy/src/main/ets/UpdateContext.ts +26 -9
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushyDownloader.mm +41 -2
- package/package.json +2 -1
- package/src/client.ts +130 -75
- package/src/provider.tsx +40 -26
- package/src/utils.ts +2 -2
- package/harmony/pushy/src/main/cpp/pushy.c +0 -117
- package/harmony/pushy/src/main/cpp/pushy.h +0 -8
- package/src/__tests__/setup.ts +0 -44
|
@@ -92,6 +92,10 @@ final class BundledResourceCopier {
|
|
|
92
92
|
SafeZipFile baseZipFile = zipFileMap.get(context.getPackageResourcePath());
|
|
93
93
|
HashMap<String, ArrayList<File>> remainingFiles =
|
|
94
94
|
new HashMap<String, ArrayList<File>>(resToCopy);
|
|
95
|
+
// Track copies that were located but failed to write. These are real
|
|
96
|
+
// failures (disk full, corrupt archive) that must fail the update,
|
|
97
|
+
// not skips — otherwise the update activates with missing resources.
|
|
98
|
+
ArrayList<String> failedCopies = new ArrayList<String>();
|
|
95
99
|
|
|
96
100
|
for (String fromPath : new ArrayList<String>(remainingFiles.keySet())) {
|
|
97
101
|
ArrayList<File> targets = remainingFiles.get(fromPath);
|
|
@@ -168,23 +172,31 @@ final class BundledResourceCopier {
|
|
|
168
172
|
}
|
|
169
173
|
lastTarget = target;
|
|
170
174
|
} catch (IOException e) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
175
|
+
// A located resource that fails to write is a hard
|
|
176
|
+
// failure, not a skip: record it and fail the update
|
|
177
|
+
// after the loop so a broken update is not activated.
|
|
178
|
+
Log.e(
|
|
179
|
+
UpdateContext.TAG,
|
|
180
|
+
"Failed to copy resource "
|
|
181
|
+
+ actualSourcePath
|
|
182
|
+
+ " to "
|
|
183
|
+
+ target,
|
|
184
|
+
e
|
|
185
|
+
);
|
|
186
|
+
failedCopies.add(actualSourcePath + " -> " + target);
|
|
182
187
|
}
|
|
183
188
|
}
|
|
184
189
|
remainingFiles.remove(fromPath);
|
|
185
190
|
}
|
|
186
191
|
|
|
187
|
-
if (!
|
|
192
|
+
if (!failedCopies.isEmpty()) {
|
|
193
|
+
throw new IOException(
|
|
194
|
+
"Failed to copy " + failedCopies.size()
|
|
195
|
+
+ " bundled resource(s): " + failedCopies
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!remainingFiles.isEmpty()) {
|
|
188
200
|
Log.w(
|
|
189
201
|
UpdateContext.TAG,
|
|
190
202
|
"Skipped " + remainingFiles.size() + " missing bundled resources"
|
|
@@ -29,6 +29,10 @@ import org.json.JSONTokener;
|
|
|
29
29
|
|
|
30
30
|
class DownloadTask implements Runnable {
|
|
31
31
|
private static final int DOWNLOAD_CHUNK_SIZE = 4096;
|
|
32
|
+
// When the server does not report Content-Length we cannot key progress
|
|
33
|
+
// events on percentage change, so throttle by bytes to avoid flooding the
|
|
34
|
+
// bridge (e.g. a 20MB chunked download would otherwise emit ~5000 events).
|
|
35
|
+
private static final long PROGRESS_BYTES_THRESHOLD = 256 * 1024;
|
|
32
36
|
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient();
|
|
33
37
|
|
|
34
38
|
static {
|
|
@@ -98,6 +102,7 @@ class DownloadTask implements Runnable {
|
|
|
98
102
|
long bytesRead;
|
|
99
103
|
long received = 0;
|
|
100
104
|
int currentPercentage = 0;
|
|
105
|
+
long lastPostedBytes = 0;
|
|
101
106
|
|
|
102
107
|
try (
|
|
103
108
|
BufferedSource source = body.source();
|
|
@@ -113,7 +118,8 @@ class DownloadTask implements Runnable {
|
|
|
113
118
|
currentPercentage = percentage;
|
|
114
119
|
postProgress(received, contentLength);
|
|
115
120
|
}
|
|
116
|
-
} else {
|
|
121
|
+
} else if (received - lastPostedBytes >= PROGRESS_BYTES_THRESHOLD) {
|
|
122
|
+
lastPostedBytes = received;
|
|
117
123
|
postProgress(received, contentLength);
|
|
118
124
|
}
|
|
119
125
|
}
|
|
@@ -394,19 +400,28 @@ class DownloadTask implements Runnable {
|
|
|
394
400
|
default:
|
|
395
401
|
break;
|
|
396
402
|
}
|
|
397
|
-
|
|
398
|
-
if (params.listener != null) {
|
|
399
|
-
params.listener.onDownloadCompleted(params);
|
|
400
|
-
}
|
|
401
403
|
} catch (Throwable error) {
|
|
402
|
-
|
|
403
|
-
Log.e(UpdateContext.TAG, "download task failed", error);
|
|
404
|
-
}
|
|
404
|
+
Log.e(UpdateContext.TAG, "download task failed", error);
|
|
405
405
|
cleanUpAfterFailure(taskType);
|
|
406
406
|
|
|
407
407
|
if (params.listener != null) {
|
|
408
408
|
params.listener.onDownloadFailed(error);
|
|
409
409
|
}
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// The task itself succeeded. Run the completion callback outside the
|
|
414
|
+
// try/catch above so an exception thrown by the callback (e.g. a
|
|
415
|
+
// FileProvider misconfiguration during installApk) is not mistaken for
|
|
416
|
+
// a download failure that deletes the successfully downloaded file and
|
|
417
|
+
// settles the promise a second time.
|
|
418
|
+
if (params.listener != null) {
|
|
419
|
+
try {
|
|
420
|
+
params.listener.onDownloadCompleted(params);
|
|
421
|
+
} catch (Throwable error) {
|
|
422
|
+
Log.e(UpdateContext.TAG, "download completion callback failed", error);
|
|
423
|
+
params.listener.onDownloadFailed(error);
|
|
424
|
+
}
|
|
410
425
|
}
|
|
411
426
|
}
|
|
412
427
|
|
|
@@ -75,7 +75,13 @@ final class ReactReloadManager {
|
|
|
75
75
|
currentReactHost,
|
|
76
76
|
createBundleLoader(application, updateBundlePath, true)
|
|
77
77
|
);
|
|
78
|
-
} catch (Throwable
|
|
78
|
+
} catch (Throwable reloadError) {
|
|
79
|
+
Log.e(
|
|
80
|
+
UpdateContext.TAG,
|
|
81
|
+
"Failed to reload via ReactHost, falling back to Activity.recreate() "
|
|
82
|
+
+ "(this may load the previous bundle until next cold start)",
|
|
83
|
+
reloadError
|
|
84
|
+
);
|
|
79
85
|
currentActivity.recreate();
|
|
80
86
|
}
|
|
81
87
|
}
|
|
@@ -172,6 +178,7 @@ final class ReactReloadManager {
|
|
|
172
178
|
}
|
|
173
179
|
}
|
|
174
180
|
} catch (Throwable ignored) {
|
|
181
|
+
Log.w(UpdateContext.TAG, "getReactHost via ReactDelegate reflection failed", ignored);
|
|
175
182
|
}
|
|
176
183
|
}
|
|
177
184
|
|
|
@@ -179,6 +186,7 @@ final class ReactReloadManager {
|
|
|
179
186
|
Method getReactHostMethod = application.getClass().getMethod("getReactHost");
|
|
180
187
|
return getReactHostMethod.invoke(application);
|
|
181
188
|
} catch (Throwable ignored) {
|
|
189
|
+
Log.w(UpdateContext.TAG, "getReactHost via Application.getReactHost() failed", ignored);
|
|
182
190
|
}
|
|
183
191
|
|
|
184
192
|
return null;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package cn.reactnative.modules.update;
|
|
2
|
+
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
5
|
+
import com.facebook.react.bridge.Promise;
|
|
6
|
+
|
|
7
|
+
import java.util.concurrent.Executor;
|
|
8
|
+
import java.util.concurrent.Executors;
|
|
9
|
+
import java.util.concurrent.ThreadFactory;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Runs state-persistence operations (switchVersion / markSuccess / setUuid /
|
|
13
|
+
* setLocalHashInfo) on a dedicated single background thread.
|
|
14
|
+
*
|
|
15
|
+
* These operations only read/modify SharedPreferences via a synchronous
|
|
16
|
+
* commit(); they were previously dispatched to the UI thread purely to
|
|
17
|
+
* serialize them. markSuccess in particular runs on every cold start, so doing
|
|
18
|
+
* its blocking disk write on the main thread caused jank/ANR on low-end
|
|
19
|
+
* devices. A single-thread executor preserves the same serialization guarantee
|
|
20
|
+
* while keeping the disk I/O off the UI thread.
|
|
21
|
+
*
|
|
22
|
+
* Note: reload/restart operations must still run on the UI thread and therefore
|
|
23
|
+
* keep using {@link UiThreadRunner}.
|
|
24
|
+
*/
|
|
25
|
+
final class StateSerialRunner {
|
|
26
|
+
interface Operation {
|
|
27
|
+
void run() throws Throwable;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Single worker thread -> operations stay serialized in submission order,
|
|
31
|
+
// matching the previous UI-thread behavior. The thread is named so it is
|
|
32
|
+
// identifiable in thread dumps / ANR traces when diagnosing persistence.
|
|
33
|
+
private static final Executor EXECUTOR = Executors.newSingleThreadExecutor(
|
|
34
|
+
new ThreadFactory() {
|
|
35
|
+
@Override
|
|
36
|
+
public Thread newThread(Runnable r) {
|
|
37
|
+
return new Thread(r, "pushy-state-serial");
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
private StateSerialRunner() {
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static void run(
|
|
45
|
+
@Nullable final Promise promise,
|
|
46
|
+
final String operationName,
|
|
47
|
+
final Operation operation
|
|
48
|
+
) {
|
|
49
|
+
EXECUTOR.execute(new Runnable() {
|
|
50
|
+
@Override
|
|
51
|
+
public void run() {
|
|
52
|
+
try {
|
|
53
|
+
operation.run();
|
|
54
|
+
} catch (Throwable error) {
|
|
55
|
+
if (promise != null) {
|
|
56
|
+
promise.reject(operationName + " failed", error);
|
|
57
|
+
} else {
|
|
58
|
+
Log.e(UpdateContext.TAG, operationName + " failed", error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -37,7 +37,7 @@ public class UpdateContext {
|
|
|
37
37
|
private static final String KEY_FIRST_LOAD_MARKED = "firstLoadMarked";
|
|
38
38
|
|
|
39
39
|
// Singleton instance
|
|
40
|
-
private static UpdateContext sInstance;
|
|
40
|
+
private static volatile UpdateContext sInstance;
|
|
41
41
|
private static final Object sLock = new Object();
|
|
42
42
|
private static ReactInstanceManager pendingReactInstanceManager;
|
|
43
43
|
|
|
@@ -55,7 +55,7 @@ public class UpdateContext {
|
|
|
55
55
|
boolean flagB
|
|
56
56
|
);
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
private UpdateContext(Context context) {
|
|
59
59
|
this.context = context.getApplicationContext();
|
|
60
60
|
this.executor = Executors.newSingleThreadExecutor();
|
|
61
61
|
|
|
@@ -149,7 +149,7 @@ public class UpdateModuleImpl {
|
|
|
149
149
|
final Promise promise
|
|
150
150
|
) {
|
|
151
151
|
final String hash = options.getString("hash");
|
|
152
|
-
|
|
152
|
+
StateSerialRunner.run(promise, "switchVersionLater", new StateSerialRunner.Operation() {
|
|
153
153
|
@Override
|
|
154
154
|
public void run() {
|
|
155
155
|
setNeedUpdateInternal(updateContext, hash);
|
|
@@ -160,7 +160,7 @@ public class UpdateModuleImpl {
|
|
|
160
160
|
|
|
161
161
|
public static void setNeedUpdate(final UpdateContext updateContext, final ReadableMap options) {
|
|
162
162
|
final String hash = options.getString("hash");
|
|
163
|
-
|
|
163
|
+
StateSerialRunner.run(null, "switchVersionLater", new StateSerialRunner.Operation() {
|
|
164
164
|
@Override
|
|
165
165
|
public void run() {
|
|
166
166
|
setNeedUpdateInternal(updateContext, hash);
|
|
@@ -173,7 +173,7 @@ public class UpdateModuleImpl {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
public static void markSuccess(final UpdateContext updateContext, final Promise promise) {
|
|
176
|
-
|
|
176
|
+
StateSerialRunner.run(promise, "markSuccess", new StateSerialRunner.Operation() {
|
|
177
177
|
@Override
|
|
178
178
|
public void run() {
|
|
179
179
|
markSuccessInternal(updateContext);
|
|
@@ -183,7 +183,7 @@ public class UpdateModuleImpl {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
public static void markSuccess(final UpdateContext updateContext) {
|
|
186
|
-
|
|
186
|
+
StateSerialRunner.run(null, "markSuccess", new StateSerialRunner.Operation() {
|
|
187
187
|
@Override
|
|
188
188
|
public void run() {
|
|
189
189
|
markSuccessInternal(updateContext);
|
|
@@ -200,7 +200,7 @@ public class UpdateModuleImpl {
|
|
|
200
200
|
final String uuid,
|
|
201
201
|
final Promise promise
|
|
202
202
|
) {
|
|
203
|
-
|
|
203
|
+
StateSerialRunner.run(promise, "setUuid", new StateSerialRunner.Operation() {
|
|
204
204
|
@Override
|
|
205
205
|
public void run() {
|
|
206
206
|
setUuidInternal(updateContext, uuid);
|
|
@@ -210,7 +210,7 @@ public class UpdateModuleImpl {
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
public static void setUuid(final UpdateContext updateContext, final String uuid) {
|
|
213
|
-
|
|
213
|
+
StateSerialRunner.run(null, "setUuid", new StateSerialRunner.Operation() {
|
|
214
214
|
@Override
|
|
215
215
|
public void run() {
|
|
216
216
|
setUuidInternal(updateContext, uuid);
|
|
@@ -235,7 +235,7 @@ public class UpdateModuleImpl {
|
|
|
235
235
|
final String info,
|
|
236
236
|
final Promise promise
|
|
237
237
|
) {
|
|
238
|
-
|
|
238
|
+
StateSerialRunner.run(promise, "setLocalHashInfo", new StateSerialRunner.Operation() {
|
|
239
239
|
@Override
|
|
240
240
|
public void run() {
|
|
241
241
|
setLocalHashInfoInternal(updateContext, hash, info);
|
|
@@ -249,7 +249,7 @@ public class UpdateModuleImpl {
|
|
|
249
249
|
final String hash,
|
|
250
250
|
final String info
|
|
251
251
|
) {
|
|
252
|
-
|
|
252
|
+
StateSerialRunner.run(null, "setLocalHashInfo", new StateSerialRunner.Operation() {
|
|
253
253
|
@Override
|
|
254
254
|
public void run() {
|
|
255
255
|
setLocalHashInfoInternal(updateContext, hash, info);
|
|
@@ -25,6 +25,22 @@ EntryAction ClassifyEntry(
|
|
|
25
25
|
return entry_name == kManifestEntryName ? EntryAction::kSkip : EntryAction::kExtract;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
bool TryParseArchivePatchType(int value, ArchivePatchType* out) {
|
|
29
|
+
switch (value) {
|
|
30
|
+
case 1:
|
|
31
|
+
*out = ArchivePatchType::kFull;
|
|
32
|
+
return true;
|
|
33
|
+
case 2:
|
|
34
|
+
*out = ArchivePatchType::kPatchFromPackage;
|
|
35
|
+
return true;
|
|
36
|
+
case 3:
|
|
37
|
+
*out = ArchivePatchType::kPatchFromPpk;
|
|
38
|
+
return true;
|
|
39
|
+
default:
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
28
44
|
patch::Status BuildArchivePatchPlan(
|
|
29
45
|
ArchivePatchType type,
|
|
30
46
|
const patch::PatchManifest& manifest,
|
|
@@ -35,6 +35,11 @@ EntryAction ClassifyEntry(
|
|
|
35
35
|
ArchivePatchType type,
|
|
36
36
|
const std::string& entry_name);
|
|
37
37
|
|
|
38
|
+
// Convert a platform-supplied integer to an ArchivePatchType. Returns false for
|
|
39
|
+
// unknown values so callers can fail loudly instead of silently treating an
|
|
40
|
+
// incremental patch as a full package (which would skip validation).
|
|
41
|
+
bool TryParseArchivePatchType(int value, ArchivePatchType* out);
|
|
42
|
+
|
|
38
43
|
patch::Status BuildArchivePatchPlan(
|
|
39
44
|
ArchivePatchType type,
|
|
40
45
|
const patch::PatchManifest& manifest,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <jni.h>
|
|
4
|
+
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <vector>
|
|
7
|
+
|
|
8
|
+
// Small JNI helpers shared by the Android glue translation units
|
|
9
|
+
// (patch_core_android.cpp and update_core_android.cpp). Header-only with inline
|
|
10
|
+
// linkage so both can include it without a separate compilation unit.
|
|
11
|
+
|
|
12
|
+
namespace pushy {
|
|
13
|
+
namespace jni_util {
|
|
14
|
+
|
|
15
|
+
inline std::string JStringToString(JNIEnv* env, jstring value) {
|
|
16
|
+
if (value == nullptr) {
|
|
17
|
+
return std::string();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const char* chars = env->GetStringUTFChars(value, nullptr);
|
|
21
|
+
if (chars == nullptr) {
|
|
22
|
+
return std::string();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
std::string result(chars);
|
|
26
|
+
env->ReleaseStringUTFChars(value, chars);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
inline std::vector<std::string> JArrayToVector(
|
|
31
|
+
JNIEnv* env,
|
|
32
|
+
jobjectArray values) {
|
|
33
|
+
std::vector<std::string> result;
|
|
34
|
+
if (values == nullptr) {
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const jsize size = env->GetArrayLength(values);
|
|
39
|
+
result.reserve(static_cast<size_t>(size));
|
|
40
|
+
for (jsize index = 0; index < size; ++index) {
|
|
41
|
+
auto* item = static_cast<jstring>(env->GetObjectArrayElement(values, index));
|
|
42
|
+
result.push_back(JStringToString(env, item));
|
|
43
|
+
env->DeleteLocalRef(item);
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
inline void ThrowRuntimeException(JNIEnv* env, const std::string& message) {
|
|
49
|
+
jclass exception = env->FindClass("java/lang/RuntimeException");
|
|
50
|
+
if (exception != nullptr) {
|
|
51
|
+
env->ThrowNew(exception, message.c_str());
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
} // namespace jni_util
|
|
56
|
+
} // namespace pushy
|
|
@@ -3,47 +3,14 @@
|
|
|
3
3
|
#include <string>
|
|
4
4
|
#include <vector>
|
|
5
5
|
|
|
6
|
+
#include "jni_util.h"
|
|
6
7
|
#include "patch_core.h"
|
|
7
8
|
|
|
8
9
|
namespace {
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
}
|
|
11
|
+
using pushy::jni_util::JArrayToVector;
|
|
12
|
+
using pushy::jni_util::JStringToString;
|
|
13
|
+
using pushy::jni_util::ThrowRuntimeException;
|
|
47
14
|
|
|
48
15
|
} // namespace
|
|
49
16
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
// Single source of truth for the state-machine operation codes shared across
|
|
4
|
+
// the platform glue layers (Android JNI in update_core_android.cpp and
|
|
5
|
+
// HarmonyOS NAPI in pushy.cpp). The integer values MUST stay in sync with the
|
|
6
|
+
// callers on each platform:
|
|
7
|
+
// - Android: UpdateContext.java (STATE_OP_* constants)
|
|
8
|
+
// - HarmonyOS: UpdateContext.ts (StateOperation usage)
|
|
9
|
+
// Do not renumber existing entries; only append new ones.
|
|
10
|
+
|
|
11
|
+
namespace pushy {
|
|
12
|
+
namespace state_ops {
|
|
13
|
+
|
|
14
|
+
enum class StateOperation {
|
|
15
|
+
kSwitchVersion = 1,
|
|
16
|
+
kMarkSuccess = 2,
|
|
17
|
+
kRollback = 3,
|
|
18
|
+
kClearFirstTime = 4,
|
|
19
|
+
kClearRollbackMark = 5,
|
|
20
|
+
kResolveLaunch = 6,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
} // namespace state_ops
|
|
24
|
+
} // namespace pushy
|
|
@@ -426,6 +426,28 @@ void TestArchivePatchCoreRejectsMissingEntries() {
|
|
|
426
426
|
"manifest entry should be skipped");
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
+
void TestTryParseArchivePatchType() {
|
|
430
|
+
pushy::archive_patch::ArchivePatchType type;
|
|
431
|
+
Expect(
|
|
432
|
+
pushy::archive_patch::TryParseArchivePatchType(1, &type) &&
|
|
433
|
+
type == pushy::archive_patch::ArchivePatchType::kFull,
|
|
434
|
+
"1 should parse to kFull");
|
|
435
|
+
Expect(
|
|
436
|
+
pushy::archive_patch::TryParseArchivePatchType(2, &type) &&
|
|
437
|
+
type == pushy::archive_patch::ArchivePatchType::kPatchFromPackage,
|
|
438
|
+
"2 should parse to kPatchFromPackage");
|
|
439
|
+
Expect(
|
|
440
|
+
pushy::archive_patch::TryParseArchivePatchType(3, &type) &&
|
|
441
|
+
type == pushy::archive_patch::ArchivePatchType::kPatchFromPpk,
|
|
442
|
+
"3 should parse to kPatchFromPpk");
|
|
443
|
+
Expect(
|
|
444
|
+
!pushy::archive_patch::TryParseArchivePatchType(0, &type),
|
|
445
|
+
"0 should be rejected");
|
|
446
|
+
Expect(
|
|
447
|
+
!pushy::archive_patch::TryParseArchivePatchType(4, &type),
|
|
448
|
+
"unknown type should be rejected, not silently coerced to kFull");
|
|
449
|
+
}
|
|
450
|
+
|
|
429
451
|
void TestArchivePatchCoreSupportsCustomBundlePatchEntry() {
|
|
430
452
|
PatchManifest manifest;
|
|
431
453
|
manifest.copies.push_back(CopyOperation{"assets/a.png", "assets/a.png"});
|
|
@@ -524,6 +546,7 @@ int main() {
|
|
|
524
546
|
{"StateCoreCanClearMarkers", TestStateCoreCanClearMarkers},
|
|
525
547
|
{"ArchivePatchCoreBuildPlanAndCopyGroups", TestArchivePatchCoreBuildPlanAndCopyGroups},
|
|
526
548
|
{"ArchivePatchCoreRejectsMissingEntries", TestArchivePatchCoreRejectsMissingEntries},
|
|
549
|
+
{"TryParseArchivePatchType", TestTryParseArchivePatchType},
|
|
527
550
|
{"ArchivePatchCoreSupportsCustomBundlePatchEntry", TestArchivePatchCoreSupportsCustomBundlePatchEntry},
|
|
528
551
|
{"ArchivePatchCoreHarmonyBundlePatchFromPackage", TestArchivePatchCoreHarmonyBundlePatchFromPackage},
|
|
529
552
|
{"StateCoreRollbackToEmptyVersion", TestStateCoreRollbackToEmptyVersion},
|
|
@@ -531,15 +554,22 @@ int main() {
|
|
|
531
554
|
{"StateCoreSwitchToSameVersion", TestStateCoreSwitchToSameVersion},
|
|
532
555
|
};
|
|
533
556
|
|
|
557
|
+
int failures = 0;
|
|
534
558
|
for (const auto& test : tests) {
|
|
535
559
|
try {
|
|
536
560
|
test.second();
|
|
537
561
|
std::fprintf(stdout, "[PASS] %s\n", test.first.c_str());
|
|
538
562
|
} catch (const std::exception& error) {
|
|
539
563
|
std::fprintf(stderr, "[FAIL] %s: %s\n", test.first.c_str(), error.what());
|
|
540
|
-
|
|
564
|
+
++failures;
|
|
541
565
|
}
|
|
542
566
|
}
|
|
543
567
|
|
|
544
|
-
|
|
568
|
+
std::fprintf(
|
|
569
|
+
stdout,
|
|
570
|
+
"\n%zu tests, %d passed, %d failed\n",
|
|
571
|
+
tests.size(),
|
|
572
|
+
static_cast<int>(tests.size()) - failures,
|
|
573
|
+
failures);
|
|
574
|
+
return failures == 0 ? 0 : 1;
|
|
545
575
|
}
|