mirror of
https://github.com/zhigang1992/react-native-code-push.git
synced 2026-06-19 18:13:46 +08:00
download-progress
This commit is contained in:
39
CodePush.h
39
CodePush.h
@@ -1,12 +1,5 @@
|
||||
#import "RCTBridgeModule.h"
|
||||
|
||||
@interface CodePush : NSObject<RCTBridgeModule>
|
||||
|
||||
+ (NSURL *)getBundleUrl;
|
||||
+ (NSString *)getDocumentsDirectory;
|
||||
|
||||
@end
|
||||
|
||||
@interface CodePushConfig : NSObject
|
||||
|
||||
+ (void)setDeploymentKey:(NSString *)deploymentKey;
|
||||
@@ -29,6 +22,31 @@
|
||||
|
||||
@end
|
||||
|
||||
@interface CodePushDownloadHandler : NSObject<NSURLConnectionDelegate>
|
||||
|
||||
@property (nonatomic, 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 CodePush : NSObject<RCTBridgeModule>
|
||||
|
||||
+ (NSURL *)getBundleUrl;
|
||||
+ (NSString *)getDocumentsDirectory;
|
||||
|
||||
@end
|
||||
|
||||
@interface CodePushPackage : NSObject
|
||||
|
||||
+ (void)applyPackage:(NSDictionary *)updatePackage
|
||||
@@ -43,10 +61,11 @@
|
||||
|
||||
+ (NSString *)getPackageFolderPath:(NSString *)packageHash;
|
||||
|
||||
|
||||
+ (void)downloadPackage:(NSDictionary *)updatePackage
|
||||
error:(NSError **)error;
|
||||
progressCallback:(void (^)(long, long))progressCallback
|
||||
doneCallback:(void (^)())doneCallback
|
||||
failCallback:(void (^)(NSError *err))failCallback;
|
||||
|
||||
+ (void)rollbackPackage;
|
||||
|
||||
@end
|
||||
@end
|
||||
|
||||
47
CodePush.m
47
CodePush.m
@@ -1,4 +1,5 @@
|
||||
#import "RCTBridgeModule.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTRootView.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "CodePush.h"
|
||||
@@ -195,27 +196,33 @@ RCT_EXPORT_METHOD(applyUpdate:(NSDictionary*)updatePackage
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
|
||||
resolver:(RCTPromiseResolveBlock)resolve
|
||||
rejecter:(RCTPromiseRejectBlock)reject)
|
||||
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 */,
|
||||
);
|
||||
|
||||
76
CodePushDownloadHandler.m
Normal file
76
CodePushDownloadHandler.m
Normal file
@@ -0,0 +1,76 @@
|
||||
#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];
|
||||
|
||||
NSUInteger bytesLeft = [data length];
|
||||
|
||||
do {
|
||||
NSUInteger bytesWritten = [self.outputFileStream write:[data bytes]
|
||||
maxLength:bytesLeft];
|
||||
if (bytesWritten == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
bytesLeft -= bytesWritten;
|
||||
} while (bytesLeft>0);
|
||||
|
||||
self.progressCallback(self.expectedContentLength, self.receivedContentLength);
|
||||
|
||||
if (bytesLeft) {
|
||||
self.failCallback([self.outputFileStream streamError]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
|
||||
{
|
||||
self.failCallback(error);
|
||||
}
|
||||
|
||||
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
|
||||
[self.outputFileStream close];
|
||||
self.doneCallback();
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -4,6 +4,17 @@
|
||||
|
||||
NSString * const StatusFile = @"codepush.json";
|
||||
|
||||
+ (CodePushPackage*)sharedInstance {
|
||||
static dispatch_once_t predicate = 0;
|
||||
__strong static id sharedInstance = nil;
|
||||
//static id sharedObject = nil; //if you're not using ARC
|
||||
dispatch_once(&predicate, ^{
|
||||
sharedInstance = [[self alloc] init];
|
||||
//sharedObject = [[[self alloc] init] retain]; // if you're not using ARC
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
+ (NSString *)getCodePushPath
|
||||
{
|
||||
return [[CodePush getDocumentsDirectory] stringByAppendingPathComponent:@"CodePush"];
|
||||
@@ -149,50 +160,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"]];
|
||||
NSString *packageFolderPath = [CodePushPackage getPackageFolderPath:updatePackage[@"packageHash"]];
|
||||
|
||||
NSError *error;
|
||||
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:@"app.jsbundle"];
|
||||
|
||||
[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
|
||||
@@ -207,7 +219,7 @@ NSString * const StatusFile = @"codepush.json";
|
||||
|
||||
[info setValue:info[@"currentPackage"] forKey:@"previousPackage"];
|
||||
[info setValue:packageHash forKey:@"currentPackage"];
|
||||
|
||||
|
||||
[self updateCurrentPackageInfo:info
|
||||
error:error];
|
||||
}
|
||||
|
||||
@@ -44,6 +44,6 @@
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>CodePushDeploymentKey</key>
|
||||
<string>deployment-key-here</string>
|
||||
<string>your-deployment-key</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -32,14 +32,23 @@ 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>
|
||||
@@ -49,6 +58,7 @@ var CodePushDemoApp = React.createClass({
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.welcome}>
|
||||
|
||||
@@ -1,20 +1,33 @@
|
||||
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"));
|
||||
}
|
||||
|
||||
|
||||
// Use event subscription to obtain download progress.
|
||||
var 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.remove();
|
||||
return extend({}, downloadedPackage, local);
|
||||
})
|
||||
.catch((error) => {
|
||||
downloadProgressSubscription.remove();
|
||||
// Rethrow the error for subsequent handlers down the promise chain.
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user