1 /*
2  * Strawberry Music Player
3  * This file was part of Clementine.
4  * Copyright 2010, David Sansome <me@davidsansome.com>
5  * Copyright 2019-2021, Jonas Kvinge <jonas@jkvinge.net>
6  *
7  * Strawberry is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Strawberry is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #ifndef ALBUMCOVERLOADER_H
23 #define ALBUMCOVERLOADER_H
24 
25 #include "config.h"
26 
27 #include <QtGlobal>
28 #include <QObject>
29 #include <QMutex>
30 #include <QPair>
31 #include <QSet>
32 #include <QHash>
33 #include <QMultiMap>
34 #include <QQueue>
35 #include <QByteArray>
36 #include <QString>
37 #include <QImage>
38 #include <QPixmap>
39 
40 #include "core/song.h"
41 #include "core/tagreaderclient.h"
42 #include "settings/collectionsettingspage.h"
43 #include "albumcoverloaderoptions.h"
44 #include "albumcoverloaderresult.h"
45 #include "albumcoverimageresult.h"
46 
47 class QThread;
48 class QNetworkReply;
49 class NetworkAccessManager;
50 
51 class AlbumCoverLoader : public QObject {
52   Q_OBJECT
53 
54  public:
55   explicit AlbumCoverLoader(QObject *parent = nullptr);
56 
57   enum State {
58     State_None,
59     State_Manual,
60     State_Automatic,
61   };
62 
63   void ReloadSettings();
64 
65   void ExitAsync();
Stop()66   void Stop() { stop_requested_ = true; }
67 
68   static QString AlbumCoverFilename(QString artist, QString album, const QString &extension);
69 
70   static QString CoverFilenameFromSource(const Song::Source source, const QUrl &cover_url, const QString &artist, const QString &album, const QString &album_id, const QString &extension);
71   QString CoverFilenameFromVariable(const QString &artist, const QString &album, const QString &extension = QString());
72   QString CoverFilePath(const Song &song, const QString &album_dir, const QUrl &cover_url, const QString &extension = QString());
73 
74   QString CoverFilePath(const Song::Source source, const QString &artist, QString album, const QString &album_id, const QString &album_dir, const QUrl &cover_url, const QString &extension = QString());
75 
76   quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const Song &song);
77   quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QUrl &art_automatic, const QUrl &art_manual, const QUrl &song_url = QUrl(), const Song::Source song_source = Song::Source_Unknown);
78   quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const AlbumCoverImageResult &album_cover);
79   quint64 LoadImageAsync(const AlbumCoverLoaderOptions &options, const QImage &image);
80 
81   void CancelTask(const quint64 id);
82   void CancelTasks(const QSet<quint64> &ids);
83 
84   quint64 SaveEmbeddedCoverAsync(const QString &song_filename, const QString &cover_filename);
85   quint64 SaveEmbeddedCoverAsync(const QString &song_filename, const QImage &image);
86   quint64 SaveEmbeddedCoverAsync(const QString &song_filename, const QByteArray &image_data);
87   quint64 SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const QString &cover_filename);
88   quint64 SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const QImage &image);
89   quint64 SaveEmbeddedCoverAsync(const QList<QUrl> &urls, const QByteArray &image_data);
90 
91  signals:
92   void ExitFinished();
93   void AlbumCoverLoaded(quint64 id, AlbumCoverLoaderResult result);
94   void SaveEmbeddedCoverAsyncFinished(quint64 id, bool success, bool cleared);
95 
96  protected slots:
97   void Exit();
98   void ProcessTasks();
99   void RemoteFetchFinished(QNetworkReply *reply, const QUrl &cover_url);
100 
101   void SaveEmbeddedCover(const qint64 id, const QString &song_filename, const QString &cover_filename);
102   void SaveEmbeddedCover(const qint64 id, const QString &song_filename, const QImage &image);
103   void SaveEmbeddedCover(const qint64 id, const QString &song_filename, const QByteArray &image_data);
104   void SaveEmbeddedCover(const qint64 id, const QList<QUrl> &urls, const QImage &image);
105   void SaveEmbeddedCover(const qint64 id, const QList<QUrl> &urls, const QString &cover_filename);
106   void SaveEmbeddedCover(const qint64 id, const QList<QUrl> &urls, const QByteArray &image_data);
107 
108   void SaveEmbeddedArtFinished(const qint64 id, TagReaderReply *reply, const bool cleared);
109 
110  protected:
111 
112   struct Task {
TaskTask113     explicit Task() : id(0), state(State_None), type(AlbumCoverLoaderResult::Type_None), art_updated(false), redirects(0) {}
114 
115     AlbumCoverLoaderOptions options;
116 
117     quint64 id;
118     Song song;
119     AlbumCoverImageResult album_cover;
120     State state;
121     AlbumCoverLoaderResult::Type type;
122     bool art_updated;
123     int redirects;
124   };
125 
126   struct TryLoadResult {
127     explicit TryLoadResult(const bool _started_async = false,
128                            const bool _loaded_success = false,
129                            const AlbumCoverLoaderResult::Type _type = AlbumCoverLoaderResult::Type_None,
130                            const AlbumCoverImageResult &_album_cover = AlbumCoverImageResult()) :
started_asyncTryLoadResult131                            started_async(_started_async),
132                            loaded_success(_loaded_success),
133                            type(_type),
134                            album_cover(_album_cover) {}
135 
136     bool started_async;
137     bool loaded_success;
138 
139     AlbumCoverLoaderResult::Type type;
140     AlbumCoverImageResult album_cover;
141   };
142 
143   quint64 EnqueueTask(Task &task);
144   void ProcessTask(Task *task);
145   void NextState(Task *task);
146   TryLoadResult TryLoadImage(Task *task);
147 
148   bool stop_requested_;
149 
150   QMutex mutex_load_image_async_;
151   QMutex mutex_save_image_async_;
152   QQueue<Task> tasks_;
153   QHash<QNetworkReply*, Task> remote_tasks_;
154   quint64 load_image_async_id_;
155   quint64 save_image_async_id_;
156 
157   NetworkAccessManager *network_;
158 
159   static const int kMaxRedirects = 3;
160 
161   CollectionSettingsPage::SaveCoverType save_cover_type_;
162   CollectionSettingsPage::SaveCoverFilename save_cover_filename_;
163   QString cover_pattern_;
164   bool cover_overwrite_;
165   bool cover_lowercase_;
166   bool cover_replace_spaces_;
167 
168   QThread *original_thread_;
169 
170   QMultiMap<qint64, TagReaderReply*> tagreader_save_embedded_art_requests_;
171 
172 };
173 
174 #endif  // ALBUMCOVERLOADER_H
175