1 #include <QFileInfo>
2 #include <QMutableListIterator>
3 #include <cmath>
4 #include "playlist.h"
5 
6 
7 
8 static char keyContents[] = "contents";
9 static char keyCreated[] = "created";
10 static char keyItems[] = "items";
11 static char keyMetadata[] = "metadata";
12 static char keyNowPlaying[] = "nowplaying";
13 static char keyShuffle[] = "shuffle";
14 static char keyTitle[] = "title";
15 static char keyUrl[] = "url";
16 static char keyUuid[] = "uuid";
17 
18 
19 
Item(QUrl url)20 Item::Item(QUrl url)
21 {
22     static int globalCounter = 0;
23     setUrl(url);
24     setUuid(QUuid::createUuid());
25     setOriginalPosition(globalCounter++);   // Preserve order on first restore
26     setQueuePosition(0);
27     setExtraPlayTimes(0);
28     setHidden(false);
29 }
30 
uuid() const31 QUuid Item::uuid() const
32 {
33     return uuid_;
34 }
35 
setUuid(const QUuid & uuid)36 void Item::setUuid(const QUuid &uuid)
37 {
38     uuid_ = uuid;
39 }
40 
playlistUuid() const41 QUuid Item::playlistUuid() const
42 {
43     return playlistUuid_;
44 }
45 
setPlaylistUuid(const QUuid & uuid)46 void Item::setPlaylistUuid(const QUuid &uuid)
47 {
48     playlistUuid_ = uuid;
49 }
50 
url() const51 QUrl Item::url() const
52 {
53     return url_;
54 }
55 
setUrl(const QUrl & url)56 void Item::setUrl(const QUrl &url)
57 {
58     url_ = url;
59 }
60 
metadata() const61 const QVariantMap &Item::metadata() const
62 {
63     return metadata_;
64 }
65 
setMetadata(const QVariantMap & qvm)66 void Item::setMetadata(const QVariantMap &qvm)
67 {
68     metadata_ = qvm;
69 }
70 
originalPosition()71 int Item::originalPosition()
72 {
73     return originalPosition_;
74 }
75 
setOriginalPosition(int i)76 void Item::setOriginalPosition(int i)
77 {
78     originalPosition_ = i;
79 }
80 
queuePosition() const81 int Item::queuePosition() const
82 {
83     return queuePosition_;
84 }
85 
setQueuePosition(int num)86 void Item::setQueuePosition(int num)
87 {
88     queuePosition_ = num;
89 }
90 
decQueuePosition()91 void Item::decQueuePosition()
92 {
93     if (queuePosition_ > 0)
94         queuePosition_--;
95 }
96 
extraPlayTimes() const97 int Item::extraPlayTimes() const
98 {
99     return extraPlayTimes_;
100 }
101 
setExtraPlayTimes(int amount)102 void Item::setExtraPlayTimes(int amount)
103 {
104     extraPlayTimes_ = std::max(amount, 0);
105 }
106 
deltaExtraPlayTimes(int delta)107 void Item::deltaExtraPlayTimes(int delta)
108 {
109     extraPlayTimes_ = std::max(0, extraPlayTimes_ + delta);
110 }
111 
incExtraPlayTimes()112 int Item::incExtraPlayTimes()
113 {
114     return ++extraPlayTimes_ > 0 ? extraPlayTimes_ : 0;
115 }
116 
decExtraPlayTimes()117 int Item::decExtraPlayTimes()
118 {
119     return extraPlayTimes_ > 0 ? --extraPlayTimes_ : 0;
120 }
121 
setHidden(bool yes)122 void Item::setHidden(bool yes)
123 {
124     hidden_ = yes;
125 }
126 
hidden()127 bool Item::hidden()
128 {
129     return hidden_;
130 }
131 
toDisplayString() const132 QString Item::toDisplayString() const
133 {
134     if (url().isLocalFile())
135         return QFileInfo(url().toLocalFile()).completeBaseName();
136     return url().toDisplayString(QUrl::PrettyDecoded);
137 }
138 
toString() const139 QString Item::toString() const
140 {
141     return url_.isLocalFile() ? url_.toLocalFile() : url_.url();
142 }
143 
fromString(QString input)144 void Item::fromString(QString input)
145 {
146     setUuid(QUuid::createUuid());
147     setUrl(QUrl::fromUserInput(input));
148 }
149 
toVMap() const150 QVariantMap Item::toVMap() const
151 {
152     QVariantMap v;
153     v.insert(keyUrl, url());
154     v.insert(keyUuid, uuid());
155     v.insert(keyMetadata, metadata());
156     return v;
157 }
158 
fromVMap(const QVariantMap & qvm)159 void Item::fromVMap(const QVariantMap &qvm)
160 {
161     url_ = qvm.contains(keyUrl) ? qvm.value(keyUrl).toUrl() : QUrl();
162     uuid_ = qvm.contains(keyUuid) ? qvm.value(keyUuid).toUuid() : QUuid::createUuid();
163     metadata_ = qvm.contains(keyMetadata) ? qvm.value(keyMetadata).toMap() : QVariantMap();
164 }
165 
166 QSharedPointer<ItemCollection> ItemCollection::collection;
167 
ItemCollection()168 ItemCollection::ItemCollection() : QObject(nullptr)
169 {
170 
171 }
172 
~ItemCollection()173 ItemCollection::~ItemCollection()
174 {
175 
176 }
177 
getSingleton()178 QSharedPointer<ItemCollection> ItemCollection::getSingleton()
179 {
180     if (collection.isNull())
181         collection.reset(new ItemCollection());
182     return collection;
183 }
184 
addItem(const QUrl url)185 QSharedPointer<Item> ItemCollection::addItem(const QUrl url)
186 {
187     auto item = QSharedPointer<Item>::create(url);
188     items.insert(item->uuid(), item);
189     return item;
190 }
191 
addItem(const QUuid & itemUuid,const QUrl & url)192 QSharedPointer<Item> ItemCollection::addItem(const QUuid &itemUuid, const QUrl &url)
193 {
194     QSharedPointer<Item> item(new Item(url));
195     item->setUuid(itemUuid);
196     items.insert(item->uuid(), item);
197     return item;
198 }
199 
itemOf(const QUuid & itemUuid)200 QSharedPointer<Item> ItemCollection::itemOf(const QUuid &itemUuid)
201 {
202     return items.value(itemUuid, QSharedPointer<Item>());
203 }
204 
removeItem(const QUuid & itemUuid)205 void ItemCollection::removeItem(const QUuid &itemUuid)
206 {
207     items.remove(itemUuid);
208 }
209 
storeItem(const QSharedPointer<Item> & item)210 void ItemCollection::storeItem(const QSharedPointer<Item> &item)
211 {
212     items.insert(item->uuid(), item);
213 }
214 
215 
216 
Playlist(const QString & title)217 Playlist::Playlist(const QString &title)
218 {
219     setUuid(QUuid::createUuid());
220     setTitle(title);
221     setCreated(QDateTime::currentDateTimeUtc());
222 }
223 
~Playlist()224 Playlist::~Playlist()
225 {
226     QWriteLocker locker(&listLock);
227     items.clear();
228 }
229 
addItem(const QUrl & url)230 QSharedPointer<Item> Playlist::addItem(const QUrl &url)
231 {
232     QWriteLocker locker(&listLock);
233     QSharedPointer<Item> i(ItemCollection::getSingleton()->addItem(url));
234     i->setPlaylistUuid(uuid_);
235     items.append(i);
236     itemsByUuid.insert(i->uuid(), i);
237     return i;
238 }
239 
addItem(const QUuid & uuid,const QUrl & url)240 QSharedPointer<Item> Playlist::addItem(const QUuid &uuid, const QUrl &url)
241 {
242     QWriteLocker locker(&listLock);
243     QSharedPointer<Item> i(ItemCollection::getSingleton()->addItem(uuid, url));
244     i->setPlaylistUuid(uuid_);
245     i->setUrl(url);
246     i->setUuid(uuid);
247     items.append(i);
248     itemsByUuid.insert(uuid, i);
249     return i;
250 }
251 
addItemClone(const QSharedPointer<Item> & item)252 QSharedPointer<Item> Playlist::addItemClone(const QSharedPointer<Item> &item)
253 {
254     QSharedPointer<Item> i = addItem(item->url());
255     i->setPlaylistUuid(uuid_);
256     i->setMetadata(item->metadata());
257     return i;
258 }
259 
addItemRaw(const QSharedPointer<Item> & item)260 void Playlist::addItemRaw(const QSharedPointer<Item> &item)
261 {
262     QWriteLocker locker(&listLock);
263     items.append(item);
264     itemsByUuid.insert(item->uuid(), item);
265 }
266 
itemAt(int index)267 QSharedPointer<Item> Playlist::itemAt(int index)
268 {
269     QReadLocker locker(&listLock);
270     if (index < 0 || index >= items.count())
271         return QSharedPointer<Item>();
272     return items.value(index);
273 }
274 
itemOf(const QUuid & uuid)275 QSharedPointer<Item> Playlist::itemOf(const QUuid &uuid)
276 {
277     QReadLocker locker(&listLock);
278     return itemsByUuid.value(uuid, QSharedPointer<Item>());
279 }
280 
itemAfter(const QUuid & uuid)281 QSharedPointer<Item> Playlist::itemAfter(const QUuid &uuid)
282 {
283     QReadLocker locker(&listLock);
284     if (!itemsByUuid.contains(uuid))
285         return QSharedPointer<Item>();
286     int index = items.indexOf(itemsByUuid[uuid]);
287     if (index < 0 || index + 1 >= items.length())
288         return QSharedPointer<Item>();
289     return items[index + 1];
290 }
291 
itemBefore(const QUuid & uuid)292 QSharedPointer<Item> Playlist::itemBefore(const QUuid &uuid)
293 {
294     QReadLocker locker(&listLock);
295     if (!itemsByUuid.contains(uuid))
296         return QSharedPointer<Item>();
297     int index = items.indexOf(itemsByUuid[uuid]);
298     if (index <= 0)
299         return QSharedPointer<Item>();
300     return items[index - 1];
301 }
302 
itemFirst()303 QSharedPointer<Item> Playlist::itemFirst()
304 {
305     QReadLocker locker(&listLock);
306     if (items.isEmpty())
307         return QSharedPointer<Item>();
308     return items.first();
309 }
310 
itemLast()311 QSharedPointer<Item> Playlist::itemLast()
312 {
313     QReadLocker locker(&listLock);
314     if (items.isEmpty())
315         return QSharedPointer<Item>();
316     return items.last();
317 }
318 
count()319 int Playlist::count()
320 {
321     QReadLocker lock(&listLock);
322     return items.count();
323 }
324 
isEmpty()325 bool Playlist::isEmpty()
326 {
327     QReadLocker lock(&listLock);
328     return items.isEmpty();
329 }
330 
contains(const QUuid & uuid)331 bool Playlist::contains(const QUuid &uuid)
332 {
333     QReadLocker lock(&listLock);
334     return itemsByUuid.contains(uuid);
335 }
336 
iterateItems(const std::function<void (QSharedPointer<Item>)> & callback)337 void Playlist::iterateItems(const std::function<void(QSharedPointer<Item>)> &callback)
338 {
339     QReadLocker locker(&listLock);
340     for (auto &item : items)
341         callback(item);
342 }
343 
addItems(const QUuid & where,const QList<QSharedPointer<Item>> & itemsToAdd)344 void Playlist::addItems(const QUuid &where,
345                         const QList<QSharedPointer<Item>> &itemsToAdd)
346 {
347     QWriteLocker locker(&listLock);
348 
349     int indexWhere = items.indexOf(itemsByUuid[where]);
350     if (indexWhere < 0)
351         indexWhere = items.size();
352     for (int i = 0; i < itemsToAdd.count(); ++i) {
353         QSharedPointer<Item> item = itemsToAdd.at(i);
354         item->setPlaylistUuid(uuid_);
355         items.insert(indexWhere + i, item);
356         itemsByUuid.insert(item->uuid(), item);
357     }
358 }
359 
removeItem(const QUuid & uuid)360 void Playlist::removeItem(const QUuid &uuid)
361 {
362     QWriteLocker locker(&listLock);
363     PlaylistCollection::queuePlaylist()->removeItem(uuid);
364     items.removeAll(itemsByUuid.take(uuid));
365     ItemCollection::getSingleton()->removeItem(uuid);
366 }
367 
takeItemsRaw(const QList<QSharedPointer<Item>> & itemsToRemove)368 void Playlist::takeItemsRaw(const QList<QSharedPointer<Item>> &itemsToRemove)
369 {
370     // "takeItemsRaw", because we don't check if it's in a queue or whatever,
371     // it's just taken raw, potentially damaging everything.  Only use if you
372     // may know what you're doing.
373     for (const QSharedPointer<Item> &item: itemsToRemove) {
374         itemsByUuid.remove(item->uuid());
375         items.removeAll(item);
376     }
377 }
378 
replaceItem(const QUuid & where,const QList<QUrl> & urls)379 QList<QUuid> Playlist::replaceItem(const QUuid &where, const QList<QUrl> &urls)
380 {
381     QWriteLocker lock(&listLock);
382     if (!itemsByUuid.contains(where))
383         return QList<QUuid>();
384 
385     itemsByUuid[where]->setUrl(urls[0]);
386 
387     QList<QUuid> addedItems;
388     // essentially insertAfter(where, urls[1..end]);
389     int insertIndex = items.indexOf(itemsByUuid[where]);
390     for (int urlIndex = 1; urlIndex < urls.count(); urlIndex++) {
391         QSharedPointer<Item> i(new Item(urls[urlIndex]));
392         i->setPlaylistUuid(uuid_);
393         items.insert(insertIndex + urlIndex, i);
394         itemsByUuid.insert(i->uuid(), i);
395         addedItems.append(i->uuid());
396     }
397     return addedItems;
398 }
399 
clear()400 void Playlist::clear()
401 {
402     QWriteLocker locker(&listLock);
403     PlaylistCollection::queuePlaylist()->removeItems(itemsByUuid.keys());
404     items.clear();
405     itemsByUuid.clear();
406 }
407 
created()408 QDateTime Playlist::created()
409 {
410     return created_;
411 }
412 
setCreated(const QDateTime & dt)413 void Playlist::setCreated(const QDateTime &dt)
414 {
415     created_ = dt;
416 }
417 
title()418 QString Playlist::title()
419 {
420     QReadLocker locker(&listLock);
421     return title_;
422 }
423 
setTitle(const QString & title)424 void Playlist::setTitle(const QString &title)
425 {
426     QWriteLocker locker(&listLock);
427     title_ = title;
428 }
429 
shuffle()430 bool Playlist::shuffle()
431 {
432     return shuffle_;
433 }
434 
setShuffle(bool shuffling)435 void Playlist::setShuffle(bool shuffling)
436 {
437     shuffle_ = shuffling;
438 }
439 
uuid()440 QUuid Playlist::uuid()
441 {
442     QReadLocker locker(&listLock);
443     return uuid_;
444 }
445 
setUuid(const QUuid & uuid)446 void Playlist::setUuid(const QUuid &uuid)
447 {
448     QWriteLocker locker(&listLock);
449     uuid_ = uuid;
450 }
451 
nowPlaying()452 QUuid Playlist::nowPlaying()
453 {
454     return nowPlaying_;
455 }
456 
setNowPlaying(const QUuid & uuid)457 void Playlist::setNowPlaying(const QUuid &uuid)
458 {
459     nowPlaying_ = uuid;
460 }
461 
toStringList()462 QStringList Playlist::toStringList()
463 {
464     QReadLocker locker(&listLock);
465     QStringList sl;
466     for (auto &i : items)
467         sl << i->toString();
468     return sl;
469 }
470 
fromStringList(QStringList sl)471 void Playlist::fromStringList(QStringList sl)
472 {
473     QWriteLocker locker(&listLock);
474     items.clear();
475     itemsByUuid.clear();
476     for (QString &s : sl) {
477         QSharedPointer<Item> item(new Item());
478         item->setPlaylistUuid(uuid_);
479         item->fromString(s);
480         items.append(item);
481         itemsByUuid.insert(item->uuid(), item);
482     }
483 }
484 
toVMap()485 QVariantMap Playlist::toVMap()
486 {
487     QWriteLocker locker(&listLock);
488     QVariantMap qvm;
489     qvm.insert(keyCreated, created_);
490     qvm.insert(keyTitle, title_);
491     qvm.insert(keyShuffle, shuffle_);
492     qvm.insert(keyUuid, uuid_);
493     qvm.insert(keyNowPlaying, nowPlaying_);
494 
495     QVariantList qvl;
496     for (auto &i : items) {
497         qvl.append(i->toVMap());
498     }
499     qvm.insert(keyItems, qvl);
500     return qvm;
501 }
502 
fromVMap(const QVariantMap & qvm)503 void Playlist::fromVMap(const QVariantMap &qvm)
504 {
505     QReadLocker locker(&listLock);
506     if (qvm.contains(keyContents)) {
507         // old format, call me back with the subnode
508         nowPlaying_ = qvm.value(keyNowPlaying, nowPlaying_).toUuid();
509         fromVMap(qvm[keyContents].toMap());
510         return;
511     }
512 
513     created_ = qvm.contains(keyCreated) ? qvm[keyCreated].toDateTime() : created_;
514     title_ = qvm.contains(keyTitle) ? qvm[keyTitle].toString() : QString();
515     shuffle_ = qvm.contains(keyShuffle) ? qvm[keyShuffle].toBool() : false;
516     uuid_ = qvm.contains(keyUuid) ? qvm[keyUuid].toUuid() : QUuid::createUuid();
517     nowPlaying_ = qvm.contains(keyNowPlaying) ? qvm[keyNowPlaying].toUuid() : nowPlaying_;
518     if (qvm.contains(keyItems)) {
519         auto items = qvm[keyItems].toList();
520         for (const QVariant &v : items) {
521             QSharedPointer<Item> i(new Item());
522             i->setPlaylistUuid(uuid_);
523             i->fromVMap(v.toMap());
524             this->items.append(i);
525             this->itemsByUuid.insert(i->uuid(), i);
526             ItemCollection::getSingleton()->storeItem(i);
527         }
528     }
529 }
530 
531 
532 
QueuePlaylist(const QString & title)533 QueuePlaylist::QueuePlaylist(const QString &title)
534     : Playlist(title)
535 {
536 
537 }
538 
first()539 QPair<QUuid,QUuid> QueuePlaylist::first()
540 {
541     QReadLocker lock(&listLock);
542     if (items.isEmpty())
543         return QPair<QUuid,QUuid>(QUuid(),QUuid());
544     return { items.first()->playlistUuid(), items.first()->uuid() };
545 }
546 
takeFirst()547 QPair<QUuid,QUuid> QueuePlaylist::takeFirst()
548 {
549     QWriteLocker lock(&listLock);
550     if (items.isEmpty())
551         return { QUuid(), QUuid() };
552     QSharedPointer<Item> item = items.takeFirst();
553     itemsByUuid.remove(item->uuid());
554     item->setQueuePosition(0);
555     int i = 1;
556     for (auto &item : items)
557         item->setQueuePosition(i++);
558     return { item->playlistUuid(), item->uuid() };
559 
560 }
561 
toggle(const QUuid & playlistUuid,const QUuid & itemUuid,bool always)562 int QueuePlaylist::toggle(const QUuid &playlistUuid, const QUuid &itemUuid, bool always)
563 {
564     QWriteLocker lock(&listLock);
565     return toggle_(playlistUuid, itemUuid, always);
566 }
567 
toggle(const QUuid & playlistUuid,const QList<QUuid> & uuids,QList<QUuid> & added,QList<int> & removed)568 void QueuePlaylist::toggle(const QUuid &playlistUuid, const QList<QUuid> &uuids, QList<QUuid> &added, QList<int> &removed)
569 {
570     QWriteLocker lock(&listLock);
571     int numberPresent = contains_(uuids);
572     if (numberPresent == uuids.count()) {
573         removed.append(removeItems_(uuids));
574         return;
575     }
576     for (const QUuid &item : uuids)
577         if (toggle_(playlistUuid, item, true) > 0)
578             added.append(item);
579 }
580 
toggleFromPlaylist(const QUuid & playlistUuid,QList<QUuid> & added,QList<int> & removedIndices)581 void QueuePlaylist::toggleFromPlaylist(const QUuid &playlistUuid, QList<QUuid> &added, QList<int> &removedIndices)
582 {
583     QWriteLocker lock(&listLock);
584     auto pl = PlaylistCollection::getSingleton()->playlistOf(playlistUuid);
585     QReadLocker plLock(&pl->listLock);
586     if (contains_(pl->itemsByUuid.keys()) == pl->itemsByUuid.count()) {
587         // remove all items from playlist
588         removedIndices.append(removeItems_(pl->itemsByUuid.keys()));
589     } else {
590         for (QSharedPointer<Item> &item : pl->items) {
591             if (!itemsByUuid.contains(item->uuid())) {
592                 items.append(item);
593                 itemsByUuid.insert(item->uuid(), item);
594                 item->setQueuePosition(items.count());
595                 added.append(item->uuid());
596             }
597         }
598     }
599 }
600 
appendItems(const QUuid & playlistUuid,const QList<QUuid> & itemsToAdd)601 void QueuePlaylist::appendItems(const QUuid &playlistUuid, const QList<QUuid> &itemsToAdd)
602 {
603     QWriteLocker lock(&listLock);
604     for (QUuid item : itemsToAdd)
605         toggle_(playlistUuid, item, true);
606 }
607 
addItems(const QUuid & where,const QList<QSharedPointer<Item>> & itemsToAdd)608 void QueuePlaylist::addItems(const QUuid &where, const QList<QSharedPointer<Item> > &itemsToAdd)
609 {
610     QWriteLocker lock(&listLock);
611     int index = items.indexOf(itemsByUuid[where]);
612     if (index < 0)
613         index = 0;
614 
615     int count = itemsToAdd.count();
616     for (int i = 0; i < count; i++) {
617         QSharedPointer<Item> item = itemsToAdd[i];
618         items.insert(index + i, item);
619         itemsByUuid.insert(item->uuid(), item);
620     }
621     count = items.count();
622     for (int i = index; i < count; i++)
623         items[i]->setQueuePosition(i+1);
624 }
625 
removeItem(const QUuid & uuid)626 void QueuePlaylist::removeItem(const QUuid &uuid)
627 {
628     QWriteLocker lock(&listLock);
629     removeItem_(uuid);
630 }
631 
removeItems(const QList<QUuid> & itemsToRemove)632 void QueuePlaylist::removeItems(const QList<QUuid> &itemsToRemove)
633 {
634     QWriteLocker lock(&listLock);
635     removeItems_(itemsToRemove);
636 }
637 
clear()638 void QueuePlaylist::clear()
639 {
640     QWriteLocker lock(&listLock);
641     for (QSharedPointer<Item> &item : items)
642         item->setQueuePosition(0);
643     items.clear();
644     itemsByUuid.clear();
645 }
646 
contains(const QList<QUuid> & itemsToCheck)647 int QueuePlaylist::contains(const QList<QUuid> &itemsToCheck)
648 {
649     QReadLocker lock(&listLock);
650     return contains_(itemsToCheck);
651 }
652 
toggle_(const QUuid & playlistUuid,const QUuid & itemUuid,bool always)653 int QueuePlaylist::toggle_(const QUuid &playlistUuid, const QUuid &itemUuid, bool always)
654 {
655     if (itemsByUuid.contains(itemUuid)) {
656         if (!always) {
657             removeItem_(itemUuid);
658             return -1;
659         }
660         return 0;
661     }
662     auto pl = PlaylistCollection::getSingleton()->playlistOf(playlistUuid);
663     if (!pl)
664         return 0;
665     QSharedPointer<Item> item = pl->itemOf(itemUuid);
666     if (!item)
667         return 0;
668     items.append(item);
669     itemsByUuid.insert(itemUuid, item);
670     item->setQueuePosition(items.count());
671     return 1;
672 }
673 
contains_(const QList<QUuid> & itemsToCheck) const674 int QueuePlaylist::contains_(const QList<QUuid> &itemsToCheck) const
675 {
676     int count = 0;
677     for (QUuid item : itemsToCheck)
678         if (itemsByUuid.contains(item))
679             count++;
680     return count;
681 }
682 
removeItem_(const QUuid & uuid)683 void QueuePlaylist::removeItem_(const QUuid &uuid)
684 {
685     if (!itemsByUuid.contains(uuid))
686         return;
687     QSharedPointer<Item> item = itemsByUuid[uuid];
688     int index = items.indexOf(item);
689     items.removeOne(item);
690     itemsByUuid.remove(uuid);
691     item->setQueuePosition(0);
692     int count = items.count();
693     for (int i = index; i < count; i++)
694         items[i]->setQueuePosition(i+1);
695 }
696 
removeItems_(const QList<QUuid> & itemsToRemove)697 QList<int> QueuePlaylist::removeItems_(const QList<QUuid> &itemsToRemove)
698 {
699     QList<int> removedIndices;
700     // Deprecated way
701     QSet<QUuid> removalSet(itemsToRemove.toSet());
702     // Non-deprecated way since Qt 5.14
703     //QSet<QUuid> removalSet({itemsToRemove.begin(), itemsToRemove.end()});
704     QMutableListIterator<QSharedPointer<Item>> i(items);
705     int index = 0;
706     while (i.hasNext()) {
707         QSharedPointer<Item> item = i.next();
708         if (removalSet.contains(item->uuid())) {
709             itemsByUuid.remove(item->uuid());
710             item->setQueuePosition(0);
711             i.remove();
712             removedIndices.append(index);
713         }
714         index++;
715     }
716     for (int i = 0; i < items.count(); i++)
717         items[i]->setQueuePosition(i+1);
718     return removedIndices;
719 }
720 
721 
722 
723 QSharedPointer<PlaylistCollection> PlaylistCollection::collection;
724 QSharedPointer<PlaylistCollection> PlaylistCollection::backup;
725 QSharedPointer<QueuePlaylist> PlaylistCollection::queue;
726 
PlaylistCollection()727 PlaylistCollection::PlaylistCollection()
728 {
729 
730 }
731 
~PlaylistCollection()732 PlaylistCollection::~PlaylistCollection()
733 {
734     playlists.clear();
735     playlistsByUuid.clear();
736 }
737 
getSingleton()738 QSharedPointer<PlaylistCollection> PlaylistCollection::getSingleton()
739 {
740     if (collection.isNull()) {
741         collection.reset(new PlaylistCollection());
742         collection->doNewPlaylist(tr("Quick playlist"), QUuid());
743     }
744     return collection;
745 }
746 
getBackup()747 QSharedPointer<PlaylistCollection> PlaylistCollection::getBackup()
748 {
749     if (backup.isNull())
750         backup.reset(new PlaylistCollection());
751     return backup;
752 }
753 
queuePlaylist()754 QSharedPointer<QueuePlaylist> PlaylistCollection::queuePlaylist()
755 {
756     if (queue.isNull())
757         queue.reset(new QueuePlaylist("Queue"));
758     return queue;
759 }
760 
iteratePlaylists(const std::function<void (QSharedPointer<Playlist>)> & callback)761 void PlaylistCollection::iteratePlaylists(const std::function<void (QSharedPointer<Playlist>)> &callback)
762 {
763     for (const auto &p : playlists) {
764         callback(p);
765     }
766 }
767 
newPlaylist(const QString & title)768 QSharedPointer<Playlist> PlaylistCollection::newPlaylist(const QString &title)
769 {
770     return doNewPlaylist(title, QUuid::createUuid());
771 }
772 
clonePlaylist(const QUuid & uuid)773 QSharedPointer<Playlist> PlaylistCollection::clonePlaylist(const QUuid &uuid)
774 {
775     if (!playlistsByUuid.contains(uuid))
776         return QSharedPointer<Playlist>();
777     auto origin = playlistsByUuid[uuid];
778     auto remote = newPlaylist(origin->title());
779     auto cloner = [remote,origin](QSharedPointer<Item> i) {
780         auto clone = remote->addItemClone(i);
781         if (origin->nowPlaying() == i->uuid()) {
782             remote->setNowPlaying(clone->uuid());
783         }
784     };
785     origin->iterateItems(cloner);
786     return remote;
787 }
788 
takePlaylist(const QUuid & uuid)789 QSharedPointer<Playlist> PlaylistCollection::takePlaylist(const QUuid &uuid)
790 {
791     QSharedPointer<Playlist> playlist = playlistOf(uuid);
792     removePlaylist(uuid);
793     return playlist;
794 }
795 
removePlaylist(const QUuid & uuid)796 void PlaylistCollection::removePlaylist(const QUuid &uuid)
797 {
798     if (!playlistsByUuid.contains(uuid))
799         return;
800     QSharedPointer<Playlist> p = playlistsByUuid.value(uuid);
801     playlists.removeAll(p);
802     playlistsByUuid.remove(uuid);
803 }
804 
removePlaylist(const QSharedPointer<Playlist> & p)805 void PlaylistCollection::removePlaylist(const QSharedPointer<Playlist> &p)
806 {
807     if (p == nullptr)
808         return;
809     removePlaylist(p->uuid());
810 }
811 
playlistAt(int col) const812 QSharedPointer<Playlist> PlaylistCollection::playlistAt(int col) const
813 {
814     return (col < playlists.count()) ? playlists.at(col) : QSharedPointer<Playlist>();
815 }
816 
playlistOf(const QUuid & uuid) const817 QSharedPointer<Playlist> PlaylistCollection::playlistOf(const QUuid &uuid) const
818 {
819     return playlistsByUuid.value(uuid);
820 }
821 
addPlaylist(const QSharedPointer<Playlist> & playlist)822 void PlaylistCollection::addPlaylist(const QSharedPointer<Playlist> &playlist)
823 {
824     if (!playlist)
825         return;
826     if (playlistsByUuid.contains(playlist->uuid())) {
827         QSharedPointer<Playlist> old = playlistsByUuid[playlist->uuid()];
828         playlistsByUuid.remove(playlist->uuid());
829         playlists.removeOne(old);
830     }
831     playlists.append(playlist);
832     playlistsByUuid.insert(playlist->uuid(), playlist);
833 }
834 
fromVList(const QVariantList & data)835 void PlaylistCollection::fromVList(const QVariantList &data)
836 {
837     for (const auto &d : data) {
838         QSharedPointer<Playlist> p(new Playlist);
839         p->fromVMap(d.toMap());
840         addPlaylist(p);
841     }
842 }
843 
toVList()844 QVariantList PlaylistCollection::toVList()
845 {
846     QVariantList l;
847     for (const auto &p : playlists) {
848         l.append(p->toVMap());
849     }
850     return l;
851 }
852 
doNewPlaylist(const QString & title,const QUuid & uuid)853 QSharedPointer<Playlist> PlaylistCollection::doNewPlaylist(const QString &title,
854                                                            const QUuid &uuid)
855 {
856     QSharedPointer<Playlist> p(new Playlist(title));
857     p->setUuid(uuid);
858     playlists.append(p);
859     playlistsByUuid.insert(p->uuid(), p);
860     return p;
861 }
862 
bump()863 void PlaylistSearcher::bump()
864 {
865     QWriteLocker locker(&bumpLock);
866     ++bumps_;
867 }
868 
unbump()869 void PlaylistSearcher::unbump()
870 {
871     QWriteLocker locker(&bumpLock);
872     --bumps_;
873 }
874 
bumps()875 int PlaylistSearcher::bumps()
876 {
877     QReadLocker locker(&bumpLock);
878     return bumps_;
879 }
880 
itemMatchesFilter(const QSharedPointer<Item> & item,const QStringList & needles)881 bool PlaylistSearcher::itemMatchesFilter(const QSharedPointer<Item> &item,
882                                          const QStringList &needles)
883 {
884     QSet<QString> found;
885     findNeedles(item->toDisplayString(), needles, found);
886     if (found.count() == needles.count())
887         return true;
888 
889     // OPTIMIZE: don't run the metadata through toLower every search
890     for (const QVariant &v : item->metadata())
891         findNeedles(v.toString(), needles, found);
892     return found.count() == needles.count();
893 }
894 
filterPlaylist(QSharedPointer<Playlist> list,QString text)895 void PlaylistSearcher::filterPlaylist(QSharedPointer<Playlist> list, QString text)
896 {
897     // Limit response - only reply if last in event queue
898     bool bumpLimited = bumps() > 1;
899     unbump();
900     if (bumpLimited)
901         return;
902 
903     QStringList needles = textToNeedles(text);
904     if (needles.isEmpty()) {
905         clearPlaylistFilter(list);
906         return;
907     }
908 
909     if (list.isNull())
910         return;
911 
912     auto marker = [&needles](QSharedPointer<Item> item) {
913         item->setHidden(!itemMatchesFilter(item, needles));
914     };
915     list->iterateItems(marker);
916     emit playlistFiltered(list->uuid());
917 }
918 
clearPlaylistFilter(QSharedPointer<Playlist> & list)919 void PlaylistSearcher::clearPlaylistFilter(QSharedPointer<Playlist> &list)
920 {
921     if (list.isNull())
922         return;
923 
924     auto clearer = [](QSharedPointer<Item> item) {
925         item->setHidden(false);
926     };
927     list->iterateItems(clearer);
928     emit playlistFiltered(list->uuid());
929 }
930 
textToNeedles(QString text)931 QStringList PlaylistSearcher::textToNeedles(QString text)
932 {
933     // Deprecated way
934     return text.toLower().split(QString(" "), QString::SkipEmptyParts);
935     // Non deprecated way since Qt 5.14
936     //return text.toLower().split(QString(" "), Qt::SkipEmptyParts);
937 }
938 
findNeedles(const QString & text,const QStringList & needles,QSet<QString> & found)939 void PlaylistSearcher::findNeedles(const QString &text,
940                                    const QStringList &needles,
941                                    QSet<QString> &found)
942 {
943     QString haystack = text.toLower();
944     for (const QString &needle : needles) {
945         if (haystack.contains(needle))
946             found.insert(needle);
947     }
948 }
949