Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit 3d0f624

Browse files
committed
Merge pull request #187 from Microsoft/sync-twice-issue
Sync Twice issue
2 parents 21a4394 + b342b81 commit 3d0f624

File tree

7 files changed

+87
-23
lines changed

7 files changed

+87
-23
lines changed

CodePush.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ failCallback:(void (^)(NSError *err))failCallback;
6969
@interface CodePushPackage : NSObject
7070

7171
+ (void)installPackage:(NSDictionary *)updatePackage
72+
removePendingUpdate:(BOOL)removePendingUpdate
7273
error:(NSError **)error;
7374

7475
+ (NSDictionary *)getCurrentPackage:(NSError **)error;

CodePush.js

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,20 @@ function log(message) {
139139
console.log(`[CodePush] ${message}`)
140140
}
141141

142-
async function notifyApplicationReady() {
142+
// This ensures that notifyApplicationReadyInternal is only called once
143+
// in the lifetime of this module instance.
144+
const notifyApplicationReady = (() => {
145+
let notifyApplicationReadyPromise;
146+
return () => {
147+
if (!notifyApplicationReadyPromise) {
148+
notifyApplicationReadyPromise = notifyApplicationReadyInternal();
149+
}
150+
151+
return notifyApplicationReadyPromise;
152+
};
153+
})();
154+
155+
async function notifyApplicationReadyInternal() {
143156
await NativeCodePush.notifyApplicationReady();
144157
const statusReport = await NativeCodePush.getNewStatusReport();
145158
if (statusReport) {
@@ -170,16 +183,41 @@ function setUpTestDependencies(testSdk, providedTestConfig, testNativeBridge) {
170183
if (testNativeBridge) NativeCodePush = testNativeBridge;
171184
}
172185

186+
// This function allows only one syncInternal operation to proceed at any given time.
187+
// Parallel calls to sync() while one is ongoing yields CodePush.SyncStatus.SYNC_IN_PROGRESS.
188+
const sync = (() => {
189+
let syncInProgress = false;
190+
const setSyncCompleted = () => { syncInProgress = false; };
191+
192+
return (options = {}, syncStatusChangeCallback, downloadProgressCallback) => {
193+
if (syncInProgress) {
194+
typeof syncStatusChangeCallback === "function"
195+
? syncStatusChangeCallback(CodePush.SyncStatus.SYNC_IN_PROGRESS)
196+
: log("Sync already in progress.");
197+
return Promise.resolve(CodePush.SyncStatus.SYNC_IN_PROGRESS);
198+
}
199+
200+
syncInProgress = true;
201+
const syncPromise = syncInternal(options, syncStatusChangeCallback, downloadProgressCallback);
202+
syncPromise
203+
.then(setSyncCompleted)
204+
.catch(setSyncCompleted)
205+
.done();
206+
207+
return syncPromise;
208+
};
209+
})();
210+
173211
/*
174-
* The sync method provides a simple, one-line experience for
212+
* The syncInternal method provides a simple, one-line experience for
175213
* incorporating the check, download and application of an update.
176214
*
177215
* It simply composes the existing API methods together and adds additional
178216
* support for respecting mandatory updates, ignoring previously failed
179217
* releases, and displaying a standard confirmation UI to the end-user
180218
* when an update is available.
181219
*/
182-
async function sync(options = {}, syncStatusChangeCallback, downloadProgressCallback) {
220+
async function syncInternal(options = {}, syncStatusChangeCallback, downloadProgressCallback) {
183221
const syncOptions = {
184222

185223
deploymentKey: null,
@@ -190,7 +228,7 @@ async function sync(options = {}, syncStatusChangeCallback, downloadProgressCall
190228
...options
191229
};
192230

193-
syncStatusChangeCallback = typeof syncStatusChangeCallback == "function"
231+
syncStatusChangeCallback = typeof syncStatusChangeCallback === "function"
194232
? syncStatusChangeCallback
195233
: (syncStatus) => {
196234
switch(syncStatus) {
@@ -229,7 +267,7 @@ async function sync(options = {}, syncStatusChangeCallback, downloadProgressCall
229267
}
230268
};
231269

232-
downloadProgressCallback = typeof downloadProgressCallback == "function"
270+
downloadProgressCallback = typeof downloadProgressCallback === "function"
233271
? downloadProgressCallback
234272
: (downloadProgress) => {
235273
log(`Expecting ${downloadProgress.totalBytes} bytes, received ${downloadProgress.receivedBytes} bytes.`);
@@ -338,6 +376,7 @@ const CodePush = {
338376
UP_TO_DATE: 4, // The running app is up-to-date
339377
UPDATE_IGNORED: 5, // The app had an optional update and the end-user chose to ignore it
340378
UPDATE_INSTALLED: 6, // The app had an optional/mandatory update that was successfully downloaded and is about to be installed.
379+
SYNC_IN_PROGRESS: 7, // There is an ongoing "sync" operation in progress.
341380
UNKNOWN_ERROR: -1
342381
},
343382
DEFAULT_UPDATE_DIALOG: {

CodePush.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ - (void)savePendingUpdate:(NSString *)packageHash
480480
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
481481
NSError *error;
482482
[CodePushPackage installPackage:updatePackage
483+
removePendingUpdate:[self isPendingUpdate:nil]
483484
error:&error];
484485

485486
if (error) {

CodePushPackage.m

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,8 @@ + (void)copyEntriesInFolder:(NSString *)sourceFolder
464464
}
465465

466466
+ (void)installPackage:(NSDictionary *)updatePackage
467-
error:(NSError **)error
467+
removePendingUpdate:(BOOL)removePendingUpdate
468+
error:(NSError **)error
468469
{
469470
NSString *packageHash = updatePackage[@"packageHash"];
470471
NSMutableDictionary *info = [self getCurrentPackageInfo:error];
@@ -473,19 +474,32 @@ + (void)installPackage:(NSDictionary *)updatePackage
473474
return;
474475
}
475476

476-
NSString *previousPackageHash = [self getPreviousPackageHash:error];
477-
if (!*error && previousPackageHash && ![previousPackageHash isEqualToString:packageHash]) {
478-
NSString *previousPackageFolderPath = [self getPackageFolderPath:previousPackageHash];
479-
// Error in deleting old package will not cause the entire operation to fail.
480-
NSError *deleteError;
481-
[[NSFileManager defaultManager] removeItemAtPath:previousPackageFolderPath
482-
error:&deleteError];
483-
if (deleteError) {
484-
NSLog(@"Error deleting old package: %@", deleteError);
477+
if (removePendingUpdate) {
478+
NSString *currentPackageFolderPath = [self getCurrentPackageFolderPath:error];
479+
if (!*error && currentPackageFolderPath) {
480+
// Error in deleting pending package will not cause the entire operation to fail.
481+
NSError *deleteError;
482+
[[NSFileManager defaultManager] removeItemAtPath:currentPackageFolderPath
483+
error:&deleteError];
484+
if (deleteError) {
485+
NSLog(@"Error deleting pending package: %@", deleteError);
486+
}
487+
}
488+
} else {
489+
NSString *previousPackageHash = [self getPreviousPackageHash:error];
490+
if (!*error && previousPackageHash && ![previousPackageHash isEqualToString:packageHash]) {
491+
NSString *previousPackageFolderPath = [self getPackageFolderPath:previousPackageHash];
492+
// Error in deleting old package will not cause the entire operation to fail.
493+
NSError *deleteError;
494+
[[NSFileManager defaultManager] removeItemAtPath:previousPackageFolderPath
495+
error:&deleteError];
496+
if (deleteError) {
497+
NSLog(@"Error deleting old package: %@", deleteError);
498+
}
485499
}
500+
[info setValue:info[@"currentPackage"] forKey:@"previousPackage"];
486501
}
487502

488-
[info setValue:info[@"currentPackage"] forKey:@"previousPackage"];
489503
[info setValue:packageHash forKey:@"currentPackage"];
490504

491505
[self updateCurrentPackageInfo:info

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ This method returns a `Promise` which is resolved to a `SyncStatus` code that in
554554
555555
* __codePush.SyncStatus.UPDATE_INSTALLED__ *(6)* - The update has been installed and will be run either immediately after the `syncStatusChangedCallback` function returns or the next time the app resumes/restarts, depending on the `InstallMode` specified in `SyncOptions`.
556556
557-
If the update check and/or the subsequent download fails for any reason, the `Promise` object returned by `sync` will be rejected with the reason.
557+
* __codePush.SyncStatus.SYNC_IN_PROGRESS__ *(7)* - There is an ongoing `sync` operation running which prevents the current call from being executed.
558558
559559
The `sync` method can be called anywhere you'd like to check for an update. That could be in the `componentWillMount` lifecycle event of your root component, the onPress handler of a `<TouchableHighlight>` component, in the callback of a periodic timer, or whatever else makes sense for your needs. Just like the `checkForUpdate` method, it will perform the network request to check for an update in the background, so it won't impact your UI thread and/or JavaScript thread's responsiveness.
560560
@@ -622,6 +622,7 @@ This enum is provided to the `syncStatusChangedCallback` function that can be pa
622622
* __codePush.SyncStatus.UP_TO_DATE__ *(4)* - The app is fully up-to-date with the configured deployment.
623623
* __codePush.SyncStatus.UPDATE_IGNORED__ *(5)* - The app has an optional update, which the end user chose to ignore. (This is only applicable when the `updateDialog` is used)
624624
* __codePush.SyncStatus.UPDATE_INSTALLED__ *(6)* - An available update has been installed and will be run either immediately after the `syncStatusChangedCallback` function returns or the next time the app resumes/restarts, depending on the `InstallMode` specified in `SyncOptions`.
625+
* __codePush.SyncStatus.SYNC_IN_PROGRESS__ *(7)* - There is an ongoing `sync` operation running which prevents the current call from being executed.
625626
* __codePush.SyncStatus.UNKNOWN_ERROR__ *(-1)* - The sync operation encountered an unknown error.
626627
627628
### Objective-C API Reference (iOS)

android/app/src/main/java/com/microsoft/codepush/react/CodePush.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,7 @@ public void installUpdate(final ReadableMap updatePackage, final int installMode
481481
@Override
482482
protected Void doInBackground(Object... params) {
483483
try {
484-
codePushPackage.installPackage(updatePackage);
484+
codePushPackage.installPackage(updatePackage, isPendingUpdate(null));
485485

486486
String pendingHash = CodePushUtils.tryGetString(updatePackage, codePushPackage.PACKAGE_HASH_KEY);
487487
if (pendingHash == null) {

android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -259,15 +259,23 @@ public void downloadPackage(Context applicationContext, ReadableMap updatePackag
259259
CodePushUtils.writeReadableMapToFile(updatePackage, bundlePath);
260260
}
261261

262-
public void installPackage(ReadableMap updatePackage) throws IOException {
262+
public void installPackage(ReadableMap updatePackage, boolean removePendingUpdate) throws IOException {
263263
String packageHash = CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY);
264264
WritableMap info = getCurrentPackageInfo();
265-
String previousPackageHash = getPreviousPackageHash();
266-
if (previousPackageHash != null && !previousPackageHash.equals(packageHash)) {
267-
FileUtils.deleteDirectoryAtPath(getPackageFolderPath(previousPackageHash));
265+
if (removePendingUpdate) {
266+
String currentPackageFolderPath = getCurrentPackageFolderPath();
267+
if (currentPackageFolderPath != null) {
268+
FileUtils.deleteDirectoryAtPath(currentPackageFolderPath);
269+
}
270+
} else {
271+
String previousPackageHash = getPreviousPackageHash();
272+
if (previousPackageHash != null && !previousPackageHash.equals(packageHash)) {
273+
FileUtils.deleteDirectoryAtPath(getPackageFolderPath(previousPackageHash));
274+
}
275+
276+
info.putString(PREVIOUS_PACKAGE_KEY, CodePushUtils.tryGetString(info, CURRENT_PACKAGE_KEY));
268277
}
269278

270-
info.putString(PREVIOUS_PACKAGE_KEY, CodePushUtils.tryGetString(info, CURRENT_PACKAGE_KEY));
271279
info.putString(CURRENT_PACKAGE_KEY, packageHash);
272280
updateCurrentPackageInfo(info);
273281
}

0 commit comments

Comments
 (0)