From 7b16ec62bb76fb502d681e4d22a9743bacf384bf Mon Sep 17 00:00:00 2001 From: Jonas Kvinge Date: Sat, 27 Dec 2025 23:33:06 +0100 Subject: [PATCH] Defer playcount and rating tag writes for currently playing Ogg songs Fixes #1816 --- src/collection/collectionlibrary.cpp | 97 ++++++++++++++++++++++++++-- src/collection/collectionlibrary.h | 22 ++++++- src/core/mainwindow.cpp | 3 + 3 files changed, 114 insertions(+), 8 deletions(-) diff --git a/src/collection/collectionlibrary.cpp b/src/collection/collectionlibrary.cpp index 06d5effab..78c297240 100644 --- a/src/collection/collectionlibrary.cpp +++ b/src/collection/collectionlibrary.cpp @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2018-2021, Jonas Kvinge + * Copyright 2018-2025, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -189,6 +189,26 @@ void CollectionLibrary::ReloadSettings() { } +void CollectionLibrary::CurrentSongChanged(const Song &song) { + + current_song_url_ = song.url(); + + if (!pending_song_saves_.isEmpty()) { + SavePendingPlaycountsAndRatings(); + } + +} + +void CollectionLibrary::Stopped() { + + current_song_url_ = QUrl(); + + if (!pending_song_saves_.isEmpty()) { + SavePendingPlaycountsAndRatings(); + } + +} + void CollectionLibrary::SyncPlaycountAndRatingToFilesAsync() { (void)QtConcurrent::run(&CollectionLibrary::SyncPlaycountAndRatingToFiles, this); @@ -212,18 +232,85 @@ void CollectionLibrary::SyncPlaycountAndRatingToFiles() { } -void CollectionLibrary::SongsPlaycountChanged(const SongList &songs, const bool save_tags) const { +void CollectionLibrary::SongsPlaycountChanged(const SongList &songs, const bool save_tags) { if (save_tags || save_playcounts_to_files_) { - tagreader_client_->SaveSongsPlaycountAsync(songs); + SongList songs_to_save_now; + for (const Song &song : songs) { + if (song.url().isLocalFile() && song.url() == current_song_url_ && + (song.filetype() == Song::FileType::OggFlac || song.filetype() == Song::FileType::OggVorbis || song.filetype() == Song::FileType::OggOpus)) { + qLog(Debug) << "Deferring playcount save for currently playing file" << song.url().toLocalFile(); + if (pending_song_saves_.contains(song.url())) { + SharedPtr pending_song_save = pending_song_saves_[song.url()]; + pending_song_save->save_playcount = true; + pending_song_save->song.set_playcount(song.playcount()); + } + else { + SharedPtr pending_song_save = make_shared(); + pending_song_save->save_playcount = true; + pending_song_save->song = song; + pending_song_saves_.insert(song.url(), pending_song_save); + } + } + else { + songs_to_save_now << song; + } + } + if (!songs_to_save_now.isEmpty()) { + tagreader_client_->SaveSongsPlaycountAsync(songs_to_save_now); + } } } -void CollectionLibrary::SongsRatingChanged(const SongList &songs, const bool save_tags) const { +void CollectionLibrary::SongsRatingChanged(const SongList &songs, const bool save_tags) { if (save_tags || save_ratings_to_files_) { - tagreader_client_->SaveSongsRatingAsync(songs); + SongList songs_to_save_now; + for (const Song &song : songs) { + if (song.url().isLocalFile() && song.url() == current_song_url_ && + (song.filetype() == Song::FileType::OggFlac || song.filetype() == Song::FileType::OggVorbis || song.filetype() == Song::FileType::OggOpus)) { + qLog(Debug) << "Deferring rating save for currently playing file" << song.url().toLocalFile(); + if (pending_song_saves_.contains(song.url())) { + SharedPtr pending_song_save = pending_song_saves_[song.url()]; + pending_song_save->save_rating = true; + pending_song_save->song.set_rating(song.rating()); + } + else { + SharedPtr pending_song_save = make_shared(); + pending_song_save->save_rating = true; + pending_song_save->song = song; + pending_song_saves_.insert(song.url(), pending_song_save); + } + } + else { + songs_to_save_now << song; + } + } + if (!songs_to_save_now.isEmpty()) { + tagreader_client_->SaveSongsRatingAsync(songs_to_save_now); + } + } + +} + +void CollectionLibrary::SavePendingPlaycountsAndRatings() { + + for (auto it = pending_song_saves_.constBegin(); it != pending_song_saves_.constEnd();) { + const QUrl url = it.key(); + SharedPtr pending_song_save = it.value(); + if (url == current_song_url_) { + ++it; + continue; + } + qLog(Debug) << "Saving deferred playcount/rating for" << url.toLocalFile(); + if (pending_song_save->save_playcount) { + tagreader_client_->SaveSongsPlaycountAsync(SongList() << pending_song_save->song); + } + if (pending_song_save->save_rating) { + tagreader_client_->SaveSongsRatingAsync(SongList() << pending_song_save->song); + } + it = pending_song_saves_.erase(it); } } diff --git a/src/collection/collectionlibrary.h b/src/collection/collectionlibrary.h index 3ee9d47a9..630f5d797 100644 --- a/src/collection/collectionlibrary.h +++ b/src/collection/collectionlibrary.h @@ -2,7 +2,7 @@ * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome - * Copyright 2018-2021, Jonas Kvinge + * Copyright 2018-2025, Jonas Kvinge * * Strawberry is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "includes/shared_ptr.h" @@ -71,6 +72,7 @@ class CollectionLibrary : public QObject { private: void SyncPlaycountAndRatingToFiles(); + void SavePendingPlaycountsAndRatings(); public Q_SLOTS: void ReloadSettings(); @@ -84,16 +86,26 @@ class CollectionLibrary : public QObject { void IncrementalScan(); + void CurrentSongChanged(const Song &song); + void Stopped(); + private Q_SLOTS: void ExitReceived(); - void SongsPlaycountChanged(const SongList &songs, const bool save_tags = false) const; - void SongsRatingChanged(const SongList &songs, const bool save_tags = false) const; + void SongsPlaycountChanged(const SongList &songs, const bool save_tags = false); + void SongsRatingChanged(const SongList &songs, const bool save_tags = false); Q_SIGNALS: void Error(const QString &error); void ExitFinished(); private: + class PendingSongSave { + public: + Song song; + bool save_playcount = false; + bool save_rating = false; + }; + const SharedPtr task_manager_; const SharedPtr tagreader_client_; @@ -111,6 +123,10 @@ class CollectionLibrary : public QObject { bool save_playcounts_to_files_; bool save_ratings_to_files_; + + QUrl current_song_url_; + + QMap> pending_song_saves_; }; #endif diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index 8ce8f4bb4..ea66e1b85 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -696,6 +696,9 @@ MainWindow::MainWindow(Application *app, QObject::connect(&*app_->task_manager(), &TaskManager::PauseCollectionWatchers, &*app_->collection(), &CollectionLibrary::PauseWatcher); QObject::connect(&*app_->task_manager(), &TaskManager::ResumeCollectionWatchers, &*app_->collection(), &CollectionLibrary::ResumeWatcher); + QObject::connect(&*app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, &*app_->collection(), &CollectionLibrary::CurrentSongChanged); + QObject::connect(&*app_->player(), &Player::Stopped, &*app_->collection(), &CollectionLibrary::Stopped); + QObject::connect(&*app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, &*app_->current_albumcover_loader(), &CurrentAlbumCoverLoader::LoadAlbumCover); QObject::connect(&*app_->current_albumcover_loader(), &CurrentAlbumCoverLoader::AlbumCoverLoaded, this, &MainWindow::AlbumCoverLoaded); QObject::connect(album_cover_choice_controller_, &AlbumCoverChoiceController::Error, this, &MainWindow::ShowErrorDialog);