WSJT-X/SampleDownloader/RemoteFile.cpp
Bill Somerville 2994a924e5 Attempt to recover from network failures
QNetworkAccessManager which is  used for all network  traffic does not
automatically  recover  from  network  outages. This  patch  tries  to
reestablish network accessibility before any requests are attempted.

git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@6471 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
2016-02-20 12:55:18 +00:00

275 lines
8.1 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, QObject * parent)
: QObject {parent}
, listener_ {listener}
, network_manager_ {network_manager}
, local_file_ {local_file_path}
, reply_ {nullptr}
, 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 const& url)
{
if (QNetworkAccessManager::Accessible != network_manager_->networkAccessible ()) {
// try and recover network access for QNAM
network_manager_->setNetworkAccessible (QNetworkAccessManager::Accessible);
}
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);
}
});
connect (reply_, &QNetworkReply::sslErrors, [this] (QList<QSslError> const& errors) {
QString message;
for (auto const& error: errors)
{
message += '\n' + reply_->request ().url ().toDisplayString () + ": "
+ error.errorString ();
}
listener_->error ("Network SSL Errors", message);
});
}
void RemoteFile::abort ()
{
if (reply_ && reply_->isRunning ())
{
reply_->abort ();
}
}
void RemoteFile::reply_finished ()
{
auto saved_reply = reply_;
auto redirect_url = reply_->attribute (QNetworkRequest::RedirectionTargetAttribute).toUrl ();
if (!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_->isFinished ()) reply_ = nullptr;
disconnect (saved_reply);
saved_reply->deleteLater (); // finished with QNetworkReply
}
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 ()));
}
}
}