Files
watrbx-game-engine/Installer/BootstrapperMac/Roblox/Controller.mm
T
2025-09-18 17:55:52 -04:00

2532 lines
88 KiB
Plaintext

//
// Controller.m
// Roblox
//
// Created by Dharmendra on 14/01/11.
// Copyright 2011 __MyCompanyName__. All rights reserved.
//
#import "Controller.h"
#import "CustomUIObjects.h"
#include "CookiesEngine.h"
#include <string>
#include <sstream>
#include <semaphore.h>
#define RAPIDJSON_ASSERT(exp) (void)(exp)
#include "document.h"
#include "writer.h"
#include "stringbuffer.h"
#include "BootstrapperSettings.h"
#include "RobloxServicesTools.h"
#include "InfluxDbHelper.h"
//Apple script code for activating an application
#define ACTIVATE_APP @"tell application \"%@\" \n activate \n end tell"
//client upgrade related
#define MAX_TICKS 20
#ifdef TARGET_IS_STUDIO
#define UPDATE_MESSAGE @"Roblox Studio will update in %d seconds..."
#else
#define UPDATE_MESSAGE @"Roblox will update in %d seconds..."
#endif
//cookies related
#define RBX_COOKIES_PATH_KEY "CPath"
#define RBX_COOKIES_PATH "/tmp/roblox/rbxcsettings.rbx" //should this file go in Application Support folder?
static NSString * const sAnalyticsPath = @"Analytics/Measurement.ashx?IpFilter=primary&SecondaryFilterName=guid&SecondaryFilterValue=";
static NSString * const sDownloadDir =@"Download";
static NSString * const sBackupDir =@"Backup";
//file to store update related information
static NSString * const sLocalUpdateInfoFile =@"updateInfo.plist";
//Keys used in Preference file
static NSString * const sPFInstallHostKey =@"RbxInstallHost";
static NSString * const sPFBaseServerKey =@"RbxBaseUrl";
static NSString * const sPFPluginVersion =@"RbxPluginVersion";
//transient keys for plugin and application interaction
static NSString * const sPFIsPluginHandlingKey =@"RbxIsPluginHandling";
//keys used in updateInfo.plist
static NSString * const sBaseServerKey =@"BaseServer";
static NSString * const sSetupServerKey =@"SetupServer";
static NSString * const sServerVersionKey =@"ServerVersion";
static NSString * const sEmbeddedKey =@"Embedded";
//keys used in RobloxPlayer-Info.list
static NSString * const sRPBaseUrlKey =@"RbxBaseUrl";
static NSString * const sRPInstallHostKey =@"RbxInstallHost";
//relaunch daemon
static NSString * const sRelaunchApplicationDaemon =@"Contents/Resources/ReLaunchApplication";
static NSString * const sStudioBootstrapperZipFile =@"RobloxStudio.zip";
static NSString * const sStudioBootstrapperApplication =@"RobloxStudio.app";
static NSString * const sServerStudioVersionFile = @"versionStudio";
static NSString * const sPreferenceFileID = @"wtf.watrbx.Roblox";
static NSString * const sURLHandlerArgsSeparator = @"+";
//version update and target related consts
#ifdef TARGET_IS_STUDIO
//TODO: replace all occurances of Roblox with appName
static NSString * const sAppName =@"ROBLOX Studio";
static NSString * const sServerVersionFile = sServerStudioVersionFile;
static NSString * const sBootstrapperVersionFile =@"RobloxStudioVersion.txt";
static NSString * const sClientApplicationBI =@"wtf.watrbx.RobloxStudio";
static NSString * const sClientApplication =@"RobloxStudio.app";
static NSString * const sClientZipFile =@"RobloxStudioApp.zip";
static NSString * const sBootstrapperApplication =@"RobloxStudio.app";
static NSString * const sBootstrapperZipFile =@"RobloxStudio.zip";
static NSString * const sNPAPIPlugin =@"NPRoblox.plugin";
static NSString * const sTransformedApplication =@"RobloxStudio.app";
static NSString * const sPFClientAppPathKey =@"RbxStudioAppPath";
static NSString * const sPFBootstrapperPathKey =@"RbxStudioBootstrapperPath";
static NSString * const sPFIsUptoDateKey =@"RbxIsStudioUptoDate";
static NSString * const sPFUpdateCancelledKey =@"RbxStudioUpdateCancelled";
static NSString * const sURLHandlerPrefix =@"roblox-studio";
static const char* sWaitingSemaphoreName ="/robloxStudioStartedEvent";
static const char* sSettingsGroup ="WindowsStudioBootstrapperSettings";
#else
static NSString * const sAppName =@"ROBLOX";
static NSString * const sServerVersionFile =@"version";
static NSString * const sBootstrapperVersionFile =@"RobloxVersion.txt";
static NSString * const sClientApplicationBI =@"wtf.watrbx.RobloxPlayer";
static NSString * const sClientApplication =@"RobloxPlayer.app";
static NSString * const sClientZipFile =@"RobloxPlayer.zip";
static NSString * const sBootstrapperApplication =@"Roblox.app";
static NSString * const sBootstrapperZipFile =@"Roblox.zip";
static NSString * const sNPAPIPlugin =@"NPRoblox.plugin";
static NSString * const sTransformedApplication =@"Roblox.app";
static NSString * const sPFClientAppPathKey =@"RbxClientAppPath";
static NSString * const sPFBootstrapperPathKey =@"RbxBootstrapperPath";
static NSString * const sPFIsUptoDateKey =@"RbxIsUptoDate";
static NSString * const sPFUpdateCancelledKey =@"RbxUpdateCancelled";
static NSString * const sURLHandlerPrefix =@"roblox-player";
static const char* sWaitingSemaphoreName ="/robloxPlayerStartedEvent";
static const char* sSettingsGroup ="WindowsBootstrapperSettings";
#endif
static NSString * const sContentsFolder =@"Contents";
static NSString * const sAppAdminInstallPath =@"/Applications";
static NSString * const sPluginAdminInstallPath =@"/Library/Internet Plug-ins";
static NSString * const sAppUserInstallPath =@"~/Applications";
static NSString * const sPluginUserInstallPath =@"~/Library/Internet Plug-ins";
@implementation Controller
- (void) initData
{
srand(time(0));
startTime = [NSDate date];
[startTime retain];
isInstalling = YES;
isDownloadingBootstrapper = NO;
isDownloadingStudioBootstrapper = NO;
isReportStatEnabled = NO;
isEmbedded = NO;
justEmbedded = NO;
installSuccess = NO;
isMac10_5 = NO;
[self setupInfluxDb];
//get the appropriate server information from the infolist
baseServer = [[NSBundle mainBundle] objectForInfoDictionaryKey:sBaseServerKey];
[baseServer retain];
setupServer = [[NSBundle mainBundle] objectForInfoDictionaryKey:sSetupServerKey];
[setupServer retain];
//get server version
serverVersion = [[self stringWithContentsOfURL:[NSString stringWithFormat:@"%@%@", setupServer, sServerVersionFile]] retain];
serverStudioVersion = [[self stringWithContentsOfURL:[NSString stringWithFormat:@"%@%@", setupServer, sServerStudioVersionFile]] retain];
//check if user is having write permissions in /Applications folder
isAdminUser = [[NSFileManager defaultManager] isWritableFileAtPath:sAppAdminInstallPath];
launchedFromReadOnlyFS = NO;
// check if the bundle path is writable
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
launchedFromReadOnlyFS = ([bundlePath hasPrefix:@"/Volumes/"] && ![[NSFileManager defaultManager] isWritableFileAtPath:bundlePath]);
NSString *isEmbeddedStr = [self getValueFromKey:sEmbeddedKey];
isEmbedded = (isEmbeddedStr != nil && [isEmbeddedStr isEqualToString:@"true"]);
isClientAppChanged = [self isVersionChanged];
NSLog(@"Client app changed - %d", isClientAppChanged);
if (isInstalling)
{
//first set cookies path
CFPreferencesSetAppValue(CFSTR(RBX_COOKIES_PATH_KEY), CFSTR(RBX_COOKIES_PATH), (CFStringRef)sClientApplicationBI);
std::wstring path = CookiesEngine::getCookiesFilePath();
if (path.length() > 0) {
CookiesEngine engine(path);
CookiesEngine::reportValue(engine, "rbx_evt_initial_install_start", "{\"os\" : \"OSX\"}");
}
}
//report stat enabling should be done only when we are connected to internet
if (serverVersion)
{
reportStatGuid = rand();
//see if we need to enable reportStat
NSRange const findTestRange = [baseServer rangeOfString:@"test"];
if (findTestRange.location != NSNotFound)
isReportStatEnabled = YES;
else
{
isReportStatEnabled = ((reportStatGuid % 10) == 0); // Push only 10%
//isReportStatEnabled = ((reportStatGuid % 100) == 0); // Push 1%
}
}
NSLog(@"setupServer = %@", setupServer);
NSLog(@"serverVersion = %@", serverVersion);
NSLog(@"bundlePath = %@", [[NSBundle mainBundle] bundlePath]);
NSLog(@"launchedFromReadOnlyFS = %@", launchedFromReadOnlyFS ? @"Yes" : @"No");
NSLog(@"isReportStatEnabled = %@", isReportStatEnabled ? @"Yes" : @"No");
}
- (void) cleanUp
{
//cancel download
if (updateDownload)
{
[updateDownload cancel];
[self endUpdateDownload];
}
//TODO: cleanup NSString also
}
- (NSString*) osVersion
{
SInt32 versionMajor=0, versionMinor=0, versionBugFix=0;
Gestalt(gestaltSystemVersionMajor, &versionMajor);
Gestalt(gestaltSystemVersionMinor, &versionMinor);
Gestalt(gestaltSystemVersionBugFix, &versionBugFix);
return [NSString stringWithFormat:@"%d.%d.%d", (int)versionMajor, (int)versionMinor, (int)versionBugFix];
}
- (void) verifyVersion
{
NSString *osVerStr = [self osVersion];
NSRange range = [osVerStr rangeOfString:@"10.5"] ;
NSString *urlString = @"http://blog.watrbx.wtf/2012/09/roblox-discontinuing-support-for-mac-os-x-10-5/";
if (range.location == 0 && range.length == 4)
{
isMac10_5 = YES;
NSString *msg = [NSString stringWithFormat:@"Mac OS X Version %@", osVerStr];
NSLog(@"Unsupported Mac OSX Version %@", osVerStr);
if (NSAlertFirstButtonReturn == [self showAlertPanel:msg messageText:@"You are running a version of Mac OS X which is not supported" buttonText:@"OK"])
{
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
}
//[self exitApplication];
return;
}
}
- (void) awakeFromNib
{
//update background color to make it less transparent
NSColor* color = [NSColor colorWithDeviceRed:256.0f/256.0f green:256.0f/256.0f blue:256.0f/256.0f alpha:1.0f];
[progressPanel setBackgroundColor:color];
[progressPanel update];
[progressField setTextColor:[NSColor colorWithDeviceRed:26.0f/256.0f green:26.0f/256.0f blue:26.0f/256.0f alpha:0.6f]];
[progressPanelButton setImageName:@"btn_cancel"];
[[progressPanelButton cell] setHighlightsBy:NSNoCellMask];
}
- (void)applicationWillFinishLaunching:(NSNotification *)aNotification
{
NSLog(@"applicationWillFinishLaunching");
// install event filter to handle URL event
[[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(handleURLEvent:withReplyEvent:) forEventClass:kInternetEventClass andEventID:kAEGetURL];
}
- (void)handleURLEvent:(NSAppleEventDescriptor*)event withReplyEvent:(NSAppleEventDescriptor*)replyEvent
{
NSString* tempString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue];
urlHandlerString = [[tempString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] retain];
NSLog(@"URL - %@", urlHandlerString);
}
- (void) applicationDidFinishLaunching:(NSNotification*) aNotification
{
bootstrapperSettings = NULL;
// Check if we need to just show install success dialog?
bool installSuccessDialogReq = false;
NSArray* args = [[NSProcessInfo processInfo] arguments];
for (int i = 0; i < [args count]; i++)
{
NSString* arg = [args objectAtIndex:i];
if ([arg isEqualToString:@"-installsuccess"]) {
installSuccessDialogReq = true;
}
}
NSLog(@"install success - %d", installSuccessDialogReq);
if (installSuccessDialogReq)
{
if ([self getSettings].GetValueShowInstallSuccessPrompt())
{
[self showInstallSuccessDialog];
}
else
{
#ifdef TARGET_IS_STUDIO
[self installDialogClosing:nil];
#else
NSString *urlString = [NSString stringWithFormat: @"%@download/thankyou", [[NSBundle mainBundle] objectForInfoDictionaryKey:sBaseServerKey]];
NSLog(@"install success url string - %@", urlString);
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
[self exitApplication];
#endif
}
return;
}
#ifdef TARGET_IS_STUDIO
NSLog(@"Target is studio");
[progressField setStringValue: @"Configuring ROBLOX Studio..."];
// update image
NSSize newSize;
newSize.width = 90;
newSize.height = 90;
NSImage *image = [NSImage imageNamed:@"RobloxStudio"];
[image setSize:newSize];
[progressPanelImage setImage:image];
#endif
if (urlHandlerString && urlHandlerString.length > 0)
{
// set process to front, so we do not redirect to download page
ProcessSerialNumber psn;
GetCurrentProcess(&psn);
SetFrontProcess(&psn);
//show progress dialog now...
[self showProgressDialog];
}
[self verifyVersion];
//initialize data
[self initData];
if (launchedFromReadOnlyFS)
{
//if we are not connected to internet then we cannot launch application in read only file system
if (!serverVersion)
{
NSLog(@"Unable to connect to Roblox server");
NSString *msg = [NSString stringWithFormat:@"%@ cannot connect to the Internet", sAppName];
[self showAlertPanel:msg messageText:@"Unable to connect" buttonText:@"Cancel"];
[self exitApplication];
return;
}
NSLog(@"Application launched from read only file system (dmg)");
[self reportStat:@"Launched From .dmg"];
//copy bootstrapper at the desired location
if ([self copyDirectory:sContentsFolder source:[[NSBundle mainBundle] bundlePath] destination:[self getClientApplication]])
{
//make sure we don't get warning while launching application again
NSString * cmd = [NSString stringWithFormat:@"xattr -w com.apple.quarantine \"\" %@", [self getClientApplication]];
system([cmd UTF8String]);
//launch update
NSLog(@"applicationDidFinishLaunching - relaunchApplication");
[self relaunchApplication:[self getClientApplication] extraArguments:nil];
}
else
{
NSLog(@"Application installation failed");
[self reportStat:@"Client installation failed"];
}
[self exitApplication];
return;
}
//make sure we modify isUptoDate flag
CFPreferencesSetAppValue((CFStringRef)sPFIsUptoDateKey, CFSTR("false"), (CFStringRef) sPreferenceFileID);
CFPreferencesSetAppValue((CFStringRef)sPFUpdateCancelledKey, CFSTR("false"), (CFStringRef) sPreferenceFileID);
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
//check if we need to handle command line options
if ([self handleCommandLineOptions])
return;
//save protocol options if any
[self saveProtocolArguments];
//embedded case should "only" be handled by command line mode (no interactive support)
if (isEmbedded && [urlHandlerArguments count] < 1)
{
NSLog(@"ERROR: Embedded application launched without command line options");
[self reportStat:@"ERROR: Embedded Application Launched"];
[self exitApplication];
return;
}
//if we are not connected to internet then we cannot launch application in read only file system
if (!serverVersion)
{
NSLog(@"Unable to connect to Roblox server");
NSString *msg = [NSString stringWithFormat: @"%@ cannot connect to the Internet.", sAppName];
[self showAlertPanel:msg messageText:@"Unable to connect" buttonText:@"Cancel"];
[self exitApplication];
return;
}
// if we have protocol arguments and no update is required, launch application NOW
if (([urlHandlerArguments count] > 0) && !isClientAppChanged && ![self isBootstrapperUpdateRequired])
{
[self launchClientApplication:urlHandlerArguments];
return;
}
NSLog(@"Application launched from app");
[self reportStat:@"Launched From .app"];
[self updateApplications];
}
- (BOOL) handleCommandLineOptions
{
NSString *checkRequired = [[NSUserDefaults standardUserDefaults] stringForKey:@"check"];
NSLog(@"Command Line: Handling command line options");
if (checkRequired && ([checkRequired isEqualToString:@"yes"] || [checkRequired isEqualToString:@"true"]))
return [self handleCheckOption];
NSLog(@"Command Line: No command line arguments");
return NO;
}
- (BOOL) handleCheckOption
{
// if version has not changed just quit the application
if (!isClientAppChanged && ![self isBootstrapperUpdateRequired])
{
NSLog(@"Command Line: No version change detected.");
[self reportStat:@"Command Line Option: Version Not Changed"];
installSuccess = YES;
//check if we need to launch player
NSString *playerLaunchRequired = [[NSUserDefaults standardUserDefaults] stringForKey:@"launchPlayer"];
NSString *checkStudioRequired = [[NSUserDefaults standardUserDefaults] stringForKey:@"checkStudio"];
if (playerLaunchRequired && [playerLaunchRequired isEqualToString:@"true"])
{
//Just in case, check if we need to deploy plugin
if ([self isNPAPIPluginDeploymentRequired])
{
NSLog(@"Plugin deployment in progress...");
[self deployNPAPIPlugin];
NSLog(@"Plugin deployment done...");
}
CFPreferencesSetAppValue((CFStringRef)sPFIsUptoDateKey, CFSTR("true"), (CFStringRef) sPreferenceFileID);
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
NSLog(@"handleCheckOption - relaunchApplication");
if ([urlHandlerArguments count] > 0)
{
[self launchClientApplication:urlHandlerArguments];
}
else
{
[self launchClientApplication:nil];
}
}
else if (checkStudioRequired && [checkStudioRequired isEqualToString:@"true"])
{
#ifndef TARGET_IS_STUDIO
// check if we need to deploy Studio bootstrapper
if (![self isStudioInstalled] || ![self applicationExistsInDock:[self getStudioApp] checkLink: YES])
{
NSLog(@"handleCheckOption - downloadStudioBootstrapper called");
[self downloadStudioBootstrapper];
}
else
{
// save settings to correctly configure studio on launch of Build or Edit
CFPreferencesSetAppValue(CFSTR("RbxStudioAppPath"), (CFStringRef) [self getStudioApp], (CFStringRef) sPreferenceFileID);
CFPreferencesSetAppValue(CFSTR("RbxStudioBootstrapperPath"), (CFStringRef) [self getStudioApp], (CFStringRef) sPreferenceFileID);
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
[self exitApplication];
}
#endif
}
else
{
[self exitApplication];
}
return YES;
}
[self reportStat:@"Command Line Option: Version Changed"];
parentProcessId = [[[NSUserDefaults standardUserDefaults] stringForKey:@"ppid"] intValue];
[self initUpdateData];
NSString *showUpdateUI = [[NSUserDefaults standardUserDefaults] stringForKey:@"updateUI"];
NSLog(@"ppid: %d, showUpdateUI: %@", parentProcessId, showUpdateUI);
// check if we have URL handler arguments
NSString *urlStringArgument = [[NSUserDefaults standardUserDefaults] stringForKey:@"urlString"];
if (urlStringArgument && [urlStringArgument length] > 0)
{
NSLog(@"Got URL arguments - %@", urlStringArgument);
urlHandlerString = [urlStringArgument retain];
//save protocol options if any
[self saveProtocolArguments];
}
//by default we will show the update panel
if (showUpdateUI != nil && [showUpdateUI isEqualToString:@"false"])
{
[self onUpdatePanelButtonClicked:NULL];
}
else
{
[updatePanel makeKeyAndOrderFront:self];
[updatePanel setLevel:NSModalPanelWindowLevel];
updateTimer = [NSTimer scheduledTimerWithTimeInterval : 1.0
target : self
selector: @selector(onTimerTick:)
userInfo: nil
repeats : YES];
}
return YES;
}
- (BOOL) saveProtocolArguments
{
if (!urlHandlerString || ![urlHandlerString hasPrefix:sURLHandlerPrefix])
return false;
NSArray *listItems = [urlHandlerString componentsSeparatedByString:sURLHandlerArgsSeparator];
if (listItems)
{
int count = [listItems count];
NSString* item;
urlHandlerArguments = [[NSMutableArray alloc] init];
for (int i = 1; i < count; i++)
{
NSLog (@"Element %i = %@", i, [listItems objectAtIndex: i]);
item = [listItems objectAtIndex: i];
NSRange range = [item rangeOfString:@":"];
if (range.location != NSNotFound)
{
if ([item hasPrefix:@"gameinfo"])
{
[urlHandlerArguments addObject:@"-ticket"];
[urlHandlerArguments addObject:[item substringFromIndex:range.location+1]];
}
else if ([item hasPrefix:@"browsertrackerid"])
{
[urlHandlerArguments addObject:@"-browserTrackerId"];
[urlHandlerArguments addObject:[item substringFromIndex:range.location+1]];
// assign to a local variable also so we can set into client requests
browserTrackerId = [item substringFromIndex:range.location+1];
}
#ifndef TARGET_IS_STUDIO
else if ([item hasPrefix:@"launchmode"])
{
//TODO
}
else if ([item hasPrefix:@"placelauncherurl"])
{
[urlHandlerArguments addObject:@"-scriptURL"];
[urlHandlerArguments addObject:[item substringFromIndex:range.location+1]];
}
#else
else if ([item hasPrefix:@"launchmode"])
{
NSString* mode = [item substringFromIndex:range.location+1];
if ([mode isEqualToString:@"build"])
[urlHandlerArguments addObject:@"-build"];
else if ([mode isEqualToString:@"ide"])
[urlHandlerArguments addObject:@"-ide"];
}
else if ([item hasPrefix:@"script"])
{
[urlHandlerArguments addObject:@"-script"];
[urlHandlerArguments addObject:[item substringFromIndex:range.location+1]];
}
#endif
}
#ifdef TARGET_IS_STUDIO
if ([item hasPrefix:@"avatar"])
{
NSRange range = [item rangeOfString:@":"];
if (range.location == NSNotFound || [[item substringFromIndex:range.location+1] isEqualToString:@"true"])
{
[urlHandlerArguments addObject:@"-avatar"];
}
}
#endif
}
#ifdef TARGET_IS_STUDIO
[urlHandlerArguments addObject:@"-url"];
#else
[urlHandlerArguments addObject:@"-authURL"];
#endif
// replace http with https
NSString* urlString =[NSString stringWithFormat:@"%@Login/Negotiate.ashx", baseServer];
urlString = [urlString stringByReplacingOccurrencesOfString:@"http://" withString:@"https://"];
[urlHandlerArguments addObject:urlString];
#ifdef TARGET_IS_STUDIO
[urlHandlerArguments addObject:@"-plugin"];
#endif
}
return true;
}
- (void) initUpdateData
{
ticks = 1;
//create update panel
updatePanel = NSGetAlertPanel(@"New updates available", @"", @"Update", @"Cancel", nil);
//get appropriate fields from the panel
NSEnumerator *objectEnum = [[[updatePanel contentView] subviews] objectEnumerator];
NSView *subView = nil;
int textFieldCount = 0;
while (subView = [objectEnum nextObject])
{
if ([subView isKindOfClass:[NSTextField class]])
{
textFieldCount++;
//we need to update second text field
if (textFieldCount == 2)
updateTextField = (NSTextField *)subView;
}
else if ([subView isKindOfClass:[NSButton class]])
{
//get update button's notifications
[(NSButton *)subView setTarget:self];
[(NSButton *)subView setAction:@selector(onUpdatePanelButtonClicked:)];
}
}
[updateTextField setStringValue:[NSString stringWithFormat:UPDATE_MESSAGE, MAX_TICKS]];
}
- (void) onTimerTick:(NSTimer *)timer
{
//if max ticks
if (ticks >= MAX_TICKS)
{
[self onUpdatePanelButtonClicked:NULL];
return;
}
//update panel text field with appropriate count
[updateTextField setStringValue:[NSString stringWithFormat:UPDATE_MESSAGE, MAX_TICKS - ticks]];
ticks++;
}
- (void) onUpdatePanelButtonClicked:(id)sender
{
[updateTimer invalidate];
[updatePanel orderOut:nil];
if (sender && ([[(NSButton *)sender title] isEqualToString:@"Cancel"]))
{
NSLog(@"Update cancelled...");
[self reportStat:@"Update User Cancelled"];
[self reportInstallMetricsToInfluxDb:TRUE result:@"Cancelled" message:@"User Cancelled"];
[self exitApplication];
return;
}
NSLog(@"Updating application...");
[updatePanel close];
[updatePanel release];
updateTextField = nil;
updatePanel = nil;
updateTimer = nil;
//kill parent process
ProcessSerialNumber psn;
NSLog(@"killing processId: %d", parentProcessId);
if (parentProcessId > 0 && (GetProcessForPID(parentProcessId, &psn) != procNotFound)) {
KillProcess(&psn);
} else {
NSLog(@"Killing process failed");
}
//application requires an update
[self updateApplications];
}
- (void) updateApplications
{
//must make sure the client application bundle is writable for an update
justEmbedded = isEmbedded ? NO : YES;
if (![[NSFileManager defaultManager] isWritableFileAtPath:[self getClientApplication]])
{
//TODO: Authorize to get an access?
[self reportStat:[NSString stringWithFormat:@"Client Deployment Failed"] requestMessage:@"App bundle readonly"];
[self reportInstallMetricsToInfluxDb:!isInstalling result:@"Failed" message:@"App bundle readonly"];
NSLog(@"Application bundle is not writable, no upgrade.");
NSString *msg = [NSString stringWithFormat: @"An error occured and %@ cannot continue.\nIf problem persists please download a new version.", sAppName];
[self showAlertPanel:msg messageText:@"Unable to upgrade" buttonText:@"Cancel"];
// set cancelled key to true
CFPreferencesSetAppValue((CFStringRef)sPFUpdateCancelledKey, CFSTR("true"), (CFStringRef) sPreferenceFileID);
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
[self exitApplication];
return;
}
justEmbedded = NO;
if (!isInstalling)
{
NSString *txt = [NSString stringWithFormat: @"Upgrading %@...", sAppName];
[progressField setStringValue:txt];
}
//show progress dialog now...
[self showProgressDialog];
//update applications appropriately
if ([self isBootstrapperUpdateRequired])
{
NSLog(@"Bootstrapper update in progress...");
[self reportStat:@"Bootstrapper Update Started"];
[self downloadBootstrapper];
}
else
{
NSLog(@"Client application update in progress...");
//deploy plugin as early as possible so browser can take control of proceedings
if (isInstalling)
{
// repair chrome
[self validateAndFixChromeState];
[self reportClientStatusSync:@"BootstrapperInstalling"];
//set only the server information
CFPreferencesSetAppValue((CFStringRef)sPFBaseServerKey, baseServer, (CFStringRef) sPreferenceFileID);
CFPreferencesSetAppValue((CFStringRef)sPFInstallHostKey, [[NSURL URLWithString:setupServer] host], (CFStringRef) sPreferenceFileID);
CFPreferencesSetAppValue((CFStringRef)sPFClientAppPathKey, NULL, (CFStringRef) sPreferenceFileID);
CFPreferencesSetAppValue((CFStringRef)sPFBootstrapperPathKey, NULL, (CFStringRef) sPreferenceFileID);
// remove Studio related keys also
#ifndef TARGET_IS_STUDIO
CFPreferencesSetAppValue(CFSTR("RbxStudioAppPath"), NULL, (CFStringRef) sPreferenceFileID);
CFPreferencesSetAppValue(CFSTR("RbxStudioBootstrapperPath"), NULL, (CFStringRef) sPreferenceFileID);
#endif
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
// set plug-in version
NSString* bundle = [[NSBundle mainBundle] resourcePath];
NSLog(@"Bootstrapper Path: %@", bundle);
bundle = [bundle stringByAppendingString:@"/../Plugins/NPRoblox.plugin/Contents/Info.plist"];
NSLog(@"Plugin Plist Path: %@", bundle);
NSDictionary *list = [NSDictionary dictionaryWithContentsOfFile:bundle];
NSString *version = [list objectForKey:@"CFBundleShortVersionString"];
CFPreferencesSetAppValue((CFStringRef)sPFPluginVersion, version, (CFStringRef) sPreferenceFileID);
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
}
else
{
// send BootstrapperInstalling event for Update as well
[self reportClientStatusSync:@"BootstrapperInstalling"];
}
//Deploy plugin
if ([self isNPAPIPluginDeploymentRequired] || isInstalling)
{
NSLog(@"Plugin deployment in progress...");
[self deployNPAPIPlugin];
NSLog(@"Plugin deployment done...");
}
[self reportStat:[NSString stringWithFormat:@"Client %@ Started", isInstalling ? @"Install" : @"Update"]];
[self downloadClientApplication];
}
}
- (void) downloadClientApplication
{
//TODO: for part update create an array of all files that can be downloaded and then download them one by one
NSString *appSource = [self getURL:sClientZipFile];
NSString *appDestination = [self getTempPath:sClientZipFile];
NSLog(@"Client Application URL = %@", appSource);
NSLog(@"Client Application DownloadPath = %@", appDestination);
isDownloadingBootstrapper = NO;
[self reportStat:@"Client Download Started"];
[self startDownload:appSource destination:appDestination];
//[self downloadDidFinish:NULL];
}
- (void) downloadBootstrapper
{
NSString *appSource = [self getURL:sBootstrapperZipFile];
NSString *appDestination = [self getTempBackupPath:sBootstrapperZipFile];
NSLog(@"Bootstrapper URL = %@", appSource);
NSLog(@"Bootstrapper DownloadPath = %@", appDestination);
isDownloadingBootstrapper = YES;
[self reportStat:@"Bootstrapper Download Started"];
[self startDownload:appSource destination:appDestination];
//[self downloadDidFinish:NULL];
}
- (void) downloadStudioBootstrapper
{
NSString *appSource = [self getStudioURL:sStudioBootstrapperZipFile];
NSString *appDestination = [self getTempPath:sStudioBootstrapperZipFile];
NSLog(@"Bootstrapper URL = %@", appSource);
NSLog(@"Bootstrapper DownloadPath = %@", appDestination);
isDownloadingStudioBootstrapper = YES;
isDownloadingBootstrapper = NO;
[self startDownload:appSource destination:appDestination];
}
- (BOOL) startDownload:(NSString *)source destination:(NSString *)destination
{
//end any download in progress
[self endUpdateDownload];
//create a new request
NSURLRequest *downloadRequest = [NSURLRequest requestWithURL:[[NSURL alloc] initWithString:source] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0];
updateDownload = [[NSURLDownload alloc] initWithRequest:downloadRequest delegate:(id)self];
if (!updateDownload)
return NO;
NSLog(@"Download started for %@", source);
[updateDownload setDestination:destination allowOverwrite:YES];
//Modify dialog properties
[progressIndicator setIndeterminate:YES];
[progressIndicator startAnimation:nil];
[progressPanel makeKeyAndOrderFront:nil];
//Make dialog as modal
[progressPanel setLevel:NSModalPanelWindowLevel];
return YES;
}
- (void)endUpdateDownload
{
[updateDownload release];
updateDownload = nil;
[downloadResponse release];
downloadResponse = nil;
}
- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response
{
[downloadResponse release];
downloadResponse = [response retain];
downloadBytes = 0;
}
- (void)download:(NSURLDownload *)download didReceiveDataOfLength:(unsigned)length
{
long long expectedLength = [downloadResponse expectedContentLength];
downloadBytes += length;
if (expectedLength > 0)
{
float percentComplete = ((float)downloadBytes / (float)expectedLength) * 100.0;
[progressIndicator setIndeterminate:NO];
[progressIndicator setDoubleValue:percentComplete];
}
}
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
NSString *msg = [NSString stringWithFormat: @"%@ was not able to update.", sAppName];
[self showAlertPanel:msg messageText:@"Unable to download" buttonText:@"Cancel"];
NSLog(@"Download failed");
// set cancelled key to true
CFPreferencesSetAppValue((CFStringRef)sPFUpdateCancelledKey, CFSTR("true"), (CFStringRef) sPreferenceFileID);
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
[self reportStat:[NSString stringWithFormat:@"ERROR: Downloading %@ Failed", isDownloadingBootstrapper ? @"Bootstrapper" : @"Client"]];
[self reportStat:@"Download Failure Reason" requestMessage:[[error userInfo] description]];
[self reportInstallMetricsToInfluxDb:!isInstalling result:@"Failed" message:[[error userInfo] description]];
[self exitApplication];
}
- (void)downloadDidFinish:(NSURLDownload *)download
{
NSLog(@"Download finished for %@", isDownloadingBootstrapper ? @"bootstrapper" : @"client application");
if (isDownloadingBootstrapper)
{
NSLog(@"Deploying bootstrapper");
[self downloadBootstrapperDidFinish];
}
else if(isDownloadingStudioBootstrapper)
{
NSLog(@"Deploying Studio bootstrapper");
[self downloadStudioBootstrapperDidFinish];
}
else
{
NSLog(@"Deploying client application");
[self downloadClientApplicationDidFinish];
}
if (!installSuccess)
{
[self reportStat:[NSString stringWithFormat:@"%@ Deployment Failed", isDownloadingBootstrapper ? @"Bootstrapper" :@"Client"]];
NSString *msgTitle = [NSString stringWithFormat:@"Unable to %@", isInstalling ? @"install" : @"upgrade"];
NSString *msg = [NSString stringWithFormat: @"An error occured and %@ cannot continue.\nIf problem persists please download a new version.", sAppName];
[self showAlertPanel:msg messageText:msgTitle buttonText:@"Cancel"];
// set cancelled key to true
CFPreferencesSetAppValue((CFStringRef)sPFUpdateCancelledKey, CFSTR("true"), (CFStringRef) sPreferenceFileID);
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
//also remove the temporary data
[self removeTemporaryDirectory];
}
//exit application
if (!isDownloadingStudioBootstrapper) {
[self exitApplication];
}
}
- (void) downloadBootstrapperDidFinish
{
if ([self deployBootstrapper])
{
NSLog(@"Bootstrapper deployed");
//make sure we save version information only when there is NO version change
if (!isInstalling)
[self saveUpdateInfo:!isClientAppChanged];
NSMutableArray *extraArguments = [NSMutableArray array];
NSString *appPath = [self getBootstrapperApplication];
if (isEmbedded)
{
NSLog(@"Extra arguments added");
[extraArguments addObject:@"-check"];
[extraArguments addObject:@"true"];
[extraArguments addObject:@"-updateUI"];
[extraArguments addObject:@"false"];
if ([urlHandlerArguments count] < 1)
{
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
CFStringRef isPluginHandling = (CFStringRef)CFPreferencesCopyAppValue((CFStringRef)sPFIsPluginHandlingKey, (CFStringRef) sPreferenceFileID);
if (isPluginHandling == NULL)
{
NSLog(@"Plugin is not handling, check if we need to launch player");
[extraArguments addObject:@"-launchPlayer"];
[extraArguments addObject:@"true"];
}
else
{
NSLog(@"Plugin handling");
CFRelease(isPluginHandling);
}
}
NSBundle *bootstrapperBundle = [[NSBundle alloc]initWithPath:appPath];
if (bootstrapperBundle != nil)
{
appPath = [bootstrapperBundle executablePath];
[bootstrapperBundle release];
}
}
installSuccess = YES;
NSLog(@"Relaunching Bootstrapper");
if ([urlHandlerArguments count] > 0)
{
[extraArguments addObject:@"-urlString"];
[extraArguments addObject:urlHandlerString];
NSLog(@"appending urlhandler arguments Bootstrapper");
}
[self reportStat:@"Bootstrapper Update Success"];
[self reportInstallMetricsToInfluxDb:TRUE result:@"Success" message:@"Bootstrapper Update"];
NSLog(@"downloadBootstrapperDidFinish - relaunchApplication");
[self relaunchApplication:appPath extraArguments:extraArguments];
}
else
{
NSLog(@"ERROR: Bootstrapper cannot be deployed");
[self reportInstallMetricsToInfluxDb:TRUE result:@"Failed" message:@"Bootstrapper cannot be deployed"];
}
}
- (void) downloadStudioBootstrapperDidFinish
{
//if application hasn't been handled by plugin then launch application
[self deployStudioBootstrapper];
// save settings to correctly configure studio on launch of Build or Edit
CFPreferencesSetAppValue(CFSTR("RbxStudioAppPath"), (CFStringRef) [self getStudioApp], (CFStringRef) sPreferenceFileID);
CFPreferencesSetAppValue(CFSTR("RbxStudioBootstrapperPath"), (CFStringRef) [self getStudioApp], (CFStringRef) sPreferenceFileID);
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
if (![self applicationExistsInDock:[self getStudioApp] checkLink: YES])
{
NSLog(@"adding studio bootstrapper to dock");
[self addApplicationInDock:[self getStudioApp]];
//delay application launch to let the creation go through
usleep(300000);
}
[self removeTemporaryDirectory];
isDownloadingStudioBootstrapper = NO;
if ([self relauchRequired])
{
if (isInstalling)
{
NSLog(@"downloadStudioBootstrapperDidFinish - showInstallSuccessDialog");
NSMutableArray *extraArgs = [NSMutableArray array];
[extraArgs addObject:@"-installsuccess"];
[self relaunchApplication:[self getBootstrapperApplication] extraArguments:extraArgs];
}
else if ([urlHandlerArguments count] > 0)
{
NSLog(@"downloadStudioBootstrapperDidFinish - with url arguments");
[self relaunchApplication:[self getClientApplicationExecutable] extraArguments:urlHandlerArguments];
}
else
{
NSLog(@"downloadStudioBootstrapperDidFinish - nil arguments");
[self relaunchApplication:[self getClientApplication] extraArguments:nil];
}
}
else
{
[self exitApplication];
}
}
- (void) downloadClientApplicationDidFinish
{
NSString *txt = [NSString stringWithFormat: @"Starting %@...", sAppName];
[progressField setStringValue:txt];
[progressIndicator setIndeterminate:YES];
[progressIndicator startAnimation:nil];
//take a back up of bootstrapper
installSuccess = [self backupBootstrapper];
justEmbedded = isEmbedded ? NO : YES;
//deploy client application
if (installSuccess && [self deployClientApplication])
{
installSuccess = NO;
NSString *clientApplication = [self getClientApplication];
if ([[NSFileManager defaultManager] fileExistsAtPath:clientApplication])
{
NSLog(@"Client Application found and ready to launch from %@", clientApplication);
//embed bootstrapper from temp location
[self embedBootstrapper];
//save version related information on successful launch
[self saveUpdateInfo:YES];
//also modify client info appropriately
[self modifyClientInfoList];
//also modify AppSettings.xml appropriately
#ifdef TARGET_IS_STUDIO
[self modifyStudioAppSettings];
#endif
//also register application
FSRef clientAppRef;
OSStatus status = FSPathMakeRef((const UInt8 *)[[self getClientApplication] fileSystemRepresentation], &clientAppRef, NULL);
if (status == noErr)
{
status = LSRegisterFSRef(&clientAppRef, true);
if (status == noErr)
NSLog(@"Application registered");
}
// touch client application (seems to resolve dock icon issue)
NSString *touchCommand = [NSString stringWithFormat:@"/usr/bin/touch %@",[self getClientApplication]];
system([touchCommand UTF8String]);
[self reportStat:[NSString stringWithFormat:@"Client %@ Success", isInstalling ? @"Install" : @"Update"]];
[self reportInstallMetricsToInfluxDb:!isInstalling result:@"Success" message:@""];
//if application doesn't exist in dock then add it
if (isInstalling && ![self applicationExistsInDock:[self getClientApplication] checkLink:NO])
{
[self addApplicationInDock:[self getClientApplication]];
//delay application launch to let the creation go through
usleep(300000);
}
installSuccess = YES;
if (isInstalling)
{
std::wstring path = CookiesEngine::getCookiesFilePath();
CookiesEngine engine(path);
CookiesEngine::reportValue(engine, "rbx_evt_initial_install_success", "{\"os\" : \"OSX\"}");
}
//if application hasn't been handled by plugin then launch application
if (![self isStudioInstalled] || ![self applicationExistsInDock:[self getStudioApp] checkLink: YES]) {
NSLog(@"downloadClientApplicationDidFinish - downloadStudioBootstrapper called");
[self downloadStudioBootstrapper];
}else {
//remove temporary data
[self removeTemporaryDirectory];
if ([self relauchRequired])
{
if (isInstalling)
{
NSLog(@"downloadClientApplicationDidFinish - showInstallSuccessDialog");
NSMutableArray *extraArgs = [NSMutableArray array];
[extraArgs addObject:@"-installsuccess"];
[self relaunchApplication:[self getBootstrapperApplication] extraArguments:extraArgs];
}
else if ([urlHandlerArguments count] > 0)
{
NSLog(@"downloadClientApplicationDidFinish - with URL arguments");
[self relaunchApplication:[self getClientApplicationExecutable] extraArguments:urlHandlerArguments];
}
else
{
NSLog(@"downloadClientApplicationDidFinish - with nil arguments");
[self relaunchApplication:clientApplication extraArguments:nil];
}
}
}
}
}
}
- (void) cancelUpdate:(id)sender
{
NSString *msgTitle = [NSString stringWithFormat:@"%@ in progress", isInstalling ? @"Installation" : @"Update"];
NSString *msg = [NSString stringWithFormat:@"%@ is %@...\nIf you cancel %@ may not work properly.", sAppName, isInstalling ? @"installing" : @"updating", sAppName];
if (NSAlertSecondButtonReturn == [self showAlertPanel:msg messageText:msgTitle button1Text:@"Continue" button2Text:@"Cancel"])
{
if (isDownloadingBootstrapper)
{
[self reportStat:@"Bootstrapper Update User Cancelled"];
[self reportInstallMetricsToInfluxDb:!isInstalling result:@"Cancelled" message:@"Bootstrapper User Cancelled"];
}
else
{
[self reportStat:[NSString stringWithFormat:@"Client %@ User Cancelled", isInstalling ? @"Install" : @"Update"]];
[self reportInstallMetricsToInfluxDb:!isInstalling result:@"Cancelled" message:@"User Cancelled"];
}
// set cancelled key to true
CFPreferencesSetAppValue((CFStringRef)sPFUpdateCancelledKey, CFSTR("true"), (CFStringRef) sPreferenceFileID);
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
[self exitApplication];
}
}
- (IBAction)onButtonClicked:(CustomImageButton *)sender
{
if (sender)
{
if ([[sender getImageName] isEqualToString:@"btn_cancel"])
{
[self cancelUpdate:nil];
}
}
}
- (BOOL) deployClientApplication
{
// TODO: In order to do part updates, create a struct of source, deployment destination
BOOL success = NO;
// unzip the zip file at temp location
if ([self unzipFile:[self getTempPath:sClientZipFile] unzipDestination:[self getTempDownloadDir]])
{
NSLog(@"File %@ unzipped successfully", sClientZipFile);
NSString *tempClientApp = [self getTempPath:sClientApplication];
//make sure client application has been unzipped successfully
if ([[NSFileManager defaultManager] fileExistsAtPath:tempClientApp])
{
//now copy downloaded app
success = [self copyDirectory:sContentsFolder source:tempClientApp destination:[self getClientApplication]];
NSLog(@"Copy Directory: %@", success ? @"Successful" : @"Unsuccessful");
}
}
return success;
}
- (BOOL) deployBootstrapper
{
BOOL success = NO;
// unzip the zip file at temp location
if ([self unzipFile:[self getTempBackupPath:sBootstrapperZipFile] unzipDestination:[self getTempBackupDir]])
{
NSLog(@"file %@ unzipped successfully", sBootstrapperZipFile);
// avoid getting into a circular loop, check for the version of downloaded bootstrapper
NSBundle *downloadedBundle = [[NSBundle alloc]initWithPath:[self getTempBackupPath:sBootstrapperApplication]];
if (downloadedBundle != nil)
{
NSString *downloadedBundleVersionSTR = [downloadedBundle objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
NSString *serverBootstrapperVersionSTR = [self stringWithContentsOfURL:[self getURL:sBootstrapperVersionFile]];
int downloadedBundleVersion = [downloadedBundleVersionSTR intValue];
int serverBootstrapperVersion = [serverBootstrapperVersionSTR intValue];
NSLog(@"downloadedBundleVersion = %@", downloadedBundleVersionSTR);
NSLog(@"serverBootstrapperVersion = %@", serverBootstrapperVersionSTR);
//server version and downloaded version should be same
if (serverBootstrapperVersion == downloadedBundleVersion)
{
NSLog(@"Attemping copyDirectory operation...");
success = [self copyDirectory:sContentsFolder source:[self getTempBackupPath:sBootstrapperApplication] destination:[self getBootstrapperApplication]];
NSLog(@"copyDirectory operation %@", success ? @"Successful" : @"Unsuccessful");
[self reportStat:[NSString stringWithFormat:@"Bootstrapper %@", success ? @"deployed" : @"cannot be deployed"]];
//deploy plugin also
[self deployNPAPIPlugin];
}
[downloadedBundle release];
}
}
return success;
}
- (BOOL) deployStudioBootstrapper
{
BOOL success = NO;
// unzip the zip file at temp location
if ([self unzipFile:[self getTempPath:sStudioBootstrapperZipFile] unzipDestination:[self getTempDownloadDir]])
{
NSLog(@"File %@ unzipped successfully", sStudioBootstrapperZipFile);
NSString *tempStudioBootstrapper = [self getTempPath:sStudioBootstrapperApplication];
//make sure client application has been unzipped successfully
if ([[NSFileManager defaultManager] fileExistsAtPath:tempStudioBootstrapper])
{
//now copy downloaded app
success = [self copyDirectory:sStudioBootstrapperApplication source:[self getTempDownloadDir] destination:[self getStudioDirectory]];
NSLog(@"Copy Directory: %@", success ? @"Successful" : @"Unsuccessful");
}
}
return success;
}
- (BOOL) deployNPAPIPlugin
{
CFPreferencesAppSynchronize((CFStringRef)sClientApplicationBI);
//TODO: enable version to verify if we need to deploy plugin
BOOL success = NO;
//first remove an already existing plugin
if (isAdminUser)
[self unInstallNPAPIPlugin:sPluginAdminInstallPath];
[self unInstallNPAPIPlugin:sPluginUserInstallPath];
//now try to copy the plugin at the desired location
NSBundle *bootstrapperBundle = [[NSBundle alloc]initWithPath:[self getBootstrapperApplication]];
if (bootstrapperBundle != nil)
{
//if admin directly copy plugin to /Library/...
if (isAdminUser)
success = [self copyDirectory:sNPAPIPlugin source:[bootstrapperBundle builtInPlugInsPath] destination:sPluginAdminInstallPath];
//if we couldn't copy then try copying to ~/Library/...
if (!success)
success = [self copyDirectory:sNPAPIPlugin source:[bootstrapperBundle builtInPlugInsPath] destination:[sPluginUserInstallPath stringByExpandingTildeInPath]];
[bootstrapperBundle release];
}
NSLog(@"NPAPI plugin deployment operation %@", success ? @"Successful" : @"Unsuccessful");
[self reportStat:[NSString stringWithFormat:@"Plugin %@", success ? @"deployed" : @"cannot be deployed"]];
return success;
}
- (void) unInstallNPAPIPlugin:(NSString *)pluginFolder
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *pluginPath = [NSString stringWithFormat:@"%@/%@", pluginFolder, sNPAPIPlugin];
if ([fileManager fileExistsAtPath:pluginPath])
{
if (![self removeDirectory:pluginPath])
{
[[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation
source:[pluginPath stringByDeletingLastPathComponent]
destination:@""
files:[NSArray arrayWithObject:[pluginPath lastPathComponent]]
tag:NULL];
}
}
}
- (BOOL) applicationExistsInDock:(NSString*)path checkLink:(BOOL)checkLink
{
//if (![path hasSuffix:@"/"])
// path = [path stringByAppendingString:@"/"];
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
[defaults addSuiteNamed:@"com.apple.dock"];
NSArray *dockApps = [defaults objectForKey:@"persistent-apps"];
NSEnumerator *dockAppsEnum = [dockApps objectEnumerator];
NSDictionary *dict = nil;
NSString *app = nil;
//NSString *path2 = [NSString stringWithFormat: @"file://localhost%@", path];
NSLog(@"applicationExistsInDock - checking for: %@, in dock", path);
while (dict = [dockAppsEnum nextObject] )
{
app = [[[dict objectForKey:@"tile-data"] objectForKey:@"file-data"] objectForKey:@"_CFURLString"];
if (![app hasSuffix:@"/"])
app = [app stringByAppendingString:@"/"];
NSLog(@"applicationExistsInDock - Comparing with: %@ ", app); //Rather wordy logging, can be removed
//if ([app isEqualToString:path] || [app isEqualToString:path2])
//Could potentially say yes incorrectly in rare cases with multiple clients in dock
if(app != nil && [app rangeOfString:path].location != NSNotFound)
{
if (!checkLink)
{
NSLog(@"applicationExistsInDock - Application: %@ exists in dock - no URL Check", app);
return YES;
}
else
{
NSLog(@"applicationExistsInDock - Application: %@ exists in dock - with URL Check", app);
NSString *appPlistFile = [NSString stringWithFormat:@"%@/Contents/Info.plist", path];
NSLog(@"applicationExistsInDock - Plist path: %@", appPlistFile);
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:appPlistFile];
if (dict != nil)
{
NSString *url;
NSLog(@"applicationExistsInDock - Got plist content");
url = [dict objectForKey:@"BaseServer"];
if (url == nil)
{
url = [dict objectForKey:@"RbxBaseUrl"];
}
if (url != nil)
{
NSLog(@"applicationExistsInDock - base URL found = %@", url);
if ([url isEqualToString:baseServer])
{
NSLog(@"applicationExistsInDock - application is in doc");
return YES;
}
}
}
}
}
}
NSLog(@"Application does not exist in dock");
return NO;
}
- (BOOL) addApplicationInDock:(NSString*)path
{
BOOL success = NO;
if (![path hasSuffix:@"/"])
path = [path stringByAppendingString:@"/"];
NSLog(@"Adding: %@ to dock", path);
// Add the application to the Dock
NSArray *args = [NSArray arrayWithObjects:@"write", @"com.apple.dock",@"persistent-apps",@"-array-add",[NSString stringWithFormat:@"<dict><key>tile-data</key><dict><key>file-data</key><dict><key>_CFURLString</key><string>%@</string><key>_CFURLStringType</key><integer>0</integer></dict></dict></dict>", path], nil];
NSTask *dockModTask = [NSTask launchedTaskWithLaunchPath:@"/usr/bin/defaults" arguments:args];
[dockModTask waitUntilExit];
if (![dockModTask isRunning] && ([dockModTask terminationStatus] >= 0))
{
// Now restart the Dock
NSTask *dockRestartTask = [NSTask launchedTaskWithLaunchPath:@"/usr/bin/killall" arguments:[NSArray arrayWithObjects:@"-HUP", @"Dock", nil]];
[dockRestartTask waitUntilExit];
if (![dockRestartTask isRunning] && ([dockRestartTask terminationStatus] >= 0))
{
NSLog(@"Application added to dock and dock restarted");
success = YES;
}
}
return success;
}
- (void) relaunchApplication:(NSString *)appPath extraArguments:(NSArray *)extraArguments
{
if(isMac10_5)
{
[NSApp terminate:self];
}
NSString *relaunchDaemonPath = [self getRelaunchDaemon];
NSLog(@"Relaunch Daemon path=%@", relaunchDaemonPath);
// need to get executable path if there are arguments
NSBundle *bundle = [[NSBundle alloc]initWithPath:appPath];
if (bundle)
{
if ([extraArguments count] > 0)
appPath = [bundle executablePath];
[bundle release];
}
NSLog(@"relaunchApplication path=%@, args=%@", appPath, extraArguments);
if (relaunchDaemonPath)
{
[self cleanUp];
appPath= [appPath stringByStandardizingPath];
NSLog(@"Application re-launched from %@", appPath);
NSMutableArray *arguments = [NSMutableArray array];
[arguments addObject:appPath];
[arguments addObject:[NSString stringWithFormat:@"%d", [[NSProcessInfo processInfo] processIdentifier]]];
if (extraArguments && [extraArguments count] > 0)
{
for (int ii = 0; ii < [extraArguments count]; ++ii)
{
NSLog(@"Relaunch Daemon: Extra Argument %d =%@", ii, [extraArguments objectAtIndex:ii]);
[arguments addObject:[extraArguments objectAtIndex:ii]];
}
}
[NSTask launchedTaskWithLaunchPath:relaunchDaemonPath arguments:arguments];
[NSApp terminate:self];
return;
}
[self reportStat:@"ERROR: Application Relaunch Failed"];
//Ideally code flow should not come here!!!!
NSString *msgTitle = [NSString stringWithFormat: @"Restart %@", sAppName];
NSString *msg = [NSString stringWithFormat: @"%@ has been upgraded to a new version.\nPlease restart %@.", sAppName, sAppName];
[self showAlertPanel:msg messageText:msgTitle buttonText:@"OK"];
[self exitApplication];
}
- (void)launchClientApplication:(NSArray *)extraArguments
{
//
[progressField setStringValue:[NSString stringWithFormat:@"Starting %@...", sAppName]];
[self showProgressDialog];
// create a timer so bootstrapper can be cleaned up
semaphoreCleanupTimer = [NSTimer scheduledTimerWithTimeInterval : 10.0
target : self
selector: @selector(cleanupSemaphore)
userInfo: nil
repeats : NO];
// create a new thread so we do not have to block main thread
[NSThread detachNewThreadSelector : @selector(launchApplicationInNewThread:)
toTarget : self
withObject : extraArguments];
}
- (void)launchApplicationInNewThread:(NSArray*)extraArguments
{
NSBundle *bundle = [[NSBundle alloc]initWithPath:[self getClientApplication]];
if (bundle)
{
NSMutableArray *arguments = [NSMutableArray array];
if (extraArguments && [extraArguments count] > 0)
{
for (int ii = 0; ii < [extraArguments count]; ++ii)
{
NSLog(@"launch application: Extra Argument %d =%@", ii, [extraArguments objectAtIndex:ii]);
[arguments addObject:[extraArguments objectAtIndex:ii]];
}
// launch executable
NSLog(@"Launching executable - %@", [bundle executablePath]);
[NSTask launchedTaskWithLaunchPath:[bundle executablePath] arguments:arguments];
}
else
{
// launch application
NSLog(@"Launching application - %@", [self getClientApplication]);
[NSTask launchedTaskWithLaunchPath:[self getClientApplication] arguments:nil];
}
// make sure semaphore doesn't exist
sem_unlink(sWaitingSemaphoreName);
sem_t* waitingSemaphore = sem_open(sWaitingSemaphoreName, O_CREAT|O_EXCL, 0644, 0);
if (waitingSemaphore != SEM_FAILED)
{
// if we are able successfully create semaphore then wait for Player to signal bootstrapper exit
sem_wait(waitingSemaphore);
sem_close(waitingSemaphore);
sem_unlink(sWaitingSemaphoreName);
}
[bundle release];
}
else
{
NSLog(@"Client application bundle couldn't be located - %@", [self getClientApplication]);
}
[self exitApplication];
}
- (void) cleanupSemaphore:(NSTimer*) timer
{
NSLog(@"cleanupSemaphore");
sem_t *sem = sem_open(sWaitingSemaphoreName, 0);
if (sem != SEM_FAILED)
{
sem_post(sem);
}
}
- (void) setupInfluxDb
{
influxDbThrottle = rand() % 10000;
std::string reporter = "MacPlayer";
#ifdef TARGET_IS_STUDIO
reporter = "MacStudio";
#endif
InfluxDb::init(reporter,
[self getSettings].GetValueInfluxUrl(),
[self getSettings].GetValueInfluxDatabase(),
[self getSettings].GetValueInfluxUser(),
[self getSettings].GetValueInfluxPassword());
NSString *localBootstrapperVersionSTR = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
InfluxDb::setAppVersion([localBootstrapperVersionSTR UTF8String]);
}
- (void) reportToInfluxDb:(int)throttle json:(NSString*)json
{
if (influxDbThrottle >= throttle)
return;
if (InfluxDb::getReportingUrl().empty())
return;
NSString *url = [NSString stringWithCString:InfluxDb::getReportingUrl().c_str() encoding:[NSString defaultCStringEncoding]];
NSArray *requestData = [NSArray arrayWithObjects:url, json, @"application/json", nil];
//create a new thread
[NSThread detachNewThreadSelector : @selector(doPostAsync:)
toTarget : [HttpRequestHandler class]
withObject : requestData];
}
- (void) reportInstallMetricsToInfluxDb:(BOOL)isUpdate result:(NSString*)result message:(NSString*)message
{
InfluxDb points;
points.addPoint("Type", isUpdate ? "Update" : "Install");
points.addPoint("Result", [result UTF8String]);
points.addPoint("Message", [message UTF8String]);
points.addPoint("ElapsedTime", fabs((double)[startTime timeIntervalSinceNow]));
std::string jsonStr = points.getJsonStringForPosting("MacInstall");
[self reportToInfluxDb:[self getSettings].GetValueInfluxInstallHundredthsPercentage() json:[NSString stringWithCString:jsonStr.c_str() encoding:[NSString defaultCStringEncoding]]];
}
- (BOOL) isStudioInstalled
{
#ifdef TARGET_IS_STUDIO
return YES;
#endif
NSFileManager *fileManager = [NSFileManager defaultManager];
return [fileManager fileExistsAtPath: [NSString stringWithFormat:@"%@/%@", [self getStudioDirectory], sStudioBootstrapperApplication]];
}
- (NSString *) getStudioDirectory
{
NSString *bundlePath;
if (isEmbedded) {
bundlePath = [[self getClientApplication] stringByDeletingLastPathComponent];
}else {
bundlePath = [[self getBootstrapperApplication] stringByDeletingLastPathComponent];
}
return bundlePath;
}
- (NSString *) getStudioApp
{
return [NSString stringWithFormat:@"%@/%@", [self getStudioDirectory], sStudioBootstrapperApplication];
}
- (BOOL) relauchRequired
{
if ([urlHandlerArguments count] > 0)
return true;
BOOL launchedRequired = YES;
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
CFStringRef isPluginHandling = (CFStringRef)CFPreferencesCopyAppValue((CFStringRef)sPFIsPluginHandlingKey, (CFStringRef) sPreferenceFileID);
if (isPluginHandling)
{
launchedRequired = (CFStringCompare(isPluginHandling, CFSTR("true"), kCFCompareCaseInsensitive) != kCFCompareEqualTo);
CFRelease(isPluginHandling);
}
return launchedRequired;
}
- (BOOL) isVersionChanged
{
//if we are not connected to internet or version strings are same then we should not continue version checking
NSString *localVersion = [self getValueFromKey:sServerVersionKey];
//if application already being downloaded then change the panel message
if (localVersion)
isInstalling = NO;
if ((serverVersion == nil) || ((localVersion != nil) && [serverVersion isEqualToString:localVersion]))
return NO;
return YES;
}
- (BOOL) isBootstrapperUpdateRequired
{
//update bootstrapper only if server bootstrapper version is greater than the local bootstrapper version
NSString *serverBootstrapperVersionSTR = [self stringWithContentsOfURL:[self getURL:sBootstrapperVersionFile]];
if (serverBootstrapperVersionSTR != nil)
{
int serverBootstrapperVersion = [serverBootstrapperVersionSTR intValue];
if (serverBootstrapperVersion > 0)
{
NSString *localBootstrapperVersionSTR = [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
if (localBootstrapperVersionSTR != nil)
{
int localBootstrapperVersion = [localBootstrapperVersionSTR intValue];
if (localBootstrapperVersion > 0)
{
BOOL bootstrapperUpdate = serverBootstrapperVersion != localBootstrapperVersion;
NSLog(@"Server version = %@, Local Version = %@", serverBootstrapperVersionSTR, localBootstrapperVersionSTR);
NSLog(@"Bootstraper update %@", bootstrapperUpdate ? @"required" : @"not required");
return bootstrapperUpdate;
}
}
}
}
return NO;
}
- (BOOL) isNPAPIPluginDeploymentRequired
{
#ifdef TARGET_IS_STUDIO
return NO;
#endif
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *sourceFullPath = [NSString stringWithFormat:@"%@/%@", isAdminUser ? sPluginAdminInstallPath : [sPluginUserInstallPath stringByExpandingTildeInPath],
sNPAPIPlugin];
if ([fileManager fileExistsAtPath:sourceFullPath])
{
NSLog(@"Plugin deployment NOT required at %@", sourceFullPath);
return NO;
}
NSLog(@"Plugin deployment required at %@", sourceFullPath);
return YES;
}
- (BOOL) backupBootstrapper
{
//copy bootstrapper to temporary location so we can get it back
NSString *bootstrapperAppName = [[[NSBundle mainBundle] bundlePath] lastPathComponent];
NSLog(@"Bootstrapper for backup =%@", bootstrapperAppName);
BOOL success = [self copyDirectory:bootstrapperAppName source:[[self getBootstrapperApplication] stringByDeletingLastPathComponent] destination:[self getTempBackupDir]];
if (success)
{
if (![bootstrapperAppName isEqualToString:sBootstrapperApplication])
success = [self renameDirectory:sBootstrapperApplication directoryPath:[NSString stringWithFormat:@"%@/%@", [self getTempBackupDir], bootstrapperAppName]];
}
return success;
}
- (BOOL) embedBootstrapper
{
//embed bootstrapper application
return [self copyDirectory:sBootstrapperApplication source:[self getTempBackupDir] destination:[self getBootstrapperPathToEmbed]];
}
- (NSString *) getURL:(NSString *)fileName
{
return [NSString stringWithFormat:@"%@%@-%@",setupServer, serverVersion, fileName];
}
- (NSString *) getStudioURL:(NSString *)fileName
{
return [NSString stringWithFormat:@"%@%@-%@",setupServer, serverStudioVersion, fileName];
}
- (NSString *) getTempPath:(NSString *)fileName
{
return [NSString stringWithFormat:@"%@/%@", [self getTempDownloadDir], fileName];
}
- (NSString *) getTempBackupPath:(NSString *)fileName
{
return [NSString stringWithFormat:@"%@/%@", [self getTempBackupDir], fileName];
}
- (NSString *) getBootstrapperContentPath:(NSString *)fileName
{
//if launched from read only file system then we must be having application in temporary location
return [NSString stringWithFormat:@"%@/%@/%@", [self getBootstrapperApplication], sContentsFolder, fileName];
}
- (NSString *) getClientApplication
{
//if launched from read only file system then we need to install application at the respective path
if (launchedFromReadOnlyFS)
{
NSString *clientApp = [NSString stringWithFormat:@"%@/%@", isAdminUser ? sAppAdminInstallPath : [sAppUserInstallPath stringByExpandingTildeInPath],
sTransformedApplication];
if (isInstalling)
[self createDirectory:clientApp];
return clientApp;
}
//main bundle has just been transformed into client application
if (justEmbedded)
return [[NSBundle mainBundle] bundlePath];
//bootstrapper is already embedded - so the client application will be 3 steps up!
//Roblox.app (client application)/Contents/MacOS/Roblox.app (bootstrapper)
return [[[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent]
stringByDeletingLastPathComponent]
stringByDeletingLastPathComponent];
}
- (NSString *) getClientApplicationExecutable
{
NSString* clientAppExecutablePath;
NSBundle *bootstrapperBundle = [[NSBundle alloc]initWithPath:[self getClientApplication]];
if (bootstrapperBundle != nil)
{
clientAppExecutablePath = [bootstrapperBundle executablePath];
#ifndef TARGET_IS_STUDIO
if (isInstalling)
clientAppExecutablePath = [clientAppExecutablePath stringByReplacingOccurrencesOfString:@"MacOS/Roblox" withString:@"MacOS/RobloxPlayer"];
#endif
[bootstrapperBundle release];
}
return clientAppExecutablePath;
}
- (NSString *) getBootstrapperApplication
{
//bootstrapper has just been embedded so get the correct path
if (justEmbedded)
{
if (launchedFromReadOnlyFS)
return [NSString stringWithFormat:@"%@/Contents/MacOS/%@", [self getClientApplication], sBootstrapperApplication];
return [NSString stringWithFormat:@"%@/%@", [[[NSBundle mainBundle] executablePath] stringByDeletingLastPathComponent], sBootstrapperApplication];
}
//in rest of the cases it will always be the bundle path
return [[NSBundle mainBundle] bundlePath];
}
- (NSString *) getRelaunchDaemon
{
return [NSString stringWithFormat:@"%@/%@", [self getBootstrapperApplication], sRelaunchApplicationDaemon];
}
- (NSString *) getBootstrapperPathToEmbed
{
return [[self getBootstrapperApplication] stringByDeletingLastPathComponent];
}
- (NSString *) getTempDownloadDir
{
if (tempDownloadDir == nil)
{
//TODO: use NSTemporaryDirectory API
//tempDownloadDirectory = NSTemporaryDirectory();
//if (tempDownloadDirectory == nil)
NSString *tempDirectory = @"/tmp";
NSString *userName = NSUserName();
NSString *subDirectory = [NSString stringWithFormat:@".rbx_%@/%@/%@", userName, sDownloadDir, serverVersion];
tempDownloadDir = [tempDirectory stringByAppendingPathComponent:subDirectory];
NSLog(@"temp download dir = %@", tempDownloadDir);
//make sure the destination folder doesn't exist before creating a new one
//[self removeDirectory:tempDownloadDir];
//create directory it it's not present already
[self createDirectory:tempDownloadDir];
[tempDownloadDir retain];
}
return tempDownloadDir;
}
- (NSString *) getTempBackupDir
{
if (tempBackupDir == nil)
{
tempBackupDir = [NSString stringWithFormat:@"%@/%@", [self getTempDownloadDir], sBackupDir];
[tempBackupDir retain];
//create directory it it's not present already
[self createDirectory:tempBackupDir];
}
return tempBackupDir;
}
// In order to make sure we have a timeout to fetch information from server, [NSString stringWithContentsOfURL] is not being used.
- (NSString *) stringWithContentsOfURL:(NSString *)urlString
{
NSURLRequest *connectionRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0];
NSLog(@"urlString = %@", urlString);
//NSString *stringContent = [NSString stringWithContentsOfURL:[[NSURL alloc] initWithString:urlString] encoding:NSASCIIStringEncoding error:NULL];
//NSLog(@"stringContent = %@", stringContent);
NSData *receivedData = [NSURLConnection sendSynchronousRequest:connectionRequest returningResponse:NULL error:NULL];
if (!receivedData)
return nil;
NSString *stringContent = [[[NSString alloc] initWithData:receivedData encoding:NSASCIIStringEncoding] autorelease];
NSLog(@"stringContent = %@", stringContent);
return stringContent;
}
- (void) saveUpdateInfo:(BOOL)saveVersionInfo
{
NSMutableDictionary *mutableDict = [NSMutableDictionary new];
[mutableDict setObject:baseServer forKey:sBaseServerKey];
[mutableDict setObject:setupServer forKey:sSetupServerKey];
[mutableDict setObject:saveVersionInfo ? serverVersion : @"dummyVersion" forKey:sServerVersionKey];
isEmbedded = justEmbedded ? justEmbedded : isEmbedded;
if (isEmbedded)
[mutableDict setObject:@"true" forKey:sEmbeddedKey];
NSLog(@"Dump of update info = %@", mutableDict);
//save file -- will overwrite the existing one!
[mutableDict writeToFile:[self getBootstrapperContentPath:sLocalUpdateInfoFile] atomically:NO];
CFPreferencesSetAppValue((CFStringRef)sPFBaseServerKey, baseServer, (CFStringRef) sPreferenceFileID);
CFPreferencesSetAppValue((CFStringRef)sPFInstallHostKey, [[NSURL URLWithString:setupServer] host], (CFStringRef) sPreferenceFileID);
CFPreferencesSetAppValue((CFStringRef)sPFClientAppPathKey, [self getClientApplication], (CFStringRef) sPreferenceFileID);
CFPreferencesSetAppValue((CFStringRef)sPFBootstrapperPathKey, [self getBootstrapperApplication], (CFStringRef) sPreferenceFileID);
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
}
- (NSString *) getValueFromKey:(NSString *)keyToSearch
{
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:[self getBootstrapperContentPath:sLocalUpdateInfoFile]];
NSArray *keys = [dict allKeys];
for (NSString *key in keys)
{
if ([keyToSearch isEqualToString:key])
{
NSObject *value = [dict objectForKey:key];
if (value != nil)
{
return (NSString *)value;
}
}
}
return nil;
}
- (void) modifyClientInfoList
{
NSString *clientInfoListFile = [NSString stringWithFormat:@"%@/Contents/Info.plist", [self getClientApplication]];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:clientInfoListFile];
if (dict)
{
//first remove keys to be added
[dict removeObjectsForKeys:[NSArray arrayWithObjects:sRPInstallHostKey, sRPBaseUrlKey, nil]];
//now add keys with new values
[dict setObject:baseServer forKey:sRPBaseUrlKey];
[dict setObject:[[NSURL URLWithString:setupServer] host] forKey:sRPInstallHostKey];
// create the cookie store value
if( [dict objectForKey:@"CPath" ] == nil )
{
NSLog( @"Added CPath for Cookie Store");
[dict setObject:@"/tmp/rbxcsettings.rbx" forKey:@"CPath" ];
}
//save file back
[dict writeToFile:clientInfoListFile atomically:NO];
}
}
- (void) modifyStudioAppSettings
{
NSLog(@"modifying AppSettings.xml");
NSXMLDocument *appSettings;
NSError *err=nil;
NSString *fileName = [NSString stringWithFormat:@"%@/Contents/MacOS/AppSettings.xml", [self getClientApplication]];
NSURL *furl = [NSURL fileURLWithPath: fileName];
if (!furl) {
NSLog(@"Can't create file path: %@", fileName);
return;
}
appSettings = [[NSXMLDocument alloc] initWithContentsOfURL:furl
options:(NSXMLNodePreserveWhitespace|NSXMLNodePreserveCDATA)
error:&err];
if (appSettings == nil) {
NSLog(@"failed to get AppSettings.xml");
return;
}
NSXMLNode *aNode = [appSettings rootElement];
//search through xml file to find BaseUrl node
while ((aNode = [aNode nextNode])) {
if ([[aNode name] isEqualToString: @"BaseUrl"]) {
[aNode setStringValue: baseServer];
NSData *xmlData = [appSettings XMLDataWithOptions:NSXMLNodePrettyPrint];
if(![xmlData writeToFile: fileName atomically:YES])
NSLog(@"Failed to write to AppSettings.xml");
return;
}
}
}
- (BOOL) unzipFile:(NSString *)zipFilePath unzipDestination:(NSString *)unzipDestination
{
//TODO: Make this code more robust...!
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:zipFilePath] && [fileManager isWritableFileAtPath:unzipDestination])
{
// unzip the zip file at temp location
NSString *unzipCommand = [NSString stringWithFormat:@"unzip -qo %@ -d %@", zipFilePath, unzipDestination];
NSLog(@"Unzip command = %@", unzipCommand);
system([unzipCommand UTF8String]);
return YES;
}
NSLog(@"ERROR: Files cannot be unziped. From: %@ To: %@", zipFilePath, unzipDestination);
[self reportStat:@"ERROR: Unzip Error"];
return NO;
}
- (BOOL) copyDirectory:(NSString *)directoryName source:(NSString *)source destination:(NSString *)destination
{
NSLog(@"copyDirectory: source = %@, destination = %@", source, destination );
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *sourceFullPath = [NSString stringWithFormat:@"%@/%@", source, directoryName];
if ([fileManager fileExistsAtPath:sourceFullPath] && [fileManager isWritableFileAtPath:destination])
{
NSString *destinationFullPath = [NSString stringWithFormat:@"%@/%@", destination, directoryName];
//make sure the destination folder doesn't exist before copying
[self removeDirectory:destinationFullPath];
NSLog(@"Copy Directory source =%@", sourceFullPath);
NSLog(@"Copy Directory destination =%@", destinationFullPath);
NSError* error = nil;
//now do a copy
if ([fileManager copyItemAtPath:sourceFullPath toPath:destinationFullPath error:&error])
{
NSLog(@"Directory sucessfully copied with NSFileManager");
}
else if (error)
{
//On MACOSX 10.5 we have seen failures with copying "symbolic links" using NSFileManager Copy
NSLog(@"ERROR: NSFileManager Copy Error = %@", error);
NSLog(@"ERROR: NSFileManager Copy Error UserInfo: %@", [error userInfo]);
[self reportStat:@"ERROR: NSFileManager Copy Error"];
//Remove any Garbage due to earlier NSFileManager Copy, otherwise cp will fail
[self removeDirectory:destinationFullPath];
//Try to do a copy with UNIX cp command
NSString *copyCommand = [NSString stringWithFormat:@"cp -R -p '%@' '%@'", sourceFullPath, destinationFullPath];
NSLog(@"Copy command = %@", copyCommand);
//launch system command -- verify success?
system([copyCommand UTF8String]);
NSLog(@"Directory sucessfully copied with UNIX cp");
}
return YES;
}
NSLog(@"ERROR: Directory %@ cannot be copied. From: %@ To: %@", directoryName, source, destination);
[self reportStat:@"ERROR: Copy Directory Error"];
return NO;
}
- (BOOL) renameDirectory:(NSString *)directoryName directoryPath:(NSString *)directoryPath
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *destination = [directoryPath stringByDeletingLastPathComponent];
if ([fileManager fileExistsAtPath:directoryPath] && [fileManager isWritableFileAtPath:directoryPath])
{
NSString *destinationFullPath = [NSString stringWithFormat:@"%@/%@", destination, directoryName];
//make sure the destination folder doesn't exist before renaming
[self removeDirectory:destinationFullPath];
NSLog(@"Rename Directory source =%@", directoryPath);
NSLog(@"Rename Directory destination =%@", destinationFullPath);
//now do a move
if ([fileManager moveItemAtPath:directoryPath toPath:destinationFullPath error:nil])
{
NSLog(@"Directory sucessfully renamed");
return YES;
}
}
NSLog(@"ERROR: Renaming directory From: %@ To: %@", directoryPath, directoryName);
[self reportStat:@"ERROR: Rename Directory Error"];
return NO;
}
- (BOOL) createDirectory:directoryPath
{
[[NSFileManager defaultManager] createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:nil];
return YES;
}
- (BOOL) removeDirectory:directoryPath
{
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:directoryPath])
{
NSLog(@"Removing Directory =%@", directoryPath);
if([fileManager removeItemAtPath:directoryPath error:nil])
{
NSLog(@"Directory successfully removed = %@", directoryPath);
return YES;
}
else
{
NSLog(@"Remove Directory Failed = %@", directoryPath);
return NO;
}
}
NSLog(@"WARNING: Remove Directory does not exist: %@", directoryPath);
return NO;
}
- (void) removeTemporaryDirectory
{
NSString *tempDir = [self getTempDownloadDir];
while ([tempDir length] > 8)
{
[self removeDirectory:tempDir];
tempDir = [tempDir stringByDeletingLastPathComponent];
}
}
- (void) showProgressDialog
{
[progressIndicator setIndeterminate:YES];
[progressIndicator startAnimation:nil];
[progressPanel makeKeyWindow];
[progressPanel makeKeyAndOrderFront:nil];
[progressPanel setLevel:NSModalPanelWindowLevel];
}
- (void) showInstallSuccessDialog
{
NSLog(@"Showing install success dialog");
// first hide progress panel
[progressPanel orderOut:nil];
// now load install success dialog
#ifndef TARGET_IS_STUDIO
installSuccessController = [[InstallSuccessController alloc] initWithWindowNibName:@"InstallSuccessController"];
#else
installSuccessController = [[InstallSuccessController alloc] initWithWindowNibName:@"StudioInstallSuccessPanel"];
#endif
[installSuccessController.window makeKeyWindow];
[installSuccessController.window makeKeyAndOrderFront:nil];
[installSuccessController.window setLevel:NSModalPanelWindowLevel];
// listen to window close notification
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(installDialogClosing:)
name:NSWindowWillCloseNotification
object:installSuccessController.window];
#ifdef TARGET_IS_STUDIO
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(developPageLinkClicked:)
name:@"developPageLinkClicked"
object:installSuccessController.window];
#endif
}
- (void)installDialogClosing:(id)sender
{
#ifndef TARGET_IS_STUDIO
NSLog(@"install dialog closing - closing application");
[self exitApplication];
#else
NSLog(@"install dialog closing - launching studio application");
[self relaunchApplication:[self getClientApplication] extraArguments:nil];
#endif
}
#ifdef TARGET_IS_STUDIO
- (void)developPageLinkClicked:(id)sender
{
NSLog(@"developPageLinkClicked");
//first remove close observer
[[NSNotificationCenter defaultCenter] removeObserver:self];
NSString *urlString = [NSString stringWithFormat: @"%@develop", [[NSBundle mainBundle] objectForInfoDictionaryKey:sBaseServerKey]];
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
[self exitApplication];
}
#endif
- (void) reportStat:(NSString *)requestId
{
NSLog(@"ID: %@", requestId);
if (!isReportStatEnabled)
return;
[self reportStat:requestId requestMessage:nil];
}
- (void) reportStat:(NSString *)requestId requestMessage:(NSString *)requestMessage
{
NSLog(@"ID: %@, Message: %@", requestId, requestMessage);
if (!isReportStatEnabled)
return;
//initialize reporting data
NSString *urlString = [[NSString stringWithFormat:@"%@%@%u&Type=INSTALLER MAC %@", baseServer, sAnalyticsPath, reportStatGuid, requestId] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSArray *requestData = [NSArray arrayWithObjects:urlString, requestMessage, nil];
NSLog(@"reportStatURL =%@", urlString);
NSLog(@"requestMessage =%@", requestMessage);
NSLog(@"New thread created...");
//create a new thread
[NSThread detachNewThreadSelector : @selector(doPostAsync:)
toTarget : [HttpRequestHandler class]
withObject : requestData];
}
- (void) reportClientStatusSync:(NSString*)status
{
if ([browserTrackerId length] < 1)
return;
NSString *requestUrlString = [[NSString stringWithFormat:@"%@/client-status/set?browserTrackerId=%@&status=%@", baseServer, browserTrackerId, status] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"reportClientStatus: %@", requestUrlString);
NSString *dummyText = @"";
NSData *postData = [dummyText dataUsingEncoding:NSUTF8StringEncoding];
NSString *postLength = [NSString stringWithFormat:@"%d", [postData length]];
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
[request setURL:[NSURL URLWithString:requestUrlString]];
[request setHTTPMethod:@"POST"];
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/xml" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];
NSData* result = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
NSLog(@"reportClientStatus result: %@", result);
}
- (void) exitApplication
{
[semaphoreCleanupTimer invalidate];
//hide progress panel
[progressPanel orderOut:nil];
if (isInstalling && !installSuccess)
{
//remove all keys, so installation can go on in the next stage
#ifndef TARGET_IS_STUDIO
CFPreferencesSetAppValue((CFStringRef)sPFBaseServerKey, NULL, (CFStringRef) sPreferenceFileID);
CFPreferencesSetAppValue((CFStringRef)sPFInstallHostKey, NULL, (CFStringRef) sPreferenceFileID);
#endif
CFPreferencesSetAppValue((CFStringRef)sPFClientAppPathKey, NULL, (CFStringRef) sPreferenceFileID);
CFPreferencesSetAppValue((CFStringRef)sPFBootstrapperPathKey, NULL, (CFStringRef) sPreferenceFileID);
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
}
//modify isUptoData key, so plugin can continue working further
CFPreferencesSetAppValue((CFStringRef)sPFIsUptoDateKey, CFSTR("true"), (CFStringRef) sPreferenceFileID);
CFPreferencesAppSynchronize((CFStringRef) sPreferenceFileID);
//sleep for some time to get the http request threads complete
sleep(3);
// now exit
ExitToShell();
}
- (BootstrapperSettings&)getSettings
{
if (!bootstrapperSettings)
{
std::string settingsURL = GetSettingsUrl([[[NSBundle mainBundle] objectForInfoDictionaryKey:sBaseServerKey] UTF8String], sSettingsGroup, "76E5A40C-3AE1-4028-9F10-7C62520BD94F");
NSString* settingsData = [self stringWithContentsOfURL:[NSString stringWithCString:settingsURL.c_str() encoding:[NSString defaultCStringEncoding]]];
bootstrapperSettings = new BootstrapperSettings;
if (settingsData && [settingsData length] > 0)
bootstrapperSettings->ReadFromStream([settingsData UTF8String]);
}
return *bootstrapperSettings;
}
- (void) validateAndFixChromeState
{
NSLog(@"Trying to patch chrome");
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString* chromeLocalStateFile = [NSString stringWithFormat:@"%@/Google/Chrome/Local State", [paths firstObject]];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSLog(@"file path: '%@'", chromeLocalStateFile);
if (![fileManager fileExistsAtPath:chromeLocalStateFile] || ![fileManager isWritableFileAtPath:chromeLocalStateFile])
{
NSLog(@"Cannot patch chrome: either file doesn't exist or readOnly");
return;
}
[chromeLocalStateFile retain];
std::ifstream ifs([chromeLocalStateFile UTF8String]);
if (!ifs.is_open())
{
NSLog(@"Cannot patch chrome: file stream is not valid");
return;
}
std::stringstream localStateStr;
localStateStr << ifs.rdbuf();
rapidjson::Document doc;
doc.Parse<rapidjson::kParseDefaultFlags>(localStateStr.str().c_str());
rapidjson::Value& ph = doc["protocol_handler"];
rapidjson::Value& es = ph["excluded_schemes"];
bool found = false;
for (rapidjson::Value::MemberIterator iter = es.MemberBegin(); iter != es.MemberEnd(); ++iter)
{
std::string name = iter->name.GetString();
if (name.find("roblox") != std::string::npos)
{
if (iter->value.GetBool())
found = true;
iter->value.SetBool(false);
}
}
if (found)
{
NSLog(@"Chrome upate required");
// try detect chrome and prompt shutdown if detected
while (found)
{
found = false;
NSWorkspace *workSpace = [NSWorkspace sharedWorkspace];
NSString *appPathIs = [workSpace fullPathForApplication:@"Google Chrome"];
NSString *identifier = [[NSBundle bundleWithPath:appPathIs] bundleIdentifier];
NSArray *selectedApps =
[NSRunningApplication runningApplicationsWithBundleIdentifier:identifier];
if (selectedApps.count > 0)
found = true;
if (found)
{
NSInteger result = [self showAlertPanel:@"Please close all instances of Google Chrome before continuing." messageText:@"Close Google Chrome" button1Text:@"Continue" button2Text:@"Cancel"];
if (result == NSAlertSecondButtonReturn)
{
result = [self showAlertPanel:@"Roblox might not launch correctly if Chrome is open during installation." messageText:@"Close Google Chrome" button1Text:@"Continue" button2Text:@"Cancel"];
// 2nd chance
if (result == NSAlertSecondButtonReturn)
{
break;
}
}
}
}
// save updated file
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
doc.Accept(writer);
const char* output = buffer.GetString();
std::ofstream ofs([chromeLocalStateFile UTF8String]);
if (ofs.is_open())
ofs << output;
}
NSLog(@"Done with chrome patching - %d", found);
[chromeLocalStateFile release];
}
- (NSInteger)showAlertPanel:(NSString*)informativeText messageText:(NSString*)msgTxt buttonText:(NSString*)buttonText
{
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:msgTxt];
[alert setInformativeText:informativeText];
[alert addButtonWithTitle:buttonText];
NSInteger result = [alert runModal];
[alert release];
return result;
}
// Button1 will be the right most, buttons are added right to left
- (NSInteger)showAlertPanel:(NSString*)informativeText messageText:(NSString*)msgTxt button1Text:(NSString*)button1Text button2Text:(NSString*)button2Text
{
NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:msgTxt];
[alert setInformativeText:informativeText];
[alert addButtonWithTitle:button1Text];
[alert addButtonWithTitle:button2Text];
NSInteger result = [alert runModal];
[alert release];
return result;
}
@end
@implementation HttpRequestHandler
+ (void) doPostAsync:(NSArray *)requestData
{
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
//create
HttpRequestHandler *requestHandler = [[self alloc] init];
//initialize data
NSURL *serverURL = [NSURL URLWithString:[requestData objectAtIndex:0]];
NSData *postData = nil;
NSString *contentType = @"text/plain";
if ([requestData count] > 1)
{
if ([requestData objectAtIndex:1] != nil)
postData = [[requestData objectAtIndex:1] dataUsingEncoding:NSUTF8StringEncoding];
if ([requestData objectAtIndex:2] != nil)
contentType = (NSString*)[requestData objectAtIndex:2];
}
//now post the request
[requestHandler doPost:serverURL dataToPost:postData contentType:contentType];
//run loop to let the request finish
[[NSRunLoop currentRunLoop] run];
//do the required cleanup
[requestHandler release];
[pool release];
}
- (void) dealloc
{
[serverConnection release];
[super dealloc];
}
- (void) doPost:(NSURL *)serverURL dataToPost:(NSData *)dataToPost contentType:(NSString *)contentType
{
NSMutableURLRequest *postRequest = [[[NSMutableURLRequest alloc] initWithURL : serverURL
cachePolicy : NSURLRequestReloadIgnoringCacheData
timeoutInterval : 60.0] autorelease];
[postRequest setValue:contentType forHTTPHeaderField:@"Content-Type"];
//if there's no data to post then just do a get
if (dataToPost != nil)
{
[postRequest setHTTPMethod:@"POST"];
[postRequest setHTTPBody:dataToPost];
}
else
{
[postRequest setHTTPMethod:@"GET"];
}
serverConnection = [[NSURLConnection alloc] initWithRequest:postRequest delegate:self];
}
- (void) connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"Connection failed with error");
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(@"Connection finished loading");
}
@end