Merge branch 'develop' for release v3.62.1
This commit is contained in:
@@ -7,7 +7,7 @@ _LMS_ is a self-hosted music streaming software: access your music collection fr
|
|||||||
A [demo instance](http://lms-demo.poupon.dev) is available. Note the administration settings are not available.
|
A [demo instance](http://lms-demo.poupon.dev) is available. Note the administration settings are not available.
|
||||||
|
|
||||||
## Main features
|
## Main features
|
||||||
* Recommendation engine
|
* [Subsonic/OpenSubsonic API](SUBSONIC.md) support
|
||||||
* Multi-valued tags: `genre`, `mood`, `artists`, ...
|
* Multi-valued tags: `genre`, `mood`, `artists`, ...
|
||||||
* Artist relationships: `composer`, `conductor`, `lyricist`, `mixer`, `performer`, `producer`, `remixer`
|
* Artist relationships: `composer`, `conductor`, `lyricist`, `mixer`, `performer`, `producer`, `remixer`
|
||||||
* [Release types](https://musicbrainz.org/doc/Release_Group/Type): `album`, `single`, `EP`, `compilation`, `live`, ...
|
* [Release types](https://musicbrainz.org/doc/Release_Group/Type): `album`, `single`, `EP`, `compilation`, `live`, ...
|
||||||
@@ -15,11 +15,13 @@ A [demo instance](http://lms-demo.poupon.dev) is available. Note the administrat
|
|||||||
* [ListenBrainz](https://listenbrainz.org) support for:
|
* [ListenBrainz](https://listenbrainz.org) support for:
|
||||||
* Scrobbling and synchronizing listens
|
* Scrobbling and synchronizing listens
|
||||||
* Synchronizing 'love' feedbacks
|
* Synchronizing 'love' feedbacks
|
||||||
* Audio transcoding for maximum interoperability and reduced bandwith requirements
|
* Recommendation engine
|
||||||
* Multi-library support
|
* Multi-library support
|
||||||
* ReplayGain support
|
* ReplayGain support
|
||||||
|
* Audio transcoding for compatibility and reduced bandwidth
|
||||||
* User management, with several [authentication backends](INSTALL.md#authentication-backend)
|
* User management, with several [authentication backends](INSTALL.md#authentication-backend)
|
||||||
* [Subsonic/OpenSubsonic API](SUBSONIC.md) support
|
* Playlists support
|
||||||
|
* Lyrics support
|
||||||
|
|
||||||
## Music discovery
|
## Music discovery
|
||||||
_LMS_ provides several ways to help you find the music you like:
|
_LMS_ provides several ways to help you find the music you like:
|
||||||
@@ -57,6 +59,12 @@ $setmulti(albumartists,%_albumartists%)
|
|||||||
$setmulti(albumartistssort,%_albumartists_sort%)
|
$setmulti(albumartistssort,%_albumartists_sort%)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Playlist support
|
||||||
|
_LMS_ supports playlist files in `m3u` and `m3u8` formats. These playlists are synced during the scan process and are available as public shared playlists.
|
||||||
|
|
||||||
|
## Lyrics support
|
||||||
|
_LMS_ supports lyrics in `lrc` files and embedded track metadata. Both synchronized and unsynchronized lyrics are supported.
|
||||||
|
|
||||||
## Keyboard shortcuts
|
## Keyboard shortcuts
|
||||||
* Play/pause: <kbd>Space</kbd>
|
* Play/pause: <kbd>Space</kbd>
|
||||||
* Previous track: <kbd>Ctrl</kbd> + <kbd>Left</kbd>
|
* Previous track: <kbd>Ctrl</kbd> + <kbd>Left</kbd>
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ namespace lms::db
|
|||||||
break;
|
break;
|
||||||
case TrackSortMethod::TrackList:
|
case TrackSortMethod::TrackList:
|
||||||
assert(params.trackList.isValid());
|
assert(params.trackList.isValid());
|
||||||
query.orderBy("t_l.id");
|
query.orderBy("t_l_e.id");
|
||||||
}
|
}
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace lms::metadata
|
|||||||
{
|
{
|
||||||
std::span<const std::filesystem::path> getSupportedPlayListFileExtensions()
|
std::span<const std::filesystem::path> getSupportedPlayListFileExtensions()
|
||||||
{
|
{
|
||||||
static const std::array<std::filesystem::path, 2> fileExtensions{ ".m3u", "m3u8" };
|
static const std::array<std::filesystem::path, 2> fileExtensions{ ".m3u", ".m3u8" };
|
||||||
return fileExtensions;
|
return fileExtensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -325,6 +325,7 @@ namespace lms::scanner
|
|||||||
AudioFileScanOperation& operator=(const AudioFileScanOperation&) = delete;
|
AudioFileScanOperation& operator=(const AudioFileScanOperation&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
const std::filesystem::path& getFile() const override { return _file; };
|
||||||
core::LiteralString getName() const override { return "ScanAudioFile"; }
|
core::LiteralString getName() const override { return "ScanAudioFile"; }
|
||||||
void scan() override;
|
void scan() override;
|
||||||
void processResult(ScanContext& context) override;
|
void processResult(ScanContext& context) override;
|
||||||
@@ -603,6 +604,11 @@ namespace lms::scanner
|
|||||||
|
|
||||||
AudioFileScanner::~AudioFileScanner() = default;
|
AudioFileScanner::~AudioFileScanner() = default;
|
||||||
|
|
||||||
|
core::LiteralString AudioFileScanner::getName() const
|
||||||
|
{
|
||||||
|
return "Audio scanner";
|
||||||
|
}
|
||||||
|
|
||||||
std::span<const std::filesystem::path> AudioFileScanner::getSupportedExtensions() const
|
std::span<const std::filesystem::path> AudioFileScanner::getSupportedExtensions() const
|
||||||
{
|
{
|
||||||
return _metadataParser->getSupportedExtensions();
|
return _metadataParser->getSupportedExtensions();
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ namespace lms::scanner
|
|||||||
AudioFileScanner& operator=(const AudioFileScanner&) = delete;
|
AudioFileScanner& operator=(const AudioFileScanner&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
core::LiteralString getName() const override;
|
||||||
std::span<const std::filesystem::path> getSupportedExtensions() const override;
|
std::span<const std::filesystem::path> getSupportedExtensions() const override;
|
||||||
bool needsScan(ScanContext& context, const FileToScan& file) const override;
|
bool needsScan(ScanContext& context, const FileToScan& file) const override;
|
||||||
std::unique_ptr<IFileScanOperation> createScanOperation(const FileToScan& fileToScan) const override;
|
std::unique_ptr<IFileScanOperation> createScanOperation(const FileToScan& fileToScan) const override;
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
#include "core/LiteralString.hpp"
|
#include "core/LiteralString.hpp"
|
||||||
|
|
||||||
namespace lms::scanner
|
namespace lms::scanner
|
||||||
@@ -31,6 +33,8 @@ namespace lms::scanner
|
|||||||
virtual ~IFileScanOperation() = default;
|
virtual ~IFileScanOperation() = default;
|
||||||
|
|
||||||
virtual core::LiteralString getName() const = 0;
|
virtual core::LiteralString getName() const = 0;
|
||||||
|
|
||||||
|
virtual const std::filesystem::path& getFile() const = 0;
|
||||||
virtual void scan() = 0;
|
virtual void scan() = 0;
|
||||||
virtual void processResult(ScanContext& context) = 0;
|
virtual void processResult(ScanContext& context) = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
|
#include "core/LiteralString.hpp"
|
||||||
|
|
||||||
#include "MediaLibraryInfo.hpp"
|
#include "MediaLibraryInfo.hpp"
|
||||||
|
|
||||||
namespace lms::scanner
|
namespace lms::scanner
|
||||||
@@ -41,6 +43,7 @@ namespace lms::scanner
|
|||||||
public:
|
public:
|
||||||
virtual ~IFileScanner() = default;
|
virtual ~IFileScanner() = default;
|
||||||
|
|
||||||
|
virtual core::LiteralString getName() const = 0;
|
||||||
virtual std::span<const std::filesystem::path> getSupportedExtensions() const = 0;
|
virtual std::span<const std::filesystem::path> getSupportedExtensions() const = 0;
|
||||||
virtual bool needsScan(ScanContext& context, const FileToScan& file) const = 0;
|
virtual bool needsScan(ScanContext& context, const FileToScan& file) const = 0;
|
||||||
virtual std::unique_ptr<IFileScanOperation> createScanOperation(const FileToScan& fileToScan) const = 0;
|
virtual std::unique_ptr<IFileScanOperation> createScanOperation(const FileToScan& fileToScan) const = 0;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ namespace lms::scanner
|
|||||||
, _db{ db } {}
|
, _db{ db } {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
const std::filesystem::path& getFile() const override { return _file; };
|
||||||
core::LiteralString getName() const override { return "ScanImageFile"; }
|
core::LiteralString getName() const override { return "ScanImageFile"; }
|
||||||
void scan() override;
|
void scan() override;
|
||||||
void processResult(ScanContext& context) override;
|
void processResult(ScanContext& context) override;
|
||||||
@@ -134,6 +135,11 @@ namespace lms::scanner
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core::LiteralString ImageFileScanner::getName() const
|
||||||
|
{
|
||||||
|
return "Image scanner";
|
||||||
|
}
|
||||||
|
|
||||||
std::span<const std::filesystem::path> ImageFileScanner::getSupportedExtensions() const
|
std::span<const std::filesystem::path> ImageFileScanner::getSupportedExtensions() const
|
||||||
{
|
{
|
||||||
return image::getSupportedFileExtensions();
|
return image::getSupportedFileExtensions();
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ namespace lms::scanner
|
|||||||
ImageFileScanner& operator=(const ImageFileScanner&) = delete;
|
ImageFileScanner& operator=(const ImageFileScanner&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
core::LiteralString getName() const override;
|
||||||
std::span<const std::filesystem::path> getSupportedExtensions() const override;
|
std::span<const std::filesystem::path> getSupportedExtensions() const override;
|
||||||
bool needsScan(ScanContext& context, const FileToScan& file) const override;
|
bool needsScan(ScanContext& context, const FileToScan& file) const override;
|
||||||
std::unique_ptr<IFileScanOperation> createScanOperation(const FileToScan& fileToScan) const override;
|
std::unique_ptr<IFileScanOperation> createScanOperation(const FileToScan& fileToScan) const override;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ namespace lms::scanner
|
|||||||
, _db{ db } {}
|
, _db{ db } {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
const std::filesystem::path& getFile() const override { return _file; };
|
||||||
core::LiteralString getName() const override { return "ScanLyricsFile"; }
|
core::LiteralString getName() const override { return "ScanLyricsFile"; }
|
||||||
void scan() override;
|
void scan() override;
|
||||||
void processResult(ScanContext& context) override;
|
void processResult(ScanContext& context) override;
|
||||||
@@ -137,6 +138,11 @@ namespace lms::scanner
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core::LiteralString LyricsFileScanner::getName() const
|
||||||
|
{
|
||||||
|
return "Lyrics scanner";
|
||||||
|
}
|
||||||
|
|
||||||
std::span<const std::filesystem::path> LyricsFileScanner::getSupportedExtensions() const
|
std::span<const std::filesystem::path> LyricsFileScanner::getSupportedExtensions() const
|
||||||
{
|
{
|
||||||
return metadata::getSupportedLyricsFileExtensions();
|
return metadata::getSupportedLyricsFileExtensions();
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ namespace lms::scanner
|
|||||||
LyricsFileScanner& operator=(const LyricsFileScanner&) = delete;
|
LyricsFileScanner& operator=(const LyricsFileScanner&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
core::LiteralString getName() const override;
|
||||||
std::span<const std::filesystem::path> getSupportedExtensions() const override;
|
std::span<const std::filesystem::path> getSupportedExtensions() const override;
|
||||||
bool needsScan(ScanContext& context, const FileToScan& file) const override;
|
bool needsScan(ScanContext& context, const FileToScan& file) const override;
|
||||||
std::unique_ptr<IFileScanOperation> createScanOperation(const FileToScan& fileToScan) const override;
|
std::unique_ptr<IFileScanOperation> createScanOperation(const FileToScan& fileToScan) const override;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ namespace lms::scanner
|
|||||||
PlayListFileScanOperation& operator=(const PlayListFileScanOperation&) = delete;
|
PlayListFileScanOperation& operator=(const PlayListFileScanOperation&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
const std::filesystem::path& getFile() const override { return _file; };
|
||||||
core::LiteralString getName() const override { return "ScanPlayListFile"; }
|
core::LiteralString getName() const override { return "ScanPlayListFile"; }
|
||||||
void scan() override;
|
void scan() override;
|
||||||
void processResult(ScanContext& context) override;
|
void processResult(ScanContext& context) override;
|
||||||
@@ -136,6 +137,11 @@ namespace lms::scanner
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core::LiteralString PlayListFileScanner::getName() const
|
||||||
|
{
|
||||||
|
return "PlayList scanner";
|
||||||
|
}
|
||||||
|
|
||||||
std::span<const std::filesystem::path> PlayListFileScanner::getSupportedExtensions() const
|
std::span<const std::filesystem::path> PlayListFileScanner::getSupportedExtensions() const
|
||||||
{
|
{
|
||||||
return metadata::getSupportedPlayListFileExtensions();
|
return metadata::getSupportedPlayListFileExtensions();
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ namespace lms::scanner
|
|||||||
PlayListFileScanner& operator=(const PlayListFileScanner&) = delete;
|
PlayListFileScanner& operator=(const PlayListFileScanner&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
core::LiteralString getName() const override;
|
||||||
std::span<const std::filesystem::path> getSupportedExtensions() const override;
|
std::span<const std::filesystem::path> getSupportedExtensions() const override;
|
||||||
bool needsScan(ScanContext& context, const FileToScan& file) const override;
|
bool needsScan(ScanContext& context, const FileToScan& file) const override;
|
||||||
std::unique_ptr<IFileScanOperation> createScanOperation(const FileToScan& fileToScan) const override;
|
std::unique_ptr<IFileScanOperation> createScanOperation(const FileToScan& fileToScan) const override;
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include <boost/asio/post.hpp>
|
#include <boost/asio/post.hpp>
|
||||||
|
|
||||||
|
#include "core/ILogger.hpp"
|
||||||
#include "core/ITraceLogger.hpp"
|
#include "core/ITraceLogger.hpp"
|
||||||
|
|
||||||
#include "scanners/IFileScanOperation.hpp"
|
#include "scanners/IFileScanOperation.hpp"
|
||||||
@@ -52,6 +53,7 @@ namespace lms::scanner
|
|||||||
{
|
{
|
||||||
{
|
{
|
||||||
LMS_SCOPED_TRACE_OVERVIEW("Scanner", operation->getName());
|
LMS_SCOPED_TRACE_OVERVIEW("Scanner", operation->getName());
|
||||||
|
LMS_LOG(DBUPDATER, DEBUG, operation->getName() << ": scanning file '" << operation->getFile().string() << "'");
|
||||||
operation->scan();
|
operation->scan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ namespace lms::scanner
|
|||||||
{
|
{
|
||||||
[[maybe_unused]] auto [it, inserted]{ _scannerByExtension.emplace(extension, scanner) };
|
[[maybe_unused]] auto [it, inserted]{ _scannerByExtension.emplace(extension, scanner) };
|
||||||
assert(inserted);
|
assert(inserted);
|
||||||
|
LMS_LOG(DBUPDATER, INFO, "Registered extension '" << extension.string() << "' for '" << scanner->getName() << "'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +140,7 @@ namespace lms::scanner
|
|||||||
if (_abortScan)
|
if (_abortScan)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
LMS_LOG(DBUPDATER, DEBUG, scanOperation->getName() << ": processing result for '" << scanOperation->getFile().string() << "'");
|
||||||
scanOperation->processResult(context);
|
scanOperation->processResult(context);
|
||||||
context.stats.scans++;
|
context.stats.scans++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace lms::api::subsonic
|
|||||||
};
|
};
|
||||||
|
|
||||||
static inline constexpr ProtocolVersion defaultServerProtocolVersion{ 1, 16, 0 };
|
static inline constexpr ProtocolVersion defaultServerProtocolVersion{ 1, 16, 0 };
|
||||||
static inline constexpr std::string_view serverVersion{ "7" };
|
static inline constexpr std::string_view serverVersion{ "8" };
|
||||||
} // namespace lms::api::subsonic
|
} // namespace lms::api::subsonic
|
||||||
|
|
||||||
namespace lms::core::stringUtils
|
namespace lms::core::stringUtils
|
||||||
|
|||||||
@@ -33,6 +33,24 @@ namespace lms::api::subsonic
|
|||||||
{
|
{
|
||||||
using namespace db;
|
using namespace db;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void checkTrackListModificationAccess(const db::TrackList::pointer& trackList, const db::UserId currentUserId)
|
||||||
|
{
|
||||||
|
if (!trackList || trackList->getType() != TrackListType::PlayList)
|
||||||
|
throw RequestedDataNotFoundError{};
|
||||||
|
|
||||||
|
// Can only modify own playlists
|
||||||
|
if (trackList->getUserId() != currentUserId)
|
||||||
|
{
|
||||||
|
if (trackList->getVisibility() == TrackList::Visibility::Public)
|
||||||
|
throw UserNotAuthorizedError{};
|
||||||
|
|
||||||
|
throw RequestedDataNotFoundError{};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Response handleGetPlaylistsRequest(RequestContext& context)
|
Response handleGetPlaylistsRequest(RequestContext& context)
|
||||||
{
|
{
|
||||||
auto transaction{ context.dbSession.createReadTransaction() };
|
auto transaction{ context.dbSession.createReadTransaction() };
|
||||||
@@ -60,10 +78,11 @@ namespace lms::api::subsonic
|
|||||||
TrackList::FindParameters params;
|
TrackList::FindParameters params;
|
||||||
params.setVisibility(TrackList::Visibility::Public);
|
params.setVisibility(TrackList::Visibility::Public);
|
||||||
params.setType(TrackListType::PlayList);
|
params.setType(TrackListType::PlayList);
|
||||||
|
params.setExcludedUser(context.user->getId());
|
||||||
|
|
||||||
db::TrackList::find(context.dbSession, params, [&](const db::TrackList::pointer& trackList) {
|
db::TrackList::find(context.dbSession, params, [&](const db::TrackList::pointer& trackList) {
|
||||||
if (trackList->getUserId() != context.user->getId()) // skip already reported
|
assert(trackList->getUserId() != context.user->getId());
|
||||||
addTrackList(trackList);
|
addTrackList(trackList);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,14 +96,17 @@ namespace lms::api::subsonic
|
|||||||
|
|
||||||
auto transaction{ context.dbSession.createReadTransaction() };
|
auto transaction{ context.dbSession.createReadTransaction() };
|
||||||
|
|
||||||
TrackList::pointer tracklist{ TrackList::find(context.dbSession, trackListId) };
|
TrackList::pointer trackList{ TrackList::find(context.dbSession, trackListId) };
|
||||||
if (!tracklist)
|
if (!trackList || trackList->getType() != TrackListType::PlayList)
|
||||||
|
throw RequestedDataNotFoundError{};
|
||||||
|
|
||||||
|
if (trackList->getUserId() != context.user->getId() && trackList->getVisibility() != TrackList::Visibility::Public)
|
||||||
throw RequestedDataNotFoundError{};
|
throw RequestedDataNotFoundError{};
|
||||||
|
|
||||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||||
Response::Node playlistNode{ createPlaylistNode(tracklist, context.dbSession) };
|
Response::Node playlistNode{ createPlaylistNode(trackList, context.dbSession) };
|
||||||
|
|
||||||
auto entries{ tracklist->getEntries() };
|
auto entries{ trackList->getEntries() };
|
||||||
for (const TrackListEntry::pointer& entry : entries.results)
|
for (const TrackListEntry::pointer& entry : entries.results)
|
||||||
playlistNode.addArrayChild("entry", createSongNode(context, entry->getTrack(), context.user));
|
playlistNode.addArrayChild("entry", createSongNode(context, entry->getTrack(), context.user));
|
||||||
|
|
||||||
@@ -106,28 +128,23 @@ namespace lms::api::subsonic
|
|||||||
|
|
||||||
auto transaction{ context.dbSession.createWriteTransaction() };
|
auto transaction{ context.dbSession.createWriteTransaction() };
|
||||||
|
|
||||||
TrackList::pointer tracklist;
|
TrackList::pointer trackList;
|
||||||
if (id)
|
if (id)
|
||||||
{
|
{
|
||||||
tracklist = TrackList::find(context.dbSession, *id);
|
trackList = TrackList::find(context.dbSession, *id);
|
||||||
if (!tracklist
|
checkTrackListModificationAccess(trackList, context.user->getId());
|
||||||
|| tracklist->getUser() != context.user
|
|
||||||
|| tracklist->getType() != TrackListType::PlayList)
|
|
||||||
{
|
|
||||||
throw RequestedDataNotFoundError{};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name)
|
if (name)
|
||||||
tracklist.modify()->setName(*name);
|
trackList.modify()->setName(*name);
|
||||||
|
|
||||||
tracklist.modify()->clear();
|
trackList.modify()->clear();
|
||||||
tracklist.modify()->setLastModifiedDateTime(Wt::WDateTime::currentDateTime());
|
trackList.modify()->setLastModifiedDateTime(Wt::WDateTime::currentDateTime());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
tracklist = context.dbSession.create<TrackList>(*name, TrackListType::PlayList);
|
trackList = context.dbSession.create<TrackList>(*name, TrackListType::PlayList);
|
||||||
tracklist.modify()->setUser(context.user);
|
trackList.modify()->setUser(context.user);
|
||||||
tracklist.modify()->setVisibility(TrackList::Visibility::Private);
|
trackList.modify()->setVisibility(TrackList::Visibility::Private);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const TrackId trackId : trackIds)
|
for (const TrackId trackId : trackIds)
|
||||||
@@ -136,13 +153,13 @@ namespace lms::api::subsonic
|
|||||||
if (!track)
|
if (!track)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
context.dbSession.create<TrackListEntry>(track, tracklist);
|
context.dbSession.create<TrackListEntry>(track, trackList);
|
||||||
}
|
}
|
||||||
|
|
||||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||||
Response::Node playlistNode{ createPlaylistNode(tracklist, context.dbSession) };
|
Response::Node playlistNode{ createPlaylistNode(trackList, context.dbSession) };
|
||||||
|
|
||||||
auto entries{ tracklist->getEntries() };
|
auto entries{ trackList->getEntries() };
|
||||||
for (const TrackListEntry::pointer& entry : entries.results)
|
for (const TrackListEntry::pointer& entry : entries.results)
|
||||||
playlistNode.addArrayChild("entry", createSongNode(context, entry->getTrack(), context.user));
|
playlistNode.addArrayChild("entry", createSongNode(context, entry->getTrack(), context.user));
|
||||||
|
|
||||||
@@ -165,19 +182,14 @@ namespace lms::api::subsonic
|
|||||||
|
|
||||||
auto transaction{ context.dbSession.createWriteTransaction() };
|
auto transaction{ context.dbSession.createWriteTransaction() };
|
||||||
|
|
||||||
TrackList::pointer tracklist{ TrackList::find(context.dbSession, id) };
|
TrackList::pointer trackList{ TrackList::find(context.dbSession, id) };
|
||||||
if (!tracklist
|
checkTrackListModificationAccess(trackList, context.user->getId());
|
||||||
|| tracklist->getUser() != context.user
|
|
||||||
|| tracklist->getType() != TrackListType::PlayList)
|
|
||||||
{
|
|
||||||
throw RequestedDataNotFoundError{};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name)
|
if (name)
|
||||||
tracklist.modify()->setName(*name);
|
trackList.modify()->setName(*name);
|
||||||
|
|
||||||
tracklist.modify()->setVisibility(isPublic ? db::TrackList::Visibility::Public : db::TrackList::Visibility::Private);
|
trackList.modify()->setVisibility(isPublic ? db::TrackList::Visibility::Public : db::TrackList::Visibility::Private);
|
||||||
tracklist.modify()->setLastModifiedDateTime(Wt::WDateTime::currentDateTime());
|
trackList.modify()->setLastModifiedDateTime(Wt::WDateTime::currentDateTime());
|
||||||
|
|
||||||
{
|
{
|
||||||
// Remove from end to make indexes stable
|
// Remove from end to make indexes stable
|
||||||
@@ -185,7 +197,7 @@ namespace lms::api::subsonic
|
|||||||
|
|
||||||
for (std::size_t trackPositionToRemove : trackPositionsToRemove)
|
for (std::size_t trackPositionToRemove : trackPositionsToRemove)
|
||||||
{
|
{
|
||||||
auto entry{ tracklist->getEntry(trackPositionToRemove) };
|
auto entry{ trackList->getEntry(trackPositionToRemove) };
|
||||||
if (entry)
|
if (entry)
|
||||||
entry.remove();
|
entry.remove();
|
||||||
}
|
}
|
||||||
@@ -198,7 +210,7 @@ namespace lms::api::subsonic
|
|||||||
if (!track)
|
if (!track)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
context.dbSession.create<TrackListEntry>(track, tracklist);
|
context.dbSession.create<TrackListEntry>(track, trackList);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response::createOkResponse(context.serverProtocolVersion);
|
return Response::createOkResponse(context.serverProtocolVersion);
|
||||||
@@ -210,15 +222,10 @@ namespace lms::api::subsonic
|
|||||||
|
|
||||||
auto transaction{ context.dbSession.createWriteTransaction() };
|
auto transaction{ context.dbSession.createWriteTransaction() };
|
||||||
|
|
||||||
TrackList::pointer tracklist{ TrackList::find(context.dbSession, id) };
|
TrackList::pointer trackList{ TrackList::find(context.dbSession, id) };
|
||||||
if (!tracklist
|
checkTrackListModificationAccess(trackList, context.user->getId());
|
||||||
|| tracklist->getUser() != context.user
|
|
||||||
|| tracklist->getType() != TrackListType::PlayList)
|
|
||||||
{
|
|
||||||
throw RequestedDataNotFoundError{};
|
|
||||||
}
|
|
||||||
|
|
||||||
tracklist.remove();
|
trackList.remove();
|
||||||
|
|
||||||
return Response::createOkResponse(context.serverProtocolVersion);
|
return Response::createOkResponse(context.serverProtocolVersion);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user