/* * Strawberry Music Player * Copyright 2019-2024, 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 * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Strawberry is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Strawberry. If not, see . * */ #include "config.h" #include #include #include #include #include #include #include #include #include "core/logging.h" #include "core/song.h" #include "tagreaderclient.h" #include "tagreadertaglib.h" #include "tagreaderresult.h" #include "tagreaderrequest.h" #include "tagreaderismediafilerequest.h" #include "tagreaderreadfilerequest.h" #include "tagreaderwritefilerequest.h" #include "tagreaderloadcoverdatarequest.h" #include "tagreaderloadcoverimagerequest.h" #include "tagreadersavecoverrequest.h" #include "tagreadersaveplaycountrequest.h" #include "tagreadersaveratingrequest.h" #include "tagreaderreply.h" #include "tagreaderreadfilereply.h" #include "tagreaderloadcoverdatareply.h" #include "tagreaderloadcoverimagereply.h" using std::dynamic_pointer_cast; using namespace Qt::Literals::StringLiterals; TagReaderClient *TagReaderClient::sInstance = nullptr; TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), original_thread_(thread()), abort_(false), processing_(false) { setObjectName(QLatin1String(metaObject()->className())); if (!sInstance) { sInstance = this; } } void TagReaderClient::ExitAsync() { Q_ASSERT(QThread::currentThread() != thread()); abort_ = true; QMetaObject::invokeMethod(this, &TagReaderClient::Exit, Qt::QueuedConnection); } void TagReaderClient::Exit() { Q_ASSERT(QThread::currentThread() == thread()); moveToThread(original_thread_); Q_EMIT ExitFinished(); } bool TagReaderClient::HaveRequests() const { Q_ASSERT(QThread::currentThread() == thread()); { QMutexLocker l(&mutex_requests_); return !requests_.isEmpty(); } } void TagReaderClient::EnqueueRequest(TagReaderRequestPtr request) { Q_ASSERT(QThread::currentThread() != thread()); { QMutexLocker l(&mutex_requests_); requests_.enqueue(request); } if (!processing_.value()) { ProcessRequestsAsync(); } } TagReaderRequestPtr TagReaderClient::DequeueRequest() { Q_ASSERT(QThread::currentThread() == thread()); { QMutexLocker l(&mutex_requests_); if (requests_.isEmpty()) return TagReaderRequestPtr(); return requests_.dequeue(); } } void TagReaderClient::ProcessRequestsAsync() { Q_ASSERT(QThread::currentThread() != thread()); QMetaObject::invokeMethod(this, &TagReaderClient::ProcessRequests, Qt::QueuedConnection); } void TagReaderClient::ProcessRequests() { Q_ASSERT(QThread::currentThread() == thread()); processing_ = true; const QScopeGuard scopeguard_processing = qScopeGuard([this]() { processing_ = false; }); while (HaveRequests()) { if (abort_.value()) return; ProcessRequest(DequeueRequest()); } } void TagReaderClient::ProcessRequest(TagReaderRequestPtr request) { Q_ASSERT(QThread::currentThread() == thread()); TagReaderReplyPtr reply = request->reply; TagReaderResult result; if (TagReaderIsMediaFileRequestPtr is_media_file_request = dynamic_pointer_cast(request)) { result = tagreader_.IsMediaFile(is_media_file_request->filename); if (result.error_code == TagReaderResult::ErrorCode::Unsupported) { result = gmereader_.IsMediaFile(is_media_file_request->filename); } } else if (TagReaderReadFileRequestPtr read_file_request = dynamic_pointer_cast(request)) { Song song; result = ReadFileBlocking(read_file_request->filename, &song); if (result.error_code == TagReaderResult::ErrorCode::Unsupported) { result = gmereader_.ReadFile(read_file_request->filename, &song); } if (result.success()) { if (TagReaderReadFileReplyPtr read_file_reply = qSharedPointerDynamicCast(reply)) { read_file_reply->set_song(song); } } } else if (TagReaderWriteFileRequestPtr write_file_request = dynamic_pointer_cast(request)) { result = WriteFileBlocking(write_file_request->filename, write_file_request->song, write_file_request->save_tags_options, write_file_request->save_tag_cover_data); } else if (TagReaderLoadCoverDataRequestPtr load_cover_data_request = dynamic_pointer_cast(request)) { QByteArray cover_data; result = LoadCoverDataBlocking(load_cover_data_request->filename, cover_data); if (result.success()) { if (TagReaderLoadCoverDataReplyPtr load_cover_data_reply = qSharedPointerDynamicCast(reply)) { load_cover_data_reply->set_data(cover_data); } } } else if (TagReaderLoadCoverImageRequestPtr load_cover_image_request = dynamic_pointer_cast(request)) { QImage cover_image; result = LoadCoverImageBlocking(load_cover_image_request->filename, cover_image); if (result.success()) { if (TagReaderLoadCoverImageReplyPtr load_cover_image_reply = qSharedPointerDynamicCast(reply)) { load_cover_image_reply->set_image(cover_image); } } } else if (TagReaderSaveCoverRequestPtr save_cover_request = dynamic_pointer_cast(request)) { result = SaveCoverBlocking(save_cover_request->filename, save_cover_request->save_tag_cover_data); } else if (TagReaderSavePlaycountRequestPtr save_playcount_request = dynamic_pointer_cast(request)) { result = SaveSongPlaycountBlocking(save_playcount_request->filename, save_playcount_request->playcount); } else if (TagReaderSaveRatingRequestPtr save_rating_request = dynamic_pointer_cast(request)) { result = SaveSongRatingBlocking(save_rating_request->filename, save_rating_request->rating); } reply->set_result(result); reply->Finish(); } bool TagReaderClient::IsMediaFileBlocking(const QString &filename) const { Q_ASSERT(QThread::currentThread() != thread()); return tagreader_.IsMediaFile(filename).success(); } TagReaderReplyPtr TagReaderClient::IsMediaFileAsync(const QString &filename) { Q_ASSERT(QThread::currentThread() != thread()); TagReaderReplyPtr reply = TagReaderReply::Create(filename); TagReaderIsMediaFileRequestPtr request = TagReaderIsMediaFileRequest::Create(filename); request->reply = reply; request->filename = filename; EnqueueRequest(request); return reply; } TagReaderResult TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) { return tagreader_.ReadFile(filename, song); } TagReaderReadFileReplyPtr TagReaderClient::ReadFileAsync(const QString &filename) { Q_ASSERT(QThread::currentThread() != thread()); TagReaderReadFileReplyPtr reply = TagReaderReply::Create(filename); TagReaderReadFileRequestPtr request = TagReaderReadFileRequest::Create(filename); request->reply = reply; request->filename = filename; EnqueueRequest(request); return reply; } TagReaderResult TagReaderClient::WriteFileBlocking(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) { return tagreader_.WriteFile(filename, song, save_tags_options, save_tag_cover_data); } TagReaderReplyPtr TagReaderClient::WriteFileAsync(const QString &filename, const Song &song, const SaveTagsOptions save_tags_options, const SaveTagCoverData &save_tag_cover_data) { Q_ASSERT(QThread::currentThread() != thread()); TagReaderReplyPtr reply = TagReaderReply::Create(filename); TagReaderWriteFileRequestPtr request = TagReaderWriteFileRequest::Create(filename); request->reply = reply; request->filename = filename; request->song = song; request->save_tags_options = save_tags_options; request->save_tag_cover_data = save_tag_cover_data; EnqueueRequest(request); return reply; } TagReaderResult TagReaderClient::LoadCoverDataBlocking(const QString &filename, QByteArray &data) { return tagreader_.LoadEmbeddedCover(filename, data); } TagReaderResult TagReaderClient::LoadCoverImageBlocking(const QString &filename, QImage &image) { QByteArray data; TagReaderResult result = LoadCoverDataBlocking(filename, data); if (result.error_code == TagReaderResult::ErrorCode::Success && !image.loadFromData(data)) { result.error_code = TagReaderResult::ErrorCode::Unsupported; result.error_text = QObject::tr("Failed to load image from data for %1").arg(filename); } return result; } TagReaderLoadCoverDataReplyPtr TagReaderClient::LoadCoverDataAsync(const QString &filename) { Q_ASSERT(QThread::currentThread() != thread()); TagReaderLoadCoverDataReplyPtr reply = TagReaderReply::Create(filename); TagReaderLoadCoverDataRequestPtr request = TagReaderLoadCoverDataRequest::Create(filename); request->reply = reply; request->filename = filename; EnqueueRequest(request); return reply; } TagReaderLoadCoverImageReplyPtr TagReaderClient::LoadCoverImageAsync(const QString &filename) { Q_ASSERT(QThread::currentThread() != thread()); TagReaderLoadCoverImageReplyPtr reply = TagReaderReply::Create(filename); TagReaderLoadCoverImageRequestPtr request = TagReaderLoadCoverImageRequest::Create(filename); request->reply = reply; request->filename = filename; EnqueueRequest(request); return reply; } TagReaderResult TagReaderClient::SaveCoverBlocking(const QString &filename, const SaveTagCoverData &save_tag_cover_data) { return tagreader_.SaveEmbeddedCover(filename, save_tag_cover_data); } TagReaderReplyPtr TagReaderClient::SaveCoverAsync(const QString &filename, const SaveTagCoverData &save_tag_cover_data) { Q_ASSERT(QThread::currentThread() != thread()); TagReaderReplyPtr reply = TagReaderReply::Create(filename); TagReaderSaveCoverRequestPtr request = TagReaderSaveCoverRequest::Create(filename); request->reply = reply; request->filename = filename; request->save_tag_cover_data = save_tag_cover_data; EnqueueRequest(request); return reply; } TagReaderReplyPtr TagReaderClient::SaveSongPlaycountAsync(const QString &filename, const uint playcount) { Q_ASSERT(QThread::currentThread() != thread()); TagReaderReplyPtr reply = TagReaderReply::Create(filename); TagReaderSavePlaycountRequestPtr request = TagReaderSavePlaycountRequest::Create(filename); request->reply = reply; request->filename = filename; request->playcount = playcount; EnqueueRequest(request); return reply; } TagReaderResult TagReaderClient::SaveSongPlaycountBlocking(const QString &filename, const uint playcount) { return tagreader_.SaveSongPlaycount(filename, playcount); } void TagReaderClient::SaveSongsPlaycountAsync(const SongList &songs) { Q_ASSERT(QThread::currentThread() != thread()); for (const Song &song : songs) { SharedPtr connection = make_shared(); TagReaderReplyPtr reply = SaveSongPlaycountAsync(song.url().toLocalFile(), song.playcount()); *connection = QObject::connect(&*reply, &TagReaderReply::Finished, this, [reply, connection]() { QObject::disconnect(*connection); }, Qt::QueuedConnection); } } TagReaderResult TagReaderClient::SaveSongRatingBlocking(const QString &filename, const float rating) { return tagreader_.SaveSongRating(filename, rating); } TagReaderReplyPtr TagReaderClient::SaveSongRatingAsync(const QString &filename, const float rating) { Q_ASSERT(QThread::currentThread() != thread()); TagReaderReplyPtr reply = TagReaderReply::Create(filename); TagReaderSaveRatingRequestPtr request = TagReaderSaveRatingRequest::Create(filename); request->reply = reply; request->filename = filename; request->rating = rating; EnqueueRequest(request); return reply; } void TagReaderClient::SaveSongsRatingAsync(const SongList &songs) { Q_ASSERT(QThread::currentThread() != thread()); for (const Song &song : songs) { SharedPtr connection = make_shared(); TagReaderReplyPtr reply = SaveSongRatingAsync(song.url().toLocalFile(), song.rating()); *connection = QObject::connect(&*reply, &TagReaderReply::Finished, this, [reply, connection]() { QObject::disconnect(*connection); }, Qt::QueuedConnection); } }