Zip extraction support.

This commit is contained in:
Paul Chote
2010-11-25 16:56:45 +13:00
parent eff5e409c0
commit 10f9e3e787
5 changed files with 146 additions and 34 deletions

View File

@@ -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]])

View File

@@ -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

View File

@@ -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];

View File

@@ -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
{

View File

@@ -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();
}
</script>
</head>
<body onload="onLoad();">
@@ -213,12 +235,15 @@
<input type="button" class="button" id="download-status" value="Initializing..." />
<input type="button" class="button" onclick="cancel();" value="Cancel" />
</div>
<div id="download-error" style="display:none">
<input type="button" class="button" onclick="download();" value="Retry" />
<span class="status" id="error-message"></span>
</div>
<div id="download-extract" style="display:none">
<input type="button" class="button" onclick="extract();" value="Install" />
</div>
<div id="download-error" style="display:none">
<input type="button" class="button" onclick="download();" value="Retry" />
<span class="desc" id="error-message"></span>
<div id="download-extracting" style="display:none">
<input type="button" class="button" value="Installing..." />
</div>
</div>
<div id="buttons-upgrade" class="buttons" style="display:none">