1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-03-18 23:09:42 +01:00

Added DownloadManager and DownloadJob.

This commit is contained in:
Christian Muehlhaeuser 2015-03-11 05:14:36 +01:00
parent 40dfa8faa0
commit 55feb3f3e1
5 changed files with 874 additions and 0 deletions

View File

@ -15,6 +15,8 @@ set( libGuiSources
ActionCollection.cpp
ContextMenu.cpp
DownloadManager.cpp
DownloadJob.cpp
DropJob.cpp
GlobalActionManager.cpp
ViewPage.cpp

View File

@ -0,0 +1,446 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2015, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DownloadJob.h"
#include "Track.h"
#include "TomahawkSettings.h"
#include "utils/NetworkAccessManager.h"
#include "utils/Logger.h"
DownloadJob::DownloadJob( const Tomahawk::track_ptr& track, DownloadFormat format, bool tryResuming, DownloadJob::TrackState state )
: m_state( state )
, m_retries( 0 )
, m_tryResuming( tryResuming )
, m_reply( 0 )
, m_file( 0 )
, m_rcvdStamp( 0 )
, m_rcvdEmit( 0 )
, m_rcvdSize( 0 )
, m_fileSize( 0 )
, m_format( format )
, m_track( track )
{
m_finished = ( state == Finished );
}
DownloadJob::~DownloadJob()
{
}
int
DownloadJob::progressPercentage() const
{
if ( m_fileSize == 0 )
return 0;
return ( (double)m_rcvdSize / (double)m_fileSize ) * 100.0;
}
void
DownloadJob::setState( TrackState state )
{
TrackState oldState = m_state;
m_state = state;
emit stateChanged( state, oldState );
if ( m_state == Finished )
{
m_rcvdSize = m_fileSize;
emit finished();
}
}
QString
DownloadJob::localFile() const
{
return m_localFile;
}
QString
DownloadJob::localPath() const
{
QDir dir = TomahawkSettings::instance()->downloadsPath();
if ( !dir.exists() )
{
dir.mkpath( "." );
}
QString path = QString( "%1/%2" ).arg( safeEncode( m_track->artist(), true ) ).arg( safeEncode( m_track->album(), true ) );
dir.mkpath( path );
return QString( dir.path() + "/" + path ).replace( "//", "/" );
}
QUrl
DownloadJob::prepareFilename()
{
QString filename = QString( "%1. %2.%3" ).arg( m_track->albumpos() ).arg( safeEncode( m_track->track() ) ).arg( m_format.extension );
QString path = localPath();
QString localFile = QString( path + "/" + filename );
if ( !m_tryResuming )
{
QFileInfo fileInfo( localFile );
unsigned int dupe = 1;
while ( dupe < 100 )
{
QFileInfo fi( localFile );
if ( fi.exists() )
{
QString dupeToken = QString( " (%1)" ).arg( dupe++ );
localFile = path + "/" + fileInfo.completeBaseName() + dupeToken + "." + fileInfo.suffix();
}
else
break;
}
}
else
{
QFileInfo fileInfo( localFile );
unsigned int dupe = 1;
QString lastFound = localFile;
while ( dupe < 100 )
{
QFileInfo fi( localFile );
if ( fi.exists() )
{
lastFound = localFile;
QString dupeToken = QString( " (%1)" ).arg( dupe++ );
localFile = path + "/" + fileInfo.completeBaseName() + dupeToken + "." + fileInfo.suffix();
}
else
break;
}
localFile = lastFound;
}
tLog() << "Storing file as" << localFile << m_tryResuming;
return QUrl( localFile );
}
void
DownloadJob::retry()
{
tLog() << Q_FUNC_INFO;
m_retries = 0;
m_reply = 0;
m_file = 0;
m_rcvdSize = 0;
m_fileSize = 0;
m_finished = false;
m_tryResuming = true;
setState( Waiting );
emit updated();
}
bool
DownloadJob::download()
{
QUrl localFile = prepareFilename();
if ( m_file )
{
tLog() << "Recovering from failed download for track:" << toString() << "-" << m_retries << "retries so far.";
m_finished = false;
delete m_file;
m_file = 0;
}
tLog() << "Saving download to file:" << localFile << localFile.toLocalFile();
m_file = new QFile( localFile.toString() );
m_localFile = localFile.toString();
if ( m_tryResuming && checkForResumedFile() )
return true;
m_reply = Tomahawk::Utils::nam()->get( QNetworkRequest( m_format.url ) );
connect( m_reply, SIGNAL( error( QNetworkReply::NetworkError ) ), SLOT( onDownloadError( QNetworkReply::NetworkError ) ) );
connect( m_reply, SIGNAL( downloadProgress( qint64, qint64 ) ), SLOT( onDownloadProgress( qint64, qint64 ) ) );
connect( m_reply, SIGNAL( finished() ), SLOT( onDownloadNetworkFinished() ) );
setState( Running );
return true;
}
void
DownloadJob::pause()
{
if ( !m_reply )
return;
setState( Paused );
// m_reply->setReadBufferSize( 0 );
}
void
DownloadJob::resume()
{
tLog() << Q_FUNC_INFO << m_finished << m_rcvdSize << m_fileSize;
if ( !m_reply )
{
tLog() << "Initiating paused download from previous session:" << toString();
download();
return;
}
setState( Running );
// m_reply->setReadBufferSize( 65536 );
onDownloadProgress( m_fileSize, m_fileSize );
}
void
DownloadJob::abort()
{
tLog() << Q_FUNC_INFO << toString();
setState( Aborted );
if ( m_reply )
{
m_reply->abort();
m_reply->deleteLater();
m_reply = 0;
}
}
void
DownloadJob::onDownloadError( QNetworkReply::NetworkError code )
{
if ( code == QNetworkReply::NoError )
return;
if ( m_state == Aborted )
return;
tLog() << "Download error for track:" << toString() << "-" << code;
if ( ++m_retries > 3 )
{
setState( Failed );
}
else
{
m_tryResuming = true;
download();
}
}
void
DownloadJob::onDownloadProgress( qint64 rcvd, qint64 total )
{
if ( m_reply == 0 )
return;
if ( rcvd >= m_fileSize && m_fileSize > 0 )
{
m_finished = true;
}
if ( state() == Paused )
return;
m_rcvdSize = rcvd;
m_fileSize = total;
qint64 now = QDateTime::currentDateTime().toMSecsSinceEpoch();
if ( ( now - 50 > m_rcvdStamp ) || ( rcvd == total ) )
{
m_rcvdStamp = now;
if ( ( m_rcvdSize - 16384 > m_rcvdEmit ) || ( rcvd == total ) )
{
m_rcvdEmit = m_rcvdSize;
emit progress( progressPercentage() );
}
}
if ( !m_file )
return;
if ( !m_file->isOpen() )
{
if ( m_tryResuming && checkForResumedFile() )
return;
if ( !m_file->open( QIODevice::WriteOnly ) )
{
tLog() << Q_FUNC_INFO << "Failed opening file:" << m_file->fileName();
setState( Failed );
return;
}
}
QByteArray data = m_reply->readAll();
if ( data.length() == 0 )
return;
if ( m_file->write( data ) < 0 )
{
tLog() << Q_FUNC_INFO << "Failed writing to file:" << data.length();
onDownloadError( QNetworkReply::UnknownContentError );
return;
}
if ( m_rcvdSize >= m_fileSize && m_fileSize > 0 )
{
onDownloadFinished();
}
else if ( m_reply->isFinished() && m_reply->bytesAvailable() == 0 )
{
if ( ( m_fileSize > 0 && m_rcvdSize < m_fileSize ) || m_rcvdSize == 0 )
{
onDownloadError( QNetworkReply::UnknownContentError );
return;
}
}
}
void
DownloadJob::onDownloadNetworkFinished()
{
tLog() << Q_FUNC_INFO << m_rcvdSize << m_fileSize;
if ( m_reply && m_reply->bytesAvailable() > 0 )
{
tLog() << "Expecting more data!";
return;
}
if ( ( m_fileSize > 0 && m_rcvdSize < m_fileSize ) || m_rcvdSize == 0 )
{
if ( m_reply )
onDownloadError( QNetworkReply::UnknownContentError );
return;
}
}
void
DownloadJob::onDownloadFinished()
{
tLog() << Q_FUNC_INFO << m_rcvdSize << m_fileSize;
if ( state() == Paused )
{
m_finished = true;
return;
}
if ( ( m_fileSize > 0 && m_rcvdSize < m_fileSize ) || m_rcvdSize == 0 )
{
onDownloadError( QNetworkReply::UnknownContentError );
return;
}
disconnect( m_reply, SIGNAL( error( QNetworkReply::NetworkError ) ), this, SLOT( onDownloadError( QNetworkReply::NetworkError ) ) );
disconnect( m_reply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( onDownloadProgress( qint64, qint64 ) ) );
disconnect( m_reply, SIGNAL( finished() ), this, SLOT( onDownloadNetworkFinished() ) );
m_reply->abort();
if ( m_file && m_file->isOpen() )
{
m_file->flush();
m_file->close();
}
delete m_file;
m_file = 0;
m_finishedTimestamp = QDateTime::currentDateTimeUtc();
setState( Finished );
tLog() << Q_FUNC_INFO << "Finished downloading:" << toString();
tLog() << Q_FUNC_INFO << m_finished;
}
bool
DownloadJob::checkForResumedFile()
{
tLog() << Q_FUNC_INFO;
if ( !m_tryResuming )
return false;
QUrl localFile = prepareFilename();
QFileInfo fi( localFile.toString() );
tLog() << Q_FUNC_INFO << m_fileSize << fi.size();
if ( fi.size() > 0 && fi.exists() && fi.size() == m_fileSize )
{
tLog() << Q_FUNC_INFO << "Detected previously finished download.";
m_rcvdSize = fi.size();
m_finished = true;
onDownloadFinished();
return true;
}
return false;
}
QString
DownloadJob::safeEncode( const QString& filename, bool removeTrailingDots ) const
{
//FIXME: make it a regexp
QString res = QString( filename ).toLatin1().replace( "/", "_" ).replace( "\\", "_" )
.replace( "*", "_" ).replace( "?", "_" ).replace( "%", "_" )
.replace( "'", "_" ).replace( "\"", "_" )
.replace( ":", "_" ).replace( "#", "_" )
.replace( "<", "_" ).replace( ">", "_" );
if ( removeTrailingDots )
{
while ( res.endsWith( "." ) )
res = res.left( res.count() - 1 );
}
return res.left( 127 );
}
QString
DownloadJob::toString() const
{
return m_track->toString();
}
DownloadFormat
DownloadJob::format() const
{
return m_format;
}

View File

@ -0,0 +1,117 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2015, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DOWNLOADJOB_H
#define DOWNLOADJOB_H
#include <QDir>
#include <QObject>
#include <QFile>
#include <QUrl>
#include <QPixmap>
#include <QDomElement>
#include <QNetworkReply>
#include <QDateTime>
#include "Track.h"
struct DownloadFormat
{
QUrl url;
QString extension;
QString mimetype;
};
class DownloadJob : public QObject
{
Q_OBJECT
public:
enum TrackState
{ Waiting = 0, Running, Paused, Failed, Finished, Aborted, Any };
DownloadJob( const Tomahawk::track_ptr& track, DownloadFormat format, bool tryResuming = false, DownloadJob::TrackState state = Waiting );
~DownloadJob();
QString toString() const;
TrackState state() const { return m_state; }
unsigned int retries() const { return m_retries; }
int progressPercentage() const;
long receivedSize() const { return m_rcvdSize; }
long fileSize() const { return m_fileSize; }
QString localPath() const;
QString localFile() const;
DownloadFormat format() const;
QDateTime finishedTimestamp() const { return m_finishedTimestamp; }
void setFinishedTimestamp( const QDateTime& timestamp ) { m_finishedTimestamp = timestamp; }
void setState( TrackState state );
public slots:
bool download();
void pause();
void resume();
void retry();
void abort();
signals:
void updated();
void progress( int percentage );
void stateChanged( DownloadJob::TrackState newState, DownloadJob::TrackState oldState );
void finished();
private slots:
void onDownloadNetworkFinished();
void onDownloadError( QNetworkReply::NetworkError code );
void onDownloadProgress( qint64, qint64 );
void onDownloadFinished();
private:
void storeState();
QString safeEncode( const QString& filename, bool removeTrailingDots = false ) const;
bool checkForResumedFile();
QUrl prepareFilename();
TrackState m_state;
unsigned int m_retries;
bool m_tryResuming;
QNetworkReply* m_reply;
QFile* m_file;
qint64 m_rcvdStamp;
long m_rcvdEmit;
long m_rcvdSize;
long m_fileSize;
bool m_finished;
QString m_localFile;
QDateTime m_finishedTimestamp;
DownloadFormat m_format;
Tomahawk::track_ptr m_track;
};
typedef QSharedPointer<DownloadJob> downloadjob_ptr;
#endif // DOWNLOADJOB_H

View File

@ -0,0 +1,239 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2015, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DownloadManager.h"
#include <QTimer>
#include "utils/Logger.h"
DownloadManager* DownloadManager::s_instance = 0;
DownloadManager*
DownloadManager::instance()
{
if ( !s_instance )
{
s_instance = new DownloadManager();
}
return s_instance;
}
DownloadManager::DownloadManager()
: m_globalState( true )
{
tLog() << Q_FUNC_INFO << "Initializing DownloadManager.";
QTimer::singleShot( 0, this, SLOT( resumeJobs() ) );
}
DownloadManager::~DownloadManager()
{
tLog() << Q_FUNC_INFO << "Shutting down DownloadManager.";
QList< downloadjob_ptr > jl;
foreach ( const downloadjob_ptr& job, jobs() )
{
jl << job;
}
// TomahawkSettings::instance()->storeJobs( jl );
}
void
DownloadManager::resumeJobs()
{
tLog() << Q_FUNC_INFO;
/* QList<downloadjob_ptr> jobs = TomahawkSettings::instance()->storedJobs();
for ( int i = jobs.count() - 1; i >= 0; i-- )
{
downloadjob_ptr job = jobs.at( i );
addJob( job );
}
tLog() << Q_FUNC_INFO << "Restored" << jobs.count() << "existing jobs.";*/
}
QList<downloadjob_ptr>
DownloadManager::jobs( DownloadJob::TrackState state ) const
{
if ( state < 0 )
return m_jobs;
QList<downloadjob_ptr> jobs;
foreach ( const downloadjob_ptr& job, m_jobs )
{
if ( job.isNull() )
continue;
if ( state == DownloadJob::TrackState::Any || job->state() == state )
jobs << job;
}
return jobs;
}
bool
DownloadManager::addJob( const downloadjob_ptr& job )
{
if ( job.isNull() )
{
tLog() << "Found invalid download job - ignoring!";
return false;
}
if ( containsJob( job ) )
{
tLog() << "Found duplicate download job - ignoring:" << job->toString();
return false;
}
m_jobs << job;
emit jobAdded( job );
connect( job.data(), SIGNAL( finished() ), SLOT( checkJobs() ) );
connect( job.data(), SIGNAL( stateChanged( DownloadJob::TrackState, DownloadJob::TrackState ) ), SLOT( checkJobs() ) ) ;
// connect( job.data(), SIGNAL( stateChanged( DownloadJob::TrackState, DownloadJob::TrackState ) ), SIGNAL( stateChanged( DownloadJob::TrackState, DownloadJob::TrackState ) ) );
checkJobs();
return true;
}
bool
DownloadManager::removeJob( const downloadjob_ptr& job )
{
tLog() << "Removing job:" << job->toString();
job->abort();
m_jobs.removeAll( job );
emit jobRemoved( job );
checkJobs();
return true;
}
bool
DownloadManager::containsJob( const downloadjob_ptr& job ) const
{
return m_jobs.contains( job );
}
downloadjob_ptr
DownloadManager::currentJob() const
{
QList<downloadjob_ptr> j = jobs( DownloadJob::TrackState::Running );
if ( j.count() )
return j.first();
j = jobs( DownloadJob::TrackState::Paused );
if ( j.count() )
return j.first();
j = jobs( DownloadJob::TrackState::Waiting );
if ( j.count() )
return j.first();
return downloadjob_ptr();
}
DownloadManager::DownloadManagerState
DownloadManager::state() const
{
if ( !currentJob().isNull() )
{
switch ( currentJob()->state() )
{
case DownloadJob::TrackState::Waiting:
return DownloadManager::DownloadManagerState::Waiting;
case DownloadJob::TrackState::Paused:
return DownloadManager::DownloadManagerState::Paused;
case DownloadJob::TrackState::Running:
return DownloadManager::DownloadManagerState::Running;
}
}
return DownloadManager::DownloadManagerState::Waiting;
}
void
DownloadManager::checkJobs()
{
if ( !m_globalState )
return;
if ( state() == DownloadManager::DownloadManagerState::Waiting && !currentJob().isNull() )
{
downloadjob_ptr job = currentJob();
/* connect( job.data(), SIGNAL( finished() ), SLOT( checkJobs() ) );
connect( job.data(), SIGNAL( stateChanged( DownloadJob::TrackState, DownloadJob::TrackState ) ), SLOT( checkJobs() ) ) ;
connect( job.data(), SIGNAL( stateChanged( DownloadJob::TrackState, DownloadJob::TrackState ) ), SIGNAL( stateChanged( Track::TrackState, Track::TrackState ) ) );*/
job->download();
}
}
void
DownloadManager::pause()
{
tLog() << Q_FUNC_INFO;
m_globalState = false;
foreach ( const downloadjob_ptr& job, jobs( DownloadJob::TrackState::Running ) )
{
job->pause();
}
}
void
DownloadManager::resume()
{
tLog() << Q_FUNC_INFO;
m_globalState = true;
if ( jobs( DownloadJob::TrackState::Paused ).count() )
{
foreach ( const downloadjob_ptr& job, jobs( DownloadJob::TrackState::Paused ) )
{
tLog() << "Resuming job:" << job->toString();
job->resume();
}
return;
}
checkJobs();
}

View File

@ -0,0 +1,70 @@
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
*
* Copyright 2015, Christian Muehlhaeuser <muesli@tomahawk-player.org>
*
* Tomahawk is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Tomahawk is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DOWNLOADMANAGER_H
#define DOWNLOADMANAGER_H
#include <QObject>
#include <QDateTime>
#include <QUrl>
#include "DownloadJob.h"
class DownloadManager : public QObject
{
Q_OBJECT
public:
enum DownloadManagerState { None, Running, Paused, Waiting };
static DownloadManager* instance();
DownloadManager();
~DownloadManager();
DownloadManagerState state() const;
QList<downloadjob_ptr> jobs( DownloadJob::TrackState state = DownloadJob::TrackState::Any ) const;
bool containsJob( const downloadjob_ptr& job ) const;
downloadjob_ptr currentJob() const;
public slots:
bool addJob( const downloadjob_ptr& job );
bool removeJob( const downloadjob_ptr& job );
void checkJobs();
void pause();
void resume();
void resumeJobs();
signals:
void jobAdded( const downloadjob_ptr& job );
void jobRemoved( const downloadjob_ptr& job );
void stateChanged( DownloadManagerState newState, DownloadManagerState oldState );
private slots:
private:
QList< downloadjob_ptr > m_jobs;
bool m_globalState;
static DownloadManager* s_instance;
};
#endif // DOWNLOADMANAGER_H