From b4b05c3f4eed8204da6b922093b99f19c3970597 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Fri, 19 Nov 2010 20:16:02 +1300 Subject: [PATCH] Notify javascript when download status changes. Allow js to query information about a download. --- OpenRA.Launcher.Mac/Controller.h | 2 + OpenRA.Launcher.Mac/Controller.m | 9 ++++- OpenRA.Launcher.Mac/Download.h | 17 +++++++++ OpenRA.Launcher.Mac/Download.m | 65 ++++++++++++++++++++++++++------ OpenRA.Launcher.Mac/JSBridge.m | 37 ++++++++++++++---- mods/cnc/mod.html | 23 ++++++----- 6 files changed, 123 insertions(+), 30 deletions(-) diff --git a/OpenRA.Launcher.Mac/Controller.h b/OpenRA.Launcher.Mac/Controller.h index 56a0aa3f2e..3af55df9d1 100644 --- a/OpenRA.Launcher.Mac/Controller.h +++ b/OpenRA.Launcher.Mac/Controller.h @@ -12,6 +12,7 @@ @class SidebarEntry; @class GameInstall; @class JSBridge; +@class Download; @interface Controller : NSObject { SidebarEntry *sidebarItems; @@ -31,5 +32,6 @@ - (BOOL)downloadUrl:(NSString *)url toFile:(NSString *)filename withId:(NSString *)key; - (void)cancelDownload:(NSString *)key; +- (Download *)downloadWithKey:(NSString *)key; @end diff --git a/OpenRA.Launcher.Mac/Controller.m b/OpenRA.Launcher.Mac/Controller.m index 49b0a271ad..14ef0c6583 100644 --- a/OpenRA.Launcher.Mac/Controller.m +++ b/OpenRA.Launcher.Mac/Controller.m @@ -17,7 +17,8 @@ @implementation Controller @synthesize allMods; @synthesize webView; -- (void) awakeFromNib + +- (void)awakeFromNib { game = [[GameInstall alloc] initWithURL:[NSURL URLWithString:@"/Users/paul/src/OpenRA"]]; [[JSBridge sharedInstance] setController:self]; @@ -101,7 +102,6 @@ return NO; } - Download *download = [Download downloadWithURL:url filename:path key:key game:game]; [downloads setObject:download forKey:key]; return YES; @@ -113,6 +113,11 @@ [downloads removeObjectForKey:key]; } +- (Download *)downloadWithKey:(NSString *)key +{ + return [downloads objectForKey:key]; +} + #pragma mark Sidebar Datasource and Delegate - (int)outlineView:(NSOutlineView *)anOutlineView numberOfChildrenOfItem:(id)item { diff --git a/OpenRA.Launcher.Mac/Download.h b/OpenRA.Launcher.Mac/Download.h index be9a5a8f5c..102fab0ea6 100644 --- a/OpenRA.Launcher.Mac/Download.h +++ b/OpenRA.Launcher.Mac/Download.h @@ -7,6 +7,15 @@ // #import + +typedef enum { + Initializing, + Downloading, + Complete, + Cancelled, + Error +} DownloadStatus; + @class GameInstall; @interface Download : NSObject { @@ -15,7 +24,15 @@ NSString *filename; GameInstall *game; NSTask *task; + DownloadStatus status; + int bytesCompleted; + int bytesTotal; } +@property(readonly) NSString *key; +@property(readonly) DownloadStatus status; +@property(readonly) int bytesCompleted; +@property(readonly) int bytesTotal; + + (id)downloadWithURL:(NSString *)aURL filename:(NSString *)aFilename key:(NSString *)aKey game:(GameInstall *)aGame; - (id)initWithURL:(NSString *)aURL filename:(NSString *)aFilename key:(NSString *)aKey game:(GameInstall *)game; - (void)cancel; diff --git a/OpenRA.Launcher.Mac/Download.m b/OpenRA.Launcher.Mac/Download.m index 0e9d23d53a..670820eb70 100644 --- a/OpenRA.Launcher.Mac/Download.m +++ b/OpenRA.Launcher.Mac/Download.m @@ -11,6 +11,10 @@ #import "JSBridge.h" @implementation Download +@synthesize key; +@synthesize status; +@synthesize bytesCompleted; +@synthesize bytesTotal; + (id)downloadWithURL:(NSString *)aURL filename:(NSString *)aFilename key:(NSString *)aKey game:(GameInstall *)aGame { @@ -28,6 +32,9 @@ filename = [aFilename retain]; key = [aKey retain]; game = [aGame retain]; + status = Initializing; + bytesCompleted = -1; + bytesTotal = -1; NSLog(@"Starting download..."); task = [game runAsyncUtilityWithArg:[NSString stringWithFormat:@"--download-url=%@,%@",url,filename] @@ -39,22 +46,47 @@ return self; } -- (void)cancel -{ - // Stop the download task. utilityTerminated: will handle the cleanup - NSLog(@"Cancelling"); - NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; - [nc removeObserver:self name:NSFileHandleReadCompletionNotification object:[[task standardOutput] fileHandleForReading]]; - [nc removeObserver:self name:NSTaskDidTerminateNotification object:task]; - [task terminate]; -} - - (void)utilityResponded:(NSNotification *)n { NSData *data = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem]; NSString *response = [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease]; - NSLog(@"r: %@",response); + // Response can contain multiple lines, or no lines. Split into lines, and parse each in turn + NSArray *lines = [response componentsSeparatedByString:@"\n"]; + for (NSString *line in lines) + { + NSRange separator = [line rangeOfString:@":"]; + if (separator.location == NSNotFound) + continue; // We only care about messages of the form key: value + + NSString *type = [line substringToIndex:separator.location]; + NSString *message = [[line substringFromIndex:separator.location+1] + stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + + if ([type isEqualToString:@"Error"]) + { + status = Error; + } + else if ([type isEqualToString:@"Status"]) + { + if ([message isEqualToString:@"Initializing"]) + { + status = Initializing; + } + else if ([message isEqualToString:@"Completed"]) + { + status = Complete; + } + + // Parse download status info + int done,total; + if (sscanf([message UTF8String], "%*d%% %d/%d bytes", &done, &total) == 2) + { + bytesCompleted = done; + bytesTotal = total; + } + } + } [[JSBridge sharedInstance] notifyDownloadProgress:self]; // Keep reading @@ -62,6 +94,17 @@ [[n object] readInBackgroundAndNotify]; } +- (void)cancel +{ + NSLog(@"Cancelling"); + status = Cancelled; + NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; + [nc removeObserver:self name:NSFileHandleReadCompletionNotification object:[[task standardOutput] fileHandleForReading]]; + [nc removeObserver:self name:NSTaskDidTerminateNotification object:task]; + [task terminate]; + [task release]; task = nil; +} + - (void)utilityTerminated:(NSNotification *)n { NSLog(@"utility terminated"); diff --git a/OpenRA.Launcher.Mac/JSBridge.m b/OpenRA.Launcher.Mac/JSBridge.m index faab102baa..40b394319e 100644 --- a/OpenRA.Launcher.Mac/JSBridge.m +++ b/OpenRA.Launcher.Mac/JSBridge.m @@ -42,10 +42,15 @@ static JSBridge *SharedInstance; methods = [[NSDictionary dictionaryWithObjectsAndKeys: @"launchMod", NSStringFromSelector(@selector(launchMod:)), @"log", NSStringFromSelector(@selector(log:)), - @"fileExistsInMod", NSStringFromSelector(@selector(fileExists:inMod:)), - @"fileExistsInCache", NSStringFromSelector(@selector(fileExistsInCache:)), - @"downloadFileToCache", NSStringFromSelector(@selector(downloadFileIntoCache:withName:key:)), + @"existsInMod", NSStringFromSelector(@selector(exists:inMod:)), + + // File downloading + @"existsInCache", NSStringFromSelector(@selector(existsInCache:)), + @"downloadToCache", NSStringFromSelector(@selector(downloadUrl:withName:key:)), @"cancelDownload", NSStringFromSelector(@selector(cancelDownload:)), + @"isDownloading", NSStringFromSelector(@selector(isDownloading:)), + @"bytesCompleted", NSStringFromSelector(@selector(bytesCompleted:)), + @"bytesTotal", NSStringFromSelector(@selector(bytesTotal:)), nil] retain]; } return self; @@ -64,9 +69,8 @@ static JSBridge *SharedInstance; - (void)notifyDownloadProgress:(Download *)download { - NSLog(@"notified"); - //[[[controller webView] windowScriptObject] evaluateWebScript: - // @"updateDownloadStatus(’sample_graphic.jpg’, ‘320’, ‘240’)"]; + [[[controller webView] windowScriptObject] evaluateWebScript: + [NSString stringWithFormat:@"downloadProgressed('%@')",[download key]]]; } #pragma mark JS API methods @@ -99,14 +103,14 @@ static JSBridge *SharedInstance; return YES; } -- (BOOL)fileExistsInCache:(NSString *)name +- (BOOL)existsInCache:(NSString *)name { // Disallow traversing directories; take only the last component id path = [[@"~/Library/Application Support/OpenRA/Downloads/" stringByAppendingPathComponent:[name lastPathComponent]] stringByExpandingTildeInPath]; return [[NSFileManager defaultManager] fileExistsAtPath:path]; } -- (void)downloadFileIntoCache:(NSString *)url withName:(NSString *)name key:(NSString *)key +- (void)downloadUrl:(NSString *)url withName:(NSString *)name key:(NSString *)key { NSLog(@"downloadFile:%@ intoCacheWithName:%@ key:%@",url,name,key); @@ -120,6 +124,23 @@ static JSBridge *SharedInstance; [controller cancelDownload:key]; } +- (BOOL)isDownloading:(NSString *)key +{ + return [controller downloadWithKey:key] != nil; +} + +- (int)bytesCompleted:(NSString *)key +{ + Download *d = [controller downloadWithKey:key]; + return (d == nil) ? -1 : [d bytesCompleted]; +} + +- (int)bytesTotal:(NSString *)key +{ + Download *d = [controller downloadWithKey:key]; + return (d == nil) ? -1 : [d bytesTotal]; +} + - (void)log:(NSString *)message { NSLog(@"js: %@",message); diff --git a/mods/cnc/mod.html b/mods/cnc/mod.html index ce341ef67b..2f2f334973 100644 --- a/mods/cnc/mod.html +++ b/mods/cnc/mod.html @@ -90,10 +90,10 @@ // Returns 2 if basic files plus music are installed function packagesInstalled() { - if (window.external.fileExistsInMod('packages/conquer.mix','cnc') != 1) + if (window.external.existsInMod('packages/conquer.mix','cnc') != 1) return 0; - return (window.external.fileExistsInMod('packages/scores.mix','cnc') == 1) ? 2 : 1; + return (window.external.existsInMod('packages/scores.mix','cnc') == 1) ? 2 : 1; } function play() @@ -101,18 +101,14 @@ window.external.launchMod("cnc"); } - function installFromCD() - { - window.external.log("installFromCD()"); - } - function download1() { - window.external.downloadFileToCache("http://www.open-ra.org/get-dependency.php?file=cnc-packages","test.zip","cnc-packages"); + window.external.downloadToCache("http://localhost/~paul/cnc-packages.zip","test.zip","cnc-packages"); } + function download2() { - window.external.downloadFileToCache("http://www.open-ra.org/get-dependency.php?file=ra-packages","test2.zip","ra-packages"); + window.external.downloadToCache("http://localhost/~paul/ra-packages.zip","test2.zip","ra-packages"); } function cancel1() @@ -131,6 +127,15 @@ document.getElementById("buttons-upgrade").style.display = (packagesInstalled() == 1) ? "" : "none"; document.getElementById("buttons-play").style.display = (packagesInstalled() == 2) ? "" : "none"; } + + function downloadProgressed(file) + { + var total = window.external.bytesTotal(file); + var downloaded = window.external.bytesCompleted(file); + var percent = (100*downloaded/total).toPrecision(3); + window.external.log("file: "+file+" "+downloaded+"/"+total+" ("+percent+"%)"); + } +