Notify javascript when download status changes. Allow js to query information about a download.

This commit is contained in:
Paul Chote
2010-11-19 20:16:02 +13:00
parent 0ee1d39bac
commit b4b05c3f4e
6 changed files with 123 additions and 30 deletions

View File

@@ -12,6 +12,7 @@
@class SidebarEntry; @class SidebarEntry;
@class GameInstall; @class GameInstall;
@class JSBridge; @class JSBridge;
@class Download;
@interface Controller : NSObject @interface Controller : NSObject
{ {
SidebarEntry *sidebarItems; SidebarEntry *sidebarItems;
@@ -31,5 +32,6 @@
- (BOOL)downloadUrl:(NSString *)url toFile:(NSString *)filename withId:(NSString *)key; - (BOOL)downloadUrl:(NSString *)url toFile:(NSString *)filename withId:(NSString *)key;
- (void)cancelDownload:(NSString *)key; - (void)cancelDownload:(NSString *)key;
- (Download *)downloadWithKey:(NSString *)key;
@end @end

View File

@@ -17,7 +17,8 @@
@implementation Controller @implementation Controller
@synthesize allMods; @synthesize allMods;
@synthesize webView; @synthesize webView;
- (void) awakeFromNib
- (void)awakeFromNib
{ {
game = [[GameInstall alloc] initWithURL:[NSURL URLWithString:@"/Users/paul/src/OpenRA"]]; game = [[GameInstall alloc] initWithURL:[NSURL URLWithString:@"/Users/paul/src/OpenRA"]];
[[JSBridge sharedInstance] setController:self]; [[JSBridge sharedInstance] setController:self];
@@ -101,7 +102,6 @@
return NO; return NO;
} }
Download *download = [Download downloadWithURL:url filename:path key:key game:game]; Download *download = [Download downloadWithURL:url filename:path key:key game:game];
[downloads setObject:download forKey:key]; [downloads setObject:download forKey:key];
return YES; return YES;
@@ -113,6 +113,11 @@
[downloads removeObjectForKey:key]; [downloads removeObjectForKey:key];
} }
- (Download *)downloadWithKey:(NSString *)key
{
return [downloads objectForKey:key];
}
#pragma mark Sidebar Datasource and Delegate #pragma mark Sidebar Datasource and Delegate
- (int)outlineView:(NSOutlineView *)anOutlineView numberOfChildrenOfItem:(id)item - (int)outlineView:(NSOutlineView *)anOutlineView numberOfChildrenOfItem:(id)item
{ {

View File

@@ -7,6 +7,15 @@
// //
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
typedef enum {
Initializing,
Downloading,
Complete,
Cancelled,
Error
} DownloadStatus;
@class GameInstall; @class GameInstall;
@interface Download : NSObject @interface Download : NSObject
{ {
@@ -15,7 +24,15 @@
NSString *filename; NSString *filename;
GameInstall *game; GameInstall *game;
NSTask *task; 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)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; - (id)initWithURL:(NSString *)aURL filename:(NSString *)aFilename key:(NSString *)aKey game:(GameInstall *)game;
- (void)cancel; - (void)cancel;

View File

@@ -11,6 +11,10 @@
#import "JSBridge.h" #import "JSBridge.h"
@implementation Download @implementation Download
@synthesize key;
@synthesize status;
@synthesize bytesCompleted;
@synthesize bytesTotal;
+ (id)downloadWithURL:(NSString *)aURL filename:(NSString *)aFilename key:(NSString *)aKey game:(GameInstall *)aGame + (id)downloadWithURL:(NSString *)aURL filename:(NSString *)aFilename key:(NSString *)aKey game:(GameInstall *)aGame
{ {
@@ -28,6 +32,9 @@
filename = [aFilename retain]; filename = [aFilename retain];
key = [aKey retain]; key = [aKey retain];
game = [aGame retain]; game = [aGame retain];
status = Initializing;
bytesCompleted = -1;
bytesTotal = -1;
NSLog(@"Starting download..."); NSLog(@"Starting download...");
task = [game runAsyncUtilityWithArg:[NSString stringWithFormat:@"--download-url=%@,%@",url,filename] task = [game runAsyncUtilityWithArg:[NSString stringWithFormat:@"--download-url=%@,%@",url,filename]
@@ -39,22 +46,47 @@
return self; 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 - (void)utilityResponded:(NSNotification *)n
{ {
NSData *data = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem]; NSData *data = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem];
NSString *response = [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease]; 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]; [[JSBridge sharedInstance] notifyDownloadProgress:self];
// Keep reading // Keep reading
@@ -62,6 +94,17 @@
[[n object] readInBackgroundAndNotify]; [[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 - (void)utilityTerminated:(NSNotification *)n
{ {
NSLog(@"utility terminated"); NSLog(@"utility terminated");

View File

@@ -42,10 +42,15 @@ static JSBridge *SharedInstance;
methods = [[NSDictionary dictionaryWithObjectsAndKeys: methods = [[NSDictionary dictionaryWithObjectsAndKeys:
@"launchMod", NSStringFromSelector(@selector(launchMod:)), @"launchMod", NSStringFromSelector(@selector(launchMod:)),
@"log", NSStringFromSelector(@selector(log:)), @"log", NSStringFromSelector(@selector(log:)),
@"fileExistsInMod", NSStringFromSelector(@selector(fileExists:inMod:)), @"existsInMod", NSStringFromSelector(@selector(exists:inMod:)),
@"fileExistsInCache", NSStringFromSelector(@selector(fileExistsInCache:)),
@"downloadFileToCache", NSStringFromSelector(@selector(downloadFileIntoCache:withName:key:)), // File downloading
@"existsInCache", NSStringFromSelector(@selector(existsInCache:)),
@"downloadToCache", NSStringFromSelector(@selector(downloadUrl:withName:key:)),
@"cancelDownload", NSStringFromSelector(@selector(cancelDownload:)), @"cancelDownload", NSStringFromSelector(@selector(cancelDownload:)),
@"isDownloading", NSStringFromSelector(@selector(isDownloading:)),
@"bytesCompleted", NSStringFromSelector(@selector(bytesCompleted:)),
@"bytesTotal", NSStringFromSelector(@selector(bytesTotal:)),
nil] retain]; nil] retain];
} }
return self; return self;
@@ -64,9 +69,8 @@ static JSBridge *SharedInstance;
- (void)notifyDownloadProgress:(Download *)download - (void)notifyDownloadProgress:(Download *)download
{ {
NSLog(@"notified"); [[[controller webView] windowScriptObject] evaluateWebScript:
//[[[controller webView] windowScriptObject] evaluateWebScript: [NSString stringWithFormat:@"downloadProgressed('%@')",[download key]]];
// @"updateDownloadStatus(sample_graphic.jpg, 320, 240)"];
} }
#pragma mark JS API methods #pragma mark JS API methods
@@ -99,14 +103,14 @@ static JSBridge *SharedInstance;
return YES; return YES;
} }
- (BOOL)fileExistsInCache:(NSString *)name - (BOOL)existsInCache:(NSString *)name
{ {
// Disallow traversing directories; take only the last component // Disallow traversing directories; take only the last component
id path = [[@"~/Library/Application Support/OpenRA/Downloads/" stringByAppendingPathComponent:[name lastPathComponent]] stringByExpandingTildeInPath]; id path = [[@"~/Library/Application Support/OpenRA/Downloads/" stringByAppendingPathComponent:[name lastPathComponent]] stringByExpandingTildeInPath];
return [[NSFileManager defaultManager] fileExistsAtPath:path]; 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); NSLog(@"downloadFile:%@ intoCacheWithName:%@ key:%@",url,name,key);
@@ -120,6 +124,23 @@ static JSBridge *SharedInstance;
[controller cancelDownload:key]; [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 - (void)log:(NSString *)message
{ {
NSLog(@"js: %@",message); NSLog(@"js: %@",message);

View File

@@ -90,10 +90,10 @@
// Returns 2 if basic files plus music are installed // Returns 2 if basic files plus music are installed
function packagesInstalled() function packagesInstalled()
{ {
if (window.external.fileExistsInMod('packages/conquer.mix','cnc') != 1) if (window.external.existsInMod('packages/conquer.mix','cnc') != 1)
return 0; 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() function play()
@@ -101,18 +101,14 @@
window.external.launchMod("cnc"); window.external.launchMod("cnc");
} }
function installFromCD()
{
window.external.log("installFromCD()");
}
function download1() 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() 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() function cancel1()
@@ -131,6 +127,15 @@
document.getElementById("buttons-upgrade").style.display = (packagesInstalled() == 1) ? "" : "none"; document.getElementById("buttons-upgrade").style.display = (packagesInstalled() == 1) ? "" : "none";
document.getElementById("buttons-play").style.display = (packagesInstalled() == 2) ? "" : "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+"%)");
}
</script> </script>
</head> </head>
<body onload="onLoad();"> <body onload="onLoad();">