summaryrefslogtreecommitdiff
path: root/indra/viewer_components/updater
diff options
context:
space:
mode:
authorOz Linden <oz@lindenlab.com>2013-11-19 17:59:55 -0500
committerOz Linden <oz@lindenlab.com>2013-11-19 17:59:55 -0500
commit0031e9a97be1bf6e9fe773c23506494d09ce91ae (patch)
tree220f195c82174b7cc8e94dceb2553e59fe5837a5 /indra/viewer_components/updater
parentb7edc965bc77ab21e9a1e3f6b424299a50053529 (diff)
parentebc9bcbf69f7a519677a6522979a6bf6cbb04bb8 (diff)
merge up to 3.6.10-release; some of the storm-68 changes lost
Diffstat (limited to 'indra/viewer_components/updater')
-rwxr-xr-x[-rw-r--r--]indra/viewer_components/updater/CMakeLists.txt10
-rwxr-xr-x[-rw-r--r--]indra/viewer_components/updater/llupdatechecker.cpp162
-rwxr-xr-x[-rw-r--r--]indra/viewer_components/updater/llupdatechecker.h75
-rwxr-xr-x[-rw-r--r--]indra/viewer_components/updater/llupdatedownloader.cpp231
-rwxr-xr-x[-rw-r--r--]indra/viewer_components/updater/llupdatedownloader.h2
-rwxr-xr-x[-rw-r--r--]indra/viewer_components/updater/llupdateinstaller.cpp22
-rwxr-xr-x[-rw-r--r--]indra/viewer_components/updater/llupdateinstaller.h0
-rwxr-xr-x[-rw-r--r--]indra/viewer_components/updater/llupdaterservice.cpp223
-rwxr-xr-x[-rw-r--r--]indra/viewer_components/updater/llupdaterservice.h13
-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.py400
-rwxr-xr-x[-rw-r--r--]indra/viewer_components/updater/scripts/linux/update_install222
-rwxr-xr-x[-rw-r--r--]indra/viewer_components/updater/tests/llupdaterservice_test.cpp44
15 files changed, 1314 insertions, 299 deletions
diff --git a/indra/viewer_components/updater/CMakeLists.txt b/indra/viewer_components/updater/CMakeLists.txt
index ef82290b47..61fd4220e0 100644..100755
--- a/indra/viewer_components/updater/CMakeLists.txt
+++ b/indra/viewer_components/updater/CMakeLists.txt
@@ -19,6 +19,10 @@ include_directories(
${LLPLUGIN_INCLUDE_DIRS}
${LLVFS_INCLUDE_DIRS}
${CURL_INCLUDE_DIRS}
+ ${CMAKE_SOURCE_DIR}/newview
+ )
+include_directories(SYSTEM
+ ${LLCOMMON_SYSTEM_INCLUDE_DIRS}
)
set(updater_service_SOURCE_FILES
@@ -38,6 +42,12 @@ set(updater_service_HEADER_FILES
set_source_files_properties(${updater_service_HEADER_FILES}
PROPERTIES HEADER_FILE_ONLY TRUE)
+set_source_files_properties(
+ llupdaterservice.cpp
+ PROPERTIES
+ COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}" # see BuildVersion.cmake
+ )
+
list(APPEND
updater_service_SOURCE_FILES
${updater_service_HEADER_FILES}
diff --git a/indra/viewer_components/updater/llupdatechecker.cpp b/indra/viewer_components/updater/llupdatechecker.cpp
index c6aa9b0f11..dc8ff2f644 100644..100755
--- a/indra/viewer_components/updater/llupdatechecker.cpp
+++ b/indra/viewer_components/updater/llupdatechecker.cpp
@@ -30,7 +30,9 @@
#include "llsd.h"
#include "llupdatechecker.h"
#include "lluri.h"
-
+#if LL_DARWIN
+#include <CoreServices/CoreServices.h>
+#endif
#if LL_WINDOWS
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
@@ -49,37 +51,6 @@ public:
};
-class LLUpdateChecker::Implementation:
- public LLHTTPClient::Responder
-{
-public:
- Implementation(Client & client);
- ~Implementation();
- void check(std::string const & protocolVersion, std::string const & hostUrl,
- std::string const & servicePath, std::string channel, std::string version);
-
- // Responder:
- virtual void completed(U32 status,
- const std::string & reason,
- const LLSD& content);
- virtual void error(U32 status, const std::string & reason);
-
-private:
- static const char * sProtocolVersion;
-
- Client & mClient;
- LLHTTPClient mHttpClient;
- bool mInProgress;
- std::string mVersion;
-
- std::string buildUrl(std::string const & protocolVersion, std::string const & hostUrl,
- std::string const & servicePath, std::string channel, std::string version);
-
- LOG_CLASS(LLUpdateChecker::Implementation);
-};
-
-
-
// LLUpdateChecker
//-----------------------------------------------------------------------------
@@ -91,10 +62,15 @@ LLUpdateChecker::LLUpdateChecker(LLUpdateChecker::Client & client):
}
-void LLUpdateChecker::check(std::string const & protocolVersion, std::string const & hostUrl,
- std::string const & servicePath, std::string channel, std::string version)
+void LLUpdateChecker::checkVersion(std::string const & urlBase,
+ std::string const & channel,
+ std::string const & version,
+ std::string const & platform,
+ std::string const & platform_version,
+ unsigned char uniqueid[MD5HEX_STR_SIZE],
+ bool willing_to_test)
{
- mImplementation->check(protocolVersion, hostUrl, servicePath, channel, version);
+ mImplementation->checkVersion(urlBase, channel, version, platform, platform_version, uniqueid, willing_to_test);
}
@@ -103,12 +79,13 @@ void LLUpdateChecker::check(std::string const & protocolVersion, std::string con
//-----------------------------------------------------------------------------
-const char * LLUpdateChecker::Implementation::sProtocolVersion = "v1.0";
+const char * LLUpdateChecker::Implementation::sProtocolVersion = "v1.1";
LLUpdateChecker::Implementation::Implementation(LLUpdateChecker::Client & client):
mClient(client),
- mInProgress(false)
+ mInProgress(false),
+ mProtocol(sProtocolVersion)
{
; // No op.
}
@@ -120,47 +97,67 @@ LLUpdateChecker::Implementation::~Implementation()
}
-void LLUpdateChecker::Implementation::check(std::string const & protocolVersion, std::string const & hostUrl,
- std::string const & servicePath, std::string channel, std::string version)
+void LLUpdateChecker::Implementation::checkVersion(std::string const & urlBase,
+ std::string const & channel,
+ std::string const & version,
+ std::string const & platform,
+ std::string const & platform_version,
+ unsigned char uniqueid[MD5HEX_STR_SIZE],
+ bool willing_to_test)
{
- llassert(!mInProgress);
+ if (!mInProgress)
+ {
+ mInProgress = true;
+
+ mUrlBase = urlBase;
+ mChannel = channel;
+ mVersion = version;
+ mPlatform = platform;
+ mPlatformVersion = platform_version;
+ memcpy(mUniqueId, uniqueid, MD5HEX_STR_SIZE);
+ mWillingToTest = willing_to_test;
- if(protocolVersion != sProtocolVersion) throw CheckError("unsupported protocol");
-
- mInProgress = true;
- mVersion = version;
- std::string checkUrl = buildUrl(protocolVersion, hostUrl, servicePath, channel, version);
- LL_INFOS("UpdateCheck") << "checking for updates at " << checkUrl << llendl;
+ mProtocol = sProtocolVersion;
+
+ std::string checkUrl = buildUrl(urlBase, channel, version, platform, platform_version, uniqueid, willing_to_test);
+ LL_INFOS("UpdaterService") << "checking for updates at " << checkUrl << LL_ENDL;
- // The HTTP client will wrap a raw pointer in a boost::intrusive_ptr causing the
- // passed object to be silently and automatically deleted. We pass a self-
- // referential intrusive pointer to which we add a reference to keep the
- // client from deleting the update checker implementation instance.
- LLHTTPClient::ResponderPtr temporaryPtr(this);
- boost::intrusive_ptr_add_ref(temporaryPtr.get());
- mHttpClient.get(checkUrl, temporaryPtr);
+ mHttpClient.get(checkUrl, this);
+ }
+ else
+ {
+ LL_WARNS("UpdaterService") << "attempting to restart a check when one is in progress; ignored" << LL_ENDL;
+ }
}
void LLUpdateChecker::Implementation::completed(U32 status,
- const std::string & reason,
- const LLSD & content)
+ const std::string & reason,
+ const LLSD & content)
{
mInProgress = false;
- if(status != 200) {
- LL_WARNS("UpdateCheck") << "html error " << status << " (" << reason << ")" << llendl;
+ if(status != 200)
+ {
+ std::string server_error;
+ if ( content.has("error_code") )
+ {
+ server_error += content["error_code"].asString();
+ }
+ if ( content.has("error_text") )
+ {
+ server_error += server_error.empty() ? "" : ": ";
+ server_error += content["error_text"].asString();
+ }
+
+ LL_WARNS("UpdaterService") << "response error " << status
+ << " " << reason
+ << " (" << server_error << ")"
+ << LL_ENDL;
mClient.error(reason);
- } else if(!content.asBoolean()) {
- LL_INFOS("UpdateCheck") << "up to date" << llendl;
- mClient.upToDate();
- } else if(content["required"].asBoolean()) {
- LL_INFOS("UpdateCheck") << "version invalid" << llendl;
- LLURI uri(content["url"].asString());
- mClient.requiredUpdate(content["version"].asString(), uri, content["hash"].asString());
- } else {
- LL_INFOS("UpdateCheck") << "newer version " << content["version"].asString() << " available" << llendl;
- LLURI uri(content["url"].asString());
- mClient.optionalUpdate(content["version"].asString(), uri, content["hash"].asString());
+ }
+ else
+ {
+ mClient.response(content);
}
}
@@ -168,27 +165,26 @@ void LLUpdateChecker::Implementation::completed(U32 status,
void LLUpdateChecker::Implementation::error(U32 status, const std::string & reason)
{
mInProgress = false;
- LL_WARNS("UpdateCheck") << "update check failed; " << reason << llendl;
+ LL_WARNS("UpdaterService") << "update check failed; " << reason << LL_ENDL;
mClient.error(reason);
}
-std::string LLUpdateChecker::Implementation::buildUrl(std::string const & protocolVersion, std::string const & hostUrl,
- std::string const & servicePath, std::string channel, std::string version)
-{
-#ifdef LL_WINDOWS
- static const char * platform = "win";
-#elif LL_DARWIN
- static const char * platform = "mac";
-#else
- static const char * platform = "lnx";
-#endif
-
+std::string LLUpdateChecker::Implementation::buildUrl(std::string const & urlBase,
+ std::string const & channel,
+ std::string const & version,
+ std::string const & platform,
+ std::string const & platform_version,
+ unsigned char uniqueid[MD5HEX_STR_SIZE],
+ bool willing_to_test)
+{
LLSD path;
- path.append(servicePath);
- path.append(protocolVersion);
+ path.append(mProtocol);
path.append(channel);
path.append(version);
path.append(platform);
- return LLURI::buildHTTP(hostUrl, path).asString();
+ path.append(platform_version);
+ path.append(willing_to_test ? "testok" : "testno");
+ path.append((char*)uniqueid);
+ return LLURI::buildHTTP(urlBase, path).asString();
}
diff --git a/indra/viewer_components/updater/llupdatechecker.h b/indra/viewer_components/updater/llupdatechecker.h
index cea1f13647..4244007340 100644..100755
--- a/indra/viewer_components/updater/llupdatechecker.h
+++ b/indra/viewer_components/updater/llupdatechecker.h
@@ -29,6 +29,8 @@
#include <boost/shared_ptr.hpp>
+#include "llmd5.h"
+#include "llhttpclient.h"
//
// Implements asynchronous checking for updates.
@@ -36,7 +38,53 @@
class LLUpdateChecker {
public:
class Client;
- class Implementation;
+ class Implementation: public LLHTTPClient::Responder
+ {
+ public:
+ Implementation(Client & client);
+ ~Implementation();
+ void checkVersion(std::string const & urlBase,
+ std::string const & channel,
+ std::string const & version,
+ std::string const & platform,
+ std::string const & platform_version,
+ unsigned char uniqueid[MD5HEX_STR_SIZE],
+ bool willing_to_test
+ );
+
+ // Responder:
+ virtual void completed(U32 status,
+ const std::string & reason,
+ const LLSD& content);
+ virtual void error(U32 status, const std::string & reason);
+
+ private:
+ static const char * sLegacyProtocolVersion;
+ static const char * sProtocolVersion;
+ const char* mProtocol;
+
+ Client & mClient;
+ LLHTTPClient mHttpClient;
+ bool mInProgress;
+ std::string mVersion;
+ std::string mUrlBase;
+ std::string mChannel;
+ std::string mPlatform;
+ std::string mPlatformVersion;
+ unsigned char mUniqueId[MD5HEX_STR_SIZE];
+ bool mWillingToTest;
+
+ std::string buildUrl(std::string const & urlBase,
+ std::string const & channel,
+ std::string const & version,
+ std::string const & platform,
+ std::string const & platform_version,
+ unsigned char uniqueid[MD5HEX_STR_SIZE],
+ bool willing_to_test);
+
+ LOG_CLASS(LLUpdateChecker::Implementation);
+ };
+
// An exception that may be raised on check errors.
class CheckError;
@@ -44,11 +92,16 @@ public:
LLUpdateChecker(Client & client);
// Check status of current app on the given host for the channel and version provided.
- void check(std::string const & protocolVersion, std::string const & hostUrl,
- std::string const & servicePath, std::string channel, std::string version);
+ void checkVersion(std::string const & urlBase,
+ std::string const & channel,
+ std::string const & version,
+ std::string const & platform,
+ std::string const & platform_version,
+ unsigned char uniqueid[MD5HEX_STR_SIZE],
+ bool willing_to_test);
private:
- boost::shared_ptr<Implementation> mImplementation;
+ LLPointer<Implementation> mImplementation;
};
@@ -64,18 +117,8 @@ public:
// An error occurred while checking for an update.
virtual void error(std::string const & message) = 0;
- // A newer version is available, but the current version may still be used.
- virtual void optionalUpdate(std::string const & newVersion,
- LLURI const & uri,
- std::string const & hash) = 0;
-
- // A newer version is available, and the current version is no longer valid.
- virtual void requiredUpdate(std::string const & newVersion,
- LLURI const & uri,
- std::string const & hash) = 0;
-
- // The checked version is up to date; no newer version exists.
- virtual void upToDate(void) = 0;
+ // A successful response was received from the viewer version manager
+ virtual void response(LLSD const & content) = 0;
};
diff --git a/indra/viewer_components/updater/llupdatedownloader.cpp b/indra/viewer_components/updater/llupdatedownloader.cpp
index 19ac418e9e..c28ad76c77 100644..100755
--- a/indra/viewer_components/updater/llupdatedownloader.cpp
+++ b/indra/viewer_components/updater/llupdatedownloader.cpp
@@ -1,24 +1,24 @@
-/**
+/**
* @file llupdatedownloader.cpp
*
* $LicenseInfo:firstyear=2010&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$
*/
@@ -50,7 +50,9 @@ public:
void cancel(void);
void download(LLURI const & uri,
std::string const & hash,
+ std::string const & updateChannel,
std::string const & updateVersion,
+ std::string const & info_url,
bool required);
bool isDownloading(void);
size_t onHeader(void * header, size_t size);
@@ -58,7 +60,7 @@ public:
int onProgress(double downloadSize, double bytesDownloaded);
void resume(void);
void setBandwidthLimit(U64 bytesPerSecond);
-
+
private:
curl_off_t mBandwidthLimit;
bool mCancelled;
@@ -69,7 +71,7 @@ private:
unsigned char mDownloadPercent;
std::string mDownloadRecordPath;
curl_slist * mHeaderList;
-
+
void initializeCurlGet(std::string const & url, bool processHeader);
void resumeDownloading(size_t startByte);
void run(void);
@@ -93,7 +95,7 @@ namespace {
}
};
-
+
const char * gSecondLifeUpdateRecord = "SecondLifeUpdateDownload.xml";
};
@@ -125,10 +127,12 @@ void LLUpdateDownloader::cancel(void)
void LLUpdateDownloader::download(LLURI const & uri,
std::string const & hash,
+ std::string const & updateChannel,
std::string const & updateVersion,
+ std::string const & info_url,
bool required)
{
- mImplementation->download(uri, hash, updateVersion, required);
+ mImplementation->download(uri, hash, updateChannel, updateVersion, info_url, required);
}
@@ -192,18 +196,18 @@ LLUpdateDownloader::Implementation::Implementation(LLUpdateDownloader::Client &
mHeaderList(0)
{
CURLcode code = curl_global_init(CURL_GLOBAL_ALL); // Just in case.
- llverify(code == CURLE_OK); // TODO: real error handling here.
+ llverify(code == CURLE_OK); // TODO: real error handling here.
}
LLUpdateDownloader::Implementation::~Implementation()
{
- if(isDownloading())
+ if(isDownloading())
{
cancel();
shutdown();
- }
- else
+ }
+ else
{
; // No op.
}
@@ -218,22 +222,32 @@ void LLUpdateDownloader::Implementation::cancel(void)
{
mCancelled = true;
}
-
+
void LLUpdateDownloader::Implementation::download(LLURI const & uri,
std::string const & hash,
+ std::string const & updateChannel,
std::string const & updateVersion,
+ std::string const & info_url,
bool required)
-{
+{
if(isDownloading()) mClient.downloadError("download in progress");
mDownloadRecordPath = downloadMarkerPath();
mDownloadData = LLSD();
mDownloadData["required"] = required;
+ mDownloadData["update_channel"] = updateChannel;
mDownloadData["update_version"] = updateVersion;
- try {
+ if (!info_url.empty())
+ {
+ mDownloadData["info_url"] = info_url;
+ }
+ try
+ {
startDownloading(uri, hash);
- } catch(DownloadError const & e) {
+ }
+ catch(DownloadError const & e)
+ {
mClient.downloadError(e.what());
}
}
@@ -249,47 +263,65 @@ void LLUpdateDownloader::Implementation::resume(void)
{
mCancelled = false;
- if(isDownloading()) {
+ if(isDownloading())
+ {
mClient.downloadError("download in progress");
}
mDownloadRecordPath = downloadMarkerPath();
llifstream dataStream(mDownloadRecordPath);
- if(!dataStream) {
+ if(!dataStream)
+ {
mClient.downloadError("no download marker");
return;
}
-
+
LLSDSerialize::fromXMLDocument(mDownloadData, dataStream);
-
- if(!mDownloadData.asBoolean()) {
+
+ if(!mDownloadData.asBoolean())
+ {
mClient.downloadError("no download information in marker");
return;
}
-
+
std::string filePath = mDownloadData["path"].asString();
- try {
- if(LLFile::isfile(filePath)) {
+ try
+ {
+ if(LLFile::isfile(filePath))
+ {
llstat fileStatus;
LLFile::stat(filePath, &fileStatus);
- if(fileStatus.st_size != mDownloadData["size"].asInteger()) {
+ if(fileStatus.st_size != mDownloadData["size"].asInteger())
+ {
resumeDownloading(fileStatus.st_size);
- } else if(!validateDownload()) {
+ }
+ else if(!validateDownload())
+ {
LLFile::remove(filePath);
- download(LLURI(mDownloadData["url"].asString()),
+ download(LLURI(mDownloadData["url"].asString()),
mDownloadData["hash"].asString(),
+ mDownloadData["update_channel"].asString(),
mDownloadData["update_version"].asString(),
+ mDownloadData["info_url"].asString(),
mDownloadData["required"].asBoolean());
- } else {
+ }
+ else
+ {
mClient.downloadComplete(mDownloadData);
}
- } else {
- download(LLURI(mDownloadData["url"].asString()),
+ }
+ else
+ {
+ download(LLURI(mDownloadData["url"].asString()),
mDownloadData["hash"].asString(),
+ mDownloadData["update_channel"].asString(),
mDownloadData["update_version"].asString(),
+ mDownloadData["info_url"].asString(),
mDownloadData["required"].asBoolean());
}
- } catch(DownloadError & e) {
+ }
+ catch(DownloadError & e)
+ {
mClient.downloadError(e.what());
}
}
@@ -297,13 +329,18 @@ void LLUpdateDownloader::Implementation::resume(void)
void LLUpdateDownloader::Implementation::setBandwidthLimit(U64 bytesPerSecond)
{
- if((mBandwidthLimit != bytesPerSecond) && isDownloading() && !mDownloadData["required"].asBoolean()) {
+ if((mBandwidthLimit != bytesPerSecond) && isDownloading() && !mDownloadData["required"].asBoolean())
+ {
llassert(mCurl != 0);
mBandwidthLimit = bytesPerSecond;
CURLcode code = curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, &mBandwidthLimit);
- if(code != CURLE_OK) LL_WARNS("UpdateDownload") <<
- "unable to change dowload bandwidth" << LL_ENDL;
- } else {
+ if(code != CURLE_OK)
+ {
+ LL_WARNS("UpdaterService") << "unable to change dowload bandwidth" << LL_ENDL;
+ }
+ }
+ else
+ {
mBandwidthLimit = bytesPerSecond;
}
}
@@ -315,26 +352,26 @@ size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)
std::string header(headerPtr, headerPtr + size);
size_t colonPosition = header.find(':');
if(colonPosition == std::string::npos) return size; // HTML response; ignore.
-
+
if(header.substr(0, colonPosition) == "Content-Length") {
try {
size_t firstDigitPos = header.find_first_of("0123456789", colonPosition);
size_t lastDigitPos = header.find_last_of("0123456789");
std::string contentLength = header.substr(firstDigitPos, lastDigitPos - firstDigitPos + 1);
size_t size = boost::lexical_cast<size_t>(contentLength);
- LL_INFOS("UpdateDownload") << "download size is " << size << LL_ENDL;
-
+ LL_INFOS("UpdaterService") << "download size is " << size << LL_ENDL;
+
mDownloadData["size"] = LLSD(LLSD::Integer(size));
llofstream odataStream(mDownloadRecordPath);
LLSDSerialize::toPrettyXML(mDownloadData, odataStream);
} catch (std::exception const & e) {
- LL_WARNS("UpdateDownload") << "unable to read content length ("
+ LL_WARNS("UpdaterService") << "unable to read content length ("
<< e.what() << ")" << LL_ENDL;
}
} else {
; // No op.
}
-
+
return size;
}
@@ -342,9 +379,9 @@ size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)
size_t LLUpdateDownloader::Implementation::onBody(void * buffer, size_t size)
{
if(mCancelled) return 0; // Forces a write error which will halt curl thread.
- if((size == 0) || (buffer == 0)) return 0;
-
- mDownloadStream.write(reinterpret_cast<const char *>(buffer), size);
+ if((size == 0) || (buffer == 0)) return 0;
+
+ mDownloadStream.write(static_cast<const char *>(buffer), size);
if(mDownloadStream.bad()) {
return 0;
} else {
@@ -358,7 +395,7 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b
int downloadPercent = static_cast<int>(100. * (bytesDownloaded / downloadSize));
if(downloadPercent > mDownloadPercent) {
mDownloadPercent = downloadPercent;
-
+
LLSD event;
event["pump"] = LLUpdaterService::pumpName();
LLSD payload;
@@ -367,12 +404,12 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b
payload["bytes_downloaded"] = bytesDownloaded;
event["payload"] = payload;
LLEventPumps::instance().obtain("mainlooprepeater").post(event);
-
- LL_INFOS("UpdateDownload") << "progress event " << payload << LL_ENDL;
+
+ LL_INFOS("UpdaterService") << "progress event " << payload << LL_ENDL;
} else {
; // Keep events to a reasonalbe number.
}
-
+
return 0;
}
@@ -381,29 +418,44 @@ void LLUpdateDownloader::Implementation::run(void)
{
CURLcode code = curl_easy_perform(mCurl);
mDownloadStream.close();
- if(code == CURLE_OK) {
+ if(code == CURLE_OK)
+ {
LLFile::remove(mDownloadRecordPath);
- if(validateDownload()) {
- LL_INFOS("UpdateDownload") << "download successful" << LL_ENDL;
+ if(validateDownload())
+ {
+ LL_INFOS("UpdaterService") << "download successful" << LL_ENDL;
mClient.downloadComplete(mDownloadData);
- } else {
- LL_INFOS("UpdateDownload") << "download failed hash check" << LL_ENDL;
+ }
+ else
+ {
+ LL_INFOS("UpdaterService") << "download failed hash check" << LL_ENDL;
std::string filePath = mDownloadData["path"].asString();
- if(filePath.size() != 0) LLFile::remove(filePath);
+ if(filePath.size() != 0)
+ {
+ LLFile::remove(filePath);
+ }
mClient.downloadError("failed hash check");
}
- } else if(mCancelled && (code == CURLE_WRITE_ERROR)) {
- LL_INFOS("UpdateDownload") << "download canceled by user" << LL_ENDL;
+ }
+ else if(mCancelled && (code == CURLE_WRITE_ERROR))
+ {
+ LL_INFOS("UpdaterService") << "download canceled by user" << LL_ENDL;
// Do not call back client.
- } else {
- LL_WARNS("UpdateDownload") << "download failed with error '" <<
+ }
+ else
+ {
+ LL_WARNS("UpdaterService") << "download failed with error '" <<
curl_easy_strerror(code) << "'" << LL_ENDL;
LLFile::remove(mDownloadRecordPath);
- if(mDownloadData.has("path")) LLFile::remove(mDownloadData["path"].asString());
+ if(mDownloadData.has("path"))
+ {
+ LLFile::remove(mDownloadData["path"].asString());
+ }
mClient.downloadError("curl error");
}
-
- if(mHeaderList) {
+
+ if(mHeaderList)
+ {
curl_slist_free_all(mHeaderList);
mHeaderList = 0;
}
@@ -412,22 +464,25 @@ void LLUpdateDownloader::Implementation::run(void)
void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url, bool processHeader)
{
- if(mCurl == 0)
+ if(mCurl == 0)
{
mCurl = LLCurl::newEasyHandle();
- }
- else
+ }
+ else
{
curl_easy_reset(mCurl);
}
-
- if(mCurl == 0) throw DownloadError("failed to initialize curl");
-
+
+ if(mCurl == 0)
+ {
+ throw DownloadError("failed to initialize curl");
+ }
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, true));
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_FOLLOWLOCATION, true));
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, &write_function));
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, this));
- if(processHeader) {
+ if(processHeader)
+ {
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERFUNCTION, &header_function));
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERDATA, this));
}
@@ -439,26 +494,29 @@ void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & u
// if it's a required update set the bandwidth limit to 0 (unlimited)
curl_off_t limit = mDownloadData["required"].asBoolean() ? 0 : mBandwidthLimit;
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, limit));
-
+
mDownloadPercent = 0;
}
void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte)
{
- LL_INFOS("UpdateDownload") << "resuming download from " << mDownloadData["url"].asString()
+ LL_INFOS("UpdaterService") << "resuming download from " << mDownloadData["url"].asString()
<< " at byte " << startByte << LL_ENDL;
initializeCurlGet(mDownloadData["url"].asString(), false);
-
+
// The header 'Range: bytes n-' will request the bytes remaining in the
// source begining with byte n and ending with the last byte.
boost::format rangeHeaderFormat("Range: bytes=%u-");
rangeHeaderFormat % startByte;
mHeaderList = curl_slist_append(mHeaderList, rangeHeaderFormat.str().c_str());
- if(mHeaderList == 0) throw DownloadError("cannot add Range header");
+ if(mHeaderList == 0)
+ {
+ throw DownloadError("cannot add Range header");
+ }
throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaderList));
-
+
mDownloadStream.open(mDownloadData["path"].asString(),
std::ios_base::out | std::ios_base::binary | std::ios_base::app);
start();
@@ -476,13 +534,13 @@ void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std
std::string filePath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, fileName);
mDownloadData["path"] = filePath;
- LL_INFOS("UpdateDownload") << "downloading " << filePath
+ LL_INFOS("UpdaterService") << "downloading " << filePath
<< " from " << uri.asString() << LL_ENDL;
- LL_INFOS("UpdateDownload") << "hash of file is " << hash << LL_ENDL;
-
+ LL_INFOS("UpdaterService") << "hash of file is " << hash << LL_ENDL;
+
llofstream dataStream(mDownloadRecordPath);
LLSDSerialize::toPrettyXML(mDownloadData, dataStream);
-
+
mDownloadStream.open(filePath, std::ios_base::out | std::ios_base::binary);
initializeCurlGet(uri.asString(), true);
start();
@@ -508,19 +566,26 @@ bool LLUpdateDownloader::Implementation::validateDownload(void)
{
std::string filePath = mDownloadData["path"].asString();
llifstream fileStream(filePath, std::ios_base::in | std::ios_base::binary);
- if(!fileStream) return false;
+ if(!fileStream)
+ {
+ return false;
+ }
std::string hash = mDownloadData["hash"].asString();
- if(hash.size() != 0) {
- LL_INFOS("UpdateDownload") << "checking hash..." << LL_ENDL;
+ if(hash.size() != 0)
+ {
+ LL_INFOS("UpdaterService") << "checking hash..." << LL_ENDL;
char digest[33];
LLMD5(fileStream).hex_digest(digest);
- if(hash != digest) {
- LL_WARNS("UpdateDownload") << "download hash mismatch; expeted " << hash <<
+ if(hash != digest)
+ {
+ LL_WARNS("UpdaterService") << "download hash mismatch; expected " << hash <<
" but download is " << digest << LL_ENDL;
}
return hash == digest;
- } else {
+ }
+ else
+ {
return true; // No hash check provided.
}
}
diff --git a/indra/viewer_components/updater/llupdatedownloader.h b/indra/viewer_components/updater/llupdatedownloader.h
index 0d635640cf..f759988f12 100644..100755
--- a/indra/viewer_components/updater/llupdatedownloader.h
+++ b/indra/viewer_components/updater/llupdatedownloader.h
@@ -54,7 +54,9 @@ public:
// Start a new download.
void download(LLURI const & uri,
std::string const & hash,
+ std::string const & updateChannel,
std::string const & updateVersion,
+ std::string const & info_url,
bool required=false);
// Returns true if a download is in progress.
diff --git a/indra/viewer_components/updater/llupdateinstaller.cpp b/indra/viewer_components/updater/llupdateinstaller.cpp
index c7b70c2de8..a0e2c0b362 100644..100755
--- a/indra/viewer_components/updater/llupdateinstaller.cpp
+++ b/indra/viewer_components/updater/llupdateinstaller.cpp
@@ -26,10 +26,10 @@
#include "linden_common.h"
#include <apr_file_io.h>
#include "llapr.h"
-#include "llprocesslauncher.h"
+#include "llprocess.h"
#include "llupdateinstaller.h"
#include "lldir.h"
-
+#include "llsd.h"
#if defined(LL_WINDOWS)
#pragma warning(disable: 4702) // disable 'unreachable code' so we can use lexical_cast (really!).
@@ -75,18 +75,16 @@ int ll_install_update(std::string const & script,
llassert(!"unpossible copy mode");
}
- llinfos << "UpdateInstaller: installing " << updatePath << " using " <<
+ LL_INFOS("Updater") << "UpdateInstaller: installing " << updatePath << " using " <<
actualScriptPath << LL_ENDL;
- LLProcessLauncher launcher;
- launcher.setExecutable(actualScriptPath);
- launcher.addArgument(updatePath);
- launcher.addArgument(ll_install_failed_marker_path().c_str());
- launcher.addArgument(boost::lexical_cast<std::string>(required));
- int result = launcher.launch();
- launcher.orphan();
-
- return result;
+ LLProcess::Params params;
+ params.executable = actualScriptPath;
+ params.args.add(updatePath);
+ params.args.add(ll_install_failed_marker_path());
+ params.args.add(boost::lexical_cast<std::string>(required));
+ params.autokill = false;
+ return LLProcess::create(params)? 0 : -1;
}
diff --git a/indra/viewer_components/updater/llupdateinstaller.h b/indra/viewer_components/updater/llupdateinstaller.h
index fe5b1d19b5..fe5b1d19b5 100644..100755
--- a/indra/viewer_components/updater/llupdateinstaller.h
+++ b/indra/viewer_components/updater/llupdateinstaller.h
diff --git a/indra/viewer_components/updater/llupdaterservice.cpp b/indra/viewer_components/updater/llupdaterservice.cpp
index 2e18218667..16950e1d62 100644..100755
--- a/indra/viewer_components/updater/llupdaterservice.cpp
+++ b/indra/viewer_components/updater/llupdaterservice.cpp
@@ -32,18 +32,24 @@
#include "lltimer.h"
#include "llupdatechecker.h"
#include "llupdateinstaller.h"
-#include "llversionviewer.h"
#include <boost/scoped_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include "lldir.h"
#include "llsdserialize.h"
#include "llfile.h"
+#include "llviewernetwork.h"
#if LL_WINDOWS
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
#endif
+#if ! defined(LL_VIEWER_VERSION_MAJOR) \
+ || ! defined(LL_VIEWER_VERSION_MINOR) \
+ || ! defined(LL_VIEWER_VERSION_PATCH) \
+ || ! defined(LL_VIEWER_VERSION_BUILD)
+#error "Version information is undefined"
+#endif
namespace
{
@@ -60,6 +66,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
@@ -71,6 +79,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
};
@@ -83,11 +93,13 @@ class LLUpdaterServiceImpl :
{
static const std::string sListenerName;
- std::string mProtocolVersion;
- std::string mUrl;
- std::string mPath;
- std::string mChannel;
- std::string mVersion;
+ std::string mProtocolVersion;
+ std::string mChannel;
+ std::string mVersion;
+ std::string mPlatform;
+ std::string mPlatformVersion;
+ unsigned char mUniqueId[MD5HEX_STR_SIZE];
+ bool mWillingToTest;
unsigned int mCheckPeriod;
bool mIsChecking;
@@ -107,11 +119,13 @@ public:
LLUpdaterServiceImpl();
virtual ~LLUpdaterServiceImpl();
- void initialize(const std::string& protocol_version,
- const std::string& url,
- const std::string& path,
- const std::string& channel,
- const std::string& version);
+ void initialize(const std::string& channel,
+ const std::string& version,
+ const std::string& platform,
+ const std::string& platform_version,
+ const unsigned char uniqueid[MD5HEX_STR_SIZE],
+ const bool& willing_to_test
+ );
void setCheckPeriod(unsigned int seconds);
void setBandwidthLimit(U64 bytesPerSecond);
@@ -129,13 +143,9 @@ public:
// LLUpdateChecker::Client:
virtual void error(std::string const & message);
- virtual void optionalUpdate(std::string const & newVersion,
- LLURI const & uri,
- std::string const & hash);
- virtual void requiredUpdate(std::string const & newVersion,
- LLURI const & uri,
- std::string const & hash);
- virtual void upToDate(void);
+
+ // A successful response was received from the viewer version manager
+ virtual void response(LLSD const & content);
// LLUpdateDownloader::Client
void downloadComplete(LLSD const & data);
@@ -144,6 +154,7 @@ public:
bool onMainLoop(LLSD const & event);
private:
+ std::string mNewChannel;
std::string mNewVersion;
void restartTimer(unsigned int seconds);
@@ -169,11 +180,12 @@ LLUpdaterServiceImpl::~LLUpdaterServiceImpl()
LLEventPumps::instance().obtain("mainloop").stopListening(sListenerName);
}
-void LLUpdaterServiceImpl::initialize(const std::string& protocol_version,
- const std::string& url,
- const std::string& path,
- const std::string& channel,
- const std::string& version)
+void LLUpdaterServiceImpl::initialize(const std::string& channel,
+ const std::string& version,
+ const std::string& platform,
+ const std::string& platform_version,
+ const unsigned char uniqueid[MD5HEX_STR_SIZE],
+ const bool& willing_to_test)
{
if(mIsChecking || mIsDownloading)
{
@@ -181,11 +193,18 @@ void LLUpdaterServiceImpl::initialize(const std::string& protocol_version,
"while updater is running.");
}
- mProtocolVersion = protocol_version;
- mUrl = url;
- mPath = path;
mChannel = channel;
mVersion = version;
+ mPlatform = platform;
+ mPlatformVersion = platform_version;
+ memcpy(mUniqueId, uniqueid, MD5HEX_STR_SIZE);
+ mWillingToTest = willing_to_test;
+ LL_DEBUGS("UpdaterService")
+ << "\n channel: " << mChannel
+ << "\n version: " << mVersion
+ << "\n uniqueid: " << mUniqueId
+ << "\n willing: " << ( mWillingToTest ? "testok" : "testno" )
+ << LL_ENDL;
}
void LLUpdaterServiceImpl::setCheckPeriod(unsigned int seconds)
@@ -200,7 +219,7 @@ void LLUpdaterServiceImpl::setBandwidthLimit(U64 bytesPerSecond)
void LLUpdaterServiceImpl::startChecking(bool install_if_ready)
{
- if(mUrl.empty() || mChannel.empty() || mVersion.empty())
+ if(mChannel.empty() || mVersion.empty())
{
throw LLUpdaterService::UsageError("Set params before call to "
"LLUpdaterService::startCheck().");
@@ -284,7 +303,7 @@ bool LLUpdaterServiceImpl::checkForInstall(bool launchInstaller)
// the update. Do not install this update.
if(!path.asString().empty())
{
- llinfos << "ignoring update dowloaded by different client version" << llendl;
+ LL_INFOS("UpdaterService") << "ignoring update dowloaded by different client version" << LL_ENDL;;
LLFile::remove(path.asString());
LLFile::remove(update_marker_path());
}
@@ -311,9 +330,13 @@ bool LLUpdaterServiceImpl::checkForInstall(bool launchInstaller)
if((result == 0) && mAppExitCallback)
{
mAppExitCallback();
- } else if(result != 0) {
- llwarns << "failed to run update install script" << LL_ENDL;
- } else {
+ }
+ else if(result != 0)
+ {
+ LL_WARNS("UpdaterService") << "failed to run update install script" << LL_ENDL;
+ }
+ else
+ {
; // No op.
}
}
@@ -341,15 +364,19 @@ bool LLUpdaterServiceImpl::checkForResume()
{
mIsDownloading = true;
mNewVersion = download_info["update_version"].asString();
+ mNewChannel = download_info["update_channel"].asString();
mUpdateDownloader.resume();
result = true;
}
else
{
// The viewer that started this download is not the same as this viewer; ignore.
- llinfos << "ignoring partial download from different viewer version" << llendl;
+ LL_INFOS("UpdaterService") << "ignoring partial download from different viewer version" << LL_ENDL;;
std::string path = download_info["path"].asString();
- if(!path.empty()) LLFile::remove(path);
+ if(!path.empty())
+ {
+ LLFile::remove(path);
+ }
LLFile::remove(download_marker_path);
}
}
@@ -366,36 +393,49 @@ void LLUpdaterServiceImpl::error(std::string const & message)
}
}
-void LLUpdaterServiceImpl::optionalUpdate(std::string const & newVersion,
- LLURI const & uri,
- std::string const & hash)
+// A successful response was received from the viewer version manager
+void LLUpdaterServiceImpl::response(LLSD const & content)
{
- stopTimer();
- mNewVersion = newVersion;
- mIsDownloading = true;
- setState(LLUpdaterService::DOWNLOADING);
- mUpdateDownloader.download(uri, hash, newVersion, false);
-}
-
-void LLUpdaterServiceImpl::requiredUpdate(std::string const & newVersion,
- LLURI const & uri,
- std::string const & hash)
-{
- stopTimer();
- mNewVersion = newVersion;
- mIsDownloading = true;
- setState(LLUpdaterService::DOWNLOADING);
- mUpdateDownloader.download(uri, hash, newVersion, true);
-}
-
-void LLUpdaterServiceImpl::upToDate(void)
-{
- if(mIsChecking)
+ if(!content.asBoolean()) // an empty response means "no update"
+ {
+ LL_INFOS("UpdaterService") << "up to date" << LL_ENDL;
+ if(mIsChecking)
+ {
+ restartTimer(mCheckPeriod);
+ }
+
+ setState(LLUpdaterService::UP_TO_DATE);
+ }
+ else if ( content.isMap() && content.has("url") )
+ {
+ // there is an update available...
+ stopTimer();
+ mNewChannel = content["channel"].asString();
+ if (mNewChannel.empty())
+ {
+ LL_INFOS("UpdaterService") << "no channel supplied, assuming current channel" << LL_ENDL;
+ mNewChannel = mChannel;
+ }
+ mNewVersion = content["version"].asString();
+ mIsDownloading = true;
+ setState(LLUpdaterService::DOWNLOADING);
+ BOOL required = content["required"].asBoolean();
+ LLURI url(content["url"].asString());
+ std::string more_info = content["more_info"].asString();
+ LL_DEBUGS("UpdaterService")
+ << "Starting download of "
+ << ( required ? "required" : "optional" ) << " update"
+ << " to channel '" << mNewChannel << "' version " << mNewVersion
+ << " more info '" << more_info << "'"
+ << LL_ENDL;
+ mUpdateDownloader.download(url, content["hash"].asString(), mNewChannel, mNewVersion, more_info, required);
+ }
+ else
{
+ LL_WARNS("UpdaterService") << "Invalid update query response ignored; retry in "
+ << mCheckPeriod << " seconds" << LL_ENDL;
restartTimer(mCheckPeriod);
}
-
- setState(LLUpdaterService::UP_TO_DATE);
}
void LLUpdaterServiceImpl::downloadComplete(LLSD const & data)
@@ -413,9 +453,19 @@ void LLUpdaterServiceImpl::downloadComplete(LLSD const & data)
payload["type"] = LLSD(LLUpdaterService::DOWNLOAD_COMPLETE);
payload["required"] = data["required"];
payload["version"] = mNewVersion;
+ payload["channel"] = mNewChannel;
+ payload["info_url"] = data["info_url"];
event["payload"] = payload;
+ LL_DEBUGS("UpdaterService")
+ << "Download complete "
+ << ( data["required"].asBoolean() ? "required" : "optional" )
+ << " channel " << mNewChannel
+ << " version " << mNewVersion
+ << " info " << data["info_url"].asString()
+ << LL_ENDL;
+
LLEventPumps::instance().obtain("mainlooprepeater").post(event);
-
+
setState(LLUpdaterService::TERMINAL);
}
@@ -489,15 +539,18 @@ bool LLUpdaterServiceImpl::onMainLoop(LLSD const & event)
// Check for failed install.
if(LLFile::isfile(ll_install_failed_marker_path()))
{
+ LL_DEBUGS("UpdaterService") << "found marker " << ll_install_failed_marker_path() << LL_ENDL;;
int requiredValue = 0;
{
llifstream stream(ll_install_failed_marker_path());
stream >> requiredValue;
- if(stream.fail()) requiredValue = 0;
+ if(stream.fail())
+ {
+ requiredValue = 0;
+ }
}
// TODO: notify the user.
- llinfos << "found marker " << ll_install_failed_marker_path() << llendl;
- llinfos << "last install attempt failed" << llendl;
+ LL_WARNS("UpdaterService") << "last install attempt failed" << LL_ENDL;;
LLFile::remove(ll_install_failed_marker_path());
LLSD event;
@@ -509,8 +562,26 @@ bool LLUpdaterServiceImpl::onMainLoop(LLSD const & event)
}
else
{
- mUpdateChecker.check(mProtocolVersion, mUrl, mPath, mChannel, mVersion);
- setState(LLUpdaterService::CHECKING_FOR_UPDATE);
+ std::string query_url = LLGridManager::getInstance()->getUpdateServiceURL();
+ if ( !query_url.empty() )
+ {
+ mUpdateChecker.checkVersion(query_url, mChannel, mVersion,
+ mPlatform, mPlatformVersion, mUniqueId,
+ mWillingToTest);
+ setState(LLUpdaterService::CHECKING_FOR_UPDATE);
+ }
+ else
+ {
+ LL_WARNS("UpdaterService")
+ << "No updater service defined for grid '" << LLGridManager::getInstance()->getGrid()
+ << "' will check again in " << mCheckPeriod << " seconds"
+ << LL_ENDL;
+ // Because the grid can be changed after the viewer is started (when the first check takes place)
+ // but before the user logs in, the next check may be on a different grid, so set the retry timer
+ // even though this check did not happen. The default time is once an hour, and if we're not
+ // doing the check anyway the performance impact is completely insignificant.
+ restartTimer(mCheckPeriod);
+ }
}
}
else
@@ -554,13 +625,15 @@ LLUpdaterService::~LLUpdaterService()
{
}
-void LLUpdaterService::initialize(const std::string& protocol_version,
- const std::string& url,
- const std::string& path,
- const std::string& channel,
- const std::string& version)
+void LLUpdaterService::initialize(const std::string& channel,
+ const std::string& version,
+ const std::string& platform,
+ const std::string& platform_version,
+ const unsigned char uniqueid[MD5HEX_STR_SIZE],
+ const bool& willing_to_test
+)
{
- mImpl->initialize(protocol_version, url, path, channel, version);
+ mImpl->initialize(channel, version, platform, platform_version, uniqueid, willing_to_test);
}
void LLUpdaterService::setCheckPeriod(unsigned int seconds)
@@ -609,10 +682,10 @@ std::string const & ll_get_version(void) {
if (version.empty()) {
std::ostringstream stream;
- stream << LL_VERSION_MAJOR << "."
- << LL_VERSION_MINOR << "."
- << LL_VERSION_PATCH << "."
- << LL_VERSION_BUILD;
+ stream << LL_VIEWER_VERSION_MAJOR << "."
+ << LL_VIEWER_VERSION_MINOR << "."
+ << LL_VIEWER_VERSION_PATCH << "."
+ << LL_VIEWER_VERSION_BUILD;
version = stream.str();
}
diff --git a/indra/viewer_components/updater/llupdaterservice.h b/indra/viewer_components/updater/llupdaterservice.h
index 450f19c1c6..0ddf24935b 100644..100755
--- a/indra/viewer_components/updater/llupdaterservice.h
+++ b/indra/viewer_components/updater/llupdaterservice.h
@@ -28,6 +28,7 @@
#include <boost/shared_ptr.hpp>
#include <boost/function.hpp>
+#include "llhasheduniqueid.h"
class LLUpdaterServiceImpl;
@@ -70,11 +71,13 @@ public:
LLUpdaterService();
~LLUpdaterService();
- void initialize(const std::string& protocol_version,
- const std::string& url,
- const std::string& path,
- const std::string& channel,
- const std::string& version);
+ void initialize(const std::string& channel,
+ const std::string& version,
+ const std::string& platform,
+ const std::string& platform_version,
+ const unsigned char uniqueid[MD5HEX_STR_SIZE],
+ const bool& willing_to_test
+ );
void setCheckPeriod(unsigned int seconds);
void setBandwidthLimit(U64 bytesPerSecond);
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..10d507c9ef
--- /dev/null
+++ b/indra/viewer_components/updater/scripts/darwin/update_install.py
@@ -0,0 +1,400 @@
+#!/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
+from contextlib import contextmanager
+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"
+# Magic OS directory name that causes Cocoa viewer to crash on OS X 10.7.5
+# (see MAINT-3331)
+STATE_DIR = os.path.join(
+ os.environ["HOME"], "Library", "Saved Application State",
+ BUNDLE_IDENTIFIER + ".savedState")
+
+# 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))
+
+# ****************************************************************************
+# Utility
+# ****************************************************************************
+@contextmanager
+def allow_errno(errn):
+ """
+ Execute body of 'with' statement, accepting OSError with specific errno
+ 'errn'. Propagate any other exception, or an OSError with any other errno.
+ """
+ try:
+ # run the body of the 'with' statement
+ yield
+ except OSError, err:
+ # unless errno == passed errn, re-raise the exception
+ if err.errno != errn:
+ raise
+
+# ****************************************************************************
+# 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")
+ # Nonexistence is okay. Anything else, not so much.
+ with allow_errno(errno.ENOENT):
+ os.rename(logname, logname + ".old")
+
+ # 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
+
+ # MAINT-3331: remove STATE_DIR. Empirically, this resolves a
+ # persistent, mysterious crash after updating our viewer on an OS
+ # X 10.7.5 system.
+ log("rm -rf '%s'" % STATE_DIR)
+ with allow_errno(errno.ENOENT):
+ shutil.rmtree(STATE_DIR)
+
+ 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:])
diff --git a/indra/viewer_components/updater/scripts/linux/update_install b/indra/viewer_components/updater/scripts/linux/update_install
index e0505a9f72..03089f192e 100644..100755
--- a/indra/viewer_components/updater/scripts/linux/update_install
+++ b/indra/viewer_components/updater/scripts/linux/update_install
@@ -1,10 +1,220 @@
#! /bin/bash
-INSTALL_DIR=$(cd "$(dirname "$0")/.." ; pwd)
-export LD_LIBRARY_PATH="$INSTALL_DIR/lib"
-bin/linux-updater.bin --file "$1" --dest "$INSTALL_DIR" --name "Second Life Viewer" --stringsdir "$INSTALL_DIR/skins/default/xui/en" --stringsfile "strings.xml"
-if [ $? -ne 0 ]
- then echo $3 >> "$2"
+# @file update_install
+# @author Nat Goodspeed
+# @date 2013-01-09
+# @brief Update the containing Second Life application bundle to the version in
+# the specified tarball.
+#
+# This bash implementation is derived from the previous linux-updater.bin
+# application.
+#
+# $LicenseInfo:firstyear=2013&license=viewerlgpl$
+# Copyright (c) 2013, Linden Research, Inc.
+# $/LicenseInfo$
+
+# ****************************************************************************
+# script parameters
+# ****************************************************************************
+tarball="$1" # the file to install
+markerfile="$2" # create this file on failure
+mandatory="$3" # what to write to markerfile on failure
+
+# ****************************************************************************
+# helper functions
+# ****************************************************************************
+# empty array
+cleanups=()
+
+# add a cleanup action to execute on exit
+function cleanup {
+ # wacky bash syntax for appending to array
+ cleanups[${#cleanups[*]}]="$*"
+}
+
+# called implicitly on exit
+function onexit {
+ for action in "${cleanups[@]}"
+ do # don't quote, support actions consisting of multiple words
+ $action
+ done
+}
+trap 'onexit' EXIT
+
+# write to log file
+function log {
+ # our log file will be open as stderr -- but until we set up that
+ # redirection, logging to stderr is better than nothing
+ echo "$*" 1>&2
+}
+
+# We display status by leaving one background xmessage process running. This
+# is the pid of that process.
+statuspid=""
+
+function clear_message {
+ [ -n "$statuspid" ] && kill $statuspid
+ statuspid=""
+}
+
+# make sure we remove any message box we might have put up
+cleanup clear_message
+
+# can we use zenity, or must we fall back to xmessage?
+zenpath="$(which zenity)"
+if [ -n "$zenpath" ]
+then # zenity on PATH and is executable
+ # display a message box and continue
+ function status {
+ # clear any previous message
+ clear_message
+ # put up a new zenity box and capture its pid
+## "$zenpath" --info --title "Second Life Viewer Updater" \
+## --width=320 --height=120 --text="$*" &
+ # MAINT-2333: use bouncing progress bar
+ "$zenpath" --progress --pulsate --no-cancel --title "Second Life Viewer Updater" \
+ --width=320 --height=120 --text "$*" </dev/null &
+ statuspid=$!
+ }
+
+ # display an error box and wait for user
+ function errorbox {
+ "$zenpath" --error --title "Second Life Viewer Updater" \
+ --width=320 --height=120 --text="$*"
+ }
+
+else # no zenity, use xmessage instead
+ # display a message box and continue
+ function status {
+ # clear any previous message
+ clear_message
+ # put up a new xmessage and capture its pid
+ xmessage -buttons OK:2 -center "$*" &
+ statuspid=$!
+ }
+
+ # display an error box and wait for user
+ function errorbox {
+ xmessage -buttons OK:2 -center "$*"
+ }
+fi
+
+# display an error box and terminate
+function fail {
+ # Log the message
+ log "$@"
+ # tell subsequent viewer things went south
+ echo "$mandatory" > "$markerfile"
+ # add boilerplate
+ errorbox "An error occurred while updating Second Life:
+$*
+Please download the latest viewer from www.secondlife.com."
+ exit 1
+}
+
+# Find a graphical sudo program and define mysudo function. On error, $? is
+# nonzero; output is in $err instead of being written to stdout/stderr.
+gksudo="$(which gksudo)"
+kdesu="$(which kdesu)"
+if [ -n "$gksudo" ]
+then function mysudo {
+ # gksudo allows you to specify description
+ err="$("$gksudo" --description "Second Life Viewer Updater" "$@" 2>&1)"
+ }
+elif [ -n "$kdesu" ]
+then function mysudo {
+ err="$("$kdesu" "$@" 2>&1)"
+ }
+else # couldn't find either one, just try it anyway
+ function mysudo {
+ err="$("$@" 2>&1)"
+ }
fi
-rm -f "$1"
+# Move directories, using mysudo if we think it necessary. On error, $? is
+# nonzero; output is in $err instead of being written to stdout/stderr.
+function sudo_mv {
+ # If we have write permission to both parent directories, shouldn't need
+ # sudo.
+ if [ -w "$(dirname "$1")" -a -w "$(dirname "$2")" ]
+ then err="$(mv "$@" 2>&1)"
+ else # use available sudo program; mysudo sets $? and $err
+ mysudo mv "$@"
+ fi
+}
+
+# ****************************************************************************
+# main script logic
+# ****************************************************************************
+mydir="$(dirname "$0")"
+# We happen to know that the viewer specifies a marker-file pathname within
+# the logs directory.
+logsdir="$(dirname "$markerfile")"
+logname="$logsdir/updater.log"
+
+# move aside old updater.log; we're about to create a new one
+[ -f "$logname" ] && mv "$logname" "$logname.old"
+
+# Set up redirections for this script such that stderr is logged. (But first
+# move the previous stderr to file descriptor 3.)
+exec 3>&2- 2> "$logname"
+
+# Rather than setting up a special pipeline to timestamp every line of stderr,
+# produce header lines into log file indicating timestamp and the arguments
+# with which we were invoked.
+date 1>&2
+log "$0 $*"
+
+# Log every command we execute, along with any stderr it might produce
+set -x
+
+status 'Installing Second Life...'
+
+# Creating tempdir under /tmp means it's possible that tempdir is on a
+# different filesystem than INSTALL_DIR. One is tempted to create tempdir on a
+# path derived from `dirname INSTALL_DIR` -- but it seems modern 'mv' can
+# handle moving across filesystems??
+tempdir="$(mktemp -d)"
+tempinstall="$tempdir/install"
+# capture the actual error message, if any
+err="$(mkdir -p "$tempinstall" 2>&1)" || fail "$err"
+cleanup rm -rf "$tempdir"
+
+# If we already knew the name of the tarball's top-level directory, we could
+# just move that when all was said and done. Since we don't, untarring to the
+# 'install' subdir with --strip 1 effectively renames that top-level
+# directory.
+# untar failures tend to be voluminous -- don't even try to capture, just log
+tar --strip 1 -xjf "$tarball" -C "$tempinstall" || fail "Untar command failed"
+
+INSTALL_DIR="$(cd "$mydir/.." ; pwd)"
+
+# Considering we're launched from a subdirectory of INSTALL_DIR, would be
+# surprising if it did NOT already exist...
+if [ -e "$INSTALL_DIR" ]
+then backup="$INSTALL_DIR.backup"
+ backupn=1
+ while [ -e "$backup" ]
+ do backup="$INSTALL_DIR.backup.$backupn"
+ ((backupn += 1))
+ done
+ # on error, fail with actual error message from sudo_mv: permissions,
+ # cross-filesystem mv, ...?
+ sudo_mv "$INSTALL_DIR" "$backup" || fail "$err"
+fi
+# We unpacked the tarball into tempinstall. Move that.
+if ! sudo_mv "$tempinstall" "$INSTALL_DIR"
+then # If we failed to move the temp install to INSTALL_DIR, try to restore
+ # INSTALL_DIR from backup. Save $err because next sudo_mv will trash it!
+ realerr="$err"
+ sudo_mv "$backup" "$INSTALL_DIR"
+ fail "$realerr"
+fi
+
+# Removing the tarball here, rather than with a 'cleanup' action, means we
+# only remove it if we succeeded.
+rm -f "$tarball"
+
+# Launch the updated viewer. Restore original stderr from file descriptor 3,
+# though -- otherwise updater.log gets cluttered with the viewer log!
+"$INSTALL_DIR/secondlife" 2>&3- &
diff --git a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
index e19d5724f1..759e41ef4c 100644..100755
--- a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
+++ b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
@@ -1,4 +1,4 @@
-/**
+/**
* @file llupdaterservice_test.cpp
* @brief Tests of llupdaterservice.cpp.
*
@@ -44,11 +44,16 @@
*****************************************************************************/
LLUpdateChecker::LLUpdateChecker(LLUpdateChecker::Client & client)
{}
-void LLUpdateChecker::check(std::string const & protocolVersion, std::string const & hostUrl,
- std::string const & servicePath, std::string channel, std::string version)
+void LLUpdateChecker::checkVersion(std::string const & urlBase,
+ std::string const & channel,
+ std::string const & version,
+ std::string const & platform,
+ std::string const & platform_version,
+ unsigned char uniqueid[MD5HEX_STR_SIZE],
+ bool willing_to_test)
{}
LLUpdateDownloader::LLUpdateDownloader(Client & ) {}
-void LLUpdateDownloader::download(LLURI const & , std::string const &, std::string const &, bool){}
+void LLUpdateDownloader::download(LLURI const & , std::string const &, std::string const &, std::string const &, std::string const &, bool){}
class LLDir_Mock : public LLDir
{
@@ -63,7 +68,7 @@ class LLDir_Mock : public LLDir
const std::string &mask,
std::string &fname) {}
std::string getCurPath() { return ""; }
- BOOL fileExists(const std::string &filename) const { return false; }
+ bool fileExists(const std::string &filename) const { return false; }
std::string getLLPluginLauncher() { return ""; }
std::string getLLPluginFilename(std::string base_name) { return ""; }
@@ -78,9 +83,27 @@ S32 LLDir::deleteFilesInDir(const std::string &dirname,
void LLDir::setChatLogsDir(const std::string &path){}
void LLDir::setPerAccountChatLogsDir(const std::string &username){}
void LLDir::setLindenUserDir(const std::string &username){}
-void LLDir::setSkinFolder(const std::string &skin_folder){}
+void LLDir::setSkinFolder(const std::string &skin_folder, const std::string& language){}
+std::string LLDir::getSkinFolder() const { return "default"; }
+std::string LLDir::getLanguage() const { return "en"; }
bool LLDir::setCacheDir(const std::string &path){ return true; }
void LLDir::dumpCurrentDirectories() {}
+void LLDir::updatePerAccountChatLogsDir() {}
+
+#include "llviewernetwork.h"
+LLGridManager::LLGridManager() :
+ mGrid("test.grid.lindenlab.com"),
+ mIsInProductionGrid(false)
+{
+}
+std::string LLGridManager::getUpdateServiceURL()
+{
+ return "https://update.secondlife.com/update";
+}
+LLGridManager::~LLGridManager()
+{
+}
+
std::string LLDir::getExpandedFilename(ELLPath location,
const std::string &filename) const
@@ -169,9 +192,11 @@ namespace tut
bool got_usage_error = false;
try
{
- updater.initialize("1.0",test_url, "update" ,test_channel, test_version);
+ unsigned char id1[MD5HEX_STR_SIZE] = "11111111111111111111111111111111";
+ updater.initialize(test_channel, test_version, "win", "1.2.3", id1, true);
updater.startChecking();
- updater.initialize("1.0", "other_url", "update", test_channel, test_version);
+ unsigned char id2[MD5HEX_STR_SIZE] = "22222222222222222222222222222222";
+ updater.initialize(test_channel, test_version, "win", "4.5.6", id2, true);
}
catch(LLUpdaterService::UsageError)
{
@@ -185,7 +210,8 @@ namespace tut
{
DEBUG;
LLUpdaterService updater;
- updater.initialize("1.0", test_url, "update", test_channel, test_version);
+ unsigned char id[MD5HEX_STR_SIZE] = "33333333333333333333333333333333";
+ updater.initialize(test_channel, test_version, "win", "7.8.9", id, true);
updater.startChecking();
ensure(updater.isChecking());
updater.stopChecking();