Added playlist import (and sync)from m3u files, ref #391
This commit is contained in:
@@ -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
|
||||
@@ -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() };
|
||||
|
||||
Reference in New Issue
Block a user