272 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			272 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
#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_.data (), &QNetworkReply::finished, this, &RemoteFile::reply_finished);
 | 
						|
  connect (reply_.data (), &QNetworkReply::readyRead, this, &RemoteFile::store);
 | 
						|
  connect (reply_.data (), &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 ()));
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |