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:
Paul Chote
2010-11-19 13:41:24 +13:00
parent c3521a2490
commit 0ee1d39bac
10 changed files with 201 additions and 66 deletions

View File

@@ -17,14 +17,19 @@
SidebarEntry *sidebarItems; SidebarEntry *sidebarItems;
GameInstall *game; GameInstall *game;
NSDictionary *allMods; NSDictionary *allMods;
NSMutableDictionary *downloads;
IBOutlet NSOutlineView *outlineView; IBOutlet NSOutlineView *outlineView;
IBOutlet WebView *webView; IBOutlet WebView *webView;
} }
@property(readonly) NSDictionary *allMods; @property(readonly) NSDictionary *allMods;
@property(readonly) WebView *webView;
- (void)launchMod:(NSString *)mod; - (void)launchMod:(NSString *)mod;
- (void)populateModInfo; - (void)populateModInfo;
- (SidebarEntry *)sidebarModsTree; - (SidebarEntry *)sidebarModsTree;
- (SidebarEntry *)sidebarOtherTree; - (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 @end

View File

@@ -12,14 +12,16 @@
#import "GameInstall.h" #import "GameInstall.h"
#import "ImageAndTextCell.h" #import "ImageAndTextCell.h"
#import "JSBridge.h" #import "JSBridge.h"
#import "Download.h"
@implementation Controller @implementation Controller
@synthesize allMods; @synthesize allMods;
@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];
downloads = [[NSMutableDictionary alloc] init];
NSTableColumn *col = [outlineView tableColumnWithIdentifier:@"mods"]; NSTableColumn *col = [outlineView tableColumnWithIdentifier:@"mods"];
ImageAndTextCell *imageAndTextCell = [[[ImageAndTextCell alloc] init] autorelease]; ImageAndTextCell *imageAndTextCell = [[[ImageAndTextCell alloc] init] autorelease];
@@ -50,6 +52,7 @@
- (void)dealloc - (void)dealloc
{ {
[sidebarItems release]; sidebarItems = nil; [sidebarItems release]; sidebarItems = nil;
[downloads release]; downloads = nil;
[super dealloc]; [super dealloc];
} }
@@ -90,10 +93,24 @@
[game launchMod:mod]; [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]; if ([downloads objectForKey:key] != nil)
return [game downloadUrl:url toPath:path withId:key]; {
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 #pragma mark Sidebar Datasource and Delegate

View 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

View 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

View File

@@ -9,8 +9,10 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
@class Mod; @class Mod;
@class Controller;
@interface GameInstall : NSObject { @interface GameInstall : NSObject {
NSURL *gameURL; NSURL *gameURL;
Controller *controller;
NSMutableDictionary *downloadTasks; NSMutableDictionary *downloadTasks;
} }
@property(readonly) NSURL *gameURL; @property(readonly) NSURL *gameURL;
@@ -20,5 +22,8 @@
- (NSString *)runUtilityQuery:(NSString *)arg; - (NSString *)runUtilityQuery:(NSString *)arg;
- (NSArray *)installedMods; - (NSArray *)installedMods;
- (NSDictionary *)infoForMods:(NSArray *)mods; - (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 @end

View File

@@ -7,6 +7,7 @@
*/ */
#import "GameInstall.h" #import "GameInstall.h"
#import "Controller.h"
#import "Mod.h" #import "Mod.h"
@implementation GameInstall @implementation GameInstall
@@ -47,7 +48,7 @@
NSString *current = nil; NSString *current = nil;
for (id l in lines) for (id l in lines)
{ {
id line = [l stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; NSString *line = [l stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if (line == nil || [line length] == 0) if (line == nil || [line length] == 0)
continue; continue;
@@ -143,21 +144,15 @@
return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease]; return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
} }
- (NSTask *)runAsyncUtilityWithArg:(NSString *)arg
- (BOOL)downloadUrl:(NSString *)url toPath:(NSString *)filename withId:(NSString *)key 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]; NSTask *task = [[[NSTask alloc] init] autorelease];
NSPipe *pipe = [NSPipe pipe]; NSPipe *pipe = [NSPipe pipe];
NSMutableArray *taskArgs = [NSMutableArray arrayWithObject:@"OpenRA.Utility.exe"]; NSMutableArray *taskArgs = [NSMutableArray arrayWithObject:@"OpenRA.Utility.exe"];
NSString *arg = [NSString stringWithFormat:@"--download-url=%@,%@",url,filename];
[taskArgs addObject:arg]; [taskArgs addObject:arg];
[task setCurrentDirectoryPath:[gameURL absoluteString]]; [task setCurrentDirectoryPath:[gameURL absoluteString]];
@@ -167,48 +162,17 @@
NSFileHandle *readHandle = [pipe fileHandleForReading]; NSFileHandle *readHandle = [pipe fileHandleForReading];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self [nc addObserver:object
selector:@selector(utilityResponded:) selector:response
name:NSFileHandleReadCompletionNotification name:NSFileHandleReadCompletionNotification
object:readHandle]; object:readHandle];
[nc addObserver:self [nc addObserver:object
selector:@selector(utilityTerminated:) selector:terminated
name:NSTaskDidTerminateNotification name:NSTaskDidTerminateNotification
object:task]; object:task];
[task launch]; [task launch];
[readHandle readInBackgroundAndNotify]; [readHandle readInBackgroundAndNotify];
return task;
[downloadTasks setObject:task forKey:key];
return YES;
} }
- (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 @end

View File

@@ -10,6 +10,7 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
@class Controller; @class Controller;
@class Download;
@interface JSBridge : NSObject { @interface JSBridge : NSObject {
Controller *controller; Controller *controller;
NSDictionary *methods; NSDictionary *methods;
@@ -18,5 +19,5 @@
+ (JSBridge *)sharedInstance; + (JSBridge *)sharedInstance;
- (void)setController:(Controller *)aController; - (void)setController:(Controller *)aController;
- (void)notifyDownloadProgress:(Download *)download;
@end @end

View File

@@ -8,6 +8,8 @@
#import "JSBridge.h" #import "JSBridge.h"
#import "Controller.h" #import "Controller.h"
#import "Download.h"
#import "Mod.h"
static JSBridge *SharedInstance; static JSBridge *SharedInstance;
@@ -40,9 +42,10 @@ 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:)),
@"installCncPackagesFromWeb", NSStringFromSelector(@selector(installCncPackagesFromWeb)),
@"fileExistsInMod", NSStringFromSelector(@selector(fileExists:inMod:)), @"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]; nil] retain];
} }
return self; return self;
@@ -59,7 +62,14 @@ static JSBridge *SharedInstance;
[super dealloc]; [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 - (BOOL)launchMod:(NSString *)aMod
{ {
@@ -89,11 +99,25 @@ static JSBridge *SharedInstance;
return YES; 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 // 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 - (void)log:(NSString *)message

View File

@@ -13,6 +13,7 @@
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
DA38212212925344003B0BB5 /* JSBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = DA38212112925344003B0BB5 /* JSBridge.m */; }; 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 */; }; DA81FA821290F5C800C48F2F /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = DA81FA811290F5C800C48F2F /* Controller.m */; };
DA81FAAA1290FA0000C48F2F /* Mod.m in Sources */ = {isa = PBXBuildFile; fileRef = DA81FAA91290FA0000C48F2F /* Mod.m */; }; DA81FAAA1290FA0000C48F2F /* Mod.m in Sources */ = {isa = PBXBuildFile; fileRef = DA81FAA91290FA0000C48F2F /* Mod.m */; };
DA81FB9312910A8B00C48F2F /* ImageAndTextCell.m in Sources */ = {isa = PBXBuildFile; fileRef = DA81FB9212910A8B00C48F2F /* ImageAndTextCell.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; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; DA81FAA81290FA0000C48F2F /* Mod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Mod.h; sourceTree = "<group>"; };
@@ -78,6 +81,8 @@
DA81FAA91290FA0000C48F2F /* Mod.m */, DA81FAA91290FA0000C48F2F /* Mod.m */,
DA81FC3D12911E2B00C48F2F /* GameInstall.h */, DA81FC3D12911E2B00C48F2F /* GameInstall.h */,
DA81FC3E12911E2B00C48F2F /* GameInstall.m */, DA81FC3E12911E2B00C48F2F /* GameInstall.m */,
DA7D85651295E92900E58547 /* Download.h */,
DA7D85661295E92900E58547 /* Download.m */,
DA92968E1292328200EDB02E /* SidebarEntry.h */, DA92968E1292328200EDB02E /* SidebarEntry.h */,
DA92968F1292328200EDB02E /* SidebarEntry.m */, DA92968F1292328200EDB02E /* SidebarEntry.m */,
DA38212012925344003B0BB5 /* JSBridge.h */, DA38212012925344003B0BB5 /* JSBridge.h */,
@@ -226,6 +231,7 @@
DA81FC3F12911E2B00C48F2F /* GameInstall.m in Sources */, DA81FC3F12911E2B00C48F2F /* GameInstall.m in Sources */,
DA9296901292328200EDB02E /* SidebarEntry.m in Sources */, DA9296901292328200EDB02E /* SidebarEntry.m in Sources */,
DA38212212925344003B0BB5 /* JSBridge.m in Sources */, DA38212212925344003B0BB5 /* JSBridge.m in Sources */,
DA7D85671295E92900E58547 /* Download.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View File

@@ -106,12 +106,25 @@
window.external.log("installFromCD()"); 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.downloadFileToCache("http://www.open-ra.org/get-dependency.php?file=cnc-packages","test.zip","cnc-packages");
//window.external.installCncPackagesFromWeb();
} }
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() function onLoad()
{ {
document.getElementById("buttons-install").style.display = (packagesInstalled() == 0) ? "" : "none"; 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 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. Installing from CD will also install the music and movie files for an improved game experience.
</div> </div>
<input type="button" class="button" onclick="installFromWeb();" value="Install from Web" /> <input type="button" class="button" onclick="download1();" value="Download1" />
<input type="button" class="button" onclick="installFromCD();" value="Install from CD" /> <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>
<div id="buttons-upgrade" class="buttons" style="display:none"> <div id="buttons-upgrade" class="buttons" style="display:none">
<div class="desc"> <div class="desc">