Support more collections

This commit is contained in:
Jonas Kvinge
2025-03-08 22:24:28 +01:00
parent 5ae0320911
commit bdbe66b116
28 changed files with 327 additions and 341 deletions

View File

@@ -92,12 +92,12 @@
#include "smartplaylists/playlistgeneratorinserter.h"
#include "smartplaylists/playlistgeneratormimedata.h"
#include "streaming/streamplaylistitem.h"
#include "streaming/streamserviceplaylistitem.h"
#include "streaming/streamsongmimedata.h"
#include "streaming/streamingservice.h"
#include "radios/radiomimedata.h"
#include "radios/radioplaylistitem.h"
#include "radios/radiostreamplaylistitem.h"
using std::make_shared;
using namespace std::chrono_literals;
@@ -186,7 +186,7 @@ Playlist::Playlist(const SharedPtr<TaskManager> task_manager,
Playlist::~Playlist() {
items_.clear();
collection_items_by_id_.clear();
ClearCollectionItems();
}
template<typename T>
@@ -828,25 +828,24 @@ bool Playlist::dropMimeData(const QMimeData *data, Qt::DropAction action, const
if (const SongMimeData *song_data = qobject_cast<const SongMimeData*>(data)) {
// Dragged from a collection
// We want to check if these songs are from the actual local file backend, if they are we treat them differently.
if (song_data->backend && song_data->backend->songs_table() == QLatin1String(CollectionLibrary::kSongsTable)) {
if (song_data->backend && Song::IsLinkedCollectionSource(song_data->backend->source())) {
InsertSongItems<CollectionPlaylistItem>(song_data->songs, row, play_now, enqueue_now, enqueue_next_now);
}
else {
InsertSongItems<SongPlaylistItem>(song_data->songs, row, play_now, enqueue_now, enqueue_next_now);
}
}
else if (const PlaylistItemMimeData *item_data = qobject_cast<const PlaylistItemMimeData*>(data)) {
InsertItems(item_data->items_, row, play_now, enqueue_now, enqueue_next_now);
else if (const PlaylistItemMimeData *item_mimedata = qobject_cast<const PlaylistItemMimeData*>(data)) {
InsertItems(item_mimedata->items_, row, play_now, enqueue_now, enqueue_next_now);
}
else if (const PlaylistGeneratorMimeData *generator_data = qobject_cast<const PlaylistGeneratorMimeData*>(data)) {
InsertSmartPlaylist(generator_data->generator_, row, play_now, enqueue_now, enqueue_next_now);
else if (const PlaylistGeneratorMimeData *generator_mimedata = qobject_cast<const PlaylistGeneratorMimeData*>(data)) {
InsertSmartPlaylist(generator_mimedata->generator_, row, play_now, enqueue_now, enqueue_next_now);
}
else if (const StreamSongMimeData *stream_song_data = qobject_cast<const StreamSongMimeData*>(data)) {
InsertStreamingItems(stream_song_data->service, stream_song_data->songs, row, play_now, enqueue_now, enqueue_next_now);
else if (const StreamSongMimeData *stream_song_mimedata = qobject_cast<const StreamSongMimeData*>(data)) {
InsertStreamingItems(stream_song_mimedata->service, stream_song_mimedata->songs, row, play_now, enqueue_now, enqueue_next_now);
}
else if (const RadioMimeData *radio_data = qobject_cast<const RadioMimeData*>(data)) {
InsertRadioItems(radio_data->songs, row, play_now, enqueue_now, enqueue_next_now);
else if (const RadioMimeData *radio_mimedata = qobject_cast<const RadioMimeData*>(data)) {
InsertRadioItems(radio_mimedata->songs, row, play_now, enqueue_now, enqueue_next_now);
}
else if (data->hasFormat(QLatin1String(kRowsMimetype))) {
// Dragged from the playlist
@@ -1131,10 +1130,10 @@ void Playlist::InsertItemsWithoutUndo(const PlaylistItemPtrList &items, const in
items_.insert(i, item);
virtual_items_ << static_cast<int>(virtual_items_.count());
if (item->source() == Song::Source::Collection) {
int id = item->Metadata().id();
if (Song::IsLinkedCollectionSource(item->source())) {
const int id = item->Metadata().id();
if (id != -1) {
collection_items_by_id_.insert(id, item);
collection_items_[item->Metadata().source_id()].insert(id, item);
}
}
@@ -1187,23 +1186,9 @@ void Playlist::InsertSongsOrCollectionItems(const SongList &songs, const QString
}
PlaylistItemPtrList items;
items.reserve(songs.count());
for (const Song &song : songs) {
if (song.url().isLocalFile()) {
if (song.is_collection_song()) {
items << make_shared<CollectionPlaylistItem>(song);
}
else {
items << make_shared<SongPlaylistItem>(song);
}
}
else {
if (song.is_radio()) {
items << make_shared<RadioPlaylistItem>(song);
}
else {
items << make_shared<StreamPlaylistItem>(song);
}
}
items << PlaylistItem::NewFromSong(song);
}
InsertItems(items, pos, play_now, enqueue, enqueue_next);
@@ -1215,7 +1200,7 @@ void Playlist::InsertStreamingItems(StreamingServicePtr service, const SongList
PlaylistItemPtrList playlist_items;
playlist_items.reserve(songs.count());
for (const Song &song : songs) {
playlist_items << make_shared<StreamPlaylistItem>(service, song);
playlist_items << make_shared<StreamServicePlaylistItem>(service, song);
}
InsertItems(playlist_items, pos, play_now, enqueue, enqueue_next);
@@ -1227,7 +1212,7 @@ void Playlist::InsertRadioItems(const SongList &songs, const int pos, const bool
PlaylistItemPtrList playlist_items;
playlist_items.reserve(songs.count());
for (const Song &song : songs) {
playlist_items << make_shared<RadioPlaylistItem>(song);
playlist_items << make_shared<RadioStreamPlaylistItem>(song);
}
InsertItems(playlist_items, pos, play_now, enqueue, enqueue_next);
@@ -1252,22 +1237,22 @@ void Playlist::UpdateItems(SongList songs) {
const PlaylistItemPtr item = items_.value(i);
if (item->Metadata().url() == song.url() && (item->Metadata().filetype() == Song::FileType::Unknown || item->Metadata().filetype() == Song::FileType::Stream || item->Metadata().filetype() == Song::FileType::CDDA || !item->Metadata().init_from_file())) {
PlaylistItemPtr new_item;
if (song.url().isLocalFile()) {
if (song.is_collection_song()) {
new_item = make_shared<CollectionPlaylistItem>(song);
if (collection_items_by_id_.contains(song.id(), item)) collection_items_by_id_.remove(song.id(), item);
collection_items_by_id_.insert(song.id(), new_item);
}
else {
new_item = make_shared<SongPlaylistItem>(song);
}
if (song.is_linked_collection_song()) {
new_item = make_shared<CollectionPlaylistItem>(song);
if (collection_items_[song.source_id()].contains(song.id(), item)) collection_items_[song.source_id()].remove(song.id(), item);
collection_items_[song.source_id()].insert(song.id(), new_item);
}
else {
if (song.is_radio()) {
new_item = make_shared<RadioPlaylistItem>(song);
if (song.url().isLocalFile()) {
new_item = make_shared<SongPlaylistItem>(song);
}
else {
new_item = make_shared<StreamPlaylistItem>(song);
if (song.is_radio()) {
new_item = make_shared<RadioStreamPlaylistItem>(song);
}
else {
new_item = make_shared<StreamServicePlaylistItem>(song);
}
}
}
items_[i] = new_item;
@@ -1580,7 +1565,7 @@ void Playlist::Restore() {
items_.clear();
virtual_items_.clear();
collection_items_by_id_.clear();
ClearCollectionItems();
cancel_restore_ = false;
QFuture<PlaylistItemPtrList> future = QtConcurrent::run(&PlaylistBackend::GetPlaylistItems, playlist_backend_, id_);
@@ -1590,6 +1575,15 @@ void Playlist::Restore() {
}
void Playlist::ClearCollectionItems() {
constexpr int collection_items_size = static_cast<int>(sizeof(collection_items_)) / sizeof(collection_items_[0]);
for (int i = 0; i < collection_items_size; ++i) {
collection_items_[i].clear();
}
}
void Playlist::ItemsLoaded() {
QFutureWatcher<PlaylistItemPtrList> *watcher = static_cast<QFutureWatcher<PlaylistItemPtrList>*>(sender());
@@ -1735,12 +1729,10 @@ PlaylistItemPtrList Playlist::RemoveItemsWithoutUndo(const int row, const int co
for (int i = 0; i < count; ++i) {
PlaylistItemPtr item(items_.takeAt(row));
items << item;
if (item->source() == Song::Source::Collection) {
int id = item->Metadata().id();
if (id != -1 && collection_items_by_id_.contains(id, item)) {
collection_items_by_id_.remove(id, item);
}
const int id = item->Metadata().id();
const int source_id = item->Metadata().source_id();
if (id != -1 && collection_items_[source_id].contains(id, item)) {
collection_items_[source_id].remove(id, item);
}
}
@@ -2069,8 +2061,8 @@ quint64 Playlist::GetTotalLength() const {
}
PlaylistItemPtrList Playlist::collection_items_by_id(const int id) const {
return collection_items_by_id_.values(id);
PlaylistItemPtrList Playlist::collection_items(const Song::Source source, const int song_id) const {
return collection_items_[static_cast<int>(source)].values(song_id);
}
void Playlist::TracksAboutToBeDequeued(const QModelIndex &idx, const int begin, const int end) {

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2024, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -203,7 +203,7 @@ class Playlist : public QAbstractListModel {
PlaylistItem::Options current_item_options() const;
Song current_item_metadata() const;
PlaylistItemPtrList collection_items_by_id(const int id) const;
PlaylistItemPtrList collection_items(const Song::Source source, const int song_id) const;
SongList GetAllSongs() const;
PlaylistItemPtrList GetAllItems() const;
@@ -357,6 +357,8 @@ class Playlist : public QAbstractListModel {
// Grays out and reloads all deleted songs in all playlists. Also, "ungreys" those songs which were once deleted but now got restored somehow.
void InvalidateDeletedSongs();
void ClearCollectionItems();
private Q_SLOTS:
void TracksAboutToBeDequeued(const QModelIndex&, const int begin, const int end);
void TracksDequeued();
@@ -393,8 +395,7 @@ class Playlist : public QAbstractListModel {
QList<QPersistentModelIndex> played_indexes_;
// A map of collection ID to playlist item - for fast lookups when collection items change.
QMultiMap<int, PlaylistItemPtr> collection_items_by_id_;
QMultiMap<int, PlaylistItemPtr> collection_items_[Song::kSourceCount];
QPersistentModelIndex current_item_index_;
QPersistentModelIndex last_played_item_index_;

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,30 +30,31 @@
#include "core/sqlquery.h"
#include "core/song.h"
#include "collection/collectionplaylistitem.h"
#include "playlistitem.h"
#include "songplaylistitem.h"
#include "streaming/streamplaylistitem.h"
#include "radios/radioplaylistitem.h"
#include "collection/collectionplaylistitem.h"
#include "streaming/streamserviceplaylistitem.h"
#include "radios/radiostreamplaylistitem.h"
using std::make_shared;
using namespace Qt::Literals::StringLiterals;
PlaylistItem::PlaylistItem(const Song::Source source) : should_skip_(false), source_(source) {}
PlaylistItemPtr PlaylistItem::NewFromSource(const Song::Source source) {
switch (source) {
case Song::Source::Collection:
return make_shared<CollectionPlaylistItem>();
return make_shared<CollectionPlaylistItem>(source);
case Song::Source::Subsonic:
case Song::Source::Tidal:
case Song::Source::Spotify:
case Song::Source::Qobuz:
return make_shared<StreamPlaylistItem>(source);
return make_shared<StreamServicePlaylistItem>(source);
case Song::Source::Stream:
case Song::Source::RadioParadise:
case Song::Source::SomaFM:
return make_shared<RadioPlaylistItem>(source);
return make_shared<RadioStreamPlaylistItem>(source);
case Song::Source::LocalFile:
case Song::Source::CDDA:
case Song::Source::Device:
@@ -74,11 +75,11 @@ PlaylistItemPtr PlaylistItem::NewFromSong(const Song &song) {
case Song::Source::Tidal:
case Song::Source::Spotify:
case Song::Source::Qobuz:
return make_shared<StreamPlaylistItem>(song);
return make_shared<StreamServicePlaylistItem>(song);
case Song::Source::Stream:
case Song::Source::RadioParadise:
case Song::Source::SomaFM:
return make_shared<RadioPlaylistItem>(song);
return make_shared<RadioStreamPlaylistItem>(song);
case Song::Source::LocalFile:
case Song::Source::CDDA:
case Song::Source::Device:
@@ -90,12 +91,10 @@ PlaylistItemPtr PlaylistItem::NewFromSong(const Song &song) {
}
PlaylistItem::~PlaylistItem() = default;
void PlaylistItem::BindToQuery(SqlQuery *query) const {
query->BindValue(u":type"_s, static_cast<int>(source_));
query->BindValue(u":collection_id"_s, DatabaseValue(Column_CollectionId));
query->BindValue(u":collection_id"_s, DatabaseValue(DatabaseColumn::CollectionId));
DatabaseSongMetadata().BindToQuery(query);
@@ -174,5 +173,5 @@ QColor PlaylistItem::GetCurrentForegroundColor() const {
bool PlaylistItem::HasCurrentForegroundColor() const {
return !foreground_colors_.isEmpty();
}
void PlaylistItem::SetShouldSkip(const bool val) { should_skip_ = val; }
void PlaylistItem::SetShouldSkip(const bool should_skip) { should_skip_ = should_skip; }
bool PlaylistItem::GetShouldSkip() const { return should_skip_; }

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -48,8 +48,7 @@ using std::enable_shared_from_this;
class PlaylistItem : public enable_shared_from_this<PlaylistItem> {
public:
explicit PlaylistItem(const Song::Source source) : should_skip_(false), source_(source) {}
virtual ~PlaylistItem();
explicit PlaylistItem(const Song::Source source);
static SharedPtr<PlaylistItem> NewFromSource(const Song::Source source);
static SharedPtr<PlaylistItem> NewFromSong(const Song &song);
@@ -80,7 +79,7 @@ class PlaylistItem : public enable_shared_from_this<PlaylistItem> {
virtual Song OriginalMetadata() const = 0;
virtual QUrl Url() const = 0;
virtual void SetMetadata(const Song&) {}
virtual void SetMetadata(const Song &song) { Q_UNUSED(song); }
void SetTemporaryMetadata(const Song &metadata);
void UpdateTemporaryMetadata(const Song &metadata);
@@ -114,21 +113,20 @@ class PlaylistItem : public enable_shared_from_this<PlaylistItem> {
// Convenience function to find out whether this item is from the local collection, as opposed to a device, a file on disk, or a stream.
// Remember that even if this returns true, the collection item might be invalid, so you might want to check that its id is not equal to -1 before actually using it.
virtual bool IsLocalCollectionItem() const { return false; }
void SetShouldSkip(const bool val);
void SetShouldSkip(const bool should_skip);
bool GetShouldSkip() const;
protected:
bool should_skip_;
enum DatabaseColumn { Column_CollectionId };
enum class DatabaseColumn {
CollectionId
};
virtual QVariant DatabaseValue(DatabaseColumn) const {
return QVariant(QString());
}
virtual QVariant DatabaseValue(const DatabaseColumn database_column) const { Q_UNUSED(database_column); return QVariant(QString()); }
virtual Song DatabaseSongMetadata() const { return Song(); }
Song::Source source_;
Song temp_metadata_;
QMap<short, QColor> background_colors_;

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -476,7 +476,7 @@ void PlaylistManager::UpdateCollectionSongs(const SongList &songs) {
for (const Song &song : songs) {
for (const Data &data : std::as_const(playlists_)) {
const PlaylistItemPtrList items = data.p->collection_items_by_id(song.id());
const PlaylistItemPtrList items = data.p->collection_items(song.source(), song.id());
for (PlaylistItemPtr item : items) {
if (item->Metadata().directory_id() != song.directory_id()) continue;
data.p->UpdateItemMetadata(item, song, false);

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -2,7 +2,7 @@
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -0,0 +1,74 @@
/*
* Strawberry Music Player
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <QApplication>
#include <QVariant>
#include "streamplaylistitem.h"
#include "core/sqlrow.h"
StreamPlaylistItem::StreamPlaylistItem(const Song::Source source)
: PlaylistItem(source),
source_(source) {}
StreamPlaylistItem::StreamPlaylistItem(const Song &song)
: PlaylistItem(song.source()),
source_(song.source()),
song_(song) {
InitMetadata();
}
bool StreamPlaylistItem::InitFromQuery(const SqlRow &query) {
song_.InitFromQuery(query, false, static_cast<int>(Song::kRowIdColumns.count()));
InitMetadata();
return true;
}
QVariant StreamPlaylistItem::DatabaseValue(const DatabaseColumn column) const {
return PlaylistItem::DatabaseValue(column);
}
void StreamPlaylistItem::InitMetadata() {
if (song_.title().isEmpty()) song_.set_title(song_.url().toString());
if (song_.source() == Song::Source::Unknown) song_.set_source(Song::Source::Stream);
if (song_.filetype() == Song::FileType::Unknown) song_.set_filetype(Song::FileType::Stream);
song_.set_valid(true);
}
Song StreamPlaylistItem::Metadata() const {
if (HasTemporaryMetadata()) return temp_metadata_;
return song_;
}
QUrl StreamPlaylistItem::Url() const { return song_.url(); }
void StreamPlaylistItem::SetArtManual(const QUrl &cover_url) {
song_.set_art_manual(cover_url);
temp_metadata_.set_art_manual(cover_url);
}

View File

@@ -0,0 +1,58 @@
/*
* Strawberry Music Player
* Copyright 2018-2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef STREAMPLAYLISTITEM_H
#define STREAMPLAYLISTITEM_H
#include <QVariant>
#include <QUrl>
#include "core/song.h"
#include "core/sqlrow.h"
#include "playlist/playlistitem.h"
class StreamPlaylistItem : public PlaylistItem {
public:
explicit StreamPlaylistItem(const Song::Source source);
explicit StreamPlaylistItem(const Song &song);
bool InitFromQuery(const SqlRow &query) override;
Song Metadata() const override;
Song OriginalMetadata() const override { return song_; }
QUrl Url() const override;
void SetMetadata(const Song &song) override { song_ = song; }
void SetArtManual(const QUrl &cover_url) override;
protected:
QVariant DatabaseValue(const DatabaseColumn column) const override;
Song DatabaseSongMetadata() const override { return song_; }
private:
void InitMetadata();
private:
Song::Source source_;
Song song_;
Q_DISABLE_COPY(StreamPlaylistItem)
};
#endif // STREAMPLAYLISTITEM_H