Subsonic API: do not use ffmpeg to remux the files when transcoding is off. fixes #36
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
add_library(lmsav SHARED
|
||||
impl/AvInfo.cpp
|
||||
impl/AvTranscoder.cpp
|
||||
impl/AvTranscodeResourceHandler.cpp
|
||||
impl/AvTypes.cpp
|
||||
)
|
||||
|
||||
@@ -18,6 +19,7 @@ target_link_libraries(lmsav PUBLIC
|
||||
lmsutils
|
||||
avformat
|
||||
avutil
|
||||
wt
|
||||
)
|
||||
|
||||
install(TARGETS lmsav DESTINATION lib)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 "AvTranscodeResourceHandler.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
std::unique_ptr<IResourceHandler>
|
||||
createTranscodeResourceHandler(const std::filesystem::path& trackPath, const TranscodeParameters& parameters)
|
||||
{
|
||||
return std::make_unique<TranscodeResourceHandler>(trackPath, parameters);
|
||||
}
|
||||
|
||||
// TODO set some nice HTTP return code
|
||||
|
||||
TranscodeResourceHandler::TranscodeResourceHandler(const std::filesystem::path& trackPath, const TranscodeParameters& parameters)
|
||||
: _transcoder {trackPath, parameters}
|
||||
{
|
||||
_transcoder.start();
|
||||
}
|
||||
|
||||
void
|
||||
TranscodeResourceHandler::processRequest(const Wt::Http::Request& /*request*/, Wt::Http::Response& response)
|
||||
{
|
||||
response.setMimeType(_transcoder.getOutputMimeType());
|
||||
|
||||
if (!_transcoder.isComplete())
|
||||
{
|
||||
std::vector<unsigned char> buffer;
|
||||
|
||||
_transcoder.process(buffer, _chunkSize);
|
||||
response.out().write(reinterpret_cast<const char *>(&buffer[0]), buffer.size());
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
TranscodeResourceHandler::isFinished() const
|
||||
{
|
||||
return _transcoder.isComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 <filesystem>
|
||||
#include "av/AvTranscoder.hpp"
|
||||
#include "utils/IResourceHandler.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
|
||||
class TranscodeResourceHandler final : public IResourceHandler
|
||||
{
|
||||
public:
|
||||
TranscodeResourceHandler(const std::filesystem::path& trackPath, const TranscodeParameters& parameters);
|
||||
|
||||
private:
|
||||
|
||||
void processRequest(const Wt::Http::Request& request, Wt::Http::Response& reponse) override;
|
||||
bool isFinished() const override;
|
||||
|
||||
static constexpr std::size_t _chunkSize {262144};
|
||||
const std::filesystem::path _trackPath;
|
||||
Transcoder _transcoder;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#include "utils/IResourceHandler.hpp"
|
||||
|
||||
namespace Av
|
||||
{
|
||||
struct TranscodeParameters;
|
||||
|
||||
std::unique_ptr<IResourceHandler> createTranscodeResourceHandler(const std::filesystem::path& trackPath, const TranscodeParameters& parameters);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
|
||||
add_library(lmssubsonic SHARED
|
||||
impl/ParameterParsing.cpp
|
||||
impl/Stream.cpp
|
||||
impl/SubsonicId.cpp
|
||||
impl/SubsonicResource.cpp
|
||||
impl/SubsonicResponse.cpp
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 "ParameterParsing.hpp"
|
||||
|
||||
namespace StringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<API::Subsonic::Id>
|
||||
StringUtils::readAs(const std::string& str)
|
||||
{
|
||||
return API::Subsonic::IdFromString(str);
|
||||
}
|
||||
|
||||
template<>
|
||||
std::optional<bool>
|
||||
StringUtils::readAs(const std::string& str)
|
||||
{
|
||||
if (str == "true")
|
||||
return true;
|
||||
else if (str == "false")
|
||||
return false;
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 <Wt/Http/Request.h>
|
||||
|
||||
#include "utils/String.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace API::Subsonic
|
||||
{
|
||||
|
||||
template<typename T>
|
||||
std::vector<T>
|
||||
getMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& paramName)
|
||||
{
|
||||
std::vector<T> res;
|
||||
|
||||
auto it = parameterMap.find(paramName);
|
||||
if (it == parameterMap.end())
|
||||
return res;
|
||||
|
||||
for (const std::string& param : it->second)
|
||||
{
|
||||
auto value {StringUtils::readAs<T>(param)};
|
||||
if (!value)
|
||||
throw BadParameterFormatGenericError {paramName};
|
||||
|
||||
res.emplace_back(std::move(*value));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T>
|
||||
getMandatoryMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
|
||||
{
|
||||
std::vector<T> res {getMultiParametersAs<T>(parameterMap, param)};
|
||||
if (res.empty())
|
||||
throw RequiredParameterMissingError {};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::optional<T>
|
||||
getParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
|
||||
{
|
||||
std::vector<T> params {getMultiParametersAs<T>(parameterMap, param)};
|
||||
|
||||
if (params.size() != 1)
|
||||
return {};
|
||||
|
||||
return T { std::move(params.front()) };
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
getMandatoryParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
|
||||
{
|
||||
auto res {getParameterAs<T>(parameterMap, param)};
|
||||
if (!res)
|
||||
throw RequiredParameterMissingError {};
|
||||
|
||||
return *res;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace StringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<API::Subsonic::Id>
|
||||
StringUtils::readAs(const std::string& str);
|
||||
|
||||
template<>
|
||||
std::optional<bool>
|
||||
StringUtils::readAs(const std::string& str);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 <Wt/Http/Request.h>
|
||||
#include <Wt/Http/ResponseContinuation.h>
|
||||
|
||||
|
||||
namespace Database
|
||||
{
|
||||
class Session;
|
||||
}
|
||||
|
||||
namespace API::Subsonic
|
||||
{
|
||||
|
||||
struct RequestContext
|
||||
{
|
||||
const Wt::Http::ParameterMap& parameters;
|
||||
Database::Session& dbSession;
|
||||
std::string userName;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 "Stream.hpp"
|
||||
|
||||
#include "av/AvTranscoder.hpp"
|
||||
#include "av/AvTranscodeResourceHandlerCreator.hpp"
|
||||
#include "av/AvTypes.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/IResourceHandler.hpp"
|
||||
#include "utils/FileResourceHandlerCreator.hpp"
|
||||
#include "utils/Utils.hpp"
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
|
||||
using namespace Database;
|
||||
|
||||
namespace API::Subsonic::Stream
|
||||
{
|
||||
|
||||
static
|
||||
Av::Encoding
|
||||
userTranscodeFormatToAvEncoding(AudioFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case AudioFormat::MP3: return Av::Encoding::MP3;
|
||||
case AudioFormat::OGG_OPUS: return Av::Encoding::OGG_OPUS;
|
||||
case AudioFormat::MATROSKA_OPUS: return Av::Encoding::MATROSKA_OPUS;
|
||||
case AudioFormat::OGG_VORBIS: return Av::Encoding::OGG_VORBIS;
|
||||
case AudioFormat::WEBM_VORBIS: return Av::Encoding::WEBM_VORBIS;
|
||||
default: return Av::Encoding::OGG_OPUS;
|
||||
}
|
||||
}
|
||||
|
||||
struct StreamParameters
|
||||
{
|
||||
std::filesystem::path trackPath;
|
||||
std::optional<Av::TranscodeParameters> transcodeParameters;
|
||||
};
|
||||
|
||||
|
||||
static
|
||||
StreamParameters
|
||||
getStreamParameters(RequestContext& context)
|
||||
{
|
||||
// Mandatory params
|
||||
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
|
||||
|
||||
// Optional params
|
||||
std::optional<std::size_t> maxBitRate {getParameterAs<std::size_t>(context.parameters, "maxBitRate")};
|
||||
std::optional<std::string> format {getParameterAs<std::string>(context.parameters, "format")};
|
||||
|
||||
StreamParameters parameters;
|
||||
|
||||
auto transaction {context.dbSession.createSharedTransaction()};
|
||||
|
||||
{
|
||||
auto track {Track::getById(context.dbSession, id.value)};
|
||||
if (!track)
|
||||
throw RequestedDataNotFoundError {};
|
||||
|
||||
parameters.trackPath = track->getPath();
|
||||
}
|
||||
|
||||
{
|
||||
const User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
|
||||
if (!user)
|
||||
throw UserNotAuthorizedError {};
|
||||
|
||||
// format = "raw" => no transcode. Other format values will be ignored
|
||||
const bool transcode {(!format || (*format != "raw")) && user->getAudioTranscodeEnable()};
|
||||
if (transcode)
|
||||
{
|
||||
// "If set to zero, no limit is imposed"
|
||||
if (!maxBitRate || *maxBitRate == 0)
|
||||
maxBitRate = user->getAudioTranscodeBitrate() / 1000;
|
||||
|
||||
*maxBitRate = clamp(*maxBitRate, std::size_t {48}, user->getMaxAudioTranscodeBitrate() / 1000);
|
||||
|
||||
Av::TranscodeParameters transcodeParameters;
|
||||
|
||||
transcodeParameters.bitrate = *maxBitRate * 1000;
|
||||
transcodeParameters.encoding = userTranscodeFormatToAvEncoding(user->getAudioTranscodeFormat());
|
||||
|
||||
parameters.transcodeParameters = std::move(transcodeParameters);
|
||||
}
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
void
|
||||
handle(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response)
|
||||
{
|
||||
std::shared_ptr<IResourceHandler> resourceHandler;
|
||||
|
||||
Wt::Http::ResponseContinuation *continuation = request.continuation();
|
||||
if (!continuation)
|
||||
{
|
||||
StreamParameters streamParameters {getStreamParameters(context)};
|
||||
if (streamParameters.transcodeParameters)
|
||||
resourceHandler = Av::createTranscodeResourceHandler(streamParameters.trackPath, *streamParameters.transcodeParameters);
|
||||
else
|
||||
resourceHandler = createFileResourceHandler(streamParameters.trackPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
resourceHandler = Wt::cpp17::any_cast<std::shared_ptr<IResourceHandler>>(continuation->data());
|
||||
}
|
||||
|
||||
resourceHandler->processRequest(request, response);
|
||||
if (!resourceHandler->isFinished())
|
||||
{
|
||||
Wt::Http::ResponseContinuation *continuation = response.createContinuation();
|
||||
continuation->setData(resourceHandler);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-12
@@ -19,22 +19,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <Wt/Http/Request.h>
|
||||
#include <Wt/Http/Response.h>
|
||||
|
||||
#include "RequestContext.hpp"
|
||||
|
||||
// Helper used to deliver file contents from a WResource
|
||||
namespace FileResourceHandler
|
||||
namespace API::Subsonic::Stream
|
||||
{
|
||||
struct ContinuationData
|
||||
{
|
||||
std::filesystem::path path;
|
||||
::uint64_t beyondLastByte;
|
||||
::uint64_t offset;
|
||||
};
|
||||
|
||||
std::optional<ContinuationData> handleInitialRequest(const Wt::Http::Request& request, Wt::Http::Response& response, const std::filesystem::path& path);
|
||||
std::optional<ContinuationData> handleContinuationRequest(const Wt::Http::Request& request, Wt::Http::Response& response, const ContinuationData& continuationData);
|
||||
void handle(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "database/Types.hpp"
|
||||
|
||||
@@ -19,15 +19,10 @@
|
||||
#include "subsonic/SubsonicResource.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
|
||||
#include <Wt/WLocalDateTime.h>
|
||||
|
||||
#include "auth/IPasswordService.hpp"
|
||||
#include "av/AvTranscoder.hpp"
|
||||
#include "cover/ICoverArtGrabber.hpp"
|
||||
#include "database/Artist.hpp"
|
||||
#include "database/Cluster.hpp"
|
||||
@@ -40,10 +35,13 @@
|
||||
#include "database/User.hpp"
|
||||
#include "recommendation/IEngine.hpp"
|
||||
#include "utils/Logger.hpp"
|
||||
#include "utils/Random.hpp"
|
||||
#include "utils/Service.hpp"
|
||||
#include "utils/String.hpp"
|
||||
#include "utils/Utils.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "RequestContext.hpp"
|
||||
#include "Stream.hpp"
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
using namespace Database;
|
||||
@@ -65,25 +63,6 @@ namespace API::Subsonic
|
||||
|
||||
namespace StringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<API::Subsonic::Id>
|
||||
StringUtils::readAs(const std::string& str)
|
||||
{
|
||||
return API::Subsonic::IdFromString(str);
|
||||
}
|
||||
|
||||
template<>
|
||||
std::optional<bool>
|
||||
StringUtils::readAs(const std::string& str)
|
||||
{
|
||||
if (str == "true")
|
||||
return true;
|
||||
else if (str == "false")
|
||||
return false;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
template<>
|
||||
std::optional<API::Subsonic::ClientVersion>
|
||||
StringUtils::readAs(const std::string& str)
|
||||
@@ -122,21 +101,6 @@ namespace StringUtils
|
||||
namespace API::Subsonic
|
||||
{
|
||||
|
||||
struct ClientInfo
|
||||
{
|
||||
std::string name;
|
||||
std::string user;
|
||||
std::string password;
|
||||
ClientVersion version;
|
||||
};
|
||||
|
||||
struct RequestContext
|
||||
{
|
||||
const Wt::Http::ParameterMap& parameters;
|
||||
Session& dbSession;
|
||||
std::string userName;
|
||||
};
|
||||
|
||||
static
|
||||
std::string
|
||||
makeNameFilesystemCompatible(const std::string& name)
|
||||
@@ -144,62 +108,6 @@ makeNameFilesystemCompatible(const std::string& name)
|
||||
return StringUtils::replaceInString(name, "/", "_");
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T>
|
||||
getMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& paramName)
|
||||
{
|
||||
std::vector<T> res;
|
||||
|
||||
auto it = parameterMap.find(paramName);
|
||||
if (it == parameterMap.end())
|
||||
return res;
|
||||
|
||||
for (const std::string& param : it->second)
|
||||
{
|
||||
auto value {StringUtils::readAs<T>(param)};
|
||||
if (!value)
|
||||
throw BadParameterFormatGenericError {paramName};
|
||||
|
||||
res.emplace_back(std::move(*value));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::vector<T>
|
||||
getMandatoryMultiParametersAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
|
||||
{
|
||||
std::vector<T> res {getMultiParametersAs<T>(parameterMap, param)};
|
||||
if (res.empty())
|
||||
throw RequiredParameterMissingError {};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::optional<T>
|
||||
getParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
|
||||
{
|
||||
std::vector<T> params {getMultiParametersAs<T>(parameterMap, param)};
|
||||
|
||||
if (params.size() != 1)
|
||||
return {};
|
||||
|
||||
return T { std::move(params.front()) };
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T
|
||||
getMandatoryParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
|
||||
{
|
||||
auto res {getParameterAs<T>(parameterMap, param)};
|
||||
if (!res)
|
||||
throw RequiredParameterMissingError {};
|
||||
|
||||
return *res;
|
||||
}
|
||||
|
||||
static
|
||||
std::string
|
||||
decodePasswordIfNeeded(const std::string& password)
|
||||
@@ -216,6 +124,14 @@ decodePasswordIfNeeded(const std::string& password)
|
||||
return password;
|
||||
}
|
||||
|
||||
struct ClientInfo
|
||||
{
|
||||
std::string name;
|
||||
std::string user;
|
||||
std::string password;
|
||||
ClientVersion version;
|
||||
};
|
||||
|
||||
static
|
||||
ClientInfo
|
||||
getClientInfo(const Wt::Http::ParameterMap& parameters)
|
||||
@@ -238,14 +154,6 @@ getClientInfo(const Wt::Http::ParameterMap& parameters)
|
||||
return res;
|
||||
}
|
||||
|
||||
// MediaRetrievals
|
||||
struct MediaRetrievalResult
|
||||
{
|
||||
std::string mimeType;
|
||||
std::vector<uint8_t> data;
|
||||
Wt::cpp17::any continuationData;
|
||||
};
|
||||
|
||||
SubsonicResource::SubsonicResource(Db& db)
|
||||
: _sessionPool {db}
|
||||
{
|
||||
@@ -1165,9 +1073,7 @@ handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3)
|
||||
std::make_move_iterator(std::end(similarArtistTracks)));
|
||||
}
|
||||
|
||||
auto now {std::chrono::system_clock::now()};
|
||||
std::mt19937 randGenerator {static_cast<std::mt19937::result_type>(std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count())};
|
||||
std::shuffle(std::begin(tracks), std::end(tracks), randGenerator);
|
||||
Random::shuffleContainer(tracks);
|
||||
|
||||
Response response {Response::createOkResponse()};
|
||||
Response::Node& similarSongsNode {response.createNode(id3 ? "similarSongs2" : "similarSongs")};
|
||||
@@ -1775,111 +1681,8 @@ handleNotImplemented(RequestContext&)
|
||||
}
|
||||
|
||||
static
|
||||
Av::Encoding
|
||||
userTranscodeFormatToAvEncoding(AudioFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case AudioFormat::MP3: return Av::Encoding::MP3;
|
||||
case AudioFormat::OGG_OPUS: return Av::Encoding::OGG_OPUS;
|
||||
case AudioFormat::MATROSKA_OPUS: return Av::Encoding::MATROSKA_OPUS;
|
||||
case AudioFormat::OGG_VORBIS: return Av::Encoding::OGG_VORBIS;
|
||||
case AudioFormat::WEBM_VORBIS: return Av::Encoding::WEBM_VORBIS;
|
||||
default: return Av::Encoding::OGG_OPUS;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
std::shared_ptr<Av::Transcoder>
|
||||
createTranscoder(RequestContext& context)
|
||||
{
|
||||
// Mandatory params
|
||||
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
|
||||
|
||||
// Optional params
|
||||
std::optional<std::size_t> maxBitRate {getParameterAs<std::size_t>(context.parameters, "maxBitRate")};
|
||||
std::optional<std::string> format {getParameterAs<std::string>(context.parameters, "format")};
|
||||
|
||||
Av::TranscodeParameters parameters {};
|
||||
parameters.stripMetadata = false; // Since it can be cached and some players read the metadata from the downloaded file
|
||||
|
||||
std::filesystem::path trackPath;
|
||||
{
|
||||
auto transaction {context.dbSession.createSharedTransaction()};
|
||||
|
||||
{
|
||||
auto track {Track::getById(context.dbSession, id.value)};
|
||||
if (!track)
|
||||
throw RequestedDataNotFoundError {};
|
||||
|
||||
trackPath = track->getPath();
|
||||
}
|
||||
|
||||
{
|
||||
const User::pointer user {User::getByLoginName(context.dbSession, context.userName)};
|
||||
if (!user)
|
||||
throw UserNotAuthorizedError {};
|
||||
|
||||
// format = "raw" => no transcode. Other format values will be ignored
|
||||
const bool transcode {(!format || (format && *format != "raw")) && user->getAudioTranscodeEnable()};
|
||||
if (transcode)
|
||||
{
|
||||
// "If set to zero, no limit is imposed"
|
||||
if (!maxBitRate || *maxBitRate == 0)
|
||||
maxBitRate = user->getAudioTranscodeBitrate() / 1000;
|
||||
|
||||
*maxBitRate = clamp(*maxBitRate, std::size_t {48}, user->getMaxAudioTranscodeBitrate() / 1000);
|
||||
|
||||
parameters.bitrate = *maxBitRate * 1000;
|
||||
parameters.encoding = userTranscodeFormatToAvEncoding(user->getAudioTranscodeFormat());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_shared<Av::Transcoder>(trackPath, parameters);
|
||||
}
|
||||
|
||||
static
|
||||
MediaRetrievalResult
|
||||
handleStream(RequestContext& context, Wt::Http::ResponseContinuation* continuation)
|
||||
{
|
||||
MediaRetrievalResult res;
|
||||
|
||||
std::shared_ptr<Av::Transcoder> transcoder;
|
||||
|
||||
if (!continuation)
|
||||
{
|
||||
transcoder = createTranscoder(context);
|
||||
transcoder->start();
|
||||
|
||||
res.mimeType = transcoder->getOutputMimeType();
|
||||
LMS_LOG(API_SUBSONIC, DEBUG) << "Mime type set to '" << transcoder->getOutputMimeType() << "'";
|
||||
}
|
||||
else
|
||||
{
|
||||
transcoder = Wt::cpp17::any_cast<std::shared_ptr<Av::Transcoder>>(continuation->data());
|
||||
}
|
||||
|
||||
if (!transcoder)
|
||||
throw InternalErrorGenericError {"Cannot create transcoder"};
|
||||
|
||||
if (!transcoder->isComplete())
|
||||
{
|
||||
static constexpr std::size_t chunkSize {65536*4};
|
||||
res.data.reserve(chunkSize);
|
||||
|
||||
transcoder->process(res.data, chunkSize);
|
||||
}
|
||||
|
||||
if (!transcoder->isComplete())
|
||||
res.continuationData = std::move(transcoder);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static
|
||||
MediaRetrievalResult
|
||||
handleGetCoverArt(RequestContext& context, Wt::Http::ResponseContinuation*)
|
||||
void
|
||||
handleGetCoverArt(RequestContext& context, const Wt::Http::Request& /*request*/, Wt::Http::Response& response)
|
||||
{
|
||||
// Mandatory params
|
||||
Id id {getMandatoryParameterAs<Id>(context.parameters, "id")};
|
||||
@@ -1887,23 +1690,21 @@ handleGetCoverArt(RequestContext& context, Wt::Http::ResponseContinuation*)
|
||||
std::size_t size {getParameterAs<std::size_t>(context.parameters, "size").value_or(256)};
|
||||
size = clamp(size, std::size_t {32}, std::size_t {1024});
|
||||
|
||||
MediaRetrievalResult res;
|
||||
|
||||
std::vector<unsigned char> data;
|
||||
switch (id.type)
|
||||
{
|
||||
case Id::Type::Track:
|
||||
res.data = ServiceProvider<CoverArt::IGrabber>::get()->getFromTrack(context.dbSession, id.value, CoverArt::Format::JPEG, size);
|
||||
data = ServiceProvider<CoverArt::IGrabber>::get()->getFromTrack(context.dbSession, id.value, CoverArt::Format::JPEG, size);
|
||||
break;
|
||||
case Id::Type::Release:
|
||||
res.data = ServiceProvider<CoverArt::IGrabber>::get()->getFromRelease(context.dbSession, id.value, CoverArt::Format::JPEG, size);
|
||||
data = ServiceProvider<CoverArt::IGrabber>::get()->getFromRelease(context.dbSession, id.value, CoverArt::Format::JPEG, size);
|
||||
break;
|
||||
default:
|
||||
throw BadParameterGenericError {"id"};
|
||||
}
|
||||
|
||||
res.mimeType = CoverArt::formatToMimeType(CoverArt::Format::JPEG);
|
||||
|
||||
return res;
|
||||
response.out().write(reinterpret_cast<const char*>(&data[0]), data.size());
|
||||
response.setMimeType(CoverArt::formatToMimeType(CoverArt::Format::JPEG));
|
||||
}
|
||||
|
||||
using RequestHandlerFunc = std::function<Response(RequestContext& context)>;
|
||||
@@ -2019,12 +1820,12 @@ static std::unordered_map<std::string, RequestEntryPointInfo> requestEntryPoints
|
||||
{"startScan", {handleNotImplemented, true}},
|
||||
};
|
||||
|
||||
using MediaRetrievalHandlerFunc = std::function<MediaRetrievalResult(RequestContext&, Wt::Http::ResponseContinuation*)>;
|
||||
using MediaRetrievalHandlerFunc = std::function<void(RequestContext&, const Wt::Http::Request&, Wt::Http::Response&)>;
|
||||
static std::unordered_map<std::string, MediaRetrievalHandlerFunc> mediaRetrievalHandlers
|
||||
{
|
||||
// Media retrieval
|
||||
{"getCoverArt", handleGetCoverArt},
|
||||
{"stream", handleStream},
|
||||
{"stream", Stream::handle},
|
||||
};
|
||||
|
||||
void
|
||||
@@ -2091,26 +1892,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
|
||||
auto itStreamHandler {mediaRetrievalHandlers.find(requestPath)};
|
||||
if (itStreamHandler != mediaRetrievalHandlers.end())
|
||||
{
|
||||
MediaRetrievalResult res {itStreamHandler->second(requestContext, request.continuation())};
|
||||
|
||||
if (!res.mimeType.empty())
|
||||
response.setMimeType(res.mimeType);
|
||||
if (!res.data.empty())
|
||||
{
|
||||
response.out().write(reinterpret_cast<const char *>(&res.data[0]), res.data.size());
|
||||
if (!response.out())
|
||||
{
|
||||
LMS_LOG(API_SUBSONIC, ERROR) << "Write failed!";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (res.continuationData.has_value())
|
||||
{
|
||||
auto continuation {response.createContinuation()};
|
||||
continuation->setData(std::move(res.continuationData));
|
||||
}
|
||||
|
||||
itStreamHandler->second(requestContext, request, response);
|
||||
LMS_LOG(API_SUBSONIC, DEBUG) << "Request " << requestId << " '" << requestPath << "' handled!";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
* 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 <map>
|
||||
#include <string>
|
||||
|
||||
@@ -17,25 +17,30 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "utils/FileResourceHandler.hpp"
|
||||
#include "FileResourceHandler.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "utils/Logger.hpp"
|
||||
|
||||
namespace FileResourceHandler
|
||||
std::unique_ptr<IResourceHandler>
|
||||
createFileResourceHandler(const std::filesystem::path& path)
|
||||
{
|
||||
return std::make_unique<FileResourceHandler>(path);
|
||||
}
|
||||
|
||||
static constexpr std::size_t _chunkSize {262144};
|
||||
|
||||
static
|
||||
std::optional<ContinuationData>
|
||||
handleRequestPiecewise(const Wt::Http::Request& request,
|
||||
Wt::Http::Response& response,
|
||||
ContinuationData continuationData)
|
||||
FileResourceHandler::FileResourceHandler(const std::filesystem::path& path)
|
||||
: _path {path}
|
||||
{
|
||||
::uint64_t startByte {continuationData.offset};
|
||||
std::ifstream ifs {continuationData.path.string().c_str(), std::ios::in | std::ios::binary};
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
FileResourceHandler::processRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
|
||||
{
|
||||
::uint64_t startByte {_offset};
|
||||
std::ifstream ifs {_path.string().c_str(), std::ios::in | std::ios::binary};
|
||||
|
||||
LMS_LOG(UTILS, DEBUG) << "startByte = " << startByte;
|
||||
|
||||
@@ -43,9 +48,10 @@ handleRequestPiecewise(const Wt::Http::Request& request,
|
||||
{
|
||||
if (!ifs)
|
||||
{
|
||||
LMS_LOG(UTILS, ERROR) << "Cannot open file stream for '" << continuationData.path.string() << "'";
|
||||
LMS_LOG(UTILS, ERROR) << "Cannot open file stream for '" << _path.string() << "'";
|
||||
response.setStatus(404);
|
||||
return std::nullopt;
|
||||
_isFinished = true;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -67,7 +73,8 @@ handleRequestPiecewise(const Wt::Http::Request& request,
|
||||
response.addHeader("Content-Range", contentRange.str());
|
||||
|
||||
LMS_LOG(UTILS, DEBUG) << "Range not satisfiable";
|
||||
return std::nullopt;
|
||||
_isFinished = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ranges.size() == 1)
|
||||
@@ -76,21 +83,21 @@ handleRequestPiecewise(const Wt::Http::Request& request,
|
||||
|
||||
response.setStatus(206);
|
||||
startByte = ranges[0].firstByte();
|
||||
continuationData.beyondLastByte = ranges[0].lastByte() + 1;
|
||||
_beyondLastByte = ranges[0].lastByte() + 1;
|
||||
|
||||
std::ostringstream contentRange;
|
||||
contentRange << "bytes " << startByte << "-"
|
||||
<< continuationData.beyondLastByte - 1 << "/" << fileSize;
|
||||
<< _beyondLastByte - 1 << "/" << fileSize;
|
||||
|
||||
response.addHeader("Content-Range", contentRange.str());
|
||||
response.setContentLength(continuationData.beyondLastByte - startByte);
|
||||
response.setContentLength(_beyondLastByte - startByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(UTILS, DEBUG) << "No range requested";
|
||||
|
||||
continuationData.beyondLastByte = fileSize;
|
||||
response.setContentLength(continuationData.beyondLastByte);
|
||||
_beyondLastByte = fileSize;
|
||||
response.setContentLength(_beyondLastByte);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +106,7 @@ handleRequestPiecewise(const Wt::Http::Request& request,
|
||||
std::vector<char> buf;
|
||||
buf.resize(_chunkSize);
|
||||
|
||||
::uint64_t restSize = continuationData.beyondLastByte - startByte;
|
||||
::uint64_t restSize = _beyondLastByte - startByte;
|
||||
::uint64_t pieceSize = buf.size() > restSize ? restSize : buf.size();
|
||||
|
||||
ifs.read(&buf[0], pieceSize);
|
||||
@@ -111,47 +118,21 @@ handleRequestPiecewise(const Wt::Http::Request& request,
|
||||
LMS_LOG(UTILS, DEBUG) << "Progress: " << actualPieceSize << "/" << restSize;
|
||||
if (ifs.good() && actualPieceSize < restSize)
|
||||
{
|
||||
ContinuationData newContinuationData {continuationData};
|
||||
newContinuationData.offset = startByte + actualPieceSize;
|
||||
_offset = startByte + actualPieceSize;
|
||||
|
||||
LMS_LOG(UTILS, DEBUG) << "Job not complete! Next chunk offset = " << newContinuationData.offset;
|
||||
|
||||
return newContinuationData;
|
||||
LMS_LOG(UTILS, DEBUG) << "Job not complete! Next chunk offset = " << _offset;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isFinished = true;
|
||||
LMS_LOG(UTILS, DEBUG) << "Job complete!";
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
|
||||
std::optional<ContinuationData>
|
||||
handleInitialRequest(const Wt::Http::Request& request,
|
||||
Wt::Http::Response& response,
|
||||
const std::filesystem::path& path)
|
||||
bool
|
||||
FileResourceHandler::isFinished() const
|
||||
{
|
||||
ContinuationData continuationData;
|
||||
continuationData.path = path;
|
||||
continuationData.offset = 0;
|
||||
continuationData.beyondLastByte = 0;
|
||||
|
||||
LMS_LOG(UTILS, DEBUG) << "Initial request for file '" << path << "'";
|
||||
|
||||
return handleRequestPiecewise(request, response, continuationData);
|
||||
return _isFinished;
|
||||
}
|
||||
|
||||
std::optional<ContinuationData>
|
||||
handleContinuationRequest(const Wt::Http::Request& request,
|
||||
Wt::Http::Response& response,
|
||||
const ContinuationData& continuationData)
|
||||
{
|
||||
LMS_LOG(UTILS, DEBUG) << "Continuation request for file '" << continuationData.path << "', offset = " << continuationData.offset;
|
||||
return handleRequestPiecewise(request, response, continuationData);
|
||||
}
|
||||
|
||||
} // ns FileResourceHandler
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 <filesystem>
|
||||
#include "utils/IResourceHandler.hpp"
|
||||
|
||||
class FileResourceHandler final : public IResourceHandler
|
||||
{
|
||||
public:
|
||||
FileResourceHandler(const std::filesystem::path& filePath);
|
||||
|
||||
private:
|
||||
|
||||
void processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override;
|
||||
bool isFinished() const override;
|
||||
|
||||
static constexpr std::size_t _chunkSize {262144};
|
||||
|
||||
std::filesystem::path _path;
|
||||
::uint64_t _beyondLastByte {};
|
||||
::uint64_t _offset {};
|
||||
bool _isFinished {};
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#include "utils/IResourceHandler.hpp"
|
||||
|
||||
std::unique_ptr<IResourceHandler> createFileResourceHandler(const std::filesystem::path& path);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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 <Wt/Http/Request.h>
|
||||
#include <Wt/Http/Response.h>
|
||||
|
||||
// Helper class to serve a resource (must be saved as continuation data if not complete)
|
||||
class IResourceHandler
|
||||
{
|
||||
public:
|
||||
virtual ~IResourceHandler() = default;
|
||||
|
||||
virtual void processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) = 0;
|
||||
virtual bool isFinished() const = 0;
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
#include "av/AvInfo.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "utils/FileResourceHandler.hpp"
|
||||
#include "utils/FileResourceHandlerCreator.hpp"
|
||||
#include "utils/Logger.hpp"
|
||||
#include "utils/String.hpp"
|
||||
#include "LmsApplication.hpp"
|
||||
@@ -88,29 +88,31 @@ void
|
||||
AudioFileResource::handleRequest(const Wt::Http::Request& request,
|
||||
Wt::Http::Response& response)
|
||||
{
|
||||
std::optional<FileResourceHandler::ContinuationData> continuationData;
|
||||
std::shared_ptr<IResourceHandler> fileResourceHandler;
|
||||
|
||||
if (!request.continuation())
|
||||
{
|
||||
auto trackPath {getTrackPathFromURLArgs(request)};
|
||||
if (!trackPath)
|
||||
return;
|
||||
|
||||
continuationData = FileResourceHandler::handleInitialRequest(request, response, *trackPath);
|
||||
fileResourceHandler = createFileResourceHandler(*trackPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto currentContinuationData {Wt::cpp17::any_cast<FileResourceHandler::ContinuationData>(request.continuation()->data())};
|
||||
continuationData = FileResourceHandler::handleContinuationRequest(request, response, currentContinuationData);
|
||||
fileResourceHandler = Wt::cpp17::any_cast<std::shared_ptr<IResourceHandler>>(request.continuation()->data());
|
||||
}
|
||||
|
||||
if (continuationData)
|
||||
fileResourceHandler->processRequest(request, response);
|
||||
|
||||
if (!fileResourceHandler->isFinished())
|
||||
{
|
||||
auto* continuation {response.createContinuation()};
|
||||
continuation->setData(*continuationData);
|
||||
continuation->setData(fileResourceHandler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user