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