react-native-update 10.59.1 → 10.60.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 +7 -7
- package/README.md +7 -6
- package/android/build.gradle +25 -33
- package/android/jni/Android.mk +16 -24
- package/android/lib/arm64-v8a/librnpushy.so +0 -0
- package/android/lib/armeabi-v7a/librnpushy.so +0 -0
- package/android/lib/x86/librnpushy.so +0 -0
- package/android/lib/x86_64/librnpushy.so +0 -0
- package/android/proguard.pro +9 -7
- package/android/src/main/java/cn/reactnative/modules/update/ApkInstaller.java +11 -12
- package/android/src/main/java/cn/reactnative/modules/update/ArchiveLimits.java +23 -6
- package/android/src/main/java/cn/reactnative/modules/update/BundledResourceCopier.java +50 -51
- package/android/src/main/java/cn/reactnative/modules/update/CleanupKeepNames.java +12 -13
- package/android/src/main/java/cn/reactnative/modules/update/CopyGroupResult.java +3 -0
- package/android/src/main/java/cn/reactnative/modules/update/CrashHold.java +22 -30
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java +181 -332
- package/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java +3 -1
- package/android/src/main/java/cn/reactnative/modules/update/ErrorCodes.java +27 -0
- package/android/src/main/java/cn/reactnative/modules/update/FileOperationException.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/FlowBridge.java +32 -8
- package/android/src/main/java/cn/reactnative/modules/update/HttpClients.java +63 -0
- package/android/src/main/java/cn/reactnative/modules/update/HttpUtils.java +0 -11
- package/android/src/main/java/cn/reactnative/modules/update/InstallRecord.java +66 -116
- package/android/src/main/java/cn/reactnative/modules/update/NativeCore.java +9 -1
- package/android/src/main/java/cn/reactnative/modules/update/PatchFailedException.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/PushyRuntime.java +32 -60
- package/android/src/main/java/cn/reactnative/modules/update/ReactReloadManager.java +8 -23
- package/android/src/main/java/cn/reactnative/modules/update/SafeZipFile.java +42 -80
- package/android/src/main/java/cn/reactnative/modules/update/StateSerialRunner.java +22 -39
- package/android/src/main/java/cn/reactnative/modules/update/SyncCoordinator.java +176 -299
- package/android/src/main/java/cn/reactnative/modules/update/Threads.java +35 -0
- package/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java +242 -174
- package/android/src/main/java/cn/reactnative/modules/update/UpdateEventEmitter.java +1 -1
- package/android/src/main/java/cn/reactnative/modules/update/UpdateFileUtils.java +75 -17
- package/android/src/main/java/cn/reactnative/modules/update/{UpdateModuleImpl.java → UpdateModule.java} +113 -95
- package/android/src/main/java/cn/reactnative/modules/update/UpdatePackage.java +16 -19
- package/android/src/oldarch/cn/reactnative/modules/update/NativePushySpec.java +69 -0
- package/cpp/android_jni/archive_limits_jni.cpp +83 -0
- package/cpp/android_jni/install_record_jni.cpp +89 -0
- package/cpp/android_jni/jni_log.h +31 -0
- package/cpp/android_jni/jni_natives.h +40 -0
- package/cpp/android_jni/jni_registration.cpp +133 -0
- package/cpp/android_jni/jni_util.h +160 -0
- package/cpp/android_jni/update_core_android.cpp +450 -0
- package/cpp/android_jni/update_flow_jni.cpp +133 -0
- package/{android/jni → cpp/hpatch}/hpatch.c +5 -5
- package/{android/jni → cpp/hpatch}/hpatch.h +4 -1
- package/cpp/patch_core/archive_limits.cpp +194 -0
- package/cpp/patch_core/archive_limits.h +99 -10
- package/cpp/patch_core/archive_patch_core.cpp +96 -12
- package/cpp/patch_core/archive_patch_core.h +49 -0
- package/cpp/patch_core/digest.h +5 -4
- package/cpp/patch_core/error_codes.h +5 -3
- package/cpp/patch_core/install_record.cpp +430 -0
- package/cpp/patch_core/install_record.h +163 -9
- package/cpp/patch_core/patch_core.cpp +62 -16
- package/cpp/patch_core/patch_core.h +27 -7
- package/cpp/patch_core/patch_manifest.cpp +201 -0
- package/cpp/patch_core/patch_manifest.h +74 -0
- package/cpp/patch_core/state_ops.h +3 -3
- package/cpp/update_flow_core/flow_json.cpp +82 -8
- package/cpp/update_flow_core/flow_json.h +21 -0
- package/cpp/update_flow_core/native_config.cpp +456 -0
- package/cpp/update_flow_core/native_config.h +84 -0
- package/cpp/update_flow_core/native_round.cpp +326 -0
- package/cpp/update_flow_core/native_round.h +90 -0
- package/cpp/update_flow_core/update_flow_core.h +2 -2
- package/harmony/hvigor-plugin.ts +11 -19
- package/harmony/pushy/src/main/cpp/CMakeLists.txt +19 -26
- package/harmony/pushy/src/main/cpp/PushyPackage.h +2 -2
- package/harmony/pushy/src/main/cpp/PushyTurboModule.cpp +55 -94
- package/harmony/pushy/src/main/cpp/pushy.cpp +694 -517
- package/harmony/pushy/src/main/ets/ArchiveLimits.ts +48 -72
- package/harmony/pushy/src/main/ets/DownloadTask.ts +164 -404
- package/harmony/pushy/src/main/ets/DownloadTaskParams.ts +1 -3
- package/harmony/pushy/src/main/ets/ErrorCodes.ts +1 -14
- package/harmony/pushy/src/main/ets/EventHub.ts +1 -1
- package/harmony/pushy/src/main/ets/InstallRecord.ts +52 -151
- package/harmony/pushy/src/main/ets/Logger.ts +20 -34
- package/harmony/pushy/src/main/ets/NativePatchCore.ts +116 -42
- package/harmony/pushy/src/main/ets/PushyConfiguration.ts +8 -85
- package/harmony/pushy/src/main/ets/PushyTurboModule.ts +11 -25
- package/harmony/pushy/src/main/ets/SaveFile.ts +99 -18
- package/harmony/pushy/src/main/ets/SyncCoordinator.ts +136 -335
- package/harmony/pushy/src/main/ets/Texts.ts +0 -4
- package/harmony/pushy/src/main/ets/UpdateContext.ts +106 -119
- package/harmony/pushy.har +0 -0
- package/ios/RCTPushy/RCTPushy+Launch.mm +185 -0
- package/ios/RCTPushy/RCTPushy.h +12 -0
- package/ios/RCTPushy/RCTPushy.mm +87 -2628
- package/ios/RCTPushy/RCTPushyCommon.h +34 -0
- package/ios/RCTPushy/RCTPushyConfiguration.h +0 -8
- package/ios/RCTPushy/RCTPushyConfiguration.mm +40 -121
- package/ios/RCTPushy/RCTPushyDownloader.mm +40 -37
- package/ios/RCTPushy/RCTPushyInstallRecord.mm +61 -0
- package/ios/RCTPushy/RCTPushyInstaller.mm +689 -0
- package/ios/RCTPushy/RCTPushyInternal.h +144 -0
- package/ios/RCTPushy/RCTPushyOrchestrator.mm +1001 -0
- package/ios/RCTPushy/RCTPushyState.mm +314 -0
- package/package.json +25 -23
- package/react-native-update.podspec +74 -83
- package/src/client.ts +361 -451
- package/src/context.ts +1 -1
- package/src/core.ts +35 -8
- package/src/endpoint.ts +0 -2
- package/src/error.ts +13 -32
- package/src/i18n.ts +18 -83
- package/src/locales/en.ts +0 -4
- package/src/locales/zh.ts +0 -4
- package/src/provider.tsx +60 -53
- package/src/telemetry.ts +0 -13
- package/src/type.ts +0 -15
- package/src/updateFlowCore.ts +116 -10
- package/src/utils.ts +49 -42
- package/android/src/main/java/cn/reactnative/modules/update/ArchivePatchPlanResult.java +0 -6
- package/android/src/main/java/cn/reactnative/modules/update/PushyConfiguration.java +0 -104
- package/android/src/main/java/cn/reactnative/modules/update/UiThreadRunner.java +0 -37
- package/android/src/newarch/cn/reactnative/modules/update/UpdateModule.java +0 -121
- package/android/src/oldarch/cn/reactnative/modules/update/UpdateModule.java +0 -125
- package/cpp/patch_core/jni_natives.h +0 -27
- package/cpp/patch_core/jni_registration.cpp +0 -103
- package/cpp/patch_core/jni_util.h +0 -60
- package/cpp/patch_core/patch_core_android.cpp +0 -97
- package/cpp/patch_core/update_core_android.cpp +0 -485
- package/cpp/update_flow_core/update_flow_jni.cpp +0 -150
|
@@ -6,15 +6,15 @@ import android.os.Looper;
|
|
|
6
6
|
import android.util.Log;
|
|
7
7
|
import com.facebook.react.bridge.Arguments;
|
|
8
8
|
import com.facebook.react.bridge.WritableMap;
|
|
9
|
-
import java.io.ByteArrayOutputStream;
|
|
10
9
|
import java.io.File;
|
|
10
|
+
import java.io.FileOutputStream;
|
|
11
11
|
import java.io.IOException;
|
|
12
12
|
import java.io.InputStream;
|
|
13
13
|
import java.nio.charset.StandardCharsets;
|
|
14
|
+
import java.security.DigestOutputStream;
|
|
15
|
+
import java.security.MessageDigest;
|
|
14
16
|
import java.util.ArrayList;
|
|
15
17
|
import java.util.Enumeration;
|
|
16
|
-
import java.util.HashMap;
|
|
17
|
-
import java.util.Iterator;
|
|
18
18
|
import java.util.zip.ZipEntry;
|
|
19
19
|
import okhttp3.Call;
|
|
20
20
|
import okhttp3.OkHttpClient;
|
|
@@ -24,7 +24,6 @@ import okhttp3.ResponseBody;
|
|
|
24
24
|
import okio.BufferedSink;
|
|
25
25
|
import okio.BufferedSource;
|
|
26
26
|
import okio.Okio;
|
|
27
|
-
import org.json.JSONException;
|
|
28
27
|
import org.json.JSONObject;
|
|
29
28
|
import org.json.JSONTokener;
|
|
30
29
|
|
|
@@ -33,7 +32,8 @@ class DownloadTask implements Runnable {
|
|
|
33
32
|
private static final String BUNDLE_DELTA_ENTRY =
|
|
34
33
|
Texts.reveal("3319f0d4b6c56a502c3b10fcdca0de7d4b3307e9");
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
// Resume sidecars are a few hundred bytes of JSON.
|
|
36
|
+
private static final long MAX_SIDECAR_BYTES = 64 * 1024;
|
|
37
37
|
// When the server does not report Content-Length we cannot key progress
|
|
38
38
|
// events on percentage change, so throttle by bytes to avoid flooding the
|
|
39
39
|
// bridge (e.g. a 20MB chunked download would otherwise emit ~5000 events).
|
|
@@ -42,17 +42,13 @@ class DownloadTask implements Runnable {
|
|
|
42
42
|
// slow-dripping connection could occupy the single-threaded download
|
|
43
43
|
// executor indefinitely and starve queued tasks. The call timeout is a
|
|
44
44
|
// generous upper bound sized for large full-package downloads.
|
|
45
|
-
private static final OkHttpClient HTTP_CLIENT =
|
|
45
|
+
private static final OkHttpClient HTTP_CLIENT = HttpClients.base().newBuilder()
|
|
46
46
|
.connectTimeout(15, java.util.concurrent.TimeUnit.SECONDS)
|
|
47
47
|
.readTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
|
|
48
48
|
.writeTimeout(30, java.util.concurrent.TimeUnit.SECONDS)
|
|
49
49
|
.callTimeout(10, java.util.concurrent.TimeUnit.MINUTES)
|
|
50
50
|
.build();
|
|
51
51
|
|
|
52
|
-
static {
|
|
53
|
-
NativeCore.ensureLoaded();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
52
|
// Two-phase install (cpp/patch_core/install_record.h): all unpack/patch
|
|
57
53
|
// work happens in <hash>.staging; the final <hash> directory only ever
|
|
58
54
|
// appears through an atomic rename after the completion record was
|
|
@@ -62,36 +58,32 @@ class DownloadTask implements Runnable {
|
|
|
62
58
|
return InstallRecord.stagingDirectoryFor(params.unzipDirectory);
|
|
63
59
|
}
|
|
64
60
|
|
|
65
|
-
// SHA-256 of the downloaded archive,
|
|
61
|
+
// SHA-256 of the downloaded archive, recorded in the install record.
|
|
66
62
|
private String artifactSha256 = "";
|
|
67
|
-
|
|
63
|
+
// The archive digest computed while its bytes streamed to disk (a resumed
|
|
64
|
+
// transfer first hashes the prefix already on disk); null when this task
|
|
65
|
+
// did not stream the whole file itself and it must be hashed from disk.
|
|
66
|
+
private String streamedArchiveSha256;
|
|
67
|
+
// Digest of the bundle computed while a full package was extracted;
|
|
68
|
+
// null when the bundle was produced some other way (patch tasks).
|
|
69
|
+
private String stagedBundleSha256;
|
|
70
|
+
|
|
71
|
+
/** What a staged patch archive declared, as the native core consumes it. */
|
|
68
72
|
private static final class PatchArchiveContents {
|
|
69
|
-
final
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
final
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
String hbcTransformMetaFor(String patchEntryName) {
|
|
78
|
-
if (hbcTransform == null) {
|
|
79
|
-
return "";
|
|
80
|
-
}
|
|
81
|
-
JSONObject meta = hbcTransform.optJSONObject(patchEntryName);
|
|
82
|
-
return meta != null ? meta.toString() : "";
|
|
73
|
+
final String[] entryNames;
|
|
74
|
+
// The raw __diff.json bytes (UTF-8): parsed only by the native core
|
|
75
|
+
// (cpp/patch_core/patch_manifest.h), never here.
|
|
76
|
+
final byte[] manifestJson;
|
|
77
|
+
|
|
78
|
+
PatchArchiveContents(ArrayList<String> entryNames, byte[] manifestJson) {
|
|
79
|
+
this.entryNames = entryNames.toArray(new String[0]);
|
|
80
|
+
this.manifestJson = manifestJson;
|
|
83
81
|
}
|
|
84
|
-
// Maps a copy source path ("from") to the CRC32 of the file content,
|
|
85
|
-
// when provided by the manifest ("copiesCrc"). Lets the resource
|
|
86
|
-
// copier locate the file by content if the path is not present on
|
|
87
|
-
// device (APK baseline -> AAB install path shortening).
|
|
88
|
-
final HashMap<String, Long> copyCrcs = new HashMap<String, Long>();
|
|
89
82
|
}
|
|
90
83
|
|
|
91
84
|
private final Context context;
|
|
92
85
|
private final DownloadTaskParams params;
|
|
93
86
|
private final Handler mainHandler = new Handler(Looper.getMainLooper());
|
|
94
|
-
private final byte[] buffer = new byte[DOWNLOAD_CHUNK_SIZE];
|
|
95
87
|
private final BundledResourceCopier bundledResourceCopier;
|
|
96
88
|
private String hash;
|
|
97
89
|
// Set once downloadFile() returns: failures after this point in a patch
|
|
@@ -108,15 +100,12 @@ class DownloadTask implements Runnable {
|
|
|
108
100
|
// Cross-platform progress contract: unknown length is reported as
|
|
109
101
|
// total=0, never a raw -1 (OkHttp's contentLength for chunked/gzip).
|
|
110
102
|
final long normalizedTotal = total > 0 ? total : 0;
|
|
111
|
-
mainHandler.post(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
progress.putString("hash", hash);
|
|
118
|
-
UpdateEventEmitter.sendEvent("RCTPushyDownloadProgress", progress);
|
|
119
|
-
}
|
|
103
|
+
mainHandler.post(() -> {
|
|
104
|
+
WritableMap progress = Arguments.createMap();
|
|
105
|
+
progress.putDouble("received", received);
|
|
106
|
+
progress.putDouble("total", normalizedTotal);
|
|
107
|
+
progress.putString("hash", hash);
|
|
108
|
+
UpdateEventEmitter.sendEvent("RCTPushyDownloadProgress", progress);
|
|
120
109
|
});
|
|
121
110
|
}
|
|
122
111
|
|
|
@@ -130,7 +119,7 @@ class DownloadTask implements Runnable {
|
|
|
130
119
|
|
|
131
120
|
static void deleteResumeSidecar(File archive) {
|
|
132
121
|
File sidecar = resumeSidecarFile(archive);
|
|
133
|
-
if (sidecar.exists() && !sidecar.delete() &&
|
|
122
|
+
if (sidecar.exists() && !sidecar.delete() && BuildConfig.DEBUG) {
|
|
134
123
|
Log.w(Texts.LOG_TAG, "Failed to delete resume sidecar " + sidecar);
|
|
135
124
|
}
|
|
136
125
|
}
|
|
@@ -140,9 +129,8 @@ class DownloadTask implements Runnable {
|
|
|
140
129
|
return null;
|
|
141
130
|
}
|
|
142
131
|
try {
|
|
143
|
-
byte[] bytes = readBytes(new java.io.FileInputStream(sidecar));
|
|
144
132
|
JSONObject meta = (JSONObject) new JSONTokener(
|
|
145
|
-
|
|
133
|
+
UpdateFileUtils.readSmallUtf8(sidecar, MAX_SIDECAR_BYTES)).nextValue();
|
|
146
134
|
if (url.equals(meta.optString("url"))) {
|
|
147
135
|
return meta;
|
|
148
136
|
}
|
|
@@ -187,6 +175,7 @@ class DownloadTask implements Runnable {
|
|
|
187
175
|
|
|
188
176
|
private void downloadFile() throws IOException {
|
|
189
177
|
this.hash = params.hash;
|
|
178
|
+
streamedArchiveSha256 = null;
|
|
190
179
|
String url = params.url;
|
|
191
180
|
File writePath = params.targetFile;
|
|
192
181
|
File sidecar = resumeSidecarFile(writePath);
|
|
@@ -212,19 +201,13 @@ class DownloadTask implements Runnable {
|
|
|
212
201
|
}
|
|
213
202
|
}
|
|
214
203
|
if (resumeOffset == 0) {
|
|
215
|
-
|
|
216
|
-
throw new IOException("Failed to replace existing file: " + writePath);
|
|
217
|
-
}
|
|
218
|
-
deleteResumeSidecar(writePath);
|
|
204
|
+
discardPartial(writePath);
|
|
219
205
|
}
|
|
220
206
|
|
|
221
207
|
if (!transferArchive(url, writePath, sidecar, resumeMeta, resumeOffset)) {
|
|
222
208
|
// The server rejected the range for a partial that no longer
|
|
223
209
|
// matches (416): drop it and retry once from zero.
|
|
224
|
-
|
|
225
|
-
throw new IOException("Failed to replace existing file: " + writePath);
|
|
226
|
-
}
|
|
227
|
-
deleteResumeSidecar(writePath);
|
|
210
|
+
discardPartial(writePath);
|
|
228
211
|
if (!transferArchive(url, writePath, sidecar, null, 0)) {
|
|
229
212
|
throw new IOException("Server rejected the download range for " + url);
|
|
230
213
|
}
|
|
@@ -232,6 +215,14 @@ class DownloadTask implements Runnable {
|
|
|
232
215
|
downloadPhaseCompleted = true;
|
|
233
216
|
}
|
|
234
217
|
|
|
218
|
+
/** Drops a partial download and its sidecar; throws when the bytes stay. */
|
|
219
|
+
private static void discardPartial(File archive) throws IOException {
|
|
220
|
+
if (archive.exists() && !archive.delete()) {
|
|
221
|
+
throw new IOException("Failed to replace existing file: " + archive);
|
|
222
|
+
}
|
|
223
|
+
deleteResumeSidecar(archive);
|
|
224
|
+
}
|
|
225
|
+
|
|
235
226
|
/**
|
|
236
227
|
* One HTTP transfer, appending from resumeOffset when the server honours
|
|
237
228
|
* the range. Returns false only for the retryable stale-partial case
|
|
@@ -263,27 +254,13 @@ class DownloadTask implements Runnable {
|
|
|
263
254
|
}
|
|
264
255
|
}
|
|
265
256
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
long remainingNanos = params.deadlineNanos - System.nanoTime();
|
|
269
|
-
if (remainingNanos <= 0) {
|
|
270
|
-
throw new IOException("Download deadline expired before start");
|
|
271
|
-
}
|
|
272
|
-
long remainingMillis = Math.max(
|
|
273
|
-
1L,
|
|
274
|
-
java.util.concurrent.TimeUnit.NANOSECONDS.toMillis(remainingNanos)
|
|
275
|
-
);
|
|
276
|
-
requestClient = HTTP_CLIENT.newBuilder()
|
|
277
|
-
.callTimeout(remainingMillis, java.util.concurrent.TimeUnit.MILLISECONDS)
|
|
278
|
-
.build();
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
Call call = requestClient.newCall(builder.build());
|
|
257
|
+
Call call = HttpClients.withDeadline(HTTP_CLIENT, params.deadlineNanos)
|
|
258
|
+
.newCall(builder.build());
|
|
282
259
|
// Registered so an orchestrated phase timeout can cancel it
|
|
283
260
|
// (CODE_AUDIT 2.12); cleared once the transfer is over.
|
|
284
261
|
params.attachCall(call);
|
|
285
262
|
try (Response response = call.execute()) {
|
|
286
|
-
rejectProtocolDowngrade(url, response);
|
|
263
|
+
HttpClients.rejectProtocolDowngrade(url, response);
|
|
287
264
|
if (response.code() == 416) {
|
|
288
265
|
long total = resumeMeta == null ? 0 : resumeMeta.optLong("total", 0);
|
|
289
266
|
if (total > 0 && writePath.length() == total) {
|
|
@@ -340,9 +317,7 @@ class DownloadTask implements Runnable {
|
|
|
340
317
|
// Destroy the old bytes before the sidecar can vouch for
|
|
341
318
|
// them with the new validators (a crash between the two
|
|
342
319
|
// writes must never leave a sidecar describing stale bytes).
|
|
343
|
-
|
|
344
|
-
throw new IOException("Failed to replace existing file: " + writePath);
|
|
345
|
-
}
|
|
320
|
+
discardPartial(writePath);
|
|
346
321
|
}
|
|
347
322
|
if (encodedBody) {
|
|
348
323
|
// No resume across an encoded transfer.
|
|
@@ -358,25 +333,34 @@ class DownloadTask implements Runnable {
|
|
|
358
333
|
long lastPostedBytes = baseOffset;
|
|
359
334
|
// Unknown length: the response-time check could only reserve the
|
|
360
335
|
// margin, so the disk is probed before any write that would run
|
|
361
|
-
// past the bytes reserved so far
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
// than the margin). A throw keeps the partial for a later attempt.
|
|
336
|
+
// past the bytes reserved so far (the shared probe plan,
|
|
337
|
+
// ArchiveLimits.planFreeSpaceProbe) — no write can ever eat into
|
|
338
|
+
// the margin. A throw keeps the partial for a later attempt.
|
|
365
339
|
long freeSpaceReservedUntil = 0;
|
|
366
340
|
|
|
341
|
+
// The archive digest is computed as the bytes reach the file, so
|
|
342
|
+
// the install does not re-read the whole archive afterwards.
|
|
343
|
+
MessageDigest digest = UpdateFileUtils.newSha256();
|
|
344
|
+
if (append) {
|
|
345
|
+
UpdateFileUtils.updateDigest(digest, writePath);
|
|
346
|
+
}
|
|
367
347
|
try (
|
|
368
348
|
BufferedSource source = body.source();
|
|
369
|
-
BufferedSink sink = Okio.buffer(
|
|
370
|
-
|
|
349
|
+
BufferedSink sink = Okio.buffer(Okio.sink(new DigestOutputStream(
|
|
350
|
+
new FileOutputStream(writePath, append), digest)))
|
|
371
351
|
) {
|
|
372
|
-
while ((bytesRead = source.read(sink.buffer(),
|
|
352
|
+
while ((bytesRead = source.read(sink.buffer(), UpdateFileUtils.BUFFER_SIZE)) != -1) {
|
|
353
|
+
// A known length was reserved up front: no per-chunk plan
|
|
354
|
+
// (and no JNI round trip on this hot loop).
|
|
355
|
+
long[] probe = totalAll > 0 ? null : ArchiveLimits.planFreeSpaceProbe(
|
|
356
|
+
totalAll, received, bytesRead, freeSpaceReservedUntil);
|
|
373
357
|
received += bytesRead;
|
|
374
|
-
if (
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
358
|
+
if (probe != null) {
|
|
359
|
+
freeSpaceReservedUntil = probe[1];
|
|
360
|
+
}
|
|
361
|
+
if (probe != null && probe[0] > 0) {
|
|
378
362
|
try {
|
|
379
|
-
ArchiveLimits.ensureFreeSpace(writePath,
|
|
363
|
+
ArchiveLimits.ensureFreeSpace(writePath, probe[0]);
|
|
380
364
|
} catch (IOException e) {
|
|
381
365
|
// The chunk is still only buffered; closing the
|
|
382
366
|
// sink would flush it onto the disk that just
|
|
@@ -385,7 +369,9 @@ class DownloadTask implements Runnable {
|
|
|
385
369
|
throw e;
|
|
386
370
|
}
|
|
387
371
|
}
|
|
388
|
-
|
|
372
|
+
// Write whole segments only: a 64KB read no longer costs
|
|
373
|
+
// a write() syscall per partially filled segment.
|
|
374
|
+
sink.emitCompleteSegments();
|
|
389
375
|
|
|
390
376
|
long overall = baseOffset + received;
|
|
391
377
|
if (overall > ArchiveLimits.MAX_ARCHIVE_BYTES) {
|
|
@@ -415,6 +401,7 @@ class DownloadTask implements Runnable {
|
|
|
415
401
|
throw new IOException("Download incomplete: expected " + totalAll
|
|
416
402
|
+ " bytes, got " + writePath.length());
|
|
417
403
|
}
|
|
404
|
+
streamedArchiveSha256 = UpdateFileUtils.toHex(digest.digest());
|
|
418
405
|
// Final progress event, skipped when the loop already posted this
|
|
419
406
|
// exact value (known length reaching 100% posts it in-loop).
|
|
420
407
|
if (baseOffset + received != lastPostedBytes) {
|
|
@@ -426,136 +413,51 @@ class DownloadTask implements Runnable {
|
|
|
426
413
|
return true;
|
|
427
414
|
}
|
|
428
415
|
|
|
429
|
-
private byte[] readBytes(InputStream input) throws IOException {
|
|
430
|
-
return readBytes(input, Long.MAX_VALUE);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
private byte[] readBytes(InputStream input, long maxBytes) throws IOException {
|
|
434
|
-
try (
|
|
435
|
-
InputStream in = input;
|
|
436
|
-
ByteArrayOutputStream out = new ByteArrayOutputStream()
|
|
437
|
-
) {
|
|
438
|
-
int count;
|
|
439
|
-
long total = 0;
|
|
440
|
-
while ((count = in.read(buffer)) != -1) {
|
|
441
|
-
total += count;
|
|
442
|
-
if (total > maxBytes) {
|
|
443
|
-
throw new IOException("content exceeds " + maxBytes + " bytes");
|
|
444
|
-
}
|
|
445
|
-
out.write(buffer, 0, count);
|
|
446
|
-
}
|
|
447
|
-
return out.toByteArray();
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
private void appendManifestEntries(
|
|
452
|
-
JSONObject manifest,
|
|
453
|
-
ArrayList<String> copyFroms,
|
|
454
|
-
ArrayList<String> copyTos,
|
|
455
|
-
ArrayList<String> deletes,
|
|
456
|
-
HashMap<String, Long> copyCrcs
|
|
457
|
-
) throws JSONException {
|
|
458
|
-
JSONObject copiesCrc = manifest.optJSONObject("copiesCrc");
|
|
459
|
-
|
|
460
|
-
JSONObject copies = manifest.optJSONObject("copies");
|
|
461
|
-
if (copies != null) {
|
|
462
|
-
Iterator<?> keys = copies.keys();
|
|
463
|
-
while (keys.hasNext()) {
|
|
464
|
-
String to = (String) keys.next();
|
|
465
|
-
String from = copies.getString(to);
|
|
466
|
-
if (from.isEmpty()) {
|
|
467
|
-
from = to;
|
|
468
|
-
}
|
|
469
|
-
copyFroms.add(from);
|
|
470
|
-
copyTos.add(to);
|
|
471
|
-
if (copiesCrc != null && copyCrcs != null && copiesCrc.has(to)) {
|
|
472
|
-
// Same content => same crc, so grouping multiple "to" under
|
|
473
|
-
// one "from" stays consistent.
|
|
474
|
-
copyCrcs.put(from, copiesCrc.getLong(to));
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
JSONObject deleteMap = manifest.optJSONObject("deletes");
|
|
480
|
-
if (deleteMap != null) {
|
|
481
|
-
Iterator<?> deleteKeys = deleteMap.keys();
|
|
482
|
-
while (deleteKeys.hasNext()) {
|
|
483
|
-
deletes.add((String) deleteKeys.next());
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
416
|
private void copyBundledAssetToFile(String assetName, File destination) throws IOException {
|
|
489
417
|
try (InputStream in = context.getAssets().open(assetName)) {
|
|
490
418
|
UpdateFileUtils.copyInputStreamToFile(in, destination);
|
|
491
419
|
}
|
|
492
420
|
}
|
|
493
421
|
|
|
494
|
-
private HashMap<String, ArrayList<File>> buildCopyList(
|
|
495
|
-
File unzipDirectory,
|
|
496
|
-
CopyGroupResult[] groups
|
|
497
|
-
) throws IOException {
|
|
498
|
-
HashMap<String, ArrayList<File>> copyList = new HashMap<String, ArrayList<File>>();
|
|
499
|
-
if (groups == null) {
|
|
500
|
-
return copyList;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
String rootPath = unzipDirectory.getCanonicalPath() + File.separator;
|
|
504
|
-
for (CopyGroupResult group : groups) {
|
|
505
|
-
ArrayList<File> targets = new ArrayList<File>();
|
|
506
|
-
if (group.toPaths != null) {
|
|
507
|
-
for (String to : group.toPaths) {
|
|
508
|
-
File toFile = new File(unzipDirectory, to);
|
|
509
|
-
String canonicalPath = toFile.getCanonicalPath();
|
|
510
|
-
if (!canonicalPath.startsWith(rootPath)) {
|
|
511
|
-
throw new SecurityException("Illegal name: " + to);
|
|
512
|
-
}
|
|
513
|
-
targets.add(toFile);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
copyList.put(group.from, targets);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
return copyList;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
422
|
private PatchArchiveContents extractPatchArchive(File archiveFile, File unzipDirectory)
|
|
523
|
-
throws IOException
|
|
423
|
+
throws IOException {
|
|
524
424
|
UpdateFileUtils.removeDirectory(unzipDirectory);
|
|
525
425
|
UpdateFileUtils.ensureDirectory(unzipDirectory);
|
|
526
426
|
|
|
527
|
-
|
|
427
|
+
ArrayList<String> entryNames = new ArrayList<String>();
|
|
428
|
+
byte[] manifestJson = new byte[0];
|
|
528
429
|
try (SafeZipFile zipFile = new SafeZipFile(archiveFile)) {
|
|
529
430
|
ensureArchiveWithinLimits(archiveFile, zipFile, unzipDirectory);
|
|
530
431
|
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
|
531
432
|
while (entries.hasMoreElements()) {
|
|
532
433
|
ZipEntry entry = entries.nextElement();
|
|
533
434
|
String name = entry.getName();
|
|
534
|
-
|
|
435
|
+
entryNames.add(name);
|
|
535
436
|
|
|
536
437
|
if (name.equals("__diff.json")) {
|
|
537
438
|
if (entry.getSize() > ArchiveLimits.MAX_MANIFEST_BYTES) {
|
|
538
439
|
throw new IOException("delta manifest too large: " + entry.getSize());
|
|
539
440
|
}
|
|
540
|
-
|
|
441
|
+
manifestJson = UpdateFileUtils.readBytes(
|
|
541
442
|
zipFile.getInputStream(entry), ArchiveLimits.MAX_MANIFEST_BYTES);
|
|
542
|
-
String json = new String(bytes, StandardCharsets.UTF_8);
|
|
543
|
-
JSONObject manifest = (JSONObject) new JSONTokener(json).nextValue();
|
|
544
|
-
appendManifestEntries(
|
|
545
|
-
manifest,
|
|
546
|
-
contents.copyFroms,
|
|
547
|
-
contents.copyTos,
|
|
548
|
-
contents.deletes,
|
|
549
|
-
contents.copyCrcs
|
|
550
|
-
);
|
|
551
|
-
contents.hbcTransform = manifest.optJSONObject("hbcTransform");
|
|
552
443
|
continue;
|
|
553
444
|
}
|
|
554
445
|
|
|
555
446
|
zipFile.unzipToPath(entry, unzipDirectory);
|
|
556
447
|
}
|
|
557
448
|
}
|
|
558
|
-
return
|
|
449
|
+
return new PatchArchiveContents(entryNames, manifestJson);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Shared first half of the two patch tasks: download the archive and
|
|
454
|
+
* unpack it into the staging directory. The native applyArchiveDelta
|
|
455
|
+
* validates what it declared.
|
|
456
|
+
*/
|
|
457
|
+
private PatchArchiveContents stagePatchArchive() throws IOException {
|
|
458
|
+
downloadFile();
|
|
459
|
+
artifactSha256 = archiveSha256();
|
|
460
|
+
return extractPatchArchive(params.targetFile, stagingDirectory());
|
|
559
461
|
}
|
|
560
462
|
|
|
561
463
|
private void doFullPatch() throws IOException {
|
|
@@ -564,25 +466,44 @@ class DownloadTask implements Runnable {
|
|
|
564
466
|
File work = stagingDirectory();
|
|
565
467
|
UpdateFileUtils.removeDirectory(work);
|
|
566
468
|
UpdateFileUtils.ensureDirectory(work);
|
|
567
|
-
artifactSha256 =
|
|
568
|
-
|
|
469
|
+
artifactSha256 = archiveSha256();
|
|
470
|
+
|
|
471
|
+
// The bundle's digest (for the install record) is taken from the
|
|
472
|
+
// bytes as they are extracted, instead of re-reading the bundle right
|
|
473
|
+
// after. Any entry resolving to the bundle path counts; the last write
|
|
474
|
+
// wins on disk and in the digest alike.
|
|
475
|
+
File bundleTarget = InstallRecord.bundleFile(work).getCanonicalFile();
|
|
476
|
+
stagedBundleSha256 = null;
|
|
569
477
|
try (SafeZipFile zipFile = new SafeZipFile(params.targetFile)) {
|
|
570
478
|
ensureArchiveWithinLimits(params.targetFile, zipFile, work);
|
|
571
479
|
Enumeration<? extends ZipEntry> entries = zipFile.entries();
|
|
572
480
|
while (entries.hasMoreElements()) {
|
|
573
|
-
|
|
481
|
+
ZipEntry entry = entries.nextElement();
|
|
482
|
+
if (!entry.isDirectory()
|
|
483
|
+
&& new File(work, entry.getName()).getCanonicalFile().equals(bundleTarget)) {
|
|
484
|
+
MessageDigest digest = UpdateFileUtils.newSha256();
|
|
485
|
+
zipFile.unzipToPath(entry, work, digest);
|
|
486
|
+
stagedBundleSha256 = UpdateFileUtils.toHex(digest.digest());
|
|
487
|
+
} else {
|
|
488
|
+
zipFile.unzipToPath(entry, work);
|
|
489
|
+
}
|
|
574
490
|
}
|
|
575
491
|
}
|
|
576
492
|
|
|
577
493
|
deleteConsumedArchive();
|
|
578
494
|
}
|
|
579
495
|
|
|
496
|
+
private String archiveSha256() throws IOException {
|
|
497
|
+
return streamedArchiveSha256 != null
|
|
498
|
+
? streamedArchiveSha256 : UpdateFileUtils.sha256Hex(params.targetFile);
|
|
499
|
+
}
|
|
500
|
+
|
|
580
501
|
/**
|
|
581
502
|
* Last step of a successful patch task: the completion record (with the
|
|
582
503
|
* final bundle's digest) goes into the staging directory, which is then
|
|
583
504
|
* renamed over the version directory in one atomic step.
|
|
584
505
|
*/
|
|
585
|
-
private void promoteStaging() throws IOException
|
|
506
|
+
private void promoteStaging() throws IOException {
|
|
586
507
|
File work = stagingDirectory();
|
|
587
508
|
if (targetsRunningVersion()) {
|
|
588
509
|
// The process switched to this very version while the task ran
|
|
@@ -592,25 +513,11 @@ class DownloadTask implements Runnable {
|
|
|
592
513
|
UpdateFileUtils.removeDirectory(work);
|
|
593
514
|
return;
|
|
594
515
|
}
|
|
595
|
-
|
|
596
|
-
if (!bundle.isFile()) {
|
|
597
|
-
throw new IOException("bundle missing after install: " + bundle);
|
|
598
|
-
}
|
|
599
|
-
String bundleSha256 = UpdateFileUtils.sha256Hex(bundle);
|
|
600
|
-
InstallRecord.write(
|
|
601
|
-
work, InstallRecord.build(params.hash, bundleSha256, artifactSha256));
|
|
516
|
+
InstallRecord.seal(work, params.hash, artifactSha256, stagedBundleSha256);
|
|
602
517
|
if (params.unzipDirectory.exists()) {
|
|
603
518
|
UpdateFileUtils.removeDirectory(params.unzipDirectory);
|
|
604
519
|
}
|
|
605
|
-
|
|
606
|
-
throw new IOException("failed to promote staging directory to " + params.unzipDirectory);
|
|
607
|
-
}
|
|
608
|
-
// The rename rewrote the versions root's entries; the record's own
|
|
609
|
-
// directory was synced by InstallRecord.write, the root was not.
|
|
610
|
-
File versionsRoot = params.unzipDirectory.getParentFile();
|
|
611
|
-
if (versionsRoot != null) {
|
|
612
|
-
InstallRecord.syncDirectory(versionsRoot);
|
|
613
|
-
}
|
|
520
|
+
InstallRecord.promote(work, params.unzipDirectory);
|
|
614
521
|
}
|
|
615
522
|
|
|
616
523
|
/**
|
|
@@ -626,8 +533,7 @@ class DownloadTask implements Runnable {
|
|
|
626
533
|
if (archiveBytes > ArchiveLimits.MAX_ARCHIVE_BYTES) {
|
|
627
534
|
throw new IOException("archive too large: " + archiveBytes + " bytes");
|
|
628
535
|
}
|
|
629
|
-
|
|
630
|
-
ArchiveLimits.ensureFreeSpace(unzipDirectory, inspection.totalUncompressed);
|
|
536
|
+
ArchiveLimits.ensureFreeSpace(unzipDirectory, zipFile.inspect(archiveBytes));
|
|
631
537
|
}
|
|
632
538
|
|
|
633
539
|
// The archive and its resume sidecar live and die together: once the
|
|
@@ -640,96 +546,78 @@ class DownloadTask implements Runnable {
|
|
|
640
546
|
deleteResumeSidecar(params.targetFile);
|
|
641
547
|
}
|
|
642
548
|
|
|
643
|
-
private void doPatchFromApk() throws IOException
|
|
644
|
-
|
|
549
|
+
private void doPatchFromApk() throws IOException {
|
|
550
|
+
PatchArchiveContents contents = stagePatchArchive();
|
|
645
551
|
File work = stagingDirectory();
|
|
646
|
-
artifactSha256 = UpdateFileUtils.sha256Hex(params.targetFile);
|
|
647
|
-
PatchArchiveContents contents = extractPatchArchive(params.targetFile, work);
|
|
648
|
-
|
|
649
|
-
buildArchivePlan(
|
|
650
|
-
DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK,
|
|
651
|
-
contents.entryNames.toArray(new String[0]),
|
|
652
|
-
contents.copyFroms.toArray(new String[0]),
|
|
653
|
-
contents.copyTos.toArray(new String[0]),
|
|
654
|
-
contents.deletes.toArray(new String[0])
|
|
655
|
-
);
|
|
656
|
-
|
|
657
|
-
HashMap<String, ArrayList<File>> copyList = buildCopyList(
|
|
658
|
-
work,
|
|
659
|
-
buildCopyGroups(
|
|
660
|
-
contents.copyFroms.toArray(new String[0]),
|
|
661
|
-
contents.copyTos.toArray(new String[0])
|
|
662
|
-
)
|
|
663
|
-
);
|
|
664
552
|
|
|
665
553
|
File originBundleFile = new File(work, ".origin.bundle");
|
|
666
554
|
copyBundledAssetToFile("index.android.bundle", originBundleFile);
|
|
555
|
+
CopyGroupResult[] copies;
|
|
667
556
|
try {
|
|
668
|
-
|
|
557
|
+
// The APK's resources are zip entries, not files: the core
|
|
558
|
+
// rebuilds the bundle and hands the manifest copies back.
|
|
559
|
+
copies = applyArchiveDelta(
|
|
560
|
+
DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK,
|
|
561
|
+
contents.entryNames,
|
|
562
|
+
contents.manifestJson,
|
|
669
563
|
work.getAbsolutePath(),
|
|
670
564
|
work.getAbsolutePath(),
|
|
671
565
|
originBundleFile.getAbsolutePath(),
|
|
672
566
|
new File(work, BUNDLE_DELTA_ENTRY).getAbsolutePath(),
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
false,
|
|
676
|
-
new String[0],
|
|
677
|
-
new String[0],
|
|
678
|
-
new String[0],
|
|
679
|
-
contents.hbcTransformMetaFor(BUNDLE_DELTA_ENTRY)
|
|
567
|
+
InstallRecord.bundleFile(work).getAbsolutePath(),
|
|
568
|
+
true
|
|
680
569
|
);
|
|
681
570
|
} finally {
|
|
682
571
|
originBundleFile.delete();
|
|
683
572
|
}
|
|
684
573
|
|
|
685
|
-
bundledResourceCopier.copyFromResource(
|
|
574
|
+
bundledResourceCopier.copyFromResource(work, copies);
|
|
686
575
|
deleteConsumedArchive();
|
|
687
576
|
}
|
|
688
577
|
|
|
689
|
-
private void doPatchFromPpk() throws IOException
|
|
690
|
-
|
|
578
|
+
private void doPatchFromPpk() throws IOException {
|
|
579
|
+
PatchArchiveContents contents = stagePatchArchive();
|
|
691
580
|
File work = stagingDirectory();
|
|
692
|
-
|
|
693
|
-
PatchArchiveContents contents = extractPatchArchive(params.targetFile, work);
|
|
694
|
-
|
|
695
|
-
ArchivePatchPlanResult plan = buildArchivePlan(
|
|
581
|
+
applyArchiveDelta(
|
|
696
582
|
DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK,
|
|
697
|
-
contents.entryNames
|
|
698
|
-
contents.
|
|
699
|
-
contents.copyTos.toArray(new String[0]),
|
|
700
|
-
contents.deletes.toArray(new String[0])
|
|
701
|
-
);
|
|
702
|
-
|
|
703
|
-
applyDeltaFromSource(
|
|
583
|
+
contents.entryNames,
|
|
584
|
+
contents.manifestJson,
|
|
704
585
|
params.originDirectory.getAbsolutePath(),
|
|
705
586
|
work.getAbsolutePath(),
|
|
706
|
-
|
|
587
|
+
InstallRecord.bundleFile(params.originDirectory).getAbsolutePath(),
|
|
707
588
|
new File(work, BUNDLE_DELTA_ENTRY).getAbsolutePath(),
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
plan.enableMerge,
|
|
711
|
-
contents.copyFroms.toArray(new String[0]),
|
|
712
|
-
contents.copyTos.toArray(new String[0]),
|
|
713
|
-
contents.deletes.toArray(new String[0]),
|
|
714
|
-
contents.hbcTransformMetaFor(BUNDLE_DELTA_ENTRY)
|
|
589
|
+
InstallRecord.bundleFile(work).getAbsolutePath(),
|
|
590
|
+
false
|
|
715
591
|
);
|
|
716
592
|
deleteConsumedArchive();
|
|
717
593
|
}
|
|
718
594
|
|
|
595
|
+
// Dot-named, so the selection under the versions root never picks it.
|
|
596
|
+
static final String TRASH_DIR_NAME = ".trash";
|
|
597
|
+
|
|
719
598
|
private void doCleanUp() {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
599
|
+
final File trash = new File(params.unzipDirectory, TRASH_DIR_NAME);
|
|
600
|
+
// Phase 1, under the version-files lock: only renames doomed entries
|
|
601
|
+
// into the trash, so activation is never blocked behind a recursive
|
|
602
|
+
// delete of several version directories.
|
|
603
|
+
UpdateContext.getInstance(context).runCleanupWithLatestState(keepNames ->
|
|
604
|
+
cleanupOldEntries(
|
|
605
|
+
params.unzipDirectory.getAbsolutePath(),
|
|
606
|
+
keepNames,
|
|
607
|
+
params.maxAgeDays,
|
|
608
|
+
trash.getAbsolutePath()
|
|
609
|
+
));
|
|
610
|
+
// Phase 2, lock released: nothing in the trash is reachable as a
|
|
611
|
+
// version any more. Also purges leftovers of an interrupted run.
|
|
612
|
+
// Best effort: phase 1 already succeeded, and a leftover the native
|
|
613
|
+
// delete cannot remove must not fail every later cleanup task.
|
|
614
|
+
if (trash.isDirectory()) {
|
|
615
|
+
try {
|
|
616
|
+
cleanupOldEntries(trash.getAbsolutePath(), new String[0], 0, null);
|
|
617
|
+
} catch (RuntimeException e) {
|
|
618
|
+
Log.w(Texts.LOG_TAG, "Failed to purge cleanup trash", e);
|
|
731
619
|
}
|
|
732
|
-
|
|
620
|
+
}
|
|
733
621
|
}
|
|
734
622
|
|
|
735
623
|
private void cleanUpAfterFailure(int taskType) {
|
|
@@ -753,37 +641,13 @@ class DownloadTask implements Runnable {
|
|
|
753
641
|
}
|
|
754
642
|
break;
|
|
755
643
|
case DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD:
|
|
756
|
-
|
|
757
|
-
params.targetFile.exists()
|
|
758
|
-
&& !params.targetFile.delete()
|
|
759
|
-
&& UpdateContext.DEBUG
|
|
760
|
-
) {
|
|
761
|
-
Log.w(Texts.LOG_TAG, "Failed to clean partial download " + params.targetFile);
|
|
762
|
-
}
|
|
763
|
-
deleteResumeSidecar(params.targetFile);
|
|
644
|
+
deleteConsumedArchive();
|
|
764
645
|
break;
|
|
765
646
|
default:
|
|
766
647
|
break;
|
|
767
648
|
}
|
|
768
649
|
}
|
|
769
650
|
|
|
770
|
-
/**
|
|
771
|
-
* An https artifact URL must stay on https through every redirect: the
|
|
772
|
-
* package is the supply-chain boundary and TLS is what authenticates
|
|
773
|
-
* it. OkHttp follows cross-scheme redirects by default, so the final
|
|
774
|
-
* request is checked here (the redirected bytes are discarded).
|
|
775
|
-
*/
|
|
776
|
-
static void rejectProtocolDowngrade(String requestedUrl, Response response)
|
|
777
|
-
throws IOException {
|
|
778
|
-
if (requestedUrl != null
|
|
779
|
-
&& requestedUrl.regionMatches(true, 0, "https:", 0, 6)
|
|
780
|
-
&& !response.request().url().isHttps()) {
|
|
781
|
-
throw new IOException(
|
|
782
|
-
"https download redirected to plaintext http: "
|
|
783
|
-
+ response.request().url());
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
651
|
private boolean isPatchTask(int taskType) {
|
|
788
652
|
return taskType == DownloadTaskParams.TASK_TYPE_PATCH_FULL
|
|
789
653
|
|| taskType == DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK
|
|
@@ -793,8 +657,7 @@ class DownloadTask implements Runnable {
|
|
|
793
657
|
private boolean hasCompletedDeltaDirectory() {
|
|
794
658
|
return params.unzipDirectory != null
|
|
795
659
|
&& params.hash != null
|
|
796
|
-
&&
|
|
797
|
-
&& InstallRecord.isComplete(params.unzipDirectory, params.hash);
|
|
660
|
+
&& InstallRecord.isInstalled(params.unzipDirectory, params.hash);
|
|
798
661
|
}
|
|
799
662
|
|
|
800
663
|
// True when this task targets the version the process is running from.
|
|
@@ -813,18 +676,12 @@ class DownloadTask implements Runnable {
|
|
|
813
676
|
* failure rather than overwritten.
|
|
814
677
|
*/
|
|
815
678
|
private void ensureNotReinstallingRunningVersion() throws IOException {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
} catch (IOException e) {
|
|
822
|
-
throw new FileOperationException("Running version " + params.hash
|
|
823
|
-
+ " cannot be reinstalled in place: " + e.getMessage());
|
|
824
|
-
}
|
|
679
|
+
try {
|
|
680
|
+
InstallRecord.verifyInstalled(params.unzipDirectory, params.hash, false);
|
|
681
|
+
} catch (IOException e) {
|
|
682
|
+
throw new FileOperationException("Running version " + params.hash
|
|
683
|
+
+ " cannot be reinstalled in place: " + e.getMessage());
|
|
825
684
|
}
|
|
826
|
-
throw new FileOperationException("Running version " + params.hash
|
|
827
|
-
+ " has no verifiable install record and cannot be reinstalled in place");
|
|
828
685
|
}
|
|
829
686
|
|
|
830
687
|
@Override
|
|
@@ -917,37 +774,29 @@ class DownloadTask implements Runnable {
|
|
|
917
774
|
}
|
|
918
775
|
}
|
|
919
776
|
|
|
920
|
-
|
|
777
|
+
// The native half of a patch install (cpp/patch_core/archive_patch_core.h
|
|
778
|
+
// ApplyArchivePatch): checks the archive's entries, parses and validates
|
|
779
|
+
// manifestJson (__diff.json), rebuilds the bundle and, unless
|
|
780
|
+
// hostCopiesResources, performs the copies, deletes and merge. Returns
|
|
781
|
+
// the copies the caller still owes (empty unless hostCopiesResources).
|
|
782
|
+
private static native CopyGroupResult[] applyArchiveDelta(
|
|
783
|
+
int patchType,
|
|
784
|
+
String[] entryNames,
|
|
785
|
+
byte[] manifestJson,
|
|
921
786
|
String sourceRoot,
|
|
922
787
|
String targetRoot,
|
|
923
788
|
String originBundlePath,
|
|
924
789
|
String bundlePatchPath,
|
|
925
790
|
String bundleOutputPath,
|
|
926
|
-
|
|
927
|
-
boolean enableMerge,
|
|
928
|
-
String[] copyFroms,
|
|
929
|
-
String[] copyTos,
|
|
930
|
-
String[] deletes,
|
|
931
|
-
String hbcTransformMeta
|
|
791
|
+
boolean hostCopiesResources
|
|
932
792
|
);
|
|
933
793
|
|
|
794
|
+
// trashDir non-empty: move the selected entries there instead of
|
|
795
|
+
// deleting them (see doCleanUp).
|
|
934
796
|
private static native void cleanupOldEntries(
|
|
935
797
|
String rootDir,
|
|
936
|
-
String
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
);
|
|
940
|
-
|
|
941
|
-
private static native ArchivePatchPlanResult buildArchivePlan(
|
|
942
|
-
int patchType,
|
|
943
|
-
String[] entryNames,
|
|
944
|
-
String[] copyFroms,
|
|
945
|
-
String[] copyTos,
|
|
946
|
-
String[] deletes
|
|
947
|
-
);
|
|
948
|
-
|
|
949
|
-
private static native CopyGroupResult[] buildCopyGroups(
|
|
950
|
-
String[] copyFroms,
|
|
951
|
-
String[] copyTos
|
|
798
|
+
String[] keepNames,
|
|
799
|
+
int maxAgeDays,
|
|
800
|
+
String trashDir
|
|
952
801
|
);
|
|
953
802
|
}
|