1 /*
2 * Cantata
3 *
4 * Copyright (c) 2017-2020 Craig Drummond <craig.p.drummond@gmail.com>
5 *
6 * ----
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; see the file COPYING. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24 #include "mpdlibrarymodel.h"
25 #include "support/globalstatic.h"
26 #include "support/configuration.h"
27 #include "db/mpdlibrarydb.h"
28 #include "gui/settings.h"
29 #include "gui/covers.h"
30 #include "roles.h"
31 #include "kcategorizedview/kcategorizedsortfilterproxymodel.h"
32 #include <QTimer>
33 #include <QDebug>
34
GLOBAL_STATIC(MpdLibraryModel,instance)35 GLOBAL_STATIC(MpdLibraryModel, instance)
36 #define COVERS_DBUG if (Covers::verboseDebugEnabled()) qWarning() << metaObject()->className() << QThread::currentThread()->objectName() << __FUNCTION__
37
38 MpdLibraryModel::MpdLibraryModel()
39 : SqlLibraryModel(new MpdLibraryDb(nullptr), nullptr)
40 , showArtistImages(false)
41 {
42 connect(Covers::self(), SIGNAL(cover(Song,QImage,QString)), this, SLOT(cover(Song,QImage,QString)));
43 connect(Covers::self(), SIGNAL(coverUpdated(Song,QImage,QString)), this, SLOT(coverUpdated(Song,QImage,QString)));
44 connect(Covers::self(), SIGNAL(artistImage(Song,QImage,QString)), this, SLOT(artistImage(Song,QImage,QString)));
45 connect(Covers::self(), SIGNAL(composerImage(Song,QImage,QString)), this, SLOT(artistImage(Song,QImage,QString)));
46 if (MPDConnection::self()->isConnected()) {
47 static_cast<MpdLibraryDb *>(db)->connectionChanged(MPDConnection::self()->getDetails());
48 }
49 }
50
data(const QModelIndex & index,int role) const51 QVariant MpdLibraryModel::data(const QModelIndex &index, int role) const
52 {
53 if (!index.isValid()) {
54 return QVariant();
55 }
56
57 switch (role) {
58 case Cantata::Role_ListImage: {
59 Item *item = static_cast<Item *>(index.internalPointer());
60 return T_Album==item->getType() || (T_Artist==item->getType() && useArtistImages());
61 }
62 case Cantata::Role_GridCoverSong:
63 case Cantata::Role_CoverSong: {
64 QVariant v;
65 Item *item = static_cast<Item *>(index.internalPointer());
66 COVERS_DBUG << index.data().toString() << item->getType() << role;
67 switch (item->getType()) {
68 case T_Album:
69 if (item->getSong().isEmpty()) {
70 Song song=static_cast<MpdLibraryDb *>(db)->getCoverSong(T_Album==topLevel() ? static_cast<AlbumItem *>(item)->getArtistId() : item->getParent()->getId(), item->getId());
71 if (song.isEmpty()) {
72 COVERS_DBUG << "Failed to locate album cover song";
73 return QVariant();
74 }
75 item->setSong(song);
76 COVERS_DBUG << "Set album cover song" << song.albumArtist() << song.album << song.file;
77 if (T_Genre==topLevel()) {
78 song.addGenre(item->getParent()->getParent()->getId());
79 }
80 }
81 v.setValue<Song>(item->getSong());
82 break;
83 case T_Artist:
84 if (!showArtistImages && Cantata::Role_CoverSong==role) {
85 COVERS_DBUG << "Not showing artist images";
86 return QVariant();
87 }
88 if (item->getSong().isEmpty()) {
89 Song song=static_cast<MpdLibraryDb *>(db)->getCoverSong(item->getId());
90 if (song.isEmpty()) {
91 COVERS_DBUG << "Failed to locate artist cover song";
92 return QVariant();
93 }
94 COVERS_DBUG << "Set artist cover song" << song.albumArtist() << song.album << song.file;
95 if (song.useComposer()) {
96 song.setComposerImageRequest();
97 } else {
98 song.setArtistImageRequest();
99 }
100 if (T_Genre==topLevel()) {
101 song.addGenre(item->getParent()->getId());
102 }
103 item->setSong(song);
104 }
105 v.setValue<Song>(item->getSong());
106 break;
107 default:
108 break;
109 }
110 return v;
111 }
112 case KCategorizedSortFilterProxyModel::CategoryDisplayRole: {
113 Item *item = static_cast<Item *>(index.internalPointer());
114 if (T_Album==item->getType()) {
115 int cat = static_cast<AlbumItem *>(item)->getCategory();
116 return cat>=0 && cat<categories.length() ? categories.at(cat) : "";
117 }
118 break;
119 }
120 case KCategorizedSortFilterProxyModel::CategorySortRole: {
121 Item *item = static_cast<Item *>(index.internalPointer());
122 if (T_Album==item->getType()) {
123 return (long long)static_cast<AlbumItem *>(item)->getCategory();
124 }
125 break;
126 }
127 }
128 return SqlLibraryModel::data(index, role);
129 }
130
setUseArtistImages(bool u)131 void MpdLibraryModel::setUseArtistImages(bool u)
132 {
133 if (u!=showArtistImages) {
134 showArtistImages=u;
135 switch(topLevel()) {
136 case T_Genre: {
137 for (const Item *g: root->getChildren()) {
138 const CollectionItem *genre=static_cast<const CollectionItem *>(g);
139 if (genre->getChildCount()) {
140 QModelIndex idx=index(genre->getRow(), 0, QModelIndex());
141 emit dataChanged(index(0, 0, idx), index(genre->getChildCount()-1, 0, idx));
142 }
143 }
144 break;
145 }
146 case T_Artist:
147 if (root->getChildCount()) {
148 emit dataChanged(index(0, 0, QModelIndex()), index(root->getChildCount()-1, 0, QModelIndex()));
149 }
150 break;
151 default:
152 break;
153 }
154 }
155 }
156
157 static QLatin1String constUseArtistImagesKey("artistImages");
158
load(Configuration & config)159 void MpdLibraryModel::load(Configuration &config)
160 {
161 showArtistImages=config.get(constUseArtistImagesKey, showArtistImages);
162 SqlLibraryModel::load(config);
163 }
164
save(Configuration & config)165 void MpdLibraryModel::save(Configuration &config)
166 {
167 config.set(constUseArtistImagesKey, showArtistImages);
168 SqlLibraryModel::save(config);
169 }
170
listSongs()171 void MpdLibraryModel::listSongs()
172 {
173 listingTotal=db->trackCount();
174 listingCurrent=0;
175 if (listingTotal>0) {
176 QTimer::singleShot(0, this, SLOT(listNextChunk()));
177 } else {
178 emit songListing(QList<Song>(), 100.0);
179 }
180 }
181
cancelListing()182 void MpdLibraryModel::cancelListing()
183 {
184 listingTotal=0;
185 }
186
187 static const int constMaxSongsInList=1000;
188
listNextChunk()189 void MpdLibraryModel::listNextChunk()
190 {
191 if (listingTotal<=0) {
192 return;
193 }
194
195 QList<Song> songs=db->getTracks(listingCurrent, constMaxSongsInList);
196 listingCurrent+=songs.count();
197 emit songListing(songs, (listingCurrent*100.0)/(listingTotal*1.0));
198 if (songs.count()>0) {
199 if (listingCurrent==listingTotal) {
200 emit songListing(QList<Song>(), 100.0);
201 } else {
202 QTimer::singleShot(0, this, SLOT(listNextChunk()));
203 }
204 }
205 }
206
cover(const Song & song,const QImage & img,const QString & file)207 void MpdLibraryModel::cover(const Song &song, const QImage &img, const QString &file)
208 {
209 if (file.isEmpty() || img.isNull() || song.isFromOnlineService()) {
210 return;
211 }
212 switch(topLevel()) {
213 case T_Genre: {
214 const Item *genre=root ? root->getChild(song.genres[0]) : nullptr;
215 if (genre) {
216 const Item *artist=static_cast<const CollectionItem *>(genre)->getChild(song.albumArtistOrComposer());
217 if (artist) {
218 const Item *album=static_cast<const CollectionItem *>(artist)->getChild(song.albumId());
219 if (album) {
220 QModelIndex idx=index(album->getRow(), 0, index(artist->getRow(), 0, index(genre->getRow(), 0, QModelIndex())));
221 emit dataChanged(idx, idx);
222 }
223 }
224 }
225 break;
226 }
227 case T_Artist: {
228 const Item *artist=root ? root->getChild(song.albumArtistOrComposer()) : nullptr;
229 if (artist) {
230 const Item *album=static_cast<const CollectionItem *>(artist)->getChild(song.albumId());
231 if (album) {
232 QModelIndex idx=index(album->getRow(), 0, index(artist->getRow(), 0, QModelIndex()));
233 emit dataChanged(idx, idx);
234 }
235 }
236 break;
237 }
238 case T_Album: {
239 const Item *album=root ? root->getChild(song.albumArtistOrComposer()+song.albumId()) : nullptr;
240 if (album) {
241 QModelIndex idx=index(album->getRow(), 0, QModelIndex());
242 emit dataChanged(idx, idx);
243 }
244 break;
245 }
246 default:
247 break;
248 }
249 }
250
coverUpdated(const Song & song,const QImage & img,const QString & file)251 void MpdLibraryModel::coverUpdated(const Song &song, const QImage &img, const QString &file)
252 {
253 if (file.isEmpty() || img.isNull() || (T_Album==topLevel() && (song.isArtistImageRequest() || song.isComposerImageRequest()))) {
254 return;
255 }
256 cover(song, img, file);
257 }
258
artistImage(const Song & song,const QImage & img,const QString & file)259 void MpdLibraryModel::artistImage(const Song &song, const QImage &img, const QString &file)
260 {
261 if (file.isEmpty() || img.isNull() || T_Album==topLevel()) {
262 return;
263 }
264 switch(topLevel()) {
265 case T_Genre: {
266 const Item *genre=root ? root->getChild(song.genres[0]) : nullptr;
267 if (genre) {
268 const Item *artist=static_cast<const CollectionItem *>(genre)->getChild(song.albumArtistOrComposer());
269 if (artist) {
270 QModelIndex idx=index(artist->getRow(), 0, index(genre->getRow(), 0, QModelIndex()));
271 emit dataChanged(idx, idx);
272 }
273 }
274 break;
275 }
276 case T_Artist: {
277 const Item *artist=root ? root->getChild(song.albumArtistOrComposer()) : nullptr;
278 if (artist) {
279 QModelIndex idx=index(artist->getRow(), 0, QModelIndex());
280 emit dataChanged(idx, idx);
281 }
282 break;
283 }
284 default:
285 break;
286 }
287 }
288
289 #include "moc_mpdlibrarymodel.cpp"
290