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
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
// The install pipeline shared by JS calls and the native round: download
|
|
2
|
+
// (RCTPushyDownloader) → unzip (archive guard) → patch (cpp/patch_core) →
|
|
3
|
+
// promote (RCTPushyInstallRecord.mm), plus version activation and cleanup.
|
|
4
|
+
#import "RCTPushyInternal.h"
|
|
5
|
+
#import "RCTPushyDownloader.h"
|
|
6
|
+
#import <SSZipArchive/SSZipArchive.h>
|
|
7
|
+
#include "../../cpp/patch_core/archive_limits.h"
|
|
8
|
+
#include "../../cpp/patch_core/archive_patch_core.h"
|
|
9
|
+
#include "../../cpp/patch_core/digest.h"
|
|
10
|
+
#include "../../cpp/patch_core/error_codes.h"
|
|
11
|
+
#include "../../cpp/patch_core/install_record.h"
|
|
12
|
+
#include "../../cpp/patch_core/patch_core.h"
|
|
13
|
+
|
|
14
|
+
#include <memory>
|
|
15
|
+
|
|
16
|
+
// file def
|
|
17
|
+
static NSString *const SOURCE_PATCH_NAME = @"__diff.json";
|
|
18
|
+
static NSString *const ERROR_FILE_OPERATION = @"file operation error";
|
|
19
|
+
|
|
20
|
+
// Protocol strings stored encoded (RCTPushyRevealText) so they do not appear
|
|
21
|
+
// as plain binary strings; decoded on first use.
|
|
22
|
+
static NSString *BUNDLE_PATCH_NAME(void) {
|
|
23
|
+
static NSString *value = RCTPushyRevealText("3319f0d4b6c56a502c3b10fcdca0de7d4b3307e9");
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
PUSHY_HIDDEN NSString *PushyOptionUrlKey(void) {
|
|
27
|
+
static NSString *value = RCTPushyRevealText("2f07f0d0ba8e5d572e");
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
static NSString *PushyIpaDeltaSuffix(void) {
|
|
31
|
+
static NSString *value = RCTPushyRevealText("741ee4d0e09b69512137");
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
static NSString *PushyPpkDeltaSuffix(void) {
|
|
35
|
+
static NSString *value = RCTPushyRevealText("7407e4dae09b69512137");
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static NSString *PushyArchiveExtension(PushyType type) {
|
|
40
|
+
switch (type) {
|
|
41
|
+
case PushyTypePatchFromPackage:
|
|
42
|
+
return PushyIpaDeltaSuffix();
|
|
43
|
+
case PushyTypePatchFromPpk:
|
|
44
|
+
return PushyPpkDeltaSuffix();
|
|
45
|
+
case PushyTypeFullDownload:
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
return @".ppk";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
PUSHY_HIDDEN dispatch_queue_t PushyFileQueue(void) {
|
|
52
|
+
// One process-wide serial queue: JS calls (from any bridge instance; a
|
|
53
|
+
// reload can briefly keep two alive) and the native round share it, so
|
|
54
|
+
// destructive file work (resetToPackagedBundle's full cleanup) stays
|
|
55
|
+
// serialized with every unzip/patch job.
|
|
56
|
+
static dispatch_queue_t queue =
|
|
57
|
+
dispatch_queue_create("cn.reactnative.pushy.file", DISPATCH_QUEUE_SERIAL);
|
|
58
|
+
return queue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// nil when the volume holding `path` can take `bytesToWrite` plus the safety
|
|
62
|
+
// margin (or the free space is unknown); a PATCH_FAILED error otherwise. The
|
|
63
|
+
// probe is the downloader's (RCTPushyFreeSpaceShortfall), which applies the
|
|
64
|
+
// same rule to the announced Content-Length before the first byte lands.
|
|
65
|
+
static NSError *PushyEnsureFreeSpace(NSString *path, long long bytesToWrite) {
|
|
66
|
+
NSString *shortfall = RCTPushyFreeSpaceShortfall(path, bytesToWrite);
|
|
67
|
+
if (shortfall == nil) {
|
|
68
|
+
return nil;
|
|
69
|
+
}
|
|
70
|
+
return PushyErrorWithCode(pushy::error_codes::kDeltaFailed, shortfall);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// SSZipArchive delegate feeding cpp/patch_core/archive_limits.h's
|
|
74
|
+
// EntryAccumulator while the archive is walked (entry count, sizes, ratios,
|
|
75
|
+
// symlinks: SSZipArchive materialises them with any target regardless of
|
|
76
|
+
// preserveAttributes, so `assets -> ../../Documents` followed by `assets/x`
|
|
77
|
+
// would write outside the version directory). The entry name is only exposed
|
|
78
|
+
// once the entry is on disk, so the reserved `.pushy-` names (the completion
|
|
79
|
+
// record is the SDK's alone) are checked in the did-unzip callback.
|
|
80
|
+
// A NO from zipArchiveShouldUnzipFileAtIndex: cancels the whole unzip (the
|
|
81
|
+
// 2.x contract the podspec pins), and the completion handler turns the
|
|
82
|
+
// recorded violation into a hard PATCH_FAILED; the caller then discards the
|
|
83
|
+
// staging directory.
|
|
84
|
+
@interface PushyUnzipGuard : NSObject <SSZipArchiveDelegate>
|
|
85
|
+
@property (nonatomic, copy) NSString *violation;
|
|
86
|
+
- (instancetype)initWithArchiveBytes:(long long)archiveBytes destination:(NSString *)destination;
|
|
87
|
+
@end
|
|
88
|
+
|
|
89
|
+
@implementation PushyUnzipGuard {
|
|
90
|
+
std::unique_ptr<pushy::archive_limits::EntryAccumulator> _accumulator;
|
|
91
|
+
NSArray<NSString *> *_destinationComponents;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
- (instancetype)initWithArchiveBytes:(long long)archiveBytes destination:(NSString *)destination {
|
|
95
|
+
if ((self = [super init])) {
|
|
96
|
+
_accumulator.reset(new pushy::archive_limits::EntryAccumulator(archiveBytes));
|
|
97
|
+
// Joined the way SSZipArchive builds each unpacked path
|
|
98
|
+
// (stringByAppendingPathComponent:), so a trailing slash compares equal.
|
|
99
|
+
NSArray<NSString *> *probe =
|
|
100
|
+
[destination stringByAppendingPathComponent:@"x"].pathComponents;
|
|
101
|
+
_destinationComponents = [probe subarrayWithRange:NSMakeRange(0, probe.count - 1)];
|
|
102
|
+
}
|
|
103
|
+
return self;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
- (void)recordStatus:(const pushy::delta::Status &)status {
|
|
107
|
+
if (!status.ok && self.violation == nil) {
|
|
108
|
+
self.violation = @(status.message.c_str());
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
- (void)zipArchiveWillUnzipArchiveAtPath:(NSString *)path zipInfo:(unz_global_info)zipInfo {
|
|
113
|
+
[self recordStatus:pushy::archive_limits::CheckEntryCount((long long)zipInfo.number_entry)];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
- (BOOL)zipArchiveShouldUnzipFileAtIndex:(NSInteger)fileIndex
|
|
117
|
+
totalFiles:(NSInteger)totalFiles
|
|
118
|
+
archivePath:(NSString *)archivePath
|
|
119
|
+
fileInfo:(unz_file_info)fileInfo {
|
|
120
|
+
if (self.violation != nil) {
|
|
121
|
+
return NO;
|
|
122
|
+
}
|
|
123
|
+
// Unix mode bits travel in the high 16 bits of the external attributes
|
|
124
|
+
// (the same field SSZipArchive keys its own symlink branch on).
|
|
125
|
+
BOOL symlink = ((fileInfo.external_fa >> 16) & S_IFMT) == S_IFLNK;
|
|
126
|
+
[self recordStatus:_accumulator->Accept((long long)fileInfo.uncompressed_size,
|
|
127
|
+
(long long)fileInfo.compressed_size,
|
|
128
|
+
symlink, std::string())];
|
|
129
|
+
return self.violation == nil;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// A reserved name is flagged here (any component of the path below the
|
|
133
|
+
// destination); the next shouldUnzip refuses (cancelling the unzip) or, for
|
|
134
|
+
// the last entry, the completion handler picks the violation up. Either way
|
|
135
|
+
// the caller drops the whole staging directory, so the written file never
|
|
136
|
+
// survives.
|
|
137
|
+
- (void)zipArchiveDidUnzipFileAtIndex:(NSInteger)fileIndex
|
|
138
|
+
totalFiles:(NSInteger)totalFiles
|
|
139
|
+
archivePath:(NSString *)archivePath
|
|
140
|
+
unzippedFilePath:(NSString *)unzippedFilePath {
|
|
141
|
+
if (self.violation != nil) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
NSArray<NSString *> *components = unzippedFilePath.pathComponents;
|
|
145
|
+
NSUInteger base = _destinationComponents.count;
|
|
146
|
+
if (components.count <= base
|
|
147
|
+
|| ![[components subarrayWithRange:NSMakeRange(0, base)]
|
|
148
|
+
isEqualToArray:_destinationComponents]) {
|
|
149
|
+
self.violation = [NSString stringWithFormat:@"archive entry #%ld outside the destination",
|
|
150
|
+
(long)fileIndex];
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
NSString *name = [[components subarrayWithRange:NSMakeRange(base, components.count - base)]
|
|
154
|
+
componentsJoinedByString:@"/"];
|
|
155
|
+
if (pushy::archive_limits::IsReservedEntryName(PushyToStdString(name))) {
|
|
156
|
+
self.violation = [NSString stringWithFormat:@"archive contains reserved entry %@", name];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
@end
|
|
161
|
+
|
|
162
|
+
// JS (any bridge instance) and the native round share one download per target
|
|
163
|
+
// hash. Without this process-wide registry two transfers race over the same
|
|
164
|
+
// archive path and the later unzip can delete the first task's valid output.
|
|
165
|
+
static NSMutableDictionary<NSString *, NSMutableDictionary *> *PushyInFlightDownloads(void) {
|
|
166
|
+
static NSMutableDictionary<NSString *, NSMutableDictionary *> *downloads =
|
|
167
|
+
[NSMutableDictionary dictionary];
|
|
168
|
+
return downloads;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
typedef NS_ENUM(NSInteger, PushyDownloadRegistration) {
|
|
172
|
+
PushyDownloadRegistrationOwner,
|
|
173
|
+
PushyDownloadRegistrationJoined,
|
|
174
|
+
PushyDownloadRegistrationDeferred,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
static PushyDownloadRegistration PushyRegisterDownload(
|
|
178
|
+
NSString *hash,
|
|
179
|
+
NSInteger type,
|
|
180
|
+
NSTimeInterval deadlineUptime,
|
|
181
|
+
void (^callback)(NSError *),
|
|
182
|
+
void (^progress)(long long, long long),
|
|
183
|
+
void (^deferredStart)(void)
|
|
184
|
+
) {
|
|
185
|
+
NSMutableDictionary *downloads = PushyInFlightDownloads();
|
|
186
|
+
@synchronized (downloads) {
|
|
187
|
+
NSMutableDictionary *entry = downloads[hash];
|
|
188
|
+
if (entry != nil) {
|
|
189
|
+
if ([entry[@"type"] integerValue] == type) {
|
|
190
|
+
NSTimeInterval ownerDeadline = [entry[@"deadlineUptime"] doubleValue];
|
|
191
|
+
// A caller with substantially more time must not inherit an
|
|
192
|
+
// owner's nearly-exhausted timeout: it observes the current
|
|
193
|
+
// transfer and restarts after the owner settles (the
|
|
194
|
+
// completion-marker preflight makes a successful owner free).
|
|
195
|
+
// The comparison is on remaining budget, not on the absolute
|
|
196
|
+
// deadline: a JS caller always computes now+600 a few seconds
|
|
197
|
+
// after the owner did, so a strict `>` would defer every
|
|
198
|
+
// second caller and turn the shared download back into
|
|
199
|
+
// serialized re-downloads. Only a genuinely starved owner
|
|
200
|
+
// (less than half the newcomer's budget left) defers.
|
|
201
|
+
const NSTimeInterval now = PushyMonotonicNow();
|
|
202
|
+
if (2 * (ownerDeadline - now) < (deadlineUptime - now)) {
|
|
203
|
+
if (progress != nil) {
|
|
204
|
+
[entry[@"progress"] addObject:[progress copy]];
|
|
205
|
+
}
|
|
206
|
+
[entry[@"deferred"] addObject:[deferredStart copy]];
|
|
207
|
+
return PushyDownloadRegistrationDeferred;
|
|
208
|
+
}
|
|
209
|
+
[entry[@"callbacks"] addObject:[callback copy]];
|
|
210
|
+
if (progress != nil) {
|
|
211
|
+
[entry[@"progress"] addObject:[progress copy]];
|
|
212
|
+
}
|
|
213
|
+
return PushyDownloadRegistrationJoined;
|
|
214
|
+
}
|
|
215
|
+
// A diff failure must not settle a joined full request. Queue the
|
|
216
|
+
// different artifact type behind the owner; once restarted it
|
|
217
|
+
// registers normally (and re-checks the completion marker).
|
|
218
|
+
if (progress != nil) {
|
|
219
|
+
// The artifact type differs, but it still installs the same
|
|
220
|
+
// target hash. Keep the waiting JS UI moving while its own
|
|
221
|
+
// transfer is queued behind the current owner.
|
|
222
|
+
[entry[@"progress"] addObject:[progress copy]];
|
|
223
|
+
}
|
|
224
|
+
[entry[@"deferred"] addObject:[deferredStart copy]];
|
|
225
|
+
return PushyDownloadRegistrationDeferred;
|
|
226
|
+
}
|
|
227
|
+
NSMutableArray *progressHandlers = [NSMutableArray array];
|
|
228
|
+
if (progress != nil) {
|
|
229
|
+
[progressHandlers addObject:[progress copy]];
|
|
230
|
+
}
|
|
231
|
+
downloads[hash] = [@{
|
|
232
|
+
@"type": @(type),
|
|
233
|
+
@"deadlineUptime": @(deadlineUptime),
|
|
234
|
+
@"callbacks": [NSMutableArray arrayWithObject:[callback copy]],
|
|
235
|
+
@"progress": progressHandlers,
|
|
236
|
+
@"deferred": [NSMutableArray array],
|
|
237
|
+
} mutableCopy];
|
|
238
|
+
return PushyDownloadRegistrationOwner;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
static void PushyReportDownloadProgress(
|
|
243
|
+
NSString *hash, long long received, long long total
|
|
244
|
+
) {
|
|
245
|
+
NSMutableDictionary *downloads = PushyInFlightDownloads();
|
|
246
|
+
NSArray *handlers = nil;
|
|
247
|
+
@synchronized (downloads) {
|
|
248
|
+
handlers = [downloads[hash][@"progress"] copy];
|
|
249
|
+
}
|
|
250
|
+
for (id value in handlers) {
|
|
251
|
+
void (^handler)(long long, long long) =
|
|
252
|
+
(void (^)(long long, long long))value;
|
|
253
|
+
handler(received, total);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
static void PushyFinishDownload(NSString *hash, NSError *error) {
|
|
258
|
+
NSMutableDictionary *downloads = PushyInFlightDownloads();
|
|
259
|
+
NSArray *callbacks = nil;
|
|
260
|
+
NSArray *deferred = nil;
|
|
261
|
+
@synchronized (downloads) {
|
|
262
|
+
callbacks = [downloads[hash][@"callbacks"] copy];
|
|
263
|
+
deferred = [downloads[hash][@"deferred"] copy];
|
|
264
|
+
[downloads removeObjectForKey:hash];
|
|
265
|
+
}
|
|
266
|
+
// Establish the next (different-type) owner before waking the completed
|
|
267
|
+
// owner's callbacks; otherwise its strategy loop could race the waiter.
|
|
268
|
+
for (id value in deferred) {
|
|
269
|
+
void (^start)(void) = (void (^)(void))value;
|
|
270
|
+
start();
|
|
271
|
+
}
|
|
272
|
+
for (id value in callbacks) {
|
|
273
|
+
void (^callback)(NSError *) = (void (^)(NSError *))value;
|
|
274
|
+
callback(error);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Everything under rctpushy is re-downloadable, and Application Support is
|
|
279
|
+
// backed up to iCloud by default — Apple requires such content to be
|
|
280
|
+
// excluded from backups.
|
|
281
|
+
static void PushyExcludeFromBackup(NSString *path) {
|
|
282
|
+
#if !TARGET_OS_TV // tvOS stores under Caches, which is never backed up.
|
|
283
|
+
NSURL *url = [NSURL fileURLWithPath:path isDirectory:YES];
|
|
284
|
+
NSError *error = nil;
|
|
285
|
+
if (![url setResourceValue:@YES
|
|
286
|
+
forKey:NSURLIsExcludedFromBackupKey
|
|
287
|
+
error:&error]) {
|
|
288
|
+
RCTLogWarn(@"Pushy exclude from backup error: %@", error.localizedDescription);
|
|
289
|
+
}
|
|
290
|
+
#endif
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
static BOOL PushyEnsureDirectoryExists(NSString *path) {
|
|
294
|
+
// No file-queue hop here: that queue also runs multi-second unzip/patch
|
|
295
|
+
// work, and a dispatch_sync onto it would block the whole module method
|
|
296
|
+
// queue for the duration. createDirectoryAtPath is idempotent and
|
|
297
|
+
// thread-safe, so checking inline is fine.
|
|
298
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
299
|
+
BOOL isDirectory = NO;
|
|
300
|
+
if ([fileManager fileExistsAtPath:path isDirectory:&isDirectory]) {
|
|
301
|
+
if (isDirectory) {
|
|
302
|
+
// Directories created by older versions never got the flag.
|
|
303
|
+
PushyExcludeFromBackup(path);
|
|
304
|
+
}
|
|
305
|
+
return isDirectory;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
NSError *error = nil;
|
|
309
|
+
BOOL success = [fileManager createDirectoryAtPath:path
|
|
310
|
+
withIntermediateDirectories:YES
|
|
311
|
+
attributes:nil
|
|
312
|
+
error:&error];
|
|
313
|
+
if (!success && error != nil) {
|
|
314
|
+
RCTLogWarn(@"Pushy create directory error: %@", error.localizedDescription);
|
|
315
|
+
}
|
|
316
|
+
if (success) {
|
|
317
|
+
PushyExcludeFromBackup(path);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return success;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
@implementation RCTPushyInstaller
|
|
324
|
+
|
|
325
|
+
+ (void)fetch:(PushyType)type
|
|
326
|
+
options:(NSDictionary *)options
|
|
327
|
+
progress:(void (^)(long long, long long))progress
|
|
328
|
+
callback:(void (^)(NSError *error))callback
|
|
329
|
+
{
|
|
330
|
+
NSString *updateUrl = PushyOptionString(options, PushyOptionUrlKey());
|
|
331
|
+
NSString *hash = PushyOptionString(options, @"hash");
|
|
332
|
+
|
|
333
|
+
if (PushyStringIsBlank(updateUrl) || !PushyIsSafePathComponent(hash)) {
|
|
334
|
+
callback(PushyErrorWithCode(pushy::error_codes::kInvalidOptions, ERROR_OPTIONS));
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
NSString *originHash = PushyOptionString(options, @"originHash");
|
|
338
|
+
if (type == PushyTypePatchFromPpk && !PushyIsSafePathComponent(originHash)) {
|
|
339
|
+
callback(PushyErrorWithCode(pushy::error_codes::kInvalidOptions, ERROR_OPTIONS));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
NSString *dir = [RCTPushy downloadDir];
|
|
344
|
+
if (!PushyEnsureDirectoryExists(dir)) {
|
|
345
|
+
callback(PushyErrorWithCode(pushy::error_codes::kFileOperationFailed, ERROR_FILE_OPERATION));
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (PushyHasCompletedVersionAtPath(PushyVersionDir(hash), hash)) {
|
|
350
|
+
callback(nil);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
NSTimeInterval deadlineUptime = PushyMonotonicNow() + 600;
|
|
355
|
+
NSNumber *configuredDeadline = options[@"deadlineUptime"];
|
|
356
|
+
if ([configuredDeadline isKindOfClass:[NSNumber class]]) {
|
|
357
|
+
deadlineUptime = configuredDeadline.doubleValue;
|
|
358
|
+
}
|
|
359
|
+
NSError *(^deadlineExpired)(void) = ^NSError * {
|
|
360
|
+
return PushyErrorWithCode(pushy::error_codes::kDownloadFailed,
|
|
361
|
+
@"download deadline expired before start");
|
|
362
|
+
};
|
|
363
|
+
if (deadlineUptime <= PushyMonotonicNow()) {
|
|
364
|
+
callback(deadlineExpired());
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
void (^deferredStart)(void) = ^{
|
|
369
|
+
[self fetch:type options:options progress:progress callback:callback];
|
|
370
|
+
};
|
|
371
|
+
PushyDownloadRegistration registration = PushyRegisterDownload(
|
|
372
|
+
hash, type, deadlineUptime, callback, progress, deferredStart);
|
|
373
|
+
if (registration != PushyDownloadRegistrationOwner) {
|
|
374
|
+
RCTLogInfo(
|
|
375
|
+
@"RCTPushy -- %@ in-flight download for %@",
|
|
376
|
+
registration == PushyDownloadRegistrationJoined ? @"join" : @"defer",
|
|
377
|
+
hash);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
NSString *zipFilePath = [dir stringByAppendingPathComponent:
|
|
382
|
+
[hash stringByAppendingString:PushyArchiveExtension(type)]];
|
|
383
|
+
// SHA-256 of the downloaded archive for the completion record. Written and
|
|
384
|
+
// read on the serial file queue only: taken before the unzip consumes the
|
|
385
|
+
// file, consumed by the completion block after it.
|
|
386
|
+
__block NSString *artifactSha256 = nil;
|
|
387
|
+
|
|
388
|
+
// Settles every JS/native waiter only after the staging directory was
|
|
389
|
+
// promoted (or dropped) on the process-wide file queue. On failure only
|
|
390
|
+
// the staging directory is removed, like Android/Harmony: a half-unzipped
|
|
391
|
+
// or half-patched dir leaks disk and could later be mistaken for a
|
|
392
|
+
// complete version, while the final version directory is never touched
|
|
393
|
+
// by a failed install. hash is validated above, so this can never resolve
|
|
394
|
+
// to the download root itself.
|
|
395
|
+
void (^completion)(NSError *) = ^(NSError *error) {
|
|
396
|
+
dispatch_async(PushyFileQueue(), ^{
|
|
397
|
+
NSError *finalError = error ?: PushyPromoteStagingDir(hash, artifactSha256);
|
|
398
|
+
if (finalError != nil) {
|
|
399
|
+
[[NSFileManager defaultManager] removeItemAtPath:PushyStagingDir(hash) error:nil];
|
|
400
|
+
}
|
|
401
|
+
PushyFinishDownload(hash, finalError);
|
|
402
|
+
});
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
RCTLogInfo(@"RCTPushy -- download file %@", updateUrl);
|
|
406
|
+
NSTimeInterval timeoutSeconds = deadlineUptime - PushyMonotonicNow();
|
|
407
|
+
if (timeoutSeconds <= 0) {
|
|
408
|
+
completion(deadlineExpired());
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
[RCTPushyDownloader download:updateUrl
|
|
412
|
+
savePath:zipFilePath
|
|
413
|
+
timeoutInterval:timeoutSeconds
|
|
414
|
+
progressHandler:^(long long receivedBytes, long long totalBytes) {
|
|
415
|
+
PushyReportDownloadProgress(hash, receivedBytes, totalBytes);
|
|
416
|
+
} completionHandler:^(NSString *path, NSError *error) {
|
|
417
|
+
if (error != nil) {
|
|
418
|
+
completion(error);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
dispatch_async(PushyFileQueue(), ^{
|
|
422
|
+
artifactSha256 = PushyFromStdString(
|
|
423
|
+
pushy::digest::Sha256File(PushyToStdString(zipFilePath)));
|
|
424
|
+
});
|
|
425
|
+
[self unzipDownloadedPackage:zipFilePath
|
|
426
|
+
hash:hash
|
|
427
|
+
type:type
|
|
428
|
+
originHash:originHash
|
|
429
|
+
callback:completion];
|
|
430
|
+
}];
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
+ (void)unzipDownloadedPackage:(NSString *)zipFilePath
|
|
434
|
+
hash:(NSString *)hash
|
|
435
|
+
type:(PushyType)type
|
|
436
|
+
originHash:(NSString *)originHash
|
|
437
|
+
callback:(void (^)(NSError *error))callback
|
|
438
|
+
{
|
|
439
|
+
RCTLogInfo(@"RCTPushy -- unzip file %@", zipFilePath);
|
|
440
|
+
// Everything lands in <hash>.staging; the completion block promotes it.
|
|
441
|
+
[self unzipFileAtPath:zipFilePath
|
|
442
|
+
toDestination:PushyStagingDir(hash)
|
|
443
|
+
completionHandler:^(NSError *error) {
|
|
444
|
+
dispatch_async(PushyFileQueue(), ^{
|
|
445
|
+
if (error != nil) {
|
|
446
|
+
callback(error);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
@try {
|
|
450
|
+
[self finishDownloadedPackage:hash type:type originHash:originHash callback:callback];
|
|
451
|
+
} @catch (NSException *exception) {
|
|
452
|
+
// An uncaught exception in a GCD block is fatal (and would
|
|
453
|
+
// trip the crash rescue); a bad package is a PATCH_FAILED.
|
|
454
|
+
callback(PushyErrorWithCode(pushy::error_codes::kDeltaFailed,
|
|
455
|
+
exception.reason ?: @"delta failed"));
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
+ (void)finishDownloadedPackage:(NSString *)hash
|
|
462
|
+
type:(PushyType)type
|
|
463
|
+
originHash:(NSString *)originHash
|
|
464
|
+
callback:(void (^)(NSError *error))callback
|
|
465
|
+
{
|
|
466
|
+
switch (type) {
|
|
467
|
+
case PushyTypePatchFromPackage:
|
|
468
|
+
callback([self applyDeltaForHash:hash
|
|
469
|
+
type:type
|
|
470
|
+
fromBundle:[[RCTPushy binaryBundleURL] path]
|
|
471
|
+
source:[[NSBundle mainBundle] resourcePath]]);
|
|
472
|
+
return;
|
|
473
|
+
case PushyTypePatchFromPpk: {
|
|
474
|
+
NSString *lastVersionDir = PushyVersionDir(originHash);
|
|
475
|
+
callback([self applyDeltaForHash:hash
|
|
476
|
+
type:type
|
|
477
|
+
fromBundle:[lastVersionDir stringByAppendingPathComponent:BUNDLE_FILE_NAME]
|
|
478
|
+
source:lastVersionDir]);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
case PushyTypeFullDownload:
|
|
482
|
+
callback(nil);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Applies the unpacked patch in the staging directory (two-phase install);
|
|
488
|
+
// nil on success, a PATCH_FAILED error otherwise. __diff.json is handed to
|
|
489
|
+
// the patch core as text (cpp/patch_core/patch_manifest.h parses it).
|
|
490
|
+
+ (NSError *)applyDeltaForHash:(NSString *)hash
|
|
491
|
+
type:(PushyType)type
|
|
492
|
+
fromBundle:(NSString *)bundleOrigin
|
|
493
|
+
source:(NSString *)sourceOrigin
|
|
494
|
+
{
|
|
495
|
+
NSString *unzipDir = PushyStagingDir(hash);
|
|
496
|
+
NSString *sourcePatch = [unzipDir stringByAppendingPathComponent:SOURCE_PATCH_NAME];
|
|
497
|
+
NSString *bundlePatch = [unzipDir stringByAppendingPathComponent:BUNDLE_PATCH_NAME()];
|
|
498
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
499
|
+
|
|
500
|
+
pushy::archive_patch::ArchivePatchRequest request;
|
|
501
|
+
request.archive.type = type == PushyTypePatchFromPackage
|
|
502
|
+
? pushy::archive_patch::ArchivePatchType::kPatchFromPackage
|
|
503
|
+
: pushy::archive_patch::ArchivePatchType::kPatchFromPpk;
|
|
504
|
+
if ([fileManager fileExistsAtPath:sourcePatch isDirectory:NULL]) {
|
|
505
|
+
long long manifestBytes = RCTPushyFileSize(sourcePatch);
|
|
506
|
+
if (manifestBytes > pushy::archive_limits::kMaxManifestBytes) {
|
|
507
|
+
return PushyErrorWithCode(pushy::error_codes::kDeltaFailed,
|
|
508
|
+
[NSString stringWithFormat:@"delta manifest too large: %lld bytes", manifestBytes]);
|
|
509
|
+
}
|
|
510
|
+
NSData *data = [NSData dataWithContentsOfFile:sourcePatch];
|
|
511
|
+
if (data == nil) {
|
|
512
|
+
return PushyErrorWithCode(pushy::error_codes::kDeltaFailed, @"unreadable delta manifest");
|
|
513
|
+
}
|
|
514
|
+
request.archive.entry_names.push_back(PushyToStdString(SOURCE_PATCH_NAME));
|
|
515
|
+
request.archive.manifest_json.assign(static_cast<const char *>(data.bytes), data.length);
|
|
516
|
+
}
|
|
517
|
+
if ([fileManager fileExistsAtPath:bundlePatch isDirectory:NULL]) {
|
|
518
|
+
request.archive.entry_names.push_back(PushyToStdString(BUNDLE_PATCH_NAME()));
|
|
519
|
+
}
|
|
520
|
+
request.source_root = PushyToStdString(sourceOrigin);
|
|
521
|
+
request.target_root = PushyToStdString(unzipDir);
|
|
522
|
+
request.origin_bundle_path = PushyToStdString(bundleOrigin);
|
|
523
|
+
request.bundle_patch_path = PushyToStdString(bundlePatch);
|
|
524
|
+
request.bundle_output_path = PushyToStdString(
|
|
525
|
+
[unzipDir stringByAppendingPathComponent:BUNDLE_FILE_NAME]);
|
|
526
|
+
|
|
527
|
+
pushy::delta::Status status = pushy::archive_patch::ApplyArchivePatch(request, nullptr);
|
|
528
|
+
if (!status.ok) {
|
|
529
|
+
return PushyErrorWithCode(pushy::error_codes::kDeltaFailed, @(status.message.c_str()));
|
|
530
|
+
}
|
|
531
|
+
return nil;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
+ (void)unzipFileAtPath:(NSString *)path
|
|
535
|
+
toDestination:(NSString *)destination
|
|
536
|
+
completionHandler:(void (^)(NSError *error))completionHandler
|
|
537
|
+
{
|
|
538
|
+
dispatch_async(PushyFileQueue(), ^{
|
|
539
|
+
NSFileManager *fileManager = [NSFileManager defaultManager];
|
|
540
|
+
if ([fileManager fileExistsAtPath:destination]) {
|
|
541
|
+
[fileManager removeItemAtPath:destination error:nil];
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Resource caps before the first byte is extracted
|
|
545
|
+
// (cpp/patch_core/archive_limits.h). SSZipArchive only exposes the
|
|
546
|
+
// entry count up front (zipArchiveWillUnzipArchiveAtPath:zipInfo:),
|
|
547
|
+
// not the uncompressed total, so the disk check uses a 2x-archive
|
|
548
|
+
// heuristic; the guard enforces the exact caps entry by entry.
|
|
549
|
+
long long archiveBytes = RCTPushyFileSize(path);
|
|
550
|
+
NSError *preflight = nil;
|
|
551
|
+
if (archiveBytes > pushy::archive_limits::kMaxArchiveBytes) {
|
|
552
|
+
preflight = PushyErrorWithCode(pushy::error_codes::kDeltaFailed,
|
|
553
|
+
[NSString stringWithFormat:@"archive too large: %lld bytes", archiveBytes]);
|
|
554
|
+
} else {
|
|
555
|
+
preflight = PushyEnsureFreeSpace(destination, MAX(0LL, archiveBytes) * 2);
|
|
556
|
+
}
|
|
557
|
+
if (preflight != nil) {
|
|
558
|
+
RCTPushyDiscardArchive(path);
|
|
559
|
+
completionHandler(preflight);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
PushyUnzipGuard *guard = [[PushyUnzipGuard alloc] initWithArchiveBytes:archiveBytes
|
|
564
|
+
destination:destination];
|
|
565
|
+
// preserveAttributes:NO — the archive's mode/mtime bits have no use
|
|
566
|
+
// here and are one less thing a hostile package controls.
|
|
567
|
+
[SSZipArchive unzipFileAtPath:path
|
|
568
|
+
toDestination:destination
|
|
569
|
+
preserveAttributes:NO
|
|
570
|
+
overwrite:YES
|
|
571
|
+
nestedZipLevel:0
|
|
572
|
+
password:nil
|
|
573
|
+
error:nil
|
|
574
|
+
delegate:guard
|
|
575
|
+
progressHandler:nil
|
|
576
|
+
completionHandler:^(NSString *archivePath, BOOL succeeded, NSError *error) {
|
|
577
|
+
// The resume sidecar dies with its archive (§11.4): whether the
|
|
578
|
+
// unzip consumed it or classified it as poisoned, nothing must
|
|
579
|
+
// survive to vouch for bytes that are gone.
|
|
580
|
+
RCTPushyDiscardArchive(archivePath);
|
|
581
|
+
|
|
582
|
+
NSError *unzipError = error;
|
|
583
|
+
if (guard.violation != nil) {
|
|
584
|
+
unzipError = PushyErrorWithCode(pushy::error_codes::kDeltaFailed, guard.violation);
|
|
585
|
+
} else if (!succeeded && unzipError == nil) {
|
|
586
|
+
unzipError = PushyErrorWithCode(pushy::error_codes::kDeltaFailed, @"unzip failed");
|
|
587
|
+
} else if (unzipError != nil && unzipError.userInfo[RCTPushyErrorCodeKey] == nil) {
|
|
588
|
+
// SSZipArchive's own NSError (corrupt zip, bad magic, ...) has
|
|
589
|
+
// no stable code; without one, fetchPackage's fallback would
|
|
590
|
+
// classify it as DOWNLOAD_FAILED even though the download
|
|
591
|
+
// succeeded — keep the classification deterministic.
|
|
592
|
+
unzipError = PushyErrorWithCode(pushy::error_codes::kDeltaFailed,
|
|
593
|
+
unzipError.localizedDescription ?: @"unzip failed");
|
|
594
|
+
}
|
|
595
|
+
completionHandler(unzipError);
|
|
596
|
+
}];
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
+ (BOOL)switchVersion:(NSString *)hash error:(NSError **)error
|
|
601
|
+
{
|
|
602
|
+
if (!PushyIsSafePathComponent(hash)) {
|
|
603
|
+
if (error != NULL) {
|
|
604
|
+
*error = PushyErrorWithCode(pushy::error_codes::kInvalidOptions, ERROR_OPTIONS);
|
|
605
|
+
}
|
|
606
|
+
return NO;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Same gate as Android and Harmony (install_record::CheckActivation):
|
|
610
|
+
// only a version this SDK recorded as completely installed, whose
|
|
611
|
+
// record's bundle digest matches the bytes on disk, may be activated.
|
|
612
|
+
// Versions activated before records existed are grandfathered through
|
|
613
|
+
// current/last state, but only while their directory has no record file.
|
|
614
|
+
std::string versionDir = PushyToStdString(PushyVersionDir(hash));
|
|
615
|
+
std::string hashStd = PushyToStdString(hash);
|
|
616
|
+
std::string bundleName = PushyToStdString(BUNDLE_FILE_NAME);
|
|
617
|
+
BOOL (^isCurrentOrLast)(void) = ^BOOL {
|
|
618
|
+
pushy::state::State state = PushyStateFromDefaults(PushyDefaults());
|
|
619
|
+
return state.current_version == hashStd || state.last_version == hashStd;
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
// The digest check reads the whole bundle, so it runs OUTSIDE the state
|
|
623
|
+
// lock: +bundleURL takes that lock on the main thread at launch.
|
|
624
|
+
__block BOOL grandfathered = NO;
|
|
625
|
+
PushyWithStateLock(^{
|
|
626
|
+
grandfathered = isCurrentOrLast();
|
|
627
|
+
});
|
|
628
|
+
pushy::install_record::Activation activation;
|
|
629
|
+
pushy::install_record::Status verified = pushy::install_record::CheckActivation(
|
|
630
|
+
versionDir, hashStd, bundleName, grandfathered, &activation);
|
|
631
|
+
|
|
632
|
+
__block NSError *lockedError = verified.ok
|
|
633
|
+
? nil
|
|
634
|
+
: PushyErrorWithCode(pushy::error_codes::kSwitchVersionFailed,
|
|
635
|
+
PushyFromStdString(verified.message));
|
|
636
|
+
if (lockedError == nil) {
|
|
637
|
+
PushyWithStateLock(^{
|
|
638
|
+
// Re-check cheaply that the directory is still the one that was
|
|
639
|
+
// verified: same grandfathering, same record, same bundle file.
|
|
640
|
+
if (!pushy::install_record::Unchanged(versionDir, bundleName, isCurrentOrLast(),
|
|
641
|
+
activation)) {
|
|
642
|
+
lockedError = PushyErrorWithCode(
|
|
643
|
+
pushy::error_codes::kSwitchVersionFailed,
|
|
644
|
+
[NSString stringWithFormat:@"Bundle version %@ changed while switching.", hash]);
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
PushySwitchVersionLocked(hash);
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
if (lockedError != nil) {
|
|
651
|
+
if (error != NULL) {
|
|
652
|
+
*error = lockedError;
|
|
653
|
+
}
|
|
654
|
+
return NO;
|
|
655
|
+
}
|
|
656
|
+
return YES;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
+ (void)clearInvalidFiles
|
|
660
|
+
{
|
|
661
|
+
dispatch_async(PushyFileQueue(), ^{
|
|
662
|
+
// Snapshot the state under the lock, but run the (slow) filesystem
|
|
663
|
+
// cleanup outside of it so state operations are not blocked.
|
|
664
|
+
__block pushy::state::State state;
|
|
665
|
+
__block NSString *launchVersion = nil;
|
|
666
|
+
PushyWithStateLock(^{
|
|
667
|
+
state = PushyStateFromDefaults(PushyDefaults());
|
|
668
|
+
launchVersion = pushyLaunchVersion;
|
|
669
|
+
});
|
|
670
|
+
// Keep the persisted current/last versions AND the version this
|
|
671
|
+
// process booted from: two switches without a restart would otherwise
|
|
672
|
+
// evict the running bundle while its on-demand assets are still
|
|
673
|
+
// being served.
|
|
674
|
+
pushy::delta::Status status = pushy::delta::CleanupOldEntries(
|
|
675
|
+
PushyToStdString([RCTPushy downloadDir]),
|
|
676
|
+
std::vector<std::string>{
|
|
677
|
+
state.current_version,
|
|
678
|
+
state.last_version,
|
|
679
|
+
PushyToStdString(launchVersion),
|
|
680
|
+
},
|
|
681
|
+
3
|
|
682
|
+
);
|
|
683
|
+
if (!status.ok) {
|
|
684
|
+
RCTLogWarn(@"Pushy cleanup error: %s", status.message.c_str());
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
@end
|