mirror of
https://github.com/tumic0/GPXSee.git
synced 2025-06-26 19:19:16 +02:00
Prefer the map DEM for track/waypoints elevation if present
This commit is contained in:
@ -1,188 +0,0 @@
|
||||
/*
|
||||
WARNING: This code uses internal Qt API - the QZipReader class for reading
|
||||
ZIP files - and things may break if Qt changes the API. For Qt5 this is not
|
||||
a problem as we can "see the future" now and there are no changes in all
|
||||
the supported Qt5 versions up to the last one (5.15). In Qt6 the class
|
||||
might change or even disappear in the future, but this is very unlikely
|
||||
as there were no changes for several years and The Qt Company's policy
|
||||
is: "do not invest any resources into any desktop related stuff unless
|
||||
absolutely necessary". There is an issue (QTBUG-3897) since the year 2009 to
|
||||
include the ZIP reader into the public API, which aptly illustrates the
|
||||
effort The Qt Company is willing to make about anything desktop related...
|
||||
*/
|
||||
|
||||
#include <QtEndian>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QRegularExpression>
|
||||
#include <QLocale>
|
||||
#include <private/qzipreader_p.h>
|
||||
#include "rectc.h"
|
||||
#include "dem.h"
|
||||
|
||||
|
||||
static unsigned int isqrt(unsigned int x)
|
||||
{
|
||||
unsigned int r = 0;
|
||||
|
||||
while ((r + 1) * (r + 1) <= x)
|
||||
r++;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static double interpolate(double dx, double dy, double p0, double p1, double p2,
|
||||
double p3)
|
||||
{
|
||||
return p0 * (1.0 - dx) * (1.0 - dy) + p1 * dx * (1.0 - dy)
|
||||
+ p2 * dy * (1.0 - dx) + p3 * dx * dy;
|
||||
}
|
||||
|
||||
static double value(int col, int row, int samples, const QByteArray &data)
|
||||
{
|
||||
int pos = ((samples - 1 - row) * samples + col) * 2;
|
||||
qint16 val = qFromBigEndian(*((const qint16*)(data.constData() + pos)));
|
||||
|
||||
return (val == -32768) ? NAN : val;
|
||||
}
|
||||
|
||||
QMutex DEM::_lock;
|
||||
|
||||
DEM::Entry::Entry(const QByteArray &data) : _data(data)
|
||||
{
|
||||
_samples = isqrt(_data.size() / 2);
|
||||
}
|
||||
|
||||
QString DEM::Tile::latStr() const
|
||||
{
|
||||
const char ns = (_lat >= 0) ? 'N' : 'S';
|
||||
return QString("%1%2").arg(ns).arg(qAbs(_lat), 2, 10, QChar('0'));
|
||||
}
|
||||
|
||||
QString DEM::Tile::lonStr() const
|
||||
{
|
||||
const char ew = (_lon >= 0) ? 'E' : 'W';
|
||||
return QString("%1%2").arg(ew).arg(qAbs(_lon), 3, 10, QChar('0'));
|
||||
}
|
||||
|
||||
QString DEM::Tile::fileName() const
|
||||
{
|
||||
return QString("%1%2.hgt").arg(latStr(), lonStr());
|
||||
}
|
||||
|
||||
QString DEM::_dir;
|
||||
DEM::TileCache DEM::_data;
|
||||
|
||||
void DEM::setCacheSize(int size)
|
||||
{
|
||||
_data.setMaxCost(size);
|
||||
}
|
||||
|
||||
void DEM::setDir(const QString &path)
|
||||
{
|
||||
_dir = path;
|
||||
}
|
||||
|
||||
void DEM::clearCache()
|
||||
{
|
||||
_data.clear();
|
||||
}
|
||||
|
||||
double DEM::height(const Coordinates &c, const Entry *e)
|
||||
{
|
||||
if (!e->samples())
|
||||
return NAN;
|
||||
|
||||
double lat = (c.lat() - floor(c.lat())) * (e->samples() - 1);
|
||||
double lon = (c.lon() - floor(c.lon())) * (e->samples() - 1);
|
||||
int row = (int)lat;
|
||||
int col = (int)lon;
|
||||
|
||||
double p0 = value(col, row, e->samples(), e->data());
|
||||
double p1 = value(col + 1, row, e->samples(), e->data());
|
||||
double p2 = value(col, row + 1, e->samples(), e->data());
|
||||
double p3 = value(col + 1, row + 1, e->samples(), e->data());
|
||||
|
||||
return interpolate(lon - col, lat - row, p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
DEM::Entry *DEM::loadTile(const Tile &tile)
|
||||
{
|
||||
QString fileName(tile.fileName());
|
||||
QString path(QDir(_dir).absoluteFilePath(fileName));
|
||||
QString zipPath(path + ".zip");
|
||||
|
||||
if (QFileInfo::exists(zipPath)) {
|
||||
QZipReader zip(zipPath, QIODevice::ReadOnly);
|
||||
return new Entry(zip.fileData(fileName));
|
||||
} else {
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qWarning("%s: %s", qPrintable(file.fileName()),
|
||||
qPrintable(file.errorString()));
|
||||
return new Entry();
|
||||
} else
|
||||
return new Entry(file.readAll());
|
||||
}
|
||||
}
|
||||
|
||||
double DEM::elevation(const Coordinates &c)
|
||||
{
|
||||
if (_dir.isEmpty())
|
||||
return NAN;
|
||||
|
||||
Tile tile(floor(c.lon()), floor(c.lat()));
|
||||
Entry *e = _data.object(tile);
|
||||
double ele;
|
||||
|
||||
if (!e) {
|
||||
e = loadTile(tile);
|
||||
ele = height(c, e);
|
||||
_data.insert(tile, e, e->data().size() / 1024);
|
||||
} else
|
||||
ele = height(c, e);
|
||||
|
||||
return ele;
|
||||
}
|
||||
|
||||
QList<Area> DEM::tiles()
|
||||
{
|
||||
static const QRegularExpression re(
|
||||
"^([NS])([0-9]{2})([EW])([0-9]{3})(\\.hgt|\\.hgt\\.zip)$");
|
||||
QDir dir(_dir);
|
||||
QFileInfoList files(dir.entryInfoList(QDir::Files | QDir::Readable));
|
||||
QLocale l(QLocale::system());
|
||||
QList<Area> list;
|
||||
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
QRegularExpressionMatch match(re.match(files.at(i).fileName()));
|
||||
if (!match.hasMatch())
|
||||
continue;
|
||||
|
||||
int lat = match.captured(2).toInt();
|
||||
int lon = match.captured(4).toInt();
|
||||
if (match.captured(1) == "S")
|
||||
lat = -lat;
|
||||
if (match.captured(3) == "W")
|
||||
lon = -lon;
|
||||
|
||||
Area area(RectC(Coordinates(lon, lat + 1), Coordinates(lon + 1, lat)));
|
||||
area.setName(files.at(i).baseName());
|
||||
area.setDescription(files.at(i).suffix().toUpper() + ", "
|
||||
+ l.formattedDataSize(files.at(i).size()));
|
||||
area.setStyle(PolygonStyle(QColor(0xFF, 0, 0, 0x40),
|
||||
QColor(0xFF, 0, 0, 0x80), 2));
|
||||
|
||||
list.append(area);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG
|
||||
QDebug operator<<(QDebug dbg, const DEM::Tile &tile)
|
||||
{
|
||||
dbg.nospace() << "Tile(" << tile.fileName() << ")";
|
||||
return dbg.space();
|
||||
}
|
||||
#endif // QT_NO_DEBUG
|
@ -1,79 +0,0 @@
|
||||
#ifndef DEM_H
|
||||
#define DEM_H
|
||||
|
||||
#include <QString>
|
||||
#include <QCache>
|
||||
#include <QByteArray>
|
||||
#include <QMutex>
|
||||
#include "common/hash.h"
|
||||
#include "data/area.h"
|
||||
|
||||
class Coordinates;
|
||||
|
||||
class DEM
|
||||
{
|
||||
public:
|
||||
class Tile {
|
||||
public:
|
||||
Tile(int lon, int lat) : _lon(lon), _lat(lat) {}
|
||||
|
||||
int lon() const {return _lon;}
|
||||
int lat() const {return _lat;}
|
||||
|
||||
QString lonStr() const;
|
||||
QString latStr() const;
|
||||
QString fileName() const;
|
||||
|
||||
bool operator==(const Tile &other) const
|
||||
{
|
||||
return (_lon == other._lon && _lat == other._lat);
|
||||
}
|
||||
|
||||
private:
|
||||
int _lon, _lat;
|
||||
};
|
||||
|
||||
static void setCacheSize(int size);
|
||||
static void setDir(const QString &path);
|
||||
static void clearCache();
|
||||
|
||||
static double elevation(const Coordinates &c);
|
||||
static void lock() {_lock.lock();}
|
||||
static void unlock() {_lock.unlock();}
|
||||
|
||||
static QList<Area> tiles();
|
||||
|
||||
private:
|
||||
class Entry {
|
||||
public:
|
||||
Entry() : _samples(0) {}
|
||||
Entry(const QByteArray &data);
|
||||
|
||||
const QByteArray &data() const {return _data;}
|
||||
int samples() const {return _samples;}
|
||||
|
||||
private:
|
||||
unsigned int _samples;
|
||||
QByteArray _data;
|
||||
};
|
||||
|
||||
typedef QCache<DEM::Tile, Entry> TileCache;
|
||||
|
||||
static double height(const Coordinates &c, const Entry *e);
|
||||
static Entry *loadTile(const Tile &tile);
|
||||
|
||||
static QString _dir;
|
||||
static TileCache _data;
|
||||
static QMutex _lock;
|
||||
};
|
||||
|
||||
inline HASH_T qHash(const DEM::Tile &tile)
|
||||
{
|
||||
return (qHash(tile.lon()) ^ qHash(tile.lat()));
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG
|
||||
QDebug operator<<(QDebug dbg, const DEM::Tile &tile);
|
||||
#endif // QT_NO_DEBUG
|
||||
|
||||
#endif // DEM_H
|
@ -1,122 +0,0 @@
|
||||
#include <QtMath>
|
||||
#include <QFileInfo>
|
||||
#include "common/rectc.h"
|
||||
#include "demloader.h"
|
||||
|
||||
|
||||
static QList<DEM::Tile> tiles(const RectC &rect)
|
||||
{
|
||||
QList<DEM::Tile> list;
|
||||
|
||||
if (rect.isNull())
|
||||
return list;
|
||||
|
||||
for (int i = qFloor(rect.top()); i >= qFloor(rect.bottom()); i--)
|
||||
for (int j = qFloor(rect.left()); j <= qFloor(rect.right()); j++)
|
||||
list.append(DEM::Tile(j, i));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
static bool isZip(const QUrl &url)
|
||||
{
|
||||
QFileInfo fi(url.fileName());
|
||||
return (fi.suffix().toLower() == "zip");
|
||||
}
|
||||
|
||||
|
||||
DEMLoader::DEMLoader(const QString &dir, QObject *parent)
|
||||
: QObject(parent), _dir(dir)
|
||||
{
|
||||
_downloader = new Downloader(this);
|
||||
connect(_downloader, &Downloader::finished, this, &DEMLoader::finished);
|
||||
}
|
||||
|
||||
int DEMLoader::numTiles(const RectC &rect) const
|
||||
{
|
||||
QList<DEM::Tile> tl(tiles(rect));
|
||||
int cnt = 0;
|
||||
|
||||
for (int i = 0; i < tl.size(); i++) {
|
||||
const DEM::Tile &t = tl.at(i);
|
||||
QString fn(tileFile(t));
|
||||
QString zn(fn + ".zip");
|
||||
|
||||
if (!(QFileInfo::exists(zn) || QFileInfo::exists(fn)))
|
||||
cnt++;
|
||||
}
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
bool DEMLoader::loadTiles(const RectC &rect)
|
||||
{
|
||||
QList<DEM::Tile> tl(tiles(rect));
|
||||
QList<Download> dl;
|
||||
|
||||
/* Create the user DEM dir only when a download is requested as it will
|
||||
override the global DEM dir. */
|
||||
if (!_dir.mkpath(_dir.absolutePath())) {
|
||||
qWarning("%s: %s", qPrintable(_dir.canonicalPath()),
|
||||
"Error creating DEM directory");
|
||||
return false;
|
||||
}
|
||||
DEM::setDir(_dir.path());
|
||||
|
||||
for (int i = 0; i < tl.size(); i++) {
|
||||
const DEM::Tile &t = tl.at(i);
|
||||
QString fn(tileFile(t));
|
||||
QString zn(fn + ".zip");
|
||||
|
||||
if (!(QFileInfo::exists(zn) || QFileInfo::exists(fn))) {
|
||||
QUrl url(tileUrl(t));
|
||||
dl.append(Download(url, isZip(url) ? zn : fn));
|
||||
}
|
||||
|
||||
if (dl.size() > DEM_DOWNLOAD_LIMIT) {
|
||||
qWarning("DEM download limit (%d) exceeded.", DEM_DOWNLOAD_LIMIT);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return _downloader->get(dl, _headers);
|
||||
}
|
||||
|
||||
bool DEMLoader::checkTiles(const RectC &rect) const
|
||||
{
|
||||
QList<DEM::Tile> tl(tiles(rect));
|
||||
|
||||
for (int i = 0; i < tl.size(); i++) {
|
||||
const DEM::Tile &t = tl.at(i);
|
||||
QString fn(tileFile(t));
|
||||
QString zn(fn + ".zip");
|
||||
|
||||
if (!(QFileInfo::exists(zn) || QFileInfo::exists(fn)))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QUrl DEMLoader::tileUrl(const DEM::Tile &tile) const
|
||||
{
|
||||
QString url(_url);
|
||||
|
||||
url.replace("$lon", tile.lonStr());
|
||||
url.replace("$lat", tile.latStr());
|
||||
|
||||
return QUrl(url);
|
||||
}
|
||||
|
||||
QString DEMLoader::tileFile(const DEM::Tile &tile) const
|
||||
{
|
||||
return _dir.absoluteFilePath(tile.fileName());
|
||||
}
|
||||
|
||||
void DEMLoader::setAuthorization(const Authorization &authorization)
|
||||
{
|
||||
QList<HTTPHeader> headers;
|
||||
if (!authorization.isNull())
|
||||
headers.append(authorization.header());
|
||||
_headers = headers;
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
#ifndef DEMLOADER_H
|
||||
#define DEMLOADER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDir>
|
||||
#include "downloader.h"
|
||||
#include "dem.h"
|
||||
|
||||
class RectC;
|
||||
|
||||
#define DEM_DOWNLOAD_WARNING 4
|
||||
#define DEM_DOWNLOAD_LIMIT 1024
|
||||
|
||||
class DEMLoader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DEMLoader(const QString &dir, QObject *parent = 0);
|
||||
|
||||
void setUrl(const QString &url) {_url = url;}
|
||||
void setAuthorization(const Authorization &authorization);
|
||||
|
||||
int numTiles(const RectC &rect) const;
|
||||
bool loadTiles(const RectC &rect);
|
||||
bool checkTiles(const RectC &rect) const;
|
||||
|
||||
const QString &url() const {return _url;}
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
|
||||
private:
|
||||
QUrl tileUrl(const DEM::Tile &tile) const;
|
||||
QString tileFile(const DEM::Tile &tile) const;
|
||||
|
||||
Downloader *_downloader;
|
||||
QString _url;
|
||||
QDir _dir;
|
||||
QList<HTTPHeader> _headers;
|
||||
};
|
||||
|
||||
#endif // DEMLOADER_H
|
@ -1,302 +0,0 @@
|
||||
#include <QFile>
|
||||
#include <QNetworkRequest>
|
||||
#include <QDir>
|
||||
#include <QTimerEvent>
|
||||
#include "common/config.h"
|
||||
#include "downloader.h"
|
||||
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
#define PLATFORM_STR "Android"
|
||||
#elif defined(Q_OS_LINUX)
|
||||
#define PLATFORM_STR "Linux"
|
||||
#elif defined(Q_OS_WIN32)
|
||||
#define PLATFORM_STR "Windows"
|
||||
#elif defined(Q_OS_MAC)
|
||||
#define PLATFORM_STR "OS X"
|
||||
#elif defined(Q_OS_BSD4)
|
||||
#define PLATFORM_STR "BSD"
|
||||
#elif defined(Q_OS_HAIKU)
|
||||
#define PLATFORM_STR "Haiku"
|
||||
#else
|
||||
#define PLATFORM_STR "Unknown"
|
||||
#endif
|
||||
|
||||
#define USER_AGENT \
|
||||
APP_NAME "/" APP_VERSION " (" PLATFORM_STR "; Qt " QT_VERSION_STR ")"
|
||||
|
||||
#define MAX_REDIRECT_LEVEL 5
|
||||
#define RETRIES 3
|
||||
|
||||
#define ATTR_REDIRECT_POLICY QNetworkRequest::RedirectPolicyAttribute
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
#define ATTR_HTTP2_ALLOWED QNetworkRequest::HTTP2AllowedAttribute
|
||||
#else // QT 5.15
|
||||
#define ATTR_HTTP2_ALLOWED QNetworkRequest::Http2AllowedAttribute
|
||||
#endif // QT 5.15
|
||||
|
||||
#define TMP_SUFFIX ".download"
|
||||
|
||||
// QNetworkReply::errorString() returns bullshit, use our own reporting
|
||||
static const char *errorString(QNetworkReply::NetworkError error)
|
||||
{
|
||||
switch (error) {
|
||||
case QNetworkReply::ConnectionRefusedError:
|
||||
return "Connection refused";
|
||||
case QNetworkReply::RemoteHostClosedError:
|
||||
return "Connection closed";
|
||||
case QNetworkReply::HostNotFoundError:
|
||||
return "Host not found";
|
||||
case QNetworkReply::TimeoutError:
|
||||
return "Connection timeout";
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
return "Operation canceled";
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
return "SSL handshake failed";
|
||||
case QNetworkReply::TemporaryNetworkFailureError:
|
||||
return "Temporary network failure";
|
||||
case QNetworkReply::NetworkSessionFailedError:
|
||||
return "Network session failed";
|
||||
case QNetworkReply::BackgroundRequestNotAllowedError:
|
||||
return "Background request not allowed";
|
||||
case QNetworkReply::TooManyRedirectsError:
|
||||
return "Too many redirects";
|
||||
case QNetworkReply::InsecureRedirectError:
|
||||
return "Insecure redirect";
|
||||
case QNetworkReply::ProxyConnectionRefusedError:
|
||||
return "Proxy connection refused";
|
||||
case QNetworkReply::ProxyConnectionClosedError:
|
||||
return "Proxy connection closed";
|
||||
case QNetworkReply::ProxyNotFoundError:
|
||||
return "Proxy not found";
|
||||
case QNetworkReply::ProxyTimeoutError:
|
||||
return "Proxy timeout error";
|
||||
case QNetworkReply::ProxyAuthenticationRequiredError:
|
||||
return "Proxy authentication required";
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
return "Content access denied";
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
return "Content operation not permitted";
|
||||
case QNetworkReply::ContentNotFoundError:
|
||||
return "Content not found";
|
||||
case QNetworkReply::AuthenticationRequiredError:
|
||||
return "Authentication required";
|
||||
case QNetworkReply::ContentReSendError:
|
||||
return "Content re-send error";
|
||||
case QNetworkReply::ContentConflictError:
|
||||
return "Content conflict";
|
||||
case QNetworkReply::ContentGoneError:
|
||||
return "Content gone";
|
||||
case QNetworkReply::InternalServerError:
|
||||
return "Internal server error";
|
||||
case QNetworkReply::OperationNotImplementedError:
|
||||
return "Operation not implemented";
|
||||
case QNetworkReply::ServiceUnavailableError:
|
||||
return "Service unavailable";
|
||||
case QNetworkReply::ProtocolUnknownError:
|
||||
return "Protocol unknown";
|
||||
case QNetworkReply::ProtocolInvalidOperationError:
|
||||
return "Protocol invalid operation";
|
||||
case QNetworkReply::UnknownNetworkError:
|
||||
return "Unknown network error";
|
||||
case QNetworkReply::UnknownProxyError:
|
||||
return "Unknown proxy error";
|
||||
case QNetworkReply::UnknownContentError:
|
||||
return "Unknown content error";
|
||||
case QNetworkReply::ProtocolFailure:
|
||||
return "Protocol failure";
|
||||
case QNetworkReply::UnknownServerError:
|
||||
return "Unknown server error";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
|
||||
static QString tmpName(const QString &origName)
|
||||
{
|
||||
return origName + TMP_SUFFIX;
|
||||
}
|
||||
|
||||
static QString origName(const QString &tmpName)
|
||||
{
|
||||
return tmpName.left(tmpName.size() - (sizeof(TMP_SUFFIX) - 1));
|
||||
}
|
||||
|
||||
Authorization::Authorization(const QString &username, const QString &password)
|
||||
{
|
||||
QString concatenated = username + ":" + password;
|
||||
QByteArray data = concatenated.toLocal8Bit().toBase64();
|
||||
_header = HTTPHeader("Authorization", "Basic " + data);
|
||||
}
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
NetworkTimeout::NetworkTimeout(int timeout, QNetworkReply *reply)
|
||||
: QObject(reply), _timeout(timeout)
|
||||
{
|
||||
connect(reply, &QIODevice::readyRead, this, &NetworkTimeout::reset);
|
||||
_timer.start(timeout * 1000, this);
|
||||
}
|
||||
|
||||
void NetworkTimeout::reset()
|
||||
{
|
||||
_timer.start(_timeout * 1000, this);
|
||||
}
|
||||
|
||||
void NetworkTimeout::timerEvent(QTimerEvent *ev)
|
||||
{
|
||||
if (!_timer.isActive() || ev->timerId() != _timer.timerId())
|
||||
return;
|
||||
QNetworkReply *reply = static_cast<QNetworkReply*>(parent());
|
||||
if (reply->isRunning())
|
||||
reply->abort();
|
||||
_timer.stop();
|
||||
}
|
||||
#endif // QT 5.15
|
||||
|
||||
|
||||
QNetworkAccessManager *Downloader::_manager = 0;
|
||||
int Downloader::_timeout = 30;
|
||||
bool Downloader::_http2 = true;
|
||||
|
||||
bool Downloader::doDownload(const Download &dl, const QList<HTTPHeader> &headers)
|
||||
{
|
||||
const QUrl &url = dl.url();
|
||||
bool userAgent = false;
|
||||
|
||||
if (!url.isValid() || !(url.scheme() == QLatin1String("http")
|
||||
|| url.scheme() == QLatin1String("https"))) {
|
||||
qWarning("%s: Invalid URL", qPrintable(url.toString()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_errorDownloads.value(url) >= RETRIES)
|
||||
return false;
|
||||
if (_currentDownloads.contains(url))
|
||||
return false;
|
||||
|
||||
QNetworkRequest request(url);
|
||||
request.setMaximumRedirectsAllowed(MAX_REDIRECT_LEVEL);
|
||||
request.setAttribute(ATTR_REDIRECT_POLICY,
|
||||
QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
request.setAttribute(ATTR_HTTP2_ALLOWED, QVariant(_http2));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
request.setTransferTimeout(_timeout * 1000);
|
||||
#endif // QT 5.15
|
||||
|
||||
for (int i = 0; i < headers.size(); i++) {
|
||||
const HTTPHeader &hdr = headers.at(i);
|
||||
request.setRawHeader(hdr.key(), hdr.value());
|
||||
// QByteArray::compare() not available in Qt < 5.12
|
||||
if (!QString(hdr.key()).compare("User-Agent", Qt::CaseInsensitive))
|
||||
userAgent = true;
|
||||
}
|
||||
if (!userAgent)
|
||||
request.setRawHeader("User-Agent", USER_AGENT);
|
||||
|
||||
QFile *file = new QFile(tmpName(dl.file()));
|
||||
if (!file->open(QIODevice::WriteOnly)) {
|
||||
qWarning("%s: %s", qPrintable(file->fileName()),
|
||||
qPrintable(file->errorString()));
|
||||
delete file;
|
||||
_errorDownloads.insert(url, RETRIES);
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(_manager);
|
||||
QNetworkReply *reply = _manager->get(request);
|
||||
file->setParent(reply);
|
||||
_currentDownloads.insert(url, file);
|
||||
|
||||
if (reply->isRunning()) {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
new NetworkTimeout(_timeout, reply);
|
||||
#endif // QT 5.15
|
||||
connect(reply, &QIODevice::readyRead, this, &Downloader::emitReadReady);
|
||||
connect(reply, &QNetworkReply::finished, this, &Downloader::emitFinished);
|
||||
} else {
|
||||
readData(reply);
|
||||
downloadFinished(reply);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Downloader::emitFinished()
|
||||
{
|
||||
downloadFinished(static_cast<QNetworkReply*>(sender()));
|
||||
}
|
||||
|
||||
void Downloader::emitReadReady()
|
||||
{
|
||||
readData(static_cast<QNetworkReply*>(sender()));
|
||||
}
|
||||
|
||||
void Downloader::insertError(const QUrl &url, QNetworkReply::NetworkError error)
|
||||
{
|
||||
switch (error) {
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
case QNetworkReply::TimeoutError:
|
||||
case QNetworkReply::RemoteHostClosedError:
|
||||
case QNetworkReply::ConnectionRefusedError:
|
||||
_errorDownloads.insert(url, _errorDownloads.value(url) + 1);
|
||||
break;
|
||||
default:
|
||||
_errorDownloads.insert(url, RETRIES);
|
||||
}
|
||||
}
|
||||
|
||||
void Downloader::readData(QNetworkReply *reply)
|
||||
{
|
||||
QFile *file = _currentDownloads.value(reply->request().url());
|
||||
Q_ASSERT(file);
|
||||
file->write(reply->readAll());
|
||||
}
|
||||
|
||||
void Downloader::downloadFinished(QNetworkReply *reply)
|
||||
{
|
||||
QUrl url(reply->request().url());
|
||||
QNetworkReply::NetworkError error = reply->error();
|
||||
|
||||
QFile *file = _currentDownloads.value(reply->request().url());
|
||||
if (error) {
|
||||
insertError(url, error);
|
||||
qWarning("%s: %s", url.toEncoded().constData(), errorString(error));
|
||||
file->remove();
|
||||
} else {
|
||||
file->close();
|
||||
file->rename(origName(file->fileName()));
|
||||
}
|
||||
|
||||
_currentDownloads.remove(url);
|
||||
reply->deleteLater();
|
||||
|
||||
if (_currentDownloads.isEmpty())
|
||||
emit finished();
|
||||
}
|
||||
|
||||
bool Downloader::get(const QList<Download> &list,
|
||||
const QList<HTTPHeader> &headers)
|
||||
{
|
||||
bool finishEmitted = false;
|
||||
|
||||
for (int i = 0; i < list.count(); i++)
|
||||
finishEmitted |= doDownload(list.at(i), headers);
|
||||
|
||||
return finishEmitted;
|
||||
}
|
||||
|
||||
void Downloader::enableHTTP2(bool enable)
|
||||
{
|
||||
Q_ASSERT(_manager);
|
||||
_http2 = enable;
|
||||
_manager->clearConnectionCache();
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG
|
||||
QDebug operator<<(QDebug dbg, const Download &download)
|
||||
{
|
||||
dbg.nospace() << "Download(" << download.url() << "," << download.file()
|
||||
<< ")";
|
||||
return dbg.space();
|
||||
}
|
||||
#endif // QT_NO_DEBUG
|
@ -1,103 +0,0 @@
|
||||
#ifndef DOWNLOADER_H
|
||||
#define DOWNLOADER_H
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
#include <QBasicTimer>
|
||||
#endif // QT 5.15
|
||||
#include <QUrl>
|
||||
#include <QList>
|
||||
#include <QHash>
|
||||
#include "common/kv.h"
|
||||
|
||||
class QFile;
|
||||
|
||||
typedef KV<QByteArray, QByteArray> HTTPHeader;
|
||||
|
||||
class Download
|
||||
{
|
||||
public:
|
||||
Download(const QUrl &url, const QString &file) : _url(url), _file(file) {}
|
||||
|
||||
const QUrl &url() const {return _url;}
|
||||
const QString &file() const {return _file;}
|
||||
|
||||
private:
|
||||
QUrl _url;
|
||||
QString _file;
|
||||
};
|
||||
|
||||
class Authorization
|
||||
{
|
||||
public:
|
||||
Authorization() {}
|
||||
Authorization(const QString &username, const QString &password);
|
||||
|
||||
const HTTPHeader &header() const {return _header;}
|
||||
bool isNull() const {return _header.key().isNull();}
|
||||
|
||||
private:
|
||||
HTTPHeader _header;
|
||||
};
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
class NetworkTimeout : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
NetworkTimeout(int timeout, QNetworkReply *reply);
|
||||
|
||||
private slots:
|
||||
void reset();
|
||||
|
||||
private:
|
||||
void timerEvent(QTimerEvent *ev);
|
||||
|
||||
QBasicTimer _timer;
|
||||
int _timeout;
|
||||
};
|
||||
#endif // QT 5.15
|
||||
|
||||
class Downloader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Downloader(QObject *parent = 0) : QObject(parent) {}
|
||||
|
||||
bool get(const QList<Download> &list, const QList<HTTPHeader> &headers);
|
||||
void clearErrors() {_errorDownloads.clear();}
|
||||
|
||||
static void setNetworkManager(QNetworkAccessManager *manager)
|
||||
{_manager = manager;}
|
||||
static void setTimeout(int timeout) {_timeout = timeout;}
|
||||
static void enableHTTP2(bool enable);
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
|
||||
private slots:
|
||||
void emitFinished();
|
||||
void emitReadReady();
|
||||
|
||||
private:
|
||||
void insertError(const QUrl &url, QNetworkReply::NetworkError error);
|
||||
bool doDownload(const Download &dl, const QList<HTTPHeader> &headers);
|
||||
void downloadFinished(QNetworkReply *reply);
|
||||
void readData(QNetworkReply *reply);
|
||||
|
||||
QHash<QUrl, QFile*> _currentDownloads;
|
||||
QHash<QUrl, int> _errorDownloads;
|
||||
|
||||
static QNetworkAccessManager *_manager;
|
||||
static int _timeout;
|
||||
static bool _http2;
|
||||
};
|
||||
|
||||
#ifndef QT_NO_DEBUG
|
||||
QDebug operator<<(QDebug dbg, const Download &download);
|
||||
#endif // QT_NO_DEBUG
|
||||
|
||||
#endif // DOWNLOADER_H
|
Reference in New Issue
Block a user