mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-10-31 13:10:19 -04:00 
			
		
		
		
	
		
			
	
	
		
			270 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			270 lines
		
	
	
		
			7.9 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) | ||
|  | { | ||
|  |   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 ())); | ||
|  |         } | ||
|  |     } | ||
|  | } |