summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorOz Linden <oz@lindenlab.com>2013-03-06 16:39:48 -0500
committerOz Linden <oz@lindenlab.com>2013-03-06 16:39:48 -0500
commitc70b0a9de66e6b8d93f1027932ba7e801de3473b (patch)
treed1157e2897ac8bf1e79ce6f55200406d1bb7fca2 /indra
parentce73cc392c3f6e2a80c03e30a7dd975408e69f1c (diff)
parent1eabdf70fc61f7f1cff5fe2522e3e769d0e35db5 (diff)
merge changes for DRTVWR-278
Diffstat (limited to 'indra')
-rw-r--r--indra/CMakeLists.txt3
-rw-r--r--indra/mac_updater/AutoUpdater.nib/classes.nib4
-rw-r--r--indra/mac_updater/AutoUpdater.nib/info.nib14
-rw-r--r--indra/mac_updater/AutoUpdater.nib/objects.xib56
-rw-r--r--indra/mac_updater/CMakeLists.txt73
-rw-r--r--indra/mac_updater/Info.plist26
-rw-r--r--indra/mac_updater/mac_updater.cpp1257
-rw-r--r--indra/mac_updater/mac_updater.h91
-rw-r--r--indra/newview/CMakeLists.txt5
-rw-r--r--indra/newview/viewer_manifest.py7
-rw-r--r--indra/viewer_components/updater/llupdaterservice.cpp4
-rw-r--r--indra/viewer_components/updater/scripts/darwin/janitor.py133
-rw-r--r--indra/viewer_components/updater/scripts/darwin/messageframe.py66
-rw-r--r--indra/viewer_components/updater/scripts/darwin/update_install10
-rwxr-xr-xindra/viewer_components/updater/scripts/darwin/update_install.py373
15 files changed, 583 insertions, 1539 deletions
diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt
index 4771923871..ae69d0b843 100644
--- a/indra/CMakeLists.txt
+++ b/indra/CMakeLists.txt
@@ -73,8 +73,7 @@ if (VIEWER)
add_dependencies(viewer linux-crash-logger-strip-target)
elseif (DARWIN)
add_subdirectory(${VIEWER_PREFIX}mac_crash_logger)
- add_subdirectory(${VIEWER_PREFIX}mac_updater)
- add_dependencies(viewer mac-updater mac-crash-logger)
+ add_dependencies(viewer mac-crash-logger)
elseif (WINDOWS)
add_subdirectory(${VIEWER_PREFIX}win_crash_logger)
# cmake EXISTS requires an absolute path, see indra/cmake/Variables.cmake
diff --git a/indra/mac_updater/AutoUpdater.nib/classes.nib b/indra/mac_updater/AutoUpdater.nib/classes.nib
deleted file mode 100644
index ea58db1189..0000000000
--- a/indra/mac_updater/AutoUpdater.nib/classes.nib
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-IBClasses = ();
-IBVersion = 1;
-}
diff --git a/indra/mac_updater/AutoUpdater.nib/info.nib b/indra/mac_updater/AutoUpdater.nib/info.nib
deleted file mode 100644
index a49a92385b..0000000000
--- a/indra/mac_updater/AutoUpdater.nib/info.nib
+++ /dev/null
@@ -1,14 +0,0 @@
-<?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
deleted file mode 100644
index 310411b711..0000000000
--- a/indra/mac_updater/AutoUpdater.nib/objects.xib
+++ /dev/null
@@ -1,56 +0,0 @@
-<?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&apos;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/CMakeLists.txt b/indra/mac_updater/CMakeLists.txt
deleted file mode 100644
index 00dcedecaa..0000000000
--- a/indra/mac_updater/CMakeLists.txt
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- cmake -*-
-
-project(mac_updater)
-
-include(00-Common)
-include(OpenSSL)
-include(CURL)
-include(CARes)
-include(LLCommon)
-include(LLVFS)
-include(Linking)
-
-include_directories(
- ${LLCOMMON_INCLUDE_DIRS}
- ${LLVFS_INCLUDE_DIRS}
- ${CURL_INCLUDE_DIRS}
- ${CARES_INCLUDE_DIRS}
- )
-
-set(mac_updater_SOURCE_FILES
- mac_updater.cpp
- )
-
-set(mac_updater_HEADER_FILES
- CMakeLists.txt
- )
-
-set_source_files_properties(${mac_updater_HEADER_FILES}
- PROPERTIES HEADER_FILE_ONLY TRUE)
-
-list(APPEND mac_updater_SOURCE_FILES ${mac_updater_HEADER_FILES})
-
-
-set(mac_updater_RESOURCE_FILES
- AutoUpdater.nib/
- )
-set_source_files_properties(
- ${mac_updater_RESOURCE_FILES}
- PROPERTIES
- HEADER_FILE_ONLY TRUE
- )
-SOURCE_GROUP("Resources" FILES ${mac_updater_RESOURCE_FILES})
-list(APPEND mac_updater_SOURCE_FILES ${mac_updater_RESOURCE_FILES})
-
-add_executable(mac-updater
- MACOSX_BUNDLE
- ${mac_updater_SOURCE_FILES})
-
-set_target_properties(mac-updater
- PROPERTIES
- MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist
- )
-
-target_link_libraries(mac-updater
- ${LLVFS_LIBRARIES}
- ${OPENSSL_LIBRARIES}
- ${CRYPTO_LIBRARIES}
- ${CURL_LIBRARIES}
- ${CARES_LIBRARIES}
- ${LLCOMMON_LIBRARIES}
- )
-
-add_custom_command(
- TARGET mac-updater POST_BUILD
- COMMAND ${CMAKE_COMMAND}
- ARGS
- -E
- copy_directory
- ${CMAKE_CURRENT_SOURCE_DIR}/AutoUpdater.nib
- ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/mac-updater.app/Contents/Resources/AutoUpdater.nib
- )
-
-ll_deploy_sharedlibs_command(mac-updater)
diff --git a/indra/mac_updater/Info.plist b/indra/mac_updater/Info.plist
deleted file mode 100644
index bb27fddb03..0000000000
--- a/indra/mac_updater/Info.plist
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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>CFBundleDevelopmentRegion</key>
- <string>English</string>
- <key>CFBundleExecutable</key>
- <string>mac-updater</string>
- <key>CFBundleGetInfoString</key>
- <string></string>
- <key>CFBundleIconFile</key>
- <string></string>
- <key>CFBundleIdentifier</key>
- <string>com.secondlife.indra.autoupdater</string>
- <key>CFBundleInfoDictionaryVersion</key>
- <string>6.0</string>
- <key>CFBundlePackageType</key>
- <string>APPL</string>
- <key>CFBundleShortVersionString</key>
- <string></string>
- <key>CFBundleSignature</key>
- <string>????</string>
- <key>CFBundleVersion</key>
- <string>1.0.0</string>
-</dict>
-</plist>
diff --git a/indra/mac_updater/mac_updater.cpp b/indra/mac_updater/mac_updater.cpp
deleted file mode 100644
index aa45c5d23f..0000000000
--- a/indra/mac_updater/mac_updater.cpp
+++ /dev/null
@@ -1,1257 +0,0 @@
-/**
- * @file mac_updater.cpp
- * @brief
- *
- * $LicenseInfo:firstyear=2006&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include "linden_common.h"
-
-#include <boost/format.hpp>
-
-#include <libgen.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 "llerrorcontrol.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;
-
-const char *gUpdateURL;
-const char *gProductName;
-const char *gBundleID;
-const char *gDmgFile;
-const char *gMarkerPath;
-
-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)
-{
- int j;
-
- for (j = 1; j < argc; j++)
- {
- if ((!strcmp(argv[j], "-url")) && (++j < argc))
- {
- gUpdateURL = argv[j];
- }
- else if ((!strcmp(argv[j], "-name")) && (++j < argc))
- {
- gProductName = argv[j];
- }
- else if ((!strcmp(argv[j], "-bundleid")) && (++j < argc))
- {
- gBundleID = argv[j];
- }
- else if ((!strcmp(argv[j], "-dmg")) && (++j < argc))
- {
- gDmgFile = argv[j];
- }
- else if ((!strcmp(argv[j], "-marker")) && (++j < argc))
- {
- gMarkerPath = 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");
-
- LLError::initForApplication( gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, ""));
-
- // Rename current log file to ".old"
- std::string old_log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "updater.log.old");
- std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "updater.log");
- LLFile::rename(log_file.c_str(), old_log_file.c_str());
-
- // Set the log file to updater.log
- LLError::logToFile(log_file);
-
- /////////////////////////////////////////
- //
- // Process command line arguments
- //
- gUpdateURL = NULL;
- gProductName = NULL;
- gBundleID = NULL;
- gDmgFile = NULL;
- gMarkerPath = NULL;
- parse_args(argc, argv);
- if ((gUpdateURL == NULL) && (gDmgFile == NULL))
- {
- llinfos << "Usage: mac_updater -url <url> | -dmg <dmg file> [-name <product_name>] [-program <program_name>]" << llendl;
- exit(1);
- }
- else
- {
- llinfos << "Update url is: " << gUpdateURL << llendl;
- if (gProductName)
- {
- llinfos << "Product name is: " << gProductName << llendl;
- }
- else
- {
- gProductName = "Second Life";
- }
- if (gBundleID)
- {
- llinfos << "Bundle ID is: " << gBundleID << llendl;
- }
- else
- {
- gBundleID = "com.secondlife.indra.viewer";
- }
- }
-
- llinfos << "Starting " << gProductName << " Updater" << llendl;
-
- // Real UI...
- OSStatus err;
- IBNibRef nib = NULL;
-
- err = CreateNibReference(CFSTR("AutoUpdater"), &nib);
-
- char windowTitle[MAX_PATH]; /* Flawfinder: ignore */
- 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);
- SelectWindow(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."),
- &params,
- &alert);
-
- if(err == noErr)
- {
- err = RunStandardAlert(
- alert,
- NULL,
- &retval_mac);
- }
-
- if(gMarkerPath != 0)
- {
- // Create a install fail marker that can be used by the viewer to
- // detect install problems.
- std::ofstream stream(gMarkerPath);
- if(stream) stream << -1;
- }
- exit(-1);
- } else {
- 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] = ""; /* Flawfinder: ignore */
-
- err = FSRefMakePath(&dir, (UInt8*)temp, sizeof(temp));
-
- if(err == noErr)
- {
- strncat(temp, "/.test_XXXXXX", (sizeof(temp) - strlen(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 std::string HFSUniStr255_to_utf8str(const HFSUniStr255* src)
-{
- llutf16string string16((U16*)&(src->unicode), src->length);
- std::string result = utf16str_to_utf8str(string16);
- return result;
-}
-
-int restoreObject(const char* aside, const char* target, const char* path, const char* object)
-{
- char source[PATH_MAX] = ""; /* Flawfinder: ignore */
- char dest[PATH_MAX] = ""; /* Flawfinder: ignore */
- 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 = FSCopyObjectSync(
- &sourceRef,
- &destRef,
- NULL,
- NULL,
- kFSFileOperationOverwrite);
-
- 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] = ""; /* Flawfinder: ignore */
- // 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); /* Flawfinder: ignore */
-
- // Now run it through sed.
- snprintf(temp, sizeof(temp),
- "sed 's/Second Life/%s/g' '%s.tmp' > '%s'", gProductName, filename, filename);
- system(temp); /* Flawfinder: ignore */
-}
-
-static bool isFSRefViewerBundle(FSRef *targetRef)
-{
- bool result = false;
- CFURLRef targetURL = NULL;
- CFBundleRef targetBundle = NULL;
- CFStringRef targetBundleID = NULL;
- CFStringRef sourceBundleID = NULL;
-
- 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
- {
- sourceBundleID = CFStringCreateWithCString(NULL, gBundleID, kCFStringEncodingUTF8);
- if(CFStringCompare(sourceBundleID, targetBundleID, 0) == kCFCompareEqualTo)
- {
- // This is the bundle we're looking for.
- result = 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);
-
- return result;
-}
-
-// Search through the directory specified by 'parent' for an item that appears to be a Second Life viewer.
-static OSErr findAppBundleOnDiskImage(FSRef *parent, FSRef *app)
-{
- FSIterator iterator;
- bool found = false;
-
- OSErr err = FSOpenIterator( parent, kFSIterateFlat, &iterator );
- if(!err)
- {
- do
- {
- ItemCount actualObjects = 0;
- Boolean containerChanged = false;
- FSCatalogInfo info;
- FSRef ref;
- HFSUniStr255 unicodeName;
- err = FSGetCatalogInfoBulk(
- iterator,
- 1,
- &actualObjects,
- &containerChanged,
- kFSCatInfoNodeFlags,
- &info,
- &ref,
- NULL,
- &unicodeName );
-
- if(actualObjects == 0)
- break;
-
- if(!err)
- {
- // Call succeeded and not done with the iteration.
- std::string name = HFSUniStr255_to_utf8str(&unicodeName);
-
- llinfos << "Considering \"" << name << "\"" << llendl;
-
- if(info.nodeFlags & kFSNodeIsDirectoryMask)
- {
- // This is a directory. See if it's a .app
- if(name.find(".app") != std::string::npos)
- {
- // Looks promising. Check to see if it has the right bundle identifier.
- if(isFSRefViewerBundle(&ref))
- {
- llinfos << name << " is the one" << llendl;
- // This is the one. Return it.
- *app = ref;
- found = true;
- break;
- } else {
- llinfos << name << " is not the bundle we are looking for; move along" << llendl;
- }
-
- }
- }
- }
- }
- while(!err);
-
- llinfos << "closing the iterator" << llendl;
-
- FSCloseIterator(iterator);
-
- llinfos << "closed" << llendl;
- }
-
- if(!err && !found)
- err = fnfErr;
-
- return err;
-}
-
-void *updatethreadproc(void*)
-{
- char tempDir[PATH_MAX] = ""; /* Flawfinder: ignore */
- FSRef tempDirRef;
- char temp[PATH_MAX] = ""; /* Flawfinder: ignore */
- // *NOTE: This buffer length is used in a scanf() below.
- char deviceNode[1024] = ""; /* Flawfinder: ignore */
- LLFILE *downloadFile = NULL;
- OSStatus err;
- ProcessSerialNumber psn;
- char target[PATH_MAX] = ""; /* Flawfinder: ignore */
- FSRef targetRef;
- FSRef targetParentRef;
- FSVolumeRefNum targetVol;
- FSRef trashFolderRef;
- 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 = FSGetCatalogInfo(&myBundle, kFSCatInfoNone, NULL, NULL, NULL, &targetRef);
- }
- if(err == noErr)
- {
- err = FSGetCatalogInfo(&targetRef, kFSCatInfoNone, NULL, NULL, NULL, &targetRef);
- }
- if(err == noErr)
- {
- err = FSGetCatalogInfo(&targetRef, kFSCatInfoNone, NULL, NULL, NULL, &targetRef);
- }
-
- // And once more to get the parent of the target
- if(err == noErr)
- {
- err = FSGetCatalogInfo(&targetRef, kFSCatInfoNone, NULL, NULL, NULL, &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)
- {
- // Assume the worst...
- err = -1;
-
- if(isFSRefViewerBundle(&targetRef))
- {
- // This is the bundle we're looking for.
- err = noErr;
- 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(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;
-
-#if 0 // *HACK for DEV-11935 see below for details.
-
- FSRef tempFolderRef;
-
- err = FSFindFolder(
- targetVol,
- kTemporaryFolderType,
- true,
- &tempFolderRef);
-
- if(err != noErr)
- throw 0;
-
- err = FSRefMakePath(&tempFolderRef, (UInt8*)temp, sizeof(temp));
-
- if(err != noErr)
- throw 0;
-
-#else
-
- // *HACK for DEV-11935 the above kTemporaryFolderType query was giving
- // back results with path names that seem to be too long to be used as
- // mount points. I suspect this incompatibility was introduced in the
- // Leopard 10.5.2 update, but I have not verified this.
- char const HARDCODED_TMP[] = "/tmp";
- strncpy(temp, HARDCODED_TMP, sizeof(HARDCODED_TMP));
-
-#endif // 0 *HACK for DEV-11935
-
- // Skip downloading the file if the dmg was passed on the command line.
- std::string dmgName;
- if(gDmgFile != NULL) {
- dmgName = basename((char *)gDmgFile);
- char * dmgDir = dirname((char *)gDmgFile);
- strncpy(tempDir, dmgDir, sizeof(tempDir));
- err = FSPathMakeRef((UInt8*)tempDir, &tempDirRef, NULL);
- if(err != noErr) throw 0;
- chdir(tempDir);
- goto begin_install;
- } else {
- // Continue on to download file.
- dmgName = "SecondLife.dmg";
- }
-
-
- strncat(temp, "/SecondLifeUpdate_XXXXXX", (sizeof(temp) - strlen(temp)) - 1);
- if(mkdtemp(temp) == NULL)
- {
- throw 0;
- }
-
- strncpy(tempDir, temp, sizeof(tempDir));
- temp[sizeof(tempDir) - 1] = '\0';
-
- 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 = LLFile::fopen(temp, "wb"); /* Flawfinder: ignore */
- 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;
- }
-
- begin_install:
- 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.
- std::string mountOutput;
- boost::format cmdFormat("hdiutil attach %s -mountpoint mnt");
- cmdFormat % dmgName;
- FILE* mounter = popen(cmdFormat.str().c_str(), "r"); /* Flawfinder: ignore */
-
- 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.
- int result = pclose(mounter);
- if(result != 0)
- {
- // NOTE: We used to abort here, but pclose() started returning
- // -1, possibly when the size of the DMG passed a certain point
- llinfos << "Unexpected result closing pipe: " << result << llendl;
- }
- mounter = NULL;
- }
- }
-
- if(!mountOutput.empty())
- {
- const char *s = mountOutput.c_str();
- const char *prefix = "/dev/";
- char *sub = strstr(s, prefix);
-
- if(sub != NULL)
- {
- sub += strlen(prefix); /* Flawfinder: ignore */
- sscanf(sub, "%1023s", deviceNode); /* Flawfinder: ignore */
- }
- }
-
- if(deviceNode[0] != 0)
- {
- llinfos << "Disk image attached on /dev/" << deviceNode << llendl;
- }
- else
- {
- llinfos << "Disk image device node not found!" << llendl;
- throw 0;
- }
-
- // Get an FSRef to the new application on the disk image
- FSRef sourceRef;
- FSRef mountRef;
- snprintf(temp, sizeof(temp), "%s/mnt", tempDir);
-
- llinfos << "Disk image mount point is: " << temp << llendl;
-
- err = FSPathMakeRef((UInt8 *)temp, &mountRef, NULL);
- if(err != noErr)
- {
- llinfos << "Couldn't make FSRef to disk image mount point." << llendl;
- throw 0;
- }
-
- sendProgress(0, 0, CFSTR("Searching for the app bundle..."));
- err = findAppBundleOnDiskImage(&mountRef, &sourceRef);
- if(err != noErr)
- {
- llinfos << "Couldn't find application bundle on mounted disk image." << llendl;
- throw 0;
- }
- else
- {
- llinfos << "found the bundle." << llendl;
- }
-
- sendProgress(0, 0, CFSTR("Preparing to copy files..."));
-
- FSRef asideRef;
- char aside[MAX_PATH]; /* Flawfinder: ignore */
-
- // this will hold the name of the destination target
- CFStringRef appNameRef;
-
- if(replacingTarget)
- {
- // Get the name of the target we're replacing
- HFSUniStr255 appNameUniStr;
- err = FSGetCatalogInfo(&targetRef, 0, NULL, &appNameUniStr, NULL, NULL);
- if(err != noErr)
- throw 0;
- appNameRef = FSCreateStringFromHFSUniStr(NULL, &appNameUniStr);
-
- // Move aside old version (into work directory)
- err = FSMoveObject(&targetRef, &tempDirRef, &asideRef);
- if(err != noErr)
- {
- llwarns << "failed to move aside old version (error code " <<
- err << ")" << llendl;
- 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]; /* Flawfinder: ignore */
- snprintf(appName, sizeof(appName), "%s.app", gProductName);
- appNameRef = CFStringCreateWithCString(NULL, appName, kCFStringEncodingUTF8);
- }
-
- sendProgress(0, 0, CFSTR("Copying files..."));
-
- llinfos << "Starting copy..." << llendl;
-
- // Copy the new version from the disk image to the target location.
- err = FSCopyObjectSync(
- &sourceRef,
- &targetParentRef,
- appNameRef,
- &targetRef,
- kFSFileOperationDefaultOptions);
-
- // 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)FSDeleteObject(&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;
-
- gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""), "*.*");
-
- 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); /* Flawfinder: ignore */
- }
-
- sendProgress(2, 3);
-
- // Move work directory to the trash
- if(tempDir[0] != 0)
- {
- llinfos << "Moving work directory to the trash." << llendl;
-
- FSRef trashRef;
- OSStatus err = FSMoveObjectToTrashSync(&tempDirRef, &trashRef, 0);
- if(err != noErr) {
- llwarns << "failed to move files to trash, (error code " <<
- err << ")" << llendl;
- }
- }
-
- if(!gCancelled && !gFailure && (target[0] != 0))
- {
- llinfos << "Touching application bundle." << llendl;
-
- snprintf(temp, sizeof(temp), "touch '%s'", target);
- system(temp); /* Flawfinder: ignore */
-
- llinfos << "Launching updated application." << llendl;
-
- snprintf(temp, sizeof(temp), "open '%s'", target);
- system(temp); /* Flawfinder: ignore */
- }
-
- sendDone();
-
- return(NULL);
-}
diff --git a/indra/mac_updater/mac_updater.h b/indra/mac_updater/mac_updater.h
deleted file mode 100644
index f65b481cb6..0000000000
--- a/indra/mac_updater/mac_updater.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/**
- * @file mac_updater.h
- * @brief
- *
- * $LicenseInfo:firstyear=2006&license=viewerlgpl$
- * Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation;
- * version 2.1 of the License only.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
- * $/LicenseInfo$
- */
-
-#include <iostream>
-#include <pthread.h>
-#include <boost/filesystem.hpp>
-
-#ifndef LL_MAC_UPDATER_H
-#define LL_MAC_UPDATER_H
-extern bool gCancelled;
-extern bool gFailure;
-
-void *updatethreadproc(void*);
-std::string* walkParents( signed int depth, std::string* childpath );
-std::string* getUserTrashFolder();
-
-void setProgress(int cur, int max);
-void setProgressText(const std::string& str);
-void sendProgress(int cur, int max, std::string str);
-void sendDone();
-void sendStopAlert();
-
-bool isFSRefViewerBundle(const std::string& targetURL);
-bool isDirWritable(const std::string& dir_name);
-bool mkTempDir(boost::filesystem::path& temp_dir);
-bool copyDir(const std::string& src_dir, const std::string& dest_dir);
-
-int oldmain();
-
-class LLMacUpdater
-{
-public:
- LLMacUpdater();
- void doUpdate();
- const std::string walkParents( signed int depth, const std::string& childpath );
- bool isApplication(const std::string& app_str);
- void filterFile(const char* filename);
-
- bool findAppBundleOnDiskImage(const boost::filesystem::path& dir_path,
- boost::filesystem::path& path_found);
-
- bool verifyDirectory(const boost::filesystem::path* directory, bool isParent=false);
- bool getViewerDir(boost::filesystem::path &app_dir);
- bool downloadDMG(const std::string& dmgName, boost::filesystem::path* temp_dir);
- bool doMount(const std::string& dmgName, char* deviceNode, const boost::filesystem::path& temp_dir);
- bool moveApplication (const boost::filesystem::path& app_dir,
- const boost::filesystem::path& temp_dir,
- boost::filesystem::path& aside_dir);
- bool doInstall(const boost::filesystem::path& app_dir,
- const boost::filesystem::path& temp_dir,
- boost::filesystem::path& mount_dir,
- bool replacingTarget);
- void* updatethreadproc(void*);
- static void* sUpdatethreadproc(void*);
-
-public:
- std::string *mUpdateURL;
- std::string *mProductName;
- std::string *mBundleID;
- std::string *mDmgFile;
- std::string *mMarkerPath;
- std::string *mApplicationPath;
- static LLMacUpdater *sInstance;
-
-};
-#endif
-
-
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 002c826a30..0b21d6c8fb 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -2000,7 +2000,7 @@ if (DARWIN)
generate_viewer_version
)
- add_dependencies(${VIEWER_BINARY_NAME} SLPlugin media_plugin_quicktime media_plugin_webkit mac-updater mac-crash-logger)
+ add_dependencies(${VIEWER_BINARY_NAME} SLPlugin media_plugin_quicktime media_plugin_webkit mac-crash-logger)
if (ENABLE_SIGNING)
set(SIGNING_SETTING "--signature=${SIGNING_IDENTITY}")
@@ -2056,12 +2056,11 @@ if (PACKAGE)
# *TODO: Generate these search dirs in the cmake files related to each binary.
list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_BINARY_DIR}/llplugin/slplugin/${CMAKE_CFG_INTDIR}")
list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_BINARY_DIR}/mac_crash_logger/${CMAKE_CFG_INTDIR}")
- list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_BINARY_DIR}/mac_updater/${CMAKE_CFG_INTDIR}")
list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_BINARY_DIR}/media_plugins/gstreamer010/${CMAKE_CFG_INTDIR}")
list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_BINARY_DIR}/media_plugins/quicktime/${CMAKE_CFG_INTDIR}")
list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_BINARY_DIR}/media_plugins/webkit/${CMAKE_CFG_INTDIR}")
set(VIEWER_SYMBOL_FILE "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/secondlife-symbols-darwin.tar.bz2")
- set(VIEWER_EXE_GLOBS "'Second Life' SLPlugin mac-updater mac-crash-logger")
+ set(VIEWER_EXE_GLOBS "'Second Life' SLPlugin mac-crash-logger")
set(VIEWER_LIB_GLOB "*.dylib")
endif (DARWIN)
if (LINUX)
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index b3c0e650ec..9f06dca17a 100644
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -635,7 +635,9 @@ class DarwinManifest(ViewerManifest):
self.path("../packages/lib/release/libndofdev.dylib", dst="Resources/libndofdev.dylib")
self.path("../packages/lib/release/libhunspell-1.3.0.dylib", dst="Resources/libhunspell-1.3.0.dylib")
- self.path("../viewer_components/updater/scripts/darwin/update_install", "MacOS/update_install")
+ if self.prefix(dst="MacOS"):
+ self.path2basename("../viewer_components/updater/scripts/darwin", "*.py")
+ self.end_prefix()
# most everything goes in the Resources directory
if self.prefix(src="", dst="Resources"):
@@ -731,7 +733,6 @@ class DarwinManifest(ViewerManifest):
# our apps
for app_bld_dir, app in (("mac_crash_logger", "mac-crash-logger.app"),
- ("mac_updater", "mac-updater.app"),
# plugin launcher
(os.path.join("llplugin", "slplugin"), "SLPlugin.app"),
):
@@ -777,7 +778,7 @@ class DarwinManifest(ViewerManifest):
def copy_finish(self):
# Force executable permissions to be set for scripts
# see CHOP-223 and http://mercurial.selenic.com/bts/issue1802
- for script in 'Contents/MacOS/update_install',:
+ for script in 'Contents/MacOS/update_install.py',:
self.run_command("chmod +x %r" % os.path.join(self.get_dst_prefix(), script))
def package_finish(self):
diff --git a/indra/viewer_components/updater/llupdaterservice.cpp b/indra/viewer_components/updater/llupdaterservice.cpp
index 324b051b21..c1e57122ee 100644
--- a/indra/viewer_components/updater/llupdaterservice.cpp
+++ b/indra/viewer_components/updater/llupdaterservice.cpp
@@ -65,6 +65,8 @@ namespace
{
#ifdef LL_WINDOWS
std::string scriptFile = "update_install.bat";
+#elif LL_DARWIN
+ std::string scriptFile = "update_install.py";
#else
std::string scriptFile = "update_install";
#endif
@@ -76,6 +78,8 @@ namespace
#ifdef LL_WINDOWS
return LL_COPY_INSTALL_SCRIPT_TO_TEMP;
#else
+ // This is important on Mac because update_install.py looks at its own
+ // script pathname to discover the viewer app bundle to update.
return LL_RUN_INSTALL_SCRIPT_IN_PLACE;
#endif
};
diff --git a/indra/viewer_components/updater/scripts/darwin/janitor.py b/indra/viewer_components/updater/scripts/darwin/janitor.py
new file mode 100644
index 0000000000..cdf33df731
--- /dev/null
+++ b/indra/viewer_components/updater/scripts/darwin/janitor.py
@@ -0,0 +1,133 @@
+#!/usr/bin/python
+"""\
+@file janitor.py
+@author Nat Goodspeed
+@date 2011-09-14
+@brief Janitor class to clean up arbitrary resources
+
+2013-01-04 cloned from vita because it's exactly what update_install.py needs.
+
+$LicenseInfo:firstyear=2011&license=viewerlgpl$
+Copyright (c) 2011, Linden Research, Inc.
+$/LicenseInfo$
+"""
+
+import sys
+import functools
+import itertools
+
+class Janitor(object):
+ """
+ Usage:
+
+ Basic:
+ self.janitor = Janitor(sys.stdout) # report cleanup actions on stdout
+ ...
+ self.janitor.later(os.remove, some_temp_file)
+ self.janitor.later(os.remove, some_other_file)
+ ...
+ self.janitor.cleanup() # perform cleanup actions
+
+ Context Manager:
+ with Janitor() as janitor: # clean up quietly
+ ...
+ janitor.later(shutil.rmtree, some_temp_directory)
+ ...
+ # exiting 'with' block performs cleanup
+
+ Test Class:
+ class TestMySoftware(unittest.TestCase, Janitor):
+ def __init__(self):
+ Janitor.__init__(self) # quiet cleanup
+ ...
+
+ def setUp(self):
+ ...
+ self.later(os.rename, saved_file, original_location)
+ ...
+
+ def tearDown(self):
+ Janitor.tearDown(self) # calls cleanup()
+ ...
+ # Or, if you have no other tearDown() logic for
+ # TestMySoftware, you can omit the TestMySoftware.tearDown()
+ # def entirely and let it inherit Janitor.tearDown().
+ """
+ def __init__(self, stream=None):
+ """
+ If you pass stream= (e.g.) sys.stdout or sys.stderr, Janitor will
+ report its cleanup operations as it performs them. If you don't, it
+ will perform them quietly -- unless one or more of the actions throws
+ an exception, in which case you'll get output on stderr.
+ """
+ self.stream = stream
+ self.cleanups = []
+
+ def later(self, func, *args, **kwds):
+ """
+ Pass the callable you want to call at cleanup() time, plus any
+ positional or keyword args you want to pass it.
+ """
+ # Get a name string for 'func'
+ try:
+ # A free function has a __name__
+ name = func.__name__
+ except AttributeError:
+ try:
+ # A class object (even builtin objects like ints!) support
+ # __class__.__name__
+ name = func.__class__.__name__
+ except AttributeError:
+ # Shrug! Just use repr() to get a string describing this func.
+ name = repr(func)
+ # Construct a description of this operation in Python syntax from
+ # args, kwds.
+ desc = "%s(%s)" % \
+ (name, ", ".join(itertools.chain((repr(a) for a in args),
+ ("%s=%r" % (k, v) for (k, v) in kwds.iteritems()))))
+ # Use functools.partial() to bind passed args and keywords to the
+ # passed func so we get a nullary callable that does what caller
+ # wants.
+ bound = functools.partial(func, *args, **kwds)
+ self.cleanups.append((desc, bound))
+
+ def cleanup(self):
+ """
+ Perform all the actions saved with later() calls.
+ """
+ # Typically one allocates resource A, then allocates resource B that
+ # depends on it. In such a scenario it's appropriate to delete B
+ # before A -- so perform cleanup actions in reverse order. (This is
+ # the same strategy used by atexit().)
+ while self.cleanups:
+ # Until our list is empty, pop the last pair.
+ desc, bound = self.cleanups.pop(-1)
+
+ # If requested, report the action.
+ if self.stream is not None:
+ print >>self.stream, desc
+
+ try:
+ # Call the bound callable
+ bound()
+ except Exception, err:
+ # This is cleanup. Report the problem but continue.
+ print >>(self.stream or sys.stderr), "Calling %s\nraised %s: %s" % \
+ (desc, err.__class__.__name__, err)
+
+ def tearDown(self):
+ """
+ If a unittest.TestCase subclass (or a nose test class) adds Janitor as
+ one of its base classes, and has no other tearDown() logic, let it
+ inherit Janitor.tearDown().
+ """
+ self.cleanup()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, tb):
+ # Perform cleanup no matter how we exit this 'with' statement
+ self.cleanup()
+ # Propagate any exception from the 'with' statement, don't swallow it
+ return False
diff --git a/indra/viewer_components/updater/scripts/darwin/messageframe.py b/indra/viewer_components/updater/scripts/darwin/messageframe.py
new file mode 100644
index 0000000000..8f58848882
--- /dev/null
+++ b/indra/viewer_components/updater/scripts/darwin/messageframe.py
@@ -0,0 +1,66 @@
+#!/usr/bin/python
+"""\
+@file messageframe.py
+@author Nat Goodspeed
+@date 2013-01-03
+@brief Define MessageFrame class for popping up messages from a command-line
+ script.
+
+$LicenseInfo:firstyear=2013&license=viewerlgpl$
+Copyright (c) 2013, Linden Research, Inc.
+$/LicenseInfo$
+"""
+
+import Tkinter as tk
+import os
+
+# Tricky way to obtain the filename of the main script (default title string)
+import __main__
+
+# This class is intended for displaying messages from a command-line script.
+# Getting the base class right took a bit of trial and error.
+# If you derive from tk.Frame, the destroy() method doesn't actually close it.
+# If you derive from tk.Toplevel, it pops up a separate Tk frame too. destroy()
+# closes this frame, but not that one.
+# Deriving from tk.Tk appears to do the right thing.
+class MessageFrame(tk.Tk):
+ def __init__(self, text="", title=os.path.splitext(os.path.basename(__main__.__file__))[0],
+ width=320, height=120):
+ tk.Tk.__init__(self)
+ self.grid()
+ self.title(title)
+ self.var = tk.StringVar()
+ self.var.set(text)
+ self.msg = tk.Label(self, textvariable=self.var)
+ self.msg.grid()
+ # from http://stackoverflow.com/questions/3352918/how-to-center-a-window-on-the-screen-in-tkinter :
+ self.update_idletasks()
+
+ # The constants below are to adjust for typical overhead from the
+ # frame borders.
+ xp = (self.winfo_screenwidth() / 2) - (width / 2) - 8
+ yp = (self.winfo_screenheight() / 2) - (height / 2) - 20
+ self.geometry('{0}x{1}+{2}+{3}'.format(width, height, xp, yp))
+ self.update()
+
+ def set(self, text):
+ self.var.set(text)
+ self.update()
+
+if __name__ == "__main__":
+ # When run as a script, just test the MessageFrame.
+ import sys
+ import time
+
+ frame = MessageFrame("something in the way she moves....")
+ time.sleep(3)
+ frame.set("smaller")
+ time.sleep(3)
+ frame.set("""this has
+several
+lines""")
+ time.sleep(3)
+ frame.destroy()
+ print "Destroyed!"
+ sys.stdout.flush()
+ time.sleep(3)
diff --git a/indra/viewer_components/updater/scripts/darwin/update_install b/indra/viewer_components/updater/scripts/darwin/update_install
deleted file mode 100644
index e7f36dc5a3..0000000000
--- a/indra/viewer_components/updater/scripts/darwin/update_install
+++ /dev/null
@@ -1,10 +0,0 @@
-#! /bin/bash
-
-#
-# The first argument contains the path to the installer app. The second a path
-# to a marker file which should be created if the installer fails.q
-#
-
-cd "$(dirname "$0")"
-(../Resources/mac-updater.app/Contents/MacOS/mac-updater -dmg "$1" -name "Second Life Viewer"; if [ $? -ne 0 ]; then echo $3 >> "$2"; fi;) &
-exit 0
diff --git a/indra/viewer_components/updater/scripts/darwin/update_install.py b/indra/viewer_components/updater/scripts/darwin/update_install.py
new file mode 100755
index 0000000000..2fc6fcdb29
--- /dev/null
+++ b/indra/viewer_components/updater/scripts/darwin/update_install.py
@@ -0,0 +1,373 @@
+#!/usr/bin/python
+"""\
+@file update_install.py
+@author Nat Goodspeed
+@date 2012-12-20
+@brief Update the containing Second Life application bundle to the version in
+ the specified disk image file.
+
+ This Python implementation is derived from the previous mac-updater
+ application, a funky mix of C++, classic C and Objective-C.
+
+$LicenseInfo:firstyear=2012&license=viewerlgpl$
+Copyright (c) 2012, Linden Research, Inc.
+$/LicenseInfo$
+"""
+
+import os
+import sys
+import cgitb
+import errno
+import glob
+import plistlib
+import re
+import shutil
+import subprocess
+import tempfile
+import time
+from janitor import Janitor
+from messageframe import MessageFrame
+import Tkinter, tkMessageBox
+
+TITLE = "Second Life Viewer Updater"
+# Magic bundle identifier used by all Second Life viewer bundles
+BUNDLE_IDENTIFIER = "com.secondlife.indra.viewer"
+
+# Global handle to the MessageFrame so we can update message
+FRAME = None
+# Global handle to logfile, once it's open
+LOGF = None
+
+# ****************************************************************************
+# Logging and messaging
+#
+# This script is normally run implicitly by the old viewer to update to the
+# new viewer. Its UI consists of a MessageFrame and possibly a Tk error box.
+# Log details to updater.log -- especially uncaught exceptions!
+# ****************************************************************************
+def log(message):
+ """write message only to LOGF (also called by status() and fail())"""
+ # If we don't even have LOGF open yet, at least write to Console log
+ logf = LOGF or sys.stderr
+ logf.writelines((time.strftime("%Y-%m-%dT%H:%M:%SZ ", time.gmtime()), message, '\n'))
+ logf.flush()
+
+def status(message):
+ """display and log normal progress message"""
+ log(message)
+
+ global FRAME
+ if not FRAME:
+ FRAME = MessageFrame(message, TITLE)
+ else:
+ FRAME.set(message)
+
+def fail(message):
+ """log message, produce error box, then terminate with nonzero rc"""
+ log(message)
+
+ # If we haven't yet called status() (we don't yet have a FRAME), perform a
+ # bit of trickery to bypass the spurious "main window" that Tkinter would
+ # otherwise pop up if the first call is showerror().
+ if not FRAME:
+ root = Tkinter.Tk()
+ root.withdraw()
+
+ # If we do have a LOGF available, mention it in the error box.
+ if LOGF:
+ message = "%s\n(Updater log in %s)" % (message, LOGF.name)
+
+ # We explicitly specify the WARNING icon because, at least on the Tkinter
+ # bundled with the system-default Python 2.7 on Mac OS X 10.7.4, the
+ # ERROR, QUESTION and INFO icons are all the silly Tk rocket ship. At
+ # least WARNING has an exclamation in a yellow triangle, even though
+ # overlaid by a smaller image of the rocket ship.
+ tkMessageBox.showerror(TITLE,
+"""An error occurred while updating Second Life:
+%s
+Please download the latest viewer from www.secondlife.com.""" % message,
+ icon=tkMessageBox.WARNING)
+ sys.exit(1)
+
+def exception(err):
+ """call fail() with an exception instance"""
+ fail("%s exception: %s" % (err.__class__.__name__, str(err)))
+
+def excepthook(type, value, traceback):
+ """
+ Store this hook function into sys.excepthook until we have a logfile.
+ """
+ # At least in older Python versions, it could be tricky to produce a
+ # string from 'type' and 'value'. For instance, an OSError exception would
+ # pass type=OSError and value=some_tuple. Empirically, this funky
+ # expression seems to work.
+ exception(type(*value))
+sys.excepthook = excepthook
+
+class ExceptHook(object):
+ """
+ Store an instance of this class into sys.excepthook once we have a logfile
+ open.
+ """
+ def __init__(self, logfile):
+ # There's no magic to the cgitb.enable() function -- it merely stores
+ # an instance of cgitb.Hook into sys.excepthook, passing enable()'s
+ # params into Hook.__init__(). Sadly, enable() doesn't forward all its
+ # params using (*args, **kwds) syntax -- another story. But the point
+ # is that all the goodness is in the cgitb.Hook class. Capture an
+ # instance.
+ self.hook = cgitb.Hook(file=logfile, format="text")
+
+ def __call__(self, type, value, traceback):
+ # produce nice text traceback to logfile
+ self.hook(type, value, traceback)
+ # Now display an error box.
+ excepthook(type, value, traceback)
+
+def write_marker(markerfile, markertext):
+ log("writing %r to %s" % (markertext, markerfile))
+ try:
+ with open(markerfile, "w") as markerf:
+ markerf.write(markertext)
+ except IOError, err:
+ # write_marker() is invoked by fail(), and fail() is invoked by other
+ # error-handling functions. If we try to invoke any of those, we'll
+ # get infinite recursion. If for any reason we can't write markerfile,
+ # try to log it -- otherwise shrug.
+ log("%s exception: %s" % (err.__class__.__name__, err))
+
+# ****************************************************************************
+# Main script logic
+# ****************************************************************************
+def main(dmgfile, markerfile, markertext):
+ # Should we fail, we're supposed to write 'markertext' to 'markerfile'.
+ # Wrap the fail() function so we do that.
+ global fail
+ oldfail = fail
+ def fail(message):
+ write_marker(markerfile, markertext)
+ oldfail(message)
+
+ try:
+ # Starting with the Cocoafied viewer, we'll find viewer logs in
+ # ~/Library/Application Support/$CFBundleIdentifier/logs rather than in
+ # ~/Library/Application Support/SecondLife/logs as before. This could be
+ # obnoxious -- but we Happen To Know that markerfile is a path specified
+ # within the viewer's logs directory. Use that.
+ logsdir = os.path.dirname(markerfile)
+
+ # Move the old updater.log file out of the way
+ logname = os.path.join(logsdir, "updater.log")
+ try:
+ os.rename(logname, logname + ".old")
+ except OSError, err:
+ # Nonexistence is okay. Anything else, not so much.
+ if err.errno != errno.ENOENT:
+ raise
+
+ # Open new updater.log.
+ global LOGF
+ LOGF = open(logname, "w")
+
+ # Now that LOGF is in fact open for business, use it to log any further
+ # uncaught exceptions.
+ sys.excepthook = ExceptHook(LOGF)
+
+ # log how this script was invoked
+ log(' '.join(repr(arg) for arg in sys.argv))
+
+ # prepare for other cleanup
+ with Janitor(LOGF) as janitor:
+
+ # Try to derive the name of the running viewer app bundle from our
+ # own pathname. (Hopefully the old viewer won't copy this script
+ # to a temp dir before running!)
+ # Somewhat peculiarly, this script is currently packaged in
+ # Appname.app/Contents/MacOS with the viewer executable. But even
+ # if we decide to move it to Appname.app/Contents/Resources, we'll
+ # still find Appname.app two levels up from dirname(__file__).
+ appdir = os.path.abspath(os.path.join(os.path.dirname(__file__),
+ os.pardir, os.pardir))
+ if not appdir.endswith(".app"):
+ # This can happen if either this script has been copied before
+ # being executed, or if it's in an unexpected place in the app
+ # bundle.
+ fail(appdir + " is not an application directory")
+
+ # We need to install into appdir's parent directory -- can we?
+ installdir = os.path.abspath(os.path.join(appdir, os.pardir))
+ if not os.access(installdir, os.W_OK):
+ fail("Can't modify " + installdir)
+
+ # invent a temporary directory
+ tempdir = tempfile.mkdtemp()
+ log("created " + tempdir)
+ # clean it up when we leave
+ janitor.later(shutil.rmtree, tempdir)
+
+ status("Mounting image...")
+
+ mntdir = os.path.join(tempdir, "mnt")
+ log("mkdir " + mntdir)
+ os.mkdir(mntdir)
+ command = ["hdiutil", "attach", dmgfile, "-mountpoint", mntdir]
+ log(' '.join(command))
+ # Instantiating subprocess.Popen launches a child process with the
+ # specified command line. stdout=PIPE passes a pipe to its stdout.
+ hdiutil = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=LOGF)
+ # Popen.communicate() reads that pipe until the child process
+ # terminates, returning (stdout, stderr) output. Select just stdout.
+ hdiutil_out = hdiutil.communicate()[0]
+ if hdiutil.returncode != 0:
+ fail("Couldn't mount " + dmgfile)
+ # hdiutil should report the devnode. Find that.
+ found = re.search(r"/dev/[^ ]*\b", hdiutil_out)
+ if not found:
+ # If we don't spot the devnode, log it and continue -- we only
+ # use it to detach it. Don't fail the whole update if we can't
+ # clean up properly.
+ log("Couldn't spot devnode in hdiutil output:\n" + hdiutil_out)
+ else:
+ # If we do spot the devnode, detach it when done.
+ janitor.later(subprocess.call, ["hdiutil", "detach", found.group(0)],
+ stdout=LOGF, stderr=subprocess.STDOUT)
+
+ status("Searching for app bundle...")
+
+ for candidate in glob.glob(os.path.join(mntdir, "*.app")):
+ log("Considering " + candidate)
+ try:
+ # By convention, a valid Mac app bundle has a
+ # Contents/Info.plist file containing at least
+ # CFBundleIdentifier.
+ CFBundleIdentifier = \
+ plistlib.readPlist(os.path.join(candidate, "Contents",
+ "Info.plist"))["CFBundleIdentifier"]
+ except Exception, err:
+ # might be IOError, xml.parsers.expat.ExpatError, KeyError
+ # Any of these means it's not a valid app bundle. Instead
+ # of aborting, just skip this candidate and continue.
+ log("%s not a valid app bundle: %s: %s" %
+ (candidate, err.__class__.__name__, err))
+ continue
+
+ if CFBundleIdentifier == BUNDLE_IDENTIFIER:
+ break
+
+ log("unrecognized CFBundleIdentifier: " + CFBundleIdentifier)
+
+ else:
+ fail("Could not find Second Life viewer in " + dmgfile)
+
+ # Here 'candidate' is the new viewer to install
+ log("Found " + candidate)
+
+ # This logic was changed to make Mac updates behave more like
+ # Windows. Most of the time, the user doesn't change the name of
+ # the app bundle on our .dmg installer (e.g. "Second Life Beta
+ # Viewer.app"). Most of the time, the version manager directs a
+ # given viewer to update to another .dmg containing an app bundle
+ # with THE SAME name. In that case, everything behaves as usual.
+
+ # The case that was changed is when the version manager offers (or
+ # mandates) an update to a .dmg containing a different app bundle
+ # name. This can happen, for instance, to a user who's downloaded
+ # a "project beta" viewer, and the project subsequently publishes
+ # a Release Candidate viewer. Say the project beta's app bundle
+ # name is something like "Second Life Beta Neato.app". Anyone
+ # launching that viewer will be offered an update to the
+ # corresponding Release Candidate viewer -- which will be built as
+ # a release viewer, with app bundle name "Second Life Viewer.app".
+
+ # On Windows, we run the NSIS installer, which will update/replace
+ # the embedded install directory name, e.g. Second Life Viewer.
+ # But the Mac installer used to locate the app bundle name in the
+ # mounted .dmg file, then ignore that name, copying its contents
+ # into the app bundle directory of the running viewer. That is,
+ # we'd install the Release Candidate from the .dmg's "Second
+ # Life.app" into "/Applications/Second Life Beta Neato.app". This
+ # is undesired behavior.
+
+ # Instead, having found the app bundle name on the mounted .dmg,
+ # we try to install that app bundle name into the parent directory
+ # of the running app bundle.
+
+ # Are we installing a different app bundle name? If so, call it
+ # out, both in the log and for the user -- this is an odd case.
+ # (Presumably they've already agreed to a similar notification in
+ # the viewer before the viewer launched this script, but still.)
+ bundlename = os.path.basename(candidate)
+ if os.path.basename(appdir) == bundlename:
+ # updating the running app bundle, which we KNOW exists
+ appexists = True
+ else:
+ # installing some other app bundle
+ newapp = os.path.join(installdir, bundlename)
+ appexists = os.path.exists(newapp)
+ message = "Note: %s %s %s" % \
+ (appdir, "updating" if appexists else "installing new", newapp)
+ status(message)
+ # okay, we have no further need of the name of the running app
+ # bundle.
+ appdir = newapp
+
+ status("Preparing to copy files...")
+
+ if appexists:
+ # move old viewer to temp location in case copy from .dmg fails
+ aside = os.path.join(tempdir, os.path.basename(appdir))
+ log("mv %r %r" % (appdir, aside))
+ # Use shutil.move() instead of os.rename(). move() first tries
+ # os.rename(), but falls back to shutil.copytree() if the dest is
+ # on a different filesystem.
+ shutil.move(appdir, aside)
+
+ status("Copying files...")
+
+ # shutil.copytree()'s target must not already exist. But we just
+ # moved appdir out of the way.
+ log("cp -p %r %r" % (candidate, appdir))
+ try:
+ # The viewer app bundle does include internal symlinks. Keep them
+ # as symlinks.
+ shutil.copytree(candidate, appdir, symlinks=True)
+ except Exception, err:
+ # copy failed -- try to restore previous viewer before crumping
+ type, value, traceback = sys.exc_info()
+ if appexists:
+ log("exception response: mv %r %r" % (aside, appdir))
+ shutil.move(aside, appdir)
+ # let our previously-set sys.excepthook handle this
+ raise type, value, traceback
+
+ status("Cleaning up...")
+
+ log("touch " + appdir)
+ os.utime(appdir, None) # set to current time
+
+ command = ["open", appdir]
+ log(' '.join(command))
+ subprocess.check_call(command, stdout=LOGF, stderr=subprocess.STDOUT)
+
+ except Exception, err:
+ # Because we carefully set sys.excepthook -- and even modify it to log
+ # the problem once we have our log file open -- you might think we
+ # could just let exceptions propagate. But when we do that, on
+ # exception in this block, we FIRST restore the no-side-effects fail()
+ # and THEN implicitly call sys.excepthook(), which calls the (no-side-
+ # effects) fail(). Explicitly call sys.excepthook() BEFORE restoring
+ # fail(). Only then do we get the enriched fail() behavior.
+ sys.excepthook(*sys.exc_info())
+
+ finally:
+ # When we leave main() -- for whatever reason -- reset fail() the way
+ # it was before, because the bound markerfile, markertext params
+ # passed to this main() call are no longer applicable.
+ fail = oldfail
+
+if __name__ == "__main__":
+ # We expect this script to be invoked with:
+ # - the pathname to the .dmg we intend to install;
+ # - the pathname to an update-error marker file to create on failure;
+ # - the content to write into the marker file.
+ main(*sys.argv[1:])