diff options
Diffstat (limited to 'indra/viewer_components')
20 files changed, 1003 insertions, 217 deletions
| diff --git a/indra/viewer_components/CMakeLists.txt b/indra/viewer_components/CMakeLists.txt index 74c9b4568d..74c9b4568d 100644..100755 --- 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 658f167c2e..6f366677c9 100644..100755 --- a/indra/viewer_components/login/CMakeLists.txt +++ b/indra/viewer_components/login/CMakeLists.txt @@ -9,6 +9,7 @@ endif(LL_TESTS)  include(LLCommon)  include(LLMath)  include(LLXML) +include(Boost)  include_directories(      ${LLCOMMON_INCLUDE_DIRS} @@ -44,12 +45,18 @@ target_link_libraries(lllogin      ${LLCOMMON_LIBRARIES}      ${LLMATH_LIBRARIES}      ${LLXML_LIBRARIES} +    ${BOOST_CONTEXT_LIBRARY}      )  if(LL_TESTS)    SET(lllogin_TEST_SOURCE_FILES        lllogin.cpp        ) +  set_source_files_properties( +    lllogin.cpp +    PROPERTIES +    LL_TEST_ADDITIONAL_LIBRARIES "${BOOST_CONTEXT_LIBRARY}" +    )    LL_ADD_PROJECT_UNIT_TESTS(lllogin "${lllogin_TEST_SOURCE_FILES}")  endif(LL_TESTS) diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp index bdcb068200..3357ad812d 100644..100755 --- a/indra/viewer_components/login/lllogin.cpp +++ b/indra/viewer_components/login/lllogin.cpp @@ -23,7 +23,6 @@   * $/LicenseInfo$   */ -#include <boost/coroutine/coroutine.hpp>  #include "linden_common.h"  #include "llsd.h"  #include "llsdutil.h" diff --git a/indra/viewer_components/login/lllogin.h b/indra/viewer_components/login/lllogin.h index 051641ff59..051641ff59 100644..100755 --- 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..58bf371a04 100644..100755 --- a/indra/viewer_components/login/tests/lllogin_test.cpp +++ b/indra/viewer_components/login/tests/lllogin_test.cpp diff --git a/indra/viewer_components/updater/CMakeLists.txt b/indra/viewer_components/updater/CMakeLists.txt index de7e336341..61fd4220e0 100644..100755 --- a/indra/viewer_components/updater/CMakeLists.txt +++ b/indra/viewer_components/updater/CMakeLists.txt @@ -19,6 +19,7 @@ include_directories(      ${LLPLUGIN_INCLUDE_DIRS}      ${LLVFS_INCLUDE_DIRS}      ${CURL_INCLUDE_DIRS} +    ${CMAKE_SOURCE_DIR}/newview      )  include_directories(SYSTEM      ${LLCOMMON_SYSTEM_INCLUDE_DIRS} @@ -41,6 +42,12 @@ set(updater_service_HEADER_FILES  set_source_files_properties(${updater_service_HEADER_FILES}                              PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties( +   llupdaterservice.cpp  +   PROPERTIES +   COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}" # see BuildVersion.cmake +   ) +  list(APPEND       updater_service_SOURCE_FILES       ${updater_service_HEADER_FILES}  diff --git a/indra/viewer_components/updater/llupdatechecker.cpp b/indra/viewer_components/updater/llupdatechecker.cpp index 5edbbf9914..39f68ac0f5 100644..100755 --- a/indra/viewer_components/updater/llupdatechecker.cpp +++ b/indra/viewer_components/updater/llupdatechecker.cpp @@ -62,10 +62,16 @@ LLUpdateChecker::LLUpdateChecker(LLUpdateChecker::Client & client):  } -void LLUpdateChecker::checkVersion(std::string const & protocolVersion, std::string const & hostUrl,  -							std::string const & servicePath, std::string channel, std::string version) +void LLUpdateChecker::checkVersion(std::string const & hostUrl,  +								   std::string const & servicePath, +								   std::string const & channel, +								   std::string const & version, +								   std::string const & platform, +								   std::string const & platform_version, +								   unsigned char       uniqueid[MD5HEX_STR_SIZE], +								   bool                willing_to_test)  { -	mImplementation->checkVersion(protocolVersion, hostUrl, servicePath, channel, version); +	mImplementation->checkVersion(hostUrl, servicePath, channel, version, platform, platform_version, uniqueid, willing_to_test);  } @@ -74,12 +80,14 @@ void LLUpdateChecker::checkVersion(std::string const & protocolVersion, std::str  //----------------------------------------------------------------------------- -const char * LLUpdateChecker::Implementation::sProtocolVersion = "v1.0"; +const char * LLUpdateChecker::Implementation::sLegacyProtocolVersion = "v1.0"; +const char * LLUpdateChecker::Implementation::sProtocolVersion = "v1.1";  LLUpdateChecker::Implementation::Implementation(LLUpdateChecker::Client & client):  	mClient(client), -	mInProgress(false) +	mInProgress(false), +	mProtocol(sProtocolVersion)  {  	; // No op.  } @@ -91,41 +99,93 @@ LLUpdateChecker::Implementation::~Implementation()  } -void LLUpdateChecker::Implementation::checkVersion(std::string const & protocolVersion, std::string const & hostUrl,  -											std::string const & servicePath, std::string channel, std::string version) +void LLUpdateChecker::Implementation::checkVersion(std::string const & hostUrl,  +												   std::string const & servicePath, +												   std::string const & channel, +												   std::string const & version, +												   std::string const & platform, +												   std::string const & platform_version, +												   unsigned char       uniqueid[MD5HEX_STR_SIZE], +												   bool                willing_to_test)  {  	llassert(!mInProgress); -	if(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; + +	mHostUrl     	 = hostUrl; +	mServicePath 	 = servicePath; +	mChannel     	 = channel; +	mVersion     	 = version; +	mPlatform        = platform; +	mPlatformVersion = platform_version; +	memcpy(mUniqueId, uniqueid, MD5HEX_STR_SIZE); +	mWillingToTest   = willing_to_test; +	 +	mProtocol = sProtocolVersion; + +	std::string checkUrl = buildUrl(hostUrl, servicePath, channel, version, platform, platform_version, uniqueid, willing_to_test); +	LL_INFOS("UpdaterService") << "checking for updates at " << checkUrl << LL_ENDL;  	mHttpClient.get(checkUrl, this);  }  void LLUpdateChecker::Implementation::completed(U32 status, -							  const std::string & reason, -							  const LLSD & content) +												const std::string & reason, +												const LLSD & content)  {  	mInProgress = false;	 -	if(status != 200) { -		LL_WARNS("UpdateCheck") << "html error " << status << " (" << reason << ")" << llendl; -		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()); +	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(); +		} + +		if (status == 404) +		{ +			if (mProtocol == sProtocolVersion) +			{ +				mProtocol = sLegacyProtocolVersion; +				std::string retryUrl = buildUrl(mHostUrl, mServicePath, mChannel, mVersion, mPlatform, mPlatformVersion, mUniqueId, mWillingToTest); + +				LL_WARNS("UpdaterService") +					<< "update response using " << sProtocolVersion +					<< " was HTTP 404 (" << server_error +					<< "); retry with legacy protocol " << mProtocol +					<< "\n at " << retryUrl +					<< LL_ENDL; +	 +				mHttpClient.get(retryUrl, this); +			} +			else +			{ +				LL_WARNS("UpdaterService") +					<< "update response using " << sLegacyProtocolVersion +					<< " was 404 (" << server_error +					<< "); request failed" +					<< LL_ENDL; +				mClient.error(reason); +			} +		} +		else +		{ +			LL_WARNS("UpdaterService") << "response error " << status +									   << " " << reason +									   << " (" << server_error << ")" +									   << LL_ENDL; +			mClient.error(reason); +		} +	} +	else +	{ +		mClient.response(content);  	}  } @@ -133,38 +193,31 @@ void LLUpdateChecker::Implementation::completed(U32 status,  void LLUpdateChecker::Implementation::error(U32 status, const std::string & reason)  {  	mInProgress = false; -	LL_WARNS("UpdateCheck") << "update check failed; " << reason << llendl; +	LL_WARNS("UpdaterService") << "update check failed; " << reason << LL_ENDL;  	mClient.error(reason);  } -std::string LLUpdateChecker::Implementation::buildUrl(std::string const & protocolVersion, std::string const & hostUrl,  -													  std::string const & servicePath, std::string channel, std::string version) +std::string LLUpdateChecker::Implementation::buildUrl(std::string const & hostUrl,  +													  std::string const & servicePath, +													  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)  {	 -#ifdef LL_WINDOWS -	static const char * platform = "win"; -#elif LL_DARWIN -    long versMin; -    Gestalt(gestaltSystemVersionMinor, &versMin); -     -    static const char *platform; -    if (versMin == 5) //OS 10.5 -    { -        platform = "mac_legacy"; -    } -    else  -    { -        platform = "mac"; -    } -#else -	static const char * platform = "lnx"; -#endif -	  	LLSD path;  	path.append(servicePath); -	path.append(protocolVersion); +	path.append(mProtocol);  	path.append(channel);  	path.append(version);  	path.append(platform); +	if (mProtocol != sLegacyProtocolVersion) +	{ +		path.append(platform_version); +		path.append(willing_to_test ? "testok" : "testno"); +		path.append((char*)uniqueid); +	}  	return LLURI::buildHTTP(hostUrl, path).asString();  } diff --git a/indra/viewer_components/updater/llupdatechecker.h b/indra/viewer_components/updater/llupdatechecker.h index 23f62a7c5e..8e85587490 100644..100755 --- a/indra/viewer_components/updater/llupdatechecker.h +++ b/indra/viewer_components/updater/llupdatechecker.h @@ -29,6 +29,7 @@  #include <boost/shared_ptr.hpp> +#include "llmd5.h"  #include "llhttpclient.h"  // @@ -37,15 +38,20 @@  class LLUpdateChecker {  public:  	class Client; -	class Implementation: - -	public LLHTTPClient::Responder +	class Implementation: public LLHTTPClient::Responder  	{  	public:  		Implementation(Client & client);  		~Implementation(); -		void checkVersion(std::string const & protocolVersion, std::string const & hostUrl,  -				   std::string const & servicePath, std::string channel, std::string version); +		void checkVersion(std::string const & hostUrl,  +						  std::string const & servicePath, +						  std::string const & channel, +						  std::string const & version, +						  std::string const & platform, +						  std::string const & platform_version, +						  unsigned char       uniqueid[MD5HEX_STR_SIZE], +						  bool                willing_to_test +						  );  		// Responder:  		virtual void completed(U32 status, @@ -54,15 +60,30 @@ public:  		virtual void error(U32 status, const std::string & reason);  	private:	 +		static const char * sLegacyProtocolVersion;  		static const char * sProtocolVersion; -	 +		const char* mProtocol; +		  		Client & mClient;  		LLHTTPClient mHttpClient; -		bool mInProgress; -		std::string mVersion; -	 -		std::string buildUrl(std::string const & protocolVersion, std::string const & hostUrl,  -							 std::string const & servicePath, std::string channel, std::string version); +		bool         mInProgress; +		std::string   mVersion; +		std::string   mHostUrl; +		std::string   mServicePath; +		std::string   mChannel; +		std::string   mPlatform; +		std::string   mPlatformVersion; +		unsigned char mUniqueId[MD5HEX_STR_SIZE]; +		bool          mWillingToTest; +		 +		std::string buildUrl(std::string const & hostUrl,  +							 std::string const & servicePath, +							 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);  	}; @@ -74,8 +95,14 @@ public:  	LLUpdateChecker(Client & client);  	// Check status of current app on the given host for the channel and version provided. -	void checkVersion(std::string const & protocolVersion, std::string const & hostUrl,  -			   std::string const & servicePath, std::string channel, std::string version); +	void checkVersion(std::string const & hostUrl,  +					  std::string const & servicePath, +					  std::string const & channel, +					  std::string const & version, +					  std::string const & platform, +					  std::string const & platform_version, +					  unsigned char       uniqueid[MD5HEX_STR_SIZE], +					  bool                willing_to_test);  private:  	LLPointer<Implementation> mImplementation; @@ -94,18 +121,8 @@ public:  	// An error occurred while checking for an update.  	virtual void error(std::string const & message) = 0; -	// A newer version is available, but the current version may still be used. -	virtual void optionalUpdate(std::string const & newVersion, -								LLURI const & uri, -								std::string const & hash) = 0; -	 -	// A newer version is available, and the current version is no longer valid.  -	virtual void requiredUpdate(std::string const & newVersion, -								LLURI const & uri, -								std::string const & hash) = 0; -	 -	// The checked version is up to date; no newer version exists. -	virtual void upToDate(void) = 0; +	// A successful response was received from the viewer version manager +	virtual void response(LLSD const & content) = 0;  }; diff --git a/indra/viewer_components/updater/llupdatedownloader.cpp b/indra/viewer_components/updater/llupdatedownloader.cpp index 75e455e3f6..c28ad76c77 100644..100755 --- a/indra/viewer_components/updater/llupdatedownloader.cpp +++ b/indra/viewer_components/updater/llupdatedownloader.cpp @@ -50,7 +50,9 @@ public:  	void cancel(void);  	void download(LLURI const & uri,  				  std::string const & hash, +				  std::string const & updateChannel,  				  std::string const & updateVersion, +				  std::string const & info_url,  				  bool required);  	bool isDownloading(void);  	size_t onHeader(void * header, size_t size); @@ -125,10 +127,12 @@ void LLUpdateDownloader::cancel(void)  void LLUpdateDownloader::download(LLURI const & uri,  								  std::string const & hash, +								  std::string const & updateChannel,  								  std::string const & updateVersion, +								  std::string const & info_url,  								  bool required)  { -	mImplementation->download(uri, hash, updateVersion, required); +	mImplementation->download(uri, hash, updateChannel, updateVersion, info_url, required);  } @@ -222,18 +226,28 @@ void LLUpdateDownloader::Implementation::cancel(void)  void LLUpdateDownloader::Implementation::download(LLURI const & uri,  												  std::string const & hash, +												  std::string const & updateChannel,  												  std::string const & updateVersion, +												  std::string const & info_url,  												  bool required) -{ +{   	if(isDownloading()) mClient.downloadError("download in progress");  	mDownloadRecordPath = downloadMarkerPath();  	mDownloadData = LLSD();  	mDownloadData["required"] = required; +	mDownloadData["update_channel"] = updateChannel;  	mDownloadData["update_version"] = updateVersion; -	try { +	if (!info_url.empty()) +	{ +		mDownloadData["info_url"] = info_url; +	} +	try +	{  		startDownloading(uri, hash); -	} catch(DownloadError const & e) { +	} +	catch(DownloadError const & e) +	{  		mClient.downloadError(e.what());  	}  } @@ -249,47 +263,65 @@ void LLUpdateDownloader::Implementation::resume(void)  {  	mCancelled = false; -	if(isDownloading()) { +	if(isDownloading()) +	{  		mClient.downloadError("download in progress");  	}  	mDownloadRecordPath = downloadMarkerPath();  	llifstream dataStream(mDownloadRecordPath); -	if(!dataStream) { +	if(!dataStream) +	{  		mClient.downloadError("no download marker");  		return;  	}  	LLSDSerialize::fromXMLDocument(mDownloadData, dataStream); -	if(!mDownloadData.asBoolean()) { +	if(!mDownloadData.asBoolean()) +	{  		mClient.downloadError("no download information in marker");  		return;  	}  	std::string filePath = mDownloadData["path"].asString(); -	try { -		if(LLFile::isfile(filePath)) { +	try +	{ +		if(LLFile::isfile(filePath)) +		{  			llstat fileStatus;  			LLFile::stat(filePath, &fileStatus); -			if(fileStatus.st_size != mDownloadData["size"].asInteger()) { +			if(fileStatus.st_size != mDownloadData["size"].asInteger()) +			{  				resumeDownloading(fileStatus.st_size); -			} else if(!validateDownload()) { +			} +			else if(!validateDownload()) +			{  				LLFile::remove(filePath);  				download(LLURI(mDownloadData["url"].asString()),  						 mDownloadData["hash"].asString(), +						 mDownloadData["update_channel"].asString(),  						 mDownloadData["update_version"].asString(), +						 mDownloadData["info_url"].asString(),  						 mDownloadData["required"].asBoolean()); -			} else { +			} +			else +			{  				mClient.downloadComplete(mDownloadData);  			} -		} else { +		} +		else +		{  			download(LLURI(mDownloadData["url"].asString()),  					 mDownloadData["hash"].asString(), +					 mDownloadData["update_channel"].asString(),  					 mDownloadData["update_version"].asString(), +					 mDownloadData["info_url"].asString(),  					 mDownloadData["required"].asBoolean());  		} -	} catch(DownloadError & e) { +	} +	catch(DownloadError & e) +	{  		mClient.downloadError(e.what());  	}  } @@ -297,13 +329,18 @@ void LLUpdateDownloader::Implementation::resume(void)  void LLUpdateDownloader::Implementation::setBandwidthLimit(U64 bytesPerSecond)  { -	if((mBandwidthLimit != bytesPerSecond) && isDownloading() && !mDownloadData["required"].asBoolean()) { +	if((mBandwidthLimit != bytesPerSecond) && isDownloading() && !mDownloadData["required"].asBoolean()) +	{  		llassert(mCurl != 0);  		mBandwidthLimit = bytesPerSecond;  		CURLcode code = curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, &mBandwidthLimit); -		if(code != CURLE_OK) LL_WARNS("UpdateDownload") << -			"unable to change dowload bandwidth" << LL_ENDL; -	} else { +		if(code != CURLE_OK) +		{ +			LL_WARNS("UpdaterService") << "unable to change dowload bandwidth" << LL_ENDL; +		} +	} +	else +	{  		mBandwidthLimit = bytesPerSecond;  	}  } @@ -322,13 +359,13 @@ size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)  			size_t lastDigitPos = header.find_last_of("0123456789");  			std::string contentLength = header.substr(firstDigitPos, lastDigitPos - firstDigitPos + 1);  			size_t size = boost::lexical_cast<size_t>(contentLength); -			LL_INFOS("UpdateDownload") << "download size is " << size << LL_ENDL; +			LL_INFOS("UpdaterService") << "download size is " << size << LL_ENDL;  			mDownloadData["size"] = LLSD(LLSD::Integer(size));  			llofstream odataStream(mDownloadRecordPath);  			LLSDSerialize::toPrettyXML(mDownloadData, odataStream);  		} catch (std::exception const & e) { -			LL_WARNS("UpdateDownload") << "unable to read content length (" +			LL_WARNS("UpdaterService") << "unable to read content length ("  				<< e.what() << ")" << LL_ENDL;  		}  	} else { @@ -368,7 +405,7 @@ int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double b  		event["payload"] = payload;  		LLEventPumps::instance().obtain("mainlooprepeater").post(event); -		LL_INFOS("UpdateDownload") << "progress event " << payload << LL_ENDL; +		LL_INFOS("UpdaterService") << "progress event " << payload << LL_ENDL;  	} else {  		; // Keep events to a reasonalbe number.  	} @@ -381,29 +418,44 @@ void LLUpdateDownloader::Implementation::run(void)  {  	CURLcode code = curl_easy_perform(mCurl);  	mDownloadStream.close(); -	if(code == CURLE_OK) { +	if(code == CURLE_OK) +	{  		LLFile::remove(mDownloadRecordPath); -		if(validateDownload()) { -			LL_INFOS("UpdateDownload") << "download successful" << LL_ENDL; +		if(validateDownload()) +		{ +			LL_INFOS("UpdaterService") << "download successful" << LL_ENDL;  			mClient.downloadComplete(mDownloadData); -		} else { -			LL_INFOS("UpdateDownload") << "download failed hash check" << LL_ENDL; +		} +		else +		{ +			LL_INFOS("UpdaterService") << "download failed hash check" << LL_ENDL;  			std::string filePath = mDownloadData["path"].asString(); -			if(filePath.size() != 0) LLFile::remove(filePath); +			if(filePath.size() != 0) +			{ +				LLFile::remove(filePath); +			}  			mClient.downloadError("failed hash check");  		} -	} else if(mCancelled && (code == CURLE_WRITE_ERROR)) { -		LL_INFOS("UpdateDownload") << "download canceled by user" << LL_ENDL; +	} +	else if(mCancelled && (code == CURLE_WRITE_ERROR)) +	{ +		LL_INFOS("UpdaterService") << "download canceled by user" << LL_ENDL;  		// Do not call back client. -	} else { -		LL_WARNS("UpdateDownload") << "download failed with error '" << +	} +	else +	{ +		LL_WARNS("UpdaterService") << "download failed with error '" <<  			curl_easy_strerror(code) << "'" << LL_ENDL;  		LLFile::remove(mDownloadRecordPath); -		if(mDownloadData.has("path")) LLFile::remove(mDownloadData["path"].asString()); +		if(mDownloadData.has("path")) +		{ +			LLFile::remove(mDownloadData["path"].asString()); +		}  		mClient.downloadError("curl error");  	} -	if(mHeaderList) { +	if(mHeaderList) +	{  		curl_slist_free_all(mHeaderList);  		mHeaderList = 0;  	} @@ -421,13 +473,16 @@ void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & u  		curl_easy_reset(mCurl);  	} -	if(mCurl == 0) throw DownloadError("failed to initialize curl"); - +	if(mCurl == 0) +	{ +		throw DownloadError("failed to initialize curl"); +	}  	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, true));  	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_FOLLOWLOCATION, true));  	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, &write_function));  	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, this)); -	if(processHeader) { +	if(processHeader) +	{  	   throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERFUNCTION, &header_function));  	   throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERDATA, this));  	} @@ -446,7 +501,7 @@ void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & u  void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte)  { -	LL_INFOS("UpdateDownload") << "resuming download from " << mDownloadData["url"].asString() +	LL_INFOS("UpdaterService") << "resuming download from " << mDownloadData["url"].asString()  		<< " at byte " << startByte << LL_ENDL;  	initializeCurlGet(mDownloadData["url"].asString(), false); @@ -456,7 +511,10 @@ void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte)  	boost::format rangeHeaderFormat("Range: bytes=%u-");  	rangeHeaderFormat % startByte;  	mHeaderList = curl_slist_append(mHeaderList, rangeHeaderFormat.str().c_str()); -	if(mHeaderList == 0) throw DownloadError("cannot add Range header"); +	if(mHeaderList == 0) +	{ +		throw DownloadError("cannot add Range header"); +	}  	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaderList));  	mDownloadStream.open(mDownloadData["path"].asString(), @@ -476,9 +534,9 @@ void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std  	std::string filePath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, fileName);  	mDownloadData["path"] = filePath; -	LL_INFOS("UpdateDownload") << "downloading " << filePath +	LL_INFOS("UpdaterService") << "downloading " << filePath  		<< " from " << uri.asString() << LL_ENDL; -	LL_INFOS("UpdateDownload") << "hash of file is " << hash << LL_ENDL; +	LL_INFOS("UpdaterService") << "hash of file is " << hash << LL_ENDL;  	llofstream dataStream(mDownloadRecordPath);  	LLSDSerialize::toPrettyXML(mDownloadData, dataStream); @@ -508,19 +566,26 @@ bool LLUpdateDownloader::Implementation::validateDownload(void)  {  	std::string filePath = mDownloadData["path"].asString();  	llifstream fileStream(filePath, std::ios_base::in | std::ios_base::binary); -	if(!fileStream) return false; +	if(!fileStream) +	{ +		return false; +	}  	std::string hash = mDownloadData["hash"].asString(); -	if(hash.size() != 0) { -		LL_INFOS("UpdateDownload") << "checking hash..." << LL_ENDL; +	if(hash.size() != 0) +	{ +		LL_INFOS("UpdaterService") << "checking hash..." << LL_ENDL;  		char digest[33];  		LLMD5(fileStream).hex_digest(digest); -		if(hash != digest) { -			LL_WARNS("UpdateDownload") << "download hash mismatch; expeted " << hash << +		if(hash != digest) +		{ +			LL_WARNS("UpdaterService") << "download hash mismatch; expected " << hash <<  				" but download is " << digest << LL_ENDL;  		}  		return hash == digest; -	} else { +	} +	else +	{  		return true; // No hash check provided.  	}  } diff --git a/indra/viewer_components/updater/llupdatedownloader.h b/indra/viewer_components/updater/llupdatedownloader.h index 0d635640cf..f759988f12 100644..100755 --- a/indra/viewer_components/updater/llupdatedownloader.h +++ b/indra/viewer_components/updater/llupdatedownloader.h @@ -54,7 +54,9 @@ public:  	// Start a new download.  	void download(LLURI const & uri,  				  std::string const & hash,  +				  std::string const & updateChannel,  				  std::string const & updateVersion, +				  std::string const & info_url,  				  bool required=false);  	// Returns true if a download is in progress. diff --git a/indra/viewer_components/updater/llupdateinstaller.cpp b/indra/viewer_components/updater/llupdateinstaller.cpp index 2f87d59373..a0e2c0b362 100644..100755 --- a/indra/viewer_components/updater/llupdateinstaller.cpp +++ b/indra/viewer_components/updater/llupdateinstaller.cpp @@ -75,7 +75,7 @@ int ll_install_update(std::string const & script,  			llassert(!"unpossible copy mode");  	} -	llinfos << "UpdateInstaller: installing " << updatePath << " using " << +	LL_INFOS("Updater") << "UpdateInstaller: installing " << updatePath << " using " <<  		actualScriptPath << LL_ENDL;  	LLProcess::Params params; diff --git a/indra/viewer_components/updater/llupdateinstaller.h b/indra/viewer_components/updater/llupdateinstaller.h index fe5b1d19b5..fe5b1d19b5 100644..100755 --- a/indra/viewer_components/updater/llupdateinstaller.h +++ b/indra/viewer_components/updater/llupdateinstaller.h diff --git a/indra/viewer_components/updater/llupdaterservice.cpp b/indra/viewer_components/updater/llupdaterservice.cpp index bc73c72ddc..1bd9fa4fc0 100644..100755 --- a/indra/viewer_components/updater/llupdaterservice.cpp +++ b/indra/viewer_components/updater/llupdaterservice.cpp @@ -32,7 +32,6 @@  #include "lltimer.h"  #include "llupdatechecker.h"  #include "llupdateinstaller.h" -#include "llversionviewer.h"  #include <boost/scoped_ptr.hpp>  #include <boost/weak_ptr.hpp> @@ -44,6 +43,12 @@  #pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally  #endif +#if ! defined(LL_VIEWER_VERSION_MAJOR)			\ + || ! defined(LL_VIEWER_VERSION_MINOR)			\ + || ! defined(LL_VIEWER_VERSION_PATCH)			\ + || ! defined(LL_VIEWER_VERSION_BUILD) +#error "Version information is undefined" +#endif  namespace   { @@ -60,6 +65,8 @@ namespace  	{  #ifdef LL_WINDOWS  		std::string scriptFile = "update_install.bat"; +#elif LL_DARWIN +		std::string scriptFile = "update_install.py";  #else  		std::string scriptFile = "update_install";  #endif @@ -71,6 +78,8 @@ namespace  #ifdef LL_WINDOWS  		return LL_COPY_INSTALL_SCRIPT_TO_TEMP;  #else +		// This is important on Mac because update_install.py looks at its own +		// script pathname to discover the viewer app bundle to update.  		return LL_RUN_INSTALL_SCRIPT_IN_PLACE;  #endif  	}; @@ -83,11 +92,15 @@ class LLUpdaterServiceImpl :  {  	static const std::string sListenerName; -	std::string mProtocolVersion; -	std::string mUrl; -	std::string mPath; -	std::string mChannel; -	std::string mVersion; +	std::string   mProtocolVersion; +	std::string   mUrl; +	std::string   mPath; +	std::string   mChannel; +	std::string   mVersion; +	std::string   mPlatform; +	std::string   mPlatformVersion; +	unsigned char mUniqueId[MD5HEX_STR_SIZE]; +	bool          mWillingToTest;  	unsigned int mCheckPeriod;  	bool mIsChecking; @@ -107,11 +120,15 @@ public:  	LLUpdaterServiceImpl();  	virtual ~LLUpdaterServiceImpl(); -	void initialize(const std::string& protocol_version, -				   const std::string& url,  -				   const std::string& path, -				   const std::string& channel, -				   const std::string& version); +	void initialize(const std::string& 	url,  +					const std::string& 	path, +					const std::string& 	channel, +					const std::string& 	version, +					const std::string&  platform, +					const std::string&  platform_version, +					const unsigned char uniqueid[MD5HEX_STR_SIZE], +					const bool&         willing_to_test					 +					);  	void setCheckPeriod(unsigned int seconds);  	void setBandwidthLimit(U64 bytesPerSecond); @@ -129,13 +146,9 @@ public:  	// LLUpdateChecker::Client:  	virtual void error(std::string const & message); -	virtual void optionalUpdate(std::string const & newVersion, -								LLURI const & uri, -								std::string const & hash); -	virtual void requiredUpdate(std::string const & newVersion, -								LLURI const & uri, -								std::string const & hash); -	virtual void upToDate(void); +	 +	// A successful response was received from the viewer version manager +	virtual void response(LLSD const & content);  	// LLUpdateDownloader::Client  	void downloadComplete(LLSD const & data); @@ -144,6 +157,7 @@ public:  	bool onMainLoop(LLSD const & event);  private: +	std::string mNewChannel;  	std::string mNewVersion;  	void restartTimer(unsigned int seconds); @@ -169,11 +183,14 @@ LLUpdaterServiceImpl::~LLUpdaterServiceImpl()  	LLEventPumps::instance().obtain("mainloop").stopListening(sListenerName);  } -void LLUpdaterServiceImpl::initialize(const std::string& protocol_version, -									  const std::string& url,  -									  const std::string& path, -									  const std::string& channel, -									  const std::string& version) +void LLUpdaterServiceImpl::initialize(const std::string&  url,  +									  const std::string&  path, +									  const std::string&  channel, +									  const std::string&  version, +									  const std::string&  platform, +									  const std::string&  platform_version, +									  const unsigned char uniqueid[MD5HEX_STR_SIZE], +									  const bool&         willing_to_test)  {  	if(mIsChecking || mIsDownloading)  	{ @@ -181,11 +198,22 @@ void LLUpdaterServiceImpl::initialize(const std::string& protocol_version,  										   "while updater is running.");  	} -	mProtocolVersion = protocol_version;  	mUrl = url;  	mPath = path;  	mChannel = channel;  	mVersion = version; +	mPlatform = platform; +	mPlatformVersion = platform_version; +	memcpy(mUniqueId, uniqueid, MD5HEX_STR_SIZE); +	mWillingToTest = willing_to_test; +	LL_DEBUGS("UpdaterService") +		<< "\n  url: " << mUrl +		<< "\n  path: " << mPath +		<< "\n  channel: " << mChannel +		<< "\n  version: " << mVersion +		<< "\n  uniqueid: " << mUniqueId +		<< "\n  willing: " << ( mWillingToTest ? "testok" : "testno" ) +		<< LL_ENDL;  }  void LLUpdaterServiceImpl::setCheckPeriod(unsigned int seconds) @@ -284,7 +312,7 @@ bool LLUpdaterServiceImpl::checkForInstall(bool launchInstaller)  			// the update.  Do not install this update.  			if(!path.asString().empty())  			{ -				llinfos << "ignoring update dowloaded by different client version" << llendl; +				LL_INFOS("UpdaterService") << "ignoring update dowloaded by different client version" << LL_ENDL;;  				LLFile::remove(path.asString());  				LLFile::remove(update_marker_path());  			} @@ -311,9 +339,13 @@ bool LLUpdaterServiceImpl::checkForInstall(bool launchInstaller)  				if((result == 0) && mAppExitCallback)  				{  					mAppExitCallback(); -				} else if(result != 0) { -					llwarns << "failed to run update install script" << LL_ENDL; -				} else { +				} +				else if(result != 0) +				{ +					LL_WARNS("UpdaterService") << "failed to run update install script" << LL_ENDL; +				} +				else +				{  					; // No op.  				}  			} @@ -341,15 +373,19 @@ bool LLUpdaterServiceImpl::checkForResume()  			{  				mIsDownloading = true;  				mNewVersion = download_info["update_version"].asString(); +				mNewChannel = download_info["update_channel"].asString();  				mUpdateDownloader.resume();  				result = true;  			}  			else   			{  				// The viewer that started this download is not the same as this viewer; ignore. -				llinfos << "ignoring partial download from different viewer version" << llendl; +				LL_INFOS("UpdaterService") << "ignoring partial download from different viewer version" << LL_ENDL;;  				std::string path = download_info["path"].asString(); -				if(!path.empty()) LLFile::remove(path); +				if(!path.empty()) +				{ +					LLFile::remove(path); +				}  				LLFile::remove(download_marker_path);  			}  		}  @@ -366,36 +402,43 @@ void LLUpdaterServiceImpl::error(std::string const & message)  	}  } -void LLUpdaterServiceImpl::optionalUpdate(std::string const & newVersion, -										  LLURI const & uri, -										  std::string const & hash) +// A successful response was received from the viewer version manager +void LLUpdaterServiceImpl::response(LLSD const & content)  { -	stopTimer(); -	mNewVersion = newVersion; -	mIsDownloading = true; -	setState(LLUpdaterService::DOWNLOADING); -	mUpdateDownloader.download(uri, hash, newVersion, false); -} - -void LLUpdaterServiceImpl::requiredUpdate(std::string const & newVersion, -										  LLURI const & uri, -										  std::string const & hash) -{ -	stopTimer(); -	mNewVersion = newVersion; -	mIsDownloading = true; -	setState(LLUpdaterService::DOWNLOADING); -	mUpdateDownloader.download(uri, hash, newVersion, true); -} - -void LLUpdaterServiceImpl::upToDate(void) -{ -	if(mIsChecking) +	if(!content.asBoolean()) // an empty response means "no update"  	{ -		restartTimer(mCheckPeriod); -	} +		LL_INFOS("UpdaterService") << "up to date" << LL_ENDL; +		if(mIsChecking) +		{ +			restartTimer(mCheckPeriod); +		} -	setState(LLUpdaterService::UP_TO_DATE); +		setState(LLUpdaterService::UP_TO_DATE); +	} +	else +	{ +		// 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); +	}  }  void LLUpdaterServiceImpl::downloadComplete(LLSD const & data)  @@ -413,9 +456,19 @@ void LLUpdaterServiceImpl::downloadComplete(LLSD const & data)  	payload["type"] = LLSD(LLUpdaterService::DOWNLOAD_COMPLETE);  	payload["required"] = data["required"];  	payload["version"] = mNewVersion; +	payload["channel"] = mNewChannel; +	payload["info_url"] = data["info_url"];  	event["payload"] = payload; +	LL_DEBUGS("UpdaterService") +		<< "Download complete " +		<< ( data["required"].asBoolean() ? "required" : "optional" ) +		<< " channel " << mNewChannel +		<< " version " << mNewVersion +		<< " info " << data["info_url"].asString() +		<< LL_ENDL; +  	LLEventPumps::instance().obtain("mainlooprepeater").post(event); -	 +  	setState(LLUpdaterService::TERMINAL);  } @@ -489,15 +542,18 @@ bool LLUpdaterServiceImpl::onMainLoop(LLSD const & event)  		// Check for failed install.  		if(LLFile::isfile(ll_install_failed_marker_path()))  		{ +			LL_DEBUGS("UpdaterService") << "found marker " << ll_install_failed_marker_path() << LL_ENDL;;  			int requiredValue = 0;   			{  				llifstream stream(ll_install_failed_marker_path());  				stream >> requiredValue; -				if(stream.fail()) requiredValue = 0; +				if(stream.fail()) +				{ +					requiredValue = 0; +				}  			}  			// TODO: notify the user. -			llinfos << "found marker " << ll_install_failed_marker_path() << llendl; -			llinfos << "last install attempt failed" << llendl; +			LL_WARNS("UpdaterService") << "last install attempt failed" << LL_ENDL;;  			LLFile::remove(ll_install_failed_marker_path());  			LLSD event; @@ -509,7 +565,7 @@ bool LLUpdaterServiceImpl::onMainLoop(LLSD const & event)  		}  		else  		{ -			mUpdateChecker.checkVersion(mProtocolVersion, mUrl, mPath, mChannel, mVersion); +			mUpdateChecker.checkVersion(mUrl, mPath, mChannel, mVersion, mPlatform, mPlatformVersion, mUniqueId, mWillingToTest);  			setState(LLUpdaterService::CHECKING_FOR_UPDATE);  		}  	}  @@ -554,13 +610,17 @@ LLUpdaterService::~LLUpdaterService()  {  } -void LLUpdaterService::initialize(const std::string& protocol_version, -								 const std::string& url,  -								 const std::string& path, -								 const std::string& channel, -								 const std::string& version) +void LLUpdaterService::initialize(const std::string& url,  +								  const std::string& path, +								  const std::string& channel, +								  const std::string& version, +								  const std::string& platform, +								  const std::string& platform_version, +								  const unsigned char uniqueid[MD5HEX_STR_SIZE], +								  const bool&         willing_to_test +)  { -	mImpl->initialize(protocol_version, url, path, channel, version); +	mImpl->initialize(url, path, channel, version, platform, platform_version, uniqueid, willing_to_test);  }  void LLUpdaterService::setCheckPeriod(unsigned int seconds) @@ -609,10 +669,10 @@ std::string const & ll_get_version(void) {  	if (version.empty()) {  		std::ostringstream stream; -		stream << LL_VERSION_MAJOR << "." -		<< LL_VERSION_MINOR << "." -		<< LL_VERSION_PATCH << "." -		<< LL_VERSION_BUILD; +		stream << LL_VIEWER_VERSION_MAJOR << "." +			   << LL_VIEWER_VERSION_MINOR << "." +			   << LL_VIEWER_VERSION_PATCH << "." +			   << LL_VIEWER_VERSION_BUILD;  		version = stream.str();  	} diff --git a/indra/viewer_components/updater/llupdaterservice.h b/indra/viewer_components/updater/llupdaterservice.h index 450f19c1c6..982f99b861 100644..100755 --- a/indra/viewer_components/updater/llupdaterservice.h +++ b/indra/viewer_components/updater/llupdaterservice.h @@ -28,6 +28,7 @@  #include <boost/shared_ptr.hpp>  #include <boost/function.hpp> +#include "llhasheduniqueid.h"  class LLUpdaterServiceImpl; @@ -70,11 +71,15 @@ public:  	LLUpdaterService();  	~LLUpdaterService(); -	void initialize(const std::string& protocol_version, -				    const std::string& url,  -				    const std::string& path, -				    const std::string& channel, -				    const std::string& version); +	void initialize(const std::string& 	url,  +				    const std::string& 	path, +				    const std::string& 	channel, +				    const std::string& 	version, +					const std::string&  platform, +					const std::string&  platform_version, +					const unsigned char uniqueid[MD5HEX_STR_SIZE], +					const bool&         willing_to_test +					);  	void setCheckPeriod(unsigned int seconds);  	void setBandwidthLimit(U64 bytesPerSecond); diff --git a/indra/viewer_components/updater/scripts/darwin/janitor.py b/indra/viewer_components/updater/scripts/darwin/janitor.py new file mode 100644 index 0000000000..cdf33df731 --- /dev/null +++ b/indra/viewer_components/updater/scripts/darwin/janitor.py @@ -0,0 +1,133 @@ +#!/usr/bin/python +"""\ +@file   janitor.py +@author Nat Goodspeed +@date   2011-09-14 +@brief  Janitor class to clean up arbitrary resources + +2013-01-04 cloned from vita because it's exactly what update_install.py needs. + +$LicenseInfo:firstyear=2011&license=viewerlgpl$ +Copyright (c) 2011, Linden Research, Inc. +$/LicenseInfo$ +""" + +import sys +import functools +import itertools + +class Janitor(object): +    """ +    Usage: + +    Basic: +    self.janitor = Janitor(sys.stdout) # report cleanup actions on stdout +    ... +    self.janitor.later(os.remove, some_temp_file) +    self.janitor.later(os.remove, some_other_file) +    ... +    self.janitor.cleanup()          # perform cleanup actions + +    Context Manager: +    with Janitor() as janitor:      # clean up quietly +        ... +        janitor.later(shutil.rmtree, some_temp_directory) +        ... +    # exiting 'with' block performs cleanup + +    Test Class: +    class TestMySoftware(unittest.TestCase, Janitor): +        def __init__(self): +            Janitor.__init__(self)  # quiet cleanup +            ... + +        def setUp(self): +            ... +            self.later(os.rename, saved_file, original_location) +            ... + +        def tearDown(self): +            Janitor.tearDown(self)  # calls cleanup() +            ... +            # Or, if you have no other tearDown() logic for +            # TestMySoftware, you can omit the TestMySoftware.tearDown() +            # def entirely and let it inherit Janitor.tearDown(). +    """ +    def __init__(self, stream=None): +        """ +        If you pass stream= (e.g.) sys.stdout or sys.stderr, Janitor will +        report its cleanup operations as it performs them. If you don't, it +        will perform them quietly -- unless one or more of the actions throws +        an exception, in which case you'll get output on stderr. +        """ +        self.stream   = stream +        self.cleanups = [] + +    def later(self, func, *args, **kwds): +        """ +        Pass the callable you want to call at cleanup() time, plus any +        positional or keyword args you want to pass it. +        """ +        # Get a name string for 'func' +        try: +            # A free function has a __name__ +            name = func.__name__ +        except AttributeError: +            try: +                # A class object (even builtin objects like ints!) support +                # __class__.__name__ +                name = func.__class__.__name__ +            except AttributeError: +                # Shrug! Just use repr() to get a string describing this func. +                name = repr(func) +        # Construct a description of this operation in Python syntax from +        # args, kwds. +        desc = "%s(%s)" % \ +               (name, ", ".join(itertools.chain((repr(a) for a in args), +                                                ("%s=%r" % (k, v) for (k, v) in kwds.iteritems())))) +        # Use functools.partial() to bind passed args and keywords to the +        # passed func so we get a nullary callable that does what caller +        # wants. +        bound = functools.partial(func, *args, **kwds) +        self.cleanups.append((desc, bound)) + +    def cleanup(self): +        """ +        Perform all the actions saved with later() calls. +        """ +        # Typically one allocates resource A, then allocates resource B that +        # depends on it. In such a scenario it's appropriate to delete B +        # before A -- so perform cleanup actions in reverse order. (This is +        # the same strategy used by atexit().) +        while self.cleanups: +            # Until our list is empty, pop the last pair. +            desc, bound = self.cleanups.pop(-1) + +            # If requested, report the action. +            if self.stream is not None: +                print >>self.stream, desc + +            try: +                # Call the bound callable +                bound() +            except Exception, err: +                # This is cleanup. Report the problem but continue. +                print >>(self.stream or sys.stderr), "Calling %s\nraised  %s: %s" % \ +                      (desc, err.__class__.__name__, err) + +    def tearDown(self): +        """ +        If a unittest.TestCase subclass (or a nose test class) adds Janitor as +        one of its base classes, and has no other tearDown() logic, let it +        inherit Janitor.tearDown(). +        """ +        self.cleanup() + +    def __enter__(self): +        return self + +    def __exit__(self, type, value, tb): +        # Perform cleanup no matter how we exit this 'with' statement +        self.cleanup() +        # Propagate any exception from the 'with' statement, don't swallow it +        return False diff --git a/indra/viewer_components/updater/scripts/darwin/messageframe.py b/indra/viewer_components/updater/scripts/darwin/messageframe.py new file mode 100644 index 0000000000..8f58848882 --- /dev/null +++ b/indra/viewer_components/updater/scripts/darwin/messageframe.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +"""\ +@file   messageframe.py +@author Nat Goodspeed +@date   2013-01-03 +@brief  Define MessageFrame class for popping up messages from a command-line +        script. + +$LicenseInfo:firstyear=2013&license=viewerlgpl$ +Copyright (c) 2013, Linden Research, Inc. +$/LicenseInfo$ +""" + +import Tkinter as tk +import os + +# Tricky way to obtain the filename of the main script (default title string) +import __main__ + +# This class is intended for displaying messages from a command-line script. +# Getting the base class right took a bit of trial and error. +# If you derive from tk.Frame, the destroy() method doesn't actually close it. +# If you derive from tk.Toplevel, it pops up a separate Tk frame too. destroy() +# closes this frame, but not that one. +# Deriving from tk.Tk appears to do the right thing. +class MessageFrame(tk.Tk): +    def __init__(self, text="", title=os.path.splitext(os.path.basename(__main__.__file__))[0], +                 width=320, height=120): +        tk.Tk.__init__(self) +        self.grid() +        self.title(title) +        self.var = tk.StringVar() +        self.var.set(text) +        self.msg = tk.Label(self, textvariable=self.var) +        self.msg.grid() +        # from http://stackoverflow.com/questions/3352918/how-to-center-a-window-on-the-screen-in-tkinter : +        self.update_idletasks() + +        # The constants below are to adjust for typical overhead from the +        # frame borders. +        xp = (self.winfo_screenwidth()  / 2) - (width  / 2) - 8 +        yp = (self.winfo_screenheight() / 2) - (height / 2) - 20 +        self.geometry('{0}x{1}+{2}+{3}'.format(width, height, xp, yp)) +        self.update() + +    def set(self, text): +        self.var.set(text) +        self.update() + +if __name__ == "__main__": +    # When run as a script, just test the MessageFrame. +    import sys +    import time + +    frame = MessageFrame("something in the way she moves....") +    time.sleep(3) +    frame.set("smaller") +    time.sleep(3) +    frame.set("""this has +several +lines""") +    time.sleep(3) +    frame.destroy() +    print "Destroyed!" +    sys.stdout.flush() +    time.sleep(3) diff --git a/indra/viewer_components/updater/scripts/darwin/update_install b/indra/viewer_components/updater/scripts/darwin/update_install deleted file mode 100644 index e7f36dc5a3..0000000000 --- a/indra/viewer_components/updater/scripts/darwin/update_install +++ /dev/null @@ -1,10 +0,0 @@ -#! /bin/bash - -# -# The first argument contains the path to the installer app.  The second a path -# to a marker file which should be created if the installer fails.q -# - -cd "$(dirname "$0")" -(../Resources/mac-updater.app/Contents/MacOS/mac-updater -dmg "$1" -name "Second Life Viewer"; if [ $? -ne 0 ]; then echo $3 >> "$2"; fi;) & -exit 0 diff --git a/indra/viewer_components/updater/scripts/darwin/update_install.py b/indra/viewer_components/updater/scripts/darwin/update_install.py new file mode 100755 index 0000000000..2fc6fcdb29 --- /dev/null +++ b/indra/viewer_components/updater/scripts/darwin/update_install.py @@ -0,0 +1,373 @@ +#!/usr/bin/python +"""\ +@file   update_install.py +@author Nat Goodspeed +@date   2012-12-20 +@brief  Update the containing Second Life application bundle to the version in +        the specified disk image file. + +        This Python implementation is derived from the previous mac-updater +        application, a funky mix of C++, classic C and Objective-C. + +$LicenseInfo:firstyear=2012&license=viewerlgpl$ +Copyright (c) 2012, Linden Research, Inc. +$/LicenseInfo$ +""" + +import os +import sys +import cgitb +import errno +import glob +import plistlib +import re +import shutil +import subprocess +import tempfile +import time +from janitor import Janitor +from messageframe import MessageFrame +import Tkinter, tkMessageBox + +TITLE = "Second Life Viewer Updater" +# Magic bundle identifier used by all Second Life viewer bundles +BUNDLE_IDENTIFIER = "com.secondlife.indra.viewer" + +# Global handle to the MessageFrame so we can update message +FRAME = None +# Global handle to logfile, once it's open +LOGF  = None + +# **************************************************************************** +#   Logging and messaging +# +#   This script is normally run implicitly by the old viewer to update to the +#   new viewer. Its UI consists of a MessageFrame and possibly a Tk error box. +#   Log details to updater.log -- especially uncaught exceptions! +# **************************************************************************** +def log(message): +    """write message only to LOGF (also called by status() and fail())""" +    # If we don't even have LOGF open yet, at least write to Console log +    logf = LOGF or sys.stderr +    logf.writelines((time.strftime("%Y-%m-%dT%H:%M:%SZ ", time.gmtime()), message, '\n')) +    logf.flush() + +def status(message): +    """display and log normal progress message""" +    log(message) + +    global FRAME +    if not FRAME: +        FRAME = MessageFrame(message, TITLE) +    else: +        FRAME.set(message) + +def fail(message): +    """log message, produce error box, then terminate with nonzero rc""" +    log(message) + +    # If we haven't yet called status() (we don't yet have a FRAME), perform a +    # bit of trickery to bypass the spurious "main window" that Tkinter would +    # otherwise pop up if the first call is showerror(). +    if not FRAME: +        root = Tkinter.Tk() +        root.withdraw() + +    # If we do have a LOGF available, mention it in the error box. +    if LOGF: +        message = "%s\n(Updater log in %s)" % (message, LOGF.name) + +    # We explicitly specify the WARNING icon because, at least on the Tkinter +    # bundled with the system-default Python 2.7 on Mac OS X 10.7.4, the +    # ERROR, QUESTION and INFO icons are all the silly Tk rocket ship. At +    # least WARNING has an exclamation in a yellow triangle, even though +    # overlaid by a smaller image of the rocket ship. +    tkMessageBox.showerror(TITLE, +"""An error occurred while updating Second Life: +%s +Please download the latest viewer from www.secondlife.com.""" % message, +                           icon=tkMessageBox.WARNING) +    sys.exit(1) + +def exception(err): +    """call fail() with an exception instance""" +    fail("%s exception: %s" % (err.__class__.__name__, str(err))) + +def excepthook(type, value, traceback): +    """ +    Store this hook function into sys.excepthook until we have a logfile. +    """ +    # At least in older Python versions, it could be tricky to produce a +    # string from 'type' and 'value'. For instance, an OSError exception would +    # pass type=OSError and value=some_tuple. Empirically, this funky +    # expression seems to work. +    exception(type(*value)) +sys.excepthook = excepthook + +class ExceptHook(object): +    """ +    Store an instance of this class into sys.excepthook once we have a logfile +    open. +    """ +    def __init__(self, logfile): +        # There's no magic to the cgitb.enable() function -- it merely stores +        # an instance of cgitb.Hook into sys.excepthook, passing enable()'s +        # params into Hook.__init__(). Sadly, enable() doesn't forward all its +        # params using (*args, **kwds) syntax -- another story. But the point +        # is that all the goodness is in the cgitb.Hook class. Capture an +        # instance. +        self.hook = cgitb.Hook(file=logfile, format="text") + +    def __call__(self, type, value, traceback): +        # produce nice text traceback to logfile +        self.hook(type, value, traceback) +        # Now display an error box. +        excepthook(type, value, traceback) + +def write_marker(markerfile, markertext): +    log("writing %r to %s" % (markertext, markerfile)) +    try: +        with open(markerfile, "w") as markerf: +            markerf.write(markertext) +    except IOError, err: +        # write_marker() is invoked by fail(), and fail() is invoked by other +        # error-handling functions. If we try to invoke any of those, we'll +        # get infinite recursion. If for any reason we can't write markerfile, +        # try to log it -- otherwise shrug. +        log("%s exception: %s" % (err.__class__.__name__, err)) + +# **************************************************************************** +#   Main script logic +# **************************************************************************** +def main(dmgfile, markerfile, markertext): +    # Should we fail, we're supposed to write 'markertext' to 'markerfile'. +    # Wrap the fail() function so we do that. +    global fail +    oldfail = fail +    def fail(message): +        write_marker(markerfile, markertext) +        oldfail(message) + +    try: +        # Starting with the Cocoafied viewer, we'll find viewer logs in +        # ~/Library/Application Support/$CFBundleIdentifier/logs rather than in +        # ~/Library/Application Support/SecondLife/logs as before. This could be +        # obnoxious -- but we Happen To Know that markerfile is a path specified +        # within the viewer's logs directory. Use that. +        logsdir = os.path.dirname(markerfile) + +        # Move the old updater.log file out of the way +        logname = os.path.join(logsdir, "updater.log") +        try: +            os.rename(logname, logname + ".old") +        except OSError, err: +            # Nonexistence is okay. Anything else, not so much. +            if err.errno != errno.ENOENT: +                raise + +        # Open new updater.log. +        global LOGF +        LOGF = open(logname, "w") + +        # Now that LOGF is in fact open for business, use it to log any further +        # uncaught exceptions. +        sys.excepthook = ExceptHook(LOGF) + +        # log how this script was invoked +        log(' '.join(repr(arg) for arg in sys.argv)) + +        # prepare for other cleanup +        with Janitor(LOGF) as janitor: + +            # Try to derive the name of the running viewer app bundle from our +            # own pathname. (Hopefully the old viewer won't copy this script +            # to a temp dir before running!) +            # Somewhat peculiarly, this script is currently packaged in +            # Appname.app/Contents/MacOS with the viewer executable. But even +            # if we decide to move it to Appname.app/Contents/Resources, we'll +            # still find Appname.app two levels up from dirname(__file__). +            appdir = os.path.abspath(os.path.join(os.path.dirname(__file__), +                                                  os.pardir, os.pardir)) +            if not appdir.endswith(".app"): +                # This can happen if either this script has been copied before +                # being executed, or if it's in an unexpected place in the app +                # bundle. +                fail(appdir + " is not an application directory") + +            # We need to install into appdir's parent directory -- can we? +            installdir = os.path.abspath(os.path.join(appdir, os.pardir)) +            if not os.access(installdir, os.W_OK): +                fail("Can't modify " + installdir) + +            # invent a temporary directory +            tempdir = tempfile.mkdtemp() +            log("created " + tempdir) +            # clean it up when we leave +            janitor.later(shutil.rmtree, tempdir) + +            status("Mounting image...") + +            mntdir = os.path.join(tempdir, "mnt") +            log("mkdir " + mntdir) +            os.mkdir(mntdir) +            command = ["hdiutil", "attach", dmgfile, "-mountpoint", mntdir] +            log(' '.join(command)) +            # Instantiating subprocess.Popen launches a child process with the +            # specified command line. stdout=PIPE passes a pipe to its stdout. +            hdiutil = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=LOGF) +            # Popen.communicate() reads that pipe until the child process +            # terminates, returning (stdout, stderr) output. Select just stdout. +            hdiutil_out = hdiutil.communicate()[0] +            if hdiutil.returncode != 0: +                fail("Couldn't mount " + dmgfile) +            # hdiutil should report the devnode. Find that. +            found = re.search(r"/dev/[^ ]*\b", hdiutil_out) +            if not found: +                # If we don't spot the devnode, log it and continue -- we only +                # use it to detach it. Don't fail the whole update if we can't +                # clean up properly. +                log("Couldn't spot devnode in hdiutil output:\n" + hdiutil_out) +            else: +                # If we do spot the devnode, detach it when done. +                janitor.later(subprocess.call, ["hdiutil", "detach", found.group(0)], +                              stdout=LOGF, stderr=subprocess.STDOUT) + +            status("Searching for app bundle...") + +            for candidate in glob.glob(os.path.join(mntdir, "*.app")): +                log("Considering " + candidate) +                try: +                    # By convention, a valid Mac app bundle has a +                    # Contents/Info.plist file containing at least +                    # CFBundleIdentifier. +                    CFBundleIdentifier = \ +                        plistlib.readPlist(os.path.join(candidate, "Contents", +                                                        "Info.plist"))["CFBundleIdentifier"] +                except Exception, err: +                    # might be IOError, xml.parsers.expat.ExpatError, KeyError +                    # Any of these means it's not a valid app bundle. Instead +                    # of aborting, just skip this candidate and continue. +                    log("%s not a valid app bundle: %s: %s" % +                        (candidate, err.__class__.__name__, err)) +                    continue + +                if CFBundleIdentifier == BUNDLE_IDENTIFIER: +                    break + +                log("unrecognized CFBundleIdentifier: " + CFBundleIdentifier) + +            else: +                fail("Could not find Second Life viewer in " + dmgfile) + +            # Here 'candidate' is the new viewer to install +            log("Found " + candidate) + +            # This logic was changed to make Mac updates behave more like +            # Windows. Most of the time, the user doesn't change the name of +            # the app bundle on our .dmg installer (e.g. "Second Life Beta +            # Viewer.app"). Most of the time, the version manager directs a +            # given viewer to update to another .dmg containing an app bundle +            # with THE SAME name. In that case, everything behaves as usual. + +            # The case that was changed is when the version manager offers (or +            # mandates) an update to a .dmg containing a different app bundle +            # name. This can happen, for instance, to a user who's downloaded +            # a "project beta" viewer, and the project subsequently publishes +            # a Release Candidate viewer. Say the project beta's app bundle +            # name is something like "Second Life Beta Neato.app". Anyone +            # launching that viewer will be offered an update to the +            # corresponding Release Candidate viewer -- which will be built as +            # a release viewer, with app bundle name "Second Life Viewer.app". + +            # On Windows, we run the NSIS installer, which will update/replace +            # the embedded install directory name, e.g. Second Life Viewer. +            # But the Mac installer used to locate the app bundle name in the +            # mounted .dmg file, then ignore that name, copying its contents +            # into the app bundle directory of the running viewer. That is, +            # we'd install the Release Candidate from the .dmg's "Second +            # Life.app" into "/Applications/Second Life Beta Neato.app". This +            # is undesired behavior. + +            # Instead, having found the app bundle name on the mounted .dmg, +            # we try to install that app bundle name into the parent directory +            # of the running app bundle. + +            # Are we installing a different app bundle name? If so, call it +            # out, both in the log and for the user -- this is an odd case. +            # (Presumably they've already agreed to a similar notification in +            # the viewer before the viewer launched this script, but still.) +            bundlename = os.path.basename(candidate) +            if os.path.basename(appdir) == bundlename: +                # updating the running app bundle, which we KNOW exists +                appexists = True +            else: +                # installing some other app bundle +                newapp = os.path.join(installdir, bundlename) +                appexists = os.path.exists(newapp) +                message = "Note: %s %s %s" % \ +                          (appdir, "updating" if appexists else "installing new", newapp) +                status(message) +                # okay, we have no further need of the name of the running app +                # bundle. +                appdir = newapp + +            status("Preparing to copy files...") + +            if appexists: +                # move old viewer to temp location in case copy from .dmg fails +                aside = os.path.join(tempdir, os.path.basename(appdir)) +                log("mv %r %r" % (appdir, aside)) +                # Use shutil.move() instead of os.rename(). move() first tries +                # os.rename(), but falls back to shutil.copytree() if the dest is +                # on a different filesystem. +                shutil.move(appdir, aside) + +            status("Copying files...") + +            # shutil.copytree()'s target must not already exist. But we just +            # moved appdir out of the way. +            log("cp -p %r %r" % (candidate, appdir)) +            try: +                # The viewer app bundle does include internal symlinks. Keep them +                # as symlinks. +                shutil.copytree(candidate, appdir, symlinks=True) +            except Exception, err: +                # copy failed -- try to restore previous viewer before crumping +                type, value, traceback = sys.exc_info() +                if appexists: +                    log("exception response: mv %r %r" % (aside, appdir)) +                    shutil.move(aside, appdir) +                # let our previously-set sys.excepthook handle this +                raise type, value, traceback + +            status("Cleaning up...") + +            log("touch " + appdir) +            os.utime(appdir, None)      # set to current time + +            command = ["open", appdir] +            log(' '.join(command)) +            subprocess.check_call(command, stdout=LOGF, stderr=subprocess.STDOUT) + +    except Exception, err: +        # Because we carefully set sys.excepthook -- and even modify it to log +        # the problem once we have our log file open -- you might think we +        # could just let exceptions propagate. But when we do that, on +        # exception in this block, we FIRST restore the no-side-effects fail() +        # and THEN implicitly call sys.excepthook(), which calls the (no-side- +        # effects) fail(). Explicitly call sys.excepthook() BEFORE restoring +        # fail(). Only then do we get the enriched fail() behavior. +        sys.excepthook(*sys.exc_info()) + +    finally: +        # When we leave main() -- for whatever reason -- reset fail() the way +        # it was before, because the bound markerfile, markertext params +        # passed to this main() call are no longer applicable. +        fail = oldfail + +if __name__ == "__main__": +    # We expect this script to be invoked with: +    # - the pathname to the .dmg we intend to install; +    # - the pathname to an update-error marker file to create on failure; +    # - the content to write into the marker file. +    main(*sys.argv[1:]) diff --git a/indra/viewer_components/updater/scripts/linux/update_install b/indra/viewer_components/updater/scripts/linux/update_install index a9df9042fd..a9df9042fd 100644..100755 --- a/indra/viewer_components/updater/scripts/linux/update_install +++ b/indra/viewer_components/updater/scripts/linux/update_install diff --git a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp index de07beee7c..4812272ebc 100644..100755 --- a/indra/viewer_components/updater/tests/llupdaterservice_test.cpp +++ b/indra/viewer_components/updater/tests/llupdaterservice_test.cpp @@ -44,11 +44,17 @@  *****************************************************************************/  LLUpdateChecker::LLUpdateChecker(LLUpdateChecker::Client & client)  {} -void LLUpdateChecker::checkVersion(std::string const & protocolVersion, std::string const & hostUrl,  -								  std::string const & servicePath, std::string channel, std::string version) +void LLUpdateChecker::checkVersion(std::string const & hostUrl,  +								   std::string const & servicePath, +								   std::string const & channel, +								   std::string const & version, +								   std::string const & platform, +								   std::string const & platform_version, +								   unsigned char       uniqueid[MD5HEX_STR_SIZE], +								   bool                willing_to_test)  {}  LLUpdateDownloader::LLUpdateDownloader(Client & ) {} -void LLUpdateDownloader::download(LLURI const & , std::string const &, std::string const &, bool){} +void LLUpdateDownloader::download(LLURI const & , std::string const &, std::string const &, std::string const &, std::string const &, bool){}  class LLDir_Mock : public LLDir  { @@ -172,9 +178,11 @@ namespace tut  		bool got_usage_error = false;  		try  		{ -			updater.initialize("1.0",test_url, "update" ,test_channel, test_version); +			unsigned char id1[MD5HEX_STR_SIZE] = "11111111111111111111111111111111"; +			updater.initialize(test_url, "update" ,test_channel, test_version, "win", "1.2.3", id1, true);  			updater.startChecking(); -			updater.initialize("1.0", "other_url", "update", test_channel, test_version); +			unsigned char id2[MD5HEX_STR_SIZE] = "22222222222222222222222222222222"; +			updater.initialize("other_url", "update", test_channel, test_version, "win", "4.5.6", id2, true);  		}  		catch(LLUpdaterService::UsageError)  		{ @@ -188,7 +196,8 @@ namespace tut      {          DEBUG;  		LLUpdaterService updater; -		updater.initialize("1.0", test_url, "update", test_channel, test_version); +		unsigned char id[MD5HEX_STR_SIZE] = "33333333333333333333333333333333"; +		updater.initialize(test_url, "update", test_channel, test_version, "win", "7.8.9", id, true);  		updater.startChecking();  		ensure(updater.isChecking());  		updater.stopChecking(); | 
