Update issue 1175:
Save rating and statistics in two distinct ways, and let users activate them separately in preferences.
This commit is contained in:
parent
87ea891755
commit
88918d45c5
@ -53,6 +53,11 @@ void TagReaderWorker::MessageArrived(const pb::tagreader::Message& message) {
|
|||||||
tag_reader_.SaveSongStatisticsToFile(
|
tag_reader_.SaveSongStatisticsToFile(
|
||||||
QStringFromStdString(message.save_song_statistics_to_file_request().filename()),
|
QStringFromStdString(message.save_song_statistics_to_file_request().filename()),
|
||||||
message.save_song_statistics_to_file_request().metadata()));
|
message.save_song_statistics_to_file_request().metadata()));
|
||||||
|
} else if (message.has_save_song_rating_to_file_request()) {
|
||||||
|
reply.mutable_save_song_rating_to_file_response()->set_success(
|
||||||
|
tag_reader_.SaveSongRatingToFile(
|
||||||
|
QStringFromStdString(message.save_song_rating_to_file_request().filename()),
|
||||||
|
message.save_song_rating_to_file_request().metadata()));
|
||||||
} else if (message.has_is_media_file_request()) {
|
} else if (message.has_is_media_file_request()) {
|
||||||
reply.mutable_is_media_file_response()->set_success(
|
reply.mutable_is_media_file_response()->set_success(
|
||||||
tag_reader_.IsMediaFile(QStringFromStdString(message.is_media_file_request().filename())));
|
tag_reader_.IsMediaFile(QStringFromStdString(message.is_media_file_request().filename())));
|
||||||
|
@ -69,6 +69,7 @@ using boost::scoped_ptr;
|
|||||||
# include "cloudstream.h"
|
# include "cloudstream.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define NumberToASFAttribute(x) TagLib::ASF::Attribute(QStringToTaglibString(QString::number(x)))
|
||||||
|
|
||||||
class FileRefFactory {
|
class FileRefFactory {
|
||||||
public:
|
public:
|
||||||
@ -472,12 +473,16 @@ void TagReader::SetVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
|
|||||||
vorbis_comments->addField("COMPILATION", StdStringToTaglibString(song.compilation() ? "1" : "0"), true);
|
vorbis_comments->addField("COMPILATION", StdStringToTaglibString(song.compilation() ? "1" : "0"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagReader::SetFMPSVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
|
void TagReader::SetFMPSStatisticsVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
|
||||||
|
const pb::tagreader::SongMetadata& song) const {
|
||||||
|
vorbis_comments->addField("FMPS_PLAYCOUNT", QStringToTaglibString(QString::number(song.playcount())));
|
||||||
|
vorbis_comments->addField("FMPS_RATING_AMAROK_SCORE", QStringToTaglibString(QString::number(song.score() / 100.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagReader::SetFMPSRatingVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
|
||||||
const pb::tagreader::SongMetadata& song) const {
|
const pb::tagreader::SongMetadata& song) const {
|
||||||
|
|
||||||
vorbis_comments->addField("FMPS_RATING", QStringToTaglibString(QString::number(song.rating())));
|
vorbis_comments->addField("FMPS_RATING", QStringToTaglibString(QString::number(song.rating())));
|
||||||
vorbis_comments->addField("FMPS_PLAYCOUNT", QStringToTaglibString(QString::number(song.playcount())));
|
|
||||||
vorbis_comments->addField("FMPS_RATING_AMAROK_SCORE", QStringToTaglibString(QString::number(song.score() / 100.0)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pb::tagreader::SongMetadata_Type TagReader::GuessFileType(
|
pb::tagreader::SongMetadata_Type TagReader::GuessFileType(
|
||||||
@ -593,47 +598,83 @@ bool TagReader::SaveSongStatisticsToFile(const QString& filename,
|
|||||||
TagLib::ID3v2::Tag* tag = file->ID3v2Tag(true);
|
TagLib::ID3v2::Tag* tag = file->ID3v2Tag(true);
|
||||||
|
|
||||||
// Save as FMPS
|
// Save as FMPS
|
||||||
SetUserTextFrame("FMPS_Rating", QString::number(song.rating()), tag);
|
|
||||||
SetUserTextFrame("FMPS_PlayCount", QString::number(song.playcount()), tag);
|
SetUserTextFrame("FMPS_PlayCount", QString::number(song.playcount()), tag);
|
||||||
SetUserTextFrame("FMPS_Rating_Amarok_Score", QString::number(song.score() / 100.0), tag);
|
SetUserTextFrame("FMPS_Rating_Amarok_Score", QString::number(song.score() / 100.0), tag);
|
||||||
|
|
||||||
// Also save as POPM
|
// Also save as POPM
|
||||||
TagLib::ID3v2::PopularimeterFrame* frame = NULL;
|
TagLib::ID3v2::PopularimeterFrame* frame = GetPOPMFrameFromTag(tag);
|
||||||
|
|
||||||
const TagLib::ID3v2::FrameListMap& map = file->ID3v2Tag()->frameListMap();
|
|
||||||
if (!map["POPM"].isEmpty()) {
|
|
||||||
frame = dynamic_cast<TagLib::ID3v2::PopularimeterFrame*>(map["POPM"].front());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!frame) {
|
|
||||||
frame = new TagLib::ID3v2::PopularimeterFrame();
|
|
||||||
tag->addFrame(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
frame->setRating(ConvertToPOPMRating(song.rating()));
|
|
||||||
frame->setCounter(song.playcount());
|
frame->setCounter(song.playcount());
|
||||||
|
|
||||||
} else if (TagLib::FLAC::File* file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
|
} else if (TagLib::FLAC::File* file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
|
||||||
TagLib::Ogg::XiphComment* vorbis_comments = file->xiphComment(true);
|
TagLib::Ogg::XiphComment* vorbis_comments = file->xiphComment(true);
|
||||||
SetFMPSVorbisComments(vorbis_comments, song);
|
SetFMPSStatisticsVorbisComments(vorbis_comments, song);
|
||||||
} else if (TagLib::Ogg::XiphComment* tag = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
} else if (TagLib::Ogg::XiphComment* tag = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||||
SetFMPSVorbisComments(tag, song);
|
SetFMPSStatisticsVorbisComments(tag, song);
|
||||||
}
|
}
|
||||||
#ifdef TAGLIB_WITH_ASF
|
#ifdef TAGLIB_WITH_ASF
|
||||||
else if (TagLib::ASF::File* file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
|
else if (TagLib::ASF::File* file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
|
||||||
TagLib::ASF::Tag* tag = file->tag();
|
TagLib::ASF::Tag* tag = file->tag();
|
||||||
#define ConvertASF(x) TagLib::ASF::Attribute(QStringToTaglibString(QString::number(x)))
|
tag->addAttribute("FMPS/Playcount", NumberToASFAttribute(song.playcount()));
|
||||||
tag->addAttribute("FMPS/Rating", ConvertASF(song.rating()));
|
tag->addAttribute("FMPS/Rating_Amarok_Score", NumberToASFAttribute(song.score() / 100.0));
|
||||||
tag->addAttribute("FMPS/Playcount", ConvertASF(song.playcount()));
|
}
|
||||||
tag->addAttribute("FMPS/Rating_Amarok_Score", ConvertASF(song.score() / 100.0));
|
#endif
|
||||||
#undef ConvertASF
|
else if (TagLib::MP4::File* file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
|
||||||
|
TagLib::MP4::Tag* tag = file->tag();
|
||||||
|
tag->itemListMap()[kMP4_FMPS_Score_ID] = TagLib::StringList(QStringToTaglibString(QString::number(song.score() / 100.0)));
|
||||||
|
tag->itemListMap()[kMP4_FMPS_Playcount_ID] = TagLib::StringList(TagLib::String::number(song.playcount()));
|
||||||
|
} else {
|
||||||
|
// Nothing to save: stop now
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ret = fileref->save();
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
if (ret) {
|
||||||
|
// Linux: inotify doesn't seem to notice the change to the file unless we
|
||||||
|
// change the timestamps as well. (this is what touch does)
|
||||||
|
utimensat(0, QFile::encodeName(filename).constData(), NULL, 0);
|
||||||
|
}
|
||||||
|
#endif // Q_OS_LINUX
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TagReader::SaveSongRatingToFile(const QString& filename,
|
||||||
|
const pb::tagreader::SongMetadata& song) const {
|
||||||
|
if (filename.isNull())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
qLog(Debug) << "Saving song rating tags to" << filename;
|
||||||
|
|
||||||
|
scoped_ptr<TagLib::FileRef> fileref(factory_->GetFileRef(filename));
|
||||||
|
|
||||||
|
if (!fileref || fileref->isNull()) // The file probably doesn't exist
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (TagLib::MPEG::File* file = dynamic_cast<TagLib::MPEG::File*>(fileref->file())) {
|
||||||
|
TagLib::ID3v2::Tag* tag = file->ID3v2Tag(true);
|
||||||
|
|
||||||
|
// Save as FMPS
|
||||||
|
SetUserTextFrame("FMPS_Rating", QString::number(song.rating()), tag);
|
||||||
|
|
||||||
|
// Also save as POPM
|
||||||
|
TagLib::ID3v2::PopularimeterFrame* frame = GetPOPMFrameFromTag(tag);
|
||||||
|
frame->setRating(ConvertToPOPMRating(song.rating()));
|
||||||
|
|
||||||
|
} else if (TagLib::FLAC::File* file = dynamic_cast<TagLib::FLAC::File*>(fileref->file())) {
|
||||||
|
TagLib::Ogg::XiphComment* vorbis_comments = file->xiphComment(true);
|
||||||
|
SetFMPSRatingVorbisComments(vorbis_comments, song);
|
||||||
|
} else if (TagLib::Ogg::XiphComment* tag = dynamic_cast<TagLib::Ogg::XiphComment*>(fileref->file()->tag())) {
|
||||||
|
SetFMPSRatingVorbisComments(tag, song);
|
||||||
|
}
|
||||||
|
#ifdef TAGLIB_WITH_ASF
|
||||||
|
else if (TagLib::ASF::File* file = dynamic_cast<TagLib::ASF::File*>(fileref->file())) {
|
||||||
|
TagLib::ASF::Tag* tag = file->tag();
|
||||||
|
tag->addAttribute("FMPS/Rating", NumberToASFAttribute(song.rating()));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
else if (TagLib::MP4::File* file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
|
else if (TagLib::MP4::File* file = dynamic_cast<TagLib::MP4::File*>(fileref->file())) {
|
||||||
TagLib::MP4::Tag* tag = file->tag();
|
TagLib::MP4::Tag* tag = file->tag();
|
||||||
tag->itemListMap()[kMP4_FMPS_Rating_ID] = TagLib::StringList(QStringToTaglibString(QString::number(song.rating())));
|
tag->itemListMap()[kMP4_FMPS_Rating_ID] = TagLib::StringList(QStringToTaglibString(QString::number(song.rating())));
|
||||||
tag->itemListMap()[kMP4_FMPS_Score_ID] = TagLib::StringList(QStringToTaglibString(QString::number(song.score() / 100.0)));
|
|
||||||
tag->itemListMap()[kMP4_FMPS_Playcount_ID] = TagLib::StringList(TagLib::String::number(song.playcount()));
|
|
||||||
} else {
|
} else {
|
||||||
// Nothing to save: stop now
|
// Nothing to save: stop now
|
||||||
return true;
|
return true;
|
||||||
@ -881,6 +922,21 @@ bool TagReader::ReadCloudFile(const QUrl& download_url,
|
|||||||
}
|
}
|
||||||
#endif // HAVE_GOOGLE_DRIVE
|
#endif // HAVE_GOOGLE_DRIVE
|
||||||
|
|
||||||
|
TagLib::ID3v2::PopularimeterFrame* TagReader::GetPOPMFrameFromTag(TagLib::ID3v2::Tag* tag) {
|
||||||
|
TagLib::ID3v2::PopularimeterFrame* frame = NULL;
|
||||||
|
|
||||||
|
const TagLib::ID3v2::FrameListMap& map = tag->frameListMap();
|
||||||
|
if (!map["POPM"].isEmpty()) {
|
||||||
|
frame = dynamic_cast<TagLib::ID3v2::PopularimeterFrame*>(map["POPM"].front());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frame) {
|
||||||
|
frame = new TagLib::ID3v2::PopularimeterFrame();
|
||||||
|
tag->addFrame(frame);
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
float TagReader::ConvertPOPMRating(const int POPM_rating) {
|
float TagReader::ConvertPOPMRating(const int POPM_rating) {
|
||||||
if (POPM_rating < 0x01) {
|
if (POPM_rating < 0x01) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
|
@ -37,6 +37,7 @@ namespace TagLib {
|
|||||||
|
|
||||||
namespace ID3v2 {
|
namespace ID3v2 {
|
||||||
class Tag;
|
class Tag;
|
||||||
|
class PopularimeterFrame;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +58,8 @@ class TagReader {
|
|||||||
// returns true if the file exists but nothing has been written inside because
|
// returns true if the file exists but nothing has been written inside because
|
||||||
// statistics tag format is not supported for this kind of file)
|
// statistics tag format is not supported for this kind of file)
|
||||||
bool SaveSongStatisticsToFile(const QString& filename, const pb::tagreader::SongMetadata& song) const;
|
bool SaveSongStatisticsToFile(const QString& filename, const pb::tagreader::SongMetadata& song) const;
|
||||||
|
bool SaveSongRatingToFile(const QString& filename, const pb::tagreader::SongMetadata& song) const;
|
||||||
|
|
||||||
bool IsMediaFile(const QString& filename) const;
|
bool IsMediaFile(const QString& filename) const;
|
||||||
QByteArray LoadEmbeddedArt(const QString& filename) const;
|
QByteArray LoadEmbeddedArt(const QString& filename) const;
|
||||||
|
|
||||||
@ -82,8 +85,10 @@ class TagReader {
|
|||||||
pb::tagreader::SongMetadata* song) const;
|
pb::tagreader::SongMetadata* song) const;
|
||||||
void SetVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
|
void SetVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
|
||||||
const pb::tagreader::SongMetadata& song) const;
|
const pb::tagreader::SongMetadata& song) const;
|
||||||
void SetFMPSVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
|
void SetFMPSStatisticsVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
|
||||||
const pb::tagreader::SongMetadata& song) const;
|
const pb::tagreader::SongMetadata& song) const;
|
||||||
|
void SetFMPSRatingVorbisComments(TagLib::Ogg::XiphComment* vorbis_comments,
|
||||||
|
const pb::tagreader::SongMetadata& song) const;
|
||||||
|
|
||||||
pb::tagreader::SongMetadata_Type GuessFileType(TagLib::FileRef* fileref) const;
|
pb::tagreader::SongMetadata_Type GuessFileType(TagLib::FileRef* fileref) const;
|
||||||
|
|
||||||
@ -105,6 +110,7 @@ private:
|
|||||||
static float ConvertPOPMRating(const int POPM_rating);
|
static float ConvertPOPMRating(const int POPM_rating);
|
||||||
// Reciprocal
|
// Reciprocal
|
||||||
static int ConvertToPOPMRating(const float rating);
|
static int ConvertToPOPMRating(const float rating);
|
||||||
|
static TagLib::ID3v2::PopularimeterFrame* GetPOPMFrameFromTag(TagLib::ID3v2::Tag* tag);
|
||||||
|
|
||||||
FileRefFactory* factory_;
|
FileRefFactory* factory_;
|
||||||
QNetworkAccessManager* network_;
|
QNetworkAccessManager* network_;
|
||||||
|
@ -98,7 +98,6 @@ message ReadCloudFileResponse {
|
|||||||
optional SongMetadata metadata = 1;
|
optional SongMetadata metadata = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
message SaveSongStatisticsToFileRequest {
|
message SaveSongStatisticsToFileRequest {
|
||||||
optional string filename = 1;
|
optional string filename = 1;
|
||||||
optional SongMetadata metadata = 2;
|
optional SongMetadata metadata = 2;
|
||||||
@ -108,6 +107,15 @@ message SaveSongStatisticsToFileResponse {
|
|||||||
optional bool success = 1;
|
optional bool success = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SaveSongRatingToFileRequest {
|
||||||
|
optional string filename = 1;
|
||||||
|
optional SongMetadata metadata = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SaveSongRatingToFileResponse {
|
||||||
|
optional bool success = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message Message {
|
message Message {
|
||||||
optional int32 id = 1;
|
optional int32 id = 1;
|
||||||
|
|
||||||
@ -129,4 +137,6 @@ message Message {
|
|||||||
optional SaveSongStatisticsToFileRequest save_song_statistics_to_file_request = 12;
|
optional SaveSongStatisticsToFileRequest save_song_statistics_to_file_request = 12;
|
||||||
optional SaveSongStatisticsToFileResponse save_song_statistics_to_file_response = 13;
|
optional SaveSongStatisticsToFileResponse save_song_statistics_to_file_response = 13;
|
||||||
|
|
||||||
|
optional SaveSongRatingToFileRequest save_song_rating_to_file_request = 14;
|
||||||
|
optional SaveSongRatingToFileResponse save_song_rating_to_file_response = 15;
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,24 @@ void TagReaderClient::UpdateSongsStatistics(const SongList& songs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TagReaderReply* TagReaderClient::UpdateSongRating(const Song& metadata) {
|
||||||
|
pb::tagreader::Message message;
|
||||||
|
pb::tagreader::SaveSongRatingToFileRequest* req =
|
||||||
|
message.mutable_save_song_rating_to_file_request();
|
||||||
|
|
||||||
|
req->set_filename(DataCommaSizeFromQString(metadata.url().toLocalFile()));
|
||||||
|
metadata.ToProtobuf(req->mutable_metadata());
|
||||||
|
|
||||||
|
return worker_pool_->SendMessageWithReply(&message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagReaderClient::UpdateSongsRating(const SongList& songs) {
|
||||||
|
foreach (const Song& song, songs) {
|
||||||
|
TagReaderReply* reply = UpdateSongRating(song);
|
||||||
|
connect(reply, SIGNAL(Finished(bool)), reply, SLOT(deleteLater()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TagReaderReply* TagReaderClient::IsMediaFile(const QString& filename) {
|
TagReaderReply* TagReaderClient::IsMediaFile(const QString& filename) {
|
||||||
pb::tagreader::Message message;
|
pb::tagreader::Message message;
|
||||||
pb::tagreader::IsMediaFileRequest* req = message.mutable_is_media_file_request();
|
pb::tagreader::IsMediaFileRequest* req = message.mutable_is_media_file_request();
|
||||||
@ -161,6 +179,20 @@ bool TagReaderClient::UpdateSongStatisticsBlocking(const Song& metadata) {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TagReaderClient::UpdateSongRatingBlocking(const Song& metadata) {
|
||||||
|
Q_ASSERT(QThread::currentThread() != thread());
|
||||||
|
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
TagReaderReply* reply = UpdateSongRating(metadata);
|
||||||
|
if (reply->WaitForFinished()) {
|
||||||
|
ret = reply->message().save_song_rating_to_file_response().success();
|
||||||
|
}
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
bool TagReaderClient::IsMediaFileBlocking(const QString& filename) {
|
bool TagReaderClient::IsMediaFileBlocking(const QString& filename) {
|
||||||
Q_ASSERT(QThread::currentThread() != thread());
|
Q_ASSERT(QThread::currentThread() != thread());
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ public:
|
|||||||
ReplyType* ReadFile(const QString& filename);
|
ReplyType* ReadFile(const QString& filename);
|
||||||
ReplyType* SaveFile(const QString& filename, const Song& metadata);
|
ReplyType* SaveFile(const QString& filename, const Song& metadata);
|
||||||
ReplyType* UpdateSongStatistics(const Song& metadata);
|
ReplyType* UpdateSongStatistics(const Song& metadata);
|
||||||
|
ReplyType* UpdateSongRating(const Song& metadata);
|
||||||
ReplyType* IsMediaFile(const QString& filename);
|
ReplyType* IsMediaFile(const QString& filename);
|
||||||
ReplyType* LoadEmbeddedArt(const QString& filename);
|
ReplyType* LoadEmbeddedArt(const QString& filename);
|
||||||
ReplyType* ReadCloudFile(const QUrl& download_url,
|
ReplyType* ReadCloudFile(const QUrl& download_url,
|
||||||
@ -58,6 +59,7 @@ public:
|
|||||||
void ReadFileBlocking(const QString& filename, Song* song);
|
void ReadFileBlocking(const QString& filename, Song* song);
|
||||||
bool SaveFileBlocking(const QString& filename, const Song& metadata);
|
bool SaveFileBlocking(const QString& filename, const Song& metadata);
|
||||||
bool UpdateSongStatisticsBlocking(const Song& metadata);
|
bool UpdateSongStatisticsBlocking(const Song& metadata);
|
||||||
|
bool UpdateSongRatingBlocking(const Song& metadata);
|
||||||
bool IsMediaFileBlocking(const QString& filename);
|
bool IsMediaFileBlocking(const QString& filename);
|
||||||
QImage LoadEmbeddedArtBlocking(const QString& filename);
|
QImage LoadEmbeddedArtBlocking(const QString& filename);
|
||||||
|
|
||||||
@ -66,6 +68,7 @@ public:
|
|||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void UpdateSongsStatistics(const SongList& songs);
|
void UpdateSongsStatistics(const SongList& songs);
|
||||||
|
void UpdateSongsRating(const SongList& songs);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void WorkerFailedToStart();
|
void WorkerFailedToStart();
|
||||||
|
@ -168,6 +168,7 @@ void Library::WriteAllSongsStatisticsToFiles() {
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (const Song& song, all_songs) {
|
foreach (const Song& song, all_songs) {
|
||||||
TagReaderClient::Instance()->UpdateSongStatisticsBlocking(song);
|
TagReaderClient::Instance()->UpdateSongStatisticsBlocking(song);
|
||||||
|
TagReaderClient::Instance()->UpdateSongRatingBlocking(song);
|
||||||
app_->task_manager()->SetTaskProgress(task_id, ++i, nb_songs);
|
app_->task_manager()->SetTaskProgress(task_id, ++i, nb_songs);
|
||||||
}
|
}
|
||||||
app_->task_manager()->SetTaskFinished(task_id);
|
app_->task_manager()->SetTaskFinished(task_id);
|
||||||
|
@ -40,7 +40,8 @@ const char* LibraryBackend::kNewScoreSql =
|
|||||||
|
|
||||||
LibraryBackend::LibraryBackend(QObject *parent)
|
LibraryBackend::LibraryBackend(QObject *parent)
|
||||||
: LibraryBackendInterface(parent),
|
: LibraryBackendInterface(parent),
|
||||||
save_statistics_in_file_(false)
|
save_statistics_in_file_(false),
|
||||||
|
save_ratings_in_file_(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1048,7 +1049,7 @@ void LibraryBackend::UpdateSongRating(int id, float rating) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
Song new_song = GetSongById(id, db);
|
Song new_song = GetSongById(id, db);
|
||||||
emit SongsStatisticsChanged(SongList() << new_song);
|
emit SongsRatingChanged(SongList() << new_song);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LibraryBackend::DeleteAll() {
|
void LibraryBackend::DeleteAll() {
|
||||||
@ -1080,15 +1081,34 @@ void LibraryBackend::ReloadSettingsAsync() {
|
|||||||
void LibraryBackend::ReloadSettings() {
|
void LibraryBackend::ReloadSettings() {
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
bool save_statistics_in_file = s.value("save_statistics_in_file", false).toBool();
|
|
||||||
// Compare with previous value to know if we should connect, disconnect or nothing
|
// Statistics
|
||||||
if (save_statistics_in_file_ && !save_statistics_in_file) {
|
{
|
||||||
disconnect(this, SIGNAL(SongsStatisticsChanged(SongList)),
|
bool save_statistics_in_file = s.value("save_statistics_in_file", false).toBool();
|
||||||
TagReaderClient::Instance(), SLOT(UpdateSongsStatistics(SongList)));
|
// Compare with previous value to know if we should connect, disconnect or nothing
|
||||||
} else if (!save_statistics_in_file_ && save_statistics_in_file) {
|
if (save_statistics_in_file_ && !save_statistics_in_file) {
|
||||||
connect(this, SIGNAL(SongsStatisticsChanged(SongList)),
|
disconnect(this, SIGNAL(SongsStatisticsChanged(SongList)),
|
||||||
TagReaderClient::Instance(), SLOT(UpdateSongsStatistics(SongList)));
|
TagReaderClient::Instance(), SLOT(UpdateSongsStatistics(SongList)));
|
||||||
|
} else if (!save_statistics_in_file_ && save_statistics_in_file) {
|
||||||
|
connect(this, SIGNAL(SongsStatisticsChanged(SongList)),
|
||||||
|
TagReaderClient::Instance(), SLOT(UpdateSongsStatistics(SongList)));
|
||||||
|
}
|
||||||
|
// Save old value
|
||||||
|
save_statistics_in_file_ = save_statistics_in_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rating
|
||||||
|
{
|
||||||
|
bool save_ratings_in_file = s.value("save_ratings_in_file", false).toBool();
|
||||||
|
// Compare with previous value to know if we should connect, disconnect or nothing
|
||||||
|
if (save_ratings_in_file_ && !save_ratings_in_file) {
|
||||||
|
disconnect(this, SIGNAL(SongsRatingChanged(SongList)),
|
||||||
|
TagReaderClient::Instance(), SLOT(UpdateSongsRating(SongList)));
|
||||||
|
} else if (!save_ratings_in_file_ && save_ratings_in_file) {
|
||||||
|
connect(this, SIGNAL(SongsRatingChanged(SongList)),
|
||||||
|
TagReaderClient::Instance(), SLOT(UpdateSongsRating(SongList)));
|
||||||
|
}
|
||||||
|
// Save old value
|
||||||
|
save_ratings_in_file_ = save_ratings_in_file;
|
||||||
}
|
}
|
||||||
// Save old value
|
|
||||||
save_statistics_in_file_ = save_statistics_in_file;
|
|
||||||
}
|
}
|
||||||
|
@ -192,6 +192,7 @@ class LibraryBackend : public LibraryBackendInterface {
|
|||||||
void SongsDiscovered(const SongList& songs);
|
void SongsDiscovered(const SongList& songs);
|
||||||
void SongsDeleted(const SongList& songs);
|
void SongsDeleted(const SongList& songs);
|
||||||
void SongsStatisticsChanged(const SongList& songs);
|
void SongsStatisticsChanged(const SongList& songs);
|
||||||
|
void SongsRatingChanged(const SongList& songs);
|
||||||
void DatabaseReset();
|
void DatabaseReset();
|
||||||
|
|
||||||
void TotalSongCountUpdated(int total);
|
void TotalSongCountUpdated(int total);
|
||||||
@ -227,6 +228,7 @@ class LibraryBackend : public LibraryBackendInterface {
|
|||||||
QString subdirs_table_;
|
QString subdirs_table_;
|
||||||
QString fts_table_;
|
QString fts_table_;
|
||||||
bool save_statistics_in_file_;
|
bool save_statistics_in_file_;
|
||||||
|
bool save_ratings_in_file_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // LIBRARYBACKEND_H
|
#endif // LIBRARYBACKEND_H
|
||||||
|
@ -101,7 +101,8 @@ LibraryModel::LibraryModel(LibraryBackend* backend, Application* app,
|
|||||||
|
|
||||||
connect(backend_, SIGNAL(SongsDiscovered(SongList)), SLOT(SongsDiscovered(SongList)));
|
connect(backend_, SIGNAL(SongsDiscovered(SongList)), SLOT(SongsDiscovered(SongList)));
|
||||||
connect(backend_, SIGNAL(SongsDeleted(SongList)), SLOT(SongsDeleted(SongList)));
|
connect(backend_, SIGNAL(SongsDeleted(SongList)), SLOT(SongsDeleted(SongList)));
|
||||||
connect(backend_, SIGNAL(SongsStatisticsChanged(SongList)), SLOT(SongsStatisticsChanged(SongList)));
|
connect(backend_, SIGNAL(SongsStatisticsChanged(SongList)), SLOT(SongsSlightlyChanged(SongList)));
|
||||||
|
connect(backend_, SIGNAL(SongsRatingChanged(SongList)), SLOT(SongsSlightlyChanged(SongList)));
|
||||||
connect(backend_, SIGNAL(DatabaseReset()), SLOT(Reset()));
|
connect(backend_, SIGNAL(DatabaseReset()), SLOT(Reset()));
|
||||||
connect(backend_, SIGNAL(TotalSongCountUpdated(int)), SLOT(TotalSongCountUpdatedSlot(int)));
|
connect(backend_, SIGNAL(TotalSongCountUpdated(int)), SLOT(TotalSongCountUpdatedSlot(int)));
|
||||||
|
|
||||||
@ -219,7 +220,7 @@ void LibraryModel::SongsDiscovered(const SongList& songs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LibraryModel::SongsStatisticsChanged(const SongList& songs) {
|
void LibraryModel::SongsSlightlyChanged(const SongList& songs) {
|
||||||
// This is called if there was a minor change to the songs that will not
|
// This is called if there was a minor change to the songs that will not
|
||||||
// normally require the library to be restructured. We can just update our
|
// normally require the library to be restructured. We can just update our
|
||||||
// internal cache of Song objects without worrying about resetting the model.
|
// internal cache of Song objects without worrying about resetting the model.
|
||||||
|
@ -183,7 +183,7 @@ class LibraryModel : public SimpleTreeModel<LibraryItem> {
|
|||||||
// From LibraryBackend
|
// From LibraryBackend
|
||||||
void SongsDiscovered(const SongList& songs);
|
void SongsDiscovered(const SongList& songs);
|
||||||
void SongsDeleted(const SongList& songs);
|
void SongsDeleted(const SongList& songs);
|
||||||
void SongsStatisticsChanged(const SongList& songs);
|
void SongsSlightlyChanged(const SongList& songs);
|
||||||
void TotalSongCountUpdatedSlot(int count);
|
void TotalSongCountUpdatedSlot(int count);
|
||||||
|
|
||||||
// Called after ResetAsync
|
// Called after ResetAsync
|
||||||
|
@ -101,6 +101,7 @@ void LibrarySettingsPage::Save() {
|
|||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
s.beginGroup(LibraryBackend::kSettingsGroup);
|
s.beginGroup(LibraryBackend::kSettingsGroup);
|
||||||
|
s.setValue("save_ratings_in_file", ui_->save_ratings_in_file->isChecked());
|
||||||
s.setValue("save_statistics_in_file", ui_->save_statistics_in_file->isChecked());
|
s.setValue("save_statistics_in_file", ui_->save_statistics_in_file->isChecked());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
}
|
}
|
||||||
@ -139,6 +140,7 @@ void LibrarySettingsPage::Load() {
|
|||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
s.beginGroup(LibraryBackend::kSettingsGroup);
|
s.beginGroup(LibraryBackend::kSettingsGroup);
|
||||||
|
ui_->save_ratings_in_file->setChecked(s.value("save_ratings_in_file", false).toBool());
|
||||||
ui_->save_statistics_in_file->setChecked(s.value("save_statistics_in_file", false).toBool());
|
ui_->save_statistics_in_file->setChecked(s.value("save_statistics_in_file", false).toBool());
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
}
|
}
|
||||||
|
@ -92,13 +92,20 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="save_ratings_in_file">
|
||||||
|
<property name="text">
|
||||||
|
<string>Save ratings in file tags when possible</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="save_statistics_in_file">
|
<widget class="QCheckBox" name="save_statistics_in_file">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p>If not checked, Clementine will try to save your ratings and other statistics only in a separate database and don't modify your files.</p><p>If checked, it will save statistics both in database and directly into the file each time they changed.</p><p>Please note it might not work for every format and, as there is no standard for doing so, other music players might not be able to read them.</p></body></html></string>
|
<string><html><head/><body><p>If not checked, Clementine will try to save your ratings and other statistics only in a separate database and don't modify your files.</p><p>If checked, it will save statistics both in database and directly into the file each time they changed.</p><p>Please note it might not work for every format and, as there is no standard for doing so, other music players might not be able to read them.</p></body></html></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Save ratings and statistics in file tags when possible</string>
|
<string>Save statistics in file tags when possible</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -68,6 +68,7 @@ void PlaylistManager::Init(LibraryBackend* library_backend,
|
|||||||
|
|
||||||
connect(library_backend_, SIGNAL(SongsDiscovered(SongList)), SLOT(SongsDiscovered(SongList)));
|
connect(library_backend_, SIGNAL(SongsDiscovered(SongList)), SLOT(SongsDiscovered(SongList)));
|
||||||
connect(library_backend_, SIGNAL(SongsStatisticsChanged(SongList)), SLOT(SongsDiscovered(SongList)));
|
connect(library_backend_, SIGNAL(SongsStatisticsChanged(SongList)), SLOT(SongsDiscovered(SongList)));
|
||||||
|
connect(library_backend_, SIGNAL(SongsRatingChanged(SongList)), SLOT(SongsDiscovered(SongList)));
|
||||||
|
|
||||||
foreach (const PlaylistBackend::Playlist& p, playlist_backend->GetAllOpenPlaylists()) {
|
foreach (const PlaylistBackend::Playlist& p, playlist_backend->GetAllOpenPlaylists()) {
|
||||||
AddPlaylist(p.id, p.name, p.special_type, p.ui_path);
|
AddPlaylist(p.id, p.name, p.special_type, p.ui_path);
|
||||||
|
@ -69,6 +69,13 @@ class SongTest : public ::testing::Test {
|
|||||||
song.ToProtobuf(&pb_song);
|
song.ToProtobuf(&pb_song);
|
||||||
tag_reader.SaveSongStatisticsToFile(filename, pb_song);
|
tag_reader.SaveSongStatisticsToFile(filename, pb_song);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void WriteSongRatingToFile(const Song& song, const QString& filename) {
|
||||||
|
TagReader tag_reader;
|
||||||
|
::pb::tagreader::SongMetadata pb_song;
|
||||||
|
song.ToProtobuf(&pb_song);
|
||||||
|
tag_reader.SaveSongRatingToFile(filename, pb_song);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -112,7 +119,7 @@ TEST_F(SongTest, FMPSRatingUser) {
|
|||||||
EXPECT_FLOAT_EQ(0.10, song.rating());
|
EXPECT_FLOAT_EQ(0.10, song.rating());
|
||||||
|
|
||||||
song.set_rating(0.20);
|
song.set_rating(0.20);
|
||||||
WriteSongStatisticsToFile(song, r.fileName());
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
Song new_song = ReadSongFromFile(r.fileName());
|
Song new_song = ReadSongFromFile(r.fileName());
|
||||||
EXPECT_FLOAT_EQ(0.20, new_song.rating());
|
EXPECT_FLOAT_EQ(0.20, new_song.rating());
|
||||||
}
|
}
|
||||||
@ -173,11 +180,22 @@ TEST_F(SongTest, BothFMPSPOPMRating) {
|
|||||||
EXPECT_FLOAT_EQ(0.42, song.rating());
|
EXPECT_FLOAT_EQ(0.42, song.rating());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SongTest, RatingAndStatisticsOgg) {
|
TEST_F(SongTest, RatingOgg) {
|
||||||
TemporaryResource r(":/testdata/beep.ogg");
|
TemporaryResource r(":/testdata/beep.ogg");
|
||||||
{
|
{
|
||||||
Song song = ReadSongFromFile(r.fileName());
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
song.set_rating(0.20);
|
song.set_rating(0.20);
|
||||||
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Song new_song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_FLOAT_EQ(0.20, new_song.rating());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SongTest, StatisticsOgg) {
|
||||||
|
TemporaryResource r(":/testdata/beep.ogg");
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
song.set_playcount(1337);
|
song.set_playcount(1337);
|
||||||
song.set_score(87);
|
song.set_score(87);
|
||||||
|
|
||||||
@ -185,16 +203,26 @@ TEST_F(SongTest, RatingAndStatisticsOgg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Song new_song = ReadSongFromFile(r.fileName());
|
Song new_song = ReadSongFromFile(r.fileName());
|
||||||
EXPECT_FLOAT_EQ(0.20, new_song.rating());
|
|
||||||
EXPECT_EQ(1337, new_song.playcount());
|
EXPECT_EQ(1337, new_song.playcount());
|
||||||
EXPECT_EQ(87, new_song.score());
|
EXPECT_EQ(87, new_song.score());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(SongTest, RatingAndStatisticsFLAC) {
|
TEST_F(SongTest, RatingFLAC) {
|
||||||
TemporaryResource r(":/testdata/beep.flac");
|
TemporaryResource r(":/testdata/beep.flac");
|
||||||
{
|
{
|
||||||
Song song = ReadSongFromFile(r.fileName());
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
song.set_rating(0.20);
|
song.set_rating(0.20);
|
||||||
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Song new_song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_FLOAT_EQ(0.20, new_song.rating());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SongTest, StatisticsFLAC) {
|
||||||
|
TemporaryResource r(":/testdata/beep.flac");
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
song.set_playcount(1337);
|
song.set_playcount(1337);
|
||||||
song.set_score(87);
|
song.set_score(87);
|
||||||
|
|
||||||
@ -202,17 +230,28 @@ TEST_F(SongTest, RatingAndStatisticsFLAC) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Song new_song = ReadSongFromFile(r.fileName());
|
Song new_song = ReadSongFromFile(r.fileName());
|
||||||
EXPECT_FLOAT_EQ(0.20, new_song.rating());
|
|
||||||
EXPECT_EQ(1337, new_song.playcount());
|
EXPECT_EQ(1337, new_song.playcount());
|
||||||
EXPECT_EQ(87, new_song.score());
|
EXPECT_EQ(87, new_song.score());
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef TAGLIB_WITH_ASF
|
#ifdef TAGLIB_WITH_ASF
|
||||||
TEST_F(SongTest, RatingAndStatisticsASF) {
|
TEST_F(SongTest, RatingASF) {
|
||||||
TemporaryResource r(":/testdata/beep.wma");
|
TemporaryResource r(":/testdata/beep.wma");
|
||||||
{
|
{
|
||||||
Song song = ReadSongFromFile(r.fileName());
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
song.set_rating(0.20);
|
song.set_rating(0.20);
|
||||||
|
|
||||||
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Song new_song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_FLOAT_EQ(0.20, new_song.rating());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SongTest, StatisticsASF) {
|
||||||
|
TemporaryResource r(":/testdata/beep.wma");
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
song.set_playcount(1337);
|
song.set_playcount(1337);
|
||||||
song.set_score(87);
|
song.set_score(87);
|
||||||
|
|
||||||
@ -220,17 +259,28 @@ TEST_F(SongTest, RatingAndStatisticsASF) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Song new_song = ReadSongFromFile(r.fileName());
|
Song new_song = ReadSongFromFile(r.fileName());
|
||||||
EXPECT_FLOAT_EQ(0.20, new_song.rating());
|
|
||||||
EXPECT_EQ(1337, new_song.playcount());
|
EXPECT_EQ(1337, new_song.playcount());
|
||||||
EXPECT_EQ(87, new_song.score());
|
EXPECT_EQ(87, new_song.score());
|
||||||
}
|
}
|
||||||
#endif // TAGLIB_WITH_ASF
|
#endif // TAGLIB_WITH_ASF
|
||||||
|
|
||||||
TEST_F(SongTest, RatingAndStatisticsMP4) {
|
TEST_F(SongTest, RatingMP4) {
|
||||||
TemporaryResource r(":/testdata/beep.m4a");
|
TemporaryResource r(":/testdata/beep.m4a");
|
||||||
{
|
{
|
||||||
Song song = ReadSongFromFile(r.fileName());
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
song.set_rating(0.20);
|
song.set_rating(0.20);
|
||||||
|
|
||||||
|
WriteSongRatingToFile(song, r.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Song new_song = ReadSongFromFile(r.fileName());
|
||||||
|
EXPECT_FLOAT_EQ(0.20, new_song.rating());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SongTest, StatisticsMP4) {
|
||||||
|
TemporaryResource r(":/testdata/beep.m4a");
|
||||||
|
{
|
||||||
|
Song song = ReadSongFromFile(r.fileName());
|
||||||
song.set_playcount(1337);
|
song.set_playcount(1337);
|
||||||
song.set_score(87);
|
song.set_score(87);
|
||||||
|
|
||||||
@ -238,7 +288,6 @@ TEST_F(SongTest, RatingAndStatisticsMP4) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Song new_song = ReadSongFromFile(r.fileName());
|
Song new_song = ReadSongFromFile(r.fileName());
|
||||||
EXPECT_FLOAT_EQ(0.20, new_song.rating());
|
|
||||||
EXPECT_EQ(1337, new_song.playcount());
|
EXPECT_EQ(1337, new_song.playcount());
|
||||||
EXPECT_EQ(87, new_song.score());
|
EXPECT_EQ(87, new_song.score());
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user