PageRenderTime 83ms CodeModel.GetById 18ms app.highlight 58ms RepoModel.GetById 1ms app.codeStats 0ms

/indra/viewer_components/updater/llupdatedownloader.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 526 lines | 400 code | 94 blank | 32 comment | 73 complexity | 5e1fdfb35071213eeb9d5e440a8af5ac MD5 | raw file
  1/** 
  2 * @file llupdatedownloader.cpp
  3 *
  4 * $LicenseInfo:firstyear=2010&license=viewerlgpl$
  5 * Second Life Viewer Source Code
  6 * Copyright (C) 2010, Linden Research, Inc.
  7 * 
  8 * This library is free software; you can redistribute it and/or
  9 * modify it under the terms of the GNU Lesser General Public
 10 * License as published by the Free Software Foundation;
 11 * version 2.1 of the License only.
 12 * 
 13 * This library is distributed in the hope that it will be useful,
 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 16 * Lesser General Public License for more details.
 17 * 
 18 * You should have received a copy of the GNU Lesser General Public
 19 * License along with this library; if not, write to the Free Software
 20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 21 * 
 22 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 23 * $/LicenseInfo$
 24 */
 25
 26#include "linden_common.h"
 27
 28#include "llupdatedownloader.h"
 29
 30#include <stdexcept>
 31#include <boost/format.hpp>
 32#include <boost/lexical_cast.hpp>
 33#include <curl/curl.h>
 34#include "lldir.h"
 35#include "llevents.h"
 36#include "llfile.h"
 37#include "llmd5.h"
 38#include "llsd.h"
 39#include "llsdserialize.h"
 40#include "llthread.h"
 41#include "llupdaterservice.h"
 42#include "llcurl.h"
 43
 44class LLUpdateDownloader::Implementation:
 45	public LLThread
 46{
 47public:
 48	Implementation(LLUpdateDownloader::Client & client);
 49	~Implementation();
 50	void cancel(void);
 51	void download(LLURI const & uri,
 52				  std::string const & hash,
 53				  std::string const & updateVersion,
 54				  bool required);
 55	bool isDownloading(void);
 56	size_t onHeader(void * header, size_t size);
 57	size_t onBody(void * header, size_t size);
 58	int onProgress(double downloadSize, double bytesDownloaded);
 59	void resume(void);
 60	void setBandwidthLimit(U64 bytesPerSecond);
 61	
 62private:
 63	curl_off_t mBandwidthLimit;
 64	bool mCancelled;
 65	LLUpdateDownloader::Client & mClient;
 66	CURL * mCurl;
 67	LLSD mDownloadData;
 68	llofstream mDownloadStream;
 69	unsigned char mDownloadPercent;
 70	std::string mDownloadRecordPath;
 71	curl_slist * mHeaderList;
 72	
 73	void initializeCurlGet(std::string const & url, bool processHeader);
 74	void resumeDownloading(size_t startByte);
 75	void run(void);
 76	void startDownloading(LLURI const & uri, std::string const & hash);
 77	void throwOnCurlError(CURLcode code);
 78	bool validateDownload(void);
 79
 80	LOG_CLASS(LLUpdateDownloader::Implementation);
 81};
 82
 83
 84namespace {
 85	class DownloadError:
 86		public std::runtime_error
 87	{
 88	public:
 89		DownloadError(const char * message):
 90			std::runtime_error(message)
 91		{
 92			; // No op.
 93		}
 94	};
 95
 96		
 97	const char * gSecondLifeUpdateRecord = "SecondLifeUpdateDownload.xml";
 98};
 99
100
101
102// LLUpdateDownloader
103//-----------------------------------------------------------------------------
104
105
106
107std::string LLUpdateDownloader::downloadMarkerPath(void)
108{
109	return gDirUtilp->getExpandedFilename(LL_PATH_LOGS, gSecondLifeUpdateRecord);
110}
111
112
113LLUpdateDownloader::LLUpdateDownloader(Client & client):
114	mImplementation(new LLUpdateDownloader::Implementation(client))
115{
116	; // No op.
117}
118
119
120void LLUpdateDownloader::cancel(void)
121{
122	mImplementation->cancel();
123}
124
125
126void LLUpdateDownloader::download(LLURI const & uri,
127								  std::string const & hash,
128								  std::string const & updateVersion,
129								  bool required)
130{
131	mImplementation->download(uri, hash, updateVersion, required);
132}
133
134
135bool LLUpdateDownloader::isDownloading(void)
136{
137	return mImplementation->isDownloading();
138}
139
140
141void LLUpdateDownloader::resume(void)
142{
143	mImplementation->resume();
144}
145
146
147void LLUpdateDownloader::setBandwidthLimit(U64 bytesPerSecond)
148{
149	mImplementation->setBandwidthLimit(bytesPerSecond);
150}
151
152
153
154// LLUpdateDownloader::Implementation
155//-----------------------------------------------------------------------------
156
157
158namespace {
159	size_t write_function(void * data, size_t blockSize, size_t blocks, void * downloader)
160	{
161		size_t bytes = blockSize * blocks;
162		return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->onBody(data, bytes);
163	}
164
165
166	size_t header_function(void * data, size_t blockSize, size_t blocks, void * downloader)
167	{
168		size_t bytes = blockSize * blocks;
169		return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->onHeader(data, bytes);
170	}
171
172
173	int progress_callback(void * downloader,
174						  double dowloadTotal,
175						  double downloadNow,
176						  double uploadTotal,
177						  double uploadNow)
178	{
179		return reinterpret_cast<LLUpdateDownloader::Implementation *>(downloader)->
180			onProgress(dowloadTotal, downloadNow);
181	}
182}
183
184
185LLUpdateDownloader::Implementation::Implementation(LLUpdateDownloader::Client & client):
186	LLThread("LLUpdateDownloader"),
187	mBandwidthLimit(0),
188	mCancelled(false),
189	mClient(client),
190	mCurl(0),
191	mDownloadPercent(0),
192	mHeaderList(0)
193{
194	CURLcode code = curl_global_init(CURL_GLOBAL_ALL); // Just in case.
195	llverify(code == CURLE_OK); // TODO: real error handling here. 
196}
197
198
199LLUpdateDownloader::Implementation::~Implementation()
200{
201	if(isDownloading()) 
202	{
203		cancel();
204		shutdown();
205	} 
206	else 
207	{
208		; // No op.
209	}
210	if(mCurl)
211	{
212		LLCurl::deleteEasyHandle(mCurl);
213	}
214}
215
216
217void LLUpdateDownloader::Implementation::cancel(void)
218{
219	mCancelled = true;
220}
221	
222
223void LLUpdateDownloader::Implementation::download(LLURI const & uri,
224												  std::string const & hash,
225												  std::string const & updateVersion,
226												  bool required)
227{
228	if(isDownloading()) mClient.downloadError("download in progress");
229
230	mDownloadRecordPath = downloadMarkerPath();
231	mDownloadData = LLSD();
232	mDownloadData["required"] = required;
233	mDownloadData["update_version"] = updateVersion;
234	try {
235		startDownloading(uri, hash);
236	} catch(DownloadError const & e) {
237		mClient.downloadError(e.what());
238	}
239}
240
241
242bool LLUpdateDownloader::Implementation::isDownloading(void)
243{
244	return !isStopped();
245}
246
247
248void LLUpdateDownloader::Implementation::resume(void)
249{
250	mCancelled = false;
251
252	if(isDownloading()) {
253		mClient.downloadError("download in progress");
254	}
255
256	mDownloadRecordPath = downloadMarkerPath();
257	llifstream dataStream(mDownloadRecordPath);
258	if(!dataStream) {
259		mClient.downloadError("no download marker");
260		return;
261	}
262	
263	LLSDSerialize::fromXMLDocument(mDownloadData, dataStream);
264	
265	if(!mDownloadData.asBoolean()) {
266		mClient.downloadError("no download information in marker");
267		return;
268	}
269	
270	std::string filePath = mDownloadData["path"].asString();
271	try {
272		if(LLFile::isfile(filePath)) {		
273			llstat fileStatus;
274			LLFile::stat(filePath, &fileStatus);
275			if(fileStatus.st_size != mDownloadData["size"].asInteger()) {
276				resumeDownloading(fileStatus.st_size);
277			} else if(!validateDownload()) {
278				LLFile::remove(filePath);
279				download(LLURI(mDownloadData["url"].asString()), 
280						 mDownloadData["hash"].asString(),
281						 mDownloadData["update_version"].asString(),
282						 mDownloadData["required"].asBoolean());
283			} else {
284				mClient.downloadComplete(mDownloadData);
285			}
286		} else {
287			download(LLURI(mDownloadData["url"].asString()), 
288					 mDownloadData["hash"].asString(),
289					 mDownloadData["update_version"].asString(),
290					 mDownloadData["required"].asBoolean());
291		}
292	} catch(DownloadError & e) {
293		mClient.downloadError(e.what());
294	}
295}
296
297
298void LLUpdateDownloader::Implementation::setBandwidthLimit(U64 bytesPerSecond)
299{
300	if((mBandwidthLimit != bytesPerSecond) && isDownloading() && !mDownloadData["required"].asBoolean()) {
301		llassert(mCurl != 0);
302		mBandwidthLimit = bytesPerSecond;
303		CURLcode code = curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, &mBandwidthLimit);
304		if(code != CURLE_OK) LL_WARNS("UpdateDownload") << 
305			"unable to change dowload bandwidth" << LL_ENDL;
306	} else {
307		mBandwidthLimit = bytesPerSecond;
308	}
309}
310
311
312size_t LLUpdateDownloader::Implementation::onHeader(void * buffer, size_t size)
313{
314	char const * headerPtr = reinterpret_cast<const char *> (buffer);
315	std::string header(headerPtr, headerPtr + size);
316	size_t colonPosition = header.find(':');
317	if(colonPosition == std::string::npos) return size; // HTML response; ignore.
318	
319	if(header.substr(0, colonPosition) == "Content-Length") {
320		try {
321			size_t firstDigitPos = header.find_first_of("0123456789", colonPosition);
322			size_t lastDigitPos = header.find_last_of("0123456789");
323			std::string contentLength = header.substr(firstDigitPos, lastDigitPos - firstDigitPos + 1);
324			size_t size = boost::lexical_cast<size_t>(contentLength);
325			LL_INFOS("UpdateDownload") << "download size is " << size << LL_ENDL;
326			
327			mDownloadData["size"] = LLSD(LLSD::Integer(size));
328			llofstream odataStream(mDownloadRecordPath);
329			LLSDSerialize::toPrettyXML(mDownloadData, odataStream);
330		} catch (std::exception const & e) {
331			LL_WARNS("UpdateDownload") << "unable to read content length (" 
332				<< e.what() << ")" << LL_ENDL;
333		}
334	} else {
335		; // No op.
336	}
337	
338	return size;
339}
340
341
342size_t LLUpdateDownloader::Implementation::onBody(void * buffer, size_t size)
343{
344	if(mCancelled) return 0; // Forces a write error which will halt curl thread.
345	if((size == 0) || (buffer == 0)) return 0; 
346	
347	mDownloadStream.write(reinterpret_cast<const char *>(buffer), size);
348	if(mDownloadStream.bad()) {
349		return 0;
350	} else {
351		return size;
352	}
353}
354
355
356int LLUpdateDownloader::Implementation::onProgress(double downloadSize, double bytesDownloaded)
357{
358	int downloadPercent = static_cast<int>(100. * (bytesDownloaded / downloadSize));
359	if(downloadPercent > mDownloadPercent) {
360		mDownloadPercent = downloadPercent;
361		
362		LLSD event;
363		event["pump"] = LLUpdaterService::pumpName();
364		LLSD payload;
365		payload["type"] = LLSD(LLUpdaterService::PROGRESS);
366		payload["download_size"] = downloadSize;
367		payload["bytes_downloaded"] = bytesDownloaded;
368		event["payload"] = payload;
369		LLEventPumps::instance().obtain("mainlooprepeater").post(event);
370		
371		LL_INFOS("UpdateDownload") << "progress event " << payload << LL_ENDL;
372	} else {
373		; // Keep events to a reasonalbe number.
374	}
375	
376	return 0;
377}
378
379
380void LLUpdateDownloader::Implementation::run(void)
381{
382	CURLcode code = curl_easy_perform(mCurl);
383	mDownloadStream.close();
384	if(code == CURLE_OK) {
385		LLFile::remove(mDownloadRecordPath);
386		if(validateDownload()) {
387			LL_INFOS("UpdateDownload") << "download successful" << LL_ENDL;
388			mClient.downloadComplete(mDownloadData);
389		} else {
390			LL_INFOS("UpdateDownload") << "download failed hash check" << LL_ENDL;
391			std::string filePath = mDownloadData["path"].asString();
392			if(filePath.size() != 0) LLFile::remove(filePath);
393			mClient.downloadError("failed hash check");
394		}
395	} else if(mCancelled && (code == CURLE_WRITE_ERROR)) {
396		LL_INFOS("UpdateDownload") << "download canceled by user" << LL_ENDL;
397		// Do not call back client.
398	} else {
399		LL_WARNS("UpdateDownload") << "download failed with error '" << 
400			curl_easy_strerror(code) << "'" << LL_ENDL;
401		LLFile::remove(mDownloadRecordPath);
402		if(mDownloadData.has("path")) LLFile::remove(mDownloadData["path"].asString());
403		mClient.downloadError("curl error");
404	}
405	
406	if(mHeaderList) {
407		curl_slist_free_all(mHeaderList);
408		mHeaderList = 0;
409	}
410}
411
412
413void LLUpdateDownloader::Implementation::initializeCurlGet(std::string const & url, bool processHeader)
414{
415	if(mCurl == 0) 
416	{
417		mCurl = LLCurl::newEasyHandle();
418	} 
419	else 
420	{
421		curl_easy_reset(mCurl);
422	}
423	
424	if(mCurl == 0) throw DownloadError("failed to initialize curl");
425	
426	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOSIGNAL, true));
427	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_FOLLOWLOCATION, true));
428	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEFUNCTION, &write_function));
429	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_WRITEDATA, this));
430	if(processHeader) {
431	   throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERFUNCTION, &header_function));
432	   throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HEADERDATA, this));
433	}
434	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPGET, true));
435	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_URL, url.c_str()));
436	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_PROGRESSFUNCTION, &progress_callback));
437	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_PROGRESSDATA, this));
438	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_NOPROGRESS, false));
439	// if it's a required update set the bandwidth limit to 0 (unlimited)
440	curl_off_t limit = mDownloadData["required"].asBoolean() ? 0 : mBandwidthLimit;
441	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_MAX_RECV_SPEED_LARGE, limit));
442	
443	mDownloadPercent = 0;
444}
445
446
447void LLUpdateDownloader::Implementation::resumeDownloading(size_t startByte)
448{
449	LL_INFOS("UpdateDownload") << "resuming download from " << mDownloadData["url"].asString()
450		<< " at byte " << startByte << LL_ENDL;
451
452	initializeCurlGet(mDownloadData["url"].asString(), false);
453	
454	// The header 'Range: bytes n-' will request the bytes remaining in the
455	// source begining with byte n and ending with the last byte.
456	boost::format rangeHeaderFormat("Range: bytes=%u-");
457	rangeHeaderFormat % startByte;
458	mHeaderList = curl_slist_append(mHeaderList, rangeHeaderFormat.str().c_str());
459	if(mHeaderList == 0) throw DownloadError("cannot add Range header");
460	throwOnCurlError(curl_easy_setopt(mCurl, CURLOPT_HTTPHEADER, mHeaderList));
461	
462	mDownloadStream.open(mDownloadData["path"].asString(),
463						 std::ios_base::out | std::ios_base::binary | std::ios_base::app);
464	start();
465}
466
467
468void LLUpdateDownloader::Implementation::startDownloading(LLURI const & uri, std::string const & hash)
469{
470	mDownloadData["url"] = uri.asString();
471	mDownloadData["hash"] = hash;
472	mDownloadData["current_version"] = ll_get_version();
473	LLSD path = uri.pathArray();
474	if(path.size() == 0) throw DownloadError("no file path");
475	std::string fileName = path[path.size() - 1].asString();
476	std::string filePath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, fileName);
477	mDownloadData["path"] = filePath;
478
479	LL_INFOS("UpdateDownload") << "downloading " << filePath
480		<< " from " << uri.asString() << LL_ENDL;
481	LL_INFOS("UpdateDownload") << "hash of file is " << hash << LL_ENDL;
482		
483	llofstream dataStream(mDownloadRecordPath);
484	LLSDSerialize::toPrettyXML(mDownloadData, dataStream);
485	
486	mDownloadStream.open(filePath, std::ios_base::out | std::ios_base::binary);
487	initializeCurlGet(uri.asString(), true);
488	start();
489}
490
491
492void LLUpdateDownloader::Implementation::throwOnCurlError(CURLcode code)
493{
494	if(code != CURLE_OK) {
495		const char * errorString = curl_easy_strerror(code);
496		if(errorString != 0) {
497			throw DownloadError(curl_easy_strerror(code));
498		} else {
499			throw DownloadError("unknown curl error");
500		}
501	} else {
502		; // No op.
503	}
504}
505
506
507bool LLUpdateDownloader::Implementation::validateDownload(void)
508{
509	std::string filePath = mDownloadData["path"].asString();
510	llifstream fileStream(filePath, std::ios_base::in | std::ios_base::binary);
511	if(!fileStream) return false;
512
513	std::string hash = mDownloadData["hash"].asString();
514	if(hash.size() != 0) {
515		LL_INFOS("UpdateDownload") << "checking hash..." << LL_ENDL;
516		char digest[33];
517		LLMD5(fileStream).hex_digest(digest);
518		if(hash != digest) {
519			LL_WARNS("UpdateDownload") << "download hash mismatch; expeted " << hash <<
520				" but download is " << digest << LL_ENDL;
521		}
522		return hash == digest;
523	} else {
524		return true; // No hash check provided.
525	}
526}