Added playlist import (and sync)from m3u files, ref #391

This commit is contained in:
emeric
2024-12-18 14:15:25 +01:00
parent ec20c9bfa6
commit 3661eaccb6
95 changed files with 3439 additions and 1634 deletions
@@ -18,11 +18,11 @@
*/
#pragma once
#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <list>
#include <memory>
#include <vector>
namespace lms::api::subsonic
{
@@ -37,24 +37,22 @@ namespace lms::api::subsonic
TLSMonotonicMemoryResource()
{
allocateNewBlock();
allocateNewBlock(defaultBlockSize);
}
[[nodiscard]] std::byte* allocate(std::size_t byteCount, std::size_t alignment)
{
if (byteCount == 0)
byteCount = 1;
std::byte* currentAddrAligned{ computeAlignedAddr(_currentAddr, alignment) };
if (currentAddrAligned + byteCount > &_currentBlock->back() + 1)
if (!_currentBlock->fitsInBlock(currentAddrAligned, byteCount))
{
allocateNewBlock();
allocateNewBlock(std::max(defaultBlockSize, byteCount + alignment /* worst case */));
currentAddrAligned = computeAlignedAddr(_currentAddr, alignment);
}
// Requested too many bytes for blockSize!
if (currentAddrAligned + byteCount > &_currentBlock->back() + 1)
throw std::bad_alloc{};
assert(currentAddrAligned >= &_currentBlock->front());
assert(_currentBlock->fitsInBlock(currentAddrAligned, byteCount));
std::byte* res{ currentAddrAligned };
_currentAddr = currentAddrAligned + byteCount;
@@ -62,26 +60,34 @@ namespace lms::api::subsonic
return res;
}
void deallocate(std::byte*)
void deallocate(std::byte* /* ptr */)
{
// nothing to do!
}
void reset()
{
// always keep at least one block
// First: remove blocks that are not default-sized
std::erase_if(_blocks, [](const Block& _block) { return _block.size != defaultBlockSize; });
// Then always keep at least one block
if (_blocks.size() > 1)
_blocks.erase(std::next(std::cbegin(_blocks), 1), std::cend(_blocks));
_currentBlock = &_blocks.front();
_currentAddr = _currentBlock->data();
_currentAddr = _currentBlock->data.get();
}
private:
void allocateNewBlock()
void allocateNewBlock(std::size_t size)
{
_currentBlock = &_blocks.emplace_back();
_currentAddr = _currentBlock->data();
Block block{
.data = std::make_unique_for_overwrite<std::byte[]>(size),
.size = size,
};
_currentAddr = block.data.get();
_currentBlock = &_blocks.emplace_back(std::move(block));
}
static std::byte* computeAlignedAddr(std::byte* addr, std::size_t alignment) noexcept
@@ -93,11 +99,20 @@ namespace lms::api::subsonic
return reinterpret_cast<std::byte*>(addr + adjustment);
}
static constexpr std::size_t blockSize{ static_cast<std::size_t>(1 * 1024 * 1024) };
using BlockType = std::array<std::byte, blockSize>;
static constexpr std::size_t defaultBlockSize{ static_cast<std::size_t>(1 * 1024 * 1024) };
struct Block
{
std::unique_ptr<std::byte[]> data;
std::size_t size;
std::list<BlockType> _blocks;
BlockType* _currentBlock{};
bool fitsInBlock(std::byte* addr, std::size_t rangeSize) const
{
return data && rangeSize && addr >= data.get() && addr + rangeSize <= data.get() + size;
}
};
std::vector<Block> _blocks;
Block* _currentBlock{};
std::byte* _currentAddr{};
};
} // namespace lms::api::subsonic
+35 -14
View File
@@ -40,15 +40,31 @@ namespace lms::api::subsonic
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
Response::Node& playlistsNode{ response.createNode("playlists") };
TrackList::FindParameters params;
params.setUser(context.user->getId());
params.setType(TrackListType::Playlist);
auto tracklistIds{ TrackList::find(context.dbSession, params) };
for (const TrackListId trackListId : tracklistIds.results)
{
const TrackList::pointer trackList{ TrackList::find(context.dbSession, trackListId) };
auto addTrackList{ [&](const db::TrackList::pointer& trackList) {
playlistsNode.addArrayChild("playlist", createPlaylistNode(trackList, context.dbSession));
} };
// First add user's playlists
{
TrackList::FindParameters params;
params.setUser(context.user->getId());
params.setType(TrackListType::PlayList);
db::TrackList::find(context.dbSession, params, [&](const db::TrackList::pointer& trackList) {
addTrackList(trackList);
});
}
// Then add others public playlists
{
TrackList::FindParameters params;
params.setVisibility(TrackList::Visibility::Public);
params.setType(TrackListType::PlayList);
db::TrackList::find(context.dbSession, params, [&](const db::TrackList::pointer& trackList) {
if (trackList->getUserId() != context.user->getId()) // skip already reported
addTrackList(trackList);
});
}
return response;
@@ -96,17 +112,22 @@ namespace lms::api::subsonic
tracklist = TrackList::find(context.dbSession, *id);
if (!tracklist
|| tracklist->getUser() != context.user
|| tracklist->getType() != TrackListType::Playlist)
|| tracklist->getType() != TrackListType::PlayList)
{
throw RequestedDataNotFoundError{};
}
if (name)
tracklist.modify()->setName(*name);
tracklist.modify()->clear();
tracklist.modify()->setLastModifiedDateTime(Wt::WDateTime::currentDateTime());
}
else
{
tracklist = context.dbSession.create<TrackList>(*name, TrackListType::Playlist, false, context.user);
tracklist = context.dbSession.create<TrackList>(*name, TrackListType::PlayList);
tracklist.modify()->setUser(context.user);
tracklist.modify()->setVisibility(TrackList::Visibility::Private);
}
for (const TrackId trackId : trackIds)
@@ -147,7 +168,7 @@ namespace lms::api::subsonic
TrackList::pointer tracklist{ TrackList::find(context.dbSession, id) };
if (!tracklist
|| tracklist->getUser() != context.user
|| tracklist->getType() != TrackListType::Playlist)
|| tracklist->getType() != TrackListType::PlayList)
{
throw RequestedDataNotFoundError{};
}
@@ -155,8 +176,8 @@ namespace lms::api::subsonic
if (name)
tracklist.modify()->setName(*name);
if (isPublic)
tracklist.modify()->setIsPublic(*isPublic);
tracklist.modify()->setVisibility(isPublic ? db::TrackList::Visibility::Public : db::TrackList::Visibility::Private);
tracklist.modify()->setLastModifiedDateTime(Wt::WDateTime::currentDateTime());
{
// Remove from end to make indexes stable
@@ -192,7 +213,7 @@ namespace lms::api::subsonic
TrackList::pointer tracklist{ TrackList::find(context.dbSession, id) };
if (!tracklist
|| tracklist->getUser() != context.user
|| tracklist->getType() != TrackListType::Playlist)
|| tracklist->getType() != TrackListType::PlayList)
{
throw RequestedDataNotFoundError{};
}
@@ -19,6 +19,7 @@
#include "Playlist.hpp"
#include "core/String.hpp"
#include "database/Track.hpp"
#include "database/TrackList.hpp"
#include "database/User.hpp"
@@ -28,11 +29,7 @@
namespace lms::api::subsonic
{
using namespace db;
static const std::string_view reportedDummyDate{ "2000-01-01T00:00:00" };
Response::Node createPlaylistNode(const TrackList::pointer& tracklist, Session& session)
Response::Node createPlaylistNode(const db::TrackList::pointer& tracklist, db::Session& session)
{
Response::Node playlistNode;
@@ -40,15 +37,17 @@ namespace lms::api::subsonic
playlistNode.setAttribute("name", tracklist->getName());
playlistNode.setAttribute("songCount", tracklist->getCount());
playlistNode.setAttribute("duration", std::chrono::duration_cast<std::chrono::seconds>(tracklist->getDuration()).count());
playlistNode.setAttribute("public", tracklist->isPublic());
playlistNode.setAttribute("created", reportedDummyDate);
playlistNode.setAttribute("owner", tracklist->getUser()->getLoginName());
playlistNode.setAttribute("public", tracklist->getVisibility() == db::TrackList::Visibility::Public);
playlistNode.setAttribute("changed", core::stringUtils::toISO8601String(tracklist->getLastModifiedDateTime()));
playlistNode.setAttribute("created", core::stringUtils::toISO8601String(tracklist->getCreationDateTime()));
if (const db::User::pointer user{ tracklist->getUser() })
playlistNode.setAttribute("owner", user->getLoginName());
db::Track::FindParameters params;
params.setTrackList(tracklist->getId());
params.setHasEmbeddedImage(true);
params.setRange(db::Range{ 0, 1 });
params.setSortMethod(TrackSortMethod::TrackList);
params.setSortMethod(db::TrackSortMethod::TrackList);
db::Track::find(session, params, [&](const db::Track::pointer& track) {
const CoverArtId coverArtId{ track->getId(), track->getLastWriteTime().toTime_t() };