1 /*
2  * Strawberry Music Player
3  * This file was part of Clementine.
4  * Copyright 2010, David Sansome <me@davidsansome.com>
5  * Copyright 2018-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 COLLECTIONMODEL_H
23 #define COLLECTIONMODEL_H
24 
25 #include "config.h"
26 
27 #include <QtGlobal>
28 #include <QObject>
29 #include <QAbstractItemModel>
30 #include <QFuture>
31 #include <QDataStream>
32 #include <QMetaType>
33 #include <QPair>
34 #include <QSet>
35 #include <QList>
36 #include <QMap>
37 #include <QVariant>
38 #include <QString>
39 #include <QStringList>
40 #include <QUrl>
41 #include <QImage>
42 #include <QIcon>
43 #include <QPixmap>
44 #include <QNetworkDiskCache>
45 
46 #include "core/simpletreemodel.h"
47 #include "core/song.h"
48 #include "covermanager/albumcoverloader.h"
49 #include "collectionquery.h"
50 #include "collectionitem.h"
51 #include "sqlrow.h"
52 #include "covermanager/albumcoverloaderoptions.h"
53 
54 class QSettings;
55 
56 class Application;
57 class CollectionBackend;
58 class CollectionDirectoryModel;
59 
60 class CollectionModel : public SimpleTreeModel<CollectionItem> {
61   Q_OBJECT
62 
63  public:
64   explicit CollectionModel(CollectionBackend *backend, Application *app, QObject *parent = nullptr);
65   ~CollectionModel() override;
66 
67   static const char *kSavedGroupingsSettingsGroup;
68 
69   static const int kPrettyCoverSize;
70   static const char *kPixmapDiskCacheDir;
71 
72   enum Role {
73     Role_Type = Qt::UserRole + 1,
74     Role_ContainerType,
75     Role_SortText,
76     Role_Key,
77     Role_Artist,
78     Role_IsDivider,
79     Role_Editable,
80     LastRole
81   };
82 
83   // These values get saved in QSettings - don't change them
84   enum GroupBy {
85     GroupBy_None = 0,
86     GroupBy_AlbumArtist = 1,
87     GroupBy_Artist = 2,
88     GroupBy_Album = 3,
89     GroupBy_AlbumDisc = 4,
90     GroupBy_YearAlbum = 5,
91     GroupBy_YearAlbumDisc = 6,
92     GroupBy_OriginalYearAlbum = 7,
93     GroupBy_OriginalYearAlbumDisc = 8,
94     GroupBy_Disc = 9,
95     GroupBy_Year = 10,
96     GroupBy_OriginalYear = 11,
97     GroupBy_Genre = 12,
98     GroupBy_Composer = 13,
99     GroupBy_Performer = 14,
100     GroupBy_Grouping = 15,
101     GroupBy_FileType = 16,
102     GroupBy_Format = 17,
103     GroupBy_Samplerate = 18,
104     GroupBy_Bitdepth = 19,
105     GroupBy_Bitrate = 20,
106     GroupByCount = 21,
107   };
108   Q_ENUM(GroupBy)
109 
110   struct Grouping {
111     explicit Grouping(GroupBy f = GroupBy_None, GroupBy s = GroupBy_None, GroupBy t = GroupBy_None)
firstGrouping112         : first(f), second(s), third(t) {}
113 
114     GroupBy first;
115     GroupBy second;
116     GroupBy third;
117 
118     const GroupBy &operator[](const int i) const;
119     GroupBy &operator[](const int i);
120     bool operator==(const Grouping other) const {
121       return first == other.first && second == other.second && third == other.third;
122     }
123     bool operator!=(const Grouping other) const { return !(*this == other); }
124   };
125 
126   struct QueryResult {
QueryResultQueryResult127     QueryResult() : create_va(false) {}
128 
129     SqlRowList rows;
130     bool create_va;
131   };
132 
backend()133   CollectionBackend *backend() const { return backend_; }
directory_model()134   CollectionDirectoryModel *directory_model() const { return dir_model_; }
135 
136   // Call before Init()
set_show_various_artists(const bool show_various_artists)137   void set_show_various_artists(const bool show_various_artists) { show_various_artists_ = show_various_artists; }
138 
139   // Get information about the collection
140   void GetChildSongs(CollectionItem *item, QList<QUrl> *urls, SongList *songs, QSet<int> *song_ids) const;
141   SongList GetChildSongs(const QModelIndex &idx) const;
142   SongList GetChildSongs(const QModelIndexList &indexes) const;
143 
144   // Might be accurate
total_song_count()145   int total_song_count() const { return total_song_count_; }
total_artist_count()146   int total_artist_count() const { return total_artist_count_; }
total_album_count()147   int total_album_count() const { return total_album_count_; }
148 
149   // QAbstractItemModel
150   QVariant data(const QModelIndex &idx, const int role = Qt::DisplayRole) const override;
151   Qt::ItemFlags flags(const QModelIndex &idx) const override;
152   QStringList mimeTypes() const override;
153   QMimeData *mimeData(const QModelIndexList &indexes) const override;
154   bool canFetchMore(const QModelIndex &parent) const override;
155 
156   // Whether or not to use album cover art, if it exists, in the collection view
157   void set_pretty_covers(const bool use_pretty_covers);
use_pretty_covers()158   bool use_pretty_covers() const { return use_pretty_covers_; }
159 
160   // Whether or not to show letters heading in the collection view
161   void set_show_dividers(const bool show_dividers);
162 
163   // Save the current grouping
164   void SaveGrouping(const QString &name);
165 
166   // Reload settings.
167   void ReloadSettings();
168 
169   // Utility functions for manipulating text
170   static QString TextOrUnknown(const QString &text);
171   static QString PrettyYearAlbum(const int year, const QString &album);
172   static QString PrettyAlbumDisc(const QString &album, const int disc);
173   static QString PrettyYearAlbumDisc(const int year, const QString &album, const int disc);
174   static QString PrettyDisc(const int disc);
175   static QString SortText(QString text);
176   static QString SortTextForNumber(const int number);
177   static QString SortTextForArtist(QString artist);
178   static QString SortTextForSong(const Song &song);
179   static QString SortTextForYear(const int year);
180   static QString SortTextForBitrate(const int bitrate);
181 
icon_cache_disk_size()182   quint64 icon_cache_disk_size() { return sIconCache->cacheSize(); }
183 
IsArtistGroupBy(const GroupBy group_by)184   static bool IsArtistGroupBy(const GroupBy group_by) {
185     return group_by == CollectionModel::GroupBy_Artist || group_by == CollectionModel::GroupBy_AlbumArtist;
186   }
IsAlbumGroupBy(const GroupBy group_by)187   static bool IsAlbumGroupBy(const GroupBy group_by) { return group_by == GroupBy_Album || group_by == GroupBy_YearAlbum || group_by == GroupBy_AlbumDisc || group_by == GroupBy_YearAlbumDisc || group_by == GroupBy_OriginalYearAlbum || group_by == GroupBy_OriginalYearAlbumDisc; }
188 
set_use_lazy_loading(const bool value)189   void set_use_lazy_loading(const bool value) { use_lazy_loading_ = value; }
190 
container_nodes(const int i)191   QMap<QString, CollectionItem*> container_nodes(const int i) { return container_nodes_[i]; }
song_nodes()192   QList<CollectionItem*> song_nodes() const { return song_nodes_.values(); }
divider_nodes_count()193   int divider_nodes_count() const { return divider_nodes_.count(); }
194 
195   void ExpandAll(CollectionItem *item = nullptr) const;
196 
GetGroupBy()197   const CollectionModel::Grouping GetGroupBy() const { return group_by_; }
198   void SetGroupBy(const CollectionModel::Grouping g);
199 
200   static QString ContainerKey(const GroupBy type, const Song &song);
201 
202  signals:
203   void TotalSongCountUpdated(int count);
204   void TotalArtistCountUpdated(int count);
205   void TotalAlbumCountUpdated(int count);
206   void GroupingChanged(CollectionModel::Grouping g);
207 
208  public slots:
209   void SetFilterAge(const int age);
210   void SetFilterText(const QString &text);
211   void SetFilterQueryMode(QueryOptions::QueryMode query_mode);
212 
213   void Init(const bool async = true);
214   void Reset();
215   void ResetAsync();
216 
217   void SongsDiscovered(const SongList &songs);
218 
219  protected:
LazyPopulate(CollectionItem * item)220   void LazyPopulate(CollectionItem *item) override { LazyPopulate(item, true); }
221   void LazyPopulate(CollectionItem *parent, const bool signal);
222 
223  private slots:
224   // From CollectionBackend
225   void SongsDeleted(const SongList &songs);
226   void SongsSlightlyChanged(const SongList &songs);
227   void TotalSongCountUpdatedSlot(const int count);
228   void TotalArtistCountUpdatedSlot(const int count);
229   void TotalAlbumCountUpdatedSlot(const int count);
230   static void ClearDiskCache();
231 
232   // Called after ResetAsync
233   void ResetAsyncQueryFinished();
234 
235   void AlbumCoverLoaded(const quint64 id, const AlbumCoverLoaderResult &result);
236 
237  private:
238   // Provides some optimisations for loading the list of items in the root.
239   // This gets called a lot when filtering the playlist, so it's nice to be able to do it in a background thread.
240   QueryResult RunQuery(CollectionItem *parent);
241   void PostQuery(CollectionItem *parent, const QueryResult &result, const bool signal);
242 
243   bool HasCompilations(const QSqlDatabase &db, const CollectionQuery &query);
244 
245   void BeginReset();
246 
247   // Functions for working with queries and creating items.
248   // When the model is reset or when a node is lazy-loaded the Collection constructs a database query to populate the items.
249   // Filters are added for each parent item, restricting the songs returned to a particular album or artist for example.
250   static void InitQuery(const GroupBy type, CollectionQuery *q);
251   static void FilterQuery(const GroupBy type, CollectionItem *item, CollectionQuery *q);
252 
253   // Items can be created either from a query that's been run to populate a node, or by a spontaneous SongsDiscovered emission from the backend.
254   CollectionItem *ItemFromQuery(const GroupBy type, const bool signal, const bool create_divider, CollectionItem *parent, const SqlRow &row, const int container_level);
255   CollectionItem *ItemFromSong(const GroupBy type, const bool signal, const bool create_divider, CollectionItem *parent, const Song &s, const int container_level);
256 
257   // The "Various Artists" node is an annoying special case.
258   CollectionItem *CreateCompilationArtistNode(const bool signal, CollectionItem *parent);
259 
260   // Helpers for ItemFromQuery and ItemFromSong
261   CollectionItem *InitItem(const GroupBy type, const bool signal, CollectionItem *parent, const int container_level);
262   void FinishItem(const GroupBy type, const bool signal, const bool create_divider, CollectionItem *parent, CollectionItem *item);
263 
264   static QString DividerKey(const GroupBy type, CollectionItem *item);
265   static QString DividerDisplayText(const GroupBy type, const QString &key);
266 
267   // Helpers
IsCompilationArtistNode(const CollectionItem * node)268   static bool IsCompilationArtistNode(const CollectionItem *node) { return node == node->parent->compilation_artist_node_; }
269   QString AlbumIconPixmapCacheKey(const QModelIndex &idx) const;
270   QVariant AlbumIcon(const QModelIndex &idx);
271   QVariant data(const CollectionItem *item, const int role) const;
272   bool CompareItems(const CollectionItem *a, const CollectionItem *b) const;
273   static qint64 MaximumCacheSize(QSettings *s, const char *size_id, const char *size_unit_id, const qint64 cache_size_default);
274 
275  private:
276   CollectionBackend *backend_;
277   Application *app_;
278   CollectionDirectoryModel *dir_model_;
279   bool show_various_artists_;
280 
281   int total_song_count_;
282   int total_artist_count_;
283   int total_album_count_;
284 
285   QueryOptions query_options_;
286   Grouping group_by_;
287 
288   // Keyed on database ID
289   QMap<int, CollectionItem*> song_nodes_;
290 
291   // Keyed on whatever the key is for that level - artist, album, year, etc.
292   QMap<QString, CollectionItem*> container_nodes_[3];
293 
294   // Keyed on a letter, a year, a century, etc.
295   QMap<QString, CollectionItem*> divider_nodes_;
296 
297   QIcon artist_icon_;
298   QIcon album_icon_;
299   // Used as a generic icon to show when no cover art is found, fixed to the same size as the artwork (32x32)
300   QPixmap no_cover_icon_;
301 
302   static QNetworkDiskCache *sIconCache;
303 
304   int init_task_id_;
305 
306   bool use_pretty_covers_;
307   bool show_dividers_;
308   bool use_disk_cache_;
309   bool use_lazy_loading_;
310 
311   AlbumCoverLoaderOptions cover_loader_options_;
312 
313   typedef QPair<CollectionItem*, QString> ItemAndCacheKey;
314   QMap<quint64, ItemAndCacheKey> pending_art_;
315   QSet<QString> pending_cache_keys_;
316 };
317 
318 Q_DECLARE_METATYPE(CollectionModel::Grouping)
319 
320 QDataStream &operator<<(QDataStream &s, const CollectionModel::Grouping g);
321 QDataStream &operator>>(QDataStream &s, CollectionModel::Grouping &g);
322 
323 #endif  // COLLECTIONMODEL_H
324