diff --git a/OpenRA.Launcher.Mac/Controller.m b/OpenRA.Launcher.Mac/Controller.m index 5342947c4f..9615da18b8 100644 --- a/OpenRA.Launcher.Mac/Controller.m +++ b/OpenRA.Launcher.Mac/Controller.m @@ -222,7 +222,7 @@ objectValueForTableColumn:(NSTableColumn *)tableColumn } - (void)outlineView:(NSOutlineView *)olv willDisplayCell:(NSCell*)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item -{ +{ if ([[tableColumn identifier] isEqualToString:@"mods"]) { if ([cell isKindOfClass:[ImageAndTextCell class]]) diff --git a/OpenRA.Launcher.Mac/Download.h b/OpenRA.Launcher.Mac/Download.h index 4d673e780f..01992c8430 100644 --- a/OpenRA.Launcher.Mac/Download.h +++ b/OpenRA.Launcher.Mac/Download.h @@ -32,5 +32,5 @@ - (id)initWithURL:(NSString *)aURL filename:(NSString *)aFilename key:(NSString *)aKey game:(GameInstall *)game; - (BOOL)start; - (BOOL)cancel; - +- (BOOL)extractToPath:(NSString *)aPath; @end \ No newline at end of file diff --git a/OpenRA.Launcher.Mac/Download.m b/OpenRA.Launcher.Mac/Download.m index 67e07e4841..77fac5509a 100644 --- a/OpenRA.Launcher.Mac/Download.m +++ b/OpenRA.Launcher.Mac/Download.m @@ -37,7 +37,7 @@ if ([[NSFileManager defaultManager] fileExistsAtPath:filename]) { - status = @"COMPLETE"; + status = @"DOWNLOADED"; bytesCompleted = bytesTotal = [[[NSFileManager defaultManager] attributesOfItemAtPath:filename error:NULL] fileSize]; } else @@ -49,7 +49,35 @@ return self; } -- (void)utilityResponded:(NSNotification *)n + +- (BOOL)start +{ + status = @"DOWNLOADING"; + task = [game runAsyncUtilityWithArg:[NSString stringWithFormat:@"--download-url=%@,%@",url,filename] + delegate:self + responseSelector:@selector(downloadResponded:) + terminatedSelector:@selector(utilityTerminated:)]; + [task retain]; + return YES; +} + +- (BOOL)cancel +{ + status = @"ERROR"; + error = @"Download Cancelled"; + + [[NSFileManager defaultManager] removeItemAtPath:filename error:NULL]; + bytesCompleted = bytesTotal = -1; + + [[JSBridge sharedInstance] notifyDownloadProgress:self]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:NSFileHandleReadCompletionNotification + object:[[task standardOutput] fileHandleForReading]]; + [task terminate]; + return YES; +} + +- (void)downloadResponded:(NSNotification *)n { NSData *data = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem]; NSString *response = [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease]; @@ -80,7 +108,7 @@ { if ([message isEqualToString:@"Completed"]) { - status = @"COMPLETE"; + status = @"DOWNLOADED"; } // Parse download status info @@ -99,36 +127,60 @@ [[n object] readInBackgroundAndNotify]; } -- (BOOL)start +- (BOOL)extractToPath:(NSString *)aPath { - status = @"DOWNLOADING"; - task = [game runAsyncUtilityWithArg:[NSString stringWithFormat:@"--download-url=%@,%@",url,filename] + status = @"EXTRACTING"; + task = [game runAsyncUtilityWithArg:[NSString stringWithFormat:@"--extract-zip=%@,%@",filename,aPath] delegate:self - responseSelector:@selector(utilityResponded:) + responseSelector:@selector(extractResponded:) terminatedSelector:@selector(utilityTerminated:)]; [task retain]; return YES; } -- (BOOL)cancel +- (void)extractResponded:(NSNotification *)n { - status = @"ERROR"; - error = @"Download Cancelled"; + NSData *data = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem]; + NSString *response = [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease]; - [[NSFileManager defaultManager] removeItemAtPath:filename error:NULL]; - bytesCompleted = bytesTotal = -1; + // 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) + { + NSLog(@"%@",line); + 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"; + [error autorelease]; + error = [message retain]; + } + + else if ([type isEqualToString:@"Status"]) + { + if ([message isEqualToString:@"Completed"]) + { + status = @"EXTRACTED"; + } + } + } + [[JSBridge sharedInstance] notifyExtractProgress:self]; - [[JSBridge sharedInstance] notifyDownloadProgress:self]; - [[NSNotificationCenter defaultCenter] removeObserver:self - name:NSFileHandleReadCompletionNotification - object:[[task standardOutput] fileHandleForReading]]; - [task terminate]; - return YES; + // Keep reading + if ([n object] != nil) + [[n object] readInBackgroundAndNotify]; } - (void)utilityTerminated:(NSNotification *)n { - NSLog(@"utility terminated"); + NSLog(@"download terminated"); NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc removeObserver:self name:NSFileHandleReadCompletionNotification object:[[task standardOutput] fileHandleForReading]]; [nc removeObserver:self name:NSTaskDidTerminateNotification object:task]; diff --git a/OpenRA.Launcher.Mac/JSBridge.m b/OpenRA.Launcher.Mac/JSBridge.m index ebb7df40da..65c2e92068 100644 --- a/OpenRA.Launcher.Mac/JSBridge.m +++ b/OpenRA.Launcher.Mac/JSBridge.m @@ -52,6 +52,7 @@ static JSBridge *SharedInstance; @"downloadError", NSStringFromSelector(@selector(downloadError:)), @"bytesCompleted", NSStringFromSelector(@selector(bytesCompleted:)), @"bytesTotal", NSStringFromSelector(@selector(bytesTotal:)), + @"extractDownload", NSStringFromSelector(@selector(extractDownload:toPath:inMod:)), nil] retain]; } return self; @@ -153,6 +154,40 @@ static JSBridge *SharedInstance; [NSString stringWithFormat:@"downloadProgressed('%@')",[download key]]]; } +- (void)notifyExtractProgress:(Download *)download +{ + [[[controller webView] windowScriptObject] evaluateWebScript: + [NSString stringWithFormat:@"extractProgressed('%@')",[download key]]]; +} + +- (BOOL)extractDownload:(NSString *)key toPath:(NSString *)aFile inMod:(NSString *)aMod +{ + Download *d = [controller downloadWithKey:key]; + if (d == nil) + { + NSLog(@"Unknown download"); + return NO; + } + if (![[d status] isEqualToString:@"DOWNLOADED"]) + { + NSLog(@"Invalid download status"); + return NO; + } + + id mod = [[controller allMods] objectForKey:aMod]; + if (mod == nil) + { + NSLog(@"Invalid or unknown mod: %@", aMod); + return NO; + } + + // Disallow traversing up the directory tree + id path = [aMod stringByAppendingPathComponent:[aFile stringByReplacingOccurrencesOfString:@"../" + withString:@""]]; + + [d extractToPath:path]; + return YES; +} - (void)log:(NSString *)message { diff --git a/mods/cnc/mod.html b/mods/cnc/mod.html index daeb1e9103..99abf38818 100644 --- a/mods/cnc/mod.html +++ b/mods/cnc/mod.html @@ -57,7 +57,12 @@ { font-size:0.75em; } - + + .status + { + font-size:0.8em; + } + .button { border: solid 3px #650b03; @@ -113,7 +118,7 @@ function extract() { - window.external.log("Todo: Extract package"); + window.external.extractDownload("cnc-packages","packages","cnc"); } function onLoad() @@ -140,24 +145,31 @@ document.getElementById("download-available").style.display = "none"; document.getElementById("download-downloading").style.display = "none"; document.getElementById("download-extract").style.display = "none"; + document.getElementById("download-extracting").style.display = "none"; document.getElementById("download-error").style.display = "none"; - // status can be NOT_REGISTERED, AVAILABLE, DOWNLOADING, COMPLETE, ERROR + + // status can be NOT_REGISTERED, AVAILABLE, DOWNLOADING, DOWNLOADED, EXTRACTING, EXTRACTED, ERROR var status = window.external.downloadStatus("cnc-packages"); - // Download complete, offer button to extract it - if (status == "COMPLETE") + if (status == "AVAILABLE") { - document.getElementById("download-extract").style.display = ""; + document.getElementById("download-available").style.display = ""; } - // Show the download progress else if (status == "DOWNLOADING") { document.getElementById("download-downloading").style.display = ""; } - // Show the download button - else if (status == "AVAILABLE") + if (status == "DOWNLOADED") { - document.getElementById("download-available").style.display = ""; + document.getElementById("download-extract").style.display = ""; + } + else if (status == "EXTRACTING") + { + document.getElementById("download-extracting").style.display = ""; + } + else if (status == "EXTRACTED") + { + refreshSections(); } else if (status == "ERROR") { @@ -183,6 +195,16 @@ } refreshDownloadButtons(); } + + function extractProgressed(file) + { + if (file != "cnc-packages") + return; + + // Todo: show an extraction ticker? + + refreshDownloadButtons(); + } @@ -213,12 +235,15 @@ + -