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

Commit 7e0645a

Browse files
committed
download-progress
1 parent 79e4c50 commit 7e0645a

File tree

8 files changed

+213
-72
lines changed

8 files changed

+213
-72
lines changed

CodePush.h

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
11
#import "RCTBridgeModule.h"
22

3-
@interface CodePush : NSObject<RCTBridgeModule>
4-
5-
+ (NSURL *)getBundleUrl;
6-
+ (NSString *)getDocumentsDirectory;
7-
8-
@end
9-
103
@interface CodePushConfig : NSObject
114

125
+ (void)setDeploymentKey:(NSString *)deploymentKey;
@@ -29,6 +22,31 @@
2922

3023
@end
3124

25+
@interface CodePushDownloadHandler : NSObject<NSURLConnectionDelegate>
26+
27+
@property (nonatomic, strong) NSOutputStream *outputFileStream;
28+
@property long expectedContentLength;
29+
@property long receivedContentLength;
30+
@property (copy)void (^progressCallback)(long, long);
31+
@property (copy)void (^doneCallback)();
32+
@property (copy)void (^failCallback)(NSError *err);
33+
34+
- (id)init:(NSString *)downloadFilePath
35+
progressCallback:(void (^)(long, long))progressCallback
36+
doneCallback:(void (^)())doneCallback
37+
failCallback:(void (^)(NSError *err))failCallback;
38+
39+
- (void)download:(NSString*)url;
40+
41+
@end
42+
43+
@interface CodePush : NSObject<RCTBridgeModule>
44+
45+
+ (NSURL *)getBundleUrl;
46+
+ (NSString *)getDocumentsDirectory;
47+
48+
@end
49+
3250
@interface CodePushPackage : NSObject
3351

3452
+ (void)applyPackage:(NSDictionary *)updatePackage
@@ -43,10 +61,11 @@
4361

4462
+ (NSString *)getPackageFolderPath:(NSString *)packageHash;
4563

46-
4764
+ (void)downloadPackage:(NSDictionary *)updatePackage
48-
error:(NSError **)error;
65+
progressCallback:(void (^)(long, long))progressCallback
66+
doneCallback:(void (^)())doneCallback
67+
failCallback:(void (^)(NSError *err))failCallback;
4968

5069
+ (void)rollbackPackage;
5170

52-
@end
71+
@end

CodePush.m

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#import "RCTBridgeModule.h"
2+
#import "RCTEventDispatcher.h"
23
#import "RCTRootView.h"
34
#import "RCTUtils.h"
45
#import "CodePush.h"
@@ -195,27 +196,33 @@ - (void)startRollbackTimer:(int)rollbackTimeout
195196
}
196197

197198
RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
198-
resolver:(RCTPromiseResolveBlock)resolve
199-
rejecter:(RCTPromiseRejectBlock)reject)
199+
resolver:(RCTPromiseResolveBlock)resolve
200+
rejecter:(RCTPromiseRejectBlock)reject)
200201
{
201-
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
202-
NSError *err;
203-
[CodePushPackage downloadPackage:updatePackage
204-
error:&err];
205-
206-
if (err) {
207-
return reject(err);
208-
}
209-
210-
NSDictionary *newPackage = [CodePushPackage getPackage:updatePackage[@"packageHash"]
211-
error:&err];
212-
213-
if (err) {
214-
return reject(err);
215-
}
216-
217-
resolve(newPackage);
218-
});
202+
[CodePushPackage downloadPackage:updatePackage
203+
progressCallback:^(long expectedContentLength, long receivedContentLength) {
204+
[self.bridge.eventDispatcher
205+
sendAppEventWithName:@"CodePushDownloadProgress"
206+
body:@{
207+
@"totalBytes":[NSNumber numberWithLong:expectedContentLength],
208+
@"receivedBytes":[NSNumber numberWithLong:receivedContentLength]
209+
}];
210+
}
211+
doneCallback:^{
212+
NSError *err;
213+
NSDictionary *newPackage = [CodePushPackage
214+
getPackage:updatePackage[@"packageHash"]
215+
error:&err];
216+
217+
if (err) {
218+
return reject(err);
219+
}
220+
221+
resolve(newPackage);
222+
}
223+
failCallback:^(NSError *err) {
224+
reject(err);
225+
}];
219226
}
220227

221228
RCT_EXPORT_METHOD(getConfiguration:(RCTPromiseResolveBlock)resolve

CodePush.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
/* Begin PBXBuildFile section */
1010
13BE3DEE1AC21097009241FE /* CodePush.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* CodePush.m */; };
11+
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */; settings = {ASSET_TAGS = (); }; };
1112
810D4E6D1B96935000B397E9 /* CodePushPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 810D4E6C1B96935000B397E9 /* CodePushPackage.m */; };
1213
81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 81D51F391B6181C2000DA084 /* CodePushConfig.m */; };
1314
/* End PBXBuildFile section */
@@ -28,6 +29,7 @@
2829
134814201AA4EA6300B7C361 /* libCodePush.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCodePush.a; sourceTree = BUILT_PRODUCTS_DIR; };
2930
13BE3DEC1AC21097009241FE /* CodePush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CodePush.h; sourceTree = "<group>"; };
3031
13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePush.m; sourceTree = "<group>"; };
32+
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushDownloadHandler.m; sourceTree = "<group>"; };
3133
810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushPackage.m; sourceTree = "<group>"; };
3234
81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushConfig.m; sourceTree = "<group>"; };
3335
/* End PBXFileReference section */
@@ -54,6 +56,7 @@
5456
58B511D21A9E6C8500147676 = {
5557
isa = PBXGroup;
5658
children = (
59+
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */,
5760
810D4E6C1B96935000B397E9 /* CodePushPackage.m */,
5861
81D51F391B6181C2000DA084 /* CodePushConfig.m */,
5962
13BE3DEC1AC21097009241FE /* CodePush.h */,
@@ -119,6 +122,7 @@
119122
buildActionMask = 2147483647;
120123
files = (
121124
81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */,
125+
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */,
122126
13BE3DEE1AC21097009241FE /* CodePush.m in Sources */,
123127
810D4E6D1B96935000B397E9 /* CodePushPackage.m in Sources */,
124128
);

CodePushDownloadHandler.m

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#import "CodePush.h"
2+
3+
@implementation CodePushDownloadHandler
4+
5+
- (id)init:(NSString *)downloadFilePath
6+
progressCallback:(void (^)(long, long))progressCallback
7+
doneCallback:(void (^)())doneCallback
8+
failCallback:(void (^)(NSError *err))failCallback {
9+
self.outputFileStream = [NSOutputStream outputStreamToFileAtPath:downloadFilePath
10+
append:NO];
11+
self.receivedContentLength = 0;
12+
self.progressCallback = progressCallback;
13+
self.doneCallback = doneCallback;
14+
self.failCallback = failCallback;
15+
return self;
16+
}
17+
18+
-(void)download:(NSString*)url {
19+
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]
20+
cachePolicy:NSURLRequestUseProtocolCachePolicy
21+
timeoutInterval:60.0];
22+
23+
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
24+
delegate:self
25+
startImmediately:NO];
26+
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop]
27+
forMode:NSDefaultRunLoopMode];
28+
[connection start];
29+
}
30+
31+
#pragma mark NSURLConnection Delegate Methods
32+
33+
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
34+
willCacheResponse:(NSCachedURLResponse*)cachedResponse {
35+
// Return nil to indicate not necessary to store a cached response for this connection
36+
return nil;
37+
}
38+
39+
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
40+
self.expectedContentLength = response.expectedContentLength;
41+
[self.outputFileStream open];
42+
}
43+
44+
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
45+
self.receivedContentLength = self.receivedContentLength + [data length];
46+
47+
NSUInteger bytesLeft = [data length];
48+
49+
do {
50+
NSUInteger bytesWritten = [self.outputFileStream write:[data bytes]
51+
maxLength:bytesLeft];
52+
if (bytesWritten == -1) {
53+
break;
54+
}
55+
56+
bytesLeft -= bytesWritten;
57+
} while (bytesLeft>0);
58+
59+
self.progressCallback(self.expectedContentLength, self.receivedContentLength);
60+
61+
if (bytesLeft) {
62+
self.failCallback([self.outputFileStream streamError]);
63+
}
64+
}
65+
66+
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
67+
{
68+
self.failCallback(error);
69+
}
70+
71+
-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
72+
[self.outputFileStream close];
73+
self.doneCallback();
74+
}
75+
76+
@end

CodePushPackage.m

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ @implementation CodePushPackage
44

55
NSString * const StatusFile = @"codepush.json";
66

7+
+ (CodePushPackage*)sharedInstance {
8+
static dispatch_once_t predicate = 0;
9+
__strong static id sharedInstance = nil;
10+
//static id sharedObject = nil; //if you're not using ARC
11+
dispatch_once(&predicate, ^{
12+
sharedInstance = [[self alloc] init];
13+
//sharedObject = [[[self alloc] init] retain]; // if you're not using ARC
14+
});
15+
return sharedInstance;
16+
}
17+
718
+ (NSString *)getCodePushPath
819
{
920
return [[CodePush getDocumentsDirectory] stringByAppendingPathComponent:@"CodePush"];
@@ -149,50 +160,51 @@ + (NSString *)getPackageFolderPath:(NSString *)packageHash
149160
}
150161

151162
+ (void)downloadPackage:(NSDictionary *)updatePackage
152-
error:(NSError **)error
163+
progressCallback:(void (^)(long, long))progressCallback
164+
doneCallback:(void (^)())doneCallback
165+
failCallback:(void (^)(NSError *err))failCallback
153166
{
154-
NSString *packageFolderPath = [self getPackageFolderPath:updatePackage[@"packageHash"]];
167+
NSString *packageFolderPath = [CodePushPackage getPackageFolderPath:updatePackage[@"packageHash"]];
155168

169+
NSError *error;
156170
if (![[NSFileManager defaultManager] fileExistsAtPath:packageFolderPath]) {
157171
[[NSFileManager defaultManager] createDirectoryAtPath:packageFolderPath
158172
withIntermediateDirectories:YES
159173
attributes:nil
160-
error:error];
161-
}
162-
163-
if (*error) {
164-
return;
165-
}
166-
167-
NSURL *url = [[NSURL alloc] initWithString:updatePackage[@"downloadUrl"]];
168-
NSString *updateContents = [[NSString alloc] initWithContentsOfURL:url
169-
encoding:NSUTF8StringEncoding
170-
error:error];
171-
if (*error) {
172-
return;
173-
}
174-
175-
[updateContents writeToFile:[packageFolderPath stringByAppendingPathComponent:@"app.jsbundle"]
176-
atomically:YES
177-
encoding:NSUTF8StringEncoding
178-
error:error];
179-
if (*error) {
180-
return;
181-
}
182-
183-
NSData *updateSerializedData = [NSJSONSerialization dataWithJSONObject:updatePackage
184-
options:0
185-
error:error];
186-
187-
if (*error) {
188-
return;
174+
error:&error];
189175
}
190176

191-
NSString *packageJsonString = [[NSString alloc] initWithData:updateSerializedData encoding:NSUTF8StringEncoding];
192-
[packageJsonString writeToFile:[packageFolderPath stringByAppendingPathComponent:@"app.json"]
193-
atomically:YES
194-
encoding:NSUTF8StringEncoding
195-
error:error];
177+
if (error) {
178+
return failCallback(error);
179+
}
180+
181+
NSString *downloadFilePath = [packageFolderPath stringByAppendingPathComponent:@"app.jsbundle"];
182+
183+
CodePushDownloadHandler *downloadHandler = [[CodePushDownloadHandler alloc]
184+
init:downloadFilePath
185+
progressCallback:progressCallback
186+
doneCallback:^{
187+
NSError *error;
188+
NSData *updateSerializedData = [NSJSONSerialization
189+
dataWithJSONObject:updatePackage
190+
options:0
191+
error:&error];
192+
NSString *packageJsonString = [[NSString alloc]
193+
initWithData:updateSerializedData encoding:NSUTF8StringEncoding];
194+
195+
[packageJsonString writeToFile:[packageFolderPath stringByAppendingPathComponent:@"app.json"]
196+
atomically:YES
197+
encoding:NSUTF8StringEncoding
198+
error:&error];
199+
if (error) {
200+
failCallback(error);
201+
} else {
202+
doneCallback();
203+
}
204+
}
205+
failCallback:failCallback];
206+
207+
[downloadHandler download:updatePackage[@"downloadUrl"]];
196208
}
197209

198210
+ (void)applyPackage:(NSDictionary *)updatePackage
@@ -207,7 +219,7 @@ + (void)applyPackage:(NSDictionary *)updatePackage
207219

208220
[info setValue:info[@"currentPackage"] forKey:@"previousPackage"];
209221
[info setValue:packageHash forKey:@"currentPackage"];
210-
222+
211223
[self updateCurrentPackageInfo:info
212224
error:error];
213225
}

Examples/CodePushDemoApp/iOS/Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,6 @@
4444
<key>NSLocationWhenInUseUsageDescription</key>
4545
<string></string>
4646
<key>CodePushDeploymentKey</key>
47-
<string>deployment-key-here</string>
47+
<string>your-deployment-key</string>
4848
</dict>
4949
</plist>

Examples/CodePushDemoApp/index.ios.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,23 @@ var CodePushDemoApp = React.createClass({
3232
return { update: false };
3333
},
3434
handlePress: function() {
35-
this.state.update.download().done((localPackage) => {
35+
this.state.update.download((progress) => {
36+
this.setState({
37+
progress:progress
38+
});
39+
}).done((localPackage) => {
3640
localPackage.apply().done();
3741
});
3842
},
3943

4044
render: function() {
4145
var updateView;
42-
if (this.state.update) {
46+
47+
if (this.state.progress) {
48+
updateView = (
49+
<Text>{this.state.progress.receivedBytes} of {this.state.progress.totalBytes} bytes received</Text>
50+
);
51+
} else if (this.state.update) {
4352
updateView = (
4453
<View>
4554
<Text>Update Available: {'\n'} {this.state.update.scriptVersion} - {this.state.update.description}</Text>
@@ -49,6 +58,7 @@ var CodePushDemoApp = React.createClass({
4958
</View>
5059
);
5160
};
61+
5262
return (
5363
<View style={styles.container}>
5464
<Text style={styles.welcome}>

0 commit comments

Comments
 (0)