mirror of
https://github.com/zhigang1992/react-native-code-push.git
synced 2026-06-19 18:13:46 +08:00
Merge pull request #49 from Microsoft/download-progress
Download progress
This commit is contained in:
23
CodePush.h
23
CodePush.h
@@ -29,6 +29,24 @@
|
||||
|
||||
@end
|
||||
|
||||
@interface CodePushDownloadHandler : NSObject<NSURLConnectionDelegate>
|
||||
|
||||
@property (strong) NSOutputStream *outputFileStream;
|
||||
@property long expectedContentLength;
|
||||
@property long receivedContentLength;
|
||||
@property (copy) void (^progressCallback)(long, long);
|
||||
@property (copy) void (^doneCallback)();
|
||||
@property (copy) void (^failCallback)(NSError *err);
|
||||
|
||||
- (id)init:(NSString *)downloadFilePath
|
||||
progressCallback:(void (^)(long, long))progressCallback
|
||||
doneCallback:(void (^)())doneCallback
|
||||
failCallback:(void (^)(NSError *err))failCallback;
|
||||
|
||||
- (void)download:(NSString*)url;
|
||||
|
||||
@end
|
||||
|
||||
@interface CodePushPackage : NSObject
|
||||
|
||||
+ (void)applyPackage:(NSDictionary *)updatePackage
|
||||
@@ -36,6 +54,7 @@
|
||||
|
||||
+ (NSDictionary *)getCurrentPackage:(NSError **)error;
|
||||
+ (NSString *)getCurrentPackageFolderPath:(NSError **)error;
|
||||
+ (NSString *)getCurrentPackageBundlePath:(NSError **)error;
|
||||
+ (NSString *)getCurrentPackageHash:(NSError **)error;
|
||||
|
||||
+ (NSDictionary *)getPackage:(NSString *)packageHash
|
||||
@@ -45,7 +64,9 @@
|
||||
|
||||
|
||||
+ (void)downloadPackage:(NSDictionary *)updatePackage
|
||||
error:(NSError **)error;
|
||||
progressCallback:(void (^)(long, long))progressCallback
|
||||
doneCallback:(void (^)())doneCallback
|
||||
failCallback:(void (^)(NSError *err))failCallback;
|
||||
|
||||
+ (void)rollbackPackage;
|
||||
|
||||
|
||||
50
CodePush.m
50
CodePush.m
@@ -1,4 +1,5 @@
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTRootView.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "CodePush.h"
|
||||
@@ -13,7 +14,6 @@ BOOL didUpdate = NO;
|
||||
|
||||
NSString * const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES";
|
||||
NSString * const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE";
|
||||
NSString * const UpdateBundleFileName = @"app.jsbundle";
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
@@ -27,16 +27,14 @@ NSString * const UpdateBundleFileName = @"app.jsbundle";
|
||||
+ (NSURL *)getBundleUrl
|
||||
{
|
||||
NSError *error;
|
||||
NSString *packageFolder = [CodePushPackage getCurrentPackageFolderPath:&error];
|
||||
NSString *packageFile = [CodePushPackage getCurrentPackageBundlePath:&error];
|
||||
NSURL *binaryJsBundleUrl = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
||||
|
||||
if (error || !packageFolder)
|
||||
if (error || !packageFile)
|
||||
{
|
||||
return binaryJsBundleUrl;
|
||||
}
|
||||
|
||||
NSString *packageFile = [packageFolder stringByAppendingPathComponent:UpdateBundleFileName];
|
||||
|
||||
NSDictionary *binaryFileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[binaryJsBundleUrl path] error:nil];
|
||||
NSDictionary *appFileAttribs = [[NSFileManager defaultManager] attributesOfItemAtPath:packageFile error:nil];
|
||||
NSDate *binaryDate = [binaryFileAttributes objectForKey:NSFileModificationDate];
|
||||
@@ -198,24 +196,30 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject)
|
||||
{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSError *err;
|
||||
[CodePushPackage downloadPackage:updatePackage
|
||||
error:&err];
|
||||
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
NSDictionary *newPackage = [CodePushPackage getPackage:updatePackage[@"packageHash"]
|
||||
error:&err];
|
||||
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(newPackage);
|
||||
});
|
||||
[CodePushPackage downloadPackage:updatePackage
|
||||
progressCallback:^(long expectedContentLength, long receivedContentLength) {
|
||||
[self.bridge.eventDispatcher
|
||||
sendAppEventWithName:@"CodePushDownloadProgress"
|
||||
body:@{
|
||||
@"totalBytes":[NSNumber numberWithLong:expectedContentLength],
|
||||
@"receivedBytes":[NSNumber numberWithLong:receivedContentLength]
|
||||
}];
|
||||
}
|
||||
doneCallback:^{
|
||||
NSError *err;
|
||||
NSDictionary *newPackage = [CodePushPackage
|
||||
getPackage:updatePackage[@"packageHash"]
|
||||
error:&err];
|
||||
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(newPackage);
|
||||
}
|
||||
failCallback:^(NSError *err) {
|
||||
reject(err);
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(getConfiguration:(RCTPromiseResolveBlock)resolve
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
13BE3DEE1AC21097009241FE /* CodePush.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* CodePush.m */; };
|
||||
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */; settings = {ASSET_TAGS = (); }; };
|
||||
810D4E6D1B96935000B397E9 /* CodePushPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 810D4E6C1B96935000B397E9 /* CodePushPackage.m */; };
|
||||
81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 81D51F391B6181C2000DA084 /* CodePushConfig.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
@@ -28,6 +29,7 @@
|
||||
134814201AA4EA6300B7C361 /* libCodePush.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCodePush.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13BE3DEC1AC21097009241FE /* CodePush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CodePush.h; sourceTree = "<group>"; };
|
||||
13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePush.m; sourceTree = "<group>"; };
|
||||
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushDownloadHandler.m; sourceTree = "<group>"; };
|
||||
810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushPackage.m; sourceTree = "<group>"; };
|
||||
81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushConfig.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
@@ -54,6 +56,7 @@
|
||||
58B511D21A9E6C8500147676 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */,
|
||||
810D4E6C1B96935000B397E9 /* CodePushPackage.m */,
|
||||
81D51F391B6181C2000DA084 /* CodePushConfig.m */,
|
||||
13BE3DEC1AC21097009241FE /* CodePush.h */,
|
||||
@@ -119,6 +122,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */,
|
||||
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */,
|
||||
13BE3DEE1AC21097009241FE /* CodePush.m in Sources */,
|
||||
810D4E6D1B96935000B397E9 /* CodePushPackage.m in Sources */,
|
||||
);
|
||||
|
||||
85
CodePushDownloadHandler.m
Normal file
85
CodePushDownloadHandler.m
Normal file
@@ -0,0 +1,85 @@
|
||||
#import "CodePush.h"
|
||||
|
||||
@implementation CodePushDownloadHandler
|
||||
|
||||
- (id)init:(NSString *)downloadFilePath
|
||||
progressCallback:(void (^)(long, long))progressCallback
|
||||
doneCallback:(void (^)())doneCallback
|
||||
failCallback:(void (^)(NSError *err))failCallback {
|
||||
self.outputFileStream = [NSOutputStream outputStreamToFileAtPath:downloadFilePath
|
||||
append:NO];
|
||||
self.receivedContentLength = 0;
|
||||
self.progressCallback = progressCallback;
|
||||
self.doneCallback = doneCallback;
|
||||
self.failCallback = failCallback;
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)download:(NSString*)url {
|
||||
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]
|
||||
cachePolicy:NSURLRequestUseProtocolCachePolicy
|
||||
timeoutInterval:60.0];
|
||||
|
||||
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
|
||||
delegate:self
|
||||
startImmediately:NO];
|
||||
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop]
|
||||
forMode:NSDefaultRunLoopMode];
|
||||
[connection start];
|
||||
}
|
||||
|
||||
#pragma mark NSURLConnection Delegate Methods
|
||||
|
||||
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
|
||||
willCacheResponse:(NSCachedURLResponse*)cachedResponse {
|
||||
// Return nil to indicate not necessary to store a cached response for this connection
|
||||
return nil;
|
||||
}
|
||||
|
||||
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
|
||||
self.expectedContentLength = response.expectedContentLength;
|
||||
[self.outputFileStream open];
|
||||
}
|
||||
|
||||
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
|
||||
self.receivedContentLength = self.receivedContentLength + [data length];
|
||||
|
||||
NSInteger bytesLeft = [data length];
|
||||
|
||||
do {
|
||||
NSInteger bytesWritten = [self.outputFileStream write:[data bytes]
|
||||
maxLength:bytesLeft];
|
||||
if (bytesWritten == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
bytesLeft -= bytesWritten;
|
||||
} while (bytesLeft > 0);
|
||||
|
||||
self.progressCallback(self.expectedContentLength, self.receivedContentLength);
|
||||
|
||||
// bytesLeft should not be negative.
|
||||
assert(bytesLeft >= 0);
|
||||
|
||||
if (bytesLeft) {
|
||||
[self.outputFileStream close];
|
||||
[connection cancel];
|
||||
self.failCallback([self.outputFileStream streamError]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
|
||||
{
|
||||
[self.outputFileStream close];
|
||||
self.failCallback(error);
|
||||
}
|
||||
|
||||
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
|
||||
// We should have received all of the bytes if this is called.
|
||||
assert(self.receivedContentLength == self.expectedContentLength);
|
||||
|
||||
[self.outputFileStream close];
|
||||
self.doneCallback();
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -3,6 +3,7 @@
|
||||
@implementation CodePushPackage
|
||||
|
||||
NSString * const StatusFile = @"codepush.json";
|
||||
NSString * const UpdateBundleFileName = @"app.jsbundle";
|
||||
|
||||
+ (NSString *)getCodePushPath
|
||||
{
|
||||
@@ -72,6 +73,17 @@ NSString * const StatusFile = @"codepush.json";
|
||||
return [self getPackageFolderPath:packageHash];
|
||||
}
|
||||
|
||||
+ (NSString *)getCurrentPackageBundlePath:(NSError **)error
|
||||
{
|
||||
NSString *packageFolder = [self getCurrentPackageFolderPath:error];
|
||||
|
||||
if(*error) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return [packageFolder stringByAppendingPathComponent:UpdateBundleFileName];
|
||||
}
|
||||
|
||||
+ (NSString *)getCurrentPackageHash:(NSError **)error
|
||||
{
|
||||
NSDictionary *info = [self getCurrentPackageInfo:error];
|
||||
@@ -149,50 +161,51 @@ NSString * const StatusFile = @"codepush.json";
|
||||
}
|
||||
|
||||
+ (void)downloadPackage:(NSDictionary *)updatePackage
|
||||
error:(NSError **)error
|
||||
progressCallback:(void (^)(long, long))progressCallback
|
||||
doneCallback:(void (^)())doneCallback
|
||||
failCallback:(void (^)(NSError *err))failCallback
|
||||
{
|
||||
NSString *packageFolderPath = [self getPackageFolderPath:updatePackage[@"packageHash"]];
|
||||
NSError *error = nil;
|
||||
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:packageFolderPath]) {
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:packageFolderPath
|
||||
withIntermediateDirectories:YES
|
||||
attributes:nil
|
||||
error:error];
|
||||
error:&error];
|
||||
}
|
||||
|
||||
if (*error) {
|
||||
return;
|
||||
if (error) {
|
||||
return failCallback(error);
|
||||
}
|
||||
|
||||
NSURL *url = [[NSURL alloc] initWithString:updatePackage[@"downloadUrl"]];
|
||||
NSString *updateContents = [[NSString alloc] initWithContentsOfURL:url
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:error];
|
||||
if (*error) {
|
||||
return;
|
||||
}
|
||||
NSString *downloadFilePath = [packageFolderPath stringByAppendingPathComponent:UpdateBundleFileName];
|
||||
|
||||
[updateContents writeToFile:[packageFolderPath stringByAppendingPathComponent:@"app.jsbundle"]
|
||||
atomically:YES
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:error];
|
||||
if (*error) {
|
||||
return;
|
||||
}
|
||||
CodePushDownloadHandler *downloadHandler = [[CodePushDownloadHandler alloc]
|
||||
init:downloadFilePath
|
||||
progressCallback:progressCallback
|
||||
doneCallback:^{
|
||||
NSError *error;
|
||||
NSData *updateSerializedData = [NSJSONSerialization
|
||||
dataWithJSONObject:updatePackage
|
||||
options:0
|
||||
error:&error];
|
||||
NSString *packageJsonString = [[NSString alloc]
|
||||
initWithData:updateSerializedData encoding:NSUTF8StringEncoding];
|
||||
|
||||
[packageJsonString writeToFile:[packageFolderPath stringByAppendingPathComponent:@"app.json"]
|
||||
atomically:YES
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:&error];
|
||||
if (error) {
|
||||
failCallback(error);
|
||||
} else {
|
||||
doneCallback();
|
||||
}
|
||||
}
|
||||
failCallback:failCallback];
|
||||
|
||||
NSData *updateSerializedData = [NSJSONSerialization dataWithJSONObject:updatePackage
|
||||
options:0
|
||||
error:error];
|
||||
|
||||
if (*error) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *packageJsonString = [[NSString alloc] initWithData:updateSerializedData encoding:NSUTF8StringEncoding];
|
||||
[packageJsonString writeToFile:[packageFolderPath stringByAppendingPathComponent:@"app.json"]
|
||||
atomically:YES
|
||||
encoding:NSUTF8StringEncoding
|
||||
error:error];
|
||||
[downloadHandler download:updatePackage[@"downloadUrl"]];
|
||||
}
|
||||
|
||||
+ (void)applyPackage:(NSDictionary *)updatePackage
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
5451ACBA1B86A5B600E2A7DF /* QueryUpdateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5451ACB81B86A5B600E2A7DF /* QueryUpdateTests.m */; };
|
||||
5451ACEC1B86E40A00E2A7DF /* libRCTTest.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5451ACEB1B86E34300E2A7DF /* libRCTTest.a */; };
|
||||
54D774BA1B87DAF800F2ABF8 /* ApplyUpdateTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54D774B91B87DAF800F2ABF8 /* ApplyUpdateTests.m */; };
|
||||
54F5F2B41BF6B45D007C3CEA /* DownloadProgressTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */; settings = {ASSET_TAGS = (); }; };
|
||||
81551E1B1B3B428000F5B9F1 /* libCodePush.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 81551E0F1B3B427200F5B9F1 /* libCodePush.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -151,6 +152,7 @@
|
||||
5451ACB81B86A5B600E2A7DF /* QueryUpdateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QueryUpdateTests.m; sourceTree = "<group>"; };
|
||||
5451ACE61B86E34300E2A7DF /* RCTTest.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTTest.xcodeproj; path = "node_modules/react-native/Libraries/RCTTest/RCTTest.xcodeproj"; sourceTree = "<group>"; };
|
||||
54D774B91B87DAF800F2ABF8 /* ApplyUpdateTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ApplyUpdateTests.m; sourceTree = "<group>"; };
|
||||
54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DownloadProgressTests.m; sourceTree = "<group>"; };
|
||||
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
|
||||
81551E0A1B3B427200F5B9F1 /* CodePush.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CodePush.xcodeproj; path = ../../CodePush.xcodeproj; sourceTree = "<group>"; };
|
||||
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
|
||||
@@ -238,6 +240,7 @@
|
||||
00E356EF1AD99517003FC87E /* CodePushDemoAppTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
54F5F2B31BF6B45D007C3CEA /* DownloadProgressTests.m */,
|
||||
5451ACB81B86A5B600E2A7DF /* QueryUpdateTests.m */,
|
||||
54D774B91B87DAF800F2ABF8 /* ApplyUpdateTests.m */,
|
||||
00E356F01AD99517003FC87E /* Supporting Files */,
|
||||
@@ -608,6 +611,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5451ACBA1B86A5B600E2A7DF /* QueryUpdateTests.m in Sources */,
|
||||
54F5F2B41BF6B45D007C3CEA /* DownloadProgressTests.m in Sources */,
|
||||
54D774BA1B87DAF800F2ABF8 /* ApplyUpdateTests.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -39,7 +39,7 @@ var DownloadAndApplyUpdateTest = React.createClass({
|
||||
runTest() {
|
||||
var update = require("./TestPackage");
|
||||
NativeBridge.downloadUpdate(update).done((downloadedPackage) => {
|
||||
NativeBridge.applyUpdate(downloadedPackage, 1000);
|
||||
NativeBridge.applyUpdate(downloadedPackage, /*rollbackTimeout*/ 1000, /*restartImmediately*/ true);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
#import <RCTTest/RCTTestRunner.h>
|
||||
|
||||
#import "RCTAssert.h"
|
||||
|
||||
#define FB_REFERENCE_IMAGE_DIR "\"$(SOURCE_ROOT)/$(PROJECT_NAME)Tests/ReferenceImages\""
|
||||
|
||||
@interface DownloadProgressTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation DownloadProgressTests
|
||||
{
|
||||
RCTTestRunner *_runner;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
#if __LP64__
|
||||
RCTAssert(false, @"Tests should be run on 32-bit device simulators (e.g. iPhone 5)");
|
||||
#endif
|
||||
|
||||
NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
|
||||
RCTAssert(version.majorVersion == 8 || version.minorVersion == 3, @"Tests should be run on iOS 8.3, found %zd.%zd.%zd", version.majorVersion, version.minorVersion, version.patchVersion);
|
||||
_runner = RCTInitRunnerForApp(@"CodePushDemoAppTests/DownloadProgressTests/DownloadProgressTestApp.ios", nil);
|
||||
}
|
||||
|
||||
#pragma mark Logic Tests
|
||||
- (void)testDownloadProgress
|
||||
{
|
||||
|
||||
[_runner runTest:_cmd module:@"DownloadProgressTest"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,101 @@
|
||||
"use strict";
|
||||
|
||||
var RCTTestModule = require("NativeModules").TestModule;
|
||||
var React = require("react-native");
|
||||
var CodePushSdk = require("react-native-code-push");
|
||||
var NativeBridge = require("react-native").NativeModules.CodePush;
|
||||
var { NativeAppEventEmitter } = require("react-native");
|
||||
|
||||
var {
|
||||
Text,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var DownloadProgressTest = React.createClass({
|
||||
propTypes: {
|
||||
shouldThrow: React.PropTypes.bool,
|
||||
waitOneFrame: React.PropTypes.bool,
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
done: false,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.waitOneFrame) {
|
||||
requestAnimationFrame(this.runTest);
|
||||
} else {
|
||||
this.runTest();
|
||||
}
|
||||
},
|
||||
|
||||
checkReceivedAndExpectedBytesEqual() {
|
||||
if (this.state.progress.receivedBytes !== this.state.progress.totalBytes) {
|
||||
throw new Error("Bytes do not tally: Received bytes=" + this.state.progress.receivedBytes + " Total bytes=" + this.state.progress.totalBytes);
|
||||
}
|
||||
},
|
||||
|
||||
runTest() {
|
||||
var downloadProgressSubscription = NativeAppEventEmitter.addListener(
|
||||
"CodePushDownloadProgress",
|
||||
(progress) => {
|
||||
this.setState({
|
||||
progress:progress,
|
||||
done: false,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
var updates = require("./TestPackages");
|
||||
NativeBridge.downloadUpdate(updates.smallPackage)
|
||||
.then((smallPackage) => {
|
||||
if (smallPackage) {
|
||||
this.checkReceivedAndExpectedBytesEqual();
|
||||
return NativeBridge.downloadUpdate(updates.mediumPackage);
|
||||
} else {
|
||||
throw new Error("Small package download failed.");
|
||||
}
|
||||
})
|
||||
.then((mediumPackage) => {
|
||||
if (mediumPackage) {
|
||||
this.checkReceivedAndExpectedBytesEqual();
|
||||
return NativeBridge.downloadUpdate(updates.largePackage);
|
||||
} else {
|
||||
throw new Error("Medium package download failed.");
|
||||
}
|
||||
})
|
||||
.done((largePackage) => {
|
||||
if (largePackage) {
|
||||
this.checkReceivedAndExpectedBytesEqual();
|
||||
this.setState({done: true}, RCTTestModule.markTestCompleted);
|
||||
} else {
|
||||
throw new Error("Large package download failed.");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
var progressView;
|
||||
if (this.state.progress) {
|
||||
progressView = (
|
||||
<Text>{this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes received</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{backgroundColor: "white", padding: 40}}>
|
||||
<Text>
|
||||
{this.constructor.displayName + ": "}
|
||||
{this.state.done ? "Done" : "Testing..."}
|
||||
</Text>
|
||||
{progressView}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
DownloadProgressTest.displayName = "DownloadProgressTest";
|
||||
|
||||
module.exports = DownloadProgressTest;
|
||||
@@ -0,0 +1,79 @@
|
||||
"use strict";
|
||||
|
||||
var React = require("react-native");
|
||||
|
||||
var {
|
||||
AppRegistry,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} = React;
|
||||
|
||||
var TESTS = [
|
||||
require("./DownloadProgressTest")
|
||||
];
|
||||
|
||||
TESTS.forEach(
|
||||
(test) => AppRegistry.registerComponent(test.displayName, () => test)
|
||||
);
|
||||
|
||||
var DownloadProgressTestApp = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
test: null,
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
if (this.state.test) {
|
||||
return (
|
||||
<ScrollView>
|
||||
<this.state.test />
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.row}>
|
||||
Click on a test to run it in this shell for easier debugging and
|
||||
development. Run all tests in the testing environment with cmd+U in
|
||||
Xcode.
|
||||
</Text>
|
||||
<View style={styles.separator} />
|
||||
<ScrollView>
|
||||
{TESTS.map((test) => [
|
||||
<TouchableOpacity
|
||||
onPress={() => this.setState({test})}
|
||||
style={styles.row}>
|
||||
<Text style={styles.testName}>
|
||||
{test.displayName}
|
||||
</Text>
|
||||
</TouchableOpacity>,
|
||||
<View style={styles.separator} />
|
||||
])}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
container: {
|
||||
backgroundColor: "white",
|
||||
marginTop: 40,
|
||||
margin: 15,
|
||||
},
|
||||
row: {
|
||||
padding: 10,
|
||||
},
|
||||
testName: {
|
||||
fontWeight: "500",
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
backgroundColor: "#bbbbbb",
|
||||
}
|
||||
});
|
||||
|
||||
AppRegistry.registerComponent("DownloadProgressTestApp", () => DownloadProgressTestApp);
|
||||
@@ -0,0 +1,35 @@
|
||||
module.exports = {
|
||||
smallPackage: {
|
||||
downloadUrl: "http://localhost:8081/CodePushDemoAppTests/DownloadProgressTests/smallFile",
|
||||
description: "Angry flappy birds",
|
||||
appVersion: "1.5.0",
|
||||
label: "2.4.0",
|
||||
isMandatory: false,
|
||||
isAvailable: true,
|
||||
updateAppVersion: false,
|
||||
packageHash: "hash240",
|
||||
packageSize: 1024
|
||||
},
|
||||
mediumPackage: {
|
||||
downloadUrl: "http://localhost:8081/CodePushDemoAppTests/DownloadProgressTests/mediumFile",
|
||||
description: "Angry flappy birds",
|
||||
appVersion: "1.5.0",
|
||||
label: "2.4.0",
|
||||
isMandatory: false,
|
||||
isAvailable: true,
|
||||
updateAppVersion: false,
|
||||
packageHash: "hash240",
|
||||
packageSize: 1024
|
||||
},
|
||||
largePackage: {
|
||||
downloadUrl: "http://localhost:8081/CodePushDemoAppTests/DownloadProgressTests/largeFile",
|
||||
description: "Angry flappy birds",
|
||||
appVersion: "1.5.0",
|
||||
label: "2.4.0",
|
||||
isMandatory: false,
|
||||
isAvailable: true,
|
||||
updateAppVersion: false,
|
||||
packageHash: "hash240",
|
||||
packageSize: 1024
|
||||
}
|
||||
};
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -32,14 +32,22 @@ var CodePushDemoApp = React.createClass({
|
||||
return { update: false };
|
||||
},
|
||||
handlePress: function() {
|
||||
this.state.update.download().done((localPackage) => {
|
||||
this.state.update.download((progress) => {
|
||||
this.setState({
|
||||
progress:progress
|
||||
});
|
||||
}).done((localPackage) => {
|
||||
localPackage.apply().done();
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var updateView;
|
||||
if (this.state.update) {
|
||||
if (this.state.progress) {
|
||||
updateView = (
|
||||
<Text>{this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes received</Text>
|
||||
);
|
||||
} else if (this.state.update) {
|
||||
updateView = (
|
||||
<View>
|
||||
<Text>Update Available: {'\n'} {this.state.update.scriptVersion} - {this.state.update.description}</Text>
|
||||
|
||||
@@ -265,7 +265,7 @@ The `RemotePackage` inherits all of the same properties as the `LocalPackage`, b
|
||||
- __downloadUrl__: The URL at which the package is available for download. (String). This property is only needed for advanced usage, since the `download` method will automatically handle the acquisition of updates for you.
|
||||
|
||||
##### Methods
|
||||
- __download(): Promise<LocalPackage>__: Downloads the package update from the CodePush service. Returns a Promise that resolves with the `LocalPackage`.
|
||||
- __download(progressHandler?: Function): Promise<LocalPackage>__: Downloads the package update from the CodePush service. If a `progressHandler` is specified, it will be called periodically with a `DownloadProgress` object (`{ totalBytes: Number, receivedBytes: Number }`) that reports the progress of the download until the download completes. Returns a Promise that resolves with the `LocalPackage`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,20 +1,36 @@
|
||||
var extend = require("extend");
|
||||
var { NativeAppEventEmitter } = require("react-native");
|
||||
|
||||
module.exports = (NativeCodePush) => {
|
||||
var remote = {
|
||||
abortDownload: function abortDownload() {
|
||||
return NativeCodePush.abortDownload(this);
|
||||
},
|
||||
download: function download() {
|
||||
download: function download(progressHandler) {
|
||||
if (!this.downloadUrl) {
|
||||
return Promise.reject(new Error("Cannot download an update without a download url"));
|
||||
}
|
||||
|
||||
var downloadProgressSubscription;
|
||||
if (progressHandler) {
|
||||
// Use event subscription to obtain download progress.
|
||||
downloadProgressSubscription = NativeAppEventEmitter.addListener(
|
||||
"CodePushDownloadProgress",
|
||||
progressHandler
|
||||
);
|
||||
}
|
||||
|
||||
// Use the downloaded package info. Native code will save the package info
|
||||
// so that the client knows what the current package version is.
|
||||
return NativeCodePush.downloadUpdate(this)
|
||||
.then((downloadedPackage) => {
|
||||
downloadProgressSubscription && downloadProgressSubscription.remove();
|
||||
return extend({}, downloadedPackage, local);
|
||||
})
|
||||
.catch((error) => {
|
||||
downloadProgressSubscription && downloadProgressSubscription.remove();
|
||||
// Rethrow the error for subsequent handlers down the promise chain.
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user