/* * Strawberry Music Player * This file was part of Clementine. * Copyright 2010, David Sansome * Copyright 2019-2021, 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/workerpool.h" #include "song.h" #include "tagreaderclient.h" #include "settings/collectionsettingspage.h" const char *TagReaderClient::kWorkerExecutableName = "strawberry-tagreader"; TagReaderClient *TagReaderClient::sInstance = nullptr; TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool_(new WorkerPool(this)) { sInstance = this; original_thread_ = thread(); QSettings s; s.beginGroup(CollectionSettingsPage::kSettingsGroup); int workers = s.value("tagreader_workers", qBound(1, QThread::idealThreadCount() / 2, 4)).toInt(); s.endGroup(); qLog(Debug) << "Using" << workers << "tagreader workers."; worker_pool_->SetExecutableName(kWorkerExecutableName); worker_pool_->SetWorkerCount(workers); QObject::connect(worker_pool_, &WorkerPool::WorkerFailedToStart, this, &TagReaderClient::WorkerFailedToStart); } void TagReaderClient::Start() { worker_pool_->Start(); } void TagReaderClient::ExitAsync() { QMetaObject::invokeMethod(this, "Exit", Qt::QueuedConnection); } void TagReaderClient::Exit() { Q_ASSERT(QThread::currentThread() == thread()); moveToThread(original_thread_); emit ExitFinished(); } void TagReaderClient::WorkerFailedToStart() { qLog(Error) << "The" << kWorkerExecutableName << "executable was not found in the current directory or on the PATH. Strawberry will not be able to read music file tags without it."; } TagReaderReply *TagReaderClient::IsMediaFile(const QString &filename) { spb::tagreader::Message message; spb::tagreader::IsMediaFileRequest *request = message.mutable_is_media_file_request(); const QByteArray filename_data = filename.toUtf8(); request->set_filename(filename_data.constData(), filename_data.length()); return worker_pool_->SendMessageWithReply(&message); } TagReaderReply *TagReaderClient::ReadFile(const QString &filename) { spb::tagreader::Message message; spb::tagreader::ReadFileRequest *request = message.mutable_read_file_request(); const QByteArray filename_data = filename.toUtf8(); request->set_filename(filename_data.constData(), filename_data.length()); return worker_pool_->SendMessageWithReply(&message); } TagReaderReply *TagReaderClient::SaveFile(const QString &filename, const Song &metadata, const SaveTags save_tags, const SavePlaycount save_playcount, const SaveRating save_rating, const SaveCoverOptions &save_cover_options) { spb::tagreader::Message message; spb::tagreader::SaveFileRequest *request = message.mutable_save_file_request(); const QByteArray filename_data = filename.toUtf8(); request->set_filename(filename_data.constData(), filename_data.length()); request->set_save_tags(save_tags == SaveTags::On); request->set_save_playcount(save_playcount == SavePlaycount::On); request->set_save_rating(save_rating == SaveRating::On); request->set_save_cover(save_cover_options.enabled); request->set_cover_is_jpeg(save_cover_options.is_jpeg); if (save_cover_options.cover_filename.length() > 0) { const QByteArray cover_filename = filename.toUtf8(); request->set_cover_filename(cover_filename.constData(), cover_filename.length()); } if (save_cover_options.cover_data.length() > 0) { request->set_cover_data(save_cover_options.cover_data.constData(), save_cover_options.cover_data.length()); } metadata.ToProtobuf(request->mutable_metadata()); ReplyType *reply = worker_pool_->SendMessageWithReply(&message); return reply; } TagReaderReply *TagReaderClient::LoadEmbeddedArt(const QString &filename) { spb::tagreader::Message message; spb::tagreader::LoadEmbeddedArtRequest *request = message.mutable_load_embedded_art_request(); const QByteArray filename_data = filename.toUtf8(); request->set_filename(filename_data.constData(), filename_data.length()); return worker_pool_->SendMessageWithReply(&message); } TagReaderReply *TagReaderClient::SaveEmbeddedArt(const QString &filename, const SaveCoverOptions &save_cover_options) { spb::tagreader::Message message; spb::tagreader::SaveEmbeddedArtRequest *request = message.mutable_save_embedded_art_request(); const QByteArray filename_data = filename.toUtf8(); request->set_filename(filename_data.constData(), filename_data.length()); request->set_cover_is_jpeg(save_cover_options.is_jpeg); if (save_cover_options.cover_filename.length() > 0) { const QByteArray cover_filename = filename.toUtf8(); request->set_cover_filename(cover_filename.constData(), cover_filename.length()); } if (save_cover_options.cover_data.length() > 0) { request->set_cover_data(save_cover_options.cover_data.constData(), save_cover_options.cover_data.length()); } return worker_pool_->SendMessageWithReply(&message); } TagReaderReply *TagReaderClient::UpdateSongPlaycount(const Song &metadata) { spb::tagreader::Message message; spb::tagreader::SaveSongPlaycountToFileRequest *request = message.mutable_save_song_playcount_to_file_request(); const QByteArray filename_data = metadata.url().toLocalFile().toUtf8(); request->set_filename(filename_data.constData(), filename_data.length()); metadata.ToProtobuf(request->mutable_metadata()); return worker_pool_->SendMessageWithReply(&message); } void TagReaderClient::UpdateSongsPlaycount(const SongList &songs) { for (const Song &song : songs) { TagReaderReply *reply = UpdateSongPlaycount(song); QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater); } } TagReaderReply *TagReaderClient::UpdateSongRating(const Song &metadata) { spb::tagreader::Message message; spb::tagreader::SaveSongRatingToFileRequest *request = message.mutable_save_song_rating_to_file_request(); const QByteArray filename_data = metadata.url().toLocalFile().toUtf8(); request->set_filename(filename_data.constData(), filename_data.length()); metadata.ToProtobuf(request->mutable_metadata()); return worker_pool_->SendMessageWithReply(&message); } void TagReaderClient::UpdateSongsRating(const SongList &songs) { for (const Song &song : songs) { TagReaderReply *reply = UpdateSongRating(song); QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater); } } bool TagReaderClient::IsMediaFileBlocking(const QString &filename) { Q_ASSERT(QThread::currentThread() != thread()); bool ret = false; TagReaderReply *reply = IsMediaFile(filename); if (reply->WaitForFinished()) { ret = reply->message().is_media_file_response().success(); } reply->deleteLater(); return ret; } void TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) { Q_ASSERT(QThread::currentThread() != thread()); TagReaderReply *reply = ReadFile(filename); if (reply->WaitForFinished()) { song->InitFromProtobuf(reply->message().read_file_response().metadata()); } reply->deleteLater(); } bool TagReaderClient::SaveFileBlocking(const QString &filename, const Song &metadata, const SaveTags save_tags, const SavePlaycount save_playcount, const SaveRating save_rating, const SaveCoverOptions &save_cover_options) { Q_ASSERT(QThread::currentThread() != thread()); bool ret = false; TagReaderReply *reply = SaveFile(filename, metadata, save_tags, save_playcount, save_rating, save_cover_options); if (reply->WaitForFinished()) { ret = reply->message().save_file_response().success(); } reply->deleteLater(); return ret; } QByteArray TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename) { Q_ASSERT(QThread::currentThread() != thread()); QByteArray ret; TagReaderReply *reply = LoadEmbeddedArt(filename); if (reply->WaitForFinished()) { const std::string &data_str = reply->message().load_embedded_art_response().data(); ret = QByteArray(data_str.data(), static_cast(data_str.size())); } reply->deleteLater(); return ret; } QImage TagReaderClient::LoadEmbeddedArtAsImageBlocking(const QString &filename) { Q_ASSERT(QThread::currentThread() != thread()); QImage ret; TagReaderReply *reply = LoadEmbeddedArt(filename); if (reply->WaitForFinished()) { const std::string &data_str = reply->message().load_embedded_art_response().data(); ret.loadFromData(QByteArray(data_str.data(), static_cast(data_str.size()))); } reply->deleteLater(); return ret; } bool TagReaderClient::SaveEmbeddedArtBlocking(const QString &filename, const SaveCoverOptions &save_cover_options) { Q_ASSERT(QThread::currentThread() != thread()); bool success = false; TagReaderReply *reply = SaveEmbeddedArt(filename, save_cover_options); if (reply->WaitForFinished()) { success = reply->message().save_embedded_art_response().success(); } reply->deleteLater(); return success; } bool TagReaderClient::UpdateSongPlaycountBlocking(const Song &metadata) { Q_ASSERT(QThread::currentThread() != thread()); bool success = false; TagReaderReply *reply = UpdateSongPlaycount(metadata); if (reply->WaitForFinished()) { success = reply->message().save_song_playcount_to_file_response().success(); } reply->deleteLater(); return success; } bool TagReaderClient::UpdateSongRatingBlocking(const Song &metadata) { Q_ASSERT(QThread::currentThread() != thread()); bool success = false; TagReaderReply *reply = UpdateSongRating(metadata); if (reply->WaitForFinished()) { success = reply->message().save_song_rating_to_file_response().success(); } reply->deleteLater(); return success; }