react-native-update 10.43.3 → 10.45.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/jni/Android.mk +0 -1
- 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/BundledResourceCopier.java +24 -12
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +33 -9
- 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 +8 -4
- package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +13 -1
- 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.cpp +16 -1
- package/cpp/patch_core/patch_core.h +6 -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 +109 -2
- package/cpp/patch_core/update_core_android.cpp +43 -69
- package/harmony/pushy/src/main/cpp/pushy.cpp +163 -128
- package/harmony/pushy/src/main/ets/DownloadTask.ts +72 -19
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +2 -6
- 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/RCTPushy.mm +142 -111
- package/ios/RCTPushy/RCTPushyDownloader.mm +41 -2
- package/package.json +7 -3
- package/src/client.ts +161 -101
- package/src/context.ts +21 -7
- package/src/core.ts +5 -1
- package/src/index.ts +7 -1
- package/src/provider.tsx +91 -56
- package/src/utils.ts +58 -18
- package/android/jni/DownloadTask.c +0 -56
- package/android/jni/cn_reactnative_modules_update_DownloadTask.h +0 -21
- 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
package/android/jni/Android.mk
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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,7 +29,20 @@ import org.json.JSONTokener;
|
|
|
29
29
|
|
|
30
30
|
class DownloadTask implements Runnable {
|
|
31
31
|
private static final int DOWNLOAD_CHUNK_SIZE = 4096;
|
|
32
|
-
|
|
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;
|
|
36
|
+
// Explicit timeouts: the default client has no call timeout, so a
|
|
37
|
+
// slow-dripping connection could occupy the single-threaded download
|
|
38
|
+
// executor indefinitely and starve queued tasks. The call timeout is a
|
|
39
|
+
// generous upper bound sized for large full-package downloads.
|
|
40
|
+
private static final OkHttpClient HTTP_CLIENT = new OkHttpClient.Builder()
|
|
41
|
+
.connectTimeout(15, java.util.concurrent.TimeUnit.SECONDS)
|
|
42
|
+
.readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
|
|
43
|
+
.writeTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
|
|
44
|
+
.callTimeout(10, java.util.concurrent.TimeUnit.MINUTES)
|
|
45
|
+
.build();
|
|
33
46
|
|
|
34
47
|
static {
|
|
35
48
|
NativeUpdateCore.ensureLoaded();
|
|
@@ -98,6 +111,7 @@ class DownloadTask implements Runnable {
|
|
|
98
111
|
long bytesRead;
|
|
99
112
|
long received = 0;
|
|
100
113
|
int currentPercentage = 0;
|
|
114
|
+
long lastPostedBytes = 0;
|
|
101
115
|
|
|
102
116
|
try (
|
|
103
117
|
BufferedSource source = body.source();
|
|
@@ -113,7 +127,8 @@ class DownloadTask implements Runnable {
|
|
|
113
127
|
currentPercentage = percentage;
|
|
114
128
|
postProgress(received, contentLength);
|
|
115
129
|
}
|
|
116
|
-
} else {
|
|
130
|
+
} else if (received - lastPostedBytes >= PROGRESS_BYTES_THRESHOLD) {
|
|
131
|
+
lastPostedBytes = received;
|
|
117
132
|
postProgress(received, contentLength);
|
|
118
133
|
}
|
|
119
134
|
}
|
|
@@ -394,19 +409,28 @@ class DownloadTask implements Runnable {
|
|
|
394
409
|
default:
|
|
395
410
|
break;
|
|
396
411
|
}
|
|
397
|
-
|
|
398
|
-
if (params.listener != null) {
|
|
399
|
-
params.listener.onDownloadCompleted(params);
|
|
400
|
-
}
|
|
401
412
|
} catch (Throwable error) {
|
|
402
|
-
|
|
403
|
-
Log.e(UpdateContext.TAG, "download task failed", error);
|
|
404
|
-
}
|
|
413
|
+
Log.e(UpdateContext.TAG, "download task failed", error);
|
|
405
414
|
cleanUpAfterFailure(taskType);
|
|
406
415
|
|
|
407
416
|
if (params.listener != null) {
|
|
408
417
|
params.listener.onDownloadFailed(error);
|
|
409
418
|
}
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// The task itself succeeded. Run the completion callback outside the
|
|
423
|
+
// try/catch above so an exception thrown by the callback (e.g. a
|
|
424
|
+
// FileProvider misconfiguration during installApk) is not mistaken for
|
|
425
|
+
// a download failure that deletes the successfully downloaded file and
|
|
426
|
+
// settles the promise a second time.
|
|
427
|
+
if (params.listener != null) {
|
|
428
|
+
try {
|
|
429
|
+
params.listener.onDownloadCompleted(params);
|
|
430
|
+
} catch (Throwable error) {
|
|
431
|
+
Log.e(UpdateContext.TAG, "download completion callback failed", error);
|
|
432
|
+
params.listener.onDownloadFailed(error);
|
|
433
|
+
}
|
|
410
434
|
}
|
|
411
435
|
}
|
|
412
436
|
|
|
@@ -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
|
|
|
@@ -214,7 +214,7 @@ public class UpdateContext {
|
|
|
214
214
|
|
|
215
215
|
public void switchVersion(String hash) {
|
|
216
216
|
if (!new File(rootDir, hash+"/index.bundlejs").exists()) {
|
|
217
|
-
throw new
|
|
217
|
+
throw new IllegalStateException("Bundle version " + hash + " not found.");
|
|
218
218
|
}
|
|
219
219
|
StateCoreResult currentState = getStateSnapshot();
|
|
220
220
|
StateCoreResult nextState = runStateCore(
|
|
@@ -385,7 +385,11 @@ public class UpdateContext {
|
|
|
385
385
|
return defaultAssetsUrl;
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
|
|
388
|
+
// Guard the rollback chain against cycles: a corrupted state returning
|
|
389
|
+
// an already-visited version would otherwise spin this loop forever on
|
|
390
|
+
// the main thread.
|
|
391
|
+
java.util.HashSet<String> visitedVersions = new java.util.HashSet<>();
|
|
392
|
+
while (currentVersion != null && visitedVersions.add(currentVersion)) {
|
|
389
393
|
File bundleFile = new File(rootDir, currentVersion+"/index.bundlejs");
|
|
390
394
|
if (!bundleFile.exists()) {
|
|
391
395
|
Log.e(TAG, "Bundle version " + currentVersion + " not found.");
|
|
@@ -24,7 +24,7 @@ final class UpdateEventEmitter {
|
|
|
24
24
|
|
|
25
25
|
static void sendEvent(String eventName, WritableMap params) {
|
|
26
26
|
ReactApplicationContext reactContext = getReactContext();
|
|
27
|
-
if (reactContext == null || !reactContext
|
|
27
|
+
if (reactContext == null || !hasActiveInstance(reactContext)) {
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -32,4 +32,16 @@ final class UpdateEventEmitter {
|
|
|
32
32
|
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
33
33
|
.emit(eventName, params);
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
@SuppressWarnings("deprecation")
|
|
37
|
+
private static boolean hasActiveInstance(ReactApplicationContext reactContext) {
|
|
38
|
+
try {
|
|
39
|
+
// hasActiveCatalystInstance() is always false in bridgeless mode, which
|
|
40
|
+
// silently drops every progress event on the new architecture.
|
|
41
|
+
return reactContext.hasActiveReactInstance();
|
|
42
|
+
} catch (NoSuchMethodError e) {
|
|
43
|
+
// RN < 0.68 has no hasActiveReactInstance(); fall back for old peers.
|
|
44
|
+
return reactContext.hasActiveCatalystInstance();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
35
47
|
}
|
|
@@ -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
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
#include <sys/types.h>
|
|
9
9
|
#include <unistd.h>
|
|
10
10
|
|
|
11
|
-
#include <set>
|
|
12
11
|
#include <vector>
|
|
13
12
|
|
|
14
13
|
extern "C" {
|
|
@@ -17,6 +16,11 @@ extern "C" {
|
|
|
17
16
|
|
|
18
17
|
namespace pushy {
|
|
19
18
|
namespace patch {
|
|
19
|
+
|
|
20
|
+
namespace internal {
|
|
21
|
+
bool g_disable_hard_links = false;
|
|
22
|
+
} // namespace internal
|
|
23
|
+
|
|
20
24
|
namespace {
|
|
21
25
|
|
|
22
26
|
constexpr size_t kCopyBufferSize = 16 * 1024;
|
|
@@ -202,6 +206,17 @@ Status CopyFile(const std::string& from, const std::string& to, bool overwrite)
|
|
|
202
206
|
}
|
|
203
207
|
}
|
|
204
208
|
|
|
209
|
+
// Prefer a hard link over copying bytes: unchanged files between versions
|
|
210
|
+
// are identical, so linking is O(1) per file, writes nothing to flash, and
|
|
211
|
+
// shares disk blocks. Version directories are immutable once created (patch
|
|
212
|
+
// outputs are always written as new files), so sharing the inode with the
|
|
213
|
+
// source is safe. Fall back to a byte copy whenever linking is not possible
|
|
214
|
+
// (cross-device source such as the installed app bundle, EPERM, EMLINK, or
|
|
215
|
+
// filesystems without hard-link support).
|
|
216
|
+
if (!internal::g_disable_hard_links && link(from.c_str(), to.c_str()) == 0) {
|
|
217
|
+
return Status::Ok();
|
|
218
|
+
}
|
|
219
|
+
|
|
205
220
|
FILE* source = std::fopen(from.c_str(), "rb");
|
|
206
221
|
if (!source) {
|
|
207
222
|
return MakeErrnoStatus("Failed to open source file " + from);
|
|
@@ -64,5 +64,11 @@ Status CleanupOldEntries(
|
|
|
64
64
|
|
|
65
65
|
bool IsSafeRelativePath(const std::string& path);
|
|
66
66
|
|
|
67
|
+
namespace internal {
|
|
68
|
+
// Test-only escape hatch: forces file copies to take the byte-copy fallback
|
|
69
|
+
// instead of hard-linking, so tests can cover both paths on one filesystem.
|
|
70
|
+
extern bool g_disable_hard_links;
|
|
71
|
+
} // namespace internal
|
|
72
|
+
|
|
67
73
|
} // namespace patch
|
|
68
74
|
} // 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
|