diff options
Diffstat (limited to 'indra/viewer_components')
14 files changed, 2080 insertions, 1 deletions
diff --git a/indra/viewer_components/CMakeLists.txt b/indra/viewer_components/CMakeLists.txt index 0993b64b14..74c9b4568d 100644 --- a/indra/viewer_components/CMakeLists.txt +++ b/indra/viewer_components/CMakeLists.txt @@ -1,4 +1,4 @@ # -*- cmake -*- add_subdirectory(login) - +add_subdirectory(updater) diff --git a/indra/viewer_components/updater/CMakeLists.txt b/indra/viewer_components/updater/CMakeLists.txt new file mode 100644 index 0000000000..0e288bb496 --- /dev/null +++ b/indra/viewer_components/updater/CMakeLists.txt @@ -0,0 +1,82 @@ +# -*- cmake -*- + +project(updater_service) + +include(00-Common) +if(LL_TESTS) + include(LLAddBuildTest) +endif(LL_TESTS) +include(CMakeCopyIfDifferent) +include(CURL) +include(LLCommon) +include(LLMessage) +include(LLPlugin) +include(LLVFS) + +include_directories( + ${LLCOMMON_INCLUDE_DIRS} + ${LLMESSAGE_INCLUDE_DIRS} + ${LLPLUGIN_INCLUDE_DIRS} + ${LLVFS_INCLUDE_DIRS} + ${CURL_INCLUDE_DIRS} + ) + +set(updater_service_SOURCE_FILES + llupdaterservice.cpp + llupdatechecker.cpp + llupdatedownloader.cpp + llupdateinstaller.cpp + ) + +set(updater_service_HEADER_FILES + llupdaterservice.h + llupdatechecker.h + llupdatedownloader.h + llupdateinstaller.h + ) + +set_source_files_properties(${updater_service_HEADER_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) + +list(APPEND + updater_service_SOURCE_FILES + ${updater_service_HEADER_FILES} + ) + +add_library(llupdaterservice + ${updater_service_SOURCE_FILES} + ) + +target_link_libraries(llupdaterservice + ${LLCOMMON_LIBRARIES} + ${LLMESSAGE_LIBRARIES} + ${LLPLUGIN_LIBRARIES} + ${LLVFS_LIBRARIES} + ) + +if(LL_TESTS) + SET(llupdater_service_TEST_SOURCE_FILES + llupdaterservice.cpp + ) + +# *NOTE:Mani - I was trying to use the preprocessor seam to mock out +# llifstream (and other) llcommon classes. I didn't work +# because of the windows declspec(dllimport)attribute. +#set_source_files_properties( +# llupdaterservice.cpp +# PROPERTIES +# LL_TEST_ADDITIONAL_CFLAGS "-Dllifstream=llus_mock_llifstream" +# ) + + LL_ADD_PROJECT_UNIT_TESTS(llupdaterservice "${llupdater_service_TEST_SOURCE_FILES}") +endif(LL_TESTS) + +set(UPDATER_INCLUDE_DIRS + ${LIBS_OPEN_DIR}/viewer_components/updater + CACHE INTERNAL "" +) + +set(UPDATER_LIBRARIES + llupdaterservice + CACHE INTERNAL "" +) diff --git a/indra/viewer_components/updater/llupdatechecker.cpp b/indra/viewer_components/updater/llupdatechecker.cpp new file mode 100644 index 0000000000..c6aa9b0f11 --- /dev/null +++ b/indra/viewer_components/updater/llupdatechecker.cpp @@ -0,0 +1,194 @@ +/** + * @file llupdaterservice.cpp + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include <stdexcept> +#include <boost/format.hpp> +#include "llhttpclient.h" +#include "llsd.h" +#include "llupdatechecker.h" +#include "lluri.h" + + +#if LL_WINDOWS +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + + +class LLUpdateChecker::CheckError: + public std::runtime_error +{ +public: + CheckError(const char * message): + std::runtime_error(message) + { + ; // No op. + } +}; + + +class LLUpdateChecker::Implementation: + public LLHTTPClient::Responder +{ +public: + Implementation(Client & client); + ~Implementation(); + void check(std::string const & protocolVersion, std::string const & hostUrl, + std::string const & servicePath, std::string channel, std::string version); + + // Responder: + virtual void completed(U32 status, + const std::string & reason, + const LLSD& content); + virtual void error(U32 status, const std::string & reason); + +private: + static const char * sProtocolVersion; + + Client & mClient; + LLHTTPClient mHttpClient; + bool mInProgress; + std::string mVersion; + + std::string buildUrl(std::string const & protocolVersion, std::string const & hostUrl, + std::string const & servicePath, std::string channel, std::string version); + + LOG_CLASS(LLUpdateChecker::Implementation); +}; + + + +// LLUpdateChecker +//----------------------------------------------------------------------------- + + +LLUpdateChecker::LLUpdateChecker(LLUpdateChecker::Client & client): + mImplementation(new LLUpdateChecker::Implementation(client)) +{ + ; // No op. +} + + +void LLUpdateChecker::check(std::string const & protocolVersion, std::string const & hostUrl, + std::string const & servicePath, std::string channel, std::string version) +{ + mImplementation->check(protocolVersion, hostUrl, servicePath, channel, version); +} + + + +// LLUpdateChecker::Implementation +//----------------------------------------------------------------------------- + + +const char * LLUpdateChecker::Implementation::sProtocolVersion = "v1.0"; + + +LLUpdateChecker::Implementation::Implementation(LLUpdateChecker::Client & client): + mClient(client), + mInProgress(false) +{ + ; // No op. +} + + +LLUpdateChecker::Implementation::~Implementation() +{ + ; // No op. +} + + +void LLUpdateChecker::Implementation::check(std::string const & protocolVersion, std::string const & hostUrl, + std::string const & servicePath, std::string channel, std::string version) +{ + llassert(!mInProgress); + + if(protocolVersion != sProtocolVersion) throw CheckError("unsupported protocol"); + + mInProgress = true; + mVersion = version; + std::string checkUrl = buildUrl(protocolVersion, hostUrl, servicePath, channel, version); + LL_INFOS("UpdateCheck") << "checking for updates at " << checkUrl << llendl; + + // The HTTP client will wrap a raw pointer in a boost::intrusive_ptr causing the + // passed object to be silently and automatically deleted. We pass a self- + // referential intrusive pointer to which we add a reference to keep the + // client from deleting the update checker implementation instance. + LLHTTPClient::ResponderPtr temporaryPtr(this); + boost::intrusive_ptr_add_ref(temporaryPtr.get()); + mHttpClient.get(checkUrl, temporaryPtr); +} + +void LLUpdateChecker::Implementation::completed(U32 status, + const std::string & reason, + const LLSD & content) +{ + mInProgress = false; + + if(status != 200) { + LL_WARNS("UpdateCheck") << "html error " << status << " (" << reason << ")" << llendl; + mClient.error(reason); + } else if(!content.asBoolean()) { + LL_INFOS("UpdateCheck") << "up to date" << llendl; + mClient.upToDate(); + } else if(content["required"].asBoolean()) { + LL_INFOS("UpdateCheck") << "version invalid" << llendl; + LLURI uri(content["url"].asString()); + mClient.requiredUpdate(content["version"].asString(), uri, content["hash"].asString()); + } else { + LL_INFOS("UpdateCheck") << "newer version " << content["version"].asString() << " available" << llendl; + LLURI uri(content["url"].asString()); + mClient.optionalUpdate(content["version"].asString(), uri, content["hash"].asString()); + } +} + + +void LLUpdateChecker::Implementation::error(U32 status, const std::string & reason) +{ + mInProgress = false; + LL_WARNS("UpdateCheck") << "update check failed; " << reason << llendl; + mClient.error(reason); +} + + +std::string LLUpdateChecker::Implementation::buildUrl(std::string const & protocolVersion, std::string const & hostUrl, + std::string const & servicePath, std::string channel, std::string version) +{ +#ifdef LL_WINDOWS + static const char * platform = "win"; +#elif LL_DARWIN + static const char * platform = "mac"; +#else + static const char * platform = "lnx"; +#endif + + LLSD path; + path.append(servicePath); + path.append(protocolVersion); + path.append(channel); + path.append(version); + path.append(platform); + return LLURI::buildHTTP(hostUrl, path).asString(); +} diff --git a/indra/viewer_components/updater/llupdatechecker.h b/indra/viewer_components/updater/llupdatechecker.h new file mode 100644 index 0000000000..cea1f13647 --- /dev/null +++ b/indra/viewer_components/updater/llupdatechecker.h @@ -0,0 +1,82 @@ +/** + * @file llupdatechecker.h + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_UPDATERCHECKER_H +#define LL_UPDATERCHECKER_H + + +#include <boost/shared_ptr.hpp> + + +// +// Implements asynchronous checking for updates. +// +class LLUpdateChecker { +public: + class Client; + class Implementation; + + // An exception that may be raised on check errors. + class CheckError; + + LLUpdateChecker(Client & client); + + // Check status of current app on the given host for the channel and version provided. + void check(std::string const & protocolVersion, std::string const & hostUrl, + std::string const & servicePath, std::string channel, std::string version); + +private: + boost::shared_ptr<Implementation> mImplementation; +}; + + +class LLURI; // From lluri.h + + +// +// The client interface implemented by a requestor checking for an update. +// +class LLUpdateChecker::Client +{ +public: + // An error occurred while checking for an update. + virtual void error(std::string const & message) = 0; + + // A newer version is available, but the current version may still be used. + virtual void optionalUpdate(std::string const & newVersion, + LLURI const & uri, + std::string const & hash) = 0; + + // A newer version is available, and the current version is no longer valid. + virtual void requiredUpdate(std::string const & newVersion, + LLURI const & uri, + std::string const & hash) = 0; + + // The checked version is up to date; no newer version exists. + virtual void upToDate(void) = 0; +}; + + +#endif diff --git a/indra/viewer_components/updater/llupdatedownloader.cpp b/indra/viewer_components/updater/llupdatedownloader.cpp new file mode 100644 index 0000000000..e88d1bf811 --- /dev/null +++ b/indra/viewer_components/updater/llupdatedownloader.cpp @@ -0,0 +1,517 @@ +/** + * @file llupdatedownloader.cpp + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llupdatedownloader.h" + +#include <stdexcept> +#include <boost/format.hpp> +#include <boost/lexical_cast.hpp> +#include <curl/curl.h> +#include "lldir.h" +#include "llevents.h" +#include "llfile.h" +#include "llmd5.h" +#include "llsd.h" +#include "llsdserialize.h" +#include "llthread.h" +#include "llupdaterservice.h" + + +class LLUpdateDownloader::Implementation: + public LLThread +{ +public: + Implementation(LLUpdateDownloader::Client & client); + ~Implementation(); + void cancel(void); + void download(LLURI const & uri, + std::string const & hash, + std::string const & updateVersion, + bool required); + bool isDownloading(void); + size_t onHeader(void * header, size_t size); + size_t onBody(void * header, size_t size); + int onProgress(double downloadSize, double bytesDownloaded); + void resume(void); + void setBandwidthLimit(U64 bytesPerSecond); + +private: + curl_off_t mBandwidthLimit; + bool mCancelled; + LLUpdateDownloader::Client & mClient; + CURL * mCurl; + LLSD mDownloadData; + llofstream mDownloadStream; + unsigned char mDownloadPercent; + std::string mDownloadRecordPath; + curl_slist * mHeaderList; + + void initializeCurlGet(std::string const & url, bool processHeader); + void resumeDownloading(size_t startByte); + void run(void); + void startDownloading(LLURI const & uri, std::string const & hash); + void throwOnCurlError(CURLcode code); + bool validateDownload(void); + + LOG_CLASS(LLUpdateDownloader::Implementation); +}; + + +namespace { + class DownloadError: + public std::runtime_error + { + public: + DownloadError(const char * message): + std::runtime_error(message) + { + ; // No op. + } + }; + + + const char * gSecondLifeUpdateRecord = "SecondLifeUpdateDownload.xml"; +}; + + + +// LLUpdateDownloader +//----------------------------------------------------------------------------- + + + +std::string LLUpdateDownloader::downloadMarkerPath(void) +{ + return gDirUtilp->getExpandedFilename(LL_PATH_LOGS, gSecondLifeUpdateRecord); +} + + +LLUpdateDownloader::LLUpdateDownloader(Client & client): + mImplementation(new LLUpdateDownloader::Implementation(client)) +{ + ; // No op. +} + + +void LLUpdateDownloader::cancel(void) +{ + mImplementation->cancel(); +} + + +void LLUpdateDownloader::download(LLURI const & uri, + std::string const & hash, + std::string const & updateVersion, + bool required) +{ + mImplementation->download(uri, hash, updateVersion, required); +} + + +bool LLUpdateDownloader::isDownloading(void) +{ + return mImplementation->isDownloading(); +} + + +void LLUpdateDownloader::resume(void) +{ + mImplementation->resume(); +} + + +void LLUpdateDownloader::setBandwidthLimit(U64 bytesPerSecond) +{ + mImplementation->setBandwidthLimit(bytesPerSecond); +} + + + +// LLUpdateDownloader::Implementation +//----------------------------------------------------------------------------- + + +namespace { + size_t write_function(void * data, size_t blockSize, size_t blocks, void * downloader) + { + size_t bytes = blockSize * blocks; + return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->onBody(data, bytes); + } + + + size_t header_function(void * data, size_t blockSize, size_t blocks, void * downloader) + { + size_t bytes = blockSize * blocks; + return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->onHeader(data, bytes); + } + + + int progress_callback(void * downloader, + double dowloadTotal, + double downloadNow, + double uploadTotal, + double uploadNow) + { + return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)-> + onProgress(dowloadTotal, downloadNow); + } +} + + +LLUpdateDownloader::Implementation::Implementation(LLUpdateDownloader::Client & client): + LLThread("LLUpdateDownloader"), + mBandwidthLimit(0), + mCancelled(false), + mClient(client), + mCurl(0), + mDownloadPercent(0), + mHeaderList(0) +{ + CURLcode code = curl_global_init(CURL_GLOBAL_ALL); // Just in case. + llverify(code == CURLE_OK); // TODO: real error handling here. +} + + +LLUpdateDownloader::Implementation::~Implementation() +{ + if(isDownloading()) { + cancel(); + shutdown(); + } else { + ; // No op. + } + if(mCurl) curl_easy_cleanup(mCurl); +} + + +void LLUpdateDownloader::Implementation::cancel(void) +{ + mCancelled = true; +} + + +void LLUpdateDownloader::Implementation::download(LLURI const & uri, + std::string const & hash, + std::string const & updateVersion, + bool required) +{ + if(isDownloading()) mClient.downloadError("download in progress"); + + mDownloadRecordPath = downloadMarkerPath(); + mDownloadData = LLSD(); + mDownloadData["required"] = required; + mDownloadData["update_version"] = updateVersion; + try { + startDownloading(uri, hash); + } catch(DownloadError const & e) { + mClient.downloadError(e.what()); + } +} + + +bool LLUpdateDownloader::Implementation::isDownloading(void) +{ + return !isStopped(); +} + + +void LLUpdateDownloader::Implementation::resume(void) +{ + mCancelled = false; + + if(isDownloading()) { + mClient.downloadError("download in progress"); + } + + mDownloadRecordPath = downloadMarkerPath(); + llifstream dataStream(mDownloadRecordPath); + if(!dataStream) { + mClient.downloadError("no download marker"); + return; + } + + LLSDSerialize::fromXMLDocument(mDownloadData, dataStream); + + if(!mDownloadData.asBoolean()) { + mClient.downloadError("no download information in marker"); + return; + } + + std::string filePath = mDownloadData["path"].asString(); + try { + if(LLFile::isfile(filePath)) { + llstat fileStatus; + LLFile::stat(filePath, &fileStatus); + if(fileStatus.st_size != mDownloadData["size"].asInteger()) { + resumeDownloading(fileStatus.st_size); + } else if(!validateDownload()) { + LLFile::remove(filePath); + download(LLURI(mDownloadData["url"].asString()), + mDownloadData["hash"].asString(), + mDownloadData["update_version"].asString(), + mDownloadData["required"].asBoolean()); + } else { + mClient.downloadComplete(mDownloadData); + } + } else { + download(LLURI(mDownloadData["url"].asString()), + mDownloadData["hash"].asString(), + mDownloadData["update_version"].asString(), + mDownloadData["required"].asBoolean()); + } + } catch(DownloadError & e) { + mClient.downloadError(e.what()); + } +} + + +void LLUpdateDownloader::Implementation::setBandwidthLimit(U64 bytesPerSecond) +{ + if((mBandwidthLimit != bytesPerSecond) && isDownloading() && !mDownloadData["required"].asBoolean()) { + llassert(mCurl != 0); + mBandwidthLimit = bytesPerSecond; + CURLcode code = curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, &mBandwidthLimit); + if(code != CURLE_OK) LL_WARNS("UpdateDownload") << + "unable to change dowload bandwidth" << LL_ENDL; + } else { + mBandwidthLimit = bytesPerSecond; + } +} + + +size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size) +{ + char const * headerPtr = reinterpret_cast<const char *> (buffer); + std::string header(headerPtr, headerPtr + size); + size_t colonPosition = header.find(':'); + if(colonPosition == std::string::npos) return size; // HTML response; ignore. + + if(header.substr(0, colonPosition) == "Content-Length") { + try { + size_t firstDigitPos = header.find_first_of("0123456789", colonPosition); + size_t lastDigitPos = header.find_last_of("0123456789"); + std::string contentLength = header.substr(firstDigitPos, lastDigitPos - firstDigitPos + 1); + size_t size = boost::lexical_cast<size_t>(contentLength); + LL_INFOS("UpdateDownload") << "download size is " << size << LL_ENDL; + + mDownloadData["size"] = LLSD(LLSD::Integer(size)); + llofstream odataStream(mDownloadRecordPath); + LLSDSerialize::toPrettyXML(mDownloadData, odataStream); + } catch (std::exception const & e) { + LL_WARNS("UpdateDownload") << "unable to read content length (" + << e.what() << ")" << LL_ENDL; + } + } else { + ; // No op. + } + + return size; +} + + +size_t LLUpdateDownloader::Implementation::onBody(void * buffer, size_t size) +{ + if(mCancelled) return 0; // Forces a write error which will halt curl thread. + if((size == 0) || (buffer == 0)) return 0; + + mDownloadStream.write(reinterpret_cast<const char *>(buffer), size); + if(mDownloadStream.bad()) { + return 0; + } else { + return size; + } +} + + +int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double bytesDownloaded) +{ + int downloadPercent = static_cast<int>(100. * (bytesDownloaded / downloadSize)); + if(downloadPercent > mDownloadPercent) { + mDownloadPercent = downloadPercent; + + LLSD event; + event["pump"] = LLUpdaterService::pumpName(); + LLSD payload; + payload["type"] = LLSD(LLUpdaterService::PROGRESS); + payload["download_size"] = downloadSize; + payload["bytes_downloaded"] = bytesDownloaded; + event["payload"] = payload; + LLEventPumps::instance().obtain("mainlooprepeater").post(event); + + LL_INFOS("UpdateDownload") << "progress event " << payload << LL_ENDL; + } else { + ; // Keep events to a reasonalbe number. + } + + return 0; +} + + +void LLUpdateDownloader::Implementation::run(void) +{ + CURLcode code = curl_easy_perform(mCurl); + mDownloadStream.close(); + if(code == CURLE_OK) { + LLFile::remove(mDownloadRecordPath); + if(validateDownload()) { + LL_INFOS("UpdateDownload") << "download successful" << LL_ENDL; + mClient.downloadComplete(mDownloadData); + } else { + LL_INFOS("UpdateDownload") << "download failed hash check" << LL_ENDL; + std::string filePath = mDownloadData["path"].asString(); + if(filePath.size() != 0) LLFile::remove(filePath); + mClient.downloadError("failed hash check"); + } + } else if(mCancelled && (code == CURLE_WRITE_ERROR)) { + LL_INFOS("UpdateDownload") << "download canceled by user" << LL_ENDL; + // Do not call back client. + } else { + LL_WARNS("UpdateDownload") << "download failed with error '" << + curl_easy_strerror(code) << "'" << LL_ENDL; + LLFile::remove(mDownloadRecordPath); + if(mDownloadData.has("path")) LLFile::remove(mDownloadData["path"].asString()); + mClient.downloadError("curl error"); + } + + if(mHeaderList) { + curl_slist_free_all(mHeaderList); + mHeaderList = 0; + } +} + + +void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url, bool processHeader) +{ + if(mCurl == 0) { + mCurl = curl_easy_init(); + } else { + curl_easy_reset(mCurl); + } + + if(mCurl == 0) throw DownloadError("failed to initialize curl"); + + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, true)); + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_FOLLOWLOCATION, true)); + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, &write_function)); + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, this)); + if(processHeader) { + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERFUNCTION, &header_function)); + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERDATA, this)); + } + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPGET, true)); + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_URL, url.c_str())); + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_PROGRESSFUNCTION, &progress_callback)); + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_PROGRESSDATA, this)); + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOPROGRESS, false)); + // if it's a required update set the bandwidth limit to 0 (unlimited) + curl_off_t limit = mDownloadData["required"].asBoolean() ? 0 : mBandwidthLimit; + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, limit)); + + mDownloadPercent = 0; +} + + +void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte) +{ + LL_INFOS("UpdateDownload") << "resuming download from " << mDownloadData["url"].asString() + << " at byte " << startByte << LL_ENDL; + + initializeCurlGet(mDownloadData["url"].asString(), false); + + // The header 'Range: bytes n-' will request the bytes remaining in the + // source begining with byte n and ending with the last byte. + boost::format rangeHeaderFormat("Range: bytes=%u-"); + rangeHeaderFormat % startByte; + mHeaderList = curl_slist_append(mHeaderList, rangeHeaderFormat.str().c_str()); + if(mHeaderList == 0) throw DownloadError("cannot add Range header"); + throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaderList)); + + mDownloadStream.open(mDownloadData["path"].asString(), + std::ios_base::out | std::ios_base::binary | std::ios_base::app); + start(); +} + + +void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std::string const & hash) +{ + mDownloadData["url"] = uri.asString(); + mDownloadData["hash"] = hash; + mDownloadData["current_version"] = ll_get_version(); + LLSD path = uri.pathArray(); + if(path.size() == 0) throw DownloadError("no file path"); + std::string fileName = path[path.size() - 1].asString(); + std::string filePath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, fileName); + mDownloadData["path"] = filePath; + + LL_INFOS("UpdateDownload") << "downloading " << filePath + << " from " << uri.asString() << LL_ENDL; + LL_INFOS("UpdateDownload") << "hash of file is " << hash << LL_ENDL; + + llofstream dataStream(mDownloadRecordPath); + LLSDSerialize::toPrettyXML(mDownloadData, dataStream); + + mDownloadStream.open(filePath, std::ios_base::out | std::ios_base::binary); + initializeCurlGet(uri.asString(), true); + start(); +} + + +void LLUpdateDownloader::Implementation::throwOnCurlError(CURLcode code) +{ + if(code != CURLE_OK) { + const char * errorString = curl_easy_strerror(code); + if(errorString != 0) { + throw DownloadError(curl_easy_strerror(code)); + } else { + throw DownloadError("unknown curl error"); + } + } else { + ; // No op. + } +} + + +bool LLUpdateDownloader::Implementation::validateDownload(void) +{ + std::string filePath = mDownloadData["path"].asString(); + llifstream fileStream(filePath, std::ios_base::in | std::ios_base::binary); + if(!fileStream) return false; + + std::string hash = mDownloadData["hash"].asString(); + if(hash.size() != 0) { + LL_INFOS("UpdateDownload") << "checking hash..." << LL_ENDL; + char digest[33]; + LLMD5(fileStream).hex_digest(digest); + if(hash != digest) { + LL_WARNS("UpdateDownload") << "download hash mismatch; expeted " << hash << + " but download is " << digest << LL_ENDL; + } + return hash == digest; + } else { + return true; // No hash check provided. + } +} diff --git a/indra/viewer_components/updater/llupdatedownloader.h b/indra/viewer_components/updater/llupdatedownloader.h new file mode 100644 index 0000000000..0d635640cf --- /dev/null +++ b/indra/viewer_components/updater/llupdatedownloader.h @@ -0,0 +1,94 @@ +/** + * @file llupdatedownloader.h + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_UPDATE_DOWNLOADER_H +#define LL_UPDATE_DOWNLOADER_H + + +#include <string> +#include <boost/shared_ptr.hpp> +#include "lluri.h" + + +// +// An asynchronous download service for fetching updates. +// +class LLUpdateDownloader +{ +public: + class Client; + class Implementation; + + // Returns the path to the download marker file containing details of the + // latest download. + static std::string downloadMarkerPath(void); + + LLUpdateDownloader(Client & client); + + // Cancel any in progress download; a no op if none is in progress. The + // client will not receive a complete or error callback. + void cancel(void); + + // Start a new download. + void download(LLURI const & uri, + std::string const & hash, + std::string const & updateVersion, + bool required=false); + + // Returns true if a download is in progress. + bool isDownloading(void); + + // Resume a partial download. + void resume(void); + + // Set a limit on the dowload rate. + void setBandwidthLimit(U64 bytesPerSecond); + +private: + boost::shared_ptr<Implementation> mImplementation; +}; + + +// +// An interface to be implemented by clients initiating a update download. +// +class LLUpdateDownloader::Client { +public: + + // The download has completed successfully. + // data is a map containing the following items: + // url - source (remote) location + // hash - the md5 sum that should match the installer file. + // path - destination (local) location + // required - boolean indicating if this is a required update. + // size - the size of the installer in bytes + virtual void downloadComplete(LLSD const & data) = 0; + + // The download failed. + virtual void downloadError(std::string const & message) = 0; +}; + + +#endif diff --git a/indra/viewer_components/updater/llupdateinstaller.cpp b/indra/viewer_components/updater/llupdateinstaller.cpp new file mode 100644 index 0000000000..d450c068ad --- /dev/null +++ b/indra/viewer_components/updater/llupdateinstaller.cpp @@ -0,0 +1,100 @@ +/** + * @file llupdateinstaller.cpp + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include <apr_file_io.h> +#include "llapr.h" +#include "llprocesslauncher.h" +#include "llupdateinstaller.h" +#include "lldir.h" + + +#if defined(LL_WINDOWS) +#pragma warning(disable: 4702) // disable 'unreachable code' so we can use lexical_cast (really!). +#endif +#include <boost/lexical_cast.hpp> + + +namespace { + class RelocateError {}; + + + std::string copy_to_temp(std::string const & path) + { + std::string scriptFile = gDirUtilp->getBaseFileName(path); + std::string newPath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, scriptFile); + apr_status_t status = apr_file_copy(path.c_str(), newPath.c_str(), APR_FILE_SOURCE_PERMS, gAPRPoolp); + if(status != APR_SUCCESS) throw RelocateError(); + + return newPath; + } +} + + +int ll_install_update(std::string const & script, + std::string const & updatePath, + bool required, + LLInstallScriptMode mode) +{ + std::string actualScriptPath; + switch(mode) { + case LL_COPY_INSTALL_SCRIPT_TO_TEMP: + try { + actualScriptPath = copy_to_temp(script); + } + catch (RelocateError &) { + return -1; + } + break; + case LL_RUN_INSTALL_SCRIPT_IN_PLACE: + actualScriptPath = script; + break; + default: + llassert(!"unpossible copy mode"); + } + + llinfos << "UpdateInstaller: installing " << updatePath << " using " << + actualScriptPath << LL_ENDL; + + LLProcessLauncher launcher; + launcher.setExecutable(actualScriptPath); + launcher.addArgument(updatePath); + launcher.addArgument(ll_install_failed_marker_path().c_str()); + launcher.addArgument(boost::lexical_cast<std::string>(required)); + int result = launcher.launch(); + launcher.orphan(); + + return result; +} + + +std::string const & ll_install_failed_marker_path(void) +{ + static std::string path; + if(path.empty()) { + path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLifeInstallFailed.marker"); + } + return path; +} diff --git a/indra/viewer_components/updater/llupdateinstaller.h b/indra/viewer_components/updater/llupdateinstaller.h new file mode 100644 index 0000000000..fe5b1d19b5 --- /dev/null +++ b/indra/viewer_components/updater/llupdateinstaller.h @@ -0,0 +1,58 @@ +/** + * @file llupdateinstaller.h + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_UPDATE_INSTALLER_H +#define LL_UPDATE_INSTALLER_H + + +#include <string> + + +enum LLInstallScriptMode { + LL_RUN_INSTALL_SCRIPT_IN_PLACE, + LL_COPY_INSTALL_SCRIPT_TO_TEMP +}; + +// +// Launch the installation script. +// +// The updater will overwrite the current installation, so it is highly recommended +// that the current application terminate once this function is called. +// +int ll_install_update( + std::string const & script, // Script to execute. + std::string const & updatePath, // Path to update file. + bool required, // Is the update required. + LLInstallScriptMode mode=LL_COPY_INSTALL_SCRIPT_TO_TEMP); // Run in place or copy to temp? + + +// +// Returns the path which points to the failed install marker file, should it +// exist. +// +std::string const & ll_install_failed_marker_path(void); + + +#endif diff --git a/indra/viewer_components/updater/llupdaterservice.cpp b/indra/viewer_components/updater/llupdaterservice.cpp new file mode 100644 index 0000000000..1888f191e2 --- /dev/null +++ b/indra/viewer_components/updater/llupdaterservice.cpp @@ -0,0 +1,621 @@ +/** + * @file llupdaterservice.cpp + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llupdaterservice.h" + +#include "llupdatedownloader.h" +#include "llevents.h" +#include "lltimer.h" +#include "llupdatechecker.h" +#include "llupdateinstaller.h" +#include "llversionviewer.h" + +#include <boost/scoped_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include "lldir.h" +#include "llsdserialize.h" +#include "llfile.h" + +#if LL_WINDOWS +#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally +#endif + + +namespace +{ + boost::weak_ptr<LLUpdaterServiceImpl> gUpdater; + + const std::string UPDATE_MARKER_FILENAME("SecondLifeUpdateReady.xml"); + std::string update_marker_path() + { + return gDirUtilp->getExpandedFilename(LL_PATH_LOGS, + UPDATE_MARKER_FILENAME); + } + + std::string install_script_path(void) + { +#ifdef LL_WINDOWS + std::string scriptFile = "update_install.bat"; +#else + std::string scriptFile = "update_install"; +#endif + return gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, scriptFile); + } + + LLInstallScriptMode install_script_mode(void) + { +#ifdef LL_WINDOWS + return LL_COPY_INSTALL_SCRIPT_TO_TEMP; +#else + return LL_RUN_INSTALL_SCRIPT_IN_PLACE; +#endif + }; + +} + +class LLUpdaterServiceImpl : + public LLUpdateChecker::Client, + public LLUpdateDownloader::Client +{ + static const std::string sListenerName; + + std::string mProtocolVersion; + std::string mUrl; + std::string mPath; + std::string mChannel; + std::string mVersion; + + unsigned int mCheckPeriod; + bool mIsChecking; + bool mIsDownloading; + + LLUpdateChecker mUpdateChecker; + LLUpdateDownloader mUpdateDownloader; + LLTimer mTimer; + + LLUpdaterService::app_exit_callback_t mAppExitCallback; + + LLUpdaterService::eUpdaterState mState; + + LOG_CLASS(LLUpdaterServiceImpl); + +public: + LLUpdaterServiceImpl(); + virtual ~LLUpdaterServiceImpl(); + + void initialize(const std::string& protocol_version, + const std::string& url, + const std::string& path, + const std::string& channel, + const std::string& version); + + void setCheckPeriod(unsigned int seconds); + void setBandwidthLimit(U64 bytesPerSecond); + + void startChecking(bool install_if_ready); + void stopChecking(); + bool isChecking(); + LLUpdaterService::eUpdaterState getState(); + + void setAppExitCallback(LLUpdaterService::app_exit_callback_t aecb) { mAppExitCallback = aecb;} + std::string updatedVersion(void); + + bool checkForInstall(bool launchInstaller); // Test if a local install is ready. + bool checkForResume(); // Test for resumeable d/l. + + // LLUpdateChecker::Client: + virtual void error(std::string const & message); + virtual void optionalUpdate(std::string const & newVersion, + LLURI const & uri, + std::string const & hash); + virtual void requiredUpdate(std::string const & newVersion, + LLURI const & uri, + std::string const & hash); + virtual void upToDate(void); + + // LLUpdateDownloader::Client + void downloadComplete(LLSD const & data); + void downloadError(std::string const & message); + + bool onMainLoop(LLSD const & event); + +private: + std::string mNewVersion; + + void restartTimer(unsigned int seconds); + void setState(LLUpdaterService::eUpdaterState state); + void stopTimer(); +}; + +const std::string LLUpdaterServiceImpl::sListenerName = "LLUpdaterServiceImpl"; + +LLUpdaterServiceImpl::LLUpdaterServiceImpl() : + mIsChecking(false), + mIsDownloading(false), + mCheckPeriod(0), + mUpdateChecker(*this), + mUpdateDownloader(*this), + mState(LLUpdaterService::INITIAL) +{ +} + +LLUpdaterServiceImpl::~LLUpdaterServiceImpl() +{ + LL_INFOS("UpdaterService") << "shutting down updater service" << LL_ENDL; + LLEventPumps::instance().obtain("mainloop").stopListening(sListenerName); +} + +void LLUpdaterServiceImpl::initialize(const std::string& protocol_version, + const std::string& url, + const std::string& path, + const std::string& channel, + const std::string& version) +{ + if(mIsChecking || mIsDownloading) + { + throw LLUpdaterService::UsageError("LLUpdaterService::initialize call " + "while updater is running."); + } + + mProtocolVersion = protocol_version; + mUrl = url; + mPath = path; + mChannel = channel; + mVersion = version; +} + +void LLUpdaterServiceImpl::setCheckPeriod(unsigned int seconds) +{ + mCheckPeriod = seconds; +} + +void LLUpdaterServiceImpl::setBandwidthLimit(U64 bytesPerSecond) +{ + mUpdateDownloader.setBandwidthLimit(bytesPerSecond); +} + +void LLUpdaterServiceImpl::startChecking(bool install_if_ready) +{ + if(mUrl.empty() || mChannel.empty() || mVersion.empty()) + { + throw LLUpdaterService::UsageError("Set params before call to " + "LLUpdaterService::startCheck()."); + } + + mIsChecking = true; + + // Check to see if an install is ready. + bool has_install = checkForInstall(install_if_ready); + if(!has_install) + { + checkForResume(); // will set mIsDownloading to true if resuming + + if(!mIsDownloading) + { + setState(LLUpdaterService::CHECKING_FOR_UPDATE); + + // Checking can only occur during the mainloop. + // reset the timer to 0 so that the next mainloop event + // triggers a check; + restartTimer(0); + } + else + { + setState(LLUpdaterService::DOWNLOADING); + } + } +} + +void LLUpdaterServiceImpl::stopChecking() +{ + if(mIsChecking) + { + mIsChecking = false; + stopTimer(); + } + + if(mIsDownloading) + { + mUpdateDownloader.cancel(); + mIsDownloading = false; + } + + setState(LLUpdaterService::TERMINAL); +} + +bool LLUpdaterServiceImpl::isChecking() +{ + return mIsChecking; +} + +LLUpdaterService::eUpdaterState LLUpdaterServiceImpl::getState() +{ + return mState; +} + +std::string LLUpdaterServiceImpl::updatedVersion(void) +{ + return mNewVersion; +} + +bool LLUpdaterServiceImpl::checkForInstall(bool launchInstaller) +{ + bool foundInstall = false; // return true if install is found. + + llifstream update_marker(update_marker_path(), + std::ios::in | std::ios::binary); + + if(update_marker.is_open()) + { + // Found an update info - now lets see if its valid. + LLSD update_info; + LLSDSerialize::fromXMLDocument(update_info, update_marker); + update_marker.close(); + + // Get the path to the installer file. + LLSD path = update_info.get("path"); + if(update_info["current_version"].asString() != ll_get_version()) + { + // This viewer is not the same version as the one that downloaded + // the update. Do not install this update. + if(!path.asString().empty()) + { + llinfos << "ignoring update dowloaded by different client version" << llendl; + LLFile::remove(path.asString()); + LLFile::remove(update_marker_path()); + } + else + { + ; // Nothing to clean up. + } + + foundInstall = false; + } + else if(path.isDefined() && !path.asString().empty()) + { + if(launchInstaller) + { + setState(LLUpdaterService::INSTALLING); + + LLFile::remove(update_marker_path()); + + int result = ll_install_update(install_script_path(), + update_info["path"].asString(), + update_info["required"].asBoolean(), + install_script_mode()); + + if((result == 0) && mAppExitCallback) + { + mAppExitCallback(); + } else if(result != 0) { + llwarns << "failed to run update install script" << LL_ENDL; + } else { + ; // No op. + } + } + + foundInstall = true; + } + } + return foundInstall; +} + +bool LLUpdaterServiceImpl::checkForResume() +{ + bool result = false; + std::string download_marker_path = mUpdateDownloader.downloadMarkerPath(); + if(LLFile::isfile(download_marker_path)) + { + llifstream download_marker_stream(download_marker_path, + std::ios::in | std::ios::binary); + if(download_marker_stream.is_open()) + { + LLSD download_info; + LLSDSerialize::fromXMLDocument(download_info, download_marker_stream); + download_marker_stream.close(); + if(download_info["current_version"].asString() == ll_get_version()) + { + mIsDownloading = true; + mNewVersion = download_info["update_version"].asString(); + mUpdateDownloader.resume(); + result = true; + } + else + { + // The viewer that started this download is not the same as this viewer; ignore. + llinfos << "ignoring partial download from different viewer version" << llendl; + std::string path = download_info["path"].asString(); + if(!path.empty()) LLFile::remove(path); + LLFile::remove(download_marker_path); + } + } + } + return result; +} + +void LLUpdaterServiceImpl::error(std::string const & message) +{ + if(mIsChecking) + { + setState(LLUpdaterService::TEMPORARY_ERROR); + restartTimer(mCheckPeriod); + } +} + +void LLUpdaterServiceImpl::optionalUpdate(std::string const & newVersion, + LLURI const & uri, + std::string const & hash) +{ + stopTimer(); + mNewVersion = newVersion; + mIsDownloading = true; + setState(LLUpdaterService::DOWNLOADING); + mUpdateDownloader.download(uri, hash, newVersion, false); +} + +void LLUpdaterServiceImpl::requiredUpdate(std::string const & newVersion, + LLURI const & uri, + std::string const & hash) +{ + stopTimer(); + mNewVersion = newVersion; + mIsDownloading = true; + setState(LLUpdaterService::DOWNLOADING); + mUpdateDownloader.download(uri, hash, newVersion, true); +} + +void LLUpdaterServiceImpl::upToDate(void) +{ + if(mIsChecking) + { + restartTimer(mCheckPeriod); + } + + setState(LLUpdaterService::UP_TO_DATE); +} + +void LLUpdaterServiceImpl::downloadComplete(LLSD const & data) +{ + mIsDownloading = false; + + // Save out the download data to the SecondLifeUpdateReady + // marker file. + llofstream update_marker(update_marker_path()); + LLSDSerialize::toPrettyXML(data, update_marker); + + LLSD event; + event["pump"] = LLUpdaterService::pumpName(); + LLSD payload; + payload["type"] = LLSD(LLUpdaterService::DOWNLOAD_COMPLETE); + payload["required"] = data["required"]; + payload["version"] = mNewVersion; + event["payload"] = payload; + LLEventPumps::instance().obtain("mainlooprepeater").post(event); + + setState(LLUpdaterService::TERMINAL); +} + +void LLUpdaterServiceImpl::downloadError(std::string const & message) +{ + LL_INFOS("UpdaterService") << "Error downloading: " << message << LL_ENDL; + + mIsDownloading = false; + + // Restart the timer on error + if(mIsChecking) + { + restartTimer(mCheckPeriod); + } + + LLSD event; + event["pump"] = LLUpdaterService::pumpName(); + LLSD payload; + payload["type"] = LLSD(LLUpdaterService::DOWNLOAD_ERROR); + payload["message"] = message; + event["payload"] = payload; + LLEventPumps::instance().obtain("mainlooprepeater").post(event); + + setState(LLUpdaterService::FAILURE); +} + +void LLUpdaterServiceImpl::restartTimer(unsigned int seconds) +{ + LL_INFOS("UpdaterService") << "will check for update again in " << + seconds << " seconds" << LL_ENDL; + mTimer.start(); + mTimer.setTimerExpirySec(seconds); + LLEventPumps::instance().obtain("mainloop").listen( + sListenerName, boost::bind(&LLUpdaterServiceImpl::onMainLoop, this, _1)); +} + +void LLUpdaterServiceImpl::setState(LLUpdaterService::eUpdaterState state) +{ + if(state != mState) + { + mState = state; + + LLSD event; + event["pump"] = LLUpdaterService::pumpName(); + LLSD payload; + payload["type"] = LLSD(LLUpdaterService::STATE_CHANGE); + payload["state"] = state; + event["payload"] = payload; + LLEventPumps::instance().obtain("mainlooprepeater").post(event); + + LL_INFOS("UpdaterService") << "setting state to " << state << LL_ENDL; + } + else + { + ; // State unchanged; noop. + } +} + +void LLUpdaterServiceImpl::stopTimer() +{ + mTimer.stop(); + LLEventPumps::instance().obtain("mainloop").stopListening(sListenerName); +} + +bool LLUpdaterServiceImpl::onMainLoop(LLSD const & event) +{ + if(mTimer.getStarted() && mTimer.hasExpired()) + { + stopTimer(); + + // Check for failed install. + if(LLFile::isfile(ll_install_failed_marker_path())) + { + int requiredValue = 0; + { + llifstream stream(ll_install_failed_marker_path()); + stream >> requiredValue; + if(stream.fail()) requiredValue = 0; + } + // TODO: notify the user. + llinfos << "found marker " << ll_install_failed_marker_path() << llendl; + llinfos << "last install attempt failed" << llendl; + LLFile::remove(ll_install_failed_marker_path()); + + LLSD event; + event["type"] = LLSD(LLUpdaterService::INSTALL_ERROR); + event["required"] = LLSD(requiredValue); + LLEventPumps::instance().obtain(LLUpdaterService::pumpName()).post(event); + + setState(LLUpdaterService::TERMINAL); + } + else + { + mUpdateChecker.check(mProtocolVersion, mUrl, mPath, mChannel, mVersion); + setState(LLUpdaterService::CHECKING_FOR_UPDATE); + } + } + else + { + // Keep on waiting... + } + + return false; +} + + +//----------------------------------------------------------------------- +// Facade interface + +std::string const & LLUpdaterService::pumpName(void) +{ + static std::string name("updater_service"); + return name; +} + +bool LLUpdaterService::updateReadyToInstall(void) +{ + return LLFile::isfile(update_marker_path()); +} + +LLUpdaterService::LLUpdaterService() +{ + if(gUpdater.expired()) + { + mImpl = + boost::shared_ptr<LLUpdaterServiceImpl>(new LLUpdaterServiceImpl()); + gUpdater = mImpl; + } + else + { + mImpl = gUpdater.lock(); + } +} + +LLUpdaterService::~LLUpdaterService() +{ +} + +void LLUpdaterService::initialize(const std::string& protocol_version, + const std::string& url, + const std::string& path, + const std::string& channel, + const std::string& version) +{ + mImpl->initialize(protocol_version, url, path, channel, version); +} + +void LLUpdaterService::setCheckPeriod(unsigned int seconds) +{ + mImpl->setCheckPeriod(seconds); +} + +void LLUpdaterService::setBandwidthLimit(U64 bytesPerSecond) +{ + mImpl->setBandwidthLimit(bytesPerSecond); +} + +void LLUpdaterService::startChecking(bool install_if_ready) +{ + mImpl->startChecking(install_if_ready); +} + +void LLUpdaterService::stopChecking() +{ + mImpl->stopChecking(); +} + +bool LLUpdaterService::isChecking() +{ + return mImpl->isChecking(); +} + +LLUpdaterService::eUpdaterState LLUpdaterService::getState() +{ + return mImpl->getState(); +} + +void LLUpdaterService::setImplAppExitCallback(LLUpdaterService::app_exit_callback_t aecb) +{ + return mImpl->setAppExitCallback(aecb); +} + +std::string LLUpdaterService::updatedVersion(void) +{ + return mImpl->updatedVersion(); +} + + +std::string const & ll_get_version(void) { + static std::string version(""); + + if (version.empty()) { + std::ostringstream stream; + stream << LL_VERSION_MAJOR << "." + << LL_VERSION_MINOR << "." + << LL_VERSION_PATCH << "." + << LL_VERSION_BUILD; + version = stream.str(); + } + + return version; +} + diff --git a/indra/viewer_components/updater/llupdaterservice.h b/indra/viewer_components/updater/llupdaterservice.h new file mode 100644 index 0000000000..450f19c1c6 --- /dev/null +++ b/indra/viewer_components/updater/llupdaterservice.h @@ -0,0 +1,108 @@ +/** + * @file llupdaterservice.h + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_UPDATERSERVICE_H +#define LL_UPDATERSERVICE_H + +#include <boost/shared_ptr.hpp> +#include <boost/function.hpp> + +class LLUpdaterServiceImpl; + +class LLUpdaterService +{ +public: + class UsageError: public std::runtime_error + { + public: + UsageError(const std::string& msg) : std::runtime_error(msg) {} + }; + + // Name of the event pump through which update events will be delivered. + static std::string const & pumpName(void); + + // Returns true if an update has been completely downloaded and is now ready to install. + static bool updateReadyToInstall(void); + + // Type codes for events posted by this service. Stored the event's 'type' element. + enum eUpdaterEvent { + INVALID, + DOWNLOAD_COMPLETE, + DOWNLOAD_ERROR, + INSTALL_ERROR, + PROGRESS, + STATE_CHANGE + }; + + enum eUpdaterState { + INITIAL, + CHECKING_FOR_UPDATE, + TEMPORARY_ERROR, + DOWNLOADING, + INSTALLING, + UP_TO_DATE, + TERMINAL, + FAILURE + }; + + LLUpdaterService(); + ~LLUpdaterService(); + + void initialize(const std::string& protocol_version, + const std::string& url, + const std::string& path, + const std::string& channel, + const std::string& version); + + void setCheckPeriod(unsigned int seconds); + void setBandwidthLimit(U64 bytesPerSecond); + + void startChecking(bool install_if_ready = false); + void stopChecking(); + bool isChecking(); + eUpdaterState getState(); + + typedef boost::function<void (void)> app_exit_callback_t; + template <typename F> + void setAppExitCallback(F const &callable) + { + app_exit_callback_t aecb = callable; + setImplAppExitCallback(aecb); + } + + // If an update is or has been downloaded, this method will return the + // version string for that update. An empty string will be returned + // otherwise. + std::string updatedVersion(void); + +private: + boost::shared_ptr<LLUpdaterServiceImpl> mImpl; + void setImplAppExitCallback(app_exit_callback_t aecb); +}; + +// Returns the full version as a string. +std::string const & ll_get_version(void); + +#endif // LL_UPDATERSERVICE_H diff --git a/indra/viewer_components/updater/scripts/darwin/update_install b/indra/viewer_components/updater/scripts/darwin/update_install new file mode 100644 index 0000000000..6a95f96d86 --- /dev/null +++ b/indra/viewer_components/updater/scripts/darwin/update_install @@ -0,0 +1,10 @@ +#! /bin/bash + +# +# The first argument contains the path to the installer app. The second a path +# to a marker file which should be created if the installer fails.q +# + +cd "$(dirname "$0")" +(../Resources/mac-updater.app/Contents/MacOS/mac-updater -dmg "$1" -name "Second Life Viewer 2"; if [ $? -ne 0 ]; then echo $3 >> "$2"; fi;) & +exit 0 diff --git a/indra/viewer_components/updater/scripts/linux/update_install b/indra/viewer_components/updater/scripts/linux/update_install new file mode 100644 index 0000000000..88451340ec --- /dev/null +++ b/indra/viewer_components/updater/scripts/linux/update_install @@ -0,0 +1,10 @@ +#! /bin/bash +INSTALL_DIR=$(cd "$(dirname "$0")/.." ; pwd) +export LD_LIBRARY_PATH="$INSTALL_DIR/lib" +bin/linux-updater.bin --file "$1" --dest "$INSTALL_DIR" --name "Second Life Viewer 2" --stringsdir "$INSTALL_DIR/skins/default/xui/en" --stringsfile "strings.xml" + +if [ $? -ne 0 ] + then echo $3 >> "$2" +fi + +rm -f "$1" diff --git a/indra/viewer_components/updater/scripts/windows/update_install.bat b/indra/viewer_components/updater/scripts/windows/update_install.bat new file mode 100644 index 0000000000..96687226a8 --- /dev/null +++ b/indra/viewer_components/updater/scripts/windows/update_install.bat @@ -0,0 +1,3 @@ +start /WAIT %1 /SKIP_DIALOGS
+IF ERRORLEVEL 1 ECHO %3 > %2
+DEL %1
diff --git a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp new file mode 100644 index 0000000000..88ab5a2284 --- /dev/null +++ b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp @@ -0,0 +1,200 @@ +/** + * @file llupdaterservice_test.cpp + * @brief Tests of llupdaterservice.cpp. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "../llupdaterservice.h" +#include "../llupdatechecker.h" +#include "../llupdatedownloader.h" +#include "../llupdateinstaller.h" + +#include "../../../test/lltut.h" +//#define DEBUG_ON +#include "../../../test/debug.h" + +#include "llevents.h" +#include "lldir.h" + +/***************************************************************************** +* MOCK'd +*****************************************************************************/ +LLUpdateChecker::LLUpdateChecker(LLUpdateChecker::Client & client) +{} +void LLUpdateChecker::check(std::string const & protocolVersion, std::string const & hostUrl, + std::string const & servicePath, std::string channel, std::string version) +{} +LLUpdateDownloader::LLUpdateDownloader(Client & ) {} +void LLUpdateDownloader::download(LLURI const & , std::string const &, std::string const &, bool){} + +class LLDir_Mock : public LLDir +{ + void initAppDirs(const std::string &app_name, + const std::string& app_read_only_data_dir = "") {} + U32 countFilesInDir(const std::string &dirname, const std::string &mask) + { + return 0; + } + + BOOL getNextFileInDir(const std::string &dirname, + const std::string &mask, + std::string &fname) + { + return false; + } + void getRandomFileInDir(const std::string &dirname, + const std::string &mask, + std::string &fname) {} + std::string getCurPath() { return ""; } + BOOL fileExists(const std::string &filename) const { return false; } + std::string getLLPluginLauncher() { return ""; } + std::string getLLPluginFilename(std::string base_name) { return ""; } + +} gDirUtil; +LLDir* gDirUtilp = &gDirUtil; +LLDir::LLDir() {} +LLDir::~LLDir() {} +S32 LLDir::deleteFilesInDir(const std::string &dirname, + const std::string &mask) +{ return 0; } + +void LLDir::setChatLogsDir(const std::string &path){} +void LLDir::setPerAccountChatLogsDir(const std::string &username){} +void LLDir::setLindenUserDir(const std::string &username){} +void LLDir::setSkinFolder(const std::string &skin_folder){} +bool LLDir::setCacheDir(const std::string &path){ return true; } +void LLDir::dumpCurrentDirectories() {} + +std::string LLDir::getExpandedFilename(ELLPath location, + const std::string &filename) const +{ + return ""; +} + +std::string LLUpdateDownloader::downloadMarkerPath(void) +{ + return ""; +} + +void LLUpdateDownloader::resume(void) {} +void LLUpdateDownloader::cancel(void) {} +void LLUpdateDownloader::setBandwidthLimit(U64 bytesPerSecond) {} + +int ll_install_update(std::string const &, std::string const &, bool, LLInstallScriptMode) +{ + return 0; +} + +std::string const & ll_install_failed_marker_path() +{ + static std::string wubba; + return wubba; +} + +/* +#pragma warning(disable: 4273) +llus_mock_llifstream::llus_mock_llifstream(const std::string& _Filename, + ios_base::openmode _Mode, + int _Prot) : + std::basic_istream<char,std::char_traits< char > >(NULL,true) +{} + +llus_mock_llifstream::~llus_mock_llifstream() {} +bool llus_mock_llifstream::is_open() const {return true;} +void llus_mock_llifstream::close() {} +*/ + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llupdaterservice_data + { + llupdaterservice_data() : + pumps(LLEventPumps::instance()), + test_url("dummy_url"), + test_channel("dummy_channel"), + test_version("dummy_version") + {} + LLEventPumps& pumps; + std::string test_url; + std::string test_channel; + std::string test_version; + }; + + typedef test_group<llupdaterservice_data> llupdaterservice_group; + typedef llupdaterservice_group::object llupdaterservice_object; + llupdaterservice_group llupdaterservicegrp("LLUpdaterService"); + + template<> template<> + void llupdaterservice_object::test<1>() + { + DEBUG; + LLUpdaterService updater; + bool got_usage_error = false; + try + { + updater.startChecking(); + } + catch(LLUpdaterService::UsageError) + { + got_usage_error = true; + } + ensure("Caught start before params", got_usage_error); + } + + template<> template<> + void llupdaterservice_object::test<2>() + { + DEBUG; + LLUpdaterService updater; + bool got_usage_error = false; + try + { + updater.initialize("1.0",test_url, "update" ,test_channel, test_version); + updater.startChecking(); + updater.initialize("1.0", "other_url", "update", test_channel, test_version); + } + catch(LLUpdaterService::UsageError) + { + got_usage_error = true; + } + ensure("Caught params while running", got_usage_error); + } + + template<> template<> + void llupdaterservice_object::test<3>() + { + DEBUG; + LLUpdaterService updater; + updater.initialize("1.0", test_url, "update", test_channel, test_version); + updater.startChecking(); + ensure(updater.isChecking()); + updater.stopChecking(); + ensure(!updater.isChecking()); + } +} |