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