WSJT-X/SampleDownloader/RemoteFile.cpp
Bill Somerville 8b1ef4d8be Fix various sample downloader issues especially server redirect handling
Thanks to Mike W9MDB for the concept  of forcing to HTTP if OpenSSL is
not installed or if the user requires it for other reasons.

The sample  downloader should  now be usable  with or  without OpenSSL
libraries being  installed, so long  as SourceForge continue  to serve
identical  content from  both HTTP  and  HTTPS schemes  on their  file
servers and mirrors.

For users with baulked  OpenSSL installations, incorrect or incomplete
CA  certificate  stores,  either  the improved  capability  to  ignore
SSL/TLS errors for  the duration of a session at  their discretion or,
as a last resort a new option to force an HTTP URL scheme is provided.

git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@7379 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
2016-12-11 21:19:31 +00:00

272 lines
8.0 KiB
C++

#include "RemoteFile.hpp"
#include <utility>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QDir>
#include <QByteArray>
#include "moc_RemoteFile.cpp"
RemoteFile::RemoteFile (ListenerInterface * listener, QNetworkAccessManager * network_manager
, QString const& local_file_path, bool http_only, QObject * parent)
: QObject {parent}
, listener_ {listener}
, network_manager_ {network_manager}
, local_file_ {local_file_path}
, http_only_ {http_only}
, is_valid_ {false}
, redirect_count_ {0}
, file_ {local_file_path}
{
local_file_.setCaching (false);
}
void RemoteFile::local_file_path (QString const& name)
{
QFileInfo new_file {name};
new_file.setCaching (false);
if (new_file != local_file_)
{
if (local_file_.exists ())
{
QFile file {local_file_.absoluteFilePath ()};
if (!file.rename (new_file.absoluteFilePath ()))
{
listener_->error (tr ("File System Error")
, tr ("Cannot rename file:\n\"%1\"\nto: \"%2\"\nError(%3): %4")
.arg (file.fileName ())
.arg (new_file.absoluteFilePath ())
.arg (file.error ())
.arg (file.errorString ()));
}
}
std::swap (local_file_, new_file);
}
}
bool RemoteFile::local () const
{
auto is_local = (reply_ && !reply_->isFinished ()) || local_file_.exists ();
if (is_local)
{
auto size = local_file_.size ();
listener_->download_progress (size, size);
listener_->download_finished (true);
}
else
{
listener_->download_progress (-1, 0);
}
return is_local;
}
bool RemoteFile::sync (QUrl const& url, bool local, bool force)
{
if (local)
{
if (!reply_ || reply_->isFinished ()) // not active download
{
if (force || !local_file_.exists () || url != url_)
{
url_ = url;
redirect_count_ = 0;
Q_ASSERT (!is_valid_);
download (url_);
}
}
else
{
return false;
}
}
else
{
if (reply_ && reply_->isRunning ())
{
reply_->abort ();
}
if (local_file_.exists ())
{
auto path = local_file_.absoluteDir ();
if (path.remove (local_file_.fileName ()))
{
listener_->download_progress (-1, 0);
}
else
{
listener_->error (tr ("File System Error")
, tr ("Cannot delete file:\n\"%1\"")
.arg (local_file_.absoluteFilePath ()));
return false;
}
path.rmpath (".");
}
}
return true;
}
void RemoteFile::download (QUrl url)
{
if (QNetworkAccessManager::Accessible != network_manager_->networkAccessible ()) {
// try and recover network access for QNAM
network_manager_->setNetworkAccessible (QNetworkAccessManager::Accessible);
}
if (url.isValid () && (!QSslSocket::supportsSsl () || http_only_))
{
url.setScheme ("http");
}
QNetworkRequest request {url};
request.setRawHeader ("User-Agent", "WSJT Sample Downloader");
request.setOriginatingObject (this);
// this blocks for a second or two the first time it is used on
// Windows - annoying
if (!is_valid_)
{
reply_ = network_manager_->head (request);
}
else
{
reply_ = network_manager_->get (request);
}
connect (reply_, &QNetworkReply::finished, this, &RemoteFile::reply_finished);
connect (reply_, &QNetworkReply::readyRead, this, &RemoteFile::store);
connect (reply_, &QNetworkReply::downloadProgress
, [this] (qint64 bytes_received, qint64 total_bytes) {
// report progress of wanted file
if (is_valid_)
{
listener_->download_progress (bytes_received, total_bytes);
}
});
}
void RemoteFile::abort ()
{
if (reply_ && reply_->isRunning ())
{
reply_->abort ();
}
}
void RemoteFile::reply_finished ()
{
if (!reply_) return; // we probably deleted it in an
// earlier call
QUrl redirect_url {reply_->attribute (QNetworkRequest::RedirectionTargetAttribute).toUrl ()};
if (reply_->error () == QNetworkReply::NoError && !redirect_url.isEmpty ())
{
if (listener_->redirect_request (redirect_url))
{
if (++redirect_count_ < 10) // maintain sanity
{
// follow redirect
download (reply_->url ().resolved (redirect_url));
}
else
{
listener_->download_finished (false);
listener_->error (tr ("Network Error")
, tr ("Too many redirects: %1")
.arg (redirect_url.toDisplayString ()));
is_valid_ = false; // reset
}
}
else
{
listener_->download_finished (false);
listener_->error (tr ("Network Error")
, tr ("Redirect not followed: %1")
.arg (redirect_url.toDisplayString ()));
is_valid_ = false; // reset
}
}
else if (reply_->error () != QNetworkReply::NoError)
{
file_.cancelWriting ();
file_.commit ();
listener_->download_finished (false);
is_valid_ = false; // reset
// report errors that are not due to abort
if (QNetworkReply::OperationCanceledError != reply_->error ())
{
listener_->error (tr ("Network Error"), reply_->errorString ());
}
}
else
{
auto path = QFileInfo {file_.fileName ()}.absoluteDir ();
if (is_valid_ && !file_.commit ())
{
listener_->error (tr ("File System Error")
, tr ("Cannot commit changes to:\n\"%1\"")
.arg (file_.fileName ()));
path.rmpath ("."); // tidy empty directories
listener_->download_finished (false);
is_valid_ = false; // reset
}
else
{
if (!is_valid_)
{
// now get the body content
is_valid_ = true;
download (reply_->url ().resolved (redirect_url));
}
else
{
listener_->download_finished (true);
is_valid_ = false; // reset
}
}
}
if (reply_ && reply_->isFinished ())
{
reply_->deleteLater ();
}
}
void RemoteFile::store ()
{
if (is_valid_)
{
if (!file_.isOpen ())
{
// create temporary file in the final location
auto path = QFileInfo {file_.fileName ()}.absoluteDir ();
if (path.mkpath ("."))
{
if (!file_.open (QSaveFile::WriteOnly))
{
abort ();
listener_->error (tr ("File System Error")
, tr ("Cannot open file:\n\"%1\"\nError(%2): %3")
.arg (path.path ())
.arg (file_.error ())
.arg (file_.errorString ()));
}
}
else
{
abort ();
listener_->error (tr ("File System Error")
, tr ("Cannot make path:\n\"%1\"")
.arg (path.path ()));
}
}
if (file_.write (reply_->read (reply_->bytesAvailable ())) < 0)
{
abort ();
listener_->error (tr ("File System Error")
, tr ("Cannot write to file:\n\"%1\"\nError(%2): %3")
.arg (file_.fileName ())
.arg (file_.error ())
.arg (file_.errorString ()));
}
}
}