forked from organicmaps/organicmaps-tmp
[downloader] Removed old implementation
This commit is contained in:
parent
2951325da5
commit
0cb0a3a23b
14 changed files with 4 additions and 1520 deletions
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
|
@ -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 \
|
||||
|
|
|
@ -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"));
|
||||
}
|
|
@ -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 \
|
||||
|
||||
|
|
|
@ -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(), ());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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; }
|
||||
};
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
};
|
Loading…
Add table
Reference in a new issue