Refactor playlist items

Fix a bug where playlist items cover is not updated
This commit is contained in:
Jonas Kvinge
2025-04-27 03:03:58 +02:00
parent baec45f742
commit 04c6c862c4
28 changed files with 310 additions and 289 deletions

View File

@@ -34,8 +34,6 @@ CollectionPlaylistItem::CollectionPlaylistItem(const Song::Source source) : Play
CollectionPlaylistItem::CollectionPlaylistItem(const Song &song) : PlaylistItem(song.source()), song_(song) {} CollectionPlaylistItem::CollectionPlaylistItem(const Song &song) : PlaylistItem(song.source()), song_(song) {}
QUrl CollectionPlaylistItem::Url() const { return song_.url(); }
bool CollectionPlaylistItem::InitFromQuery(const SqlRow &query) { bool CollectionPlaylistItem::InitFromQuery(const SqlRow &query) {
int col = 0; int col = 0;
@@ -62,7 +60,7 @@ void CollectionPlaylistItem::Reload() {
qLog(Error) << "Could not reload file" << song_.url() << result.error_string(); qLog(Error) << "Could not reload file" << song_.url() << result.error_string();
return; return;
} }
UpdateTemporaryMetadata(song_); UpdateStreamMetadata(song_);
} }
} }
@@ -78,16 +76,9 @@ QVariant CollectionPlaylistItem::DatabaseValue(const DatabaseColumn database_col
} }
Song CollectionPlaylistItem::Metadata() const {
if (HasTemporaryMetadata()) return temp_metadata_;
return song_;
}
void CollectionPlaylistItem::SetArtManual(const QUrl &cover_url) { void CollectionPlaylistItem::SetArtManual(const QUrl &cover_url) {
song_.set_art_manual(cover_url); song_.set_art_manual(cover_url);
if (HasTemporaryMetadata()) temp_metadata_.set_art_manual(cover_url); if (HasStreamMetadata()) stream_song_.set_art_manual(cover_url);
} }

View File

@@ -35,19 +35,17 @@ class CollectionPlaylistItem : public PlaylistItem {
explicit CollectionPlaylistItem(const Song::Source source); explicit CollectionPlaylistItem(const Song::Source source);
explicit CollectionPlaylistItem(const Song &song); explicit CollectionPlaylistItem(const Song &song);
QUrl Url() const override; Song OriginalMetadata() const override { return song_; }
void SetOriginalMetadata(const Song &song) override { song_ = song; }
QUrl OriginalUrl() const override { return song_.url(); }
bool IsLocalCollectionItem() const override { return song_.source() == Song::Source::Collection; }
bool InitFromQuery(const SqlRow &query) override; bool InitFromQuery(const SqlRow &query) override;
void Reload() override; void Reload() override;
Song Metadata() const override;
Song OriginalMetadata() const override { return song_; }
void SetMetadata(const Song &song) override { song_ = song; }
void SetArtManual(const QUrl &cover_url) override; void SetArtManual(const QUrl &cover_url) override;
bool IsLocalCollectionItem() const override { return song_.source() == Song::Source::Collection; }
protected: protected:
QVariant DatabaseValue(const DatabaseColumn database_column) const override; QVariant DatabaseValue(const DatabaseColumn database_column) const override;
Song DatabaseSongMetadata() const override { return Song(source_); } Song DatabaseSongMetadata() const override { return Song(source_); }

View File

@@ -1515,8 +1515,8 @@ void MainWindow::SendNowPlaying() {
// Send now playing to scrobble services // Send now playing to scrobble services
Playlist *playlist = app_->playlist_manager()->active(); Playlist *playlist = app_->playlist_manager()->active();
if (app_->scrobbler()->enabled() && playlist && playlist->current_item() && playlist->current_item()->Metadata().is_metadata_good()) { if (app_->scrobbler()->enabled() && playlist && playlist->current_item() && playlist->current_item()->EffectiveMetadata().is_metadata_good()) {
app_->scrobbler()->UpdateNowPlaying(playlist->current_item()->Metadata()); app_->scrobbler()->UpdateNowPlaying(playlist->current_item()->EffectiveMetadata());
ui_->action_love->setEnabled(true); ui_->action_love->setEnabled(true);
ui_->button_love->setEnabled(true); ui_->button_love->setEnabled(true);
tray_icon_->LoveStateChanged(true); tray_icon_->LoveStateChanged(true);
@@ -1562,9 +1562,9 @@ void MainWindow::TrackSkipped(PlaylistItemPtr item) {
// If it was a collection item then we have to increment its skipped count in the database. // If it was a collection item then we have to increment its skipped count in the database.
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) { if (item && item->IsLocalCollectionItem() && item->EffectiveMetadata().id() != -1) {
Song song = item->Metadata(); Song song = item->EffectiveMetadata();
const qint64 position = app_->player()->engine()->position_nanosec(); const qint64 position = app_->player()->engine()->position_nanosec();
const qint64 length = app_->player()->engine()->length_nanosec(); const qint64 length = app_->player()->engine()->length_nanosec();
const float percentage = (length == 0 ? 1 : static_cast<float>(position) / static_cast<float>(length)); const float percentage = (length == 0 ? 1 : static_cast<float>(position) / static_cast<float>(length));
@@ -1758,7 +1758,7 @@ void MainWindow::FilePathChanged(const QString &path) {
void MainWindow::Seeked(const qint64 microseconds) { void MainWindow::Seeked(const qint64 microseconds) {
const qint64 position = microseconds / kUsecPerSec; const qint64 position = microseconds / kUsecPerSec;
const qint64 length = app_->player()->GetCurrentItem()->Metadata().length_nanosec() / kNsecPerSec; const qint64 length = app_->player()->GetCurrentItem()->EffectiveMetadata().length_nanosec() / kNsecPerSec;
tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0)); tray_icon_->SetProgress(static_cast<int>(static_cast<double>(position) / static_cast<double>(length) * 100.0));
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
@@ -1774,7 +1774,7 @@ void MainWindow::UpdateTrackPosition() {
PlaylistItemPtr item(app_->player()->GetCurrentItem()); PlaylistItemPtr item(app_->player()->GetCurrentItem());
if (!item) return; if (!item) return;
const qint64 length = (item->Metadata().length_nanosec() / kNsecPerSec); const qint64 length = (item->EffectiveMetadata().length_nanosec() / kNsecPerSec);
if (length <= 0) return; if (length <= 0) return;
const int position = std::floor(static_cast<float>(app_->player()->engine()->position_nanosec()) / static_cast<float>(kNsecPerSec) + 0.5); const int position = std::floor(static_cast<float>(app_->player()->engine()->position_nanosec()) / static_cast<float>(kNsecPerSec) + 0.5);
@@ -1788,12 +1788,12 @@ void MainWindow::UpdateTrackPosition() {
#endif #endif
// Send Scrobble // Send Scrobble
if (app_->scrobbler()->enabled() && item->Metadata().is_metadata_good()) { if (app_->scrobbler()->enabled() && item->EffectiveMetadata().is_metadata_good()) {
Playlist *playlist = app_->playlist_manager()->active(); Playlist *playlist = app_->playlist_manager()->active();
if (playlist && !playlist->scrobbled()) { if (playlist && !playlist->scrobbled()) {
const qint64 scrobble_point = (playlist->scrobble_point_nanosec() / kNsecPerSec); const qint64 scrobble_point = (playlist->scrobble_point_nanosec() / kNsecPerSec);
if (position >= scrobble_point) { if (position >= scrobble_point) {
app_->scrobbler()->Scrobble(item->Metadata(), scrobble_point); app_->scrobbler()->Scrobble(item->EffectiveMetadata(), scrobble_point);
playlist->set_scrobbled(true); playlist->set_scrobbled(true);
} }
} }
@@ -1910,7 +1910,7 @@ void MainWindow::AddToPlaylistFromAction(QAction *action) {
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row()); PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
if (!item) continue; if (!item) continue;
items << item; items << item;
songs << item->Metadata(); songs << item->EffectiveMetadata();
} }
// We're creating a new playlist // We're creating a new playlist
@@ -1989,12 +1989,12 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(src_idx.row()); PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(src_idx.row());
if (!item) continue; if (!item) continue;
if (item->Metadata().url().isLocalFile()) ++local_songs; if (item->EffectiveMetadata().url().isLocalFile()) ++local_songs;
if (item->Metadata().has_cue()) { if (item->EffectiveMetadata().has_cue()) {
cue_selected = true; cue_selected = true;
} }
else if (item->Metadata().IsEditable()) { else if (item->EffectiveMetadata().IsEditable()) {
++editable; ++editable;
} }
@@ -2097,7 +2097,7 @@ void MainWindow::PlaylistRightClick(const QPoint global_pos, const QModelIndex &
// Is it a collection item? // Is it a collection item?
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row()); PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) { if (item && item->IsLocalCollectionItem() && item->EffectiveMetadata().id() != -1) {
playlist_organize_->setVisible(local_songs > 0 && editable > 0 && !cue_selected); playlist_organize_->setVisible(local_songs > 0 && editable > 0 && !cue_selected);
playlist_show_in_collection_->setVisible(true); playlist_show_in_collection_->setVisible(true);
playlist_open_in_browser_->setVisible(true); playlist_open_in_browser_->setVisible(true);
@@ -2189,9 +2189,9 @@ void MainWindow::RescanSongs() {
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row())); PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(source_index.row()));
if (!item) continue; if (!item) continue;
if (item->IsLocalCollectionItem()) { if (item->IsLocalCollectionItem()) {
songs << item->Metadata(); songs << item->EffectiveMetadata();
} }
else if (item->Metadata().source() == Song::Source::LocalFile) { else if (item->EffectiveMetadata().source() == Song::Source::LocalFile) {
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index); QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
app_->playlist_manager()->current()->ItemReload(persistent_index, item->OriginalMetadata(), false); app_->playlist_manager()->current()->ItemReload(persistent_index, item->OriginalMetadata(), false);
} }
@@ -2823,7 +2823,7 @@ void MainWindow::PlaylistOpenInBrowser() {
for (const QModelIndex &proxy_index : proxy_indexes) { for (const QModelIndex &proxy_index : proxy_indexes) {
const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index); const QModelIndex source_index = app_->playlist_manager()->current()->filter()->mapToSource(proxy_index);
if (!source_index.isValid()) continue; if (!source_index.isValid()) continue;
urls << QUrl(source_index.sibling(source_index.row(), static_cast<int>(Playlist::Column::Filename)).data().toString()); urls << QUrl(source_index.sibling(source_index.row(), static_cast<int>(Playlist::Column::URL)).data().toString());
} }
Utilities::OpenInFileBrowser(urls); Utilities::OpenInFileBrowser(urls);
@@ -2839,7 +2839,7 @@ void MainWindow::PlaylistCopyUrl() {
if (!source_index.isValid()) continue; if (!source_index.isValid()) continue;
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row()); PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_index.row());
if (!item) continue; if (!item) continue;
urls << item->StreamUrl(); urls << item->EffectiveUrl();
} }
if (urls.count() > 0) { if (urls.count() > 0) {
@@ -3321,10 +3321,10 @@ void MainWindow::PlaylistDelete() {
for (const QModelIndex &proxy_idx : proxy_indexes) { for (const QModelIndex &proxy_idx : proxy_indexes) {
QModelIndex source_idx = app_->playlist_manager()->current()->filter()->mapToSource(proxy_idx); QModelIndex source_idx = app_->playlist_manager()->current()->filter()->mapToSource(proxy_idx);
PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_idx.row()); PlaylistItemPtr item = app_->playlist_manager()->current()->item_at(source_idx.row());
if (!item || !item->Metadata().url().isLocalFile()) continue; if (!item || !item->EffectiveMetadata().url().isLocalFile()) continue;
QString filename = item->Metadata().url().toLocalFile(); QString filename = item->EffectiveMetadata().url().toLocalFile();
if (files.contains(filename)) continue; if (files.contains(filename)) continue;
selected_songs << item->Metadata(); selected_songs << item->EffectiveMetadata();
files << filename; files << filename;
if (item == app_->player()->GetCurrentItem()) is_current_item = true; if (item == app_->player()->GetCurrentItem()) is_current_item = true;
} }

View File

@@ -288,10 +288,10 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
bool is_current = false; bool is_current = false;
bool is_next = false; bool is_next = false;
if (result.media_url_ == current_item->Url()) { if (result.media_url_ == current_item->OriginalUrl()) {
is_current = true; is_current = true;
} }
else if (has_next_row && next_item->Url() == result.media_url_) { else if (has_next_row && next_item->OriginalUrl() == result.media_url_) {
is_next = true; is_next = true;
} }
else { else {
@@ -316,8 +316,8 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
qLog(Debug) << "URL handler for" << result.media_url_ << "returned" << result.stream_url_; qLog(Debug) << "URL handler for" << result.media_url_ << "returned" << result.stream_url_;
Song song; Song song;
if (is_current) song = current_item->Metadata(); if (is_current) song = current_item->EffectiveMetadata();
else if (is_next) song = next_item->Metadata(); else if (is_next) song = next_item->EffectiveMetadata();
bool update = false; bool update = false;
@@ -325,7 +325,7 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
if ( if (
(result.stream_url_.isValid()) (result.stream_url_.isValid())
&& &&
(result.stream_url_ != song.url()) (result.stream_url_ != song.effective_url())
) )
{ {
song.set_stream_url(result.stream_url_); song.set_stream_url(result.stream_url_);
@@ -371,14 +371,14 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
} }
if (is_current) { if (is_current) {
qLog(Debug) << "Playing song" << current_item->Metadata().title() << result.stream_url_ << "position" << play_offset_nanosec_; qLog(Debug) << "Playing song" << current_item->EffectiveMetadata().title() << result.stream_url_ << "position" << play_offset_nanosec_;
engine_->Play(result.media_url_, result.stream_url_, pause_, stream_change_type_, song.has_cue(), static_cast<quint64>(song.beginning_nanosec()), song.end_nanosec(), play_offset_nanosec_, song.ebur128_integrated_loudness_lufs()); engine_->Play(result.media_url_, result.stream_url_, pause_, stream_change_type_, song.has_cue(), static_cast<quint64>(song.beginning_nanosec()), song.end_nanosec(), play_offset_nanosec_, song.ebur128_integrated_loudness_lufs());
current_item_ = current_item; current_item_ = current_item;
play_offset_nanosec_ = 0; play_offset_nanosec_ = 0;
} }
else if (is_next && !current_item->Metadata().is_module_music()) { else if (is_next && !current_item->EffectiveMetadata().is_module_music()) {
qLog(Debug) << "Preloading next song" << next_item->Metadata().title() << result.stream_url_; qLog(Debug) << "Preloading next song" << next_item->EffectiveMetadata().title() << result.stream_url_;
engine_->StartPreloading(next_item->Url(), result.stream_url_, song.has_cue(), song.beginning_nanosec(), song.end_nanosec()); engine_->StartPreloading(next_item->OriginalUrl(), result.stream_url_, song.has_cue(), song.beginning_nanosec(), song.end_nanosec());
} }
break; break;
@@ -504,8 +504,8 @@ bool Player::HandleStopAfter(const Playlist::AutoScroll autoscroll) {
void Player::TrackEnded() { void Player::TrackEnded() {
if (current_item_ && current_item_->IsLocalCollectionItem() && current_item_->Metadata().id() != -1) { if (current_item_ && current_item_->IsLocalCollectionItem() && current_item_->EffectiveMetadata().id() != -1) {
playlist_manager_->collection_backend()->IncrementPlayCountAsync(current_item_->Metadata().id()); playlist_manager_->collection_backend()->IncrementPlayCountAsync(current_item_->EffectiveMetadata().id());
} }
if (HandleStopAfter(Playlist::AutoScroll::Maybe)) return; if (HandleStopAfter(Playlist::AutoScroll::Maybe)) return;
@@ -554,7 +554,7 @@ void Player::PlayPause(const quint64 offset_nanosec, const Playlist::AutoScroll
void Player::UnPause() { void Player::UnPause() {
if (current_item_ && pause_time_.isValid()) { if (current_item_ && pause_time_.isValid()) {
const Song &song = current_item_->Metadata(); const Song &song = current_item_->EffectiveMetadata();
if (url_handlers_->CanHandle(song.url()) && song.stream_url_can_expire()) { if (url_handlers_->CanHandle(song.url()) && song.stream_url_can_expire()) {
const qint64 time = QDateTime::currentSecsSinceEpoch() - pause_time_.toSecsSinceEpoch(); const qint64 time = QDateTime::currentSecsSinceEpoch() - pause_time_.toSecsSinceEpoch();
if (time >= 30) { // Stream URL might be expired. if (time >= 30) { // Stream URL might be expired.
@@ -745,7 +745,7 @@ void Player::PlayAt(const int index, const bool pause, const quint64 offset_nano
Q_EMIT TrackSkipped(current_item_); Q_EMIT TrackSkipped(current_item_);
} }
if (current_item_ && playlist_manager_->active()->has_item_at(index) && current_item_->Metadata().IsOnSameAlbum(playlist_manager_->active()->item_at(index)->Metadata())) { if (current_item_ && playlist_manager_->active()->has_item_at(index) && current_item_->EffectiveMetadata().IsOnSameAlbum(playlist_manager_->active()->item_at(index)->EffectiveMetadata())) {
change |= EngineBase::TrackChangeType::SameAlbum; change |= EngineBase::TrackChangeType::SameAlbum;
} }
@@ -758,7 +758,7 @@ void Player::PlayAt(const int index, const bool pause, const quint64 offset_nano
} }
current_item_ = playlist_manager_->active()->current_item(); current_item_ = playlist_manager_->active()->current_item();
const QUrl url = current_item_->StreamUrl(); const QUrl url = current_item_->EffectiveUrl();
if (url_handlers_->CanHandle(url)) { if (url_handlers_->CanHandle(url)) {
// It's already loading // It's already loading
@@ -773,8 +773,8 @@ void Player::PlayAt(const int index, const bool pause, const quint64 offset_nano
HandleLoadResult(url_handler->StartLoading(url)); HandleLoadResult(url_handler->StartLoading(url));
} }
else { else {
qLog(Debug) << "Playing song" << current_item_->Metadata().title() << url << "position" << offset_nanosec; qLog(Debug) << "Playing song" << current_item_->EffectiveMetadata().title() << url << "position" << offset_nanosec;
engine_->Play(current_item_->Url(), url, pause, change, current_item_->Metadata().has_cue(), static_cast<quint64>(current_item_->effective_beginning_nanosec()), current_item_->effective_end_nanosec(), offset_nanosec, current_item_->effective_ebur128_integrated_loudness_lufs()); engine_->Play(current_item_->OriginalUrl(), url, pause, change, current_item_->EffectiveMetadata().has_cue(), static_cast<quint64>(current_item_->effective_beginning_nanosec()), current_item_->effective_end_nanosec(), offset_nanosec, current_item_->EffectiveMetadata().ebur128_integrated_loudness_lufs());
} }
} }
@@ -823,8 +823,8 @@ void Player::EngineMetadataReceived(const EngineMetadata &engine_metadata) {
const int current_row = playlist_manager_->active()->current_row(); const int current_row = playlist_manager_->active()->current_row();
if (current_row != -1) { if (current_row != -1) {
PlaylistItemPtr item = playlist_manager_->active()->current_item(); PlaylistItemPtr item = playlist_manager_->active()->current_item();
if (item && engine_metadata.media_url == item->Url()) { if (item && engine_metadata.media_url == item->OriginalUrl()) {
Song song = item->Metadata(); Song song = item->EffectiveMetadata();
song.MergeFromEngineMetadata(engine_metadata); song.MergeFromEngineMetadata(engine_metadata);
playlist_manager_->active()->UpdateItemMetadata(current_row, item, song, true); playlist_manager_->active()->UpdateItemMetadata(current_row, item, song, true);
return; return;
@@ -836,8 +836,8 @@ void Player::EngineMetadataReceived(const EngineMetadata &engine_metadata) {
const int next_row = playlist_manager_->active()->next_row(); const int next_row = playlist_manager_->active()->next_row();
if (next_row != -1) { if (next_row != -1) {
PlaylistItemPtr next_item = playlist_manager_->active()->item_at(next_row); PlaylistItemPtr next_item = playlist_manager_->active()->item_at(next_row);
if (engine_metadata.media_url == next_item->Url()) { if (engine_metadata.media_url == next_item->OriginalUrl()) {
Song song = next_item->Metadata(); Song song = next_item->EffectiveMetadata();
song.MergeFromEngineMetadata(engine_metadata); song.MergeFromEngineMetadata(engine_metadata);
playlist_manager_->active()->UpdateItemMetadata(next_row, next_item, song, true); playlist_manager_->active()->UpdateItemMetadata(next_row, next_item, song, true);
} }
@@ -905,11 +905,11 @@ void Player::PlayWithPause(const quint64 offset_nanosec) {
} }
void Player::ShowOSD() { void Player::ShowOSD() {
if (current_item_) Q_EMIT ForceShowOSD(current_item_->Metadata(), false); if (current_item_) Q_EMIT ForceShowOSD(current_item_->EffectiveMetadata(), false);
} }
void Player::TogglePrettyOSD() { void Player::TogglePrettyOSD() {
if (current_item_) Q_EMIT ForceShowOSD(current_item_->Metadata(), true); if (current_item_) Q_EMIT ForceShowOSD(current_item_->EffectiveMetadata(), true);
} }
void Player::TrackAboutToEnd() { void Player::TrackAboutToEnd() {
@@ -932,7 +932,7 @@ void Player::TrackAboutToEnd() {
// If the next track is on the same album (or same cue file), // If the next track is on the same album (or same cue file),
// and the user doesn't want to crossfade between tracks on the same album, then don't do this automatic crossfading. // and the user doesn't want to crossfade between tracks on the same album, then don't do this automatic crossfading.
if (engine_->crossfade_same_album() || !has_next_row || !next_item || !current_item_->Metadata().IsOnSameAlbum(next_item->Metadata())) { if (engine_->crossfade_same_album() || !has_next_row || !next_item || !current_item_->EffectiveMetadata().IsOnSameAlbum(next_item->EffectiveMetadata())) {
TrackEnded(); TrackEnded();
return; return;
} }
@@ -941,7 +941,7 @@ void Player::TrackAboutToEnd() {
// Crossfade is off, so start preloading the next track, so we don't get a gap between songs. // Crossfade is off, so start preloading the next track, so we don't get a gap between songs.
if (!has_next_row || !next_item) return; if (!has_next_row || !next_item) return;
QUrl url = next_item->StreamUrl(); QUrl url = next_item->EffectiveUrl();
// Get the actual track URL rather than the stream URL. // Get the actual track URL rather than the stream URL.
if (url_handlers_->CanHandle(url)) { if (url_handlers_->CanHandle(url)) {
@@ -961,20 +961,20 @@ void Player::TrackAboutToEnd() {
case UrlHandler::LoadResult::Type::TrackAvailable: case UrlHandler::LoadResult::Type::TrackAvailable:
qLog(Debug) << "URL handler for" << result.media_url_ << "returned" << result.stream_url_; qLog(Debug) << "URL handler for" << result.media_url_ << "returned" << result.stream_url_;
url = result.stream_url_; url = result.stream_url_;
Song song = next_item->Metadata(); Song song = next_item->EffectiveMetadata();
song.set_stream_url(url); song.set_stream_url(url);
next_item->SetTemporaryMetadata(song); next_item->SetStreamMetadata(song);
break; break;
} }
} }
// Preloading any format while currently playing module music is broken in GStreamer. // Preloading any format while currently playing module music is broken in GStreamer.
// See: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/769 // See: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/769
if (current_item_ && current_item_->Metadata().is_module_music()) { if (current_item_ && current_item_->EffectiveMetadata().is_module_music()) {
return; return;
} }
engine_->StartPreloading(next_item->Url(), url, next_item->Metadata().has_cue(), next_item->effective_beginning_nanosec(), next_item->effective_end_nanosec()); engine_->StartPreloading(next_item->OriginalUrl(), url, next_item->EffectiveMetadata().has_cue(), next_item->effective_beginning_nanosec(), next_item->effective_end_nanosec());
} }

View File

@@ -342,7 +342,7 @@ struct Song::Private : public QSharedData {
QString artist_sortable_; QString artist_sortable_;
QString albumartist_sortable_; QString albumartist_sortable_;
QUrl stream_url_; // Temporary stream url set by the URL handler. QUrl stream_url_; // Temporary stream URL set by the URL handler.
}; };
@@ -653,7 +653,7 @@ void Song::set_musicbrainz_disc_id(const TagLib::String &v) { d->musicbrainz_dis
void Song::set_musicbrainz_release_group_id(const TagLib::String &v) { d->musicbrainz_release_group_id_ = TagLibStringToQString(v).remove(u' ').replace(u';', u'/'); } void Song::set_musicbrainz_release_group_id(const TagLib::String &v) { d->musicbrainz_release_group_id_ = TagLibStringToQString(v).remove(u' ').replace(u';', u'/'); }
void Song::set_musicbrainz_work_id(const TagLib::String &v) { d->musicbrainz_work_id_ = TagLibStringToQString(v).remove(u' ').replace(u';', u'/'); } void Song::set_musicbrainz_work_id(const TagLib::String &v) { d->musicbrainz_work_id_ = TagLibStringToQString(v).remove(u' ').replace(u';', u'/'); }
const QUrl &Song::effective_stream_url() const { return !d->stream_url_.isEmpty() && d->stream_url_.isValid() ? d->stream_url_ : d->url_; } const QUrl &Song::effective_url() const { return !d->stream_url_.isEmpty() && d->stream_url_.isValid() ? d->stream_url_ : d->url_; }
const QString &Song::effective_albumartist() const { return d->albumartist_.isEmpty() ? d->artist_ : d->albumartist_; } const QString &Song::effective_albumartist() const { return d->albumartist_.isEmpty() ? d->artist_ : d->albumartist_; }
const QString &Song::effective_albumartist_sortable() const { return d->albumartist_.isEmpty() ? d->artist_sortable_ : d->albumartist_sortable_; } const QString &Song::effective_albumartist_sortable() const { return d->albumartist_.isEmpty() ? d->artist_sortable_ : d->albumartist_sortable_; }
const QString &Song::effective_album() const { return d->album_.isEmpty() ? d->title_ : d->album_; } const QString &Song::effective_album() const { return d->album_.isEmpty() ? d->title_ : d->album_; }
@@ -923,39 +923,54 @@ bool Song::IsEditable() const {
return d->valid_ && d->url_.isValid() && ((d->url_.isLocalFile() && write_tags_supported() && !has_cue()) || d->source_ == Source::Stream); return d->valid_ && d->url_.isValid() && ((d->url_.isLocalFile() && write_tags_supported() && !has_cue()) || d->source_ == Source::Stream);
} }
bool Song::IsFileInfoEqual(const Song &other) const {
return d->beginning_ == other.d->beginning_ &&
d->end_ == other.d->end_ &&
d->url_ == other.d->url_ &&
d->basefilename_ == other.d->basefilename_ &&
d->filetype_ == other.d->filetype_ &&
d->filesize_ == other.d->filesize_ &&
d->mtime_ == other.d->mtime_ &&
d->ctime_ == other.d->ctime_ &&
d->mtime_ == other.d->mtime_ &&
d->stream_url_ == other.d->stream_url_;
}
bool Song::IsMetadataEqual(const Song &other) const { bool Song::IsMetadataEqual(const Song &other) const {
return d->title_ == other.d->title_ && return d->title_ == other.d->title_ &&
d->album_ == other.d->album_ && d->album_ == other.d->album_ &&
d->artist_ == other.d->artist_ && d->artist_ == other.d->artist_ &&
d->albumartist_ == other.d->albumartist_ && d->albumartist_ == other.d->albumartist_ &&
d->track_ == other.d->track_ && d->track_ == other.d->track_ &&
d->disc_ == other.d->disc_ && d->disc_ == other.d->disc_ &&
d->year_ == other.d->year_ && d->year_ == other.d->year_ &&
d->originalyear_ == other.d->originalyear_ && d->originalyear_ == other.d->originalyear_ &&
d->genre_ == other.d->genre_ && d->genre_ == other.d->genre_ &&
d->compilation_ == other.d->compilation_ && d->compilation_ == other.d->compilation_ &&
d->composer_ == other.d->composer_ && d->composer_ == other.d->composer_ &&
d->performer_ == other.d->performer_ && d->performer_ == other.d->performer_ &&
d->grouping_ == other.d->grouping_ && d->grouping_ == other.d->grouping_ &&
d->comment_ == other.d->comment_ && d->comment_ == other.d->comment_ &&
d->lyrics_ == other.d->lyrics_ && d->lyrics_ == other.d->lyrics_ &&
d->artist_id_ == other.d->artist_id_ && d->artist_id_ == other.d->artist_id_ &&
d->album_id_ == other.d->album_id_ && d->album_id_ == other.d->album_id_ &&
d->song_id_ == other.d->song_id_ && d->song_id_ == other.d->song_id_ &&
d->beginning_ == other.d->beginning_ && d->beginning_ == other.d->beginning_ &&
length_nanosec() == other.length_nanosec() && length_nanosec() == other.length_nanosec() &&
d->bitrate_ == other.d->bitrate_ && d->bitrate_ == other.d->bitrate_ &&
d->samplerate_ == other.d->samplerate_ && d->samplerate_ == other.d->samplerate_ &&
d->bitdepth_ == other.d->bitdepth_ && d->bitdepth_ == other.d->bitdepth_ &&
d->cue_path_ == other.d->cue_path_; d->cue_path_ == other.d->cue_path_;
} }
bool Song::IsPlayStatisticsEqual(const Song &other) const { bool Song::IsPlayStatisticsEqual(const Song &other) const {
return d->playcount_ == other.d->playcount_ && return d->playcount_ == other.d->playcount_ &&
d->skipcount_ == other.d->skipcount_ && d->skipcount_ == other.d->skipcount_ &&
d->lastplayed_ == other.d->lastplayed_; d->lastplayed_ == other.d->lastplayed_;
} }
@@ -980,42 +995,70 @@ bool Song::IsAcoustIdEqual(const Song &other) const {
bool Song::IsMusicBrainzEqual(const Song &other) const { bool Song::IsMusicBrainzEqual(const Song &other) const {
return d->musicbrainz_album_artist_id_ == other.d->musicbrainz_album_artist_id_ && return d->musicbrainz_album_artist_id_ == other.d->musicbrainz_album_artist_id_ &&
d->musicbrainz_artist_id_ == other.d->musicbrainz_artist_id_ && d->musicbrainz_artist_id_ == other.d->musicbrainz_artist_id_ &&
d->musicbrainz_original_artist_id_ == other.d->musicbrainz_original_artist_id_ && d->musicbrainz_original_artist_id_ == other.d->musicbrainz_original_artist_id_ &&
d->musicbrainz_album_id_ == other.d->musicbrainz_album_id_ && d->musicbrainz_album_id_ == other.d->musicbrainz_album_id_ &&
d->musicbrainz_original_album_id_ == other.d->musicbrainz_original_album_id_ && d->musicbrainz_original_album_id_ == other.d->musicbrainz_original_album_id_ &&
d->musicbrainz_recording_id_ == other.d->musicbrainz_recording_id_ && d->musicbrainz_recording_id_ == other.d->musicbrainz_recording_id_ &&
d->musicbrainz_track_id_ == other.d->musicbrainz_track_id_ && d->musicbrainz_track_id_ == other.d->musicbrainz_track_id_ &&
d->musicbrainz_disc_id_ == other.d->musicbrainz_disc_id_ && d->musicbrainz_disc_id_ == other.d->musicbrainz_disc_id_ &&
d->musicbrainz_release_group_id_ == other.d->musicbrainz_release_group_id_ && d->musicbrainz_release_group_id_ == other.d->musicbrainz_release_group_id_ &&
d->musicbrainz_work_id_ == other.d->musicbrainz_work_id_; d->musicbrainz_work_id_ == other.d->musicbrainz_work_id_;
} }
bool Song::IsEBUR128Equal(const Song &other) const { bool Song::IsEBUR128Equal(const Song &other) const {
return d->ebur128_integrated_loudness_lufs_ == other.d->ebur128_integrated_loudness_lufs_ && return d->ebur128_integrated_loudness_lufs_ == other.d->ebur128_integrated_loudness_lufs_ &&
d->ebur128_loudness_range_lu_ == other.d->ebur128_loudness_range_lu_; d->ebur128_loudness_range_lu_ == other.d->ebur128_loudness_range_lu_;
} }
bool Song::IsArtEqual(const Song &other) const { bool Song::IsArtEqual(const Song &other) const {
return d->art_embedded_ == other.d->art_embedded_ && return d->art_embedded_ == other.d->art_embedded_ &&
d->art_automatic_ == other.d->art_automatic_ && d->art_automatic_ == other.d->art_automatic_ &&
d->art_manual_ == other.d->art_manual_ && d->art_manual_ == other.d->art_manual_ &&
d->art_unset_ == other.d->art_unset_; d->art_unset_ == other.d->art_unset_;
}
bool Song::IsCompilationEqual(const Song &other) const {
return d->compilation_ == other.d->compilation_ &&
d->compilation_detected_ == other.d->compilation_detected_ &&
d->compilation_on_ == other.d->compilation_on_ &&
d->compilation_off_ == other.d->compilation_off_;
}
bool Song::IsSettingsEqual(const Song &other) const {
return d->source_ == other.d->source_ &&
d->directory_id_ == other.d->directory_id_ &&
d->unavailable_ == other.d->unavailable_;
} }
bool Song::IsAllMetadataEqual(const Song &other) const { bool Song::IsAllMetadataEqual(const Song &other) const {
return IsMetadataEqual(other) && return IsMetadataEqual(other) &&
IsPlayStatisticsEqual(other) && IsPlayStatisticsEqual(other) &&
IsRatingEqual(other) && IsRatingEqual(other) &&
IsAcoustIdEqual(other) && IsAcoustIdEqual(other) &&
IsMusicBrainzEqual(other) && IsMusicBrainzEqual(other) &&
IsArtEqual(other); IsArtEqual(other) &&
IsEBUR128Equal(other);
}
bool Song::IsEqual(const Song &other) const {
return IsFileInfoEqual(other) &&
IsSettingsEqual(other) &&
IsAllMetadataEqual(other) &&
IsFingerprintEqual(other) &&
IsCompilationEqual(other);
} }
@@ -1819,7 +1862,7 @@ void Song::ToXesam(QVariantMap *map) const {
using mpris::AddMetadataAsList; using mpris::AddMetadataAsList;
using mpris::AsMPRISDateTimeType; using mpris::AsMPRISDateTimeType;
AddMetadata(u"xesam:url"_s, effective_stream_url().toString(), map); AddMetadata(u"xesam:url"_s, effective_url().toString(), map);
AddMetadata(u"xesam:title"_s, PrettyTitle(), map); AddMetadata(u"xesam:title"_s, PrettyTitle(), map);
AddMetadataAsList(u"xesam:artist"_s, artist(), map); AddMetadataAsList(u"xesam:artist"_s, artist(), map);
AddMetadata(u"xesam:album"_s, album(), map); AddMetadata(u"xesam:album"_s, album(), map);

View File

@@ -365,7 +365,7 @@ class Song {
void set_musicbrainz_release_group_id(const TagLib::String &v); void set_musicbrainz_release_group_id(const TagLib::String &v);
void set_musicbrainz_work_id(const TagLib::String &v); void set_musicbrainz_work_id(const TagLib::String &v);
const QUrl &effective_stream_url() const; const QUrl &effective_url() const;
const QString &effective_albumartist() const; const QString &effective_albumartist() const;
const QString &effective_albumartist_sortable() const; const QString &effective_albumartist_sortable() const;
const QString &effective_album() const; const QString &effective_album() const;
@@ -430,6 +430,7 @@ class Song {
bool IsEditable() const; bool IsEditable() const;
// Comparison functions // Comparison functions
bool IsFileInfoEqual(const Song &other) const;
bool IsMetadataEqual(const Song &other) const; bool IsMetadataEqual(const Song &other) const;
bool IsPlayStatisticsEqual(const Song &other) const; bool IsPlayStatisticsEqual(const Song &other) const;
bool IsRatingEqual(const Song &other) const; bool IsRatingEqual(const Song &other) const;
@@ -438,7 +439,10 @@ class Song {
bool IsMusicBrainzEqual(const Song &other) const; bool IsMusicBrainzEqual(const Song &other) const;
bool IsEBUR128Equal(const Song &other) const; bool IsEBUR128Equal(const Song &other) const;
bool IsArtEqual(const Song &other) const; bool IsArtEqual(const Song &other) const;
bool IsCompilationEqual(const Song &other) const;
bool IsSettingsEqual(const Song &other) const;
bool IsAllMetadataEqual(const Song &other) const; bool IsAllMetadataEqual(const Song &other) const;
bool IsEqual(const Song &other) const;
bool IsOnSameAlbum(const Song &other) const; bool IsOnSameAlbum(const Song &other) const;
bool IsSimilar(const Song &other) const; bool IsSimilar(const Song &other) const;

View File

@@ -51,7 +51,7 @@ QVariant FilterTree::DataFromColumn(const QString &column, const Song &metadata)
if (column == "playcount"_L1) return metadata.playcount(); if (column == "playcount"_L1) return metadata.playcount();
if (column == "skipcount"_L1) return metadata.skipcount(); if (column == "skipcount"_L1) return metadata.skipcount();
if (column == "filename"_L1) return metadata.basefilename(); if (column == "filename"_L1) return metadata.basefilename();
if (column == "url"_L1) return metadata.effective_stream_url().toString(); if (column == "url"_L1) return metadata.effective_url().toString();
return QVariant(); return QVariant();

View File

@@ -97,7 +97,7 @@ void MoodbarController::AsyncLoadComplete(MoodbarPipelinePtr pipeline, const QUr
// Is this song still playing? // Is this song still playing?
PlaylistItemPtr current_item = player_->GetCurrentItem(); PlaylistItemPtr current_item = player_->GetCurrentItem();
if (current_item && current_item->Url() != url) { if (current_item && current_item->OriginalUrl() != url) {
return; return;
} }
// Did we stop the song? // Did we stop the song?

View File

@@ -108,7 +108,7 @@ void MoodbarItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &o
QPixmap MoodbarItemDelegate::PixmapForIndex(const QModelIndex &idx, const QSize size) { QPixmap MoodbarItemDelegate::PixmapForIndex(const QModelIndex &idx, const QSize size) {
// Pixmaps are keyed off URL. // Pixmaps are keyed off URL.
const QUrl url = idx.sibling(idx.row(), static_cast<int>(Playlist::Column::Filename)).data().toUrl(); const QUrl url = idx.sibling(idx.row(), static_cast<int>(Playlist::Column::URL)).data().toUrl();
const bool has_cue = idx.sibling(idx.row(), static_cast<int>(Playlist::Column::HasCUE)).data().toBool(); const bool has_cue = idx.sibling(idx.row(), static_cast<int>(Playlist::Column::HasCUE)).data().toBool();
Data *data = nullptr; Data *data = nullptr;
@@ -294,7 +294,7 @@ void MoodbarItemDelegate::ImageLoaded(const QUrl &url, const QImage &image) {
// Update all the indices with the new pixmap. // Update all the indices with the new pixmap.
for (const QPersistentModelIndex &idx : std::as_const(data->indexes_)) { for (const QPersistentModelIndex &idx : std::as_const(data->indexes_)) {
if (idx.isValid() && idx.sibling(idx.row(), static_cast<int>(Playlist::Column::Filename)).data().toUrl() == url) { if (idx.isValid() && idx.sibling(idx.row(), static_cast<int>(Playlist::Column::URL)).data().toUrl() == url) {
QModelIndex source_index = idx; QModelIndex source_index = idx;
if (idx.model() == filter) { if (idx.model() == filter) {
source_index = filter->mapToSource(source_index); source_index = filter->mapToSource(source_index);

View File

@@ -522,7 +522,7 @@ bool Mpris2::CanSeek() const {
} }
bool Mpris2::CanSeek(EngineBase::State state) const { bool Mpris2::CanSeek(EngineBase::State state) const {
return player_->GetCurrentItem() && state != EngineBase::State::Empty && !player_->GetCurrentItem()->Metadata().is_stream(); return player_->GetCurrentItem() && state != EngineBase::State::Empty && !player_->GetCurrentItem()->EffectiveMetadata().is_stream();
} }
bool Mpris2::CanControl() const { return true; } bool Mpris2::CanControl() const { return true; }
@@ -586,7 +586,7 @@ void Mpris2::SetPosition(const QDBusObjectPath &trackId, qint64 offset) {
if (CanSeek() && trackId == current_track_id(current_row) && offset >= 0) { if (CanSeek() && trackId == current_track_id(current_row) && offset >= 0) {
offset *= kNsecPerUsec; offset *= kNsecPerUsec;
if (offset < player_->GetCurrentItem()->Metadata().length_nanosec()) { if (offset < player_->GetCurrentItem()->EffectiveMetadata().length_nanosec()) {
player_->SeekTo(offset / kNsecPerSec); player_->SeekTo(offset / kNsecPerSec);
} }
} }

View File

@@ -308,13 +308,13 @@ QVariant Playlist::data(const QModelIndex &idx, const int role) const {
return queue_->PositionOf(idx); return queue_->PositionOf(idx);
case Role_CanSetRating: case Role_CanSetRating:
return static_cast<Column>(idx.column()) == Column::Rating && items_[idx.row()]->IsLocalCollectionItem() && items_[idx.row()]->Metadata().id() != -1; return static_cast<Column>(idx.column()) == Column::Rating && items_[idx.row()]->IsLocalCollectionItem() && items_[idx.row()]->EffectiveMetadata().id() != -1;
case Qt::EditRole: case Qt::EditRole:
case Qt::ToolTipRole: case Qt::ToolTipRole:
case Qt::DisplayRole:{ case Qt::DisplayRole:{
const PlaylistItemPtr item = items_[idx.row()]; const PlaylistItemPtr item = items_[idx.row()];
const Song song = item->Metadata(); const Song song = item->EffectiveMetadata();
// Don't forget to change Playlist::CompareItems when adding new columns // Don't forget to change Playlist::CompareItems when adding new columns
switch (static_cast<Column>(idx.column())) { switch (static_cast<Column>(idx.column())) {
@@ -340,7 +340,7 @@ QVariant Playlist::data(const QModelIndex &idx, const int role) const {
case Column::Bitdepth: return song.bitdepth(); case Column::Bitdepth: return song.bitdepth();
case Column::Bitrate: return song.bitrate(); case Column::Bitrate: return song.bitrate();
case Column::Filename: return song.effective_stream_url(); case Column::URL: return song.effective_url();
case Column::BaseFilename: return song.basefilename(); case Column::BaseFilename: return song.basefilename();
case Column::Filesize: return song.filesize(); case Column::Filesize: return song.filesize();
case Column::Filetype: return QVariant::fromValue(song.filetype()); case Column::Filetype: return QVariant::fromValue(song.filetype());
@@ -441,7 +441,7 @@ bool Playlist::setData(const QModelIndex &idx, const QVariant &value, const int
}, Qt::QueuedConnection); }, Qt::QueuedConnection);
} }
else if (song.is_radio()) { else if (song.is_radio()) {
item->SetMetadata(song); item->SetOriginalMetadata(song);
ScheduleSave(); ScheduleSave();
} }
@@ -489,13 +489,13 @@ void Playlist::ItemReloadComplete(const QPersistentModelIndex &idx, const Song &
if (idx.isValid()) { if (idx.isValid()) {
const PlaylistItemPtr item = item_at(idx.row()); const PlaylistItemPtr item = item_at(idx.row());
if (item) { if (item) {
ItemChanged(idx.row(), ChangedColumns(old_metadata, item->Metadata())); RowDataChanged(idx.row(), ChangedColumns(old_metadata, item->EffectiveMetadata()));
if (idx.row() == current_row()) { if (idx.row() == current_row()) {
if (MinorMetadataChange(old_metadata, item->Metadata())) { if (MinorMetadataChange(old_metadata, item->EffectiveMetadata())) {
Q_EMIT CurrentSongMetadataChanged(item->Metadata()); Q_EMIT CurrentSongMetadataChanged(item->EffectiveMetadata());
} }
else { else {
Q_EMIT CurrentSongChanged(item->Metadata()); Q_EMIT CurrentSongChanged(item->EffectiveMetadata());
} }
} }
if (metadata_edit) { if (metadata_edit) {
@@ -560,7 +560,7 @@ int Playlist::NextVirtualIndex(int i, const bool ignore_repeat_track) const {
if (item_at(virtual_items_[j])->GetShouldSkip()) { if (item_at(virtual_items_[j])->GetShouldSkip()) {
continue; continue;
} }
const Song this_song = item_at(virtual_items_[j])->Metadata(); const Song this_song = item_at(virtual_items_[j])->EffectiveMetadata();
if (((last_song.is_compilation() && this_song.is_compilation()) || if (((last_song.is_compilation() && this_song.is_compilation()) ||
last_song.effective_albumartist() == this_song.effective_albumartist()) && last_song.effective_albumartist() == this_song.effective_albumartist()) &&
last_song.album() == this_song.album() && last_song.album() == this_song.album() &&
@@ -600,7 +600,7 @@ int Playlist::PreviousVirtualIndex(int i, const bool ignore_repeat_track) const
if (item_at(virtual_items_[j])->GetShouldSkip()) { if (item_at(virtual_items_[j])->GetShouldSkip()) {
continue; continue;
} }
Song this_song = item_at(virtual_items_[j])->Metadata(); Song this_song = item_at(virtual_items_[j])->EffectiveMetadata();
if (((last_song.is_compilation() && this_song.is_compilation()) || last_song.artist() == this_song.artist()) && last_song.album() == this_song.album() && FilterContainsVirtualIndex(j)) { if (((last_song.is_compilation() && this_song.is_compilation()) || last_song.artist() == this_song.artist()) && last_song.album() == this_song.album() && FilterContainsVirtualIndex(j)) {
return j; // Found one return j; // Found one
} }
@@ -687,7 +687,7 @@ void Playlist::set_current_row(const int i, const AutoScroll autoscroll, const b
if (nextrow != -1 && nextrow != i) { if (nextrow != -1 && nextrow != i) {
PlaylistItemPtr next_item = item_at(nextrow); PlaylistItemPtr next_item = item_at(nextrow);
if (next_item) { if (next_item) {
next_item->ClearTemporaryMetadata(); next_item->ClearStreamMetadata();
Q_EMIT dataChanged(index(nextrow, 0), index(nextrow, ColumnCount - 1)); Q_EMIT dataChanged(index(nextrow, 0), index(nextrow, ColumnCount - 1));
} }
} }
@@ -788,7 +788,7 @@ Qt::ItemFlags Playlist::flags(const QModelIndex &idx) const {
if (idx.isValid()) { if (idx.isValid()) {
Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled; Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled;
if (item_at(idx.row())->Metadata().IsEditable() && column_is_editable(static_cast<Column>(idx.column()))) flags |= Qt::ItemIsEditable; if (item_at(idx.row())->EffectiveMetadata().IsEditable() && column_is_editable(static_cast<Column>(idx.column()))) flags |= Qt::ItemIsEditable;
return flags; return flags;
} }
@@ -1131,9 +1131,9 @@ void Playlist::InsertItemsWithoutUndo(const PlaylistItemPtrList &items, const in
virtual_items_ << static_cast<int>(virtual_items_.count()); virtual_items_ << static_cast<int>(virtual_items_.count());
if (Song::IsLinkedCollectionSource(item->source())) { if (Song::IsLinkedCollectionSource(item->source())) {
const int id = item->Metadata().id(); const int id = item->EffectiveMetadata().id();
if (id != -1) { if (id != -1) {
collection_items_[item->Metadata().source_id()].insert(id, item); collection_items_[item->EffectiveMetadata().source_id()].insert(id, item);
} }
} }
@@ -1235,7 +1235,7 @@ void Playlist::UpdateItems(SongList songs) {
while (it.hasNext()) { while (it.hasNext()) {
const Song &song = it.next(); const Song &song = it.next();
const PlaylistItemPtr item = items_.value(i); 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())) { if (item->EffectiveMetadata().url() == song.url() && (item->EffectiveMetadata().filetype() == Song::FileType::Unknown || item->EffectiveMetadata().filetype() == Song::FileType::Stream || item->EffectiveMetadata().filetype() == Song::FileType::CDDA || !item->EffectiveMetadata().init_from_file())) {
PlaylistItemPtr new_item; PlaylistItemPtr new_item;
if (song.is_linked_collection_song()) { if (song.is_linked_collection_song()) {
new_item = make_shared<CollectionPlaylistItem>(song); new_item = make_shared<CollectionPlaylistItem>(song);
@@ -1290,7 +1290,7 @@ QMimeData *Playlist::mimeData(const QModelIndexList &indexes) const {
for (const QModelIndex &idx : indexes) { for (const QModelIndex &idx : indexes) {
if (idx.column() != first_column) continue; if (idx.column() != first_column) continue;
urls << items_[idx.row()]->Url(); urls << items_[idx.row()]->OriginalUrl();
rows << idx.row(); rows << idx.row();
} }
@@ -1321,8 +1321,8 @@ bool Playlist::CompareItems(const Column column, const Qt::SortOrder order, Play
PlaylistItemPtr a = order == Qt::AscendingOrder ? _a : _b; PlaylistItemPtr a = order == Qt::AscendingOrder ? _a : _b;
PlaylistItemPtr b = order == Qt::AscendingOrder ? _b : _a; PlaylistItemPtr b = order == Qt::AscendingOrder ? _b : _a;
#define cmp(field) return a->Metadata().field() < b->Metadata().field() #define cmp(field) return a->EffectiveMetadata().field() < b->EffectiveMetadata().field()
#define strcmp(field) return QString::localeAwareCompare(a->Metadata().field().toLower(), b->Metadata().field().toLower()) < 0; #define strcmp(field) return QString::localeAwareCompare(a->EffectiveMetadata().field().toLower(), b->EffectiveMetadata().field().toLower()) < 0;
switch (column) { switch (column) {
case Column::Title: strcmp(title_sortable); case Column::Title: strcmp(title_sortable);
@@ -1346,8 +1346,8 @@ bool Playlist::CompareItems(const Column column, const Qt::SortOrder order, Play
case Column::Bitrate: cmp(bitrate); case Column::Bitrate: cmp(bitrate);
case Column::Samplerate: cmp(samplerate); case Column::Samplerate: cmp(samplerate);
case Column::Bitdepth: cmp(bitdepth); case Column::Bitdepth: cmp(bitdepth);
case Column::Filename: case Column::URL:
return QString::localeAwareCompare(a->Url().path(), b->Url().path()) < 0; return QString::localeAwareCompare(a->OriginalUrl().path(), b->OriginalUrl().path()) < 0;
case Column::BaseFilename: cmp(basefilename); case Column::BaseFilename: cmp(basefilename);
case Column::Filesize: cmp(filesize); case Column::Filesize: cmp(filesize);
case Column::Filetype: cmp(filetype); case Column::Filetype: cmp(filetype);
@@ -1401,7 +1401,7 @@ QString Playlist::column_name(const Column column) {
case Column::Bitdepth: return tr("Bit Depth"); case Column::Bitdepth: return tr("Bit Depth");
case Column::Bitrate: return tr("Bitrate"); case Column::Bitrate: return tr("Bitrate");
case Column::Filename: return tr("File Name"); case Column::URL: return tr("URL");
case Column::BaseFilename: return tr("File Name (without path)"); case Column::BaseFilename: return tr("File Name (without path)");
case Column::Filesize: return tr("File Size"); case Column::Filesize: return tr("File Size");
case Column::Filetype: return tr("File Type"); case Column::Filetype: return tr("File Type");
@@ -1595,7 +1595,7 @@ void Playlist::ItemsLoaded() {
while (it.hasNext()) { while (it.hasNext()) {
PlaylistItemPtr item = it.next(); PlaylistItemPtr item = it.next();
if (item->IsLocalCollectionItem() && item->Metadata().url().isEmpty()) { if (item->IsLocalCollectionItem() && item->EffectiveMetadata().url().isEmpty()) {
it.remove(); it.remove();
} }
} }
@@ -1727,8 +1727,8 @@ PlaylistItemPtrList Playlist::RemoveItemsWithoutUndo(const int row, const int co
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
PlaylistItemPtr item(items_.takeAt(row)); PlaylistItemPtr item(items_.takeAt(row));
items << item; items << item;
const int id = item->Metadata().id(); const int id = item->EffectiveMetadata().id();
const int source_id = item->Metadata().source_id(); const int source_id = item->EffectiveMetadata().source_id();
if (id != -1 && collection_items_[source_id].contains(id, item)) { if (id != -1 && collection_items_[source_id].contains(id, item)) {
collection_items_[source_id].remove(id, item); collection_items_[source_id].remove(id, item);
} }
@@ -1792,11 +1792,11 @@ void Playlist::ClearStreamMetadata() {
if (!current_item() || !current_item_index_.isValid()) return; if (!current_item() || !current_item_index_.isValid()) return;
const Song old_metadata = current_item()->Metadata(); const Song old_metadata = current_item()->EffectiveMetadata();
current_item()->ClearTemporaryMetadata(); current_item()->ClearStreamMetadata();
const Song &new_metadata = current_item()->Metadata(); const Song &new_metadata = current_item()->EffectiveMetadata();
ItemChanged(current_row(), ChangedColumns(old_metadata, new_metadata)); RowDataChanged(current_row(), ChangedColumns(old_metadata, new_metadata));
if (old_metadata.length_nanosec() != new_metadata.length_nanosec()) { if (old_metadata.length_nanosec() != new_metadata.length_nanosec()) {
UpdateScrobblePoint(); UpdateScrobblePoint();
@@ -1832,7 +1832,7 @@ PlaylistItem::Options Playlist::current_item_options() const {
Song Playlist::current_item_metadata() const { Song Playlist::current_item_metadata() const {
if (!current_item()) return Song(); if (!current_item()) return Song();
return current_item()->Metadata(); return current_item()->EffectiveMetadata();
} }
void Playlist::Clear() { void Playlist::Clear() {
@@ -1910,7 +1910,7 @@ void Playlist::ReloadItems(const QList<int> &rows) {
const PlaylistItemPtr item = item_at(row); const PlaylistItemPtr item = item_at(row);
const QPersistentModelIndex idx = index(row, 0); const QPersistentModelIndex idx = index(row, 0);
if (idx.isValid()) { if (idx.isValid()) {
ItemReload(idx, item->Metadata(), false); ItemReload(idx, item->EffectiveMetadata(), false);
} }
} }
@@ -1981,7 +1981,7 @@ void Playlist::ReshuffleIndices() {
// Find all the unique albums in the playlist // Find all the unique albums in the playlist
for (QList<int>::const_iterator it = virtual_items_.constBegin(); it != virtual_items_.constEnd(); ++it) { for (QList<int>::const_iterator it = virtual_items_.constBegin(); it != virtual_items_.constEnd(); ++it) {
const int index = *it; const int index = *it;
const QString key = items_[index]->Metadata().AlbumKey(); const QString key = items_[index]->EffectiveMetadata().AlbumKey();
album_keys[index] = key; album_keys[index] = key;
album_key_set << key; album_key_set << key;
} }
@@ -1993,7 +1993,7 @@ void Playlist::ReshuffleIndices() {
// If the user is currently playing a song, force its album to be first // If the user is currently playing a song, force its album to be first
if (current_row() != -1) { if (current_row() != -1) {
const QString key = items_[current_row()]->Metadata().AlbumKey(); const QString key = items_[current_row()]->EffectiveMetadata().AlbumKey();
const qint64 pos = shuffled_album_keys.indexOf(key); const qint64 pos = shuffled_album_keys.indexOf(key);
if (pos >= 1) { if (pos >= 1) {
std::swap(shuffled_album_keys[0], shuffled_album_keys[pos]); std::swap(shuffled_album_keys[0], shuffled_album_keys[pos]);
@@ -2039,7 +2039,7 @@ SongList Playlist::GetAllSongs() const {
SongList songs; SongList songs;
songs.reserve(items_.count()); songs.reserve(items_.count());
for (PlaylistItemPtr item : items_) { // clazy:exclude=range-loop-reference for (PlaylistItemPtr item : items_) { // clazy:exclude=range-loop-reference
songs << item->Metadata(); songs << item->EffectiveMetadata();
} }
return songs; return songs;
@@ -2051,7 +2051,7 @@ quint64 Playlist::GetTotalLength() const {
quint64 total_length = 0; quint64 total_length = 0;
for (PlaylistItemPtr item : items_) { // clazy:exclude=range-loop-reference for (PlaylistItemPtr item : items_) { // clazy:exclude=range-loop-reference
qint64 length = item->Metadata().length_nanosec(); qint64 length = item->EffectiveMetadata().length_nanosec();
if (length > 0) total_length += length; if (length > 0) total_length += length;
} }
@@ -2151,8 +2151,9 @@ Playlist::Columns Playlist::ChangedColumns(const Song &metadata1, const Song &me
if (metadata1.bitrate() != metadata2.bitrate()) { if (metadata1.bitrate() != metadata2.bitrate()) {
columns << Column::Bitrate; columns << Column::Bitrate;
} }
if (metadata1.url() != metadata2.url()) { if (metadata1.effective_url() != metadata2.effective_url()) {
columns << Column::Filename; qLog(Debug) << "URL is changed for" << metadata1.PrettyTitleWithArtist();
columns << Column::URL;
columns << Column::BaseFilename; columns << Column::BaseFilename;
} }
if (metadata1.filesize() != metadata2.filesize()) { if (metadata1.filesize() != metadata2.filesize()) {
@@ -2211,36 +2212,38 @@ bool Playlist::MinorMetadataChange(const Song &old_metadata, const Song &new_met
} }
void Playlist::UpdateItemMetadata(PlaylistItemPtr item, const Song &new_metadata, const bool temporary) { void Playlist::UpdateItemMetadata(PlaylistItemPtr item, const Song &new_metadata, const bool stream_metadata_update) {
if (!items_.contains(item)) { if (!items_.contains(item)) {
return; return;
} }
for (int row = static_cast<int>(items_.indexOf(item, 0)); row != -1; row = static_cast<int>(items_.indexOf(item, row + 1))) { for (int row = static_cast<int>(items_.indexOf(item, 0)); row != -1; row = static_cast<int>(items_.indexOf(item, row + 1))) {
UpdateItemMetadata(row, item, new_metadata, temporary); UpdateItemMetadata(row, item, new_metadata, stream_metadata_update);
} }
} }
void Playlist::UpdateItemMetadata(const int row, PlaylistItemPtr item, const Song &new_metadata, const bool temporary) { void Playlist::UpdateItemMetadata(const int row, PlaylistItemPtr item, const Song &new_metadata, const bool stream_metadata_update) {
const Song old_metadata = item->Metadata(); if (new_metadata.IsEqual(stream_metadata_update ? item->EffectiveMetadata() : item->OriginalMetadata())) return;
const Columns columns = ChangedColumns(old_metadata, new_metadata); const Song old_metadata = item->EffectiveMetadata();
if (columns.isEmpty()) return; const Columns changed_columns = ChangedColumns(old_metadata, new_metadata);
if (temporary) { if (stream_metadata_update) {
item->SetTemporaryMetadata(new_metadata); item->SetStreamMetadata(new_metadata);
} }
else { else {
item->SetMetadata(new_metadata); item->SetOriginalMetadata(new_metadata);
if (item->HasTemporaryMetadata()) { if (item->HasStreamMetadata()) {
item->UpdateTemporaryMetadata(new_metadata); item->UpdateStreamMetadata(new_metadata);
} }
} }
ItemChanged(row, columns); if (!changed_columns.isEmpty()) {
RowDataChanged(row, changed_columns);
}
if (row == current_row()) { if (row == current_row()) {
InformOfCurrentSongChange(MinorMetadataChange(old_metadata, new_metadata)); InformOfCurrentSongChange(MinorMetadataChange(old_metadata, new_metadata));
@@ -2251,7 +2254,7 @@ void Playlist::UpdateItemMetadata(const int row, PlaylistItemPtr item, const Son
} }
void Playlist::ItemChanged(const int row, const Columns &columns) { void Playlist::RowDataChanged(const int row, const Columns &columns) {
if (columns.count() > 5) { if (columns.count() > 5) {
const QModelIndex idx_column_first = index(row, 0); const QModelIndex idx_column_first = index(row, 0);
@@ -2293,7 +2296,7 @@ void Playlist::InvalidateDeletedSongs() {
for (int row = 0; row < items_.count(); ++row) { for (int row = 0; row < items_.count(); ++row) {
PlaylistItemPtr item = items_.value(row); PlaylistItemPtr item = items_.value(row);
const Song song = item->Metadata(); const Song song = item->EffectiveMetadata();
if (song.url().isValid() && song.url().isLocalFile()) { if (song.url().isValid() && song.url().isLocalFile()) {
const bool exists = QFile::exists(song.url().toLocalFile()); const bool exists = QFile::exists(song.url().toLocalFile());
@@ -2322,7 +2325,7 @@ void Playlist::RemoveDeletedSongs() {
for (int row = 0; row < items_.count(); ++row) { for (int row = 0; row < items_.count(); ++row) {
const PlaylistItemPtr item = items_.value(row); const PlaylistItemPtr item = items_.value(row);
const Song song = item->Metadata(); const Song song = item->EffectiveMetadata();
if (song.url().isLocalFile() && !QFile::exists(song.url().toLocalFile())) { if (song.url().isLocalFile() && !QFile::exists(song.url().toLocalFile())) {
rows_to_remove.append(row); // clazy:exclude=reserve-candidates rows_to_remove.append(row); // clazy:exclude=reserve-candidates
@@ -2356,7 +2359,7 @@ void Playlist::RemoveDuplicateSongs() {
for (int row = 0; row < items_.count(); ++row) { for (int row = 0; row < items_.count(); ++row) {
const PlaylistItemPtr item = items_.value(row); const PlaylistItemPtr item = items_.value(row);
const Song &song = item->Metadata(); const Song &song = item->EffectiveMetadata();
bool found_duplicate = false; bool found_duplicate = false;
@@ -2389,7 +2392,7 @@ void Playlist::RemoveUnavailableSongs() {
QList<int> rows_to_remove; QList<int> rows_to_remove;
for (int row = 0; row < items_.count(); ++row) { for (int row = 0; row < items_.count(); ++row) {
const PlaylistItemPtr item = items_.value(row); const PlaylistItemPtr item = items_.value(row);
const Song &song = item->Metadata(); const Song &song = item->EffectiveMetadata();
// Check only local files // Check only local files
if (song.url().isLocalFile() && !QFile::exists(song.url().toLocalFile())) { if (song.url().isLocalFile() && !QFile::exists(song.url().toLocalFile())) {
@@ -2406,7 +2409,7 @@ bool Playlist::ApplyValidityOnCurrentSong(const QUrl &url, const bool valid) {
const PlaylistItemPtr current = current_item(); const PlaylistItemPtr current = current_item();
if (current) { if (current) {
const Song current_song = current->Metadata(); const Song current_song = current->EffectiveMetadata();
// If validity has changed, reload the item // If validity has changed, reload the item
if (current_song.source() == Song::Source::LocalFile || current_song.source() == Song::Source::Collection) { if (current_song.source() == Song::Source::LocalFile || current_song.source() == Song::Source::Collection) {
@@ -2472,7 +2475,7 @@ void Playlist::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &
// Update art_manual for local songs that are not in the collection. // Update art_manual for local songs that are not in the collection.
if (((result.type == AlbumCoverLoaderResult::Type::Manual && result.album_cover.cover_url.isLocalFile()) || result.type == AlbumCoverLoaderResult::Type::Unset) && (song.source() == Song::Source::LocalFile || song.source() == Song::Source::CDDA || song.source() == Song::Source::Device)) { if (((result.type == AlbumCoverLoaderResult::Type::Manual && result.album_cover.cover_url.isLocalFile()) || result.type == AlbumCoverLoaderResult::Type::Unset) && (song.source() == Song::Source::LocalFile || song.source() == Song::Source::CDDA || song.source() == Song::Source::Device)) {
PlaylistItemPtr item = current_item(); PlaylistItemPtr item = current_item();
if (item && item->Metadata() == song && (!item->Metadata().art_manual_is_valid() || (result.type == AlbumCoverLoaderResult::Type::Unset && !item->Metadata().art_unset()))) { if (item && item->EffectiveMetadata() == song && (!item->EffectiveMetadata().art_manual_is_valid() || (result.type == AlbumCoverLoaderResult::Type::Unset && !item->EffectiveMetadata().art_unset()))) {
qLog(Debug) << "Updating art manual for local song" << song.title() << song.album() << song.title() << "to" << result.album_cover.cover_url << "in playlist."; qLog(Debug) << "Updating art manual for local song" << song.title() << song.album() << song.title() << "to" << result.album_cover.cover_url << "in playlist.";
item->SetArtManual(result.album_cover.cover_url); item->SetArtManual(result.album_cover.cover_url);
ScheduleSaveAsync(); ScheduleSaveAsync();
@@ -2503,8 +2506,8 @@ void Playlist::RateSong(const QModelIndex &idx, const float rating) {
if (has_item_at(idx.row())) { if (has_item_at(idx.row())) {
const PlaylistItemPtr item = item_at(idx.row()); const PlaylistItemPtr item = item_at(idx.row());
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) { if (item && item->IsLocalCollectionItem() && item->EffectiveMetadata().id() != -1) {
collection_backend_->UpdateSongRatingAsync(item->Metadata().id(), rating); collection_backend_->UpdateSongRatingAsync(item->EffectiveMetadata().id(), rating);
} }
} }
@@ -2517,8 +2520,8 @@ void Playlist::RateSongs(const QModelIndexList &index_list, const float rating)
const int row = idx.row(); const int row = idx.row();
if (has_item_at(row)) { if (has_item_at(row)) {
const PlaylistItemPtr item = item_at(row); const PlaylistItemPtr item = item_at(row);
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) { if (item && item->IsLocalCollectionItem() && item->EffectiveMetadata().id() != -1) {
id_list << item->Metadata().id(); // clazy:exclude=reserve-candidates id_list << item->EffectiveMetadata().id(); // clazy:exclude=reserve-candidates
} }
} }
} }

View File

@@ -115,7 +115,7 @@ class Playlist : public QAbstractListModel {
Samplerate, Samplerate,
Bitdepth, Bitdepth,
Bitrate, Bitrate,
Filename, URL,
BaseFilename, BaseFilename,
Filesize, Filesize,
Filetype, Filetype,
@@ -268,9 +268,9 @@ class Playlist : public QAbstractListModel {
static Columns ChangedColumns(const Song &metadata1, const Song &metadata2); static Columns ChangedColumns(const Song &metadata1, const Song &metadata2);
static bool MinorMetadataChange(const Song &old_metadata, const Song &new_metadata); static bool MinorMetadataChange(const Song &old_metadata, const Song &new_metadata);
void UpdateItemMetadata(PlaylistItemPtr item, const Song &new_metadata, const bool temporary); void UpdateItemMetadata(PlaylistItemPtr item, const Song &new_metadata, const bool stream_metadata_update);
void UpdateItemMetadata(const int row, PlaylistItemPtr item, const Song &new_metadata, const bool temporary); void UpdateItemMetadata(const int row, PlaylistItemPtr item, const Song &new_metadata, const bool stream_metadata_update);
void ItemChanged(const int row, const Columns &columns); void RowDataChanged(const int row, const Columns &columns);
// Changes rating of a song to the given value asynchronously // Changes rating of a song to the given value asynchronously
void RateSong(const QModelIndex &idx, const float rating); void RateSong(const QModelIndex &idx, const float rating);

View File

@@ -273,7 +273,7 @@ PlaylistItemPtr PlaylistBackend::NewPlaylistItemFromQuery(const SqlRow &row, Sha
Song PlaylistBackend::NewSongFromQuery(const SqlRow &row, SharedPtr<NewSongFromQueryState> state) { Song PlaylistBackend::NewSongFromQuery(const SqlRow &row, SharedPtr<NewSongFromQueryState> state) {
return NewPlaylistItemFromQuery(row, state)->Metadata(); return NewPlaylistItemFromQuery(row, state)->EffectiveMetadata();
} }
@@ -286,7 +286,7 @@ PlaylistItemPtr PlaylistBackend::RestoreCueData(PlaylistItemPtr item, SharedPtr<
CueParser cue_parser(tagreader_client_, collection_backend_); CueParser cue_parser(tagreader_client_, collection_backend_);
Song song = item->Metadata(); Song song = item->EffectiveMetadata();
// We're only interested in .cue songs here // We're only interested in .cue songs here
if (!song.has_cue()) return item; if (!song.has_cue()) return item;

View File

@@ -64,7 +64,7 @@ bool PlaylistFilter::filterAcceptsRow(const int source_row, const QModelIndex &s
query_hash_ = hash; query_hash_ = hash;
} }
return filter_tree_->accept(item->Metadata()); return filter_tree_->accept(item->EffectiveMetadata());
} }

View File

@@ -93,6 +93,28 @@ PlaylistItemPtr PlaylistItem::NewFromSong(const Song &song) {
} }
void PlaylistItem::SetStreamMetadata(const Song &song) {
stream_song_ = song;
}
void PlaylistItem::UpdateStreamMetadata(const Song &song) {
if (!stream_song_.is_valid()) return;
const Song old_stream_song = stream_song_;
stream_song_ = song;
// Keep samplerate, bitdepth and bitrate from the old metadata if it's not present in the new.
if (stream_song_.samplerate() <= 0 && old_stream_song.samplerate() > 0) stream_song_.set_samplerate(old_stream_song.samplerate());
if (stream_song_.bitdepth() <= 0 && old_stream_song.bitdepth() > 0) stream_song_.set_bitdepth(old_stream_song.bitdepth());
if (stream_song_.bitrate() <= 0 && old_stream_song.bitrate() > 0) stream_song_.set_bitrate(old_stream_song.bitrate());
}
void PlaylistItem::ClearStreamMetadata() {
stream_song_ = Song();
}
void PlaylistItem::BindToQuery(SqlQuery *query) const { void PlaylistItem::BindToQuery(SqlQuery *query) const {
query->BindValue(u":type"_s, static_cast<int>(source_)); query->BindValue(u":type"_s, static_cast<int>(source_));
@@ -102,28 +124,6 @@ void PlaylistItem::BindToQuery(SqlQuery *query) const {
} }
void PlaylistItem::SetTemporaryMetadata(const Song &metadata) {
temp_metadata_ = metadata;
}
void PlaylistItem::UpdateTemporaryMetadata(const Song &metadata) {
if (!temp_metadata_.is_valid()) return;
const Song old_metadata = temp_metadata_;
temp_metadata_ = metadata;
// Keep samplerate, bitdepth and bitrate from the old metadata if it's not present in the new.
if (temp_metadata_.samplerate() <= 0 && old_metadata.samplerate() > 0) temp_metadata_.set_samplerate(old_metadata.samplerate());
if (temp_metadata_.bitdepth() <= 0 && old_metadata.bitdepth() > 0) temp_metadata_.set_bitdepth(old_metadata.bitdepth());
if (temp_metadata_.bitrate() <= 0 && old_metadata.bitrate() > 0) temp_metadata_.set_bitrate(old_metadata.bitrate());
}
void PlaylistItem::ClearTemporaryMetadata() {
temp_metadata_ = Song();
}
static void ReloadPlaylistItem(PlaylistItemPtr item) { static void ReloadPlaylistItem(PlaylistItemPtr item) {
item->Reload(); item->Reload();
} }
@@ -135,12 +135,15 @@ QFuture<void> PlaylistItem::BackgroundReload() {
void PlaylistItem::SetBackgroundColor(short priority, const QColor &color) { void PlaylistItem::SetBackgroundColor(short priority, const QColor &color) {
background_colors_[priority] = color; background_colors_[priority] = color;
} }
bool PlaylistItem::HasBackgroundColor(short priority) const { bool PlaylistItem::HasBackgroundColor(short priority) const {
return background_colors_.contains(priority); return background_colors_.contains(priority);
} }
void PlaylistItem::RemoveBackgroundColor(short priority) { void PlaylistItem::RemoveBackgroundColor(short priority) {
background_colors_.remove(priority); background_colors_.remove(priority);
} }
QColor PlaylistItem::GetCurrentBackgroundColor() const { QColor PlaylistItem::GetCurrentBackgroundColor() const {
if (background_colors_.isEmpty()) { if (background_colors_.isEmpty()) {
@@ -151,6 +154,7 @@ QColor PlaylistItem::GetCurrentBackgroundColor() const {
return background_colors_[background_colors_keys.last()]; return background_colors_[background_colors_keys.last()];
} }
bool PlaylistItem::HasCurrentBackgroundColor() const { bool PlaylistItem::HasCurrentBackgroundColor() const {
return !background_colors_.isEmpty(); return !background_colors_.isEmpty();
} }
@@ -158,12 +162,15 @@ bool PlaylistItem::HasCurrentBackgroundColor() const {
void PlaylistItem::SetForegroundColor(const short priority, const QColor &color) { void PlaylistItem::SetForegroundColor(const short priority, const QColor &color) {
foreground_colors_[priority] = color; foreground_colors_[priority] = color;
} }
bool PlaylistItem::HasForegroundColor(const short priority) const { bool PlaylistItem::HasForegroundColor(const short priority) const {
return foreground_colors_.contains(priority); return foreground_colors_.contains(priority);
} }
void PlaylistItem::RemoveForegroundColor(const short priority) { void PlaylistItem::RemoveForegroundColor(const short priority) {
foreground_colors_.remove(priority); foreground_colors_.remove(priority);
} }
QColor PlaylistItem::GetCurrentForegroundColor() const { QColor PlaylistItem::GetCurrentForegroundColor() const {
if (foreground_colors_.isEmpty()) return QColor(); if (foreground_colors_.isEmpty()) return QColor();
@@ -172,8 +179,11 @@ QColor PlaylistItem::GetCurrentForegroundColor() const {
return foreground_colors_[foreground_colors_keys.last()]; return foreground_colors_[foreground_colors_keys.last()];
} }
bool PlaylistItem::HasCurrentForegroundColor() const { bool PlaylistItem::HasCurrentForegroundColor() const {
return !foreground_colors_.isEmpty(); return !foreground_colors_.isEmpty();
} }
void PlaylistItem::SetShouldSkip(const bool should_skip) { should_skip_ = should_skip; } void PlaylistItem::SetShouldSkip(const bool should_skip) { should_skip_ = should_skip; }
bool PlaylistItem::GetShouldSkip() const { return should_skip_; } bool PlaylistItem::GetShouldSkip() const { return should_skip_; }

View File

@@ -66,37 +66,31 @@ class PlaylistItem : public enable_shared_from_this<PlaylistItem> {
Q_DECLARE_FLAGS(Options, Option) Q_DECLARE_FLAGS(Options, Option)
virtual Song::Source source() const { return source_; } virtual Song::Source source() const { return source_; }
virtual Options options() const { return Option::Default; } virtual Options options() const { return Option::Default; }
virtual QList<QAction*> actions() { return QList<QAction*>(); } virtual QList<QAction*> actions() { return QList<QAction*>(); }
virtual Song OriginalMetadata() const = 0;
virtual QUrl OriginalUrl() const = 0;
virtual void SetOriginalMetadata(const Song &song) { Q_UNUSED(song); }
Song EffectiveMetadata() const { return HasStreamMetadata() ? stream_song_ : OriginalMetadata(); }
QUrl EffectiveUrl() const { return stream_song_.effective_url().isValid() ? stream_song_.effective_url() : OriginalUrl(); }
void SetStreamMetadata(const Song &song);
void UpdateStreamMetadata(const Song &song);
void ClearStreamMetadata();
bool HasStreamMetadata() const { return stream_song_.is_valid(); }
qint64 effective_beginning_nanosec() const { return stream_song_.is_valid() && stream_song_.beginning_nanosec() != -1 ? stream_song_.beginning_nanosec() : OriginalMetadata().beginning_nanosec(); }
qint64 effective_end_nanosec() const { return stream_song_.is_valid() && stream_song_.end_nanosec() != -1 ? stream_song_.end_nanosec() : OriginalMetadata().end_nanosec(); }
virtual void SetArtManual(const QUrl &cover_url) = 0;
virtual bool InitFromQuery(const SqlRow &query) = 0; virtual bool InitFromQuery(const SqlRow &query) = 0;
void BindToQuery(SqlQuery *query) const; void BindToQuery(SqlQuery *query) const;
virtual void Reload() {} virtual void Reload() {}
QFuture<void> BackgroundReload(); QFuture<void> BackgroundReload();
virtual Song Metadata() const = 0;
virtual Song OriginalMetadata() const = 0;
virtual QUrl Url() const = 0;
virtual void SetMetadata(const Song &song) { Q_UNUSED(song); }
void SetTemporaryMetadata(const Song &metadata);
void UpdateTemporaryMetadata(const Song &metadata);
void ClearTemporaryMetadata();
bool HasTemporaryMetadata() const { return temp_metadata_.is_valid(); }
Song StreamMetadata() { return HasTemporaryMetadata() ? temp_metadata_ : Metadata(); }
QUrl StreamUrl() const { return HasTemporaryMetadata() && temp_metadata_.effective_stream_url().isValid() ? temp_metadata_.effective_stream_url() : Url(); }
std::optional<double> effective_ebur128_integrated_loudness_lufs() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() ? temp_metadata_.ebur128_integrated_loudness_lufs() : Metadata().ebur128_integrated_loudness_lufs(); }
qint64 effective_beginning_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.beginning_nanosec() != -1 ? temp_metadata_.beginning_nanosec() : Metadata().beginning_nanosec(); }
qint64 effective_end_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.end_nanosec() != -1 ? temp_metadata_.end_nanosec() : Metadata().end_nanosec(); }
virtual void SetArtManual(const QUrl &cover_url) = 0;
// Background colors. // Background colors.
void SetBackgroundColor(const short priority, const QColor &color); void SetBackgroundColor(const short priority, const QColor &color);
bool HasBackgroundColor(const short priority) const; bool HasBackgroundColor(const short priority) const;
@@ -128,7 +122,7 @@ class PlaylistItem : public enable_shared_from_this<PlaylistItem> {
virtual Song DatabaseSongMetadata() const { return Song(); } virtual Song DatabaseSongMetadata() const { return Song(); }
Song::Source source_; Song::Source source_;
Song temp_metadata_; Song stream_song_;
QMap<short, QColor> background_colors_; QMap<short, QColor> background_colors_;
QMap<short, QColor> foreground_colors_; QMap<short, QColor> foreground_colors_;

View File

@@ -478,7 +478,7 @@ void PlaylistManager::UpdateCollectionSongs(const SongList &songs) {
for (const Data &data : std::as_const(playlists_)) { for (const Data &data : std::as_const(playlists_)) {
const PlaylistItemPtrList items = data.p->collection_items(song.source(), song.id()); const PlaylistItemPtrList items = data.p->collection_items(song.source(), song.id());
for (PlaylistItemPtr item : items) { for (PlaylistItemPtr item : items) {
if (item->Metadata().directory_id() != song.directory_id()) continue; if (item->EffectiveMetadata().directory_id() != song.directory_id()) continue;
data.p->UpdateItemMetadata(item, song, false); data.p->UpdateItemMetadata(item, song, false);
} }
} }

View File

@@ -48,7 +48,7 @@ bool PlaylistUndoCommandInsertItems::UpdateItem(const PlaylistItemPtr &updated_i
for (int i = 0; i < items_.size(); i++) { for (int i = 0; i < items_.size(); i++) {
PlaylistItemPtr item = items_.value(i); PlaylistItemPtr item = items_.value(i);
if (item->Metadata().url() == updated_item->Metadata().url()) { if (item->EffectiveMetadata().url() == updated_item->EffectiveMetadata().url()) {
items_[i] = updated_item; items_[i] = updated_item;
return true; return true;
} }

View File

@@ -234,7 +234,7 @@ void PlaylistView::SetItemDelegates() {
setItemDelegateForColumn(static_cast<int>(Playlist::Column::Bitdepth), new PlaylistDelegateBase(this, tr("Bit"))); setItemDelegateForColumn(static_cast<int>(Playlist::Column::Bitdepth), new PlaylistDelegateBase(this, tr("Bit")));
setItemDelegateForColumn(static_cast<int>(Playlist::Column::Bitrate), new PlaylistDelegateBase(this, tr("kbps"))); setItemDelegateForColumn(static_cast<int>(Playlist::Column::Bitrate), new PlaylistDelegateBase(this, tr("kbps")));
setItemDelegateForColumn(static_cast<int>(Playlist::Column::Filename), new NativeSeparatorsDelegate(this)); setItemDelegateForColumn(static_cast<int>(Playlist::Column::URL), new NativeSeparatorsDelegate(this));
setItemDelegateForColumn(static_cast<int>(Playlist::Column::LastPlayed), new LastPlayedItemDelegate(this)); setItemDelegateForColumn(static_cast<int>(Playlist::Column::LastPlayed), new LastPlayedItemDelegate(this));
setItemDelegateForColumn(static_cast<int>(Playlist::Column::Source), new SongSourceDelegate(this)); setItemDelegateForColumn(static_cast<int>(Playlist::Column::Source), new SongSourceDelegate(this));
@@ -354,7 +354,7 @@ void PlaylistView::RestoreHeaderState() {
header_->HideSection(static_cast<int>(Playlist::Column::OriginalYear)); header_->HideSection(static_cast<int>(Playlist::Column::OriginalYear));
header_->HideSection(static_cast<int>(Playlist::Column::Disc)); header_->HideSection(static_cast<int>(Playlist::Column::Disc));
header_->HideSection(static_cast<int>(Playlist::Column::Genre)); header_->HideSection(static_cast<int>(Playlist::Column::Genre));
header_->HideSection(static_cast<int>(Playlist::Column::Filename)); header_->HideSection(static_cast<int>(Playlist::Column::URL));
header_->HideSection(static_cast<int>(Playlist::Column::BaseFilename)); header_->HideSection(static_cast<int>(Playlist::Column::BaseFilename));
header_->HideSection(static_cast<int>(Playlist::Column::Filesize)); header_->HideSection(static_cast<int>(Playlist::Column::Filesize));
header_->HideSection(static_cast<int>(Playlist::Column::DateCreated)); header_->HideSection(static_cast<int>(Playlist::Column::DateCreated));
@@ -1406,7 +1406,7 @@ void PlaylistView::CopyCurrentSongToClipboard() const {
} }
// Get the song's URL // Get the song's URL
const QUrl url = model()->data(currentIndex().sibling(currentIndex().row(), static_cast<int>(Playlist::Column::Filename))).toUrl(); const QUrl url = model()->data(currentIndex().sibling(currentIndex().row(), static_cast<int>(Playlist::Column::URL))).toUrl();
QMimeData *mime_data = new QMimeData; QMimeData *mime_data = new QMimeData;
mime_data->setUrls(QList<QUrl>() << url); mime_data->setUrls(QList<QUrl>() << url);

View File

@@ -38,8 +38,6 @@ bool SongPlaylistItem::InitFromQuery(const SqlRow &query) {
return true; return true;
} }
QUrl SongPlaylistItem::Url() const { return song_.url(); }
void SongPlaylistItem::Reload() { void SongPlaylistItem::Reload() {
if (!song_.url().isLocalFile()) return; if (!song_.url().isLocalFile()) return;
@@ -49,18 +47,13 @@ void SongPlaylistItem::Reload() {
qLog(Error) << "Could not reload file" << song_.url() << result.error_string(); qLog(Error) << "Could not reload file" << song_.url() << result.error_string();
} }
UpdateTemporaryMetadata(song_); UpdateStreamMetadata(song_);
} }
Song SongPlaylistItem::Metadata() const {
if (HasTemporaryMetadata()) return temp_metadata_;
return song_;
}
void SongPlaylistItem::SetArtManual(const QUrl &cover_url) { void SongPlaylistItem::SetArtManual(const QUrl &cover_url) {
song_.set_art_manual(cover_url); song_.set_art_manual(cover_url);
if (HasTemporaryMetadata()) temp_metadata_.set_art_manual(cover_url); if (HasStreamMetadata()) stream_song_.set_art_manual(cover_url);
} }

View File

@@ -40,10 +40,8 @@ class SongPlaylistItem : public PlaylistItem {
bool InitFromQuery(const SqlRow &query) override; bool InitFromQuery(const SqlRow &query) override;
void Reload() override; void Reload() override;
Song Metadata() const override;
Song OriginalMetadata() const override { return song_; } Song OriginalMetadata() const override { return song_; }
QUrl OriginalUrl() const override { return song_.url(); }
QUrl Url() const override;
void SetArtManual(const QUrl &cover_url) override; void SetArtManual(const QUrl &cover_url) override;

View File

@@ -36,6 +36,15 @@ StreamPlaylistItem::StreamPlaylistItem(const Song &song)
InitMetadata(); InitMetadata();
} }
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);
}
bool StreamPlaylistItem::InitFromQuery(const SqlRow &query) { bool StreamPlaylistItem::InitFromQuery(const SqlRow &query) {
song_.InitFromQuery(query, false, static_cast<int>(Song::kRowIdColumns.count())); song_.InitFromQuery(query, false, static_cast<int>(Song::kRowIdColumns.count()));
@@ -48,27 +57,9 @@ QVariant StreamPlaylistItem::DatabaseValue(const DatabaseColumn column) const {
return PlaylistItem::DatabaseValue(column); 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) { void StreamPlaylistItem::SetArtManual(const QUrl &cover_url) {
song_.set_art_manual(cover_url); song_.set_art_manual(cover_url);
temp_metadata_.set_art_manual(cover_url); stream_song_.set_art_manual(cover_url);
} }

View File

@@ -33,12 +33,10 @@ class StreamPlaylistItem : public PlaylistItem {
explicit StreamPlaylistItem(const Song::Source source); explicit StreamPlaylistItem(const Song::Source source);
explicit StreamPlaylistItem(const Song &song); explicit StreamPlaylistItem(const Song &song);
bool InitFromQuery(const SqlRow &query) override;
Song Metadata() const override;
Song OriginalMetadata() const override { return song_; } Song OriginalMetadata() const override { return song_; }
QUrl Url() const override; QUrl OriginalUrl() const override { return song_.url(); }
void SetOriginalMetadata(const Song &song) override { song_ = song; }
void SetMetadata(const Song &song) override { song_ = song; } bool InitFromQuery(const SqlRow &query) override;
void SetArtManual(const QUrl &cover_url) override; void SetArtManual(const QUrl &cover_url) override;
protected: protected:

View File

@@ -241,7 +241,7 @@ void Queue::UpdateTotalLength() {
Q_ASSERT(playlist_->has_item_at(id)); Q_ASSERT(playlist_->has_item_at(id));
const qint64 length = playlist_->item_at(id)->Metadata().length_nanosec(); const qint64 length = playlist_->item_at(id)->EffectiveMetadata().length_nanosec();
if (length > 0) total += static_cast<quint64>(length); if (length > 0) total += static_cast<quint64>(length);
} }

View File

@@ -392,7 +392,7 @@ QString SmartPlaylistSearchTerm::FieldName(const Field field) {
case Field::Length: case Field::Length:
return Playlist::column_name(Playlist::Column::Length); return Playlist::column_name(Playlist::Column::Length);
case Field::Filepath: case Field::Filepath:
return Playlist::column_name(Playlist::Column::Filename); return Playlist::column_name(Playlist::Column::URL);
case Field::Filetype: case Field::Filetype:
return Playlist::column_name(Playlist::Column::Filetype); return Playlist::column_name(Playlist::Column::Filetype);
case Field::Filesize: case Field::Filesize:

View File

@@ -36,16 +36,14 @@ class MockPlaylistItem : public PlaylistItem {
public: public:
MockPlaylistItem(); MockPlaylistItem();
MOCK_CONST_METHOD0(options, Options()); MOCK_CONST_METHOD0(options, Options());
MOCK_CONST_METHOD0(OriginalMetadata, Song());
MOCK_CONST_METHOD0(OriginalUrl, QUrl());
MOCK_METHOD1(SetStreamMetadata, void(const Song &song));
MOCK_METHOD0(ClearStreamMetadata, void());
MOCK_METHOD1(SetArtManual, void(const QUrl &cover_url));
MOCK_METHOD1(InitFromQuery, bool(const SqlRow &settings)); MOCK_METHOD1(InitFromQuery, bool(const SqlRow &settings));
MOCK_METHOD0(Reload, void()); MOCK_METHOD0(Reload, void());
MOCK_CONST_METHOD0(Metadata, Song());
MOCK_CONST_METHOD0(OriginalMetadata, Song());
MOCK_CONST_METHOD0(Url, QUrl());
MOCK_METHOD1(SetTemporaryMetadata, void(const Song &metadata));
MOCK_METHOD0(ClearTemporaryMetadata, void());
MOCK_METHOD1(SetArtManual, void(const QUrl &cover_url));
MOCK_CONST_METHOD1(DatabaseValue, QVariant(DatabaseColumn)); MOCK_CONST_METHOD1(DatabaseValue, QVariant(DatabaseColumn));
}; };
#endif // MOCK_PLAYLISTITEM_H #endif // MOCK_PLAYLISTITEM_H

View File

@@ -56,7 +56,7 @@ class PlaylistTest : public ::testing::Test {
metadata.Init(title, artist, album, length); metadata.Init(title, artist, album, length);
MockPlaylistItem *ret = new MockPlaylistItem; MockPlaylistItem *ret = new MockPlaylistItem;
EXPECT_CALL(*ret, Metadata()).WillRepeatedly(Return(metadata)); EXPECT_CALL(*ret, OriginalMetadata()).WillRepeatedly(Return(metadata));
return ret; return ret;
} }
@@ -100,7 +100,7 @@ TEST_F(PlaylistTest, Indexes) {
// Start "playing" track 1 // Start "playing" track 1
playlist_.set_current_row(0); playlist_.set_current_row(0);
EXPECT_EQ(0, playlist_.current_row()); EXPECT_EQ(0, playlist_.current_row());
EXPECT_EQ(u"One"_s, playlist_.current_item()->Metadata().title()); EXPECT_EQ(u"One"_s, playlist_.current_item()->EffectiveMetadata().title());
EXPECT_EQ(-1, playlist_.previous_row()); EXPECT_EQ(-1, playlist_.previous_row());
EXPECT_EQ(1, playlist_.next_row()); EXPECT_EQ(1, playlist_.next_row());
@@ -113,14 +113,14 @@ TEST_F(PlaylistTest, Indexes) {
// Play track 2 // Play track 2
playlist_.set_current_row(1); playlist_.set_current_row(1);
EXPECT_EQ(1, playlist_.current_row()); EXPECT_EQ(1, playlist_.current_row());
EXPECT_EQ(u"Two"_s, playlist_.current_item()->Metadata().title()); EXPECT_EQ(u"Two"_s, playlist_.current_item()->EffectiveMetadata().title());
EXPECT_EQ(0, playlist_.previous_row()); EXPECT_EQ(0, playlist_.previous_row());
EXPECT_EQ(2, playlist_.next_row()); EXPECT_EQ(2, playlist_.next_row());
// Play track 3 // Play track 3
playlist_.set_current_row(2); playlist_.set_current_row(2);
EXPECT_EQ(2, playlist_.current_row()); EXPECT_EQ(2, playlist_.current_row());
EXPECT_EQ(u"Three"_s, playlist_.current_item()->Metadata().title()); EXPECT_EQ(u"Three"_s, playlist_.current_item()->EffectiveMetadata().title());
EXPECT_EQ(1, playlist_.previous_row()); EXPECT_EQ(1, playlist_.previous_row());
EXPECT_EQ(-1, playlist_.next_row()); EXPECT_EQ(-1, playlist_.next_row());
@@ -453,7 +453,7 @@ TEST_F(PlaylistTest, ShuffleThenNext) {
} }
int index = playlist_.current_row(); int index = playlist_.current_row();
EXPECT_EQ(u"Item 0"_s, playlist_.current_item()->Metadata().title()); EXPECT_EQ(u"Item 0"_s, playlist_.current_item()->EffectiveMetadata().title());
EXPECT_EQ(u"Item 0"_s, playlist_.data(playlist_.index(index, static_cast<int>(Playlist::Column::Title)))); EXPECT_EQ(u"Item 0"_s, playlist_.data(playlist_.index(index, static_cast<int>(Playlist::Column::Title))));
EXPECT_EQ(index, playlist_.last_played_row()); EXPECT_EQ(index, playlist_.last_played_row());
//EXPECT_EQ(index + 1, playlist_.next_row()); //EXPECT_EQ(index + 1, playlist_.next_row());
@@ -466,7 +466,7 @@ TEST_F(PlaylistTest, ShuffleThenNext) {
//} //}
index = playlist_.current_row(); index = playlist_.current_row();
EXPECT_EQ(u"Item 0"_s, playlist_.current_item()->Metadata().title()); EXPECT_EQ(u"Item 0"_s, playlist_.current_item()->EffectiveMetadata().title());
EXPECT_EQ(u"Item 0"_s, playlist_.data(playlist_.index(index, static_cast<int>(Playlist::Column::Title)))); EXPECT_EQ(u"Item 0"_s, playlist_.data(playlist_.index(index, static_cast<int>(Playlist::Column::Title))));
EXPECT_EQ(index, playlist_.last_played_row()); EXPECT_EQ(index, playlist_.last_played_row());
//EXPECT_EQ(-1, playlist_.next_row()); //EXPECT_EQ(-1, playlist_.next_row());
@@ -487,7 +487,7 @@ TEST_F(PlaylistTest, CollectionIdMapSingle) {
EXPECT_EQ(0, playlist_.collection_items(Song::Source::Collection, 0).count()); EXPECT_EQ(0, playlist_.collection_items(Song::Source::Collection, 0).count());
EXPECT_EQ(0, playlist_.collection_items(Song::Source::Collection, 2).count()); EXPECT_EQ(0, playlist_.collection_items(Song::Source::Collection, 2).count());
ASSERT_EQ(1, playlist_.collection_items(Song::Source::Collection, 1).count()); ASSERT_EQ(1, playlist_.collection_items(Song::Source::Collection, 1).count());
EXPECT_EQ(song.title(), playlist_.collection_items(Song::Source::Collection, 1)[0]->Metadata().title()); // clazy:exclude=detaching-temporary EXPECT_EQ(song.title(), playlist_.collection_items(Song::Source::Collection, 1)[0]->EffectiveMetadata().title()); // clazy:exclude=detaching-temporary
playlist_.Clear(); playlist_.Clear();

View File

@@ -78,7 +78,7 @@ TEST_P(SongPlaylistItemTest, Url) {
expected.setScheme(u"file"_s); expected.setScheme(u"file"_s);
expected.setPath(absolute_file_name_); expected.setPath(absolute_file_name_);
EXPECT_EQ(expected, item_->Url()); EXPECT_EQ(expected, item_->OriginalUrl());
} }