Reworked internal namespaces
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
add_subdirectory(av)
|
||||
add_subdirectory(core)
|
||||
add_subdirectory(database)
|
||||
add_subdirectory(image)
|
||||
add_subdirectory(metadata)
|
||||
add_subdirectory(services)
|
||||
add_subdirectory(som)
|
||||
add_subdirectory(subsonic)
|
||||
add_subdirectory(utils)
|
||||
|
||||
@@ -18,7 +18,7 @@ target_include_directories(lmsav PRIVATE
|
||||
)
|
||||
|
||||
target_link_libraries(lmsav PUBLIC
|
||||
lmsutils
|
||||
lmscore
|
||||
std::filesystem
|
||||
)
|
||||
|
||||
|
||||
@@ -31,10 +31,10 @@ extern "C"
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/String.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
namespace Av
|
||||
namespace lms::av
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -48,11 +48,11 @@ namespace Av
|
||||
return "Unknown error";
|
||||
}
|
||||
|
||||
class AudioFileException : public Av::Exception
|
||||
class AudioFileException : public Exception
|
||||
{
|
||||
public:
|
||||
AudioFileException(int avError)
|
||||
: Av::Exception{ "AudioFileException: " + averror_to_string(avError) }
|
||||
: Exception{ "AudioFileException: " + averror_to_string(avError) }
|
||||
{}
|
||||
};
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Av
|
||||
AVDictionaryEntry* tag = NULL;
|
||||
while ((tag = ::av_dict_get(dictionnary, "", tag, AV_DICT_IGNORE_SUFFIX)))
|
||||
{
|
||||
res[StringUtils::stringToUpper(tag->key)] = tag->value;
|
||||
res[core::stringUtils::stringToUpper(tag->key)] = tag->value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,10 +320,10 @@ namespace Av
|
||||
{".mka", "audio/x-matroska"},
|
||||
};
|
||||
|
||||
auto it{ entries.find(StringUtils::stringToLower(fileExtension.string())) };
|
||||
auto it{ entries.find(core::stringUtils::stringToLower(fileExtension.string())) };
|
||||
if (it == std::cend(entries))
|
||||
return "";
|
||||
|
||||
return it->second;
|
||||
}
|
||||
} // namespace Av
|
||||
} // namespace lms::av
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
struct AVFormatContext;
|
||||
|
||||
namespace Av
|
||||
namespace lms::av
|
||||
{
|
||||
|
||||
class AudioFile final : public IAudioFile
|
||||
@@ -53,5 +53,5 @@ namespace Av
|
||||
AVFormatContext* _context{};
|
||||
};
|
||||
|
||||
} // namespace Av
|
||||
} // namespace lms::av
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
#include "av/RawResourceHandlerCreator.hpp"
|
||||
|
||||
#include "av/IAudioFile.hpp"
|
||||
#include "utils/FileResourceHandlerCreator.hpp"
|
||||
#include "core/FileResourceHandlerCreator.hpp"
|
||||
|
||||
namespace Av
|
||||
namespace lms::av
|
||||
{
|
||||
std::unique_ptr<IResourceHandler> createRawResourceHandler(const std::filesystem::path& path)
|
||||
{
|
||||
std::string_view mimeType{ Av::getMimeType(path.extension()) };
|
||||
std::string_view mimeType{ getMimeType(path.extension()) };
|
||||
return createFileResourceHandler(path, mimeType.empty() ? "application/octet-stream" : mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
#include <atomic>
|
||||
#include <iomanip>
|
||||
|
||||
#include "utils/IChildProcessManager.hpp"
|
||||
#include "utils/IConfig.hpp"
|
||||
#include "utils/Path.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Service.hpp"
|
||||
#include "core/IChildProcessManager.hpp"
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/Path.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/Service.hpp"
|
||||
|
||||
namespace Av::Transcoding
|
||||
namespace lms::av::transcoding
|
||||
{
|
||||
|
||||
#define LOG(severity, message) LMS_LOG(TRANSCODING, severity, "[" << _debugId << "] - " << message)
|
||||
@@ -52,7 +52,7 @@ namespace Av::Transcoding
|
||||
|
||||
void Transcoder::init()
|
||||
{
|
||||
ffmpegPath = Service<IConfig>::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg");
|
||||
ffmpegPath = core::Service<core::IConfig>::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg");
|
||||
if (!std::filesystem::exists(ffmpegPath))
|
||||
throw Exception{ "File '" + ffmpegPath.string() + "' does not exist!" };
|
||||
}
|
||||
@@ -183,9 +183,9 @@ namespace Av::Transcoding
|
||||
// Caution: stdin must have been closed before
|
||||
try
|
||||
{
|
||||
_childProcess = Service<IChildProcessManager>::get()->spawnChildProcess(ffmpegPath, args);
|
||||
_childProcess = core::Service<core::IChildProcessManager>::get()->spawnChildProcess(ffmpegPath, args);
|
||||
}
|
||||
catch (ChildProcessException& exception)
|
||||
catch (core::ChildProcessException& exception)
|
||||
{
|
||||
throw Exception{ "Cannot execute '" + ffmpegPath.string() + "': " + exception.what() };
|
||||
}
|
||||
@@ -195,7 +195,7 @@ namespace Av::Transcoding
|
||||
{
|
||||
assert(_childProcess);
|
||||
|
||||
return _childProcess->asyncRead(buffer, bufferSize, [readCallback{ std::move(readCallback) }](IChildProcess::ReadResult /*res*/, std::size_t nbBytesRead)
|
||||
return _childProcess->asyncRead(buffer, bufferSize, [readCallback{ std::move(readCallback) }](core::IChildProcess::ReadResult /*res*/, std::size_t nbBytesRead)
|
||||
{
|
||||
readCallback(nbBytesRead);
|
||||
});
|
||||
@@ -215,4 +215,4 @@ namespace Av::Transcoding
|
||||
return _childProcess->finished();
|
||||
}
|
||||
|
||||
} // namespace Av::Transcoding
|
||||
} // namespace lms::av::Transcoding
|
||||
|
||||
@@ -25,9 +25,12 @@
|
||||
#include "av/TranscodingParameters.hpp"
|
||||
#include "av/Types.hpp"
|
||||
|
||||
class IChildProcess;
|
||||
namespace lms::core
|
||||
{
|
||||
class IChildProcess;
|
||||
}
|
||||
|
||||
namespace Av::Transcoding
|
||||
namespace lms::av::transcoding
|
||||
{
|
||||
class Transcoder
|
||||
{
|
||||
@@ -60,7 +63,6 @@ namespace Av::Transcoding
|
||||
const OutputParameters _outputParameters;
|
||||
std::string _outputMimeType;
|
||||
|
||||
std::unique_ptr<IChildProcess> _childProcess;
|
||||
std::unique_ptr<core::IChildProcess> _childProcess;
|
||||
};
|
||||
|
||||
} // namespace Av::Transcoding
|
||||
}
|
||||
@@ -18,9 +18,9 @@
|
||||
*/
|
||||
|
||||
#include "TranscodingResourceHandler.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace Av::Transcoding
|
||||
namespace lms::av::transcoding
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
#include <optional>
|
||||
|
||||
#include "av/TranscodingParameters.hpp"
|
||||
#include "utils/IResourceHandler.hpp"
|
||||
#include "core/IResourceHandler.hpp"
|
||||
#include "Transcoder.hpp"
|
||||
|
||||
namespace Av::Transcoding
|
||||
namespace lms::av::transcoding
|
||||
{
|
||||
class TranscodingResourceHandler final : public IResourceHandler
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
#include "Types.hpp"
|
||||
|
||||
namespace Av
|
||||
namespace lms::av
|
||||
{
|
||||
// List should be sync with the codecs shipped in the lms's docker version
|
||||
enum class DecodingCodec
|
||||
@@ -107,5 +107,5 @@ namespace Av
|
||||
|
||||
std::string_view getMimeType(const std::filesystem::path& fileExtension);
|
||||
|
||||
} // namespace Av
|
||||
} // namespace lms::av
|
||||
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#include "utils/IResourceHandler.hpp"
|
||||
#include "core/IResourceHandler.hpp"
|
||||
|
||||
namespace Av
|
||||
namespace lms::av
|
||||
{
|
||||
std::unique_ptr<IResourceHandler> createRawResourceHandler(const std::filesystem::path& path);
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
#include "Types.hpp"
|
||||
|
||||
namespace Av::Transcoding
|
||||
namespace lms::av::transcoding
|
||||
{
|
||||
struct InputParameters
|
||||
{
|
||||
@@ -52,5 +52,5 @@ namespace Av::Transcoding
|
||||
std::chrono::milliseconds offset{ 0 };
|
||||
bool stripMetadata{ true };
|
||||
};
|
||||
} // namespace Av::Transcoding
|
||||
} // namespace lms::av::Transcoding
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "utils/IResourceHandler.hpp"
|
||||
#include "core/IResourceHandler.hpp"
|
||||
|
||||
namespace Av::Transcoding
|
||||
namespace lms::av::transcoding
|
||||
{
|
||||
struct InputParameters;
|
||||
struct OutputParameters;
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "utils/Exception.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
|
||||
namespace Av
|
||||
namespace lms::av
|
||||
{
|
||||
class Exception : public LmsException
|
||||
class Exception : public core::LmsException
|
||||
{
|
||||
public:
|
||||
using LmsException::LmsException;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
add_library(lmsutils SHARED
|
||||
add_library(lmscore SHARED
|
||||
impl/http/Client.cpp
|
||||
impl/http/SendQueue.cpp
|
||||
impl/ArchiveZipper.cpp
|
||||
@@ -19,26 +19,26 @@ add_library(lmsutils SHARED
|
||||
impl/WtLogger.cpp
|
||||
)
|
||||
|
||||
target_include_directories(lmsutils INTERFACE
|
||||
target_include_directories(lmscore INTERFACE
|
||||
include
|
||||
)
|
||||
|
||||
target_include_directories(lmsutils PRIVATE
|
||||
target_include_directories(lmscore PRIVATE
|
||||
include
|
||||
)
|
||||
|
||||
target_link_libraries(lmsutils PRIVATE
|
||||
target_link_libraries(lmscore PRIVATE
|
||||
PkgConfig::Config++
|
||||
PkgConfig::Archive
|
||||
)
|
||||
|
||||
target_link_libraries(lmsutils PUBLIC
|
||||
target_link_libraries(lmscore PUBLIC
|
||||
Boost::system
|
||||
std::filesystem
|
||||
Wt::Wt
|
||||
)
|
||||
|
||||
install(TARGETS lmsutils DESTINATION ${CMAKE_INSTALL_LIBDIR})
|
||||
install(TARGETS lmscore DESTINATION lib ${CMAKE_INSTALL_LIBDIR})
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(test)
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
add_executable(bench-core
|
||||
TraceLoggerBench.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(bench-core PRIVATE
|
||||
lmscore
|
||||
benchmark
|
||||
)
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 <iostream>
|
||||
#include <thread>
|
||||
#include <benchmark/benchmark.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
#include "core/StreamLogger.hpp"
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
// The trace logger is meant to built/destroyed once
|
||||
Service<logging::ILogger> logger{ std::make_unique<logging::StreamLogger>(std::cout, logging::StreamLogger::allSeverities) };
|
||||
Service<tracing::ITraceLogger> traceLogger{ tracing::createTraceLogger(tracing::Level::Overview) };
|
||||
|
||||
static void BM_TraceLogger_Overview(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
LMS_SCOPED_TRACE_OVERVIEW("Cat", "Test");
|
||||
}
|
||||
}
|
||||
|
||||
static void BM_TraceLogger_Detailed(benchmark::State& state)
|
||||
{
|
||||
for (auto _ : state)
|
||||
{
|
||||
// Should do nothing
|
||||
LMS_SCOPED_TRACE_DETAILED("Cat", "Test");
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARK(BM_TraceLogger_Overview)->Threads(1)->Threads(std::thread::hardware_concurrency());
|
||||
BENCHMARK(BM_TraceLogger_Detailed)->Threads(1)->Threads(std::thread::hardware_concurrency());
|
||||
}
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* 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 "ArchiveZipper.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring> // strerror
|
||||
#include <fstream>
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace lms::zip
|
||||
{
|
||||
std::unique_ptr<IZipper> createArchiveZipper(const EntryContainer& entries)
|
||||
{
|
||||
return std::make_unique<ArchiveZipper>(entries);
|
||||
}
|
||||
|
||||
class FileException : public Exception
|
||||
{
|
||||
public:
|
||||
FileException(const std::filesystem::path& p, std::string_view message)
|
||||
: Exception{ "File '" + p.string() + "': " + std::string {message} }
|
||||
{}
|
||||
|
||||
FileException(const std::filesystem::path& p, std::string_view message, int err)
|
||||
: Exception{ "File '" + p.string() + "': " + std::string {message} + ": " + ::strerror(err) }
|
||||
{}
|
||||
};
|
||||
|
||||
class ArchiveException : public Exception
|
||||
{
|
||||
public:
|
||||
ArchiveException(struct ::archive* arch)
|
||||
: Exception{ getError(arch) }
|
||||
{}
|
||||
|
||||
static std::string_view getError(struct ::archive* arch)
|
||||
{
|
||||
const char* str{ archive_error_string(arch) };
|
||||
if (!str)
|
||||
{
|
||||
static std::string unknownError{ "Unknown archive error" };
|
||||
return unknownError;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
};
|
||||
|
||||
void ArchiveZipper::ArchiveDeleter::operator()(struct ::archive* arch)
|
||||
{
|
||||
const int res{ ::archive_write_free(arch) };
|
||||
if (res != ARCHIVE_OK)
|
||||
LMS_LOG(UTILS, ERROR, "Failure while freeing archive control struct: " << std::string{ ::strerror(res) });
|
||||
}
|
||||
|
||||
void ArchiveZipper::ArchiveEntryDeleter::operator()(struct ::archive_entry* archEntry)
|
||||
{
|
||||
::archive_entry_free(archEntry);
|
||||
}
|
||||
|
||||
ArchiveZipper::ArchiveZipper(const EntryContainer& entries)
|
||||
: _entries{ entries }
|
||||
, _readBuffer(_readBufferSize, {})
|
||||
, _currentEntry{ std::cbegin(_entries) }
|
||||
{
|
||||
_archive = ArchivePtr{ ::archive_write_new() };
|
||||
if (!_archive)
|
||||
throw Exception{ "Cannot create archive control struct" };
|
||||
|
||||
auto archiveOpen{ [](struct ::archive*, void*)
|
||||
{
|
||||
return ARCHIVE_OK;
|
||||
} };
|
||||
|
||||
auto archiveWrite{ [](struct ::archive*, void* clientData, const void* buff, ::size_t n) -> la_ssize_t
|
||||
{
|
||||
ArchiveZipper* zipper {static_cast<ArchiveZipper*>(clientData)};
|
||||
return zipper->onWriteCallback(static_cast<const std::byte*>(buff), n);
|
||||
} };
|
||||
|
||||
auto archiveClose{ [](struct ::archive*, void*)
|
||||
{
|
||||
return ARCHIVE_OK;
|
||||
} };
|
||||
|
||||
if (::archive_write_set_bytes_per_block(_archive.get(), _writeBlockSize) != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
// 1 => no padding for last block
|
||||
if (::archive_write_set_bytes_in_last_block(_archive.get(), 1) != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
if (::archive_write_set_format_zip(_archive.get()) != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
if (::archive_write_set_option(_archive.get(), "zip", "compression", "deflate") != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
int res{ ::archive_write_open(_archive.get(), this, archiveOpen, archiveWrite, archiveClose) };
|
||||
if (res != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
}
|
||||
|
||||
std::uint64_t ArchiveZipper::writeSome(std::ostream& output)
|
||||
{
|
||||
assert(!_currentOutputStream);
|
||||
|
||||
_currentOutputStream = &output;
|
||||
_bytesWrittenInCurrentOutputStream = 0;
|
||||
|
||||
while (_bytesWrittenInCurrentOutputStream == 0)
|
||||
{
|
||||
if (!_currentArchiveEntry)
|
||||
{
|
||||
if (_currentEntry == std::cend(_entries))
|
||||
{
|
||||
if (::archive_write_close(_archive.get()) != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
_archive.reset();
|
||||
break;
|
||||
}
|
||||
|
||||
_currentArchiveEntry = createArchiveEntry(*_currentEntry);
|
||||
_currentEntryOffset = 0;
|
||||
if (::archive_write_header(_archive.get(), _currentArchiveEntry.get()) != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
}
|
||||
|
||||
if (writeSomeCurrentFileData())
|
||||
{
|
||||
// entry complete
|
||||
if (::archive_write_finish_entry(_archive.get()) != ARCHIVE_OK)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
_currentArchiveEntry.reset();
|
||||
_currentEntry++;
|
||||
}
|
||||
}
|
||||
|
||||
_currentOutputStream = nullptr;
|
||||
return _bytesWrittenInCurrentOutputStream;
|
||||
}
|
||||
|
||||
bool ArchiveZipper::isComplete() const
|
||||
{
|
||||
return !_archive;
|
||||
}
|
||||
|
||||
void ArchiveZipper::abort()
|
||||
{
|
||||
LMS_LOG(UTILS, DEBUG, "Aborting zip creation");
|
||||
if (_archive)
|
||||
{
|
||||
::archive_write_fail(_archive.get());
|
||||
_archive.reset();
|
||||
}
|
||||
}
|
||||
|
||||
static ::mode_t permsToMode(const std::filesystem::perms p)
|
||||
{
|
||||
using std::filesystem::perms;
|
||||
::mode_t mode{};
|
||||
|
||||
auto testPerm{ [](perms p, perms permToTest)
|
||||
{
|
||||
return (p & permToTest) == permToTest;
|
||||
} };
|
||||
|
||||
if (testPerm(p, perms::owner_read))
|
||||
mode |= S_IRUSR;
|
||||
if (testPerm(p, perms::owner_write))
|
||||
mode |= S_IWUSR;
|
||||
if (testPerm(p, perms::owner_exec))
|
||||
mode |= S_IXUSR;
|
||||
if (testPerm(p, perms::group_read))
|
||||
mode |= S_IRGRP;
|
||||
if (testPerm(p, perms::group_write))
|
||||
mode |= S_IWGRP;
|
||||
if (testPerm(p, perms::group_exec))
|
||||
mode |= S_IXGRP;
|
||||
if (testPerm(p, perms::others_read))
|
||||
mode |= S_IROTH;
|
||||
if (testPerm(p, perms::others_write))
|
||||
mode |= S_IWOTH;
|
||||
if (testPerm(p, perms::others_exec))
|
||||
mode |= S_IXOTH;
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
ArchiveZipper::ArchiveEntryPtr ArchiveZipper::createArchiveEntry(const Entry& entry)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!std::filesystem::is_regular_file(entry.filePath))
|
||||
throw FileException{ entry.filePath, "not a regular file" };
|
||||
|
||||
ArchiveEntryPtr archiveEntry{ archive_entry_new() };
|
||||
if (!archiveEntry)
|
||||
throw Exception{ "Cannot create archive entry control struct" };
|
||||
|
||||
archive_entry_set_pathname(archiveEntry.get(), entry.fileName.c_str());
|
||||
archive_entry_set_size(archiveEntry.get(), std::filesystem::file_size(entry.filePath));
|
||||
archive_entry_set_mode(archiveEntry.get(), permsToMode(std::filesystem::status(entry.filePath).permissions()));
|
||||
archive_entry_set_filetype(archiveEntry.get(), AE_IFREG);
|
||||
|
||||
return archiveEntry;
|
||||
}
|
||||
catch (const std::filesystem::filesystem_error& error)
|
||||
{
|
||||
throw FileException{ entry.filePath, error.what() };
|
||||
}
|
||||
}
|
||||
|
||||
bool ArchiveZipper::writeSomeCurrentFileData()
|
||||
{
|
||||
assert(_currentEntry != std::cend(_entries));
|
||||
|
||||
std::ifstream ifs{ _currentEntry->filePath.c_str(), std::ios_base::binary };
|
||||
if (!ifs)
|
||||
throw FileException{ _currentEntry->filePath, "cannot open file", errno };
|
||||
|
||||
ifs.seekg(0, std::ios::end);
|
||||
const std::uint64_t fileSize{ static_cast<std::uint64_t>(ifs.tellg()) };
|
||||
ifs.seekg(0, std::ios::beg);
|
||||
|
||||
// TODO: store file size?
|
||||
if (fileSize < _currentEntryOffset)
|
||||
throw FileException{ _currentEntry->filePath, "size changed?" };
|
||||
|
||||
const std::uint64_t bytesToRead{ std::min(fileSize - _currentEntryOffset, static_cast<std::uint64_t>(_readBufferSize)) };
|
||||
|
||||
// read from file
|
||||
if (!ifs.seekg(_currentEntryOffset, std::ios::beg))
|
||||
throw FileException{ _currentEntry->filePath, "seek failed", errno };
|
||||
|
||||
if (!ifs.read(reinterpret_cast<char*>(&_readBuffer[0]), bytesToRead))
|
||||
throw FileException{ _currentEntry->filePath, "read failed", errno };
|
||||
|
||||
const std::uint64_t actualBytesRead{ static_cast<std::uint64_t>(ifs.gcount()) };
|
||||
|
||||
// write to archive
|
||||
{
|
||||
std::uint64_t remainingBytesToWrite{ actualBytesRead };
|
||||
while (remainingBytesToWrite > 0)
|
||||
{
|
||||
const auto writtenBytes{ archive_write_data(_archive.get(), &_readBuffer[actualBytesRead - remainingBytesToWrite], remainingBytesToWrite) };
|
||||
if (writtenBytes < 0)
|
||||
throw ArchiveException{ _archive.get() };
|
||||
|
||||
assert(static_cast<std::uint64_t>(writtenBytes) <= remainingBytesToWrite);
|
||||
remainingBytesToWrite -= writtenBytes;
|
||||
}
|
||||
}
|
||||
|
||||
_currentEntryOffset += actualBytesRead;
|
||||
return (_currentEntryOffset >= fileSize);
|
||||
}
|
||||
|
||||
std::int64_t ArchiveZipper::onWriteCallback(const std::byte* buffer, std::size_t bufferSize)
|
||||
{
|
||||
if (!_currentOutputStream)
|
||||
{
|
||||
archive_set_error(_archive.get(), EIO, "IO error: operation cancelled");
|
||||
return -1;
|
||||
}
|
||||
|
||||
_currentOutputStream->write(reinterpret_cast<const char*>(buffer), bufferSize);
|
||||
if (!*_currentOutputStream)
|
||||
throw Exception{ "Failed to write " + std::to_string(bufferSize) + " bytes in final archive output!" };
|
||||
|
||||
_bytesWrittenInCurrentOutputStream += bufferSize;
|
||||
|
||||
return bufferSize;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 <cstddef>
|
||||
#include <memory>
|
||||
#include "core/IZipper.hpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
struct archive;
|
||||
struct archive_entry;
|
||||
}
|
||||
|
||||
namespace lms::zip
|
||||
{
|
||||
class ArchiveZipper : public IZipper
|
||||
{
|
||||
public:
|
||||
ArchiveZipper(const EntryContainer& files);
|
||||
ArchiveZipper(const ArchiveZipper&) = delete;
|
||||
ArchiveZipper& operator=(const ArchiveZipper&) = delete;
|
||||
|
||||
private:
|
||||
std::uint64_t writeSome(std::ostream& output) override;
|
||||
bool isComplete() const override;
|
||||
void abort() override;
|
||||
|
||||
class ArchiveDeleter
|
||||
{
|
||||
public:
|
||||
void operator()(struct ::archive* arch);
|
||||
};
|
||||
using ArchivePtr = std::unique_ptr<struct ::archive, ArchiveDeleter>;
|
||||
|
||||
class ArchiveEntryDeleter
|
||||
{
|
||||
public:
|
||||
void operator()(struct ::archive_entry* archEntry);
|
||||
};
|
||||
using ArchiveEntryPtr = std::unique_ptr<struct ::archive_entry, ArchiveEntryDeleter>;
|
||||
|
||||
void prepareCurrentEntry();
|
||||
static ArchiveEntryPtr createArchiveEntry(const Entry& entry);
|
||||
bool writeSomeCurrentFileData();
|
||||
std::int64_t onWriteCallback(const std::byte* buff, std::size_t size);
|
||||
|
||||
const EntryContainer _entries;
|
||||
ArchivePtr _archive;
|
||||
|
||||
static inline constexpr std::size_t _writeBlockSize{ 65536 };
|
||||
static inline constexpr std::size_t _readBufferSize{ 65536 };
|
||||
std::vector<std::byte> _readBuffer;
|
||||
|
||||
EntryContainer::const_iterator _currentEntry;
|
||||
ArchiveEntryPtr _currentArchiveEntry;
|
||||
|
||||
std::uint64_t _currentEntryOffset{};
|
||||
std::ostream* _currentOutputStream{};
|
||||
std::uint64_t _bytesWrittenInCurrentOutputStream{};
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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 "ChildProcess.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <cerrno>
|
||||
#include <fcntl.h>
|
||||
#include <stdexcept>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
|
||||
#include <boost/asio/read.hpp>
|
||||
#include <boost/asio/buffer.hpp>
|
||||
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
namespace
|
||||
{
|
||||
class SystemException : public ChildProcessException
|
||||
{
|
||||
public:
|
||||
SystemException(int err, const std::string& errMsg)
|
||||
: ChildProcessException{ errMsg + ": " + ::strerror(err) }
|
||||
{}
|
||||
|
||||
SystemException(boost::system::error_code ec, const std::string& errMsg)
|
||||
: ChildProcessException{ errMsg + ": " + ec.message() }
|
||||
{}
|
||||
};
|
||||
}
|
||||
|
||||
ChildProcess::ChildProcess(boost::asio::io_context& ioContext, const std::filesystem::path& path, const Args& args)
|
||||
: _ioContext{ ioContext }
|
||||
, _childStdout{ _ioContext }
|
||||
|
||||
{
|
||||
// make sure only one thread is executing this part of code
|
||||
static std::mutex mutex;
|
||||
std::unique_lock<std::mutex> lock{ mutex };
|
||||
|
||||
int pipe[2];
|
||||
|
||||
int res{ pipe2(pipe, O_NONBLOCK | O_CLOEXEC) };
|
||||
if (res < 0)
|
||||
throw SystemException{ errno, "pipe2 failed!" };
|
||||
|
||||
{
|
||||
#if defined(__linux__) && defined(F_SETPIPE_SZ)
|
||||
// Just a hint here to prevent the writer from writing too many bytes ahead of the reader
|
||||
constexpr std::size_t pipeSize{ 65536 * 4 };
|
||||
|
||||
if (fcntl(pipe[0], F_SETPIPE_SZ, pipeSize) == -1)
|
||||
throw SystemException{ errno, "fcntl failed!" };
|
||||
if (fcntl(pipe[1], F_SETPIPE_SZ, pipeSize) == -1)
|
||||
throw SystemException{ errno, "fcntl failed!" };
|
||||
#endif
|
||||
}
|
||||
|
||||
res = fork();
|
||||
if (res == -1)
|
||||
throw SystemException{ errno, "fork failed!" };
|
||||
|
||||
if (res == 0) // CHILD
|
||||
{
|
||||
close(pipe[0]);
|
||||
close(STDIN_FILENO);
|
||||
close(STDERR_FILENO);
|
||||
|
||||
// Replace stdout with pipe write
|
||||
if (dup2(pipe[1], STDOUT_FILENO) == -1)
|
||||
exit(-1);
|
||||
|
||||
std::vector<const char*> execArgs;
|
||||
std::transform(std::cbegin(args), std::cend(args), std::back_inserter(execArgs), [](const std::string& arg) { return arg.c_str(); });
|
||||
execArgs.push_back(nullptr);
|
||||
|
||||
res = execv(path.string().c_str(), (char* const*)&execArgs[0]);
|
||||
if (res == -1)
|
||||
exit(-1);
|
||||
}
|
||||
else // PARENT
|
||||
{
|
||||
close(pipe[1]);
|
||||
{
|
||||
boost::system::error_code assignError;
|
||||
_childStdout.assign(pipe[0], assignError);
|
||||
if (assignError)
|
||||
throw SystemException{ assignError, "fork failed!" };
|
||||
}
|
||||
_childPID = res;
|
||||
}
|
||||
}
|
||||
|
||||
ChildProcess::~ChildProcess()
|
||||
{
|
||||
LMS_LOG(CHILDPROCESS, DEBUG, "Closing child process...");
|
||||
{
|
||||
boost::system::error_code closeError;
|
||||
_childStdout.close(closeError);
|
||||
if (closeError)
|
||||
LMS_LOG(CHILDPROCESS, ERROR, "Closed failed: " << closeError.message());
|
||||
}
|
||||
|
||||
if (!_finished)
|
||||
kill();
|
||||
|
||||
wait(true);
|
||||
}
|
||||
|
||||
void ChildProcess::kill()
|
||||
{
|
||||
// process may already have finished
|
||||
LMS_LOG(CHILDPROCESS, DEBUG, "Killing child process...");
|
||||
if (::kill(_childPID, SIGKILL) == -1)
|
||||
LMS_LOG(CHILDPROCESS, DEBUG, "Kill failed: " << ::strerror(errno));
|
||||
}
|
||||
|
||||
bool ChildProcess::wait(bool block)
|
||||
{
|
||||
assert(!_waited);
|
||||
|
||||
int wstatus{};
|
||||
const pid_t pid{ waitpid(_childPID, &wstatus, block ? 0 : WNOHANG) };
|
||||
|
||||
if (pid == -1)
|
||||
throw SystemException{ errno, "waitpid failed!" };
|
||||
else if (pid == 0)
|
||||
return false;
|
||||
|
||||
if (WIFEXITED(wstatus))
|
||||
{
|
||||
_exitCode = WEXITSTATUS(wstatus);
|
||||
LMS_LOG(CHILDPROCESS, DEBUG, "Exit code = " << *_exitCode);
|
||||
}
|
||||
|
||||
_waited = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChildProcess::asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback)
|
||||
{
|
||||
assert(!finished());
|
||||
|
||||
LMS_LOG(CHILDPROCESS, DEBUG, "Async read, bufferSize = " << bufferSize);
|
||||
|
||||
boost::asio::async_read(_childStdout, boost::asio::buffer(data, bufferSize),
|
||||
[this, callback{ std::move(callback) }](const boost::system::error_code& error, std::size_t bytesTransferred)
|
||||
{
|
||||
LMS_LOG(CHILDPROCESS, DEBUG, "Async read cb - ec = '" << error.message() << "' (" << error.value() << "), bytesTransferred = " << bytesTransferred);
|
||||
|
||||
ReadResult readResult{ ReadResult::Success };
|
||||
if (error)
|
||||
{
|
||||
if (error != boost::asio::error::eof)
|
||||
{
|
||||
// forbidden to read any captured param here as the ChildProcess instance may already have been killed
|
||||
return;
|
||||
}
|
||||
|
||||
readResult = ReadResult::EndOfFile;
|
||||
_finished = true;
|
||||
}
|
||||
|
||||
callback(readResult, bytesTransferred);
|
||||
});
|
||||
}
|
||||
|
||||
std::size_t ChildProcess::readSome(std::byte* data, std::size_t bufferSize)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
const std::size_t res{ _childStdout.read_some(boost::asio::buffer(data, bufferSize), ec) };
|
||||
LMS_LOG(CHILDPROCESS, DEBUG, "read some " << res << " bytes, ec = " << ec.message());
|
||||
if (ec)
|
||||
_childStdout.close(ec);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ChildProcess::finished() const
|
||||
{
|
||||
return _finished;
|
||||
}
|
||||
}
|
||||
@@ -29,29 +29,31 @@
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/posix/stream_descriptor.hpp>
|
||||
|
||||
#include "utils/IChildProcess.hpp"
|
||||
#include "core/IChildProcess.hpp"
|
||||
|
||||
|
||||
class ChildProcess : public IChildProcess
|
||||
namespace lms::core
|
||||
{
|
||||
public:
|
||||
~ChildProcess();
|
||||
ChildProcess(boost::asio::io_context& ioContext, const std::filesystem::path& path, const Args& args);
|
||||
class ChildProcess : public IChildProcess
|
||||
{
|
||||
public:
|
||||
~ChildProcess();
|
||||
ChildProcess(boost::asio::io_context& ioContext, const std::filesystem::path& path, const Args& args);
|
||||
|
||||
private:
|
||||
void asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) override;
|
||||
std::size_t readSome(std::byte* data, std::size_t bufferSize) override;
|
||||
bool finished() const override;
|
||||
private:
|
||||
void asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) override;
|
||||
std::size_t readSome(std::byte* data, std::size_t bufferSize) override;
|
||||
bool finished() const override;
|
||||
|
||||
void kill();
|
||||
bool wait(bool block); // return true if waited
|
||||
void kill();
|
||||
bool wait(bool block); // return true if waited
|
||||
|
||||
using FileDescriptor = boost::asio::posix::stream_descriptor;
|
||||
using FileDescriptor = boost::asio::posix::stream_descriptor;
|
||||
|
||||
boost::asio::io_context& _ioContext;
|
||||
FileDescriptor _childStdout;
|
||||
::pid_t _childPID {};
|
||||
bool _waited {};
|
||||
bool _finished {};
|
||||
std::optional<int> _exitCode;
|
||||
};
|
||||
boost::asio::io_context& _ioContext;
|
||||
FileDescriptor _childStdout;
|
||||
::pid_t _childPID{};
|
||||
bool _waited{};
|
||||
bool _finished{};
|
||||
std::optional<int> _exitCode;
|
||||
};
|
||||
}
|
||||
+17
-17
@@ -19,26 +19,26 @@
|
||||
|
||||
#include "ChildProcessManager.hpp"
|
||||
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
#include "ChildProcess.hpp"
|
||||
|
||||
|
||||
std::unique_ptr<IChildProcessManager>
|
||||
createChildProcessManager(boost::asio::io_context& ioContext)
|
||||
namespace lms::core
|
||||
{
|
||||
return std::make_unique<ChildProcessManager>(ioContext);
|
||||
}
|
||||
|
||||
ChildProcessManager::ChildProcessManager(boost::asio::io_context& ioContext)
|
||||
: _ioContext {ioContext}
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<IChildProcess>
|
||||
ChildProcessManager::spawnChildProcess(const std::filesystem::path& path, const IChildProcess::Args& args)
|
||||
{
|
||||
return std::make_unique<ChildProcess>(_ioContext, path, args);
|
||||
}
|
||||
std::unique_ptr<IChildProcessManager>
|
||||
createChildProcessManager(boost::asio::io_context& ioContext)
|
||||
{
|
||||
return std::make_unique<ChildProcessManager>(ioContext);
|
||||
}
|
||||
|
||||
ChildProcessManager::ChildProcessManager(boost::asio::io_context& ioContext)
|
||||
: _ioContext{ ioContext }
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<IChildProcess>
|
||||
ChildProcessManager::spawnChildProcess(const std::filesystem::path& path, const IChildProcess::Args& args)
|
||||
{
|
||||
return std::make_unique<ChildProcess>(_ioContext, path, args);
|
||||
}
|
||||
}
|
||||
+16
-15
@@ -24,23 +24,24 @@
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
#include "utils/IChildProcessManager.hpp"
|
||||
#include "core/IChildProcessManager.hpp"
|
||||
|
||||
class ChildProcessManager : public IChildProcessManager
|
||||
namespace lms::core
|
||||
{
|
||||
public:
|
||||
ChildProcessManager(boost::asio::io_context& ioContext);
|
||||
~ChildProcessManager() = default;
|
||||
class ChildProcessManager : public IChildProcessManager
|
||||
{
|
||||
public:
|
||||
ChildProcessManager(boost::asio::io_context& ioContext);
|
||||
~ChildProcessManager() = default;
|
||||
|
||||
ChildProcessManager(const ChildProcessManager&) = delete;
|
||||
ChildProcessManager(ChildProcessManager&&) = delete;
|
||||
ChildProcessManager& operator=(const ChildProcessManager&) = delete;
|
||||
ChildProcessManager& operator=(ChildProcessManager&&) = delete;
|
||||
|
||||
private:
|
||||
std::unique_ptr<IChildProcess> spawnChildProcess(const std::filesystem::path& path, const IChildProcess::Args& args) override;
|
||||
|
||||
boost::asio::io_context& _ioContext;
|
||||
};
|
||||
ChildProcessManager(const ChildProcessManager&) = delete;
|
||||
ChildProcessManager(ChildProcessManager&&) = delete;
|
||||
ChildProcessManager& operator=(const ChildProcessManager&) = delete;
|
||||
ChildProcessManager& operator=(ChildProcessManager&&) = delete;
|
||||
|
||||
private:
|
||||
std::unique_ptr<IChildProcess> spawnChildProcess(const std::filesystem::path& path, const IChildProcess::Args& args) override;
|
||||
|
||||
boost::asio::io_context& _ioContext;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 "Config.hpp"
|
||||
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
std::unique_ptr<IConfig> createConfig(const std::filesystem::path& p)
|
||||
{
|
||||
return std::make_unique<Config>(p);
|
||||
}
|
||||
|
||||
Config::Config(const std::filesystem::path& p)
|
||||
{
|
||||
try
|
||||
{
|
||||
_config.readFile(p.string().c_str());
|
||||
}
|
||||
catch (libconfig::FileIOException& e)
|
||||
{
|
||||
throw LmsException{ "Cannot open config file '" + p.string() + "'" };
|
||||
}
|
||||
catch (libconfig::ParseException& e)
|
||||
{
|
||||
throw LmsException{ "Cannot parse config file '" + p.string() + "', line = " + std::to_string(e.getLine()) + ", error = '" + e.getError() + "'" };
|
||||
}
|
||||
catch (libconfig::ConfigException& e)
|
||||
{
|
||||
throw LmsException{ "Cannot open config file '" + p.string() + "': " + e.what() };
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view Config::getString(std::string_view setting, std::string_view def)
|
||||
{
|
||||
try
|
||||
{
|
||||
return static_cast<const char*>(_config.lookup(std::string{ setting }));
|
||||
}
|
||||
catch (libconfig::ConfigException&)
|
||||
{
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
void Config::visitStrings(std::string_view setting, std::function<void(std::string_view)> _func, std::initializer_list<std::string_view> defs)
|
||||
{
|
||||
try
|
||||
{
|
||||
const libconfig::Setting& values{ _config.lookup(std::string {setting}) };
|
||||
for (int i{}; i < values.getLength(); ++i)
|
||||
_func(static_cast<const char*>(values[i]));
|
||||
}
|
||||
catch (const libconfig::SettingNotFoundException&)
|
||||
{
|
||||
for (std::string_view def : defs)
|
||||
_func(def);
|
||||
}
|
||||
catch (libconfig::ConfigException&)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path Config::getPath(std::string_view setting, const std::filesystem::path& path)
|
||||
{
|
||||
try
|
||||
{
|
||||
const char* res{ _config.lookup(std::string {setting}) };
|
||||
return std::filesystem::path{ std::string(res) };
|
||||
}
|
||||
catch (libconfig::ConfigException&)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long Config::getULong(std::string_view setting, unsigned long def)
|
||||
{
|
||||
try
|
||||
{
|
||||
return static_cast<unsigned int>(_config.lookup(std::string{ setting }));
|
||||
}
|
||||
catch (libconfig::ConfigException&)
|
||||
{
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
long Config::getLong(std::string_view setting, long def)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _config.lookup(std::string{ setting });
|
||||
}
|
||||
catch (libconfig::ConfigException&)
|
||||
{
|
||||
return def;
|
||||
}
|
||||
}
|
||||
|
||||
bool Config::getBool(std::string_view setting, bool def)
|
||||
{
|
||||
try
|
||||
{
|
||||
return _config.lookup(std::string{ setting });
|
||||
}
|
||||
catch (libconfig::ConfigException&)
|
||||
{
|
||||
return def;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 "core/IConfig.hpp"
|
||||
|
||||
#include <libconfig.h++>
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
// Used to get config values from configuration files
|
||||
class Config final : public IConfig
|
||||
{
|
||||
public:
|
||||
Config(const std::filesystem::path& p);
|
||||
~Config() = default;
|
||||
|
||||
Config(const Config&) = delete;
|
||||
Config& operator=(const Config&) = delete;
|
||||
Config(Config&&) = delete;
|
||||
Config& operator=(Config&&) = delete;
|
||||
|
||||
// Default values are returned in case of setting not found
|
||||
std::string_view getString(std::string_view setting, std::string_view def = "") override;
|
||||
void visitStrings(std::string_view setting, std::function<void(std::string_view)> _func, std::initializer_list<std::string_view> defs) override;
|
||||
std::filesystem::path getPath(std::string_view setting, const std::filesystem::path& def = std::filesystem::path()) override;
|
||||
unsigned long getULong(std::string_view setting, unsigned long def = 0) override;
|
||||
long getLong(std::string_view setting, long def = 0) override;
|
||||
bool getBool(std::string_view setting, bool def = false) override;
|
||||
|
||||
private:
|
||||
|
||||
libconfig::Config _config;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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 "FileResourceHandler.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace lms
|
||||
{
|
||||
std::unique_ptr<IResourceHandler> createFileResourceHandler(const std::filesystem::path& path, std::string_view mimeType)
|
||||
{
|
||||
return std::make_unique<FileResourceHandler>(path, mimeType);
|
||||
}
|
||||
|
||||
FileResourceHandler::FileResourceHandler(const std::filesystem::path& path, std::string_view mimeType)
|
||||
: _path{ path }
|
||||
, _mimeType{ mimeType }
|
||||
{
|
||||
}
|
||||
|
||||
Wt::Http::ResponseContinuation* 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 };
|
||||
|
||||
if (startByte == 0)
|
||||
{
|
||||
if (!ifs)
|
||||
{
|
||||
LMS_LOG(UTILS, ERROR, "Cannot open file stream for '" << _path.string() << "'");
|
||||
response.setStatus(404);
|
||||
return {};
|
||||
}
|
||||
|
||||
ifs.seekg(0, std::ios::end);
|
||||
const ::uint64_t fileSize{ static_cast<::uint64_t>(ifs.tellg()) };
|
||||
ifs.seekg(0, std::ios::beg);
|
||||
|
||||
LMS_LOG(UTILS, DEBUG, "File '" << _path.string() << "', fileSize = " << fileSize);
|
||||
|
||||
response.addHeader("Accept-Ranges", "bytes");
|
||||
|
||||
const Wt::Http::Request::ByteRangeSpecifier ranges{ request.getRanges(fileSize) };
|
||||
if (!ranges.isSatisfiable())
|
||||
{
|
||||
std::ostringstream contentRange;
|
||||
contentRange << "bytes */" << fileSize;
|
||||
response.setStatus(416); // Requested range not satisfiable
|
||||
response.addHeader("Content-Range", contentRange.str());
|
||||
|
||||
LMS_LOG(UTILS, DEBUG, "Range not satisfiable");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (ranges.size() == 1)
|
||||
{
|
||||
LMS_LOG(UTILS, DEBUG, "Range requested = " << ranges[0].firstByte() << "-" << ranges[0].lastByte());
|
||||
|
||||
response.setStatus(206);
|
||||
startByte = ranges[0].firstByte();
|
||||
_beyondLastByte = ranges[0].lastByte() + 1;
|
||||
|
||||
std::ostringstream contentRange;
|
||||
contentRange << "bytes " << startByte << "-"
|
||||
<< _beyondLastByte - 1 << "/" << fileSize;
|
||||
|
||||
response.addHeader("Content-Range", contentRange.str());
|
||||
response.setContentLength(_beyondLastByte - startByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(UTILS, DEBUG, "No range requested");
|
||||
|
||||
response.setStatus(200);
|
||||
_beyondLastByte = fileSize;
|
||||
response.setContentLength(_beyondLastByte);
|
||||
}
|
||||
|
||||
LMS_LOG(UTILS, DEBUG, "Mimetype set to '" << _mimeType << "'");
|
||||
response.setMimeType(_mimeType);
|
||||
}
|
||||
else if (!ifs)
|
||||
{
|
||||
LMS_LOG(UTILS, ERROR, "Cannot reopen file stream for '" << _path.string() << "'");
|
||||
return {};
|
||||
}
|
||||
|
||||
ifs.seekg(static_cast<std::istream::pos_type>(startByte));
|
||||
|
||||
std::vector<char> buf;
|
||||
buf.resize(_chunkSize);
|
||||
|
||||
::uint64_t restSize = _beyondLastByte - startByte;
|
||||
::uint64_t pieceSize = buf.size() > restSize ? restSize : buf.size();
|
||||
|
||||
ifs.read(&buf[0], pieceSize);
|
||||
const ::uint64_t actualPieceSize{ static_cast<::uint64_t>(ifs.gcount()) };
|
||||
if (actualPieceSize > 0)
|
||||
{
|
||||
response.out().write(&buf[0], actualPieceSize);
|
||||
LMS_LOG(UTILS, DEBUG, "Written " << actualPieceSize << " bytes, range = " << startByte << "-" << startByte + actualPieceSize - 1 << "");
|
||||
}
|
||||
else
|
||||
{
|
||||
LMS_LOG(UTILS, DEBUG, "Written 0 byte");
|
||||
}
|
||||
|
||||
if (ifs.good() && actualPieceSize < restSize)
|
||||
{
|
||||
_offset = startByte + actualPieceSize;
|
||||
LMS_LOG(UTILS, DEBUG, "Job not complete! Remaining range: " << _offset << "-" << _beyondLastByte - 1);
|
||||
|
||||
return response.createContinuation();
|
||||
}
|
||||
|
||||
LMS_LOG(UTILS, DEBUG, "Job complete!");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
+16
-14
@@ -22,22 +22,24 @@
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include "utils/IResourceHandler.hpp"
|
||||
#include "core/IResourceHandler.hpp"
|
||||
|
||||
class FileResourceHandler final : public IResourceHandler
|
||||
namespace lms
|
||||
{
|
||||
public:
|
||||
FileResourceHandler(const std::filesystem::path& filePath, std::string_view mimeType);
|
||||
class FileResourceHandler final : public IResourceHandler
|
||||
{
|
||||
public:
|
||||
FileResourceHandler(const std::filesystem::path& filePath, std::string_view mimeType);
|
||||
|
||||
private:
|
||||
Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override;
|
||||
void abort() override {};
|
||||
private:
|
||||
Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override;
|
||||
void abort() override {};
|
||||
|
||||
static constexpr std::size_t _chunkSize{ 262'144 };
|
||||
|
||||
std::filesystem::path _path;
|
||||
std::string _mimeType;
|
||||
::uint64_t _beyondLastByte{};
|
||||
::uint64_t _offset{};
|
||||
};
|
||||
static constexpr std::size_t _chunkSize{ 262'144 };
|
||||
|
||||
std::filesystem::path _path;
|
||||
std::string _mimeType;
|
||||
::uint64_t _beyondLastByte{};
|
||||
::uint64_t _offset{};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 "core/IOContextRunner.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
IOContextRunner::IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount, std::string_view name)
|
||||
: _ioService{ ioService }
|
||||
, _work{ ioService }
|
||||
{
|
||||
LMS_LOG(UTILS, INFO, "Starting IO context with " << threadCount << " threads...");
|
||||
|
||||
for (std::size_t i{}; i < threadCount; ++i)
|
||||
{
|
||||
std::string threadName{ name };
|
||||
if (!threadName.empty())
|
||||
{
|
||||
threadName += "Thread_";
|
||||
threadName += std::to_string(i);
|
||||
}
|
||||
|
||||
_threads.emplace_back([this, threadName]
|
||||
{
|
||||
if (!threadName.empty())
|
||||
{
|
||||
if (auto * traceLogger{ Service<tracing::ITraceLogger>::get() })
|
||||
traceLogger->setThreadName(std::this_thread::get_id(), threadName);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_ioService.run();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
LMS_LOG(UTILS, FATAL, "Exception caught in IO context: " << e.what());
|
||||
std::abort();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void IOContextRunner::stop()
|
||||
{
|
||||
LMS_LOG(UTILS, DEBUG, "Stopping IO context...");
|
||||
_work.reset();
|
||||
_ioService.stop();
|
||||
LMS_LOG(UTILS, DEBUG, "IO context stopped!");
|
||||
}
|
||||
|
||||
std::size_t IOContextRunner::getThreadCount() const
|
||||
{
|
||||
return _threads.size();
|
||||
}
|
||||
|
||||
IOContextRunner::~IOContextRunner()
|
||||
{
|
||||
stop();
|
||||
|
||||
for (std::thread& t : _threads)
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 "core/ILogger.hpp"
|
||||
|
||||
namespace lms::core::logging
|
||||
{
|
||||
const char* getModuleName(Module mod)
|
||||
{
|
||||
switch (mod)
|
||||
{
|
||||
case Module::API_SUBSONIC: return "API_SUBSONIC";
|
||||
case Module::AUTH: return "AUTH";
|
||||
case Module::AV: return "AV";
|
||||
case Module::CHILDPROCESS: return "CHILDPROC";
|
||||
case Module::COVER: return "COVER";
|
||||
case Module::DB: return "DB";
|
||||
case Module::DBUPDATER: return "DB UPDATER";
|
||||
case Module::FEATURE: return "FEATURE";
|
||||
case Module::FEEDBACK: return "FEEDBACK";
|
||||
case Module::HTTP: return "HTTP";
|
||||
case Module::MAIN: return "MAIN";
|
||||
case Module::METADATA: return "METADATA";
|
||||
case Module::REMOTE: return "REMOTE";
|
||||
case Module::SCROBBLING: return "SCROBBLING";
|
||||
case Module::SERVICE: return "SERVICE";
|
||||
case Module::RECOMMENDATION: return "RECOMMENDATION";
|
||||
case Module::TRANSCODING: return "TRANSCODING";
|
||||
case Module::UI: return "UI";
|
||||
case Module::UTILS: return "UTILS";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
const char* getSeverityName(Severity sev)
|
||||
{
|
||||
switch (sev)
|
||||
{
|
||||
case Severity::FATAL: return "fatal";
|
||||
case Severity::ERROR: return "error";
|
||||
case Severity::WARNING: return "warning";
|
||||
case Severity::INFO: return "info";
|
||||
case Severity::DEBUG: return "debug";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
Log::Log(ILogger& logger, Module module, Severity severity)
|
||||
: _logger{ logger }
|
||||
, _module{ module }
|
||||
, _severity{ severity }
|
||||
|
||||
{}
|
||||
|
||||
Log::~Log()
|
||||
{
|
||||
assert(_logger.isSeverityActive(_severity));
|
||||
_logger.processLog(*this);
|
||||
}
|
||||
|
||||
std::string Log::getMessage() const
|
||||
{
|
||||
return _oss.str();
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "utils/NetAddress.hpp"
|
||||
#include "core/NetAddress.hpp"
|
||||
|
||||
#ifndef BOOST_ASIO_HAS_STD_HASH
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "utils/Path.hpp"
|
||||
#include "core/Path.hpp"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
@@ -28,16 +28,16 @@
|
||||
|
||||
#include <boost/tokenizer.hpp>
|
||||
|
||||
#include "utils/Crc32Calculator.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/String.hpp"
|
||||
#include "core/Crc32Calculator.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
namespace PathUtils
|
||||
namespace lms::core::pathUtils
|
||||
{
|
||||
std::uint32_t computeCrc32(const std::filesystem::path& p)
|
||||
{
|
||||
Utils::Crc32Calculator crc32;
|
||||
core::Crc32Calculator crc32;
|
||||
|
||||
std::ifstream ifs{ p.string().c_str(), std::ios_base::binary };
|
||||
if (ifs)
|
||||
@@ -134,7 +134,7 @@ namespace PathUtils
|
||||
|
||||
bool hasFileAnyExtension(const std::filesystem::path& file, const std::vector<std::filesystem::path>& supportedExtensions)
|
||||
{
|
||||
const std::filesystem::path extension{ StringUtils::stringToLower(file.extension().string()) };
|
||||
const std::filesystem::path extension{ stringUtils::stringToLower(file.extension().string()) };
|
||||
|
||||
return (std::find(std::cbegin(supportedExtensions), std::cend(supportedExtensions), extension) != std::cend(supportedExtensions));
|
||||
}
|
||||
@@ -157,10 +157,10 @@ namespace PathUtils
|
||||
|
||||
if (curPath == rootPath)
|
||||
return true;
|
||||
|
||||
|
||||
if (curPath == curPath.root_path())
|
||||
break;
|
||||
|
||||
|
||||
curPath = curPath.parent_path();
|
||||
}
|
||||
|
||||
@@ -183,4 +183,4 @@ namespace PathUtils
|
||||
|
||||
return longestCommonPath;
|
||||
}
|
||||
} // ns PathUtils
|
||||
}
|
||||
@@ -17,22 +17,22 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "utils/Random.hpp"
|
||||
#include "core/Random.hpp"
|
||||
|
||||
namespace Random {
|
||||
|
||||
RandGenerator& getRandGenerator()
|
||||
namespace lms::core::random
|
||||
{
|
||||
static thread_local std::random_device rd;
|
||||
static thread_local RandGenerator randGenerator(rd());
|
||||
|
||||
return randGenerator;
|
||||
}
|
||||
RandGenerator& getRandGenerator()
|
||||
{
|
||||
static thread_local std::random_device rd;
|
||||
static thread_local RandGenerator randGenerator(rd());
|
||||
|
||||
RandGenerator createSeededGenerator(uint_fast32_t seed)
|
||||
{
|
||||
return RandGenerator {seed};
|
||||
}
|
||||
return randGenerator;
|
||||
}
|
||||
|
||||
} // Random
|
||||
RandGenerator createSeededGenerator(uint_fast32_t seed)
|
||||
{
|
||||
return RandGenerator{ seed };
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 "core/RecursiveSharedMutex.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
void RecursiveSharedMutex::lock()
|
||||
{
|
||||
const auto thisThreadId{ std::this_thread::get_id() };
|
||||
|
||||
if (_uniqueOwner == thisThreadId)
|
||||
{
|
||||
// already locked
|
||||
_uniqueCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
_mutex.lock();
|
||||
_uniqueOwner = thisThreadId;
|
||||
assert(_uniqueCount == 0);
|
||||
_uniqueCount = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void RecursiveSharedMutex::unlock()
|
||||
{
|
||||
assert(_uniqueCount > 0);
|
||||
|
||||
if (--_uniqueCount == 0)
|
||||
{
|
||||
_uniqueOwner = {};
|
||||
_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void RecursiveSharedMutex::lock_shared()
|
||||
{
|
||||
const auto thisThreadId{ std::this_thread::get_id() };
|
||||
|
||||
if (_uniqueOwner == thisThreadId)
|
||||
{
|
||||
// alone here, no need to lock
|
||||
_sharedCounts[thisThreadId]++;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool needLock{};
|
||||
|
||||
{
|
||||
std::scoped_lock lock{ _sharedCountMutex };
|
||||
|
||||
auto& sharedCount{ _sharedCounts[thisThreadId] };
|
||||
if (sharedCount == 0)
|
||||
needLock = true;
|
||||
else
|
||||
++sharedCount;
|
||||
}
|
||||
|
||||
if (needLock)
|
||||
{
|
||||
_mutex.lock_shared();
|
||||
|
||||
assert(_uniqueOwner == std::thread::id{});
|
||||
|
||||
std::scoped_lock lock{ _sharedCountMutex };
|
||||
_sharedCounts[thisThreadId]++;
|
||||
}
|
||||
}
|
||||
|
||||
void RecursiveSharedMutex::unlock_shared()
|
||||
{
|
||||
const auto thisThreadId{ std::this_thread::get_id() };
|
||||
|
||||
if (_uniqueOwner == thisThreadId)
|
||||
{
|
||||
// alone here, no need to lock
|
||||
auto& sharedCount{ _sharedCounts[thisThreadId] };
|
||||
assert(sharedCount > 0);
|
||||
--sharedCount;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
bool needUnlock{};
|
||||
|
||||
{
|
||||
std::scoped_lock lock{ _sharedCountMutex };
|
||||
|
||||
auto& sharedCount{ _sharedCounts[thisThreadId] };
|
||||
assert(sharedCount > 0);
|
||||
needUnlock = (--sharedCount == 0);
|
||||
}
|
||||
|
||||
if (needUnlock)
|
||||
_mutex.unlock_shared();
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool RecursiveSharedMutex::isUniqueLocked()
|
||||
{
|
||||
return _uniqueOwner == std::this_thread::get_id();
|
||||
}
|
||||
|
||||
bool RecursiveSharedMutex::isSharedLocked()
|
||||
{
|
||||
const auto thisThreadId{ std::this_thread::get_id() };
|
||||
if (_uniqueOwner == thisThreadId)
|
||||
return true;
|
||||
|
||||
std::scoped_lock lock{ _sharedCountMutex };
|
||||
return _sharedCounts[thisThreadId] > 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -20,17 +20,19 @@
|
||||
#include <cassert>
|
||||
#include <thread>
|
||||
|
||||
#include "utils/StreamLogger.hpp"
|
||||
#include "core/StreamLogger.hpp"
|
||||
|
||||
StreamLogger::StreamLogger(std::ostream& os, EnumSet<Severity> severities)
|
||||
: _os{ os }
|
||||
, _severities{ severities }
|
||||
namespace lms::core::logging
|
||||
{
|
||||
}
|
||||
|
||||
void StreamLogger::processLog(const Log& log)
|
||||
{
|
||||
assert(isSeverityActive(log.getSeverity()));
|
||||
_os << std::this_thread::get_id() << " [" << getSeverityName(log.getSeverity()) << "] [" << getModuleName(log.getModule()) << "] " << log.getMessage() << std::endl;
|
||||
}
|
||||
StreamLogger::StreamLogger(std::ostream& os, EnumSet<Severity> severities)
|
||||
: _os{ os }
|
||||
, _severities{ severities }
|
||||
{
|
||||
}
|
||||
|
||||
void StreamLogger::processLog(const Log& log)
|
||||
{
|
||||
assert(isSeverityActive(log.getSeverity()));
|
||||
_os << std::this_thread::get_id() << " [" << getSeverityName(log.getSeverity()) << "] [" << getModuleName(log.getModule()) << "] " << log.getMessage() << std::endl;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "utils/String.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
@@ -26,9 +26,8 @@
|
||||
#include <Wt/WDateTime.h>
|
||||
#include <Wt/WDate.h>
|
||||
|
||||
namespace StringUtils
|
||||
namespace lms::core::stringUtils
|
||||
{
|
||||
|
||||
namespace details
|
||||
{
|
||||
constexpr std::pair<char, std::string_view> jsEscapeChars[]
|
||||
@@ -437,5 +436,4 @@ namespace StringUtils
|
||||
// assume UTC
|
||||
return date.toString("yyyy-MM-dd").toUTF8();
|
||||
}
|
||||
} // StringUtils
|
||||
|
||||
}
|
||||
@@ -22,10 +22,10 @@
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "utils/Exception.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace tracing
|
||||
namespace lms::core::tracing
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -26,9 +26,9 @@
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "utils/ITraceLogger.hpp"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
|
||||
namespace tracing
|
||||
namespace lms::core::tracing
|
||||
{
|
||||
class TraceLogger : public ITraceLogger
|
||||
{
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 "core/UUID.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <iomanip>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
|
||||
#include "core/Random.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
namespace stringUtils
|
||||
{
|
||||
template <>
|
||||
std::optional<UUID>
|
||||
readAs(std::string_view str)
|
||||
{
|
||||
return UUID::fromString(str);
|
||||
}
|
||||
}
|
||||
namespace
|
||||
{
|
||||
bool stringIsUUID(std::string_view str)
|
||||
{
|
||||
static const std::regex re{ R"([0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})" };
|
||||
|
||||
return std::regex_match(std::cbegin(str), std::cend(str), re);
|
||||
}
|
||||
}
|
||||
|
||||
UUID::UUID(std::string_view str)
|
||||
: _value{ stringUtils::stringToLower(str) }
|
||||
{
|
||||
}
|
||||
|
||||
std::optional<UUID> UUID::fromString(std::string_view str)
|
||||
{
|
||||
if (!stringIsUUID(str))
|
||||
return std::nullopt;
|
||||
|
||||
return UUID{ str };
|
||||
}
|
||||
|
||||
UUID UUID::generate()
|
||||
{
|
||||
// Form is "123e4567-e89b-12d3-a456-426614174000"
|
||||
// TODO: store 128 bits and only convert to string when necessary
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
auto concatRandomBytes{ [](std::ostream& os, std::size_t byteCount)
|
||||
{
|
||||
for (std::size_t i {}; i < byteCount; ++i)
|
||||
os << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(random::getRandom<std::uint8_t>(0, 255));
|
||||
} };
|
||||
|
||||
concatRandomBytes(oss, 4);
|
||||
oss << "-";
|
||||
concatRandomBytes(oss, 2);
|
||||
oss << "-";
|
||||
concatRandomBytes(oss, 2);
|
||||
oss << "-";
|
||||
concatRandomBytes(oss, 2);
|
||||
oss << "-";
|
||||
concatRandomBytes(oss, 6);
|
||||
|
||||
const auto uuid{ fromString(oss.str()) };
|
||||
assert(uuid);
|
||||
return uuid.value();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 "core/WtLogger.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <sstream>
|
||||
#include <Wt/WServer.h>
|
||||
#include <Wt/WLogger.h>
|
||||
|
||||
#include "core/Exception.hpp"
|
||||
|
||||
namespace lms::core::logging
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::string to_string(std::thread::id id)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << id;
|
||||
return oss.str();
|
||||
}
|
||||
}
|
||||
|
||||
WtLogger::WtLogger(Severity minSeverity)
|
||||
: _minSeverity{ minSeverity }
|
||||
{
|
||||
}
|
||||
|
||||
std::string WtLogger::computeLogConfig(Severity minSeverity)
|
||||
{
|
||||
switch (minSeverity)
|
||||
{
|
||||
case Severity::DEBUG: return "*";
|
||||
case Severity::INFO: return "* -debug";
|
||||
case Severity::WARNING: return "* -debug -info";
|
||||
case Severity::ERROR: return "* -debug -info -warning";
|
||||
case Severity::FATAL: return "* -debug -info -warning -error";
|
||||
}
|
||||
|
||||
throw LmsException{ "Unhandled severity" };
|
||||
}
|
||||
|
||||
bool WtLogger::isSeverityActive(Severity severity) const
|
||||
{
|
||||
return static_cast<int>(severity) <= static_cast<int>(_minSeverity);
|
||||
}
|
||||
|
||||
void WtLogger::processLog(const Log& log)
|
||||
{
|
||||
Wt::log(getSeverityName(log.getSeverity())) << Wt::WLogger::sep << to_string(std::this_thread::get_id()) << Wt::WLogger::sep << "[" << getModuleName(log.getModule()) << "]" << Wt::WLogger::sep << log.getMessage();
|
||||
}
|
||||
}
|
||||
@@ -18,26 +18,22 @@
|
||||
*/
|
||||
|
||||
#include "Client.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
|
||||
namespace Http
|
||||
namespace lms::core::http
|
||||
{
|
||||
std::unique_ptr<IClient>
|
||||
createClient(boost::asio::io_context& ioContext, std::string_view baseUrl)
|
||||
{
|
||||
return std::make_unique<Client>(ioContext, baseUrl);
|
||||
}
|
||||
std::unique_ptr<IClient> createClient(boost::asio::io_context& ioContext, std::string_view baseUrl)
|
||||
{
|
||||
return std::make_unique<Client>(ioContext, baseUrl);
|
||||
}
|
||||
|
||||
void
|
||||
Client::sendGETRequest(ClientGETRequestParameters&& GETParams)
|
||||
{
|
||||
_sendQueue.sendRequest(std::make_unique<ClientRequest>(std::move(GETParams)));
|
||||
}
|
||||
|
||||
void
|
||||
Client::sendPOSTRequest(ClientPOSTRequestParameters&& POSTParams)
|
||||
{
|
||||
_sendQueue.sendRequest(std::make_unique<ClientRequest>(std::move(POSTParams)));
|
||||
}
|
||||
} // namespace Http
|
||||
void Client::sendGETRequest(ClientGETRequestParameters&& GETParams)
|
||||
{
|
||||
_sendQueue.sendRequest(std::make_unique<ClientRequest>(std::move(GETParams)));
|
||||
}
|
||||
|
||||
void Client::sendPOSTRequest(ClientPOSTRequestParameters&& POSTParams)
|
||||
{
|
||||
_sendQueue.sendRequest(std::make_unique<ClientRequest>(std::move(POSTParams)));
|
||||
}
|
||||
}
|
||||
@@ -23,23 +23,22 @@
|
||||
#include <string>
|
||||
#include <shared_mutex>
|
||||
|
||||
#include "utils/http/IClient.hpp"
|
||||
#include "core/http/IClient.hpp"
|
||||
#include "SendQueue.hpp"
|
||||
|
||||
namespace Http
|
||||
namespace lms::core::http
|
||||
{
|
||||
class Client final : public IClient
|
||||
{
|
||||
public:
|
||||
Client(boost::asio::io_context& ioContext, std::string_view baseUrl)
|
||||
: _sendQueue {ioContext, baseUrl}
|
||||
{}
|
||||
class Client final : public IClient
|
||||
{
|
||||
public:
|
||||
Client(boost::asio::io_context& ioContext, std::string_view baseUrl)
|
||||
: _sendQueue{ ioContext, baseUrl }
|
||||
{}
|
||||
|
||||
private:
|
||||
void sendGETRequest(ClientGETRequestParameters&& request) override;
|
||||
void sendPOSTRequest(ClientPOSTRequestParameters&& request) override;
|
||||
|
||||
SendQueue _sendQueue;
|
||||
};
|
||||
} // namespace Http
|
||||
private:
|
||||
void sendGETRequest(ClientGETRequestParameters&& request) override;
|
||||
void sendPOSTRequest(ClientPOSTRequestParameters&& request) override;
|
||||
|
||||
SendQueue _sendQueue;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <memory>
|
||||
#include <variant>
|
||||
#include "core/http/ClientRequestParameters.hpp"
|
||||
|
||||
namespace lms::core::http
|
||||
{
|
||||
class ClientRequest
|
||||
{
|
||||
public:
|
||||
ClientRequest(ClientGETRequestParameters&& GETParams) : _parameters{ std::move(GETParams) } {}
|
||||
ClientRequest(ClientPOSTRequestParameters&& POSTParams) : _parameters{ std::move(POSTParams) } {}
|
||||
|
||||
std::size_t retryCount{};
|
||||
|
||||
const ClientRequestParameters& getParameters() const
|
||||
{
|
||||
const ClientRequestParameters* res;
|
||||
|
||||
std::visit([&](const auto& parameters)
|
||||
{
|
||||
res = &static_cast<const ClientRequestParameters&>(parameters);
|
||||
}, _parameters);
|
||||
|
||||
return *res;
|
||||
}
|
||||
|
||||
|
||||
enum class Type
|
||||
{
|
||||
GET,
|
||||
POST
|
||||
};
|
||||
Type getType() const
|
||||
{
|
||||
if (std::holds_alternative<ClientGETRequestParameters>(_parameters))
|
||||
return Type::GET;
|
||||
else
|
||||
return Type::POST;
|
||||
}
|
||||
|
||||
const ClientGETRequestParameters& getGETParameters() const
|
||||
{
|
||||
return std::get<ClientGETRequestParameters>(_parameters);
|
||||
}
|
||||
|
||||
const ClientPOSTRequestParameters& getPOSTParameters() const
|
||||
{
|
||||
return std::get<ClientPOSTRequestParameters>(_parameters);
|
||||
}
|
||||
|
||||
private:
|
||||
std::variant<ClientGETRequestParameters, ClientPOSTRequestParameters> _parameters;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 "SendQueue.hpp"
|
||||
|
||||
#include <boost/asio/dispatch.hpp>
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#define LOG(sev, message) LMS_LOG(SCROBBLING, sev, "[Http SendQueue] - " << message)
|
||||
|
||||
namespace lms::core::stringUtils
|
||||
{
|
||||
template<>
|
||||
std::optional<std::chrono::seconds> readAs(std::string_view str)
|
||||
{
|
||||
std::optional<std::chrono::seconds> res;
|
||||
|
||||
if (const std::optional<std::size_t> value{ readAs<std::size_t>(str) })
|
||||
res = std::chrono::seconds{ *value };
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
namespace lms::core::http
|
||||
{
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
std::optional<T> headerReadAs(const Wt::Http::Message& msg, std::string_view headerName)
|
||||
{
|
||||
std::optional<T> res;
|
||||
|
||||
if (const std::string * headerValue{ msg.getHeader(std::string {headerName}) })
|
||||
res = stringUtils::readAs<T>(*headerValue);
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
SendQueue::SendQueue(boost::asio::io_context& ioContext, std::string_view baseUrl)
|
||||
: _ioContext{ ioContext }
|
||||
, _baseUrl{ baseUrl }
|
||||
{
|
||||
_client.done().connect([this](Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg)
|
||||
{
|
||||
_strand.dispatch([this, ec, msg = std::move(msg)]
|
||||
{
|
||||
onClientDone(ec, msg);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SendQueue::~SendQueue()
|
||||
{
|
||||
_client.abort();
|
||||
}
|
||||
|
||||
void SendQueue::sendRequest(std::unique_ptr<ClientRequest> request)
|
||||
{
|
||||
boost::asio::dispatch(_strand, [this, request = std::move(request)]() mutable
|
||||
{
|
||||
_sendQueue[request->getParameters().priority].emplace_back(std::move(request));
|
||||
|
||||
if (_state == State::Idle)
|
||||
sendNextQueuedRequest();
|
||||
});
|
||||
}
|
||||
|
||||
void SendQueue::sendNextQueuedRequest()
|
||||
{
|
||||
assert(_state == State::Idle);
|
||||
assert(!_currentRequest);
|
||||
|
||||
for (auto& [prio, requests] : _sendQueue)
|
||||
{
|
||||
LOG(DEBUG, "Processing prio " << static_cast<int>(prio) << ", request count = " << requests.size());
|
||||
while (!requests.empty())
|
||||
{
|
||||
std::unique_ptr<ClientRequest> request{ std::move(requests.front()) };
|
||||
requests.pop_front();
|
||||
|
||||
if (!sendRequest(*request))
|
||||
continue;
|
||||
|
||||
_state = State::Sending;
|
||||
_currentRequest = std::move(request);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SendQueue::sendRequest(const ClientRequest& request)
|
||||
{
|
||||
LMS_SCOPED_TRACE_DETAILED("SendQueue", "SendRequest");
|
||||
|
||||
std::string url{ _baseUrl + request.getParameters().relativeUrl };
|
||||
LOG(DEBUG, "Sending request to url '" << url << "'");
|
||||
|
||||
bool res{};
|
||||
switch (request.getType())
|
||||
{
|
||||
case ClientRequest::Type::GET:
|
||||
res = _client.get(url, request.getGETParameters().headers);
|
||||
break;
|
||||
|
||||
case ClientRequest::Type::POST:
|
||||
res = _client.post(url, request.getPOSTParameters().message);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!res)
|
||||
LOG(ERROR, "Send failed, bad url or unsupported scheme?");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void SendQueue::onClientDone(Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg)
|
||||
{
|
||||
LMS_SCOPED_TRACE_DETAILED("SendQueue", "OnClientDone");
|
||||
|
||||
if (ec == boost::asio::error::operation_aborted)
|
||||
{
|
||||
LOG(DEBUG, "Client aborted");
|
||||
return;
|
||||
}
|
||||
|
||||
assert(_currentRequest);
|
||||
_state = State::Idle;
|
||||
|
||||
LOG(DEBUG, "Client done. status = " << msg.status());
|
||||
if (ec)
|
||||
onClientDoneError(std::move(_currentRequest), ec);
|
||||
else
|
||||
onClientDoneSuccess(std::move(_currentRequest), msg);
|
||||
}
|
||||
|
||||
void SendQueue::onClientDoneError(std::unique_ptr<ClientRequest> request, Wt::AsioWrapper::error_code ec)
|
||||
{
|
||||
LOG(ERROR, "Retry " << request->retryCount << ", client error: '" << ec.message() << "'");
|
||||
|
||||
// may be a network error, try again later
|
||||
throttle(_defaultRetryWaitDuration);
|
||||
|
||||
if (request->retryCount++ < _maxRetryCount)
|
||||
{
|
||||
_sendQueue[request->getParameters().priority].emplace_front(std::move(request));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(ERROR, "Too many retries, giving up operation and throttle");
|
||||
if (request->getParameters().onFailureFunc)
|
||||
request->getParameters().onFailureFunc();
|
||||
}
|
||||
}
|
||||
|
||||
void SendQueue::onClientDoneSuccess(std::unique_ptr<ClientRequest> request, const Wt::Http::Message& msg)
|
||||
{
|
||||
const ClientRequestParameters& requestParameters{ request->getParameters() };
|
||||
bool mustThrottle{};
|
||||
if (msg.status() == 429)
|
||||
{
|
||||
_sendQueue[requestParameters.priority].emplace_front(std::move(request));
|
||||
mustThrottle = true;
|
||||
}
|
||||
|
||||
const auto remainingCount{ headerReadAs<std::size_t>(msg, "X-RateLimit-Remaining") };
|
||||
LOG(DEBUG, "Remaining messages = " << (remainingCount ? *remainingCount : 0));
|
||||
if (mustThrottle || (remainingCount && *remainingCount == 0))
|
||||
{
|
||||
const auto waitDuration{ headerReadAs<std::chrono::seconds>(msg, "X-RateLimit-Reset-In") };
|
||||
throttle(waitDuration.value_or(_defaultRetryWaitDuration));
|
||||
}
|
||||
|
||||
if (!mustThrottle)
|
||||
{
|
||||
if (msg.status() == 200)
|
||||
{
|
||||
if (requestParameters.onSuccessFunc)
|
||||
requestParameters.onSuccessFunc(msg.body());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(ERROR, "Send error: '" << msg.body() << "'");
|
||||
if (requestParameters.onFailureFunc)
|
||||
requestParameters.onFailureFunc();
|
||||
}
|
||||
}
|
||||
|
||||
if (_state == State::Idle)
|
||||
sendNextQueuedRequest();
|
||||
}
|
||||
|
||||
void SendQueue::throttle(std::chrono::seconds requestedDuration)
|
||||
{
|
||||
assert(_state == State::Idle);
|
||||
|
||||
const std::chrono::seconds duration{ clamp(requestedDuration, _minRetryWaitDuration, _maxRetryWaitDuration) };
|
||||
LOG(DEBUG, "Throttling for " << duration.count() << " seconds");
|
||||
|
||||
_throttleTimer.expires_after(duration);
|
||||
_throttleTimer.async_wait([this](const boost::system::error_code& ec)
|
||||
{
|
||||
if (ec == boost::asio::error::operation_aborted)
|
||||
{
|
||||
LOG(DEBUG, "Throttle aborted");
|
||||
return;
|
||||
}
|
||||
else if (ec)
|
||||
{
|
||||
throw LmsException{ "Throttle timer failure: " + std::string {ec.message()} };
|
||||
}
|
||||
|
||||
_state = State::Idle;
|
||||
sendNextQueuedRequest();
|
||||
});
|
||||
_state = State::Throttled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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 <deque>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/io_context_strand.hpp>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <Wt/Http/Client.h>
|
||||
#include "ClientRequest.hpp"
|
||||
|
||||
namespace lms::core::http
|
||||
{
|
||||
class SendQueue
|
||||
{
|
||||
public:
|
||||
SendQueue(boost::asio::io_context& ioContext, std::string_view baseUrl);
|
||||
~SendQueue();
|
||||
|
||||
SendQueue(const SendQueue&) = delete;
|
||||
SendQueue(const SendQueue&&) = delete;
|
||||
SendQueue& operator=(const SendQueue&) = delete;
|
||||
SendQueue& operator=(const SendQueue&&) = delete;
|
||||
|
||||
void sendRequest(std::unique_ptr<ClientRequest> request);
|
||||
|
||||
private:
|
||||
void sendNextQueuedRequest();
|
||||
bool sendRequest(const ClientRequest& request);
|
||||
void onClientDone(Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg);
|
||||
void onClientDoneError(std::unique_ptr<ClientRequest> request, Wt::AsioWrapper::error_code ec);
|
||||
void onClientDoneSuccess(std::unique_ptr<ClientRequest> request, const Wt::Http::Message& msg);
|
||||
void throttle(std::chrono::seconds duration);
|
||||
|
||||
const std::size_t _maxRetryCount{ 2 };
|
||||
const std::chrono::seconds _defaultRetryWaitDuration{ 30 };
|
||||
const std::chrono::seconds _minRetryWaitDuration{ 1 };
|
||||
const std::chrono::seconds _maxRetryWaitDuration{ 300 };
|
||||
|
||||
boost::asio::io_context& _ioContext;
|
||||
boost::asio::io_context::strand _strand{ _ioContext };
|
||||
boost::asio::steady_timer _throttleTimer{ _ioContext };
|
||||
std::string _baseUrl;
|
||||
|
||||
enum class State
|
||||
{
|
||||
Idle,
|
||||
Throttled,
|
||||
Sending,
|
||||
};
|
||||
State _state{ State::Idle };
|
||||
Wt::Http::Client _client{ _ioContext };
|
||||
std::map<ClientRequestParameters::Priority, std::deque<std::unique_ptr<ClientRequest>>> _sendQueue;
|
||||
std::unique_ptr<ClientRequest> _currentRequest;
|
||||
};
|
||||
|
||||
}
|
||||
+16
-20
@@ -21,27 +21,23 @@
|
||||
|
||||
#include <boost/crc.hpp> // for boost::crc_32_type
|
||||
|
||||
namespace Utils
|
||||
namespace lms::core
|
||||
{
|
||||
class Crc32Calculator
|
||||
{
|
||||
public:
|
||||
void processBytes(const std::byte* _data, std::size_t dataSize)
|
||||
{
|
||||
_result.process_bytes(_data, dataSize);
|
||||
}
|
||||
|
||||
class Crc32Calculator
|
||||
{
|
||||
public:
|
||||
|
||||
void processBytes(const std::byte* _data, std::size_t dataSize)
|
||||
{
|
||||
_result.process_bytes(_data, dataSize);
|
||||
}
|
||||
|
||||
std::uint32_t getResult() const
|
||||
{
|
||||
return _result.checksum();
|
||||
}
|
||||
|
||||
private:
|
||||
using Crc32Type = boost::crc_32_type;
|
||||
Crc32Type _result;
|
||||
};
|
||||
std::uint32_t getResult() const
|
||||
{
|
||||
return _result.checksum();
|
||||
}
|
||||
|
||||
private:
|
||||
using Crc32Type = boost::crc_32_type;
|
||||
Crc32Type _result;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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 <cassert>
|
||||
#include <cstdint>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
template <typename T, typename underlying_type = std::uint32_t>
|
||||
class EnumSet
|
||||
{
|
||||
static_assert(std::is_enum<T>::value);
|
||||
static_assert(std::is_same<underlying_type, std::uint64_t>::value || std::is_same<underlying_type, std::uint32_t>::value);
|
||||
|
||||
using IndexType = std::uint_fast8_t;
|
||||
|
||||
public:
|
||||
using ValueType = underlying_type;
|
||||
|
||||
EnumSet() = default;
|
||||
constexpr EnumSet(std::initializer_list<T> values)
|
||||
{
|
||||
for (T value : values)
|
||||
insert(value);
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
constexpr EnumSet(It begin, It end)
|
||||
{
|
||||
assign(begin, end);
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
constexpr void assign(It begin, It end)
|
||||
{
|
||||
clear();
|
||||
for (It it{ begin }; it != end; ++it)
|
||||
insert(*it);
|
||||
}
|
||||
|
||||
constexpr void insert(T value)
|
||||
{
|
||||
assert(static_cast<std::size_t>(value) < sizeof(_bitfield) * 8);
|
||||
_bitfield |= (underlying_type{ 1 } << static_cast<underlying_type>(value));
|
||||
}
|
||||
|
||||
constexpr void erase(T value)
|
||||
{
|
||||
assert(static_cast<std::size_t>(value) < sizeof(_bitfield) * 8);
|
||||
_bitfield &= ~(underlying_type{ 1 } << static_cast<underlying_type>(value));
|
||||
}
|
||||
|
||||
constexpr bool empty() const
|
||||
{
|
||||
return _bitfield == 0;
|
||||
}
|
||||
|
||||
constexpr bool contains(T value) const
|
||||
{
|
||||
assert(static_cast<std::size_t>(value) < sizeof(_bitfield) * 8);
|
||||
return _bitfield & (underlying_type{ 1 } << static_cast<underlying_type>(value));
|
||||
}
|
||||
|
||||
constexpr void clear()
|
||||
{
|
||||
_bitfield = 0;
|
||||
}
|
||||
|
||||
class iterator
|
||||
{
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
constexpr value_type operator*() const
|
||||
{
|
||||
return static_cast<value_type>(_index);
|
||||
}
|
||||
|
||||
constexpr bool operator==(const iterator& _other) const
|
||||
{
|
||||
return &_container == &_other._container && _index == _other._index;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const iterator& _other) const
|
||||
{
|
||||
return !(*this == _other);
|
||||
}
|
||||
|
||||
constexpr iterator& operator++()
|
||||
{
|
||||
_index = _container.getFirstBitSetIndex(_index + 1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
friend class EnumSet;
|
||||
|
||||
constexpr iterator(const EnumSet& _container, IndexType _index)
|
||||
: _container{ _container }
|
||||
, _index{ _index }
|
||||
{
|
||||
}
|
||||
|
||||
const EnumSet& _container;
|
||||
IndexType _index;
|
||||
};
|
||||
|
||||
constexpr iterator begin() const
|
||||
{
|
||||
return iterator{ *this, getFirstBitSetIndex() };
|
||||
}
|
||||
|
||||
constexpr iterator end() const
|
||||
{
|
||||
return iterator{ *this, npos };
|
||||
}
|
||||
|
||||
constexpr underlying_type getBitfield() const
|
||||
{
|
||||
return _bitfield;
|
||||
}
|
||||
|
||||
constexpr void setBitfield(underlying_type bitfield)
|
||||
{
|
||||
_bitfield = bitfield;
|
||||
}
|
||||
|
||||
constexpr bool operator==(const EnumSet other) const
|
||||
{
|
||||
return _bitfield == other._bitfield;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(const EnumSet other) const
|
||||
{
|
||||
return _bitfield != other._bitfield;
|
||||
}
|
||||
|
||||
private:
|
||||
static_assert(std::numeric_limits<IndexType>::max() >= sizeof(underlying_type) * 8);
|
||||
enum : IndexType { npos = sizeof(underlying_type) * 8 };
|
||||
|
||||
constexpr IndexType getFirstBitSetIndex(IndexType start = {}) const
|
||||
{
|
||||
assert(start < npos);
|
||||
|
||||
// return npos if no bit found
|
||||
IndexType res{ countTrailingZero(_bitfield >> start) };
|
||||
if (res == npos)
|
||||
return res;
|
||||
|
||||
return res + start;
|
||||
}
|
||||
|
||||
static constexpr IndexType countTrailingZero(underlying_type bitField)
|
||||
{
|
||||
IndexType res{};
|
||||
|
||||
while (res < (sizeof(underlying_type) * 8) && (bitField & 1) == 0)
|
||||
{
|
||||
++res;
|
||||
bitField >>= 1;
|
||||
}
|
||||
|
||||
if (res == sizeof(underlying_type) * 8)
|
||||
res = npos;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
underlying_type _bitfield{};
|
||||
};
|
||||
}
|
||||
+8
-5
@@ -23,9 +23,12 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
class LmsException : public std::runtime_error
|
||||
namespace lms::core
|
||||
{
|
||||
public:
|
||||
LmsException(std::string_view error = "") : std::runtime_error {std::string {error}} {}
|
||||
};
|
||||
|
||||
// TODO, rename to Exception
|
||||
class LmsException : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
LmsException(std::string_view error = "") : std::runtime_error{ std::string{ error } } {}
|
||||
};
|
||||
}
|
||||
+5
-3
@@ -23,7 +23,9 @@
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#include "utils/IResourceHandler.hpp"
|
||||
|
||||
std::unique_ptr<IResourceHandler> createFileResourceHandler(const std::filesystem::path& path, std::string_view mimeType);
|
||||
#include "core/IResourceHandler.hpp"
|
||||
|
||||
namespace lms
|
||||
{
|
||||
std::unique_ptr<IResourceHandler> createFileResourceHandler(const std::filesystem::path& path, std::string_view mimeType);
|
||||
}
|
||||
+24
-22
@@ -23,32 +23,34 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/Exception.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
|
||||
class ChildProcessException : public LmsException
|
||||
namespace lms::core
|
||||
{
|
||||
public:
|
||||
using LmsException::LmsException;
|
||||
};
|
||||
class ChildProcessException : public LmsException
|
||||
{
|
||||
public:
|
||||
using LmsException::LmsException;
|
||||
};
|
||||
|
||||
class IChildProcess
|
||||
{
|
||||
public:
|
||||
using Args = std::vector<std::string>;
|
||||
class IChildProcess
|
||||
{
|
||||
public:
|
||||
using Args = std::vector<std::string>;
|
||||
|
||||
virtual ~IChildProcess() = default;
|
||||
virtual ~IChildProcess() = default;
|
||||
|
||||
enum class ReadResult
|
||||
{
|
||||
Success,
|
||||
Error,
|
||||
EndOfFile,
|
||||
};
|
||||
enum class ReadResult
|
||||
{
|
||||
Success,
|
||||
Error,
|
||||
EndOfFile,
|
||||
};
|
||||
|
||||
using ReadCallback = std::function<void(ReadResult, std::size_t)>;
|
||||
virtual void asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) = 0;
|
||||
|
||||
virtual std::size_t readSome(std::byte* data, std::size_t bufferSize) = 0;
|
||||
virtual bool finished() const = 0;
|
||||
};
|
||||
using ReadCallback = std::function<void(ReadResult, std::size_t)>;
|
||||
virtual void asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) = 0;
|
||||
|
||||
virtual std::size_t readSome(std::byte* data, std::size_t bufferSize) = 0;
|
||||
virtual bool finished() const = 0;
|
||||
};
|
||||
}
|
||||
+9
-8
@@ -24,14 +24,15 @@
|
||||
|
||||
#include "IChildProcess.hpp"
|
||||
|
||||
class IChildProcessManager
|
||||
namespace lms::core
|
||||
{
|
||||
public:
|
||||
virtual ~IChildProcessManager() = default;
|
||||
|
||||
virtual std::unique_ptr<IChildProcess> spawnChildProcess(const std::filesystem::path& path, const IChildProcess::Args& args) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IChildProcessManager> createChildProcessManager(boost::asio::io_service& ioService);
|
||||
class IChildProcessManager
|
||||
{
|
||||
public:
|
||||
virtual ~IChildProcessManager() = default;
|
||||
|
||||
virtual std::unique_ptr<IChildProcess> spawnChildProcess(const std::filesystem::path& path, const IChildProcess::Args& args) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IChildProcessManager> createChildProcessManager(boost::asio::io_service& ioService);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 <functional>
|
||||
#include <string_view>
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
// Used to get config values from configuration files
|
||||
class IConfig
|
||||
{
|
||||
public:
|
||||
virtual ~IConfig() = default;
|
||||
|
||||
// Default values are returned in case of setting not found
|
||||
virtual std::string_view getString(std::string_view setting, std::string_view def = "") = 0;
|
||||
virtual void visitStrings(std::string_view setting, std::function<void(std::string_view)> _func, std::initializer_list<std::string_view> def = {}) = 0;
|
||||
virtual std::filesystem::path getPath(std::string_view setting, const std::filesystem::path& def = std::filesystem::path()) = 0;
|
||||
virtual unsigned long getULong(std::string_view setting, unsigned long def = 0) = 0;
|
||||
virtual long getLong(std::string_view setting, long def = 0) = 0;
|
||||
virtual bool getBool(std::string_view setting, bool def = false) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IConfig> createConfig(const std::filesystem::path& p);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2013 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 <sstream>
|
||||
|
||||
#include "core/String.hpp"
|
||||
#include "Service.hpp"
|
||||
|
||||
namespace lms::core::logging
|
||||
{
|
||||
enum class Severity
|
||||
{
|
||||
FATAL,
|
||||
ERROR,
|
||||
WARNING,
|
||||
INFO,
|
||||
DEBUG,
|
||||
};
|
||||
|
||||
enum class Module
|
||||
{
|
||||
API_SUBSONIC,
|
||||
AUTH,
|
||||
AV,
|
||||
CHILDPROCESS,
|
||||
COVER,
|
||||
DB,
|
||||
DBUPDATER,
|
||||
FEATURE,
|
||||
FEEDBACK,
|
||||
HTTP,
|
||||
MAIN,
|
||||
METADATA,
|
||||
REMOTE,
|
||||
SCROBBLING,
|
||||
SERVICE,
|
||||
RECOMMENDATION,
|
||||
TRANSCODING,
|
||||
UI,
|
||||
UTILS,
|
||||
};
|
||||
|
||||
const char* getModuleName(Module mod);
|
||||
const char* getSeverityName(Severity sev);
|
||||
|
||||
class ILogger;
|
||||
class Log
|
||||
{
|
||||
public:
|
||||
Log(ILogger& logger, Module module, Severity severity);
|
||||
~Log();
|
||||
|
||||
Module getModule() const { return _module; }
|
||||
Severity getSeverity() const { return _severity; }
|
||||
std::string getMessage() const;
|
||||
|
||||
std::ostringstream& getOstream() { return _oss; }
|
||||
|
||||
private:
|
||||
Log(const Log&) = delete;
|
||||
Log& operator=(const Log&) = delete;
|
||||
|
||||
ILogger& _logger;
|
||||
Module _module;
|
||||
Severity _severity;
|
||||
std::ostringstream _oss;
|
||||
};
|
||||
|
||||
class ILogger
|
||||
{
|
||||
public:
|
||||
virtual ~ILogger() = default;
|
||||
|
||||
virtual bool isSeverityActive(Severity severity) const = 0;
|
||||
virtual void processLog(const Log& log) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#define LMS_LOG(module, severity, message) \
|
||||
do \
|
||||
{ \
|
||||
if (auto* logger_ {::lms::core::Service<::lms::core::logging::ILogger>::get()}; logger_ && logger_->isSeverityActive(::lms::core::logging::Severity::severity)) \
|
||||
::lms::core::logging::Log{ *logger_, ::lms::core::logging::Module::module, ::lms::core::logging::Severity::severity }.getOstream() << message; \
|
||||
} while(0)
|
||||
+16
-13
@@ -23,20 +23,23 @@
|
||||
#include <thread>
|
||||
#include <boost/asio/io_service.hpp>
|
||||
|
||||
class IOContextRunner
|
||||
namespace lms::core
|
||||
{
|
||||
public:
|
||||
IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount, std::string_view name);
|
||||
~IOContextRunner();
|
||||
class IOContextRunner
|
||||
{
|
||||
public:
|
||||
IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount, std::string_view name);
|
||||
~IOContextRunner();
|
||||
|
||||
void stop();
|
||||
std::size_t getThreadCount() const;
|
||||
void stop();
|
||||
std::size_t getThreadCount() const;
|
||||
|
||||
private:
|
||||
IOContextRunner(const IOContextRunner&) = delete;
|
||||
IOContextRunner& operator=(const IOContextRunner&) = delete;
|
||||
private:
|
||||
IOContextRunner(const IOContextRunner&) = delete;
|
||||
IOContextRunner& operator=(const IOContextRunner&) = delete;
|
||||
|
||||
boost::asio::io_service& _ioService;
|
||||
std::optional<boost::asio::io_service::work> _work;
|
||||
std::vector<std::thread> _threads;
|
||||
};
|
||||
boost::asio::io_service& _ioService;
|
||||
std::optional<boost::asio::io_service::work> _work;
|
||||
std::vector<std::thread> _threads;
|
||||
};
|
||||
}
|
||||
+7
-4
@@ -22,13 +22,16 @@
|
||||
#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
|
||||
// TODO, move elsewhere
|
||||
namespace lms
|
||||
{
|
||||
// Helper class to serve a resource (must be saved as continuation data if not complete)
|
||||
class IResourceHandler
|
||||
{
|
||||
public:
|
||||
virtual ~IResourceHandler() = default;
|
||||
|
||||
[[nodiscard]] virtual Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) = 0;
|
||||
virtual void abort() = 0;
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
+4
-4
@@ -34,16 +34,16 @@
|
||||
#define LMS_CONCAT(x, y) LMS_CONCAT_IMPL(x, y)
|
||||
|
||||
#if LMS_SUPPORT_TRACING
|
||||
#define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME) ::tracing::ScopedTrace LMS_CONCAT(ScopedTrace_, __LINE__){ CATEGORY, LEVEL, NAME }
|
||||
#define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME) ::lms::core::tracing::ScopedTrace LMS_CONCAT(ScopedTrace_, __LINE__){ CATEGORY, LEVEL, NAME }
|
||||
|
||||
#else
|
||||
#define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME) (void)0
|
||||
#endif
|
||||
|
||||
#define LMS_SCOPED_TRACE_OVERVIEW(CATEGORY, NAME) LMS_SCOPED_TRACE(CATEGORY, ::tracing::Level::Overview, NAME)
|
||||
#define LMS_SCOPED_TRACE_DETAILED(CATEGORY, NAME) LMS_SCOPED_TRACE(CATEGORY, ::tracing::Level::Detailed, NAME)
|
||||
#define LMS_SCOPED_TRACE_OVERVIEW(CATEGORY, NAME) LMS_SCOPED_TRACE(CATEGORY, ::lms::core::tracing::Level::Overview, NAME)
|
||||
#define LMS_SCOPED_TRACE_DETAILED(CATEGORY, NAME) LMS_SCOPED_TRACE(CATEGORY, ::lms::core::tracing::Level::Detailed, NAME)
|
||||
|
||||
namespace tracing
|
||||
namespace lms::core::tracing
|
||||
{
|
||||
using clock = std::chrono::steady_clock;
|
||||
|
||||
@@ -25,30 +25,30 @@
|
||||
|
||||
#include "Exception.hpp"
|
||||
|
||||
namespace Zip
|
||||
// TODO, move elsewhere?
|
||||
namespace lms::zip
|
||||
{
|
||||
struct Entry
|
||||
{
|
||||
std::string fileName;
|
||||
std::filesystem::path filePath;
|
||||
};
|
||||
using EntryContainer = std::vector<Entry>;
|
||||
struct Entry
|
||||
{
|
||||
std::string fileName;
|
||||
std::filesystem::path filePath;
|
||||
};
|
||||
using EntryContainer = std::vector<Entry>;
|
||||
|
||||
class Exception : public LmsException
|
||||
{
|
||||
using LmsException::LmsException;
|
||||
};
|
||||
class Exception : public core::LmsException
|
||||
{
|
||||
using core::LmsException::LmsException;
|
||||
};
|
||||
|
||||
class IZipper
|
||||
{
|
||||
public:
|
||||
virtual ~IZipper() = default;
|
||||
class IZipper
|
||||
{
|
||||
public:
|
||||
virtual ~IZipper() = default;
|
||||
|
||||
virtual std::uint64_t writeSome(std::ostream& output) = 0;
|
||||
virtual bool isComplete() const = 0;
|
||||
virtual void abort() = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IZipper> createArchiveZipper(const EntryContainer& entries);
|
||||
} // namespace Zip
|
||||
virtual std::uint64_t writeSome(std::ostream& output) = 0;
|
||||
virtual bool isComplete() const = 0;
|
||||
virtual void abort() = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IZipper> createArchiveZipper(const EntryContainer& entries);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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 <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
class LiteralString
|
||||
{
|
||||
public:
|
||||
constexpr LiteralString() noexcept = default;
|
||||
template<std::size_t N>
|
||||
constexpr LiteralString(const char(&str)[N]) noexcept : _str{ str, N - 1 } { static_assert(N > 0); }
|
||||
|
||||
constexpr const char* c_str() const noexcept { return _str.data(); }
|
||||
constexpr std::size_t length() const noexcept { return _str.length(); }
|
||||
constexpr std::string_view str() const noexcept { return _str; }
|
||||
constexpr auto operator<=>(const LiteralString& other) const = default;
|
||||
|
||||
private:
|
||||
std::string_view _str;
|
||||
};
|
||||
}
|
||||
|
||||
namespace std
|
||||
{
|
||||
template<>
|
||||
struct hash<lms::core::LiteralString>
|
||||
{
|
||||
size_t operator()(const lms::core::LiteralString& str) const
|
||||
{
|
||||
return hash<std::string_view>{}(str.str());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
struct LiteralStringHash
|
||||
{
|
||||
using hash_type = std::hash<std::string_view>;
|
||||
using is_transparent = void;
|
||||
|
||||
[[nodiscard]] size_t operator()(const LiteralString& str) const {
|
||||
return hash_type{}(str.str());
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t operator()(std::string_view str) const {
|
||||
return hash_type{}(str);
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t operator()(const std::string& str) const {
|
||||
return hash_type{}(str);
|
||||
}
|
||||
};
|
||||
|
||||
struct LiteralStringEqual
|
||||
{
|
||||
using is_transparent = void;
|
||||
|
||||
[[nodiscard]] bool operator()(const LiteralString& lhs, const LiteralString& rhs) const {
|
||||
return lhs == rhs;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator()(const LiteralString& lhs, const std::string& rhs) const {
|
||||
return lhs.str() == rhs;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator()(const LiteralString& lhs, std::string_view rhs) const {
|
||||
return lhs.str() == rhs;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator()(const std::string_view& lhs, LiteralString rhs) const {
|
||||
return lhs == rhs.str();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator()(const std::string& lhs, const LiteralString& rhs) const {
|
||||
return lhs == rhs.str();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
#include <Wt/WDateTime.h>
|
||||
|
||||
namespace PathUtils
|
||||
namespace lms::core::pathUtils
|
||||
{
|
||||
std::uint32_t computeCrc32(const std::filesystem::path& p);
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace PathUtils
|
||||
longestCommonPath = *first++;
|
||||
|
||||
while (first != last)
|
||||
longestCommonPath = PathUtils::getLongestCommonPath(*first++, longestCommonPath);
|
||||
longestCommonPath = core::pathUtils::getLongestCommonPath(*first++, longestCommonPath);
|
||||
|
||||
return longestCommonPath;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 <algorithm>
|
||||
#include <random>
|
||||
|
||||
namespace lms::core::random
|
||||
{
|
||||
using RandGenerator = std::mt19937;
|
||||
RandGenerator& getRandGenerator();
|
||||
|
||||
RandGenerator createSeededGenerator(uint_fast32_t seed);
|
||||
|
||||
template <typename T>
|
||||
T getRandom(T min, T max)
|
||||
{
|
||||
std::uniform_int_distribution<> dist{ min, max };
|
||||
return dist(getRandGenerator());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T getRealRandom(T min, T max)
|
||||
{
|
||||
std::uniform_real_distribution<> dist{ min, max };
|
||||
return dist(getRandGenerator());
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
void shuffleContainer(Container& container)
|
||||
{
|
||||
std::shuffle(std::begin(container), std::end(container), getRandGenerator());
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
typename Container::const_iterator pickRandom(const Container& container)
|
||||
{
|
||||
if (container.empty())
|
||||
return std::end(container);
|
||||
|
||||
return std::next(std::begin(container), getRandom(0, static_cast<int>(container.size() - 1)));
|
||||
}
|
||||
}
|
||||
+19
-17
@@ -24,26 +24,28 @@
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
// API compatible with shared_mutex
|
||||
class RecursiveSharedMutex
|
||||
namespace lms::core
|
||||
{
|
||||
public:
|
||||
void lock();
|
||||
void unlock();
|
||||
// API compatible with shared_mutex
|
||||
class RecursiveSharedMutex
|
||||
{
|
||||
public:
|
||||
void lock();
|
||||
void unlock();
|
||||
|
||||
void lock_shared();
|
||||
void unlock_shared();
|
||||
void lock_shared();
|
||||
void unlock_shared();
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool isSharedLocked();
|
||||
bool isUniqueLocked();
|
||||
bool isSharedLocked();
|
||||
bool isUniqueLocked();
|
||||
#endif // NDEBUG
|
||||
private:
|
||||
std::shared_mutex _mutex;
|
||||
std::thread::id _uniqueOwner;
|
||||
std::size_t _uniqueCount{};
|
||||
|
||||
std::mutex _sharedCountMutex;
|
||||
std::unordered_map<std::thread::id, std::size_t> _sharedCounts;
|
||||
};
|
||||
private:
|
||||
std::shared_mutex _mutex;
|
||||
std::thread::id _uniqueOwner;
|
||||
std::size_t _uniqueCount{};
|
||||
|
||||
std::mutex _sharedCountMutex;
|
||||
std::unordered_map<std::thread::id, std::size_t> _sharedCounts;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 <cassert>
|
||||
#include <memory>
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
template <typename Class>
|
||||
class Service
|
||||
{
|
||||
public:
|
||||
Service() = default;
|
||||
Service(std::unique_ptr<Class> service)
|
||||
{
|
||||
assign(std::move(service));
|
||||
}
|
||||
|
||||
~Service()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
Service(const Service&) = delete;
|
||||
Service(Service&&) = delete;
|
||||
Service& operator=(const Service&) = delete;
|
||||
Service& operator=(Service&&) = delete;
|
||||
|
||||
Class* operator->() const
|
||||
{
|
||||
return Service<Class>::get();
|
||||
}
|
||||
|
||||
Class& operator*() const
|
||||
{
|
||||
return *Service<Class>::get();
|
||||
}
|
||||
|
||||
static Class* get() { return _service.get(); }
|
||||
static bool exists() { return _service.get(); }
|
||||
|
||||
template <typename SubClass>
|
||||
static Class& assign(std::unique_ptr<SubClass> service)
|
||||
{
|
||||
assert(!_service);
|
||||
_service = std::move(service);
|
||||
return *get();
|
||||
}
|
||||
|
||||
private:
|
||||
static void clear() { _service.reset(); }
|
||||
|
||||
static inline std::unique_ptr<Class> _service;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 "core/EnumSet.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace lms::core::logging
|
||||
{
|
||||
class StreamLogger final : public ILogger
|
||||
{
|
||||
public:
|
||||
static constexpr EnumSet<Severity> allSeverities{ Severity::FATAL, Severity::ERROR, Severity::WARNING, Severity::INFO, Severity::DEBUG };
|
||||
static constexpr EnumSet<Severity> defaultSeverities{ Severity::FATAL, Severity::ERROR, Severity::WARNING, Severity::INFO };
|
||||
|
||||
StreamLogger(std::ostream& oss, EnumSet<Severity> severities = defaultSeverities);
|
||||
|
||||
bool isSeverityActive(Severity severity) const override { return _severities.contains(severity); }
|
||||
void processLog(const Log& log) override;
|
||||
|
||||
private:
|
||||
std::ostream& _os;
|
||||
const EnumSet<Severity> _severities;
|
||||
};
|
||||
}
|
||||
@@ -36,7 +36,7 @@ namespace Wt
|
||||
class WDateTime;
|
||||
}
|
||||
|
||||
namespace StringUtils
|
||||
namespace lms::core::stringUtils
|
||||
{
|
||||
[[nodiscard]] std::vector<std::string_view> splitString(std::string_view string, char separator);
|
||||
[[nodiscard]] std::vector<std::string_view> splitString(std::string_view string, std::string_view separator);
|
||||
@@ -100,5 +100,4 @@ namespace StringUtils
|
||||
|
||||
[[nodiscard]] std::string toISO8601String(const Wt::WDateTime& dateTime);
|
||||
[[nodiscard]] std::string toISO8601String(const Wt::WDate& date);
|
||||
|
||||
} // StringUtils
|
||||
}
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace Utils
|
||||
namespace lms::core
|
||||
{
|
||||
namespace Details
|
||||
namespace details
|
||||
{
|
||||
template<int... Is>
|
||||
struct Seq { };
|
||||
@@ -44,8 +44,6 @@ namespace Utils
|
||||
template<typename... Ts, typename Func>
|
||||
void forEachTypeInTuple(std::tuple<Ts...> const& t, Func f)
|
||||
{
|
||||
Details::forEachTypeInTuple(t, f, Details::GenSeq<sizeof...(Ts)>());
|
||||
details::forEachTypeInTuple(t, f, details::GenSeq<sizeof...(Ts)>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -23,27 +23,29 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "utils/String.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
class UUID
|
||||
namespace lms::core
|
||||
{
|
||||
public:
|
||||
static std::optional<UUID> fromString(std::string_view str);
|
||||
static UUID generate();
|
||||
class UUID
|
||||
{
|
||||
public:
|
||||
static std::optional<UUID> fromString(std::string_view str);
|
||||
static UUID generate();
|
||||
|
||||
std::string_view getAsString() const { return _value; }
|
||||
std::string_view getAsString() const { return _value; }
|
||||
|
||||
bool operator<=>(const UUID&) const = default;
|
||||
bool operator<=>(const core::UUID&) const = default;
|
||||
|
||||
private:
|
||||
UUID(std::string_view value);
|
||||
std::string _value;
|
||||
};
|
||||
private:
|
||||
UUID(std::string_view value);
|
||||
std::string _value;
|
||||
};
|
||||
}
|
||||
|
||||
namespace StringUtils
|
||||
namespace lms::core::stringUtils
|
||||
{
|
||||
template <>
|
||||
std::optional<UUID>
|
||||
readAs(std::string_view str);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,8 @@
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
namespace Utils
|
||||
namespace lms::core::utils
|
||||
{
|
||||
|
||||
template<class T, class Compare = std::less<>>
|
||||
constexpr T clamp(T v, T lo, T hi, Compare comp = {})
|
||||
{
|
||||
@@ -39,5 +38,4 @@ namespace Utils
|
||||
if (std::find(std::cbegin(container), std::cend(container), val) == std::cend(container))
|
||||
container.push_back(val);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+13
-10
@@ -21,17 +21,20 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
class WtLogger final : public ILogger
|
||||
namespace lms::core::logging
|
||||
{
|
||||
public:
|
||||
WtLogger(Severity minSeverity);
|
||||
class WtLogger final : public ILogger
|
||||
{
|
||||
public:
|
||||
WtLogger(Severity minSeverity);
|
||||
|
||||
static std::string computeLogConfig(Severity minSeverity);
|
||||
static std::string computeLogConfig(Severity minSeverity);
|
||||
|
||||
private:
|
||||
bool isSeverityActive(Severity severity) const override;
|
||||
void processLog(const Log& log) override;
|
||||
const Severity _minSeverity;
|
||||
};
|
||||
private:
|
||||
bool isSeverityActive(Severity severity) const override;
|
||||
void processLog(const Log& log) override;
|
||||
const Severity _minSeverity;
|
||||
};
|
||||
}
|
||||
+6
-4
@@ -21,8 +21,10 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "utils/IResourceHandler.hpp"
|
||||
#include "utils/IZipper.hpp"
|
||||
|
||||
std::unique_ptr<IResourceHandler> createZipperResourceHandler(std::unique_ptr<Zip::IZipper> zipper);
|
||||
#include "core/IResourceHandler.hpp"
|
||||
#include "core/IZipper.hpp"
|
||||
|
||||
namespace lms::zip
|
||||
{
|
||||
std::unique_ptr<IResourceHandler> createZipperResourceHandler(std::unique_ptr<IZipper> zipper);
|
||||
}
|
||||
+25
-26
@@ -25,36 +25,35 @@
|
||||
|
||||
#include <Wt/Http/Message.h>
|
||||
|
||||
namespace Http
|
||||
namespace lms::core::http
|
||||
{
|
||||
struct ClientRequestParameters
|
||||
{
|
||||
enum class Priority
|
||||
{
|
||||
High,
|
||||
Normal,
|
||||
Low,
|
||||
};
|
||||
struct ClientRequestParameters
|
||||
{
|
||||
enum class Priority
|
||||
{
|
||||
High,
|
||||
Normal,
|
||||
Low,
|
||||
};
|
||||
|
||||
Priority priority {Priority::Normal};
|
||||
std::string relativeUrl; // relative to baseUrl used by the client
|
||||
Priority priority{ Priority::Normal };
|
||||
std::string relativeUrl; // relative to baseUrl used by the client
|
||||
|
||||
using OnSuccessFunc = std::function<void(std::string_view msgBody)>;
|
||||
OnSuccessFunc onSuccessFunc;
|
||||
using OnSuccessFunc = std::function<void(std::string_view msgBody)>;
|
||||
OnSuccessFunc onSuccessFunc;
|
||||
|
||||
using OnFailureFunc = std::function<void()>;
|
||||
OnFailureFunc onFailureFunc;
|
||||
};
|
||||
using OnFailureFunc = std::function<void()>;
|
||||
OnFailureFunc onFailureFunc;
|
||||
};
|
||||
|
||||
struct ClientGETRequestParameters final : public ClientRequestParameters
|
||||
{
|
||||
std::vector<Wt::Http::Message::Header> headers;
|
||||
};
|
||||
struct ClientGETRequestParameters final : public ClientRequestParameters
|
||||
{
|
||||
std::vector<Wt::Http::Message::Header> headers;
|
||||
};
|
||||
|
||||
struct ClientPOSTRequestParameters final : public ClientRequestParameters
|
||||
{
|
||||
Wt::Http::Message message;
|
||||
};
|
||||
|
||||
} // namespace Http
|
||||
struct ClientPOSTRequestParameters final : public ClientRequestParameters
|
||||
{
|
||||
Wt::Http::Message message;
|
||||
};
|
||||
|
||||
}
|
||||
+7
-8
@@ -22,19 +22,18 @@
|
||||
#include <string_view>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
|
||||
#include "utils/http/ClientRequestParameters.hpp"
|
||||
#include "core/http/ClientRequestParameters.hpp"
|
||||
|
||||
namespace Http
|
||||
namespace lms::core::http
|
||||
{
|
||||
class IClient
|
||||
{
|
||||
public:
|
||||
virtual ~IClient() = default;
|
||||
public:
|
||||
virtual ~IClient() = default;
|
||||
|
||||
virtual void sendGETRequest(ClientGETRequestParameters&& request) = 0;
|
||||
virtual void sendPOSTRequest(ClientPOSTRequestParameters&& request) = 0;
|
||||
virtual void sendGETRequest(ClientGETRequestParameters&& request) = 0;
|
||||
virtual void sendPOSTRequest(ClientPOSTRequestParameters&& request) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IClient> createClient(boost::asio::io_context& ioContext, std::string_view baseUrl);
|
||||
} // namespace Http
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
include(GoogleTest)
|
||||
|
||||
add_executable(test-utils
|
||||
add_executable(test-core
|
||||
EnumSet.cpp
|
||||
LiteralString.cpp
|
||||
Path.cpp
|
||||
@@ -10,13 +10,13 @@ add_executable(test-utils
|
||||
Utils.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(test-utils PRIVATE
|
||||
lmsutils
|
||||
target_link_libraries(test-core PRIVATE
|
||||
lmscore
|
||||
Threads::Threads
|
||||
GTest::GTest
|
||||
)
|
||||
|
||||
if (NOT CMAKE_CROSSCOMPILING)
|
||||
gtest_discover_tests(test-utils)
|
||||
gtest_discover_tests(test-core)
|
||||
endif()
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 <gtest/gtest.h>
|
||||
|
||||
#include "core/EnumSet.hpp"
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
TEST(EnumSet, ctr)
|
||||
{
|
||||
enum class Foo
|
||||
{
|
||||
One,
|
||||
Two,
|
||||
};
|
||||
|
||||
{
|
||||
constexpr EnumSet<Foo> test{ Foo::One };
|
||||
|
||||
static_assert(!test.empty());
|
||||
static_assert(test.contains(Foo::One));
|
||||
static_assert(!test.contains(Foo::Two));
|
||||
|
||||
EXPECT_TRUE(!test.empty());
|
||||
EXPECT_TRUE(test.contains(Foo::One));
|
||||
EXPECT_FALSE(test.contains(Foo::Two));
|
||||
|
||||
static_assert(test.getBitfield() != 0);
|
||||
}
|
||||
|
||||
{
|
||||
constexpr EnumSet<Foo> test{ Foo::One, Foo::Two };
|
||||
constexpr auto bitfield{ test.getBitfield() };
|
||||
|
||||
EnumSet<Foo> test2;
|
||||
EXPECT_FALSE(test2.contains(Foo::One));
|
||||
EXPECT_FALSE(test2.contains(Foo::Two));
|
||||
|
||||
test2.setBitfield(bitfield);
|
||||
|
||||
EXPECT_TRUE(test2.contains(Foo::One));
|
||||
EXPECT_TRUE(test2.contains(Foo::Two));
|
||||
EXPECT_EQ(test, test2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 <unordered_map>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "core/LiteralString.hpp"
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
TEST(LiteralString, ctr)
|
||||
{
|
||||
{
|
||||
constexpr LiteralString foo{ "abc" };
|
||||
static_assert(foo.length() == 3);
|
||||
static_assert(foo == "abc");
|
||||
static_assert(foo == LiteralString{ "abc" });
|
||||
static_assert(foo < LiteralString{ "abcd" });
|
||||
static_assert(foo > LiteralString{ "aac" });
|
||||
|
||||
EXPECT_EQ(::strlen(foo.c_str()), 3);
|
||||
EXPECT_EQ(foo, LiteralString{ "abc" });
|
||||
EXPECT_GT(foo, LiteralString{ "abb" });
|
||||
EXPECT_LT(foo, LiteralString{ "abcd" });
|
||||
}
|
||||
|
||||
{
|
||||
constexpr LiteralString foo{ "" };
|
||||
static_assert(foo.length() == 0);
|
||||
static_assert(foo == "");
|
||||
static_assert(foo == LiteralString{ "" });
|
||||
static_assert(foo < LiteralString{ "a" });
|
||||
EXPECT_EQ(::strlen(foo.c_str()), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(LiteralString, unordered_map)
|
||||
{
|
||||
{
|
||||
std::unordered_map<LiteralString, int> myMap{ {"abc", 42} };
|
||||
|
||||
EXPECT_TRUE(myMap.contains("abc"));
|
||||
EXPECT_TRUE(myMap.contains(LiteralString{ "abc" }));
|
||||
EXPECT_FALSE(myMap.contains("abcd"));
|
||||
}
|
||||
|
||||
{
|
||||
std::unordered_map<LiteralString, int, LiteralStringHash, LiteralStringEqual> myMap{ {"abc", 42} };
|
||||
EXPECT_TRUE(myMap.contains(std::string{ "abc" }));
|
||||
EXPECT_TRUE(myMap.contains(std::string_view{ "abc" }));
|
||||
EXPECT_FALSE(myMap.contains(std::string{ "abcd" }));
|
||||
EXPECT_FALSE(myMap.contains(std::string_view{ "abcd" }));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 <gtest/gtest.h>
|
||||
|
||||
#include "core/Path.hpp"
|
||||
|
||||
namespace lms::core::pathUtils::tests
|
||||
{
|
||||
TEST(Path, getLongestCommonPath)
|
||||
{
|
||||
struct TestCase
|
||||
{
|
||||
std::filesystem::path path1;
|
||||
std::filesystem::path path2;
|
||||
std::filesystem::path expectedCommonPath;
|
||||
};
|
||||
|
||||
TestCase tests[]
|
||||
{
|
||||
{"foo.txt", "/foo/foo.txt", ""},
|
||||
{"/", "/file.txt", "/"},
|
||||
{"/foo/bar/file1.txt", "/foo/bar/file2.txt", "/foo/bar"},
|
||||
{"/foo/bar/file.txt", "/foo/bar/file.txt", "/foo/bar/file.txt"},
|
||||
{"/dir1/file.txt", "/dir2/file.txt", "/"},
|
||||
{"/prefix/folder/file.txt", "/prefix/folder/subfolder/file.txt", "/prefix/folder"},
|
||||
};
|
||||
|
||||
for (const TestCase& test : tests)
|
||||
{
|
||||
EXPECT_EQ(core::pathUtils::getLongestCommonPath(test.path1, test.path2), test.expectedCommonPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TEST(Path, getLongestCommonPathIterator)
|
||||
{
|
||||
struct TestCase
|
||||
{
|
||||
std::vector<std::filesystem::path> paths;
|
||||
std::filesystem::path expectedCommonPath;
|
||||
};
|
||||
|
||||
TestCase tests[]
|
||||
{
|
||||
{{}, ""},
|
||||
{{"/"}, "/"},
|
||||
{{"/foo", "/bar"}, "/"},
|
||||
{{"/foo/bar/file1.txt", "/foo/bar/file2.txt"}, "/foo/bar"},
|
||||
{{"/foo", "/foo/"}, "/foo"},
|
||||
{{"/foo", "/foo"}, "/foo"},
|
||||
{{"/foo/", "/foo/"}, "/foo/"},
|
||||
{{"/foo/", "/foo/", "/bar"}, "/"},
|
||||
{{"/foo/", "/foo/", "/foo/bar"}, "/foo"},
|
||||
};
|
||||
|
||||
for (const TestCase& test : tests)
|
||||
{
|
||||
EXPECT_EQ(core::pathUtils::getLongestCommonPath(std::cbegin(test.paths), std::cend(test.paths)), test.expectedCommonPath);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Path, isPathInRootPath)
|
||||
{
|
||||
struct TestCase
|
||||
{
|
||||
std::filesystem::path path;
|
||||
std::filesystem::path rootPath;
|
||||
bool expectedResult;
|
||||
};
|
||||
|
||||
TestCase tests[]
|
||||
{
|
||||
{"/file.txt", "/", true},
|
||||
{"/root/folder/file.txt", "/root", true},
|
||||
{"/root/file.txt", "/root", true},
|
||||
{"/root/file.txt", "/root/", true},
|
||||
{"/root", "/root", true},
|
||||
{"/root", "/root/", true},
|
||||
{"/root/", "/root", true},
|
||||
{"/root/", "/root/", true},
|
||||
{"/folder/file.txt", "/root", false},
|
||||
{"/folder/file.txt", "/root/", false},
|
||||
{"", "/root", false},
|
||||
};
|
||||
|
||||
for (const TestCase& test : tests)
|
||||
{
|
||||
EXPECT_EQ(core::pathUtils::isPathInRootPath(test.path, test.rootPath), test.expectedResult) << "Failed: path = " << test.path << ", rootPath = " << test.rootPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 <atomic>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "core/RecursiveSharedMutex.hpp"
|
||||
|
||||
namespace lms::core
|
||||
{
|
||||
TEST(RecursiveSharedMutex, SingleThreaded)
|
||||
{
|
||||
RecursiveSharedMutex mutex;
|
||||
|
||||
{
|
||||
std::unique_lock lock{ mutex };
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock lock{ mutex };
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock lock1{ mutex };
|
||||
std::unique_lock lock2{ mutex };
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock lock1{ mutex };
|
||||
std::shared_lock lock2{ mutex };
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock lock1{ mutex };
|
||||
std::shared_lock lock2{ mutex };
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RecursiveSharedMutex, MultiThreaded)
|
||||
{
|
||||
constexpr std::size_t nbThreads{ 10 };
|
||||
std::vector<std::thread> threads;
|
||||
|
||||
RecursiveSharedMutex mutex;
|
||||
std::atomic<std::size_t> nbUnique{};
|
||||
std::atomic<std::size_t> nbShared{};
|
||||
for (std::size_t i{}; i < nbThreads; ++i)
|
||||
{
|
||||
threads.emplace_back([&]
|
||||
{
|
||||
{
|
||||
std::unique_lock lock{ mutex };
|
||||
|
||||
std::shared_lock lock2{ mutex };
|
||||
|
||||
assert(nbUnique == 0);
|
||||
assert(nbShared == 0);
|
||||
nbUnique++;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
assert(nbUnique == 1);
|
||||
assert(nbShared == 0);
|
||||
nbUnique--;
|
||||
}
|
||||
|
||||
{
|
||||
std::shared_lock lock{ mutex };
|
||||
std::shared_lock lock2{ mutex };
|
||||
|
||||
assert(nbUnique == 0);
|
||||
nbShared++;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(15));
|
||||
|
||||
assert(nbShared > 0);
|
||||
assert(nbShared <= nbThreads);
|
||||
assert(nbUnique == 0);
|
||||
nbShared--;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (std::thread& t : threads)
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 <gtest/gtest.h>
|
||||
|
||||
#include <Wt/WDateTime.h>
|
||||
#include <Wt/WDate.h>
|
||||
#include <Wt/WTime.h>
|
||||
#include "core/String.hpp"
|
||||
|
||||
namespace lms::core::stringUtils::tests
|
||||
{
|
||||
TEST(StringUtils, splitString_charDelim)
|
||||
{
|
||||
struct TestCase
|
||||
{
|
||||
std::string_view input;
|
||||
char delimiter;
|
||||
std::vector<std::string_view> expectedOutput;
|
||||
};
|
||||
|
||||
TestCase tests[]
|
||||
{
|
||||
{"abc", '-', {"abc"}},
|
||||
{"a", '-', {"a"}},
|
||||
{"", '-', {""}},
|
||||
{"a-b-c", '-', {"a", "b", "c"}},
|
||||
{"a|b|c", '|', {"a", "b", "c"}},
|
||||
{"a;b;c", ';', {"a", "b", "c"}},
|
||||
{";b;c", ';', {"", "b", "c"}},
|
||||
{" ;b;c", ';', {" ", "b", "c"}},
|
||||
{" ;;c", ';', {" ", "", "c"}},
|
||||
{" ; ;c", ';', {" ", " ", "c"}},
|
||||
{"a;b; ", ';', {"a", "b", " "}},
|
||||
{"a;b", ';', {"a", "b"}},
|
||||
{";b", ';', {"", "b"}},
|
||||
{";", ';', {"", ""}},
|
||||
{";;", ';', {"", "", ""}},
|
||||
{";;;", ';', {"", "", "", ""}},
|
||||
{";;a;;b;;", ';', {"", "", "a", "", "b", "", ""}},
|
||||
{"a b", ' ', {"a", "b"}},
|
||||
{"", ' ', {""}},
|
||||
{"a-b|c", '-', {"a","b|c"}},
|
||||
{"a|b-c", '-', {"a|b", "c"}},
|
||||
{"test=foo bar", '=', {"test", "foo bar"}},
|
||||
};
|
||||
|
||||
for (const TestCase& test : tests)
|
||||
{
|
||||
const std::vector<std::string_view> res{ splitString(test.input, test.delimiter) };
|
||||
EXPECT_EQ(res, test.expectedOutput) << "Input = '" << test.input << "', delims = '" << test.delimiter << "'";
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StringUtils, splitString_stringDelim)
|
||||
{
|
||||
struct TestCase
|
||||
{
|
||||
std::string_view input;
|
||||
std::string_view delimiter;
|
||||
std::vector<std::string_view> expectedOutput;
|
||||
};
|
||||
|
||||
TestCase tests[]
|
||||
{
|
||||
{"abc", "", {"abc"}},
|
||||
{"abc", "-", {"abc"}},
|
||||
{"abc", "b", {"a", "c"}},
|
||||
{"ab/cd", "/", {"ab", "cd"}},
|
||||
{"ab/cd", "/ ", {"ab/cd"}},
|
||||
{"ab/cd", " /", {"ab/cd"}},
|
||||
{"ab /cd", " /", {"ab", "cd"}},
|
||||
{"ab/ cd", "/ ", {"ab", "cd"}},
|
||||
{"ab / cd", " / ", {"ab", "cd"}},
|
||||
{"ab/cd", " / ", {"ab/cd"}},
|
||||
{"ab/cd / ", " / ", {"ab/cd", ""}},
|
||||
};
|
||||
|
||||
for (const TestCase& test : tests)
|
||||
{
|
||||
const std::vector<std::string_view> res{ splitString(test.input, test.delimiter) };
|
||||
EXPECT_EQ(res, test.expectedOutput) << "Input = '" << test.input << "', delims = '" << test.delimiter << "'";
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StringUtils, joinStrings)
|
||||
{
|
||||
struct TestCase
|
||||
{
|
||||
std::vector<std::string_view> input;
|
||||
std::string delimiter;
|
||||
std::string expectedOutput;
|
||||
};
|
||||
|
||||
TestCase tests[]
|
||||
{
|
||||
{{"a", "b", "c"}, "-", "a-b-c"},
|
||||
{{"a", "b", "c"}, ",", "a,b,c"},
|
||||
{{"a", "b", "c"}, "***", "a***b***c"},
|
||||
{{"a", "", "c"}, "-", "a--c"},
|
||||
{{"", "b", "c"}, "-", "-b-c"},
|
||||
{{"a"}, "-", "a"},
|
||||
{{"a"}, ",", "a"},
|
||||
};
|
||||
|
||||
for (const TestCase& test : tests)
|
||||
{
|
||||
const std::string str{ joinStrings(test.input, test.delimiter) };
|
||||
EXPECT_EQ(str, test.expectedOutput);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StringUtils, escapeAndJoinStrings)
|
||||
{
|
||||
struct TestCase
|
||||
{
|
||||
std::vector<std::string_view> input;
|
||||
char delimiter;
|
||||
char escapeChar;
|
||||
std::string expectedOutput;
|
||||
};
|
||||
|
||||
TestCase tests[]
|
||||
{
|
||||
{{""}, ';', '\\', ""},
|
||||
{{";"}, ';', '\\', "\\;"},
|
||||
{{";;"}, ';', '\\', "\\;\\;"},
|
||||
{{"a;", "b"}, ';', '\\', "a\\;;b"},
|
||||
{{"a;", "b;"}, ';', '\\', "a\\;;b\\;"},
|
||||
};
|
||||
|
||||
for (const TestCase& test : tests)
|
||||
{
|
||||
const std::string str{ escapeAndJoinStrings(test.input, test.delimiter, test.escapeChar) };
|
||||
EXPECT_EQ(str, test.expectedOutput);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StringUtils, splitEscapedStrings)
|
||||
{
|
||||
struct TestCase
|
||||
{
|
||||
std::string input;
|
||||
char delimiter;
|
||||
char escapeChar;
|
||||
std::vector<std::string> expectedOutput;
|
||||
};
|
||||
|
||||
TestCase tests[]
|
||||
{
|
||||
{"", ';', '\\', {}},
|
||||
{"\\;", ';', '\\', {";"}},
|
||||
{"\\;\\;", ';', '\\', {";;"}},
|
||||
{"a\\;;b", ';', '\\', {"a;", "b"}},
|
||||
{"a\\;;b\\;", ';', '\\', {"a;", "b;"}},
|
||||
};
|
||||
|
||||
for (const TestCase& test : tests)
|
||||
{
|
||||
const std::vector<std::string> str{ splitEscapedStrings(test.input, test.delimiter, test.escapeChar) };
|
||||
EXPECT_EQ(str, test.expectedOutput);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StringUtils, escapeJSString)
|
||||
{
|
||||
EXPECT_EQ(jsEscape(""), "");
|
||||
EXPECT_EQ(jsEscape(R"(Test'.mp3)"), R"(Test\'.mp3)");
|
||||
EXPECT_EQ(jsEscape(R"(Test"".mp3)"), R"(Test\"\".mp3)");
|
||||
EXPECT_EQ(jsEscape(R"(\Test\.mp3)"), R"(\\Test\\.mp3)");
|
||||
}
|
||||
|
||||
TEST(StringUtils, escapeJsonString)
|
||||
{
|
||||
EXPECT_EQ(jsonEscape(""), "");
|
||||
EXPECT_EQ(jsonEscape(R"(Test'.mp3)"), R"(Test'.mp3)");
|
||||
EXPECT_EQ(jsonEscape(R"(Test"".mp3)"), R"(Test\"\".mp3)");
|
||||
EXPECT_EQ(jsonEscape(R"(\Test\.mp3)"), R"(\\Test\\.mp3)");
|
||||
}
|
||||
|
||||
TEST(StringUtils, escapeString)
|
||||
{
|
||||
EXPECT_EQ(escapeString("", "*", ' '), "");
|
||||
EXPECT_EQ(escapeString("", "", ' '), "");
|
||||
EXPECT_EQ(escapeString("a", "", ' '), "a");
|
||||
EXPECT_EQ(escapeString("*", "*", '_'), "_*");
|
||||
EXPECT_EQ(escapeString("*a*", "*", '_'), "_*a_*");
|
||||
EXPECT_EQ(escapeString("*a|", "*|", '_'), "_*a_|");
|
||||
EXPECT_EQ(escapeString("**||", "*|", '_'), "_*_*_|_|");
|
||||
EXPECT_EQ(escapeString("one;two", ";", '\\'), "one\\;two");
|
||||
EXPECT_EQ(escapeString("one\\;two", ";", '\\'), "one\\\\;two");
|
||||
EXPECT_EQ(escapeString("one;", ";", '\\'), "one\\;");
|
||||
}
|
||||
|
||||
TEST(StringUtils, unescapeString)
|
||||
{
|
||||
EXPECT_EQ(unescapeString("one\\", '\\'), "one\\");
|
||||
EXPECT_EQ(unescapeString("\\\\one", '\\'), "\\one");
|
||||
EXPECT_EQ(unescapeString("one\\;two", '\\'), "one;two");
|
||||
EXPECT_EQ(unescapeString("one\\\\;two", '\\'), "one\\;two");
|
||||
}
|
||||
|
||||
TEST(StringUtils, readAs_bool)
|
||||
{
|
||||
EXPECT_EQ(readAs<bool>("true"), true);
|
||||
EXPECT_EQ(readAs<bool>("1"), true);
|
||||
EXPECT_EQ(readAs<bool>("false"), false);
|
||||
EXPECT_EQ(readAs<bool>("0"), false);
|
||||
EXPECT_EQ(readAs<bool>("foo"), std::nullopt);
|
||||
EXPECT_EQ(readAs<bool>(""), std::nullopt);
|
||||
}
|
||||
|
||||
TEST(StringUtils, readAs_int)
|
||||
{
|
||||
EXPECT_EQ(readAs<int>("1024"), 1024);
|
||||
EXPECT_EQ(readAs<int>("0"), 0);
|
||||
EXPECT_EQ(readAs<int>("-0"), 0);
|
||||
EXPECT_EQ(readAs<int>("-1"), -1);
|
||||
EXPECT_EQ(readAs<int>(""), std::nullopt);
|
||||
EXPECT_EQ(readAs<int>("a"), std::nullopt);
|
||||
EXPECT_EQ(readAs<int>("-"), std::nullopt);
|
||||
EXPECT_EQ(readAs<int>("1024-1"), 1024);
|
||||
EXPECT_EQ(readAs<int>("1024-"), 1024);
|
||||
EXPECT_EQ(readAs<int>("1024/5"), 1024);
|
||||
EXPECT_EQ(readAs<int>("1024a"), 1024);
|
||||
EXPECT_EQ(readAs<int>("a1024a"), std::nullopt);
|
||||
}
|
||||
|
||||
TEST(StringUtils, capitalize)
|
||||
{
|
||||
struct TestCase
|
||||
{
|
||||
std::string input;
|
||||
std::string expectedOutput;
|
||||
};
|
||||
|
||||
TestCase tests[]
|
||||
{
|
||||
{"", ""},
|
||||
{"C", "C"},
|
||||
{"c", "C"},
|
||||
{" c", " C"},
|
||||
{" cc", " Cc"},
|
||||
{"(c", "(c"},
|
||||
{"1c", "1c"},
|
||||
{"&c", "&c"},
|
||||
{"c c", "C c"}
|
||||
};
|
||||
|
||||
for (const TestCase& test : tests)
|
||||
{
|
||||
std::string str{ test.input };
|
||||
capitalize(str);
|
||||
EXPECT_EQ(str, test.expectedOutput) << " str was '" << test.input << "'";
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Stringutils, date)
|
||||
{
|
||||
const Wt::WDate date{ 2020, 01, 03 };
|
||||
EXPECT_EQ(toISO8601String(date), "2020-01-03");
|
||||
}
|
||||
|
||||
TEST(Stringutils, dateTime)
|
||||
{
|
||||
const Wt::WDateTime dateTime{ Wt::WDate {2020, 01, 03 }, Wt::WTime{9, 8, 11, 75} };
|
||||
EXPECT_EQ(toISO8601String(dateTime), "2020-01-03T09:08:11.075");
|
||||
}
|
||||
|
||||
TEST(StringUtils, stringEndsWith)
|
||||
{
|
||||
EXPECT_TRUE(stringEndsWith("FooBar", "Bar"));
|
||||
EXPECT_TRUE(stringEndsWith("FooBar", ""));
|
||||
EXPECT_TRUE(stringEndsWith("", ""));
|
||||
EXPECT_TRUE(stringEndsWith("FooBar", "ar"));
|
||||
EXPECT_TRUE(stringEndsWith("FooBar", "FooBar"));
|
||||
EXPECT_FALSE(stringEndsWith("FooBar", "1FooBar"));
|
||||
EXPECT_FALSE(stringEndsWith("FooBar", "1FooBar"));
|
||||
EXPECT_FALSE(stringEndsWith("FooBar", "R"));
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,9 @@
|
||||
#include <thread>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "utils/ITraceLogger.hpp"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
|
||||
namespace tracing::tests
|
||||
namespace lms::core::tracing::tests
|
||||
{
|
||||
// not much can be tested with this implementation
|
||||
TEST(TraceLogger, MultipleThreads)
|
||||
@@ -45,7 +45,7 @@ namespace tracing::tests
|
||||
|
||||
std::ostringstream oss;
|
||||
traceLogger->dumpCurrentBuffer(oss);
|
||||
|
||||
|
||||
EXPECT_NE(oss.str().find("MyEventLogged"), std::string::npos);
|
||||
EXPECT_EQ(oss.str().find("MyEventNotLogged"), std::string::npos);
|
||||
}
|
||||
@@ -37,7 +37,7 @@ target_link_libraries(lmsdatabase PRIVATE
|
||||
)
|
||||
|
||||
target_link_libraries(lmsdatabase PUBLIC
|
||||
lmsutils
|
||||
lmscore
|
||||
std::filesystem
|
||||
Wt::Dbo
|
||||
)
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "EnumSetTraits.hpp"
|
||||
#include "IdTypeTraits.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -67,16 +67,16 @@ namespace Database
|
||||
for (std::string_view keyword : params.keywords)
|
||||
{
|
||||
clauses.push_back("a.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'");
|
||||
query.bind("%" + Utils::escapeLikeKeyword(keyword) + "%");
|
||||
query.bind("%" + utils::escapeLikeKeyword(keyword) + "%");
|
||||
}
|
||||
|
||||
for (std::string_view keyword : params.keywords)
|
||||
{
|
||||
sortClauses.push_back("a.sort_name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'");
|
||||
query.bind("%" + Utils::escapeLikeKeyword(keyword) + "%");
|
||||
query.bind("%" + utils::escapeLikeKeyword(keyword) + "%");
|
||||
}
|
||||
|
||||
query.where("(" + StringUtils::joinStrings(clauses, " AND ") + ") OR (" + StringUtils::joinStrings(sortClauses, " AND ") + ")");
|
||||
query.where("(" + core::stringUtils::joinStrings(clauses, " AND ") + ") OR (" + core::stringUtils::joinStrings(sortClauses, " AND ") + ")");
|
||||
}
|
||||
|
||||
if (params.starringUser.isValid())
|
||||
@@ -165,16 +165,16 @@ namespace Database
|
||||
}
|
||||
}
|
||||
|
||||
Artist::Artist(const std::string& name, const std::optional<UUID>& MBID)
|
||||
Artist::Artist(const std::string& name, const std::optional<core::UUID>& MBID)
|
||||
: _name{ std::string(name, 0 , _maxNameLength) },
|
||||
_sortName{ _name },
|
||||
_MBID{ MBID ? MBID->getAsString() : "" }
|
||||
{
|
||||
}
|
||||
|
||||
Artist::pointer Artist::create(Session& session, const std::string& name, const std::optional<UUID>& MBID)
|
||||
Artist::pointer Artist::create(Session& session, const std::string& name, const std::optional<core::UUID>& MBID)
|
||||
{
|
||||
return session.getDboSession().add(std::unique_ptr<Artist> {new Artist{ name, MBID }});
|
||||
return session.getDboSession().add(std::unique_ptr<Artist>{ new Artist{ name, MBID } });
|
||||
}
|
||||
|
||||
std::size_t Artist::getCount(Session& session)
|
||||
@@ -195,7 +195,7 @@ namespace Database
|
||||
return std::vector<Artist::pointer>(res.begin(), res.end());
|
||||
}
|
||||
|
||||
Artist::pointer Artist::find(Session& session, const UUID& mbid)
|
||||
Artist::pointer Artist::find(Session& session, const core::UUID& mbid)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
return session.getDboSession().find<Artist>().where("mbid = ?").bind(std::string{ mbid.getAsString() }).resultValue();
|
||||
@@ -218,7 +218,7 @@ namespace Database
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ session.getDboSession().query<ArtistId>("SELECT DISTINCT a.id FROM artist a WHERE NOT EXISTS(SELECT 1 FROM track t INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id WHERE t.id = t_a_l.track_id)") };
|
||||
return Utils::execQuery<ArtistId>(query, range);
|
||||
return utils::execQuery<ArtistId>(query, range);
|
||||
}
|
||||
|
||||
RangeResults<ArtistId> Artist::findIds(Session& session, const FindParameters& params)
|
||||
@@ -226,7 +226,7 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ createQuery<ArtistId>(session, params) };
|
||||
return Utils::execQuery<ArtistId>(query, params.range);
|
||||
return utils::execQuery<ArtistId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<Artist::pointer> Artist::find(Session& session, const FindParameters& params)
|
||||
@@ -234,7 +234,7 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Artist>>(session, params) };
|
||||
return Utils::execQuery<Artist::pointer>(query, params.range);
|
||||
return utils::execQuery<Artist::pointer>(query, params.range);
|
||||
}
|
||||
|
||||
void Artist::find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func)
|
||||
@@ -242,10 +242,10 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Artist>>(session, params) };
|
||||
Utils::execQuery(query, params.range, func);
|
||||
utils::execQuery(query, params.range, func);
|
||||
}
|
||||
|
||||
RangeResults<ArtistId> Artist::findSimilarArtistIds(EnumSet<TrackArtistLinkType> artistLinkTypes, std::optional<Range> range) const
|
||||
RangeResults<ArtistId> Artist::findSimilarArtistIds(core::EnumSet<TrackArtistLinkType> artistLinkTypes, std::optional<Range> range) const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
@@ -289,7 +289,7 @@ namespace Database
|
||||
for (TrackArtistLinkType type : artistLinkTypes)
|
||||
query.bind(type);
|
||||
|
||||
return Utils::execQuery<ArtistId>(query, range);
|
||||
return utils::execQuery<ArtistId>(query, range);
|
||||
}
|
||||
|
||||
std::vector<std::vector<Cluster::pointer>> Artist::getClusterGroups(std::vector<ClusterTypeId> clusterTypeIds, std::size_t size) const
|
||||
@@ -338,4 +338,4 @@ namespace Database
|
||||
_sortName = std::string(sortName, 0, _maxNameLength);
|
||||
}
|
||||
|
||||
} // namespace Database
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "IdTypeTraits.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
AuthToken::AuthToken(std::string_view value, const Wt::WDateTime& expiry, ObjectPtr<User> user)
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -101,7 +101,7 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
auto query{ createQuery<ClusterId>(session, params) };
|
||||
|
||||
return Utils::execQuery<ClusterId>(query, params.range);
|
||||
return utils::execQuery<ClusterId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<Cluster::pointer> Cluster::find(Session& session, const FindParameters& params)
|
||||
@@ -109,7 +109,7 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Cluster>>(session, params) };
|
||||
|
||||
return Utils::execQuery<Cluster::pointer>(query, params.range);
|
||||
return utils::execQuery<Cluster::pointer>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<ClusterId> Cluster::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
@@ -117,7 +117,7 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
auto query{ session.getDboSession().query<ClusterId>("SELECT DISTINCT c.id FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track_cluster t_c WHERE t_c.cluster_id = c.id)") };
|
||||
|
||||
return Utils::execQuery<ClusterId>(query, range);
|
||||
return utils::execQuery<ClusterId>(query, range);
|
||||
}
|
||||
|
||||
Cluster::pointer Cluster::find(Session& session, ClusterId id)
|
||||
@@ -155,7 +155,7 @@ namespace Database
|
||||
auto query{ session()->query<TrackId>("SELECT t.id FROM track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
|
||||
.where("c.id = ?").bind(getId()) };
|
||||
|
||||
return Utils::execQuery<TrackId>(query, range);
|
||||
return utils::execQuery<TrackId>(query, range);
|
||||
}
|
||||
|
||||
ClusterType::ClusterType(std::string_view name)
|
||||
@@ -185,7 +185,7 @@ namespace Database
|
||||
" LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id")
|
||||
.where("c.id IS NULL") };
|
||||
|
||||
return Utils::execQuery<ClusterTypeId>(query, range);
|
||||
return utils::execQuery<ClusterTypeId>(query, range);
|
||||
}
|
||||
|
||||
RangeResults<ClusterTypeId> ClusterType::findUsed(Session& session, std::optional<Range> range)
|
||||
@@ -196,7 +196,7 @@ namespace Database
|
||||
"SELECT DISTINCT c_t.id from cluster_type c_t")
|
||||
.join("cluster c ON c_t.id = c.cluster_type_id") };
|
||||
|
||||
return Utils::execQuery<ClusterTypeId>(query, range);
|
||||
return utils::execQuery<ClusterTypeId>(query, range);
|
||||
}
|
||||
|
||||
ClusterType::pointer ClusterType::find(Session& session, std::string_view name)
|
||||
@@ -219,7 +219,7 @@ namespace Database
|
||||
|
||||
auto query{ session.getDboSession().query<ClusterTypeId>("SELECT id from cluster_type") };
|
||||
|
||||
return Utils::execQuery<ClusterTypeId>(query, range);
|
||||
return utils::execQuery<ClusterTypeId>(query, range);
|
||||
}
|
||||
|
||||
Cluster::pointer ClusterType::getCluster(const std::string& name) const
|
||||
@@ -244,4 +244,4 @@ namespace Database
|
||||
|
||||
return std::vector<Cluster::pointer>(res.begin(), res.end());
|
||||
}
|
||||
} // namespace Database
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -24,11 +24,11 @@
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/IConfig.hpp"
|
||||
#include "utils/Service.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/IConfig.hpp"
|
||||
#include "core/Service.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -89,7 +89,7 @@ namespace Database
|
||||
LMS_LOG(DB, INFO, "Creating connection pool on file " << dbPath.string());
|
||||
|
||||
auto connection{ std::make_unique<Connection>(dbPath.string()) };
|
||||
if (IConfig * config{ Service<IConfig>::get() })// may not be here on testU
|
||||
if (core::IConfig * config{ core::Service<core::IConfig>::get() })// may not be here on testU
|
||||
connection->setProperty("show-queries", config->getBool("db-show-queries", false) ? "true" : "false");
|
||||
|
||||
auto connectionPool{ std::make_unique<Wt::Dbo::FixedSqlConnectionPool>(std::move(connection), connectionCount) };
|
||||
@@ -138,4 +138,4 @@ namespace Database
|
||||
return _connection.get();
|
||||
}
|
||||
|
||||
} // namespace Database
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -22,22 +22,22 @@
|
||||
#include <type_traits>
|
||||
#include <Wt/Dbo/StdSqlTraits.h>
|
||||
|
||||
#include "utils/EnumSet.hpp"
|
||||
#include "core/EnumSet.hpp"
|
||||
|
||||
namespace Wt::Dbo
|
||||
{
|
||||
template<typename T>
|
||||
struct sql_value_traits<EnumSet<T>, void> : public sql_value_traits<long long>
|
||||
struct sql_value_traits<lms::core::EnumSet<T>, void> : public sql_value_traits<long long>
|
||||
{
|
||||
using ValueType = typename EnumSet<T>::ValueType;
|
||||
using ValueType = typename lms::core::EnumSet<T>::ValueType;
|
||||
static_assert(sizeof(long long) > sizeof(ValueType));
|
||||
|
||||
static void bind(EnumSet<T> v, SqlStatement* statement, int column, int size)
|
||||
static void bind(lms::core::EnumSet<T> v, SqlStatement* statement, int column, int size)
|
||||
{
|
||||
sql_value_traits<long long>::bind(static_cast<long long>(v.getBitfield()), statement, column, size);
|
||||
}
|
||||
|
||||
static bool read(EnumSet<T>& v, SqlStatement* statement, int column, int size)
|
||||
static bool read(lms::core::EnumSet<T>& v, SqlStatement* statement, int column, int size)
|
||||
{
|
||||
long long val;
|
||||
if (sql_value_traits<long long>::read(val, statement, column, size))
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
namespace Wt::Dbo
|
||||
{
|
||||
template<typename T>
|
||||
struct sql_value_traits<T, typename std::enable_if<std::is_base_of<Database::IdType, T>::value>::type>
|
||||
struct sql_value_traits<T, typename std::enable_if<std::is_base_of<lms::db::IdType, T>::value>::type>
|
||||
{
|
||||
static_assert(!std::is_same_v<Database::IdType, T>, "Cannot use IdType, use derived types");
|
||||
static_assert(!std::is_same_v<lms::db::IdType, T>, "Cannot use IdType, use derived types");
|
||||
static const bool specialized = true;
|
||||
|
||||
static std::string type(SqlConnection* conn, int size)
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -205,7 +205,7 @@ namespace Database
|
||||
if (parameters.syncState)
|
||||
query.where("sync_state = ?").bind(*parameters.syncState);
|
||||
|
||||
return Utils::execQuery<ListenId>(query, parameters.range);
|
||||
return utils::execQuery<ListenId>(query, parameters.range);
|
||||
}
|
||||
|
||||
Listen::pointer Listen::find(Session& session, UserId userId, TrackId trackId, ScrobblingBackend backend, const Wt::WDateTime& dateTime)
|
||||
@@ -229,7 +229,7 @@ namespace Database
|
||||
.orderBy("COUNT(a.id) DESC")
|
||||
.groupBy("a.id") };
|
||||
|
||||
return Utils::execQuery<ArtistId>(query, params.range);
|
||||
return utils::execQuery<ArtistId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<ReleaseId> Listen::getTopReleases(Session& session, const StatsFindParameters& params)
|
||||
@@ -239,7 +239,7 @@ namespace Database
|
||||
.orderBy("COUNT(r.id) DESC")
|
||||
.groupBy("r.id") };
|
||||
|
||||
return Utils::execQuery<ReleaseId>(query, params.range);
|
||||
return utils::execQuery<ReleaseId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<TrackId> Listen::getTopTracks(Session& session, const StatsFindParameters& params)
|
||||
@@ -249,7 +249,7 @@ namespace Database
|
||||
.orderBy("COUNT(t.id) DESC")
|
||||
.groupBy("t.id") };
|
||||
|
||||
return Utils::execQuery<TrackId>(query, params.range);
|
||||
return utils::execQuery<TrackId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<ArtistId> Listen::getRecentArtists(Session& session, const ArtistStatsFindParameters& params)
|
||||
@@ -259,7 +259,7 @@ namespace Database
|
||||
.groupBy("a.id").having("l.date_time = MAX(l.date_time)")
|
||||
.orderBy("l.date_time DESC") };
|
||||
|
||||
return Utils::execQuery<ArtistId>(query, params.range);
|
||||
return utils::execQuery<ArtistId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<ReleaseId> Listen::getRecentReleases(Session& session, const StatsFindParameters& params)
|
||||
@@ -269,7 +269,7 @@ namespace Database
|
||||
.groupBy("r.id").having("l.date_time = MAX(l.date_time)")
|
||||
.orderBy("l.date_time DESC") };
|
||||
|
||||
return Utils::execQuery<ReleaseId>(query, params.range);
|
||||
return utils::execQuery<ReleaseId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<TrackId> Listen::getRecentTracks(Session& session, const StatsFindParameters& params)
|
||||
@@ -279,7 +279,7 @@ namespace Database
|
||||
.groupBy("t.id").having("l.date_time = MAX(l.date_time)")
|
||||
.orderBy("l.date_time DESC") };
|
||||
|
||||
return Utils::execQuery<TrackId>(query, params.range);
|
||||
return utils::execQuery<TrackId>(query, params.range);
|
||||
}
|
||||
|
||||
std::size_t Listen::getCount(Session& session, UserId userId, TrackId trackId)
|
||||
@@ -339,4 +339,4 @@ namespace Database
|
||||
.limit(1)
|
||||
.resultValue();
|
||||
}
|
||||
} // namespace Database
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "PathTraits.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
MediaLibrary::MediaLibrary(const std::filesystem::path& p, std::string_view name)
|
||||
: _path{ p },
|
||||
@@ -75,4 +75,4 @@ namespace Database
|
||||
for (const auto& result : results)
|
||||
func(result);
|
||||
}
|
||||
} // namespace Database
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
#include "database/ScanSettings.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -58,7 +58,7 @@ namespace Database
|
||||
}
|
||||
}
|
||||
|
||||
namespace Database::Migration
|
||||
namespace lms::db::Migration
|
||||
{
|
||||
class ScopedNoForeignKeys
|
||||
{
|
||||
@@ -473,14 +473,14 @@ SELECT
|
||||
catch (std::exception& e)
|
||||
{
|
||||
LMS_LOG(DB, ERROR, "Cannot get database version info: " << e.what());
|
||||
throw LmsException{ outdatedMsg };
|
||||
throw core::LmsException{ outdatedMsg };
|
||||
}
|
||||
|
||||
if (version > LMS_DATABASE_VERSION)
|
||||
throw LmsException{ "Server binary outdated, please upgrade it to handle this database" };
|
||||
throw core::LmsException{ "Server binary outdated, please upgrade it to handle this database" };
|
||||
|
||||
if (version < migrationFunctions.begin()->first)
|
||||
throw LmsException{ outdatedMsg };
|
||||
throw core::LmsException{ outdatedMsg };
|
||||
|
||||
while (version < LMS_DATABASE_VERSION)
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include <Wt/Dbo/Dbo.h>
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
class Session;
|
||||
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "SqlQuery.hpp"
|
||||
#include "EnumSetTraits.hpp"
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -76,7 +76,7 @@ namespace Database
|
||||
}
|
||||
|
||||
for (std::string_view keyword : params.keywords)
|
||||
query.where("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + Utils::escapeLikeKeyword(keyword) + "%");
|
||||
query.where("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeLikeKeyword(keyword) + "%");
|
||||
|
||||
if (params.starringUser.isValid())
|
||||
{
|
||||
@@ -227,13 +227,13 @@ namespace Database
|
||||
.resultValue();
|
||||
}
|
||||
|
||||
Release::Release(const std::string& name, const std::optional<UUID>& MBID)
|
||||
Release::Release(const std::string& name, const std::optional<core::UUID>& MBID)
|
||||
: _name{ std::string(name, 0 , _maxNameLength) },
|
||||
_MBID{ MBID ? MBID->getAsString() : "" }
|
||||
{
|
||||
}
|
||||
|
||||
Release::pointer Release::create(Session& session, const std::string& name, const std::optional<UUID>& MBID)
|
||||
Release::pointer Release::create(Session& session, const std::string& name, const std::optional<core::UUID>& MBID)
|
||||
{
|
||||
return session.getDboSession().add(std::unique_ptr<Release> {new Release{ name, MBID }});
|
||||
}
|
||||
@@ -246,13 +246,13 @@ namespace Database
|
||||
.query<Wt::Dbo::ptr<Release>>("SELECT DISTINCT r from release r")
|
||||
.join("track t ON t.release_id = r.id")
|
||||
.where("r.name = ?").bind(std::string(name, 0, _maxNameLength))
|
||||
.where("t.file_path LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind(Utils::escapeLikeKeyword(releaseDirectory.string()) + "%")
|
||||
.where("t.file_path LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind(utils::escapeLikeKeyword(releaseDirectory.string()) + "%")
|
||||
.resultList() };
|
||||
|
||||
return std::vector<Release::pointer>(res.begin(), res.end());
|
||||
}
|
||||
|
||||
Release::pointer Release::find(Session& session, const UUID& mbid)
|
||||
Release::pointer Release::find(Session& session, const core::UUID& mbid)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -290,7 +290,7 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ session.getDboSession().query<ReleaseId>("select r.id from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL") };
|
||||
return Utils::execQuery<ReleaseId>(query, range);
|
||||
return utils::execQuery<ReleaseId>(query, range);
|
||||
}
|
||||
|
||||
RangeResults<Release::pointer> Release::find(Session& session, const FindParameters& params)
|
||||
@@ -298,7 +298,7 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Release>>(session, "DISTINCT r", params) };
|
||||
return Utils::execQuery<pointer>(query, params.range);
|
||||
return utils::execQuery<pointer>(query, params.range);
|
||||
}
|
||||
|
||||
void Release::find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func)
|
||||
@@ -306,7 +306,7 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Release>>(session, "DISTINCT r", params) };
|
||||
Utils::execQuery<pointer>(query, params.range, func);
|
||||
utils::execQuery<pointer>(query, params.range, func);
|
||||
}
|
||||
|
||||
RangeResults<ReleaseId> Release::findIds(Session& session, const FindParameters& params)
|
||||
@@ -314,7 +314,7 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ createQuery<ReleaseId>(session, "DISTINCT r.id", params) };
|
||||
return Utils::execQuery<ReleaseId>(query, params.range);
|
||||
return utils::execQuery<ReleaseId>(query, params.range);
|
||||
}
|
||||
|
||||
std::size_t Release::getCount(Session& session, const FindParameters& params)
|
||||
@@ -602,4 +602,4 @@ namespace Database
|
||||
return res;
|
||||
}
|
||||
|
||||
} // namespace Database
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
#include "database/MediaLibrary.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "utils/String.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
void ScanSettings::init(Session& session)
|
||||
{
|
||||
@@ -46,7 +46,7 @@ namespace Database
|
||||
|
||||
std::vector<std::filesystem::path> ScanSettings::getAudioFileExtensions() const
|
||||
{
|
||||
const auto extensions{ StringUtils::splitString(_audioFileExtensions, ' ') };
|
||||
const auto extensions{ core::stringUtils::splitString(_audioFileExtensions, ' ') };
|
||||
|
||||
std::vector<std::filesystem::path> res(std::cbegin(extensions), std::cend(extensions));
|
||||
std::sort(std::begin(res), std::end(res));
|
||||
@@ -57,22 +57,22 @@ namespace Database
|
||||
|
||||
std::vector<std::string_view> ScanSettings::getExtraTagsToScan() const
|
||||
{
|
||||
return StringUtils::splitString(_extraTagsToScan, ';');
|
||||
return core::stringUtils::splitString(_extraTagsToScan, ';');
|
||||
}
|
||||
|
||||
std::vector<std::string> ScanSettings::getArtistTagDelimiters() const
|
||||
{
|
||||
return StringUtils::splitEscapedStrings(_artistTagDelimiters, ';', '\\');
|
||||
return core::stringUtils::splitEscapedStrings(_artistTagDelimiters, ';', '\\');
|
||||
}
|
||||
|
||||
std::vector<std::string> ScanSettings::getDefaultTagDelimiters() const
|
||||
{
|
||||
return StringUtils::splitEscapedStrings(_defaultTagDelimiters, ';', '\\');
|
||||
return core::stringUtils::splitEscapedStrings(_defaultTagDelimiters, ';', '\\');
|
||||
}
|
||||
|
||||
void ScanSettings::setExtraTagsToScan(const std::vector<std::string_view>& extraTags)
|
||||
{
|
||||
std::string newTagsToScan{ StringUtils::joinStrings(extraTags, ";") };
|
||||
std::string newTagsToScan{ core::stringUtils::joinStrings(extraTags, ";") };
|
||||
if (newTagsToScan != _extraTagsToScan)
|
||||
incScanVersion();
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace Database
|
||||
|
||||
void ScanSettings::setArtistTagDelimiters(std::span<const std::string_view> delimiters)
|
||||
{
|
||||
std::string tagDelimiters{ StringUtils::escapeAndJoinStrings(delimiters, ';', '\\') };
|
||||
std::string tagDelimiters{ core::stringUtils::escapeAndJoinStrings(delimiters, ';', '\\') };
|
||||
if (tagDelimiters != _artistTagDelimiters)
|
||||
{
|
||||
_artistTagDelimiters.swap(tagDelimiters);
|
||||
@@ -91,7 +91,7 @@ namespace Database
|
||||
|
||||
void ScanSettings::setDefaultTagDelimiters(std::span<const std::string_view> delimiters)
|
||||
{
|
||||
std::string tagDelimiters{ StringUtils::escapeAndJoinStrings(delimiters, ';', '\\') };
|
||||
std::string tagDelimiters{ core::stringUtils::escapeAndJoinStrings(delimiters, ';', '\\') };
|
||||
if (tagDelimiters != _defaultTagDelimiters)
|
||||
{
|
||||
_defaultTagDelimiters.swap(tagDelimiters);
|
||||
@@ -103,4 +103,4 @@ namespace Database
|
||||
{
|
||||
_scanVersion += 1;
|
||||
}
|
||||
} // namespace Database
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "utils/Exception.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/ITraceLogger.hpp"
|
||||
#include "core/Exception.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
|
||||
#include "database/Artist.hpp"
|
||||
#include "database/AuthToken.hpp"
|
||||
@@ -47,10 +47,9 @@
|
||||
#include "PathTraits.hpp"
|
||||
#include "Migration.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
|
||||
WriteTransaction::WriteTransaction(RecursiveSharedMutex& mutex, Wt::Dbo::Session& session)
|
||||
WriteTransaction::WriteTransaction(core::RecursiveSharedMutex& mutex, Wt::Dbo::Session& session)
|
||||
: _lock{ mutex },
|
||||
_transaction{ session }
|
||||
{
|
||||
@@ -210,4 +209,4 @@ namespace Database
|
||||
LMS_LOG(DB, INFO, "Database optimizing complete");
|
||||
}
|
||||
|
||||
} // namespace Database
|
||||
} // namespace lms::db
|
||||
|
||||
+127
-138
@@ -23,177 +23,166 @@
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
|
||||
WhereClause&
|
||||
WhereClause::And(const WhereClause& otherClause)
|
||||
namespace lms::db
|
||||
{
|
||||
if (!otherClause._clause.empty()) {
|
||||
if (!_clause.empty())
|
||||
_clause += " AND ";
|
||||
_clause += "(" + otherClause._clause + ")";
|
||||
WhereClause& WhereClause::And(const WhereClause& otherClause)
|
||||
{
|
||||
if (!otherClause._clause.empty()) {
|
||||
if (!_clause.empty())
|
||||
_clause += " AND ";
|
||||
_clause += "(" + otherClause._clause + ")";
|
||||
|
||||
// Add associated bind args
|
||||
for (const std::string& otherBindArg : otherClause._bindArgs)
|
||||
{
|
||||
_bindArgs.push_back(otherBindArg);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
// Add associated bind args
|
||||
for (const std::string& otherBindArg : otherClause._bindArgs)
|
||||
{
|
||||
_bindArgs.push_back(otherBindArg);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
WhereClause&
|
||||
WhereClause::Or(const WhereClause& otherClause)
|
||||
{
|
||||
if (!otherClause._clause.empty()) {
|
||||
if (!_clause.empty())
|
||||
_clause += " OR ";
|
||||
_clause += "(" + otherClause._clause + ")";
|
||||
WhereClause& WhereClause::Or(const WhereClause& otherClause)
|
||||
{
|
||||
if (!otherClause._clause.empty()) {
|
||||
if (!_clause.empty())
|
||||
_clause += " OR ";
|
||||
_clause += "(" + otherClause._clause + ")";
|
||||
|
||||
// Add associated bind args
|
||||
for (const std::string& otherBindArg : otherClause._bindArgs)
|
||||
{
|
||||
_bindArgs.push_back(otherBindArg);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
// Add associated bind args
|
||||
for (const std::string& otherBindArg : otherClause._bindArgs)
|
||||
{
|
||||
_bindArgs.push_back(otherBindArg);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string
|
||||
WhereClause::get() const
|
||||
{
|
||||
if (!_clause.empty())
|
||||
return "WHERE " + _clause;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
std::string WhereClause::get() const
|
||||
{
|
||||
if (!_clause.empty())
|
||||
return "WHERE " + _clause;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
WhereClause&
|
||||
WhereClause::bind(std::string_view bindArg)
|
||||
{
|
||||
assert(_bindArgs.size() < static_cast<std::size_t>(std::count(_clause.begin(), _clause.end(), '?')));
|
||||
WhereClause& WhereClause::bind(std::string_view bindArg)
|
||||
{
|
||||
assert(_bindArgs.size() < static_cast<std::size_t>(std::count(_clause.begin(), _clause.end(), '?')));
|
||||
|
||||
_bindArgs.push_back(std::string{ bindArg });
|
||||
_bindArgs.push_back(std::string{ bindArg });
|
||||
|
||||
return *this;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
InnerJoinClause::InnerJoinClause(const std::string& clause)
|
||||
:_clause(clause)
|
||||
{
|
||||
}
|
||||
InnerJoinClause::InnerJoinClause(const std::string& clause)
|
||||
:_clause(clause)
|
||||
{
|
||||
}
|
||||
|
||||
InnerJoinClause&
|
||||
InnerJoinClause::And(const InnerJoinClause& clause)
|
||||
{
|
||||
if (!_clause.empty())
|
||||
_clause += " ";
|
||||
InnerJoinClause& InnerJoinClause::And(const InnerJoinClause& clause)
|
||||
{
|
||||
if (!_clause.empty())
|
||||
_clause += " ";
|
||||
|
||||
_clause += "INNER JOIN " + clause._clause;
|
||||
_clause += "INNER JOIN " + clause._clause;
|
||||
|
||||
return *this;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
SelectStatement::SelectStatement(const std::string& statement)
|
||||
{
|
||||
And(statement);
|
||||
}
|
||||
SelectStatement::SelectStatement(const std::string& statement)
|
||||
{
|
||||
And(statement);
|
||||
}
|
||||
|
||||
SelectStatement&
|
||||
SelectStatement::And(const std::string& statement)
|
||||
{
|
||||
_statement.push_back(statement);
|
||||
SelectStatement& SelectStatement::And(const std::string& statement)
|
||||
{
|
||||
_statement.push_back(statement);
|
||||
|
||||
std::sort(_statement.begin(), _statement.end());
|
||||
_statement.erase(std::unique(_statement.begin(), _statement.end()), _statement.end());
|
||||
std::sort(_statement.begin(), _statement.end());
|
||||
_statement.erase(std::unique(_statement.begin(), _statement.end()), _statement.end());
|
||||
|
||||
return *this;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::string
|
||||
SelectStatement::get() const
|
||||
{
|
||||
std::string res = "SELECT ";
|
||||
std::string SelectStatement::get() const
|
||||
{
|
||||
std::string res = "SELECT ";
|
||||
|
||||
for (auto it = _statement.begin(); it != _statement.end(); ++it)
|
||||
{
|
||||
if (it != _statement.begin())
|
||||
res += ",";
|
||||
res += *it;
|
||||
}
|
||||
for (auto it = _statement.begin(); it != _statement.end(); ++it)
|
||||
{
|
||||
if (it != _statement.begin())
|
||||
res += ",";
|
||||
res += *it;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
GroupByStatement& GroupByStatement::And(const GroupByStatement& statement)
|
||||
{
|
||||
if (_statement.empty() && !statement._statement.empty())
|
||||
_statement = "GROUP BY ";
|
||||
else if (!_statement.empty() && !statement._statement.empty())
|
||||
_statement += ",";
|
||||
|
||||
_statement += statement._statement;
|
||||
return *this;
|
||||
}
|
||||
|
||||
GroupByStatement&
|
||||
GroupByStatement::And(const GroupByStatement& statement)
|
||||
{
|
||||
if( _statement.empty() && !statement._statement.empty())
|
||||
_statement = "GROUP BY ";
|
||||
else if (!_statement.empty() && !statement._statement.empty())
|
||||
_statement += ",";
|
||||
FromClause::FromClause(const std::string& clause)
|
||||
{
|
||||
_clause.push_back(clause);
|
||||
}
|
||||
|
||||
_statement += statement._statement;
|
||||
return *this;
|
||||
}
|
||||
FromClause& FromClause::And(const FromClause& clause)
|
||||
{
|
||||
for (const std::string& fromClause : clause._clause)
|
||||
{
|
||||
_clause.push_back(fromClause);
|
||||
}
|
||||
|
||||
FromClause::FromClause(const std::string& clause)
|
||||
{
|
||||
_clause.push_back(clause);
|
||||
}
|
||||
std::sort(_clause.begin(), _clause.end());
|
||||
_clause.erase(std::unique(_clause.begin(), _clause.end()), _clause.end());
|
||||
|
||||
FromClause&
|
||||
FromClause::And(const FromClause& clause)
|
||||
{
|
||||
for (const std::string& fromClause : clause._clause)
|
||||
{
|
||||
_clause.push_back(fromClause);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::sort(_clause.begin(), _clause.end());
|
||||
_clause.erase(std::unique(_clause.begin(), _clause.end()), _clause.end());
|
||||
std::string FromClause::get() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
|
||||
return *this;
|
||||
}
|
||||
if (!_clause.empty())
|
||||
{
|
||||
oss << "FROM ";
|
||||
for (auto it = _clause.begin(); it != _clause.end(); ++it) {
|
||||
if (it != _clause.begin())
|
||||
oss << ",";
|
||||
|
||||
std::string
|
||||
FromClause::get() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << *it;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_clause.empty())
|
||||
{
|
||||
oss << "FROM ";
|
||||
for (auto it = _clause.begin(); it != _clause.end(); ++it) {
|
||||
if (it != _clause.begin())
|
||||
oss << ",";
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
oss << *it;
|
||||
}
|
||||
}
|
||||
std::string SqlQuery::get() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
oss << _selectStatement.get();
|
||||
|
||||
std::string
|
||||
SqlQuery::get() const
|
||||
{
|
||||
std::ostringstream oss;
|
||||
if (!_fromClause.get().empty())
|
||||
oss << " " << _fromClause.get();
|
||||
|
||||
oss << _selectStatement.get();
|
||||
if (!_innerJoinClause.get().empty())
|
||||
oss << " " << _innerJoinClause.get();
|
||||
|
||||
if (!_fromClause.get().empty())
|
||||
oss << " " << _fromClause.get();
|
||||
if (!_whereClause.get().empty())
|
||||
oss << " " << _whereClause.get();
|
||||
|
||||
if (!_innerJoinClause.get().empty())
|
||||
oss << " " << _innerJoinClause.get();
|
||||
|
||||
if (!_whereClause.get().empty())
|
||||
oss << " " << _whereClause.get();
|
||||
|
||||
if (!_groupByStatement.get().empty())
|
||||
oss << " " << _groupByStatement.get();
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
if (!_groupByStatement.get().empty())
|
||||
oss << " " << _groupByStatement.get();
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
}
|
||||
@@ -22,102 +22,103 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
|
||||
class WhereClause
|
||||
namespace lms::db
|
||||
{
|
||||
public:
|
||||
WhereClause() {}
|
||||
WhereClause(const std::string& clause) { _clause = clause; }
|
||||
class WhereClause
|
||||
{
|
||||
public:
|
||||
WhereClause() {}
|
||||
WhereClause(const std::string& clause) { _clause = clause; }
|
||||
|
||||
WhereClause& And(const WhereClause& clause);
|
||||
WhereClause& Or(const WhereClause& clause);
|
||||
WhereClause& And(const WhereClause& clause);
|
||||
WhereClause& Or(const WhereClause& clause);
|
||||
|
||||
// Arguments binding (for each '?' in where clause)
|
||||
WhereClause& bind(std::string_view arg);
|
||||
// Arguments binding (for each '?' in where clause)
|
||||
WhereClause& bind(std::string_view arg);
|
||||
|
||||
std::string get() const;
|
||||
const std::vector<std::string>& getBindArgs() const {return _bindArgs;}
|
||||
std::string get() const;
|
||||
const std::vector<std::string>& getBindArgs() const { return _bindArgs; }
|
||||
|
||||
private:
|
||||
std::string _clause; // WHERE clause
|
||||
std::vector<std::string> _bindArgs;
|
||||
};
|
||||
private:
|
||||
std::string _clause; // WHERE clause
|
||||
std::vector<std::string> _bindArgs;
|
||||
};
|
||||
|
||||
class InnerJoinClause
|
||||
{
|
||||
public:
|
||||
InnerJoinClause() {}
|
||||
InnerJoinClause(const std::string& clause);
|
||||
class InnerJoinClause
|
||||
{
|
||||
public:
|
||||
InnerJoinClause() {}
|
||||
InnerJoinClause(const std::string& clause);
|
||||
|
||||
InnerJoinClause& And(const InnerJoinClause& clause);
|
||||
std::string get() const { return _clause;}
|
||||
InnerJoinClause& And(const InnerJoinClause& clause);
|
||||
std::string get() const { return _clause; }
|
||||
|
||||
private:
|
||||
std::string _clause;
|
||||
};
|
||||
private:
|
||||
std::string _clause;
|
||||
};
|
||||
|
||||
class GroupByStatement
|
||||
{
|
||||
public:
|
||||
GroupByStatement() {}
|
||||
GroupByStatement(const std::string& statement) { _statement = statement; }
|
||||
class GroupByStatement
|
||||
{
|
||||
public:
|
||||
GroupByStatement() {}
|
||||
GroupByStatement(const std::string& statement) { _statement = statement; }
|
||||
|
||||
GroupByStatement& And(const GroupByStatement& statement);
|
||||
GroupByStatement& And(const GroupByStatement& statement);
|
||||
|
||||
std::string get() const {return _statement;}
|
||||
std::string get() const { return _statement; }
|
||||
|
||||
private:
|
||||
std::string _statement; // SELECT statement
|
||||
};
|
||||
private:
|
||||
std::string _statement; // SELECT statement
|
||||
};
|
||||
|
||||
class SelectStatement
|
||||
{
|
||||
public:
|
||||
SelectStatement() {};
|
||||
SelectStatement(const std::string& item);
|
||||
class SelectStatement
|
||||
{
|
||||
public:
|
||||
SelectStatement() {};
|
||||
SelectStatement(const std::string& item);
|
||||
|
||||
SelectStatement& And(const std::string& item);
|
||||
SelectStatement& And(const std::string& item);
|
||||
|
||||
std::string get() const;
|
||||
std::string get() const;
|
||||
|
||||
private:
|
||||
std::vector<std::string> _statement;
|
||||
};
|
||||
private:
|
||||
std::vector<std::string> _statement;
|
||||
};
|
||||
|
||||
class FromClause
|
||||
{
|
||||
public:
|
||||
FromClause() {}
|
||||
FromClause(const std::string& clause);
|
||||
class FromClause
|
||||
{
|
||||
public:
|
||||
FromClause() {}
|
||||
FromClause(const std::string& clause);
|
||||
|
||||
FromClause& And(const FromClause& clause);
|
||||
FromClause& And(const FromClause& clause);
|
||||
|
||||
std::string get() const;
|
||||
std::string get() const;
|
||||
|
||||
private:
|
||||
std::vector<std::string> _clause;
|
||||
};
|
||||
private:
|
||||
std::vector<std::string> _clause;
|
||||
};
|
||||
|
||||
class SqlQuery
|
||||
{
|
||||
public:
|
||||
SelectStatement& select() { return _selectStatement;}
|
||||
SelectStatement& select(const std::string& statement) { _selectStatement = SelectStatement(statement); return _selectStatement; }
|
||||
FromClause& from() { return _fromClause; }
|
||||
FromClause& from(const std::string& clause) { _whereClause = WhereClause(clause); return _fromClause; }
|
||||
InnerJoinClause& innerJoin() { return _innerJoinClause; }
|
||||
WhereClause& where() { return _whereClause; }
|
||||
const WhereClause& where() const { return _whereClause; }
|
||||
GroupByStatement& groupBy() { return _groupByStatement; }
|
||||
const GroupByStatement& groupBy() const { return _groupByStatement; }
|
||||
class SqlQuery
|
||||
{
|
||||
public:
|
||||
SelectStatement& select() { return _selectStatement; }
|
||||
SelectStatement& select(const std::string& statement) { _selectStatement = SelectStatement(statement); return _selectStatement; }
|
||||
FromClause& from() { return _fromClause; }
|
||||
FromClause& from(const std::string& clause) { _whereClause = WhereClause(clause); return _fromClause; }
|
||||
InnerJoinClause& innerJoin() { return _innerJoinClause; }
|
||||
WhereClause& where() { return _whereClause; }
|
||||
const WhereClause& where() const { return _whereClause; }
|
||||
GroupByStatement& groupBy() { return _groupByStatement; }
|
||||
const GroupByStatement& groupBy() const { return _groupByStatement; }
|
||||
|
||||
std::string get() const;
|
||||
|
||||
private:
|
||||
SelectStatement _selectStatement; // SELECT statement
|
||||
InnerJoinClause _innerJoinClause; // INNER JOIN
|
||||
FromClause _fromClause; // FROM tables
|
||||
WhereClause _whereClause; // WHERE clause
|
||||
GroupByStatement _groupByStatement; // GROUP BY statement
|
||||
};
|
||||
std::string get() const;
|
||||
|
||||
private:
|
||||
SelectStatement _selectStatement; // SELECT statement
|
||||
InnerJoinClause _innerJoinClause; // INNER JOIN
|
||||
FromClause _fromClause; // FROM tables
|
||||
WhereClause _whereClause; // WHERE clause
|
||||
GroupByStatement _groupByStatement; // GROUP BY statement
|
||||
};
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
StarredArtist::StarredArtist(ObjectPtr<Artist> artist, ObjectPtr<User> user, FeedbackBackend backend)
|
||||
: _backend{ backend }
|
||||
@@ -76,6 +76,6 @@ namespace Database
|
||||
|
||||
void StarredArtist::setDateTime(const Wt::WDateTime& dateTime)
|
||||
{
|
||||
_dateTime = Utils::normalizeDateTime(dateTime);
|
||||
_dateTime = utils::normalizeDateTime(dateTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
StarredRelease::StarredRelease(ObjectPtr<Release> release, ObjectPtr<User> user, FeedbackBackend backend)
|
||||
: _backend{ backend }
|
||||
@@ -76,6 +76,6 @@ namespace Database
|
||||
|
||||
void StarredRelease::setDateTime(const Wt::WDateTime& dateTime)
|
||||
{
|
||||
_dateTime = Utils::normalizeDateTime(dateTime);
|
||||
_dateTime = utils::normalizeDateTime(dateTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
StarredTrack::StarredTrack(ObjectPtr<Track> track, ObjectPtr<User> user, FeedbackBackend backend)
|
||||
: _backend{ backend }
|
||||
@@ -96,11 +96,11 @@ namespace Database
|
||||
if (params.user.isValid())
|
||||
query.where("s_t.user_id = ?").bind(params.user);
|
||||
|
||||
return Utils::execQuery<StarredTrackId>(query, params.range);
|
||||
return utils::execQuery<StarredTrackId>(query, params.range);
|
||||
}
|
||||
|
||||
void StarredTrack::setDateTime(const Wt::WDateTime& dateTime)
|
||||
{
|
||||
_dateTime = Utils::normalizeDateTime(dateTime);
|
||||
_dateTime = utils::normalizeDateTime(dateTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,14 +29,14 @@
|
||||
#include "database/TrackFeatures.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/User.hpp"
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "core/ILogger.hpp"
|
||||
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "SqlQuery.hpp"
|
||||
#include "StringViewTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -49,7 +49,7 @@ namespace Database
|
||||
|
||||
assert(params.keywords.empty() || params.name.empty());
|
||||
for (std::string_view keyword : params.keywords)
|
||||
query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + Utils::escapeLikeKeyword(keyword) + "%");
|
||||
query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeLikeKeyword(keyword) + "%");
|
||||
|
||||
if (!params.name.empty())
|
||||
query.where("t.name = ?").bind(params.name);
|
||||
@@ -235,7 +235,7 @@ namespace Database
|
||||
return session.getDboSession().query<int>("SELECT 1 from track").where("id = ?").bind(id).resultValue() == 1;
|
||||
}
|
||||
|
||||
std::vector<Track::pointer> Track::findByMBID(Session& session, const UUID& mbid)
|
||||
std::vector<Track::pointer> Track::findByMBID(Session& session, const core::UUID& mbid)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -246,7 +246,7 @@ namespace Database
|
||||
return std::vector<Track::pointer>(res.begin(), res.end());
|
||||
}
|
||||
|
||||
std::vector<Track::pointer> Track::findByRecordingMBID(Session& session, const UUID& mbid)
|
||||
std::vector<Track::pointer> Track::findByRecordingMBID(Session& session, const core::UUID& mbid)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -265,7 +265,7 @@ namespace Database
|
||||
// TODO Dbo traits on filesystem
|
||||
auto query{ session.getDboSession().query<QueryResultType>("SELECT id, file_path FROM track") };
|
||||
|
||||
RangeResults<QueryResultType> queryResults{ Utils::execQuery<QueryResultType>(query, range) };
|
||||
RangeResults<QueryResultType> queryResults{ utils::execQuery<QueryResultType>(query, range) };
|
||||
|
||||
RangeResults<PathResult> res;
|
||||
res.range = queryResults.range;
|
||||
@@ -288,7 +288,7 @@ namespace Database
|
||||
auto query{ session.getDboSession().query<TrackId>("SELECT track.id FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)")
|
||||
.orderBy("track.release_id,track.disc_number,track.track_number,track.mbid") };
|
||||
|
||||
return Utils::execQuery<TrackId>(query, range);
|
||||
return utils::execQuery<TrackId>(query, range);
|
||||
}
|
||||
|
||||
RangeResults<TrackId> Track::findIdsWithRecordingMBIDAndMissingFeatures(Session& session, std::optional<Range> range)
|
||||
@@ -299,7 +299,7 @@ namespace Database
|
||||
.where("LENGTH(t.recording_mbid) > 0")
|
||||
.where("NOT EXISTS (SELECT * FROM track_features t_f WHERE t_f.track_id = t.id)") };
|
||||
|
||||
return Utils::execQuery<TrackId>(query, range);
|
||||
return utils::execQuery<TrackId>(query, range);
|
||||
}
|
||||
|
||||
std::vector<Cluster::pointer> Track::getClusters() const
|
||||
@@ -324,7 +324,7 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ createQuery<TrackId>(session, parameters) };
|
||||
return Utils::execQuery<TrackId>(query, parameters.range);
|
||||
return utils::execQuery<TrackId>(query, parameters.range);
|
||||
}
|
||||
|
||||
RangeResults<Track::pointer> Track::find(Session& session, const FindParameters& parameters)
|
||||
@@ -332,7 +332,7 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Track>>(session, parameters) };
|
||||
return Utils::execQuery<Track::pointer>(query, parameters.range);
|
||||
return utils::execQuery<Track::pointer>(query, parameters.range);
|
||||
}
|
||||
|
||||
void Track::find(Session& session, const FindParameters& params, std::function<void(const Track::pointer&)> func)
|
||||
@@ -340,7 +340,7 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Track>>(session, params)};
|
||||
Utils::execQuery(query, params.range, func);
|
||||
utils::execQuery(query, params.range, func);
|
||||
}
|
||||
|
||||
RangeResults<TrackId> Track::findSimilarTrackIds(Session& session, const std::vector<TrackId>& tracks, std::optional<Range> range)
|
||||
@@ -370,7 +370,7 @@ namespace Database
|
||||
for (TrackId trackId : tracks)
|
||||
query.bind(trackId);
|
||||
|
||||
return Utils::execQuery<TrackId>(query, range);
|
||||
return utils::execQuery<TrackId>(query, range);
|
||||
}
|
||||
|
||||
void Track::clearArtistLinks()
|
||||
@@ -400,7 +400,7 @@ namespace Database
|
||||
return _copyrightURL != "" ? std::make_optional<std::string>(_copyrightURL) : std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<Artist::pointer> Track::getArtists(EnumSet<TrackArtistLinkType> linkTypes) const
|
||||
std::vector<Artist::pointer> Track::getArtists(core::EnumSet<TrackArtistLinkType> linkTypes) const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
@@ -435,7 +435,7 @@ namespace Database
|
||||
return std::vector<Artist::pointer>(std::begin(res), std::end(res));
|
||||
}
|
||||
|
||||
std::vector<ArtistId> Track::getArtistIds(EnumSet<TrackArtistLinkType> linkTypes) const
|
||||
std::vector<ArtistId> Track::getArtistIds(core::EnumSet<TrackArtistLinkType> linkTypes) const
|
||||
{
|
||||
assert(self());
|
||||
assert(session());
|
||||
@@ -544,4 +544,4 @@ namespace Database
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Database
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include "IdTypeTraits.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace Database
|
||||
namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -84,19 +84,19 @@ namespace Database
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ createQuery(session, params) };
|
||||
return Utils::execQuery<TrackArtistLinkId>(query, params.range);
|
||||
return utils::execQuery<TrackArtistLinkId>(query, params.range);
|
||||
}
|
||||
|
||||
EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session)
|
||||
core::EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto res{ session.getDboSession().query<TrackArtistLinkType>("SELECT DISTINCT type from track_artist_link").resultList() };
|
||||
|
||||
return EnumSet<TrackArtistLinkType>(std::begin(res), std::end(res));
|
||||
return core::EnumSet<TrackArtistLinkType>(std::begin(res), std::end(res));
|
||||
}
|
||||
|
||||
EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session, ArtistId artistId)
|
||||
core::EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session, ArtistId artistId)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace Database
|
||||
.where("artist_id = ?").bind(artistId)
|
||||
.resultList() };
|
||||
|
||||
return EnumSet<TrackArtistLinkType>(std::begin(res), std::end(res));
|
||||
return core::EnumSet<TrackArtistLinkType>(std::begin(res), std::end(res));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user