summaryrefslogtreecommitdiff
path: root/indra/viewer_components/updater/llupdaterservice.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/viewer_components/updater/llupdaterservice.cpp')
-rwxr-xr-xindra/viewer_components/updater/llupdaterservice.cpp710
1 files changed, 710 insertions, 0 deletions
diff --git a/indra/viewer_components/updater/llupdaterservice.cpp b/indra/viewer_components/updater/llupdaterservice.cpp
new file mode 100755
index 0000000000..c152493a51
--- /dev/null
+++ b/indra/viewer_components/updater/llupdaterservice.cpp
@@ -0,0 +1,710 @@
+/**
+ * @file llupdaterservice.cpp
+ *
+ * $LicenseInfo:firstyear=2010&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "linden_common.h"
+
+#include "llupdaterservice.h"
+
+#include "llupdatedownloader.h"
+#include "llevents.h"
+#include "lltimer.h"
+#include "llupdatechecker.h"
+#include "llupdateinstaller.h"
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include "lldir.h"
+#include "llsdserialize.h"
+#include "llfile.h"
+#include "llviewernetwork.h"
+
+#if LL_WINDOWS
+#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
+#endif
+
+#if ! defined(LL_VIEWER_VERSION_MAJOR) \
+ || ! defined(LL_VIEWER_VERSION_MINOR) \
+ || ! defined(LL_VIEWER_VERSION_PATCH) \
+ || ! defined(LL_VIEWER_VERSION_BUILD)
+#error "Version information is undefined"
+#endif
+
+namespace
+{
+ boost::weak_ptr<LLUpdaterServiceImpl> gUpdater;
+
+ const std::string UPDATE_MARKER_FILENAME("SecondLifeUpdateReady.xml");
+ std::string update_marker_path()
+ {
+ return gDirUtilp->getExpandedFilename(LL_PATH_LOGS,
+ UPDATE_MARKER_FILENAME);
+ }
+
+ std::string install_script_path(void)
+ {
+#ifdef LL_WINDOWS
+ std::string scriptFile = "update_install.bat";
+#elif LL_DARWIN
+ std::string scriptFile = "update_install.py";
+#else
+ std::string scriptFile = "update_install";
+#endif
+ return gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, scriptFile);
+ }
+
+ LLInstallScriptMode install_script_mode(void)
+ {
+#ifdef LL_WINDOWS
+ return LL_COPY_INSTALL_SCRIPT_TO_TEMP;
+#else
+ // This is important on Mac because update_install.py looks at its own
+ // script pathname to discover the viewer app bundle to update.
+ return LL_RUN_INSTALL_SCRIPT_IN_PLACE;
+#endif
+ };
+
+}
+
+class LLUpdaterServiceImpl :
+ public LLUpdateChecker::Client,
+ public LLUpdateDownloader::Client
+{
+ static const std::string sListenerName;
+
+ std::string mProtocolVersion;
+ std::string mChannel;
+ std::string mVersion;
+ std::string mPlatform;
+ std::string mPlatformVersion;
+ unsigned char mUniqueId[MD5HEX_STR_SIZE];
+ bool mWillingToTest;
+
+ unsigned int mCheckPeriod;
+ bool mIsChecking;
+ bool mIsDownloading;
+
+ LLUpdateChecker mUpdateChecker;
+ LLUpdateDownloader mUpdateDownloader;
+ LLTimer mTimer;
+
+ LLUpdaterService::app_exit_callback_t mAppExitCallback;
+
+ LLUpdaterService::eUpdaterState mState;
+
+ LOG_CLASS(LLUpdaterServiceImpl);
+
+public:
+ LLUpdaterServiceImpl();
+ virtual ~LLUpdaterServiceImpl();
+
+ void initialize(const std::string& channel,
+ const std::string& version,
+ const std::string& platform,
+ const std::string& platform_version,
+ const unsigned char uniqueid[MD5HEX_STR_SIZE],
+ const bool& willing_to_test
+ );
+
+ void setCheckPeriod(unsigned int seconds);
+ void setBandwidthLimit(U64 bytesPerSecond);
+
+ void startChecking(bool install_if_ready);
+ void stopChecking();
+ bool isChecking();
+ LLUpdaterService::eUpdaterState getState();
+
+ void setAppExitCallback(LLUpdaterService::app_exit_callback_t aecb) { mAppExitCallback = aecb;}
+ std::string updatedVersion(void);
+
+ bool checkForInstall(bool launchInstaller); // Test if a local install is ready.
+ bool checkForResume(); // Test for resumeable d/l.
+
+ // LLUpdateChecker::Client:
+ virtual void error(std::string const & message);
+
+ // A successful response was received from the viewer version manager
+ virtual void response(LLSD const & content);
+
+ // LLUpdateDownloader::Client
+ void downloadComplete(LLSD const & data);
+ void downloadError(std::string const & message);
+
+ bool onMainLoop(LLSD const & event);
+
+private:
+ std::string mNewChannel;
+ std::string mNewVersion;
+
+ void restartTimer(unsigned int seconds);
+ void setState(LLUpdaterService::eUpdaterState state);
+ void stopTimer();
+};
+
+const std::string LLUpdaterServiceImpl::sListenerName = "LLUpdaterServiceImpl";
+
+LLUpdaterServiceImpl::LLUpdaterServiceImpl() :
+ mIsChecking(false),
+ mIsDownloading(false),
+ mCheckPeriod(0),
+ mUpdateChecker(*this),
+ mUpdateDownloader(*this),
+ mState(LLUpdaterService::INITIAL)
+{
+}
+
+LLUpdaterServiceImpl::~LLUpdaterServiceImpl()
+{
+ LL_INFOS("UpdaterService") << "shutting down updater service" << LL_ENDL;
+ LLEventPumps::instance().obtain("mainloop").stopListening(sListenerName);
+}
+
+void LLUpdaterServiceImpl::initialize(const std::string& channel,
+ const std::string& version,
+ const std::string& platform,
+ const std::string& platform_version,
+ const unsigned char uniqueid[MD5HEX_STR_SIZE],
+ const bool& willing_to_test)
+{
+ if(mIsChecking || mIsDownloading)
+ {
+ throw LLUpdaterService::UsageError("LLUpdaterService::initialize call "
+ "while updater is running.");
+ }
+
+ mChannel = channel;
+ mVersion = version;
+ mPlatform = platform;
+ mPlatformVersion = platform_version;
+ memcpy(mUniqueId, uniqueid, MD5HEX_STR_SIZE);
+ mWillingToTest = willing_to_test;
+ LL_DEBUGS("UpdaterService")
+ << "\n channel: " << mChannel
+ << "\n version: " << mVersion
+ << "\n uniqueid: " << mUniqueId
+ << "\n willing: " << ( mWillingToTest ? "testok" : "testno" )
+ << LL_ENDL;
+}
+
+void LLUpdaterServiceImpl::setCheckPeriod(unsigned int seconds)
+{
+ mCheckPeriod = seconds;
+}
+
+void LLUpdaterServiceImpl::setBandwidthLimit(U64 bytesPerSecond)
+{
+ mUpdateDownloader.setBandwidthLimit(bytesPerSecond);
+}
+
+void LLUpdaterServiceImpl::startChecking(bool install_if_ready)
+{
+ if(mChannel.empty() || mVersion.empty())
+ {
+ throw LLUpdaterService::UsageError("Set params before call to "
+ "LLUpdaterService::startCheck().");
+ }
+
+ mIsChecking = true;
+
+ // Check to see if an install is ready.
+ bool has_install = checkForInstall(install_if_ready);
+ if(!has_install)
+ {
+ checkForResume(); // will set mIsDownloading to true if resuming
+
+ if(!mIsDownloading)
+ {
+ setState(LLUpdaterService::CHECKING_FOR_UPDATE);
+
+ // Checking can only occur during the mainloop.
+ // reset the timer to 0 so that the next mainloop event
+ // triggers a check;
+ restartTimer(0);
+ }
+ else
+ {
+ setState(LLUpdaterService::DOWNLOADING);
+ }
+ }
+}
+
+void LLUpdaterServiceImpl::stopChecking()
+{
+ if(mIsChecking)
+ {
+ mIsChecking = false;
+ stopTimer();
+ }
+
+ if(mIsDownloading)
+ {
+ mUpdateDownloader.cancel();
+ mIsDownloading = false;
+ }
+
+ setState(LLUpdaterService::TERMINAL);
+}
+
+bool LLUpdaterServiceImpl::isChecking()
+{
+ return mIsChecking;
+}
+
+LLUpdaterService::eUpdaterState LLUpdaterServiceImpl::getState()
+{
+ return mState;
+}
+
+std::string LLUpdaterServiceImpl::updatedVersion(void)
+{
+ return mNewVersion;
+}
+
+bool LLUpdaterServiceImpl::checkForInstall(bool launchInstaller)
+{
+ bool foundInstall = false; // return true if install is found.
+
+ llifstream update_marker(update_marker_path().c_str(),
+ std::ios::in | std::ios::binary);
+
+ if(update_marker.is_open())
+ {
+ // Found an update info - now lets see if its valid.
+ LLSD update_info;
+ LLSDSerialize::fromXMLDocument(update_info, update_marker);
+ update_marker.close();
+
+ // Get the path to the installer file.
+ std::string path(update_info.get("path"));
+ std::string downloader_version(update_info["current_version"]);
+ if (downloader_version != ll_get_version())
+ {
+ // This viewer is not the same version as the one that downloaded
+ // the update. Do not install this update.
+ LL_INFOS("UpdaterService") << "ignoring update downloaded by "
+ << "different viewer version "
+ << downloader_version << LL_ENDL;
+ if (! path.empty())
+ {
+ LL_INFOS("UpdaterService") << "removing " << path << LL_ENDL;
+ LLFile::remove(path);
+ LLFile::remove(update_marker_path());
+ }
+
+ foundInstall = false;
+ }
+ else if (path.empty())
+ {
+ LL_WARNS("UpdaterService") << "Marker file " << update_marker_path()
+ << " 'path' entry empty, ignoring" << LL_ENDL;
+ foundInstall = false;
+ }
+ else if (! LLFile::isfile(path))
+ {
+ LL_WARNS("UpdaterService") << "Nonexistent installer " << path
+ << ", ignoring" << LL_ENDL;
+ foundInstall = false;
+ }
+ else
+ {
+ if(launchInstaller)
+ {
+ setState(LLUpdaterService::INSTALLING);
+
+ LLFile::remove(update_marker_path());
+
+ int result = ll_install_update(install_script_path(),
+ path,
+ update_info["required"].asBoolean(),
+ install_script_mode());
+
+ if((result == 0) && mAppExitCallback)
+ {
+ mAppExitCallback();
+ }
+ else if(result != 0)
+ {
+ LL_WARNS("UpdaterService") << "failed to run update install script" << LL_ENDL;
+ }
+ else
+ {
+ ; // No op.
+ }
+ }
+
+ foundInstall = true;
+ }
+ }
+ return foundInstall;
+}
+
+bool LLUpdaterServiceImpl::checkForResume()
+{
+ bool result = false;
+ std::string download_marker_path = mUpdateDownloader.downloadMarkerPath();
+ if(LLFile::isfile(download_marker_path))
+ {
+ llifstream download_marker_stream(download_marker_path.c_str(),
+ std::ios::in | std::ios::binary);
+ if(download_marker_stream.is_open())
+ {
+ LLSD download_info;
+ LLSDSerialize::fromXMLDocument(download_info, download_marker_stream);
+ download_marker_stream.close();
+ std::string downloader_version(download_info["current_version"]);
+ if (downloader_version == ll_get_version())
+ {
+ mIsDownloading = true;
+ mNewVersion = download_info["update_version"].asString();
+ mNewChannel = download_info["update_channel"].asString();
+ mUpdateDownloader.resume();
+ result = true;
+ }
+ else
+ {
+ // The viewer that started this download is not the same as this viewer; ignore.
+ LL_INFOS("UpdaterService") << "ignoring partial download "
+ << "from different viewer version "
+ << downloader_version << LL_ENDL;
+ std::string path = download_info["path"].asString();
+ if(!path.empty())
+ {
+ LL_INFOS("UpdaterService") << "removing " << path << LL_ENDL;
+ LLFile::remove(path);
+ }
+ LLFile::remove(download_marker_path);
+ }
+ }
+ }
+ return result;
+}
+
+void LLUpdaterServiceImpl::error(std::string const & message)
+{
+ if(mIsChecking)
+ {
+ setState(LLUpdaterService::TEMPORARY_ERROR);
+ restartTimer(mCheckPeriod);
+ }
+}
+
+// A successful response was received from the viewer version manager
+void LLUpdaterServiceImpl::response(LLSD const & content)
+{
+ if(!content.asBoolean()) // an empty response means "no update"
+ {
+ LL_INFOS("UpdaterService") << "up to date" << LL_ENDL;
+ if(mIsChecking)
+ {
+ restartTimer(mCheckPeriod);
+ }
+
+ setState(LLUpdaterService::UP_TO_DATE);
+ }
+ else if ( content.isMap() && content.has("url") )
+ {
+ // there is an update available...
+ stopTimer();
+ mNewChannel = content["channel"].asString();
+ if (mNewChannel.empty())
+ {
+ LL_INFOS("UpdaterService") << "no channel supplied, assuming current channel" << LL_ENDL;
+ mNewChannel = mChannel;
+ }
+ mNewVersion = content["version"].asString();
+ mIsDownloading = true;
+ setState(LLUpdaterService::DOWNLOADING);
+ BOOL required = content["required"].asBoolean();
+ LLURI url(content["url"].asString());
+ std::string more_info = content["more_info"].asString();
+ LL_DEBUGS("UpdaterService")
+ << "Starting download of "
+ << ( required ? "required" : "optional" ) << " update"
+ << " to channel '" << mNewChannel << "' version " << mNewVersion
+ << " more info '" << more_info << "'"
+ << LL_ENDL;
+ mUpdateDownloader.download(url, content["hash"].asString(), mNewChannel, mNewVersion, more_info, required);
+ }
+ else
+ {
+ LL_WARNS("UpdaterService") << "Invalid update query response ignored; retry in "
+ << mCheckPeriod << " seconds" << LL_ENDL;
+ restartTimer(mCheckPeriod);
+ }
+}
+
+void LLUpdaterServiceImpl::downloadComplete(LLSD const & data)
+{
+ mIsDownloading = false;
+
+ // Save out the download data to the SecondLifeUpdateReady
+ // marker file.
+ llofstream update_marker(update_marker_path().c_str());
+ LLSDSerialize::toPrettyXML(data, update_marker);
+
+ LLSD event;
+ event["pump"] = LLUpdaterService::pumpName();
+ LLSD payload;
+ payload["type"] = LLSD(LLUpdaterService::DOWNLOAD_COMPLETE);
+ payload["required"] = data["required"];
+ payload["version"] = mNewVersion;
+ payload["channel"] = mNewChannel;
+ payload["info_url"] = data["info_url"];
+ event["payload"] = payload;
+ LL_DEBUGS("UpdaterService")
+ << "Download complete "
+ << ( data["required"].asBoolean() ? "required" : "optional" )
+ << " channel " << mNewChannel
+ << " version " << mNewVersion
+ << " info " << data["info_url"].asString()
+ << LL_ENDL;
+
+ LLEventPumps::instance().obtain("mainlooprepeater").post(event);
+
+ setState(LLUpdaterService::TERMINAL);
+}
+
+void LLUpdaterServiceImpl::downloadError(std::string const & message)
+{
+ LL_INFOS("UpdaterService") << "Error downloading: " << message << LL_ENDL;
+
+ mIsDownloading = false;
+
+ // Restart the timer on error
+ if(mIsChecking)
+ {
+ restartTimer(mCheckPeriod);
+ }
+
+ LLSD event;
+ event["pump"] = LLUpdaterService::pumpName();
+ LLSD payload;
+ payload["type"] = LLSD(LLUpdaterService::DOWNLOAD_ERROR);
+ payload["message"] = message;
+ event["payload"] = payload;
+ LLEventPumps::instance().obtain("mainlooprepeater").post(event);
+
+ setState(LLUpdaterService::FAILURE);
+}
+
+void LLUpdaterServiceImpl::restartTimer(unsigned int seconds)
+{
+ LL_INFOS("UpdaterService") << "will check for update again in " <<
+ seconds << " seconds" << LL_ENDL;
+ mTimer.start();
+ mTimer.setTimerExpirySec((F32)seconds);
+ LLEventPumps::instance().obtain("mainloop").listen(
+ sListenerName, boost::bind(&LLUpdaterServiceImpl::onMainLoop, this, _1));
+}
+
+void LLUpdaterServiceImpl::setState(LLUpdaterService::eUpdaterState state)
+{
+ if(state != mState)
+ {
+ mState = state;
+
+ LLSD event;
+ event["pump"] = LLUpdaterService::pumpName();
+ LLSD payload;
+ payload["type"] = LLSD(LLUpdaterService::STATE_CHANGE);
+ payload["state"] = state;
+ event["payload"] = payload;
+ LLEventPumps::instance().obtain("mainlooprepeater").post(event);
+
+ LL_INFOS("UpdaterService") << "setting state to " << state << LL_ENDL;
+ }
+ else
+ {
+ ; // State unchanged; noop.
+ }
+}
+
+void LLUpdaterServiceImpl::stopTimer()
+{
+ mTimer.stop();
+ LLEventPumps::instance().obtain("mainloop").stopListening(sListenerName);
+}
+
+bool LLUpdaterServiceImpl::onMainLoop(LLSD const & event)
+{
+ if(mTimer.getStarted() && mTimer.hasExpired())
+ {
+ stopTimer();
+
+ // Check for failed install.
+ if(LLFile::isfile(ll_install_failed_marker_path()))
+ {
+ LL_DEBUGS("UpdaterService") << "found marker " << ll_install_failed_marker_path() << LL_ENDL;
+ int requiredValue = 0;
+ {
+ llifstream stream(ll_install_failed_marker_path().c_str());
+ stream >> requiredValue;
+ if(stream.fail())
+ {
+ requiredValue = 0;
+ }
+ }
+ // TODO: notify the user.
+ LL_WARNS("UpdaterService") << "last install attempt failed" << LL_ENDL;;
+ LLFile::remove(ll_install_failed_marker_path());
+
+ LLSD event;
+ event["type"] = LLSD(LLUpdaterService::INSTALL_ERROR);
+ event["required"] = LLSD(requiredValue);
+ LLEventPumps::instance().obtain(LLUpdaterService::pumpName()).post(event);
+
+ setState(LLUpdaterService::TERMINAL);
+ }
+ else
+ {
+ std::string query_url = LLGridManager::getInstance()->getUpdateServiceURL();
+ if ( !query_url.empty() )
+ {
+ mUpdateChecker.checkVersion(query_url, mChannel, mVersion,
+ mPlatform, mPlatformVersion, mUniqueId,
+ mWillingToTest);
+ setState(LLUpdaterService::CHECKING_FOR_UPDATE);
+ }
+ else
+ {
+ LL_WARNS("UpdaterService")
+ << "No updater service defined for grid '" << LLGridManager::getInstance()->getGrid()
+ << "' will check again in " << mCheckPeriod << " seconds"
+ << LL_ENDL;
+ // Because the grid can be changed after the viewer is started (when the first check takes place)
+ // but before the user logs in, the next check may be on a different grid, so set the retry timer
+ // even though this check did not happen. The default time is once an hour, and if we're not
+ // doing the check anyway the performance impact is completely insignificant.
+ restartTimer(mCheckPeriod);
+ }
+ }
+ }
+ else
+ {
+ // Keep on waiting...
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------
+// Facade interface
+
+std::string const & LLUpdaterService::pumpName(void)
+{
+ static std::string name("updater_service");
+ return name;
+}
+
+bool LLUpdaterService::updateReadyToInstall(void)
+{
+ return LLFile::isfile(update_marker_path());
+}
+
+LLUpdaterService::LLUpdaterService()
+{
+ if(gUpdater.expired())
+ {
+ mImpl =
+ boost::shared_ptr<LLUpdaterServiceImpl>(new LLUpdaterServiceImpl());
+ gUpdater = mImpl;
+ }
+ else
+ {
+ mImpl = gUpdater.lock();
+ }
+}
+
+LLUpdaterService::~LLUpdaterService()
+{
+}
+
+void LLUpdaterService::initialize(const std::string& channel,
+ const std::string& version,
+ const std::string& platform,
+ const std::string& platform_version,
+ const unsigned char uniqueid[MD5HEX_STR_SIZE],
+ const bool& willing_to_test
+)
+{
+ mImpl->initialize(channel, version, platform, platform_version, uniqueid, willing_to_test);
+}
+
+void LLUpdaterService::setCheckPeriod(unsigned int seconds)
+{
+ mImpl->setCheckPeriod(seconds);
+}
+
+void LLUpdaterService::setBandwidthLimit(U64 bytesPerSecond)
+{
+ mImpl->setBandwidthLimit(bytesPerSecond);
+}
+
+void LLUpdaterService::startChecking(bool install_if_ready)
+{
+ mImpl->startChecking(install_if_ready);
+}
+
+void LLUpdaterService::stopChecking()
+{
+ mImpl->stopChecking();
+}
+
+bool LLUpdaterService::isChecking()
+{
+ return mImpl->isChecking();
+}
+
+LLUpdaterService::eUpdaterState LLUpdaterService::getState()
+{
+ return mImpl->getState();
+}
+
+void LLUpdaterService::setImplAppExitCallback(LLUpdaterService::app_exit_callback_t aecb)
+{
+ return mImpl->setAppExitCallback(aecb);
+}
+
+std::string LLUpdaterService::updatedVersion(void)
+{
+ return mImpl->updatedVersion();
+}
+
+
+std::string const & ll_get_version(void) {
+ static std::string version("");
+
+ if (version.empty()) {
+ std::ostringstream stream;
+ stream << LL_VIEWER_VERSION_MAJOR << "."
+ << LL_VIEWER_VERSION_MINOR << "."
+ << LL_VIEWER_VERSION_PATCH << "."
+ << LL_VIEWER_VERSION_BUILD;
+ version = stream.str();
+ }
+
+ return version;
+}
+