mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-05 05:07:48 +00:00
2532 lines
88 KiB
Plaintext
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
|