Refactored namespaces

This commit is contained in:
emeric
2024-03-12 08:32:08 +01:00
parent 487960b413
commit 4b7c4295ec
501 changed files with 12605 additions and 12631 deletions
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -18,7 +18,7 @@ target_include_directories(lmsav PRIVATE
)
target_link_libraries(lmsav PUBLIC
lmsutils
lmscore
std::filesystem
)
+8 -8
View File
@@ -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
+2 -2
View File
@@ -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);
}
}
+11 -11
View File
@@ -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
+7 -5
View File
@@ -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
{
+2 -2
View File
@@ -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;
+3 -3
View File
@@ -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 lib)
install(TARGETS lmscore DESTINATION lib)
if(BUILD_TESTING)
add_subdirectory(test)
+9
View File
@@ -0,0 +1,9 @@
add_executable(bench-core
TraceLoggerBench.cpp
)
target_link_libraries(bench-core PRIVATE
lmscore
benchmark
)
+55
View File
@@ -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();
+298
View File
@@ -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;
}
}
+80
View File
@@ -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{};
};
}
+209
View File
@@ -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;
};
}
@@ -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);
}
}
@@ -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;
};
}
+130
View File
@@ -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;
}
}
}
+51
View File
@@ -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;
};
}
+137
View File
@@ -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;
}
}
@@ -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{};
};
}
+85
View File
@@ -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();
}
}
+81
View File
@@ -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 };
}
}
+134
View File
@@ -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
{
+91
View File
@@ -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();
}
}
+69
View File
@@ -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;
};
}
+75
View File
@@ -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;
};
}
+240
View File
@@ -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;
}
}
+78
View File
@@ -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;
};
}
@@ -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;
};
}
+193
View File
@@ -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{};
};
}
@@ -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 } } {}
};
}
@@ -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);
}
@@ -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;
};
}
@@ -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);
}
+43
View File
@@ -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);
}
+103
View File
@@ -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)
@@ -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;
};
}
@@ -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;
};
};
}
@@ -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;
}
+60
View File
@@ -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)));
}
}
@@ -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;
};
}
+73
View File
@@ -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);
}
}
}
@@ -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;
};
}
@@ -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,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;
};
}
@@ -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()
+63
View File
@@ -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);
}
}
}
+72
View File
@@ -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" }));
}
}
}
+108
View File
@@ -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;
}
}
}
+105
View File
@@ -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();
}
}
+297
View File
@@ -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);
}
+1 -1
View File
@@ -37,7 +37,7 @@ target_link_libraries(lmsdatabase PRIVATE
)
target_link_libraries(lmsdatabase PUBLIC
lmsutils
lmscore
std::filesystem
Wt::Dbo
)
+16 -16
View File
@@ -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
+1 -1
View File
@@ -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)
+9 -9
View File
@@ -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
+6 -6
View File
@@ -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
+5 -5
View File
@@ -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))
+2 -2
View File
@@ -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)
+9 -9
View File
@@ -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
+2 -2
View File
@@ -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
+7 -7
View File
@@ -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)
{
+1 -1
View File
@@ -21,7 +21,7 @@
#include <Wt/Dbo/Dbo.h>
namespace Database
namespace lms::db
{
class Session;
+12 -12
View File
@@ -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
+10 -10
View File
@@ -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
+6 -7
View File
@@ -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
View File
@@ -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();
}
}
+77 -76
View File
@@ -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
};
}
+2 -2
View File
@@ -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);
}
}
+2 -2
View File
@@ -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);
}
}
+3 -3
View File
@@ -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);
}
}
+15 -15
View File
@@ -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
+6 -6
View File
@@ -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