/* \package fbgui */ #include "downloadmanager.h" #include #include "qlog4cxx.h" using namespace log4cxx; using namespace log4cxx::helpers; LoggerPtr dmLogger(Logger::getLogger("fbgui.dm")); // static counter to save the number of successfully downloaded files. int DownloadManager::_downloaded = 0; // ------------------------------------------------------------------------------------------------------- // Initialisation // ------------------------------------------------------------------------------------------------------- // Constructor initialises a QNetworkAccessManager needed to create/received download requests/replies // This object is then moved to the thread for the download manager, to ensure that downloads // are done in a separate thread (to avoid GUI blocking.) // The flag "_dip" starts as false, since no download is in progress. // This flag is needed to control queueing functionality. // Lastly the given download directory's existance is checked. (see below) DownloadManager::DownloadManager() { LOG4CXX_DEBUG(dmLogger, "Initializing download manager..."); checkDownloadDirectory(); _qnam = new QNetworkAccessManager(); _qnam->moveToThread(&dmThread); _dip = false; } // Destructor DownloadManager::~DownloadManager() { delete _qnam; } // ------------------------------------------------------------------------------------------------------- void DownloadManager::checkDownloadDirectory() { // check if downloadPath exists, if not create it. _downloadDir = QDir(downloadPath); if (!_downloadDir.exists()) { LOG4CXX_DEBUG(dmLogger, "Download directory: " << _downloadDir.path() << " doesn't exist."); // try to create the directory if (QDir::current().mkdir(downloadPath)){ LOG4CXX_DEBUG(dmLogger, "Created download directory: " << _downloadDir.path()); } else { LOG4CXX_DEBUG(dmLogger, "Failed to create directory: " << _downloadDir.path()); // try to save to /tmp/fbgui _downloadDir.setPath(QDir::tempPath() + "/fbgui"); if (!_downloadDir.exists()) { if (QDir::current().mkdir(QDir::tempPath() + "/fbgui")) { LOG4CXX_DEBUG(dmLogger, "Successfully created: " << _downloadDir.absolutePath()); } else { // just in case LOG4CXX_DEBUG(dmLogger, "Failed to create: " << _downloadDir.absolutePath()); LOG4CXX_DEBUG(dmLogger, "Exiting..."); exit( EXIT_FAILURE); } } else LOG4CXX_DEBUG(dmLogger, "" << _downloadDir.absolutePath() << " already exists."); } } else LOG4CXX_DEBUG(dmLogger, "Download directory: " << _downloadDir.absolutePath() << " already exists."); LOG4CXX_DEBUG(dmLogger, "Saving downloads to: " << _downloadDir.absolutePath()); downloadPath = _downloadDir.absolutePath(); } // ------------------------------------------------------------------------------------------------------- // Public access // ------------------------------------------------------------------------------------------------------- void DownloadManager::downloadFile(const QString& filename) { QUrl fileUrl(baseURL.resolved(QUrl(filename))); this->processDownloadRequest(fileUrl); } // ------------------------------------------------------------------------------------------------------- void DownloadManager::downloadFile(const QUrl& fileUrl) { this->processDownloadRequest(fileUrl); } // ------------------------------------------------------------------------------------------------------- // Private functions handling download requests and queueing // ------------------------------------------------------------------------------------------------------- void DownloadManager::processDownloadRequest(const QUrl& url) { if (url.isEmpty()) { LOG4CXX_DEBUG(dmLogger, "No URL specified for download."); return; } LOG4CXX_DEBUG(dmLogger, "Enqueueing: " << url.toString()); _downloadQueue.enqueue(url); if (_dip) { // download in progress, return. LOG4CXX_DEBUG(dmLogger, "Download in progress! Queued:" << url.toString() << "(" << _downloadQueue.size() << " in queue)"); return; } // no running downloads: start next in queue startNextDownload(); } // ------------------------------------------------------------------------------------------------------- void DownloadManager::startNextDownload() { //QWSServer::instance()->setCursorVisible(false); if (_downloadQueue.isEmpty()) { emit downloadQueueEmpty(); LOG4CXX_DEBUG(dmLogger, "Download manager ready. (1)"); return; } LOG4CXX_DEBUG(dmLogger, "Starting next download: " << _downloadQueue.head().toString() << " (" << _downloadQueue.size() - 1 << " in queue.)"); // dequeue next URL to download. QUrl url = _downloadQueue.dequeue(); // get filename from URL. QString tmp = url.path(); tmp.remove(0, tmp.lastIndexOf(QChar('/')) + 1); // check if filename exists on target file system if (_downloadDir.exists(tmp)) { LOG4CXX_DEBUG(dmLogger, "File already exists: " << _downloadDir.absoluteFilePath(tmp)); _outfile.setFileName( QString(_downloadDir.absolutePath() + "/" + tmp + ".\%1").arg(_downloaded)); } else _outfile.setFileName(_downloadDir.absoluteFilePath(tmp)); LOG4CXX_DEBUG(dmLogger, "Saving to: " << _outfile.fileName()); // try to open for writing if (!_outfile.open(QIODevice::WriteOnly)) { LOG4CXX_DEBUG(dmLogger, "No write access to " << _outfile.fileName() << " . Skipping download..."); return; } // send the request for the file QNetworkRequest request(url); _currentDownload = _qnam->get(request); _lastProgress = 0; _currentProgress = 0; _dip = true; time.start(); QObject::connect(_currentDownload, SIGNAL(readyRead()), this, SLOT( downloadReady())); QObject::connect(_currentDownload, SIGNAL(metaDataChanged()), this, SLOT( processMetaInfo())); QObject::connect(_currentDownload, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); QObject::connect(_currentDownload, SIGNAL(finished()), this, SLOT( downloadFinished())); } // ------------------------------------------------------------------------------------------------------- // Private slots to handle a download in progress // ------------------------------------------------------------------------------------------------------- void DownloadManager::processMetaInfo() { // fetch filesize from header & filename from URL (for now) const QByteArray cltag = "Content-Length"; QByteArray clinfo = _currentDownload->rawHeader(cltag); QFileInfo fi(_outfile); LOG4CXX_DEBUG(dmLogger, "Download Info: " << fi.fileName() << " (Size: " << clinfo.toDouble() << ")"); emit downloadInfo(fi.fileName(), clinfo.toDouble()); } // ------------------------------------------------------------------------------------------------------- void DownloadManager::downloadReady() { // data ready, save it _outfile.write(_currentDownload->readAll()); } // ------------------------------------------------------------------------------------------------------- void DownloadManager::downloadProgress(qint64 bytesIn, qint64 bytesTotal) { if (bytesIn > bytesTotal || bytesTotal <= 0) { LOG4CXX_DEBUG(dmLogger, "downloadProgress invalid values:" << "In:" << bytesIn << " / Total: " << bytesTotal); return; } // calculate current speed double speed = bytesIn * 1000 / time.elapsed(); QString unit; if (speed < 1024) { unit = "bytes/sec"; } else if (speed < 1024 * 1024) { speed /= 1024; unit = "KB/s"; } else { speed /= 1024 * 1024; unit = "MB/s"; } // update progress only if difference higher than the updateInterval setting _currentProgress = ((bytesIn * 100) / bytesTotal); if (_currentProgress - _lastProgress >= updateInterval) { _lastProgress = _currentProgress; emit updateProgress(_currentProgress, speed, unit); LOG4CXX_DEBUG(dmLogger, "Download progress of " << _currentDownload->url().toString() << ": " << bytesIn << "/" << bytesTotal << "(" << _currentProgress << "\%)"); } } // ------------------------------------------------------------------------------------------------------- void DownloadManager::downloadFinished() { // check for errors if (_currentDownload->error()) { _outfile.close(); _outfile.remove(); int statusCode = _currentDownload->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); LOG4CXX_DEBUG(dmLogger, "Download of " << _currentDownload->url().toString() << " failed with HTTP error code: " << statusCode); emit notify(QString("Download failed! HTTP Status Code: %1").arg(statusCode)); _currentDownload->deleteLater(); } else { // end download _outfile.close(); _downloaded++; LOG4CXX_DEBUG(dmLogger, "Download of " << _currentDownload->url().toString() << " finished. (downloaded = " << _downloaded << ")"); emit notify(QString("Successfully downloaded %1").arg(_currentDownload->url().toString())); _currentDownload->deleteLater(); } _dip = false; // process next in queue, if any if (_downloadQueue.isEmpty()) { emit downloadQueueEmpty(); LOG4CXX_DEBUG(dmLogger, "Download manager ready. (2)"); return; } startNextDownload(); }