[downloader] Removed old implementation

This commit is contained in:
Alex Zolotarev 2011-11-11 16:08:43 +03:00 committed by Alex Zolotarev
parent 2951325da5
commit 0cb0a3a23b
14 changed files with 4 additions and 1520 deletions

View file

@ -1,29 +0,0 @@
#pragma once
#import <Foundation/Foundation.h>
#include "download_manager.hpp"
#include "url_generator.hpp"
#include "../std/string.hpp"
@interface AppleDownload : NSObject
{
HttpStartParams m_params;
string m_currentUrl;
UrlGenerator m_urlGenerator;
FILE * m_file;
/// stores information from the server, can be zero
int64_t m_projectedFileSize;
NSURLConnection * m_connection;
string m_receivedBuffer;
}
- (void) dealloc;
- (std::string const &) Url;
- (BOOL) StartDownload: (HttpStartParams const &)params;
// Added because release from manager doesn't destroy it immediately...
- (void) Cancel;
@end

View file

@ -1,301 +0,0 @@
#import "apple_download.h"
#include "../base/logging.hpp"
#include "../std/target_os.hpp"
#ifdef OMIM_OS_IPHONE
#import <UIKit/UIApplication.h>
#endif
#include "download_manager.hpp"
#include "platform.hpp"
#define TIMEOUT_IN_SECONDS 15.0
static bool NeedToGenerateUrl(string const & url)
{
return url.find("http://") != 0 && url.find("https://") != 0;
}
@implementation AppleDownload
- (string const &) Url
{
return m_params.m_url;
}
- (void) Cancel
{
if (m_connection)
[m_connection cancel];
}
- (void) dealloc
{
LOG(LDEBUG, ("destructor for url", m_params.m_url));
if (m_connection)
{
[m_connection cancel];
[m_connection release];
}
// Non-zero means that download is canceled
if (m_file)
{
fclose(m_file);
if (!m_params.m_fileToSave.empty())
remove((m_params.m_fileToSave + DOWNLOADING_FILE_EXTENSION).c_str());
}
[super dealloc];
}
- (NSMutableURLRequest *) CreateRequest
{
// Create the request.
NSMutableURLRequest * request =
[NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithUTF8String:m_currentUrl.c_str()]]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:TIMEOUT_IN_SECONDS];
if (m_file)
{
long long fileSize = ftello(m_file);
if (fileSize > 0)
{
LOG(LDEBUG, ("Resuming download for file", m_params.m_fileToSave + DOWNLOADING_FILE_EXTENSION,
"from position", fileSize));
NSString * val = [[NSString alloc] initWithFormat: @"bytes=%qi-", fileSize];
[request addValue:val forHTTPHeaderField:@"Range"];
[val release];
}
}
if (!m_params.m_contentType.empty())
{
[request addValue:[NSString stringWithUTF8String: m_params.m_contentType.c_str()] forHTTPHeaderField:@"Content-Type"];
}
if (!m_params.m_postData.empty())
{
NSData * postData = [NSData dataWithBytes:m_params.m_postData.data() length:m_params.m_postData.size()];
[request setHTTPBody:postData];
[request setHTTPMethod:@"POST"];
}
// set user-agent with unique client id only for mapswithme requests
if (m_currentUrl.find("mapswithme.com") != string::npos)
{
static string const uid = GetPlatform().UniqueClientId();
[request addValue:[NSString stringWithUTF8String: uid.c_str()] forHTTPHeaderField:@"User-Agent"];
}
return request;
}
- (BOOL) StartDownload: (HttpStartParams const &)params
{
m_params = params;
if (!params.m_fileToSave.empty())
{
// try to create file first
string const tmpFile = m_params.m_fileToSave + DOWNLOADING_FILE_EXTENSION;
m_file = fopen(tmpFile.c_str(), "ab");
if (m_file == 0)
{
LOG(LERROR, ("Error opening file for download", tmpFile, strerror(errno)));
// notify observer about error and exit
if (m_params.m_finish)
{
HttpFinishedParams result;
result.m_url = m_params.m_url;
result.m_file = m_params.m_fileToSave;
result.m_error = EHttpDownloadCantCreateFile;
m_params.m_finish(result);
}
return NO;
}
}
if (NeedToGenerateUrl(m_params.m_url))
m_currentUrl = m_urlGenerator.PopNextUrl() + m_params.m_url;
else
m_currentUrl = m_params.m_url;
// create the connection with the request and start loading the data
m_connection = [[NSURLConnection alloc] initWithRequest:[self CreateRequest] delegate:self];
if (m_connection == 0)
{
LOG(LERROR, ("Can't create connection for url", m_currentUrl));
// notify observer about error and exit
if (m_params.m_finish)
{
HttpFinishedParams result;
result.m_url = m_params.m_url;
result.m_file = m_params.m_fileToSave;
result.m_error = EHttpDownloadNoConnectionAvailable;
m_params.m_finish(result);
}
return NO;
}
return YES;
}
- (void) connection: (NSURLConnection *)connection didReceiveResponse: (NSURLResponse *)response
{
// This method is called when the server has determined that it
// has enough information to create the NSURLResponse.
// check if this is OK (not a 404 or the like)
if ([response respondsToSelector:@selector(statusCode)])
{
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode < 200 || statusCode > 299)
{
LOG(LWARNING, ("Received HTTP error, canceling download", statusCode));
if (m_file)
{
long long fileSize = ftello(m_file);
fclose(m_file);
m_file = 0;
// delete file only if it's size is zero to resume download later
if (fileSize == 0)
remove((m_params.m_fileToSave + DOWNLOADING_FILE_EXTENSION).c_str());
}
// notify user
if (m_params.m_finish)
{
HttpFinishedParams result;
result.m_url = m_params.m_url;
result.m_file = m_params.m_fileToSave;
result.m_error = statusCode == 404 ? EHttpDownloadFileNotFound : EHttpDownloadFailed;
m_params.m_finish(result);
}
// and selfdestruct...
GetDownloadManager().CancelDownload(m_params.m_url);
return;
}
}
#ifdef OMIM_OS_IPHONE
// enable network activity indicator in top system toolbar
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
#endif
m_projectedFileSize = [response expectedContentLength];
// if server doesn't support resume, make sure we're downloading file from scratch
if (!m_params.m_fileToSave.empty() && m_projectedFileSize < 0)
{
fclose(m_file);
m_file = fopen((m_params.m_fileToSave + DOWNLOADING_FILE_EXTENSION).c_str(), "wb");
}
LOG(LDEBUG, ("Projected download file size", m_projectedFileSize));
}
- (void) connection: (NSURLConnection *)connection didReceiveData: (NSData *)data
{
// Append the new data
int64_t size = -1;
if (m_file)
{
fwrite([data bytes], 1, [data length], m_file);
size = ftello(m_file);
}
else
{
m_receivedBuffer.append(static_cast<char const *>([data bytes]), [data length]);
size = m_receivedBuffer.size();
}
if (m_params.m_progress)
{
HttpProgressT progress;
progress.m_url = m_params.m_url;
progress.m_current = size;
progress.m_total = m_projectedFileSize;
m_params.m_progress(progress);
}
}
- (BOOL) shouldRetry: (NSError *)error
{
NSInteger const code = [error code];
return (code < 0 || code == 401 || code == 403 || code == 404) && NeedToGenerateUrl(m_params.m_url);
}
- (void) connection: (NSURLConnection *)connection didFailWithError: (NSError *)error
{
// inform the user
LOG(LWARNING, ("Connection failed for", m_currentUrl, [[error localizedDescription] cStringUsingEncoding:NSUTF8StringEncoding]));
// retry connection if it's network-specific error
if ([self shouldRetry:error])
{
string const newUrl = m_urlGenerator.PopNextUrl();
if (!newUrl.empty())
{
m_currentUrl = newUrl + m_params.m_url;
[m_connection release];
// create the connection with the request and start loading the data
m_connection = [[NSURLConnection alloc] initWithRequest:[self CreateRequest] delegate:self];
if (m_connection)
return; // successfully restarted connection
LOG(LWARNING, ("Can't retry connection"));
// notify observer about error and exit after this if-block
}
}
if (m_file)
{
long long fileSize = ftello(m_file);
fclose(m_file);
m_file = 0;
// delete file only if it's size is zero to resume download later
if (fileSize == 0)
remove((m_params.m_fileToSave + DOWNLOADING_FILE_EXTENSION).c_str());
}
if (m_params.m_finish)
{
HttpFinishedParams result;
result.m_url = m_params.m_url;
result.m_file = m_params.m_fileToSave;
result.m_error = EHttpDownloadFailed;
m_params.m_finish(result);
}
// and selfdestruct...
GetDownloadManager().CancelDownload(m_params.m_url);
}
- (void) connectionDidFinishLoading: (NSURLConnection *)connection
{
bool isLocked = false;
if (m_file)
{
// close file
fclose(m_file);
m_file = 0;
// remove temporary extension from downloaded file
remove(m_params.m_fileToSave.c_str());
if (rename((m_params.m_fileToSave + DOWNLOADING_FILE_EXTENSION).c_str(), m_params.m_fileToSave.c_str()))
{
isLocked = true;
LOG(LERROR, ("Can't rename to file", m_params.m_fileToSave));
// delete downloaded file
remove((m_params.m_fileToSave + DOWNLOADING_FILE_EXTENSION).c_str());
}
else
LOG(LDEBUG, ("Successfully downloaded", m_params.m_url));
}
if (m_params.m_finish)
{
HttpFinishedParams result;
result.m_url = m_params.m_url;
result.m_file = m_params.m_fileToSave;
result.m_data.swap(m_receivedBuffer);
result.m_error = isLocked ? EHttpDownloadFileIsLocked : EHttpDownloadOk;
m_params.m_finish(result);
}
// and selfdestruct...
GetDownloadManager().CancelDownload(m_params.m_url);
}
@end

View file

@ -1,110 +0,0 @@
#include "download_manager.hpp"
#include "../std/target_os.hpp"
#include "../base/logging.hpp"
#import "apple_download.h"
#ifdef OMIM_OS_IPHONE
#import "../iphone/Maps/Classes/MapsAppDelegate.h"
#import <UIKit/UIApplication.h>
#endif
class AppleDownloadManager : public DownloadManager
{
NSMutableArray * activeDownloads;
public:
AppleDownloadManager()
{
activeDownloads = [[NSMutableArray alloc] init];
}
virtual ~AppleDownloadManager()
{
for (NSUInteger i = 0; i < [activeDownloads count]; ++i)
[[activeDownloads objectAtIndex:i] release];
[activeDownloads removeAllObjects];
[activeDownloads release];
}
virtual void HttpRequest(HttpStartParams const & params)
{
// check if download is already active
for (NSUInteger i = 0; i < [activeDownloads count]; ++i)
{
AppleDownload * download = [activeDownloads objectAtIndex:i];
if ([download Url] == params.m_url)
{
LOG(LWARNING, ("Download is already active for url %s", params.m_url));
return;
}
}
AppleDownload * download = [[AppleDownload alloc] init];
if ([download StartDownload:params])
{
// save download in array to cancel it later if necessary
[activeDownloads addObject:download];
#ifdef OMIM_OS_IPHONE
// prevent device from going to standby
[[MapsAppDelegate theApp] disableStandby];
#endif
}
else
{
// free memory
[download release];
}
}
/// @note Doesn't notify clients on canceling!
virtual void CancelDownload(string const & url)
{
#ifdef OMIM_OS_IPHONE
// disable network activity indicator in top system toolbar
// note that this method is called also from successful/failed download to "selfdestruct" below
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
#endif
for (NSUInteger i = 0; i < [activeDownloads count]; ++i)
{
AppleDownload * download = [activeDownloads objectAtIndex:i];
if ([download Url] == url)
{
[activeDownloads removeObjectAtIndex:i];
[download Cancel];
[download release];
break;
}
}
#ifdef OMIM_OS_IPHONE
// enable standby if no more downloads are left
if ([activeDownloads count] == 0)
[[MapsAppDelegate theApp] enableStandby];
#endif
}
virtual void CancelAllDownloads()
{
#ifdef OMIM_OS_IPHONE
// disable network activity indicator in top system toolbar
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
#endif
for (NSUInteger i = 0; i < [activeDownloads count]; ++i) {
AppleDownload * download = [activeDownloads objectAtIndex:i];
[download Cancel];
[download release];
}
[activeDownloads removeAllObjects];
}
};
DownloadManager & GetDownloadManager()
{
static AppleDownloadManager downloadManager;
return downloadManager;
}

View file

@ -1,65 +0,0 @@
#pragma once
#include "../std/stdint.hpp"
#include "../std/utility.hpp"
#include "../std/string.hpp"
#include "../std/function.hpp"
/// Appended to all downloading files and removed after successful download
#define DOWNLOADING_FILE_EXTENSION ".downloading"
/// Notifies client about donwload progress
struct HttpProgressT
{
string m_url;
int64_t m_current;
int64_t m_total;
int64_t m_bytesPerSec;
};
typedef function<void (HttpProgressT const &)> HttpProgressCallbackT;
enum DownloadResultT
{
EHttpDownloadOk,
EHttpDownloadFileNotFound, //!< HTTP 404
EHttpDownloadFailed,
EHttpDownloadFileIsLocked, //!< downloaded file can't replace existing locked file
EHttpDownloadCantCreateFile, //!< file for downloading can't be created
EHttpDownloadNoConnectionAvailable
};
struct HttpFinishedParams
{
string m_url;
string m_file; //!< if not empty, contains file with retrieved data
string m_data; //!< if not empty, contains received data
DownloadResultT m_error;
};
typedef function<void (HttpFinishedParams const &)> HttpFinishedCallbackT;
struct HttpStartParams
{
string m_url;
string m_fileToSave;
HttpFinishedCallbackT m_finish;
HttpProgressCallbackT m_progress;
string m_contentType;
string m_postData; //!< if not empty, send POST instead of GET
};
/// Platform-dependent implementations should derive it
/// and implement pure virtual methods
class DownloadManager
{
public:
virtual ~DownloadManager() {}
/// By default, http resume feature is used for requests which contains out file
/// If url doesn't contain http:// or https:// Url_Generator is used for base server url
virtual void HttpRequest(HttpStartParams const & params) = 0;
/// @note Doesn't notifies clients on canceling!
virtual void CancelDownload(string const & url) = 0;
virtual void CancelAllDownloads() = 0;
};
extern "C" DownloadManager & GetDownloadManager();

View file

@ -20,6 +20,7 @@ include($$ROOT_DIR/common.pri)
HEADERS += wifi_info.hpp \
location_service.hpp
!macx* {
QT *= network
SOURCES += http_thread_qt.cpp
HEADERS += http_thread_qt.hpp
}
@ -44,33 +45,20 @@ include($$ROOT_DIR/common.pri)
}
macx*|iphone* {
HEADERS += apple_download.h \
http_thread_apple.h
OBJECTIVE_SOURCES += apple_download.mm \
apple_download_manager.mm \
http_thread_apple.mm
}
win32*|linux* {
QT *= network
HEADERS += qt_download_manager.hpp \
qt_download.hpp
SOURCES += qt_download_manager.cpp \
qt_download.cpp
HEADERS += http_thread_apple.h
OBJECTIVE_SOURCES += http_thread_apple.mm
}
# common sources for all platforms
HEADERS += \
platform.hpp \
download_manager.hpp \
location.hpp \
concurrent_runner.hpp \
preferred_languages.hpp \
settings.hpp \
video_timer.hpp \
languages.hpp \
url_generator.hpp \
http_request.hpp \
http_thread_callback.hpp \
chunks_download_strategy.hpp \
@ -80,6 +68,5 @@ SOURCES += \
settings.cpp \
video_timer.cpp \
languages.cpp \
url_generator.cpp \
http_request.cpp \
chunks_download_strategy.cpp \

View file

@ -1,374 +0,0 @@
#include "../../testing/testing.hpp"
#include "../platform.hpp"
#include "../download_manager.hpp"
#include "../../coding/file_writer.hpp"
#include "../../coding/file_reader.hpp"
#include "../../std/bind.hpp"
#include <QCoreApplication>
// file on the server contains "Test1"
#define TEST_FILE_URL1 "http://melnichek.ath.cx:34568/unit_tests/1.txt"
#define TEST_FILE_NAME1 "unit_test1.tmp"
// file on the server contains "Test2"
#define TEST_FILE_URL2 "http://melnichek.ath.cx:34568/unit_tests/2.txt"
#define TEST_FILE_NAME2 "unit_test2.tmp"
// file on the server contains "Test3"
#define TEST_FILE_URL3 "http://melnichek.ath.cx:34568/unit_tests/3.txt"
#define TEST_FILE_NAME3 "unit_test3.tmp"
#define TEST_INVALID_URL "http://very_invalid_url.kotorogo.net/okak.test"
#define TEST_ABSENT_FILE_URL "http://melnichek.ath.cx:34568/unit_tests/not_existing_file"
#define TEST_ABSENT_FILE_NAME "not_existing_file"
#define TEST_LOCKED_FILE_URL "http://melnichek.ath.cx:34568/unit_tests/1.txt"
#define TEST_LOCKED_FILE_NAME "locked_file.tmp"
#define TEST_BIG_FILE_URL "http://melnichek.ath.cx:34568/unit_tests/47kb.file"
Platform & gPlatform = GetPlatform();
DownloadManager & gMgr = GetDownloadManager();
template<int TMaxDownloadsNum>
struct DlObserver
{
size_t m_downloadsProcessed;
HttpFinishedParams m_result[TMaxDownloadsNum];
string m_progressUrl;
DlObserver() : m_downloadsProcessed(0)
{
for (size_t i = 0; i < TMaxDownloadsNum; ++i)
m_result[i].m_error = EHttpDownloadFailed;
}
void OnDownloadFinished(HttpFinishedParams const & result)
{
m_result[m_downloadsProcessed++] = result;
if (m_downloadsProcessed >= TMaxDownloadsNum)
QCoreApplication::quit(); // return control to test function body
}
void OnDownloadProgress(HttpProgressT const & progress)
{
m_progressUrl = progress.m_url;
if (progress.m_total < 0)
{
cerr << "Your hosting doesn't support total file size or invalid resume range was given for "
<< m_progressUrl << endl;
}
// for big file - cancel downloading after 40Kb were transferred
if (progress.m_current > 40 * 1024)
gMgr.CancelDownload(progress.m_url);
}
};
int gArgc = 1;
char * gArgv[] = { 0 };
UNIT_TEST(SingleDownload)
{
size_t const NUM = 1;
DlObserver<NUM> observer;
FileWriter::DeleteFileX(TEST_FILE_NAME1);
HttpStartParams params;
params.m_url = TEST_FILE_URL1;
params.m_fileToSave = TEST_FILE_NAME1;
params.m_finish = bind(&DlObserver<NUM>::OnDownloadFinished, &observer, _1);
params.m_progress = bind(&DlObserver<NUM>::OnDownloadProgress, &observer, _1);
gMgr.HttpRequest(params);
QCoreApplication::exec();
TEST_EQUAL( observer.m_result[0].m_error, EHttpDownloadOk, ("Do you have internet connection?") );
TEST( gPlatform.IsFileExists(TEST_FILE_NAME1), () );
FileWriter::DeleteFileX(TEST_FILE_NAME1);
}
UNIT_TEST(MultiDownload)
{
size_t const NUM = 3;
DlObserver<NUM> observer;
FileWriter::DeleteFileX(TEST_FILE_NAME1);
FileWriter::DeleteFileX(TEST_FILE_NAME2);
FileWriter::DeleteFileX(TEST_FILE_NAME3);
HttpStartParams params;
params.m_url = TEST_FILE_URL1;
params.m_fileToSave = TEST_FILE_NAME1;
params.m_finish = bind(&DlObserver<NUM>::OnDownloadFinished, &observer, _1);
params.m_progress = bind(&DlObserver<NUM>::OnDownloadProgress, &observer, _1);
gMgr.HttpRequest(params);
params.m_url = TEST_FILE_URL2;
params.m_fileToSave = TEST_FILE_NAME2;
gMgr.HttpRequest(params);
params.m_url = TEST_FILE_URL3;
params.m_fileToSave = TEST_FILE_NAME3;
gMgr.HttpRequest(params);
QCoreApplication::exec();
TEST_EQUAL( observer.m_result[0].m_error, EHttpDownloadOk, ("Do you have internet connection?") );
TEST( gPlatform.IsFileExists(TEST_FILE_NAME1), () );
FileWriter::DeleteFileX(TEST_FILE_NAME1);
TEST_EQUAL( observer.m_result[1].m_error, EHttpDownloadOk, ("Do you have internet connection?") );
TEST( gPlatform.IsFileExists(TEST_FILE_NAME2), () );
FileWriter::DeleteFileX(TEST_FILE_NAME2);
TEST_EQUAL( observer.m_result[2].m_error, EHttpDownloadOk, ("Do you have internet connection?") );
TEST( gPlatform.IsFileExists(TEST_FILE_NAME3), () );
FileWriter::DeleteFileX(TEST_FILE_NAME3);
}
UNIT_TEST(InvalidUrl)
{
size_t const NUM = 1;
DlObserver<NUM> observer;
// this should be set to error after executing
observer.m_result[0].m_error = EHttpDownloadOk;
HttpStartParams params;
params.m_url = TEST_INVALID_URL;
params.m_fileToSave = TEST_FILE_NAME1;
params.m_finish = bind(&DlObserver<NUM>::OnDownloadFinished, &observer, _1);
params.m_progress = bind(&DlObserver<NUM>::OnDownloadProgress, &observer, _1);
gMgr.HttpRequest(params);
QCoreApplication::exec();
TEST_EQUAL( observer.m_result[0].m_error, EHttpDownloadFailed, () );
FileWriter::DeleteFileX(TEST_FILE_NAME1);
}
namespace
{
void ReadFileToString(char const * fName, string & str)
{
FileReader f(fName);
vector<char> buf;
buf.resize(f.Size());
f.Read(0, &buf[0], f.Size());
str.assign(buf.begin(), buf.end());
}
}
UNIT_TEST(DownloadFileExists)
{
size_t const NUM = 1;
DlObserver<NUM> observer;
string const fileContent("Some Contents");
{
FileWriter f(TEST_FILE_NAME1);
f.Write(fileContent.c_str(), fileContent.size());
}
{
string str;
ReadFileToString(TEST_FILE_NAME1, str);
TEST_EQUAL(fileContent, str, ("Writer or Reader don't work?.."));
}
HttpStartParams params;
params.m_url = TEST_FILE_URL1;
params.m_fileToSave = TEST_FILE_NAME1;
params.m_finish = bind(&DlObserver<NUM>::OnDownloadFinished, &observer, _1);
params.m_progress = bind(&DlObserver<NUM>::OnDownloadProgress, &observer, _1);
gMgr.HttpRequest(params);
QCoreApplication::exec();
TEST_EQUAL( observer.m_result[0].m_error, EHttpDownloadOk, () );
{
string str;
ReadFileToString(TEST_FILE_NAME1, str);
TEST_NOT_EQUAL(fileContent, str, ("File should be overwritten by download manager"));
}
FileWriter::DeleteFileX(TEST_FILE_NAME1);
}
UNIT_TEST(DownloadResume)
{
size_t const NUM = 1;
string const fileContent("aLeX");
{
FileWriter f(TEST_FILE_NAME1 DOWNLOADING_FILE_EXTENSION);
f.Write(fileContent.c_str(), fileContent.size());
}
DlObserver<NUM> observer1;
HttpStartParams params;
params.m_url = TEST_FILE_URL1;
params.m_fileToSave = TEST_FILE_NAME1;
params.m_finish = bind(&DlObserver<NUM>::OnDownloadFinished, &observer1, _1);
params.m_progress = bind(&DlObserver<NUM>::OnDownloadProgress, &observer1, _1);
gMgr.HttpRequest(params);
QCoreApplication::exec();
TEST_EQUAL( observer1.m_result[0].m_error, EHttpDownloadOk, () );
DlObserver<NUM> observer2;
params.m_url = TEST_FILE_URL1;
params.m_fileToSave = TEST_FILE_NAME2;
params.m_finish = bind(&DlObserver<NUM>::OnDownloadFinished, &observer2, _1);
params.m_progress = bind(&DlObserver<NUM>::OnDownloadProgress, &observer2, _1);
gMgr.HttpRequest(params);
QCoreApplication::exec();
TEST_EQUAL( observer2.m_result[0].m_error, EHttpDownloadOk, () );
uint64_t size1 = 4, size2 = 5;
TEST( GetPlatform().GetFileSize(TEST_FILE_NAME1, size1), ());
TEST( GetPlatform().GetFileSize(TEST_FILE_NAME2, size2), ());
TEST_EQUAL(size1, size2, ("Sizes should be equal - no resume was enabled"));
// resume should append content to 1st file
{
FileWriter f(TEST_FILE_NAME1 DOWNLOADING_FILE_EXTENSION);
f.Write(fileContent.c_str(), fileContent.size());
}
DlObserver<NUM> observer3;
params.m_url = TEST_FILE_URL1;
params.m_fileToSave = TEST_FILE_NAME1;
params.m_finish = bind(&DlObserver<NUM>::OnDownloadFinished, &observer3, _1);
params.m_progress = bind(&DlObserver<NUM>::OnDownloadProgress, &observer3, _1);
gMgr.HttpRequest(params);
QCoreApplication::exec();
TEST_EQUAL( observer3.m_result[0].m_error, EHttpDownloadOk, () );
TEST( GetPlatform().GetFileSize(TEST_FILE_NAME1, size1), ());
TEST_EQUAL( size1, size2, () );
{
string str;
ReadFileToString(TEST_FILE_NAME1, str);
TEST_EQUAL(fileContent, str.substr(0, fileContent.size()), ());
TEST_NOT_EQUAL(fileContent, str, ());
}
FileWriter::DeleteFileX(TEST_FILE_NAME1);
FileWriter::DeleteFileX(TEST_FILE_NAME2);
}
UNIT_TEST(DownloadAbsentFile)
{
size_t const NUM = 1;
DlObserver<NUM> observer;
HttpStartParams params;
params.m_url = TEST_ABSENT_FILE_URL;
params.m_fileToSave = TEST_ABSENT_FILE_NAME;
params.m_finish = bind(&DlObserver<NUM>::OnDownloadFinished, &observer, _1);
params.m_progress = bind(&DlObserver<NUM>::OnDownloadProgress, &observer, _1);
gMgr.HttpRequest(params);
QCoreApplication::exec();
TEST_EQUAL( observer.m_result[0].m_error, EHttpDownloadFileNotFound, () );
TEST( !GetPlatform().IsFileExists(TEST_ABSENT_FILE_NAME), () );
FileWriter::DeleteFileX(TEST_ABSENT_FILE_NAME);
}
UNIT_TEST(DownloadUsingUrlGenerator)
{
size_t const NUM = 1;
DlObserver<NUM> observer;
string const LOCAL_FILE = "unit_test.txt";
HttpStartParams params;
params.m_url = LOCAL_FILE;
params.m_fileToSave = LOCAL_FILE;
params.m_finish = bind(&DlObserver<NUM>::OnDownloadFinished, &observer, _1);
params.m_progress = bind(&DlObserver<NUM>::OnDownloadProgress, &observer, _1);
gMgr.HttpRequest(params);
QCoreApplication::exec();
TEST_NOT_EQUAL( observer.m_result[0].m_error, EHttpDownloadFileNotFound, () );
TEST( GetPlatform().IsFileExists(LOCAL_FILE), () );
FileWriter::DeleteFileX(LOCAL_FILE);
}
// only on Windows files are actually locked by system
#ifdef OMIM_OS_WINDOWS
UNIT_TEST(DownloadLockedFile)
{
{
size_t const NUM = 1;
DlObserver<NUM> observer;
FileWriter lockedFile(TEST_LOCKED_FILE_NAME);
// check that file is actually exists
{
bool exists = true;
try { FileReader f(TEST_LOCKED_FILE_NAME); }
catch (Reader::OpenException const &) { exists = false; }
TEST(exists, ("Locked file wasn't created"));
}
HttpStartParams params;
params.m_url = TEST_LOCKED_FILE_URL;
params.m_fileToSave = TEST_LOCKED_FILE_NAME;
params.m_finish = bind(&DlObserver<NUM>::OnDownloadFinished, &observer, _1);
params.m_progress = bind(&DlObserver<NUM>::OnDownloadProgress, &observer, _1);
gMgr.HttpRequest(params);
QCoreApplication::exec();
TEST_EQUAL( observer.m_result[0].m_error, EHttpDownloadFileIsLocked, () );
}
FileWriter::DeleteFileX(GetPlatform().WritablePathForFile(TEST_LOCKED_FILE_NAME));
}
#endif
struct HttpPostCallbackHolder
{
HttpFinishedParams * m_pResult;
HttpPostCallbackHolder() : m_pResult(NULL) {}
~HttpPostCallbackHolder() { delete m_pResult; }
void OnHttpPost(HttpFinishedParams const & result)
{
m_pResult = new HttpFinishedParams(result);
QCoreApplication::quit();
}
};
UNIT_TEST(HttpPost)
{
HttpPostCallbackHolder cbHolder;
HttpStartParams params;
params.m_finish = bind(&HttpPostCallbackHolder::OnHttpPost, &cbHolder, _1);
params.m_contentType = "application/json";
params.m_postData = "{\"version\":\"1.1.0\",\"request_address\":true}";
params.m_url = "http://melnichek.ath.cx:34568/unit_tests/post.php";
gMgr.HttpRequest(params);
QCoreApplication::exec();
TEST( cbHolder.m_pResult, () );
TEST_EQUAL(cbHolder.m_pResult->m_error, EHttpDownloadOk,
("HTTP POST failed with error", cbHolder.m_pResult->m_error));
TEST_EQUAL(cbHolder.m_pResult->m_data, "HTTP POST is OK",
("Server sent invalid POST reply"));
}

View file

@ -26,13 +26,11 @@ win32*|linux* {
QT *= network
}
# download_test.cpp \
SOURCES += \
../../testing/testingmain.cpp \
platform_test.cpp \
jansson_test.cpp \
concurrent_runner_test.cpp \
language_test.cpp \
url_generator_test.cpp \
downloader_test.cpp \

View file

@ -1,44 +0,0 @@
#include "../../testing/testing.hpp"
#include "../url_generator.hpp"
#include "../../std/algorithm.hpp"
void VectorContains(vector<string> & v, string const & s)
{
vector<string>::iterator found = find(v.begin(), v.end(), s);
TEST(found != v.end(), (s, "was not found in", v));
v.erase(found);
}
UNIT_TEST(UrlGenerator)
{
vector<string> first;
first.push_back("A");
first.push_back("B");
first.push_back("C");
first.push_back("D");
first.push_back("E");
vector<string> second;
second.push_back("F");
second.push_back("G");
second.push_back("H");
UrlGenerator g(first, second);
{
size_t const count = first.size();
for (size_t i = 0; i < count; ++i)
VectorContains(first, g.PopNextUrl());
}
{
size_t const count = second.size();
for (size_t i = 0; i < count; ++i)
VectorContains(second, g.PopNextUrl());
}
{
for (size_t i = 0; i < 10; ++i)
TEST_EQUAL(g.PopNextUrl(), string(), ());
}
}

View file

@ -1,324 +0,0 @@
#include "qt_download.hpp"
#include "qt_download_manager.hpp"
#include "../base/logging.hpp"
#include "../base/assert.hpp"
#include "../coding/sha2.hpp"
#include "../coding/base64.hpp"
#include "../std/target_os.hpp"
#include <QNetworkInterface>
#include <QFSFileEngine>
#include <QDateTime>
// How many times we try to automatically reconnect in the case of network errors
#define MAX_AUTOMATIC_RETRIES 2
#ifdef OMIM_OS_WINDOWS
#define LOGIN_VAR "USERNAME"
#else
#define LOGIN_VAR "USER"
#endif
/// @return login name for active user and local machine hostname
static QString UserLoginAndHostname()
{
char const * login = getenv(LOGIN_VAR);
QString result(login ? login : "");
result += QHostInfo::localHostName();
return result;
}
/// @return mac address of active interface or empty string if not found
static QString MacAddress()
{
QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < interfaces.size(); ++i)
{
QNetworkInterface const & iface = interfaces.at(i);
QString hwAddr = iface.hardwareAddress();
if (!iface.addressEntries().empty()
&& (iface.flags() & (QNetworkInterface::IsUp | QNetworkInterface::IsRunning
| QNetworkInterface::CanBroadcast | QNetworkInterface::CanMulticast)) == iface.flags())
{
return hwAddr;
}
}
// no valid interface was found
return QString();
}
/// @return creation time of the root file system or empty string
static QString FsCreationTime()
{
QFileInfoList drives = QFSFileEngine::drives();
for (int i = 0; i < drives.size(); ++i)
{
QFileInfo const & info = drives.at(i);
QString const path = info.absolutePath();
if (path == "/" || path.startsWith("C:"))
return QString("%1").arg(info.created().toTime_t());
}
return QString();
}
static QString UniqueClientId()
{
QString result = MacAddress();
if (result.size() == 0)
{
result = FsCreationTime();
if (result.size() == 0)
result = QString("------------");
}
// add salt - login user name and local hostname
result += UserLoginAndHostname();
// calculate one-way hash
QByteArray const original = QByteArray::fromHex(result.toLocal8Bit());
string const hash = sha2::digest256(original.constData(), original.size(), false);
// xor hash
size_t const offset = hash.size() / 4;
string xoredHash;
for (size_t i = 0; i < offset; ++i)
xoredHash.push_back(hash[i] ^ hash[i + offset] ^ hash[i + offset * 2] ^ hash[i + offset * 3]);
return base64::encode(xoredHash).c_str();
}
static QString UserAgent()
{
static QString userAgent = UniqueClientId();
return userAgent;
}
static bool NeedToUseUrlGenerator(string const & url)
{
return url.find("http://") != 0 && url.find("https://") != 0;
}
QtDownload::QtDownload(QtDownloadManager & manager, HttpStartParams const & params)
: QObject(&manager), m_currentUrl(params.m_url.c_str()), m_reply(0),
m_params(params)
{
// if we need to save server response to the file...
if (!m_params.m_fileToSave.empty())
{
// use temporary file for download
m_file.setFileName((m_params.m_fileToSave + DOWNLOADING_FILE_EXTENSION).c_str());
if (!m_file.open(QIODevice::Append))
{
QString const err = m_file.errorString();
LOG(LERROR, ("Can't open file while downloading", qPrintable(m_file.fileName()), qPrintable(err)));
if (m_params.m_finish)
{
HttpFinishedParams result;
result.m_error = EHttpDownloadCantCreateFile;
result.m_file = m_params.m_fileToSave;
result.m_url = m_params.m_url;
m_params.m_finish(result);
}
// mark itself to delete
deleteLater();
return;
}
}
// url acts as a key to find this download by QtDownloadManager::findChild(url)
setObjectName(m_params.m_url.c_str());
// if url doesn't contain valid http:// or https:// we use UrlGenerator
if (NeedToUseUrlGenerator(m_params.m_url))
m_currentUrl = (m_urlGenerator.PopNextUrl() + m_params.m_url).c_str();
StartRequest();
}
QtDownload::~QtDownload()
{
if (m_reply)
{
// don't notify client when aborted
disconnect(m_reply, SIGNAL(finished()), this, SLOT(OnHttpFinished()));
disconnect(m_reply, SIGNAL(readyRead()), this, SLOT(OnHttpReadyRead()));
disconnect(m_reply, SIGNAL(downloadProgress(qint64, qint64)),
this, SLOT(OnUpdateDataReadProgress(qint64, qint64)));
m_reply->abort();
delete m_reply;
if (m_file.isOpen())
{
m_file.close();
m_file.remove();
}
}
LOG(LDEBUG, (m_currentUrl.toString().toLocal8Bit().data()));
}
void QtDownload::StartRequest()
{
QNetworkRequest httpRequest(m_currentUrl);
// set user-agent with unique client id only for mapswithme requests
if (m_currentUrl.host().contains("mapswithme.com"))
httpRequest.setRawHeader("User-Agent", UserAgent().toAscii());
if (m_file.isOpen())
{
qint64 const fileSize = m_file.size();
if (fileSize > 0) // need resume
httpRequest.setRawHeader("Range", QString("bytes=%1-").arg(fileSize).toAscii());
}
if (!m_params.m_contentType.empty())
httpRequest.setHeader(QNetworkRequest::ContentTypeHeader, m_params.m_contentType.c_str());
if (m_params.m_postData.empty())
m_reply = static_cast<QtDownloadManager *>(parent())->NetAccessManager().get(httpRequest);
else
{
m_reply = static_cast<QtDownloadManager *>(parent())->NetAccessManager().post(
httpRequest, m_params.m_postData.c_str());
}
m_reply->ignoreSslErrors();
connect(m_reply, SIGNAL(finished()), this, SLOT(OnHttpFinished()));
connect(m_reply, SIGNAL(readyRead()), this, SLOT(OnHttpReadyRead()));
connect(m_reply, SIGNAL(downloadProgress(qint64, qint64)),
this, SLOT(OnUpdateDataReadProgress(qint64, qint64)));
}
static DownloadResultT TranslateError(QNetworkReply::NetworkError err)
{
switch (err)
{
case QNetworkReply::NoError: return EHttpDownloadOk;
case QNetworkReply::ContentNotFoundError: return EHttpDownloadFileNotFound;
case QNetworkReply::HostNotFoundError: return EHttpDownloadFailed;
default: return EHttpDownloadFailed;
}
}
void QtDownload::OnHttpFinished()
{
QVariant const redirectionTarget = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
QNetworkReply::NetworkError const netError = m_reply->error();
if (netError)
{
if (netError <= QNetworkReply::UnknownNetworkError && NeedToUseUrlGenerator(m_params.m_url))
{
string const nextUrl = m_urlGenerator.PopNextUrl();
if (!nextUrl.empty())
{
// try one more time
m_reply->deleteLater();
m_currentUrl = (nextUrl + m_params.m_url).c_str();
StartRequest();
return;
}
}
// do not delete file if we can resume it's downloading later
if (m_file.isOpen())
{
m_file.close();
if (m_file.size() == 0)
m_file.remove();
}
QString const err = m_reply->errorString();
LOG(LWARNING, ("Download failed", qPrintable(err)));
m_reply->deleteLater();
m_reply = 0;
if (m_params.m_finish)
{
HttpFinishedParams result;
result.m_file = m_params.m_fileToSave;
result.m_url = m_params.m_url;
result.m_error = TranslateError(netError);
m_params.m_finish(result);
}
// selfdestruct
deleteLater();
}
else if (!redirectionTarget.isNull())
{
m_currentUrl = m_currentUrl.resolved(redirectionTarget.toUrl());
LOG(LINFO, ("HTTP redirect", m_currentUrl.toEncoded().data()));
if (m_file.isOpen())
{
m_file.close();
m_file.open(QIODevice::WriteOnly);
m_file.resize(0);
}
m_reply->deleteLater();
m_reply = 0;
StartRequest();
return;
}
else
{ // download succeeded
bool fileIsLocked = false;
if (m_file.isOpen())
{
m_file.close();
// delete original file if it exists
QFile::remove(m_params.m_fileToSave.c_str());
if (!m_file.rename(m_params.m_fileToSave.c_str()))
{ // sh*t... file is locked and can't be removed
m_file.remove();
// report error to GUI
LOG(LWARNING, ("File exists and can't be replaced by downloaded one:", m_params.m_fileToSave));
fileIsLocked = true;
}
}
if (m_params.m_finish)
{
HttpFinishedParams result;
result.m_url = m_params.m_url;
result.m_file = m_params.m_fileToSave;
QByteArray data = m_reply->readAll();
if (!data.isEmpty())
result.m_data.assign(data.constData(), data.size());
result.m_error = fileIsLocked ? EHttpDownloadFileIsLocked : EHttpDownloadOk;
m_params.m_finish(result);
}
m_reply->deleteLater();
m_reply = 0;
// selfdestruct
deleteLater();
}
}
void QtDownload::OnHttpReadyRead()
{
// this slot gets called everytime the QNetworkReply has new data.
// We read all of its new data and write it into the file.
// That way we use less RAM than when reading it at the finished()
// signal of the QNetworkReply
if (m_file.isOpen() && m_reply)
m_file.write(m_reply->readAll());
// @note that for requests where m_file is not used, data accumulates
// and m_reply->readAll() should be called in finish() slot handler
}
void QtDownload::OnUpdateDataReadProgress(qint64 bytesRead, qint64 totalBytes)
{
m_urlGenerator.UpdateSpeed(bytesRead);
if (m_params.m_progress)
{
HttpProgressT p;
p.m_current = bytesRead;
p.m_total = totalBytes;
p.m_url = m_params.m_url;
p.m_bytesPerSec = m_urlGenerator.CurrentSpeed();
m_params.m_progress(p);
}
// if download speed is slow, use next server
string const fasterUrl = m_urlGenerator.GetFasterUrl();
if (!fasterUrl.empty())
{
m_reply->abort();
m_reply->deleteLater();
m_currentUrl.setUrl(fasterUrl.c_str());
StartRequest();
}
}

View file

@ -1,35 +0,0 @@
#pragma once
#include "download_manager.hpp"
#include "url_generator.hpp"
#include <QtNetwork/QtNetwork>
class QtDownloadManager;
class QtDownload : public QObject
{
Q_OBJECT
// can be changed during server redirections
QUrl m_currentUrl;
QNetworkReply * m_reply;
QFile m_file;
HttpStartParams m_params;
UrlGenerator m_urlGenerator;
void StartRequest();
private slots:
void OnHttpFinished();
void OnHttpReadyRead();
void OnUpdateDataReadProgress(qint64 bytesRead, qint64 totalBytes);
public:
/// Created instance is automanaged as a manager's child
/// and can be manipulated later by manager->findChild(url);
QtDownload(QtDownloadManager & manager, HttpStartParams const & params);
virtual ~QtDownload();
};

View file

@ -1,47 +0,0 @@
#include "qt_download_manager.hpp"
#include "qt_download.hpp"
#include "../base/assert.hpp"
#include "../base/logging.hpp"
void QtDownloadManager::CreateOnMainThreadSlot(HttpStartParams const & params)
{
// manager is responsible for auto deleting
new QtDownload(*this, params);
}
void QtDownloadManager::HttpRequest(HttpStartParams const & params)
{
ASSERT(!params.m_url.empty(), ());
QList<QtDownload *> downloads = findChildren<QtDownload *>(params.m_url.c_str());
if (downloads.empty())
{
// small trick so all connections will be created on the main thread
qRegisterMetaType<HttpStartParams>("HttpStartParams");
QMetaObject::invokeMethod(this, "CreateOnMainThreadSlot", Q_ARG(HttpStartParams const & , params));
}
else
{
LOG(LWARNING, ("Download with the same url is already in progress, ignored", params.m_url));
}
}
void QtDownloadManager::CancelDownload(string const & url)
{
QList<QtDownload *> downloads = findChildren<QtDownload *>(url.c_str());
while (!downloads.isEmpty())
delete downloads.takeFirst();
}
void QtDownloadManager::CancelAllDownloads()
{
QList<QtDownload *> downloads = findChildren<QtDownload *>();
while (!downloads.isEmpty())
delete downloads.takeFirst();
}
extern "C" DownloadManager & GetDownloadManager()
{
static QtDownloadManager dlMgr;
return dlMgr;
}

View file

@ -1,21 +0,0 @@
#include "download_manager.hpp"
#include <QtNetwork/QtNetwork>
class QtDownloadManager : public QObject, public DownloadManager
{
Q_OBJECT
QNetworkAccessManager m_netAccessManager;
/// Should be called using inter-thread signal
private slots:
void CreateOnMainThreadSlot(HttpStartParams const & params);
public:
virtual void HttpRequest(HttpStartParams const & params);
virtual void CancelDownload(string const & url);
virtual void CancelAllDownloads();
QNetworkAccessManager & NetAccessManager() { return m_netAccessManager; }
};

View file

@ -1,116 +0,0 @@
#include "url_generator.hpp"
#include "../base/macros.hpp"
#include "../std/ctime.hpp"
static char const * g_defaultFirstGroup[] = {
#ifdef OMIM_PRODUCTION
"http://a0.mapswithme.com/",
"http://a1.mapswithme.com/",
"http://a2.mapswithme.com/",
"http://a3.mapswithme.com/",
"http://a4.mapswithme.com/",
"http://a5.mapswithme.com/",
"http://a6.mapswithme.com/",
"http://a7.mapswithme.com/",
"http://a8.mapswithme.com/",
"http://a9.mapswithme.com/",
"http://a10.mapswithme.com/",
"http://a11.mapswithme.com/"
#else
"http://svobodu404popugajam.mapswithme.com:34568/maps/"
#endif
};
static char const * g_defaultSecondGroup[] = {
#ifdef OMIM_PRODUCTION
"http://b0.mapswithme.com/",
"http://b1.mapswithme.com/",
"http://b2.mapswithme.com/",
"http://b3.mapswithme.com/",
"http://b4.mapswithme.com/",
"http://b5.mapswithme.com/"
#else
"http://svobodu404popugajam.mapswithme.com:34568/maps/"
#endif
};
UrlGenerator::UrlGenerator()
: m_randomGenerator(static_cast<uint32_t>(time(NULL))),
m_firstGroup(&g_defaultFirstGroup[0], &g_defaultFirstGroup[0] + ARRAY_SIZE(g_defaultFirstGroup)),
m_secondGroup(&g_defaultSecondGroup[0], &g_defaultSecondGroup[0] + ARRAY_SIZE(g_defaultSecondGroup)),
m_lastSecond(0.)
{
}
UrlGenerator::UrlGenerator(vector<string> const & firstGroup, vector<string> const & secondGroup)
: m_randomGenerator(static_cast<uint32_t>(time(NULL))), m_firstGroup(firstGroup), m_secondGroup(secondGroup),
m_lastSecond(0.)
{
}
string UrlGenerator::PopNextUrl()
{
string s;
switch (m_firstGroup.size())
{
case 1:
s = m_firstGroup.front();
m_firstGroup.pop_back();
break;
case 0:
switch (m_secondGroup.size())
{
case 1:
s = m_secondGroup.front();
m_secondGroup.pop_back();
break;
case 0: // nothing left to return
break;
default:
{
vector<string>::iterator const it = m_secondGroup.begin()
+ static_cast<vector<string>::difference_type>(m_randomGenerator.Generate() % m_secondGroup.size());
s = *it;
m_secondGroup.erase(it);
}
}
break;
default:
{
vector<string>::iterator const it = m_firstGroup.begin()
+ static_cast<vector<string>::difference_type>(m_randomGenerator.Generate() % m_firstGroup.size());
s = *it;
m_firstGroup.erase(it);
}
}
return s;
}
void UrlGenerator::UpdateSpeed(int64_t totalBytesRead)
{
double const seconds = m_timer.ElapsedSeconds();
if (static_cast<int>(seconds) - static_cast<int>(m_lastSecond) > 0)
{
m_lastSecond = seconds;
m_speeds.push_back(make_pair(seconds, totalBytesRead));
if (m_speeds.size() > 6)
m_speeds.pop_front();
}
}
int64_t UrlGenerator::CurrentSpeed() const
{
switch (m_speeds.size())
{
case 0:
case 1: return -1;
default: return static_cast<int64_t>((m_speeds.back().second - m_speeds.front().second)
/ (m_speeds.back().first - m_speeds.front().first));
}
}
string UrlGenerator::GetFasterUrl()
{
return string();
}

View file

@ -1,35 +0,0 @@
#pragma once
#include "../base/pseudo_random.hpp"
#include "../base/timer.hpp"
#include "../std/vector.hpp"
#include "../std/list.hpp"
#include "../std/string.hpp"
#include "../std/utility.hpp"
class UrlGenerator
{
LCG32 m_randomGenerator;
vector<string> m_firstGroup;
vector<string> m_secondGroup;
/// For moving average speed calculations
my::Timer m_timer;
/// Stores time in seconds from start and downloaded amount at that moment
typedef pair<double, int64_t> MarkT;
list<MarkT> m_speeds;
double m_lastSecond;
public:
UrlGenerator();
explicit UrlGenerator(vector<string> const & firstGroup, vector<string> const & secondGroup);
/// @return Always return empty string if all urls were already popped
string PopNextUrl();
/// @return -1 means speed is unknown
void UpdateSpeed(int64_t totalBytesRead);
int64_t CurrentSpeed() const;
string GetFasterUrl();
};