Refactor download mechanism to allow multiple concurrent downloads. Allow downloads to be cancelled. Allow js to query if files exist in the cache. Fix some compiler warnings.
This commit is contained in:
@@ -17,14 +17,19 @@
|
||||
SidebarEntry *sidebarItems;
|
||||
GameInstall *game;
|
||||
NSDictionary *allMods;
|
||||
NSMutableDictionary *downloads;
|
||||
IBOutlet NSOutlineView *outlineView;
|
||||
IBOutlet WebView *webView;
|
||||
}
|
||||
@property(readonly) NSDictionary *allMods;
|
||||
@property(readonly) WebView *webView;
|
||||
|
||||
- (void)launchMod:(NSString *)mod;
|
||||
- (void)populateModInfo;
|
||||
- (SidebarEntry *)sidebarModsTree;
|
||||
- (SidebarEntry *)sidebarOtherTree;
|
||||
- (BOOL)downloadUrl:(NSString *)url intoCache:(NSString *)filename withId:(NSString *)key;
|
||||
|
||||
- (BOOL)downloadUrl:(NSString *)url toFile:(NSString *)filename withId:(NSString *)key;
|
||||
- (void)cancelDownload:(NSString *)key;
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,14 +12,16 @@
|
||||
#import "GameInstall.h"
|
||||
#import "ImageAndTextCell.h"
|
||||
#import "JSBridge.h"
|
||||
#import "Download.h"
|
||||
|
||||
@implementation Controller
|
||||
@synthesize allMods;
|
||||
|
||||
@synthesize webView;
|
||||
- (void) awakeFromNib
|
||||
{
|
||||
game = [[GameInstall alloc] initWithURL:[NSURL URLWithString:@"/Users/paul/src/OpenRA"]];
|
||||
[[JSBridge sharedInstance] setController:self];
|
||||
downloads = [[NSMutableDictionary alloc] init];
|
||||
|
||||
NSTableColumn *col = [outlineView tableColumnWithIdentifier:@"mods"];
|
||||
ImageAndTextCell *imageAndTextCell = [[[ImageAndTextCell alloc] init] autorelease];
|
||||
@@ -50,6 +52,7 @@
|
||||
- (void)dealloc
|
||||
{
|
||||
[sidebarItems release]; sidebarItems = nil;
|
||||
[downloads release]; downloads = nil;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
@@ -90,10 +93,24 @@
|
||||
[game launchMod:mod];
|
||||
}
|
||||
|
||||
- (BOOL)downloadUrl:(NSString *)url intoCache:(NSString *)filename withId:(NSString *)key
|
||||
- (BOOL)downloadUrl:(NSString *)url toFile:(NSString *)path withId:(NSString *)key
|
||||
{
|
||||
id path = [[@"~/Library/Application Support/OpenRA/Downloads/" stringByAppendingPathComponent:filename] stringByExpandingTildeInPath];
|
||||
return [game downloadUrl:url toPath:path withId:key];
|
||||
if ([downloads objectForKey:key] != nil)
|
||||
{
|
||||
NSLog(@"Download already in progress for %@",key);
|
||||
return NO;
|
||||
}
|
||||
|
||||
|
||||
Download *download = [Download downloadWithURL:url filename:path key:key game:game];
|
||||
[downloads setObject:download forKey:key];
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)cancelDownload:(NSString *)key
|
||||
{
|
||||
[[downloads objectForKey:key] cancel];
|
||||
[downloads removeObjectForKey:key];
|
||||
}
|
||||
|
||||
#pragma mark Sidebar Datasource and Delegate
|
||||
|
||||
23
OpenRA.Launcher.Mac/Download.h
Normal file
23
OpenRA.Launcher.Mac/Download.h
Normal file
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Download.h
|
||||
// OpenRA
|
||||
//
|
||||
// Created by Paul Chote on 19/11/10.
|
||||
// Copyright 2010 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
@class GameInstall;
|
||||
@interface Download : NSObject
|
||||
{
|
||||
NSString *key;
|
||||
NSString *url;
|
||||
NSString *filename;
|
||||
GameInstall *game;
|
||||
NSTask *task;
|
||||
}
|
||||
+ (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;
|
||||
|
||||
@end
|
||||
74
OpenRA.Launcher.Mac/Download.m
Normal file
74
OpenRA.Launcher.Mac/Download.m
Normal file
@@ -0,0 +1,74 @@
|
||||
//
|
||||
// Download.m
|
||||
// OpenRA
|
||||
//
|
||||
// Created by Paul Chote on 19/11/10.
|
||||
// Copyright 2010 __MyCompanyName__. All rights reserved.
|
||||
//
|
||||
|
||||
#import "Download.h"
|
||||
#import "GameInstall.h"
|
||||
#import "JSBridge.h"
|
||||
|
||||
@implementation Download
|
||||
|
||||
+ (id)downloadWithURL:(NSString *)aURL filename:(NSString *)aFilename key:(NSString *)aKey game:(GameInstall *)aGame
|
||||
{
|
||||
id newObject = [[self alloc] initWithURL:aURL filename:aFilename key:aKey game:aGame];
|
||||
[newObject autorelease];
|
||||
return newObject;
|
||||
}
|
||||
|
||||
- (id)initWithURL:(NSString *)aURL filename:(NSString *)aFilename key:(NSString *)aKey game:(GameInstall *)aGame;
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil)
|
||||
{
|
||||
url = [aURL retain];
|
||||
filename = [aFilename retain];
|
||||
key = [aKey retain];
|
||||
game = [aGame retain];
|
||||
|
||||
NSLog(@"Starting download...");
|
||||
task = [game runAsyncUtilityWithArg:[NSString stringWithFormat:@"--download-url=%@,%@",url,filename]
|
||||
delegate:self
|
||||
responseSelector:@selector(utilityResponded:)
|
||||
terminatedSelector:@selector(utilityTerminated:)];
|
||||
[task retain];
|
||||
}
|
||||
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);
|
||||
|
||||
[[JSBridge sharedInstance] notifyDownloadProgress:self];
|
||||
|
||||
// Keep reading
|
||||
if ([n object] != nil)
|
||||
[[n object] readInBackgroundAndNotify];
|
||||
}
|
||||
|
||||
- (void)utilityTerminated:(NSNotification *)n
|
||||
{
|
||||
NSLog(@"utility terminated");
|
||||
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
||||
[nc removeObserver:self name:NSFileHandleReadCompletionNotification object:[[task standardOutput] fileHandleForReading]];
|
||||
[nc removeObserver:self name:NSTaskDidTerminateNotification object:task];
|
||||
[task release]; task = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -9,8 +9,10 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class Mod;
|
||||
@class Controller;
|
||||
@interface GameInstall : NSObject {
|
||||
NSURL *gameURL;
|
||||
Controller *controller;
|
||||
NSMutableDictionary *downloadTasks;
|
||||
}
|
||||
@property(readonly) NSURL *gameURL;
|
||||
@@ -20,5 +22,8 @@
|
||||
- (NSString *)runUtilityQuery:(NSString *)arg;
|
||||
- (NSArray *)installedMods;
|
||||
- (NSDictionary *)infoForMods:(NSArray *)mods;
|
||||
- (BOOL)downloadUrl:(NSString *)url toPath:(NSString *)filename withId:(NSString *)key;
|
||||
- (NSTask *)runAsyncUtilityWithArg:(NSString *)arg
|
||||
delegate:(id)object
|
||||
responseSelector:(SEL)response
|
||||
terminatedSelector:(SEL)terminated;
|
||||
@end
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
#import "GameInstall.h"
|
||||
#import "Controller.h"
|
||||
#import "Mod.h"
|
||||
|
||||
@implementation GameInstall
|
||||
@@ -47,7 +48,7 @@
|
||||
NSString *current = nil;
|
||||
for (id l in lines)
|
||||
{
|
||||
id line = [l stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
NSString *line = [l stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
if (line == nil || [line length] == 0)
|
||||
continue;
|
||||
|
||||
@@ -143,21 +144,15 @@
|
||||
return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)downloadUrl:(NSString *)url toPath:(NSString *)filename withId:(NSString *)key
|
||||
- (NSTask *)runAsyncUtilityWithArg:(NSString *)arg
|
||||
delegate:(id)object
|
||||
responseSelector:(SEL)response
|
||||
terminatedSelector:(SEL)terminated
|
||||
{
|
||||
NSLog(@"Starting download...");
|
||||
if ([downloadTasks objectForKey:key] != nil)
|
||||
{
|
||||
NSLog(@"Error: a download is already in progress for %@",key);
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSTask *task = [[[NSTask alloc] init] autorelease];
|
||||
NSPipe *pipe = [NSPipe pipe];
|
||||
|
||||
NSMutableArray *taskArgs = [NSMutableArray arrayWithObject:@"OpenRA.Utility.exe"];
|
||||
NSString *arg = [NSString stringWithFormat:@"--download-url=%@,%@",url,filename];
|
||||
[taskArgs addObject:arg];
|
||||
|
||||
[task setCurrentDirectoryPath:[gameURL absoluteString]];
|
||||
@@ -167,48 +162,17 @@
|
||||
|
||||
NSFileHandle *readHandle = [pipe fileHandleForReading];
|
||||
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
||||
[nc addObserver:self
|
||||
selector:@selector(utilityResponded:)
|
||||
[nc addObserver:object
|
||||
selector:response
|
||||
name:NSFileHandleReadCompletionNotification
|
||||
object:readHandle];
|
||||
[nc addObserver:self
|
||||
selector:@selector(utilityTerminated:)
|
||||
[nc addObserver:object
|
||||
selector:terminated
|
||||
name:NSTaskDidTerminateNotification
|
||||
object:task];
|
||||
[task launch];
|
||||
[readHandle readInBackgroundAndNotify];
|
||||
|
||||
[downloadTasks setObject:task forKey:key];
|
||||
return YES;
|
||||
return task;
|
||||
}
|
||||
|
||||
- (void)utilityResponded:(NSNotification *)n
|
||||
{
|
||||
NSData *data = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem];
|
||||
NSString *response = [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
|
||||
NSLog(@"r: %@",response);
|
||||
|
||||
// Keep reading
|
||||
if ([n object] != nil)
|
||||
[[n object] readInBackgroundAndNotify];
|
||||
}
|
||||
|
||||
- (void)utilityTerminated:(NSNotification *)n
|
||||
{
|
||||
id task = [n object];
|
||||
id pipe = [task standardOutput];
|
||||
|
||||
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
|
||||
[nc removeObserver:self name:NSFileHandleReadCompletionNotification object:[pipe fileHandleForReading]];
|
||||
[nc removeObserver:self name:NSTaskDidTerminateNotification object:task];
|
||||
}
|
||||
|
||||
- (void)cancelDownload:(NSString *)key
|
||||
{
|
||||
id task = [downloadTasks objectForKey:key];
|
||||
if (task == nil)
|
||||
return;
|
||||
|
||||
[task interrupt];
|
||||
}
|
||||
@end
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class Controller;
|
||||
@class Download;
|
||||
@interface JSBridge : NSObject {
|
||||
Controller *controller;
|
||||
NSDictionary *methods;
|
||||
@@ -18,5 +19,5 @@
|
||||
|
||||
+ (JSBridge *)sharedInstance;
|
||||
- (void)setController:(Controller *)aController;
|
||||
|
||||
- (void)notifyDownloadProgress:(Download *)download;
|
||||
@end
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#import "JSBridge.h"
|
||||
#import "Controller.h"
|
||||
#import "Download.h"
|
||||
#import "Mod.h"
|
||||
|
||||
static JSBridge *SharedInstance;
|
||||
|
||||
@@ -40,9 +42,10 @@ static JSBridge *SharedInstance;
|
||||
methods = [[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
@"launchMod", NSStringFromSelector(@selector(launchMod:)),
|
||||
@"log", NSStringFromSelector(@selector(log:)),
|
||||
@"installCncPackagesFromWeb", NSStringFromSelector(@selector(installCncPackagesFromWeb)),
|
||||
@"fileExistsInMod", NSStringFromSelector(@selector(fileExists:inMod:)),
|
||||
@"downloadFileToCache",NSStringFromSelector(@selector(downloadFile:intoCacheWithName:id:)),
|
||||
@"fileExistsInCache", NSStringFromSelector(@selector(fileExistsInCache:)),
|
||||
@"downloadFileToCache", NSStringFromSelector(@selector(downloadFileIntoCache:withName:key:)),
|
||||
@"cancelDownload", NSStringFromSelector(@selector(cancelDownload:)),
|
||||
nil] retain];
|
||||
}
|
||||
return self;
|
||||
@@ -59,7 +62,14 @@ static JSBridge *SharedInstance;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
#pragma mark JS methods
|
||||
- (void)notifyDownloadProgress:(Download *)download
|
||||
{
|
||||
NSLog(@"notified");
|
||||
//[[[controller webView] windowScriptObject] evaluateWebScript:
|
||||
// @"updateDownloadStatus(’sample_graphic.jpg’, ‘320’, ‘240’)"];
|
||||
}
|
||||
|
||||
#pragma mark JS API methods
|
||||
|
||||
- (BOOL)launchMod:(NSString *)aMod
|
||||
{
|
||||
@@ -89,11 +99,25 @@ static JSBridge *SharedInstance;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)downloadFile:(NSString *)url intoCacheWithName:(NSString *)name id:(NSString *)did
|
||||
- (BOOL)fileExistsInCache:(NSString *)name
|
||||
{
|
||||
NSLog(@"downloadFile:%@ intoCacheWithName:%@ id:%@",url,name,did);
|
||||
// Disallow traversing directories; take only the last component
|
||||
[controller downloadUrl:url intoCache:[name lastPathComponent] withId:did];
|
||||
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
|
||||
{
|
||||
NSLog(@"downloadFile:%@ intoCacheWithName:%@ key:%@",url,name,key);
|
||||
|
||||
// Disallow traversing directories; take only the last component
|
||||
id path = [[@"~/Library/Application Support/OpenRA/Downloads/" stringByAppendingPathComponent:[name lastPathComponent]] stringByExpandingTildeInPath];
|
||||
[controller downloadUrl:url toFile:path withId:key];
|
||||
}
|
||||
|
||||
- (void)cancelDownload:(NSString *)key
|
||||
{
|
||||
[controller cancelDownload:key];
|
||||
}
|
||||
|
||||
- (void)log:(NSString *)message
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
|
||||
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
|
||||
DA38212212925344003B0BB5 /* JSBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = DA38212112925344003B0BB5 /* JSBridge.m */; };
|
||||
DA7D85671295E92900E58547 /* Download.m in Sources */ = {isa = PBXBuildFile; fileRef = DA7D85661295E92900E58547 /* Download.m */; };
|
||||
DA81FA821290F5C800C48F2F /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = DA81FA811290F5C800C48F2F /* Controller.m */; };
|
||||
DA81FAAA1290FA0000C48F2F /* Mod.m in Sources */ = {isa = PBXBuildFile; fileRef = DA81FAA91290FA0000C48F2F /* Mod.m */; };
|
||||
DA81FB9312910A8B00C48F2F /* ImageAndTextCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DA81FB9212910A8B00C48F2F /* ImageAndTextCell.m */; };
|
||||
@@ -37,6 +38,8 @@
|
||||
8D1107320486CEB800E47090 /* OpenRA.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenRA.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
DA38212012925344003B0BB5 /* JSBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSBridge.h; sourceTree = "<group>"; };
|
||||
DA38212112925344003B0BB5 /* JSBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSBridge.m; sourceTree = "<group>"; };
|
||||
DA7D85651295E92900E58547 /* Download.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Download.h; sourceTree = "<group>"; };
|
||||
DA7D85661295E92900E58547 /* Download.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Download.m; sourceTree = "<group>"; };
|
||||
DA81FA801290F5C800C48F2F /* Controller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Controller.h; sourceTree = "<group>"; };
|
||||
DA81FA811290F5C800C48F2F /* Controller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Controller.m; sourceTree = "<group>"; };
|
||||
DA81FAA81290FA0000C48F2F /* Mod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Mod.h; sourceTree = "<group>"; };
|
||||
@@ -78,6 +81,8 @@
|
||||
DA81FAA91290FA0000C48F2F /* Mod.m */,
|
||||
DA81FC3D12911E2B00C48F2F /* GameInstall.h */,
|
||||
DA81FC3E12911E2B00C48F2F /* GameInstall.m */,
|
||||
DA7D85651295E92900E58547 /* Download.h */,
|
||||
DA7D85661295E92900E58547 /* Download.m */,
|
||||
DA92968E1292328200EDB02E /* SidebarEntry.h */,
|
||||
DA92968F1292328200EDB02E /* SidebarEntry.m */,
|
||||
DA38212012925344003B0BB5 /* JSBridge.h */,
|
||||
@@ -226,6 +231,7 @@
|
||||
DA81FC3F12911E2B00C48F2F /* GameInstall.m in Sources */,
|
||||
DA9296901292328200EDB02E /* SidebarEntry.m in Sources */,
|
||||
DA38212212925344003B0BB5 /* JSBridge.m in Sources */,
|
||||
DA7D85671295E92900E58547 /* Download.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -106,12 +106,25 @@
|
||||
window.external.log("installFromCD()");
|
||||
}
|
||||
|
||||
function installFromWeb()
|
||||
function download1()
|
||||
{
|
||||
window.external.downloadFileToCache("http://www.open-ra.org/get-dependency.php?file=cnc-packages","test.zip","cnc-packages");
|
||||
//window.external.installCncPackagesFromWeb();
|
||||
window.external.downloadFileToCache("http://www.open-ra.org/get-dependency.php?file=cnc-packages","test.zip","cnc-packages");
|
||||
}
|
||||
|
||||
function download2()
|
||||
{
|
||||
window.external.downloadFileToCache("http://www.open-ra.org/get-dependency.php?file=ra-packages","test2.zip","ra-packages");
|
||||
}
|
||||
|
||||
function cancel1()
|
||||
{
|
||||
window.external.cancelDownload("cnc-packages");
|
||||
}
|
||||
|
||||
function cancel2()
|
||||
{
|
||||
window.external.cancelDownload("ra-packages");
|
||||
}
|
||||
|
||||
function onLoad()
|
||||
{
|
||||
document.getElementById("buttons-install").style.display = (packagesInstalled() == 0) ? "" : "none";
|
||||
@@ -142,8 +155,11 @@
|
||||
Installing from web will install the minimal files required to play.<br />
|
||||
Installing from CD will also install the music and movie files for an improved game experience.
|
||||
</div>
|
||||
<input type="button" class="button" onclick="installFromWeb();" value="Install from Web" />
|
||||
<input type="button" class="button" onclick="installFromCD();" value="Install from CD" />
|
||||
<input type="button" class="button" onclick="download1();" value="Download1" />
|
||||
<input type="button" class="button" onclick="cancel1();" value="Cancel1" />
|
||||
<input type="button" class="button" onclick="download2();" value="Download2" />
|
||||
<input type="button" class="button" onclick="cancel2();" value="Cancel2" />
|
||||
|
||||
</div>
|
||||
<div id="buttons-upgrade" class="buttons" style="display:none">
|
||||
<div class="desc">
|
||||
|
||||
Reference in New Issue
Block a user