Made Audinaut work. fixes #76

This commit is contained in:
emeric
2020-07-03 21:03:16 +02:00
parent b2863e927c
commit 718d2ee6d7
6 changed files with 73 additions and 65 deletions
@@ -38,6 +38,7 @@ namespace API::Subsonic
const Wt::Http::ParameterMap& parameters; const Wt::Http::ParameterMap& parameters;
Database::Session& dbSession; Database::Session& dbSession;
std::string userName; std::string userName;
std::string clientName;
}; };
} }
+4 -4
View File
@@ -43,20 +43,20 @@ namespace API::Subsonic::Scan
Response Response
handleGetScanStatus(RequestContext&) handleGetScanStatus(RequestContext& context)
{ {
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
response.addNode("scanStatus", createStatusResponseNode()); response.addNode("scanStatus", createStatusResponseNode());
return response; return response;
} }
Response Response
handleStartScan(RequestContext&) handleStartScan(RequestContext& context)
{ {
ServiceProvider<IMediaScanner>::get()->requestImmediateScan(); ServiceProvider<IMediaScanner>::get()->requestImmediateScan();
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
response.addNode("scanStatus", createStatusResponseNode()); response.addNode("scanStatus", createStatusResponseNode());
return response; return response;
+43 -49
View File
@@ -141,15 +141,15 @@ getClientInfo(const Wt::Http::ParameterMap& parameters)
ClientInfo res; ClientInfo res;
// Mandatory parameters // Mandatory parameters
res.name = getMandatoryParameterAs<std::string>(parameters, "c");
res.version = getMandatoryParameterAs<ClientVersion>(parameters, "v"); res.version = getMandatoryParameterAs<ClientVersion>(parameters, "v");
if (res.version.major > API_VERSION_MAJOR) if (res.version.major > API_VERSION_MAJOR)
throw ServerMustUpgradeError {}; throw ServerMustUpgradeError {};
if (res.version.major < API_VERSION_MAJOR) if (res.version.major < API_VERSION_MAJOR)
throw ClientMustUpgradeError {}; throw ClientMustUpgradeError {};
if (res.version.minor > API_VERSION_MINOR) if (res.version.minor > Response::getAPIMinorVersion(res.name))
throw ServerMustUpgradeError {}; throw ServerMustUpgradeError {};
res.name = getMandatoryParameterAs<std::string>(parameters, "c");
res.user = getMandatoryParameterAs<std::string>(parameters, "u"); res.user = getMandatoryParameterAs<std::string>(parameters, "u");
res.password = decodePasswordIfNeeded(getMandatoryParameterAs<std::string>(parameters, "p")); res.password = decodePasswordIfNeeded(getMandatoryParameterAs<std::string>(parameters, "p"));
@@ -493,9 +493,9 @@ userToResponseNode(const User::pointer& user)
static static
Response Response
handlePingRequest(RequestContext&) handlePingRequest(RequestContext& context)
{ {
return Response::createOkResponse(); return Response::createOkResponse(context);
} }
static static
@@ -521,7 +521,7 @@ handleChangePassword(RequestContext& context)
user.modify()->setPasswordHash(hash); user.modify()->setPasswordHash(hash);
user.modify()->clearAuthTokens(); user.modify()->clearAuthTokens();
return Response::createOkResponse(); return Response::createOkResponse(context);
} }
static static
@@ -576,7 +576,7 @@ handleCreatePlaylistRequest(RequestContext& context)
TrackListEntry::create(context.dbSession, track, tracklist ); TrackListEntry::create(context.dbSession, track, tracklist );
} }
return Response::createOkResponse(); return Response::createOkResponse(context);
} }
static static
@@ -599,7 +599,7 @@ handleCreateUserRequest(RequestContext& context)
User::pointer user {User::create(context.dbSession, username, hash)}; User::pointer user {User::create(context.dbSession, username, hash)};
return Response::createOkResponse(); return Response::createOkResponse(context);
} }
static static
@@ -626,7 +626,7 @@ handleDeletePlaylistRequest(RequestContext& context)
tracklist.remove(); tracklist.remove();
return Response::createOkResponse(); return Response::createOkResponse(context);
} }
static static
@@ -647,14 +647,14 @@ handleDeleteUserRequest(RequestContext& context)
user.remove(); user.remove();
return Response::createOkResponse(); return Response::createOkResponse(context);
} }
static static
Response Response
handleGetLicenseRequest(RequestContext&) handleGetLicenseRequest(RequestContext& context)
{ {
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& licenseNode {response.createNode("license")}; Response::Node& licenseNode {response.createNode("license")};
licenseNode.setAttribute("licenseExpires", "2025-09-03T14:46:43"); licenseNode.setAttribute("licenseExpires", "2025-09-03T14:46:43");
@@ -680,7 +680,7 @@ handleGetRandomSongsRequest(RequestContext& context)
auto tracks {Track::getAllRandom(context.dbSession, {}, size)}; auto tracks {Track::getAllRandom(context.dbSession, {}, size)};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& randomSongsNode {response.createNode("randomSongs")}; Response::Node& randomSongsNode {response.createNode("randomSongs")};
for (const Track::pointer& track : tracks) for (const Track::pointer& track : tracks)
@@ -757,7 +757,7 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3)
else else
throw NotImplementedGenericError {}; throw NotImplementedGenericError {};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& albumListNode {response.createNode(id3 ? "albumList2" : "albumList")}; Response::Node& albumListNode {response.createNode(id3 ? "albumList2" : "albumList")};
for (const Release::pointer& release : releases) for (const Release::pointer& release : releases)
@@ -800,7 +800,7 @@ handleGetAlbumRequest(RequestContext& context)
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node releaseNode {releaseToResponseNode(release, context.dbSession, user, true /* id3 */)}; Response::Node releaseNode {releaseToResponseNode(release, context.dbSession, user, true /* id3 */)};
auto tracks {release->getTracks()}; auto tracks {release->getTracks()};
@@ -832,7 +832,7 @@ handleGetArtistRequest(RequestContext& context)
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node artistNode {artistToResponseNode(user, artist, true /* id3 */)}; Response::Node artistNode {artistToResponseNode(user, artist, true /* id3 */)};
auto releases {artist->getReleases()}; auto releases {artist->getReleases()};
@@ -856,7 +856,7 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
// Optional params // Optional params
std::size_t count {getParameterAs<std::size_t>(context.parameters, "count").value_or(20)}; std::size_t count {getParameterAs<std::size_t>(context.parameters, "count").value_or(20)};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& artistInfoNode {response.createNode(id3 ? "artistInfo2" : "artistInfo")}; Response::Node& artistInfoNode {response.createNode(id3 ? "artistInfo2" : "artistInfo")};
{ {
@@ -909,7 +909,7 @@ static
Response Response
handleGetArtistsRequest(RequestContext& context) handleGetArtistsRequest(RequestContext& context)
{ {
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& artistsNode {response.createNode("artists")}; Response::Node& artistsNode {response.createNode("artists")};
Response::Node& indexNode {artistsNode.createArrayChild("index")}; Response::Node& indexNode {artistsNode.createArrayChild("index")};
@@ -951,7 +951,7 @@ handleGetMusicDirectoryRequest(RequestContext& context)
// Mandatory params // Mandatory params
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")}; Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& directoryNode {response.createNode("directory")}; Response::Node& directoryNode {response.createNode("directory")};
directoryNode.setAttribute("id", IdToString(id)); directoryNode.setAttribute("id", IdToString(id));
@@ -1015,9 +1015,9 @@ handleGetMusicDirectoryRequest(RequestContext& context)
static static
Response Response
handleGetMusicFoldersRequest(RequestContext&) handleGetMusicFoldersRequest(RequestContext& context)
{ {
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& musicFoldersNode {response.createNode("musicFolders")}; Response::Node& musicFoldersNode {response.createNode("musicFolders")};
Response::Node& musicFolderNode {musicFoldersNode.createArrayChild("musicFolder")}; Response::Node& musicFolderNode {musicFoldersNode.createArrayChild("musicFolder")};
@@ -1031,7 +1031,7 @@ static
Response Response
handleGetGenresRequest(RequestContext& context) handleGetGenresRequest(RequestContext& context)
{ {
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& genresNode {response.createNode("genres")}; Response::Node& genresNode {response.createNode("genres")};
@@ -1053,7 +1053,7 @@ static
Response Response
handleGetIndexesRequest(RequestContext& context) handleGetIndexesRequest(RequestContext& context)
{ {
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& artistsNode {response.createNode("indexes")}; Response::Node& artistsNode {response.createNode("indexes")};
Response::Node& indexNode {artistsNode.createArrayChild("index")}; Response::Node& indexNode {artistsNode.createArrayChild("index")};
@@ -1129,7 +1129,7 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
Random::shuffleContainer(tracks); Random::shuffleContainer(tracks);
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& similarSongsNode {response.createNode(id3 ? "similarSongs2" : "similarSongs")}; Response::Node& similarSongsNode {response.createNode(id3 ? "similarSongs2" : "similarSongs")};
for (const Track::pointer& track : tracks) for (const Track::pointer& track : tracks)
similarSongsNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user)); similarSongsNode.addArrayChild("song", trackToResponseNode(track, context.dbSession, user));
@@ -1161,7 +1161,7 @@ handleGetStarredRequestCommon(RequestContext& context, bool id3)
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& starredNode {response.createNode(id3 ? "starred2" : "starred")}; Response::Node& starredNode {response.createNode(id3 ? "starred2" : "starred")};
{ {
@@ -1236,7 +1236,7 @@ handleGetPlaylistRequest(RequestContext& context)
if (!tracklist) if (!tracklist)
throw RequestedDataNotFoundError {}; throw RequestedDataNotFoundError {};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node playlistNode {tracklistToResponseNode(tracklist, context.dbSession)}; Response::Node playlistNode {tracklistToResponseNode(tracklist, context.dbSession)};
auto entries {tracklist->getEntries()}; auto entries {tracklist->getEntries()};
@@ -1258,7 +1258,7 @@ handleGetPlaylistsRequest(RequestContext& context)
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& playlistsNode {response.createNode("playlists")}; Response::Node& playlistsNode {response.createNode("playlists")};
auto tracklists {TrackList::getAll(context.dbSession, user, TrackList::Type::Playlist)}; auto tracklists {TrackList::getAll(context.dbSession, user, TrackList::Type::Playlist)};
@@ -1295,7 +1295,7 @@ handleGetSongsByGenreRequest(RequestContext& context)
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& songsByGenreNode {response.createNode("songsByGenre")}; Response::Node& songsByGenreNode {response.createNode("songsByGenre")};
bool more; bool more;
@@ -1320,7 +1320,7 @@ handleGetUserRequest(RequestContext& context)
if (!user) if (!user)
throw RequestedDataNotFoundError {}; throw RequestedDataNotFoundError {};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
response.addNode("user", userToResponseNode(user)); response.addNode("user", userToResponseNode(user));
return response; return response;
@@ -1332,7 +1332,7 @@ handleGetUsersRequest(RequestContext& context)
{ {
auto transaction {context.dbSession.createSharedTransaction()}; auto transaction {context.dbSession.createSharedTransaction()};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& usersNode {response.createNode("users")}; Response::Node& usersNode {response.createNode("users")};
const auto users {User::getAll(context.dbSession)}; const auto users {User::getAll(context.dbSession)};
@@ -1365,7 +1365,7 @@ handleSearchRequestCommon(RequestContext& context, bool id3)
if (!user) if (!user)
throw UserNotAuthorizedError {}; throw UserNotAuthorizedError {};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& searchResult2Node {response.createNode(id3 ? "searchResult3" : "searchResult2")}; Response::Node& searchResult2Node {response.createNode(id3 ? "searchResult3" : "searchResult2")};
bool more; bool more;
@@ -1474,7 +1474,7 @@ handleStarRequest(RequestContext& context)
user.modify()->starTrack(track); user.modify()->starTrack(track);
} }
return Response::createOkResponse(); return Response::createOkResponse(context);
} }
static static
@@ -1531,7 +1531,7 @@ handleUnstarRequest(RequestContext& context)
} }
return Response::createOkResponse(); return Response::createOkResponse(context);
} }
static static
@@ -1563,7 +1563,7 @@ handleUpdateUserRequest(RequestContext& context)
user.modify()->clearAuthTokens(); user.modify()->clearAuthTokens();
} }
return Response::createOkResponse(); return Response::createOkResponse(context);
} }
static static
@@ -1627,7 +1627,7 @@ handleUpdatePlaylistRequest(RequestContext& context)
TrackListEntry::create(context.dbSession, track, tracklist ); TrackListEntry::create(context.dbSession, track, tracklist );
} }
return Response::createOkResponse(); return Response::createOkResponse(context);
} }
static static
@@ -1642,7 +1642,7 @@ handleGetBookmarks(RequestContext& context)
const auto bookmarks {TrackBookmark::getByUser(context.dbSession, user)}; const auto bookmarks {TrackBookmark::getByUser(context.dbSession, user)};
Response response {Response::createOkResponse()}; Response response {Response::createOkResponse(context)};
Response::Node& bookmarksNode {response.createNode("bookmarks")}; Response::Node& bookmarksNode {response.createNode("bookmarks")};
for (const TrackBookmark::pointer& bookmark : bookmarks) for (const TrackBookmark::pointer& bookmark : bookmarks)
@@ -1687,7 +1687,7 @@ handleCreateBookmark(RequestContext& context)
if (comment) if (comment)
bookmark.modify()->setComment(*comment); bookmark.modify()->setComment(*comment);
return Response::createOkResponse(); return Response::createOkResponse(context);
} }
static static
@@ -1715,7 +1715,7 @@ handleDeleteBookmark(RequestContext& context)
bookmark.remove(); bookmark.remove();
return Response::createOkResponse(); return Response::createOkResponse(context);
} }
static static
@@ -1891,12 +1891,15 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
// Optional parameters // Optional parameters
const ResponseFormat format {getParameterAs<std::string>(parameters, "f").value_or("xml") == "json" ? ResponseFormat::json : ResponseFormat::xml}; const ResponseFormat format {getParameterAs<std::string>(parameters, "f").value_or("xml") == "json" ? ResponseFormat::json : ResponseFormat::xml};
std::string clientName;
try try
{ {
// Mandatory parameters // Mandatory parameters
const ClientInfo clientInfo {getClientInfo(parameters)}; const ClientInfo clientInfo {getClientInfo(parameters)};
clientName = clientInfo.name;
SessionPool::ScopedSession dbSession {_sessionPool}; SessionPool::ScopedSession dbSession {_sessionPool};
switch (ServiceProvider<Auth::IPasswordService>::get()->checkUserPassword(dbSession.get(), switch (ServiceProvider<Auth::IPasswordService>::get()->checkUserPassword(dbSession.get(),
@@ -1911,7 +1914,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
throw LoginThrottledGenericError {}; throw LoginThrottledGenericError {};
} }
RequestContext requestContext {parameters, dbSession.get(), clientInfo.user}; RequestContext requestContext {parameters, dbSession.get(), clientInfo.user, clientInfo.name};
auto itEntryPoint {requestEntryPoints.find(requestPath)}; auto itEntryPoint {requestEntryPoints.find(requestPath)};
if (itEntryPoint != requestEntryPoints.end()) if (itEntryPoint != requestEntryPoints.end())
@@ -1945,21 +1948,12 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
LMS_LOG(API_SUBSONIC, ERROR) << "Unhandled command '" << requestPath << "'"; LMS_LOG(API_SUBSONIC, ERROR) << "Unhandled command '" << requestPath << "'";
throw UnknownEntryPointGenericError {}; throw UnknownEntryPointGenericError {};
} }
catch (const NotImplementedGenericError& e)
{
LMS_LOG(API_SUBSONIC, INFO) << "Command '" << requestPath << "'"
<< ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]"
<< ", code = " << static_cast<int>(e.getCode()) << ", msg = '" << static_cast<const Error&>(e).getMessage() << "'";
Response resp {Response::createFailedResponse(e)};
resp.write(response.out(), format);
response.setMimeType(ResponseFormatToMimeType(format));
}
catch (const Error& e) catch (const Error& e)
{ {
LMS_LOG(API_SUBSONIC, ERROR) << "Error while processing request '" << requestPath << "'" LMS_LOG(API_SUBSONIC, ERROR) << "Error while processing request '" << requestPath << "'"
<< ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]" << ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]"
<< ", code = " << static_cast<int>(e.getCode()) << ", msg = '" << e.getMessage() << "'"; << ", code = " << static_cast<int>(e.getCode()) << ", msg = '" << e.getMessage() << "'";
Response resp {Response::createFailedResponse(e)}; Response resp {Response::createFailedResponse(clientName, e)};
resp.write(response.out(), format); resp.write(response.out(), format);
response.setMimeType(ResponseFormatToMimeType(format)); response.setMimeType(ResponseFormatToMimeType(format));
} }
+15 -4
View File
@@ -28,6 +28,7 @@
#include <boost/property_tree/xml_parser.hpp> #include <boost/property_tree/xml_parser.hpp>
#include "utils/Exception.hpp" #include "utils/Exception.hpp"
#include "utils/String.hpp"
namespace API::Subsonic namespace API::Subsonic
{ {
@@ -108,25 +109,25 @@ Response::Node::createArrayChild(const std::string& key)
} }
Response Response
Response::createOkResponse() Response::createOkResponse(const RequestContext& context)
{ {
Response response; Response response;
Node& responseNode {response._root.createChild("subsonic-response")}; Node& responseNode {response._root.createChild("subsonic-response")};
responseNode.setAttribute("status", "ok"); responseNode.setAttribute("status", "ok");
responseNode.setAttribute("version", API_VERSION_STR); responseNode.setAttribute("version", std::string {QUOTEME(API_VERSION_MAJOR) "."} + std::to_string(getAPIMinorVersion(context.clientName)) + ".0");
return response; return response;
} }
Response Response
Response::createFailedResponse(const Error& error) Response::createFailedResponse(std::string_view clientName, const Error& error)
{ {
Response response; Response response;
Node& responseNode {response._root.createChild("subsonic-response")}; Node& responseNode {response._root.createChild("subsonic-response")};
responseNode.setAttribute("status", "failed"); responseNode.setAttribute("status", "failed");
responseNode.setAttribute("version", API_VERSION_STR); responseNode.setAttribute("version", std::string {QUOTEME(API_VERSION_MAJOR) "."} + std::to_string(getAPIMinorVersion(clientName)) + ".0");
Node& errorNode {responseNode.createChild("error")}; Node& errorNode {responseNode.createChild("error")};
errorNode.setAttribute("code", std::to_string(static_cast<int>(error.getCode()))); errorNode.setAttribute("code", std::to_string(static_cast<int>(error.getCode())));
@@ -213,6 +214,16 @@ Response::writeXML(std::ostream& os)
boost::property_tree::write_xml(os, root); boost::property_tree::write_xml(os, root);
} }
unsigned
Response::getAPIMinorVersion(std::string_view clientName)
{
// Audinaut does not lower its client API version in plain text authentication mode
if (clientName == "Audinaut")
return 13;
else
return 12;
}
void void
Response::writeJSON(std::ostream& os) Response::writeJSON(std::ostream& os)
{ {
+7 -7
View File
@@ -24,15 +24,13 @@
#include <variant> #include <variant>
#include <vector> #include <vector>
#include "RequestContext.hpp"
#define API_VERSION_MAJOR 1
namespace API::Subsonic namespace API::Subsonic
{ {
#define API_VERSION_MAJOR 1
#define API_VERSION_MINOR 12
#define API_VERSION_PATCH 0
#define API_VERSION_STR "1.12.0"
enum class ResponseFormat enum class ResponseFormat
{ {
xml, xml,
@@ -201,8 +199,8 @@ class Response
std::map<std::string, std::vector<Node>> _childrenArrays; std::map<std::string, std::vector<Node>> _childrenArrays;
}; };
static Response createOkResponse(); static Response createOkResponse(const RequestContext& context);
static Response createFailedResponse(const Error& error); static Response createFailedResponse(std::string_view clientName, const Error& error);
virtual ~Response() {} virtual ~Response() {}
Response(const Response&) = delete; Response(const Response&) = delete;
@@ -215,6 +213,8 @@ class Response
Node& createArrayNode(const std::string& key); Node& createArrayNode(const std::string& key);
void write(std::ostream& os, ResponseFormat format); void write(std::ostream& os, ResponseFormat format);
static unsigned getAPIMinorVersion(std::string_view clientName);
private: private:
void writeJSON(std::ostream& os); void writeJSON(std::ostream& os);
+3 -1
View File
@@ -25,8 +25,10 @@
#include <sstream> #include <sstream>
#include <vector> #include <vector>
namespace StringUtils { #define QUOTEME(x) QUOTEME_1(x)
#define QUOTEME_1(x) #x
namespace StringUtils {
std::vector<std::string> std::vector<std::string>
splitString(const std::string& string, const std::string& separators); splitString(const std::string& string, const std::string& separators);