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 "browsemodel.h"
25 #include "roles.h"
26 #include "playqueuemodel.h"
27 #include "widgets/icons.h"
28 #include "gui/settings.h"
29 #include "mpd-interface/mpdconnection.h"
30 #include "mpd-interface/mpdstats.h"
31 #include "support/monoicon.h"
32 #include "support/utils.h"
33 #include <QMimeData>
34 
add(Item * i)35 void BrowseModel::FolderItem::add(Item *i)
36 {
37     i->setRow(children.count());
38     children.append(i);
39 }
40 
allEntries(bool allowPlaylists) const41 QStringList BrowseModel::FolderItem::allEntries(bool allowPlaylists) const
42 {
43     QStringList entries;
44     if (children.isEmpty()) {
45         entries << MPDConnection::constDirPrefix+path;
46     } else {
47         for (Item *i: children) {
48             if (i->isFolder()) {
49                 entries+=static_cast<FolderItem *>(i)->allEntries(allowPlaylists);
50             } else if (allowPlaylists || Song::Playlist!=static_cast<TrackItem *>(i)->getSong().type) {
51                 entries+=static_cast<TrackItem *>(i)->getSong().file;
52             }
53         }
54     }
55     return entries;
56 }
57 
BrowseModel(QObject * p)58 BrowseModel::BrowseModel(QObject *p)
59     : ActionModel(p)
60     , root(new FolderItem("/", nullptr))
61     , enabled(false)
62     , dbVersion(0)
63 {
64     icn=MonoIcon::icon(FontAwesome::server, Utils::monoIconColor());
65     connect(this, SIGNAL(listFolder(QString)), MPDConnection::self(), SLOT(listFolder(QString)));
66     folderIndex.insert(root->getPath(), root);
67 }
68 
name() const69 QString BrowseModel::name() const
70 {
71     return QLatin1String("mpdbrowse");
72 }
73 
title() const74 QString BrowseModel::title() const
75 {
76     return tr("Server Folders");
77 }
78 
descr() const79 QString BrowseModel::descr() const
80 {
81     return tr("MPD virtual file-system");
82 }
83 
clear()84 void BrowseModel::clear()
85 {
86     beginResetModel();
87     root->clear();
88     folderIndex.clear();
89     folderIndex.insert(root->getPath(), root);
90     endResetModel();
91 }
92 
load()93 void BrowseModel::load()
94 {
95     if (!enabled || (root && (root->getChildCount() || root->isFetching()))) {
96         return;
97     }
98     root->setState(FolderItem::State_Fetching);
99     emit listFolder(root->getPath());
100 }
101 
setEnabled(bool e)102 void BrowseModel::setEnabled(bool e)
103 {
104     if (e==enabled) {
105         return;
106     }
107     enabled=e;
108 
109     if (enabled) {
110         connect(MPDConnection::self(), SIGNAL(folderContents(QString,QStringList,QList<Song>)), this, SLOT(folderContents(QString,QStringList,QList<Song>)));
111         connect(MPDConnection::self(), SIGNAL(connectionChanged(MPDConnectionDetails)), this, SLOT(connectionChanged()));
112         connect(MPDConnection::self(), SIGNAL(statsUpdated(MPDStatsValues)), this, SLOT(statsUpdated(MPDStatsValues)));
113     } else {
114         disconnect(MPDConnection::self(), SIGNAL(folderContents(QString,QStringList,QList<Song>)), this, SLOT(folderContents(QString,QStringList,QList<Song>)));
115         disconnect(MPDConnection::self(), SIGNAL(connectionChanged(MPDConnectionDetails)), this, SLOT(connectionChanged()));
116         disconnect(MPDConnection::self(), SIGNAL(statsUpdated(MPDStatsValues)), this, SLOT(statsUpdated(MPDStatsValues)));
117         clear();
118     }
119 }
120 
flags(const QModelIndex & index) const121 Qt::ItemFlags BrowseModel::flags(const QModelIndex &index) const
122 {
123     if (index.isValid()) {
124         return Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled;
125     }
126     return Qt::ItemIsDropEnabled;
127 }
128 
index(int row,int column,const QModelIndex & parent) const129 QModelIndex BrowseModel::index(int row, int column, const QModelIndex &parent) const
130 {
131     if (!hasIndex(row, column, parent)) {
132         return QModelIndex();
133     }
134     const FolderItem * p = parent.isValid() ? static_cast<FolderItem *>(parent.internalPointer()) : root;
135     const Item * c = row<p->getChildCount() ? p->getChildren().at(row) : nullptr;
136     return c ? createIndex(row, column, (void *)c) : QModelIndex();
137 }
138 
parent(const QModelIndex & child) const139 QModelIndex BrowseModel::parent(const QModelIndex &child) const
140 {
141     if (!child.isValid()) {
142         return QModelIndex();
143     }
144 
145     const Item * const item = static_cast<Item *>(child.internalPointer());
146     Item * const parentItem = item->getParent();
147     if (parentItem == root || nullptr==parentItem) {
148         return QModelIndex();
149     }
150 
151     return createIndex(parentItem->getRow(), 0, parentItem);
152 }
153 
rowCount(const QModelIndex & parent) const154 int BrowseModel::rowCount(const QModelIndex &parent) const
155 {
156     if (parent.column() > 0) {
157         return 0;
158     }
159 
160     const FolderItem *parentItem=parent.isValid() ? static_cast<FolderItem *>(parent.internalPointer()) : root;
161     return parentItem ? parentItem->getChildCount() : 0;
162 }
163 
columnCount(const QModelIndex & parent) const164 int BrowseModel::columnCount(const QModelIndex &parent) const
165 {
166     Q_UNUSED(parent)
167     return 1;
168 }
169 
hasChildren(const QModelIndex & index) const170 bool BrowseModel::hasChildren(const QModelIndex &index) const
171 {
172     Item *item=toItem(index);
173     return item && item->isFolder();
174 }
175 
canFetchMore(const QModelIndex & index) const176 bool BrowseModel::canFetchMore(const QModelIndex &index) const
177 {
178     if (index.isValid()) {
179         Item *item = toItem(index);
180         return item && item->isFolder() && static_cast<FolderItem *>(item)->canFetchMore();
181     } else {
182         return false;
183     }
184 }
185 
fetchMore(const QModelIndex & index)186 void BrowseModel::fetchMore(const QModelIndex &index)
187 {
188     if (!index.isValid()) {
189         return;
190     }
191 
192     FolderItem *item = static_cast<FolderItem *>(toItem(index));
193     if (!item->isFetching()) {
194         item->setState(FolderItem::State_Fetching);
195         emit listFolder(item->getPath());
196     }
197 }
198 
data(const QModelIndex & index,int role) const199 QVariant BrowseModel::data(const QModelIndex &index, int role) const
200 {
201     if (!index.isValid()) {
202         switch (role) {
203         case Cantata::Role_TitleText:
204             return title();
205         case Cantata::Role_SubText:
206             return descr();
207         case Qt::DecorationRole:
208             return icon();
209         }
210         return QVariant();
211     }
212 
213     Item *item = static_cast<Item *>(index.internalPointer());
214 
215     switch (role) {
216     case Qt::DecorationRole:
217         if (item->isFolder()) {
218             return Icons::self()->folderListIcon;
219         } else {
220             TrackItem *track = static_cast<TrackItem *>(item);
221             return Song::Playlist==track->getSong().type ? Icons::self()->playlistListIcon : Icons::self()->audioListIcon;
222         }
223         break;
224     case Cantata::Role_BriefMainText:
225     case Cantata::Role_MainText:
226     case Qt::DisplayRole:
227         if (!item->isFolder()) {
228             TrackItem *track = static_cast<TrackItem *>(item);
229             if (Song::Playlist==track->getSong().type) {
230                 return Utils::getFile(track->getSong().file);
231             }
232         }
233         return item->getText();
234     case Qt::ToolTipRole:
235         if (!Settings::self()->infoTooltips()) {
236             return QVariant();
237         }
238         if (item->isFolder() || Song::Playlist==static_cast<TrackItem *>(item)->getSong().type) {
239             return static_cast<FolderItem *>(item)->getPath();
240         }
241         return static_cast<TrackItem *>(item)->getSong().toolTip();
242     case Cantata::Role_SubText:
243         if (!item->isFolder()) {
244             TrackItem *track = static_cast<TrackItem *>(item);
245             if (Song::Playlist==track->getSong().type) {
246                 return track->getSong().isCueFile() ? tr("Cue Sheet") : tr("Playlist");
247             }
248         }
249         return item->getSubText();
250     case Cantata::Role_TitleText:
251         return item->getText();
252     case Cantata::Role_TitleActions:
253         return item->isFolder();
254     default:
255         break;
256     }
257     return ActionModel::data(index, role);
258 }
259 
mimeData(const QModelIndexList & indexes) const260 QMimeData * BrowseModel::mimeData(const QModelIndexList &indexes) const
261 {
262     QMimeData *mimeData = new QMimeData();
263     QStringList files;
264     for (const QModelIndex &idx: indexes) {
265         Item *item=toItem(idx);
266         if (item) {
267             if (item->isFolder()) {
268                 files+=static_cast<FolderItem *>(item)->allEntries(false);
269             } else {
270                 files.append(static_cast<TrackItem *>(item)->getSong().file);
271             }
272         }
273     }
274 
275     PlayQueueModel::encode(*mimeData, PlayQueueModel::constFileNameMimeType, files);
276     return mimeData;
277 }
278 
songs(const QModelIndexList & indexes,bool allowPlaylists) const279 QList<Song> BrowseModel::songs(const QModelIndexList &indexes, bool allowPlaylists) const
280 {
281     QList<Song> songList;
282     for (const QModelIndex &idx: indexes) {
283         Item *item=toItem(idx);
284         if (item && !item->isFolder() && (allowPlaylists || Song::Standard==static_cast<TrackItem *>(item)->getSong().type)) {
285             songList.append(static_cast<TrackItem *>(item)->getSong());
286         }
287     }
288     return songList;
289 }
290 
connectionChanged()291 void BrowseModel::connectionChanged()
292 {
293     clear();
294     if (!root->isFetching() && MPDConnection::self()->isConnected()) {
295         dbVersion=0;
296         root->setState(FolderItem::State_Fetching);
297         emit listFolder(root->getPath());
298     }
299 }
300 
statsUpdated(const MPDStatsValues & stats)301 void BrowseModel::statsUpdated(const MPDStatsValues &stats)
302 {
303     if (stats.dbUpdate!=dbVersion) {
304         if (0!=dbVersion) {
305             connectionChanged();
306         }
307         dbVersion=stats.dbUpdate;
308     }
309 }
310 
folderContents(const QString & path,const QStringList & folders,const QList<Song> & songs)311 void BrowseModel::folderContents(const QString &path, const QStringList &folders, const QList<Song> &songs)
312 {
313     QMap<QString, FolderItem *>::Iterator it=folderIndex.find(path);
314     if (it==folderIndex.end() || 0!=it.value()->getChildCount()) {
315         return;
316     }
317 
318 	if (folders.count() + songs.count()) {
319 		QModelIndex idx=it.value()==root ? QModelIndex() : createIndex(it.value()->getRow(), 0, it.value());
320 
321 		beginInsertRows(idx, 0, folders.count() + songs.count() - 1);
322         for (const QString &folder: folders) {
323 			FolderItem *item = new FolderItem(folder.split("/", QString::SkipEmptyParts).last(), folder, it.value());
324 			it.value()->add(item);
325 			folderIndex.insert(folder, item);
326 		}
327         for (const Song &song: songs) {
328 			it.value()->add(new TrackItem(song, it.value()));
329 		}
330 		it.value()->setState(FolderItem::State_Fetched);
331 		endInsertRows();
332 	}
333 }
334 
335 #include "moc_browsemodel.cpp"
336