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