Remove external tagreader

This commit is contained in:
Jonas Kvinge
2024-09-15 23:17:14 +02:00
parent 3cb0f60900
commit daaacf4663
104 changed files with 3524 additions and 4634 deletions

View File

@@ -36,12 +36,11 @@
#include "shared_ptr.h"
#include "lazy.h"
#include "tagreaderclient.h"
#include "database.h"
#include "taskmanager.h"
#include "player.h"
#include "networkaccessmanager.h"
#include "tagreader/tagreaderclient.h"
#include "engine/devicefinders.h"
#ifndef Q_OS_WIN
# include "device/devicemanager.h"
@@ -118,7 +117,6 @@ class ApplicationImpl {
tag_reader_client_([app](){
TagReaderClient *client = new TagReaderClient();
app->MoveToNewThread(client);
client->Start();
return client;
}),
database_([app]() {

439
src/core/logging.cpp Normal file
View File

@@ -0,0 +1,439 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <QtGlobal>
#include "config.h"
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <utility>
#include <memory>
#include <chrono>
#ifndef _MSC_VER
# include <cxxabi.h>
#endif
#ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wold-style-cast"
#endif
#include <glib.h>
#ifdef __clang__
# pragma clang diagnostic pop
#endif
#ifdef HAVE_BACKTRACE
# include <execinfo.h>
#endif
#include <QByteArray>
#include <QList>
#include <QMap>
#include <QString>
#include <QStringList>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
#include <QDateTime>
#include <QIODevice>
#include <QBuffer>
#include <QtMessageHandler>
#include <QMessageLogContext>
#include <QDebug>
#include "logging.h"
using namespace Qt::StringLiterals;
namespace logging {
static Level sDefaultLevel = Level_Debug;
static QMap<QString, Level> *sClassLevels = nullptr;
static QIODevice *sNullDevice = nullptr;
const char *kDefaultLogLevels = "*:3";
static constexpr char kMessageHandlerMagic[] = "__logging_message__";
static const size_t kMessageHandlerMagicLen = strlen(kMessageHandlerMagic);
static QtMessageHandler sOriginalMessageHandler = nullptr;
template<class T>
static T CreateLogger(Level level, const QString &class_name, int line, const char *category);
void GLog(const char *domain, int level, const char *message, void*) {
switch (level) {
case G_LOG_FLAG_RECURSION:
case G_LOG_FLAG_FATAL:
case G_LOG_LEVEL_ERROR:
case G_LOG_LEVEL_CRITICAL:
qLogCat(Error, domain) << message;
break;
case G_LOG_LEVEL_WARNING:
qLogCat(Warning, domain) << message;
break;
case G_LOG_LEVEL_MESSAGE:
case G_LOG_LEVEL_INFO:
qLogCat(Info, domain) << message;
break;
case G_LOG_LEVEL_DEBUG:
default:
qLogCat(Debug, domain) << message;
break;
}
}
template<class T>
class DebugBase : public QDebug {
public:
DebugBase() : QDebug(sNullDevice) {}
explicit DebugBase(QtMsgType t) : QDebug(t) {}
T &space() { return static_cast<T&>(QDebug::space()); }
T &nospace() { return static_cast<T&>(QDebug::nospace()); }
};
// Debug message will be stored in a buffer.
class BufferedDebug : public DebugBase<BufferedDebug> {
public:
BufferedDebug() = default;
explicit BufferedDebug(QtMsgType msg_type) : buf_(new QBuffer, later_deleter) {
Q_UNUSED(msg_type)
buf_->open(QIODevice::WriteOnly);
// QDebug doesn't have a method to set a new io device, but swap() allows the devices to be swapped between two instances.
QDebug other(buf_.get());
swap(other);
}
// Delete function for the buffer. Since a base class is holding a reference to the raw pointer,
// it shouldn't be deleted until after the deletion of this object is complete.
static void later_deleter(QBuffer *b) { b->deleteLater(); }
std::shared_ptr<QBuffer> buf_;
};
// Debug message will be logged immediately.
class LoggedDebug : public DebugBase<LoggedDebug> {
public:
LoggedDebug() = default;
explicit LoggedDebug(QtMsgType t) : DebugBase(t) { nospace() << kMessageHandlerMagic; }
};
static void MessageHandler(QtMsgType type, const QMessageLogContext &message_log_context, const QString &message) {
Q_UNUSED(message_log_context)
if (message.startsWith(QLatin1String(kMessageHandlerMagic))) {
QByteArray message_data = message.toUtf8();
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", message_data.constData() + kMessageHandlerMagicLen);
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
return;
}
Level level = Level_Debug;
switch (type) {
case QtFatalMsg:
case QtCriticalMsg:
level = Level_Error;
break;
case QtWarningMsg:
level = Level_Warning;
break;
case QtDebugMsg:
default:
level = Level_Debug;
break;
}
const QStringList lines = message.split(u'\n');
for (const QString &line : lines) {
BufferedDebug d = CreateLogger<BufferedDebug>(level, QStringLiteral("unknown"), -1, nullptr);
d << line.toLocal8Bit().constData();
if (d.buf_) {
d.buf_->close();
fprintf(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout, "%s\n", d.buf_->buffer().constData());
fflush(type == QtCriticalMsg || type == QtFatalMsg ? stderr : stdout);
}
}
if (type == QtFatalMsg) {
abort();
}
}
void Init() {
delete sClassLevels;
delete sNullDevice;
sClassLevels = new QMap<QString, Level>();
sNullDevice = new NullDevice;
sNullDevice->open(QIODevice::ReadWrite);
// Catch other messages from Qt
if (!sOriginalMessageHandler) {
sOriginalMessageHandler = qInstallMessageHandler(MessageHandler);
}
}
void SetLevels(const QString &levels) {
if (!sClassLevels) return;
const QStringList items = levels.split(u',');
for (const QString &item : items) {
const QStringList class_level = item.split(u':');
QString class_name;
bool ok = false;
int level = Level_Error;
if (class_level.count() == 1) {
level = class_level.last().toInt(&ok);
}
else if (class_level.count() == 2) {
class_name = class_level.first();
level = class_level.last().toInt(&ok);
}
if (!ok || level < Level_Error || level > Level_Debug) {
continue;
}
if (class_name.isEmpty() || class_name == u'*') {
sDefaultLevel = static_cast<Level>(level);
}
else {
sClassLevels->insert(class_name, static_cast<Level>(level));
}
}
}
static QString ParsePrettyFunction(const char *pretty_function) {
// Get the class name out of the function name.
QString class_name = QLatin1String(pretty_function);
const qint64 paren = class_name.indexOf(u'(');
if (paren != -1) {
const qint64 colons = class_name.lastIndexOf("::"_L1, paren);
if (colons != -1) {
class_name = class_name.left(colons);
}
else {
class_name = class_name.left(paren);
}
}
const qint64 space = class_name.lastIndexOf(u' ');
if (space != -1) {
class_name = class_name.mid(space + 1);
}
return class_name;
}
template <class T>
static T CreateLogger(Level level, const QString &class_name, int line, const char *category) {
// Map the level to a string
const char *level_name = nullptr;
switch (level) {
case Level_Debug: level_name = " DEBUG "; break;
case Level_Info: level_name = " INFO "; break;
case Level_Warning: level_name = " WARN "; break;
case Level_Error: level_name = " ERROR "; break;
case Level_Fatal: level_name = " FATAL "; break;
}
QString filter_category = (category != nullptr) ? QLatin1String(category) : class_name;
// Check the settings to see if we're meant to show or hide this message.
Level threshold_level = sDefaultLevel;
if (sClassLevels && sClassLevels->contains(filter_category)) {
threshold_level = sClassLevels->value(filter_category);
}
if (level > threshold_level) {
return T();
}
QString function_line = class_name;
if (line != -1) {
function_line += QLatin1Char(':') + QString::number(line);
}
if (category) {
function_line += QLatin1Char('(') + QLatin1String(category) + QLatin1Char(')');
}
QtMsgType type = QtDebugMsg;
if (level == Level_Fatal) {
type = QtFatalMsg;
}
T ret(type);
ret.nospace() << QDateTime::currentDateTime().toString(QStringLiteral("hh:mm:ss.zzz")).toLatin1().constData() << level_name << function_line.leftJustified(32).toLatin1().constData();
return ret.space();
}
#ifdef Q_OS_UNIX
QString CXXDemangle(const QString &mangled_function);
QString CXXDemangle(const QString &mangled_function) {
int status = 0;
char *demangled_function = abi::__cxa_demangle(mangled_function.toLatin1().constData(), nullptr, nullptr, &status);
if (status == 0) {
QString ret = QString::fromLatin1(demangled_function);
free(demangled_function);
return ret;
}
return mangled_function; // Probably not a C++ function.
}
#endif // Q_OS_UNIX
#ifdef Q_OS_LINUX
QString LinuxDemangle(const QString &symbol);
QString LinuxDemangle(const QString &symbol) {
static const QRegularExpression regex_symbol(QStringLiteral("\\(([^+]+)"));
QRegularExpressionMatch match = regex_symbol.match(symbol);
if (!match.hasMatch()) {
return symbol;
}
QString mangled_function = match.captured(1);
return CXXDemangle(mangled_function);
}
#endif // Q_OS_LINUX
#ifdef Q_OS_MACOS
QString DarwinDemangle(const QString &symbol);
QString DarwinDemangle(const QString &symbol) {
const QStringList split = symbol.split(QLatin1Char(' '), Qt::SkipEmptyParts);
QString mangled_function = split[3];
return CXXDemangle(mangled_function);
}
#endif // Q_OS_MACOS
QString DemangleSymbol(const QString &symbol);
QString DemangleSymbol(const QString &symbol) {
#ifdef Q_OS_MACOS
return DarwinDemangle(symbol);
#elif defined(Q_OS_LINUX)
return LinuxDemangle(symbol);
#else
return symbol;
#endif
}
void DumpStackTrace() {
#ifdef HAVE_BACKTRACE
void *callstack[128];
int callstack_size = backtrace(reinterpret_cast<void**>(&callstack), sizeof(callstack));
char **symbols = backtrace_symbols(reinterpret_cast<void**>(&callstack), callstack_size);
// Start from 1 to skip ourself.
for (int i = 1; i < callstack_size; ++i) {
std::cerr << DemangleSymbol(QString::fromLatin1(symbols[i])).toStdString() << std::endl;
}
free(symbols);
#else
qLog(Debug) << "FIXME: Implement printing stack traces on this platform";
#endif
}
// These are the functions that create loggers for the rest of Strawberry.
// It's okay that the LoggedDebug instance is copied to a QDebug in these. It doesn't override any behavior that should be needed after return.
#define qCreateLogger(line, pretty_function, category, level) logging::CreateLogger<LoggedDebug>(logging::Level_##level, logging::ParsePrettyFunction(pretty_function), line, category)
QDebug CreateLoggerFatal(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Fatal); }
QDebug CreateLoggerError(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Error); }
#ifdef QT_NO_INFO_OUTPUT
QNoDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category) {
Q_UNUSED(line)
Q_UNUSED(pretty_function)
Q_UNUSED(category)
return QNoDebug();
}
#else
QDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Info); }
#endif // QT_NO_INFO_OUTPUT
#ifdef QT_NO_WARNING_OUTPUT
QNoDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category) {
Q_UNUSED(line)
Q_UNUSED(pretty_function)
Q_UNUSED(category)
return QNoDebug();
}
#else
QDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Warning); }
#endif // QT_NO_WARNING_OUTPUT
#ifdef QT_NO_DEBUG_OUTPUT
QNoDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category) {
Q_UNUSED(line)
Q_UNUSED(pretty_function)
Q_UNUSED(category)
return QNoDebug();
}
#else
QDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category) { return qCreateLogger(line, pretty_function, category, Debug); }
#endif // QT_NO_DEBUG_OUTPUT
} // namespace logging
namespace {
template<typename T>
QString print_duration(T duration, const std::string &unit) {
return QStringLiteral("%1%2").arg(duration.count()).arg(QString::fromStdString(unit));
}
} // namespace
QDebug operator<<(QDebug dbg, std::chrono::seconds secs) {
dbg.nospace() << print_duration(secs, "s");
return dbg.space();
}

104
src/core/logging.h Normal file
View File

@@ -0,0 +1,104 @@
/* This file is part of Strawberry.
Copyright 2011, David Sansome <me@davidsansome.com>
Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef LOGGING_H
#define LOGGING_H
#include <chrono>
#include <QtGlobal>
#include <QIODevice>
#include <QString>
#include <QDebug>
#ifdef QT_NO_DEBUG_STREAM
# define qLog(level) while (false) QNoDebug()
# define qLogCat(level, category) while (false) QNoDebug()
#else
# ifdef _MSC_VER
# define qLog(level) logging::CreateLogger##level(__LINE__, __FUNCSIG__, nullptr)
# else
# define qLog(level) logging::CreateLogger##level(__LINE__, __PRETTY_FUNCTION__, nullptr)
# endif // _MSC_VER
// This macro specifies a separate category for message filtering.
// The default qLog will use the class name extracted from the function name for this purpose.
// The category is also printed in the message along with the class name.
# ifdef _MSC_VER
# define qLogCat(level, category) logging::CreateLogger##level(__LINE__, __FUNCSIG__, category)
# else
# define qLogCat(level, category) logging::CreateLogger##level(__LINE__, __PRETTY_FUNCTION__, category)
# endif // _MSC_VER
#endif // QT_NO_DEBUG_STREAM
namespace logging {
class NullDevice : public QIODevice {
Q_OBJECT
public:
NullDevice(QObject *parent = nullptr) : QIODevice(parent) {}
protected:
qint64 readData(char*, qint64) override { return -1; }
qint64 writeData(const char*, qint64 len) override { return len; }
};
enum Level {
Level_Fatal = -1,
Level_Error = 0,
Level_Warning,
Level_Info,
Level_Debug,
};
void Init();
void SetLevels(const QString &levels);
void DumpStackTrace();
QDebug CreateLoggerFatal(const int line, const char *pretty_function, const char *category);
QDebug CreateLoggerError(const int line, const char *pretty_function, const char *category);
#ifdef QT_NO_INFO_OUTPUT
QNoDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category);
#else
QDebug CreateLoggerInfo(const int line, const char *pretty_function, const char *category);
#endif // QT_NO_INFO_OUTPUT
#ifdef QT_NO_WARNING_OUTPUT
QNoDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category);
#else
QDebug CreateLoggerWarning(const int line, const char *pretty_function, const char *category);
#endif // QT_NO_WARNING_OUTPUT
#ifdef QT_NO_DEBUG_OUTPUT
QNoDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category);
#else
QDebug CreateLoggerDebug(const int line, const char *pretty_function, const char *category);
#endif // QT_NO_DEBUG_OUTPUT
void GLog(const char *domain, int level, const char *message, void *user_data);
extern const char *kDefaultLogLevels;
} // namespace logging
QDebug operator<<(QDebug dbg, std::chrono::seconds secs);
#endif // LOGGING_H

View File

@@ -2177,21 +2177,20 @@ void MainWindow::RenumberTracks() {
Song song = item->OriginalMetadata();
if (song.IsEditable()) {
song.set_track(track);
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(song.url().toLocalFile(), song);
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(song.url().toLocalFile(), song);
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
}
++track;
}
}
void MainWindow::SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx) {
void MainWindow::SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx) {
if (reply->is_successful() && idx.isValid()) {
if (reply->success() && idx.isValid()) {
app_->playlist_manager()->current()->ReloadItems(QList<int>() << idx.row());
}
reply->deleteLater();
}
@@ -2209,9 +2208,9 @@ void MainWindow::SelectionSetValue() {
Song song = item->OriginalMetadata();
if (!song.is_valid()) continue;
if (song.url().isLocalFile() && Playlist::set_column_value(song, column, column_value)) {
TagReaderReply *reply = TagReaderClient::Instance()->WriteFile(song.url().toLocalFile(), song);
TagReaderReplyPtr reply = TagReaderClient::Instance()->WriteFileAsync(song.url().toLocalFile(), song);
QPersistentModelIndex persistent_index = QPersistentModelIndex(source_index);
QObject::connect(reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
QObject::connect(&*reply, &TagReaderReply::Finished, this, [this, reply, persistent_index]() { SongSaveComplete(reply, persistent_index); }, Qt::QueuedConnection);
}
else if (song.source() == Song::Source::Stream) {
app_->playlist_manager()->current()->setData(source_index, column_value, 0);

View File

@@ -51,8 +51,8 @@
#include "lazy.h"
#include "platforminterface.h"
#include "song.h"
#include "tagreaderclient.h"
#include "settings.h"
#include "tagreader/tagreaderclient.h"
#include "engine/enginebase.h"
#include "osd/osdbase.h"
#include "playlist/playlist.h"
@@ -218,7 +218,7 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void PlayingWidgetPositionChanged(const bool above_status_bar);
void SongSaveComplete(TagReaderReply *reply, const QPersistentModelIndex &idx);
void SongSaveComplete(TagReaderReplyPtr reply, const QPersistentModelIndex &idx);
void ShowCoverManager();
void ShowEqualizer();

View File

@@ -48,6 +48,8 @@
#include <QStandardPaths>
#include <QSqlRecord>
#include <taglib/tstring.h>
#include "core/iconloader.h"
#include "engine/enginemetadata.h"
#include "utilities/strutils.h"
@@ -55,13 +57,13 @@
#include "utilities/coverutils.h"
#include "utilities/timeconstants.h"
#include "utilities/sqlhelper.h"
#include "song.h"
#include "sqlquery.h"
#include "sqlrow.h"
#ifdef HAVE_DBUS
# include "mpris_common.h"
#endif
#include "tagreadermessages.pb.h"
using namespace Qt::StringLiterals;
@@ -482,6 +484,29 @@ const QString &Song::musicbrainz_work_id() const { return d->musicbrainz_work_id
std::optional<double> Song::ebur128_integrated_loudness_lufs() const { return d->ebur128_integrated_loudness_lufs_; }
std::optional<double> Song::ebur128_loudness_range_lu() const { return d->ebur128_loudness_range_lu_; }
QString *Song::mutable_title() { return &d->title_; }
QString *Song::mutable_album() { return &d->album_; }
QString *Song::mutable_artist() { return &d->artist_; }
QString *Song::mutable_albumartist() { return &d->albumartist_; }
QString *Song::mutable_genre() { return &d->genre_; }
QString *Song::mutable_composer() { return &d->composer_; }
QString *Song::mutable_performer() { return &d->performer_; }
QString *Song::mutable_grouping() { return &d->grouping_; }
QString *Song::mutable_comment() { return &d->comment_; }
QString *Song::mutable_lyrics() { return &d->lyrics_; }
QString *Song::mutable_acoustid_id() { return &d->acoustid_id_; }
QString *Song::mutable_acoustid_fingerprint() { return &d->acoustid_fingerprint_; }
QString *Song::mutable_musicbrainz_album_artist_id() { return &d->musicbrainz_album_artist_id_; }
QString *Song::mutable_musicbrainz_artist_id() { return &d->musicbrainz_artist_id_; }
QString *Song::mutable_musicbrainz_original_artist_id() { return &d->musicbrainz_original_artist_id_; }
QString *Song::mutable_musicbrainz_album_id() { return &d->musicbrainz_album_id_; }
QString *Song::mutable_musicbrainz_original_album_id() { return &d->musicbrainz_original_album_id_; }
QString *Song::mutable_musicbrainz_recording_id() { return &d->musicbrainz_recording_id_; }
QString *Song::mutable_musicbrainz_track_id() { return &d->musicbrainz_track_id_; }
QString *Song::mutable_musicbrainz_disc_id() { return &d->musicbrainz_disc_id_; }
QString *Song::mutable_musicbrainz_release_group_id() { return &d->musicbrainz_release_group_id_; }
QString *Song::mutable_musicbrainz_work_id() { return &d->musicbrainz_work_id_; }
bool Song::init_from_file() const { return d->init_from_file_; }
const QString &Song::title_sortable() const { return d->title_sortable_; }
@@ -503,7 +528,7 @@ void Song::set_disc(const int v) { d->disc_ = v; }
void Song::set_year(const int v) { d->year_ = v; }
void Song::set_originalyear(const int v) { d->originalyear_ = v; }
void Song::set_genre(const QString &v) { d->genre_ = v; }
void Song::set_compilation(bool v) { d->compilation_ = v; }
void Song::set_compilation(const bool v) { d->compilation_ = v; }
void Song::set_composer(const QString &v) { d->composer_ = v; }
void Song::set_performer(const QString &v) { d->performer_ = v; }
void Song::set_grouping(const QString &v) { d->grouping_ = v; }
@@ -571,6 +596,59 @@ void Song::set_ebur128_loudness_range_lu(const std::optional<double> v) { d->ebu
void Song::set_stream_url(const QUrl &v) { d->stream_url_ = v; }
void Song::set_title(const TagLib::String &v) {
const QString title = TagLibStringToQString(v);
d->title_sortable_ = sortable(title);
d->title_ = title;
}
void Song::set_album(const TagLib::String &v) {
const QString album = TagLibStringToQString(v);
d->album_sortable_ = sortable(album);
d->album_ = album;
}
void Song::set_artist(const TagLib::String &v) {
const QString artist = TagLibStringToQString(v);
d->artist_sortable_ = sortable(artist);
d->artist_ = artist;
}
void Song::set_albumartist(const TagLib::String &v) {
const QString albumartist = TagLibStringToQString(v);
d->albumartist_sortable_ = sortable(albumartist);
d->albumartist_ = albumartist;
}
void Song::set_genre(const TagLib::String &v) { d->genre_ = TagLibStringToQString(v); }
void Song::set_composer(const TagLib::String &v) { d->composer_ = TagLibStringToQString(v); }
void Song::set_performer(const TagLib::String &v) { d->performer_ = TagLibStringToQString(v); }
void Song::set_grouping(const TagLib::String &v) { d->grouping_ = TagLibStringToQString(v); }
void Song::set_comment(const TagLib::String &v) { d->comment_ = TagLibStringToQString(v); }
void Song::set_lyrics(const TagLib::String &v) { d->lyrics_ = TagLibStringToQString(v); }
void Song::set_artist_id(const TagLib::String &v) { d->artist_id_ = TagLibStringToQString(v); }
void Song::set_album_id(const TagLib::String &v) { d->album_id_ = TagLibStringToQString(v); }
void Song::set_song_id(const TagLib::String &v) { d->song_id_ = TagLibStringToQString(v); }
void Song::set_acoustid_id(const TagLib::String &v) { d->acoustid_id_ = TagLibStringToQString(v); }
void Song::set_acoustid_fingerprint(const TagLib::String &v) { d->acoustid_fingerprint_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_album_artist_id(const TagLib::String &v) { d->musicbrainz_album_artist_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_artist_id(const TagLib::String &v) { d->musicbrainz_artist_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_original_artist_id(const TagLib::String &v) { d->musicbrainz_original_artist_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_album_id(const TagLib::String &v) { d->musicbrainz_album_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_original_album_id(const TagLib::String &v) { d->musicbrainz_original_album_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_recording_id(const TagLib::String &v) { d->musicbrainz_recording_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_track_id(const TagLib::String &v) { d->musicbrainz_track_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_disc_id(const TagLib::String &v) { d->musicbrainz_disc_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_release_group_id(const TagLib::String &v) { d->musicbrainz_release_group_id_ = TagLibStringToQString(v); }
void Song::set_musicbrainz_work_id(const TagLib::String &v) { d->musicbrainz_work_id_ = TagLibStringToQString(v); }
const QUrl &Song::effective_stream_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_sortable() const { return d->albumartist_.isEmpty() ? d->artist_sortable_ : d->albumartist_sortable_; }
@@ -1312,125 +1390,6 @@ void Song::Init(const QString &title, const QString &artist, const QString &albu
}
void Song::InitFromProtobuf(const spb::tagreader::SongMetadata &pb) {
if (d->source_ == Source::Unknown) d->source_ = Source::LocalFile;
d->init_from_file_ = true;
d->valid_ = pb.valid();
set_title(QString::fromStdString(pb.title()));
set_album(QString::fromStdString(pb.album()));
set_artist(QString::fromStdString(pb.artist()));
set_albumartist(QString::fromStdString(pb.albumartist()));
d->track_ = pb.track();
d->disc_ = pb.disc();
d->year_ = pb.year();
d->originalyear_ = pb.originalyear();
d->genre_ = QString::fromStdString(pb.genre());
d->compilation_ = pb.compilation();
d->composer_ = QString::fromStdString(pb.composer());
d->performer_ = QString::fromStdString(pb.performer());
d->grouping_ = QString::fromStdString(pb.grouping());
d->comment_ = QString::fromStdString(pb.comment());
d->lyrics_ = QString::fromStdString(pb.lyrics());
set_length_nanosec(static_cast<qint64>(pb.length_nanosec()));
d->bitrate_ = pb.bitrate();
d->samplerate_ = pb.samplerate();
d->bitdepth_ = pb.bitdepth();
set_url(QUrl::fromEncoded(QString::fromStdString(pb.url()).toUtf8()));
d->basefilename_ = QString::fromStdString(pb.basefilename());
d->filetype_ = static_cast<FileType>(pb.filetype());
d->filesize_ = pb.filesize();
d->mtime_ = pb.mtime();
d->ctime_ = pb.ctime();
d->skipcount_ = pb.skipcount();
d->lastplayed_ = pb.lastplayed();
d->lastseen_ = pb.lastseen();
if (pb.has_playcount()) {
d->playcount_ = pb.playcount();
}
if (pb.has_rating()) {
d->rating_ = pb.rating();
}
d->art_embedded_ = pb.has_art_embedded();
d->acoustid_id_ = QString::fromStdString(pb.acoustid_id());
d->acoustid_fingerprint_ = QString::fromStdString(pb.acoustid_fingerprint());
d->musicbrainz_album_artist_id_ = QString::fromStdString(pb.musicbrainz_album_artist_id());
d->musicbrainz_artist_id_ = QString::fromStdString(pb.musicbrainz_artist_id().data());
d->musicbrainz_original_artist_id_ = QString::fromStdString(pb.musicbrainz_original_artist_id());
d->musicbrainz_album_id_ = QString::fromStdString(pb.musicbrainz_album_id());
d->musicbrainz_original_album_id_ = QString::fromStdString(pb.musicbrainz_original_album_id());
d->musicbrainz_recording_id_ = QString::fromStdString(pb.musicbrainz_recording_id());
d->musicbrainz_track_id_ = QString::fromStdString(pb.musicbrainz_track_id());
d->musicbrainz_disc_id_ = QString::fromStdString(pb.musicbrainz_disc_id());
d->musicbrainz_release_group_id_ = QString::fromStdString(pb.musicbrainz_release_group_id());
d->musicbrainz_work_id_ = QString::fromStdString(pb.musicbrainz_work_id());
d->suspicious_tags_ = pb.suspicious_tags();
InitArtManual();
}
void Song::ToProtobuf(spb::tagreader::SongMetadata *pb) const {
const QByteArray url(d->url_.toEncoded());
pb->set_valid(d->valid_);
pb->set_title(d->title_.toStdString());
pb->set_album(d->album_.toStdString());
pb->set_artist(d->artist_.toStdString());
pb->set_albumartist(d->albumartist_.toStdString());
pb->set_track(d->track_);
pb->set_disc(d->disc_);
pb->set_year(d->year_);
pb->set_originalyear(d->originalyear_);
pb->set_genre(d->genre_.toStdString());
pb->set_compilation(d->compilation_);
pb->set_composer(d->composer_.toStdString());
pb->set_performer(d->performer_.toStdString());
pb->set_grouping(d->grouping_.toStdString());
pb->set_comment(d->comment_.toStdString());
pb->set_lyrics(d->lyrics_.toStdString());
pb->set_length_nanosec(length_nanosec());
pb->set_bitrate(d->bitrate_);
pb->set_samplerate(d->samplerate_);
pb->set_bitdepth(d->bitdepth_);
pb->set_url(url.constData(), url.size());
pb->set_basefilename(d->basefilename_.toStdString());
pb->set_filetype(static_cast<spb::tagreader::SongMetadata_FileType>(d->filetype_));
pb->set_filesize(d->filesize_);
pb->set_mtime(d->mtime_);
pb->set_ctime(d->ctime_);
pb->set_playcount(d->playcount_);
pb->set_skipcount(d->skipcount_);
pb->set_lastplayed(d->lastplayed_);
pb->set_lastseen(d->lastseen_);
pb->set_art_embedded(d->art_embedded_);
pb->set_rating(d->rating_);
pb->set_acoustid_id(d->acoustid_id_.toStdString());
pb->set_acoustid_fingerprint(d->acoustid_fingerprint_.toStdString());
pb->set_musicbrainz_album_artist_id(d->musicbrainz_album_artist_id_.toStdString());
pb->set_musicbrainz_artist_id(d->musicbrainz_artist_id_.toStdString());
pb->set_musicbrainz_original_artist_id(d->musicbrainz_original_artist_id_.toStdString());
pb->set_musicbrainz_album_id(d->musicbrainz_album_id_.toStdString());
pb->set_musicbrainz_original_album_id(d->musicbrainz_original_album_id_.toStdString());
pb->set_musicbrainz_recording_id(d->musicbrainz_recording_id_.toStdString());
pb->set_musicbrainz_track_id(d->musicbrainz_track_id_.toStdString());
pb->set_musicbrainz_disc_id(d->musicbrainz_disc_id_.toStdString());
pb->set_musicbrainz_release_group_id(d->musicbrainz_release_group_id_.toStdString());
pb->set_musicbrainz_work_id(d->musicbrainz_work_id_.toStdString());
pb->set_suspicious_tags(d->suspicious_tags_);
}
void Song::InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, const int col) {
Q_ASSERT(kRowIdColumns.count() + col <= r.count());

View File

@@ -41,17 +41,15 @@
#include <QFileInfo>
#include <QIcon>
#include <taglib/tstring.h>
#undef TStringToQString
#undef QStringToTString
class SqlQuery;
class QSqlRecord;
class EngineMetadata;
namespace spb {
namespace tagreader {
class SongMetadata;
} // namespace tagreader
} // namespace spb
#ifdef HAVE_LIBGPOD
struct _Itdb_Track;
#endif
@@ -81,9 +79,6 @@ class Song {
Spotify = 11
};
// Don't change these values - they're stored in the database, and defined in the tag reader protobuf.
// If a new lossless file is added, also add it to IsFileLossless().
enum class FileType {
Unknown = 0,
WAV = 1,
@@ -227,6 +222,29 @@ class Song {
std::optional<double> ebur128_integrated_loudness_lufs() const;
std::optional<double> ebur128_loudness_range_lu() const;
QString *mutable_title();
QString *mutable_album();
QString *mutable_artist();
QString *mutable_albumartist();
QString *mutable_genre();
QString *mutable_composer();
QString *mutable_performer();
QString *mutable_grouping();
QString *mutable_comment();
QString *mutable_lyrics();
QString *mutable_acoustid_id();
QString *mutable_acoustid_fingerprint();
QString *mutable_musicbrainz_album_artist_id();
QString *mutable_musicbrainz_artist_id();
QString *mutable_musicbrainz_original_artist_id();
QString *mutable_musicbrainz_album_id();
QString *mutable_musicbrainz_original_album_id();
QString *mutable_musicbrainz_recording_id();
QString *mutable_musicbrainz_track_id();
QString *mutable_musicbrainz_disc_id();
QString *mutable_musicbrainz_release_group_id();
QString *mutable_musicbrainz_work_id();
bool init_from_file() const;
const QString &title_sortable() const;
@@ -317,6 +335,32 @@ class Song {
void set_stream_url(const QUrl &v);
void set_title(const TagLib::String &v);
void set_album(const TagLib::String &v);
void set_artist(const TagLib::String &v);
void set_albumartist(const TagLib::String &v);
void set_genre(const TagLib::String &v);
void set_composer(const TagLib::String &v);
void set_performer(const TagLib::String &v);
void set_grouping(const TagLib::String &v);
void set_comment(const TagLib::String &v);
void set_lyrics(const TagLib::String &v);
void set_artist_id(const TagLib::String &v);
void set_album_id(const TagLib::String &v);
void set_song_id(const TagLib::String &v);
void set_acoustid_id(const TagLib::String &v);
void set_acoustid_fingerprint(const TagLib::String &v);
void set_musicbrainz_album_artist_id(const TagLib::String &v);
void set_musicbrainz_artist_id(const TagLib::String &v);
void set_musicbrainz_original_artist_id(const TagLib::String &v);
void set_musicbrainz_album_id(const TagLib::String &v);
void set_musicbrainz_original_album_id(const TagLib::String &v);
void set_musicbrainz_recording_id(const TagLib::String &v);
void set_musicbrainz_track_id(const TagLib::String &v);
void set_musicbrainz_disc_id(const TagLib::String &v);
void set_musicbrainz_release_group_id(const TagLib::String &v);
void set_musicbrainz_work_id(const TagLib::String &v);
const QUrl &effective_stream_url() const;
const QString &effective_albumartist() const;
const QString &effective_albumartist_sortable() const;
@@ -422,7 +466,6 @@ class Song {
// Constructors
void Init(const QString &title, const QString &artist, const QString &album, const qint64 length_nanosec);
void Init(const QString &title, const QString &artist, const QString &album, const qint64 beginning, const qint64 end);
void InitFromProtobuf(const spb::tagreader::SongMetadata &pb);
void InitFromQuery(const QSqlRecord &r, const bool reliable_metadata, const int col = 0);
void InitFromQuery(const SqlQuery &query, const bool reliable_metadata, const int col = 0);
void InitFromQuery(const SqlRow &row, const bool reliable_metadata, const int col = 0);
@@ -445,7 +488,6 @@ class Song {
#ifdef HAVE_DBUS
void ToXesam(QVariantMap *map) const;
#endif
void ToProtobuf(spb::tagreader::SongMetadata *pb) const;
bool MergeFromEngineMetadata(const EngineMetadata &engine_metadata);
@@ -465,6 +507,10 @@ class Song {
static QString AlbumRemoveDiscMisc(const QString &album);
static QString TitleRemoveMisc(const QString &title);
static inline QString TagLibStringToQString(const TagLib::String &s) {
return QString::fromUtf8((s).toCString(true));
}
private:
struct Private;

View File

@@ -48,10 +48,10 @@
#include "player.h"
#include "song.h"
#include "songloader.h"
#include "tagreaderclient.h"
#include "database.h"
#include "sqlrow.h"
#include "engine/enginebase.h"
#include "tagreader/tagreaderclient.h"
#include "collection/collectionbackend.h"
#include "collection/collectionquery.h"
#include "playlistparsers/cueparser.h"
@@ -362,9 +362,9 @@ void SongLoader::EffectiveSongLoad(Song *song) {
else {
// It's a normal media file
const QString filename = song->url().toLocalFile();
const TagReaderClient::Result result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
const TagReaderResult result = TagReaderClient::Instance()->ReadFileBlocking(filename, song);
if (!result.success()) {
qLog(Error) << "Could not read file" << song->url() << result.error;
qLog(Error) << "Could not read file" << song->url() << result.error_string();
}
}

View File

@@ -1,377 +0,0 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2010, David Sansome <me@davidsansome.com>
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <string>
#include <QtGlobal>
#include <QObject>
#include <QThread>
#include <QByteArray>
#include <QString>
#include <QImage>
#include "core/logging.h"
#include "core/workerpool.h"
#include "song.h"
#include "tagreaderclient.h"
using namespace Qt::StringLiterals;
namespace {
constexpr char kWorkerExecutableName[] = "strawberry-tagreader";
}
TagReaderClient *TagReaderClient::sInstance = nullptr;
TagReaderClient::TagReaderClient(QObject *parent) : QObject(parent), worker_pool_(new WorkerPool<HandlerType>(this)) {
setObjectName(QLatin1String(metaObject()->className()));
sInstance = this;
original_thread_ = thread();
worker_pool_->SetExecutableName(QLatin1String(kWorkerExecutableName));
QObject::connect(worker_pool_, &WorkerPool<HandlerType>::WorkerFailedToStart, this, &TagReaderClient::WorkerFailedToStart);
}
void TagReaderClient::Start() { worker_pool_->Start(); }
void TagReaderClient::ExitAsync() {
QMetaObject::invokeMethod(this, &TagReaderClient::Exit, Qt::QueuedConnection);
}
void TagReaderClient::Exit() {
Q_ASSERT(QThread::currentThread() == thread());
moveToThread(original_thread_);
Q_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;
message.mutable_is_media_file_request()->set_filename(filename.toStdString());
return worker_pool_->SendMessageWithReply(&message);
}
TagReaderReply *TagReaderClient::ReadFile(const QString &filename) {
spb::tagreader::Message message;
message.mutable_read_file_request()->set_filename(filename.toStdString());
return worker_pool_->SendMessageWithReply(&message);
}
TagReaderReply *TagReaderClient::WriteFile(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) {
spb::tagreader::Message message;
spb::tagreader::WriteFileRequest *request = message.mutable_write_file_request();
request->set_filename(filename.toStdString());
request->set_save_tags(save_types.testFlag(SaveType::Tags));
request->set_save_playcount(save_types.testFlag(SaveType::PlayCount));
request->set_save_rating(save_types.testFlag(SaveType::Rating));
request->set_save_cover(save_types.testFlag(SaveType::Cover));
if (!save_cover_options.cover_filename.isEmpty()) {
request->set_cover_filename(save_cover_options.cover_filename.toStdString());
}
if (!save_cover_options.cover_data.isEmpty()) {
request->set_cover_data(save_cover_options.cover_data.toStdString());
}
if (!save_cover_options.mime_type.isEmpty()) {
request->set_cover_mime_type(save_cover_options.mime_type.toStdString());
}
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();
request->set_filename(filename.toStdString());
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();
request->set_filename(filename.toStdString());
if (!save_cover_options.cover_filename.isEmpty()) {
request->set_cover_filename(save_cover_options.cover_filename.toStdString());
}
if (!save_cover_options.cover_data.isEmpty()) {
request->set_cover_data(save_cover_options.cover_data.toStdString());
}
if (!save_cover_options.mime_type.isEmpty()) {
request->set_cover_mime_type(save_cover_options.mime_type.toStdString());
}
return worker_pool_->SendMessageWithReply(&message);
}
TagReaderReply *TagReaderClient::SaveSongPlaycount(const QString &filename, const uint playcount) {
spb::tagreader::Message message;
spb::tagreader::SaveSongPlaycountToFileRequest *request = message.mutable_save_song_playcount_to_file_request();
request->set_filename(filename.toStdString());
request->set_playcount(playcount);
return worker_pool_->SendMessageWithReply(&message);
}
void TagReaderClient::SaveSongsPlaycount(const SongList &songs) {
for (const Song &song : songs) {
TagReaderReply *reply = SaveSongPlaycount(song.url().toLocalFile(), song.playcount());
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
}
}
TagReaderReply *TagReaderClient::SaveSongRating(const QString &filename, const float rating) {
spb::tagreader::Message message;
spb::tagreader::SaveSongRatingToFileRequest *request = message.mutable_save_song_rating_to_file_request();
request->set_filename(filename.toStdString());
request->set_rating(rating);
return worker_pool_->SendMessageWithReply(&message);
}
void TagReaderClient::SaveSongsRating(const SongList &songs) {
for (const Song &song : songs) {
TagReaderReply *reply = SaveSongRating(song.url().toLocalFile(), song.rating());
QObject::connect(reply, &TagReaderReply::Finished, reply, &TagReaderReply::deleteLater);
}
}
bool TagReaderClient::IsMediaFileBlocking(const QString &filename) {
Q_ASSERT(QThread::currentThread() != thread());
bool success = false;
TagReaderReply *reply = IsMediaFile(filename);
if (reply->WaitForFinished()) {
const spb::tagreader::IsMediaFileResponse &response = reply->message().is_media_file_response();
if (response.has_success()) {
success = response.success();
}
}
reply->deleteLater();
return success;
}
TagReaderClient::Result TagReaderClient::ReadFileBlocking(const QString &filename, Song *song) {
Q_ASSERT(QThread::currentThread() != thread());
Result result(Result::ErrorCode::Failure);
TagReaderReply *reply = ReadFile(filename);
if (reply->WaitForFinished()) {
const spb::tagreader::ReadFileResponse &response = reply->message().read_file_response();
if (response.has_success()) {
if (response.success()) {
result.error_code = Result::ErrorCode::Success;
if (response.has_metadata()) {
song->InitFromProtobuf(response.metadata());
}
}
else {
result.error_code = Result::ErrorCode::Failure;
if (response.has_error()) {
result.error = QString::fromStdString(response.error());
}
}
}
}
reply->deleteLater();
return result;
}
TagReaderClient::Result TagReaderClient::WriteFileBlocking(const QString &filename, const Song &metadata, const SaveTypes save_types, const SaveCoverOptions &save_cover_options) {
Q_ASSERT(QThread::currentThread() != thread());
Result result(Result::ErrorCode::Failure);
TagReaderReply *reply = WriteFile(filename, metadata, save_types, save_cover_options);
if (reply->WaitForFinished()) {
const spb::tagreader::WriteFileResponse &response = reply->message().write_file_response();
if (response.has_success()) {
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
if (response.has_error()) {
result.error = QString::fromStdString(response.error());
}
}
}
reply->deleteLater();
return result;
}
TagReaderClient::Result TagReaderClient::LoadEmbeddedArtBlocking(const QString &filename, QByteArray &data) {
Q_ASSERT(QThread::currentThread() != thread());
Result result(Result::ErrorCode::Failure);
TagReaderReply *reply = LoadEmbeddedArt(filename);
if (reply->WaitForFinished()) {
const spb::tagreader::LoadEmbeddedArtResponse &response = reply->message().load_embedded_art_response();
if (response.has_success()) {
if (response.success()) {
result.error_code = Result::ErrorCode::Success;
if (response.has_data()) {
data = QByteArray(response.data().data(), static_cast<qint64>(response.data().size()));
}
}
else {
result.error_code = Result::ErrorCode::Failure;
if (response.has_error()) {
result.error = QString::fromStdString(response.error());
}
}
}
}
reply->deleteLater();
return result;
}
TagReaderClient::Result TagReaderClient::LoadEmbeddedArtAsImageBlocking(const QString &filename, QImage &image) {
Q_ASSERT(QThread::currentThread() != thread());
QByteArray data;
Result result = LoadEmbeddedArtBlocking(filename, data);
if (result.error_code == Result::ErrorCode::Success && !image.loadFromData(data)) {
result.error_code = Result::ErrorCode::Failure;
result.error = QObject::tr("Failed to load image from data for %1").arg(filename);
}
return result;
}
TagReaderClient::Result TagReaderClient::SaveEmbeddedArtBlocking(const QString &filename, const SaveCoverOptions &save_cover_options) {
Q_ASSERT(QThread::currentThread() != thread());
Result result(Result::ErrorCode::Failure);
TagReaderReply *reply = SaveEmbeddedArt(filename, save_cover_options);
if (reply->WaitForFinished()) {
const spb::tagreader::SaveEmbeddedArtResponse &response = reply->message().save_embedded_art_response();
if (response.has_success()) {
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
if (response.has_error()) {
result.error = QString::fromStdString(response.error());
}
}
}
reply->deleteLater();
return result;
}
TagReaderClient::Result TagReaderClient::SaveSongPlaycountBlocking(const QString &filename, const uint playcount) {
Q_ASSERT(QThread::currentThread() != thread());
Result result(Result::ErrorCode::Failure);
TagReaderReply *reply = SaveSongPlaycount(filename, playcount);
if (reply->WaitForFinished()) {
const spb::tagreader::SaveSongPlaycountToFileResponse &response = reply->message().save_song_playcount_to_file_response();
if (response.has_success()) {
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
if (response.has_error()) {
result.error = QString::fromStdString(response.error());
}
}
}
reply->deleteLater();
return result;
}
TagReaderClient::Result TagReaderClient::SaveSongRatingBlocking(const QString &filename, const float rating) {
Q_ASSERT(QThread::currentThread() != thread());
Result result(Result::ErrorCode::Failure);
TagReaderReply *reply = SaveSongRating(filename, rating);
if (reply->WaitForFinished()) {
const spb::tagreader::SaveSongRatingToFileResponse &response = reply->message().save_song_rating_to_file_response();
if (response.has_success()) {
result.error_code = response.success() ? Result::ErrorCode::Success : Result::ErrorCode::Failure;
if (response.has_error()) {
result.error = QString::fromStdString(response.error());
}
}
}
reply->deleteLater();
return result;
}

View File

@@ -1,137 +0,0 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2011, David Sansome <me@davidsansome.com>
* Copyright 2019-2024, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef TAGREADERCLIENT_H
#define TAGREADERCLIENT_H
#include "config.h"
#include <QObject>
#include <QList>
#include <QString>
#include <QImage>
#include "core/messagehandler.h"
#include "core/workerpool.h"
#include "song.h"
#include "tagreadermessages.pb.h"
class QThread;
class Song;
template<typename HandlerType> class WorkerPool;
class TagReaderClient : public QObject {
Q_OBJECT
public:
explicit TagReaderClient(QObject *parent = nullptr);
using HandlerType = AbstractMessageHandler<spb::tagreader::Message>;
using ReplyType = HandlerType::ReplyType;
void Start();
void ExitAsync();
enum class SaveType {
NoType = 0,
Tags = 1,
PlayCount = 2,
Rating = 4,
Cover = 8
};
Q_DECLARE_FLAGS(SaveTypes, SaveType)
class SaveCoverOptions {
public:
explicit SaveCoverOptions(const QString &_cover_filename = QString(), const QByteArray &_cover_data = QByteArray(), const QString &_mime_type = QString()) : cover_filename(_cover_filename), cover_data(_cover_data), mime_type(_mime_type) {}
explicit SaveCoverOptions(const QString &_cover_filename, const QString &_mime_type = QString()) : cover_filename(_cover_filename), mime_type(_mime_type) {}
explicit SaveCoverOptions(const QByteArray &_cover_data, const QString &_mime_type = QString()) : cover_data(_cover_data), mime_type(_mime_type) {}
QString cover_filename;
QByteArray cover_data;
QString mime_type;
};
class Result {
public:
enum class ErrorCode {
Success,
Unsupported,
Failure,
};
Result(const ErrorCode _error_code, const QString &_error = QString()) : error_code(_error_code), error(_error) {}
ErrorCode error_code;
QString error;
bool success() const { return error_code == TagReaderClient::Result::ErrorCode::Success; }
};
class Cover {
public:
explicit Cover(const QByteArray &_data = QByteArray(), const QString &_mime_type = QString()) : data(_data), mime_type(_mime_type) {}
QByteArray data;
QString mime_type;
QString error;
};
ReplyType *IsMediaFile(const QString &filename);
ReplyType *ReadFile(const QString &filename);
ReplyType *WriteFile(const QString &filename, const Song &metadata, const SaveTypes types = SaveType::Tags, const SaveCoverOptions &save_cover_options = SaveCoverOptions());
ReplyType *LoadEmbeddedArt(const QString &filename);
ReplyType *SaveEmbeddedArt(const QString &filename, const SaveCoverOptions &save_cover_options);
ReplyType *SaveSongPlaycount(const QString &filename, const uint playcount);
ReplyType *SaveSongRating(const QString &filename, const float rating);
// Convenience functions that call the above functions and wait for a response.
// These block the calling thread with a semaphore, and must NOT be called from the TagReaderClient's thread.
Result ReadFileBlocking(const QString &filename, Song *song);
Result WriteFileBlocking(const QString &filename, const Song &metadata, const SaveTypes types = SaveType::Tags, const SaveCoverOptions &save_cover_options = SaveCoverOptions());
bool IsMediaFileBlocking(const QString &filename);
Result LoadEmbeddedArtBlocking(const QString &filename, QByteArray &data);
Result LoadEmbeddedArtAsImageBlocking(const QString &filename, QImage &image);
Result SaveEmbeddedArtBlocking(const QString &filename, const SaveCoverOptions &save_cover_options);
Result SaveSongPlaycountBlocking(const QString &filename, const uint playcount);
Result SaveSongRatingBlocking(const QString &filename, const float rating);
// TODO: Make this not a singleton
static TagReaderClient *Instance() { return sInstance; }
Q_SIGNALS:
void ExitFinished();
private Q_SLOTS:
void Exit();
void WorkerFailedToStart();
public Q_SLOTS:
void SaveSongsPlaycount(const SongList &songs);
void SaveSongsRating(const SongList &songs);
private:
static TagReaderClient *sInstance;
WorkerPool<HandlerType> *worker_pool_;
QList<spb::tagreader::Message> message_queue_;
QThread *original_thread_;
};
using TagReaderReply = TagReaderClient::ReplyType;
#endif // TAGREADERCLIENT_H