1 /****************************************************************************************
2  * Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org>                                *
3  * Copyright (c) 2007 Casey Link <unnamedrambler@gmail.com>                             *
4  *                                                                                      *
5  * This program is free software; you can redistribute it and/or modify it under        *
6  * the terms of the GNU General Public License as published by the Free Software        *
7  * Foundation; either version 2 of the License, or (at your option) any later           *
8  * version.                                                                             *
9  *                                                                                      *
10  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
11  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
12  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
13  *                                                                                      *
14  * You should have received a copy of the GNU General Public License along with         *
15  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
16  ****************************************************************************************/
17 
18 #define DEBUG_PREFIX "ServiceAlbumCoverDownloader"
19 
20 #include "ServiceAlbumCoverDownloader.h"
21 
22 #include "core/support/Amarok.h"
23 #include "amarokconfig.h"
24 #include "core/support/Debug.h"
25 #include "covermanager/CoverCache.h"
26 
27 #include <thread>
28 
29 #include <QDir>
30 #include <QImage>
31 
32 using namespace Meta;
33 
34 
ServiceAlbumWithCover(const QString & name)35 Meta::ServiceAlbumWithCover::ServiceAlbumWithCover( const QString &name )
36     : ServiceAlbum( name )
37     , m_hasFetchedCover( false )
38     , m_isFetchingCover ( false )
39 {}
40 
ServiceAlbumWithCover(const QStringList & resultRow)41 Meta::ServiceAlbumWithCover::ServiceAlbumWithCover( const QStringList &resultRow )
42     : ServiceAlbum( resultRow )
43     , m_hasFetchedCover( false )
44     , m_isFetchingCover ( false )
45 {}
46 
~ServiceAlbumWithCover()47 Meta::ServiceAlbumWithCover::~ServiceAlbumWithCover()
48 {
49     CoverCache::invalidateAlbum( this );
50 }
51 
52 QImage
image(int size) const53 ServiceAlbumWithCover::image( int size ) const
54 {
55     if( size > 1000 )
56     {
57         debug() << "Giant image detected, are you sure you want this?";
58         return Meta::Album::image( size );
59     }
60 
61     const QString artist = hasAlbumArtist() ?
62         albumArtist()->name() :
63         QStringLiteral("NULL"); //no need to translate, only used as a caching key/temp filename
64 
65     const QString coverName = QStringLiteral( "%1_%2_%3_cover.png" ).arg( downloadPrefix(), artist, name() );
66     const QString saveLocation = Amarok::saveLocation( QStringLiteral("albumcovers/cache/") );
67     const QDir cacheCoverDir = QDir( saveLocation );
68 
69     //make sure that this dir exists
70     if( !cacheCoverDir.exists() )
71         cacheCoverDir.mkpath( saveLocation );
72 
73     if( size <= 1 )
74         size = 100;
75 
76     const QString sizeKey = QString::number( size ) + QLatin1Char('@');
77     const QString cacheCoverPath = cacheCoverDir.filePath( sizeKey + coverName );
78 
79     if( QFile::exists( cacheCoverPath ) )
80     {
81         return QImage( cacheCoverPath );
82     }
83     else if( m_hasFetchedCover && !m_cover.isNull() )
84     {
85         QImage image( m_cover.scaled( size, size, Qt::KeepAspectRatio, Qt::SmoothTransformation ) );
86         std::thread thread( QOverload<const QString&, const char*, int>::of( &QImage::save ), image, cacheCoverPath, "PNG", -1 );
87         thread.detach();
88         return image;
89     }
90     else if( !m_isFetchingCover && !coverUrl().isEmpty() )
91     {
92         m_isFetchingCover = true;
93 
94         ( new ServiceAlbumCoverDownloader )->downloadCover(
95             ServiceAlbumWithCoverPtr(const_cast<ServiceAlbumWithCover*>(this)) );
96     }
97 
98     return Meta::Album::image( size );
99 }
100 
101 void
setImage(const QImage & image)102 ServiceAlbumWithCover::setImage( const QImage& image )
103 {
104     m_cover = image;
105     m_hasFetchedCover = true;
106     m_isFetchingCover = false;
107     CoverCache::invalidateAlbum( this );
108 
109     notifyObservers();
110 }
111 
112 void
imageDownloadCanceled() const113 ServiceAlbumWithCover::imageDownloadCanceled() const
114 {
115     m_hasFetchedCover = true;
116     m_isFetchingCover = false;
117 }
118 
119 
120 ///////////////////////////////////////////////////////////////////////////////
121 // Class ServiceAlbumCoverDownloader
122 ///////////////////////////////////////////////////////////////////////////////
123 
ServiceAlbumCoverDownloader()124 ServiceAlbumCoverDownloader::ServiceAlbumCoverDownloader()
125     : m_albumDownloadJob( )
126 {
127     m_tempDir = new QTemporaryDir();
128     m_tempDir->setAutoRemove( true );
129 }
130 
~ServiceAlbumCoverDownloader()131 ServiceAlbumCoverDownloader::~ServiceAlbumCoverDownloader()
132 {
133     delete m_tempDir;
134 }
135 
136 void
downloadCover(ServiceAlbumWithCoverPtr album)137 ServiceAlbumCoverDownloader::downloadCover( ServiceAlbumWithCoverPtr album )
138 {
139     m_album = album;
140 
141     QUrl downloadUrl( album->coverUrl() );
142 
143     m_coverDownloadPath = m_tempDir->path() + QLatin1Char('/') + downloadUrl.fileName();
144 
145     debug() << "Download Cover: " << downloadUrl.url() << " to: " << m_coverDownloadPath;
146 
147     m_albumDownloadJob = KIO::file_copy( downloadUrl, QUrl::fromLocalFile( m_coverDownloadPath ), -1, KIO::Overwrite | KIO::HideProgressInfo );
148 
149     connect( m_albumDownloadJob, &KJob::result, this, &ServiceAlbumCoverDownloader::coverDownloadComplete );
150 }
151 
152 void
coverDownloadComplete(KJob * downloadJob)153 ServiceAlbumCoverDownloader::coverDownloadComplete( KJob * downloadJob )
154 {
155     if( !m_album ) // album was removed in between
156     {
157         debug() << "Bad album pointer";
158         return;
159     }
160 
161     if ( downloadJob != m_albumDownloadJob )
162         return; //not the right job, so let's ignore it
163 
164     if( !downloadJob || downloadJob->error() )
165     {
166         debug() << "Download Job failed!";
167 
168         //we could not download, so inform album
169         coverDownloadCanceled( downloadJob );
170         return;
171     }
172 
173     const QImage cover = QImage( m_coverDownloadPath );
174     if ( cover.isNull() )
175     {
176         debug() << "file not a valid image";
177         //the file wasn't an image, so inform album
178         m_album->imageDownloadCanceled();
179         return;
180     }
181 
182     m_album->setImage( cover );
183 
184     downloadJob->deleteLater();
185 
186     deleteLater();
187 }
188 
189 void
coverDownloadCanceled(KJob * downloadJob)190 ServiceAlbumCoverDownloader::coverDownloadCanceled( KJob *downloadJob )
191 {
192     Q_UNUSED( downloadJob );
193     DEBUG_BLOCK
194 
195     if( !m_album ) // album was removed in between
196         return;
197 
198     debug() << "Cover download cancelled";
199     m_album->imageDownloadCanceled();
200     deleteLater();
201 }
202 
203 
204