react-native-update 10.59.2 → 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 +2 -7
- 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 +17 -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 -2634
- 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 +73 -82
- 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
|
@@ -4,7 +4,12 @@ import common from '@ohos.app.ability.common';
|
|
|
4
4
|
import { zlib } from '@kit.BasicServicesKit';
|
|
5
5
|
import { EventHub } from './EventHub';
|
|
6
6
|
import { DownloadTaskParams } from './DownloadTaskParams';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
saveFileToSandbox,
|
|
9
|
+
unlinkIfExists,
|
|
10
|
+
withRawFd,
|
|
11
|
+
writeFully,
|
|
12
|
+
} from './SaveFile';
|
|
8
13
|
import { util } from '@kit.ArkTS';
|
|
9
14
|
import NativePatchCore, {
|
|
10
15
|
ARCHIVE_PATCH_TYPE_FROM_PACKAGE,
|
|
@@ -16,16 +21,16 @@ import logger from './Logger';
|
|
|
16
21
|
import {
|
|
17
22
|
MAX_ARCHIVE_BYTES,
|
|
18
23
|
MAX_MANIFEST_BYTES,
|
|
19
|
-
|
|
24
|
+
checkExtractedDirectory,
|
|
25
|
+
checkOriginalSize,
|
|
20
26
|
checkUncompressedSize,
|
|
21
27
|
ensureFreeSpace,
|
|
22
|
-
measureExtractedDirectory,
|
|
23
28
|
} from './ArchiveLimits';
|
|
24
29
|
import {
|
|
25
30
|
HARMONY_BUNDLE_FILE_NAME,
|
|
26
|
-
|
|
31
|
+
promoteStaging,
|
|
32
|
+
sealInstallRecord,
|
|
27
33
|
stagingDirectoryFor,
|
|
28
|
-
writeInstallRecord,
|
|
29
34
|
} from './InstallRecord';
|
|
30
35
|
import {
|
|
31
36
|
ERROR_DOWNLOAD_FAILED,
|
|
@@ -40,94 +45,18 @@ import { BUNDLE_DELTA_ENTRY } from './Texts';
|
|
|
40
45
|
|
|
41
46
|
const TAG = 'DownloadTask';
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
copyTos: string[];
|
|
46
|
-
// 与 copyFroms/copyTos 逐位对齐的 copiesCrc(CLI >= 2.21.2 的 pdiff
|
|
47
|
-
// manifest 携带,键是 to);无声明时为 null。拷贝前用它校验包内资源内容,
|
|
48
|
-
// 不符则整次 patch 失败落 full——重打包二进制不能静默拷出漂移的资源。
|
|
49
|
-
copyCrcs: (number | null)[];
|
|
50
|
-
deletes: string[];
|
|
51
|
-
// __diff.json 中对应 bundle patch 条目的 hbcTransform 元数据(原始 JSON
|
|
52
|
-
// 字符串);为空时 native 走现状路径
|
|
53
|
-
hbcTransformMeta: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function parseManifestToArrays(
|
|
57
|
-
manifest: Record<string, any>,
|
|
58
|
-
normalizeResourceCopies: boolean,
|
|
59
|
-
): PatchManifestArrays {
|
|
60
|
-
const copyFroms: string[] = [];
|
|
61
|
-
const copyTos: string[] = [];
|
|
62
|
-
const copyCrcs: (number | null)[] = [];
|
|
63
|
-
const deletesValue = manifest.deletes;
|
|
64
|
-
const deletes = Array.isArray(deletesValue)
|
|
65
|
-
? deletesValue.map(item => String(item))
|
|
66
|
-
: deletesValue && typeof deletesValue === 'object'
|
|
67
|
-
? Object.keys(deletesValue)
|
|
68
|
-
: [];
|
|
69
|
-
|
|
70
|
-
const copies = (manifest.copies || {}) as Record<string, string>;
|
|
71
|
-
const copiesCrcValue = manifest.copiesCrc;
|
|
72
|
-
const copiesCrc =
|
|
73
|
-
copiesCrcValue && typeof copiesCrcValue === 'object'
|
|
74
|
-
? (copiesCrcValue as Record<string, number>)
|
|
75
|
-
: ({} as Record<string, number>);
|
|
76
|
-
for (const to of Object.keys(copies)) {
|
|
77
|
-
const rawFrom = copies[to];
|
|
78
|
-
let from = String(rawFrom || '');
|
|
79
|
-
if (normalizeResourceCopies) {
|
|
80
|
-
from = from.replace('resources/rawfile/', '');
|
|
81
|
-
if (!from) {
|
|
82
|
-
from = to;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
copyFroms.push(from);
|
|
86
|
-
copyTos.push(to);
|
|
87
|
-
const crc = copiesCrc[to];
|
|
88
|
-
copyCrcs.push(typeof crc === 'number' && Number.isFinite(crc) ? crc : null);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const hbcTransform = manifest.hbcTransform as
|
|
92
|
-
| Record<string, object>
|
|
93
|
-
| undefined;
|
|
94
|
-
const hbcTransformEntry =
|
|
95
|
-
hbcTransform && typeof hbcTransform === 'object'
|
|
96
|
-
? hbcTransform[BUNDLE_DELTA_ENTRY]
|
|
97
|
-
: undefined;
|
|
98
|
-
const hbcTransformMeta =
|
|
99
|
-
hbcTransformEntry && typeof hbcTransformEntry === 'object'
|
|
100
|
-
? JSON.stringify(hbcTransformEntry)
|
|
101
|
-
: '';
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
copyFroms,
|
|
105
|
-
copyTos,
|
|
106
|
-
copyCrcs,
|
|
107
|
-
deletes,
|
|
108
|
-
hbcTransformMeta,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
48
|
+
// staging 目录里的条目名 + __diff.json 原文:由原生内核统一校验与解析
|
|
49
|
+
// (cpp/patch_core/patch_manifest.h),这里不解析 manifest。
|
|
112
50
|
interface DeltaInputs {
|
|
113
51
|
entryNames: string[];
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function toArrayBufferSlice(
|
|
118
|
-
payload: Uint8Array,
|
|
119
|
-
offset: number,
|
|
120
|
-
length: number,
|
|
121
|
-
): ArrayBuffer {
|
|
122
|
-
return (payload.buffer as ArrayBuffer).slice(
|
|
123
|
-
payload.byteOffset + offset,
|
|
124
|
-
payload.byteOffset + offset + length,
|
|
125
|
-
);
|
|
52
|
+
manifestJson: string;
|
|
126
53
|
}
|
|
127
54
|
|
|
55
|
+
// 包内 rawfile 在 HAP 中的路径前缀:pdiff 的拷贝源按 HAP 路径命名,
|
|
56
|
+
// resourceManager 按去掉前缀后的路径读取。原生解析时只剥开头一次。
|
|
57
|
+
const RAWFILE_COPY_SOURCE_PREFIX = 'resources/rawfile/';
|
|
128
58
|
const DIFF_MANIFEST_ENTRY = '__diff.json';
|
|
129
|
-
const TEMP_ORIGIN_BUNDLE_ENTRY =
|
|
130
|
-
const FILE_COPY_BUFFER_SIZE = 64 * 1024;
|
|
59
|
+
const TEMP_ORIGIN_BUNDLE_ENTRY = `.origin.${HARMONY_BUNDLE_FILE_NAME}`;
|
|
131
60
|
const DOWNLOAD_CALL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
132
61
|
|
|
133
62
|
// 断点续传 sidecar(NATIVE_CHECKUPDATE_DESIGN §11.4):记录 partial 属于
|
|
@@ -194,23 +123,12 @@ export class DownloadTask {
|
|
|
194
123
|
|
|
195
124
|
private async promoteStaging(params: DownloadTaskParams): Promise<void> {
|
|
196
125
|
const work = this.stagingDirectory(params);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
throw createPushyError(
|
|
200
|
-
ERROR_DELTA_FAILED,
|
|
201
|
-
`bundle missing after install: ${bundlePath}`,
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
// 整 bundle 几十 MB:摘要在 native 工作线程算,不冻结 UI 线程。
|
|
205
|
-
const bundleSha256 = await NativePatchCore.sha256HexFileAsync(bundlePath);
|
|
206
|
-
await writeInstallRecord(
|
|
207
|
-
work,
|
|
208
|
-
buildInstallRecord(params.hash, bundleSha256, this.artifactSha256),
|
|
209
|
-
);
|
|
126
|
+
// 整 bundle 几十 MB:摘要与记录写入在 native 工作线程完成,不冻结 UI 线程。
|
|
127
|
+
await sealInstallRecord(work, params.hash, this.artifactSha256);
|
|
210
128
|
if (fileIo.accessSync(params.unzipDirectory)) {
|
|
211
129
|
await this.removeDirectory(params.unzipDirectory);
|
|
212
130
|
}
|
|
213
|
-
await
|
|
131
|
+
await promoteStaging(work, params.unzipDirectory);
|
|
214
132
|
}
|
|
215
133
|
|
|
216
134
|
constructor(context: common.Context) {
|
|
@@ -258,10 +176,7 @@ export class DownloadTask {
|
|
|
258
176
|
|
|
259
177
|
private async deleteResumeSidecar(targetFile: string): Promise<void> {
|
|
260
178
|
try {
|
|
261
|
-
|
|
262
|
-
if (fileIo.accessSync(sidecar)) {
|
|
263
|
-
await fileIo.unlink(sidecar);
|
|
264
|
-
}
|
|
179
|
+
await unlinkIfExists(this.resumeSidecarPath(targetFile));
|
|
265
180
|
} catch (e) {
|
|
266
181
|
logger.error(TAG, `Failed to delete resume sidecar: ${getErrorMessage(e)}`);
|
|
267
182
|
}
|
|
@@ -270,9 +185,7 @@ export class DownloadTask {
|
|
|
270
185
|
// 归档连同 sidecar 一起删除(被消费或被判定有毒)。
|
|
271
186
|
private async deleteArchiveAndSidecar(targetFile: string): Promise<void> {
|
|
272
187
|
try {
|
|
273
|
-
|
|
274
|
-
await fileIo.unlink(targetFile);
|
|
275
|
-
}
|
|
188
|
+
await unlinkIfExists(targetFile);
|
|
276
189
|
} catch (e) {
|
|
277
190
|
logger.error(TAG, `Failed to delete archive: ${getErrorMessage(e)}`);
|
|
278
191
|
}
|
|
@@ -328,35 +241,37 @@ export class DownloadTask {
|
|
|
328
241
|
|
|
329
242
|
/**
|
|
330
243
|
* 解压前读取归档解压后的总字节数(zlib.getOriginalSize,API 12——RNOH 的
|
|
331
|
-
* 最低 API 已覆盖)
|
|
332
|
-
*
|
|
333
|
-
*
|
|
244
|
+
* 最低 API 已覆盖)。读不到就拒绝归档(checkOriginalSize):zlib.decompressFile
|
|
245
|
+
* 没有逐条钩子,不知道展开量就放行等于允许一个 20MB 的归档在
|
|
246
|
+
* checkExtractedDirectory 跑到之前先把磁盘写满。拒绝带专属错误细节
|
|
247
|
+
* (ORIGINAL_SIZE_UNAVAILABLE)并单独记日志,与"超限"拒绝可区分。
|
|
334
248
|
*/
|
|
335
249
|
private async readOriginalSize(archiveFile: string): Promise<number> {
|
|
336
250
|
let size: number | undefined;
|
|
251
|
+
let failure: string | undefined;
|
|
337
252
|
try {
|
|
338
253
|
size = await zlib.getOriginalSize(archiveFile);
|
|
339
254
|
} catch (e) {
|
|
340
|
-
|
|
341
|
-
ERROR_DELTA_FAILED,
|
|
342
|
-
`cannot determine archive expansion size: ${getErrorMessage(e)}`,
|
|
343
|
-
);
|
|
255
|
+
failure = getErrorMessage(e);
|
|
344
256
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
257
|
+
try {
|
|
258
|
+
return checkOriginalSize(size, failure);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
logger.error(
|
|
261
|
+
TAG,
|
|
262
|
+
`Refusing archive, expansion size unknown: ${getErrorMessage(error)}`,
|
|
349
263
|
);
|
|
264
|
+
throw error;
|
|
350
265
|
}
|
|
351
|
-
return size;
|
|
352
266
|
}
|
|
353
267
|
|
|
354
268
|
/**
|
|
355
269
|
* 解压 + 资源上限(cpp/patch_core/archive_limits.h)。zlib.decompressFile
|
|
356
270
|
* 没有逐条钩子:解压前用 getOriginalSize 把总解压量与整包压缩比挡在
|
|
357
271
|
* 上限内(20MB 归档 100:1 的 2GB 载荷不会先解出来再量),按已知解压量查
|
|
358
|
-
*
|
|
359
|
-
*
|
|
272
|
+
* 磁盘,解压量未知直接拒绝;解压后再逐条核对条目数/单条大小/总字节数,
|
|
273
|
+
* 拒绝任意深度的 `.pushy-` 保留条目与符号链接(规则见
|
|
274
|
+
* cpp/patch_core/archive_limits.h;超限即失败,staging 目录由失败清理删除)。
|
|
360
275
|
*/
|
|
361
276
|
private async extractArchive(
|
|
362
277
|
archiveFile: string,
|
|
@@ -377,7 +292,7 @@ export class DownloadTask {
|
|
|
377
292
|
} catch (e) {
|
|
378
293
|
throw toPushyError(e, ERROR_DELTA_FAILED);
|
|
379
294
|
}
|
|
380
|
-
await
|
|
295
|
+
await checkExtractedDirectory(unzipDirectory, archiveStat.size);
|
|
381
296
|
}
|
|
382
297
|
|
|
383
298
|
private async recreateDirectory(path: string): Promise<void> {
|
|
@@ -415,33 +330,26 @@ export class DownloadTask {
|
|
|
415
330
|
): Promise<void> {
|
|
416
331
|
const payload =
|
|
417
332
|
content instanceof Uint8Array ? content : new Uint8Array(content);
|
|
333
|
+
// writeFully writes from the start of its buffer: a view into a larger
|
|
334
|
+
// buffer at a non-zero offset needs its own copy.
|
|
335
|
+
const bytes =
|
|
336
|
+
payload.byteOffset === 0
|
|
337
|
+
? (payload.buffer as ArrayBuffer)
|
|
338
|
+
: (payload.buffer as ArrayBuffer).slice(
|
|
339
|
+
payload.byteOffset,
|
|
340
|
+
payload.byteOffset + payload.byteLength,
|
|
341
|
+
);
|
|
418
342
|
await this.ensureParentDirectory(targetFile);
|
|
419
|
-
|
|
420
|
-
await fileIo.unlink(targetFile);
|
|
421
|
-
}
|
|
343
|
+
await unlinkIfExists(targetFile);
|
|
422
344
|
|
|
423
|
-
|
|
345
|
+
const writer = await fileIo.open(
|
|
346
|
+
targetFile,
|
|
347
|
+
fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY,
|
|
348
|
+
);
|
|
424
349
|
try {
|
|
425
|
-
writer
|
|
426
|
-
targetFile,
|
|
427
|
-
fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY,
|
|
428
|
-
);
|
|
429
|
-
const chunkSize = FILE_COPY_BUFFER_SIZE;
|
|
430
|
-
let bytesWritten = 0;
|
|
431
|
-
|
|
432
|
-
while (bytesWritten < payload.byteLength) {
|
|
433
|
-
const chunkLength = Math.min(
|
|
434
|
-
chunkSize,
|
|
435
|
-
payload.byteLength - bytesWritten,
|
|
436
|
-
);
|
|
437
|
-
const chunk = toArrayBufferSlice(payload, bytesWritten, chunkLength);
|
|
438
|
-
await fileIo.write(writer.fd, chunk);
|
|
439
|
-
bytesWritten += chunkLength;
|
|
440
|
-
}
|
|
350
|
+
await writeFully(writer.fd, bytes, payload.byteLength);
|
|
441
351
|
} finally {
|
|
442
|
-
|
|
443
|
-
await fileIo.close(writer);
|
|
444
|
-
}
|
|
352
|
+
await fileIo.close(writer);
|
|
445
353
|
}
|
|
446
354
|
}
|
|
447
355
|
|
|
@@ -451,19 +359,11 @@ export class DownloadTask {
|
|
|
451
359
|
) as Record<string, any>;
|
|
452
360
|
}
|
|
453
361
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
normalizeResourceCopies: boolean,
|
|
457
|
-
): Promise<PatchManifestArrays> {
|
|
362
|
+
// __diff.json 原文;归档里没有时为空串(原生内核报 "diff.json not found")。
|
|
363
|
+
private async readManifestJson(directory: string): Promise<string> {
|
|
458
364
|
const manifestPath = `${directory}/${DIFF_MANIFEST_ENTRY}`;
|
|
459
365
|
if (!fileIo.accessSync(manifestPath)) {
|
|
460
|
-
return
|
|
461
|
-
copyFroms: [],
|
|
462
|
-
copyTos: [],
|
|
463
|
-
copyCrcs: [],
|
|
464
|
-
deletes: [],
|
|
465
|
-
hbcTransformMeta: '',
|
|
466
|
-
};
|
|
366
|
+
return '';
|
|
467
367
|
}
|
|
468
368
|
|
|
469
369
|
const manifestStat = await fileIo.stat(manifestPath);
|
|
@@ -473,34 +373,39 @@ export class DownloadTask {
|
|
|
473
373
|
`delta manifest too large: ${manifestStat.size} bytes`,
|
|
474
374
|
);
|
|
475
375
|
}
|
|
476
|
-
return
|
|
477
|
-
|
|
478
|
-
normalizeResourceCopies,
|
|
376
|
+
return new util.TextDecoder().decodeToString(
|
|
377
|
+
new Uint8Array(await this.readFileContent(manifestPath)),
|
|
479
378
|
);
|
|
480
379
|
}
|
|
481
380
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
381
|
+
/**
|
|
382
|
+
* pdiff:hpatch 需要文件源,先把二进制内嵌的 bundle 从 rawfile descriptor
|
|
383
|
+
* 异步流式拷到 staging 里的临时文件(不整包读进内存、不在 UI 线程同步
|
|
384
|
+
* IO),原生内核只重建 bundle,拷贝分组交回由 copyFromResource 完成。
|
|
385
|
+
*/
|
|
386
|
+
private async applyPatchFromEmbeddedBundle(
|
|
387
|
+
work: string,
|
|
388
|
+
inputs: DeltaInputs,
|
|
389
|
+
): Promise<CopyGroupResult[]> {
|
|
390
|
+
const originBundlePath = `${work}/${TEMP_ORIGIN_BUNDLE_ENTRY}`;
|
|
491
391
|
try {
|
|
492
|
-
await
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
392
|
+
await withRawFd(
|
|
393
|
+
this.context.resourceManager,
|
|
394
|
+
HARMONY_BUNDLE_FILE_NAME,
|
|
395
|
+
descriptor => saveFileToSandbox(descriptor, originBundlePath),
|
|
396
|
+
);
|
|
397
|
+
return await NativePatchCore.applyArchiveDelta({
|
|
398
|
+
archiveType: ARCHIVE_PATCH_TYPE_FROM_PACKAGE,
|
|
399
|
+
entryNames: inputs.entryNames,
|
|
400
|
+
manifestJson: inputs.manifestJson,
|
|
401
|
+
bundleDeltaEntry: BUNDLE_DELTA_ENTRY,
|
|
402
|
+
copySourcePrefix: RAWFILE_COPY_SOURCE_PREFIX,
|
|
403
|
+
sourceRoot: work,
|
|
404
|
+
targetRoot: work,
|
|
499
405
|
originBundlePath,
|
|
500
|
-
bundleDeltaPath
|
|
501
|
-
bundleOutputPath:
|
|
502
|
-
|
|
503
|
-
bundleHbcTransformMeta: hbcTransformMeta,
|
|
406
|
+
bundleDeltaPath: `${work}/${BUNDLE_DELTA_ENTRY}`,
|
|
407
|
+
bundleOutputPath: `${work}/${HARMONY_BUNDLE_FILE_NAME}`,
|
|
408
|
+
hostCopiesResources: true,
|
|
504
409
|
});
|
|
505
410
|
} catch (error) {
|
|
506
411
|
throw createPushyError(
|
|
@@ -508,56 +413,18 @@ export class DownloadTask {
|
|
|
508
413
|
`Failed to process bundle delta: ${getErrorMessage(error)}`,
|
|
509
414
|
);
|
|
510
415
|
} finally {
|
|
511
|
-
|
|
512
|
-
await fileIo.unlink(originBundlePath);
|
|
513
|
-
}
|
|
416
|
+
await unlinkIfExists(originBundlePath);
|
|
514
417
|
}
|
|
515
418
|
}
|
|
516
419
|
|
|
420
|
+
// 沙箱内文件复制(同一 rawfile 落到多个目标时,首个之外的目标从首个复制)。
|
|
421
|
+
// fileIo.copyFile 在工作线程整文件复制并覆盖目标,无需逐块搬运。
|
|
517
422
|
private async copySandboxFile(
|
|
518
423
|
sourceFile: string,
|
|
519
424
|
targetFile: string,
|
|
520
425
|
): Promise<void> {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const buffer = new ArrayBuffer(FILE_COPY_BUFFER_SIZE);
|
|
524
|
-
let offset = 0;
|
|
525
|
-
|
|
526
|
-
try {
|
|
527
|
-
reader = await fileIo.open(sourceFile, fileIo.OpenMode.READ_ONLY);
|
|
528
|
-
await this.ensureParentDirectory(targetFile);
|
|
529
|
-
if (fileIo.accessSync(targetFile)) {
|
|
530
|
-
await fileIo.unlink(targetFile);
|
|
531
|
-
}
|
|
532
|
-
writer = await fileIo.open(
|
|
533
|
-
targetFile,
|
|
534
|
-
fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY,
|
|
535
|
-
);
|
|
536
|
-
|
|
537
|
-
while (true) {
|
|
538
|
-
const readLength = await fileIo.read(reader.fd, buffer, {
|
|
539
|
-
offset,
|
|
540
|
-
length: FILE_COPY_BUFFER_SIZE,
|
|
541
|
-
});
|
|
542
|
-
if (readLength <= 0) {
|
|
543
|
-
break;
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
await fileIo.write(writer.fd, buffer.slice(0, readLength));
|
|
547
|
-
offset += readLength;
|
|
548
|
-
|
|
549
|
-
if (readLength < FILE_COPY_BUFFER_SIZE) {
|
|
550
|
-
break;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
} finally {
|
|
554
|
-
if (reader) {
|
|
555
|
-
await fileIo.close(reader);
|
|
556
|
-
}
|
|
557
|
-
if (writer) {
|
|
558
|
-
await fileIo.close(writer);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
426
|
+
await this.ensureParentDirectory(targetFile);
|
|
427
|
+
await fileIo.copyFile(sourceFile, targetFile);
|
|
561
428
|
}
|
|
562
429
|
|
|
563
430
|
private async downloadFile(params: DownloadTaskParams): Promise<void> {
|
|
@@ -651,6 +518,24 @@ export class DownloadTask {
|
|
|
651
518
|
authorize = resolve;
|
|
652
519
|
});
|
|
653
520
|
|
|
521
|
+
// 本 url 的 sidecar 内容:验证器优先取本次响应头,缺失时沿用上次的;
|
|
522
|
+
// 总长未知(0)时不写。
|
|
523
|
+
const buildResumeMeta = (total: number): ResumeMeta => {
|
|
524
|
+
const meta: ResumeMeta = { url: params.url };
|
|
525
|
+
const etag = etagHeader || resumeMeta?.etag;
|
|
526
|
+
const lastModified = lastModifiedHeader || resumeMeta?.lastModified;
|
|
527
|
+
if (etag) {
|
|
528
|
+
meta.etag = etag;
|
|
529
|
+
}
|
|
530
|
+
if (lastModified) {
|
|
531
|
+
meta.lastModified = lastModified;
|
|
532
|
+
}
|
|
533
|
+
if (total > 0) {
|
|
534
|
+
meta.total = total;
|
|
535
|
+
}
|
|
536
|
+
return meta;
|
|
537
|
+
};
|
|
538
|
+
|
|
654
539
|
const reportProgress = () => {
|
|
655
540
|
const overall = baseOffset + received;
|
|
656
541
|
const overallTotal = totalAll > 0
|
|
@@ -689,19 +574,18 @@ export class DownloadTask {
|
|
|
689
574
|
`archive too large: exceeded ${MAX_ARCHIVE_BYTES}`,
|
|
690
575
|
);
|
|
691
576
|
}
|
|
692
|
-
//
|
|
693
|
-
//
|
|
694
|
-
//
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
const reserve = Math.max(
|
|
699
|
-
UNKNOWN_LENGTH_FREE_SPACE_PROBE_BYTES,
|
|
577
|
+
// 未知长度:响应到达时只能预留安全余量,任何会写过已预留位置的写入
|
|
578
|
+
// 之前都要探测(archive_limits::PlanUnknownLengthFreeSpaceProbe)。
|
|
579
|
+
// 失败保留 partial 供下次续传。
|
|
580
|
+
const plan = NativePatchCore.planFreeSpaceProbe(
|
|
581
|
+
totalAll,
|
|
582
|
+
received - data.byteLength,
|
|
700
583
|
data.byteLength,
|
|
584
|
+
freeSpaceReservedUntil,
|
|
701
585
|
);
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
586
|
+
freeSpaceReservedUntil = plan.reservedUntil;
|
|
587
|
+
const probeFreeSpace = plan.probe;
|
|
588
|
+
const reserve = plan.reserve;
|
|
705
589
|
writeQueue = writeQueue.then(async () => {
|
|
706
590
|
if (!writer || writeError) {
|
|
707
591
|
return;
|
|
@@ -934,9 +818,7 @@ export class DownloadTask {
|
|
|
934
818
|
);
|
|
935
819
|
} else {
|
|
936
820
|
// 服务端忽略了 Range(或本就没发):从零开始。
|
|
937
|
-
|
|
938
|
-
await fileIo.unlink(params.targetFile);
|
|
939
|
-
}
|
|
821
|
+
await unlinkIfExists(params.targetFile);
|
|
940
822
|
baseOffset = 0;
|
|
941
823
|
totalAll = contentLength > 0 ? contentLength : 0;
|
|
942
824
|
writer = await fileIo.open(
|
|
@@ -956,19 +838,7 @@ export class DownloadTask {
|
|
|
956
838
|
totalAll > 0 ? totalAll - baseOffset : 0,
|
|
957
839
|
);
|
|
958
840
|
// 先落 sidecar 再放行写入,流中崩溃才有得续。
|
|
959
|
-
|
|
960
|
-
const etag = etagHeader || resumeMeta?.etag;
|
|
961
|
-
const lastModified = lastModifiedHeader || resumeMeta?.lastModified;
|
|
962
|
-
if (etag) {
|
|
963
|
-
meta.etag = etag;
|
|
964
|
-
}
|
|
965
|
-
if (lastModified) {
|
|
966
|
-
meta.lastModified = lastModified;
|
|
967
|
-
}
|
|
968
|
-
if (totalAll > 0) {
|
|
969
|
-
meta.total = totalAll;
|
|
970
|
-
}
|
|
971
|
-
await this.writeResumeMeta(params.targetFile, meta);
|
|
841
|
+
await this.writeResumeMeta(params.targetFile, buildResumeMeta(totalAll));
|
|
972
842
|
|
|
973
843
|
// 鉴权通过:回放缓冲,之后的数据直写。
|
|
974
844
|
const buffered = pendingChunks ?? [];
|
|
@@ -1010,15 +880,10 @@ export class DownloadTask {
|
|
|
1010
880
|
}
|
|
1011
881
|
// 用最终观测值刷新 sidecar:首次写入可能因头部竞态缺 total/验证器,
|
|
1012
882
|
// 补齐后“下载完、解压前死”的窗口才能被 complete 检测覆盖。
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
if (lastModifiedHeader || resumeMeta?.lastModified) {
|
|
1018
|
-
finalMeta.lastModified =
|
|
1019
|
-
lastModifiedHeader || resumeMeta?.lastModified;
|
|
1020
|
-
}
|
|
1021
|
-
await this.writeResumeMeta(params.targetFile, finalMeta);
|
|
883
|
+
await this.writeResumeMeta(
|
|
884
|
+
params.targetFile,
|
|
885
|
+
buildResumeMeta(stats.size),
|
|
886
|
+
);
|
|
1022
887
|
} else {
|
|
1023
888
|
// 编码传输不可续传:盘上是解码后的字节,编码域的长度与偏移都失效。
|
|
1024
889
|
await this.deleteResumeSidecar(params.targetFile);
|
|
@@ -1074,16 +939,12 @@ export class DownloadTask {
|
|
|
1074
939
|
return work;
|
|
1075
940
|
}
|
|
1076
941
|
|
|
1077
|
-
|
|
1078
|
-
private async readDeltaInputs(
|
|
1079
|
-
work: string,
|
|
1080
|
-
normalizeResourceCopies: boolean,
|
|
1081
|
-
): Promise<DeltaInputs> {
|
|
942
|
+
private async readDeltaInputs(work: string): Promise<DeltaInputs> {
|
|
1082
943
|
const results = await Promise.all([
|
|
1083
944
|
this.listEntryNames(work),
|
|
1084
|
-
this.
|
|
945
|
+
this.readManifestJson(work),
|
|
1085
946
|
]);
|
|
1086
|
-
return { entryNames: results[0],
|
|
947
|
+
return { entryNames: results[0], manifestJson: results[1] };
|
|
1087
948
|
}
|
|
1088
949
|
|
|
1089
950
|
private async doFullPatch(params: DownloadTaskParams): Promise<void> {
|
|
@@ -1093,78 +954,27 @@ export class DownloadTask {
|
|
|
1093
954
|
|
|
1094
955
|
private async doPatchFromApp(params: DownloadTaskParams): Promise<void> {
|
|
1095
956
|
const work = await this.downloadAndExtract(params);
|
|
1096
|
-
const
|
|
1097
|
-
const manifestArrays = inputs.manifestArrays;
|
|
1098
|
-
|
|
1099
|
-
NativePatchCore.buildArchivePlan(
|
|
1100
|
-
ARCHIVE_PATCH_TYPE_FROM_PACKAGE,
|
|
1101
|
-
inputs.entryNames,
|
|
1102
|
-
manifestArrays.copyFroms,
|
|
1103
|
-
manifestArrays.copyTos,
|
|
1104
|
-
manifestArrays.deletes,
|
|
1105
|
-
BUNDLE_DELTA_ENTRY,
|
|
1106
|
-
);
|
|
1107
|
-
|
|
1108
|
-
const bundleDeltaPath = `${work}/${BUNDLE_DELTA_ENTRY}`;
|
|
1109
|
-
if (!fileIo.accessSync(bundleDeltaPath)) {
|
|
1110
|
-
throw createPushyError(ERROR_DELTA_FAILED, 'bundle delta not found');
|
|
1111
|
-
}
|
|
1112
|
-
const resourceManager = this.context.resourceManager;
|
|
1113
|
-
const originContent = await resourceManager.getRawFileContent(
|
|
1114
|
-
'bundle.harmony.js',
|
|
1115
|
-
);
|
|
1116
|
-
await this.applyBundlePatchFromFileSource(
|
|
1117
|
-
originContent.buffer as ArrayBuffer,
|
|
957
|
+
const copyGroups = await this.applyPatchFromEmbeddedBundle(
|
|
1118
958
|
work,
|
|
1119
|
-
|
|
1120
|
-
`${work}/${HARMONY_BUNDLE_FILE_NAME}`,
|
|
1121
|
-
manifestArrays.hbcTransformMeta,
|
|
1122
|
-
);
|
|
1123
|
-
// 组按 from 聚合;同一 from 的内容必然一致,所以每组一个 CRC 即可
|
|
1124
|
-
// (与 manifest 逐位对齐的 copyCrcs 里取第一个非空值)。
|
|
1125
|
-
const crcByFrom = new Map<string, number>();
|
|
1126
|
-
manifestArrays.copyFroms.forEach((from, index) => {
|
|
1127
|
-
const crc = manifestArrays.copyCrcs[index];
|
|
1128
|
-
if (crc !== null && !crcByFrom.has(from)) {
|
|
1129
|
-
crcByFrom.set(from, crc);
|
|
1130
|
-
}
|
|
1131
|
-
});
|
|
1132
|
-
await this.copyFromResource(
|
|
1133
|
-
NativePatchCore.buildCopyGroups(
|
|
1134
|
-
manifestArrays.copyFroms,
|
|
1135
|
-
manifestArrays.copyTos,
|
|
1136
|
-
),
|
|
1137
|
-
work,
|
|
1138
|
-
crcByFrom,
|
|
959
|
+
await this.readDeltaInputs(work),
|
|
1139
960
|
);
|
|
961
|
+
await this.copyFromResource(copyGroups, work);
|
|
1140
962
|
await this.deleteArchiveAndSidecar(params.targetFile);
|
|
1141
963
|
}
|
|
1142
964
|
|
|
1143
965
|
private async doPatchFromPpk(params: DownloadTaskParams): Promise<void> {
|
|
1144
966
|
const work = await this.downloadAndExtract(params);
|
|
1145
|
-
const inputs = await this.readDeltaInputs(work
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
manifestArrays.copyFroms,
|
|
1152
|
-
manifestArrays.copyTos,
|
|
1153
|
-
manifestArrays.deletes,
|
|
1154
|
-
BUNDLE_DELTA_ENTRY,
|
|
1155
|
-
);
|
|
1156
|
-
await NativePatchCore.applyDeltaFromSource({
|
|
1157
|
-
copyFroms: manifestArrays.copyFroms,
|
|
1158
|
-
copyTos: manifestArrays.copyTos,
|
|
1159
|
-
deletes: manifestArrays.deletes,
|
|
967
|
+
const inputs = await this.readDeltaInputs(work);
|
|
968
|
+
await NativePatchCore.applyArchiveDelta({
|
|
969
|
+
archiveType: ARCHIVE_PATCH_TYPE_FROM_PPK,
|
|
970
|
+
entryNames: inputs.entryNames,
|
|
971
|
+
manifestJson: inputs.manifestJson,
|
|
972
|
+
bundleDeltaEntry: BUNDLE_DELTA_ENTRY,
|
|
1160
973
|
sourceRoot: params.originDirectory,
|
|
1161
974
|
targetRoot: work,
|
|
1162
|
-
originBundlePath: `${params.originDirectory}
|
|
975
|
+
originBundlePath: `${params.originDirectory}/${HARMONY_BUNDLE_FILE_NAME}`,
|
|
1163
976
|
bundleDeltaPath: `${work}/${BUNDLE_DELTA_ENTRY}`,
|
|
1164
977
|
bundleOutputPath: `${work}/${HARMONY_BUNDLE_FILE_NAME}`,
|
|
1165
|
-
mergeSourceSubdir: plan.mergeSourceSubdir,
|
|
1166
|
-
enableMerge: plan.enableMerge,
|
|
1167
|
-
bundleHbcTransformMeta: manifestArrays.hbcTransformMeta,
|
|
1168
978
|
});
|
|
1169
979
|
logger.info(TAG, 'Delta from PPK completed');
|
|
1170
980
|
await this.deleteArchiveAndSidecar(params.targetFile);
|
|
@@ -1173,7 +983,6 @@ export class DownloadTask {
|
|
|
1173
983
|
private async copyFromResource(
|
|
1174
984
|
copyGroups: CopyGroupResult[],
|
|
1175
985
|
targetRoot: string,
|
|
1176
|
-
crcByFrom?: Map<string, number>,
|
|
1177
986
|
): Promise<void> {
|
|
1178
987
|
let currentFrom = '';
|
|
1179
988
|
try {
|
|
@@ -1185,7 +994,7 @@ export class DownloadTask {
|
|
|
1185
994
|
if (targets.length === 0) {
|
|
1186
995
|
continue;
|
|
1187
996
|
}
|
|
1188
|
-
const expectedCrc =
|
|
997
|
+
const expectedCrc = group.expectedCrc;
|
|
1189
998
|
|
|
1190
999
|
if (currentFrom.startsWith('resources/base/media/')) {
|
|
1191
1000
|
// Strip only the final extension: 'icon.round.png' -> 'icon.round',
|
|
@@ -1218,28 +1027,15 @@ export class DownloadTask {
|
|
|
1218
1027
|
continue;
|
|
1219
1028
|
}
|
|
1220
1029
|
|
|
1221
|
-
const
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
await Promise.all(
|
|
1231
|
-
restTargets.map(target => this.copySandboxFile(firstTarget, target)),
|
|
1232
|
-
);
|
|
1233
|
-
} finally {
|
|
1234
|
-
try {
|
|
1235
|
-
await resourceManager.closeRawFd(currentFrom);
|
|
1236
|
-
} catch (closeError) {
|
|
1237
|
-
logger.error(
|
|
1238
|
-
TAG,
|
|
1239
|
-
`Failed to close raw fd for ${currentFrom}: ${getErrorMessage(closeError)}`,
|
|
1240
|
-
);
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1030
|
+
const firstTarget = targets[0];
|
|
1031
|
+
const restTargets = targets.slice(1);
|
|
1032
|
+
await this.ensureParentDirectories(targets);
|
|
1033
|
+
await withRawFd(resourceManager, currentFrom, descriptor =>
|
|
1034
|
+
saveFileToSandbox(descriptor, firstTarget),
|
|
1035
|
+
);
|
|
1036
|
+
await Promise.all(
|
|
1037
|
+
restTargets.map(target => this.copySandboxFile(firstTarget, target)),
|
|
1038
|
+
);
|
|
1243
1039
|
}
|
|
1244
1040
|
} catch (error) {
|
|
1245
1041
|
const coded = toPushyError(error, ERROR_DELTA_FAILED);
|
|
@@ -1282,25 +1078,7 @@ export class DownloadTask {
|
|
|
1282
1078
|
}
|
|
1283
1079
|
}
|
|
1284
1080
|
|
|
1285
|
-
private async doCleanUp(params: DownloadTaskParams): Promise<void> {
|
|
1286
|
-
try {
|
|
1287
|
-
await NativePatchCore.cleanupOldEntries(
|
|
1288
|
-
params.unzipDirectory,
|
|
1289
|
-
[params.hash, params.originHash].filter(name => !!name),
|
|
1290
|
-
3,
|
|
1291
|
-
);
|
|
1292
|
-
} catch (error) {
|
|
1293
|
-
const message = `Cleanup failed: ${getErrorMessage(error)}`;
|
|
1294
|
-
logger.error(TAG, message);
|
|
1295
|
-
throw createPushyError(ERROR_FILE_OPERATION_FAILED, message);
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
1081
|
public async execute(params: DownloadTaskParams): Promise<void> {
|
|
1300
|
-
const isPatchTask =
|
|
1301
|
-
params.type === DownloadTaskParams.TASK_TYPE_PATCH_FULL ||
|
|
1302
|
-
params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_APP ||
|
|
1303
|
-
params.type === DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK;
|
|
1304
1082
|
try {
|
|
1305
1083
|
switch (params.type) {
|
|
1306
1084
|
case DownloadTaskParams.TASK_TYPE_PATCH_FULL:
|
|
@@ -1312,24 +1090,13 @@ export class DownloadTask {
|
|
|
1312
1090
|
case DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK:
|
|
1313
1091
|
await this.doPatchFromPpk(params);
|
|
1314
1092
|
break;
|
|
1315
|
-
case DownloadTaskParams.TASK_TYPE_CLEANUP:
|
|
1316
|
-
await this.doCleanUp(params);
|
|
1317
|
-
break;
|
|
1318
|
-
case DownloadTaskParams.TASK_TYPE_PLAIN_DOWNLOAD:
|
|
1319
|
-
await this.downloadFile(params);
|
|
1320
|
-
// 文件在完成时即是最终产物——残留的 sidecar 会让下次同 URL 的
|
|
1321
|
-
// 下载不发请求直接返回这份旧字节。
|
|
1322
|
-
await this.deleteResumeSidecar(params.targetFile);
|
|
1323
|
-
break;
|
|
1324
1093
|
default:
|
|
1325
1094
|
throw createPushyError(
|
|
1326
1095
|
ERROR_INVALID_OPTIONS,
|
|
1327
1096
|
`Unknown task type: ${params.type}`,
|
|
1328
1097
|
);
|
|
1329
1098
|
}
|
|
1330
|
-
|
|
1331
|
-
await this.promoteStaging(params);
|
|
1332
|
-
}
|
|
1099
|
+
await this.promoteStaging(params);
|
|
1333
1100
|
} catch (rawError) {
|
|
1334
1101
|
// 稳定错误码:下载阶段的失败是 DOWNLOAD_FAILED;归档收齐之后的失败
|
|
1335
1102
|
// (解压/hpatch/资源拷贝含 copiesCrc 校验/完成记录)一律 PATCH_FAILED,
|
|
@@ -1340,29 +1107,22 @@ export class DownloadTask {
|
|
|
1340
1107
|
this.downloadPhaseCompleted ? ERROR_DELTA_FAILED : ERROR_DOWNLOAD_FAILED,
|
|
1341
1108
|
);
|
|
1342
1109
|
logger.error(TAG, `Task execution failed: ${error.message}`);
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
if (this.downloadPhaseCompleted) {
|
|
1354
|
-
// 收齐后才失败的是解压/patch 失败:归档有毒,续传只会撞上
|
|
1355
|
-
// 同一个失败。下载阶段的失败则保留 partial + sidecar——那
|
|
1356
|
-
// 就是续传状态。
|
|
1357
|
-
await this.deleteArchiveAndSidecar(params.targetFile);
|
|
1358
|
-
}
|
|
1359
|
-
}
|
|
1360
|
-
} catch (cleanupError) {
|
|
1361
|
-
logger.error(
|
|
1362
|
-
TAG,
|
|
1363
|
-
`Cleanup after error failed: ${getErrorMessage(cleanupError)}`,
|
|
1364
|
-
);
|
|
1110
|
+
try {
|
|
1111
|
+
// Only the staging directory is ours to drop: the final version
|
|
1112
|
+
// directory is never touched by a failed install, so a failed
|
|
1113
|
+
// duplicate task cannot remove an earlier task's install.
|
|
1114
|
+
await this.removeDirectory(this.stagingDirectory(params));
|
|
1115
|
+
if (this.downloadPhaseCompleted) {
|
|
1116
|
+
// 收齐后才失败的是解压/patch 失败:归档有毒,续传只会撞上
|
|
1117
|
+
// 同一个失败。下载阶段的失败则保留 partial + sidecar——那
|
|
1118
|
+
// 就是续传状态。
|
|
1119
|
+
await this.deleteArchiveAndSidecar(params.targetFile);
|
|
1365
1120
|
}
|
|
1121
|
+
} catch (cleanupError) {
|
|
1122
|
+
logger.error(
|
|
1123
|
+
TAG,
|
|
1124
|
+
`Cleanup after error failed: ${getErrorMessage(cleanupError)}`,
|
|
1125
|
+
);
|
|
1366
1126
|
}
|
|
1367
1127
|
throw error;
|
|
1368
1128
|
}
|