Listenbrainz: count fetched listen even if we can't parse them
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ListenTypes.hpp"
|
||||
|
||||
namespace Scrobbling::ListenBrainz
|
||||
{
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, const Listen& listen)
|
||||
{
|
||||
os << "track name = '" << listen.trackName << "', artistName = '" << listen.artistName << "'";
|
||||
if (listen.listenedAt.isValid())
|
||||
os << ", listenedAt = " << listen.listenedAt.toString();
|
||||
if (!listen.releaseName.empty())
|
||||
os << ", releaseName = '" << listen.releaseName << "'";
|
||||
if (listen.trackNumber)
|
||||
os << ", trackNumber = " << *listen.trackNumber;
|
||||
if (listen.recordingMBID)
|
||||
os << ", recordingMBID = '" << listen.recordingMBID->getAsString() << "'";
|
||||
|
||||
return os;
|
||||
}
|
||||
} // Scrobbling::ListenBrainz
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS 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.
|
||||
*
|
||||
* LMS 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 LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <ostream>
|
||||
#include <Wt/WDateTime.h>
|
||||
|
||||
#include "utils/UUID.hpp"
|
||||
|
||||
namespace Scrobbling::ListenBrainz
|
||||
{
|
||||
struct Listen
|
||||
{
|
||||
std::string trackName;
|
||||
std::string releaseName;
|
||||
std::string artistName;
|
||||
std::optional<UUID> recordingMBID;
|
||||
std::optional<UUID> releaseMBID;
|
||||
std::optional<unsigned> trackNumber;
|
||||
Wt::WDateTime listenedAt;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const Listen& listen);
|
||||
} // Scrobbling::ListenBrainz
|
||||
@@ -31,44 +31,44 @@ namespace
|
||||
{
|
||||
using namespace Scrobbling::ListenBrainz;
|
||||
|
||||
ListensParser::Entry
|
||||
parseListen(const Wt::Json::Object& listen)
|
||||
Listen
|
||||
parseListen(const Wt::Json::Object& listenObject)
|
||||
{
|
||||
ListensParser::Entry entry;
|
||||
Listen listen;
|
||||
|
||||
// Mandatory fields
|
||||
const Wt::Json::Object& metadata = listen.get("track_metadata");
|
||||
entry.trackName = static_cast<std::string>(metadata.get("track_name"));
|
||||
entry.artistName = static_cast<std::string>(metadata.get("artist_name"));
|
||||
const Wt::Json::Object& metadata = listenObject.get("track_metadata");
|
||||
listen.trackName = static_cast<std::string>(metadata.get("track_name"));
|
||||
listen.artistName = static_cast<std::string>(metadata.get("artist_name"));
|
||||
|
||||
// Optional fields
|
||||
entry.releaseName = static_cast<std::string>(metadata.get("release_name").orIfNull(""));
|
||||
if (listen.type("listened_at") == Wt::Json::Type::Number)
|
||||
entry.listenedAt = Wt::WDateTime::fromTime_t(static_cast<int>(listen.get("listened_at")));
|
||||
if (!entry.listenedAt.isValid())
|
||||
listen.releaseName = static_cast<std::string>(metadata.get("release_name").orIfNull(""));
|
||||
if (listenObject.type("listened_at") == Wt::Json::Type::Number)
|
||||
listen.listenedAt = Wt::WDateTime::fromTime_t(static_cast<int>(listenObject.get("listened_at")));
|
||||
if (!listen.listenedAt.isValid())
|
||||
LOG(ERROR) << "Invalid or missing 'listened_at' field!";
|
||||
|
||||
if (metadata.type("additional_info") == Wt::Json::Type::Object)
|
||||
{
|
||||
const Wt::Json::Object& additionalInfo = metadata.get("additional_info");
|
||||
entry.recordingMBID = UUID::fromString(additionalInfo.get("recording_mbid").orIfNull(""));
|
||||
entry.releaseMBID = UUID::fromString(additionalInfo.get("release_mbid").orIfNull(""));
|
||||
listen.recordingMBID = UUID::fromString(additionalInfo.get("recording_mbid").orIfNull(""));
|
||||
listen.releaseMBID = UUID::fromString(additionalInfo.get("release_mbid").orIfNull(""));
|
||||
|
||||
int trackNumber {additionalInfo.get("tracknumber").orIfNull(-1)};
|
||||
if (trackNumber > 0)
|
||||
entry.trackNumber = trackNumber;
|
||||
listen.trackNumber = trackNumber;
|
||||
}
|
||||
|
||||
return entry;
|
||||
return listen;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace Scrobbling::ListenBrainz
|
||||
{
|
||||
std::vector<ListensParser::Entry>
|
||||
ListensParser::Result
|
||||
ListensParser::parse(std::string_view msgBody)
|
||||
{
|
||||
std::vector<Entry> entries;
|
||||
Result result;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -79,16 +79,17 @@ namespace Scrobbling::ListenBrainz
|
||||
const Wt::Json::Array& listens = payload.get("listens");
|
||||
|
||||
LOG(DEBUG) << "Parsing " << listens.size() << " listens...";
|
||||
result.listenCount = listens.size();
|
||||
|
||||
if (listens.empty())
|
||||
return entries;
|
||||
return result;
|
||||
|
||||
for (const Wt::Json::Value& value : listens)
|
||||
{
|
||||
try
|
||||
{
|
||||
const Wt::Json::Object& listen = value;
|
||||
entries.push_back(parseListen(listen));
|
||||
result.listens.push_back(parseListen(listen));
|
||||
}
|
||||
catch (const Wt::WException& error)
|
||||
{
|
||||
@@ -101,22 +102,6 @@ namespace Scrobbling::ListenBrainz
|
||||
LOG(ERROR) << "Cannot parse 'listens': " << error.what();
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
operator<<(std::ostream& os, const ListensParser::Entry& entry)
|
||||
{
|
||||
os << "track name = '" << entry.trackName << "', artistName = '" << entry.artistName << "'";
|
||||
if (entry.listenedAt.isValid())
|
||||
os << ", listenedAt = " << entry.listenedAt.toString();
|
||||
if (!entry.releaseName.empty())
|
||||
os << ", releaseName = '" << entry.releaseName << "'";
|
||||
if (entry.trackNumber)
|
||||
os << ", trackNumber = " << *entry.trackNumber;
|
||||
if (entry.recordingMBID)
|
||||
os << ", recordingMBID = '" << entry.recordingMBID->getAsString() << "'";
|
||||
|
||||
return os;
|
||||
return result;
|
||||
}
|
||||
} // Scrobbling::ListenBrainz
|
||||
|
||||
@@ -19,31 +19,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <ostream>
|
||||
#include <Wt/WDateTime.h>
|
||||
|
||||
#include "utils/UUID.hpp"
|
||||
#include "ListenTypes.hpp"
|
||||
|
||||
namespace Scrobbling::ListenBrainz
|
||||
{
|
||||
class ListensParser
|
||||
{
|
||||
public:
|
||||
struct Entry
|
||||
struct Result
|
||||
{
|
||||
std::string trackName;
|
||||
std::string releaseName;
|
||||
std::string artistName;
|
||||
std::optional<UUID> recordingMBID;
|
||||
std::optional<UUID> releaseMBID;
|
||||
std::optional<unsigned> trackNumber;
|
||||
Wt::WDateTime listenedAt;
|
||||
std::size_t listenCount; // may be > than listens.size()
|
||||
std::vector<Listen> listens; // successfully parsed listens
|
||||
};
|
||||
|
||||
static std::vector<Entry> parse(std::string_view msgBody);
|
||||
static Result parse(std::string_view msgBody);
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const ListensParser::Entry& entry);
|
||||
|
||||
} // Scrobbling::ListenBrainz
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace
|
||||
}
|
||||
|
||||
Database::TrackId
|
||||
tryGetMatchingTrack(Database::Session& session, const ListensParser::Entry& listen)
|
||||
tryGetMatchingTrack(Database::Session& session, const Listen& listen)
|
||||
{
|
||||
using namespace Database;
|
||||
|
||||
@@ -216,13 +216,13 @@ namespace Scrobbling::ListenBrainz
|
||||
}
|
||||
|
||||
void
|
||||
ListensSynchronizer::enqueListenNow(const Listen& listen)
|
||||
ListensSynchronizer::enqueListenNow(const Scrobbling::Listen& listen)
|
||||
{
|
||||
enqueListen(listen, {});
|
||||
}
|
||||
|
||||
void
|
||||
ListensSynchronizer::enqueListen(const Listen& listen, const Wt::WDateTime& timePoint)
|
||||
ListensSynchronizer::enqueListen(const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint)
|
||||
{
|
||||
Http::ClientPOSTRequestParameters request;
|
||||
request.relativeUrl = "/1/submit-listens";
|
||||
@@ -548,10 +548,10 @@ namespace Scrobbling::ListenBrainz
|
||||
Database::Session& session {_db.getTLSSession()};
|
||||
|
||||
context.maxDateTime = {}; // invalidate to break in case no more listens are fetched
|
||||
std::vector<ListensParser::Entry> parsedListens {ListensParser::parse(msgBody)};
|
||||
context.fetchedListenCount += parsedListens.size();
|
||||
ListensParser::Result result {ListensParser::parse(msgBody)};
|
||||
context.fetchedListenCount += result.listenCount;
|
||||
|
||||
for (const ListensParser::Entry& parsedListen : parsedListens)
|
||||
for (const Listen& parsedListen : result.listens)
|
||||
{
|
||||
// update oldest listen for the next query
|
||||
if (!parsedListen.listenedAt.isValid())
|
||||
|
||||
Reference in New Issue
Block a user