summaryrefslogtreecommitdiff
path: root/indra/viewer_components
diff options
context:
space:
mode:
Diffstat (limited to 'indra/viewer_components')
-rw-r--r--indra/viewer_components/CMakeLists.txt4
-rw-r--r--indra/viewer_components/login/CMakeLists.txt51
-rw-r--r--indra/viewer_components/login/lllogin.cpp434
-rw-r--r--indra/viewer_components/login/lllogin.h127
-rw-r--r--indra/viewer_components/login/tests/lllogin_test.cpp498
-rw-r--r--indra/viewer_components/updater/CMakeLists.txt82
-rw-r--r--indra/viewer_components/updater/llupdatechecker.cpp194
-rw-r--r--indra/viewer_components/updater/llupdatechecker.h82
-rw-r--r--indra/viewer_components/updater/llupdatedownloader.cpp517
-rw-r--r--indra/viewer_components/updater/llupdatedownloader.h94
-rw-r--r--indra/viewer_components/updater/llupdateinstaller.cpp100
-rw-r--r--indra/viewer_components/updater/llupdateinstaller.h58
-rw-r--r--indra/viewer_components/updater/llupdaterservice.cpp621
-rw-r--r--indra/viewer_components/updater/llupdaterservice.h108
-rw-r--r--indra/viewer_components/updater/scripts/darwin/update_install10
-rw-r--r--indra/viewer_components/updater/scripts/linux/update_install10
-rw-r--r--indra/viewer_components/updater/scripts/windows/update_install.bat3
-rw-r--r--indra/viewer_components/updater/tests/llupdaterservice_test.cpp194
18 files changed, 3187 insertions, 0 deletions
diff --git a/indra/viewer_components/CMakeLists.txt b/indra/viewer_components/CMakeLists.txt
new file mode 100644
index 0000000000..74c9b4568d
--- /dev/null
+++ b/indra/viewer_components/CMakeLists.txt
@@ -0,0 +1,4 @@
+# -*- cmake -*-
+
+add_subdirectory(login)
+add_subdirectory(updater)
diff --git a/indra/viewer_components/login/CMakeLists.txt b/indra/viewer_components/login/CMakeLists.txt
new file mode 100644
index 0000000000..7720619df3
--- /dev/null
+++ b/indra/viewer_components/login/CMakeLists.txt
@@ -0,0 +1,51 @@
+# -*- cmake -*-
+
+project(login)
+
+include(00-Common)
+if(LL_TESTS)
+ include(LLAddBuildTest)
+endif(LL_TESTS)
+include(LLCommon)
+include(LLMath)
+include(LLXML)
+
+include_directories(
+ ${LLCOMMON_INCLUDE_DIRS}
+ ${LLMATH_INCLUDE_DIRS}
+ ${LLXML_INCLUDE_DIRS}
+ )
+
+set(login_SOURCE_FILES
+ lllogin.cpp
+ )
+
+set(login_HEADER_FILES
+ lllogin.h
+ )
+
+set_source_files_properties(${login_HEADER_FILES}
+ PROPERTIES HEADER_FILE_ONLY TRUE)
+
+list(APPEND
+ login_SOURCE_FILES
+ ${login_HEADER_FILES}
+ )
+
+add_library(lllogin
+ ${login_SOURCE_FILES}
+ )
+
+target_link_libraries(lllogin
+ ${LLCOMMON_LIBRARIES}
+ ${LLMATH_LIBRARIES}
+ ${LLXML_LIBRARIES}
+ )
+
+if(LL_TESTS)
+ SET(lllogin_TEST_SOURCE_FILES
+ lllogin.cpp
+ )
+
+ LL_ADD_PROJECT_UNIT_TESTS(lllogin "${lllogin_TEST_SOURCE_FILES}")
+endif(LL_TESTS)
diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp
new file mode 100644
index 0000000000..d480b63094
--- /dev/null
+++ b/indra/viewer_components/login/lllogin.cpp
@@ -0,0 +1,434 @@
+/**
+ * @file lllogin.cpp
+ *
+ * $LicenseInfo:firstyear=2009&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 <boost/coroutine/coroutine.hpp>
+#include "linden_common.h"
+#include "llsd.h"
+#include "llsdutil.h"
+
+/*==========================================================================*|
+#ifdef LL_WINDOWS
+ // non-virtual destructor warning, boost::statechart does this intentionally.
+ #pragma warning (disable : 4265)
+#endif
+|*==========================================================================*/
+
+#include "lllogin.h"
+
+#include <boost/bind.hpp>
+
+#include "llcoros.h"
+#include "llevents.h"
+#include "lleventfilter.h"
+#include "lleventcoro.h"
+
+//*********************
+// LLLogin
+// *NOTE:Mani - Is this Impl needed now that the state machine runs the show?
+class LLLogin::Impl
+{
+public:
+ Impl():
+ mPump("login", true) // Create the module's event pump with a tweaked (unique) name.
+ {
+ mValidAuthResponse["status"] = LLSD();
+ mValidAuthResponse["errorcode"] = LLSD();
+ mValidAuthResponse["error"] = LLSD();
+ mValidAuthResponse["transfer_rate"] = LLSD();
+ }
+
+ void connect(const std::string& uri, const LLSD& credentials);
+ void disconnect();
+ LLEventPump& getEventPump() { return mPump; }
+
+private:
+ LLSD getProgressEventLLSD(const std::string& state, const std::string& change,
+ const LLSD& data = LLSD())
+ {
+ LLSD status_data;
+ status_data["state"] = state;
+ status_data["change"] = change;
+ status_data["progress"] = 0.0f;
+
+ if(mAuthResponse.has("transfer_rate"))
+ {
+ status_data["transfer_rate"] = mAuthResponse["transfer_rate"];
+ }
+
+ if(data.isDefined())
+ {
+ status_data["data"] = data;
+ }
+ return status_data;
+ }
+
+ void sendProgressEvent(const std::string& state, const std::string& change,
+ const LLSD& data = LLSD())
+ {
+ LLSD status_data = getProgressEventLLSD(state, change, data);
+ mPump.post(status_data);
+ }
+
+ LLSD validateResponse(const std::string& pumpName, const LLSD& response)
+ {
+ // Validate the response. If we don't recognize it, things
+ // could get ugly.
+ std::string mismatch(llsd_matches(mValidAuthResponse, response));
+ if (! mismatch.empty())
+ {
+ LL_ERRS("LLLogin") << "Received unrecognized event (" << mismatch << ") on "
+ << pumpName << "pump: " << response
+ << LL_ENDL;
+ return LLSD();
+ }
+
+ return response;
+ }
+
+ // In a coroutine's top-level function args, do NOT NOT NOT accept
+ // references (const or otherwise) to anything but the self argument! Pass
+ // by value only!
+ void login_(LLCoros::self& self, std::string uri, LLSD credentials);
+
+ LLEventStream mPump;
+ LLSD mAuthResponse, mValidAuthResponse;
+};
+
+void LLLogin::Impl::connect(const std::string& uri, const LLSD& login_params)
+{
+ LL_DEBUGS("LLLogin") << " connect with uri '" << uri << "', login_params " << login_params << LL_ENDL;
+
+ // Launch a coroutine with our login_() method. Run the coroutine until
+ // its first wait; at that point, return here.
+ std::string coroname =
+ LLCoros::instance().launch("LLLogin::Impl::login_",
+ boost::bind(&Impl::login_, this, _1, uri, login_params));
+ LL_DEBUGS("LLLogin") << " connected with uri '" << uri << "', login_params " << login_params << LL_ENDL;
+}
+
+void LLLogin::Impl::login_(LLCoros::self& self, std::string uri, LLSD login_params)
+{
+ try
+ {
+ LLSD printable_params = login_params;
+ //if(printable_params.has("params")
+ // && printable_params["params"].has("passwd"))
+ //{
+ // printable_params["params"]["passwd"] = "*******";
+ //}
+ LL_DEBUGS("LLLogin") << "Entering coroutine " << LLCoros::instance().getName(self)
+ << " with uri '" << uri << "', parameters " << printable_params << LL_ENDL;
+
+ // Arriving in SRVRequest state
+ LLEventStream replyPump("SRVreply", true);
+ // Should be an array of one or more uri strings.
+
+ LLSD rewrittenURIs;
+ {
+ LLEventTimeout filter(replyPump);
+ sendProgressEvent("offline", "srvrequest");
+
+ // Request SRV record.
+ LL_DEBUGS("LLLogin") << "Requesting SRV record from " << uri << LL_ENDL;
+
+ // *NOTE:Mani - Completely arbitrary default timeout value for SRV request.
+ F32 seconds_to_timeout = 5.0f;
+ if(login_params.has("cfg_srv_timeout"))
+ {
+ seconds_to_timeout = login_params["cfg_srv_timeout"].asReal();
+ }
+
+ // If the SRV request times out (e.g. EXT-3934), simulate response: an
+ // array containing our original URI.
+ LLSD fakeResponse(LLSD::emptyArray());
+ fakeResponse.append(uri);
+ filter.eventAfter(seconds_to_timeout, fakeResponse);
+
+ std::string srv_pump_name = "LLAres";
+ if(login_params.has("cfg_srv_pump"))
+ {
+ srv_pump_name = login_params["cfg_srv_pump"].asString();
+ }
+
+ // Make request
+ LLSD request;
+ request["op"] = "rewriteURI";
+ request["uri"] = uri;
+ request["reply"] = replyPump.getName();
+ rewrittenURIs = postAndWait(self, request, srv_pump_name, filter);
+ // EXP-772: If rewrittenURIs fail, try original URI as a fallback.
+ rewrittenURIs.append(uri);
+ } // we no longer need the filter
+
+ LLEventPump& xmlrpcPump(LLEventPumps::instance().obtain("LLXMLRPCTransaction"));
+ // EXT-4193: use a DIFFERENT reply pump than for the SRV request. We used
+ // to share them -- but the EXT-3934 fix made it possible for an abandoned
+ // SRV response to arrive just as we were expecting the XMLRPC response.
+ LLEventStream loginReplyPump("loginreply", true);
+
+ // Loop through the rewrittenURIs, counting attempts along the way.
+ // Because of possible redirect responses, we may make more than one
+ // attempt per rewrittenURIs entry.
+ LLSD::Integer attempts = 0;
+ for (LLSD::array_const_iterator urit(rewrittenURIs.beginArray()),
+ urend(rewrittenURIs.endArray());
+ urit != urend; ++urit)
+ {
+ LLSD request(login_params);
+ request["reply"] = loginReplyPump.getName();
+ request["uri"] = *urit;
+ std::string status;
+
+ // Loop back to here if login attempt redirects to a different
+ // request["uri"]
+ for (;;)
+ {
+ ++attempts;
+ LLSD progress_data;
+ progress_data["attempt"] = attempts;
+ progress_data["request"] = request;
+ if(progress_data["request"].has("params")
+ && progress_data["request"]["params"].has("passwd"))
+ {
+ progress_data["request"]["params"]["passwd"] = "*******";
+ }
+ sendProgressEvent("offline", "authenticating", progress_data);
+
+ // We expect zero or more "Downloading" status events, followed by
+ // exactly one event with some other status. Use postAndWait() the
+ // first time, because -- at least in unit-test land -- it's
+ // possible for the reply to arrive before the post() call
+ // returns. Subsequent responses, of course, must be awaited
+ // without posting again.
+ for (mAuthResponse = validateResponse(loginReplyPump.getName(),
+ postAndWait(self, request, xmlrpcPump, loginReplyPump, "reply"));
+ mAuthResponse["status"].asString() == "Downloading";
+ mAuthResponse = validateResponse(loginReplyPump.getName(),
+ waitForEventOn(self, loginReplyPump)))
+ {
+ // Still Downloading -- send progress update.
+ sendProgressEvent("offline", "downloading");
+ }
+
+ LL_DEBUGS("LLLogin") << "Auth Response: " << mAuthResponse << LL_ENDL;
+ status = mAuthResponse["status"].asString();
+
+ // Okay, we've received our final status event for this
+ // request. Unless we got a redirect response, break the retry
+ // loop for the current rewrittenURIs entry.
+ if (!(status == "Complete" &&
+ mAuthResponse["responses"]["login"].asString() == "indeterminate"))
+ {
+ break;
+ }
+
+ sendProgressEvent("offline", "indeterminate", mAuthResponse["responses"]);
+
+ // Here the login service at the current URI is redirecting us
+ // to some other URI ("indeterminate" -- why not "redirect"?).
+ // The response should contain another uri to try, with its
+ // own auth method.
+ request["uri"] = mAuthResponse["responses"]["next_url"].asString();
+ request["method"] = mAuthResponse["responses"]["next_method"].asString();
+ } // loop back to try the redirected URI
+
+ // Here we're done with redirects for the current rewrittenURIs
+ // entry.
+ if (status == "Complete")
+ {
+ // StatusComplete does not imply auth success. Check the
+ // actual outcome of the request. We've already handled the
+ // "indeterminate" case in the loop above.
+ if (mAuthResponse["responses"]["login"].asString() == "true")
+ {
+ sendProgressEvent("online", "connect", mAuthResponse["responses"]);
+ }
+ else
+ {
+ sendProgressEvent("offline", "fail.login", mAuthResponse["responses"]);
+ }
+ return; // Done!
+ }
+ // If we don't recognize status at all, trouble
+ if (! (status == "CURLError"
+ || status == "XMLRPCError"
+ || status == "OtherError"))
+ {
+ LL_ERRS("LLLogin") << "Unexpected status from " << xmlrpcPump.getName() << " pump: "
+ << mAuthResponse << LL_ENDL;
+ return;
+ }
+
+ // Here status IS one of the errors tested above.
+ } // Retry if there are any more rewrittenURIs.
+
+ // Here we got through all the rewrittenURIs without succeeding. Tell
+ // caller this didn't work out so well. Of course, the only failure data
+ // we can reasonably show are from the last of the rewrittenURIs.
+
+ // *NOTE: The response from LLXMLRPCListener's Poller::poll method returns an
+ // llsd with no "responses" node. To make the output from an incomplete login symmetrical
+ // to success, add a data/message and data/reason fields.
+ LLSD error_response;
+ error_response["reason"] = mAuthResponse["status"];
+ error_response["errorcode"] = mAuthResponse["errorcode"];
+ error_response["message"] = mAuthResponse["error"];
+ if(mAuthResponse.has("certificate"))
+ {
+ error_response["certificate"] = mAuthResponse["certificate"];
+ }
+ sendProgressEvent("offline", "fail.login", error_response);
+ }
+ catch (...) {
+ llerrs << "login exception caught" << llendl;
+ }
+}
+
+void LLLogin::Impl::disconnect()
+{
+ sendProgressEvent("offline", "disconnect");
+}
+
+//*********************
+// LLLogin
+LLLogin::LLLogin() :
+ mImpl(new LLLogin::Impl())
+{
+}
+
+LLLogin::~LLLogin()
+{
+}
+
+void LLLogin::connect(const std::string& uri, const LLSD& credentials)
+{
+ mImpl->connect(uri, credentials);
+}
+
+
+void LLLogin::disconnect()
+{
+ mImpl->disconnect();
+}
+
+LLEventPump& LLLogin::getEventPump()
+{
+ return mImpl->getEventPump();
+}
+
+// The following is the list of important functions that happen in the
+// current login process that we want to move to this login module.
+
+// The list associates to event with the original idle_startup() 'STATE'.
+
+// Rewrite URIs
+ // State_LOGIN_AUTH_INIT
+// Given a vector of login uris (usually just one), perform a dns lookup for the
+// SRV record from each URI. I think this is used to distribute login requests to
+// a single URI to multiple hosts.
+// This is currently a synchronous action. (See LLSRV::rewriteURI() implementation)
+// On dns lookup error the output uris == the input uris.
+//
+// Input: A vector of login uris
+// Output: A vector of login uris
+//
+// Code:
+// std::vector<std::string> uris;
+// LLViewerLogin::getInstance()->getLoginURIs(uris);
+// std::vector<std::string>::const_iterator iter, end;
+// for (iter = uris.begin(), end = uris.end(); iter != end; ++iter)
+// {
+// std::vector<std::string> rewritten;
+// rewritten = LLSRV::rewriteURI(*iter);
+// sAuthUris.insert(sAuthUris.end(),
+// rewritten.begin(), rewritten.end());
+// }
+// sAuthUriNum = 0;
+
+// Authenticate
+// STATE_LOGIN_AUTHENTICATE
+// Connect to the login server, presumably login.cgi, requesting the login
+// and a slew of related initial connection information.
+// This is an asynch action. The final response, whether success or error
+// is handled by STATE_LOGIN_PROCESS_REPONSE.
+// There is no immediate error or output from this call.
+//
+// Input:
+// URI
+// Credentials (first, last, password)
+// Start location
+// Bool Flags:
+// skip optional update
+// accept terms of service
+// accept critical message
+// Last exec event. (crash state of previous session)
+// requested optional data (inventory skel, initial outfit, etc.)
+// local mac address
+// viewer serial no. (md5 checksum?)
+
+//sAuthUriNum = llclamp(sAuthUriNum, 0, (S32)sAuthUris.size()-1);
+//LLUserAuth::getInstance()->authenticate(
+// sAuthUris[sAuthUriNum],
+// auth_method,
+// firstname,
+// lastname,
+// password, // web_login_key,
+// start.str(),
+// gSkipOptionalUpdate,
+// gAcceptTOS,
+// gAcceptCriticalMessage,
+// gLastExecEvent,
+// requested_options,
+// hashed_mac_string,
+// LLAppViewer::instance()->getSerialNumber());
+
+//
+// Download the Response
+// STATE_LOGIN_NO_REPONSE_YET and STATE_LOGIN_DOWNLOADING
+// I had assumed that this was default behavior of the message system. However...
+// During login, the message system is checked only by these two states in idle_startup().
+// I guess this avoids the overhead of checking network messages for those login states
+// that don't need to do so, but geez!
+// There are two states to do this one function just to update the login
+// status text from 'Logging In...' to 'Downloading...'
+//
+
+//
+// Handle Login Response
+// STATE_LOGIN_PROCESS_RESPONSE
+//
+// This state handle the result of the request to login. There is a metric ton of
+// code in this case. This state will transition to:
+// STATE_WORLD_INIT, on success.
+// STATE_AUTHENTICATE, on failure.
+// STATE_UPDATE_CHECK, to handle user during login interaction like TOS display.
+//
+// Much of the code in this case belongs on the viewer side of the fence and not in login.
+// Login should probably return with a couple of events, success and failure.
+// Failure conditions can be specified in the events data pacet to allow the viewer
+// to re-engauge login as is appropriate. (Or should there be multiple failure messages?)
+// Success is returned with the data requested from the login. According to OGP specs
+// there may be intermediate steps before reaching this result in future login
+// implementations.
diff --git a/indra/viewer_components/login/lllogin.h b/indra/viewer_components/login/lllogin.h
new file mode 100644
index 0000000000..051641ff59
--- /dev/null
+++ b/indra/viewer_components/login/lllogin.h
@@ -0,0 +1,127 @@
+/**
+ * @file lllogin.h
+ *
+ * $LicenseInfo:firstyear=2009&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$
+ */
+
+#ifndef LL_LLLOGIN_H
+#define LL_LLLOGIN_H
+
+#include <boost/scoped_ptr.hpp>
+
+class LLSD;
+class LLEventPump;
+
+/**
+ * @class LLLogin
+ * @brief Class to encapsulate the action and state of grid login.
+ */
+class LLLogin
+{
+public:
+ LLLogin();
+ ~LLLogin();
+
+ /**
+ * Make a connection to a grid.
+ * @param uri The 'well known and published' authentication URL.
+ * @param credentials LLSD data that contians the credentials.
+ * *NOTE:Mani The credential data can vary depending upon the authentication
+ * method used. The current interface matches the values passed to
+ * the XMLRPC login request.
+ {
+ method : string,
+ first : string,
+ last : string,
+ passwd : string,
+ start : string,
+ skipoptional : bool,
+ agree_to_tos : bool,
+ read_critical : bool,
+ last_exec_event : int,
+ version : string,
+ channel : string,
+ mac : string,
+ id0 : string,
+ options : [ strings ]
+ }
+
+ */
+ void connect(const std::string& uri, const LLSD& credentials);
+
+ /**
+ * Disconnect from a the current connection.
+ */
+ void disconnect();
+
+ /**
+ * Retrieve the event pump from this login class.
+ */
+ LLEventPump& getEventPump();
+
+ /*
+ Event API
+
+ LLLogin will issue multiple events to it pump to indicate the
+ progression of states through login. The most important
+ states are "offline" and "online" which indicate auth failure
+ and auth success respectively.
+
+ pump: login (tweaked)
+ These are the events posted to the 'login'
+ event pump from the login module.
+ {
+ state : string, // See below for the list of states.
+ progress : real // for progress bar.
+ data : LLSD // Dependent upon state.
+ }
+
+ States for method 'login_to_simulator'
+ offline - set initially state and upon failure. data is the server response.
+ srvrequest - upon uri rewrite request. no data.
+ authenticating - upon auth request. data, 'attempt' number and 'request' llsd.
+ downloading - upon ack from auth server, before completion. no data
+ online - upon auth success. data is server response.
+
+
+ Dependencies:
+ pump: LLAres
+ LLLogin makes a request for a SRV record from the uri provided by the connect method.
+ The following event pump should exist to service that request.
+ pump name: LLAres
+ request = {
+ op : "rewriteURI"
+ uri : string
+ reply : string
+
+ pump: LLXMLRPCListener
+ The request merely passes the credentials LLSD along, with one additional
+ member, 'reply', which is the string name of the event pump to reply on.
+
+ */
+
+private:
+ class Impl;
+ boost::scoped_ptr<Impl> mImpl;
+};
+
+#endif // LL_LLLOGIN_H
diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp
new file mode 100644
index 0000000000..58bf371a04
--- /dev/null
+++ b/indra/viewer_components/login/tests/lllogin_test.cpp
@@ -0,0 +1,498 @@
+/**
+ * @file lllogin_test.cpp
+ * @author Mark Palange
+ * @date 2009-02-26
+ * @brief Tests of lllogin.cpp.
+ *
+ * $LicenseInfo:firstyear=2009&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2009-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$
+ */
+
+#if LL_WINDOWS
+#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
+#endif
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "../lllogin.h"
+// STL headers
+// std headers
+#include <iostream>
+// external library headers
+// other Linden headers
+#include "llsd.h"
+#include "../../../test/lltut.h"
+//#define DEBUG_ON
+#include "../../../test/debug.h"
+#include "llevents.h"
+#include "stringize.h"
+
+#if LL_WINDOWS
+#define skipwin(arg) skip(arg)
+#define skipmac(arg)
+#define skiplinux(arg)
+#elif LL_DARWIN
+#define skipwin(arg)
+#define skipmac(arg) skip(arg)
+#define skiplinux(arg)
+#elif LL_LINUX
+#define skipwin(arg)
+#define skipmac(arg)
+#define skiplinux(arg) skip(arg)
+#endif
+
+/*****************************************************************************
+* Helper classes
+*****************************************************************************/
+// This is a listener to receive results from lllogin.
+class LoginListener: public LLEventTrackable
+{
+ std::string mName;
+ LLSD mLastEvent;
+ Debug mDebug;
+public:
+ LoginListener(const std::string& name) :
+ mName(name),
+ mDebug(stringize(*this))
+ {}
+
+ bool call(const LLSD& event)
+ {
+ mDebug(STRINGIZE("LoginListener called!: " << event));
+
+ mLastEvent = event;
+ return false;
+ }
+
+ LLBoundListener listenTo(LLEventPump& pump)
+ {
+ return pump.listen(mName, boost::bind(&LoginListener::call, this, _1));
+ }
+
+ LLSD lastEvent() const { return mLastEvent; }
+
+ friend std::ostream& operator<<(std::ostream& out, const LoginListener& listener)
+ {
+ return out << "LoginListener(" << listener.mName << ')';
+ }
+};
+
+class LLAresListener: public LLEventTrackable
+{
+ std::string mName;
+ LLSD mEvent;
+ bool mImmediateResponse;
+ bool mMultipleURIResponse;
+ Debug mDebug;
+
+public:
+ LLAresListener(const std::string& name,
+ bool i = false,
+ bool m = false
+ ) :
+ mName(name),
+ mImmediateResponse(i),
+ mMultipleURIResponse(m),
+ mDebug(stringize(*this))
+ {}
+
+ bool handle_event(const LLSD& event)
+ {
+ mDebug(STRINGIZE("LLAresListener called!: " << event));
+ mEvent = event;
+ if(mImmediateResponse)
+ {
+ sendReply();
+ }
+ return false;
+ }
+
+ void sendReply()
+ {
+ if(mEvent["op"].asString() == "rewriteURI")
+ {
+ LLSD result;
+ if(mMultipleURIResponse)
+ {
+ result.append(LLSD("login.foo.com"));
+ }
+ result.append(mEvent["uri"]);
+ LLEventPumps::instance().obtain(mEvent["reply"]).post(result);
+ }
+ }
+
+ LLBoundListener listenTo(LLEventPump& pump)
+ {
+ return pump.listen(mName, boost::bind(&LLAresListener::handle_event, this, _1));
+ }
+
+ friend std::ostream& operator<<(std::ostream& out, const LLAresListener& listener)
+ {
+ return out << "LLAresListener(" << listener.mName << ')';
+ }
+};
+
+class LLXMLRPCListener: public LLEventTrackable
+{
+ std::string mName;
+ LLSD mEvent;
+ bool mImmediateResponse;
+ LLSD mResponse;
+ Debug mDebug;
+
+public:
+ LLXMLRPCListener(const std::string& name,
+ bool i = false,
+ const LLSD& response = LLSD()
+ ) :
+ mName(name),
+ mImmediateResponse(i),
+ mResponse(response),
+ mDebug(stringize(*this))
+ {
+ if(mResponse.isUndefined())
+ {
+ mResponse["status"] = "Complete"; // StatusComplete
+ mResponse["errorcode"] = 0;
+ mResponse["error"] = "dummy response";
+ mResponse["transfer_rate"] = 0;
+ mResponse["responses"]["login"] = true;
+ }
+ }
+
+ void setResponse(const LLSD& r)
+ {
+ mResponse = r;
+ }
+
+ bool handle_event(const LLSD& event)
+ {
+ mDebug(STRINGIZE("LLXMLRPCListener called!: " << event));
+ mEvent = event;
+ if(mImmediateResponse)
+ {
+ sendReply();
+ }
+ return false;
+ }
+
+ void sendReply()
+ {
+ LLEventPumps::instance().obtain(mEvent["reply"]).post(mResponse);
+ }
+
+ LLBoundListener listenTo(LLEventPump& pump)
+ {
+ return pump.listen(mName, boost::bind(&LLXMLRPCListener::handle_event, this, _1));
+ }
+
+ friend std::ostream& operator<<(std::ostream& out, const LLXMLRPCListener& listener)
+ {
+ return out << "LLXMLRPCListener(" << listener.mName << ')';
+ }
+};
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llviewerlogin_data
+ {
+ llviewerlogin_data() :
+ pumps(LLEventPumps::instance())
+ {}
+ LLEventPumps& pumps;
+ };
+
+ typedef test_group<llviewerlogin_data> llviewerlogin_group;
+ typedef llviewerlogin_group::object llviewerlogin_object;
+ llviewerlogin_group llviewerlogingrp("LLViewerLogin");
+
+ template<> template<>
+ void llviewerlogin_object::test<1>()
+ {
+ DEBUG;
+ // Testing login with immediate responses from Ares and XMLPRC
+ // The response from both requests will come before the post request exits.
+ // This tests an edge case of the login state handling.
+ LLEventStream llaresPump("LLAres"); // Dummy LLAres pump.
+ LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump
+
+ bool respond_immediately = true;
+ // Have 'dummy ares' respond immediately.
+ LLAresListener dummyLLAres("dummy_llares", respond_immediately);
+ dummyLLAres.listenTo(llaresPump);
+
+ // Have dummy XMLRPC respond immediately.
+ LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc", respond_immediately);
+ dummyXMLRPC.listenTo(xmlrpcPump);
+
+ LLLogin login;
+
+ LoginListener listener("test_ear");
+ listener.listenTo(login.getEventPump());
+
+ LLSD credentials;
+ credentials["first"] = "foo";
+ credentials["last"] = "bar";
+ credentials["passwd"] = "secret";
+
+ login.connect("login.bar.com", credentials);
+
+ ensure_equals("Online state", listener.lastEvent()["state"].asString(), "online");
+ }
+
+ template<> template<>
+ void llviewerlogin_object::test<2>()
+ {
+ DEBUG;
+ // Tests a successful login in with delayed responses.
+ // Also includes 'failure' that cause the login module
+ // to re-attempt connection, once from a basic failure
+ // and once from the 'indeterminate' response.
+
+ set_test_name("LLLogin multiple srv uris w/ success");
+
+ // Testing normal login procedure.
+ LLEventStream llaresPump("LLAres"); // Dummy LLAres pump.
+ LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump
+
+ bool respond_immediately = false;
+ bool multiple_addresses = true;
+ LLAresListener dummyLLAres("dummy_llares", respond_immediately, multiple_addresses);
+ dummyLLAres.listenTo(llaresPump);
+
+ LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc");
+ dummyXMLRPC.listenTo(xmlrpcPump);
+
+ LLLogin login;
+
+ LoginListener listener("test_ear");
+ listener.listenTo(login.getEventPump());
+
+ LLSD credentials;
+ credentials["first"] = "foo";
+ credentials["last"] = "bar";
+ credentials["passwd"] = "secret";
+
+ login.connect("login.bar.com", credentials);
+
+ ensure_equals("SRV state", listener.lastEvent()["change"].asString(), "srvrequest");
+
+ dummyLLAres.sendReply();
+
+ // Test Authenticating State prior to first response.
+ ensure_equals("Auth state 1", listener.lastEvent()["change"].asString(), "authenticating");
+ ensure_equals("Attempt 1", listener.lastEvent()["data"]["attempt"].asInteger(), 1);
+ ensure_equals("URI 1", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.foo.com");
+
+ // First send emulated LLXMLRPCListener failure,
+ // this should return login to the authenticating step and increase the attempt
+ // count.
+ LLSD data;
+ data["status"] = "OtherError";
+ data["errorcode"] = 0;
+ data["error"] = "dummy response";
+ data["transfer_rate"] = 0;
+ dummyXMLRPC.setResponse(data);
+ dummyXMLRPC.sendReply();
+
+ ensure_equals("Fail back to authenticate 1", listener.lastEvent()["change"].asString(), "authenticating");
+ ensure_equals("Attempt 2", listener.lastEvent()["data"]["attempt"].asInteger(), 2);
+ ensure_equals("URI 2", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.bar.com");
+
+ // Now send the 'indeterminate' response.
+ data.clear();
+ data["status"] = "Complete"; // StatusComplete
+ data["errorcode"] = 0;
+ data["error"] = "dummy response";
+ data["transfer_rate"] = 0;
+ data["responses"]["login"] = "indeterminate";
+ data["responses"]["next_url"] = "login.indeterminate.com";
+ data["responses"]["next_method"] = "test_login_method";
+ dummyXMLRPC.setResponse(data);
+ dummyXMLRPC.sendReply();
+
+ ensure_equals("Fail back to authenticate 2", listener.lastEvent()["change"].asString(), "authenticating");
+ ensure_equals("Attempt 3", listener.lastEvent()["data"]["attempt"].asInteger(), 3);
+ ensure_equals("URI 3", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.indeterminate.com");
+ ensure_equals("Method 3", listener.lastEvent()["data"]["request"]["method"].asString(), "test_login_method");
+
+ // Finally let the auth succeed.
+ data.clear();
+ data["status"] = "Complete"; // StatusComplete
+ data["errorcode"] = 0;
+ data["error"] = "dummy response";
+ data["transfer_rate"] = 0;
+ data["responses"]["login"] = "true";
+ dummyXMLRPC.setResponse(data);
+ dummyXMLRPC.sendReply();
+
+ ensure_equals("Success state", listener.lastEvent()["state"].asString(), "online");
+
+ login.disconnect();
+
+ ensure_equals("Disconnected state", listener.lastEvent()["state"].asString(), "offline");
+ }
+
+ template<> template<>
+ void llviewerlogin_object::test<3>()
+ {
+ DEBUG;
+ // Test completed response, that fails to login.
+ set_test_name("LLLogin valid response, failure (eg. bad credentials)");
+
+ // Testing normal login procedure.
+ LLEventStream llaresPump("LLAres"); // Dummy LLAres pump.
+ LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump
+
+ LLAresListener dummyLLAres("dummy_llares");
+ dummyLLAres.listenTo(llaresPump);
+
+ LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc");
+ dummyXMLRPC.listenTo(xmlrpcPump);
+
+ LLLogin login;
+ LoginListener listener("test_ear");
+ listener.listenTo(login.getEventPump());
+
+ LLSD credentials;
+ credentials["first"] = "who";
+ credentials["last"] = "what";
+ credentials["passwd"] = "badpasswd";
+
+ login.connect("login.bar.com", credentials);
+
+ ensure_equals("SRV state", listener.lastEvent()["change"].asString(), "srvrequest");
+
+ dummyLLAres.sendReply();
+
+ ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating");
+
+ // Send the failed auth request reponse
+ LLSD data;
+ data["status"] = "Complete";
+ data["errorcode"] = 0;
+ data["error"] = "dummy response";
+ data["transfer_rate"] = 0;
+ data["responses"]["login"] = "false";
+ dummyXMLRPC.setResponse(data);
+ dummyXMLRPC.sendReply();
+
+ ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline");
+ }
+
+ template<> template<>
+ void llviewerlogin_object::test<4>()
+ {
+ DEBUG;
+ // Test incomplete response, that end the attempt.
+ set_test_name("LLLogin valid response, failure (eg. bad credentials)");
+
+ // Testing normal login procedure.
+ LLEventStream llaresPump("LLAres"); // Dummy LLAres pump.
+ LLEventStream xmlrpcPump("LLXMLRPCTransaction"); // Dummy XMLRPC pump
+
+ LLAresListener dummyLLAres("dummy_llares");
+ dummyLLAres.listenTo(llaresPump);
+
+ LLXMLRPCListener dummyXMLRPC("dummy_xmlrpc");
+ dummyXMLRPC.listenTo(xmlrpcPump);
+
+ LLLogin login;
+ LoginListener listener("test_ear");
+ listener.listenTo(login.getEventPump());
+
+ LLSD credentials;
+ credentials["first"] = "these";
+ credentials["last"] = "don't";
+ credentials["passwd"] = "matter";
+
+ login.connect("login.bar.com", credentials);
+
+ ensure_equals("SRV state", listener.lastEvent()["change"].asString(), "srvrequest");
+
+ dummyLLAres.sendReply();
+
+ ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating");
+
+ // Send the failed auth request reponse
+ LLSD data;
+ data["status"] = "OtherError";
+ data["errorcode"] = 0;
+ data["error"] = "dummy response";
+ data["transfer_rate"] = 0;
+ dummyXMLRPC.setResponse(data);
+ dummyXMLRPC.sendReply();
+
+ ensure_equals("Failed to offline", listener.lastEvent()["state"].asString(), "offline");
+ }
+
+ template<> template<>
+ void llviewerlogin_object::test<5>()
+ {
+ DEBUG;
+ // Test SRV request timeout.
+ set_test_name("LLLogin SRV timeout testing");
+
+ // Testing normal login procedure.
+ LLEventStream llaresPump("LLAres"); // Dummy LLAres pump.
+
+ LLAresListener dummyLLAres("dummy_llares");
+ dummyLLAres.listenTo(llaresPump);
+
+ LLLogin login;
+ LoginListener listener("test_ear");
+ listener.listenTo(login.getEventPump());
+
+ LLSD credentials;
+ credentials["first"] = "these";
+ credentials["last"] = "don't";
+ credentials["passwd"] = "matter";
+ credentials["cfg_srv_timeout"] = 0.0f;
+
+ login.connect("login.bar.com", credentials);
+
+ ensure_equals("SRV State", listener.lastEvent()["change"].asString(), "srvrequest");
+
+ // Get the mainloop eventpump, which needs a pinging in order to drive the
+ // SRV timeout.
+ LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop"));
+ LLSD frame_event;
+ mainloop.post(frame_event);
+
+ // In this state we have NOT sent a reply from LLAresListener -- in
+ // fact there's no such object. Nonetheless, we expect the timeout to
+ // have stepped the login module forward to try to authenticate with
+ // the original URI.
+ ensure_equals("Auth state", listener.lastEvent()["change"].asString(), "authenticating");
+ ensure_equals("Attempt", listener.lastEvent()["data"]["attempt"].asInteger(), 1);
+ ensure_equals("URI", listener.lastEvent()["data"]["request"]["uri"].asString(), "login.bar.com");
+
+ // EXT-4193: if the SRV reply isn't lost but merely late, and if it
+ // arrives just at the moment we're expecting the XMLRPC reply, the
+ // original code got confused and crashed. Drive that case here. We
+ // observe that without the fix, this call DOES repro.
+ dummyLLAres.sendReply();
+ }
+}
diff --git a/indra/viewer_components/updater/CMakeLists.txt b/indra/viewer_components/updater/CMakeLists.txt
new file mode 100644
index 0000000000..0e288bb496
--- /dev/null
+++ b/indra/viewer_components/updater/CMakeLists.txt
@@ -0,0 +1,82 @@
+# -*- cmake -*-
+
+project(updater_service)
+
+include(00-Common)
+if(LL_TESTS)
+ include(LLAddBuildTest)
+endif(LL_TESTS)
+include(CMakeCopyIfDifferent)
+include(CURL)
+include(LLCommon)
+include(LLMessage)
+include(LLPlugin)
+include(LLVFS)
+
+include_directories(
+ ${LLCOMMON_INCLUDE_DIRS}
+ ${LLMESSAGE_INCLUDE_DIRS}
+ ${LLPLUGIN_INCLUDE_DIRS}
+ ${LLVFS_INCLUDE_DIRS}
+ ${CURL_INCLUDE_DIRS}
+ )
+
+set(updater_service_SOURCE_FILES
+ llupdaterservice.cpp
+ llupdatechecker.cpp
+ llupdatedownloader.cpp
+ llupdateinstaller.cpp
+ )
+
+set(updater_service_HEADER_FILES
+ llupdaterservice.h
+ llupdatechecker.h
+ llupdatedownloader.h
+ llupdateinstaller.h
+ )
+
+set_source_files_properties(${updater_service_HEADER_FILES}
+ PROPERTIES HEADER_FILE_ONLY TRUE)
+
+list(APPEND
+ updater_service_SOURCE_FILES
+ ${updater_service_HEADER_FILES}
+ )
+
+add_library(llupdaterservice
+ ${updater_service_SOURCE_FILES}
+ )
+
+target_link_libraries(llupdaterservice
+ ${LLCOMMON_LIBRARIES}
+ ${LLMESSAGE_LIBRARIES}
+ ${LLPLUGIN_LIBRARIES}
+ ${LLVFS_LIBRARIES}
+ )
+
+if(LL_TESTS)
+ SET(llupdater_service_TEST_SOURCE_FILES
+ llupdaterservice.cpp
+ )
+
+# *NOTE:Mani - I was trying to use the preprocessor seam to mock out
+# llifstream (and other) llcommon classes. I didn't work
+# because of the windows declspec(dllimport)attribute.
+#set_source_files_properties(
+# llupdaterservice.cpp
+# PROPERTIES
+# LL_TEST_ADDITIONAL_CFLAGS "-Dllifstream=llus_mock_llifstream"
+# )
+
+ LL_ADD_PROJECT_UNIT_TESTS(llupdaterservice "${llupdater_service_TEST_SOURCE_FILES}")
+endif(LL_TESTS)
+
+set(UPDATER_INCLUDE_DIRS
+ ${LIBS_OPEN_DIR}/viewer_components/updater
+ CACHE INTERNAL ""
+)
+
+set(UPDATER_LIBRARIES
+ llupdaterservice
+ CACHE INTERNAL ""
+)
diff --git a/indra/viewer_components/updater/llupdatechecker.cpp b/indra/viewer_components/updater/llupdatechecker.cpp
new file mode 100644
index 0000000000..c6aa9b0f11
--- /dev/null
+++ b/indra/viewer_components/updater/llupdatechecker.cpp
@@ -0,0 +1,194 @@
+/**
+ * @file llupdaterservice.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$
+ */
+
+#include "linden_common.h"
+#include <stdexcept>
+#include <boost/format.hpp>
+#include "llhttpclient.h"
+#include "llsd.h"
+#include "llupdatechecker.h"
+#include "lluri.h"
+
+
+#if LL_WINDOWS
+#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
+#endif
+
+
+class LLUpdateChecker::CheckError:
+ public std::runtime_error
+{
+public:
+ CheckError(const char * message):
+ std::runtime_error(message)
+ {
+ ; // No op.
+ }
+};
+
+
+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
+//-----------------------------------------------------------------------------
+
+
+LLUpdateChecker::LLUpdateChecker(LLUpdateChecker::Client & client):
+ mImplementation(new LLUpdateChecker::Implementation(client))
+{
+ ; // No op.
+}
+
+
+void LLUpdateChecker::check(std::string const & protocolVersion, std::string const & hostUrl,
+ std::string const & servicePath, std::string channel, std::string version)
+{
+ mImplementation->check(protocolVersion, hostUrl, servicePath, channel, version);
+}
+
+
+
+// LLUpdateChecker::Implementation
+//-----------------------------------------------------------------------------
+
+
+const char * LLUpdateChecker::Implementation::sProtocolVersion = "v1.0";
+
+
+LLUpdateChecker::Implementation::Implementation(LLUpdateChecker::Client & client):
+ mClient(client),
+ mInProgress(false)
+{
+ ; // No op.
+}
+
+
+LLUpdateChecker::Implementation::~Implementation()
+{
+ ; // No op.
+}
+
+
+void LLUpdateChecker::Implementation::check(std::string const & protocolVersion, std::string const & hostUrl,
+ std::string const & servicePath, std::string channel, std::string version)
+{
+ llassert(!mInProgress);
+
+ 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;
+
+ // 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);
+}
+
+void LLUpdateChecker::Implementation::completed(U32 status,
+ const std::string & reason,
+ const LLSD & content)
+{
+ mInProgress = false;
+
+ if(status != 200) {
+ LL_WARNS("UpdateCheck") << "html error " << status << " (" << reason << ")" << llendl;
+ 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());
+ }
+}
+
+
+void LLUpdateChecker::Implementation::error(U32 status, const std::string & reason)
+{
+ mInProgress = false;
+ LL_WARNS("UpdateCheck") << "update check failed; " << reason << llendl;
+ 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
+
+ LLSD path;
+ path.append(servicePath);
+ path.append(protocolVersion);
+ path.append(channel);
+ path.append(version);
+ path.append(platform);
+ return LLURI::buildHTTP(hostUrl, path).asString();
+}
diff --git a/indra/viewer_components/updater/llupdatechecker.h b/indra/viewer_components/updater/llupdatechecker.h
new file mode 100644
index 0000000000..cea1f13647
--- /dev/null
+++ b/indra/viewer_components/updater/llupdatechecker.h
@@ -0,0 +1,82 @@
+/**
+ * @file llupdatechecker.h
+ *
+ * $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$
+ */
+
+#ifndef LL_UPDATERCHECKER_H
+#define LL_UPDATERCHECKER_H
+
+
+#include <boost/shared_ptr.hpp>
+
+
+//
+// Implements asynchronous checking for updates.
+//
+class LLUpdateChecker {
+public:
+ class Client;
+ class Implementation;
+
+ // An exception that may be raised on check errors.
+ class CheckError;
+
+ 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);
+
+private:
+ boost::shared_ptr<Implementation> mImplementation;
+};
+
+
+class LLURI; // From lluri.h
+
+
+//
+// The client interface implemented by a requestor checking for an update.
+//
+class LLUpdateChecker::Client
+{
+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;
+};
+
+
+#endif
diff --git a/indra/viewer_components/updater/llupdatedownloader.cpp b/indra/viewer_components/updater/llupdatedownloader.cpp
new file mode 100644
index 0000000000..e88d1bf811
--- /dev/null
+++ b/indra/viewer_components/updater/llupdatedownloader.cpp
@@ -0,0 +1,517 @@
+/**
+ * @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$
+ */
+
+#include "linden_common.h"
+
+#include "llupdatedownloader.h"
+
+#include <stdexcept>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+#include <curl/curl.h>
+#include "lldir.h"
+#include "llevents.h"
+#include "llfile.h"
+#include "llmd5.h"
+#include "llsd.h"
+#include "llsdserialize.h"
+#include "llthread.h"
+#include "llupdaterservice.h"
+
+
+class LLUpdateDownloader::Implementation:
+ public LLThread
+{
+public:
+ Implementation(LLUpdateDownloader::Client & client);
+ ~Implementation();
+ void cancel(void);
+ void download(LLURI const & uri,
+ std::string const & hash,
+ std::string const & updateVersion,
+ bool required);
+ bool isDownloading(void);
+ size_t onHeader(void * header, size_t size);
+ size_t onBody(void * header, size_t size);
+ int onProgress(double downloadSize, double bytesDownloaded);
+ void resume(void);
+ void setBandwidthLimit(U64 bytesPerSecond);
+
+private:
+ curl_off_t mBandwidthLimit;
+ bool mCancelled;
+ LLUpdateDownloader::Client & mClient;
+ CURL * mCurl;
+ LLSD mDownloadData;
+ llofstream mDownloadStream;
+ 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);
+ void startDownloading(LLURI const & uri, std::string const & hash);
+ void throwOnCurlError(CURLcode code);
+ bool validateDownload(void);
+
+ LOG_CLASS(LLUpdateDownloader::Implementation);
+};
+
+
+namespace {
+ class DownloadError:
+ public std::runtime_error
+ {
+ public:
+ DownloadError(const char * message):
+ std::runtime_error(message)
+ {
+ ; // No op.
+ }
+ };
+
+
+ const char * gSecondLifeUpdateRecord = "SecondLifeUpdateDownload.xml";
+};
+
+
+
+// LLUpdateDownloader
+//-----------------------------------------------------------------------------
+
+
+
+std::string LLUpdateDownloader::downloadMarkerPath(void)
+{
+ return gDirUtilp->getExpandedFilename(LL_PATH_LOGS, gSecondLifeUpdateRecord);
+}
+
+
+LLUpdateDownloader::LLUpdateDownloader(Client & client):
+ mImplementation(new LLUpdateDownloader::Implementation(client))
+{
+ ; // No op.
+}
+
+
+void LLUpdateDownloader::cancel(void)
+{
+ mImplementation->cancel();
+}
+
+
+void LLUpdateDownloader::download(LLURI const & uri,
+ std::string const & hash,
+ std::string const & updateVersion,
+ bool required)
+{
+ mImplementation->download(uri, hash, updateVersion, required);
+}
+
+
+bool LLUpdateDownloader::isDownloading(void)
+{
+ return mImplementation->isDownloading();
+}
+
+
+void LLUpdateDownloader::resume(void)
+{
+ mImplementation->resume();
+}
+
+
+void LLUpdateDownloader::setBandwidthLimit(U64 bytesPerSecond)
+{
+ mImplementation->setBandwidthLimit(bytesPerSecond);
+}
+
+
+
+// LLUpdateDownloader::Implementation
+//-----------------------------------------------------------------------------
+
+
+namespace {
+ size_t write_function(void * data, size_t blockSize, size_t blocks, void * downloader)
+ {
+ size_t bytes = blockSize * blocks;
+ return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->onBody(data, bytes);
+ }
+
+
+ size_t header_function(void * data, size_t blockSize, size_t blocks, void * downloader)
+ {
+ size_t bytes = blockSize * blocks;
+ return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->onHeader(data, bytes);
+ }
+
+
+ int progress_callback(void * downloader,
+ double dowloadTotal,
+ double downloadNow,
+ double uploadTotal,
+ double uploadNow)
+ {
+ return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->
+ onProgress(dowloadTotal, downloadNow);
+ }
+}
+
+
+LLUpdateDownloader::Implementation::Implementation(LLUpdateDownloader::Client & client):
+ LLThread("LLUpdateDownloader"),
+ mBandwidthLimit(0),
+ mCancelled(false),
+ mClient(client),
+ mCurl(0),
+ mDownloadPercent(0),
+ mHeaderList(0)
+{
+ CURLcode code = curl_global_init(CURL_GLOBAL_ALL); // Just in case.
+ llverify(code == CURLE_OK); // TODO: real error handling here.
+}
+
+
+LLUpdateDownloader::Implementation::~Implementation()
+{
+ if(isDownloading()) {
+ cancel();
+ shutdown();
+ } else {
+ ; // No op.
+ }
+ if(mCurl) curl_easy_cleanup(mCurl);
+}
+
+
+void LLUpdateDownloader::Implementation::cancel(void)
+{
+ mCancelled = true;
+}
+
+
+void LLUpdateDownloader::Implementation::download(LLURI const & uri,
+ std::string const & hash,
+ std::string const & updateVersion,
+ bool required)
+{
+ if(isDownloading()) mClient.downloadError("download in progress");
+
+ mDownloadRecordPath = downloadMarkerPath();
+ mDownloadData = LLSD();
+ mDownloadData["required"] = required;
+ mDownloadData["update_version"] = updateVersion;
+ try {
+ startDownloading(uri, hash);
+ } catch(DownloadError const & e) {
+ mClient.downloadError(e.what());
+ }
+}
+
+
+bool LLUpdateDownloader::Implementation::isDownloading(void)
+{
+ return !isStopped();
+}
+
+
+void LLUpdateDownloader::Implementation::resume(void)
+{
+ mCancelled = false;
+
+ if(isDownloading()) {
+ mClient.downloadError("download in progress");
+ }
+
+ mDownloadRecordPath = downloadMarkerPath();
+ llifstream dataStream(mDownloadRecordPath);
+ if(!dataStream) {
+ mClient.downloadError("no download marker");
+ return;
+ }
+
+ LLSDSerialize::fromXMLDocument(mDownloadData, dataStream);
+
+ if(!mDownloadData.asBoolean()) {
+ mClient.downloadError("no download information in marker");
+ return;
+ }
+
+ std::string filePath = mDownloadData["path"].asString();
+ try {
+ if(LLFile::isfile(filePath)) {
+ llstat fileStatus;
+ LLFile::stat(filePath, &fileStatus);
+ if(fileStatus.st_size != mDownloadData["size"].asInteger()) {
+ resumeDownloading(fileStatus.st_size);
+ } else if(!validateDownload()) {
+ LLFile::remove(filePath);
+ download(LLURI(mDownloadData["url"].asString()),
+ mDownloadData["hash"].asString(),
+ mDownloadData["update_version"].asString(),
+ mDownloadData["required"].asBoolean());
+ } else {
+ mClient.downloadComplete(mDownloadData);
+ }
+ } else {
+ download(LLURI(mDownloadData["url"].asString()),
+ mDownloadData["hash"].asString(),
+ mDownloadData["update_version"].asString(),
+ mDownloadData["required"].asBoolean());
+ }
+ } catch(DownloadError & e) {
+ mClient.downloadError(e.what());
+ }
+}
+
+
+void LLUpdateDownloader::Implementation::setBandwidthLimit(U64 bytesPerSecond)
+{
+ 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 {
+ mBandwidthLimit = bytesPerSecond;
+ }
+}
+
+
+size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)
+{
+ char const * headerPtr = reinterpret_cast<const char *> (buffer);
+ 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;
+
+ 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 ("
+ << e.what() << ")" << LL_ENDL;
+ }
+ } else {
+ ; // No op.
+ }
+
+ return 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(mDownloadStream.bad()) {
+ return 0;
+ } else {
+ return size;
+ }
+}
+
+
+int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double bytesDownloaded)
+{
+ int downloadPercent = static_cast<int>(100. * (bytesDownloaded / downloadSize));
+ if(downloadPercent > mDownloadPercent) {
+ mDownloadPercent = downloadPercent;
+
+ LLSD event;
+ event["pump"] = LLUpdaterService::pumpName();
+ LLSD payload;
+ payload["type"] = LLSD(LLUpdaterService::PROGRESS);
+ payload["download_size"] = downloadSize;
+ payload["bytes_downloaded"] = bytesDownloaded;
+ event["payload"] = payload;
+ LLEventPumps::instance().obtain("mainlooprepeater").post(event);
+
+ LL_INFOS("UpdateDownload") << "progress event " << payload << LL_ENDL;
+ } else {
+ ; // Keep events to a reasonalbe number.
+ }
+
+ return 0;
+}
+
+
+void LLUpdateDownloader::Implementation::run(void)
+{
+ CURLcode code = curl_easy_perform(mCurl);
+ mDownloadStream.close();
+ if(code == CURLE_OK) {
+ LLFile::remove(mDownloadRecordPath);
+ if(validateDownload()) {
+ LL_INFOS("UpdateDownload") << "download successful" << LL_ENDL;
+ mClient.downloadComplete(mDownloadData);
+ } else {
+ LL_INFOS("UpdateDownload") << "download failed hash check" << LL_ENDL;
+ std::string filePath = mDownloadData["path"].asString();
+ 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;
+ // Do not call back client.
+ } else {
+ LL_WARNS("UpdateDownload") << "download failed with error '" <<
+ curl_easy_strerror(code) << "'" << LL_ENDL;
+ LLFile::remove(mDownloadRecordPath);
+ if(mDownloadData.has("path")) LLFile::remove(mDownloadData["path"].asString());
+ mClient.downloadError("curl error");
+ }
+
+ if(mHeaderList) {
+ curl_slist_free_all(mHeaderList);
+ mHeaderList = 0;
+ }
+}
+
+
+void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url, bool processHeader)
+{
+ if(mCurl == 0) {
+ mCurl = curl_easy_init();
+ } else {
+ curl_easy_reset(mCurl);
+ }
+
+ 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) {
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERFUNCTION, &header_function));
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERDATA, this));
+ }
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPGET, true));
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_URL, url.c_str()));
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_PROGRESSFUNCTION, &progress_callback));
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_PROGRESSDATA, this));
+ throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOPROGRESS, false));
+ // 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()
+ << " 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");
+ 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();
+}
+
+
+void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std::string const & hash)
+{
+ mDownloadData["url"] = uri.asString();
+ mDownloadData["hash"] = hash;
+ mDownloadData["current_version"] = ll_get_version();
+ LLSD path = uri.pathArray();
+ if(path.size() == 0) throw DownloadError("no file path");
+ std::string fileName = path[path.size() - 1].asString();
+ std::string filePath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, fileName);
+ mDownloadData["path"] = filePath;
+
+ LL_INFOS("UpdateDownload") << "downloading " << filePath
+ << " from " << uri.asString() << LL_ENDL;
+ LL_INFOS("UpdateDownload") << "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();
+}
+
+
+void LLUpdateDownloader::Implementation::throwOnCurlError(CURLcode code)
+{
+ if(code != CURLE_OK) {
+ const char * errorString = curl_easy_strerror(code);
+ if(errorString != 0) {
+ throw DownloadError(curl_easy_strerror(code));
+ } else {
+ throw DownloadError("unknown curl error");
+ }
+ } else {
+ ; // No op.
+ }
+}
+
+
+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;
+
+ std::string hash = mDownloadData["hash"].asString();
+ if(hash.size() != 0) {
+ LL_INFOS("UpdateDownload") << "checking hash..." << LL_ENDL;
+ char digest[33];
+ LLMD5(fileStream).hex_digest(digest);
+ if(hash != digest) {
+ LL_WARNS("UpdateDownload") << "download hash mismatch; expeted " << hash <<
+ " but download is " << digest << LL_ENDL;
+ }
+ return hash == digest;
+ } else {
+ return true; // No hash check provided.
+ }
+}
diff --git a/indra/viewer_components/updater/llupdatedownloader.h b/indra/viewer_components/updater/llupdatedownloader.h
new file mode 100644
index 0000000000..0d635640cf
--- /dev/null
+++ b/indra/viewer_components/updater/llupdatedownloader.h
@@ -0,0 +1,94 @@
+/**
+ * @file llupdatedownloader.h
+ *
+ * $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$
+ */
+
+#ifndef LL_UPDATE_DOWNLOADER_H
+#define LL_UPDATE_DOWNLOADER_H
+
+
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include "lluri.h"
+
+
+//
+// An asynchronous download service for fetching updates.
+//
+class LLUpdateDownloader
+{
+public:
+ class Client;
+ class Implementation;
+
+ // Returns the path to the download marker file containing details of the
+ // latest download.
+ static std::string downloadMarkerPath(void);
+
+ LLUpdateDownloader(Client & client);
+
+ // Cancel any in progress download; a no op if none is in progress. The
+ // client will not receive a complete or error callback.
+ void cancel(void);
+
+ // Start a new download.
+ void download(LLURI const & uri,
+ std::string const & hash,
+ std::string const & updateVersion,
+ bool required=false);
+
+ // Returns true if a download is in progress.
+ bool isDownloading(void);
+
+ // Resume a partial download.
+ void resume(void);
+
+ // Set a limit on the dowload rate.
+ void setBandwidthLimit(U64 bytesPerSecond);
+
+private:
+ boost::shared_ptr<Implementation> mImplementation;
+};
+
+
+//
+// An interface to be implemented by clients initiating a update download.
+//
+class LLUpdateDownloader::Client {
+public:
+
+ // The download has completed successfully.
+ // data is a map containing the following items:
+ // url - source (remote) location
+ // hash - the md5 sum that should match the installer file.
+ // path - destination (local) location
+ // required - boolean indicating if this is a required update.
+ // size - the size of the installer in bytes
+ virtual void downloadComplete(LLSD const & data) = 0;
+
+ // The download failed.
+ virtual void downloadError(std::string const & message) = 0;
+};
+
+
+#endif
diff --git a/indra/viewer_components/updater/llupdateinstaller.cpp b/indra/viewer_components/updater/llupdateinstaller.cpp
new file mode 100644
index 0000000000..c7b70c2de8
--- /dev/null
+++ b/indra/viewer_components/updater/llupdateinstaller.cpp
@@ -0,0 +1,100 @@
+/**
+ * @file llupdateinstaller.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$
+ */
+
+#include "linden_common.h"
+#include <apr_file_io.h>
+#include "llapr.h"
+#include "llprocesslauncher.h"
+#include "llupdateinstaller.h"
+#include "lldir.h"
+
+
+#if defined(LL_WINDOWS)
+#pragma warning(disable: 4702) // disable 'unreachable code' so we can use lexical_cast (really!).
+#endif
+#include <boost/lexical_cast.hpp>
+
+
+namespace {
+ class RelocateError {};
+
+
+ std::string copy_to_temp(std::string const & path)
+ {
+ std::string scriptFile = gDirUtilp->getBaseFileName(path);
+ std::string newPath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, scriptFile);
+ apr_status_t status = apr_file_copy(path.c_str(), newPath.c_str(), APR_FILE_SOURCE_PERMS, gAPRPoolp);
+ if(status != APR_SUCCESS) throw RelocateError();
+
+ return newPath;
+ }
+}
+
+
+int ll_install_update(std::string const & script,
+ std::string const & updatePath,
+ bool required,
+ LLInstallScriptMode mode)
+{
+ std::string actualScriptPath;
+ switch(mode) {
+ case LL_COPY_INSTALL_SCRIPT_TO_TEMP:
+ try {
+ actualScriptPath = copy_to_temp(script);
+ }
+ catch (RelocateError &) {
+ return -1;
+ }
+ break;
+ case LL_RUN_INSTALL_SCRIPT_IN_PLACE:
+ actualScriptPath = script;
+ break;
+ default:
+ llassert(!"unpossible copy mode");
+ }
+
+ llinfos << "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;
+}
+
+
+std::string const & ll_install_failed_marker_path(void)
+{
+ static std::string path;
+ if(path.empty()) {
+ path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLifeInstallFailed.marker");
+ }
+ return path;
+}
diff --git a/indra/viewer_components/updater/llupdateinstaller.h b/indra/viewer_components/updater/llupdateinstaller.h
new file mode 100644
index 0000000000..fe5b1d19b5
--- /dev/null
+++ b/indra/viewer_components/updater/llupdateinstaller.h
@@ -0,0 +1,58 @@
+/**
+ * @file llupdateinstaller.h
+ *
+ * $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$
+ */
+
+#ifndef LL_UPDATE_INSTALLER_H
+#define LL_UPDATE_INSTALLER_H
+
+
+#include <string>
+
+
+enum LLInstallScriptMode {
+ LL_RUN_INSTALL_SCRIPT_IN_PLACE,
+ LL_COPY_INSTALL_SCRIPT_TO_TEMP
+};
+
+//
+// Launch the installation script.
+//
+// The updater will overwrite the current installation, so it is highly recommended
+// that the current application terminate once this function is called.
+//
+int ll_install_update(
+ std::string const & script, // Script to execute.
+ std::string const & updatePath, // Path to update file.
+ bool required, // Is the update required.
+ LLInstallScriptMode mode=LL_COPY_INSTALL_SCRIPT_TO_TEMP); // Run in place or copy to temp?
+
+
+//
+// Returns the path which points to the failed install marker file, should it
+// exist.
+//
+std::string const & ll_install_failed_marker_path(void);
+
+
+#endif
diff --git a/indra/viewer_components/updater/llupdaterservice.cpp b/indra/viewer_components/updater/llupdaterservice.cpp
new file mode 100644
index 0000000000..1888f191e2
--- /dev/null
+++ b/indra/viewer_components/updater/llupdaterservice.cpp
@@ -0,0 +1,621 @@
+/**
+ * @file llupdaterservice.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$
+ */
+
+#include "linden_common.h"
+
+#include "llupdaterservice.h"
+
+#include "llupdatedownloader.h"
+#include "llevents.h"
+#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"
+
+#if LL_WINDOWS
+#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
+#endif
+
+
+namespace
+{
+ boost::weak_ptr<LLUpdaterServiceImpl> gUpdater;
+
+ const std::string UPDATE_MARKER_FILENAME("SecondLifeUpdateReady.xml");
+ std::string update_marker_path()
+ {
+ return gDirUtilp->getExpandedFilename(LL_PATH_LOGS,
+ UPDATE_MARKER_FILENAME);
+ }
+
+ std::string install_script_path(void)
+ {
+#ifdef LL_WINDOWS
+ std::string scriptFile = "update_install.bat";
+#else
+ std::string scriptFile = "update_install";
+#endif
+ return gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, scriptFile);
+ }
+
+ LLInstallScriptMode install_script_mode(void)
+ {
+#ifdef LL_WINDOWS
+ return LL_COPY_INSTALL_SCRIPT_TO_TEMP;
+#else
+ return LL_RUN_INSTALL_SCRIPT_IN_PLACE;
+#endif
+ };
+
+}
+
+class LLUpdaterServiceImpl :
+ public LLUpdateChecker::Client,
+ public LLUpdateDownloader::Client
+{
+ static const std::string sListenerName;
+
+ std::string mProtocolVersion;
+ std::string mUrl;
+ std::string mPath;
+ std::string mChannel;
+ std::string mVersion;
+
+ unsigned int mCheckPeriod;
+ bool mIsChecking;
+ bool mIsDownloading;
+
+ LLUpdateChecker mUpdateChecker;
+ LLUpdateDownloader mUpdateDownloader;
+ LLTimer mTimer;
+
+ LLUpdaterService::app_exit_callback_t mAppExitCallback;
+
+ LLUpdaterService::eUpdaterState mState;
+
+ LOG_CLASS(LLUpdaterServiceImpl);
+
+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 setCheckPeriod(unsigned int seconds);
+ void setBandwidthLimit(U64 bytesPerSecond);
+
+ void startChecking(bool install_if_ready);
+ void stopChecking();
+ bool isChecking();
+ LLUpdaterService::eUpdaterState getState();
+
+ void setAppExitCallback(LLUpdaterService::app_exit_callback_t aecb) { mAppExitCallback = aecb;}
+ std::string updatedVersion(void);
+
+ bool checkForInstall(bool launchInstaller); // Test if a local install is ready.
+ bool checkForResume(); // Test for resumeable d/l.
+
+ // 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);
+
+ // LLUpdateDownloader::Client
+ void downloadComplete(LLSD const & data);
+ void downloadError(std::string const & message);
+
+ bool onMainLoop(LLSD const & event);
+
+private:
+ std::string mNewVersion;
+
+ void restartTimer(unsigned int seconds);
+ void setState(LLUpdaterService::eUpdaterState state);
+ void stopTimer();
+};
+
+const std::string LLUpdaterServiceImpl::sListenerName = "LLUpdaterServiceImpl";
+
+LLUpdaterServiceImpl::LLUpdaterServiceImpl() :
+ mIsChecking(false),
+ mIsDownloading(false),
+ mCheckPeriod(0),
+ mUpdateChecker(*this),
+ mUpdateDownloader(*this),
+ mState(LLUpdaterService::INITIAL)
+{
+}
+
+LLUpdaterServiceImpl::~LLUpdaterServiceImpl()
+{
+ LL_INFOS("UpdaterService") << "shutting down updater service" << LL_ENDL;
+ 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)
+{
+ if(mIsChecking || mIsDownloading)
+ {
+ throw LLUpdaterService::UsageError("LLUpdaterService::initialize call "
+ "while updater is running.");
+ }
+
+ mProtocolVersion = protocol_version;
+ mUrl = url;
+ mPath = path;
+ mChannel = channel;
+ mVersion = version;
+}
+
+void LLUpdaterServiceImpl::setCheckPeriod(unsigned int seconds)
+{
+ mCheckPeriod = seconds;
+}
+
+void LLUpdaterServiceImpl::setBandwidthLimit(U64 bytesPerSecond)
+{
+ mUpdateDownloader.setBandwidthLimit(bytesPerSecond);
+}
+
+void LLUpdaterServiceImpl::startChecking(bool install_if_ready)
+{
+ if(mUrl.empty() || mChannel.empty() || mVersion.empty())
+ {
+ throw LLUpdaterService::UsageError("Set params before call to "
+ "LLUpdaterService::startCheck().");
+ }
+
+ mIsChecking = true;
+
+ // Check to see if an install is ready.
+ bool has_install = checkForInstall(install_if_ready);
+ if(!has_install)
+ {
+ checkForResume(); // will set mIsDownloading to true if resuming
+
+ if(!mIsDownloading)
+ {
+ setState(LLUpdaterService::CHECKING_FOR_UPDATE);
+
+ // Checking can only occur during the mainloop.
+ // reset the timer to 0 so that the next mainloop event
+ // triggers a check;
+ restartTimer(0);
+ }
+ else
+ {
+ setState(LLUpdaterService::DOWNLOADING);
+ }
+ }
+}
+
+void LLUpdaterServiceImpl::stopChecking()
+{
+ if(mIsChecking)
+ {
+ mIsChecking = false;
+ stopTimer();
+ }
+
+ if(mIsDownloading)
+ {
+ mUpdateDownloader.cancel();
+ mIsDownloading = false;
+ }
+
+ setState(LLUpdaterService::TERMINAL);
+}
+
+bool LLUpdaterServiceImpl::isChecking()
+{
+ return mIsChecking;
+}
+
+LLUpdaterService::eUpdaterState LLUpdaterServiceImpl::getState()
+{
+ return mState;
+}
+
+std::string LLUpdaterServiceImpl::updatedVersion(void)
+{
+ return mNewVersion;
+}
+
+bool LLUpdaterServiceImpl::checkForInstall(bool launchInstaller)
+{
+ bool foundInstall = false; // return true if install is found.
+
+ llifstream update_marker(update_marker_path(),
+ std::ios::in | std::ios::binary);
+
+ if(update_marker.is_open())
+ {
+ // Found an update info - now lets see if its valid.
+ LLSD update_info;
+ LLSDSerialize::fromXMLDocument(update_info, update_marker);
+ update_marker.close();
+
+ // Get the path to the installer file.
+ LLSD path = update_info.get("path");
+ if(update_info["current_version"].asString() != ll_get_version())
+ {
+ // This viewer is not the same version as the one that downloaded
+ // the update. Do not install this update.
+ if(!path.asString().empty())
+ {
+ llinfos << "ignoring update dowloaded by different client version" << llendl;
+ LLFile::remove(path.asString());
+ LLFile::remove(update_marker_path());
+ }
+ else
+ {
+ ; // Nothing to clean up.
+ }
+
+ foundInstall = false;
+ }
+ else if(path.isDefined() && !path.asString().empty())
+ {
+ if(launchInstaller)
+ {
+ setState(LLUpdaterService::INSTALLING);
+
+ LLFile::remove(update_marker_path());
+
+ int result = ll_install_update(install_script_path(),
+ update_info["path"].asString(),
+ update_info["required"].asBoolean(),
+ install_script_mode());
+
+ if((result == 0) && mAppExitCallback)
+ {
+ mAppExitCallback();
+ } else if(result != 0) {
+ llwarns << "failed to run update install script" << LL_ENDL;
+ } else {
+ ; // No op.
+ }
+ }
+
+ foundInstall = true;
+ }
+ }
+ return foundInstall;
+}
+
+bool LLUpdaterServiceImpl::checkForResume()
+{
+ bool result = false;
+ std::string download_marker_path = mUpdateDownloader.downloadMarkerPath();
+ if(LLFile::isfile(download_marker_path))
+ {
+ llifstream download_marker_stream(download_marker_path,
+ std::ios::in | std::ios::binary);
+ if(download_marker_stream.is_open())
+ {
+ LLSD download_info;
+ LLSDSerialize::fromXMLDocument(download_info, download_marker_stream);
+ download_marker_stream.close();
+ if(download_info["current_version"].asString() == ll_get_version())
+ {
+ mIsDownloading = true;
+ mNewVersion = download_info["update_version"].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;
+ std::string path = download_info["path"].asString();
+ if(!path.empty()) LLFile::remove(path);
+ LLFile::remove(download_marker_path);
+ }
+ }
+ }
+ return result;
+}
+
+void LLUpdaterServiceImpl::error(std::string const & message)
+{
+ if(mIsChecking)
+ {
+ setState(LLUpdaterService::TEMPORARY_ERROR);
+ restartTimer(mCheckPeriod);
+ }
+}
+
+void LLUpdaterServiceImpl::optionalUpdate(std::string const & newVersion,
+ LLURI const & uri,
+ std::string const & hash)
+{
+ 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)
+ {
+ restartTimer(mCheckPeriod);
+ }
+
+ setState(LLUpdaterService::UP_TO_DATE);
+}
+
+void LLUpdaterServiceImpl::downloadComplete(LLSD const & data)
+{
+ mIsDownloading = false;
+
+ // Save out the download data to the SecondLifeUpdateReady
+ // marker file.
+ llofstream update_marker(update_marker_path());
+ LLSDSerialize::toPrettyXML(data, update_marker);
+
+ LLSD event;
+ event["pump"] = LLUpdaterService::pumpName();
+ LLSD payload;
+ payload["type"] = LLSD(LLUpdaterService::DOWNLOAD_COMPLETE);
+ payload["required"] = data["required"];
+ payload["version"] = mNewVersion;
+ event["payload"] = payload;
+ LLEventPumps::instance().obtain("mainlooprepeater").post(event);
+
+ setState(LLUpdaterService::TERMINAL);
+}
+
+void LLUpdaterServiceImpl::downloadError(std::string const & message)
+{
+ LL_INFOS("UpdaterService") << "Error downloading: " << message << LL_ENDL;
+
+ mIsDownloading = false;
+
+ // Restart the timer on error
+ if(mIsChecking)
+ {
+ restartTimer(mCheckPeriod);
+ }
+
+ LLSD event;
+ event["pump"] = LLUpdaterService::pumpName();
+ LLSD payload;
+ payload["type"] = LLSD(LLUpdaterService::DOWNLOAD_ERROR);
+ payload["message"] = message;
+ event["payload"] = payload;
+ LLEventPumps::instance().obtain("mainlooprepeater").post(event);
+
+ setState(LLUpdaterService::FAILURE);
+}
+
+void LLUpdaterServiceImpl::restartTimer(unsigned int seconds)
+{
+ LL_INFOS("UpdaterService") << "will check for update again in " <<
+ seconds << " seconds" << LL_ENDL;
+ mTimer.start();
+ mTimer.setTimerExpirySec(seconds);
+ LLEventPumps::instance().obtain("mainloop").listen(
+ sListenerName, boost::bind(&LLUpdaterServiceImpl::onMainLoop, this, _1));
+}
+
+void LLUpdaterServiceImpl::setState(LLUpdaterService::eUpdaterState state)
+{
+ if(state != mState)
+ {
+ mState = state;
+
+ LLSD event;
+ event["pump"] = LLUpdaterService::pumpName();
+ LLSD payload;
+ payload["type"] = LLSD(LLUpdaterService::STATE_CHANGE);
+ payload["state"] = state;
+ event["payload"] = payload;
+ LLEventPumps::instance().obtain("mainlooprepeater").post(event);
+
+ LL_INFOS("UpdaterService") << "setting state to " << state << LL_ENDL;
+ }
+ else
+ {
+ ; // State unchanged; noop.
+ }
+}
+
+void LLUpdaterServiceImpl::stopTimer()
+{
+ mTimer.stop();
+ LLEventPumps::instance().obtain("mainloop").stopListening(sListenerName);
+}
+
+bool LLUpdaterServiceImpl::onMainLoop(LLSD const & event)
+{
+ if(mTimer.getStarted() && mTimer.hasExpired())
+ {
+ stopTimer();
+
+ // Check for failed install.
+ if(LLFile::isfile(ll_install_failed_marker_path()))
+ {
+ int requiredValue = 0;
+ {
+ llifstream stream(ll_install_failed_marker_path());
+ stream >> requiredValue;
+ if(stream.fail()) requiredValue = 0;
+ }
+ // TODO: notify the user.
+ llinfos << "found marker " << ll_install_failed_marker_path() << llendl;
+ llinfos << "last install attempt failed" << llendl;
+ LLFile::remove(ll_install_failed_marker_path());
+
+ LLSD event;
+ event["type"] = LLSD(LLUpdaterService::INSTALL_ERROR);
+ event["required"] = LLSD(requiredValue);
+ LLEventPumps::instance().obtain(LLUpdaterService::pumpName()).post(event);
+
+ setState(LLUpdaterService::TERMINAL);
+ }
+ else
+ {
+ mUpdateChecker.check(mProtocolVersion, mUrl, mPath, mChannel, mVersion);
+ setState(LLUpdaterService::CHECKING_FOR_UPDATE);
+ }
+ }
+ else
+ {
+ // Keep on waiting...
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------
+// Facade interface
+
+std::string const & LLUpdaterService::pumpName(void)
+{
+ static std::string name("updater_service");
+ return name;
+}
+
+bool LLUpdaterService::updateReadyToInstall(void)
+{
+ return LLFile::isfile(update_marker_path());
+}
+
+LLUpdaterService::LLUpdaterService()
+{
+ if(gUpdater.expired())
+ {
+ mImpl =
+ boost::shared_ptr<LLUpdaterServiceImpl>(new LLUpdaterServiceImpl());
+ gUpdater = mImpl;
+ }
+ else
+ {
+ mImpl = gUpdater.lock();
+ }
+}
+
+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)
+{
+ mImpl->initialize(protocol_version, url, path, channel, version);
+}
+
+void LLUpdaterService::setCheckPeriod(unsigned int seconds)
+{
+ mImpl->setCheckPeriod(seconds);
+}
+
+void LLUpdaterService::setBandwidthLimit(U64 bytesPerSecond)
+{
+ mImpl->setBandwidthLimit(bytesPerSecond);
+}
+
+void LLUpdaterService::startChecking(bool install_if_ready)
+{
+ mImpl->startChecking(install_if_ready);
+}
+
+void LLUpdaterService::stopChecking()
+{
+ mImpl->stopChecking();
+}
+
+bool LLUpdaterService::isChecking()
+{
+ return mImpl->isChecking();
+}
+
+LLUpdaterService::eUpdaterState LLUpdaterService::getState()
+{
+ return mImpl->getState();
+}
+
+void LLUpdaterService::setImplAppExitCallback(LLUpdaterService::app_exit_callback_t aecb)
+{
+ return mImpl->setAppExitCallback(aecb);
+}
+
+std::string LLUpdaterService::updatedVersion(void)
+{
+ return mImpl->updatedVersion();
+}
+
+
+std::string const & ll_get_version(void) {
+ static std::string version("");
+
+ if (version.empty()) {
+ std::ostringstream stream;
+ stream << LL_VERSION_MAJOR << "."
+ << LL_VERSION_MINOR << "."
+ << LL_VERSION_PATCH << "."
+ << LL_VERSION_BUILD;
+ version = stream.str();
+ }
+
+ return version;
+}
+
diff --git a/indra/viewer_components/updater/llupdaterservice.h b/indra/viewer_components/updater/llupdaterservice.h
new file mode 100644
index 0000000000..450f19c1c6
--- /dev/null
+++ b/indra/viewer_components/updater/llupdaterservice.h
@@ -0,0 +1,108 @@
+/**
+ * @file llupdaterservice.h
+ *
+ * $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$
+ */
+
+#ifndef LL_UPDATERSERVICE_H
+#define LL_UPDATERSERVICE_H
+
+#include <boost/shared_ptr.hpp>
+#include <boost/function.hpp>
+
+class LLUpdaterServiceImpl;
+
+class LLUpdaterService
+{
+public:
+ class UsageError: public std::runtime_error
+ {
+ public:
+ UsageError(const std::string& msg) : std::runtime_error(msg) {}
+ };
+
+ // Name of the event pump through which update events will be delivered.
+ static std::string const & pumpName(void);
+
+ // Returns true if an update has been completely downloaded and is now ready to install.
+ static bool updateReadyToInstall(void);
+
+ // Type codes for events posted by this service. Stored the event's 'type' element.
+ enum eUpdaterEvent {
+ INVALID,
+ DOWNLOAD_COMPLETE,
+ DOWNLOAD_ERROR,
+ INSTALL_ERROR,
+ PROGRESS,
+ STATE_CHANGE
+ };
+
+ enum eUpdaterState {
+ INITIAL,
+ CHECKING_FOR_UPDATE,
+ TEMPORARY_ERROR,
+ DOWNLOADING,
+ INSTALLING,
+ UP_TO_DATE,
+ TERMINAL,
+ FAILURE
+ };
+
+ 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 setCheckPeriod(unsigned int seconds);
+ void setBandwidthLimit(U64 bytesPerSecond);
+
+ void startChecking(bool install_if_ready = false);
+ void stopChecking();
+ bool isChecking();
+ eUpdaterState getState();
+
+ typedef boost::function<void (void)> app_exit_callback_t;
+ template <typename F>
+ void setAppExitCallback(F const &callable)
+ {
+ app_exit_callback_t aecb = callable;
+ setImplAppExitCallback(aecb);
+ }
+
+ // If an update is or has been downloaded, this method will return the
+ // version string for that update. An empty string will be returned
+ // otherwise.
+ std::string updatedVersion(void);
+
+private:
+ boost::shared_ptr<LLUpdaterServiceImpl> mImpl;
+ void setImplAppExitCallback(app_exit_callback_t aecb);
+};
+
+// Returns the full version as a string.
+std::string const & ll_get_version(void);
+
+#endif // LL_UPDATERSERVICE_H
diff --git a/indra/viewer_components/updater/scripts/darwin/update_install b/indra/viewer_components/updater/scripts/darwin/update_install
new file mode 100644
index 0000000000..6a95f96d86
--- /dev/null
+++ b/indra/viewer_components/updater/scripts/darwin/update_install
@@ -0,0 +1,10 @@
+#! /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 2"; if [ $? -ne 0 ]; then echo $3 >> "$2"; fi;) &
+exit 0
diff --git a/indra/viewer_components/updater/scripts/linux/update_install b/indra/viewer_components/updater/scripts/linux/update_install
new file mode 100644
index 0000000000..88451340ec
--- /dev/null
+++ b/indra/viewer_components/updater/scripts/linux/update_install
@@ -0,0 +1,10 @@
+#! /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 2" --stringsdir "$INSTALL_DIR/skins/default/xui/en" --stringsfile "strings.xml"
+
+if [ $? -ne 0 ]
+ then echo $3 >> "$2"
+fi
+
+rm -f "$1"
diff --git a/indra/viewer_components/updater/scripts/windows/update_install.bat b/indra/viewer_components/updater/scripts/windows/update_install.bat
new file mode 100644
index 0000000000..96687226a8
--- /dev/null
+++ b/indra/viewer_components/updater/scripts/windows/update_install.bat
@@ -0,0 +1,3 @@
+start /WAIT %1 /SKIP_DIALOGS
+IF ERRORLEVEL 1 ECHO %3 > %2
+DEL %1
diff --git a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
new file mode 100644
index 0000000000..e19d5724f1
--- /dev/null
+++ b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
@@ -0,0 +1,194 @@
+/**
+ * @file llupdaterservice_test.cpp
+ * @brief Tests of llupdaterservice.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$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "../llupdaterservice.h"
+#include "../llupdatechecker.h"
+#include "../llupdatedownloader.h"
+#include "../llupdateinstaller.h"
+
+#include "../../../test/lltut.h"
+//#define DEBUG_ON
+#include "../../../test/debug.h"
+
+#include "llevents.h"
+#include "lldir.h"
+
+/*****************************************************************************
+* MOCK'd
+*****************************************************************************/
+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)
+{}
+LLUpdateDownloader::LLUpdateDownloader(Client & ) {}
+void LLUpdateDownloader::download(LLURI const & , std::string const &, std::string const &, bool){}
+
+class LLDir_Mock : public LLDir
+{
+ void initAppDirs(const std::string &app_name,
+ const std::string& app_read_only_data_dir = "") {}
+ U32 countFilesInDir(const std::string &dirname, const std::string &mask)
+ {
+ return 0;
+ }
+
+ void getRandomFileInDir(const std::string &dirname,
+ const std::string &mask,
+ std::string &fname) {}
+ std::string getCurPath() { return ""; }
+ BOOL fileExists(const std::string &filename) const { return false; }
+ std::string getLLPluginLauncher() { return ""; }
+ std::string getLLPluginFilename(std::string base_name) { return ""; }
+
+} gDirUtil;
+LLDir* gDirUtilp = &gDirUtil;
+LLDir::LLDir() {}
+LLDir::~LLDir() {}
+S32 LLDir::deleteFilesInDir(const std::string &dirname,
+ const std::string &mask)
+{ return 0; }
+
+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){}
+bool LLDir::setCacheDir(const std::string &path){ return true; }
+void LLDir::dumpCurrentDirectories() {}
+
+std::string LLDir::getExpandedFilename(ELLPath location,
+ const std::string &filename) const
+{
+ return "";
+}
+
+std::string LLUpdateDownloader::downloadMarkerPath(void)
+{
+ return "";
+}
+
+void LLUpdateDownloader::resume(void) {}
+void LLUpdateDownloader::cancel(void) {}
+void LLUpdateDownloader::setBandwidthLimit(U64 bytesPerSecond) {}
+
+int ll_install_update(std::string const &, std::string const &, bool, LLInstallScriptMode)
+{
+ return 0;
+}
+
+std::string const & ll_install_failed_marker_path()
+{
+ static std::string wubba;
+ return wubba;
+}
+
+/*
+#pragma warning(disable: 4273)
+llus_mock_llifstream::llus_mock_llifstream(const std::string& _Filename,
+ ios_base::openmode _Mode,
+ int _Prot) :
+ std::basic_istream<char,std::char_traits< char > >(NULL,true)
+{}
+
+llus_mock_llifstream::~llus_mock_llifstream() {}
+bool llus_mock_llifstream::is_open() const {return true;}
+void llus_mock_llifstream::close() {}
+*/
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llupdaterservice_data
+ {
+ llupdaterservice_data() :
+ pumps(LLEventPumps::instance()),
+ test_url("dummy_url"),
+ test_channel("dummy_channel"),
+ test_version("dummy_version")
+ {}
+ LLEventPumps& pumps;
+ std::string test_url;
+ std::string test_channel;
+ std::string test_version;
+ };
+
+ typedef test_group<llupdaterservice_data> llupdaterservice_group;
+ typedef llupdaterservice_group::object llupdaterservice_object;
+ llupdaterservice_group llupdaterservicegrp("LLUpdaterService");
+
+ template<> template<>
+ void llupdaterservice_object::test<1>()
+ {
+ DEBUG;
+ LLUpdaterService updater;
+ bool got_usage_error = false;
+ try
+ {
+ updater.startChecking();
+ }
+ catch(LLUpdaterService::UsageError)
+ {
+ got_usage_error = true;
+ }
+ ensure("Caught start before params", got_usage_error);
+ }
+
+ template<> template<>
+ void llupdaterservice_object::test<2>()
+ {
+ DEBUG;
+ LLUpdaterService updater;
+ bool got_usage_error = false;
+ try
+ {
+ updater.initialize("1.0",test_url, "update" ,test_channel, test_version);
+ updater.startChecking();
+ updater.initialize("1.0", "other_url", "update", test_channel, test_version);
+ }
+ catch(LLUpdaterService::UsageError)
+ {
+ got_usage_error = true;
+ }
+ ensure("Caught params while running", got_usage_error);
+ }
+
+ template<> template<>
+ void llupdaterservice_object::test<3>()
+ {
+ DEBUG;
+ LLUpdaterService updater;
+ updater.initialize("1.0", test_url, "update", test_channel, test_version);
+ updater.startChecking();
+ ensure(updater.isChecking());
+ updater.stopChecking();
+ ensure(!updater.isChecking());
+ }
+}