diff options
author | James Cook <james@lindenlab.com> | 2007-01-02 08:33:20 +0000 |
---|---|---|
committer | James Cook <james@lindenlab.com> | 2007-01-02 08:33:20 +0000 |
commit | 420b91db29485df39fd6e724e782c449158811cb (patch) | |
tree | b471a94563af914d3ed3edd3e856d21cb1b69945 /indra/mac_updater |
Print done when done.
Diffstat (limited to 'indra/mac_updater')
-rw-r--r-- | indra/mac_updater/AutoUpdater.nib/classes.nib | 4 | ||||
-rw-r--r-- | indra/mac_updater/AutoUpdater.nib/info.nib | 14 | ||||
-rw-r--r-- | indra/mac_updater/AutoUpdater.nib/objects.xib | 56 | ||||
-rw-r--r-- | indra/mac_updater/mac_updater.cpp | 1087 |
4 files changed, 1161 insertions, 0 deletions
diff --git a/indra/mac_updater/AutoUpdater.nib/classes.nib b/indra/mac_updater/AutoUpdater.nib/classes.nib new file mode 100644 index 0000000000..ea58db1189 --- /dev/null +++ b/indra/mac_updater/AutoUpdater.nib/classes.nib @@ -0,0 +1,4 @@ +{ +IBClasses = (); +IBVersion = 1; +} diff --git a/indra/mac_updater/AutoUpdater.nib/info.nib b/indra/mac_updater/AutoUpdater.nib/info.nib new file mode 100644 index 0000000000..a49a92385b --- /dev/null +++ b/indra/mac_updater/AutoUpdater.nib/info.nib @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IBDocumentLocation</key> + <string>103 138 356 240 0 0 1280 1002 </string> + <key>IBFramework Version</key> + <string>362.0</string> + <key>IBSystem Version</key> + <string>7D24</string> + <key>targetFramework</key> + <string>IBCarbonFramework</string> +</dict> +</plist> diff --git a/indra/mac_updater/AutoUpdater.nib/objects.xib b/indra/mac_updater/AutoUpdater.nib/objects.xib new file mode 100644 index 0000000000..310411b711 --- /dev/null +++ b/indra/mac_updater/AutoUpdater.nib/objects.xib @@ -0,0 +1,56 @@ +<?xml version="1.0" standalone="yes"?> +<object class="NSIBObjectData"> + <string name="targetFramework">IBCarbonFramework</string> + <object name="rootObject" class="NSCustomObject" id="1"> + <string name="customClass">NSApplication</string> + </object> + <array count="5" name="allObjects"> + <object class="IBCarbonWindow" id="166"> + <string name="windowRect">405 222 533 663 </string> + <string name="title">Second Life Updater</string> + <object name="rootControl" class="IBCarbonRootControl" id="167"> + <string name="bounds">0 0 128 441 </string> + <array count="3" name="subviews"> + <object class="IBCarbonStaticText" id="181"> + <string name="bounds">20 20 44 421 </string> + <ostype name="controlSignature">what</ostype> + <string name="title">Initializing…</string> + </object> + <object class="IBCarbonButton" id="183"> + <string name="bounds">88 351 108 421 </string> + <string name="title">Cancel</string> + <ostype name="command">not!</ostype> + <int name="buttonType">2</int> + </object> + <object class="IBCarbonProgressBar" id="193"> + <string name="bounds">51 19 70 422 </string> + <ostype name="controlSignature">prog</ostype> + <int name="initialValue">50</int> + </object> + </array> + </object> + <boolean name="isResizable">FALSE</boolean> + <int name="carbonWindowClass">2</int> + <int name="themeBrush">3</int> + <int name="windowPosition">7</int> + </object> + <reference idRef="167"/> + <reference idRef="181"/> + <reference idRef="183"/> + <reference idRef="193"/> + </array> + <array count="5" name="allParents"> + <reference idRef="1"/> + <reference idRef="166"/> + <reference idRef="167"/> + <reference idRef="167"/> + <reference idRef="167"/> + </array> + <dictionary count="2" name="nameTable"> + <string>File's Owner</string> + <reference idRef="1"/> + <string>Updater</string> + <reference idRef="166"/> + </dictionary> + <unsigned_int name="nextObjectID">194</unsigned_int> +</object> diff --git a/indra/mac_updater/mac_updater.cpp b/indra/mac_updater/mac_updater.cpp new file mode 100644 index 0000000000..df054fb94a --- /dev/null +++ b/indra/mac_updater/mac_updater.cpp @@ -0,0 +1,1087 @@ +/** + * @file mac_updater.cpp + * @brief + * + * Copyright (c) 2006-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include <stdio.h> +#include <stdlib.h> +//#include <direct.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include <curl/curl.h> +#include <pthread.h> + +#include "llerror.h" +#include "lltimer.h" +#include "lldir.h" +#include "llfile.h" + +#include "llstring.h" + +#include <Carbon/Carbon.h> + +#include "MoreFilesX.h" +#include "FSCopyObject.h" + +enum +{ + kEventClassCustom = 'Cust', + kEventCustomProgress = 'Prog', + kEventParamCustomCurValue = 'Cur ', + kEventParamCustomMaxValue = 'Max ', + kEventParamCustomText = 'Text', + kEventCustomDone = 'Done', +}; + +WindowRef gWindow = NULL; +EventHandlerRef gEventHandler = NULL; +OSStatus gFailure = noErr; +Boolean gCancelled = false; + +char *gUserServer; +char *gProductName; +char gUpdateURL[2048]; + +void *updatethreadproc(void*); + +pthread_t updatethread; + +OSStatus setProgress(int cur, int max) +{ + OSStatus err; + ControlRef progressBar = NULL; + ControlID id; + + id.signature = 'prog'; + id.id = 0; + + err = GetControlByID(gWindow, &id, &progressBar); + if(err == noErr) + { + Boolean indeterminate; + + if(max == 0) + { + indeterminate = true; + err = SetControlData(progressBar, kControlEntireControl, kControlProgressBarIndeterminateTag, sizeof(Boolean), (Ptr)&indeterminate); + } + else + { + double percentage = (double)cur / (double)max; + SetControlMinimum(progressBar, 0); + SetControlMaximum(progressBar, 100); + SetControlValue(progressBar, (SInt16)(percentage * 100)); + + indeterminate = false; + err = SetControlData(progressBar, kControlEntireControl, kControlProgressBarIndeterminateTag, sizeof(Boolean), (Ptr)&indeterminate); + + Draw1Control(progressBar); + } + } + + return(err); +} + +OSStatus setProgressText(CFStringRef text) +{ + OSStatus err; + ControlRef progressText = NULL; + ControlID id; + + id.signature = 'what'; + id.id = 0; + + err = GetControlByID(gWindow, &id, &progressText); + if(err == noErr) + { + err = SetControlData(progressText, kControlEntireControl, kControlStaticTextCFStringTag, sizeof(CFStringRef), (Ptr)&text); + Draw1Control(progressText); + } + + return(err); +} + +OSStatus sendProgress(long cur, long max, CFStringRef text = NULL) +{ + OSStatus result; + EventRef evt; + + result = CreateEvent( + NULL, + kEventClassCustom, + kEventCustomProgress, + 0, + kEventAttributeNone, + &evt); + + // This event needs to be targeted at the window so it goes to the window's handler. + if(result == noErr) + { + EventTargetRef target = GetWindowEventTarget(gWindow); + result = SetEventParameter ( + evt, + kEventParamPostTarget, + typeEventTargetRef, + sizeof(target), + &target); + } + + if(result == noErr) + { + result = SetEventParameter ( + evt, + kEventParamCustomCurValue, + typeLongInteger, + sizeof(cur), + &cur); + } + + if(result == noErr) + { + result = SetEventParameter ( + evt, + kEventParamCustomMaxValue, + typeLongInteger, + sizeof(max), + &max); + } + + if(result == noErr) + { + if(text != NULL) + { + result = SetEventParameter ( + evt, + kEventParamCustomText, + typeCFStringRef, + sizeof(text), + &text); + } + } + + if(result == noErr) + { + // Send the event + PostEventToQueue( + GetMainEventQueue(), + evt, + kEventPriorityStandard); + + } + + return(result); +} + +OSStatus sendDone(void) +{ + OSStatus result; + EventRef evt; + + result = CreateEvent( + NULL, + kEventClassCustom, + kEventCustomDone, + 0, + kEventAttributeNone, + &evt); + + // This event needs to be targeted at the window so it goes to the window's handler. + if(result == noErr) + { + EventTargetRef target = GetWindowEventTarget(gWindow); + result = SetEventParameter ( + evt, + kEventParamPostTarget, + typeEventTargetRef, + sizeof(target), + &target); + } + + if(result == noErr) + { + // Send the event + PostEventToQueue( + GetMainEventQueue(), + evt, + kEventPriorityStandard); + + } + + return(result); +} + +OSStatus dialogHandler(EventHandlerCallRef handler, EventRef event, void *userdata) +{ + OSStatus result = eventNotHandledErr; + OSStatus err; + UInt32 evtClass = GetEventClass(event); + UInt32 evtKind = GetEventKind(event); + + if((evtClass == kEventClassCommand) && (evtKind == kEventCommandProcess)) + { + HICommand cmd; + err = GetEventParameter(event, kEventParamDirectObject, typeHICommand, NULL, sizeof(cmd), NULL, &cmd); + + if(err == noErr) + { + switch(cmd.commandID) + { + case kHICommandCancel: + gCancelled = true; +// QuitAppModalLoopForWindow(gWindow); + result = noErr; + break; + } + } + } + else if((evtClass == kEventClassCustom) && (evtKind == kEventCustomProgress)) + { + // Request to update the progress dialog + long cur = 0; + long max = 0; + CFStringRef text = NULL; + (void) GetEventParameter(event, kEventParamCustomCurValue, typeLongInteger, NULL, sizeof(cur), NULL, &cur); + (void) GetEventParameter(event, kEventParamCustomMaxValue, typeLongInteger, NULL, sizeof(max), NULL, &max); + (void) GetEventParameter(event, kEventParamCustomText, typeCFStringRef, NULL, sizeof(text), NULL, &text); + + err = setProgress(cur, max); + if(err == noErr) + { + if(text != NULL) + { + setProgressText(text); + } + } + + result = noErr; + } + else if((evtClass == kEventClassCustom) && (evtKind == kEventCustomDone)) + { + // We're done. Exit the modal loop. + QuitAppModalLoopForWindow(gWindow); + result = noErr; + } + + return(result); +} + +#if 0 +size_t curl_download_callback(void *data, size_t size, size_t nmemb, + void *user_data) +{ + S32 bytes = size * nmemb; + char *cdata = (char *) data; + for (int i =0; i < bytes; i += 1) + { + gServerResponse.append(cdata[i]); + } + return bytes; +} +#endif + +int curl_progress_callback_func(void *clientp, + double dltotal, + double dlnow, + double ultotal, + double ulnow) +{ + int max = (int)(dltotal / 1024.0); + int cur = (int)(dlnow / 1024.0); + sendProgress(cur, max); + + if(gCancelled) + return(1); + + return(0); +} + +int parse_args(int argc, char **argv) +{ + // Check for old-type arguments. + if (2 == argc) + { + gUserServer = argv[1]; + return 0; + } + + int j; + + for (j = 1; j < argc; j++) + { + if ((!strcmp(argv[j], "-userserver")) && (++j < argc)) + { + gUserServer = argv[j]; + } + else if ((!strcmp(argv[j], "-name")) && (++j < argc)) + { + gProductName = argv[j]; + } + } + + return 0; +} + +int main(int argc, char **argv) +{ + // We assume that all the logs we're looking for reside on the current drive + gDirUtilp->initAppDirs("SecondLife"); + + ///////////////////////////////////////// + // + // Process command line arguments + // + gUserServer = NULL; + gProductName = NULL; + parse_args(argc, argv); + if (!gUserServer) + { + llinfos << "Usage: mac_updater -userserver <server> [-name <product_name>] [-program <program_name>]" << llendl; + exit(1); + } + else + { + llinfos << "User server is: " << gUserServer << llendl; + if (gProductName) + { + llinfos << "Product name is: " << gProductName << llendl; + } + else + { + gProductName = "Second Life"; + } + } + + llinfos << "Starting " << gProductName << " Updater" << llendl; + + // Build the URL to download the update + snprintf(gUpdateURL, sizeof(gUpdateURL), "http://secondlife.com/update-macos.php?userserver=%s", gUserServer); + + // Real UI... + OSStatus err; + IBNibRef nib = NULL; + + err = CreateNibReference(CFSTR("AutoUpdater"), &nib); + + char windowTitle[MAX_PATH]; + snprintf(windowTitle, sizeof(windowTitle), "%s Updater", gProductName); + CFStringRef windowTitleRef = NULL; + windowTitleRef = CFStringCreateWithCString(NULL, windowTitle, kCFStringEncodingUTF8); + + if(err == noErr) + { + err = CreateWindowFromNib(nib, CFSTR("Updater"), &gWindow); + } + + if (err == noErr) + { + err = SetWindowTitleWithCFString(gWindow, windowTitleRef); + } + CFRelease(windowTitleRef); + + if(err == noErr) + { + // Set up an event handler for the window. + EventTypeSpec handlerEvents[] = + { + { kEventClassCommand, kEventCommandProcess }, + { kEventClassCustom, kEventCustomProgress }, + { kEventClassCustom, kEventCustomDone } + }; + InstallStandardEventHandler(GetWindowEventTarget(gWindow)); + InstallWindowEventHandler( + gWindow, + NewEventHandlerUPP(dialogHandler), + GetEventTypeCount (handlerEvents), + handlerEvents, + 0, + &gEventHandler); + } + + if(err == noErr) + { + ShowWindow(gWindow); + } + + if(err == noErr) + { + pthread_create(&updatethread, + NULL, + &updatethreadproc, + NULL); + + } + + if(err == noErr) + { + RunAppModalLoopForWindow(gWindow); + } + + void *threadresult; + + pthread_join(updatethread, &threadresult); + + if(!gCancelled && (gFailure != noErr)) + { + // Something went wrong. Since we always just tell the user to download a new version, we don't really care what. + AlertStdCFStringAlertParamRec params; + SInt16 retval_mac = 1; + DialogRef alert = NULL; + OSStatus err; + + params.version = kStdCFStringAlertVersionOne; + params.movable = false; + params.helpButton = false; + params.defaultText = (CFStringRef)kAlertDefaultOKText; + params.cancelText = 0; + params.otherText = 0; + params.defaultButton = 1; + params.cancelButton = 0; + params.position = kWindowDefaultPosition; + params.flags = 0; + + err = CreateStandardAlert( + kAlertStopAlert, + CFSTR("Error"), + CFSTR("An error occurred while updating Second Life. Please download the latest version from www.secondlife.com."), + ¶ms, + &alert); + + if(err == noErr) + { + err = RunStandardAlert( + alert, + NULL, + &retval_mac); + } + + } + + // Don't dispose of things, just exit. This keeps the update thread from potentially getting hosed. + exit(0); + + if(gWindow != NULL) + { + DisposeWindow(gWindow); + } + + if(nib != NULL) + { + DisposeNibReference(nib); + } + + return 0; +} + +bool isDirWritable(FSRef &dir) +{ + bool result = false; + + // Test for a writable directory by creating a directory, then deleting it again. + // This is kinda lame, but will pretty much always give the right answer. + + OSStatus err = noErr; + char temp[PATH_MAX]; + + err = FSRefMakePath(&dir, (UInt8*)temp, sizeof(temp)); + + if(err == noErr) + { + temp[0] = '\0'; + strncat(temp, "/.test_XXXXXX", sizeof(temp) - 1); + + if(mkdtemp(temp) != NULL) + { + // We were able to make the directory. This means the directory is writable. + result = true; + + // Clean up. + rmdir(temp); + } + } + +#if 0 + // This seemed like a good idea, but won't tell us if we're on a volume mounted read-only. + UInt8 perm; + err = FSGetUserPrivilegesPermissions(&targetParentRef, &perm, NULL); + if(err == noErr) + { + if(perm & kioACUserNoMakeChangesMask) + { + // Parent directory isn't writable. + llinfos << "Target parent directory not writable." << llendl; + err = -1; + replacingTarget = false; + } + } +#endif + + return result; +} + +static void utf8str_to_HFSUniStr255(HFSUniStr255 *dest, const char* src) +{ + LLWString wstr = utf8str_to_wstring(src); + llutf16string utf16str = wstring_to_utf16str(wstr); + + dest->length = utf16str.size(); + if(dest->length > 255) + { + // There's onl room for 255 chars in a HFSUniStr25.. + // Truncate to avoid stack smaching or other badness. + dest->length = 255; + } + memcpy(dest->unicode, utf16str.data(), sizeof(UniChar)* dest->length); +} + +int restoreObject(const char* aside, const char* target, const char* path, const char* object) +{ + char source[PATH_MAX]; + char dest[PATH_MAX]; + snprintf(source, sizeof(source), "%s/%s/%s", aside, path, object); + snprintf(dest, sizeof(dest), "%s/%s", target, path); + FSRef sourceRef; + FSRef destRef; + OSStatus err; + err = FSPathMakeRef((UInt8 *)source, &sourceRef, NULL); + if(err != noErr) return false; + err = FSPathMakeRef((UInt8 *)dest, &destRef, NULL); + if(err != noErr) return false; + + llinfos << "Copying " << source << " to " << dest << llendl; + + err = FSCopyObject( + &sourceRef, + &destRef, + 0, + kFSCatInfoNone, + kDupeActionReplace, + NULL, + false, + false, + NULL, + NULL, + NULL, + NULL); + + if(err != noErr) return false; + return true; +} + +// Replace any mention of "Second Life" with the product name. +void filterFile(const char* filename) +{ + char temp[PATH_MAX]; + // First copy the target's version, so we can run it through sed. + snprintf(temp, sizeof(temp), "cp '%s' '%s.tmp'", filename, filename); + system(temp); + + // Now run it through sed. + snprintf(temp, sizeof(temp), + "sed 's/Second Life/%s/g' '%s.tmp' > '%s'", gProductName, filename, filename); + system(temp); +} + +void *updatethreadproc(void*) +{ + char tempDir[PATH_MAX] = ""; + FSRef tempDirRef; + char temp[PATH_MAX]; + char deviceNode[1024] = ""; + FILE *downloadFile = NULL; + OSStatus err; + ProcessSerialNumber psn; + char target[PATH_MAX]; + FSRef targetRef; + FSRef targetParentRef; + FSVolumeRefNum targetVol; + FSRef trashFolderRef, tempFolderRef; + Boolean replacingTarget = false; + + memset(&tempDirRef, 0, sizeof(tempDirRef)); + memset(&targetRef, 0, sizeof(targetRef)); + memset(&targetParentRef, 0, sizeof(targetParentRef)); + + try + { + // Attempt to get a reference to the Second Life application bundle containing this updater. + // Any failures during this process will cause us to default to updating /Applications/Second Life.app + { + FSRef myBundle; + + err = GetCurrentProcess(&psn); + if(err == noErr) + { + err = GetProcessBundleLocation(&psn, &myBundle); + } + + if(err == noErr) + { + // Sanity check: Make sure the name of the item referenced by targetRef is "Second Life.app". + FSRefMakePath(&myBundle, (UInt8*)target, sizeof(target)); + + llinfos << "Updater bundle location: " << target << llendl; + } + + // Our bundle should be in Second Life.app/Contents/Resources/AutoUpdater.app + // so we need to go up 3 levels to get the path to the main application bundle. + if(err == noErr) + { + err = FSGetParentRef(&myBundle, &targetRef); + } + if(err == noErr) + { + err = FSGetParentRef(&targetRef, &targetRef); + } + if(err == noErr) + { + err = FSGetParentRef(&targetRef, &targetRef); + } + + // And once more to get the parent of the target + if(err == noErr) + { + err = FSGetParentRef(&targetRef, &targetParentRef); + } + + if(err == noErr) + { + FSRefMakePath(&targetRef, (UInt8*)target, sizeof(target)); + llinfos << "Path to target: " << target << llendl; + } + + // Sanity check: make sure the target is a bundle with the right identifier + if(err == noErr) + { + CFURLRef targetURL = NULL; + CFBundleRef targetBundle = NULL; + CFStringRef targetBundleID = NULL; + + // Assume the worst... + err = -1; + + targetURL = CFURLCreateFromFSRef(NULL, &targetRef); + + if(targetURL == NULL) + { + llinfos << "Error creating target URL." << llendl; + } + else + { + targetBundle = CFBundleCreate(NULL, targetURL); + } + + if(targetBundle == NULL) + { + llinfos << "Failed to create target bundle." << llendl; + } + else + { + targetBundleID = CFBundleGetIdentifier(targetBundle); + } + + if(targetBundleID == NULL) + { + llinfos << "Couldn't retrieve target bundle ID." << llendl; + } + else + { + if(CFStringCompare(targetBundleID, CFSTR("com.secondlife.indra.viewer"), 0) == kCFCompareEqualTo) + { + // This is the bundle we're looking for. + err = noErr; + replacingTarget = true; + } + else + { + llinfos << "Target bundle ID mismatch." << llendl; + } + } + + // Don't release targetBundleID -- since we don't retain it, it's released when targetBundle is released. + if(targetURL != NULL) + CFRelease(targetURL); + if(targetBundle != NULL) + CFRelease(targetBundle); + + } + + // Make sure the target's parent directory is writable. + if(err == noErr) + { + if(!isDirWritable(targetParentRef)) + { + // Parent directory isn't writable. + llinfos << "Target parent directory not writable." << llendl; + err = -1; + replacingTarget = false; + } + } + + if(err != noErr) + { + Boolean isDirectory; + llinfos << "Target search failed, defaulting to /Applications/" << gProductName << ".app." << llendl; + + // Set up the parent directory + err = FSPathMakeRef((UInt8*)"/Applications", &targetParentRef, &isDirectory); + if((err != noErr) || (!isDirectory)) + { + // We're so hosed. + llinfos << "Applications directory not found, giving up." << llendl; + throw 0; + } + + snprintf(target, sizeof(target), "/Applications/%s.app", gProductName); + + memset(&targetRef, 0, sizeof(targetRef)); + err = FSPathMakeRef((UInt8*)target, &targetRef, NULL); + if(err == fnfErr) + { + // This is fine, just means we're not replacing anything. + err = noErr; + replacingTarget = false; + } + else + { + replacingTarget = true; + } + + // Make sure the target's parent directory is writable. + if(err == noErr) + { + if(!isDirWritable(targetParentRef)) + { + // Parent directory isn't writable. + llinfos << "Target parent directory not writable." << llendl; + err = -1; + replacingTarget = false; + } + } + + } + + // If we haven't fixed all problems by this point, just bail. + if(err != noErr) + { + llinfos << "Unable to pick a target, giving up." << llendl; + throw 0; + } + } + + // Find the volID of the volume the target resides on + { + FSCatalogInfo info; + err = FSGetCatalogInfo( + &targetParentRef, + kFSCatInfoVolume, + &info, + NULL, + NULL, + NULL); + + if(err != noErr) + throw 0; + + targetVol = info.volume; + } + + // Find the temporary items and trash folders on that volume. + err = FSFindFolder( + targetVol, + kTrashFolderType, + true, + &trashFolderRef); + + if(err != noErr) + throw 0; + + err = FSFindFolder( + targetVol, + kTemporaryFolderType, + true, + &tempFolderRef); + + if(err != noErr) + throw 0; + + err = FSRefMakePath(&tempFolderRef, (UInt8*)temp, sizeof(temp)); + + if(err != noErr) + throw 0; + + temp[0] = '\0'; + strncat(temp, "/SecondLifeUpdate_XXXXXX", sizeof(temp) - 1); + if(mkdtemp(temp) == NULL) + { + throw 0; + } + + strcpy(tempDir, temp); + + llinfos << "tempDir is " << tempDir << llendl; + + err = FSPathMakeRef((UInt8*)tempDir, &tempDirRef, NULL); + + if(err != noErr) + throw 0; + + chdir(tempDir); + + snprintf(temp, sizeof(temp), "SecondLife.dmg"); + + downloadFile = fopen(temp, "wb"); + if(downloadFile == NULL) + { + throw 0; + } + + { + CURL *curl = curl_easy_init(); + + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + // curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &curl_download_callback); + curl_easy_setopt(curl, CURLOPT_FILE, downloadFile); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, &curl_progress_callback_func); + curl_easy_setopt(curl, CURLOPT_URL, gUpdateURL); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + + sendProgress(0, 1, CFSTR("Downloading...")); + + CURLcode result = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + + if(gCancelled) + { + llinfos << "User cancel, bailing out."<< llendl; + throw 0; + } + + if(result != CURLE_OK) + { + llinfos << "Error " << result << " while downloading disk image."<< llendl; + throw 0; + } + + fclose(downloadFile); + downloadFile = NULL; + } + + sendProgress(0, 0, CFSTR("Mounting image...")); + LLFile::mkdir("mnt", 0700); + + // NOTE: we could add -private at the end of this command line to keep the image from showing up in the Finder, + // but if our cleanup fails, this makes it much harder for the user to unmount the image. + LLString mountOutput; + FILE *mounter = popen("hdiutil attach SecondLife.dmg -mountpoint mnt", "r"); + + if(mounter == NULL) + { + llinfos << "Failed to mount disk image, exiting."<< llendl; + throw 0; + } + + // We need to scan the output from hdiutil to find the device node it uses to attach the disk image. + // If we don't have this information, we can't detach it later. + while(mounter != NULL) + { + size_t len = fread(temp, 1, sizeof(temp)-1, mounter); + temp[len] = 0; + mountOutput.append(temp); + if(len < sizeof(temp)-1) + { + // End of file or error. + if(pclose(mounter) != 0) + { + llinfos << "Failed to mount disk image, exiting."<< llendl; + throw 0; + } + mounter = NULL; + } + } + + if(!mountOutput.empty()) + { + const char *s = mountOutput.c_str(); + char *prefix = "/dev/"; + char *sub = strstr(s, prefix); + + if(sub != NULL) + { + sub += strlen(prefix); + sscanf(sub, "%s", deviceNode); + } + } + + if(deviceNode[0] != 0) + { + llinfos << "Disk image attached on /dev/" << deviceNode << llendl; + } + else + { + llinfos << "Disk image device node not found!" << llendl; + } + + // Get an FSRef to the new application on the disk image + FSRef sourceRef; + snprintf(temp, sizeof(temp), "%s/mnt/Second Life.app", tempDir); + + llinfos << "Source application is: " << temp << llendl; + + err = FSPathMakeRef((UInt8 *)temp, &sourceRef, NULL); + if(err != noErr) + throw 0; + + FSRef asideRef; + char aside[MAX_PATH]; + + // this will hold the name of the destination target + HFSUniStr255 appNameUniStr; + + if(replacingTarget) + { + // Get the name of the target we're replacing + err = FSGetCatalogInfo(&targetRef, 0, NULL, &appNameUniStr, NULL, NULL); + if(err != noErr) + throw 0; + + // Move aside old version (into work directory) + err = FSMoveObject(&targetRef, &tempDirRef, &asideRef); + if(err != noErr) + throw 0; + + // Grab the path for later use. + err = FSRefMakePath(&asideRef, (UInt8*)aside, sizeof(aside)); + } + else + { + // Construct the name of the target based on the product name + char appName[MAX_PATH]; + snprintf(appName, sizeof(appName), "%s.app", gProductName); + utf8str_to_HFSUniStr255( &appNameUniStr, appName ); + } + + sendProgress(0, 0, CFSTR("Copying files...")); + + llinfos << "Starting copy..." << llendl; + + // Copy the new version from the disk image to the target location. + err = FSCopyObject( + &sourceRef, + &targetParentRef, + 0, + kFSCatInfoNone, + kDupeActionStandard, + &appNameUniStr, + false, + false, + NULL, + NULL, + &targetRef, + NULL); + + // Grab the path for later use. + err = FSRefMakePath(&targetRef, (UInt8*)target, sizeof(target)); + if(err != noErr) + throw 0; + + llinfos << "Copy complete. Target = " << target << llendl; + + if(err != noErr) + { + // Something went wrong during the copy. Attempt to put the old version back and bail. + (void)FSDeleteObjects(&targetRef); + if(replacingTarget) + { + (void)FSMoveObject(&asideRef, &targetParentRef, NULL); + } + throw 0; + } + else + { + // The update has succeeded. Clear the cache directory. + + sendProgress(0, 0, CFSTR("Clearing cache...")); + + llinfos << "Clearing cache..." << llendl; + + char mask[LL_MAX_PATH]; + sprintf(mask, "%s*.*", gDirUtilp->getDirDelimiter().c_str()); + gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""),mask); + + llinfos << "Clear complete." << llendl; + + } + } + catch(...) + { + if(!gCancelled) + if(gFailure == noErr) + gFailure = -1; + } + + // Failures from here on out are all non-fatal and not reported. + sendProgress(0, 3, CFSTR("Cleaning up...")); + + // Close disk image file if necessary + if(downloadFile != NULL) + { + llinfos << "Closing download file." << llendl; + + fclose(downloadFile); + downloadFile = NULL; + } + + sendProgress(1, 3); + // Unmount image + if(deviceNode[0] != 0) + { + llinfos << "Detaching disk image." << llendl; + + snprintf(temp, sizeof(temp), "hdiutil detach '%s'", deviceNode); + system(temp); + } + + sendProgress(2, 3); + + // Move work directory to the trash + if(tempDir[0] != 0) + { +// chdir("/"); +// FSDeleteObjects(tempDirRef); + + llinfos << "Moving work directory to the trash." << llendl; + + err = FSMoveObject(&tempDirRef, &trashFolderRef, NULL); + +// snprintf(temp, sizeof(temp), "rm -rf '%s'", tempDir); +// printf("%s\n", temp); +// system(temp); + } + + if(!gCancelled && !gFailure && (target[0] != 0)) + { + llinfos << "Touching application bundle." << llendl; + + snprintf(temp, sizeof(temp), "touch '%s'", target); + system(temp); + + llinfos << "Launching updated application." << llendl; + + snprintf(temp, sizeof(temp), "open '%s'", target); + system(temp); + } + + sendDone(); + + return(NULL); +} |