summaryrefslogtreecommitdiff
path: root/indra/viewer_components
diff options
context:
space:
mode:
Diffstat (limited to 'indra/viewer_components')
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/CMakeLists.txt0
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/login/CMakeLists.txt8
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/login/lllogin.cpp251
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/login/lllogin.h0
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/login/tests/lllogin_test.cpp191
-rw-r--r--indra/viewer_components/manager/InstallerUserMessage.py299
-rwxr-xr-xindra/viewer_components/manager/SL_Launcher95
-rwxr-xr-xindra/viewer_components/manager/apply_update.py277
-rwxr-xr-xindra/viewer_components/manager/download_update.py103
-rwxr-xr-xindra/viewer_components/manager/update_manager.py455
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/updater/CMakeLists.txt28
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/updater/llupdatechecker.cpp89
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/updater/llupdatechecker.h121
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/updater/llupdatedownloader.cpp77
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/updater/llupdatedownloader.h0
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/updater/llupdateinstaller.cpp0
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/updater/llupdateinstaller.h0
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/updater/llupdaterservice.cpp0
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/updater/llupdaterservice.h0
-rw-r--r--[-rwxr-xr-x]indra/viewer_components/updater/tests/llupdaterservice_test.cpp0
20 files changed, 1492 insertions, 502 deletions
diff --git a/indra/viewer_components/CMakeLists.txt b/indra/viewer_components/CMakeLists.txt
index 74c9b4568d..74c9b4568d 100755..100644
--- a/indra/viewer_components/CMakeLists.txt
+++ b/indra/viewer_components/CMakeLists.txt
diff --git a/indra/viewer_components/login/CMakeLists.txt b/indra/viewer_components/login/CMakeLists.txt
index c152f7c0a6..3bedeb7292 100755..100644
--- a/indra/viewer_components/login/CMakeLists.txt
+++ b/indra/viewer_components/login/CMakeLists.txt
@@ -10,6 +10,8 @@ include(LLCommon)
include(LLMath)
include(LLXML)
include(Boost)
+include(LLCoreHttp)
+include(LLMessage)
include_directories(
${LLCOMMON_INCLUDE_DIRS}
@@ -42,12 +44,14 @@ add_library(lllogin
)
target_link_libraries(lllogin
+ ${LLMESSAGE_LIBRARIES}
+ ${LLCOREHTTP_LIBRARIES}
${LLCOMMON_LIBRARIES}
${LLMATH_LIBRARIES}
${LLXML_LIBRARIES}
- ${BOOST_CONTEXT_LIBRARY}
${BOOST_THREAD_LIBRARY}
${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_CONTEXT_LIBRARY}
${BOOST_SYSTEM_LIBRARY}
)
@@ -58,7 +62,7 @@ if(LL_TESTS)
set_source_files_properties(
lllogin.cpp
PROPERTIES
- LL_TEST_ADDITIONAL_LIBRARIES "${BOOST_COROUTINE_LIBRARY};${BOOST_CONTEXT_LIBRARY};${BOOST_THREAD_LIBRARY};${BOOST_SYSTEM_LIBRARY}"
+ LL_TEST_ADDITIONAL_LIBRARIES "${LLMESSAGE_LIBRARIES};${LLCOREHTTP_LIBRARIES};${LLCOMMON_LIBRARIES};${BOOST_COROUTINE_LIBRARY};${BOOST_CONTEXT_LIBRARY};${BOOST_THREAD_LIBRARY};${BOOST_SYSTEM_LIBRARY}"
)
LL_ADD_PROJECT_UNIT_TESTS(lllogin "${lllogin_TEST_SOURCE_FILES}")
diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp
index b8408a6fb4..53d4acc9e0 100755..100644
--- a/indra/viewer_components/login/lllogin.cpp
+++ b/indra/viewer_components/login/lllogin.cpp
@@ -107,9 +107,8 @@ private:
}
// 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);
+ // references (const or otherwise) to anything! Pass by value only!
+ void loginCoro(std::string uri, LLSD credentials);
LLEventStream mPump;
LLSD mAuthResponse, mValidAuthResponse;
@@ -123,11 +122,11 @@ void LLLogin::Impl::connect(const std::string& uri, const LLSD& login_params)
// 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));
+ boost::bind(&Impl::loginCoro, this, 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)
+void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params)
{
try
{
@@ -137,50 +136,13 @@ void LLLogin::Impl::login_(LLCoros::self& self, std::string uri, LLSD login_para
//{
// printable_params["params"]["passwd"] = "*******";
//}
- LL_DEBUGS("LLLogin") << "Entering coroutine " << LLCoros::instance().getName(self)
+ LL_DEBUGS("LLLogin") << "Entering coroutine " << LLCoros::instance().getName()
<< " 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
@@ -191,107 +153,103 @@ void LLLogin::Impl::login_(LLCoros::self& self, std::string uri, LLSD login_para
// 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"] = uri;
+ std::string status;
+
+ // Loop back to here if login attempt redirects to a different
+ // request["uri"]
+ for (;;)
{
- 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 postAndSuspend() 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(),
+ llcoro::postAndSuspend(request, xmlrpcPump, loginReplyPump, "reply"));
+ mAuthResponse["status"].asString() == "Downloading";
+ mAuthResponse = validateResponse(loginReplyPump.getName(),
+ llcoro::suspendUntilEventOn(loginReplyPump)))
{
- ++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");
- }
+ // 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")
+ 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"))
{
- // 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!
+ break;
}
- /* Sometimes we end with "Started" here. Slightly slow server?
- * Seems to be ok to just skip it. Otherwise we'd error out and crash in the if below.
- */
- if( status == "Started")
- {
- LL_DEBUGS("LLLogin") << mAuthResponse << LL_ENDL;
- continue;
- }
+ sendProgressEvent("offline", "indeterminate", mAuthResponse["responses"]);
- // If we don't recognize status at all, trouble
- if (! (status == "CURLError"
- || status == "XMLRPCError"
- || status == "OtherError"))
+ // 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
{
- LL_ERRS("LLLogin") << "Unexpected status from " << xmlrpcPump.getName() << " pump: "
- << mAuthResponse << LL_ENDL;
- return;
+ sendProgressEvent("offline", "fail.login", mAuthResponse["responses"]);
}
+ return; // Done!
+ }
- // Here status IS one of the errors tested above.
- } // Retry if there are any more rewrittenURIs.
+// /* Sometimes we end with "Started" here. Slightly slow server?
+// * Seems to be ok to just skip it. Otherwise we'd error out and crash in the if below.
+// */
+// if( status == "Started")
+// {
+// LL_DEBUGS("LLLogin") << mAuthResponse << LL_ENDL;
+// continue;
+// }
+
+ // 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.
// Here we got through all the rewrittenURIs without succeeding. Tell
// caller this didn't work out so well. Of course, the only failure data
@@ -352,29 +310,8 @@ LLEventPump& LLLogin::getEventPump()
// 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;
+// Setup login
+// State_LOGIN_AUTH_INIT
// Authenticate
// STATE_LOGIN_AUTHENTICATE
diff --git a/indra/viewer_components/login/lllogin.h b/indra/viewer_components/login/lllogin.h
index 051641ff59..051641ff59 100755..100644
--- a/indra/viewer_components/login/lllogin.h
+++ b/indra/viewer_components/login/lllogin.h
diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp
index 58bf371a04..e96c495446 100755..100644
--- a/indra/viewer_components/login/tests/lllogin_test.cpp
+++ b/indra/viewer_components/login/tests/lllogin_test.cpp
@@ -96,61 +96,6 @@ public:
}
};
-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;
@@ -232,16 +177,12 @@ namespace tut
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.
+ // Testing login with an immediate response from XMLPRC
+ // The response 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);
@@ -266,109 +207,12 @@ namespace tut
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);
@@ -383,10 +227,6 @@ namespace tut
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
@@ -403,19 +243,15 @@ namespace tut
}
template<> template<>
- void llviewerlogin_object::test<4>()
+ void llviewerlogin_object::test<3>()
{
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);
@@ -430,10 +266,6 @@ namespace tut
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
@@ -449,17 +281,13 @@ namespace tut
}
template<> template<>
- void llviewerlogin_object::test<5>()
+ void llviewerlogin_object::test<4>()
{
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");
@@ -473,26 +301,15 @@ namespace tut
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/manager/InstallerUserMessage.py b/indra/viewer_components/manager/InstallerUserMessage.py
new file mode 100644
index 0000000000..f66af81d06
--- /dev/null
+++ b/indra/viewer_components/manager/InstallerUserMessage.py
@@ -0,0 +1,299 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $LicenseInfo:firstyear=2013&license=viewerlgpl$
+# Copyright (c) 2013, Linden Research, Inc.
+# $/LicenseInfo$
+
+"""
+@file InstallerUserMessage.py
+@author coyot
+@date 2016-05-16
+"""
+
+"""
+This does everything the old updater/scripts/darwin/messageframe.py script did and some more bits.
+Pushed up the manager directory to be multiplatform.
+"""
+
+import os
+import Queue
+import threading
+import time
+import Tkinter as tk
+import ttk
+
+
+class InstallerUserMessage(tk.Tk):
+ #Goals for this class:
+ # Provide a uniform look and feel
+ # Provide an easy to use convenience class for other scripts
+ # Provide windows that automatically disappear when done (for differing notions of done)
+ # Provide a progress bar that isn't a glorified spinner, but based on download progress
+ #Non-goals:
+ # No claim to threadsafety is made or warranted. Your mileage may vary.
+ # Please consult a doctor if you experience thread pain.
+
+ #Linden standard green color, from Marketing
+ linden_green = "#487A7B"
+
+ def __init__(self, text="", title="", width=500, height=200, icon_name = None, icon_path = None):
+ tk.Tk.__init__(self)
+ self.grid()
+ self.title(title)
+ self.choice = tk.BooleanVar()
+ self.config(background = 'black')
+ # background="..." doesn't work on MacOS for radiobuttons or progress bars
+ # http://tinyurl.com/tkmacbuttons
+ ttk.Style().configure('Linden.TLabel', foreground=InstallerUserMessage.linden_green, background='black')
+ ttk.Style().configure('Linden.TButton', foreground=InstallerUserMessage.linden_green, background='black')
+ ttk.Style().configure("black.Horizontal.TProgressbar", foreground=InstallerUserMessage.linden_green, background='black')
+
+ #This bit of configuration centers the window on the screen
+ # The constants below are to adjust for typical overhead from the
+ # frame borders.
+ self.xp = (self.winfo_screenwidth() / 2) - (width / 2) - 8
+ self.yp = (self.winfo_screenheight() / 2) - (height / 2) - 20
+ self.geometry('{0}x{1}+{2}+{3}'.format(width, height, self.xp, self.yp))
+
+ #find a few things
+ self.script_dir = os.path.dirname(os.path.realpath(__file__))
+ self.icon_dir = os.path.abspath(os.path.join(self.script_dir, 'icons'))
+
+ #finds the icon and creates the widget
+ self.find_icon(icon_path, icon_name)
+
+ #defines what to do when window is closed
+ self.protocol("WM_DELETE_WINDOW", self._delete_window)
+
+ def _delete_window(self):
+ #capture and discard all destroy events before the choice is set
+ if not ((self.choice == None) or (self.choice == "")):
+ try:
+ self.destroy()
+ except:
+ #tk may try to destroy the same object twice
+ pass
+
+ def set_colors(self, widget):
+ # #487A7B is "Linden Green"
+ widget.config(foreground = InstallerUserMessage.linden_green)
+ widget.config(background='black')
+
+ def find_icon(self, icon_path = None, icon_name = None):
+ #we do this in each message, let's do it just once instead.
+ if not icon_path:
+ icon_path = self.icon_dir
+ icon_path = os.path.join(icon_path, icon_name)
+ if os.path.exists(icon_path):
+ icon = tk.PhotoImage(file=icon_path)
+ self.image_label = tk.Label(image = icon)
+ self.image_label.image = icon
+ else:
+ #default to text if image not available
+ self.image_label = tk.Label(text = "Second Life")
+
+ def auto_resize(self, row_count = 0, column_count = 0, heavy_row = None, heavy_column = None):
+ #auto resize window to fit all rows and columns
+ #"heavy" gets extra weight
+ for x in range(column_count):
+ if x == heavy_column:
+ self.columnconfigure(x, weight = 2)
+ else:
+ self.columnconfigure(x, weight=1)
+
+ for y in range(row_count):
+ if y == heavy_row:
+ self.rowconfigure(y, weight = 2)
+ else:
+ self.rowconfigure(x, weight=1)
+
+ def basic_message(self, message):
+ #message: text to be displayed
+ #icon_path: directory holding the icon, defaults to icons subdir of script dir
+ #icon_name: filename of icon to be displayed
+ self.choice.set(True)
+ self.text_label = tk.Label(text = message)
+ self.set_colors(self.text_label)
+ self.set_colors(self.image_label)
+ #pad, direction and weight are all experimentally derived by retrying various values
+ self.image_label.grid(row = 1, column = 1, sticky = 'W')
+ self.text_label.grid(row = 1, column = 2, sticky = 'W', padx =100)
+ self.auto_resize(row_count = 1, column_count = 2)
+ self.mainloop()
+
+ def binary_choice_message(self, message, true = 'Yes', false = 'No'):
+ #true: first option, returns True
+ #false: second option, returns False
+ #usage is kind of opaque and relies on this object persisting after the window destruction to pass back choice
+ #usage:
+ # frame = InstallerUserMessage.InstallerUserMessage( ... )
+ # frame = frame.binary_choice_message( ... )
+ # (wait for user to click)
+ # value = frame.choice.get()
+
+ self.text_label = tk.Label(text = message)
+ #command registers the callback to the method named. We want the frame to go away once clicked.
+ #button 1 returns True/1, button 2 returns False/0
+ self.button_one = ttk.Radiobutton(text = true, variable = self.choice, value = True,
+ command = self._delete_window, style = 'Linden.TButton')
+ self.button_two = ttk.Radiobutton(text = false, variable = self.choice, value = False,
+ command = self._delete_window, style = 'Linden.TButton')
+ self.set_colors(self.text_label)
+ self.set_colors(self.image_label)
+ #pad, direction and weight are all experimentally derived by retrying various values
+ self.image_label.grid(row = 1, column = 1, rowspan = 3, sticky = 'W')
+ self.text_label.grid(row = 1, column = 2, rowspan = 3)
+ self.button_one.grid(row = 1, column = 3, sticky = 'W', pady = 40)
+ self.button_two.grid(row = 2, column = 3, sticky = 'W', pady = 0)
+ self.auto_resize(row_count = 2, column_count = 3, heavy_column = 3)
+ #self.button_two.deselect()
+ self.update()
+ self.mainloop()
+
+ def trinary_choice_message(self, message, one = 1, two = 2, three = 3):
+ #one: first option, returns 1
+ #two: second option, returns 2
+ #three: third option, returns 3
+ #usage is kind of opaque and relies on this object persisting after the window destruction to pass back choice
+ #usage:
+ # frame = InstallerUserMessage.InstallerUserMessage( ... )
+ # frame = frame.binary_choice_message( ... )
+ # (wait for user to click)
+ # value = frame.choice.get()
+
+ self.text_label = tk.Label(text = message)
+ #command registers the callback to the method named. We want the frame to go away once clicked.
+ self.button_one = ttk.Radiobutton(text = one, variable = self.choice, value = 1,
+ command = self._delete_window, style = 'Linden.TButton')
+ self.button_two = ttk.Radiobutton(text = two, variable = self.choice, value = 2,
+ command = self._delete_window, style = 'Linden.TButton')
+ self.button_three = ttk.Radiobutton(text = three, variable = self.choice, value = 3,
+ command = self._delete_window, style = 'Linden.TButton')
+ self.set_colors(self.text_label)
+ self.set_colors(self.image_label)
+ #pad, direction and weight are all experimentally derived by retrying various values
+ self.image_label.grid(row = 1, column = 1, rowspan = 4, sticky = 'W')
+ self.text_label.grid(row = 1, column = 2, rowspan = 4, padx = 5)
+ self.button_one.grid(row = 1, column = 3, sticky = 'W', pady = 5)
+ self.button_two.grid(row = 2, column = 3, sticky = 'W', pady = 5)
+ self.button_three.grid(row = 3, column = 3, sticky = 'W', pady = 5)
+ self.auto_resize(row_count = 3, column_count = 3, heavy_column = 3)
+ #self.button_two.deselect()
+ self.update()
+ self.mainloop()
+
+ def progress_bar(self, message = None, size = 0, interval = 100, pb_queue = None):
+ #Best effort attempt at a real progress bar
+ # This is what Tk calls "determinate mode" rather than "indeterminate mode"
+ #size: denominator of percent complete
+ #interval: frequency, in ms, of how often to poll the file for progress
+ #pb_queue: queue object used to send updates to the bar
+ self.text_label = tk.Label(text = message)
+ self.set_colors(self.text_label)
+ self.set_colors(self.image_label)
+ self.image_label.grid(row = 1, column = 1, sticky = 'NSEW')
+ self.text_label.grid(row = 2, column = 1, sticky = 'NSEW')
+ self.progress = ttk.Progressbar(self, style = 'black.Horizontal.TProgressbar', orient="horizontal", length=100, mode="determinate")
+ self.progress.grid(row = 3, column = 1, sticky = 'NSEW')
+ self.value = 0
+ self.progress["maximum"] = size
+ self.auto_resize(row_count = 1, column_count = 3)
+ self.queue = pb_queue
+ self.check_scheduler()
+
+ def check_scheduler(self):
+ if self.value < self.progress["maximum"]:
+ self.check_queue()
+ self.after(100, self.check_scheduler)
+
+ def check_queue(self):
+ while self.queue.qsize():
+ try:
+ msg = float(self.queue.get(0))
+ #custom signal, time to tear down
+ if msg == -1:
+ self.choice.set(True)
+ self.destroy()
+ else:
+ self.progress.step(msg)
+ self.value = msg
+ except Queue.Empty:
+ #nothing to do
+ return
+
+class ThreadedClient(threading.Thread):
+ #for test only, not part of the functional code
+ def __init__(self, queue):
+ threading.Thread.__init__(self)
+ self.queue = queue
+
+ def run(self):
+ for x in range(1, 90, 10):
+ time.sleep(1)
+ print "run " + str(x)
+ self.queue.put(10)
+ #tkk progress bars wrap at exactly 100 percent, look full at 99%
+ print "leftovers"
+ self.queue.put(9)
+ time.sleep(5)
+ # -1 is a custom signal to the progress_bar to quit
+ self.queue.put(-1)
+
+if __name__ == "__main__":
+ #When run as a script, just test the InstallUserMessage.
+ #To proceed with the test, close the first window, select on the second. The third will close by itself.
+ import sys
+ import tempfile
+
+ def set_and_check(frame, value):
+ print "value: " + str(value)
+ frame.progress.step(value)
+ if frame.progress["value"] < frame.progress["maximum"]:
+ print "In Progress"
+ else:
+ print "Over now"
+
+ #basic message window test
+ frame2 = InstallerUserMessage(text = "Something in the way she moves....", title = "Beatles Quotes for 100", icon_name="head-sl-logo.gif")
+ frame2.basic_message(message = "...attracts me like no other.")
+ print "Destroyed!"
+ sys.stdout.flush()
+
+ #binary choice test. User destroys window when they select.
+ frame3 = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 200", icon_name="head-sl-logo.gif")
+ frame3.binary_choice_message(message = "And all I have to do is think of her.",
+ true = "Don't want to leave her now", false = 'You know I believe and how')
+ print frame3.choice.get()
+ sys.stdout.flush()
+
+ #progress bar
+ queue = Queue.Queue()
+ thread = ThreadedClient(queue)
+ thread.start()
+ print "thread started"
+
+ frame4 = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 300", icon_name="head-sl-logo.gif")
+ frame4.progress_bar(message = "You're asking me will my love grow", size = 100, pb_queue = queue)
+ print "frame defined"
+ frame4.mainloop()
+
+ #trinary choice test. User destroys window when they select.
+ frame3a = InstallerUserMessage(text = "Something in the way she knows....", title = "Beatles Quotes for 200", icon_name="head-sl-logo.gif")
+ frame3a.trinary_choice_message(message = "And all I have to do is think of her.",
+ one = "Don't want to leave her now", two = 'You know I believe and how', three = 'John is Dead')
+ print frame3a.choice.get()
+ sys.stdout.flush()
diff --git a/indra/viewer_components/manager/SL_Launcher b/indra/viewer_components/manager/SL_Launcher
new file mode 100755
index 0000000000..ecf88a1105
--- /dev/null
+++ b/indra/viewer_components/manager/SL_Launcher
@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+# Copyright (c) 2013, Linden Research, Inc.
+
+import argparse
+import InstallerUserMessage
+import os
+import sys
+import subprocess
+import update_manager
+
+def after_frame(my_message, timeout = 10000):
+ #pop up a InstallerUserMessage.basic_message that kills itself after timeout milliseconds
+ #note that this blocks the caller for the duration of timeout
+ frame = InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif")
+ #this is done before basic_message so that we aren't blocked by mainloop()
+ frame.after(timout, lambda: frame._delete_window)
+ frame.basic_message(message = my_message)
+
+cwd = os.path.dirname(os.path.realpath(__file__))
+
+executable_name = ""
+if sys.platform.startswith('darwin'):
+ executable_name = "Second Life"
+elif sys.platform.startswith("win") or sys.platform.startswith("cyg"):
+ if os.path.isfile(os.path.join(cwd,"SecondLifeViewer.exe")):
+ executable_name = "SecondLifeViewer.exe"
+ elif os.path.isfile(os.path.join(cwd,"SecondLifeTest.exe")):
+ executable_name = "SecondLifeTest.exe"
+ else:
+ sys.exit("Can't find Windows viewer binary")
+elif sys.platform.startswith("linux"):
+ executable_name = "secondlife"
+else:
+ #SL doesn't run on VMS or punch cards
+ sys.exit("Unsupported platform")
+
+#check for an update
+#TODO
+
+#find the viewer to be lauched
+viewer_binary = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])),executable_name)
+
+parser = argparse.ArgumentParser()
+args = parser.parse_known_args(sys.argv)
+args_list_to_pass = args[1][1:]
+#make a copy by value, not by reference
+command = list(args_list_to_pass)
+
+(success, state, condition) = update_manager.update_manager()
+# From update_manager:
+# (False, 'setup', None): error occurred before we knew what the update was (e.g., in setup or parsing)
+# (False, 'download', version): we failed to download the new version
+# (False, 'apply', version): we failed to apply the new version
+# (True, None, None): No update found
+# (True, 'in place', True): update applied in place
+# (True, 'in place', path_to_new_launcher): Update applied by a new install to a new location
+# (True, 'background', True): background download initiated
+#These boil down three cases:
+# Success is False, then pop up a message and launch the current viewer
+# No update, update succeeded in place in foreground, or background update started: silently launch the current viewer channel
+# Updated succeed to a different channel, launch that viewer and exit
+if not success:
+ msg = 'Update failed in the %s process. Please check logs. Viewer will launch starting momentarily.'
+ after_frame(msg)
+ command.insert(0,viewer_binary)
+ viewer_process = subprocess.Popen(command)
+ #at the moment, we just exit here. Later, the crash monitor will be launched at this point
+elif (success == True and
+ (state == None
+ or (state == 'background' and condition == True)
+ or (state == 'in_place' and condition == True))):
+ command.insert(0,viewer_binary)
+ viewer_process = subprocess.Popen(command)
+ #at the moment, we just exit here. Later, the crash monitor will be launched at this point
+else:
+ #'condition' is the path to the new launcher.
+ command.insert(0,condition)
+ viewer_process = subprocess.Popen(command)
+ sys.exit(0)
diff --git a/indra/viewer_components/manager/apply_update.py b/indra/viewer_components/manager/apply_update.py
new file mode 100755
index 0000000000..643e4ad2bc
--- /dev/null
+++ b/indra/viewer_components/manager/apply_update.py
@@ -0,0 +1,277 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $LicenseInfo:firstyear=2016&license=viewerlgpl$
+# Copyright (c) 2016, Linden Research, Inc.
+# $/LicenseInfo$
+
+"""
+@file apply_update.py
+@author coyot
+@date 2016-06-28
+"""
+
+"""
+Applies an already downloaded update.
+"""
+
+import argparse
+import errno
+import fnmatch
+import InstallerUserMessage as IUM
+import os
+import os.path
+import plistlib
+import re
+import shutil
+import subprocess
+import sys
+import tarfile
+import tempfile
+
+#Module level variables
+
+#fnmatch expressions
+LNX_REGEX = '*' + '.bz2'
+MAC_REGEX = '*' + '.dmg'
+MAC_APP_REGEX = '*' + '.app'
+WIN_REGEX = '*' + '.exe'
+
+#which install the updater is run from
+INSTALL_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+
+#whether the update is to the INSTALL_DIR or not. Most of the time this is the case.
+IN_PLACE = True
+
+BUNDLE_IDENTIFIER = "com.secondlife.indra.viewer"
+# Magic OS directory name that causes Cocoa viewer to crash on OS X 10.7.5
+# (see MAINT-3331)
+STATE_DIR = os.path.join(os.environ["HOME"], "Library", "Saved Application State",
+ BUNDLE_IDENTIFIER + ".savedState")
+
+def silent_write(log_file_handle, text):
+ #if we have a log file, write. If not, do nothing.
+ if (log_file_handle):
+ #prepend text for easy grepping
+ log_file_handle.write("APPLY UPDATE: " + text + "\n")
+
+def get_filename(download_dir = None):
+ #given a directory that supposedly has the download, find the installable
+ #if you are on platform X and you give the updater a directory with an installable
+ #for platform Y, you are either trying something fancy or get what you deserve
+ #or both
+ for filename in os.listdir(download_dir):
+ if (fnmatch.fnmatch(filename, LNX_REGEX)
+ or fnmatch.fnmatch(filename, MAC_REGEX)
+ or fnmatch.fnmatch(filename, WIN_REGEX)):
+ return os.path.join(download_dir, filename)
+ #someone gave us a bad directory
+ return None
+
+def try_dismount(log_file_handle = None, installable = None, tmpdir = None):
+ #best effort cleanup try to dismount the dmg file if we have mounted one
+ #the French judge gave it a 5.8
+ try:
+ #use the df command to find the device name
+ #Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on
+ #/dev/disk1s2 2047936 643280 1404656 32% 80408 175582 31% /private/tmp/mnt/Second Life Installer
+ command = ["df", os.path.join(tmpdir, "Second Life Installer")]
+ output = subprocess.check_output(command)
+ #first word of second line of df output is the device name
+ mnt_dev = output.split('\n')[1].split()[0]
+ #do the dismount
+ command = ["hdiutil", "detach", "-force", mnt_dev]
+ output = subprocess.check_output(command)
+ silent_write(log_file_handle, "hdiutil detach succeeded")
+ silent_write(log_file_handle, output)
+ except Exception, e:
+ silent_write(log_file_handle, "Could not detach dmg file %s. Error messages: %s" % (installable, e.message))
+
+def apply_update(download_dir = None, platform_key = None, log_file_handle = None, in_place = True):
+ #for lnx and mac, returns path to newly installed viewer
+ #for win, return the name of the executable
+ #returns None on failure for all three
+ #throws an exception if it can't find an installable at all
+
+ IN_PLACE = in_place
+
+ installable = get_filename(download_dir)
+ if not installable:
+ #could not find the download
+ raise ValueError("Could not find installable in " + download_dir)
+
+ #apply update using the platform specific tools
+ if platform_key == 'lnx':
+ installed = apply_linux_update(installable, log_file_handle)
+ elif platform_key == 'mac':
+ installed = apply_mac_update(installable, log_file_handle)
+ elif platform_key == 'win':
+ installed = apply_windows_update(installable, log_file_handle)
+ else:
+ #wtf?
+ raise ValueError("Unknown Platform: " + platform_key)
+
+ if not installed:
+ #only mark the download as done when everything is done
+ done_filename = os.path.join(os.path.dirname(installable), ".done")
+ open(done_filename, 'w+').close()
+
+ return installed
+
+def apply_linux_update(installable = None, log_file_handle = None):
+ try:
+ #untar to tmpdir
+ tmpdir = tempfile.mkdtemp()
+ tar = tarfile.open(name = installable, mode="r:bz2")
+ tar.extractall(path = tmpdir)
+ if IN_PLACE:
+ #rename current install dir
+ shutil.move(INSTALL_DIR,install_dir + ".bak")
+ #mv new to current
+ shutil.move(tmpdir, INSTALL_DIR)
+ #delete tarball on success
+ os.remove(installable)
+ except Exception, e:
+ silent_write(log_file_handle, "Update failed due to " + repr(e))
+ return None
+ return INSTALL_DIR
+
+def apply_mac_update(installable = None, log_file_handle = None):
+ #INSTALL_DIR is something like /Applications/Second Life Viewer.app/Contents/MacOS, need to jump up two levels for the install base
+ install_base = os.path.dirname(INSTALL_DIR)
+ install_base = os.path.dirname(install_base)
+
+ #verify dmg file
+ try:
+ output = subprocess.check_output(["hdiutil", "verify", installable], stderr=subprocess.STDOUT)
+ silent_write(log_file_handle, "dmg verification succeeded")
+ silent_write(log_file_handle, output)
+ except Exception, e:
+ silent_write(log_file_handle, "Could not verify dmg file %s. Error messages: %s" % (installable, e.message))
+ return None
+ #make temp dir and mount & attach dmg
+ tmpdir = tempfile.mkdtemp()
+ try:
+ output = subprocess.check_output(["hdiutil", "attach", installable, "-mountroot", tmpdir])
+ silent_write(log_file_handle, "hdiutil attach succeeded")
+ silent_write(log_file_handle, output)
+ except Exception, e:
+ silent_write(log_file_handle, "Could not attach dmg file %s. Error messages: %s" % (installable, e.message))
+ return None
+ #verify plist
+ mounted_appdir = None
+ for top_dir in os.listdir(tmpdir):
+ for appdir in os.listdir(os.path.join(tmpdir, top_dir)):
+ appdir = os.path.join(os.path.join(tmpdir, top_dir), appdir)
+ if fnmatch.fnmatch(appdir, MAC_APP_REGEX):
+ try:
+ plist = os.path.join(appdir, "Contents", "Info.plist")
+ CFBundleIdentifier = plistlib.readPlist(plist)["CFBundleIdentifier"]
+ mounted_appdir = appdir
+ except:
+ #there is no except for this try because there are multiple directories that legimately don't have what we are looking for
+ pass
+ if not mounted_appdir:
+ silent_write(log_file_handle, "Could not find app bundle in dmg %s." % (installable,))
+ return None
+ if CFBundleIdentifier != BUNDLE_IDENTIFIER:
+ silent_write(log_file_handle, "Wrong or null bundle identifier for dmg %s. Bundle identifier: %s" % (installable, CFBundleIdentifier))
+ try_dismount(log_file_handle, installable, tmpdir)
+ return None
+ #do the install, finally
+ if IN_PLACE:
+ # swap out old install directory
+ bundlename = os.path.basename(mounted_appdir)
+ silent_write(log_file_handle, "Updating %s" % bundlename)
+ swapped_out = os.path.join(tmpdir, INSTALL_DIR.lstrip('/'))
+ shutil.move(install_base, swapped_out)
+ else:
+ silent_write(log_file_handle, "Installing %s" % install_base)
+
+ # copy over the new bits
+ try:
+ shutil.copytree(mounted_appdir, install_base, symlinks=True)
+ retcode = 0
+ except Exception, e:
+ # try to restore previous viewer
+ if os.path.exists(swapped_out):
+ silent_write(log_file_handle, "Install of %s failed, rolling back to previous viewer." % installable)
+ shutil.move(swapped_out, installed_test)
+ retcode = 1
+ finally:
+ try_dismount(log_file_handle, installable, tmpdir)
+ if retcode:
+ return None
+
+ #see MAINT-3331
+ try:
+ shutil.rmtree(STATE_DIR)
+ except Exception, e:
+ #if we fail to delete something that isn't there, that's okay
+ if e[0] == errno.ENOENT:
+ pass
+ else:
+ raise e
+
+ os.remove(installable)
+ return install_base
+
+def apply_windows_update(installable = None, log_file_handle = None):
+ #the windows install is just running the NSIS installer executable
+ #from VMP's perspective, it is a black box
+ try:
+ output = subprocess.check_output(installable, stderr=subprocess.STDOUT)
+ silent_write(log_file_handle, "Install of %s succeeded." % installable)
+ silent_write(log_file_handle, output)
+ except subprocess.CalledProcessError, cpe:
+ silent_write(log_file_handle, "%s failed with return code %s. Error messages: %s." %
+ (cpe.cmd, cpe.returncode, cpe.message))
+ return None
+ #Due to the black box nature of the install, we have to derive the application path from the
+ #name of the installable. This is essentially reverse-engineering app_name()/app_name_oneword()
+ #in viewer_manifest.py
+ #the format of the filename is: Second_Life_{Project Name}_A-B-C-XXXXXX_i686_Setup.exe
+ #which deploys to C:\Program Files (x86)\SecondLifeProjectName\
+ #so we want all but the last four phrases and tack on Viewer if there is no project
+ if re.search('Project', installable):
+ winstall = os.path.join("C:\\Program Files (x86)\\", "".join(installable.split("_")[:-3]))
+ else:
+ winstall = os.path.join("C:\\Program Files (x86)\\", "".join(installable.split("_")[:-3])+"Viewer")
+ return winstall
+
+def main():
+ parser = argparse.ArgumentParser("Apply Downloaded Update")
+ parser.add_argument('--dir', dest = 'download_dir', help = 'directory to find installable', required = True)
+ parser.add_argument('--pkey', dest = 'platform_key', help =' OS: lnx|mac|win', required = True)
+ parser.add_argument('--in_place', action = 'store_false', help = 'This upgrade is for a different channel', default = True)
+ parser.add_argument('--log_file', dest = 'log_file', default = None, help = 'file to write messages to')
+ args = parser.parse_args()
+
+ if args.log_file:
+ try:
+ f = open(args.log_file,'w+')
+ except:
+ print "%s could not be found or opened" % args.log_file
+ sys.exit(1)
+
+ IN_PLACE = args.in_place
+ result = apply_update(download_dir = args.download_dir, platform_key = args.platform_key, log_file_handle = f)
+ if not result:
+ sys.exit("Update failed")
+ else:
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/indra/viewer_components/manager/download_update.py b/indra/viewer_components/manager/download_update.py
new file mode 100755
index 0000000000..23f784c6c1
--- /dev/null
+++ b/indra/viewer_components/manager/download_update.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $LicenseInfo:firstyear=2016&license=viewerlgpl$
+# Copyright (c) 2016, Linden Research, Inc.
+# $/LicenseInfo$
+
+"""
+@file download_update.py
+@author coyot
+@date 2016-06-23
+"""
+
+"""
+Performs a download of an update. In a separate script from update_manager so that we can
+call it with subprocess.
+"""
+
+import argparse
+import InstallerUserMessage as IUM
+import os
+import Queue
+import requests
+import threading
+
+#module default
+CHUNK_SIZE = 1024
+
+def download_update(url = None, download_dir = None, size = None, progressbar = False, chunk_size = CHUNK_SIZE):
+ #url to download from
+ #download_dir to download to
+ #total size (for progressbar) of download
+ #progressbar: whether to display one (not used for background downloads)
+ #chunk_size is in bytes, amount to download at once
+
+ queue = Queue.Queue()
+ #the url split provides the basename of the filename
+ filename = os.path.join(download_dir, url.split('/')[-1])
+ req = requests.get(url, stream=True)
+ down_thread = ThreadedDownload(req, filename, chunk_size, progressbar, queue)
+ down_thread.start()
+
+ if progressbar:
+ frame = IUM.InstallerUserMessage(title = "Second Life Downloader", icon_name="head-sl-logo.gif")
+ frame.progress_bar(message = "Download Progress", size = size, pb_queue = queue)
+ frame.mainloop()
+ else:
+ #nothing for the main thread to do
+ down_thread.join()
+
+class ThreadedDownload(threading.Thread):
+ def __init__(self, req, filename, chunk_size, progressbar, in_queue):
+ #req is a python request object
+ #target filename to download to
+ #chunk_size is in bytes, amount to download at once
+ #progressbar: whether to display one (not used for background downloads)
+ #in_queue mediates communication between this thread and the progressbar
+ threading.Thread.__init__(self)
+ self.req = req
+ self.filename = filename
+ self.chunk_size = int(chunk_size)
+ self.progressbar = progressbar
+ self.in_queue = in_queue
+
+ def run(self):
+ with open(self.filename, 'wb') as fd:
+ #keep downloading until we run out of chunks, then download the last bit
+ for chunk in self.req.iter_content(self.chunk_size):
+ fd.write(chunk)
+ if self.progressbar:
+ #this will increment the progress bar by len(chunk)/size units
+ self.in_queue.put(len(chunk))
+ #signal value saying to the progress bar that it is done and can destroy itself
+ #if len(chunk) is ever -1, we get to file a bug against Python
+ self.in_queue.put(-1)
+
+def main():
+ #main method is for standalone use such as support and QA
+ #VMP will import this module and run download_update directly
+ parser = argparse.ArgumentParser("Download URI to directory")
+ parser.add_argument('--url', dest='url', help='URL of file to be downloaded', required=True)
+ parser.add_argument('--dir', dest='download_dir', help='directory to be downloaded to', required=True)
+ parser.add_argument('--pb', dest='progressbar', help='whether or not to show a progressbar', action="store_true", default = False)
+ parser.add_argument('--size', dest='size', help='size of download for progressbar')
+ parser.add_argument('--chunk_size', dest='chunk_size', default=CHUNK_SIZE, help='max portion size of download to be loaded in memory in bytes.')
+ args = parser.parse_args()
+
+ download_update(url = args.url, download_dir = args.download_dir, size = args.size, progressbar = args.progressbar, chunk_size = args.chunk_size)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/indra/viewer_components/manager/update_manager.py b/indra/viewer_components/manager/update_manager.py
new file mode 100755
index 0000000000..ec6df17a6c
--- /dev/null
+++ b/indra/viewer_components/manager/update_manager.py
@@ -0,0 +1,455 @@
+#!/usr/bin/env python
+
+# $LicenseInfo:firstyear=2016&license=internal$
+#
+# Copyright (c) 2016, Linden Research, Inc.
+#
+# The following source code is PROPRIETARY AND CONFIDENTIAL. Use of
+# this source code is governed by the Linden Lab Source Code Disclosure
+# Agreement ("Agreement") previously entered between you and Linden
+# Lab. By accessing, using, copying, modifying or distributing this
+# software, you acknowledge that you have been informed of your
+# obligations under the Agreement and agree to abide by those obligations.
+#
+# ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
+# WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
+# COMPLETENESS OR PERFORMANCE.
+# $/LicenseInfo$
+# Copyright (c) 2013, Linden Research, Inc.
+
+"""
+@file update_manager.py
+@author coyot
+@date 2016-05-16
+"""
+
+from llbase import llrest
+from llbase import llsd
+from urlparse import urljoin
+
+import apply_update
+import download_update
+import errno
+import fnmatch
+import hashlib
+import InstallerUserMessage
+import json
+import os
+import platform
+import re
+import shutil
+import subprocess
+import sys
+import tempfile
+import thread
+import urllib
+
+def silent_write(log_file_handle, text):
+ #if we have a log file, write. If not, do nothing.
+ #this is so we don't have to keep trapping for an exception with a None handle
+ #oh and because it is best effort, it is also a holey_write ;)
+ if (log_file_handle):
+ #prepend text for easy grepping
+ log_file_handle.write("UPDATE MANAGER: " + text + "\n")
+
+def after_frame(my_message, timeout = 10000):
+ #pop up a InstallerUserMessage.basic_message that kills itself after timeout milliseconds
+ #note that this blocks the caller for the duration of timeout
+ frame = InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif")
+ #this is done before basic_message so that we aren't blocked by mainloop()
+ frame.after(timout, lambda: frame._delete_window)
+ frame.basic_message(message = my_message)
+
+def convert_version_file_style(version):
+ #converts a version string a.b.c.d to a_b_c_d as used in downloaded filenames
+ #re will throw a TypeError if it gets None, just return that.
+ try:
+ pattern = re.compile('\.')
+ return pattern.sub('_', version)
+ except TypeError, te:
+ return None
+
+def get_platform_key():
+ #this is the name that is inserted into the VVM URI
+ #and carried forward through the rest of the updater to determine
+ #platform specific actions as appropriate
+ platform_dict = {'Darwin':'mac', 'Linux':'lnx', 'Windows':'win'}
+ platform_uname = platform.system()
+ try:
+ return platform_dict[platform_uname]
+ except KeyError:
+ return None
+
+def get_summary(platform_name, launcher_path):
+ #get the contents of the summary.json file.
+ #for linux and windows, this file is in the same directory as the script
+ #for mac, the script is in ../Contents/MacOS/ and the file is in ../Contents/Resources/
+ script_dir = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
+ if (platform_name == 'mac'):
+ summary_dir = os.path.abspath(os.path.join(script_dir, "../Resources"))
+ else:
+ summary_dir = script_dir
+ summary_file = os.path.join(summary_dir,"summary.json")
+ with open(summary_file) as summary_handle:
+ return json.load(summary_handle)
+
+def get_parent_path(platform_name):
+ #find the parent of the logs and user_settings directories
+ if (platform_name == 'mac'):
+ settings_dir = os.path.join(os.path.expanduser('~'),'Library','Application Support','SecondLife')
+ elif (platform_name == 'lnx'):
+ settings_dir = os.path.join(os.path.expanduser('~'),'.secondlife')
+ #using list format of join is important here because the Windows pathsep in a string escapes the next char
+ elif (platform_name == 'win'):
+ settings_dir = os.path.join(os.path.expanduser('~'),'AppData','Roaming','SecondLife')
+ else:
+ settings_dir = None
+ return settings_dir
+
+def make_download_dir(parent_dir, new_version):
+ #make a canonical download dir if it does not already exist
+ #format: ../user_settings/downloads/1.2.3.456789
+ #we do this so that multiple viewers on the same host can update separately
+ #this also functions as a getter
+ try:
+ download_dir = os.path.join(parent_dir, "downloads", new_version)
+ os.makedirs(download_dir)
+ except OSError, hell:
+ #Directory already exists, that's okay. Other OSErrors are not okay.
+ if hell[0] == errno.EEXIST:
+ pass
+ else:
+ raise hell
+ return download_dir
+
+def check_for_completed_download(download_dir):
+ #there will be two files on completion, the download and a marker file called "".done""
+ #for optional upgrades, there may also be a .skip file to skip this particular upgrade
+ #or .next to install on next run
+ completed = None
+ marker_regex = '*' + '.done'
+ skip_regex = '*' + '.skip'
+ next_regex = '*' + '.next'
+ for filename in os.listdir(download_dir):
+ if fnmatch.fnmatch(filename, marker_regex):
+ completed = 'done'
+ elif fnmatch.fnmatch(filename, skip_regex):
+ completed = 'skip'
+ elif fnmatch.fnmatch(filename, next_regex):
+ #so we don't skip infinitely
+ os.remove(filename)
+ completed = 'next'
+ if not completed:
+ #cleanup
+ shutil.rmtree(download_dir)
+ return completed
+
+def get_settings(log_file_handle, parent_dir):
+ #return the settings file parsed into a dict
+ try:
+ settings_file = os.path.abspath(os.path.join(parent_dir,'user_settings','settings.xml'))
+ settings = llsd.parse((open(settings_file)).read())
+ except llsd.LLSDParseError as lpe:
+ silent_write(log_file_handle, "Could not parse settings file %s" % lpe)
+ return None
+ return settings
+
+def get_log_file_handle(parent_dir):
+ #return a write handle on the log file
+ #plus log rotation and not dying on failure
+ log_file = os.path.join(parent_dir, 'update_manager.log')
+ old_file = log_file + '.old'
+ #if someone's log files are present but not writable, they've screwed up their install.
+ if os.access(log_file, os.W_OK):
+ if os.access(old_file, os.W_OK):
+ os.unlink(old_file)
+ os.rename(log_file, old_file)
+ elif not os.path.exists(log_file):
+ #reimplement TOUCH(1) in Python
+ #perms default to 644 which is fine
+ open(log_file, 'w+').close()
+ try:
+ f = open(log_file,'w+')
+ except Exception as e:
+ #we don't have a log file to write to, make a best effort and sally onward
+ print "Could not open update manager log file %s" % log_file
+ f = None
+ return f
+
+def make_VVM_UUID_hash(platform_key):
+ #NOTE: There is no python library support for a persistent machine specific UUID
+ # AND all three platforms do this a different way, so exec'ing out is really the best we can do
+ #Lastly, this is a best effort service. If we fail, we should still carry on with the update
+ uuid = None
+ if (platform_key == 'lnx'):
+ uuid = subprocess.check_output(['/usr/bin/hostid']).rstrip()
+ elif (platform_key == 'mac'):
+ #this is absurdly baroque
+ #/usr/sbin/system_profiler SPHardwareDataType | fgrep 'Serial' | awk '{print $NF}'
+ uuid = subprocess.check_output(["/usr/sbin/system_profiler", "SPHardwareDataType"])
+ #findall[0] does the grep for the value we are looking for: "Serial Number (system): XXXXXXXX"
+ #split(:)[1] gets us the XXXXXXX part
+ #lstrip shaves off the leading space that was after the colon
+ uuid = re.split(":", re.findall('Serial Number \(system\): \S*', uuid)[0])[1].lstrip()
+ elif (platform_key == 'win'):
+ # wmic csproduct get UUID | grep -v UUID
+ uuid = subprocess.check_output(['wmic','csproduct','get','UUID'])
+ #outputs in two rows:
+ #UUID
+ #XXXXXXX-XXXX...
+ uuid = re.split('\n',uuid)[1].rstrip()
+ if uuid is not None:
+ return hashlib.md5(uuid).hexdigest()
+ else:
+ #fake it
+ return hashlib.md5(str(uuid.uuid1())).hexdigest()
+
+def query_vvm(log_file_handle, platform_key, settings, summary_dict):
+ result_data = None
+ #URI template /update/v1.1/channelname/version/platformkey/platformversion/willing-to-test/uniqueid
+ #https://wiki.lindenlab.com/wiki/Viewer_Version_Manager_REST_API#Viewer_Update_Query
+ base_URI = 'https://update.secondlife.com/update/'
+ channelname = summary_dict['Channel']
+ #this is kind of a mess because the settings value a) in a map and b) is both the cohort and the version
+ version = summary_dict['Version']
+ platform_version = platform.release()
+ #this will always return something usable, error handling in method
+ hashed_UUID = make_VVM_UUID_hash(platform_key)
+ #note that this will not normally be in a settings.xml file and is only here for test builds.
+ #for test builds, add this key to the ../user_settings/settings.xml
+ """
+ <key>test</key>
+ <map>
+ <key>Comment</key>
+ <string>Tell update manager you aren't willing to test.</string>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <integer>testno</integer>
+ </map>
+ </map>
+ """
+ try:
+ test_ok = settings['test']['Value']
+ except KeyError as ke:
+ #normal case, no testing key
+ test_ok = 'testok'
+ UUID = make_VVM_UUID_hash(platform_key)
+ #because urljoin can't be arsed to take multiple elements
+ query_string = '/v1.0/' + channelname + '/' + version + '/' + platform_key + '/' + platform_version + '/' + test_ok + '/' + UUID
+ VVMService = llrest.SimpleRESTService(name='VVM', baseurl=base_URI)
+ try:
+ result_data = VVMService.get(query_string)
+ except RESTError as re:
+ silent_write.write(log_file_handle, "Failed to query VVM using %s failed as %s" % (urljoin(base_URI,query_string, re)))
+ return None
+ return result_data
+
+def download(url = None, version = None, download_dir = None, size = 0, background = False):
+ download_tries = 0
+ download_success = False
+ #for background execution
+ path_to_downloader = os.path.join(os.path.dirname(os.path.realpath(__file__)), "download_update.py")
+ #three strikes and you're out
+ while download_tries < 3 and not download_success:
+ #323: Check for a partial update of the required update; in either event, display an alert that a download is required, initiate the download, and then install and launch
+ if download_tries == 0:
+ after_frame(message = "Downloading new version " + version + " Please wait.")
+ else:
+ after_frame(message = "Trying again to download new version " + version + " Please wait.")
+ if not background:
+ try:
+ download_update.download_update(url = url, download_dir = download_dir, size = size, progressbar = True)
+ download_success = True
+ except:
+ download_tries += 1
+ silent_write(log_file_handle, "Failed to download new version " + version + ". Trying again.")
+ else:
+ try:
+ #Python does not have a facility to multithread a method, so we make the method a standalone
+ #and subprocess that
+ subprocess.call(path_to_downloader, "--url = %s --dir = %s --pb --size= %s" % (url, download_dir, size))
+ download_success = True
+ except:
+ download_tries += 1
+ silent_write(log_file_handle, "Failed to download new version " + version + ". Trying again.")
+ if not download_success:
+ silent_write(log_file_handle, "Failed to download new version " + version)
+ after_frame(message = "Failed to download new version " + version + " Please check connectivity.")
+ return False
+ return True
+
+def install(platform_key = None, download_dir = None, log_file_handle = None, in_place = None, downloaded = None):
+ #user said no to this one
+ if downloaded != 'skip':
+ after_frame(message = "New version downloaded. Installing now, please wait.")
+ success = apply_update.apply_update(download_dir, platform_key, log_file_handle, in_place)
+ if success:
+ silent_write(log_file_handle, "successfully updated to " + version)
+ shutil.rmtree(download_dir)
+ #this is either True for in place or the path to the new install for not in place
+ return success
+ else:
+ after_frame(message = "Failed to apply " + version)
+ silent_write(log_file_handle, "Failed to update viewer to " + version)
+ return False
+
+def download_and_install(downloaded = None, url = None, version = None, download_dir = None, size = None, platform_key = None, log_file_handle = None, in_place = None):
+ #extracted to a method because we do it twice in update_manager() and this makes the logic clearer
+ if not downloaded:
+ #do the download, exit if we fail
+ if not download(url = url, version = version, download_dir = download_dir, size = size):
+ return (False, 'download', version)
+ #do the install
+ path_to_new_launcher = install(platform_key = platform_key, download_dir = download_dir,
+ log_file_handle = log_file_handle, in_place = in_place, downloaded = downloaded)
+ if path_to_new_launcher:
+ #if we succeed, propagate the success type upwards
+ if in_place:
+ return (True, 'in place', True)
+ else:
+ return (True, 'in place', path_to_new_launcher)
+ else:
+ #propagate failure
+ return (False, 'apply', version)
+
+def update_manager():
+ #comments that begin with '323:' are steps taken from the algorithm in the description of SL-323.
+ # Note that in the interest of efficiency, such as determining download success once at the top
+ # The code does follow precisely the same order as the algorithm.
+ #return values rather than exit codes. All of them are to communicate with launcher
+ #we print just before we return so that __main__ outputs something - returns are swallowed
+ # (False, 'setup', None): error occurred before we knew what the update was (e.g., in setup or parsing)
+ # (False, 'download', version): we failed to download the new version
+ # (False, 'apply', version): we failed to apply the new version
+ # (True, None, None): No update found
+ # (True, 'in place, True): update applied in place
+ # (True, 'in place', path_to_new_launcher): Update applied by a new install to a new location
+ # (True, 'background', True): background download initiated
+
+ #setup and getting initial parameters
+ platform_key = get_platform_key()
+ parent_dir = get_parent_path(platform_key)
+ log_file_handle = get_log_file_handle(parent_dir)
+
+ #check to see if user has install rights
+ #get the owner of the install and the current user
+ script_owner_id = os.stat(os.path.realpath(__file__)).st_uid
+ user_id = os.geteuid()
+ #if we are on lnx or mac, we can pretty print the IDs as names using the pwd module
+ #win does not provide this support and Python will throw an ImportError there, so just use raw IDs
+ if script_owner_id != user_id:
+ if platform_key != 'win':
+ import pwd
+ script_owner_name = pwd.getpwuid(script_owner_id)[0]
+ username = pwd.getpwuid(user_id)[0]
+ else:
+ username = user_id
+ script_owner_name = script_owner_id
+ silent_write(log_file_handle, "Upgrade notification attempted by userid " + username)
+ frame = InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif")
+ frame.binary_choice_message(message = "Second Life was installed by userid " + script_owner_name
+ + ". Do you have privileges to install?", true = "Yes", false = 'No')
+ if not frame.choice.get():
+ silent_write(log_file_handle, "Upgrade attempt declined by userid " + username)
+ after_frame(message = "Please find a system admin to upgrade Second Life")
+ print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None)
+ return (False, 'setup', None)
+
+ settings = get_settings(log_file_handle, parent_dir)
+ if settings is None:
+ silent_write(log_file_handle, "Failed to load viewer settings")
+ print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None)
+ return (False, 'setup', None)
+
+ #323: If a complete download of that update is found, check the update preference:
+ #settings['UpdaterServiceSetting'] = 0 is manual install
+ """
+ <key>UpdaterServiceSetting</key>
+ <map>
+ <key>Comment</key>
+ <string>Configure updater service.</string>
+ <key>Type</key>
+ <string>U32</string>
+ <key>Value</key>
+ <string>0</string>
+ </map>
+ """
+ try:
+ install_automatically = settings['UpdaterServiceSetting']['Value']
+ #because, for some godforsaken reason, we delete the setting rather than changing the value
+ except KeyError:
+ install_automatically = 1
+
+ #get channel and version
+ try:
+ summary_dict = get_summary(platform_key, os.path.abspath(os.path.realpath(__file__)))
+ except:
+ silent_write(log_file_handle, "Could not obtain channel and version, exiting.")
+ print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None)
+ return (False, 'setup', None)
+
+ #323: On launch, the Viewer Manager should query the Viewer Version Manager update api.
+ result_data = query_vvm(log_file_handle, platform_key, settings, summary_dict)
+ #nothing to do or error
+ if not result_data:
+ silent_write.write(og_file_handle, "No update found.")
+ print "Update manager exited with (%s, %s, %s)" % (True, None, None)
+ return (True, None, None)
+
+ #get download directory, if there are perm issues or similar problems, give up
+ try:
+ download_dir = make_download_dir(parent_dir, result_data['version'])
+ except Exception, e:
+ print "Update manager exited with (%s, %s, %s)" % (False, 'setup', None)
+ return (False, 'setup', None)
+
+ #if the channel name of the response is the same as the channel we are launched from, the update is "in place"
+ #and launcher will launch the viewer in this install location. Otherwise, it will launch the Launcher from
+ #the new location and kill itself.
+ in_place = (summary_dict['Channel'] == result_data['channel'])
+
+ #determine if we've tried this download before
+ downloaded = check_for_completed_download(download_dir)
+
+ #323: If the response indicates that there is a required update:
+ if result_data['required'] or (not result_data['required'] and install_automatically):
+ #323: Check for a completed download of the required update; if found, display an alert, install the required update, and launch the newly installed viewer.
+ #323: If [optional download and] Install Automatically: display an alert, install the update and launch updated viewer.
+ return download_and_install(downloaded = downloaded, url = result_data['url'], version = result_data['version'], download_dir = download_dir,
+ size = result_data['size'], platform_key = platform_key, log_file_handle = log_file_handle, in_place = in_place)
+ else:
+ #323: If the update response indicates that there is an optional update:
+ #323: Check to see if the optional update has already been downloaded.
+ #323: If a complete download of that update is found, check the update preference:
+ #note: automatic install handled above as the steps are the same as required upgrades
+ #323: If Install Manually: display a message with the update information and ask the user whether or not to install the update with three choices:
+ #323: Skip this update: create a marker that subsequent launches should not prompt for this update as long as it is optional,
+ # but leave the download in place so that if it becomes required it will be there.
+ #323: Install next time: create a marker that skips the prompt and installs on the next launch
+ #323: Install and launch now: do it.
+ if downloaded is not None and downloaded != 'skip':
+ frame = InstallerUserMessage(title = "Second Life Installer", icon_name="head-sl-logo.gif")
+ #The choices are reordered slightly to encourage immediate install and slightly discourage skipping
+ frame.trinary_message(message = "Please make a selection",
+ one = "Install new version now.", two = 'Install the next time the viewer is launched.', three = 'Skip this update.')
+ choice = frame.choice.get()
+ if choice == 1:
+ return download_and_install(downloaded = downloaded, url = result_data['url'], version = result_data['version'], download_dir = download_dir,
+ size = result_data['size'], platform_key = platform_key, log_file_handle = log_file_handle, in_place = in_place)
+ elif choice == 2:
+ tempfile.mkstmp(suffix = ".next", dir = download_dir)
+ return (True, None, None)
+ else:
+ tempfile.mkstmp(suffix = ".skip", dir = download_dir)
+ return (True, None, None)
+ else:
+ #multithread a download
+ download(url = result_data['url'], version = result_data['version'], download_dir = download_dir, size = result_data['size'], background = True)
+ print "Update manager exited with (%s, %s, %s)" % (True, 'background', True)
+ return (True, 'background', True)
+
+
+if __name__ == '__main__':
+ #there is no argument parsing or other main() work to be done
+ update_manager()
diff --git a/indra/viewer_components/updater/CMakeLists.txt b/indra/viewer_components/updater/CMakeLists.txt
index 61fd4220e0..73e18aacb3 100755..100644
--- a/indra/viewer_components/updater/CMakeLists.txt
+++ b/indra/viewer_components/updater/CMakeLists.txt
@@ -6,15 +6,18 @@ include(00-Common)
if(LL_TESTS)
include(LLAddBuildTest)
endif(LL_TESTS)
+include(Boost)
include(CMakeCopyIfDifferent)
include(CURL)
include(LLCommon)
+include(LLCoreHttp)
include(LLMessage)
include(LLPlugin)
include(LLVFS)
include_directories(
${LLCOMMON_INCLUDE_DIRS}
+ ${LLCOREHTTP_INCLUDE_DIRS}
${LLMESSAGE_INCLUDE_DIRS}
${LLPLUGIN_INCLUDE_DIRS}
${LLVFS_INCLUDE_DIRS}
@@ -60,25 +63,36 @@ add_library(llupdaterservice
target_link_libraries(llupdaterservice
${LLCOMMON_LIBRARIES}
${LLMESSAGE_LIBRARIES}
+ ${LLCOREHTTP_LIBRARIES}
${LLPLUGIN_LIBRARIES}
${LLVFS_LIBRARIES}
)
if(LL_TESTS)
+if (NOT LINUX)
SET(llupdater_service_TEST_SOURCE_FILES
llupdaterservice.cpp
)
+set(test_libs
+ ${LLCOMMON_LIBRARIES}
+ ${BOOST_COROUTINE_LIBRARY}
+ ${BOOST_CONTEXT_LIBRARY}
+ ${BOOST_THREAD_LIBRARY}
+ ${BOOST_SYSTEM_LIBRARY})
+
+set_source_files_properties(
+ llupdaterservice.cpp
+ PROPERTIES
+ LL_TEST_ADDITIONAL_LIBRARIES ${test_libs}
# *NOTE:Mani - I was trying to use the preprocessor seam to mock out
-# llifstream (and other) llcommon classes. I didn't work
+# llifstream (and other) llcommon classes. It 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_TEST_ADDITIONAL_CFLAGS "-Dllifstream=llus_mock_llifstream"
+ )
- LL_ADD_PROJECT_UNIT_TESTS(llupdaterservice "${llupdater_service_TEST_SOURCE_FILES}")
+ LL_ADD_PROJECT_UNIT_TESTS(llupdaterservice "${llupdater_service_TEST_SOURCE_FILES}" ${test_libs})
+endif (NOT LINUX)
endif(LL_TESTS)
set(UPDATER_INCLUDE_DIRS
diff --git a/indra/viewer_components/updater/llupdatechecker.cpp b/indra/viewer_components/updater/llupdatechecker.cpp
index 8da4f88905..1bb5e95740 100755..100644
--- a/indra/viewer_components/updater/llupdatechecker.cpp
+++ b/indra/viewer_components/updater/llupdatechecker.cpp
@@ -26,10 +26,10 @@
#include "linden_common.h"
#include <stdexcept>
#include <boost/format.hpp>
-#include "llhttpclient.h"
#include "llsd.h"
#include "llupdatechecker.h"
#include "lluri.h"
+#include "llcorehttputil.h"
#if LL_DARWIN
#include <CoreServices/CoreServices.h>
#endif
@@ -53,15 +53,12 @@ public:
// LLUpdateChecker
//-----------------------------------------------------------------------------
-
-
LLUpdateChecker::LLUpdateChecker(LLUpdateChecker::Client & client):
mImplementation(new LLUpdateChecker::Implementation(client))
{
; // No op.
}
-
void LLUpdateChecker::checkVersion(std::string const & urlBase,
std::string const & channel,
std::string const & version,
@@ -74,11 +71,8 @@ void LLUpdateChecker::checkVersion(std::string const & urlBase,
}
-
// LLUpdateChecker::Implementation
//-----------------------------------------------------------------------------
-
-
const char * LLUpdateChecker::Implementation::sProtocolVersion = "v1.1";
@@ -121,57 +115,58 @@ void LLUpdateChecker::Implementation::checkVersion(std::string const & urlBase,
std::string checkUrl = buildUrl(urlBase, channel, version, platform, platform_version, uniqueid, willing_to_test);
LL_INFOS("UpdaterService") << "checking for updates at " << checkUrl << LL_ENDL;
-
- mHttpClient.get(checkUrl, this);
- }
- else
- {
- LL_WARNS("UpdaterService") << "attempting to restart a check when one is in progress; ignored" << LL_ENDL;
- }
-}
-void LLUpdateChecker::Implementation::httpCompleted()
-{
- mInProgress = false;
+ LLCoros::instance().launch("LLUpdateChecker::Implementation::checkVersionCoro",
+ boost::bind(&Implementation::checkVersionCoro, this, checkUrl));
- S32 status = getStatus();
- const LLSD& content = getContent();
- const std::string& reason = getReason();
- if(status != 200)
- {
- std::string server_error;
- if ( content.has("error_code") )
- {
- server_error += content["error_code"].asString();
- }
- if ( content.has("error_text") )
- {
- server_error += server_error.empty() ? "" : ": ";
- server_error += content["error_text"].asString();
- }
-
- LL_WARNS("UpdaterService") << "response error " << status
- << " " << reason
- << " (" << server_error << ")"
- << LL_ENDL;
- mClient.error(reason);
}
else
{
- mClient.response(content);
+ LL_WARNS("UpdaterService") << "attempting to restart a check when one is in progress; ignored" << LL_ENDL;
}
}
-
-void LLUpdateChecker::Implementation::httpFailure()
+void LLUpdateChecker::Implementation::checkVersionCoro(std::string url)
{
- const std::string& reason = getReason();
- mInProgress = false;
- LL_WARNS("UpdaterService") << "update check failed; " << reason << LL_ENDL;
- mClient.error(reason);
+ LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
+ LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t
+ httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("checkVersionCoro", httpPolicy));
+ LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest);
+
+ LL_INFOS("checkVersionCoro") << "Getting update information from " << url << LL_ENDL;
+
+ LLSD result = httpAdapter->getAndSuspend(httpRequest, url);
+
+ LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
+ LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
+
+ mInProgress = false;
+
+ if (status != LLCore::HttpStatus(HTTP_OK))
+ {
+ std::string server_error;
+ if (result.has("error_code"))
+ {
+ server_error += result["error_code"].asString();
+ }
+ if (result.has("error_text"))
+ {
+ server_error += server_error.empty() ? "" : ": ";
+ server_error += result["error_text"].asString();
+ }
+
+ LL_WARNS("UpdaterService") << "response error " << status.getStatus()
+ << " " << status.toString()
+ << " (" << server_error << ")"
+ << LL_ENDL;
+ mClient.error(status.toString());
+ return;
+ }
+
+ result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS);
+ mClient.response(result);
}
-
std::string LLUpdateChecker::Implementation::buildUrl(std::string const & urlBase,
std::string const & channel,
std::string const & version,
diff --git a/indra/viewer_components/updater/llupdatechecker.h b/indra/viewer_components/updater/llupdatechecker.h
index 3163a6d53c..d10ea4cf42 100755..100644
--- a/indra/viewer_components/updater/llupdatechecker.h
+++ b/indra/viewer_components/updater/llupdatechecker.h
@@ -30,61 +30,27 @@
#include <boost/shared_ptr.hpp>
#include "llmd5.h"
-#include "llhttpclient.h"
+#include "lleventcoro.h"
+#include "llcoros.h"
//
// Implements asynchronous checking for updates.
//
class LLUpdateChecker {
public:
- class Client;
- class Implementation: public LLHTTPClient::Responder
- {
- public:
- Implementation(Client & client);
- ~Implementation();
- void checkVersion(std::string const & urlBase,
- std::string const & channel,
- std::string const & version,
- std::string const & platform,
- std::string const & platform_version,
- unsigned char uniqueid[MD5HEX_STR_SIZE],
- bool willing_to_test
- );
-
- protected:
- // Responder:
- virtual void httpCompleted();
- virtual void httpFailure();
-
- private:
- static const char * sLegacyProtocolVersion;
- static const char * sProtocolVersion;
- const char* mProtocol;
-
- Client & mClient;
- LLHTTPClient mHttpClient;
- bool mInProgress;
- std::string mVersion;
- std::string mUrlBase;
- std::string mChannel;
- std::string mPlatform;
- std::string mPlatformVersion;
- unsigned char mUniqueId[MD5HEX_STR_SIZE];
- bool mWillingToTest;
-
- std::string buildUrl(std::string const & urlBase,
- std::string const & channel,
- std::string const & version,
- std::string const & platform,
- std::string const & platform_version,
- unsigned char uniqueid[MD5HEX_STR_SIZE],
- bool willing_to_test);
-
- LOG_CLASS(LLUpdateChecker::Implementation);
- };
+ //
+ // The client interface implemented by a requestor checking for an update.
+ //
+ class Client
+ {
+ public:
+ // An error occurred while checking for an update.
+ virtual void error(std::string const & message) = 0;
+
+ // A successful response was received from the viewer version manager
+ virtual void response(LLSD const & content) = 0;
+ };
-
// An exception that may be raised on check errors.
class CheckError;
@@ -100,25 +66,54 @@ public:
bool willing_to_test);
private:
- LLPointer<Implementation> mImplementation;
-};
+ class Implementation
+ {
+ public:
+ typedef boost::shared_ptr<Implementation> ptr_t;
+ Implementation(Client & client);
+ ~Implementation();
+ void checkVersion(std::string const & urlBase,
+ std::string const & channel,
+ std::string const & version,
+ std::string const & platform,
+ std::string const & platform_version,
+ unsigned char uniqueid[MD5HEX_STR_SIZE],
+ bool willing_to_test
+ );
-class LLURI; // From lluri.h
+ private:
+ static const char * sLegacyProtocolVersion;
+ static const char * sProtocolVersion;
+ const char* mProtocol;
-//
-// 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 successful response was received from the viewer version manager
- virtual void response(LLSD const & content) = 0;
-};
+ Client & mClient;
+ bool mInProgress;
+ std::string mVersion;
+ std::string mUrlBase;
+ std::string mChannel;
+ std::string mPlatform;
+ std::string mPlatformVersion;
+ unsigned char mUniqueId[MD5HEX_STR_SIZE];
+ bool mWillingToTest;
+
+ std::string buildUrl(std::string const & urlBase,
+ std::string const & channel,
+ std::string const & version,
+ std::string const & platform,
+ std::string const & platform_version,
+ unsigned char uniqueid[MD5HEX_STR_SIZE],
+ bool willing_to_test);
+
+ void checkVersionCoro(std::string url);
+ LOG_CLASS(LLUpdateChecker::Implementation);
+ };
+
+
+ Implementation::ptr_t mImplementation;
+ //LLPointer<Implementation> mImplementation;
+};
#endif
diff --git a/indra/viewer_components/updater/llupdatedownloader.cpp b/indra/viewer_components/updater/llupdatedownloader.cpp
index f868e5cc2c..382689afa0 100755..100644
--- a/indra/viewer_components/updater/llupdatedownloader.cpp
+++ b/indra/viewer_components/updater/llupdatedownloader.cpp
@@ -26,7 +26,7 @@
#include "linden_common.h"
#include "llupdatedownloader.h"
-
+#include "httpcommon.h"
#include <stdexcept>
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
@@ -39,7 +39,6 @@
#include "llsdserialize.h"
#include "llthread.h"
#include "llupdaterservice.h"
-#include "llcurl.h"
class LLUpdateDownloader::Implementation:
public LLThread
@@ -57,7 +56,7 @@ public:
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);
+ int onProgress(curl_off_t downloadSize, curl_off_t bytesDownloaded);
void resume(void);
void setBandwidthLimit(U64 bytesPerSecond);
@@ -65,7 +64,7 @@ private:
curl_off_t mBandwidthLimit;
bool mCancelled;
LLUpdateDownloader::Client & mClient;
- CURL * mCurl;
+ LLCore::LLHttp::CURL_ptr mCurl;
LLSD mDownloadData;
llofstream mDownloadStream;
unsigned char mDownloadPercent;
@@ -175,11 +174,11 @@ namespace {
}
- int progress_callback(void * downloader,
- double dowloadTotal,
- double downloadNow,
- double uploadTotal,
- double uploadNow)
+ int xferinfo_callback(void * downloader,
+ curl_off_t dowloadTotal,
+ curl_off_t downloadNow,
+ curl_off_t uploadTotal,
+ curl_off_t uploadNow)
{
return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->
onProgress(dowloadTotal, downloadNow);
@@ -192,7 +191,7 @@ LLUpdateDownloader::Implementation::Implementation(LLUpdateDownloader::Client &
mBandwidthLimit(0),
mCancelled(false),
mClient(client),
- mCurl(0),
+ mCurl(),
mDownloadPercent(0),
mHeaderList(0)
{
@@ -212,10 +211,7 @@ LLUpdateDownloader::Implementation::~Implementation()
{
; // No op.
}
- if(mCurl)
- {
- LLCurl::deleteEasyHandle(mCurl);
- }
+ mCurl.reset();
}
@@ -331,9 +327,9 @@ void LLUpdateDownloader::Implementation::setBandwidthLimit(U64 bytesPerSecond)
{
if((mBandwidthLimit != bytesPerSecond) && isDownloading() && !mDownloadData["required"].asBoolean())
{
- llassert(mCurl != 0);
+ llassert(static_cast<bool>(mCurl));
mBandwidthLimit = bytesPerSecond;
- CURLcode code = curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, &mBandwidthLimit);
+ CURLcode code = curl_easy_setopt(mCurl.get(), CURLOPT_MAX_RECV_SPEED_LARGE, &mBandwidthLimit);
if(code != CURLE_OK)
{
LL_WARNS("UpdaterService") << "unable to change dowload bandwidth" << LL_ENDL;
@@ -390,9 +386,9 @@ size_t LLUpdateDownloader::Implementation::onBody(void * buffer, size_t size)
}
-int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double bytesDownloaded)
+int LLUpdateDownloader::Implementation::onProgress(curl_off_t downloadSize, curl_off_t bytesDownloaded)
{
- int downloadPercent = static_cast<int>(100. * (bytesDownloaded / downloadSize));
+ int downloadPercent = static_cast<int>(100.0 * ((double) bytesDownloaded / (double) downloadSize));
if(downloadPercent > mDownloadPercent) {
mDownloadPercent = downloadPercent;
@@ -400,8 +396,8 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b
event["pump"] = LLUpdaterService::pumpName();
LLSD payload;
payload["type"] = LLSD(LLUpdaterService::PROGRESS);
- payload["download_size"] = downloadSize;
- payload["bytes_downloaded"] = bytesDownloaded;
+ payload["download_size"] = (LLSD::Integer) downloadSize;
+ payload["bytes_downloaded"] = (LLSD::Integer) bytesDownloaded;
event["payload"] = payload;
LLEventPumps::instance().obtain("mainlooprepeater").post(event);
@@ -416,7 +412,7 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b
void LLUpdateDownloader::Implementation::run(void)
{
- CURLcode code = curl_easy_perform(mCurl);
+ CURLcode code = curl_easy_perform(mCurl.get());
mDownloadStream.close();
if(code == CURLE_OK)
{
@@ -460,36 +456,39 @@ void LLUpdateDownloader::Implementation::run(void)
void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url, bool processHeader)
{
- if(mCurl == 0)
+ if(!mCurl)
{
- mCurl = LLCurl::newEasyHandle();
+ mCurl = LLCore::LLHttp::createEasyHandle();
}
else
{
- curl_easy_reset(mCurl);
+ curl_easy_reset(mCurl.get());
}
- if(mCurl == 0)
+ if(!mCurl)
{
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));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_NOSIGNAL, true));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_FOLLOWLOCATION, true));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_WRITEFUNCTION, &write_function));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), 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));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_HEADERFUNCTION, &header_function));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_HEADERDATA, this));
+ }
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_HTTPGET, true));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_URL, url.c_str()));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_XFERINFOFUNCTION, &xferinfo_callback));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_XFERINFODATA, this));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_NOPROGRESS, 0));
// 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));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_MAX_RECV_SPEED_LARGE, limit));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_CAINFO, gDirUtilp->getCAFile().c_str()));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_SSL_VERIFYHOST, 2));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_SSL_VERIFYPEER, 1));
mDownloadPercent = 0;
}
@@ -511,7 +510,7 @@ void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte)
{
throw DownloadError("cannot add Range header");
}
- throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaderList));
+ throwOnCurlError(curl_easy_setopt(mCurl.get(), CURLOPT_HTTPHEADER, mHeaderList));
mDownloadStream.open(mDownloadData["path"].asString().c_str(),
std::ios_base::out | std::ios_base::binary | std::ios_base::app);
diff --git a/indra/viewer_components/updater/llupdatedownloader.h b/indra/viewer_components/updater/llupdatedownloader.h
index f759988f12..f759988f12 100755..100644
--- a/indra/viewer_components/updater/llupdatedownloader.h
+++ b/indra/viewer_components/updater/llupdatedownloader.h
diff --git a/indra/viewer_components/updater/llupdateinstaller.cpp b/indra/viewer_components/updater/llupdateinstaller.cpp
index a0e2c0b362..a0e2c0b362 100755..100644
--- a/indra/viewer_components/updater/llupdateinstaller.cpp
+++ b/indra/viewer_components/updater/llupdateinstaller.cpp
diff --git a/indra/viewer_components/updater/llupdateinstaller.h b/indra/viewer_components/updater/llupdateinstaller.h
index fe5b1d19b5..fe5b1d19b5 100755..100644
--- a/indra/viewer_components/updater/llupdateinstaller.h
+++ b/indra/viewer_components/updater/llupdateinstaller.h
diff --git a/indra/viewer_components/updater/llupdaterservice.cpp b/indra/viewer_components/updater/llupdaterservice.cpp
index 788955a1b2..788955a1b2 100755..100644
--- a/indra/viewer_components/updater/llupdaterservice.cpp
+++ b/indra/viewer_components/updater/llupdaterservice.cpp
diff --git a/indra/viewer_components/updater/llupdaterservice.h b/indra/viewer_components/updater/llupdaterservice.h
index 95bbe1695c..95bbe1695c 100755..100644
--- a/indra/viewer_components/updater/llupdaterservice.h
+++ b/indra/viewer_components/updater/llupdaterservice.h
diff --git a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
index 759e41ef4c..759e41ef4c 100755..100644
--- a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp
+++ b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp