Compare commits
355 Commits
1.4.0
..
new-groups
| Author | SHA1 | Date | |
|---|---|---|---|
| b54c6d4e04 | |||
| 1e7738bd24 | |||
| bfeee49507 | |||
| 03bad9e6f5 | |||
| f059e6b3c8 | |||
| 053eb1a64e | |||
| fefe550805 | |||
| 521b6fa111 | |||
| bebcb9bfb2 | |||
| 92fcfbecca | |||
| a888b3aff7 | |||
| fb28e613cd | |||
| 707ede7a6f | |||
| 19b7a9702a | |||
| 7753c95906 | |||
| cdf8aa1d69 | |||
| 25d57ce825 | |||
| 8710f0c31b | |||
| ae4f890574 | |||
| 03312b89c7 | |||
| 0aad154c15 | |||
| bc2c951dde | |||
| 89f4ed05b2 | |||
| 6631cd26c5 | |||
| 356687b0be | |||
| 05f834b47d | |||
| 03a0cec60e | |||
| e99c1b442a | |||
| f7039187b2 | |||
| 45229e1e8e | |||
| db8e232bfa | |||
| 08cd7e9d9e | |||
| 31fd33513f | |||
| 2c66e9022f | |||
| 1e1227773a | |||
| 9428c411ae | |||
| 63eccff578 | |||
| cd5804125d | |||
| abeeae4ed5 | |||
| 6e2e005ed7 | |||
| f41cfb8c30 | |||
| 25b454ad0e | |||
| 6b774d3a80 | |||
| bf643b4797 | |||
| 3aa50a2c1a | |||
| f410c9d467 | |||
| d6ec52b25b | |||
| 968cd7172b | |||
| ea0f75756d | |||
| 90fcc074f2 | |||
| 41f5b30f56 | |||
| ceb77a2c85 | |||
| fb517f0b72 | |||
| 82aa59fdf9 | |||
| 5991e5f4ad | |||
| 061ce4c284 | |||
| 7604b03f02 | |||
| 5dd3fd3e13 | |||
| 92a3faaed7 | |||
| 2d0b9da08d | |||
| 449df23a4b | |||
| f213b199fc | |||
| c8438c5867 | |||
| db71461646 | |||
| 1bdb178a3f | |||
| 6a7e29b1eb | |||
| 6f2d8ab6e4 | |||
| ee52f4b8d9 | |||
| 1ea630b326 | |||
| 2c7e7e43d4 | |||
| 7325caa7e8 | |||
| 22d366edc7 | |||
| 863bf0df65 | |||
| 0033f40eca | |||
| b40e1326cc | |||
| 71b2d734bd | |||
| e9445d6568 | |||
| be1a189076 | |||
| 61b6f13bbd | |||
| 58691a23b8 | |||
| 84f4885466 | |||
| 2b38287317 | |||
| c31eb18849 | |||
| 7dd8513f62 | |||
| 54e0571132 | |||
| a608b52269 | |||
| 37b3101561 | |||
| 9a523f1525 | |||
| 9af0a76ed6 | |||
| e2335becd7 | |||
| bb3cc465d7 | |||
| c28f779484 | |||
| 678f5e0a6b | |||
| 902b1f3511 | |||
| 9a5aa1d42b | |||
| ffd5b64177 | |||
| 9784ce9351 | |||
| 660fe3317f | |||
| e75b2342e2 | |||
| 94c7eb2f39 | |||
| f2c5b5d750 | |||
| 8be83fc51d | |||
| a233915064 | |||
| 3c0c48c00c | |||
| 960186d55e | |||
| 6a502e23f2 | |||
| 6ee02a6a17 | |||
| 1ece4bca8b | |||
| 25504abe7e | |||
| c6ed584d97 | |||
| ccd0f7fcfd | |||
| 0cff59328f | |||
| 6605a440bd | |||
| e80afb1145 | |||
| 4fddc87866 | |||
| 644391f58a | |||
| 9b8d1d4d65 | |||
| 835471c2dd | |||
| 4849e17221 | |||
| f240402113 | |||
| 93db485d78 | |||
| 4e8f7c7669 | |||
| 26f90efcbf | |||
| c15595d66e | |||
| fcac375dc3 | |||
| da159c1ba5 | |||
| 5398ca65f2 | |||
| 99e68cd5c4 | |||
| 0d976ec0b8 | |||
| 4249b4fe82 | |||
| 88c4485278 | |||
| da5f5bfebe | |||
| f2e5387318 | |||
| 1c99f093c8 | |||
| 8ca58056d4 | |||
| 065dac2caf | |||
| 912d5e2c3e | |||
| 80370ff73e | |||
| 7d66afc24c | |||
| 75b6d0c677 | |||
| 97e2ded4fb | |||
| 4c7a70bc81 | |||
| c21838d4f6 | |||
| 5ee731bc92 | |||
| 364b3781b1 | |||
| db63248677 | |||
| 4ca32f4453 | |||
| 64106c83eb | |||
| a21a28fede | |||
| 706cecb66c | |||
| 8714761afa | |||
| 5cf3b0b75c | |||
| 5ed16d3756 | |||
| f0b094d7e4 | |||
| ccc3bad705 | |||
| 73cf372a84 | |||
| 818cffd368 | |||
| eaca25ea30 | |||
| 76f22ff337 | |||
| 39d1959dde | |||
| 6e93c5e25a | |||
| 225c006140 | |||
| 83fa79a51d | |||
| b438684f20 | |||
| 9b1de50f1b | |||
| dd4a3f65a4 | |||
| e925c80991 | |||
| be58f24196 | |||
| 1ad3187a86 | |||
| 9544657f3b | |||
| 7fb5d2f8f1 | |||
| a37ba81a4f | |||
| 6cd481e824 | |||
| 1686ce095f | |||
| 6150957689 | |||
| 3b4d519178 | |||
| 35c852b2cd | |||
| bd7ff3e4e0 | |||
| f5d5766644 | |||
| 0a3585f4f8 | |||
| a8bd42fd3f | |||
| 38d6ad4920 | |||
| 2a1f0187ac | |||
| 4f5a4dc993 | |||
| 7d0db0dea0 | |||
| add439de00 | |||
| 4a7a9e7228 | |||
| a78c36c999 | |||
| a80c90f025 | |||
| 28b13093f6 | |||
| 7dcf4a54ef | |||
| 7fdd272d76 | |||
| d5ce71b769 | |||
| fa7a390fe3 | |||
| e49b091b92 | |||
| 8c3d756842 | |||
| 97cf371362 | |||
| f14e5e0148 | |||
| e9388e5e5e | |||
| 669b3ae349 | |||
| bd9cca6fb1 | |||
| 8284748381 | |||
| 7aa37e40b9 | |||
| eab2155384 | |||
| 3e36d70164 | |||
| 3dea4906e1 | |||
| 72abd7e20e | |||
| c60119af00 | |||
| 1e0c9eabe3 | |||
| 3f700e79d3 | |||
| 7a974677fb | |||
| 4c91a7a3bf | |||
| ad51f8118d | |||
| 071a6533e0 | |||
| 9f24a71aed | |||
| 94453894e9 | |||
| 9c7223d016 | |||
| e01811e20e | |||
| 6e6eee39d4 | |||
| 68cfab1ac9 | |||
| 8e16309930 | |||
| b7953f5535 | |||
| ce5d5c5cfa | |||
| b0c8f04a53 | |||
| c59346ea68 | |||
| 14d2578a67 | |||
| f03af7a9bf | |||
| a23002ce66 | |||
| ed7cbd38e8 | |||
| cbb0bd6864 | |||
| 6e1323cc23 | |||
| 006fd6ebec | |||
| abcd35e443 | |||
| 9e964b3ea8 | |||
| bfdf940dbf | |||
| 4015f11718 | |||
| cd0ef02ab1 | |||
| 68716f38e0 | |||
| f7924d29df | |||
| c7f989da8b | |||
| 14870efc11 | |||
| c7751efa71 | |||
| b987583770 | |||
| 5245e4ffc1 | |||
| 1a3235697e | |||
| f6058d9ac0 | |||
| ffa691ac78 | |||
| 1d413f9b76 | |||
| e3bf46a89b | |||
| 90b1646876 | |||
| 1a2dd4a008 | |||
| 4e3921502d | |||
| dd4a871bf0 | |||
| 5e8ed17ef7 | |||
| 885cf52bdc | |||
| 9ef9ce2b22 | |||
| 92bb168b4e | |||
| dbca214ef2 | |||
| 48326bd102 | |||
| fd256411d1 | |||
| f3441e0115 | |||
| cd8e2974f2 | |||
| dfd33eb674 | |||
| ff88705f09 | |||
| 633fe10821 | |||
| 58aa7fe9bc | |||
| 7d4df36049 | |||
| 004ec89f44 | |||
| cbfd27b954 | |||
| ac89b3a423 | |||
| 40dfbd64fa | |||
| 3e787a1d9f | |||
| 0a2c1bf3d9 | |||
| f6932f0512 | |||
| 3f98bcf9cf | |||
| 09d5e97d5d | |||
| eeca625af6 | |||
| 512aa54700 | |||
| 85df6e096f | |||
| dbde035d77 | |||
| ee00935cfc | |||
| 4de657a9a2 | |||
| 1c225c0e10 | |||
| c50aa0f862 | |||
| 2bdead3676 | |||
| 099a041ed8 | |||
| 4914b1fbd3 | |||
| 271d79bb64 | |||
| b6fdcbebfd | |||
| b79b496ad1 | |||
| 9705f84bc0 | |||
| 6d19526458 | |||
| b7cbf4b20a | |||
| afa2b40b50 | |||
| 10280da419 | |||
| bb935dd214 | |||
| d92f5d4bb5 | |||
| ee69287813 | |||
| f205075d97 | |||
| d570d03307 | |||
| 483e188c37 | |||
| 0f1665c97d | |||
| 7ef77c3160 | |||
| 74fa735004 | |||
| eb61daab43 | |||
| a2f52d98db | |||
| b0f0710b5b | |||
| 421f04fe60 | |||
| 3d90e8b57a | |||
| a1ea11a196 | |||
| f830a8023d | |||
| a36f0dbf02 | |||
| 824aec6322 | |||
| 8e4d52ddd2 | |||
| 240052da3a | |||
| 8d42156383 | |||
| 95d52e4997 | |||
| e439d4bc39 | |||
| 702dd87c41 | |||
| ca2de244d8 | |||
| b594c9566c | |||
| 1865a3b20d | |||
| 924e553664 | |||
| b005016c48 | |||
| c4eff7c743 | |||
| d669708989 | |||
| 90353c2bc5 | |||
| 5c408948f6 | |||
| 63219434d3 | |||
| f596151c42 | |||
| b7b22dc89e | |||
| 0778b04b6b | |||
| c627690011 | |||
| d52496600f | |||
| 1a4a6721a1 | |||
| 124844b9d4 | |||
| c74804ccf5 | |||
| e5f7b3bd32 | |||
| 0705c68b7b | |||
| 82e65c712b | |||
| 3f9ee1c444 | |||
| d6314f22b2 | |||
| 3aafe342c7 | |||
| 383fab15a6 | |||
| e47d38da79 | |||
| f48fa94b82 | |||
| 7e82f4c8ea | |||
| dfb61b9fea | |||
| 12d86cbfea | |||
| 67becd9496 | |||
| 8de9bc921f | |||
| 96bb7ff56f | |||
| 7cbe5717ce | |||
| e78b900937 | |||
| 5a740e1853 |
@@ -0,0 +1,66 @@
|
||||
# Generated from CLion C/C++ Code Style settings
|
||||
BasedOnStyle: LLVM
|
||||
AccessModifierOffset: 0
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignOperands: true
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: Always
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: Always
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: true
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakInheritanceList: BeforeColon
|
||||
ColumnLimit: 0
|
||||
CompactNamespaces: false
|
||||
ContinuationIndentWidth: 8
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MaxEmptyLinesToKeep: 2
|
||||
NamespaceIndentation: All
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PointerAlignment: Right
|
||||
ReflowComments: false
|
||||
SpaceAfterCStyleCast: true
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 0
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
+7
-1
@@ -4,7 +4,13 @@
|
||||
branch = master
|
||||
[submodule "shared"]
|
||||
path = shared
|
||||
url = https://git.did.science/WolverinDEV/TeaSpeak-SharedLib.git
|
||||
url = https://git.did.science/TeaSpeak/TeaSpeakLibrary.git
|
||||
[submodule "music"]
|
||||
path = music
|
||||
url = https://github.com/TeaSpeak/TeaMusic-Providers.git
|
||||
[submodule "rtclib"]
|
||||
path = rtclib
|
||||
url = https://git.did.science/TeaSpeak/Server/rtc.git
|
||||
[submodule "file-rust"]
|
||||
path = file-rust
|
||||
url = https://git.did.science/TeaSpeak/Server/teafile.git
|
||||
|
||||
+8
-6
@@ -8,7 +8,9 @@ set(TEASPEAK_SERVER ON)
|
||||
|
||||
#end now
|
||||
#set(MEMORY_DEBUG_FLAGS " -fsanitize=leak -fsanitize=address -fstack-protector-all ")
|
||||
#set(MEMORY_DEBUG_FLAGS "-fsanitize=address")
|
||||
#set(MEMORY_DEBUG_FLAGS "-fsanitize=address -fstack-protector-all")
|
||||
#set(MEMORY_DEBUG_FLAGS "-fstack-protector-all")
|
||||
#set(MEMORY_DEBUG_FLAGS " -fsanitize=address -static-libasan")
|
||||
|
||||
if (NOT BUILD_OS_ARCH)
|
||||
set(BUILD_OS_ARCH $ENV{build_os_arch})
|
||||
@@ -23,7 +25,6 @@ if (BUILD_INCLUDE_FILE)
|
||||
include(${BUILD_INCLUDE_FILE})
|
||||
endif ()
|
||||
|
||||
set(CMAKE_PREFIX_PATH "/home/wolverindev/clib/qt/5.6.1/5.6/gcc_64/lib/cmake")
|
||||
set(LIBEVENT_PATH "${LIBRARY_PATH}/event/build/lib/")
|
||||
|
||||
function(resolve_library VARIABLE FALLBACK PATHS)
|
||||
@@ -49,18 +50,19 @@ find_package(TomMath REQUIRED)
|
||||
find_package(TomCrypt REQUIRED)
|
||||
find_package(Breakpad REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
find_package(Boringssl REQUIRED)
|
||||
find_package(Crypto REQUIRED)
|
||||
find_package(ThreadPool REQUIRED)
|
||||
find_package(CXXTerminal REQUIRED)
|
||||
find_package(StringVariable REQUIRED)
|
||||
find_package(yaml-cpp REQUIRED)
|
||||
find_package(jsoncpp REQUIRED)
|
||||
find_package(Ed25519 REQUIRED)
|
||||
find_package(DataPipes REQUIRED)
|
||||
find_package(Opus REQUIRED)
|
||||
find_package(spdlog REQUIRED)
|
||||
find_package(Jemalloc REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
message("${zstd_DIR}")
|
||||
find_package(zstd REQUIRED)
|
||||
|
||||
include_directories(${StringVariable_INCLUDE_DIR})
|
||||
add_subdirectory(music/)
|
||||
@@ -92,7 +94,6 @@ endif()
|
||||
|
||||
#FIXME: Use module for this
|
||||
include_directories(${breakpad_INCLUDE_DIR})
|
||||
include_directories(${ed25519_INCLUDE_DIR})
|
||||
include_directories(${ThreadPool_INCLUDE_DIR})
|
||||
include_directories(${DataPipes_INCLUDE_DIR})
|
||||
include_directories(${LIBEVENT_INCLUDE_DIRS})
|
||||
@@ -103,4 +104,5 @@ add_definitions(-DINET -DINET6)
|
||||
add_subdirectory(shared/)
|
||||
add_subdirectory(server/)
|
||||
add_subdirectory(license/)
|
||||
add_subdirectory(MusicBot/)
|
||||
add_subdirectory(MusicBot/)
|
||||
add_subdirectory(file/)
|
||||
@@ -30,9 +30,12 @@ void AbstractMusicPlayer::unregisterEventHandler(const std::string& string) {
|
||||
}
|
||||
|
||||
void AbstractMusicPlayer::fireEvent(MusicEvent event) {
|
||||
std::lock_guard lock(this->eventLock);
|
||||
auto listCopy = this->eventHandlers; //Copy for remove while fire
|
||||
for(const auto& entry : listCopy)
|
||||
decltype(this->eventHandlers) handlers{};
|
||||
{
|
||||
std::lock_guard lock(this->eventLock);
|
||||
handlers = this->eventHandlers; //Copy for remove while fire
|
||||
}
|
||||
for(const auto& entry : handlers)
|
||||
entry.second(event);
|
||||
}
|
||||
|
||||
@@ -75,11 +78,16 @@ void manager::loadProviders(const std::string& path) {
|
||||
}
|
||||
|
||||
deque<fs::path> paths;
|
||||
for(const auto& entry : fs::directory_iterator(dir)){
|
||||
error_code error_code{};
|
||||
for(const auto& entry : fs::directory_iterator(dir, error_code)){
|
||||
if(!entry.path().has_extension()) continue;
|
||||
if(entry.path().extension().string() == ".so")
|
||||
paths.push_back(entry.path());
|
||||
}
|
||||
if(error_code) {
|
||||
log::log(log::err, "Failed to scan the target directory (" + dir.string() + "): " + error_code.message());
|
||||
return;
|
||||
}
|
||||
std::sort(paths.begin(), paths.end(), [](const fs::path& a, const fs::path& b){ return a.filename().string() < b.filename().string(); });
|
||||
|
||||
int index = 0;
|
||||
|
||||
Submodule
+1
Submodule file-rust added at 29f9579435
@@ -0,0 +1,50 @@
|
||||
cmake_minimum_required(VERSION 3.6)
|
||||
project(TeaSpeak-Files)
|
||||
|
||||
#set(CMAKE_CXX_STANDARD 20)
|
||||
#set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
add_library(TeaSpeak-FileServer STATIC
|
||||
local_server/LocalFileProvider.cpp
|
||||
local_server/LocalFileSystem.cpp
|
||||
local_server/LocalFileTransfer.cpp
|
||||
local_server/LocalFileTransferClientWorker.cpp
|
||||
local_server/LocalFileTransferDisk.cpp
|
||||
local_server/LocalFileTransferNetwork.cpp
|
||||
local_server/clnpath.cpp
|
||||
local_server/NetTools.cpp
|
||||
local_server/Config.cpp
|
||||
local_server/HTTPUtils.cpp
|
||||
)
|
||||
|
||||
add_library(TeaSpeak-FileRust STATIC
|
||||
local_rust/provider.cpp
|
||||
local_rust/lib.cpp
|
||||
)
|
||||
target_include_directories(TeaSpeak-FileRust PUBLIC include/)
|
||||
target_link_libraries(TeaSpeak-FileRust PUBLIC TeaSpeak ${CMAKE_SOURCE_DIR}/file-rust/target/debug/libteaspeak_file_capi.so)
|
||||
# target_link_directories(TeaSpeak-FileRust PUBLIC ${CMAKE_SOURCE_DIR}/file-rust/target/debug/)
|
||||
|
||||
target_link_libraries(TeaSpeak-FileServer PUBLIC TeaSpeak ${StringVariable_LIBRARIES_STATIC} stdc++fs
|
||||
libevent::core libevent::pthreads
|
||||
# We're not linking this here, since we may later use DataPipes::shared linking
|
||||
# DataPipes::core::static
|
||||
openssl::ssl::shared
|
||||
openssl::crypto::shared
|
||||
)
|
||||
|
||||
target_include_directories(TeaSpeak-FileServer PUBLIC include/)
|
||||
target_compile_options(TeaSpeak-FileServer PUBLIC "-Wswitch-enum")
|
||||
target_compile_features(TeaSpeak-FileServer PUBLIC cxx_std_20)
|
||||
|
||||
add_executable(TeaSpeak-FileServerTest test/main.cpp)
|
||||
target_link_libraries(TeaSpeak-FileServerTest PUBLIC TeaSpeak-FileServer
|
||||
TeaMusic #Static (Must be in here, so we link against TeaMusic which uses C++11. That forbids GCC to use the newer glibc version)
|
||||
CXXTerminal::static
|
||||
DataPipes::core::static
|
||||
stdc++fs
|
||||
)
|
||||
target_compile_options(TeaSpeak-FileServerTest PUBLIC -static-libgcc -static-libstdc++)
|
||||
|
||||
add_executable(FileServer-CLNText local_server/clnpath.cpp)
|
||||
target_compile_definitions(FileServer-CLNText PUBLIC -DCLN_EXEC)
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <pipes/ssl.h>
|
||||
|
||||
namespace ts::server::file::config {
|
||||
enum struct Key {
|
||||
SSL_OPTION_SUPPLIER
|
||||
};
|
||||
|
||||
extern void value_updated(Key /* value */);
|
||||
extern std::function<std::shared_ptr<pipes::SSL::Options>()> ssl_option_supplier;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
namespace ts::server::file {
|
||||
enum struct ExecuteStatus {
|
||||
UNKNOWN,
|
||||
WAITING,
|
||||
SUCCESS,
|
||||
ERROR
|
||||
};
|
||||
|
||||
template<typename VariantType, typename T, std::size_t index = 0>
|
||||
constexpr std::size_t variant_index() {
|
||||
if constexpr (index == std::variant_size_v<VariantType>) {
|
||||
return index;
|
||||
} else if constexpr (std::is_same_v<std::variant_alternative_t<index, VariantType>, T>) {
|
||||
return index;
|
||||
} else {
|
||||
return variant_index<VariantType, T, index + 1>();
|
||||
}
|
||||
}
|
||||
|
||||
struct EmptyExecuteResponse { };
|
||||
template <class error_t, class response_t = EmptyExecuteResponse>
|
||||
class ExecuteResponse {
|
||||
typedef std::variant<EmptyExecuteResponse, error_t, response_t> variant_t;
|
||||
public:
|
||||
ExecuteStatus status{ExecuteStatus::WAITING};
|
||||
|
||||
[[nodiscard]] inline auto response() const -> const response_t& { return std::get<response_t>(this->response_); }
|
||||
|
||||
template <typename = std::enable_if_t<!std::is_void<error_t>::value>>
|
||||
[[nodiscard]] inline const error_t& error() const { return std::get<error_t>(this->response_); }
|
||||
|
||||
inline void wait() const {
|
||||
std::unique_lock nlock{this->notify_mutex};
|
||||
this->notify_cv.wait(nlock, [&]{ return this->status != ExecuteStatus::WAITING; });
|
||||
}
|
||||
|
||||
template<typename _Rep, typename _Period>
|
||||
[[nodiscard]] inline bool wait_for(const std::chrono::duration<_Rep, _Period>& time) const {
|
||||
std::unique_lock nlock{this->notify_mutex};
|
||||
return this->notify_cv.wait_for(nlock, time, [&]{ return this->status != ExecuteStatus::WAITING; });
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void emplace_success(Args&&... args) {
|
||||
constexpr auto success_index = variant_index<variant_t, response_t>();
|
||||
|
||||
std::lock_guard rlock{this->notify_mutex};
|
||||
this->response_.template emplace<success_index, Args...>(std::forward<Args>(args)...);
|
||||
this->status = ExecuteStatus::SUCCESS;
|
||||
this->notify_cv.notify_all();
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void emplace_fail(Args&&... args) {
|
||||
constexpr auto error_index = variant_index<variant_t, error_t>();
|
||||
|
||||
std::lock_guard rlock{this->notify_mutex};
|
||||
this->response_.template emplace<error_index, Args...>(std::forward<Args>(args)...);
|
||||
this->status = ExecuteStatus::ERROR;
|
||||
this->notify_cv.notify_all();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool succeeded() const {
|
||||
return this->status == ExecuteStatus::SUCCESS;
|
||||
}
|
||||
|
||||
ExecuteResponse(std::mutex& notify_mutex, std::condition_variable& notify_cv)
|
||||
: notify_mutex{notify_mutex}, notify_cv{notify_cv} {}
|
||||
private:
|
||||
variant_t response_{}; /* void* as default value so we don't initialize error_t or response_t */
|
||||
|
||||
std::mutex& notify_mutex;
|
||||
std::condition_variable& notify_cv;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
|
||||
#include "./ExecuteResponse.h"
|
||||
|
||||
#define TRANSFER_KEY_LENGTH (32)
|
||||
#define TRANSFER_MEDIA_BYTES_LENGTH (32)
|
||||
|
||||
namespace ts::server::file {
|
||||
class VirtualFileServer;
|
||||
|
||||
typedef uint64_t ChannelId;
|
||||
typedef uint64_t ServerId;
|
||||
typedef uint64_t ClientId;
|
||||
|
||||
namespace filesystem {
|
||||
template <typename ErrorCodes>
|
||||
struct DetailedError {
|
||||
ErrorCodes error_type{ErrorCodes::UNKNOWN};
|
||||
std::string error_message{};
|
||||
|
||||
DetailedError(ErrorCodes type, std::string extraMessage) : error_type{type}, error_message{std::move(extraMessage)} {}
|
||||
};
|
||||
|
||||
enum struct DirectoryQueryErrorType {
|
||||
UNKNOWN,
|
||||
PATH_EXCEEDS_ROOT_PATH,
|
||||
PATH_IS_A_FILE,
|
||||
PATH_DOES_NOT_EXISTS,
|
||||
FAILED_TO_LIST_FILES
|
||||
};
|
||||
constexpr std::array<std::string_view, 5> directory_query_error_messages = {
|
||||
"unknown error",
|
||||
"path exceeds base path",
|
||||
"path is a file",
|
||||
"path does not exists",
|
||||
"failed to list files"
|
||||
};
|
||||
|
||||
typedef DetailedError<DirectoryQueryErrorType> DirectoryQueryError;
|
||||
|
||||
struct DirectoryEntry {
|
||||
enum Type {
|
||||
UNKNOWN,
|
||||
DIRECTORY,
|
||||
FILE
|
||||
};
|
||||
|
||||
Type type{Type::UNKNOWN};
|
||||
std::string name{};
|
||||
std::chrono::system_clock::time_point modified_at{};
|
||||
|
||||
size_t size{0}; /* file only */
|
||||
bool empty{false}; /* directory only */
|
||||
};
|
||||
|
||||
enum struct DirectoryModifyErrorType {
|
||||
UNKNOWN,
|
||||
PATH_EXCEEDS_ROOT_PATH,
|
||||
PATH_ALREADY_EXISTS,
|
||||
FAILED_TO_CREATE_DIRECTORIES
|
||||
};
|
||||
typedef DetailedError<DirectoryModifyErrorType> DirectoryModifyError;
|
||||
|
||||
enum struct FileModifyErrorType {
|
||||
UNKNOWN,
|
||||
PATH_EXCEEDS_ROOT_PATH,
|
||||
TARGET_PATH_EXCEEDS_ROOT_PATH,
|
||||
PATH_DOES_NOT_EXISTS,
|
||||
TARGET_PATH_ALREADY_EXISTS,
|
||||
FAILED_TO_DELETE_FILES,
|
||||
FAILED_TO_RENAME_FILE,
|
||||
FAILED_TO_CREATE_DIRECTORIES,
|
||||
|
||||
SOME_FILES_ARE_LOCKED
|
||||
};
|
||||
typedef DetailedError<FileModifyErrorType> FileModifyError;
|
||||
|
||||
enum struct FileDeleteErrorType {
|
||||
UNKNOWN,
|
||||
};
|
||||
typedef DetailedError<FileDeleteErrorType> FileDeleteError;
|
||||
|
||||
struct FileDeleteResponse {
|
||||
enum struct StatusType {
|
||||
SUCCESS,
|
||||
|
||||
PATH_EXCEEDS_ROOT_PATH,
|
||||
PATH_DOES_NOT_EXISTS,
|
||||
FAILED_TO_DELETE_FILES,
|
||||
SOME_FILES_ARE_LOCKED
|
||||
};
|
||||
|
||||
struct DeleteResult {
|
||||
StatusType status{StatusType::SUCCESS};
|
||||
std::string error_detail{};
|
||||
|
||||
DeleteResult(StatusType status, std::string errorDetail) : status{status},
|
||||
error_detail{std::move(errorDetail)} {}
|
||||
DeleteResult() = default;
|
||||
};
|
||||
|
||||
std::vector<DeleteResult> delete_results{};
|
||||
};
|
||||
|
||||
enum struct ServerCommandErrorType {
|
||||
UNKNOWN,
|
||||
FAILED_TO_CREATE_DIRECTORIES,
|
||||
FAILED_TO_DELETE_DIRECTORIES
|
||||
};
|
||||
typedef DetailedError<ServerCommandErrorType> ServerCommandError;
|
||||
|
||||
struct FileInfoResponse {
|
||||
enum struct StatusType {
|
||||
SUCCESS,
|
||||
|
||||
PATH_EXCEEDS_ROOT_PATH,
|
||||
PATH_DOES_NOT_EXISTS,
|
||||
|
||||
FAILED_TO_QUERY_INFO,
|
||||
UNKNOWN_FILE_TYPE
|
||||
};
|
||||
|
||||
struct FileInfo {
|
||||
StatusType status{StatusType::SUCCESS};
|
||||
std::string error_detail{};
|
||||
|
||||
DirectoryEntry info{};
|
||||
|
||||
FileInfo(StatusType status, std::string errorDetail, DirectoryEntry info) : status{status},
|
||||
error_detail{std::move(errorDetail)}, info{std::move(info)} {}
|
||||
FileInfo() = default;
|
||||
};
|
||||
|
||||
std::vector<FileInfo> file_info{};
|
||||
};
|
||||
|
||||
enum struct FileInfoErrorType {
|
||||
UNKNOWN,
|
||||
};
|
||||
typedef DetailedError<FileInfoErrorType> FileInfoError;
|
||||
|
||||
class AbstractProvider {
|
||||
public:
|
||||
typedef ExecuteResponse<DirectoryQueryError, std::deque<DirectoryEntry>> directory_query_response_t;
|
||||
|
||||
/* server */
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
|
||||
|
||||
/* channels */
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_channel_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* files */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_channel_directory(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<DirectoryModifyError>> create_channel_directory(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_channel_files(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::vector<std::string>& /* paths */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileModifyError>> rename_channel_file(const std::shared_ptr<VirtualFileServer> &/* server */, ChannelId /* channel */, const std::string& /* path */, ChannelId /* target channel */, const std::string& /* target */) = 0;
|
||||
|
||||
/* icons */
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_icon_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_icon_directory(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_icons(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
|
||||
|
||||
/* avatars */
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> query_avatar_info(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<directory_query_response_t> query_avatar_directory(const std::shared_ptr<VirtualFileServer> &/* server */) = 0;
|
||||
[[nodiscard]] virtual std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> delete_avatars(const std::shared_ptr<VirtualFileServer> &/* server */, const std::vector<std::string>& /* names */) = 0;
|
||||
private:
|
||||
};
|
||||
}
|
||||
|
||||
namespace transfer {
|
||||
typedef uint16_t transfer_id;
|
||||
|
||||
struct Transfer {
|
||||
transfer_id server_transfer_id{0};
|
||||
transfer_id client_transfer_id{0};
|
||||
|
||||
std::shared_ptr<VirtualFileServer> server{nullptr};
|
||||
ChannelId channel_id{0};
|
||||
|
||||
ClientId client_id{0};
|
||||
std::string client_unique_id{};
|
||||
|
||||
std::string transfer_key{};
|
||||
std::chrono::system_clock::time_point initialized_timestamp{};
|
||||
enum Direction {
|
||||
DIRECTION_UNKNOWN,
|
||||
DIRECTION_UPLOAD,
|
||||
DIRECTION_DOWNLOAD
|
||||
} direction{DIRECTION_UNKNOWN};
|
||||
|
||||
struct Address {
|
||||
std::string hostname{};
|
||||
uint16_t port{0};
|
||||
};
|
||||
std::vector<Address> server_addresses{};
|
||||
|
||||
enum TargetType {
|
||||
TARGET_TYPE_UNKNOWN,
|
||||
TARGET_TYPE_CHANNEL_FILE,
|
||||
TARGET_TYPE_ICON,
|
||||
TARGET_TYPE_AVATAR
|
||||
} target_type{TARGET_TYPE_UNKNOWN};
|
||||
std::string target_file_path{};
|
||||
std::string absolute_file_path{};
|
||||
|
||||
std::string relative_file_path{};
|
||||
std::string file_name{};
|
||||
|
||||
int64_t max_bandwidth{-1};
|
||||
size_t expected_file_size{0}; /* incl. the offset! */
|
||||
size_t file_offset{0};
|
||||
bool override_exiting{false};
|
||||
};
|
||||
|
||||
struct TransferStatistics {
|
||||
uint64_t network_bytes_send{0};
|
||||
uint64_t network_bytes_received{0};
|
||||
|
||||
uint64_t delta_network_bytes_send{0};
|
||||
uint64_t delta_network_bytes_received{0};
|
||||
|
||||
uint64_t file_bytes_transferred{0};
|
||||
uint64_t delta_file_bytes_transferred{0};
|
||||
|
||||
size_t file_start_offset{0};
|
||||
size_t file_current_offset{0};
|
||||
size_t file_total_size{0};
|
||||
|
||||
double average_speed{0};
|
||||
double current_speed{0};
|
||||
};
|
||||
|
||||
struct TransferInitError {
|
||||
enum Type {
|
||||
UNKNOWN,
|
||||
|
||||
INVALID_FILE_TYPE,
|
||||
FILE_DOES_NOT_EXISTS,
|
||||
FILE_IS_NOT_A_FILE,
|
||||
|
||||
CLIENT_TOO_MANY_TRANSFERS,
|
||||
SERVER_TOO_MANY_TRANSFERS,
|
||||
|
||||
SERVER_QUOTA_EXCEEDED,
|
||||
CLIENT_QUOTA_EXCEEDED,
|
||||
|
||||
IO_ERROR
|
||||
} error_type{UNKNOWN};
|
||||
std::string error_message{};
|
||||
|
||||
TransferInitError(Type errorType, std::string errorMessage) : error_type{errorType},
|
||||
error_message{std::move(errorMessage)} {}
|
||||
};
|
||||
|
||||
struct TransferActionError {
|
||||
enum Type {
|
||||
UNKNOWN,
|
||||
|
||||
UNKNOWN_TRANSFER
|
||||
} error_type{UNKNOWN};
|
||||
std::string error_message{};
|
||||
};
|
||||
|
||||
struct TransferError {
|
||||
enum Type {
|
||||
UNKNOWN,
|
||||
|
||||
TRANSFER_TIMEOUT,
|
||||
|
||||
DISK_IO_ERROR,
|
||||
DISK_TIMEOUT,
|
||||
DISK_INITIALIZE_ERROR,
|
||||
|
||||
NETWORK_IO_ERROR,
|
||||
|
||||
UNEXPECTED_CLIENT_DISCONNECT,
|
||||
UNEXPECTED_DISK_EOF,
|
||||
|
||||
USER_REQUEST
|
||||
} error_type{UNKNOWN};
|
||||
std::string error_message{};
|
||||
};
|
||||
|
||||
struct ActiveFileTransfer {
|
||||
transfer_id client_transfer_id{0};
|
||||
transfer_id server_transfer_id{0};
|
||||
|
||||
Transfer::Direction direction{Transfer::DIRECTION_UNKNOWN};
|
||||
|
||||
ClientId client_id{};
|
||||
std::string client_unique_id{};
|
||||
|
||||
std::string file_path{};
|
||||
std::string file_name{};
|
||||
|
||||
size_t expected_size{};
|
||||
size_t size_done{};
|
||||
|
||||
enum Status {
|
||||
NOT_STARTED,
|
||||
RUNNING,
|
||||
INACTIVE /* (not used yet) */
|
||||
} status{Status::NOT_STARTED};
|
||||
|
||||
std::chrono::milliseconds runtime{};
|
||||
|
||||
double average_speed{0};
|
||||
double current_speed{0};
|
||||
};
|
||||
|
||||
enum struct TransferListError {
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
class AbstractProvider {
|
||||
public:
|
||||
struct TransferInfo {
|
||||
std::string file_path{};
|
||||
std::string client_unique_id{};
|
||||
ClientId client_id{};
|
||||
|
||||
bool override_exiting{false}; /* only for upload valid */
|
||||
|
||||
size_t file_offset{0};
|
||||
size_t expected_file_size{0};
|
||||
|
||||
int64_t max_bandwidth{-1};
|
||||
int64_t max_concurrent_transfers{-1};
|
||||
|
||||
/* only used for upload, for download the quotas could be checked before */
|
||||
size_t download_server_quota_limit{(size_t) -1};
|
||||
size_t download_client_quota_limit{(size_t) -1};
|
||||
};
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_channel_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, ChannelId /* channel */, const TransferInfo& /* info */) = 0;
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_icon_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, const TransferInfo& /* info */) = 0;
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_avatar_transfer(Transfer::Direction /* direction */, const std::shared_ptr<VirtualFileServer>& /* server */, const TransferInfo& /* info */) = 0;
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>> list_transfer() = 0;
|
||||
|
||||
virtual std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(const std::shared_ptr<VirtualFileServer>& /* server */, transfer_id /* id */, bool /* flush */) = 0;
|
||||
|
||||
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_registered{}; /* transfer has been registered */
|
||||
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_started{}; /* transfer has been started */
|
||||
std::function<void(const std::shared_ptr<Transfer>&)> callback_transfer_finished{}; /* transfer has been finished */
|
||||
std::function<void(const std::shared_ptr<Transfer>&, const TransferStatistics&)> callback_transfer_statistics{};
|
||||
std::function<void(const std::shared_ptr<Transfer>&, const transfer::TransferStatistics&, const TransferError&)> callback_transfer_aborted{}; /* an error happened while transferring the data */
|
||||
};
|
||||
}
|
||||
|
||||
class VirtualFileServer {
|
||||
public:
|
||||
explicit VirtualFileServer(ServerId server_id, std::string unique_id) : server_id_{server_id}, unique_id_{std::move(unique_id)} {}
|
||||
|
||||
[[nodiscard]] inline auto unique_id() const -> const std::string& { return this->unique_id_; }
|
||||
[[nodiscard]] inline auto server_id() const -> ServerId { return this->server_id_; }
|
||||
|
||||
[[nodiscard]] inline auto max_networking_upload_bandwidth() const -> int64_t { return this->max_networking_upload_bandwidth_; }
|
||||
virtual void max_networking_upload_bandwidth(int64_t value) {
|
||||
this->max_networking_upload_bandwidth_ = value;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto max_networking_download_bandwidth() const -> int64_t { return this->max_networking_download_bandwidth_; }
|
||||
virtual void max_networking_download_bandwidth(int64_t value) {
|
||||
this->max_networking_download_bandwidth_ = value;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto generate_transfer_id() {
|
||||
return ++this->current_transfer_id;
|
||||
}
|
||||
private:
|
||||
ServerId server_id_;
|
||||
std::string unique_id_;
|
||||
|
||||
int64_t max_networking_upload_bandwidth_{-1};
|
||||
int64_t max_networking_download_bandwidth_{-1};
|
||||
|
||||
std::atomic<transfer::transfer_id> current_transfer_id{0};
|
||||
};
|
||||
|
||||
class AbstractFileServer {
|
||||
public:
|
||||
[[nodiscard]] virtual std::string file_base_path() const = 0;
|
||||
[[nodiscard]] virtual filesystem::AbstractProvider& file_system() = 0;
|
||||
[[nodiscard]] virtual transfer::AbstractProvider& file_transfer() = 0;
|
||||
|
||||
[[nodiscard]] inline auto virtual_servers() const -> std::deque<std::shared_ptr<VirtualFileServer>> {
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
return this->servers_;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto find_virtual_server(ServerId server_id) const -> std::shared_ptr<VirtualFileServer> {
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
auto it = std::find_if(this->servers_.begin(), this->servers_.end(), [&](const std::shared_ptr<VirtualFileServer>& server) {
|
||||
return server->server_id() == server_id;
|
||||
});
|
||||
return it == this->servers_.end() ? nullptr : *it;
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<VirtualFileServer> register_server(ServerId /* server id */) = 0;
|
||||
virtual void unregister_server(ServerId /* server id */, bool /* delete tiles */) = 0;
|
||||
protected:
|
||||
mutable std::mutex servers_mutex{};
|
||||
std::deque<std::shared_ptr<VirtualFileServer>> servers_{};
|
||||
};
|
||||
|
||||
extern bool initialize(std::string& /* error */, const std::string& /* host names */, uint16_t /* port */);
|
||||
extern void finalize();
|
||||
|
||||
extern std::shared_ptr<AbstractFileServer> server();
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// Created by WolverinDEV on 20/04/2021.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Attention: Do not call any libteaspeak_file_* functions within being in the callback! */
|
||||
struct TeaFileNativeCallbacks {
|
||||
uint32_t version;
|
||||
|
||||
void(*log)(uint8_t /* level */, const void* /* callback data */, const char* /* message */, uint32_t /* length */);
|
||||
};
|
||||
|
||||
struct TeaFilePath {
|
||||
uint32_t type;
|
||||
uint64_t channel_id;
|
||||
const char* path;
|
||||
};
|
||||
|
||||
struct TeaFilePathInfo {
|
||||
int32_t query_result;
|
||||
uint32_t file_type;
|
||||
const char* name;
|
||||
uint64_t modify_timestamp;
|
||||
uint64_t file_size;
|
||||
bool directory_empty;
|
||||
};
|
||||
|
||||
extern const char* libteaspeak_file_version();
|
||||
extern void libteaspeak_free_str(const char* /* ptr */);
|
||||
|
||||
extern const char* libteaspeak_file_initialize(const TeaFileNativeCallbacks* /* */, size_t /* size of the callback struct */);
|
||||
extern void libteaspeak_file_finalize();
|
||||
|
||||
extern void libteaspeak_file_system_register_server(const char* /* server unique id */);
|
||||
extern void libteaspeak_file_system_unregister_server(const char* /* server unique id */, bool /* delete files */);
|
||||
|
||||
extern void libteaspeak_file_free_file_info(const TeaFilePathInfo*);
|
||||
extern const char* libteaspeak_file_system_query_file_info(const char* /* server unique id */, const TeaFilePath* /* file path */, size_t /* path count */, const TeaFilePathInfo ** /* result */);
|
||||
extern const char* libteaspeak_file_system_query_directory(const char* /* server unique id */, const TeaFilePath* /* file path */, const TeaFilePathInfo ** /* result */);
|
||||
extern const char* libteaspeak_file_system_delete_files(const char* /* server unique id */, const TeaFilePath* /* file path */, size_t /* path count */, const TeaFilePathInfo ** /* result */);
|
||||
extern uint32_t libteaspeak_file_system_create_channel_directory(const char* /* server unique id */, uint32_t /* channel id */, const char* /* path */);
|
||||
extern uint32_t libteaspeak_file_system_rename_channel_file(const char* /* server unique id */, uint32_t /* old channel id */, const char* /* old path */, uint32_t /* new channel id */, const char* /* new path */);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
//
|
||||
// Created by WolverinDEV on 20/04/2021.
|
||||
//
|
||||
|
||||
#include "./lib.h"
|
||||
#include "./imports.h"
|
||||
@@ -0,0 +1,5 @@
|
||||
//
|
||||
// Created by WolverinDEV on 20/04/2021.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
@@ -0,0 +1,587 @@
|
||||
//
|
||||
// Created by WolverinDEV on 20/04/2021.
|
||||
//
|
||||
|
||||
#include <cassert>
|
||||
#include <log/LogUtils.h>
|
||||
#include "./provider.h"
|
||||
#include "./imports.h"
|
||||
|
||||
using namespace ts::server;
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
using namespace ts::server::file::filesystem;
|
||||
|
||||
RustFileSystem::~RustFileSystem() = default;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> RustFileSystem::initialize_server(
|
||||
const std::shared_ptr<VirtualFileServer> &) {
|
||||
/* Nothing to do. */
|
||||
auto response = this->create_execute_response<ServerCommandError>();
|
||||
response->emplace_success();
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> RustFileSystem::query_channel_info(
|
||||
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::tuple<ChannelId, std::string>> &files) {
|
||||
std::vector<TeaFilePath> file_paths{};
|
||||
file_paths.reserve(files.size());
|
||||
for(const auto& [ channel_id, path ] : files) {
|
||||
file_paths.push_back(TeaFilePath{
|
||||
.type = 0,
|
||||
.channel_id = channel_id,
|
||||
.path = path.c_str()
|
||||
});
|
||||
}
|
||||
|
||||
return this->execute_info(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
|
||||
}
|
||||
|
||||
std::shared_ptr<RustFileSystem::directory_query_response_t> RustFileSystem::query_channel_directory(
|
||||
const std::shared_ptr<VirtualFileServer> &virtual_server, ChannelId channelId, const std::string &directory) {
|
||||
TeaFilePath path{
|
||||
.type = 0,
|
||||
.channel_id = channelId,
|
||||
.path = directory.c_str()
|
||||
};
|
||||
return this->execute_query(virtual_server->unique_id().c_str(), &path);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> RustFileSystem::query_icon_info(
|
||||
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::string> &files) {
|
||||
std::vector<TeaFilePath> file_paths{};
|
||||
file_paths.reserve(files.size());
|
||||
for(const auto& path : files) {
|
||||
file_paths.push_back(TeaFilePath{
|
||||
.type = 1,
|
||||
.channel_id = 0,
|
||||
.path = path.c_str()
|
||||
});
|
||||
}
|
||||
|
||||
return this->execute_info(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
|
||||
}
|
||||
|
||||
std::shared_ptr<RustFileSystem::directory_query_response_t> RustFileSystem::query_icon_directory(
|
||||
const std::shared_ptr<VirtualFileServer> &virtual_server) {
|
||||
TeaFilePath path{
|
||||
.type = 1,
|
||||
.channel_id = 0,
|
||||
.path = nullptr
|
||||
};
|
||||
return this->execute_query(virtual_server->unique_id().c_str(), &path);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> RustFileSystem::query_avatar_info(
|
||||
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::string> &files) {
|
||||
std::vector<TeaFilePath> file_paths{};
|
||||
file_paths.reserve(files.size());
|
||||
for(const auto& path : files) {
|
||||
file_paths.push_back(TeaFilePath{
|
||||
.type = 2,
|
||||
.channel_id = 0,
|
||||
.path = path.c_str()
|
||||
});
|
||||
}
|
||||
|
||||
return this->execute_info(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
|
||||
}
|
||||
|
||||
std::shared_ptr<RustFileSystem::directory_query_response_t> RustFileSystem::query_avatar_directory(
|
||||
const std::shared_ptr<VirtualFileServer> &virtual_server) {
|
||||
TeaFilePath path{
|
||||
.type = 2,
|
||||
.channel_id = 0,
|
||||
.path = nullptr
|
||||
};
|
||||
return this->execute_query(virtual_server->unique_id().c_str(), &path);
|
||||
}
|
||||
|
||||
inline void path_info_to_directory_entry(DirectoryEntry& target, const TeaFilePathInfo& info) {
|
||||
switch (info.file_type) {
|
||||
case 1:
|
||||
target.type = DirectoryEntry::FILE;
|
||||
break;
|
||||
case 2:
|
||||
target.type = DirectoryEntry::DIRECTORY;
|
||||
break;
|
||||
|
||||
default:
|
||||
target.type = DirectoryEntry::UNKNOWN;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(info.name);
|
||||
target.name = std::string{info.name};
|
||||
target.modified_at = std::chrono::system_clock::time_point{} + std::chrono::seconds{info.modify_timestamp};
|
||||
target.size = info.file_size;
|
||||
target.empty = info.directory_empty;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> RustFileSystem::execute_info(const char *server_unique_id, void *paths,
|
||||
size_t paths_count) {
|
||||
auto response = this->create_execute_response<FileInfoError, FileInfoResponse>();
|
||||
|
||||
const TeaFilePathInfo* result{nullptr};
|
||||
auto error_ptr = libteaspeak_file_system_query_file_info(server_unique_id, (const TeaFilePath*) paths, paths_count, &result);
|
||||
if(error_ptr) {
|
||||
libteaspeak_file_free_file_info(result);
|
||||
|
||||
response->emplace_fail(FileInfoErrorType::UNKNOWN, error_ptr);
|
||||
libteaspeak_free_str(error_ptr);
|
||||
} else {
|
||||
assert(result);
|
||||
std::vector<FileInfoResponse::FileInfo> file_infos{};
|
||||
file_infos.reserve(paths_count);
|
||||
|
||||
auto info_ptr{result};
|
||||
while(info_ptr->query_result >= 0) {
|
||||
auto& info = file_infos.emplace_back();
|
||||
switch (info_ptr->query_result) {
|
||||
case 0:
|
||||
/* success */
|
||||
info.status = FileInfoResponse::StatusType::SUCCESS;
|
||||
path_info_to_directory_entry(info.info, *info_ptr);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
info.status = FileInfoResponse::StatusType::PATH_EXCEEDS_ROOT_PATH;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
info.status = FileInfoResponse::StatusType::PATH_DOES_NOT_EXISTS;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
info.status = FileInfoResponse::StatusType::UNKNOWN_FILE_TYPE;
|
||||
break;
|
||||
|
||||
default:
|
||||
info.status = FileInfoResponse::StatusType::FAILED_TO_QUERY_INFO;
|
||||
info.error_detail = std::string{"invalid query result "} + std::to_string(info_ptr->query_result);
|
||||
break;
|
||||
}
|
||||
|
||||
info_ptr++;
|
||||
}
|
||||
|
||||
assert(file_infos.size() == paths_count);
|
||||
libteaspeak_file_free_file_info(result);
|
||||
response->emplace_success(FileInfoResponse{file_infos});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<RustFileSystem::directory_query_response_t> RustFileSystem::execute_query(const char *server_unique_id, void *path) {
|
||||
auto response = this->create_execute_response<DirectoryQueryError, std::deque<DirectoryEntry>>();
|
||||
|
||||
const TeaFilePathInfo* result{nullptr};
|
||||
auto error_ptr = libteaspeak_file_system_query_directory(server_unique_id, (const TeaFilePath*) path, &result);
|
||||
if(error_ptr) {
|
||||
libteaspeak_file_free_file_info(result);
|
||||
|
||||
response->emplace_fail(DirectoryQueryErrorType::UNKNOWN, error_ptr);
|
||||
libteaspeak_free_str(error_ptr);
|
||||
} else {
|
||||
assert(result);
|
||||
/* result->query_result could be zero or minus one in both cases we're iterating the query result */
|
||||
if(result->query_result > 0) {
|
||||
/* An error occurred */
|
||||
switch(result->query_result) {
|
||||
case 1:
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_EXCEEDS_ROOT_PATH, "");
|
||||
break;
|
||||
|
||||
case 2:
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
break;
|
||||
|
||||
case 4:
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_IS_A_FILE, "");
|
||||
break;
|
||||
|
||||
case 5:
|
||||
response->emplace_fail(DirectoryQueryErrorType::UNKNOWN, "io error");
|
||||
break;
|
||||
|
||||
default:
|
||||
response->emplace_fail(DirectoryQueryErrorType::UNKNOWN, std::string{"unknown error result "} + std::to_string(result->query_result));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
std::deque<DirectoryEntry> entries{};
|
||||
|
||||
auto info_ptr{result};
|
||||
while(info_ptr->query_result >= 0) {
|
||||
assert(info_ptr->query_result == 0);
|
||||
path_info_to_directory_entry(entries.emplace_back(), *info_ptr);
|
||||
info_ptr++;
|
||||
}
|
||||
|
||||
response->emplace_success(std::move(entries));
|
||||
}
|
||||
libteaspeak_file_free_file_info(result);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> RustFileSystem::delete_channel_files(
|
||||
const std::shared_ptr<VirtualFileServer> &virtual_server, ChannelId channel_id, const std::vector<std::string> &files) {
|
||||
std::vector<TeaFilePath> file_paths{};
|
||||
file_paths.reserve(files.size());
|
||||
for(const auto& path : files) {
|
||||
file_paths.push_back(TeaFilePath{
|
||||
.type = 0,
|
||||
.channel_id = channel_id,
|
||||
.path = path.c_str()
|
||||
});
|
||||
}
|
||||
|
||||
return this->execute_delete(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> RustFileSystem::delete_icons(
|
||||
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::string> &files) {
|
||||
std::vector<TeaFilePath> file_paths{};
|
||||
file_paths.reserve(files.size());
|
||||
for(const auto& path : files) {
|
||||
file_paths.push_back(TeaFilePath{
|
||||
.type = 1,
|
||||
.channel_id = 0,
|
||||
.path = path.c_str()
|
||||
});
|
||||
}
|
||||
|
||||
return this->execute_delete(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> RustFileSystem::delete_avatars(
|
||||
const std::shared_ptr<VirtualFileServer> &virtual_server, const std::vector<std::string> &files) {
|
||||
std::vector<TeaFilePath> file_paths{};
|
||||
file_paths.reserve(files.size());
|
||||
for(const auto& path : files) {
|
||||
file_paths.push_back(TeaFilePath{
|
||||
.type = 2,
|
||||
.channel_id = 0,
|
||||
.path = path.c_str()
|
||||
});
|
||||
}
|
||||
|
||||
return this->execute_delete(virtual_server->unique_id().c_str(), file_paths.data(), file_paths.size());
|
||||
}
|
||||
|
||||
inline void path_info_to_delete_result(FileDeleteResponse::DeleteResult& result, const TeaFilePathInfo& info) {
|
||||
switch (info.query_result) {
|
||||
case 0:
|
||||
result.status = FileDeleteResponse::StatusType::SUCCESS;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
result.status = FileDeleteResponse::StatusType::PATH_EXCEEDS_ROOT_PATH;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
result.status = FileDeleteResponse::StatusType::PATH_DOES_NOT_EXISTS;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
result.status = FileDeleteResponse::StatusType::FAILED_TO_DELETE_FILES;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
result.status = FileDeleteResponse::StatusType::SOME_FILES_ARE_LOCKED;
|
||||
break;
|
||||
|
||||
default:
|
||||
result.status = FileDeleteResponse::StatusType::FAILED_TO_DELETE_FILES;
|
||||
result.error_detail = std::string{"unknown delete error "} + std::to_string(info.query_result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> RustFileSystem::execute_delete(const char *server_unique_id, void *paths,
|
||||
size_t paths_count) {
|
||||
auto response = this->create_execute_response<FileDeleteError, FileDeleteResponse>();
|
||||
|
||||
const TeaFilePathInfo* result{nullptr};
|
||||
auto error_ptr = libteaspeak_file_system_delete_files(server_unique_id, (const TeaFilePath*) paths, paths_count, &result);
|
||||
if(error_ptr) {
|
||||
libteaspeak_file_free_file_info(result);
|
||||
|
||||
response->emplace_fail(FileDeleteErrorType::UNKNOWN, error_ptr);
|
||||
libteaspeak_free_str(error_ptr);
|
||||
} else {
|
||||
assert(result);
|
||||
std::vector<FileDeleteResponse::DeleteResult> results{};
|
||||
results.reserve(paths_count);
|
||||
|
||||
auto info_ptr{result};
|
||||
while(info_ptr->query_result >= 0) {
|
||||
path_info_to_delete_result(results.emplace_back(), *info_ptr);
|
||||
info_ptr++;
|
||||
}
|
||||
|
||||
assert(results.size() == paths_count);
|
||||
libteaspeak_file_free_file_info(result);
|
||||
response->emplace_success(FileDeleteResponse{results});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<DirectoryModifyError>> RustFileSystem::create_channel_directory(
|
||||
const std::shared_ptr<VirtualFileServer> &virtual_server, ChannelId channelId, const std::string &path) {
|
||||
auto response = this->create_execute_response<DirectoryModifyError>();
|
||||
auto result = libteaspeak_file_system_create_channel_directory(virtual_server->unique_id().c_str(), channelId, path.c_str());
|
||||
switch (result) {
|
||||
case 0:
|
||||
response->emplace_success();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
response->emplace_fail(DirectoryModifyErrorType::UNKNOWN, "server not found");
|
||||
break;
|
||||
|
||||
case 2:
|
||||
response->emplace_fail(DirectoryModifyErrorType::PATH_EXCEEDS_ROOT_PATH, "");
|
||||
break;
|
||||
|
||||
case 3:
|
||||
response->emplace_fail(DirectoryModifyErrorType::PATH_ALREADY_EXISTS, "");
|
||||
break;
|
||||
|
||||
case 4:
|
||||
response->emplace_fail(DirectoryModifyErrorType::FAILED_TO_CREATE_DIRECTORIES, "");
|
||||
break;
|
||||
|
||||
default:
|
||||
response->emplace_fail(DirectoryModifyErrorType::UNKNOWN, std::string{"invalid return code "} + std::to_string(result));
|
||||
break;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>> RustFileSystem::rename_channel_file(
|
||||
const std::shared_ptr<VirtualFileServer> &virtual_server, ChannelId old_channel_id, const std::string &old_path, ChannelId new_channel_id,
|
||||
const std::string &new_path) {
|
||||
auto response = this->create_execute_response<FileModifyError>();
|
||||
auto result = libteaspeak_file_system_rename_channel_file(virtual_server->unique_id().c_str(), old_channel_id, old_path.c_str(), new_channel_id, new_path.c_str());
|
||||
switch (result) {
|
||||
case 0:
|
||||
response->emplace_success();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
response->emplace_fail(FileModifyErrorType::UNKNOWN, "server not found");
|
||||
break;
|
||||
|
||||
case 2:
|
||||
response->emplace_fail(FileModifyErrorType::UNKNOWN, "invalid path types");
|
||||
break;
|
||||
|
||||
case 3:
|
||||
response->emplace_fail(FileModifyErrorType::PATH_EXCEEDS_ROOT_PATH, "");
|
||||
break;
|
||||
|
||||
case 4:
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_EXCEEDS_ROOT_PATH, "");
|
||||
break;
|
||||
|
||||
case 5:
|
||||
response->emplace_fail(FileModifyErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
break;
|
||||
|
||||
case 6:
|
||||
response->emplace_fail(FileModifyErrorType::SOME_FILES_ARE_LOCKED, "");
|
||||
break;
|
||||
|
||||
case 7:
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
|
||||
break;
|
||||
|
||||
case 8:
|
||||
response->emplace_fail(FileModifyErrorType::FAILED_TO_RENAME_FILE, "");
|
||||
break;
|
||||
|
||||
default:
|
||||
response->emplace_fail(FileModifyErrorType::UNKNOWN, std::string{"invalid return code "} + std::to_string(result));
|
||||
break;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
RustFileTransfer::initialize_channel_transfer(transfer::Transfer::Direction direction,
|
||||
const std::shared_ptr<VirtualFileServer> &ptr, ChannelId id,
|
||||
const transfer::AbstractProvider::TransferInfo &info) {
|
||||
auto result = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
|
||||
result->emplace_fail(TransferInitError::Type::UNKNOWN, "");
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
RustFileTransfer::initialize_icon_transfer(transfer::Transfer::Direction direction,
|
||||
const std::shared_ptr<VirtualFileServer> &ptr,
|
||||
const transfer::AbstractProvider::TransferInfo &info) {
|
||||
auto result = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
|
||||
result->emplace_fail(TransferInitError::Type::UNKNOWN, "");
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
RustFileTransfer::initialize_avatar_transfer(transfer::Transfer::Direction direction,
|
||||
const std::shared_ptr<VirtualFileServer> &ptr,
|
||||
const transfer::AbstractProvider::TransferInfo &info) {
|
||||
auto result = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
|
||||
result->emplace_fail(TransferInitError::Type::UNKNOWN, "");
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>>
|
||||
RustFileTransfer::list_transfer() {
|
||||
auto result = this->create_execute_response<TransferListError, std::vector<ActiveFileTransfer>>();
|
||||
result->emplace_fail(TransferListError::UNKNOWN);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferActionError>>
|
||||
RustFileTransfer::stop_transfer(const std::shared_ptr<VirtualFileServer> &ptr, transfer::transfer_id id,
|
||||
bool b) {
|
||||
auto result = this->create_execute_response<TransferActionError>();
|
||||
result->emplace_fail(TransferActionError{TransferActionError::Type::UNKNOWN_TRANSFER});
|
||||
return result;
|
||||
}
|
||||
|
||||
RustFileProvider::RustFileProvider() :
|
||||
file_system_{new RustFileSystem{}},
|
||||
file_transfer_{new RustFileTransfer{}}
|
||||
{}
|
||||
|
||||
RustFileProvider::~RustFileProvider() {
|
||||
delete this->file_transfer_;
|
||||
delete this->file_system_;
|
||||
}
|
||||
|
||||
std::string RustFileProvider::file_base_path() const {
|
||||
return "TODO!"; /* FIXME! */
|
||||
}
|
||||
|
||||
filesystem::AbstractProvider & RustFileProvider::file_system() {
|
||||
return *this->file_system_;
|
||||
}
|
||||
|
||||
transfer::AbstractProvider & RustFileProvider::file_transfer() {
|
||||
return *this->file_transfer_;
|
||||
}
|
||||
|
||||
std::shared_ptr<VirtualFileServer> RustFileProvider::register_server(ServerId server_id) {
|
||||
auto server = this->find_virtual_server(server_id);
|
||||
if(server) return server;
|
||||
|
||||
server = std::make_shared<file::RustVirtualFileServer>(server_id, std::to_string(server_id));
|
||||
libteaspeak_file_system_register_server(server->unique_id().c_str());
|
||||
{
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
this->servers_.push_back(server);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
void RustFileProvider::unregister_server(ServerId server_id, bool delete_files) {
|
||||
auto server_unique_id = std::to_string(server_id);
|
||||
std::shared_ptr<VirtualFileServer> server{};
|
||||
|
||||
{
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
auto it = std::find_if(this->servers_.begin(), this->servers_.end(), [&](const std::shared_ptr<VirtualFileServer>& server) {
|
||||
return server->unique_id() == server_unique_id;
|
||||
});
|
||||
|
||||
if(it == this->servers_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
server = *it;
|
||||
this->servers_.erase(it);
|
||||
}
|
||||
|
||||
libteaspeak_file_system_unregister_server(server->unique_id().c_str(), delete_files);
|
||||
}
|
||||
|
||||
void RustVirtualFileServer::max_networking_download_bandwidth(int64_t value) {
|
||||
VirtualFileServer::max_networking_download_bandwidth(value);
|
||||
}
|
||||
|
||||
void RustVirtualFileServer::max_networking_upload_bandwidth(int64_t value) {
|
||||
VirtualFileServer::max_networking_upload_bandwidth(value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define log(...) \
|
||||
switch(level) { \
|
||||
case 0: \
|
||||
logTrace(__VA_ARGS__); \
|
||||
break; \
|
||||
case 1: \
|
||||
debugMessage(__VA_ARGS__); \
|
||||
break; \
|
||||
case 2: \
|
||||
logMessage(__VA_ARGS__); \
|
||||
break; \
|
||||
case 3: \
|
||||
logWarning(__VA_ARGS__); \
|
||||
break; \
|
||||
case 4: \
|
||||
logError(__VA_ARGS__); \
|
||||
break; \
|
||||
case 5: \
|
||||
logCritical(__VA_ARGS__); \
|
||||
break; \
|
||||
default: \
|
||||
debugMessage(__VA_ARGS__); \
|
||||
break; \
|
||||
}
|
||||
|
||||
void libteaspeak_file_callback_log(uint8_t level, const void* callback_data_ptr, const char* message_ptr, uint32_t length) {
|
||||
std::string_view message{message_ptr, length};
|
||||
log(LOG_GENERAL, "{}", message);
|
||||
}
|
||||
|
||||
#undef log
|
||||
|
||||
static TeaFileNativeCallbacks native_callbacks{
|
||||
.version = 1,
|
||||
|
||||
.log = libteaspeak_file_callback_log,
|
||||
};
|
||||
|
||||
std::shared_ptr<AbstractFileServer> file_instance{};
|
||||
bool file::initialize(std::string& error, const std::string& host_names, uint16_t port) {
|
||||
logMessage(LOG_FT, "Initializing file server with version {}", libteaspeak_file_version());
|
||||
|
||||
auto error_ptr = libteaspeak_file_initialize(&native_callbacks, sizeof native_callbacks);
|
||||
if(error_ptr) {
|
||||
error = error_ptr;
|
||||
libteaspeak_free_str(error_ptr);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* FIXME: Start server */
|
||||
|
||||
file_instance = std::make_shared<RustFileProvider>();
|
||||
return true;
|
||||
}
|
||||
|
||||
void file::finalize() {
|
||||
file_instance = nullptr;
|
||||
libteaspeak_file_finalize();
|
||||
}
|
||||
|
||||
std::shared_ptr<AbstractFileServer> file::server() {
|
||||
return file_instance;
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// Created by WolverinDEV on 20/04/2021.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <files/FileServer.h>
|
||||
|
||||
namespace ts::server::file {
|
||||
namespace filesystem {
|
||||
class RustFileSystem : public filesystem::AbstractProvider {
|
||||
using FileModifyError = filesystem::FileModifyError;
|
||||
using DirectoryModifyError = filesystem::DirectoryModifyError;
|
||||
public:
|
||||
virtual ~RustFileSystem();
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(const std::shared_ptr<VirtualFileServer> & /* server */) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_channel_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* names */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t>
|
||||
query_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<DirectoryModifyError>>
|
||||
create_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_channel_files(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::vector<std::string> &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
rename_channel_file(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &, ChannelId, const std::string &) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_icon_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_icon_directory(const std::shared_ptr<VirtualFileServer> & id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_icons(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_avatar_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_avatar_directory(const std::shared_ptr<VirtualFileServer> & id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_avatars(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
|
||||
|
||||
private:
|
||||
template <typename error_t, typename result_t = EmptyExecuteResponse>
|
||||
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
|
||||
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> execute_info(const char* /* server unique id */, void* /* query */, size_t /* query count */);
|
||||
std::shared_ptr<directory_query_response_t> execute_query(const char* /* server unique id */, void* /* directory */);
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> execute_delete(const char* /* server unique id */, void* /* files */, size_t /* file count */);
|
||||
|
||||
std::mutex result_notify_mutex{};
|
||||
std::condition_variable result_notify_cv{};
|
||||
};
|
||||
}
|
||||
|
||||
namespace transfer {
|
||||
class RustFileTransfer : public AbstractProvider {
|
||||
public:
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_channel_transfer(Transfer::Direction direction,
|
||||
const std::shared_ptr<VirtualFileServer> &ptr, ChannelId id,
|
||||
const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_icon_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &ptr,
|
||||
const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_avatar_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &ptr,
|
||||
const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>>
|
||||
list_transfer() override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferActionError>>
|
||||
stop_transfer(const std::shared_ptr<VirtualFileServer> &ptr, transfer_id id, bool b) override;
|
||||
|
||||
private:
|
||||
template <typename error_t, typename result_t = EmptyExecuteResponse>
|
||||
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
|
||||
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
|
||||
}
|
||||
|
||||
std::mutex result_notify_mutex{};
|
||||
std::condition_variable result_notify_cv{};
|
||||
};
|
||||
}
|
||||
|
||||
class RustVirtualFileServer : public VirtualFileServer {
|
||||
public:
|
||||
explicit RustVirtualFileServer(ServerId server_id, std::string unique_id) : VirtualFileServer{server_id, std::move(unique_id)} {}
|
||||
|
||||
void max_networking_upload_bandwidth(int64_t value) override;
|
||||
void max_networking_download_bandwidth(int64_t value) override;
|
||||
};
|
||||
|
||||
class RustFileProvider : public AbstractFileServer {
|
||||
public:
|
||||
RustFileProvider();
|
||||
virtual ~RustFileProvider();
|
||||
|
||||
[[nodiscard]] std::string file_base_path() const override;
|
||||
|
||||
filesystem::AbstractProvider &file_system() override;
|
||||
transfer::AbstractProvider &file_transfer() override;
|
||||
|
||||
std::shared_ptr<VirtualFileServer> register_server(ServerId /* server id */) override;
|
||||
void unregister_server(ServerId /* server id */, bool /* delete files */) override;
|
||||
private:
|
||||
filesystem::RustFileSystem* file_system_;
|
||||
transfer::RustFileTransfer* file_transfer_;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Created by WolverinDEV on 21/05/2020.
|
||||
//
|
||||
|
||||
#include "files/Config.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
|
||||
std::function<std::shared_ptr<pipes::SSL::Options>()> config::ssl_option_supplier{nullptr};
|
||||
|
||||
void config::value_updated(ts::server::file::config::Key) {
|
||||
/* we currently do nothing */
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// Created by WolverinDEV on 22/05/2020.
|
||||
//
|
||||
|
||||
#include <pipes/misc/http.h>
|
||||
#include "HTTPUtils.h"
|
||||
|
||||
bool http::parse_url_parameters(const std::string_view &query, std::map<std::string, std::string>& result) {
|
||||
const auto query_offset = query.find('?');
|
||||
if(query_offset == std::string::npos) return false;
|
||||
|
||||
const auto query_end_offset = query.find('#', query_offset); /* fragment (if there is any) */
|
||||
|
||||
auto offset = query_offset + 1;
|
||||
size_t next_param;
|
||||
while(offset > 0) {
|
||||
next_param = query.find('&', offset);
|
||||
if(next_param >= query_end_offset)
|
||||
next_param = query_end_offset;
|
||||
|
||||
if(offset >= next_param)
|
||||
break;
|
||||
|
||||
/* parameter: [offset;next_param) */
|
||||
const auto param_view = query.substr(offset, next_param - offset);
|
||||
const auto assignment_index = param_view.find('=');
|
||||
if(assignment_index == std::string::npos)
|
||||
result[std::string{param_view}] = "";
|
||||
else
|
||||
result[std::string{param_view.substr(0, assignment_index)}] = http::decode_url(std::string{param_view.substr(assignment_index + 1)});
|
||||
|
||||
offset = next_param + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace http {
|
||||
bool parse_url_parameters(const std::string_view& /* url */, std::map<std::string, std::string>& /* result */);
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
//
|
||||
// Created by WolverinDEV on 28/04/2020.
|
||||
//
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "LocalFileProvider.h"
|
||||
#include "LocalFileSystem.h"
|
||||
#include "LocalFileTransfer.h"
|
||||
|
||||
using namespace ts::server;
|
||||
using LocalFileServer = file::LocalFileProvider;
|
||||
using LocalVirtualFileServer = file::LocalVirtualFileServer;
|
||||
|
||||
std::shared_ptr<LocalFileServer> server_instance{};
|
||||
bool file::initialize(std::string &error, const std::string& hostnames, uint16_t port) {
|
||||
server_instance = std::make_shared<LocalFileProvider>();
|
||||
|
||||
if(!server_instance->initialize(error)) {
|
||||
server_instance = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool any_bind{false};
|
||||
for(const auto& binding : net::resolve_bindings(hostnames, port)) {
|
||||
if(!std::get<2>(binding).empty()) {
|
||||
logError(LOG_FT, "Failed to resolve binding for {}: {}", std::get<0>(binding), std::get<2>(binding));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto result = dynamic_cast<transfer::LocalFileTransfer&>(server_instance->file_transfer()).add_network_binding({ std::get<0>(binding), std::get<1>(binding) });
|
||||
switch (result) {
|
||||
case transfer::NetworkingBindResult::SUCCESS:
|
||||
any_bind = true;
|
||||
break;
|
||||
|
||||
case transfer::NetworkingBindResult::OUT_OF_MEMORY:
|
||||
logWarning(LOG_FT, "Failed to listen to address {}: Out of memory", std::get<0>(binding));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::FAILED_TO_LISTEN:
|
||||
logWarning(LOG_FT, "Failed to listen on {}: {}/{}", std::get<0>(binding), errno, strerror(errno));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::FAILED_TO_BIND:
|
||||
logWarning(LOG_FT, "Failed to bind on {}: {}/{}", std::get<0>(binding), errno, strerror(errno));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::BINDING_ALREADY_EXISTS:
|
||||
logWarning(LOG_FT, "Failed to bind on {}: binding already exists", std::get<0>(binding));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::NETWORKING_NOT_INITIALIZED:
|
||||
logWarning(LOG_FT, "Failed to bind on {}: networking not initialized", std::get<0>(binding));
|
||||
continue;
|
||||
|
||||
case transfer::NetworkingBindResult::FAILED_TO_ALLOCATE_SOCKET:
|
||||
logWarning(LOG_FT, "Failed to allocate a socket for {}: {}/{}", std::get<0>(binding), errno, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return any_bind;
|
||||
}
|
||||
|
||||
void file::finalize() {
|
||||
auto server = std::exchange(server_instance, nullptr);
|
||||
if(!server) return;
|
||||
|
||||
server->finalize();
|
||||
}
|
||||
|
||||
std::shared_ptr<file::AbstractFileServer> file::server() {
|
||||
return server_instance;
|
||||
}
|
||||
|
||||
LocalFileServer::LocalFileProvider() {
|
||||
this->file_system_ = new filesystem::LocalFileSystem();
|
||||
this->file_transfer_ = new transfer::LocalFileTransfer(this->file_system_);
|
||||
}
|
||||
LocalFileServer::~LocalFileProvider() {
|
||||
delete this->file_transfer_;
|
||||
delete this->file_system_;
|
||||
};
|
||||
|
||||
bool LocalFileServer::initialize(std::string &error) {
|
||||
if(!this->file_system_->initialize(error, "files/"))
|
||||
return false;
|
||||
|
||||
if(!this->file_transfer_->start()) {
|
||||
error = "transfer server startup failed";
|
||||
this->file_system_->finalize();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalFileServer::finalize() {
|
||||
this->file_transfer_->stop();
|
||||
this->file_system_->finalize();
|
||||
}
|
||||
|
||||
file::filesystem::AbstractProvider &LocalFileServer::file_system() {
|
||||
return *this->file_system_;
|
||||
}
|
||||
|
||||
file::transfer::AbstractProvider &LocalFileServer::file_transfer() {
|
||||
return *this->file_transfer_;
|
||||
}
|
||||
|
||||
std::string file::LocalFileProvider::file_base_path() const {
|
||||
return this->file_system_->root_path();
|
||||
}
|
||||
|
||||
std::shared_ptr<file::VirtualFileServer> LocalFileServer::register_server(ServerId server_id) {
|
||||
auto server = this->find_virtual_server(server_id);
|
||||
if(server) return server;
|
||||
|
||||
server = std::make_shared<file::LocalVirtualFileServer>(server_id, std::to_string(server_id));
|
||||
{
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
this->servers_.push_back(server);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
void LocalFileServer::unregister_server(ServerId server_id, bool delete_files) {
|
||||
auto server_unique_id = std::to_string(server_id);
|
||||
std::shared_ptr<VirtualFileServer> server{};
|
||||
|
||||
{
|
||||
std::lock_guard slock{this->servers_mutex};
|
||||
auto it = std::find_if(this->servers_.begin(), this->servers_.end(), [&](const std::shared_ptr<VirtualFileServer>& server) {
|
||||
return server->unique_id() == server_unique_id;
|
||||
});
|
||||
|
||||
if(it == this->servers_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
server = *it;
|
||||
this->servers_.erase(it);
|
||||
}
|
||||
|
||||
using ErrorType = file::filesystem::ServerCommandErrorType;
|
||||
|
||||
auto delete_result = this->file_system_->delete_server(server);
|
||||
if(!delete_result->wait_for(std::chrono::seconds{5})) {
|
||||
logError(LOG_INSTANCE, "Failed to wait for file directory deletion.");
|
||||
} else if(!delete_result->succeeded()) {
|
||||
switch (delete_result->error().error_type) {
|
||||
case ErrorType::FAILED_TO_DELETE_DIRECTORIES:
|
||||
logError(LOG_INSTANCE, "Failed to delete server {} file directories ({}).", server->server_id(), delete_result->error().error_message);
|
||||
break;
|
||||
|
||||
case ErrorType::UNKNOWN:
|
||||
case ErrorType::FAILED_TO_CREATE_DIRECTORIES:
|
||||
logError(LOG_INSTANCE, "Failed to delete server {} file directory due to an unknown error: {}/{}",
|
||||
server->server_id(), (int) delete_result->error().error_type, delete_result->error().error_message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocalVirtualFileServer::max_networking_upload_bandwidth(int64_t value) {
|
||||
VirtualFileServer::max_networking_upload_bandwidth(value);
|
||||
this->upload_throttle.set_max_bandwidth(value);
|
||||
}
|
||||
|
||||
void LocalVirtualFileServer::max_networking_download_bandwidth(int64_t value) {
|
||||
VirtualFileServer::max_networking_download_bandwidth(value);
|
||||
this->download_throttle.set_max_bandwidth(value);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <thread>
|
||||
#include <shared_mutex>
|
||||
#include <sys/socket.h>
|
||||
#include <pipes/ws.h>
|
||||
#include <pipes/ssl.h>
|
||||
#include <misc/net.h>
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <random>
|
||||
#include <misc/memtracker.h>
|
||||
#include "./NetTools.h"
|
||||
|
||||
namespace ts::server::file {
|
||||
namespace filesystem { class LocalFileSystem; }
|
||||
namespace transfer { class LocalFileTransfer; }
|
||||
|
||||
class LocalVirtualFileServer : public VirtualFileServer {
|
||||
public:
|
||||
explicit LocalVirtualFileServer(ServerId server_id, std::string unique_id) : VirtualFileServer{server_id, std::move(unique_id)} {}
|
||||
|
||||
void max_networking_upload_bandwidth(int64_t value) override;
|
||||
void max_networking_download_bandwidth(int64_t value) override;
|
||||
|
||||
networking::NetworkThrottle upload_throttle{};
|
||||
networking::NetworkThrottle download_throttle{};
|
||||
};
|
||||
|
||||
class LocalFileProvider : public AbstractFileServer {
|
||||
public:
|
||||
LocalFileProvider();
|
||||
virtual ~LocalFileProvider();
|
||||
|
||||
[[nodiscard]] bool initialize(std::string& /* error */);
|
||||
void finalize();
|
||||
|
||||
[[nodiscard]] std::string file_base_path() const override;
|
||||
|
||||
filesystem::AbstractProvider &file_system() override;
|
||||
transfer::AbstractProvider &file_transfer() override;
|
||||
|
||||
|
||||
std::shared_ptr<VirtualFileServer> register_server(ServerId /* server id */) override;
|
||||
void unregister_server(ServerId /* server id */, bool /* delete server */) override;
|
||||
private:
|
||||
filesystem::LocalFileSystem* file_system_;
|
||||
transfer::LocalFileTransfer* file_transfer_;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,457 @@
|
||||
//
|
||||
// Created by WolverinDEV on 29/04/2020.
|
||||
//
|
||||
#include <experimental/filesystem>
|
||||
#define FS_INCLUDED
|
||||
|
||||
#include <log/LogUtils.h>
|
||||
#include "./LocalFileSystem.h"
|
||||
#include "./clnpath.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::filesystem;
|
||||
namespace fs = std::experimental::filesystem;
|
||||
using directory_query_response_t = AbstractProvider::directory_query_response_t;
|
||||
|
||||
LocalFileSystem::~LocalFileSystem() = default;
|
||||
|
||||
bool LocalFileSystem::initialize(std::string &error_message, const std::string &root_path_string) {
|
||||
auto root_path = fs::u8path(root_path_string);
|
||||
std::error_code error{};
|
||||
|
||||
if(!fs::exists(root_path, error)) {
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to check root path existence. Assuming it does not exist. ({}/{})", error.value(), error.message());
|
||||
if(!fs::create_directories(root_path, error) || error) {
|
||||
error_message = "Failed to create root file system at " + root_path_string + ": " + std::to_string(error.value()) + "/" + error.message();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto croot = clnpath(fs::absolute(root_path).string());
|
||||
logMessage(LOG_FT, "Started file system root at {}", croot);
|
||||
this->root_path_ = croot;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalFileSystem::finalize() {}
|
||||
|
||||
fs::path LocalFileSystem::server_path(const std::shared_ptr<VirtualFileServer> &server) {
|
||||
return fs::u8path(this->root_path_) / fs::u8path("server_" + std::to_string(server->server_id()));
|
||||
}
|
||||
|
||||
fs::path LocalFileSystem::server_channel_path(const std::shared_ptr<VirtualFileServer> &server, ts::ChannelId cid) {
|
||||
return this->server_path(server) / fs::u8path("channel_" + std::to_string(cid));
|
||||
}
|
||||
|
||||
bool LocalFileSystem::exceeds_base_path(const fs::path &base, const fs::path &target) {
|
||||
auto rel_target = clnpath(target.string());
|
||||
if(rel_target.starts_with("..")) return true;
|
||||
|
||||
auto base_string = clnpath(fs::absolute(base).string());
|
||||
auto target_string = clnpath(fs::absolute(target).string());
|
||||
return !target_string.starts_with(base_string);
|
||||
}
|
||||
|
||||
bool LocalFileSystem::is_any_file_locked(const fs::path &base, const std::string &path, std::string &locked_file) {
|
||||
auto c_path = clnpath(fs::absolute(base / fs::u8path(path)).string());
|
||||
|
||||
std::lock_guard lock{this->locked_files_mutex};
|
||||
for(const auto& lfile : this->locked_files_) {
|
||||
if(lfile.starts_with(c_path)) {
|
||||
locked_file = lfile.substr(base.string().length());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::target_file_path(FileCategory type, const std::shared_ptr<VirtualFileServer> &server, ts::ChannelId cid, const std::string &path) {
|
||||
fs::path target_path{};
|
||||
switch (type) {
|
||||
case FileCategory::CHANNEL:
|
||||
target_path = this->server_channel_path(server, cid) / path;
|
||||
break;
|
||||
case FileCategory::ICON:
|
||||
target_path = this->server_path(server) / "icons" / path;
|
||||
break;
|
||||
case FileCategory::AVATAR:
|
||||
target_path = this->server_path(server) / "avatars" / path;
|
||||
break;
|
||||
}
|
||||
|
||||
return clnpath(fs::absolute(target_path).string());
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::absolute_avatar_path(const std::shared_ptr<VirtualFileServer> &sid, const std::string &path) {
|
||||
return this->target_file_path(FileCategory::AVATAR, sid, 0, path);
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::absolute_icon_path(const std::shared_ptr<VirtualFileServer> &sid, const std::string &path) {
|
||||
return this->target_file_path(FileCategory::ICON, sid, 0, path);
|
||||
}
|
||||
|
||||
std::string LocalFileSystem::absolute_channel_path(const std::shared_ptr<VirtualFileServer> &sid, ChannelId cid, const std::string &path) {
|
||||
return this->target_file_path(FileCategory::CHANNEL, sid, cid, path);
|
||||
}
|
||||
|
||||
void LocalFileSystem::lock_file(const std::string &c_path) {
|
||||
std::lock_guard lock{this->locked_files_mutex};
|
||||
this->locked_files_.push_back(c_path);
|
||||
}
|
||||
|
||||
void LocalFileSystem::unlock_file(const std::string &c_path) {
|
||||
std::lock_guard lock{this->locked_files_mutex};
|
||||
|
||||
this->locked_files_.erase(std::remove_if(this->locked_files_.begin(), this->locked_files_.end(), [&](const auto& p) { return p == c_path; }), this->locked_files_.end());
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::initialize_server(const std::shared_ptr<VirtualFileServer> &id) {
|
||||
auto path = this->server_path(id);
|
||||
std::error_code error{};
|
||||
|
||||
auto response = this->create_execute_response<ServerCommandError>();
|
||||
|
||||
if(!fs::exists(path, error)) {
|
||||
if(!fs::create_directories(path, error) || error) {
|
||||
response->emplace_fail(ServerCommandErrorType::FAILED_TO_CREATE_DIRECTORIES, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: Copy the default icon
|
||||
|
||||
response->emplace_success();
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> LocalFileSystem::delete_server(const std::shared_ptr<VirtualFileServer> &id) {
|
||||
auto path = this->server_path(id);
|
||||
std::error_code error{};
|
||||
|
||||
auto response = this->create_execute_response<ServerCommandError>();
|
||||
|
||||
//TODO: Stop all running file transfers!
|
||||
|
||||
if(fs::exists(path, error)) {
|
||||
if(!fs::remove_all(path, error) || error) {
|
||||
response->emplace_fail(ServerCommandErrorType::FAILED_TO_DELETE_DIRECTORIES, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
response->emplace_success();
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_directory(const fs::path &base,
|
||||
const std::string &path,
|
||||
bool allow_non_existance) {
|
||||
std::error_code error{};
|
||||
auto response = this->create_execute_response<DirectoryQueryError, std::deque<DirectoryEntry>>();
|
||||
auto target_path = base / fs::u8path(path);
|
||||
if(this->exceeds_base_path(base, target_path)) {
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_EXCEEDS_ROOT_PATH, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!fs::exists(target_path, error)) {
|
||||
if(allow_non_existance)
|
||||
response->emplace_success();
|
||||
else
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!fs::is_directory(target_path, error)) {
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_IS_A_FILE, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for directory at {}: {}. Assuming its not a directory.", target_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(DirectoryQueryErrorType::PATH_IS_A_FILE, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
std::deque<DirectoryEntry> entries{};
|
||||
for(auto& entry : fs::directory_iterator(target_path, error)) {
|
||||
auto status = entry.status(error);
|
||||
if(error) {
|
||||
logWarning(LOG_FT, "Failed to query file status for {} ({}/{}). Skipping entry for directory query.", entry.path().string(), error.value(), error.message());
|
||||
continue;
|
||||
}
|
||||
|
||||
if(status.type() == fs::file_type::directory) {
|
||||
auto& dentry = entries.emplace_back();
|
||||
dentry.type = DirectoryEntry::DIRECTORY;
|
||||
dentry.name = entry.path().filename();
|
||||
|
||||
dentry.empty = fs::is_empty(entry.path(), error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query directory empty state for directory {} ({}/{})", target_path.string(), error.value(), error.message());
|
||||
|
||||
dentry.modified_at = fs::last_write_time(entry.path(), error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query last write time for directory {} ({}/{})", entry.path().string(), error.value(), error.message());
|
||||
dentry.size = 0;
|
||||
} else if(status.type() == fs::file_type::regular) {
|
||||
auto& dentry = entries.emplace_back();
|
||||
dentry.type = DirectoryEntry::FILE;
|
||||
dentry.name = entry.path().filename();
|
||||
|
||||
dentry.modified_at = fs::last_write_time(entry.path(), error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query last write time for file {} ({}/{}).", entry.path().string(), error.value(), error.message());
|
||||
dentry.size = fs::file_size(entry.path(), error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query size for file {} ({}/{}).", entry.path().string(), error.value(), error.message());
|
||||
} else {
|
||||
logWarning(LOG_FT, "Directory query listed an unknown file type for file {} ({}).", entry.path().string(), (int) status.type());
|
||||
}
|
||||
}
|
||||
if(error && entries.empty()) {
|
||||
response->emplace_fail(DirectoryQueryErrorType::FAILED_TO_LIST_FILES, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
response->emplace_success(std::forward<decltype(entries)>(entries));
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_icon_directory(const std::shared_ptr<VirtualFileServer> &id) {
|
||||
return this->query_directory(this->server_path(id) / fs::u8path("icons"), "/", true);
|
||||
}
|
||||
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_avatar_directory(const std::shared_ptr<VirtualFileServer> &id) {
|
||||
return this->query_directory(this->server_path(id) / fs::u8path("avatars"), "/", true);
|
||||
}
|
||||
|
||||
std::shared_ptr<directory_query_response_t> LocalFileSystem::query_channel_directory(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::string &path) {
|
||||
return this->query_directory(this->server_channel_path(id, channelId), path, false);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<DirectoryModifyError>> LocalFileSystem::create_channel_directory(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::string &path) {
|
||||
auto channel_path_root = this->server_channel_path(id, channelId);
|
||||
std::error_code error{};
|
||||
|
||||
auto response = this->create_execute_response<DirectoryModifyError>();
|
||||
auto target_path = channel_path_root / fs::u8path(path);
|
||||
if(this->exceeds_base_path(channel_path_root, target_path)) {
|
||||
response->emplace_fail(DirectoryModifyErrorType::PATH_EXCEEDS_ROOT_PATH, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(fs::exists(target_path, error)) {
|
||||
response->emplace_fail(DirectoryModifyErrorType::PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
|
||||
}
|
||||
|
||||
if(!fs::create_directories(target_path, error) || error) {
|
||||
response->emplace_fail(DirectoryModifyErrorType::FAILED_TO_CREATE_DIRECTORIES, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
|
||||
response->emplace_success();
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>> LocalFileSystem::rename_channel_file(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::string ¤t_path_string, ChannelId targetChannelId, const std::string &new_path_string) {
|
||||
auto channel_path_root = this->server_channel_path(id, channelId);
|
||||
auto target_path_root = this->server_channel_path(id, targetChannelId);
|
||||
std::error_code error{};
|
||||
std::string locked_file{};
|
||||
|
||||
auto response = this->create_execute_response<FileModifyError>();
|
||||
auto current_path = channel_path_root / fs::u8path(current_path_string);
|
||||
auto target_path = target_path_root / fs::u8path(new_path_string);
|
||||
|
||||
if(this->exceeds_base_path(channel_path_root, current_path)) {
|
||||
response->emplace_fail(FileModifyErrorType::PATH_EXCEEDS_ROOT_PATH, "");
|
||||
return response;
|
||||
}
|
||||
if(this->exceeds_base_path(target_path_root, target_path)) {
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_EXCEEDS_ROOT_PATH, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!fs::exists(current_path, error)) {
|
||||
response->emplace_fail(FileModifyErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", current_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(FileModifyErrorType::PATH_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(!fs::exists(target_path.parent_path(), error)) {
|
||||
if(!fs::create_directories(target_path.parent_path(), error)) {
|
||||
response->emplace_fail(FileModifyErrorType::FAILED_TO_CREATE_DIRECTORIES, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to test for target directory existence for {}: {}/{}. Assuming it does exists", target_path.parent_path().string(), error.value(), error.message());
|
||||
}
|
||||
|
||||
if(fs::exists(target_path, error)) {
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does exists.", current_path.string(), error.value(), error.message());
|
||||
response->emplace_fail(FileModifyErrorType::TARGET_PATH_ALREADY_EXISTS, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(this->is_any_file_locked(channel_path_root, current_path, locked_file)) {
|
||||
response->emplace_fail(FileModifyErrorType::SOME_FILES_ARE_LOCKED, locked_file);
|
||||
return response;
|
||||
}
|
||||
|
||||
fs::rename(current_path, target_path, error);
|
||||
if(error) {
|
||||
response->emplace_fail(FileModifyErrorType::FAILED_TO_RENAME_FILE, std::to_string(error.value()) + "/" + error.message());
|
||||
return response;
|
||||
}
|
||||
|
||||
response->emplace_success();
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_files(const fs::path &base,
|
||||
const std::vector<std::string> &paths) {
|
||||
std::error_code error{};
|
||||
std::string locked_file{};
|
||||
auto response = this->create_execute_response<FileDeleteError, FileDeleteResponse>();
|
||||
|
||||
std::vector<FileDeleteResponse::DeleteResult> delete_results{};
|
||||
for(const auto& path : paths) {
|
||||
auto target_path = base / fs::u8path(path);
|
||||
|
||||
if(!fs::exists(target_path, error)) {
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::PATH_DOES_NOT_EXISTS, "");
|
||||
continue;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does exists.", target_path.string(), error.value(), error.message());
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::PATH_DOES_NOT_EXISTS, "");
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this->is_any_file_locked(base, path, locked_file)) {
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::SOME_FILES_ARE_LOCKED, locked_file);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!fs::remove_all(target_path, error) || error) {
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::FAILED_TO_DELETE_FILES, std::to_string(error.value()) + "/" + error.message());
|
||||
continue;
|
||||
}
|
||||
|
||||
delete_results.emplace_back(FileDeleteResponse::StatusType::SUCCESS, "");
|
||||
}
|
||||
|
||||
response->emplace_success(FileDeleteResponse{delete_results});
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_channel_files(const std::shared_ptr<VirtualFileServer> &id, ChannelId channelId, const std::vector<std::string> &path) {
|
||||
return this->delete_files(this->server_channel_path(id, channelId), path);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_icons(const std::shared_ptr<VirtualFileServer> &id, const std::vector<std::string> &icon) {
|
||||
return this->delete_files(this->server_path(id) / fs::u8path("icons"), icon);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>> LocalFileSystem::delete_avatars(const std::shared_ptr<VirtualFileServer> &id, const std::vector<std::string> &avatar) {
|
||||
return this->delete_files(this->server_path(id) / fs::u8path("avatars"), avatar);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_file_info(const std::vector<std::tuple<fs::path, std::string>> &paths) {
|
||||
std::error_code error{};
|
||||
auto response = this->create_execute_response<FileInfoError, FileInfoResponse>();
|
||||
std::vector<FileInfoResponse::FileInfo> file_infos{};
|
||||
file_infos.reserve(paths.size());
|
||||
|
||||
for(const auto& [base, path] : paths) {
|
||||
auto target_path = base / fs::u8path(path);
|
||||
if(this->exceeds_base_path(base, target_path)) {
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::PATH_EXCEEDS_ROOT_PATH, "", DirectoryEntry{});
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!fs::exists(target_path, error)) {
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::PATH_DOES_NOT_EXISTS, "", DirectoryEntry{});
|
||||
continue;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", target_path.string(), error.value(), error.message());
|
||||
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::PATH_DOES_NOT_EXISTS, "", DirectoryEntry{});
|
||||
continue;
|
||||
}
|
||||
|
||||
auto status = fs::status(target_path, error);
|
||||
if(error) {
|
||||
logWarning(LOG_FT, "Failed to query file status for {} ({}/{}). Skipping entry for file info query.", target_path.string(), error.value(), error.message());
|
||||
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::FAILED_TO_QUERY_INFO, "", DirectoryEntry{});
|
||||
continue;
|
||||
}
|
||||
|
||||
if(status.type() == fs::file_type::directory) {
|
||||
DirectoryEntry dentry{};
|
||||
dentry.type = DirectoryEntry::DIRECTORY;
|
||||
dentry.name = target_path.filename();
|
||||
dentry.empty = fs::is_empty(target_path, error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query directory empty state for directory {} ({}/{})", target_path.string(), error.value(), error.message());
|
||||
|
||||
dentry.modified_at = fs::last_write_time(target_path, error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query last write time for directory {} ({}/{})", target_path.string(), error.value(), error.message());
|
||||
dentry.size = 0;
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::SUCCESS, "", dentry);
|
||||
} else if(status.type() == fs::file_type::regular) {
|
||||
DirectoryEntry dentry{};
|
||||
dentry.type = DirectoryEntry::FILE;
|
||||
dentry.name = target_path.filename();
|
||||
|
||||
dentry.modified_at = fs::last_write_time(target_path, error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query last write time for file {} ({}/{}).", target_path.string(), error.value(), error.message());
|
||||
dentry.size = fs::file_size(target_path, error);
|
||||
if(error)
|
||||
logWarning(LOG_FT, "Failed to query size for file {} ({}/{}).", target_path.string(), error.value(), error.message());
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::SUCCESS, "", dentry);
|
||||
} else {
|
||||
logWarning(LOG_FT, "File info query contains an unknown file type for file {} ({}).", target_path.string(), (int) status.type());
|
||||
file_infos.emplace_back(FileInfoResponse::StatusType::UNKNOWN_FILE_TYPE, "", DirectoryEntry{});
|
||||
}
|
||||
}
|
||||
|
||||
response->emplace_success(FileInfoResponse{file_infos});
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_channel_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::tuple<ChannelId, std::string>>& files) {
|
||||
std::vector<std::tuple<fs::path, std::string>> file_paths{};
|
||||
for(const auto& [channelId, path] : files)
|
||||
file_paths.emplace_back(this->server_channel_path(sid, channelId), path);
|
||||
return this->query_file_info(file_paths);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>> LocalFileSystem::query_icon_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::string> &paths) {
|
||||
std::vector<std::tuple<fs::path, std::string>> file_paths{};
|
||||
for(const auto& path : paths)
|
||||
file_paths.emplace_back(this->server_path(sid) / fs::u8path("icons"), path);
|
||||
return this->query_file_info(file_paths);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse> > LocalFileSystem::query_avatar_info(const std::shared_ptr<VirtualFileServer> &sid, const std::vector<std::string> &paths) {
|
||||
std::vector<std::tuple<fs::path, std::string>> file_paths{};
|
||||
for(const auto& path : paths)
|
||||
file_paths.emplace_back(this->server_path(sid) / fs::u8path("avatars"), path);
|
||||
return this->query_file_info(file_paths);
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// Created by WolverinDEV on 16/09/2020.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <thread>
|
||||
#include <shared_mutex>
|
||||
#include <sys/socket.h>
|
||||
#include <pipes/ws.h>
|
||||
#include <pipes/ssl.h>
|
||||
#include <misc/net.h>
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <random>
|
||||
#include <misc/memtracker.h>
|
||||
#include "./NetTools.h"
|
||||
|
||||
namespace ts::server::file::filesystem {
|
||||
#ifdef FS_INCLUDED
|
||||
namespace fs = std::experimental::filesystem;
|
||||
#endif
|
||||
|
||||
class LocalFileSystem : public filesystem::AbstractProvider {
|
||||
using FileModifyError = filesystem::FileModifyError;
|
||||
using DirectoryModifyError = filesystem::DirectoryModifyError;
|
||||
public:
|
||||
enum struct FileCategory {
|
||||
ICON,
|
||||
AVATAR,
|
||||
CHANNEL
|
||||
};
|
||||
|
||||
virtual ~LocalFileSystem();
|
||||
|
||||
bool initialize(std::string & /* error */, const std::string & /* root path */);
|
||||
void finalize();
|
||||
|
||||
void lock_file(const std::string& /* absolute path */);
|
||||
void unlock_file(const std::string& /* absolute path */);
|
||||
|
||||
[[nodiscard]] inline const auto &root_path() const { return this->root_path_; }
|
||||
|
||||
[[nodiscard]] std::string absolute_avatar_path(const std::shared_ptr<VirtualFileServer> &, const std::string&);
|
||||
[[nodiscard]] std::string absolute_icon_path(const std::shared_ptr<VirtualFileServer> &, const std::string&);
|
||||
[[nodiscard]] std::string absolute_channel_path(const std::shared_ptr<VirtualFileServer> &, ChannelId, const std::string&);
|
||||
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> initialize_server(const std::shared_ptr<VirtualFileServer> & /* server */) override;
|
||||
std::shared_ptr<ExecuteResponse<ServerCommandError>> delete_server(const std::shared_ptr<VirtualFileServer> & /* server */);
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_channel_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::tuple<ChannelId, std::string>>& /* names */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t>
|
||||
query_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<DirectoryModifyError>>
|
||||
create_channel_directory(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_channel_files(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::vector<std::string> &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileModifyError>>
|
||||
rename_channel_file(const std::shared_ptr<VirtualFileServer> & id, ChannelId channelId, const std::string &, ChannelId, const std::string &) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_icon_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_icon_directory(const std::shared_ptr<VirtualFileServer> & id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_icons(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_avatar_info(const std::shared_ptr<VirtualFileServer> & /* server */, const std::vector<std::string>& /* names */) override;
|
||||
|
||||
std::shared_ptr<directory_query_response_t> query_avatar_directory(const std::shared_ptr<VirtualFileServer> & id) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_avatars(const std::shared_ptr<VirtualFileServer> & id, const std::vector<std::string> &string) override;
|
||||
|
||||
private:
|
||||
#ifdef FS_INCLUDED
|
||||
[[nodiscard]] fs::path server_path(const std::shared_ptr<VirtualFileServer> &);
|
||||
[[nodiscard]] fs::path server_channel_path(const std::shared_ptr<VirtualFileServer> &, ChannelId);
|
||||
[[nodiscard]] static bool exceeds_base_path(const fs::path& /* base */, const fs::path& /* target */);
|
||||
[[nodiscard]] bool is_any_file_locked(const fs::path& /* base */, const std::string& /* path */, std::string& /* file (relative to the base) */);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ExecuteResponse<FileDeleteError, FileDeleteResponse>>
|
||||
delete_files(const fs::path& /* base */, const std::vector<std::string> &string);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<directory_query_response_t>
|
||||
query_directory(const fs::path& /* base */, const std::string &string, bool);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ExecuteResponse<FileInfoError, FileInfoResponse>>
|
||||
query_file_info(const std::vector<std::tuple<fs::path, std::string>> &string);
|
||||
#endif
|
||||
|
||||
template <typename error_t, typename result_t = EmptyExecuteResponse>
|
||||
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
|
||||
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
|
||||
}
|
||||
std::string target_file_path(FileCategory type, const std::shared_ptr<VirtualFileServer> &sid, ts::ChannelId cid, const std::string &path);
|
||||
|
||||
std::mutex result_notify_mutex{};
|
||||
std::condition_variable result_notify_cv{};
|
||||
|
||||
std::string root_path_{};
|
||||
|
||||
std::mutex locked_files_mutex{};
|
||||
std::deque<std::string> locked_files_{};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,395 @@
|
||||
//
|
||||
// Created by WolverinDEV on 04/05/2020.
|
||||
//
|
||||
|
||||
#include <cassert>
|
||||
#include <event2/event.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <random>
|
||||
#include "./LocalFileProvider.h"
|
||||
#include "./LocalFileTransfer.h"
|
||||
#include <experimental/filesystem>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
|
||||
Buffer* transfer::allocate_buffer(size_t size) {
|
||||
auto total_size = sizeof(Buffer) + size;
|
||||
auto buffer = (Buffer*) malloc(total_size);
|
||||
new (buffer) Buffer{};
|
||||
buffer->capacity = size;
|
||||
buffer->ref_count = 1;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
Buffer* transfer::ref_buffer(Buffer *buffer) {
|
||||
buffer->ref_count++;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void transfer::deref_buffer(Buffer *buffer) {
|
||||
if(--buffer->ref_count == 0) {
|
||||
buffer->~Buffer();
|
||||
free(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
FileClient::~FileClient() {
|
||||
this->flush_network_buffer();
|
||||
this->flush_disk_buffer();
|
||||
|
||||
assert(!this->disk_buffer.buffer_head);
|
||||
assert(!this->network_buffer.buffer_head);
|
||||
|
||||
assert(!this->file.file_descriptor);
|
||||
assert(!this->file.currently_processing);
|
||||
assert(!this->file.next_client);
|
||||
|
||||
assert(!this->networking.event_read);
|
||||
assert(!this->networking.event_write);
|
||||
|
||||
assert(this->state == STATE_DISCONNECTED);
|
||||
memtrack::freed<FileClient>(this);
|
||||
}
|
||||
|
||||
LocalFileTransfer::LocalFileTransfer(filesystem::LocalFileSystem *fs) : file_system_{fs} {}
|
||||
LocalFileTransfer::~LocalFileTransfer() = default;
|
||||
|
||||
bool LocalFileTransfer::start() {
|
||||
(void) this->start_client_worker();
|
||||
|
||||
{
|
||||
auto start_result = this->start_disk_io();
|
||||
switch (start_result) {
|
||||
case DiskIOStartResult::SUCCESS:
|
||||
break;
|
||||
case DiskIOStartResult::OUT_OF_MEMORY:
|
||||
logError(LOG_FT, "Failed to start disk worker (Out of memory)");
|
||||
goto error_exit_disk;
|
||||
default:
|
||||
logError(LOG_FT, "Failed to start disk worker ({})", (int) start_result);
|
||||
goto error_exit_disk;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto start_result = this->start_networking();
|
||||
switch (start_result) {
|
||||
case NetworkingStartResult::SUCCESS:
|
||||
break;
|
||||
|
||||
case NetworkingStartResult::OUT_OF_MEMORY:
|
||||
logError(LOG_FT, "Failed to start networking (Out of memory)");
|
||||
goto error_exit_network;
|
||||
|
||||
default:
|
||||
logError(LOG_FT, "Failed to start networking ({})", (int) start_result);
|
||||
goto error_exit_network;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
error_exit_network:
|
||||
this->shutdown_networking();
|
||||
|
||||
error_exit_disk:
|
||||
this->shutdown_disk_io();
|
||||
this->shutdown_client_worker();
|
||||
return false;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::stop() {
|
||||
this->shutdown_networking();
|
||||
this->shutdown_disk_io();
|
||||
this->shutdown_client_worker();
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
LocalFileTransfer::initialize_icon_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &server, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, server, 0, Transfer::TARGET_TYPE_ICON, info);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
LocalFileTransfer::initialize_avatar_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &server, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, server, 0, Transfer::TARGET_TYPE_AVATAR, info);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
LocalFileTransfer::initialize_channel_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &server, ChannelId cid, const TransferInfo &info) {
|
||||
return this->initialize_transfer(direction, server, cid, Transfer::TARGET_TYPE_CHANNEL_FILE, info);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>> LocalFileTransfer::initialize_transfer(
|
||||
Transfer::Direction direction, const std::shared_ptr<VirtualFileServer> &server, ChannelId cid,
|
||||
Transfer::TargetType ttype,
|
||||
const TransferInfo &info) {
|
||||
auto response = this->create_execute_response<TransferInitError, std::shared_ptr<Transfer>>();
|
||||
|
||||
std::lock_guard clock{this->transfer_create_mutex};
|
||||
if(info.max_concurrent_transfers > 0) {
|
||||
std::unique_lock tlock{this->transfers_mutex};
|
||||
{
|
||||
auto transfers = std::count_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& client) {
|
||||
return client->transfer && client->transfer->client_unique_id == info.client_unique_id && client->state < FileClient::STATE_FLUSHING;
|
||||
});
|
||||
transfers += std::count_if(this->pending_transfers.begin(), this->pending_transfers.end(), [&](const std::shared_ptr<Transfer>& transfer) {
|
||||
return transfer->client_unique_id == info.client_unique_id;
|
||||
});
|
||||
|
||||
if(transfers >= info.max_concurrent_transfers) {
|
||||
response->emplace_fail(TransferInitError::CLIENT_TOO_MANY_TRANSFERS, std::to_string(transfers));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto server_transfers = this->pending_transfers.size();
|
||||
server_transfers += std::count_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& client) {
|
||||
return client->transfer;
|
||||
});
|
||||
if(server_transfers >= this->max_concurrent_transfers) {
|
||||
response->emplace_fail(TransferInitError::SERVER_TOO_MANY_TRANSFERS, std::to_string(server_transfers));
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto transfer = std::make_shared<Transfer>();
|
||||
transfer->server_transfer_id = server->generate_transfer_id();
|
||||
transfer->server = server;
|
||||
transfer->channel_id = cid;
|
||||
transfer->target_type = ttype;
|
||||
transfer->direction = direction;
|
||||
|
||||
transfer->client_id = 0; /* must be provided externally */
|
||||
transfer->client_transfer_id = 0; /* must be provided externally */
|
||||
|
||||
transfer->server_addresses.reserve(this->network.bindings.size());
|
||||
for(auto& binding : this->network.bindings) {
|
||||
if(!binding->file_descriptor) continue;
|
||||
|
||||
transfer->server_addresses.emplace_back(Transfer::Address{binding->hostname, net::port(binding->address)});
|
||||
}
|
||||
|
||||
transfer->target_file_path = info.file_path;
|
||||
transfer->file_offset = info.file_offset;
|
||||
transfer->expected_file_size = info.expected_file_size;
|
||||
transfer->max_bandwidth = info.max_bandwidth;
|
||||
transfer->client_unique_id = info.client_unique_id;
|
||||
transfer->client_id = info.client_id;
|
||||
|
||||
constexpr static std::string_view kTokenCharacters{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"};
|
||||
transfer->transfer_key.resize(TRANSFER_KEY_LENGTH);
|
||||
for(auto& c : transfer->transfer_key) {
|
||||
c = kTokenCharacters[transfer_random_token_generator() % kTokenCharacters.length()];
|
||||
}
|
||||
transfer->transfer_key[0] = (char) 'r'; /* (114) */ /* a non valid SSL header type to indicate that we're using a file transfer key and not doing a SSL handshake */
|
||||
transfer->transfer_key[1] = (char) 'a'; /* ( 97) */
|
||||
transfer->transfer_key[2] = (char) 'w'; /* (119) */
|
||||
|
||||
transfer->initialized_timestamp = std::chrono::system_clock::now();
|
||||
|
||||
{
|
||||
std::string absolute_path{};
|
||||
switch (transfer->target_type) {
|
||||
case Transfer::TARGET_TYPE_AVATAR:
|
||||
absolute_path = this->file_system_->absolute_avatar_path(transfer->server, transfer->target_file_path);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_ICON:
|
||||
absolute_path = this->file_system_->absolute_icon_path(transfer->server, transfer->target_file_path);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_CHANNEL_FILE:
|
||||
absolute_path = this->file_system_->absolute_channel_path(transfer->server, transfer->channel_id, transfer->target_file_path);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_UNKNOWN:
|
||||
default:
|
||||
response->emplace_fail(TransferInitError::INVALID_FILE_TYPE, "");
|
||||
return response;
|
||||
}
|
||||
transfer->absolute_file_path = absolute_path;
|
||||
|
||||
const auto root_path_length = this->file_system_->root_path().size();
|
||||
if(root_path_length < absolute_path.size())
|
||||
transfer->relative_file_path = absolute_path.substr(root_path_length);
|
||||
else
|
||||
transfer->relative_file_path = "error";
|
||||
transfer->file_name = fs::u8path(absolute_path).filename();
|
||||
}
|
||||
|
||||
if(direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
auto path = fs::u8path(transfer->absolute_file_path);
|
||||
std::error_code error{};
|
||||
if(!fs::exists(path, error)) {
|
||||
response->emplace_fail(TransferInitError::FILE_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
} else if(error) {
|
||||
logWarning(LOG_FT, "Failed to check for file at {}: {}. Assuming it does not exists.", transfer->absolute_file_path, error.value(), error.message());
|
||||
response->emplace_fail(TransferInitError::FILE_DOES_NOT_EXISTS, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
auto status = fs::status(path, error);
|
||||
if(error) {
|
||||
logWarning(LOG_FT, "Failed to status for file at {}: {}. Ignoring file transfer.", transfer->absolute_file_path, error.value(), error.message());
|
||||
response->emplace_fail(TransferInitError::IO_ERROR, "stat");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(status.type() != fs::file_type::regular) {
|
||||
response->emplace_fail(TransferInitError::FILE_IS_NOT_A_FILE, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
transfer->expected_file_size = fs::file_size(path, error);
|
||||
if(error) {
|
||||
logWarning(LOG_FT, "Failed to get file size for file at {}: {}. Ignoring file transfer.", transfer->absolute_file_path, error.value(), error.message());
|
||||
response->emplace_fail(TransferInitError::IO_ERROR, "file_size");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(info.download_client_quota_limit > 0 && info.download_client_quota_limit <= transfer->expected_file_size) {
|
||||
response->emplace_fail(TransferInitError::CLIENT_QUOTA_EXCEEDED, "");
|
||||
return response;
|
||||
}
|
||||
if(info.download_server_quota_limit > 0 && info.download_server_quota_limit <= transfer->expected_file_size) {
|
||||
response->emplace_fail(TransferInitError::SERVER_QUOTA_EXCEEDED, "");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard tlock{this->transfers_mutex};
|
||||
this->pending_transfers.push_back(transfer);
|
||||
}
|
||||
|
||||
switch (transfer->target_type) {
|
||||
case Transfer::TARGET_TYPE_AVATAR:
|
||||
logMessage(LOG_FT, "Initialized avatar transfer for avatar \"{}\" ({} bytes, transferring {} bytes).", transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_ICON:
|
||||
logMessage(LOG_FT, "Initialized icon transfer for icon \"{}\" ({} bytes, transferring {} bytes).",
|
||||
transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_CHANNEL_FILE:
|
||||
logMessage(LOG_FT, "Initialized channel transfer for file \"{}/{}\" ({} bytes, transferring {} bytes).",
|
||||
transfer->channel_id, transfer->target_file_path, transfer->expected_file_size, transfer->expected_file_size - transfer->file_offset);
|
||||
break;
|
||||
case Transfer::TARGET_TYPE_UNKNOWN:
|
||||
default:
|
||||
response->emplace_fail(TransferInitError::INVALID_FILE_TYPE, "");
|
||||
return response;
|
||||
}
|
||||
|
||||
if(auto callback{this->callback_transfer_registered}; callback)
|
||||
callback(transfer);
|
||||
|
||||
response->emplace_success(std::move(transfer));
|
||||
return response;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferActionError>> LocalFileTransfer::stop_transfer(const std::shared_ptr<VirtualFileServer>& server, transfer_id id, bool flush) {
|
||||
auto response = this->create_execute_response<TransferActionError>();
|
||||
|
||||
std::shared_ptr<Transfer> transfer{};
|
||||
std::shared_ptr<FileClient> connected_transfer{};
|
||||
|
||||
{
|
||||
std::lock_guard tlock{this->transfers_mutex};
|
||||
|
||||
auto ct_it = std::find_if(this->transfers_.begin(), this->transfers_.end(), [&](const std::shared_ptr<FileClient>& t) {
|
||||
return t->transfer && t->transfer->server_transfer_id == id && t->transfer->server == server;
|
||||
});
|
||||
if(ct_it != this->transfers_.end())
|
||||
connected_transfer = *ct_it;
|
||||
else {
|
||||
auto t_it = std::find_if(this->pending_transfers.begin(), this->pending_transfers.end(), [&](const std::shared_ptr<Transfer>& t) {
|
||||
return t->server_transfer_id == id && t->server == server;
|
||||
});
|
||||
if(t_it != this->pending_transfers.end()) {
|
||||
transfer = *t_it;
|
||||
this->pending_transfers.erase(t_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!transfer) {
|
||||
if(connected_transfer)
|
||||
transfer = connected_transfer->transfer;
|
||||
else {
|
||||
response->emplace_fail(TransferActionError{TransferActionError::UNKNOWN_TRANSFER, ""});
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
if(connected_transfer) {
|
||||
this->invoke_aborted_callback(connected_transfer, { TransferError::USER_REQUEST, "" });
|
||||
logMessage(LOG_FT, "{} Stopping transfer due to an user request.", connected_transfer->log_prefix());
|
||||
|
||||
std::unique_lock slock{connected_transfer->state_mutex};
|
||||
this->disconnect_client(connected_transfer, slock, flush);
|
||||
} else {
|
||||
this->invoke_aborted_callback(transfer, { TransferError::USER_REQUEST, "" });
|
||||
logMessage(LOG_FT, "Removing pending file transfer for id {}", id);
|
||||
}
|
||||
|
||||
response->emplace_success();
|
||||
return response;
|
||||
}
|
||||
|
||||
inline void apply_transfer_info(const std::shared_ptr<Transfer>& transfer, ActiveFileTransfer& info) {
|
||||
info.server_transfer_id = transfer->server_transfer_id;
|
||||
info.client_transfer_id = transfer->client_transfer_id;
|
||||
info.direction = transfer->direction;
|
||||
info.client_id = transfer->client_id;
|
||||
info.client_unique_id = transfer->client_unique_id;
|
||||
|
||||
info.file_path = transfer->relative_file_path;
|
||||
info.file_name = transfer->file_name;
|
||||
|
||||
info.expected_size = transfer->expected_file_size;
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>> LocalFileTransfer::list_transfer() {
|
||||
std::vector<ActiveFileTransfer> transfer_infos{};
|
||||
auto response = this->create_execute_response<TransferListError, std::vector<ActiveFileTransfer>>();
|
||||
|
||||
std::unique_lock tlock{this->transfers_mutex};
|
||||
auto awaiting_transfers = this->pending_transfers;
|
||||
auto running_transfers = this->transfers_;
|
||||
tlock.unlock();
|
||||
|
||||
transfer_infos.reserve(awaiting_transfers.size() + running_transfers.size());
|
||||
for(const auto& transfer : awaiting_transfers) {
|
||||
ActiveFileTransfer info{};
|
||||
apply_transfer_info(transfer, info);
|
||||
info.size_done = transfer->file_offset;
|
||||
|
||||
info.status = ActiveFileTransfer::NOT_STARTED;
|
||||
info.runtime = std::chrono::milliseconds{0};
|
||||
|
||||
info.average_speed = 0;
|
||||
info.current_speed = 0;
|
||||
transfer_infos.push_back(info);
|
||||
}
|
||||
|
||||
for(const auto& client : running_transfers) {
|
||||
auto transfer = client->transfer;
|
||||
if(!transfer) continue;
|
||||
|
||||
ActiveFileTransfer info{};
|
||||
apply_transfer_info(transfer, info);
|
||||
info.size_done = transfer->file_offset + client->statistics.file_transferred.total_bytes;
|
||||
|
||||
info.status = ActiveFileTransfer::RUNNING;
|
||||
info.runtime = std::chrono::floor<std::chrono::milliseconds>(std::chrono::system_clock::now() - client->timings.key_received);
|
||||
|
||||
info.average_speed = client->statistics.file_transferred.average_bandwidth();
|
||||
info.current_speed = client->statistics.file_transferred.current_bandwidth();
|
||||
transfer_infos.push_back(info);
|
||||
}
|
||||
|
||||
response->emplace_success(std::move(transfer_infos));
|
||||
return response;
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
//
|
||||
// Created by WolverinDEV on 16/09/2020.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
#include <thread>
|
||||
#include <shared_mutex>
|
||||
#include <sys/socket.h>
|
||||
#include <pipes/ws.h>
|
||||
#include <pipes/ssl.h>
|
||||
#include <misc/net.h>
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <random>
|
||||
#include <misc/memtracker.h>
|
||||
#include "./NetTools.h"
|
||||
#include "LocalFileSystem.h"
|
||||
|
||||
#define TRANSFER_MAX_CACHED_BYTES (1024 * 1024 * 1) // Buffer up to 1mb
|
||||
|
||||
namespace ts::server::file::transfer {
|
||||
class LocalFileTransfer;
|
||||
|
||||
struct Buffer {
|
||||
Buffer* next{nullptr};
|
||||
|
||||
std::atomic_uint32_t ref_count{0};
|
||||
uint32_t capacity{0};
|
||||
uint32_t length{0};
|
||||
uint32_t offset{0};
|
||||
|
||||
char data[1]{};
|
||||
};
|
||||
|
||||
[[nodiscard]] extern Buffer* allocate_buffer(size_t);
|
||||
[[nodiscard]] extern Buffer* ref_buffer(Buffer*);
|
||||
extern void deref_buffer(Buffer*);
|
||||
|
||||
/* all variables are locked via the state_mutex */
|
||||
struct FileClient : std::enable_shared_from_this<FileClient> {
|
||||
LocalFileTransfer* handle;
|
||||
std::shared_ptr<Transfer> transfer{nullptr};
|
||||
|
||||
std::shared_mutex state_mutex{};
|
||||
enum {
|
||||
STATE_AWAITING_KEY, /* includes SSL/HTTP init */
|
||||
STATE_TRANSFERRING,
|
||||
STATE_FLUSHING,
|
||||
STATE_DISCONNECTED
|
||||
} state{STATE_AWAITING_KEY};
|
||||
|
||||
bool finished_signal_send{false};
|
||||
|
||||
enum NetworkingProtocol {
|
||||
PROTOCOL_UNKNOWN,
|
||||
PROTOCOL_HTTPS,
|
||||
PROTOCOL_TS_V1
|
||||
};
|
||||
|
||||
enum HTTPUploadState {
|
||||
HTTP_AWAITING_HEADER,
|
||||
HTTP_STATE_AWAITING_BOUNDARY,
|
||||
HTTP_STATE_AWAITING_BOUNDARY_END,
|
||||
HTTP_STATE_UPLOADING,
|
||||
HTTP_STATE_DOWNLOADING
|
||||
};
|
||||
|
||||
struct {
|
||||
bool file_locked{false};
|
||||
int file_descriptor{0};
|
||||
|
||||
bool currently_processing{false};
|
||||
FileClient* next_client{nullptr};
|
||||
|
||||
bool query_media_bytes{false};
|
||||
uint8_t media_bytes[TRANSFER_MEDIA_BYTES_LENGTH]{0};
|
||||
uint8_t media_bytes_length{0};
|
||||
} file{};
|
||||
|
||||
struct {
|
||||
size_t provided_bytes{0};
|
||||
char key[TRANSFER_KEY_LENGTH]{0};
|
||||
} transfer_key{};
|
||||
|
||||
struct {
|
||||
std::mutex mutex{};
|
||||
size_t bytes{0};
|
||||
|
||||
bool buffering_stopped{false};
|
||||
bool write_disconnected{false};
|
||||
|
||||
Buffer* buffer_head{nullptr};
|
||||
Buffer** buffer_tail{&buffer_head};
|
||||
} network_buffer{};
|
||||
|
||||
struct {
|
||||
std::mutex mutex{};
|
||||
size_t bytes{0};
|
||||
|
||||
bool buffering_stopped{false};
|
||||
bool write_disconnected{false};
|
||||
|
||||
Buffer* buffer_head{nullptr};
|
||||
Buffer** buffer_tail{&buffer_head};
|
||||
} disk_buffer{};
|
||||
|
||||
struct {
|
||||
sockaddr_storage address{};
|
||||
int file_descriptor{0};
|
||||
|
||||
NetworkingProtocol protocol{PROTOCOL_UNKNOWN};
|
||||
|
||||
struct event* event_read{nullptr};
|
||||
struct event* event_write{nullptr};
|
||||
struct event* event_throttle{nullptr};
|
||||
|
||||
bool add_event_write{false}, add_event_read{false};
|
||||
|
||||
std::chrono::system_clock::time_point disconnect_timeout{};
|
||||
|
||||
networking::NetworkThrottle client_throttle{};
|
||||
/* the right side is the server throttle */
|
||||
networking::DualNetworkThrottle throttle{&client_throttle, &networking::NetworkThrottle::kNoThrottle};
|
||||
|
||||
pipes::SSL pipe_ssl{};
|
||||
bool pipe_ssl_init{false};
|
||||
std::unique_ptr<Buffer, decltype(deref_buffer)*> http_header_buffer{nullptr, deref_buffer};
|
||||
HTTPUploadState http_state{HTTPUploadState::HTTP_AWAITING_HEADER};
|
||||
|
||||
std::string http_boundary{};
|
||||
|
||||
/* Only read the transfer key length at the beginning. We than have the actual limit which will be set via throttle */
|
||||
size_t max_read_buffer_size{TRANSFER_KEY_LENGTH};
|
||||
} networking{};
|
||||
|
||||
struct {
|
||||
networking::TransferStatistics network_send{};
|
||||
networking::TransferStatistics network_received{};
|
||||
|
||||
networking::TransferStatistics file_transferred{};
|
||||
|
||||
networking::TransferStatistics disk_bytes_read{};
|
||||
networking::TransferStatistics disk_bytes_write{};
|
||||
} statistics{};
|
||||
|
||||
struct {
|
||||
std::chrono::system_clock::time_point last_write{};
|
||||
std::chrono::system_clock::time_point last_read{};
|
||||
|
||||
std::chrono::system_clock::time_point connected{};
|
||||
std::chrono::system_clock::time_point key_received{};
|
||||
std::chrono::system_clock::time_point disconnecting{};
|
||||
} timings;
|
||||
|
||||
explicit FileClient(LocalFileTransfer* handle) : handle{handle} { memtrack::allocated<FileClient>(this); }
|
||||
~FileClient();
|
||||
|
||||
void add_network_write_event(bool /* ignore bandwidth limits */);
|
||||
void add_network_write_event_nolock(bool /* ignore bandwidth limits */);
|
||||
|
||||
/* will check if we've enough space in out read buffer again */
|
||||
void add_network_read_event(bool /* ignore bandwidth limits */);
|
||||
|
||||
bool send_file_bytes(const void* /* buffer */, size_t /* length */);
|
||||
bool enqueue_network_buffer_bytes(const void* /* buffer */, size_t /* length */);
|
||||
bool enqueue_disk_buffer_bytes(const void* /* buffer */, size_t /* length */);
|
||||
|
||||
/* these function clear the buffers and set the write disconnected flags to true so no new buffers will be enqueued */
|
||||
size_t flush_network_buffer();
|
||||
void flush_disk_buffer();
|
||||
|
||||
[[nodiscard]] bool buffers_flushed();
|
||||
[[nodiscard]] inline std::string log_prefix() const { return "[" + net::to_string(this->networking.address) + "]"; }
|
||||
};
|
||||
|
||||
enum struct DiskIOStartResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY
|
||||
};
|
||||
|
||||
enum struct NetworkingStartResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY
|
||||
};
|
||||
|
||||
enum struct NetworkingBindResult {
|
||||
SUCCESS,
|
||||
|
||||
BINDING_ALREADY_EXISTS,
|
||||
NETWORKING_NOT_INITIALIZED,
|
||||
FAILED_TO_ALLOCATE_SOCKET, /* errno is set */
|
||||
FAILED_TO_BIND,
|
||||
FAILED_TO_LISTEN,
|
||||
|
||||
OUT_OF_MEMORY,
|
||||
};
|
||||
|
||||
enum struct NetworkingUnbindResult {
|
||||
SUCCESS,
|
||||
UNKNOWN_BINDING
|
||||
};
|
||||
|
||||
enum struct ClientWorkerStartResult {
|
||||
SUCCESS
|
||||
};
|
||||
|
||||
enum struct NetworkInitializeResult {
|
||||
SUCCESS,
|
||||
OUT_OF_MEMORY
|
||||
};
|
||||
|
||||
enum struct FileInitializeResult {
|
||||
SUCCESS,
|
||||
|
||||
INVALID_TRANSFER_DIRECTION,
|
||||
OUT_OF_MEMORY,
|
||||
|
||||
PROCESS_FILE_LIMIT_REACHED,
|
||||
SYSTEM_FILE_LIMIT_REACHED,
|
||||
|
||||
FILE_IS_BUSY,
|
||||
FILE_DOES_NOT_EXISTS,
|
||||
FILE_SYSTEM_ERROR,
|
||||
FILE_IS_A_DIRECTORY,
|
||||
|
||||
FILE_TOO_LARGE,
|
||||
DISK_IS_READ_ONLY,
|
||||
|
||||
FILE_SEEK_FAILED,
|
||||
FILE_SIZE_MISMATCH,
|
||||
|
||||
FILE_IS_NOT_ACCESSIBLE,
|
||||
|
||||
FAILED_TO_READ_MEDIA_BYTES,
|
||||
COUNT_NOT_CREATE_DIRECTORIES,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
constexpr static std::array<std::string_view, (size_t) FileInitializeResult::MAX> kFileInitializeResultMessages{
|
||||
/* SUCCESS */ "success",
|
||||
|
||||
/* INVALID_TRANSFER_DIRECTION */ "invalid file transfer direction",
|
||||
/* OUT_OF_MEMORY */ "out of memory",
|
||||
|
||||
/* PROCESS_FILE_LIMIT_REACHED */ "process file limit reached",
|
||||
/* SYSTEM_FILE_LIMIT_REACHED */ "system file limit reached",
|
||||
|
||||
/* FILE_IS_BUSY */ "target file is busy",
|
||||
/* FILE_DOES_NOT_EXISTS */ "target file does not exists",
|
||||
/* FILE_SYSTEM_ERROR */ "internal file system error",
|
||||
/* FILE_IS_A_DIRECTORY */ "target file is a directory",
|
||||
|
||||
/* FILE_TOO_LARGE */ "file is too large",
|
||||
/* DISK_IS_READ_ONLY */ "disk is in read only mode",
|
||||
|
||||
/* FILE_SEEK_FAILED */ "failed to seek to target file offset",
|
||||
/* FILE_SIZE_MISMATCH */ "file size miss match",
|
||||
/* FILE_IS_NOT_ACCESSIBLE */ "file is not accessible",
|
||||
/* FAILED_TO_READ_MEDIA_BYTES */ "failed to read file media bytes",
|
||||
/* COUNT_NOT_CREATE_DIRECTORIES */ "could not create required directories"
|
||||
};
|
||||
|
||||
enum struct TransferKeyApplyResult {
|
||||
SUCCESS,
|
||||
FILE_ERROR,
|
||||
UNKNOWN_KEY,
|
||||
|
||||
INTERNAL_ERROR
|
||||
};
|
||||
|
||||
enum struct TransferUploadRawResult {
|
||||
MORE_DATA_TO_RECEIVE,
|
||||
FINISH,
|
||||
FINISH_OVERFLOW,
|
||||
|
||||
/* UNKNOWN ERROR */
|
||||
};
|
||||
|
||||
enum struct TransferUploadHTTPResult {
|
||||
MORE_DATA_TO_RECEIVE,
|
||||
FINISH,
|
||||
|
||||
BOUNDARY_MISSING,
|
||||
BOUNDARY_TOKEN_INVALID,
|
||||
BOUNDARY_INVALID,
|
||||
|
||||
MISSING_CONTENT_TYPE,
|
||||
INVALID_CONTENT_TYPE
|
||||
/* UNKNOWN ERROR */
|
||||
};
|
||||
|
||||
struct NetworkBinding {
|
||||
std::string hostname{};
|
||||
sockaddr_storage address{};
|
||||
};
|
||||
|
||||
struct ActiveNetworkBinding : std::enable_shared_from_this<ActiveNetworkBinding> {
|
||||
std::string hostname{};
|
||||
sockaddr_storage address{};
|
||||
|
||||
int file_descriptor{-1};
|
||||
struct event* accept_event{nullptr};
|
||||
|
||||
LocalFileTransfer* handle{nullptr};
|
||||
};
|
||||
|
||||
class LocalFileTransfer : public AbstractProvider {
|
||||
public:
|
||||
explicit LocalFileTransfer(filesystem::LocalFileSystem*);
|
||||
~LocalFileTransfer();
|
||||
|
||||
[[nodiscard]] bool start();
|
||||
void stop();
|
||||
|
||||
[[nodiscard]] NetworkingBindResult add_network_binding(const NetworkBinding& /* binding */);
|
||||
[[nodiscard]] std::vector<NetworkBinding> active_network_bindings();
|
||||
[[nodiscard]] NetworkingUnbindResult remove_network_binding(const NetworkBinding& /* binding */);
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_channel_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer>& server, ChannelId channelId,
|
||||
const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_icon_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer>& server, const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_avatar_transfer(Transfer::Direction direction, const std::shared_ptr<VirtualFileServer>& server, const TransferInfo &info) override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferListError, std::vector<ActiveFileTransfer>>> list_transfer() override;
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferActionError>> stop_transfer(const std::shared_ptr<VirtualFileServer>& /* server */, transfer_id id, bool) override;
|
||||
private:
|
||||
enum struct DiskIOLoopState {
|
||||
STOPPED,
|
||||
RUNNING,
|
||||
|
||||
STOPPING,
|
||||
FORCE_STOPPING
|
||||
};
|
||||
filesystem::LocalFileSystem* file_system_;
|
||||
|
||||
size_t max_concurrent_transfers{1024};
|
||||
std::mt19937 transfer_random_token_generator{std::random_device{}()};
|
||||
|
||||
std::mutex result_notify_mutex{};
|
||||
std::condition_variable result_notify_cv{};
|
||||
|
||||
std::mutex transfers_mutex{};
|
||||
std::mutex transfer_create_mutex{};
|
||||
std::deque<std::shared_ptr<FileClient>> transfers_{};
|
||||
std::deque<std::shared_ptr<Transfer>> pending_transfers{};
|
||||
|
||||
enum ServerState {
|
||||
STOPPED,
|
||||
RUNNING
|
||||
} state{ServerState::STOPPED};
|
||||
|
||||
struct {
|
||||
bool active{false};
|
||||
|
||||
std::thread dispatch_thread{};
|
||||
std::mutex mutex{};
|
||||
std::condition_variable notify_cv{};
|
||||
} disconnect;
|
||||
|
||||
struct {
|
||||
std::mutex mutex;
|
||||
|
||||
bool active{false};
|
||||
std::thread dispatch_thread{};
|
||||
struct event_base* event_base{nullptr};
|
||||
|
||||
std::deque<std::shared_ptr<ActiveNetworkBinding>> bindings{};
|
||||
} network{};
|
||||
|
||||
struct {
|
||||
DiskIOLoopState state{DiskIOLoopState::STOPPED};
|
||||
std::thread dispatch_thread{};
|
||||
std::mutex queue_lock{};
|
||||
std::condition_variable notify_work_awaiting{};
|
||||
std::condition_variable notify_client_processed{};
|
||||
|
||||
FileClient* queue_head{nullptr};
|
||||
FileClient** queue_tail{&queue_head};
|
||||
} disk_io{};
|
||||
|
||||
template <typename error_t, typename result_t = EmptyExecuteResponse>
|
||||
std::shared_ptr<ExecuteResponse<error_t, result_t>> create_execute_response() {
|
||||
return std::make_shared<ExecuteResponse<error_t, result_t>>(this->result_notify_mutex, this->result_notify_cv);
|
||||
}
|
||||
|
||||
std::shared_ptr<ExecuteResponse<TransferInitError, std::shared_ptr<Transfer>>>
|
||||
initialize_transfer(Transfer::Direction, const std::shared_ptr<VirtualFileServer> &, ChannelId, Transfer::TargetType, const TransferInfo &info);
|
||||
|
||||
[[nodiscard]] NetworkingStartResult start_networking();
|
||||
[[nodiscard]] DiskIOStartResult start_disk_io();
|
||||
[[nodiscard]] ClientWorkerStartResult start_client_worker();
|
||||
|
||||
void shutdown_networking();
|
||||
void shutdown_disk_io();
|
||||
void shutdown_client_worker();
|
||||
|
||||
void disconnect_client(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */, bool /* flush network */);
|
||||
|
||||
[[nodiscard]] NetworkInitializeResult initialize_networking(const std::shared_ptr<FileClient>& /* client */, int /* file descriptor */);
|
||||
/* might block 'till all IO operations have been succeeded */
|
||||
void finalize_networking(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */);
|
||||
|
||||
[[nodiscard]] FileInitializeResult initialize_file_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
void finalize_file_io(const std::shared_ptr<FileClient>& /* client */, std::unique_lock<std::shared_mutex>& /* state lock */);
|
||||
|
||||
[[nodiscard]] bool initialize_client_ssl(const std::shared_ptr<FileClient>& /* client */);
|
||||
void finalize_client_ssl(const std::shared_ptr<FileClient>& /* client */);
|
||||
|
||||
void enqueue_disk_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
void execute_disk_io(const std::shared_ptr<FileClient>& /* client */);
|
||||
|
||||
void test_disconnecting_state(const std::shared_ptr<FileClient>& /* client */);
|
||||
|
||||
[[nodiscard]] TransferUploadRawResult handle_transfer_upload_raw(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */, size_t* /* bytes written */);
|
||||
[[nodiscard]] TransferUploadHTTPResult handle_transfer_upload_http(const std::shared_ptr<FileClient>& /* client */, const char * /* buffer */, size_t /* length */);
|
||||
|
||||
void send_http_response(const std::shared_ptr<FileClient>& /* client */, http::HttpResponse& /* response */);
|
||||
|
||||
static void callback_transfer_network_write(int, short, void*);
|
||||
static void callback_transfer_network_read(int, short, void*);
|
||||
static void callback_transfer_network_throttle(int, short, void*);
|
||||
static void callback_transfer_network_accept(int, short, void*);
|
||||
|
||||
static void dispatch_loop_client_worker(void*);
|
||||
static void dispatch_loop_network(void*);
|
||||
static void dispatch_loop_disk_io(void*);
|
||||
|
||||
void report_transfer_statistics(const std::shared_ptr<FileClient>& /* client */);
|
||||
[[nodiscard]] TransferStatistics generate_transfer_statistics_report(const std::shared_ptr<FileClient>& /* client */);
|
||||
void invoke_aborted_callback(const std::shared_ptr<FileClient>& /* client */, const TransferError& /* error */);
|
||||
void invoke_aborted_callback(const std::shared_ptr<Transfer>& /* pending transfer */, const TransferError& /* error */);
|
||||
|
||||
size_t handle_transfer_read(const std::shared_ptr<FileClient>& /* client */, const char* /* buffer */, size_t /* bytes */);
|
||||
size_t handle_transfer_read_raw(const std::shared_ptr<FileClient>& /* client */, const char* /* buffer */, size_t /* bytes */);
|
||||
[[nodiscard]] TransferKeyApplyResult handle_transfer_key_provided(const std::shared_ptr<FileClient>& /* client */, std::string& /* error */);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
//
|
||||
// Created by WolverinDEV on 04/05/2020.
|
||||
//
|
||||
|
||||
#include <cassert>
|
||||
#include <event2/event.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "./LocalFileProvider.h"
|
||||
#include "./LocalFileTransfer.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
|
||||
ClientWorkerStartResult LocalFileTransfer::start_client_worker() {
|
||||
assert(!this->disconnect.active);
|
||||
this->disconnect.active = true;
|
||||
|
||||
this->disconnect.dispatch_thread = std::thread(&LocalFileTransfer::dispatch_loop_client_worker, this);
|
||||
return ClientWorkerStartResult::SUCCESS;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::shutdown_client_worker() {
|
||||
if(!this->disconnect.active) return;
|
||||
this->disconnect.active = false;
|
||||
|
||||
this->disconnect.notify_cv.notify_all();
|
||||
if(this->disconnect.dispatch_thread.joinable())
|
||||
this->disconnect.dispatch_thread.join();
|
||||
|
||||
{
|
||||
std::unique_lock tlock{this->transfers_mutex};
|
||||
if(!this->transfers_.empty())
|
||||
logWarning(LOG_FT, "Shutting down disconnect worker even thou we still have some active clients. This could cause memory leaks.");
|
||||
}
|
||||
}
|
||||
|
||||
void LocalFileTransfer::disconnect_client(const std::shared_ptr<FileClient> &client, std::unique_lock<std::shared_mutex>& state_lock, bool flush) {
|
||||
assert(state_lock.owns_lock());
|
||||
|
||||
if(client->state == FileClient::STATE_DISCONNECTED || (client->state == FileClient::STATE_FLUSHING && flush)) {
|
||||
return; /* shall NOT happen */
|
||||
}
|
||||
|
||||
#define del_ev_noblock(event) if(event) event_del_noblock(event)
|
||||
|
||||
client->state = flush ? FileClient::STATE_FLUSHING : FileClient::STATE_DISCONNECTED;
|
||||
client->timings.disconnecting = std::chrono::system_clock::now();
|
||||
if(flush) {
|
||||
const auto network_flush_time = client->networking.throttle.expected_writing_time(client->network_buffer.bytes) + std::chrono::seconds{10};
|
||||
|
||||
del_ev_noblock(client->networking.event_read);
|
||||
|
||||
client->networking.disconnect_timeout = std::chrono::system_clock::now() + network_flush_time;
|
||||
debugMessage(LOG_FT, "{} Disconnecting client. Flushing pending bytes (max {} seconds)", client->log_prefix(), std::chrono::floor<std::chrono::seconds>(network_flush_time).count());
|
||||
|
||||
client->add_network_write_event_nolock(false);
|
||||
this->enqueue_disk_io(client);
|
||||
} else {
|
||||
del_ev_noblock(client->networking.event_read);
|
||||
del_ev_noblock(client->networking.event_write);
|
||||
del_ev_noblock(client->networking.event_throttle);
|
||||
|
||||
this->disconnect.notify_cv.notify_one();
|
||||
}
|
||||
|
||||
#undef del_ev_noblock
|
||||
}
|
||||
|
||||
void LocalFileTransfer::test_disconnecting_state(const std::shared_ptr<FileClient> &client) {
|
||||
if(client->state != FileClient::STATE_FLUSHING)
|
||||
return;
|
||||
|
||||
if(!client->buffers_flushed())
|
||||
return;
|
||||
|
||||
debugMessage(LOG_FT, "{} Disk and network buffers are flushed. Closing connection.", client->log_prefix());
|
||||
std::unique_lock s_lock{client->state_mutex};
|
||||
this->disconnect_client(client, s_lock, false);
|
||||
}
|
||||
|
||||
void LocalFileTransfer::dispatch_loop_client_worker(void *ptr_transfer) {
|
||||
auto provider = reinterpret_cast<LocalFileTransfer*>(ptr_transfer);
|
||||
|
||||
while(provider->disconnect.active) {
|
||||
{
|
||||
std::unique_lock dlock{provider->disconnect.mutex};
|
||||
provider->disconnect.notify_cv.wait_for(dlock, std::chrono::milliseconds {500}); /* report all 500ms the statistics */
|
||||
}
|
||||
/* run the disconnect worker at least once before exiting */
|
||||
|
||||
/* transfer statistics */
|
||||
{
|
||||
std::unique_lock tlock{provider->transfers_mutex};
|
||||
auto transfers = provider->transfers_;
|
||||
tlock.unlock();
|
||||
for(const auto& transfer : transfers) {
|
||||
switch(transfer->state) {
|
||||
case FileClient::STATE_TRANSFERRING:
|
||||
break;
|
||||
case FileClient::STATE_FLUSHING:
|
||||
if(!transfer->transfer)
|
||||
continue;
|
||||
|
||||
if(transfer->transfer->direction != Transfer::DIRECTION_DOWNLOAD)
|
||||
continue;
|
||||
|
||||
if(transfer->buffers_flushed())
|
||||
continue;
|
||||
|
||||
break; /* we're still transferring (sending data) */
|
||||
case FileClient::STATE_AWAITING_KEY:
|
||||
case FileClient::STATE_DISCONNECTED:
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
provider->report_transfer_statistics(transfer);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::deque<std::shared_ptr<Transfer>> timeouted_transfers{};
|
||||
|
||||
{
|
||||
std::unique_lock tlock{provider->transfers_mutex};
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::copy_if(provider->pending_transfers.begin(), provider->pending_transfers.end(), std::back_inserter(timeouted_transfers), [&](const std::shared_ptr<Transfer>& t) {
|
||||
return t->initialized_timestamp + std::chrono::seconds{10} < now;
|
||||
});
|
||||
provider->pending_transfers.erase(std::remove_if(provider->pending_transfers.begin(), provider->pending_transfers.end(), [&](const auto& t) {
|
||||
return std::find(timeouted_transfers.begin(), timeouted_transfers.end(), t) != timeouted_transfers.end();
|
||||
}), provider->pending_transfers.end());
|
||||
}
|
||||
|
||||
for(const auto& pt : timeouted_transfers)
|
||||
provider->invoke_aborted_callback(pt, { TransferError::TRANSFER_TIMEOUT, "" });
|
||||
|
||||
if(!timeouted_transfers.empty())
|
||||
logMessage(LOG_FT, "Removed {} pending transfers because no request has been made for them.", timeouted_transfers.size());
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
std::deque<std::shared_ptr<FileClient>> disconnected_clients{};
|
||||
{
|
||||
std::unique_lock tlock{provider->transfers_mutex};
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::copy_if(provider->transfers_.begin(), provider->transfers_.end(), std::back_inserter(disconnected_clients), [&](const std::shared_ptr<FileClient>& t) {
|
||||
std::shared_lock slock{t->state_mutex};
|
||||
if(t->state == FileClient::STATE_DISCONNECTED) {
|
||||
return true;
|
||||
} else if(t->state == FileClient::STATE_AWAITING_KEY) {
|
||||
return t->timings.connected + std::chrono::seconds{10} < now;
|
||||
} else if(t->state == FileClient::STATE_TRANSFERRING) {
|
||||
assert(t->transfer);
|
||||
if(t->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
return t->timings.last_read + std::chrono::seconds{5} < now;
|
||||
} else if(t->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
return t->timings.last_write + std::chrono::seconds{5} < now;
|
||||
}
|
||||
} else if(t->state == FileClient::STATE_FLUSHING) {
|
||||
if(t->networking.disconnect_timeout.time_since_epoch().count() > 0)
|
||||
return t->networking.disconnect_timeout + std::chrono::seconds{5} < now;
|
||||
return t->timings.disconnecting + std::chrono::seconds{30} < now;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
provider->transfers_.erase(std::remove_if(provider->transfers_.begin(), provider->transfers_.end(), [&](const auto& t) {
|
||||
return std::find(disconnected_clients.begin(), disconnected_clients.end(), t) != disconnected_clients.end();
|
||||
}), provider->transfers_.end());
|
||||
}
|
||||
|
||||
for(auto& client : disconnected_clients) {
|
||||
switch(client->state) {
|
||||
case FileClient::STATE_AWAITING_KEY:
|
||||
logMessage(LOG_FT, "{} Received no key. Dropping client.", client->log_prefix());
|
||||
break;
|
||||
case FileClient::STATE_TRANSFERRING:
|
||||
logMessage(LOG_FT, "{} Networking timeout. Dropping client", client->log_prefix());
|
||||
provider->invoke_aborted_callback(client, { TransferError::TRANSFER_TIMEOUT, "" });
|
||||
break;
|
||||
case FileClient::STATE_FLUSHING:
|
||||
if(!client->buffers_flushed())
|
||||
logMessage(LOG_FT, "{} Failed to flush connection. Dropping client", client->log_prefix());
|
||||
else
|
||||
; /* we just awaited a client disconnect */
|
||||
break;
|
||||
case FileClient::STATE_DISCONNECTED:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->state = FileClient::STATE_DISCONNECTED;
|
||||
/*
|
||||
* First of all disconnect the client from the network so no actions could be triggered by that way.
|
||||
* Secondly finalize all network components, so no data is pending anywhere
|
||||
* Thirdly drop the client's disk worker (if it's an upload the data should be written already, else we don't care anyways)
|
||||
*/
|
||||
provider->finalize_networking(client, slock);
|
||||
provider->finalize_client_ssl(client);
|
||||
provider->finalize_file_io(client, slock);
|
||||
}
|
||||
|
||||
debugMessage(LOG_FT, "{} Destroying transfer.", client->log_prefix());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LocalFileTransfer::report_transfer_statistics(const std::shared_ptr<FileClient> &client) {
|
||||
auto callback{this->callback_transfer_statistics};
|
||||
if(!callback) return;
|
||||
|
||||
callback(client->transfer, this->generate_transfer_statistics_report(client));
|
||||
}
|
||||
|
||||
TransferStatistics LocalFileTransfer::generate_transfer_statistics_report(const std::shared_ptr<FileClient> &client) {
|
||||
TransferStatistics stats{};
|
||||
|
||||
stats.network_bytes_send = client->statistics.network_send.total_bytes;
|
||||
stats.network_bytes_received = client->statistics.network_received.total_bytes;
|
||||
stats.file_bytes_transferred = client->statistics.file_transferred.total_bytes;
|
||||
|
||||
stats.delta_network_bytes_received = client->statistics.network_received.take_delta();
|
||||
stats.delta_network_bytes_send = client->statistics.network_received.take_delta();
|
||||
|
||||
stats.delta_file_bytes_transferred = client->statistics.file_transferred.take_delta();
|
||||
|
||||
stats.file_start_offset = client->transfer->file_offset;
|
||||
stats.file_current_offset = client->statistics.file_transferred.total_bytes + client->transfer->file_offset;
|
||||
stats.file_total_size = client->transfer->expected_file_size;
|
||||
|
||||
stats.average_speed = client->statistics.file_transferred.average_bandwidth();
|
||||
stats.current_speed = client->statistics.file_transferred.current_bandwidth();
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::invoke_aborted_callback(const std::shared_ptr<FileClient> &client,
|
||||
const ts::server::file::transfer::TransferError &error) {
|
||||
auto callback{this->callback_transfer_aborted};
|
||||
if(!callback || !client->transfer) return;
|
||||
|
||||
callback(client->transfer, this->generate_transfer_statistics_report(client), error);
|
||||
}
|
||||
|
||||
void LocalFileTransfer::invoke_aborted_callback(const std::shared_ptr<Transfer> &transfer,
|
||||
const ts::server::file::transfer::TransferError &error) {
|
||||
auto callback{this->callback_transfer_aborted};
|
||||
if(!callback) return;
|
||||
|
||||
TransferStatistics stats{};
|
||||
|
||||
stats.network_bytes_send = 0;
|
||||
stats.network_bytes_received = 0;
|
||||
stats.file_bytes_transferred = 0;
|
||||
|
||||
stats.delta_network_bytes_received = 0;
|
||||
stats.delta_network_bytes_send = 0;
|
||||
|
||||
stats.delta_file_bytes_transferred = 0;
|
||||
|
||||
stats.file_start_offset = transfer->file_offset;
|
||||
stats.file_current_offset = transfer->file_offset;
|
||||
stats.file_total_size = transfer->expected_file_size;
|
||||
|
||||
callback(transfer, stats, error);
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
//
|
||||
// Created by WolverinDEV on 04/05/2020.
|
||||
//
|
||||
|
||||
#include <cassert>
|
||||
#include <event.h>
|
||||
#include <experimental/filesystem>
|
||||
#include <log/LogUtils.h>
|
||||
#include "./LocalFileProvider.h"
|
||||
#include "./LocalFileTransfer.h"
|
||||
#include "duration_utils.h"
|
||||
|
||||
using namespace ts::server::file;
|
||||
using namespace ts::server::file::transfer;
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
DiskIOStartResult LocalFileTransfer::start_disk_io() {
|
||||
assert(this->disk_io.state == DiskIOLoopState::STOPPED);
|
||||
|
||||
this->disk_io.state = DiskIOLoopState::RUNNING;
|
||||
this->disk_io.dispatch_thread = std::thread(&LocalFileTransfer::dispatch_loop_disk_io, this);
|
||||
return DiskIOStartResult::SUCCESS;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::shutdown_disk_io() {
|
||||
if(this->disk_io.state == DiskIOLoopState::STOPPED) return;
|
||||
|
||||
this->disk_io.state = DiskIOLoopState::STOPPING;
|
||||
{
|
||||
std::unique_lock qlock{this->disk_io.queue_lock};
|
||||
this->disk_io.notify_work_awaiting.notify_all();
|
||||
while(this->disk_io.queue_head) {
|
||||
this->disk_io.notify_client_processed.wait_for(qlock, std::chrono::seconds{10});
|
||||
}
|
||||
|
||||
if(this->disk_io.queue_head) {
|
||||
logWarning(0, "Failed to flush disk IO. Force aborting.");
|
||||
this->disk_io.state = DiskIOLoopState::FORCE_STOPPING;
|
||||
this->disk_io.notify_work_awaiting.notify_all();
|
||||
this->disk_io.notify_client_processed.wait(qlock);
|
||||
}
|
||||
}
|
||||
|
||||
if(this->disk_io.dispatch_thread.joinable())
|
||||
this->disk_io.dispatch_thread.join();
|
||||
|
||||
this->disk_io.state = DiskIOLoopState::STOPPED;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::dispatch_loop_disk_io(void *provider_ptr) {
|
||||
auto provider = reinterpret_cast<LocalFileTransfer*>(provider_ptr);
|
||||
|
||||
std::shared_ptr<FileClient> client{};
|
||||
while(true) {
|
||||
{
|
||||
std::unique_lock qlock{provider->disk_io.queue_lock};
|
||||
if(client) {
|
||||
client->file.currently_processing = false;
|
||||
provider->disk_io.notify_client_processed.notify_all();
|
||||
client = nullptr;
|
||||
}
|
||||
|
||||
provider->disk_io.notify_work_awaiting.wait(qlock, [&]{ return provider->disk_io.state != DiskIOLoopState::RUNNING || provider->disk_io.queue_head != nullptr; });
|
||||
|
||||
if(provider->disk_io.queue_head) {
|
||||
try {
|
||||
client = provider->disk_io.queue_head->shared_from_this();
|
||||
} catch (std::bad_weak_ptr& ex) {
|
||||
logCritical(LOG_FT, "Disk worker encountered a bad weak ptr. This indicated something went horribly wrong! Please submit this on https://forum.teaspeak.de !!!");
|
||||
client.reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
provider->disk_io.queue_head = provider->disk_io.queue_head->file.next_client;
|
||||
if(!provider->disk_io.queue_head) {
|
||||
provider->disk_io.queue_tail = &provider->disk_io.queue_head;
|
||||
}
|
||||
}
|
||||
|
||||
if(provider->disk_io.state != DiskIOLoopState::RUNNING) {
|
||||
if(provider->disk_io.state == DiskIOLoopState::STOPPING) {
|
||||
if(!client)
|
||||
break;
|
||||
/* break only if all clients have been flushed */
|
||||
} else {
|
||||
/* force stopping without any flushing */
|
||||
auto fclient = &*client;
|
||||
while(fclient) {
|
||||
fclient = std::exchange(fclient->file.next_client, nullptr);
|
||||
}
|
||||
|
||||
provider->disk_io.queue_head = nullptr;
|
||||
provider->disk_io.queue_tail = &provider->disk_io.queue_head;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!client) {
|
||||
continue;
|
||||
}
|
||||
|
||||
client->file.currently_processing = true;
|
||||
client->file.next_client = nullptr;
|
||||
}
|
||||
|
||||
provider->execute_disk_io(client);
|
||||
}
|
||||
provider->disk_io.notify_client_processed.notify_all();
|
||||
}
|
||||
|
||||
bool FileClient::enqueue_disk_buffer_bytes(const void *snd_buffer, size_t size) {
|
||||
auto tbuffer = allocate_buffer(size);
|
||||
tbuffer->length = size;
|
||||
tbuffer->offset = 0;
|
||||
memcpy(tbuffer->data, snd_buffer, size);
|
||||
|
||||
size_t buffer_size;
|
||||
{
|
||||
std::lock_guard block{this->disk_buffer.mutex};
|
||||
if(this->disk_buffer.write_disconnected) {
|
||||
goto write_disconnected;
|
||||
}
|
||||
|
||||
*this->disk_buffer.buffer_tail = tbuffer;
|
||||
this->disk_buffer.buffer_tail = &tbuffer->next;
|
||||
buffer_size = (this->disk_buffer.bytes += size);
|
||||
}
|
||||
|
||||
return buffer_size > TRANSFER_MAX_CACHED_BYTES;
|
||||
|
||||
write_disconnected:
|
||||
deref_buffer(tbuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
void FileClient::flush_disk_buffer() {
|
||||
Buffer* current_head;
|
||||
{
|
||||
std::lock_guard block{this->disk_buffer.mutex};
|
||||
|
||||
this->disk_buffer.write_disconnected = true;
|
||||
this->disk_buffer.bytes = 0;
|
||||
current_head = std::exchange(this->disk_buffer.buffer_head, nullptr);
|
||||
this->disk_buffer.buffer_tail = &this->disk_buffer.buffer_head;
|
||||
}
|
||||
|
||||
while(current_head) {
|
||||
auto next = current_head->next;
|
||||
deref_buffer(current_head);
|
||||
current_head = next;
|
||||
}
|
||||
}
|
||||
|
||||
bool FileClient::buffers_flushed() {
|
||||
{
|
||||
std::lock_guard db_lock{this->disk_buffer.mutex};
|
||||
if(this->disk_buffer.bytes > 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard nb_lock{this->network_buffer.mutex};
|
||||
if(this->network_buffer.bytes > 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FileInitializeResult LocalFileTransfer::initialize_file_io(const std::shared_ptr<FileClient> &transfer) {
|
||||
FileInitializeResult result{FileInitializeResult::SUCCESS};
|
||||
assert(transfer->transfer);
|
||||
|
||||
std::shared_lock slock{transfer->state_mutex};
|
||||
auto& file_data = transfer->file;
|
||||
assert(!file_data.file_descriptor);
|
||||
assert(!file_data.next_client);
|
||||
|
||||
auto absolute_path = transfer->transfer->absolute_file_path;
|
||||
{
|
||||
unsigned int open_flags{0};
|
||||
if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
open_flags = O_RDONLY;
|
||||
|
||||
std::error_code fs_error{};
|
||||
if(absolute_path.empty() || !fs::exists(absolute_path, fs_error)) {
|
||||
result = FileInitializeResult::FILE_DOES_NOT_EXISTS;
|
||||
goto error_exit;
|
||||
} else if(fs_error) {
|
||||
logWarning(LOG_FT, "{} Failed to check for file existence of {}: {}/{}", transfer->log_prefix(), absolute_path, fs_error.value(), fs_error.message());
|
||||
result = FileInitializeResult::FILE_SYSTEM_ERROR;
|
||||
goto error_exit;
|
||||
}
|
||||
} else if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
std::error_code fs_error{};
|
||||
auto parent_path = fs::u8path(absolute_path).parent_path();
|
||||
if(!fs::exists(parent_path)) {
|
||||
if(!fs::create_directories(parent_path, fs_error) || fs_error) {
|
||||
logError(LOG_FT, "{} Failed to create required directories for file upload for {}: {}/{}", transfer->log_prefix(), absolute_path, fs_error.value(), fs_error.message());
|
||||
result = FileInitializeResult::COUNT_NOT_CREATE_DIRECTORIES;
|
||||
goto error_exit;
|
||||
}
|
||||
} else if(fs_error) {
|
||||
logWarning(LOG_FT, "{} Failed to check for directory existence of {}: {}/{}. Assuming it exists", transfer->log_prefix(), parent_path.string(), fs_error.value(), fs_error.message());
|
||||
}
|
||||
|
||||
open_flags = (unsigned) O_WRONLY | (unsigned) O_CREAT;
|
||||
if(transfer->transfer->override_exiting)
|
||||
open_flags |= (unsigned) O_TRUNC;
|
||||
} else {
|
||||
return FileInitializeResult::INVALID_TRANSFER_DIRECTION;
|
||||
}
|
||||
|
||||
file_data.file_descriptor = open(absolute_path.c_str(), (int) open_flags, 0644);
|
||||
if(file_data.file_descriptor <= 0) {
|
||||
const auto errno_ = errno;
|
||||
switch (errno_) {
|
||||
case EACCES:
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
break;
|
||||
case EDQUOT:
|
||||
logWarning(LOG_FT, "{} Disk inode limit has been reached. Failed to start file transfer for file {}", transfer->log_prefix(), absolute_path);
|
||||
result = FileInitializeResult::FILE_SYSTEM_ERROR;
|
||||
break;
|
||||
case EISDIR:
|
||||
result = FileInitializeResult::FILE_IS_A_DIRECTORY;
|
||||
break;
|
||||
case EMFILE:
|
||||
result = FileInitializeResult::PROCESS_FILE_LIMIT_REACHED;
|
||||
break;
|
||||
case ENFILE:
|
||||
result = FileInitializeResult::SYSTEM_FILE_LIMIT_REACHED;
|
||||
break;
|
||||
case ETXTBSY:
|
||||
result = FileInitializeResult::FILE_IS_BUSY;
|
||||
break;
|
||||
case EROFS:
|
||||
result = FileInitializeResult::DISK_IS_READ_ONLY;
|
||||
break;
|
||||
default:
|
||||
logWarning(LOG_FT, "{} Failed to start file {} for file {}: {}/{}", transfer->log_prefix(),
|
||||
transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD ? "download" : "upload", absolute_path, errno_, strerror(errno_));
|
||||
result = FileInitializeResult::FILE_SYSTEM_ERROR;
|
||||
break;
|
||||
}
|
||||
goto error_exit;
|
||||
}
|
||||
}
|
||||
|
||||
this->file_system_->lock_file(absolute_path);
|
||||
transfer->file.file_locked = true;
|
||||
|
||||
if(transfer->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
if(ftruncate(file_data.file_descriptor, transfer->transfer->expected_file_size) != 0) {
|
||||
const auto errno_ = errno;
|
||||
switch (errno_) {
|
||||
case EACCES:
|
||||
logWarning(LOG_FT, "{} File {} got inaccessible on truncating, but not on opening.", transfer->log_prefix(), absolute_path);
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
goto error_exit;
|
||||
|
||||
case EFBIG:
|
||||
result = FileInitializeResult::FILE_TOO_LARGE;
|
||||
goto error_exit;
|
||||
|
||||
case EIO:
|
||||
logWarning(LOG_FT, "{} A disk IO error occurred while resizing file {}.", transfer->log_prefix(), absolute_path);
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
goto error_exit;
|
||||
|
||||
case EROFS:
|
||||
logWarning(LOG_FT, "{} Failed to resize file {} because disk is in read only mode.", transfer->log_prefix(), absolute_path);
|
||||
result = FileInitializeResult::FILE_IS_NOT_ACCESSIBLE;
|
||||
goto error_exit;
|
||||
default:
|
||||
debugMessage(LOG_FT, "{} Failed to truncate file {}: {}/{}. Trying to upload file anyways.", transfer->log_prefix(), absolute_path, errno_, strerror(errno_));
|
||||
}
|
||||
}
|
||||
} else if(transfer->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
auto file_size = lseek(file_data.file_descriptor, 0, SEEK_END);
|
||||
if(file_size != transfer->transfer->expected_file_size) {
|
||||
logWarning(LOG_FT, "{} Expected target file to be of size {}, but file is actually of size {}", transfer->log_prefix(), transfer->transfer->expected_file_size, file_size);
|
||||
result = FileInitializeResult::FILE_SIZE_MISMATCH;
|
||||
goto error_exit;
|
||||
}
|
||||
if(transfer->file.query_media_bytes && file_size > 0) {
|
||||
auto new_pos = lseek(file_data.file_descriptor, 0, SEEK_SET);
|
||||
if(new_pos < 0) {
|
||||
logWarning(LOG_FT, "{} Failed to seek to file start: {}/{}", transfer->log_prefix(), errno, strerror(errno));
|
||||
result = FileInitializeResult::FILE_SEEK_FAILED;
|
||||
goto error_exit;
|
||||
}
|
||||
|
||||
auto read = ::read(file_data.file_descriptor, transfer->file.media_bytes, TRANSFER_MEDIA_BYTES_LENGTH);
|
||||
if(read <= 0) {
|
||||
logWarning(LOG_FT, "{} Failed to read file media bytes: {}/{}", transfer->log_prefix(), errno, strerror(errno));
|
||||
result = FileInitializeResult::FAILED_TO_READ_MEDIA_BYTES;
|
||||
goto error_exit;
|
||||
}
|
||||
transfer->file.media_bytes_length = read;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto new_pos = lseek(file_data.file_descriptor, transfer->transfer->file_offset, SEEK_SET);
|
||||
if(new_pos < 0) {
|
||||
logWarning(LOG_FT, "{} Failed to seek to target file offset ({}): {}/{}", transfer->log_prefix(), transfer->transfer->file_offset, errno, strerror(errno));
|
||||
result = FileInitializeResult::FILE_SEEK_FAILED;
|
||||
goto error_exit;
|
||||
} else if(new_pos != transfer->transfer->file_offset) {
|
||||
logWarning(LOG_FT, "{} File rw offset mismatch after seek. Expected {} but received {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos);
|
||||
result = FileInitializeResult::FILE_SEEK_FAILED;
|
||||
goto error_exit;
|
||||
}
|
||||
logTrace(LOG_FT, "{} Seek to file offset {}. New actual offset is {}", transfer->log_prefix(), transfer->transfer->file_offset, new_pos);
|
||||
}
|
||||
|
||||
return FileInitializeResult::SUCCESS;
|
||||
error_exit:
|
||||
if(std::exchange(transfer->file.file_locked, false))
|
||||
this->file_system_->unlock_file(absolute_path);
|
||||
|
||||
if(file_data.file_descriptor > 0)
|
||||
::close(file_data.file_descriptor);
|
||||
file_data.file_descriptor = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::finalize_file_io(const std::shared_ptr<FileClient> &transfer,
|
||||
std::unique_lock<std::shared_mutex> &state_lock) {
|
||||
assert(state_lock.owns_lock());
|
||||
if(!transfer->transfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto absolute_path = transfer->transfer->absolute_file_path;
|
||||
|
||||
auto& file_data = transfer->file;
|
||||
|
||||
state_lock.unlock();
|
||||
{
|
||||
std::unique_lock dlock{this->disk_io.queue_lock};
|
||||
while(true) {
|
||||
if(file_data.currently_processing) {
|
||||
this->disk_io.notify_client_processed.wait(dlock);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(this->disk_io.queue_head == &*transfer) {
|
||||
this->disk_io.queue_head = file_data.next_client;
|
||||
if (!this->disk_io.queue_head) {
|
||||
this->disk_io.queue_tail = &this->disk_io.queue_head;
|
||||
}
|
||||
} else if(file_data.next_client || this->disk_io.queue_tail == &file_data.next_client) {
|
||||
FileClient* head{this->disk_io.queue_head};
|
||||
while(head->file.next_client != &*transfer) {
|
||||
assert(head->file.next_client);
|
||||
head = head->file.next_client;
|
||||
}
|
||||
|
||||
head->file.next_client = file_data.next_client;
|
||||
if(!file_data.next_client)
|
||||
this->disk_io.queue_tail = &head->file.next_client;
|
||||
}
|
||||
file_data.next_client = nullptr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
state_lock.lock();
|
||||
|
||||
if(std::exchange(file_data.file_locked, false)) {
|
||||
this->file_system_->unlock_file(absolute_path);
|
||||
}
|
||||
|
||||
if(file_data.file_descriptor > 0)
|
||||
::close(file_data.file_descriptor);
|
||||
file_data.file_descriptor = 0;
|
||||
}
|
||||
|
||||
void LocalFileTransfer::enqueue_disk_io(const std::shared_ptr<FileClient> &client) {
|
||||
if(!client->file.file_descriptor) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!client->transfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
if(client->state != FileClient::STATE_TRANSFERRING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(client->disk_buffer.bytes > TRANSFER_MAX_CACHED_BYTES) {
|
||||
return;
|
||||
}
|
||||
} else if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
/* we don't do this check because this might be a flush instruction, where the buffer is actually zero bytes filled */
|
||||
/*
|
||||
if(client->buffer.bytes == 0)
|
||||
return;
|
||||
*/
|
||||
}
|
||||
|
||||
std::lock_guard dlock{this->disk_io.queue_lock};
|
||||
if(client->file.next_client || this->disk_io.queue_tail == &client->file.next_client) {
|
||||
return;
|
||||
}
|
||||
|
||||
*this->disk_io.queue_tail = &*client;
|
||||
this->disk_io.queue_tail = &client->file.next_client;
|
||||
|
||||
this->disk_io.notify_work_awaiting.notify_all();
|
||||
}
|
||||
|
||||
void LocalFileTransfer::execute_disk_io(const std::shared_ptr<FileClient> &client) {
|
||||
if(!client->transfer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(client->transfer->direction == Transfer::DIRECTION_UPLOAD) {
|
||||
Buffer* buffer;
|
||||
size_t buffer_left_size;
|
||||
|
||||
while(true) {
|
||||
{
|
||||
std::lock_guard block{client->disk_buffer.mutex};
|
||||
|
||||
if(!client->disk_buffer.buffer_head) {
|
||||
assert(client->disk_buffer.bytes == 0);
|
||||
buffer_left_size = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
buffer_left_size = client->disk_buffer.bytes;
|
||||
buffer = ref_buffer(client->disk_buffer.buffer_head);
|
||||
}
|
||||
|
||||
assert(buffer->offset < buffer->length);
|
||||
auto written = ::write(client->file.file_descriptor, buffer->data + buffer->offset, buffer->length - buffer->offset);
|
||||
if(written <= 0) {
|
||||
deref_buffer(buffer);
|
||||
|
||||
if(errno == EAGAIN) {
|
||||
//TODO: Timeout?
|
||||
this->enqueue_disk_io(client);
|
||||
break;
|
||||
}
|
||||
|
||||
if(written == 0) {
|
||||
/* EOF, how the hell is this event possible?! */
|
||||
auto offset_written = client->statistics.disk_bytes_write.total_bytes + client->transfer->file_offset;
|
||||
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
|
||||
logError(LOG_FT, "{} Received unexpected file write EOF. EOF received at {} but expected {}. Actual file offset: {}. Closing transfer.",
|
||||
client->log_prefix(), offset_written, client->transfer->expected_file_size, aoffset);
|
||||
|
||||
|
||||
this->invoke_aborted_callback(client, { TransferError::UNEXPECTED_DISK_EOF, strerror(errno) });
|
||||
|
||||
client->flush_disk_buffer();
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
}
|
||||
} else {
|
||||
auto offset_written = client->statistics.disk_bytes_write.total_bytes + client->transfer->file_offset;
|
||||
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
|
||||
logError(LOG_FT, "{} Received write to disk IO error. Write pointer is at {} of {}. Actual file offset: {}. Closing transfer.",
|
||||
client->log_prefix(), offset_written, client->transfer->expected_file_size, aoffset);
|
||||
|
||||
this->invoke_aborted_callback(client, { TransferError::DISK_IO_ERROR, strerror(errno) });
|
||||
|
||||
client->flush_disk_buffer();
|
||||
{
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
buffer->offset += written;
|
||||
assert(buffer->offset <= buffer->length);
|
||||
|
||||
if(buffer->length == buffer->offset) {
|
||||
{
|
||||
std::lock_guard block{client->disk_buffer.mutex};
|
||||
if(client->disk_buffer.buffer_head == buffer) {
|
||||
client->disk_buffer.buffer_head = buffer->next;
|
||||
if(!buffer->next) {
|
||||
client->disk_buffer.buffer_tail = &client->disk_buffer.buffer_head;
|
||||
}
|
||||
|
||||
assert(client->disk_buffer.bytes >= written);
|
||||
client->disk_buffer.bytes -= written;
|
||||
buffer_left_size = client->disk_buffer.bytes;
|
||||
|
||||
/* We have to deref the buffer twice since we've removed it from the list which owns us one reference */
|
||||
/* Will not trigger a memory free since we're still holding onto one reference */
|
||||
deref_buffer(buffer);
|
||||
} else {
|
||||
/* The buffer got removed */
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::lock_guard block{client->disk_buffer.mutex};
|
||||
if(client->disk_buffer.buffer_head == buffer) {
|
||||
assert(client->disk_buffer.bytes >= written);
|
||||
client->disk_buffer.bytes -= written;
|
||||
buffer_left_size = client->disk_buffer.bytes;
|
||||
} else {
|
||||
/* The buffer got removed */
|
||||
}
|
||||
}
|
||||
|
||||
deref_buffer(buffer);
|
||||
client->statistics.disk_bytes_write.increase_bytes(written);
|
||||
}
|
||||
}
|
||||
|
||||
if(buffer_left_size > 0) {
|
||||
this->enqueue_disk_io(client);
|
||||
} else if(client->state == FileClient::STATE_FLUSHING) {
|
||||
this->test_disconnecting_state(client);
|
||||
}
|
||||
|
||||
if(client->state == FileClient::STATE_TRANSFERRING && buffer_left_size < TRANSFER_MAX_CACHED_BYTES / 2) {
|
||||
if(client->disk_buffer.buffering_stopped) {
|
||||
logMessage(LOG_FT, "{} Starting network read, buffer is capable for reading again.", client->log_prefix());
|
||||
}
|
||||
|
||||
client->add_network_read_event(false);
|
||||
}
|
||||
} else if(client->transfer->direction == Transfer::DIRECTION_DOWNLOAD) {
|
||||
if(client->state == FileClient::STATE_FLUSHING) {
|
||||
client->flush_disk_buffer(); /* just in case, file download usually don't write to the disk */
|
||||
return;
|
||||
}
|
||||
|
||||
while(true) {
|
||||
constexpr auto buffer_capacity{4096};
|
||||
char buffer[buffer_capacity];
|
||||
|
||||
auto read = ::read(client->file.file_descriptor, buffer, buffer_capacity);
|
||||
if(read <= 0) {
|
||||
if(errno == EAGAIN) {
|
||||
this->enqueue_disk_io(client);
|
||||
return;
|
||||
}
|
||||
|
||||
if(read == 0) {
|
||||
/* EOF */
|
||||
auto offset_send = client->statistics.disk_bytes_read.total_bytes + client->transfer->file_offset;
|
||||
if(client->transfer->expected_file_size == offset_send) {
|
||||
debugMessage(LOG_FT, "{} Finished file reading. Flushing and disconnecting transfer. Reading took {} seconds.",
|
||||
client->log_prefix(), duration_to_string(std::chrono::system_clock::now() - client->timings.key_received));
|
||||
} else {
|
||||
auto aoffset = lseek(client->file.file_descriptor, 0, SEEK_CUR);
|
||||
logError(LOG_FT, "{} Received unexpected read EOF. EOF received at {} but expected {}. Actual file offset: {}. Disconnecting client.",
|
||||
client->log_prefix(), offset_send, client->transfer->expected_file_size, aoffset);
|
||||
|
||||
this->invoke_aborted_callback(client, { TransferError::UNEXPECTED_DISK_EOF, "" });
|
||||
}
|
||||
} else {
|
||||
logWarning(LOG_FT, "{} Failed to read from file {} ({}/{}). Aborting transfer.", client->log_prefix(), client->transfer->absolute_file_path, errno, strerror(errno));
|
||||
|
||||
this->invoke_aborted_callback(client, { TransferError::DISK_IO_ERROR, strerror(errno) });
|
||||
}
|
||||
|
||||
std::unique_lock slock{client->state_mutex};
|
||||
client->handle->disconnect_client(client, slock, true);
|
||||
return;
|
||||
} else {
|
||||
auto buffer_full = client->send_file_bytes(buffer, read);
|
||||
client->statistics.disk_bytes_read.increase_bytes(read);
|
||||
client->statistics.file_transferred.increase_bytes(read);
|
||||
|
||||
std::shared_lock slock{client->state_mutex};
|
||||
if(buffer_full) {
|
||||
//logTrace(LOG_FT, "{} Stopping buffering from disk. Buffer full ({}bytes)", client->log_prefix(), client->buffer.bytes);
|
||||
break;
|
||||
}
|
||||
|
||||
/* we've stuff to write again, yeahr */
|
||||
client->add_network_write_event(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logError(LOG_FT, "{} Disk IO scheduled, but transfer direction is unknown.", client->log_prefix());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
//
|
||||
// Created by WolverinDEV on 12/05/2020.
|
||||
//
|
||||
|
||||
#include "./NetTools.h"
|
||||
|
||||
using namespace ts::server::file::networking;
|
||||
|
||||
NetworkThrottle NetworkThrottle::kNoThrottle{-1};
|
||||
@@ -0,0 +1,169 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <cassert>
|
||||
#include <array>
|
||||
|
||||
#include <misc/spin_mutex.h>
|
||||
#include <numeric>
|
||||
|
||||
namespace ts::server::file::networking {
|
||||
struct NetworkThrottle {
|
||||
constexpr static auto kThrottleTimespanMs{250};
|
||||
|
||||
typedef uint8_t span_t;
|
||||
|
||||
static NetworkThrottle kNoThrottle;
|
||||
|
||||
ssize_t max_bytes{0};
|
||||
|
||||
span_t current_index{0};
|
||||
size_t bytes_send{0};
|
||||
|
||||
mutable spin_mutex mutex{};
|
||||
|
||||
inline bool increase_bytes(size_t bytes) {
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
|
||||
std::lock_guard slock{this->mutex};
|
||||
if(this->current_index != current_span) {
|
||||
this->current_index = current_span;
|
||||
this->bytes_send = bytes;
|
||||
} else {
|
||||
this->bytes_send += bytes;
|
||||
}
|
||||
return this->max_bytes > 0 && this->bytes_send >= this->max_bytes;
|
||||
}
|
||||
|
||||
inline void set_max_bandwidth(ssize_t bytes_per_second) {
|
||||
std::lock_guard slock{this->mutex};
|
||||
if(bytes_per_second <= 0)
|
||||
this->max_bytes = -1;
|
||||
else
|
||||
this->max_bytes = bytes_per_second * kThrottleTimespanMs / 1000;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool should_throttle(timeval& next_timestamp) {
|
||||
if(this->max_bytes <= 0) return false;
|
||||
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
|
||||
std::lock_guard slock{this->mutex};
|
||||
if(this->max_bytes <= 0) return false; /* we've to test here again, else out arithmetic will fail */
|
||||
if(this->current_index != current_span) return false;
|
||||
if(this->bytes_send < this->max_bytes) return false;
|
||||
|
||||
next_timestamp.tv_usec = (kThrottleTimespanMs - current_ms % kThrottleTimespanMs) * 1000;
|
||||
next_timestamp.tv_sec = next_timestamp.tv_usec / 1000000;
|
||||
next_timestamp.tv_usec -= next_timestamp.tv_sec * 1000000;
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline size_t bytes_left() const {
|
||||
if(this->max_bytes <= 0) return (size_t) -1;
|
||||
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kThrottleTimespanMs);
|
||||
|
||||
std::lock_guard slock{this->mutex};
|
||||
if(this->max_bytes <= 0) return false; /* we've to test here again, else out arithmetic will fail */
|
||||
if(this->current_index != current_span) return this->max_bytes;
|
||||
if(this->bytes_send < this->max_bytes) return this->max_bytes - this->bytes_send;
|
||||
return 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::chrono::milliseconds expected_writing_time(size_t bytes) const {
|
||||
std::lock_guard slock{this->mutex};
|
||||
|
||||
if(this->max_bytes <= 0) return std::chrono::milliseconds{0};
|
||||
return std::chrono::seconds{bytes / (this->max_bytes * (1000 / kThrottleTimespanMs))};
|
||||
}
|
||||
};
|
||||
|
||||
struct DualNetworkThrottle {
|
||||
NetworkThrottle *left, *right;
|
||||
|
||||
explicit DualNetworkThrottle(NetworkThrottle* left, NetworkThrottle* right) : left{left}, right{right} {
|
||||
assert(left);
|
||||
assert(right);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline size_t bytes_left() const {
|
||||
return std::min(this->left->bytes_left(), this->right->bytes_left());
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::chrono::milliseconds expected_writing_time(size_t bytes) const {
|
||||
return std::max(this->left->expected_writing_time(bytes), this->right->expected_writing_time(bytes));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool should_throttle(timeval& next_timestamp) const {
|
||||
bool throttle = false;
|
||||
timeval right_timestamp{};
|
||||
throttle |= this->left->should_throttle(next_timestamp);
|
||||
throttle |= this->right->should_throttle(right_timestamp);
|
||||
if(!throttle) return false;
|
||||
|
||||
if(right_timestamp.tv_sec > next_timestamp.tv_sec || (right_timestamp.tv_sec == next_timestamp.tv_sec && right_timestamp.tv_usec > next_timestamp.tv_usec)) {
|
||||
next_timestamp = right_timestamp;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool increase_bytes(size_t bytes) {
|
||||
bool result = false;
|
||||
result |= this->left->increase_bytes(bytes);
|
||||
result |= this->right->increase_bytes(bytes);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
struct TransferStatistics {
|
||||
constexpr static auto kMeasureTimespanMs{1000};
|
||||
constexpr static auto kAverageTimeCount{60};
|
||||
typedef uint8_t span_t;
|
||||
|
||||
size_t total_bytes{0};
|
||||
size_t delta_bytes{0}; /* used for statistics propagation */
|
||||
|
||||
span_t span_index{0};
|
||||
size_t span_bytes{0};
|
||||
std::array<size_t, kAverageTimeCount> history{};
|
||||
|
||||
spin_mutex mutex{};
|
||||
|
||||
inline void increase_bytes(size_t bytes) {
|
||||
auto current_ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto current_span = (span_t) (current_ms / kMeasureTimespanMs);
|
||||
|
||||
std::lock_guard slock{this->mutex};
|
||||
this->total_bytes += bytes;
|
||||
|
||||
if(this->span_index != current_span) {
|
||||
this->history[this->span_index % kAverageTimeCount] = std::exchange(this->span_bytes, 0);
|
||||
}
|
||||
|
||||
this->span_index = current_span;
|
||||
this->span_bytes += bytes;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline size_t take_delta() {
|
||||
std::lock_guard slock{this->mutex};
|
||||
assert(this->delta_bytes <= this->total_bytes);
|
||||
auto delta = this->total_bytes - this->delta_bytes;
|
||||
this->delta_bytes = this->total_bytes;
|
||||
return delta;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline double current_bandwidth() const {
|
||||
return (this->history[(this->span_index - 1) % kAverageTimeCount] * (double) 1000) / (double) kMeasureTimespanMs;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline double average_bandwidth() const {
|
||||
return (std::accumulate(this->history.begin(), this->history.end(), 0UL) * (double) 1000) / (double) (kMeasureTimespanMs * kAverageTimeCount);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
//
|
||||
// Created by WolverinDEV on 29/04/2020.
|
||||
//
|
||||
|
||||
#include "clnpath.h"
|
||||
#include <cstring>
|
||||
|
||||
#define MAX_PATH_ELEMENTS 128 /* Number of levels of directory */
|
||||
void ts_clnpath(char *path)
|
||||
{
|
||||
char *src;
|
||||
char *dst;
|
||||
char c;
|
||||
int slash = 0;
|
||||
|
||||
/* Convert multiple adjacent slashes to single slash */
|
||||
src = dst = path;
|
||||
while ((c = *dst++ = *src++) != '\0')
|
||||
{
|
||||
if (c == '/')
|
||||
{
|
||||
slash = 1;
|
||||
while (*src == '/')
|
||||
src++;
|
||||
}
|
||||
}
|
||||
|
||||
if (slash == 0)
|
||||
return;
|
||||
|
||||
/* Remove "./" from "./xxx" but leave "./" alone. */
|
||||
/* Remove "/." from "xxx/." but reduce "/." to "/". */
|
||||
/* Reduce "xxx/./yyy" to "xxx/yyy" */
|
||||
src = dst = (*path == '/') ? path + 1 : path;
|
||||
while (src[0] == '.' && src[1] == '/' && src[2] != '\0')
|
||||
src += 2;
|
||||
while ((c = *dst++ = *src++) != '\0')
|
||||
{
|
||||
if (c == '/' && src[0] == '.' && (src[1] == '\0' || src[1] == '/'))
|
||||
{
|
||||
src++;
|
||||
dst--;
|
||||
}
|
||||
}
|
||||
if (path[0] == '/' && path[1] == '.' &&
|
||||
(path[2] == '\0' || (path[2] == '/' && path[3] == '\0')))
|
||||
path[1] = '\0';
|
||||
|
||||
/* Remove trailing slash, if any. There is at most one! */
|
||||
/* dst is pointing one beyond terminating null */
|
||||
if ((dst -= 2) > path && *dst == '/')
|
||||
*dst++ = '\0';
|
||||
}
|
||||
|
||||
bool ts_strequal(const char* a, const char* b) {
|
||||
return strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
int ts_tokenise(char* ostring, const char* del, char** result, int max_tokens) {
|
||||
int num_tokens{0};
|
||||
|
||||
char* token, *string, *tofree;
|
||||
tofree = string = strdup(ostring);
|
||||
while ((token = strsep(&string, del)) != nullptr) {
|
||||
result[num_tokens++] = strdup(token);
|
||||
if(num_tokens > max_tokens)
|
||||
break;
|
||||
}
|
||||
|
||||
free(tofree);
|
||||
return num_tokens;
|
||||
}
|
||||
|
||||
/*
|
||||
** clnpath2() is not part of the basic clnpath() function because it can
|
||||
** change the meaning of a path name if there are symbolic links on the
|
||||
** system. For example, suppose /usr/tmp is a symbolic link to /var/tmp.
|
||||
** If the user supplies /usr/tmp/../abcdef as the directory name, clnpath
|
||||
** would transform that to /usr/abcdef, not to /var/abcdef which is what
|
||||
** the kernel would interpret it as.
|
||||
*/
|
||||
void ts_clnpath2(char *path)
|
||||
{
|
||||
char *token[MAX_PATH_ELEMENTS], *otoken[MAX_PATH_ELEMENTS];
|
||||
int ntok, ontok;
|
||||
|
||||
ts_clnpath(path);
|
||||
|
||||
/* Reduce "<name>/.." to "/" */
|
||||
ntok = ontok = ts_tokenise(path, "/", otoken, MAX_PATH_ELEMENTS);
|
||||
memcpy(token, otoken, sizeof(char*) * ntok);
|
||||
|
||||
if (ntok > 1) {
|
||||
for (int i = 0; i < ntok - 1; i++)
|
||||
{
|
||||
if (!ts_strequal(token[i], "..") && ts_strequal(token[i + 1], ".."))
|
||||
{
|
||||
if (*token[i] == '\0')
|
||||
continue;
|
||||
while (i < ntok - 1)
|
||||
{
|
||||
token[i] = token[i + 2];
|
||||
i++;
|
||||
}
|
||||
ntok -= 2;
|
||||
i = -1; /* Restart enclosing for loop */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Reassemble string */
|
||||
char *dst = path;
|
||||
if (ntok == 0)
|
||||
{
|
||||
*dst++ = '.';
|
||||
*dst = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
if (token[0][0] == '\0')
|
||||
{
|
||||
int i;
|
||||
for (i = 1; i < ntok && ts_strequal(token[i], ".."); i++)
|
||||
;
|
||||
if (i > 1)
|
||||
{
|
||||
int j;
|
||||
for (j = 1; i < ntok; i++)
|
||||
token[j++] = token[i];
|
||||
ntok = j;
|
||||
}
|
||||
}
|
||||
if (ntok == 1 && token[0][0] == '\0')
|
||||
{
|
||||
*dst++ = '/';
|
||||
*dst = '\0';
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < ntok; i++)
|
||||
{
|
||||
char *src = token[i];
|
||||
while ((*dst++ = *src++) != '\0')
|
||||
;
|
||||
*(dst - 1) = '/';
|
||||
}
|
||||
*(dst - 1) = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
for(int i{0}; i < ontok; i++)
|
||||
::free(otoken[i]);
|
||||
}
|
||||
|
||||
std::string clnpath(const std::string_view& data) {
|
||||
std::string result{data};
|
||||
ts_clnpath2(result.data());
|
||||
auto index = result.find((char) 0);
|
||||
if(index != std::string::npos)
|
||||
result.resize(index);
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef CLN_EXEC
|
||||
typedef struct p1_test_case
|
||||
{
|
||||
const char *input;
|
||||
const char *output;
|
||||
} p1_test_case;
|
||||
|
||||
/* This stress tests the cleaning, concentrating on the boundaries. */
|
||||
static const p1_test_case p1_tests[] =
|
||||
{
|
||||
{ "/", "/", },
|
||||
{ "//", "/", },
|
||||
{ "///", "/", },
|
||||
{ "/.", "/", },
|
||||
{ "/./", "/", },
|
||||
{ "/./.", "/", },
|
||||
{ "/././.profile", "/.profile", },
|
||||
{ "./", ".", },
|
||||
{ "./.", ".", },
|
||||
{ "././", ".", },
|
||||
{ "./././.profile", ".profile", },
|
||||
{ "abc/.", "abc", },
|
||||
{ "abc/./def", "abc/def", },
|
||||
{ "./abc", "abc", },
|
||||
|
||||
{ "//abcd///./abcd////", "/abcd/abcd", },
|
||||
{ "//abcd///././../defg///ddd//.", "/abcd/../defg/ddd", },
|
||||
{ "/abcd/./../././defg/./././ddd", "/abcd/../defg/ddd", },
|
||||
{ "//abcd//././../defg///ddd//.///", "/abcd/../defg/ddd", },
|
||||
|
||||
/* Most of these are minimal interest in phase 1 */
|
||||
{ "/usr/tmp/clnpath.c", "/usr/tmp/clnpath.c", },
|
||||
{ "/usr/tmp/", "/usr/tmp", },
|
||||
{ "/bin/..", "/bin/..", },
|
||||
{ "bin/..", "bin/..", },
|
||||
{ "/bin/.", "/bin", },
|
||||
{ "sub/directory", "sub/directory", },
|
||||
{ "sub/directory/file", "sub/directory/file", },
|
||||
{ "/part1/part2/../.././../", "/part1/part2/../../..", },
|
||||
{ "/.././../usr//.//bin/./cc", "/../../usr/bin/cc", },
|
||||
};
|
||||
|
||||
static void p1_tester(const void *data)
|
||||
{
|
||||
const p1_test_case *test = (const p1_test_case *)data;
|
||||
char buffer[256];
|
||||
|
||||
strcpy(buffer, test->input);
|
||||
ts_clnpath(buffer);
|
||||
if (strcmp(buffer, test->output) == 0)
|
||||
printf("<<%s>> cleans to <<%s>>\n", test->input, buffer);
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "<<%s>> - unexpected output from clnpath()\n", test->input);
|
||||
fprintf(stderr, "Wanted <<%s>>\n", test->output);
|
||||
fprintf(stderr, "Actual <<%s>>\n", buffer);
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct p2_test_case
|
||||
{
|
||||
const char *input;
|
||||
const char *output;
|
||||
} p2_test_case;
|
||||
|
||||
static const p2_test_case p2_tests[] =
|
||||
{
|
||||
{ "/abcd/../defg/ddd", "/defg/ddd" },
|
||||
{ "/bin/..", "/" },
|
||||
{ "bin/..", "." },
|
||||
{ "/usr/bin/..", "/usr" },
|
||||
{ "/usr/bin/../..", "/" },
|
||||
{ "usr/bin/../..", "." },
|
||||
{ "../part/of/../the/way", "../part/the/way" },
|
||||
{ "/../part/of/../the/way", "/part/the/way" },
|
||||
{ "part1/part2/../../part3", "part3" },
|
||||
{ "part1/part2/../../../part3", "../part3" },
|
||||
{ "/part1/part2/../../../part3", "/part3" },
|
||||
{ "/part1/part2/../../../", "/" },
|
||||
{ "/../../usr/bin/cc", "/usr/bin/cc" },
|
||||
{ "../../usr/bin/cc", "../../usr/bin/cc" },
|
||||
{ "part1/./part2/../../part3", "part3" },
|
||||
{ "./part1/part2/../../../part3", "../part3" },
|
||||
{ "/part1/part2/.././../../part3", "/part3" },
|
||||
{ "/part1/part2/../.././../", "/" },
|
||||
{ "/.././..//./usr///bin/cc/", "/usr/bin/cc" },
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
|
||||
static void p2_tester(const void *data)
|
||||
{
|
||||
auto test = (const p2_test_case *)data;
|
||||
char buffer[256];
|
||||
|
||||
strcpy(buffer, test->input);
|
||||
ts_clnpath2(buffer);
|
||||
if (strcmp(buffer, test->output) == 0)
|
||||
printf("<<%s>> cleans to <<%s>>\n", test->input, buffer);
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "<<%s>> - unexpected output from clnpath2()\n", test->input);
|
||||
fprintf(stderr, "Wanted <<%s>>\n", test->output);
|
||||
fprintf(stderr, "Actual <<%s>>\n", buffer);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
for(const auto& test : p1_tests) {
|
||||
if(!test.input) break;
|
||||
p1_tester(&test);
|
||||
}
|
||||
|
||||
printf("------------------------------\n");
|
||||
for(const auto& test : p2_tests) {
|
||||
if(!test.input) break;
|
||||
p2_tester(&test);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
extern std::string clnpath(const std::string_view&);
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
template <typename Rep, typename Per>
|
||||
inline std::string duration_to_string(std::chrono::duration<Rep, Per> ms) {
|
||||
std::string result{};
|
||||
|
||||
{
|
||||
auto hours = std::chrono::duration_cast<std::chrono::hours>(ms);
|
||||
if(hours.count())
|
||||
result += std::to_string(hours.count()) + " hours ";
|
||||
ms -= hours;
|
||||
}
|
||||
{
|
||||
auto minutes = std::chrono::duration_cast<std::chrono::minutes>(ms);
|
||||
if(minutes.count())
|
||||
result += std::to_string(minutes.count()) + " minutes ";
|
||||
ms -= minutes;
|
||||
}
|
||||
{
|
||||
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(ms);
|
||||
if(seconds.count())
|
||||
result += std::to_string(seconds.count()) + " seconds ";
|
||||
ms -= seconds;
|
||||
}
|
||||
if(result.empty()) {
|
||||
auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(ms);
|
||||
if(milliseconds.count())
|
||||
result = std::to_string(milliseconds.count()) + " milliseconds ";
|
||||
}
|
||||
if(result.empty()) {
|
||||
auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(ms);
|
||||
result = std::to_string(microseconds.count()) + " microseconds ";
|
||||
}
|
||||
return result.substr(0, result.length() - 1);
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
//
|
||||
// Created by WolverinDEV on 29/04/2020.
|
||||
//
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <log/LogUtils.h>
|
||||
|
||||
#include <experimental/filesystem>
|
||||
#include <local_server/clnpath.h>
|
||||
#include <event2/thread.h>
|
||||
#include <include/files/Config.h>
|
||||
#include <local_server/HTTPUtils.h>
|
||||
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
using namespace ts::server;
|
||||
|
||||
struct Nothing {};
|
||||
|
||||
template <typename ErrorType, typename ResponseType>
|
||||
inline void print_fs_response(const std::string& message, const std::shared_ptr<file::ExecuteResponse<file::filesystem::DetailedError<ErrorType>, ResponseType>>& response) {
|
||||
if(response->status == file::ExecuteStatus::ERROR)
|
||||
logError(LOG_FT, "{}: {} => {}", message, (int) response->error().error_type, response->error().error_message);
|
||||
else if(response->status == file::ExecuteStatus::SUCCESS)
|
||||
logMessage(LOG_FT, "{}: success", message);
|
||||
else
|
||||
logWarning(LOG_FT, "Unknown response state ({})!", (int) response->status);
|
||||
}
|
||||
|
||||
template <typename ErrorType, typename ResponseType>
|
||||
inline void print_ft_response(const std::string& message, const std::shared_ptr<file::ExecuteResponse<ErrorType, ResponseType>>& response) {
|
||||
if(response->status == file::ExecuteStatus::ERROR)
|
||||
logError(LOG_FT, "{}: {} => {}", message, (int) response->error().error_type, response->error().error_message);
|
||||
else if(response->status == file::ExecuteStatus::SUCCESS)
|
||||
logMessage(LOG_FT, "{}: success", message);
|
||||
else
|
||||
logWarning(LOG_FT, "Unknown response state ({})!", (int) response->status);
|
||||
}
|
||||
|
||||
inline void print_query(const std::string& message, const file::filesystem::AbstractProvider::directory_query_response_t& response) {
|
||||
if(response.status == file::ExecuteStatus::ERROR)
|
||||
logError(LOG_FT, "{}: {} => {}", message, (int) response.error().error_type, response.error().error_message);
|
||||
else if(response.status == file::ExecuteStatus::SUCCESS) {
|
||||
const auto& entries = response.response();
|
||||
logMessage(LOG_FT, "{}: Found {} entries", message, entries.size());
|
||||
for(auto& entry : entries) {
|
||||
if(entry.type == file::filesystem::DirectoryEntry::FILE)
|
||||
logMessage(LOG_FT, " - File {}", entry.name);
|
||||
else if(entry.type == file::filesystem::DirectoryEntry::DIRECTORY)
|
||||
logMessage(LOG_FT, " - Directory {}", entry.name);
|
||||
else
|
||||
logMessage(LOG_FT, " - Unknown {}", entry.name);
|
||||
logMessage(LOG_FT, " Write timestamp: {}", std::chrono::floor<std::chrono::seconds>(entry.modified_at.time_since_epoch()).count());
|
||||
logMessage(LOG_FT, " Size: {}", entry.size);
|
||||
}
|
||||
} else
|
||||
logWarning(LOG_FT, "{}: Unknown response state ({})!", message, (int) response.status);
|
||||
}
|
||||
|
||||
EVP_PKEY* ssl_generate_key() {
|
||||
auto key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(EVP_PKEY_new(), ::EVP_PKEY_free);
|
||||
|
||||
auto rsa = RSA_new();
|
||||
auto e = std::unique_ptr<BIGNUM, decltype(&BN_free)>(BN_new(), ::BN_free);
|
||||
BN_set_word(e.get(), RSA_F4);
|
||||
if(!RSA_generate_key_ex(rsa, 2048, e.get(), nullptr)) return nullptr;
|
||||
EVP_PKEY_assign_RSA(key.get(), rsa);
|
||||
return key.release();
|
||||
}
|
||||
|
||||
X509* ssl_generate_certificate(EVP_PKEY* key) {
|
||||
auto cert = X509_new();
|
||||
X509_set_pubkey(cert, key);
|
||||
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(cert), 3);
|
||||
X509_gmtime_adj(X509_get_notBefore(cert), 0);
|
||||
X509_gmtime_adj(X509_get_notAfter(cert), 31536000L);
|
||||
|
||||
X509_NAME* name = nullptr;
|
||||
name = X509_get_subject_name(cert);
|
||||
//for(const auto& subject : this->subjects)
|
||||
// X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0);
|
||||
X509_set_subject_name(cert, name);
|
||||
|
||||
name = X509_get_issuer_name(cert);
|
||||
//for(const auto& subject : this->issues)
|
||||
// X509_NAME_add_entry_by_txt(name, subject.first.c_str(), MBSTRING_ASC, (unsigned char *) subject.second.c_str(), subject.second.length(), -1, 0);
|
||||
|
||||
X509_set_issuer_name(cert, name);
|
||||
|
||||
X509_sign(cert, key, EVP_sha512());
|
||||
return cert;
|
||||
}
|
||||
|
||||
int main() {
|
||||
evthread_use_pthreads();
|
||||
{
|
||||
std::map<std::string, std::string> query{};
|
||||
http::parse_url_parameters("http://www.example.org/suche?stichwort=wiki&no-arg&1arg=&ausgabe=liste&test=test#bla=d&blub=c", query);
|
||||
for(const auto& [key, value] : query)
|
||||
std::cout << key << " => " << value << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto log_config = std::make_shared<logger::LoggerConfig>();
|
||||
log_config->terminalLevel = spdlog::level::trace;
|
||||
logger::setup(log_config);
|
||||
|
||||
std::string error{};
|
||||
if(!file::initialize(error)) {
|
||||
logError(LOG_FT, "Failed to initialize file server: {}", error);
|
||||
return 0;
|
||||
}
|
||||
logMessage(LOG_FT, "File server started");
|
||||
auto instance = file::server();
|
||||
|
||||
|
||||
{
|
||||
auto options = std::make_shared<pipes::SSL::Options>();
|
||||
options->verbose_io = true;
|
||||
options->context_method = SSLv23_method();
|
||||
options->free_unused_keypairs = false;
|
||||
|
||||
{
|
||||
std::shared_ptr<EVP_PKEY> pkey{ssl_generate_key(), ::EVP_PKEY_free};
|
||||
std::shared_ptr<X509> cert{ssl_generate_certificate(&*pkey), ::X509_free};
|
||||
|
||||
options->default_keypair({pkey, cert});
|
||||
}
|
||||
file::config::ssl_option_supplier = [options]{
|
||||
return options;
|
||||
};
|
||||
}
|
||||
|
||||
#if 0
|
||||
auto& fs = instance->file_system();
|
||||
{
|
||||
auto response = fs.initialize_server(0);
|
||||
response->wait();
|
||||
print_fs_response("Server init result", response);
|
||||
if(response->status != file::ExecuteStatus::SUCCESS)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto response = fs.create_channel_directory(0, 2, "/");
|
||||
response->wait();
|
||||
print_fs_response("Channel dir create A", response);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto response = fs.create_channel_directory(0, 2, "/test-folder/");
|
||||
response->wait();
|
||||
print_fs_response("Channel dir create B", response);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto response = fs.create_channel_directory(0, 2, "../test-folder/");
|
||||
response->wait();
|
||||
print_fs_response("Channel dir create C", response);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
auto response = fs.create_channel_directory(0, 2, "./test-folder/../test-folder-2");
|
||||
response->wait();
|
||||
print_fs_response("Channel dir create D", response);
|
||||
}
|
||||
|
||||
{
|
||||
auto response = fs.query_channel_directory(0, 2, "/");
|
||||
response->wait();
|
||||
print_query("Channel query", *response);
|
||||
}
|
||||
|
||||
{
|
||||
auto response = fs.query_icon_directory(0);
|
||||
response->wait();
|
||||
print_query("Icons", *response);
|
||||
}
|
||||
|
||||
{
|
||||
auto response = fs.query_avatar_directory(0);
|
||||
response->wait();
|
||||
print_query("Avatars", *response);
|
||||
}
|
||||
|
||||
{
|
||||
auto response = fs.rename_channel_file(0, 2, "./test-folder/../test-folder-2", "./test-folder/../test-folder-3");
|
||||
response->wait();
|
||||
print_fs_response("Folder rename A", response);
|
||||
}
|
||||
|
||||
{
|
||||
auto response = fs.rename_channel_file(0, 2, "./test-folder/../test-folder-3", "./test-folder/../test-folder-2");
|
||||
response->wait();
|
||||
print_fs_response("Folder rename B", response);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
auto& ft = instance->file_transfer();
|
||||
|
||||
ft.callback_transfer_finished = [](const std::shared_ptr<file::transfer::Transfer>& transfer) {
|
||||
logMessage(0, "Transfer finished");
|
||||
};
|
||||
|
||||
ft.callback_transfer_started = [](const std::shared_ptr<file::transfer::Transfer>& transfer) {
|
||||
logMessage(0, "Transfer started");
|
||||
};
|
||||
|
||||
ft.callback_transfer_aborted = [](const std::shared_ptr<file::transfer::Transfer>& transfer, const transfer::TransferStatistics&, const file::transfer::TransferError& error) {
|
||||
logMessage(0, "Transfer aborted ({}/{})", (int) error.error_type, error.error_message);
|
||||
};
|
||||
|
||||
ft.callback_transfer_statistics = [](const std::shared_ptr<file::transfer::Transfer>& transfer, const file::transfer::TransferStatistics& stats) {
|
||||
logMessage(0, "Transfer stats. New file bytes: {}, delta bytes send {}", stats.delta_file_bytes_transferred, stats.delta_network_bytes_send);
|
||||
};
|
||||
|
||||
{
|
||||
auto response = ft.initialize_channel_transfer(file::transfer::Transfer::DIRECTION_UPLOAD, 0, 2, {
|
||||
"test2.txt",
|
||||
false,
|
||||
4,
|
||||
120,
|
||||
32
|
||||
});
|
||||
response->wait();
|
||||
print_ft_response("Download test.txt", response);
|
||||
if(response->succeeded())
|
||||
logMessage(LOG_FT, "Download key: {}", std::string{response->response()->transfer_key, TRANSFER_KEY_LENGTH});
|
||||
}
|
||||
#endif
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds{120});
|
||||
//TODO: Test file locking
|
||||
file::finalize();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
Hello World
|
||||
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
@@ -0,0 +1 @@
|
||||
Test HTTPS file upload!
|
||||
+1
-1
Submodule git-teaspeak updated: 9a26231c1f...b1565e43b0
@@ -29,12 +29,13 @@ target_link_libraries(TeaLicenseHelper PUBLIC
|
||||
TeaSpeak
|
||||
protobuf::libprotobuf
|
||||
libevent::core libevent::pthreads
|
||||
openssl::ssl::shared
|
||||
openssl::crypto::shared
|
||||
# openssl::ssl::shared
|
||||
# openssl::crypto::shared
|
||||
|
||||
${StringVariable_LIBRARIES_STATIC}
|
||||
${ed25519_LIBRARIES_STATIC}
|
||||
stdc++fs
|
||||
rt
|
||||
)
|
||||
target_include_directories(TeaLicenseHelper PUBLIC shared/include/)
|
||||
target_include_directories(TeaLicenseHelper PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
@@ -49,7 +50,7 @@ add_executable(TeaLicenseServer ${LICENCE_SOURCE_FILES}
|
||||
server/StatisticManager.cpp
|
||||
server/UserManager.cpp
|
||||
LicenseServerMain.cpp
|
||||
MySQLLibSSLFix.c
|
||||
# Only for boringssl: MySQLLibSSLFix.c
|
||||
)
|
||||
|
||||
target_link_libraries(TeaLicenseServer
|
||||
@@ -61,13 +62,14 @@ target_link_libraries(TeaLicenseServer
|
||||
|
||||
mysqlclient
|
||||
jsoncpp_lib
|
||||
DataPipes::core::shared
|
||||
openssl::ssl::shared
|
||||
openssl::crypto::shared
|
||||
DataPipes::core::static
|
||||
openssl::ssl::static
|
||||
openssl::crypto::static
|
||||
|
||||
pthread
|
||||
dl
|
||||
z
|
||||
z
|
||||
rt
|
||||
)
|
||||
|
||||
#The test license client
|
||||
|
||||
@@ -18,7 +18,7 @@ using namespace license;
|
||||
|
||||
/*
|
||||
* Requests/license: SELECT `tmp`.`keyId`, `tmp`.`key`, `tmp`.`ip`, `tmp`.`count`, `license_info`.`username`, `tmp`.`type` FROM (SELECT DISTINCT `license_request`.`keyId`, `key`, `license_request`.`ip`, COUNT(`license_request`.`keyId`) AS `count`, `license`.`type` FROM `license_request` INNER JOIN `license` ON `license`.`keyId` = `license_request`.`keyId` GROUP BY (`license_request`.`ip`)) AS `tmp` INNER JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId`
|
||||
* Different IP's: SELECT `tmp`.`keyId`, `license_info`.`username`, COUNT(`ip`) FROM (SELECT DISTINCT `ip`, `keyId` FROM `license_request`) AS `tmp` LEFT JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId` GROUP BY (`tmp`.`keyId`)
|
||||
* Different IP's: SELECT `tmp`.`keyId`, `license_info`.`username`, COUNT(`ip`) FROM (SELECT DISTINCT `ip`, `keyId` FROM `license_request`) AS `tmp` LEFT JOIN `license_info` ON `license_info`.`keyId` = `tmp`.`keyId` GROUP BY (`tmp`.`keyId`) ORDER BY COUNT(`ip`) DESC
|
||||
*
|
||||
* Requests (license) / ip: SELECT DISTINCT `ip`, COUNT(`ip`) FROM `license_request` WHERE `keyId` = ? GROUP BY `ip`
|
||||
*
|
||||
@@ -26,12 +26,28 @@ using namespace license;
|
||||
* //462
|
||||
*
|
||||
* SELECT DISTINCT(`ip`), `license_request`.`keyId`, `license_info`.`username` FROM `license_request` LEFT JOIN `license_info` ON `license_info`.`keyId` = `license_request`.`keyId` WHERE `timestamp` > (UNIX_TIMESTAMP() - 2 * 60 * 60) * 1000
|
||||
* License too many request looking: SELECT `keyId`, `username`, COUNT(`ip`) FROM `unique_license_requests` GROUP BY `keyId` ORDER BY COUNT(`ip`) DESC
|
||||
*
|
||||
*
|
||||
*
|
||||
* Online clients: SELECT SUM(`clients`) FROM (SELECT DISTINCT(`ip`), `clients` FROM `history_online` WHERE `timestamp` > (UNIX_TIMESTAMP() - 60 * 60 * 2) * 1000) AS `a`
|
||||
* Online bots: SELECT SUM(`clients`) FROM (SELECT DISTINCT(`ip`), `clients` FROM `history_online` WHERE `timestamp` > (UNIX_TIMESTAMP() - 60 * 60 * 2) * 1000) AS `a`
|
||||
* Online VS Server: SELECT SUM(`music`) FROM (SELECT DISTINCT(`ip`), `music` FROM `history_online` WHERE `timestamp` > (UNIX_TIMESTAMP() - 60 * 60 * 2) * 1000) AS `a`
|
||||
*
|
||||
* Empty instances: curl -ik "https://stats.teaspeak.de:27788?type=request&request_type=general" -X GET 2>&1 | grep "data:"
|
||||
*/
|
||||
//SELECT SUM(`clients`) FROM `history_online` WHERE `keyId` = 701 OR `keyId` = 795 OR `keyId` = 792 OR `keyId` = 582 OR `keyId` = 753 OR `keyId` = 764 OR `keyId` = 761 OR `keyId` = 796 WHERE `timestamp` > (UNIX_TIMESTAMP() - 2.1 * 60 * 60) * 1000
|
||||
|
||||
/*
|
||||
* Extra users:
|
||||
* - ServerSponsoring
|
||||
* - Davide550
|
||||
* - xDeyego?
|
||||
* - latters
|
||||
* - Pamonha
|
||||
* - vova3639 (5 licenses)
|
||||
*/
|
||||
|
||||
bool handle_command(string& line);
|
||||
|
||||
shared_ptr<server::database::DatabaseHandler> license_manager;
|
||||
@@ -64,10 +80,21 @@ inline std::shared_ptr<WebCertificate> load_web_certificate() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if(!BIO_mem_contents(&*bio, &mem_ptr, &length)) {
|
||||
logError(0, "Failed to receive memptr to private key");
|
||||
return nullptr;
|
||||
}
|
||||
#ifdef CRYPTO_BORINGSSL
|
||||
if(!BIO_mem_contents(&*bio_private_key, &mem_ptr, &length)) {
|
||||
logError(0, "Failed to receive memptr to private key");
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
BUF_MEM* memory{nullptr};
|
||||
if(!BIO_get_mem_ptr(&*bio, &memory) || !memory) {
|
||||
logError(0, "Failed to receive memptr to private key");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mem_ptr = (uint8_t*) memory->data;
|
||||
length = memory->length;
|
||||
#endif
|
||||
key.resize(length);
|
||||
memcpy(key.data(), mem_ptr, length);
|
||||
}
|
||||
@@ -78,10 +105,21 @@ inline std::shared_ptr<WebCertificate> load_web_certificate() {
|
||||
logError(0, "Failed to export certificate");
|
||||
return nullptr;
|
||||
}
|
||||
if(!BIO_mem_contents(&*bio, &mem_ptr, &length)) {
|
||||
logError(0, "Failed to receive memptr to certificate");
|
||||
return nullptr;
|
||||
}
|
||||
#ifdef CRYPTO_BORINGSSL
|
||||
if(!BIO_mem_contents(&*bio_private_key, &mem_ptr, &length)) {
|
||||
logError(0, "Failed to receive memptr to public key");
|
||||
return nullptr;
|
||||
}
|
||||
#else
|
||||
BUF_MEM* memory{nullptr};
|
||||
if(!BIO_get_mem_ptr(&*bio, &memory) || !memory) {
|
||||
logError(0, "Failed to receive memptr to public key");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mem_ptr = (uint8_t*) memory->data;
|
||||
length = memory->length;
|
||||
#endif
|
||||
certificate.resize(length);
|
||||
memcpy(certificate.data(), mem_ptr, length);
|
||||
}
|
||||
|
||||
@@ -117,12 +117,12 @@ bool DatabaseHandler::setup(std::string& error) {
|
||||
SET_VERSION(6);
|
||||
|
||||
case 6:
|
||||
CTBL("CREATE TABLE license_upgrade_log (`upgrade_id` INT, `timestamp` INT, `unique_id` VARCHAR(64), `server_ip` INT, `succeeded` TINYINT);");
|
||||
CTBL("CREATE TABLE license_upgrade_log (`upgrade_id` INT, `timestamp` INT, `unique_id` VARCHAR(64), `server_ip` VARCHAR(32), `succeeded` TINYINT);");
|
||||
CIDX("CREATE INDEX `upgrade_id_timestamp` ON `license_upgrade_log` (`upgrade_id`, `timestamp`)");
|
||||
SET_VERSION(7);
|
||||
|
||||
default:;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -408,6 +408,7 @@ std::shared_ptr<DatabaseHandler::UserHistory> DatabaseHandler::list_statistics_u
|
||||
continue; /* last server request is too old to be counted */
|
||||
|
||||
info->instance_online++;
|
||||
info->instance_empty += second.web_clients_online == 0 && second.clients_online == 0;
|
||||
info->queries_online += second.queries_online;
|
||||
info->bots_online += second.bots_online;
|
||||
info->web_clients_online += second.web_clients_online;
|
||||
@@ -441,6 +442,7 @@ std::shared_ptr<DatabaseHandler::UserHistory> DatabaseHandler::list_statistics_u
|
||||
continue; /* last server request is too old to be counted */
|
||||
|
||||
info->instance_online++;
|
||||
info->instance_empty += second.web_clients_online == 0 && second.clients_online == 0;
|
||||
info->queries_online += second.queries_online;
|
||||
info->bots_online += second.bots_online;
|
||||
info->web_clients_online += second.web_clients_online;
|
||||
@@ -531,7 +533,7 @@ std::deque<std::unique_ptr<DatabaseHandler::GlobalVersionsStatistic>> DatabaseHa
|
||||
|
||||
bool DatabaseHandler::register_license_upgrade(license_key_id_t old_key_id, license_key_id_t new_key_id,
|
||||
const std::chrono::system_clock::time_point &begin_timestamp, const std::chrono::system_clock::time_point &end_timestamp, const std::string &license_key) {
|
||||
auto upgrade_id = std::chrono::system_clock::now().time_since_epoch().count();
|
||||
auto upgrade_id = std::chrono::ceil<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto sql_result = sql::command(this->sql(), "INSERT INTO `license_upgrades` (`upgrade_id`, `old_key_id`, `new_key_id`, `timestamp_begin`, `timestamp_end`, `valid`, `use_count`, `license`) VALUES"
|
||||
"(:upgrade_id, :old_key_id, :new_key_id, :timestamp_begin, :timestamp_end, 1, 0, :license)",
|
||||
variable{":upgrade_id", upgrade_id},
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
#include <LicenseRequest.pb.h>
|
||||
|
||||
namespace license::server::database {
|
||||
typedef size_t license_key_id_t;
|
||||
typedef size_t upgrade_id_t;
|
||||
typedef uint64_t license_key_id_t;
|
||||
typedef uint64_t upgrade_id_t;
|
||||
|
||||
class DatabaseHandler;
|
||||
class KeyIdCache {
|
||||
@@ -70,7 +70,8 @@ namespace license::server::database {
|
||||
};
|
||||
|
||||
struct GlobalUserStatistics : public UserStatistics {
|
||||
uint64_t instance_online = 0;
|
||||
uint64_t instance_online{0};
|
||||
uint64_t instance_empty{0};
|
||||
};
|
||||
|
||||
struct DatabaseUserStatistics : public UserStatistics {
|
||||
|
||||
@@ -134,16 +134,19 @@ void LicenseServer::handleEventWrite(int fd, short, void* ptrServer) {
|
||||
if(!client) return;
|
||||
|
||||
buffer::RawBuffer* write_buffer{nullptr};
|
||||
std::unique_lock write_lock(client->network.write_queue_lock);
|
||||
while(true) { //TODO: May add some kind of timeout?
|
||||
std::lock_guard lock(client->network.write_queue_lock);
|
||||
write_buffer = TAILQ_FIRST(&client->network.write_queue);
|
||||
if(!write_buffer) return;
|
||||
|
||||
auto writtenBytes = send(fd, &write_buffer->buffer[write_buffer->index], write_buffer->length - write_buffer->index, 0);
|
||||
if(writtenBytes <= 0) {
|
||||
write_lock.unlock();
|
||||
if(writtenBytes == -1 && errno == EAGAIN)
|
||||
return;
|
||||
logError(LOG_LICENSE_CONTROLL, "Invalid write. Disconnecting remote client. Message: {}/{}", errno, strerror(errno));
|
||||
server->unregisterClient(client);
|
||||
return;
|
||||
} else {
|
||||
write_buffer->index += writtenBytes;
|
||||
}
|
||||
@@ -187,7 +190,7 @@ void ConnectedClient::init() {
|
||||
TAILQ_INIT(&network.write_queue);
|
||||
}
|
||||
|
||||
void ConnectedClient::uninit() {
|
||||
void ConnectedClient::uninit(bool blocking) {
|
||||
{
|
||||
lock_guard queue_lock{this->network.write_queue_lock};
|
||||
ts::buffer::RawBuffer* buffer;
|
||||
@@ -201,11 +204,21 @@ void ConnectedClient::uninit() {
|
||||
close(this->network.fileDescriptor);
|
||||
network.fileDescriptor = 0;
|
||||
}
|
||||
if(this->network.readEvent) event_del(this->network.readEvent);
|
||||
this->network.readEvent = nullptr;
|
||||
|
||||
if(this->network.writeEvent) event_del(this->network.writeEvent);
|
||||
this->network.writeEvent = nullptr;
|
||||
std::unique_lock elock{this->network.event_mutex};
|
||||
auto read_event = std::exchange(this->network.readEvent, nullptr);
|
||||
auto write_event = std::exchange(this->network.writeEvent, nullptr);
|
||||
elock.unlock();
|
||||
|
||||
if(blocking) {
|
||||
if(read_event) event_del_block(read_event);
|
||||
if(write_event) event_del_block(write_event);
|
||||
} else {
|
||||
if(read_event) event_del_noblock(read_event);
|
||||
if(write_event) event_del_noblock(write_event);
|
||||
}
|
||||
if(read_event) event_free(read_event);
|
||||
if(write_event) event_free(write_event);
|
||||
}
|
||||
|
||||
void LicenseServer::handleEventRead(int fd, short, void* ptrServer) {
|
||||
@@ -221,12 +234,20 @@ void LicenseServer::handleEventRead(int fd, short, void* ptrServer) {
|
||||
if(read < 0){
|
||||
if(errno == EWOULDBLOCK) return;
|
||||
logError(LOG_LICENSE_CONTROLL, "Invalid read. Disconnecting remote client. Message: {}/{}", errno, strerror(errno));
|
||||
event_del_noblock(client->network.readEvent);
|
||||
{
|
||||
std::lock_guard elock{client->network.event_mutex};
|
||||
if(client->network.readEvent)
|
||||
event_del_noblock(client->network.readEvent);
|
||||
}
|
||||
server->closeConnection(client);
|
||||
return;
|
||||
} else if(read == 0) {
|
||||
logMessage(LOG_LICENSE_CONTROLL, "[CLIENT][" + client->address() + "] Received EOF for client. Removing client.");
|
||||
event_del_noblock(client->network.readEvent);
|
||||
{
|
||||
std::lock_guard elock{client->network.event_mutex};
|
||||
if(client->network.readEvent)
|
||||
event_del_noblock(client->network.readEvent);
|
||||
}
|
||||
server->closeConnection(client);
|
||||
return;
|
||||
}
|
||||
@@ -312,7 +333,7 @@ void LicenseServer::unregisterClient(const std::shared_ptr<ConnectedClient> &cli
|
||||
std::lock_guard state_lock{client->protocol.state_lock};
|
||||
client->protocol.state = protocol::UNCONNECTED;
|
||||
}
|
||||
client->uninit();
|
||||
client->uninit(this_thread::get_id() != this->event_base_dispatch.get_id());
|
||||
}
|
||||
|
||||
void LicenseServer::cleanup_clients() {
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace license {
|
||||
struct {
|
||||
sockaddr_in remoteAddr;
|
||||
int fileDescriptor = 0;
|
||||
|
||||
std::mutex event_mutex{};
|
||||
event* readEvent = nullptr; /* protected via state_lock (check state and the use these variables) */
|
||||
event* writeEvent = nullptr;
|
||||
|
||||
@@ -57,7 +59,7 @@ namespace license {
|
||||
bool invalid_license = false;
|
||||
|
||||
void init();
|
||||
void uninit();
|
||||
void uninit(bool /* blocking */);
|
||||
void sendPacket(const protocol::packet&);
|
||||
|
||||
inline std::string address() { return inet_ntoa(network.remoteAddr.sin_addr); }
|
||||
|
||||
@@ -188,7 +188,7 @@ bool LicenseServer::handleServerValidation(shared_ptr<ConnectedClient> &client,
|
||||
logMessage(LOG_GENERAL, "[CLIENT][{}] Remote license has been deleted! Shutting down server!", client->address());
|
||||
} else {
|
||||
fill_info(response.mutable_license_info(), info, remote_license->data.licenceKey);
|
||||
auto is_invalid = !info->isValid();
|
||||
auto is_invalid = !info->isNotExpired();
|
||||
if(is_invalid) {
|
||||
response.set_invalid_reason("license is invalid");
|
||||
response.set_valid(false);
|
||||
@@ -198,9 +198,15 @@ bool LicenseServer::handleServerValidation(shared_ptr<ConnectedClient> &client,
|
||||
}
|
||||
}
|
||||
this->manager->logRequest(remote_license->key(), client->unique_identifier, client->address(), pkt.info().version(), response.valid());
|
||||
} else {
|
||||
} else {
|
||||
/* shall never happen, by default each server has the default license */
|
||||
response.set_valid(true);
|
||||
}
|
||||
if(pkt.has_memory_valid() && !pkt.memory_valid()) {
|
||||
response.set_invalid_reason("server memory seems to be invalid");
|
||||
response.set_valid(false);
|
||||
logError(LOG_GENERAL, "Server {} has patched license memory!", client->address());
|
||||
}
|
||||
|
||||
if(client->protocol.version == 2) {
|
||||
if(response.valid())
|
||||
@@ -289,17 +295,17 @@ bool LicenseServer::handlePacketPropertyUpdate(shared_ptr<ConnectedClient> &clie
|
||||
this->manager->logStatistic(client->key, client->unique_identifier, client->address(), pkt);
|
||||
//TODO test stuff if its possible!
|
||||
|
||||
ts::proto::license::WebCertificate* web_certificate{nullptr};
|
||||
ts::proto::license::WebCertificate* proto_web_certificate{nullptr};
|
||||
if(pkt.has_web_cert_revision()) {
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] -------------------------------");
|
||||
logMessage(LOG_GENERAL, "[CLIENT][" + client->address() + "] Web cert revision : " + hex::hex(pkt.web_cert_revision()));
|
||||
|
||||
auto cert = this->web_certificate;
|
||||
if(cert && cert->revision != pkt.web_cert_revision()) {
|
||||
web_certificate = new ts::proto::license::WebCertificate{};
|
||||
web_certificate->set_key(cert->key);
|
||||
web_certificate->set_certificate(cert->certificate);
|
||||
web_certificate->set_revision(cert->revision);
|
||||
proto_web_certificate = new ts::proto::license::WebCertificate{};
|
||||
proto_web_certificate->set_key(cert->key);
|
||||
proto_web_certificate->set_certificate(cert->certificate);
|
||||
proto_web_certificate->set_revision(cert->revision);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +314,7 @@ bool LicenseServer::handlePacketPropertyUpdate(shared_ptr<ConnectedClient> &clie
|
||||
response.set_reset_speach(pkt.speach_total() < 0);
|
||||
response.set_speach_total_remote(pkt.speach_total());
|
||||
response.set_speach_varianz_corrector(0);
|
||||
response.set_allocated_web_certificate(web_certificate);
|
||||
response.set_allocated_web_certificate(proto_web_certificate);
|
||||
client->sendPacket(protocol::packet{protocol::PACKET_SERVER_PROPERTY_ADJUSTMENT, response});
|
||||
this->disconnectClient(client, "finished");
|
||||
|
||||
|
||||
@@ -139,6 +139,7 @@ std::shared_ptr<GeneralStatistics> StatisticManager::general_statistics() {
|
||||
|
||||
deque<unique_ptr<GeneralStatisticEntry>> entries;
|
||||
|
||||
//TODO: Calculate web clients!
|
||||
auto result = sql::command(this->license_manager->sql(), "SELECT `keyId`, `unique_id`, `timestamp`,`server`,`clients`,`music` FROM `history_online` WHERE `timestamp` > :time ORDER BY `timestamp` ASC",
|
||||
variable{":time", duration_cast<milliseconds>(system_clock::now().time_since_epoch() - hours(2) - minutes(10)).count()}) //10min as buffer
|
||||
.query(std::function<decltype(parse_general_entry)>{parse_general_entry}, entries, true);
|
||||
@@ -148,6 +149,7 @@ std::shared_ptr<GeneralStatistics> StatisticManager::general_statistics() {
|
||||
stats->bots += entry->bots;
|
||||
stats->clients += entry->clients;
|
||||
stats->servers += entry->servers;
|
||||
stats->empty_instances += entry->clients == 0;
|
||||
stats->instances++;
|
||||
}
|
||||
stats->age = system_clock::now();
|
||||
|
||||
@@ -5,70 +5,69 @@
|
||||
#include <chrono>
|
||||
#include "DatabaseHandler.h"
|
||||
|
||||
namespace license {
|
||||
namespace stats {
|
||||
struct GeneralStatistics {
|
||||
std::chrono::system_clock::time_point age;
|
||||
namespace license::stats {
|
||||
struct GeneralStatistics {
|
||||
std::chrono::system_clock::time_point age;
|
||||
|
||||
uint64_t instances = 0;
|
||||
uint64_t servers = 0;
|
||||
uint64_t clients = 0;
|
||||
uint64_t bots = 0;
|
||||
};
|
||||
uint64_t empty_instances{0};
|
||||
uint64_t instances{0};
|
||||
uint64_t servers{0};
|
||||
uint64_t clients{0};
|
||||
uint64_t bots{0};
|
||||
};
|
||||
|
||||
struct HistoryStatistics {
|
||||
enum HistoryPeriod {
|
||||
DAY,
|
||||
WEEK,
|
||||
MONTH,
|
||||
HALF_YEAR,
|
||||
YEAR
|
||||
};
|
||||
enum HistoryOffset {
|
||||
NOW,
|
||||
ONE_BEFORE,
|
||||
SEVEN_BEFORE,
|
||||
TWELVE_BEFORE
|
||||
};
|
||||
enum HistoryType {
|
||||
LAST_DAY,
|
||||
DAY_YESTERDAY,
|
||||
DAY_7DAYS_AGO,
|
||||
LAST_WEEK,
|
||||
LAST_MONTH,
|
||||
LAST_HALF_YEAR
|
||||
};
|
||||
static std::chrono::system_clock::time_point align_type(HistoryType type, const std::chrono::system_clock::time_point&);
|
||||
static std::chrono::milliseconds time_period(HistoryType type);
|
||||
static std::chrono::milliseconds cache_timeout(HistoryType type);
|
||||
static std::chrono::milliseconds type_duration(HistoryType type);
|
||||
struct HistoryStatistics {
|
||||
enum HistoryPeriod {
|
||||
DAY,
|
||||
WEEK,
|
||||
MONTH,
|
||||
HALF_YEAR,
|
||||
YEAR
|
||||
};
|
||||
enum HistoryOffset {
|
||||
NOW,
|
||||
ONE_BEFORE,
|
||||
SEVEN_BEFORE,
|
||||
TWELVE_BEFORE
|
||||
};
|
||||
enum HistoryType {
|
||||
LAST_DAY,
|
||||
DAY_YESTERDAY,
|
||||
DAY_7DAYS_AGO,
|
||||
LAST_WEEK,
|
||||
LAST_MONTH,
|
||||
LAST_HALF_YEAR
|
||||
};
|
||||
static std::chrono::system_clock::time_point align_type(HistoryType type, const std::chrono::system_clock::time_point&);
|
||||
static std::chrono::milliseconds time_period(HistoryType type);
|
||||
static std::chrono::milliseconds cache_timeout(HistoryType type);
|
||||
static std::chrono::milliseconds type_duration(HistoryType type);
|
||||
|
||||
std::chrono::system_clock::time_point evaluated;
|
||||
std::chrono::system_clock::time_point begin;
|
||||
std::chrono::system_clock::time_point end;
|
||||
std::chrono::milliseconds period;
|
||||
HistoryType type;
|
||||
std::chrono::system_clock::time_point evaluated;
|
||||
std::chrono::system_clock::time_point begin;
|
||||
std::chrono::system_clock::time_point end;
|
||||
std::chrono::milliseconds period;
|
||||
HistoryType type;
|
||||
|
||||
std::shared_ptr<server::database::DatabaseHandler::UserHistory> statistics;
|
||||
};
|
||||
std::shared_ptr<server::database::DatabaseHandler::UserHistory> statistics;
|
||||
};
|
||||
|
||||
class StatisticManager {
|
||||
public:
|
||||
explicit StatisticManager(std::shared_ptr<server::database::DatabaseHandler> /* manager */);
|
||||
virtual ~StatisticManager();
|
||||
class StatisticManager {
|
||||
public:
|
||||
explicit StatisticManager(std::shared_ptr<server::database::DatabaseHandler> /* manager */);
|
||||
virtual ~StatisticManager();
|
||||
|
||||
void reset_cache_general();
|
||||
std::shared_ptr<GeneralStatistics> general_statistics();
|
||||
std::shared_ptr<HistoryStatistics> history(HistoryStatistics::HistoryType);
|
||||
private:
|
||||
std::shared_ptr<server::database::DatabaseHandler> license_manager;
|
||||
void reset_cache_general();
|
||||
std::shared_ptr<GeneralStatistics> general_statistics();
|
||||
std::shared_ptr<HistoryStatistics> history(HistoryStatistics::HistoryType);
|
||||
private:
|
||||
std::shared_ptr<server::database::DatabaseHandler> license_manager;
|
||||
|
||||
std::recursive_mutex _general_statistics_lock;
|
||||
std::recursive_mutex _general_statistics_generate_lock;
|
||||
std::shared_ptr<GeneralStatistics> _general_statistics;
|
||||
std::recursive_mutex _general_statistics_lock;
|
||||
std::recursive_mutex _general_statistics_generate_lock;
|
||||
std::shared_ptr<GeneralStatistics> _general_statistics;
|
||||
|
||||
std::map<HistoryStatistics::HistoryType, std::recursive_mutex> _history_locks;
|
||||
std::map<HistoryStatistics::HistoryType, std::shared_ptr<HistoryStatistics>> _history;
|
||||
};
|
||||
}
|
||||
std::map<HistoryStatistics::HistoryType, std::recursive_mutex> _history_locks;
|
||||
std::map<HistoryStatistics::HistoryType, std::shared_ptr<HistoryStatistics>> _history;
|
||||
};
|
||||
}
|
||||
+38
-22
@@ -7,6 +7,7 @@
|
||||
#include <misc/net.h>
|
||||
#include <misc/endianness.h>
|
||||
#include <json/json.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "StatisticManager.h"
|
||||
#include "WebAPI.h"
|
||||
|
||||
@@ -197,14 +198,15 @@ void WebStatistics::initialize_client(const std::shared_ptr<license::web::WebSta
|
||||
});
|
||||
|
||||
{
|
||||
std::string error{};
|
||||
auto options = make_shared<pipes::SSL::Options>();
|
||||
options->type = pipes::SSL::SERVER;
|
||||
options->context_method = TLS_method();
|
||||
options->free_unused_keypairs = false; /* we dont want our keys get removed */
|
||||
|
||||
options->default_keypair({this->ssl->privateKey, this->ssl->certificate});
|
||||
if(!client->pipe_ssl->initialize(options)) {
|
||||
logError(LOG_LICENSE_WEB, "[{}][SSL] Failed to setup ssl! Disconnecting client", client->client_prefix());
|
||||
if(!client->pipe_ssl->initialize(options, error)) {
|
||||
logError(LOG_LICENSE_WEB, "[{}][SSL] Failed to setup ssl ({})! Disconnecting client", client->client_prefix(), error);
|
||||
this->close_connection(client);
|
||||
}
|
||||
}
|
||||
@@ -296,17 +298,21 @@ void WebStatistics::handleEventWrite(int file_descriptor, short, void* ptr_serve
|
||||
}
|
||||
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(client->execute_lock);
|
||||
std::unique_lock elock(client->execute_lock);
|
||||
if(client->buffer_write.empty()) return;
|
||||
auto& buffer = client->buffer_write.front();
|
||||
|
||||
auto written = send(file_descriptor, buffer.data(), buffer.length(), MSG_DONTWAIT | MSG_NOSIGNAL);
|
||||
if(written < 0){
|
||||
elock.unlock();
|
||||
|
||||
if(errno == EWOULDBLOCK) return;
|
||||
logError(LOG_LICENSE_WEB, "[{}] Invalid write: {}/{}. Closing connection.", client->client_prefix(), errno, strerror(errno));
|
||||
server->close_connection(client);
|
||||
return;
|
||||
} else if(written == 0) {
|
||||
elock.unlock();
|
||||
|
||||
logError(LOG_LICENSE_WEB, "[{}] Invalid write (eof). Closing connection", client->client_prefix());
|
||||
server->close_connection(client);
|
||||
return;
|
||||
@@ -333,17 +339,20 @@ void WebStatistics::close_connection(const std::shared_ptr<license::web::WebStat
|
||||
else; //TODO Error handling?
|
||||
}
|
||||
|
||||
std::lock_guard<std::recursive_mutex> lock(client->execute_lock);
|
||||
if(client->event_read) {
|
||||
event_del(client->event_read);
|
||||
event_free(client->event_read);
|
||||
client->event_read = nullptr;
|
||||
std::unique_lock elock(client->execute_lock);
|
||||
auto event_read = std::exchange(client->event_read, nullptr);
|
||||
auto event_write = std::exchange(client->event_write, nullptr);
|
||||
elock.unlock();
|
||||
if(event_read) {
|
||||
event_del(event_read);
|
||||
event_free(event_read);
|
||||
}
|
||||
if(client->event_write) {
|
||||
event_del(client->event_write);
|
||||
event_free(client->event_write);
|
||||
client->event_write = nullptr;
|
||||
if(event_write) {
|
||||
event_del(event_write);
|
||||
event_free(event_write);
|
||||
}
|
||||
|
||||
elock.lock();
|
||||
if(client->file_descriptor > 0) {
|
||||
if(shutdown(client->file_descriptor, SHUT_RDWR) < 0); //TODO error handling
|
||||
if(close(client->file_descriptor) < 0); //TODO error handling
|
||||
@@ -352,6 +361,7 @@ void WebStatistics::close_connection(const std::shared_ptr<license::web::WebStat
|
||||
|
||||
if(client->pipe_websocket)
|
||||
client->pipe_websocket = nullptr;
|
||||
|
||||
if(client->pipe_ssl) {
|
||||
client->pipe_ssl->finalize();
|
||||
client->pipe_ssl = nullptr;
|
||||
@@ -472,6 +482,7 @@ bool WebStatistics::handle_message(const std::shared_ptr<license::web::WebStatis
|
||||
auto& history_data = response["history"]["data"];
|
||||
for(index = 0; index < stats->record_count; index++) {
|
||||
auto& indexed_data = history_data[index];
|
||||
indexed_data["instances_empty"] = stats->history[index].instance_empty;
|
||||
indexed_data["instances"] = stats->history[index].instance_online;
|
||||
indexed_data["servers"] = stats->history[index].servers_online;
|
||||
indexed_data["clients"] = stats->history[index].clients_online;
|
||||
@@ -519,6 +530,7 @@ bool WebStatistics::handle_message(const std::shared_ptr<license::web::WebStatis
|
||||
auto& history_data = response["history"]["data"];
|
||||
for(index = 0; index < data->record_count; index++) {
|
||||
auto& indexed_data = history_data[index];
|
||||
indexed_data["instances_empty"] = data->history[index].instance_empty;
|
||||
indexed_data["instances"] = data->history[index].instance_online;
|
||||
indexed_data["servers"] = data->history[index].servers_online;
|
||||
indexed_data["clients"] = data->history[index].clients_online;
|
||||
@@ -555,16 +567,20 @@ bool WebStatistics::handle_request(const std::shared_ptr<license::web::WebStatis
|
||||
auto type = request.parameters.at("type");
|
||||
logMessage(LOG_LICENSE_WEB, "[{}] Received HTTP status request of type {}", client->client_prefix(), type);
|
||||
|
||||
if(type == "request" && request.parameters.at("request_type") == "general") {
|
||||
Json::Value json;
|
||||
json["type"] = "response";
|
||||
auto stats = this->statistics_manager->general_statistics();
|
||||
json["statistics"]["instances"] = to_string(stats->instances);
|
||||
json["statistics"]["servers"] = to_string(stats->servers);
|
||||
json["statistics"]["clients"] = to_string(stats->clients);
|
||||
json["statistics"]["music"] = to_string(stats->bots);
|
||||
response.setHeader("data", {json_dump(json).string()});
|
||||
response.code = http::code::_200;
|
||||
if(type == "request" && request.parameters.count("request_type") > 0) {
|
||||
const auto& type = request.parameters.at("request_type");
|
||||
if(type == "general") {
|
||||
Json::Value json;
|
||||
json["type"] = "response";
|
||||
auto stats = this->statistics_manager->general_statistics();
|
||||
json["statistics"]["instances_empty"] = to_string(stats->empty_instances);
|
||||
json["statistics"]["instances"] = to_string(stats->instances);
|
||||
json["statistics"]["servers"] = to_string(stats->servers);
|
||||
json["statistics"]["clients"] = to_string(stats->clients);
|
||||
json["statistics"]["music"] = to_string(stats->bots);
|
||||
response.setHeader("data", {json_dump(json).string()});
|
||||
response.code = http::code::_200;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -92,7 +92,6 @@ namespace license {
|
||||
std::deque<std::shared_ptr<Client>> clients;
|
||||
|
||||
threads::ThreadPool scheduler{1, "WebStatistics #"};
|
||||
|
||||
protected:
|
||||
static void handleEventAccept(int, short, void*);
|
||||
static void handleEventRead(int, short, void*);
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "license.h"
|
||||
|
||||
namespace license::client {
|
||||
class LicenseServerClient {
|
||||
class LicenseServerClient : public std::enable_shared_from_this<LicenseServerClient> {
|
||||
public:
|
||||
enum ConnectionState {
|
||||
CONNECTING,
|
||||
|
||||
@@ -346,9 +346,9 @@ namespace license {
|
||||
std::chrono::system_clock::time_point creation;
|
||||
|
||||
bool deleted{false};
|
||||
uint32_t upgrade_id{0};
|
||||
uint64_t upgrade_id{0};
|
||||
|
||||
inline bool isValid() { return (end.time_since_epoch().count() == 0 || std::chrono::system_clock::now() < this->end); }
|
||||
inline bool isNotExpired() { return (end.time_since_epoch().count() == 0 || std::chrono::system_clock::now() < this->end); }
|
||||
};
|
||||
|
||||
extern std::shared_ptr<License> readLocalLicence(const std::string &, std::string &);
|
||||
@@ -422,7 +422,7 @@ namespace license {
|
||||
#ifdef GOOGLE_PROTOBUF_MESSAGE_H__
|
||||
packet(PacketType packetId, const ::google::protobuf::Message&);
|
||||
#endif
|
||||
packet(PacketType packetId, nullptr_t);
|
||||
packet(PacketType packetId, std::nullptr_t);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,15 +41,17 @@ message ServerValidation {
|
||||
required bool license_info = 2;
|
||||
optional bytes license = 3;
|
||||
optional ServerInfo info = 4; //Change somewhere to required but its currently for legacy support
|
||||
optional bool memory_valid = 5;
|
||||
}
|
||||
|
||||
message LicenseResponse {
|
||||
required bool valid = 1;
|
||||
optional string invalid_reason = 5; /* in protocol version 2 the blacklist.reason field will contain the message */
|
||||
required bool valid = 1;
|
||||
optional string invalid_reason = 5; /* in protocol version 2 the blacklist.reason field will contain the message */
|
||||
|
||||
required Blacklist blacklist = 2;
|
||||
optional LicenseInfo license_info = 3; //Only available when ServerValidation::license_info = true
|
||||
optional bool update_pending = 4; /* if an update is pending */
|
||||
required Blacklist blacklist = 2;
|
||||
optional LicenseInfo license_info = 3; //Only available when ServerValidation::license_info = true
|
||||
optional bool update_pending = 4; /* if an update is pending */
|
||||
optional bool deprecate_third_party_clients = 6;
|
||||
}
|
||||
|
||||
message PropertyUpdateRequest {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <event.h>
|
||||
#include <ThreadPool/ThreadHelper.h>
|
||||
#include <misc/endianness.h>
|
||||
#include <shared/include/license/client.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include "shared/include/license/client.h"
|
||||
#include "crypt.h"
|
||||
|
||||
@@ -42,11 +44,16 @@ LicenseServerClient::LicenseServerClient(const sockaddr_in &address, int pversio
|
||||
}
|
||||
|
||||
LicenseServerClient::~LicenseServerClient() {
|
||||
this->close_connection();
|
||||
const auto is_event_loop = this->network.event_dispatch.get_id() == std::this_thread::get_id();
|
||||
{
|
||||
std::unique_lock slock{this->connection_lock};
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
}
|
||||
this->cleanup_network_resources(); /* force cleanup ignoring the previous state */
|
||||
if(is_event_loop) this->network.event_dispatch.detach();
|
||||
|
||||
if(this->buffers.read)
|
||||
Buffer::free(this->buffers.read);
|
||||
threads::save_join(this->network.event_dispatch, false);
|
||||
}
|
||||
|
||||
bool LicenseServerClient::start_connection(std::string &error) {
|
||||
@@ -88,14 +95,25 @@ bool LicenseServerClient::start_connection(std::string &error) {
|
||||
this->network.event_base = event_base_new();
|
||||
this->network.event_read = event_new(this->network.event_base, this->network.file_descriptor, EV_READ | EV_PERSIST, [](int, short e, void* _this) {
|
||||
auto client = reinterpret_cast<LicenseServerClient*>(_this);
|
||||
auto client_ref = client->weak_from_this().lock();
|
||||
if(!client_ref) {
|
||||
logCritical(0, "Network callback for expired client (E011)!");
|
||||
return;
|
||||
}
|
||||
client->callback_read(e);
|
||||
client_ref.reset();
|
||||
}, this);
|
||||
this->network.event_write = event_new(this->network.event_base, this->network.file_descriptor, EV_WRITE, [](int, short e, void* _this) {
|
||||
auto client = reinterpret_cast<LicenseServerClient*>(_this);
|
||||
auto client_ref = client->weak_from_this().lock();
|
||||
if(!client_ref) {
|
||||
logCritical(0, "Network callback for expired client (E012)!");
|
||||
return;
|
||||
}
|
||||
client->callback_write(e);
|
||||
client_ref.reset();
|
||||
}, this);
|
||||
|
||||
event_dispatch_spawned = true;
|
||||
this->network.event_dispatch = std::thread([&] {
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
@@ -114,10 +132,6 @@ bool LicenseServerClient::start_connection(std::string &error) {
|
||||
return true;
|
||||
error_cleanup:
|
||||
this->cleanup_network_resources();
|
||||
if(!event_dispatch_spawned) {
|
||||
event_base_free(this->network.event_base);
|
||||
this->network.event_base = nullptr;
|
||||
}
|
||||
this->connection_state = ConnectionState::UNCONNECTED;
|
||||
return false;
|
||||
}
|
||||
@@ -164,7 +178,7 @@ void LicenseServerClient::cleanup_network_resources() {
|
||||
auto buffer = TAILQ_FIRST(&this->buffers.write);
|
||||
while(buffer) {
|
||||
auto next = TAILQ_NEXT(buffer, tail);
|
||||
Buffer::free(next);
|
||||
Buffer::free(buffer);
|
||||
buffer = next;
|
||||
}
|
||||
TAILQ_INIT(&this->buffers.write);
|
||||
@@ -243,8 +257,11 @@ void LicenseServerClient::callback_write(short events) {
|
||||
}
|
||||
|
||||
if(events & EV_WRITE) {
|
||||
if(this->connection_state == ConnectionState::CONNECTING)
|
||||
if(this->connection_state == ConnectionState::CONNECTING) {
|
||||
this->callback_socket_connected();
|
||||
if(this->connection_state == ConnectionState::UNCONNECTED) /* state may change in the callback */
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t written_bytes{0};
|
||||
|
||||
@@ -365,6 +382,8 @@ void LicenseServerClient::handle_data(void *recv_buffer, size_t length) {
|
||||
if(buffer_length < header->length + sizeof(protocol::packet_header)) return;
|
||||
|
||||
this->handle_raw_packet(header->packetId, buffer_ptr + buffer_offset + sizeof(protocol::packet_header), header->length);
|
||||
if(this->connection_state == ConnectionState::UNCONNECTED) return; /* state may change while we're handing the packet */
|
||||
|
||||
buffer_offset += header->length + sizeof(protocol::packet_header);
|
||||
buffer_length -= header->length + sizeof(protocol::packet_header);
|
||||
}
|
||||
|
||||
+1
-1
Submodule music updated: ae7f8c7f3d...a4dabbb5c4
Submodule
+1
Submodule rtclib added at 6bb3ce6050
+90
-70
@@ -3,7 +3,7 @@ project(TeaSpeak-Server)
|
||||
|
||||
set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
#--allow-multiple-definition
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive -Wall -Wno-reorder -Wno-sign-compare -static-libgcc -static-libstdc++ -g -Wl,--no-whole-archive -pthread ${MEMORY_DEBUG_FLAGS} -Werror=return-type")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive -Wall -Wno-reorder -Wno-sign-compare -static-libgcc -static-libstdc++ -g -Wl,--no-whole-archive -pthread ${MEMORY_DEBUG_FLAGS} -Werror=return-type -Werror=delete-non-virtual-dtor")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -O3")
|
||||
|
||||
@@ -39,40 +39,36 @@ set(CMAKE_VERBOSE_MAKEFILE ON)
|
||||
set(SERVER_SOURCE_FILES
|
||||
main.cpp
|
||||
tomcryptTest.cpp
|
||||
MySQLLibSSLFix.c
|
||||
# Only needed for boringssl
|
||||
# MySQLLibSSLFix.c
|
||||
|
||||
src/client/ConnectedClient.cpp
|
||||
src/client/voice/PrecomputedPuzzles.cpp
|
||||
src/server/PrecomputedPuzzles.cpp
|
||||
src/client/voice/VoiceClient.cpp
|
||||
src/client/voice/VoiceClientHandschake.cpp
|
||||
src/client/voice/VoiceClientCommandHandler.cpp
|
||||
src/client/voice/VoiceClientPacketHandler.cpp
|
||||
src/client/voice/VoiceClientView.cpp
|
||||
src/client/voice/VoiceClientConnectionPacketHandler.cpp
|
||||
src/TS3ServerClientManager.cpp
|
||||
src/VirtualServer.cpp
|
||||
src/FileServerHandler.cpp
|
||||
src/TS3ServerHeartbeat.cpp
|
||||
src/SignalHandler.cpp
|
||||
src/server/VoiceServer.cpp
|
||||
src/server/VoiceServerSocket.cpp
|
||||
src/server/POWHandler.cpp
|
||||
src/client/voice/VoiceClientConnection.cpp
|
||||
#src/client/ConnectedClientCommandHandler.cpp
|
||||
src/client/command_handler/groups.cpp
|
||||
src/client/command_handler/channel.cpp
|
||||
src/client/command_handler/client.cpp
|
||||
src/client/command_handler/server.cpp
|
||||
src/client/command_handler/misc.cpp
|
||||
src/client/command_handler/bulk_parsers.cpp
|
||||
|
||||
src/client/ConnectedClientNotifyHandler.cpp
|
||||
src/VirtualServerManager.cpp
|
||||
src/server/file/FileServer.cpp
|
||||
src/channel/ServerChannel.cpp
|
||||
src/channel/ClientChannelView.cpp
|
||||
src/client/file/FileClient.cpp
|
||||
src/client/file/FileClientIO.cpp
|
||||
src/Group.cpp
|
||||
src/manager/BanManager.cpp
|
||||
src/client/InternalClient.cpp
|
||||
#src/weblist/WeblistClient.cpp
|
||||
#src/weblist/WebList.cpp
|
||||
|
||||
src/client/DataClient.cpp
|
||||
src/server/QueryServer.cpp
|
||||
@@ -80,12 +76,12 @@ set(SERVER_SOURCE_FILES
|
||||
src/client/query/QueryClientCommands.cpp
|
||||
src/client/query/QueryClientNotify.cpp
|
||||
|
||||
|
||||
src/manager/IpListManager.cpp
|
||||
src/server/GlobalNetworkEvents.cpp
|
||||
|
||||
src/ConnectionStatistics.cpp
|
||||
|
||||
src/manager/TokeManager.cpp
|
||||
src/manager/TokenManager.cpp
|
||||
|
||||
src/terminal/CommandHandler.cpp
|
||||
|
||||
@@ -95,13 +91,14 @@ set(SERVER_SOURCE_FILES
|
||||
src/manager/LetterManager.cpp
|
||||
src/manager/PermissionNameManager.cpp
|
||||
|
||||
src/pinteraction/ApplicationInteraction.cpp
|
||||
src/ServerManagerSnapshot.cpp
|
||||
src/ServerManagerSnapshotDeploy.cpp
|
||||
src/client/music/Song.cpp
|
||||
src/music/PlayablePlaylist.cpp
|
||||
src/InstanceHandler.cpp
|
||||
src/InstanceHandlerSetup.cpp
|
||||
src/InstanceHandlerPermissions.cpp
|
||||
src/PermissionCalculator.cpp
|
||||
|
||||
src/Configuration.cpp
|
||||
|
||||
@@ -120,8 +117,6 @@ set(SERVER_SOURCE_FILES
|
||||
|
||||
src/client/query/XMacroEventTypes.h
|
||||
|
||||
src/server/VoiceIOManager.cpp
|
||||
src/server/WebIoManager.cpp
|
||||
src/client/SpeakingClient.cpp
|
||||
|
||||
../shared/src/ssl/SSLManager.cpp
|
||||
@@ -129,15 +124,41 @@ set(SERVER_SOURCE_FILES
|
||||
src/manager/SqlDataManager.cpp
|
||||
|
||||
src/ShutdownHelper.cpp
|
||||
src/client/music/MusicQueue.cpp
|
||||
src/lincense/TeamSpeakLicense.cpp
|
||||
|
||||
src/weblist/WebListManager.cpp
|
||||
src/weblist/TeamSpeakWebClient.cpp
|
||||
src/snapshots/permission.cpp
|
||||
src/snapshots/client.cpp
|
||||
src/snapshots/channel.cpp
|
||||
src/snapshots/server.cpp
|
||||
src/snapshots/groups.cpp
|
||||
src/snapshots/deploy.cpp
|
||||
src/snapshots/music.cpp
|
||||
src/snapshots/parser.cpp
|
||||
|
||||
src/groups/Group.cpp
|
||||
src/groups/GroupManager.cpp
|
||||
src/groups/GroupAssignmentManager.cpp
|
||||
|
||||
src/manager/ActionLogger.cpp
|
||||
src/manager/ActionLoggerImpl.cpp
|
||||
|
||||
src/manager/ConversationManager.cpp
|
||||
src/client/SpeakingClientHandshake.cpp
|
||||
src/client/command_handler/music.cpp src/client/command_handler/file.cpp)
|
||||
src/client/command_handler/music.cpp src/client/command_handler/file.cpp
|
||||
|
||||
src/client/voice/PacketEncoder.cpp
|
||||
src/client/shared/ServerCommandExecutor.cpp
|
||||
src/client/voice/CryptSetupHandler.cpp
|
||||
src/client/shared/WhisperHandler.cpp
|
||||
|
||||
src/terminal/PipedTerminal.cpp
|
||||
|
||||
src/server/voice/UDPVoiceServer.cpp
|
||||
src/server/voice/DatagramPacket.cpp
|
||||
|
||||
src/rtc/lib.cpp
|
||||
)
|
||||
|
||||
if (COMPILE_WEB_CLIENT)
|
||||
add_definitions(-DCOMPILE_WEB_CLIENT)
|
||||
|
||||
@@ -146,51 +167,18 @@ if (COMPILE_WEB_CLIENT)
|
||||
|
||||
src/server/WebServer.cpp
|
||||
src/client/web/WebClient.cpp
|
||||
# src/server/web/WebRTCServer.cpp
|
||||
src/client/web/WSWebClient.cpp
|
||||
src/client/web/SampleHandler.cpp
|
||||
src/client/web/VoiceBridge.cpp
|
||||
src/client/command_handler/helpers.h src/music/PlaylistPermissions.cpp src/music/PlaylistPermissions.h src/lincense/LicenseService.cpp src/lincense/LicenseService.h)
|
||||
endif ()
|
||||
|
||||
add_executable(PermHelper helpers/permgen.cpp)
|
||||
target_link_libraries(PermHelper
|
||||
${LIBRARY_PATH_ED255}
|
||||
|
||||
TeaSpeak #Static
|
||||
TeaLicenseHelper #Static
|
||||
TeaMusic #Static
|
||||
${LIBRARY_PATH_THREAD_POOL} #Static
|
||||
${LIBRARY_PATH_TERMINAL} #Static
|
||||
${LIBRARY_PATH_VARIBALES}
|
||||
${LIBRARY_PATH_YAML}
|
||||
pthread
|
||||
TeaSpeak
|
||||
stdc++fs
|
||||
${LIBEVENT_PATH}/libevent.a
|
||||
${LIBEVENT_PATH}/libevent_pthreads.a
|
||||
${LIBRARY_PATH_OPUS}
|
||||
${LIBRARY_PATH_JSON}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
|
||||
#${LIBWEBRTC_LIBRARIES} #ATTENTIAN! WebRTC does not work with crypto! (Already contains a crypto version)
|
||||
${LIBRARY_TOM_CRYPT}
|
||||
${LIBRARY_TOM_MATH}
|
||||
|
||||
#We're forsed to use boringssl caused by the fact that boringssl is already within webrtc!
|
||||
|
||||
#Require a so
|
||||
sqlite3
|
||||
|
||||
${LIBRARY_PATH_BREAKPAD}
|
||||
${LIBRARY_PATH_JDBC}
|
||||
${LIBRARY_PATH_PROTOBUF}
|
||||
|
||||
${LIBRARY_PATH_DATA_PIPES}
|
||||
${LIBRARY_PATH_BORINGSSL_SSL}
|
||||
${LIBRARY_PATH_BORINGSSL_CRYPTO}
|
||||
dl
|
||||
jemalloc
|
||||
)
|
||||
CXXTerminal::static
|
||||
libevent::core libevent::pthreads
|
||||
${StringVariable_LIBRARIES_STATIC}
|
||||
)
|
||||
|
||||
add_executable(PermMapHelper helpers/PermMapGen.cpp)
|
||||
target_link_libraries(PermMapHelper
|
||||
@@ -233,8 +221,8 @@ target_link_libraries(PermMapHelper
|
||||
|
||||
|
||||
SET(CPACK_PACKAGE_VERSION_MAJOR "1")
|
||||
SET(CPACK_PACKAGE_VERSION_MINOR "4")
|
||||
SET(CPACK_PACKAGE_VERSION_PATCH "10")
|
||||
SET(CPACK_PACKAGE_VERSION_MINOR "5")
|
||||
SET(CPACK_PACKAGE_VERSION_PATCH "6")
|
||||
if (BUILD_TYPE_NAME EQUAL OFF)
|
||||
SET(CPACK_PACKAGE_VERSION_DATA "beta")
|
||||
elseif (BUILD_TYPE_NAME STREQUAL "")
|
||||
@@ -256,6 +244,8 @@ target_link_libraries(TeaSpeakServer
|
||||
TeaLicenseHelper #Static
|
||||
TeaMusic #Static
|
||||
CXXTerminal::static #Static
|
||||
TeaSpeak-FileServer
|
||||
# TeaSpeak-FileRust
|
||||
${StringVariable_LIBRARIES_STATIC}
|
||||
${YAML_CPP_LIBRARIES}
|
||||
pthread
|
||||
@@ -266,24 +256,34 @@ target_link_libraries(TeaSpeakServer
|
||||
|
||||
#Require a so
|
||||
sqlite3
|
||||
DataPipes::rtc::shared
|
||||
DataPipes::core::static
|
||||
|
||||
breakpad::static
|
||||
protobuf::libprotobuf
|
||||
jemalloc::shared
|
||||
#jemalloc::shared
|
||||
|
||||
tomcrypt::static
|
||||
tommath::static
|
||||
|
||||
jsoncpp_lib
|
||||
${ed25519_LIBRARIES_STATIC}
|
||||
zstd::libzstd_static
|
||||
)
|
||||
|
||||
if (COMPILE_WEB_CLIENT)
|
||||
target_link_libraries(TeaSpeakServer ${glib20_DIR}/lib/x86_64-linux-gnu/libffi.so.7 ${nice_DIR}/lib/libnice.so.10)
|
||||
endif ()
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/rtclib/libteaspeak_rtc.so" AND NOT NO_RELEASE_RTC)
|
||||
message("Linking to release librtc file")
|
||||
target_link_libraries(TeaSpeakServer
|
||||
${CMAKE_SOURCE_DIR}/rtclib/libteaspeak_rtc.so
|
||||
)
|
||||
elseif(EXISTS "${CMAKE_SOURCE_DIR}/rtclib/libteaspeak_rtc_development.so")
|
||||
message("Linkding against debug libteaspeak_rtc_development.so")
|
||||
target_link_libraries(TeaSpeakServer
|
||||
${CMAKE_SOURCE_DIR}/rtclib/libteaspeak_rtc_development.so
|
||||
)
|
||||
else()
|
||||
message(FATAL_ERROR "Missing librtc library file")
|
||||
endif()
|
||||
|
||||
include_directories(${LIBRARY_PATH}/boringssl/include/)
|
||||
# include_directories(${LIBRARY_PATH}/boringssl/include/)
|
||||
target_link_libraries(TeaSpeakServer
|
||||
openssl::ssl::shared
|
||||
openssl::crypto::shared
|
||||
@@ -306,4 +306,24 @@ if (NOT DISABLE_JEMALLOC)
|
||||
jemalloc
|
||||
)
|
||||
add_definitions(-DHAVE_JEMALLOC)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
|
||||
add_executable(Snapshots-Permissions-Test src/snapshots/permission.cpp tests/snapshots/permission.cpp)
|
||||
target_link_libraries(Snapshots-Permissions-Test PUBLIC
|
||||
TeaSpeak
|
||||
CXXTerminal::static #Static
|
||||
${StringVariable_LIBRARIES_STATIC}
|
||||
${YAML_CPP_LIBRARIES}
|
||||
pthread
|
||||
stdc++fs
|
||||
libevent::core libevent::pthreads
|
||||
|
||||
#Require a so
|
||||
sqlite3
|
||||
DataPipes::core::static
|
||||
|
||||
tomcrypt::static
|
||||
tommath::static
|
||||
)
|
||||
target_include_directories(Snapshots-Permissions-Test PUBLIC ${CMAKE_SOURCE_DIR}/server/src/)
|
||||
@@ -1 +0,0 @@
|
||||
../repro/env/geoloc/
|
||||
@@ -1 +0,0 @@
|
||||
../../music/bin/providers/
|
||||
@@ -1 +0,0 @@
|
||||
../repro/env/resources/
|
||||
@@ -1,10 +1,7 @@
|
||||
#include <fstream>
|
||||
#include <query/Command.h>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
|
||||
#include <functional> /* required from permission manager */
|
||||
#include "log/LogUtils.h"
|
||||
#include "Definitions.h"
|
||||
#include "PermissionManager.h"
|
||||
|
||||
@@ -135,7 +132,7 @@ int main(int argc, char** argv) {
|
||||
group.target = line == "2" ? TARGET_QUERY : TARGET_SERVER;
|
||||
read_line(file, line);
|
||||
auto data = "perms " + line;
|
||||
ts::Command group_parms = ts::Command::parse(pipes::buffer_view(data.data(), data.length()));
|
||||
ts::Command group_parms = ts::Command::parse(data);
|
||||
|
||||
map<permission::PermissionType, permission::update::UpdatePermission> grantMapping;
|
||||
for (int index = 0; index < group_parms.bulkCount(); index++) {
|
||||
@@ -184,7 +181,7 @@ int main(int argc, char** argv) {
|
||||
group.target = TARGET_CHANNEL;
|
||||
read_line(file, line);
|
||||
auto data = "perms " + line;
|
||||
ts::Command group_parms = ts::Command::parse(pipes::buffer_view(data.data(), data.length()));
|
||||
ts::Command group_parms = ts::Command::parse(data);
|
||||
|
||||
map<permission::PermissionType, permission::update::UpdatePermission> grantMapping;
|
||||
for (int index = 0; index < group_parms.bulkCount(); index++) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+7
-1
@@ -1,3 +1,9 @@
|
||||
Locking Order:
|
||||
Channel Tree:
|
||||
1. Server Channel Tree
|
||||
2. Client Channel Tree
|
||||
|
||||
|
||||
CommandResult handleCommandClientUpdate(Command&);
|
||||
CommandResult handleCommandClientEdit(Command&);
|
||||
CommandResult handleCommandClientEdit(Command&, const std::shared_ptr<ConnectedClient>& /* target */);
|
||||
@@ -55,4 +61,4 @@ Move client acts like access server channel tree: write lock channel_tree_lock =
|
||||
TODO: Some kind of perm channel lock
|
||||
TODO: Fix handleCommandChannelEdit
|
||||
|
||||
Test: Channel hide & show with clients! Multible clients as well!
|
||||
Test: Channel hide & show with clients! Multiple clients as well!
|
||||
+205
-24
@@ -1,4 +1,6 @@
|
||||
#include <client/linux/handler/exception_handler.h>
|
||||
|
||||
//include <iterator> /* Required for breakpad */
|
||||
//#include <client/linux/handler/exception_handler.h>
|
||||
#include <iostream>
|
||||
#include <misc/strobf.h>
|
||||
#include <CXXTerminal/QuickTerminal.h>
|
||||
@@ -10,11 +12,13 @@
|
||||
#include "src/VirtualServer.h"
|
||||
#include "src/InstanceHandler.h"
|
||||
#include "src/server/QueryServer.h"
|
||||
#include "src/server/file/FileServer.h"
|
||||
#include "src/terminal/CommandHandler.h"
|
||||
#include "src/client/InternalClient.h"
|
||||
#include "src/SignalHandler.h"
|
||||
#include "src/build.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@@ -44,11 +48,12 @@ extern void testTomMath();
|
||||
#define DB_NAME "TeaData.sqlite"
|
||||
#endif
|
||||
|
||||
#include <regex>
|
||||
#include <codecvt>
|
||||
#include "src/client/music/internal_provider/channel_replay/ChannelProvider.h"
|
||||
#include <codecvt>
|
||||
#include <src/rtc/lib.h>
|
||||
#include <src/terminal/PipedTerminal.h>
|
||||
|
||||
class CLIParser{
|
||||
class CLIParser {
|
||||
public:
|
||||
CLIParser (int &argc, char **argv){
|
||||
for (int i = 1; i < argc; i++)
|
||||
@@ -114,6 +119,82 @@ int main(int argc, char** argv) {
|
||||
(void*) malloc_conf;
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
{
|
||||
//ts::property::list<ts::property::InstanceProperties>()
|
||||
std::cout << "| Name | Type | Flags | Default Value | Description | \n";
|
||||
std::cout << "|:-- | -- | -- | -- |:-- | \n";
|
||||
for(const auto& property : ts::property::list<ts::property::VirtualServerProperties>()) {
|
||||
std::cout << "| `" << property->name << "` | ";
|
||||
|
||||
switch(property->type_value) {
|
||||
case ts::property::TYPE_STRING:
|
||||
std::cout << "String | ";
|
||||
break;
|
||||
|
||||
case ts::property::TYPE_BOOL:
|
||||
std::cout << "Boolean | ";
|
||||
break;
|
||||
|
||||
case ts::property::TYPE_SIGNED_NUMBER:
|
||||
std::cout << "Signed number | ";
|
||||
break;
|
||||
|
||||
case ts::property::TYPE_UNSIGNED_NUMBER:
|
||||
std::cout << "Unsigned number | ";
|
||||
break;
|
||||
|
||||
case ts::property::TYPE_FLOAT:
|
||||
std::cout << "Float | ";
|
||||
break;
|
||||
|
||||
default:
|
||||
std::cout << "Unknown | ";
|
||||
break;
|
||||
}
|
||||
|
||||
std::string flags{};
|
||||
if(property->flags & ts::property::FLAG_INTERNAL) { flags += ", internal"; }
|
||||
if(property->flags & ts::property::FLAG_GLOBAL) { flags += ", global"; }
|
||||
|
||||
if(property->flags & ts::property::FLAG_SNAPSHOT) { flags += ", snapshot"; }
|
||||
if(property->flags & ts::property::FLAG_SAVE) { flags += ", saved"; }
|
||||
if(property->flags & ts::property::FLAG_SAVE_MUSIC) { flags += ", saved (music)"; }
|
||||
|
||||
if(property->flags & ts::property::FLAG_NEW) { flags += ", new"; }
|
||||
if(property->flags & ts::property::FLAG_SERVER_VARIABLE) { flags += ", server variable"; }
|
||||
if(property->flags & ts::property::FLAG_SERVER_VIEW) { flags += ", server view variable"; }
|
||||
|
||||
if(property->flags & ts::property::FLAG_CLIENT_VARIABLE) { flags += ", client variable"; }
|
||||
if(property->flags & ts::property::FLAG_CLIENT_VIEW) { flags += ", client view variable"; }
|
||||
if(property->flags & ts::property::FLAG_CLIENT_INFO) { flags += ", client info variable"; }
|
||||
|
||||
if(property->flags & ts::property::FLAG_CHANNEL_VARIABLE) { flags += ", channel variable"; }
|
||||
if(property->flags & ts::property::FLAG_CHANNEL_VIEW) { flags += ", channel view variable"; }
|
||||
|
||||
// FLAG_GROUP_VIEW = FLAG_CHANNEL_VIEW << 1UL,
|
||||
if(property->flags & ts::property::FLAG_INSTANCE_VARIABLE) { flags += ", instance variable"; }
|
||||
if(property->flags & ts::property::FLAG_PLAYLIST_VARIABLE) { flags += ", playlist variable"; }
|
||||
if(property->flags & ts::property::FLAG_USER_EDITABLE) { flags += ", editable"; }
|
||||
|
||||
if(!flags.empty()) {
|
||||
std::cout << flags.substr(2);
|
||||
}
|
||||
std::cout << "| ";
|
||||
if(property->default_value.empty()) {
|
||||
std::cout << "empty";
|
||||
} else {
|
||||
std::cout << "`" << property->default_value << "` ";
|
||||
}
|
||||
std::cout << "| ";
|
||||
std::cout << "No description ";
|
||||
std::cout << "| ";
|
||||
std::cout << " \n";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
CLIParser arguments(argc, argv);
|
||||
SSL_load_error_strings();
|
||||
OpenSSL_add_ssl_algorithms();
|
||||
@@ -127,9 +208,8 @@ int main(int argc, char** argv) {
|
||||
|
||||
if(!arguments.cmdOptionExists("--no-terminal")) {
|
||||
terminal::install();
|
||||
if(!terminal::active()){ cerr << "could not setup terminal!" << endl; return -1; }
|
||||
if(!terminal::active()) { cerr << "could not setup terminal!" << endl; return -1; }
|
||||
}
|
||||
assert(ts::property::impl::validateUnique());
|
||||
|
||||
if(arguments.cmdOptionExists("--help") || arguments.cmdOptionExists("-h")) {
|
||||
#define HELP_FMT " {} {} | {}"
|
||||
@@ -218,6 +298,21 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
/*
|
||||
{
|
||||
auto a = malloc(10); // 0xa04010
|
||||
auto b = malloc(10); // 0xa04030
|
||||
auto c = malloc(10); // 0xa04050
|
||||
|
||||
free(a);
|
||||
free(b); // To bypass "double free or corruption (fasttop)" check
|
||||
free(a); // Double Free !!
|
||||
|
||||
auto d = malloc(10); // 0xa04010
|
||||
auto e = malloc(10); // 0xa04030
|
||||
auto f = malloc(10); // 0xa04010 - Same as 'd' !
|
||||
}
|
||||
*/
|
||||
/*
|
||||
std::string error;
|
||||
if(!interaction::waitForAttach(error)){
|
||||
cerr << "Rsult: " << error << endl;
|
||||
@@ -232,14 +327,16 @@ int main(int argc, char** argv) {
|
||||
if(true) return 0;
|
||||
*/
|
||||
|
||||
//debugMessage(LOG_GENERAL, "Sizeof ViewEntry {} Sizeof LinkedTreeEntry {} Sizeof shared_ptr<ViewEntry> {} Sizeof ClientChannelView {}", sizeof(ts::ViewEntry), sizeof(ts::TreeView::LinkedTreeEntry), sizeof(shared_ptr<ts::ViewEntry>), sizeof(ts::ClientChannelView));
|
||||
{
|
||||
//http://git.mcgalaxy.de/WolverinDEV/tomcrypt/blob/develop/src/misc/crypt/crypt_inits.c#L40-86
|
||||
std::string descriptors = "LTGE";
|
||||
bool crypt_init = false;
|
||||
for(const auto& c : descriptors)
|
||||
if((crypt_init |= crypt_mp_init(&c)))
|
||||
for(const auto& c : descriptors) {
|
||||
if((crypt_init |= crypt_mp_init(&c))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!crypt_init) {
|
||||
logCritical(LOG_GENERAL, "Could not initialise libtomcrypt mp descriptors!");
|
||||
return 1;
|
||||
@@ -265,7 +362,7 @@ int main(int argc, char** argv) {
|
||||
if(!cfgErrors.empty()){
|
||||
logErrorFmt(true, LOG_GENERAL, "Could not load configuration. Errors: (" + to_string(cfgErrors.size()) + ")");
|
||||
for(const auto& entry : cfgErrors)
|
||||
logError(true, LOG_GENERAL, " - {}", entry);
|
||||
logErrorFmt(true, LOG_GENERAL, " - {}", entry);
|
||||
logErrorFmt(true, LOG_GENERAL, "Stopping server...");
|
||||
goto stopApp;
|
||||
}
|
||||
@@ -289,6 +386,18 @@ int main(int argc, char** argv) {
|
||||
};
|
||||
|
||||
logger::updateLogLevels();
|
||||
logMessage(LOG_GENERAL, "Starting TeaSpeak-Server v{}", build::version()->string(true));
|
||||
|
||||
{
|
||||
debugMessage(LOG_GENERAL, "Initializing RTP library version {}", ts::rtc::version());
|
||||
|
||||
std::string error;
|
||||
if(!ts::rtc::initialize(error)) {
|
||||
logCritical(LOG_GENERAL, "Failed to initialize RTC library: {}", error);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if(ts::config::license_original && ts::config::license_original->data.type != license::LicenseType::DEMO){
|
||||
logMessageFmt(true, LOG_GENERAL, strobf("[]---------------------------------------------------------[]").string());
|
||||
logMessageFmt(true, LOG_GENERAL, strobf(" §aThank you for buying the TeaSpeak-§lPremium-§aSoftware! ").string());
|
||||
@@ -309,7 +418,32 @@ int main(int argc, char** argv) {
|
||||
logMessageFmt(true, LOG_GENERAL, strobf("[]---------------------------------------------------------[]").string());
|
||||
}
|
||||
|
||||
logMessage(LOG_GENERAL, "Starting TeaSpeak-Server v{}", build::version()->string(true));
|
||||
{
|
||||
rlimit rlimit{0, 0};
|
||||
//forum.teaspeak.de/index.php?threads/2570/
|
||||
constexpr auto seek_help_message = "For more help visit the forum and read this thread (https://forum.teaspeak.de/index.php?threads/2570/).";
|
||||
if(getrlimit(RLIMIT_NOFILE, &rlimit) != 0) {
|
||||
//prlimit -n4096 -p pid_of_process
|
||||
logWarningFmt(true, LOG_INSTANCE, "Failed to get open file rlimit ({}). Please ensure its over 16384.", strerror(errno));
|
||||
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
|
||||
} else {
|
||||
const auto original = rlimit.rlim_cur;
|
||||
rlimit.rlim_cur = std::max(rlimit.rlim_cur, std::min(rlimit.rlim_max, (rlim_t) 16384));
|
||||
if(original != rlimit.rlim_cur) {
|
||||
if(setrlimit(RLIMIT_NOFILE, &rlimit) != 0) {
|
||||
logErrorFmt(true, LOG_INSTANCE, "Failed to set open file rlimit to {} ({}). Please ensure its over 16384.", rlimit.rlim_cur, strerror(errno));
|
||||
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
|
||||
goto rlimit_updates;
|
||||
}
|
||||
}
|
||||
if(rlimit.rlim_cur < 16384) {
|
||||
logWarningFmt(true, LOG_INSTANCE, "Open file rlimit is bellow 16384 ({}). Please increase the system file descriptor limits.", rlimit.rlim_cur);
|
||||
logWarningFmt(true, LOG_INSTANCE, seek_help_message);
|
||||
}
|
||||
}
|
||||
rlimit_updates:;
|
||||
}
|
||||
|
||||
logMessage(LOG_GENERAL, "Starting music providers");
|
||||
|
||||
if(terminal::instance()) terminal::instance()->setPrompt("§aStarting server. §7[§aloading music§7]");
|
||||
@@ -352,7 +486,7 @@ int main(int argc, char** argv) {
|
||||
logCriticalFmt(true, LOG_GENERAL, "Could not initialize SQL!");
|
||||
if(errorMessage.find("database is locked") != string::npos) {
|
||||
logCriticalFmt(true, LOG_GENERAL, "----------------------------[ ATTENTION ]----------------------------");
|
||||
logCriticalFmt(true, LOG_GENERAL, "{:^69}", "You're database is already in use!");
|
||||
logCriticalFmt(true, LOG_GENERAL, "{:^69}", "Your database is already in use!");
|
||||
logCriticalFmt(true, LOG_GENERAL, "{:^69}", "Stop the other instance first!");
|
||||
logCriticalFmt(true, LOG_GENERAL, "----------------------------[ ATTENTION ]----------------------------");
|
||||
} else {
|
||||
@@ -371,22 +505,18 @@ int main(int argc, char** argv) {
|
||||
auto password = arguments.cmdOptionExists("-q") ? arguments.get_option("-q") : arguments.get_option("--set_query_password");
|
||||
if(!password.empty()) {
|
||||
logMessageFmt(true, LOG_GENERAL, "Updating server admin query password to \"{}\"", password);
|
||||
auto accounts = serverInstance->getQueryServer()->find_query_accounts_by_unique_id(serverInstance->getInitialServerAdmin()->getUid());
|
||||
bool found = false;
|
||||
for(const auto& account : accounts) {
|
||||
if(account->bound_server != 0) continue;
|
||||
auto account = serverInstance->getQueryServer()->find_query_account_by_name("serveradmin");
|
||||
if(!account) {
|
||||
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! Login does not exists!");
|
||||
} else {
|
||||
if(!serverInstance->getQueryServer()->change_query_password(account, password)) {
|
||||
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! (Internal error)");
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if(!found) {
|
||||
logErrorFmt(true, LOG_GENERAL, "Failed to update server admin query password! Login does not exists!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
terminal::initialize_pipe(arguments.get_option("--pipe-path"));
|
||||
if(terminal::instance()) terminal::instance()->setPrompt("§7> §f");
|
||||
while(mainThreadActive) {
|
||||
usleep(5 * 1000);
|
||||
@@ -394,11 +524,23 @@ int main(int argc, char** argv) {
|
||||
if(terminal::instance()) {
|
||||
if(terminal::instance()->linesAvailable() > 0){
|
||||
while(!(line = terminal::instance()->readLine("§7> §f")).empty())
|
||||
threads::Thread(THREAD_DETACHED, [line](){ terminal::chandler::handleCommand(line); });
|
||||
threads::Thread(THREAD_DETACHED, [line]{
|
||||
terminal::chandler::CommandHandle handle{};
|
||||
handle.command = line;
|
||||
|
||||
if(!terminal::chandler::handleCommand(handle)) {
|
||||
for(const auto& response : handle.response)
|
||||
logErrorFmt(true, LOG_GENERAL, "{}", response);
|
||||
} else {
|
||||
for(const auto& response : handle.response)
|
||||
logMessageFmt(true, LOG_GENERAL, "{}", response);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
terminal::finalize_pipe();
|
||||
|
||||
stopApp:
|
||||
logMessageFmt(true, LOG_GENERAL, "Stopping application");
|
||||
@@ -420,4 +562,43 @@ int main(int argc, char** argv) {
|
||||
terminal::uninstall();
|
||||
mainThreadDone = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Fix for Virtuzzo 7 where sometimes the pthread create fails! */
|
||||
|
||||
typedef int (*pthread_create_t)(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
|
||||
pthread_create_t original_pthread_create{nullptr};
|
||||
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg) {
|
||||
if(!original_pthread_create) {
|
||||
original_pthread_create = (pthread_create_t) dlsym(RTLD_NEXT, "pthread_create");
|
||||
if(!original_pthread_create) {
|
||||
std::cerr << "[CRITICAL] Missing original pthread_create function. Aborting execution!" << std::endl;
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
int result, attempt{0}, sleep{5};
|
||||
while((result = original_pthread_create(thread, attr, start_routine, arg)) != 0 && errno == EAGAIN) {
|
||||
if(attempt > 55) {
|
||||
std::cerr << "[CRITICAL] pthread_create(...) cause EAGAIN for the last 50 attempts (~4.7seconds)! Aborting application execution!" << std::endl;
|
||||
std::abort();
|
||||
} else if(attempt > 5) {
|
||||
/* let some other threads do work */
|
||||
pthread_yield();
|
||||
} else if(attempt == 0) {
|
||||
std::string message{"[CRITICAL] Failed to spawn thread (Resource temporarily unavailable). Trying to recover."};
|
||||
std::cerr << message << std::endl;
|
||||
}
|
||||
|
||||
//std::string message{"[CRITICAL] pthread_create(...) cause EAGAIN! Trying again in " + std::to_string(sleep) + "usec (Attempt: " + std::to_string(attempt) + ")"};
|
||||
//std::cerr << message << std::endl;
|
||||
usleep(sleep);
|
||||
attempt++;
|
||||
sleep = (int) (sleep * 1.25);
|
||||
}
|
||||
if(attempt > 0) {
|
||||
std::string message{"[CRITICAL] Successfully recovered from pthread_create() EAGAIN error. Took " + std::to_string(attempt) + " attempts."};
|
||||
std::cerr << message << std::endl;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
+41
-1
@@ -6,6 +6,40 @@ if [[ -z "${BUILD_PATH}" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm buildVersion.txt
|
||||
cp env/buildVersion.txt .
|
||||
rm -r env
|
||||
mkdir env
|
||||
cd env
|
||||
|
||||
[[ $? -ne 0 ]] && {
|
||||
echo "Failed to create the env"
|
||||
exit 1
|
||||
}
|
||||
cp -rf ../../../git-teaspeak/default_files/{certs,commanddocs,geoloc,resources,*.sh} .
|
||||
[[ $? -ne 0 ]] && {
|
||||
echo "Failed to copy env"
|
||||
exit 1
|
||||
}
|
||||
|
||||
cp -rf ../../../music/bin/providers .
|
||||
[[ $? -ne 0 ]] && {
|
||||
echo "Failed to copy providers"
|
||||
exit 1
|
||||
}
|
||||
#
|
||||
cp ../../environment/TeaSpeakServer .
|
||||
[[ $? -ne 0 ]] && {
|
||||
echo "Failed to copy server"
|
||||
exit 1
|
||||
}
|
||||
cd ..
|
||||
mv buildVersion.txt env/buildVersion.txt
|
||||
[[ $? -ne 0 ]] && {
|
||||
echo "Failed to move the build version back"
|
||||
exit 1
|
||||
}
|
||||
|
||||
./generate_version.sh "${BUILD_PATH}" || {
|
||||
echo "Failed to generate version! ($?)"
|
||||
exit 1
|
||||
@@ -16,6 +50,12 @@ fi
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
./make_symbol.sh || {
|
||||
echo "Failed to generate debug symbols"
|
||||
exit 1
|
||||
}
|
||||
|
||||
./package_server.sh "${BUILD_PATH}" || {
|
||||
echo "Failed to package server! ($?)"
|
||||
exit 1
|
||||
@@ -24,4 +64,4 @@ fi
|
||||
./deploy_build.sh "${BUILD_PATH}" || {
|
||||
echo "Failed to deploy package! ($?)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
../../environment/TeaSpeakServer
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
../../../git-teaspeak/default_files/certs/
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
../../../git-teaspeak/default_files/commanddocs/
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
../../../git-teaspeak/default_files/geoloc/
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
../../../git-teaspeak/default_files/install_music.sh
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
../../../music/bin/providers/
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
../../../git-teaspeak/default_files/resources/
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
../../../git-teaspeak/default_files/tealoop.sh
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
../../../git-teaspeak/default_files/teastart.sh
|
||||
-1
@@ -1 +0,0 @@
|
||||
../../../git-teaspeak/default_files/teastart_autorestart.sh
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
../../../git-teaspeak/default_files/teastart_minimal.sh
|
||||
@@ -3,15 +3,10 @@
|
||||
#Required libraries:
|
||||
# "libssl.so"
|
||||
# "libcrypto.so"
|
||||
# "libDataPipes.so"
|
||||
# "libjemalloc.so.2"
|
||||
# "libsqlite3.so.0"
|
||||
# "libTeaMusic.so"
|
||||
# "libnice.so.10"
|
||||
# "libpcre.so.3" (only for web)
|
||||
# "libgobject-2.0.so.0" (only for web)
|
||||
# "libglib-2.0.so.0" (only for web)
|
||||
# "libffi.so.7"
|
||||
# "libteaspeak_rtc.so"
|
||||
|
||||
[[ -z "${build_os_type}" ]] && { echo "missing build os type"; exit 1; }
|
||||
[[ -z "${build_os_arch}" ]] && { echo "missing build os arch"; exit 1; }
|
||||
@@ -42,17 +37,12 @@ cd libs || { echo "failed to enter lib directory"; exit 1; }
|
||||
library_base=$(realpath ../../../../../libraries)
|
||||
|
||||
# Setting up ssl
|
||||
library_path=$(realpath "${library_base}/boringssl/${build_path}/ssl/libssl.so")
|
||||
cp "$library_path" . || { echo "failed to copy libssl.so"; exit 1; }
|
||||
query_system_link "libssl.so.1.1"
|
||||
cp "${library_path}" . || { echo "failed to copy libssl.so.1.1"; exit 1; }
|
||||
|
||||
# Setting up crypto
|
||||
library_path=$(realpath "${library_base}/boringssl/${build_path}/crypto/libcrypto.so")
|
||||
cp "$library_path" . || { echo "failed to copy libcrypto.so"; exit 1; }
|
||||
|
||||
# Setting up DataPipes
|
||||
library_path=$(realpath "${library_base}/DataPipes/${build_path}/lib/libDataPipes-RTC.so")
|
||||
cp "$library_path" . || { echo "failed to copy libDataPipes-RTC.so"; exit 1; }
|
||||
_dp_path="$library_path"
|
||||
query_system_link "libcrypto.so.1.1"
|
||||
cp "${library_path}" . || { echo "failed to copy libcrypto.so.1.1"; exit 1; }
|
||||
|
||||
# Setting up Sqlite3
|
||||
query_system_link "libsqlite3.so.0"
|
||||
@@ -66,35 +56,9 @@ cp "${library_path}" . || { echo "failed to copy libjemalloc.so.2"; exit 1; }
|
||||
library_path=$(realpath "../../../../MusicBot/libs/libTeaMusic.so")
|
||||
cp "$library_path" . || { echo "failed to copy libTeaMusic.so"; exit 1; }
|
||||
|
||||
if ldd "../../../environment/TeaSpeakServer" | grep -q "libnice.so.10"; then
|
||||
echo "Adding web libraries"
|
||||
|
||||
# Setting up libnice
|
||||
library_path=$(realpath "${library_base}/libnice/${build_os_type}_${build_os_arch}/lib/libnice.so.10")
|
||||
cp "$library_path" libnice.so.10 || { echo "failed to copy libnice.so.10"; exit 1; }
|
||||
|
||||
|
||||
glib_libs=$(realpath "${library_base}//glibc/${build_os_type}_${build_os_arch}/lib/"*"/")
|
||||
cp "$glib_libs/libgobject-2.0.so.0" . || { echo "failed to copy libgobject-2.0.so.0"; exit 1; }
|
||||
cp "$glib_libs/libgmodule-2.0.so.0" . || { echo "failed to copy libgmodule-2.0.so.0"; exit 1; }
|
||||
cp "$glib_libs/libglib-2.0.so.0" . || { echo "failed to copy libglib-2.0.so.0"; exit 1; }
|
||||
cp "$glib_libs/libgio-2.0.so.0" . || { echo "failed to copy libgio-2.0.so.0"; exit 1; }
|
||||
cp "$glib_libs/libffi.so.7" . || { echo "failed to copy libffi.so.7"; exit 1; }
|
||||
|
||||
# "libgobject-2.0.so.0" (only for web)
|
||||
# "libglib-2.0.so.0" (only for web)
|
||||
|
||||
# Setting up libpcre
|
||||
query_system_link "libpcre.so.3" "$_dp_path"
|
||||
cp "${library_path}" . || { echo "failed to copy libpcre.so.3"; exit 1; }
|
||||
fi
|
||||
query_system_link "libteaspeak_rtc.so"
|
||||
cp "${library_path}" . || { echo "failed to copy libteaspeak_rtc.so"; exit 1; }
|
||||
|
||||
# Doing some prostprocessing
|
||||
chmod 755 *
|
||||
for file in *.so*; do
|
||||
echo "Editing rpath for $file"
|
||||
strip -s "$file"
|
||||
patchelf --set-rpath "./libs/:./" "$file"
|
||||
done
|
||||
|
||||
echo "All libraries have been copied successfully"
|
||||
|
||||
@@ -38,4 +38,5 @@ function create_dump() {
|
||||
create_dump "env" "TeaSpeakServer"
|
||||
create_dump "env/providers" "000ProviderFFMpeg.so"
|
||||
create_dump "env/providers" "001ProviderYT.so"
|
||||
create_dump "env/libs/" "libteaspeak_rtc.so"
|
||||
echo "Created dump symbols!"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# shellcheck disable=SC2207
|
||||
BUILD_INFO=($(cat build_version.txt))
|
||||
BUILD_FULL_NAME=${BUILD_INFO[0]}
|
||||
BUILD_NAME=${BUILD_INFO[1]}
|
||||
@@ -22,18 +23,22 @@ echo -e "# Version: ${BUILD_FULL_NAME}
|
||||
|
||||
{\"build_name\": \"${BUILD_FULL_NAME}\", \"build_version\": \"${BUILD_NAME}\", \"build_index\": ${BUILD_VERSION}}" > buildVersion.txt
|
||||
|
||||
#Create a copy and save unstripped
|
||||
cp TeaSpeakServer TeaSpeakServerTmp
|
||||
rm TeaSpeakServer
|
||||
mv TeaSpeakServerTmp TeaSpeakServer
|
||||
|
||||
echo "Stripping symbols"
|
||||
strip -s -p -v TeaSpeakServer || { echo "failed to strip symbols!"; exit 1; }
|
||||
patchelf --set-rpath ./libs/ TeaSpeakServer || { echo "failed to set rpath!"; exit 1; }
|
||||
|
||||
cd libs/ || exit 1
|
||||
for file in *.so*; do
|
||||
echo "Editing rpath for $file"
|
||||
strip --strip-all "$file"
|
||||
patchelf --set-rpath "./libs/:./" "$file"
|
||||
done
|
||||
cd ..
|
||||
|
||||
tar --dereference -cvf - * | gzip -f -9 > "../${BUILD_FILENAME}"
|
||||
[[ $? -ne 0 ]] && { echo "failed to package server"; exit 1; }
|
||||
|
||||
cd ..
|
||||
rm -r finalenv
|
||||
./make_symbol.sh
|
||||
|
||||
echo "Package created (${BUILD_FILENAME})"
|
||||
|
||||
+221
-105
@@ -1,5 +1,6 @@
|
||||
#include <utility>
|
||||
|
||||
#include <log/LogUtils.h>
|
||||
#include "Configuration.h"
|
||||
#include "build.h"
|
||||
#include "../../license/shared/include/license/license.h"
|
||||
@@ -41,19 +42,39 @@ uint16_t ts::config::binding::DefaultFilePort;
|
||||
std::string config::server::DefaultServerVersion;
|
||||
std::string config::server::DefaultServerPlatform;
|
||||
LicenseType config::server::DefaultServerLicense;
|
||||
bool config::server::enable_teamspeak_weblist;
|
||||
bool config::server::strict_ut8_mode;
|
||||
bool config::server::show_invisible_clients_as_online;
|
||||
bool config::server::disable_ip_saving;
|
||||
bool config::server::default_music_bot;
|
||||
/*
|
||||
* namespace limits {
|
||||
extern size_t poke_message_length;
|
||||
extern size_t talk_power_request_message_length;
|
||||
}
|
||||
*/
|
||||
size_t config::server::limits::poke_message_length;
|
||||
size_t config::server::limits::talk_power_request_message_length;
|
||||
size_t config::server::limits::afk_message_length;
|
||||
|
||||
ssize_t config::server::max_virtual_server;
|
||||
bool config::server::badges::allow_badges;
|
||||
bool config::server::badges::allow_overwolf;
|
||||
bool config::server::authentication::name;
|
||||
|
||||
bool config::server::clients::teamspeak;
|
||||
std::string config::server::clients::extra_welcome_message_teamspeak;
|
||||
std::string config::server::clients::teamspeak_not_allowed_message;
|
||||
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teamspeak;
|
||||
|
||||
bool config::server::clients::teaweb;
|
||||
bool config::server::clients::teaspeak;
|
||||
std::string config::server::clients::extra_welcome_message_teaweb;
|
||||
std::string config::server::clients::teaweb_not_allowed_message;
|
||||
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teaweb;
|
||||
|
||||
std::string config::server::clients::extra_welcome_message_teaspeak;
|
||||
config::server::clients::WelcomeMessageType config::server::clients::extra_welcome_message_type_teaspeak;
|
||||
|
||||
bool config::server::clients::ignore_max_clone_permissions;
|
||||
|
||||
uint16_t config::voice::default_voice_port;
|
||||
size_t config::voice::DefaultPuzzlePrecomputeSize;
|
||||
@@ -70,6 +91,7 @@ bool config::voice::allow_session_reinitialize;
|
||||
|
||||
std::string config::query::motd;
|
||||
std::string config::query::newlineCharacter;
|
||||
size_t config::query::max_line_buffer;
|
||||
int config::query::sslMode;
|
||||
std::string config::query::ssl::certFile;
|
||||
std::string config::query::ssl::keyFile;
|
||||
@@ -99,24 +121,23 @@ std::string config::messages::timeout::packet_resend_failed;
|
||||
std::string config::messages::timeout::connection_reinitialized;
|
||||
|
||||
size_t config::threads::ticking;
|
||||
size_t config::threads::voice::execute_limit;
|
||||
size_t config::threads::voice::execute_per_server;
|
||||
size_t config::threads::command_execute;
|
||||
size_t config::threads::network_events;
|
||||
size_t config::threads::voice::events_per_server;
|
||||
size_t config::threads::voice::io_min;
|
||||
size_t config::threads::voice::io_per_server;
|
||||
size_t config::threads::voice::io_limit;
|
||||
bool config::threads::voice::bind_io_thread_to_kernel_thread;
|
||||
size_t config::threads::music::execute_limit;
|
||||
size_t config::threads::music::execute_per_bot;
|
||||
size_t config::threads::web::io_loops;
|
||||
std::string config::messages::teamspeak_permission_editor;
|
||||
|
||||
bool config::web::activated;
|
||||
std::deque<std::tuple<std::string, std::string, std::string>> config::web::ssl::certificates;
|
||||
uint16_t config::web::webrtc_port_max;
|
||||
uint16_t config::web::webrtc_port_min;
|
||||
deque<string> config::web::ice_servers;
|
||||
bool config::web::stun_enabled;
|
||||
std::string config::web::stun_host;
|
||||
uint16_t config::web::stun_port;
|
||||
bool config::web::enable_upnp;
|
||||
bool config::web::udp_enabled;
|
||||
bool config::web::tcp_enabled;
|
||||
|
||||
size_t config::log::vs_size;
|
||||
std::string config::log::path;
|
||||
@@ -323,7 +344,7 @@ void read_bindings(YAML::Node& root, const std::deque<std::shared_ptr<EntryBindi
|
||||
inline string apply_comments(stringstream &in, map<string, deque<string>>& comments);
|
||||
std::deque<std::shared_ptr<EntryBinding>> create_local_bindings(int& version, std::string& license);
|
||||
|
||||
#define CURRENT_CONFIG_VERSION 15
|
||||
#define CURRENT_CONFIG_VERSION 16
|
||||
static std::string _config_path;
|
||||
vector<string> config::parseConfig(const std::string& path) {
|
||||
_config_path = path;
|
||||
@@ -438,6 +459,13 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
nodes_key = "2";
|
||||
}
|
||||
}
|
||||
case 15: {
|
||||
auto nodes_key = resolveNode(config, "web.webrtc.stun.ip").back();
|
||||
if(nodes_key.IsDefined() && nodes_key.as<std::string>() == "127.0.0.1") {
|
||||
nodes_key.reset();
|
||||
resolveNode(config, "web.webrtc.stun.enabled").back() = "1";
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -468,7 +496,6 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
goto license_parsing;
|
||||
}
|
||||
|
||||
/*
|
||||
if(!config::license->isValid()) {
|
||||
if(config::license->data.type == license::LicenseType::INVALID) {
|
||||
errors.emplace_back(strobf("Give license isn't valid!").string());
|
||||
@@ -478,38 +505,18 @@ vector<string> config::parseConfig(const std::string& path) {
|
||||
logErrorFmt(true, LOG_GENERAL, strobf("The given license isn't valid!").string());
|
||||
logErrorFmt(true, LOG_GENERAL, strobf("Falling back to the default license.").string());
|
||||
teaspeak_license = "none";
|
||||
|
||||
goto license_parsing;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
{
|
||||
auto bindings = create_bindings();
|
||||
read_bindings(config, bindings);
|
||||
build_comments(comments, bindings);
|
||||
|
||||
for(const auto& entry : config::web::ice_servers) {
|
||||
auto dp = entry.find(':');
|
||||
if(dp == string::npos) {
|
||||
errors.emplace_back("Invalid ice server entry! Missing port");
|
||||
continue;
|
||||
}
|
||||
auto host = entry.substr(0, dp);
|
||||
auto port = entry.substr(dp + 1);
|
||||
if(port.find_last_not_of("0123456789") != string::npos) {
|
||||
errors.push_back("Invalid ice server entry! Invalid port (" + port + ")");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
stoi(port);
|
||||
} catch(std::exception& ex) {
|
||||
errors.push_back("Invalid ice server entry! Invalid port (" + port + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto currentVersion = strobf("TeaSpeak ").string() + build::version()->string(true);
|
||||
auto currentVersion = config::server::default_version();
|
||||
if(currentVersion != config::server::DefaultServerVersion) {
|
||||
auto ref = config::server::DefaultServerVersion;
|
||||
try {
|
||||
@@ -573,6 +580,14 @@ std::vector<std::string> config::reload() {
|
||||
|
||||
auto bindings = create_bindings();
|
||||
read_bindings(config, bindings, FLAG_RELOADABLE);
|
||||
|
||||
|
||||
const auto& logConfig = logger::currentConfig();
|
||||
if(logConfig) {
|
||||
logConfig->logfileLevel = (spdlog::level::level_enum) ts::config::log::logfileLevel;
|
||||
logConfig->terminalLevel = (spdlog::level::level_enum) ts::config::log::terminalLevel;
|
||||
logger::updateLogLevels();
|
||||
}
|
||||
} catch(const YAML::Exception& ex) {
|
||||
errors.emplace_back("Could not read config: " + ex.msg + " @" + to_string(ex.mark.line) + ":" + to_string(ex.mark.column));
|
||||
return errors;
|
||||
@@ -1014,8 +1029,9 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
{
|
||||
BIND_GROUP(log)
|
||||
{
|
||||
CREATE_BINDING("level", 0);
|
||||
CREATE_BINDING("level", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::log::logfileLevel, spdlog::level::debug, spdlog::level::trace, spdlog::level::off);
|
||||
ADD_NOTE_RELOADABLE();
|
||||
ADD_DESCRIPTION("The log level within the log files");
|
||||
ADD_DESCRIPTION("Available types:");
|
||||
ADD_DESCRIPTION(" 0: Trace");
|
||||
@@ -1027,8 +1043,9 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
ADD_DESCRIPTION(" 6: Off");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("terminal_level", 0);
|
||||
CREATE_BINDING("terminal_level", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::log::terminalLevel, spdlog::level::info, spdlog::level::trace, spdlog::level::off);
|
||||
ADD_NOTE_RELOADABLE();
|
||||
ADD_DESCRIPTION("The log level within the TeaSpeak server terminal");
|
||||
ADD_DESCRIPTION("Available types:");
|
||||
ADD_DESCRIPTION(" 0: Trace");
|
||||
@@ -1106,18 +1123,24 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
{
|
||||
BIND_GROUP(query);
|
||||
{
|
||||
CREATE_BINDING("nl_char", 0);
|
||||
BIND_STRING(config::query::newlineCharacter, "\r\n");
|
||||
CREATE_BINDING("nl_char", FLAG_RELOADABLE);
|
||||
BIND_STRING(config::query::newlineCharacter, "\n");
|
||||
ADD_DESCRIPTION("Change the query newline character");
|
||||
ADD_NOTE("NOTE: TS3 - Compatible bots may require \"\n\r\"");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("motd", 0);
|
||||
BIND_STRING(config::query::motd, "TeaSpeak\r\nWelcome on the TeaSpeak ServerQuery interface.\r\n");
|
||||
CREATE_BINDING("max_line_buffer", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::query::max_line_buffer, 1024 * 1024, 1024 * 8, 1024 * 1024 * 512);
|
||||
ADD_DESCRIPTION("Max number of characters one query command could contain.");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("motd", FLAG_RELOADABLE);
|
||||
BIND_STRING(config::query::motd, "TeaSpeak\nWelcome on the TeaSpeak ServerQuery interface.\n");
|
||||
ADD_DESCRIPTION("The query welcome message");
|
||||
|
||||
ADD_NOTE("If not like TeamSpeak then some applications may not recognize the Query");
|
||||
ADD_NOTE("Default TeamSpeak 3 MOTD:");
|
||||
ADD_NOTE("TS3\r\nWelcome to the TeamSpeak 3 ServerQuery interface, type \"help\" for a list of commands and \"help <command>\" for information on a specific command.\r\n");
|
||||
ADD_NOTE("TS3\n\rWelcome to the TeamSpeak 3 ServerQuery interface, type \"help\" for a list of commands and \"help <command>\" for information on a specific command.\n\r");
|
||||
ADD_NOTE("NOTE: Sometimes you have to append one \r\n more!");
|
||||
}
|
||||
{
|
||||
@@ -1127,7 +1150,7 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
ADD_DESCRIPTION("Available modes:");
|
||||
ADD_DESCRIPTION(" 0: Disabled");
|
||||
ADD_DESCRIPTION(" 1: Enabled (Enforced encryption)");
|
||||
ADD_DESCRIPTION(" 2: Hybrid (Prefer encryption but fallback when it isnt available)");
|
||||
ADD_DESCRIPTION(" 2: Hybrid (Prefer encryption but fallback when it isn't available)");
|
||||
}
|
||||
{
|
||||
BIND_GROUP(ssl);
|
||||
@@ -1154,17 +1177,17 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
"The start point for the server creation still apply.");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("notifymute", 0);
|
||||
CREATE_BINDING("notifymute", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::voice::notifyMuted, false);
|
||||
ADD_DESCRIPTION("Enable/disable the mute notify");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("suppress_myts_warnings", 0);
|
||||
CREATE_BINDING("suppress_myts_warnings", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::voice::suppress_myts_warnings, true);
|
||||
ADD_DESCRIPTION("Suppress the MyTS integration warnings");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("allow_session_reinitialize", 0);
|
||||
CREATE_BINDING("allow_session_reinitialize", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::voice::allow_session_reinitialize, true);
|
||||
ADD_DESCRIPTION("Enable/disable fast session reinitialisation.");
|
||||
ADD_SENSITIVE();
|
||||
@@ -1198,19 +1221,19 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
}
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("connect_limit", 0);
|
||||
CREATE_BINDING("connect_limit", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::voice::connectLimit, 10, 0, 1024);
|
||||
ADD_DESCRIPTION("Maximum amount of join attempts per second.");
|
||||
ADD_NOTE("A value of zero means unlimited");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("client_connect_limit", 0);
|
||||
CREATE_BINDING("client_connect_limit", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::voice::clientConnectLimit, 3, 0, 1024);
|
||||
ADD_DESCRIPTION("Maximum amount of join attempts per second per ip.");
|
||||
ADD_NOTE("A value of zero means unlimited");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("protocol.experimental_31", 0);
|
||||
CREATE_BINDING("protocol.experimental_31", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::experimental_31, false);
|
||||
ADD_DESCRIPTION("Enables the newer and safer protocol based on TeamSpeak's documented standard");
|
||||
ADD_NOTE("An invalid protocol chain could lead clients to calculate a wrong shared secret result");
|
||||
@@ -1255,24 +1278,21 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
BIND_BOOL(config::server::delete_old_bans, true);
|
||||
ADD_DESCRIPTION("Enable/disable the deletion of old bans within the database");
|
||||
}
|
||||
#if 0
|
||||
{
|
||||
CREATE_BINDING("delete_missing_icon_permissions", 0);
|
||||
BIND_BOOL(config::server::delete_missing_icon_permissions, true);
|
||||
ADD_DESCRIPTION("Enable/disable the deletion of invalid icon id permissions");
|
||||
}
|
||||
#endif
|
||||
{
|
||||
CREATE_BINDING("allow_weblist", 0);
|
||||
BIND_BOOL(config::server::enable_teamspeak_weblist, true);
|
||||
ADD_DESCRIPTION("Enable/disable weblist reports globally! (Server setting wount be disabled, they will be just not send)");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("strict_ut8_mode", 0);
|
||||
CREATE_BINDING("strict_ut8_mode", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::strict_ut8_mode, false);
|
||||
ADD_DESCRIPTION("If enabled an error will be throws on invalid UTF-8 characters within the protocol (Query & Client).");
|
||||
ADD_DESCRIPTION("Else the property pair will be dropped silently!");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("show_invisible_clients", 0);
|
||||
CREATE_BINDING("show_invisible_clients", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::show_invisible_clients_as_online, true);
|
||||
ADD_DESCRIPTION("Allow anybody to send text messages to clients which are in invisible channels");
|
||||
}
|
||||
@@ -1282,7 +1302,7 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
ADD_DESCRIPTION("Disable the saving of IP addresses within the database.");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("default_music_bot", 0);
|
||||
CREATE_BINDING("default_music_bot", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::default_music_bot, true);
|
||||
ADD_DESCRIPTION("Add by default a new music bot to each created virtual server.");
|
||||
}
|
||||
@@ -1292,6 +1312,27 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
ADD_DESCRIPTION("Set the limit for maximal virtual servers. -1 means unlimited.");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
BIND_GROUP(limits);
|
||||
|
||||
{
|
||||
CREATE_BINDING("poke_message_length", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::server::limits::poke_message_length, 1024, 1, 262144);
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
|
||||
{
|
||||
CREATE_BINDING("talk_power_request_message_length", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::server::limits::talk_power_request_message_length, 50, 1, 262144);
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
|
||||
{
|
||||
CREATE_BINDING("afk_message_length", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::server::limits::afk_message_length, 50, 1, 262144);
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
}
|
||||
{
|
||||
/*
|
||||
BIND_GROUP(badges);
|
||||
@@ -1317,13 +1358,67 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
}
|
||||
}
|
||||
{
|
||||
using WelcomeMessageType = config::server::clients::WelcomeMessageType;
|
||||
BIND_GROUP(clients);
|
||||
|
||||
/* TeamSpeak */
|
||||
{
|
||||
CREATE_BINDING("teamspeak", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::clients::teamspeak, true);
|
||||
ADD_DESCRIPTION("Allow/disallow the TeamSpeak 3 client to join the server.");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("not_allowed_message", FLAG_RELOADABLE);
|
||||
BIND_STRING(config::server::clients::teamspeak_not_allowed_message, "");
|
||||
ADD_DESCRIPTION("Change the message, displayed when denying the access to the server");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("teamspeak_message", 0); /* No reload flag else we could just manipulate the licensing thing */
|
||||
BIND_STRING(config::server::clients::extra_welcome_message_teamspeak, "");
|
||||
ADD_DESCRIPTION("Add an extra welcome message for TeamSpeak client users");
|
||||
/* Don't make the reloadable else you could just disable the outdated client server join message */
|
||||
//ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("teamspeak_message_type", 0); /* No reload flag else we could just manipulate the licensing thing */
|
||||
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teamspeak, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
|
||||
ADD_DESCRIPTION("The welcome message type modes");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
|
||||
/* Don't make the reloadable else you could just disable the outdated client server join message */
|
||||
//ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
|
||||
/* TeaSpeak */
|
||||
/*
|
||||
{
|
||||
CREATE_BINDING("teaspeak", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::clients::teaspeak, true);
|
||||
ADD_DESCRIPTION("Allow/disallow the TeaSpeak - Client to join the server.");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
*/
|
||||
{
|
||||
CREATE_BINDING("teaspeak_message", FLAG_RELOADABLE);
|
||||
BIND_STRING(config::server::clients::extra_welcome_message_teaspeak, "");
|
||||
ADD_DESCRIPTION("Add an extra welcome message for the TeaSpeak - Client users");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("teaspeak_message_type", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teaspeak, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
|
||||
ADD_DESCRIPTION("The welcome message type modes");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
|
||||
|
||||
/* TeaWeb */
|
||||
{
|
||||
CREATE_BINDING("teaweb", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::clients::teaweb, true);
|
||||
@@ -1331,9 +1426,32 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("teaspeak", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::clients::teaspeak, true);
|
||||
ADD_DESCRIPTION("Allow/disallow the TeaSpeak - Client to join the server.");
|
||||
CREATE_BINDING("not_allowed_message", FLAG_RELOADABLE);
|
||||
BIND_STRING(config::server::clients::teaweb_not_allowed_message, "");
|
||||
ADD_DESCRIPTION("Chaneg the message, displayed when denying the access to the server");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("teaweb_message", FLAG_RELOADABLE);
|
||||
BIND_STRING(config::server::clients::extra_welcome_message_teaweb, "");
|
||||
ADD_DESCRIPTION("Add an extra welcome message for the TeaSpeak - Web client users");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("teaweb_message_type", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::server::clients::extra_welcome_message_type_teaweb, WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MIN, WelcomeMessageType::WELCOME_MESSAGE_TYPE_MAX);
|
||||
ADD_DESCRIPTION("The welcome message type modes");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_NONE) + " - None, do nothing");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_CHAT) + " - Message, sends this message before the server welcome message");
|
||||
ADD_DESCRIPTION(std::to_string(WelcomeMessageType::WELCOME_MESSAGE_TYPE_POKE) + " - Message, pokes the client with the message when he enters the server");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
|
||||
{
|
||||
CREATE_BINDING("ignore_max_clone_permissions", FLAG_RELOADABLE);
|
||||
BIND_BOOL(config::server::clients::ignore_max_clone_permissions, false);
|
||||
ADD_DESCRIPTION("Allows you to disable the permission checks for i_client_max_clones_uid, i_client_max_clones_ip and i_client_max_clones_hwid");
|
||||
ADD_SENSITIVE();
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
}
|
||||
@@ -1346,12 +1464,14 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
ADD_DESCRIPTION("Disable/enable the possibility to connect via the TeaSpeak web client");
|
||||
ADD_NOTE("If you've disabled this feature the TeaClient wound be able to join too.");
|
||||
}
|
||||
/* LibNice has been build without this
|
||||
{
|
||||
CREATE_BINDING("upnp", 0);
|
||||
BIND_BOOL(config::web::enable_upnp, false);
|
||||
ADD_DESCRIPTION("Disable/enable UPNP support");
|
||||
ADD_SENSITIVE();
|
||||
}
|
||||
*/
|
||||
{
|
||||
BIND_GROUP(ssl)
|
||||
{
|
||||
@@ -1412,23 +1532,48 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
}
|
||||
|
||||
{
|
||||
CREATE_BINDING("webrtc.port_min", 0);
|
||||
CREATE_BINDING("webrtc.port_min", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::web::webrtc_port_min, 50000, 0, 65535);
|
||||
ADD_DESCRIPTION("Define the port range within the web client and TeaClient operates in");
|
||||
ADD_DESCRIPTION("A port of zero stands for no limit");
|
||||
ADD_NOTE("These ports must opened to use the voice bridge (Protocol: UDP)");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("webrtc.port_max", 0);
|
||||
CREATE_BINDING("webrtc.port_max", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::web::webrtc_port_max, 56000, 0, 65535);
|
||||
ADD_DESCRIPTION("Define the port range within the web client and TeaClient operates in");
|
||||
ADD_DESCRIPTION("A port of zero stands for no limit");
|
||||
ADD_NOTE("These ports must opened to use the voice bridge (Protocol: UDP)");
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("webrtc.ice", 0);
|
||||
BIND_VECTOR(config::web::ice_servers, {"stun.l.google.com:19302"});
|
||||
ADD_DESCRIPTION("A list of possible offered ice servers");
|
||||
CREATE_BINDING("webrtc.stun.enabled", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::web::stun_enabled, true, false, true);
|
||||
ADD_DESCRIPTION("Whatever to use a STUN server");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("webrtc.stun.host", FLAG_RELOADABLE);
|
||||
BIND_STRING(config::web::stun_host, "stun.l.google.com");
|
||||
ADD_DESCRIPTION("The address of the stun server to use.");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("webrtc.stun.port", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::web::stun_port, 19302, 1, 0xFFFF);
|
||||
ADD_DESCRIPTION("Port of the stun server");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("webrtc.udp", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::web::udp_enabled, true, false, true);
|
||||
ADD_DESCRIPTION("Enable UDP for theweb client");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("webrtc.tcp", FLAG_RELOADABLE);
|
||||
BIND_INTEGRAL(config::web::tcp_enabled, true, false, true);
|
||||
ADD_DESCRIPTION("Enable TCP for theweb client");
|
||||
ADD_NOTE_RELOADABLE();
|
||||
}
|
||||
}
|
||||
{
|
||||
@@ -1677,53 +1822,24 @@ std::deque<std::shared_ptr<EntryBinding>> config::create_bindings() {
|
||||
}
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("web.io_loops", 0);
|
||||
BIND_INTEGRAL(config::threads::web::io_loops, 2, 1, 128);
|
||||
ADD_DESCRIPTION("Thread pool size for the ticking task of a VirtualServer");
|
||||
CREATE_BINDING("command_execute", 0);
|
||||
BIND_INTEGRAL(config::threads::command_execute, 4, 1, 128);
|
||||
ADD_DESCRIPTION("Command executors");
|
||||
ADD_SENSITIVE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("network_events", 0);
|
||||
BIND_INTEGRAL(config::threads::network_events, 4, 1, 128);
|
||||
ADD_DESCRIPTION("Network event loops");
|
||||
ADD_SENSITIVE();
|
||||
}
|
||||
{
|
||||
BIND_GROUP(voice)
|
||||
{
|
||||
CREATE_BINDING("events_per_server", 0);
|
||||
BIND_INTEGRAL(config::threads::voice::events_per_server, 2, 1, 16);
|
||||
ADD_DESCRIPTION("Kernel events per server");
|
||||
ADD_SENSITIVE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("execute_per_server", 0);
|
||||
BIND_INTEGRAL(config::threads::voice::execute_per_server, 2, 1, 128);
|
||||
ADD_DESCRIPTION("Threads per server for command executing");
|
||||
ADD_SENSITIVE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("execute_limit", 0);
|
||||
BIND_INTEGRAL(config::threads::voice::execute_limit, 5, 1, 1024);
|
||||
ADD_DESCRIPTION("Max number of threads for command handling threads within the instance");
|
||||
ADD_SENSITIVE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("io_min", 0);
|
||||
BIND_INTEGRAL(config::threads::voice::io_min, 2, 1, 1024);
|
||||
ADD_DESCRIPTION("Minimum IO threads");
|
||||
ADD_SENSITIVE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("io_per_server", 0);
|
||||
BIND_INTEGRAL(config::threads::voice::io_per_server, 2, 1, 64);
|
||||
ADD_DESCRIPTION("IO Thread increase per server");
|
||||
ADD_SENSITIVE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("io_limit", 0);
|
||||
BIND_INTEGRAL(config::threads::voice::io_limit, 5, 1, 1024);
|
||||
ADD_DESCRIPTION("Max IO threads");
|
||||
ADD_SENSITIVE();
|
||||
}
|
||||
{
|
||||
CREATE_BINDING("bind_io_thread_to_kernel_thread", 0);
|
||||
BIND_BOOL(config::threads::voice::bind_io_thread_to_kernel_thread, false);
|
||||
ADD_DESCRIPTION("Bind each IO thread to one kernel thread to improve socket IO performance");
|
||||
BIND_INTEGRAL(config::threads::voice::events_per_server, 4, 1, 128);
|
||||
ADD_DESCRIPTION("Kernel events per server socket");
|
||||
ADD_DESCRIPTION("This value is upper bound to threads.network_events and should not exceed it.");
|
||||
ADD_SENSITIVE();
|
||||
}
|
||||
}
|
||||
|
||||
+44
-15
@@ -7,8 +7,10 @@
|
||||
#undef byte
|
||||
#endif
|
||||
#include <spdlog/common.h>
|
||||
#include <misc/strobf.h>
|
||||
#include "geo/GeoLocation.h"
|
||||
#include "../../license/shared/include/license/license.h"
|
||||
#include "build.h"
|
||||
|
||||
namespace YAML {
|
||||
class Node;
|
||||
@@ -71,11 +73,16 @@ namespace ts::config {
|
||||
|
||||
extern bool strict_ut8_mode;
|
||||
|
||||
extern bool enable_teamspeak_weblist;
|
||||
extern bool show_invisible_clients_as_online;
|
||||
extern bool disable_ip_saving;
|
||||
extern bool default_music_bot;
|
||||
|
||||
namespace limits {
|
||||
extern size_t poke_message_length;
|
||||
extern size_t talk_power_request_message_length;
|
||||
extern size_t afk_message_length;
|
||||
}
|
||||
|
||||
namespace badges {
|
||||
extern bool allow_overwolf;
|
||||
extern bool allow_badges;
|
||||
@@ -86,12 +93,36 @@ namespace ts::config {
|
||||
}
|
||||
|
||||
namespace clients {
|
||||
enum WelcomeMessageType {
|
||||
WELCOME_MESSAGE_TYPE_MIN,
|
||||
WELCOME_MESSAGE_TYPE_NONE = WELCOME_MESSAGE_TYPE_MIN,
|
||||
WELCOME_MESSAGE_TYPE_CHAT,
|
||||
WELCOME_MESSAGE_TYPE_POKE,
|
||||
WELCOME_MESSAGE_TYPE_MAX
|
||||
};
|
||||
|
||||
extern bool teamspeak;
|
||||
extern bool teaspeak;
|
||||
extern std::string teamspeak_not_allowed_message;
|
||||
extern std::string extra_welcome_message_teamspeak;
|
||||
extern WelcomeMessageType extra_welcome_message_type_teamspeak;
|
||||
|
||||
extern std::string extra_welcome_message_teaspeak;
|
||||
extern WelcomeMessageType extra_welcome_message_type_teaspeak;
|
||||
|
||||
extern bool teaweb;
|
||||
extern std::string teaweb_not_allowed_message;
|
||||
extern std::string extra_welcome_message_teaweb;
|
||||
extern WelcomeMessageType extra_welcome_message_type_teaweb;
|
||||
|
||||
extern bool ignore_max_clone_permissions;
|
||||
}
|
||||
|
||||
extern ssize_t max_virtual_server;
|
||||
|
||||
__attribute__((always_inline)) inline std::string default_version() { return strobf("TeaSpeak ").string() + build::version()->string(true); }
|
||||
__attribute__((always_inline)) inline bool check_server_version_with_license() {
|
||||
return default_version() == DefaultServerVersion || (license->isPremium() && license->isValid());
|
||||
}
|
||||
}
|
||||
|
||||
namespace voice {
|
||||
@@ -125,6 +156,7 @@ namespace ts::config {
|
||||
namespace query {
|
||||
extern std::string motd;
|
||||
extern std::string newlineCharacter;
|
||||
extern size_t max_line_buffer;
|
||||
|
||||
extern int sslMode;
|
||||
namespace ssl {
|
||||
@@ -185,33 +217,30 @@ namespace ts::config {
|
||||
|
||||
extern uint16_t webrtc_port_min;
|
||||
extern uint16_t webrtc_port_max;
|
||||
extern std::deque<std::string> ice_servers;
|
||||
|
||||
extern bool enable_upnp;
|
||||
|
||||
extern bool stun_enabled;
|
||||
extern std::string stun_host;
|
||||
extern uint16_t stun_port;
|
||||
|
||||
extern bool tcp_enabled;
|
||||
extern bool udp_enabled;
|
||||
}
|
||||
|
||||
namespace threads {
|
||||
extern size_t ticking;
|
||||
extern size_t command_execute;
|
||||
extern size_t network_events;
|
||||
|
||||
namespace voice {
|
||||
extern size_t execute_per_server;
|
||||
extern size_t execute_limit;
|
||||
|
||||
extern size_t events_per_server;
|
||||
extern size_t io_min;
|
||||
extern size_t io_per_server;
|
||||
extern size_t io_limit;
|
||||
|
||||
extern bool bind_io_thread_to_kernel_thread;
|
||||
}
|
||||
|
||||
namespace music {
|
||||
extern size_t execute_per_bot;
|
||||
extern size_t execute_limit;
|
||||
}
|
||||
|
||||
namespace web {
|
||||
extern size_t io_loops;
|
||||
}
|
||||
}
|
||||
|
||||
namespace log {
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
//
|
||||
|
||||
#include <misc/memtracker.h>
|
||||
|
||||
#include <utility>
|
||||
#include "ConnectionStatistics.h"
|
||||
#include "VirtualServer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@@ -13,321 +14,101 @@ using namespace ts::server;
|
||||
using namespace ts::stats;
|
||||
using namespace ts::protocol;
|
||||
|
||||
ConnectionStatistics::ConnectionStatistics(const shared_ptr<ConnectionStatistics>& handle, bool properties) : handle(handle) {
|
||||
ConnectionStatistics::ConnectionStatistics(shared_ptr<ConnectionStatistics> handle) : handle(std::move(handle)) {
|
||||
memtrack::allocated<ConnectionStatistics>(this);
|
||||
|
||||
if(properties) {
|
||||
this->properties = make_shared<Properties>(); //TODO load etc?
|
||||
this->properties->register_property_type<property::ConnectionProperties>();
|
||||
}
|
||||
|
||||
/*
|
||||
this->properties->registerProperty("connection_packets_sent_speech", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_sent_speech", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_packets_received_speech", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_received_speech", 0, PROP_STATISTIC);
|
||||
|
||||
this->properties->registerProperty("connection_packets_sent_keepalive", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_sent_keepalive", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_packets_received_keepalive", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_received_keepalive", 0, PROP_STATISTIC);
|
||||
|
||||
this->properties->registerProperty("connection_packets_sent_control", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_sent_control", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_packets_received_control", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_received_control", 0, PROP_STATISTIC);
|
||||
|
||||
this->properties->registerProperty("connection_packets_sent_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_sent_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_packets_received_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bytes_received_total", 0, PROP_STATISTIC);
|
||||
|
||||
this->properties->registerProperty("connection_bandwidth_sent_last_second_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bandwidth_sent_last_minute_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bandwidth_received_last_second_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_bandwidth_received_last_minute_total", 0, PROP_STATISTIC);
|
||||
|
||||
this->properties->registerProperty("connection_filetransfer_bandwidth_sent", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_filetransfer_bandwidth_received", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_filetransfer_bytes_sent_total", 0, PROP_STATISTIC);
|
||||
this->properties->registerProperty("connection_filetransfer_bytes_received_total", 0, PROP_STATISTIC);
|
||||
*/
|
||||
}
|
||||
|
||||
ConnectionStatistics::~ConnectionStatistics() {
|
||||
memtrack::freed<ConnectionStatistics>(this);
|
||||
|
||||
{
|
||||
lock_guard lock(this->history_lock_incoming);
|
||||
|
||||
for(auto entry : this->history_incoming)
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
for(auto entry : this->history_file_incoming)
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
|
||||
this->history_incoming.clear();
|
||||
this->history_file_incoming.clear();
|
||||
}
|
||||
{
|
||||
lock_guard lock(this->history_lock_outgoing);
|
||||
|
||||
for(auto entry : this->history_outgoing)
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
for(auto entry : this->history_file_outgoing)
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
|
||||
this->history_outgoing.clear();
|
||||
this->history_file_outgoing.clear();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Properties> ConnectionStatistics::statistics() {
|
||||
return this->properties;
|
||||
}
|
||||
|
||||
void ConnectionStatistics::logIncomingPacket(const category::value &category, size_t size) {
|
||||
auto info_entry = new StatisticEntry{};
|
||||
info_entry->timestamp = system_clock::now();
|
||||
info_entry->size = uint16_t(size);
|
||||
assert(category >= 0 && category <= 2);
|
||||
this->statistics_second_current.connection_bytes_received[category] += size;
|
||||
this->statistics_second_current.connection_packets_received[category] += 1;
|
||||
|
||||
this->_log_incoming_packet(info_entry, category);
|
||||
}
|
||||
|
||||
void ConnectionStatistics::_log_incoming_packet(ts::stats::StatisticEntry *info_entry, int8_t index) {
|
||||
if(index >= 0 && index <= 3) {
|
||||
this->connection_packets_received[index] ++;
|
||||
this->connection_bytes_received[index] += info_entry->size;
|
||||
}
|
||||
this->connection_packets_received[0] ++;
|
||||
this->connection_bytes_received[0] += info_entry->size;
|
||||
|
||||
if(this->_measure_bandwidths) {
|
||||
auto lock_count = info_entry->use_count++;
|
||||
assert(lock_count >= 0);
|
||||
(void) lock_count;
|
||||
|
||||
lock_guard lock(this->history_lock_incoming);
|
||||
this->history_incoming.push_back(info_entry);
|
||||
}
|
||||
if(this->handle)
|
||||
this->handle->_log_incoming_packet(info_entry, index);
|
||||
this->handle->logIncomingPacket(category, size);
|
||||
}
|
||||
|
||||
void ConnectionStatistics::logOutgoingPacket(const category::value &category, size_t size) {
|
||||
auto info_entry = new StatisticEntry{};
|
||||
info_entry->timestamp = system_clock::now();
|
||||
info_entry->size = uint16_t(size);
|
||||
assert(category >= 0 && category <= 2);
|
||||
this->statistics_second_current.connection_bytes_sent[category] += size;
|
||||
this->statistics_second_current.connection_packets_sent[category] += 1;
|
||||
|
||||
this->_log_outgoing_packet(info_entry, category);
|
||||
}
|
||||
|
||||
|
||||
void ConnectionStatistics::_log_outgoing_packet(ts::stats::StatisticEntry *info_entry, int8_t index) {
|
||||
if(index >= 0 && index <= 3) {
|
||||
this->connection_packets_sent[index] ++;
|
||||
this->connection_bytes_sent[index] += info_entry->size;
|
||||
if(this->handle) {
|
||||
this->handle->logOutgoingPacket(category, size);
|
||||
}
|
||||
this->connection_packets_sent[0] ++;
|
||||
this->connection_bytes_sent[0] += info_entry->size;
|
||||
|
||||
if(this->_measure_bandwidths) {
|
||||
auto lock_count = info_entry->use_count++;
|
||||
assert(lock_count >= 0);
|
||||
(void) lock_count;
|
||||
|
||||
lock_guard lock(this->history_lock_outgoing);
|
||||
this->history_outgoing.push_back(info_entry);
|
||||
}
|
||||
if(this->handle)
|
||||
this->handle->_log_outgoing_packet(info_entry, index);
|
||||
}
|
||||
|
||||
/* file transfer */
|
||||
void ConnectionStatistics::logFileTransferIn(uint64_t bytes) {
|
||||
auto info_entry = new StatisticEntry{};
|
||||
info_entry->timestamp = system_clock::now();
|
||||
info_entry->size = bytes;
|
||||
|
||||
this->_log_incoming_file_packet(info_entry);
|
||||
}
|
||||
|
||||
void ConnectionStatistics::_log_incoming_file_packet(ts::stats::StatisticEntry *info_entry) {
|
||||
this->file_bytes_received += info_entry->size;
|
||||
|
||||
if(this->_measure_bandwidths) {
|
||||
auto lock_count = info_entry->use_count++;
|
||||
assert(lock_count >= 0);
|
||||
(void) lock_count;
|
||||
|
||||
lock_guard lock(this->history_lock_incoming);
|
||||
this->history_file_incoming.push_back(info_entry);
|
||||
}
|
||||
void ConnectionStatistics::logFileTransferIn(uint32_t bytes) {
|
||||
this->statistics_second_current.file_bytes_received += bytes;
|
||||
this->file_bytes_received += bytes;
|
||||
|
||||
if(this->handle)
|
||||
this->handle->_log_incoming_file_packet(info_entry);
|
||||
this->handle->logFileTransferIn(bytes);
|
||||
}
|
||||
|
||||
void ConnectionStatistics::logFileTransferOut(uint64_t bytes) {
|
||||
auto info_entry = new StatisticEntry{};
|
||||
info_entry->timestamp = system_clock::now();
|
||||
info_entry->size = bytes;
|
||||
|
||||
this->_log_outgoing_file_packet(info_entry);
|
||||
}
|
||||
|
||||
void ConnectionStatistics::_log_outgoing_file_packet(ts::stats::StatisticEntry *info_entry) {
|
||||
this->file_bytes_sent += info_entry->size;
|
||||
|
||||
if(this->_measure_bandwidths) {
|
||||
auto lock_count = info_entry->use_count++;
|
||||
assert(lock_count >= 0);
|
||||
(void) lock_count;
|
||||
|
||||
lock_guard lock(this->history_lock_outgoing);
|
||||
this->history_file_outgoing.push_back(info_entry);
|
||||
}
|
||||
void ConnectionStatistics::logFileTransferOut(uint32_t bytes) {
|
||||
this->statistics_second_current.file_bytes_sent += bytes;
|
||||
this->file_bytes_sent += bytes;
|
||||
|
||||
if(this->handle)
|
||||
this->handle->_log_outgoing_file_packet(info_entry);
|
||||
this->handle->logFileTransferOut(bytes);
|
||||
}
|
||||
|
||||
void ConnectionStatistics::tick() {
|
||||
StatisticEntry* entry;
|
||||
{
|
||||
auto timeout_min = system_clock::now() - minutes(1);
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto time_difference = this->last_second_tick.time_since_epoch().count() > 0 ? now - this->last_second_tick : std::chrono::seconds{1};
|
||||
if(time_difference >= std::chrono::seconds{1}) {
|
||||
BandwidthEntry<uint32_t> current{};
|
||||
current.atomic_exchange(this->statistics_second_current);
|
||||
|
||||
lock_guard lock(this->history_lock_incoming);
|
||||
auto period_ms = std::chrono::floor<std::chrono::milliseconds>(time_difference).count();
|
||||
auto current_normalized = current.mul<long double>(1000.0 / period_ms);
|
||||
|
||||
while(!this->history_incoming.empty() && (entry = this->history_incoming[0])->timestamp < timeout_min) {
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
this->statistics_second = this->statistics_second.mul<long double>(.2) + current_normalized.mul<long double>(.8);
|
||||
this->total_statistics += current;
|
||||
|
||||
this->history_incoming.pop_front();
|
||||
auto current_second = std::chrono::floor<std::chrono::seconds>(now.time_since_epoch()).count();
|
||||
if(statistics_minute_offset == 0) {
|
||||
statistics_minute_offset = current_second;
|
||||
}
|
||||
|
||||
while(!this->history_file_incoming.empty() && (entry = this->history_file_incoming[0])->timestamp < timeout_min) {
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
|
||||
this->history_file_incoming.pop_front();
|
||||
/* fill all "lost" with the current bandwidth as well */
|
||||
while(statistics_minute_offset <= current_second) {
|
||||
this->statistics_minute[statistics_minute_offset++ % this->statistics_minute.size()] = current_normalized;
|
||||
}
|
||||
}
|
||||
{
|
||||
auto timeout_min = system_clock::now() - minutes(1);
|
||||
lock_guard lock(this->history_lock_outgoing);
|
||||
|
||||
while(!this->history_outgoing.empty() && (entry = this->history_outgoing[0])->timestamp < timeout_min) {
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
|
||||
this->history_outgoing.pop_front();
|
||||
}
|
||||
|
||||
while(!this->history_file_outgoing.empty() && (entry = this->history_file_outgoing[0])->timestamp < timeout_min) {
|
||||
if(entry->use_count.fetch_sub(1) == 1)
|
||||
delete entry;
|
||||
|
||||
this->history_file_outgoing.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
if(this->properties) {
|
||||
auto& _properties = *this->properties;
|
||||
#define M(type, index) \
|
||||
_properties[property::CONNECTION_BYTES_SENT_ ##type] = (uint64_t) this->connection_bytes_sent[index]; \
|
||||
_properties[property::CONNECTION_PACKETS_SENT_ ##type] = (uint64_t) this->connection_packets_sent[index]; \
|
||||
_properties[property::CONNECTION_BYTES_RECEIVED_ ##type] = (uint64_t) this->connection_bytes_received[index]; \
|
||||
_properties[property::CONNECTION_PACKETS_RECEIVED_ ##type] = (uint64_t) this->connection_packets_received[index]; \
|
||||
|
||||
M(TOTAL, 0);
|
||||
M(CONTROL, 1);
|
||||
M(KEEPALIVE, 2);
|
||||
M(SPEECH, 3);
|
||||
|
||||
_properties[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL] = (uint64_t) this->file_bytes_received;
|
||||
_properties[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL] = (uint64_t) this->file_bytes_sent;
|
||||
|
||||
_properties[property::CONNECTION_FILETRANSFER_BYTES_RECEIVED_TOTAL] = (uint64_t) this->file_bytes_received;
|
||||
_properties[property::CONNECTION_FILETRANSFER_BYTES_SENT_TOTAL] = (uint64_t) this->file_bytes_sent;
|
||||
this->last_second_tick = now;
|
||||
}
|
||||
}
|
||||
|
||||
DataSummery ConnectionStatistics::dataReport() {
|
||||
DataSummery report{};
|
||||
auto minTimeout = system_clock::now() - seconds(1);
|
||||
|
||||
|
||||
{
|
||||
lock_guard lock(this->history_lock_incoming);
|
||||
|
||||
for(const auto& elm : this->history_incoming){
|
||||
if(elm->timestamp >= minTimeout) {
|
||||
report.recv_second += elm->size;
|
||||
}
|
||||
|
||||
report.recv_minute += elm->size;
|
||||
}
|
||||
|
||||
for(const auto& elm : this->history_file_incoming) {
|
||||
report.file_recv += elm->size;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
lock_guard lock(this->history_lock_outgoing);
|
||||
|
||||
for(const auto& elm : this->history_outgoing){
|
||||
if(elm->timestamp >= minTimeout) {
|
||||
report.send_second += elm->size;
|
||||
}
|
||||
|
||||
report.send_minute += elm->size;
|
||||
}
|
||||
|
||||
for(const auto& elm : this->history_file_outgoing) {
|
||||
report.file_send += elm->size;
|
||||
}
|
||||
}
|
||||
|
||||
report.recv_minute /= 60;
|
||||
report.send_minute /= 60;
|
||||
return report;
|
||||
BandwidthEntry<uint32_t> ConnectionStatistics::minute_stats() const {
|
||||
BandwidthEntry<uint32_t> result{};
|
||||
for(const auto& second : this->statistics_minute)
|
||||
result += second;
|
||||
return result.mul<uint32_t>(1. / (double) this->statistics_minute.size());
|
||||
}
|
||||
|
||||
FullReport ConnectionStatistics::full_report() {
|
||||
FullReport report{};
|
||||
FileTransferStatistics ConnectionStatistics::file_stats() {
|
||||
FileTransferStatistics result{};
|
||||
|
||||
for(size_t index = 0 ; index < 4; index++) {
|
||||
report.connection_bytes_sent[index] = (uint64_t) this->connection_bytes_sent[index];
|
||||
report.connection_packets_sent[index] = (uint64_t) this->connection_packets_sent[index];
|
||||
report.connection_bytes_received[index] = (uint64_t) this->connection_bytes_received[index];
|
||||
report.connection_packets_received[index] = (uint64_t) this->connection_packets_received[index];
|
||||
}
|
||||
result.bytes_received = this->file_bytes_received;
|
||||
result.bytes_sent = this->file_bytes_sent;
|
||||
|
||||
report.file_bytes_sent = this->file_bytes_sent;
|
||||
report.file_bytes_received = this->file_bytes_received;
|
||||
|
||||
return report;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::pair<uint64_t, uint64_t> ConnectionStatistics::mark_file_bytes() {
|
||||
std::pair<uint64_t, uint64_t> result;
|
||||
|
||||
{
|
||||
lock_guard lock(this->history_lock_incoming);
|
||||
if(this->mark_file_bytes_received < this->file_bytes_received)
|
||||
result.second = this->file_bytes_received - this->mark_file_bytes_received;
|
||||
this->mark_file_bytes_received = (uint64_t) this->file_bytes_received;
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
lock_guard lock(this->history_lock_outgoing);
|
||||
|
||||
if(this->mark_file_bytes_sent < this->file_bytes_sent)
|
||||
result.first = this->file_bytes_sent - this->mark_file_bytes_sent;
|
||||
this->mark_file_bytes_sent = (uint64_t) this->file_bytes_sent;
|
||||
|
||||
@@ -11,41 +11,97 @@ namespace ts {
|
||||
}
|
||||
|
||||
namespace stats {
|
||||
struct StatisticEntry {
|
||||
std::atomic<int8_t> use_count{0};
|
||||
std::chrono::time_point<std::chrono::system_clock> timestamp;
|
||||
uint16_t size = 0;
|
||||
template <typename value_t>
|
||||
struct BandwidthEntry {
|
||||
std::array<value_t, 3> connection_packets_sent{};
|
||||
std::array<value_t, 3> connection_bytes_sent{};
|
||||
std::array<value_t, 3> connection_packets_received{};
|
||||
std::array<value_t, 3> connection_bytes_received{};
|
||||
|
||||
value_t file_bytes_sent{0};
|
||||
value_t file_bytes_received{0};
|
||||
|
||||
template <typename other_type>
|
||||
inline BandwidthEntry& operator=(const BandwidthEntry<other_type>& other) {
|
||||
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
|
||||
this->connection_packets_sent[index] = other.connection_packets_sent[index];
|
||||
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
|
||||
this->connection_bytes_sent[index] = other.connection_bytes_sent[index];
|
||||
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
|
||||
this->connection_packets_received[index] = other.connection_packets_received[index];
|
||||
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
|
||||
this->connection_bytes_received[index] = other.connection_bytes_received[index];
|
||||
|
||||
this->file_bytes_sent = other.file_bytes_sent;
|
||||
this->file_bytes_received = other.file_bytes_received;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename target_t>
|
||||
inline BandwidthEntry<target_t> mul(double factor) const {
|
||||
BandwidthEntry<target_t> result{};
|
||||
result = *this;
|
||||
for(auto& val : result.connection_packets_sent) val *= factor;
|
||||
for(auto& val : result.connection_bytes_sent) val *= factor;
|
||||
for(auto& val : result.connection_packets_received) val *= factor;
|
||||
for(auto& val : result.connection_bytes_received) val *= factor;
|
||||
|
||||
result.file_bytes_sent *= factor;
|
||||
result.file_bytes_received *= factor;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename other_type>
|
||||
inline BandwidthEntry& operator+=(const BandwidthEntry<other_type>& other) {
|
||||
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
|
||||
this->connection_packets_sent[index] += other.connection_packets_sent[index];
|
||||
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
|
||||
this->connection_bytes_sent[index] += other.connection_bytes_sent[index];
|
||||
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
|
||||
this->connection_packets_received[index] += other.connection_packets_received[index];
|
||||
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
|
||||
this->connection_bytes_received[index] += other.connection_bytes_received[index];
|
||||
|
||||
this->file_bytes_sent += other.file_bytes_sent;
|
||||
this->file_bytes_received += other.file_bytes_received;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename other_type>
|
||||
inline BandwidthEntry operator+(const BandwidthEntry<other_type>& other) {
|
||||
return BandwidthEntry{*this} += other;
|
||||
}
|
||||
|
||||
template <typename atomic_t>
|
||||
inline void atomic_exchange(BandwidthEntry<std::atomic<atomic_t>>& source) {
|
||||
for(size_t index{0}; index < this->connection_packets_sent.size(); index++)
|
||||
this->connection_packets_sent[index] = source.connection_packets_sent[index].exchange(0);
|
||||
for(size_t index{0}; index < this->connection_bytes_sent.size(); index++)
|
||||
this->connection_bytes_sent[index] = source.connection_bytes_sent[index].exchange(0);
|
||||
for(size_t index{0}; index < this->connection_packets_received.size(); index++)
|
||||
this->connection_packets_received[index] = source.connection_packets_received[index].exchange(0);
|
||||
for(size_t index{0}; index < this->connection_bytes_received.size(); index++)
|
||||
this->connection_bytes_received[index] = source.connection_bytes_received[index].exchange(0);
|
||||
|
||||
this->file_bytes_sent = source.file_bytes_sent.exchange(0);
|
||||
this->file_bytes_received = source.file_bytes_received.exchange(0);
|
||||
}
|
||||
};
|
||||
|
||||
struct DataSummery {
|
||||
uint32_t send_minute;
|
||||
uint32_t send_second;
|
||||
|
||||
uint32_t recv_minute;
|
||||
uint32_t recv_second;
|
||||
|
||||
uint32_t file_recv;
|
||||
uint32_t file_send;
|
||||
};
|
||||
|
||||
struct FullReport {
|
||||
uint64_t connection_packets_sent[4]{0, 0, 0, 0};
|
||||
uint64_t connection_bytes_sent[4]{0, 0, 0, 0};
|
||||
uint64_t connection_packets_received[4]{0, 0, 0, 0};
|
||||
uint64_t connection_bytes_received[4]{0, 0, 0, 0};
|
||||
|
||||
uint64_t file_bytes_sent = 0;
|
||||
uint64_t file_bytes_received = 0;
|
||||
struct FileTransferStatistics {
|
||||
uint64_t bytes_received{0};
|
||||
uint64_t bytes_sent{0};
|
||||
};
|
||||
|
||||
class ConnectionStatistics {
|
||||
public:
|
||||
struct category {
|
||||
/* Only three categories. Map unknown to category 0 */
|
||||
enum value {
|
||||
COMMAND,
|
||||
ACK,
|
||||
KEEP_ALIVE,
|
||||
VOICE,
|
||||
UNKNOWN
|
||||
UNKNOWN = COMMAND
|
||||
};
|
||||
|
||||
constexpr static std::array<category::value, 16> lookup_table{
|
||||
@@ -53,10 +109,10 @@ namespace ts {
|
||||
VOICE, /* VoiceWhisper */
|
||||
COMMAND, /* Command */
|
||||
COMMAND, /* CommandLow */
|
||||
ACK, /* Ping */
|
||||
ACK, /* Pong */
|
||||
ACK, /* Ack */
|
||||
ACK, /* AckLow */
|
||||
KEEP_ALIVE, /* Ping */
|
||||
KEEP_ALIVE, /* Pong */
|
||||
COMMAND, /* Ack */
|
||||
COMMAND, /* AckLow */
|
||||
COMMAND, /* */
|
||||
|
||||
UNKNOWN,
|
||||
@@ -71,63 +127,39 @@ namespace ts {
|
||||
inline static category::value from_type(uint8_t type){
|
||||
return lookup_table[type & 0xFU];
|
||||
}
|
||||
|
||||
inline static category::value from_type(const protocol::PacketTypeInfo& type){
|
||||
return from_type(type.type());
|
||||
}
|
||||
};
|
||||
explicit ConnectionStatistics(const std::shared_ptr<ConnectionStatistics>& /* root */, bool /* spawn properties */);
|
||||
explicit ConnectionStatistics(std::shared_ptr<ConnectionStatistics> /* root */);
|
||||
~ConnectionStatistics();
|
||||
|
||||
std::shared_ptr<Properties> statistics();
|
||||
|
||||
inline void logIncomingPacket(const protocol::ClientPacket& packet) { this->logIncomingPacket(category::from_type(packet.type()), packet.length()); }
|
||||
void logIncomingPacket(const category::value& /* category */, size_t /* length */);
|
||||
inline void logOutgoingPacket(const protocol::ServerPacket& packet) { this->logOutgoingPacket(category::from_type(packet.type()), packet.length()); }
|
||||
void logOutgoingPacket(const category::value& /* category */, size_t /* length */);
|
||||
void logFileTransferIn(uint64_t);
|
||||
void logFileTransferOut(uint64_t);
|
||||
void logFileTransferIn(uint32_t);
|
||||
void logFileTransferOut(uint32_t);
|
||||
|
||||
void tick();
|
||||
|
||||
DataSummery dataReport();
|
||||
FullReport full_report();
|
||||
[[nodiscard]] inline const BandwidthEntry<uint64_t>& total_stats() const { return this->total_statistics; }
|
||||
[[nodiscard]] inline BandwidthEntry<uint32_t> second_stats() const { return this->statistics_second; }
|
||||
[[nodiscard]] BandwidthEntry<uint32_t> minute_stats() const;
|
||||
|
||||
FileTransferStatistics file_stats();
|
||||
std::pair<uint64_t, uint64_t> mark_file_bytes();
|
||||
|
||||
inline bool measure_bandwidths() { return this->_measure_bandwidths; }
|
||||
void measure_bandwidths(bool flag) { this->_measure_bandwidths = flag; }
|
||||
|
||||
inline bool has_properties() { return !!this->properties; }
|
||||
private:
|
||||
bool _measure_bandwidths = true;
|
||||
std::shared_ptr<ConnectionStatistics> handle;
|
||||
std::shared_ptr<Properties> properties;
|
||||
|
||||
BandwidthEntry<uint64_t> total_statistics{};
|
||||
|
||||
std::atomic<uint64_t> connection_packets_sent[4]{0, 0, 0, 0};
|
||||
std::atomic<uint64_t> connection_bytes_sent[4]{0, 0, 0, 0};
|
||||
std::atomic<uint64_t> connection_packets_received[4]{0, 0, 0, 0};
|
||||
std::atomic<uint64_t> connection_bytes_received[4]{0, 0, 0, 0};
|
||||
BandwidthEntry<std::atomic<uint64_t>> statistics_second_current{};
|
||||
BandwidthEntry<uint32_t> statistics_second{}; /* will be updated every second by the stats from the "current_second" */
|
||||
std::array<BandwidthEntry<uint32_t>, 60> statistics_minute{};
|
||||
uint32_t statistics_minute_offset{0}; /* pointing to the upcoming minute */
|
||||
std::chrono::system_clock::time_point last_second_tick{};
|
||||
|
||||
std::atomic<uint64_t> file_bytes_sent = 0;
|
||||
std::atomic<uint64_t> file_bytes_received = 0;
|
||||
std::atomic<uint64_t> file_bytes_sent{0};
|
||||
std::atomic<uint64_t> file_bytes_received{0};
|
||||
|
||||
std::atomic<uint64_t> mark_file_bytes_sent = 0;
|
||||
std::atomic<uint64_t> mark_file_bytes_received = 0;
|
||||
|
||||
spin_lock history_lock_outgoing;
|
||||
spin_lock history_lock_incoming;
|
||||
std::deque<StatisticEntry*> history_file_incoming{};
|
||||
std::deque<StatisticEntry*> history_file_outgoing{};
|
||||
std::deque<StatisticEntry*> history_incoming{};
|
||||
std::deque<StatisticEntry*> history_outgoing{};
|
||||
|
||||
void _log_incoming_packet(StatisticEntry */* statistics */, int8_t /* type index */);
|
||||
void _log_outgoing_packet(StatisticEntry* /* statistics */, int8_t /* type index */);
|
||||
|
||||
void _log_incoming_file_packet(StatisticEntry */* statistics */);
|
||||
void _log_outgoing_file_packet(StatisticEntry* /* statistics */);
|
||||
uint64_t mark_file_bytes_sent{0};
|
||||
uint64_t mark_file_bytes_received{0};
|
||||
};
|
||||
}
|
||||
}
|
||||
+419
-259
@@ -13,156 +13,168 @@ using namespace ts::permission;
|
||||
|
||||
//#define DISABLE_CACHING
|
||||
|
||||
struct ts::server::CachedPermissionManager {
|
||||
ServerId server_id{0};
|
||||
ClientDbId client_database_id{0};
|
||||
std::weak_ptr<permission::v2::PermissionManager> instance{};
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> instance_ref{}; /* reference to the current instance, will be refreshed every time the instance gets accessed */
|
||||
std::chrono::time_point<std::chrono::system_clock> last_access{};
|
||||
};
|
||||
|
||||
|
||||
struct ts::server::StartupCacheEntry {
|
||||
ServerId sid{0};
|
||||
|
||||
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions{};
|
||||
std::deque<std::unique_ptr<StartupPropertyEntry>> properties{};
|
||||
};
|
||||
|
||||
DatabaseHelper::DatabaseHelper(sql::SqlManager* srv) : sql(srv) {}
|
||||
DatabaseHelper::~DatabaseHelper() {
|
||||
for(const auto& elm : cachedPermissionManagers)
|
||||
delete elm;
|
||||
cachedPermissionManagers.clear();
|
||||
this->cached_permission_managers.clear();
|
||||
}
|
||||
|
||||
void DatabaseHelper::tick() {
|
||||
auto cache_timeout = std::chrono::system_clock::now() - std::chrono::minutes{10};
|
||||
{
|
||||
threads::MutexLock l(this->permManagerLock);
|
||||
auto cpy = this->cachedPermissionManagers;
|
||||
for(const auto& mgr : cpy){
|
||||
//if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5)) //TODO remove instand delete!
|
||||
mgr->ownLock.reset();
|
||||
if(mgr->manager.expired()){
|
||||
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), mgr));
|
||||
delete mgr;
|
||||
}
|
||||
}
|
||||
}
|
||||
std::lock_guard cp_lock{this->cached_permission_manager_lock};
|
||||
|
||||
{
|
||||
threads::MutexLock l(this->propsLock);
|
||||
auto pcpy = this->cachedProperties;
|
||||
for(const auto& mgr : pcpy){
|
||||
if(mgr->ownLock && system_clock::now() - mgr->lastAccess > minutes(5))
|
||||
mgr->ownLock.reset();
|
||||
if(mgr->properties.expired()) {
|
||||
this->cachedProperties.erase(std::find(this->cachedProperties.begin(), this->cachedProperties.end(), mgr));
|
||||
delete mgr;
|
||||
}
|
||||
}
|
||||
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const std::unique_ptr<CachedPermissionManager>& manager) {
|
||||
if(manager->last_access < cache_timeout)
|
||||
manager->instance_ref = nullptr;
|
||||
|
||||
if(manager->instance.expired())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}), this->cached_permission_managers.end());
|
||||
}
|
||||
}
|
||||
|
||||
int collectData(deque<shared_ptr<ClientDatabaseInfo>>* list, int length, char** values, char** columns){
|
||||
shared_ptr<ClientDatabaseInfo> entry = std::make_shared<ClientDatabaseInfo>();
|
||||
constexpr static std::string_view kSqlBase{"SELECT `client_unique_id`, `client_database_id`, `client_nickname`, `client_created`, `client_last_connected`, `client_ip`, `client_total_connections` FROM `clients_server`"};
|
||||
inline std::deque<std::shared_ptr<ClientDatabaseInfo>> query_database_client_info(sql::SqlManager* sql_manager, ServerId server_id, const std::string& query, const std::vector<variable>& variables) {
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> result{};
|
||||
|
||||
for(int index = 0; index < length; index++)
|
||||
if(strcmp(columns[index], "cldbid") == 0)
|
||||
entry->cldbid = static_cast<ClientDbId>(stol(values[index]));
|
||||
else if(strcmp(columns[index], "clientUid") == 0)
|
||||
entry->uniqueId = values[index];
|
||||
else if(strcmp(columns[index], "firstConnect") == 0)
|
||||
entry->created = time_point<system_clock>() + seconds(stoll(values[index]));
|
||||
else if(strcmp(columns[index], "lastConnect") == 0)
|
||||
entry->lastjoin = time_point<system_clock>() + seconds(stoll(values[index]));
|
||||
else if(strcmp(columns[index], "connections") == 0)
|
||||
entry->connections = static_cast<uint32_t>(stoi(values[index]));
|
||||
else if(strcmp(columns[index], "lastName") == 0)
|
||||
entry->lastName = values[index];
|
||||
else if(strcmp(columns[index], "serverId") == 0);
|
||||
else logError(LOG_GENERAL, "Invalid db key for manager data. Key: {}", columns[index]);
|
||||
sql::command command{sql_manager, query};
|
||||
for(const auto& variable : variables)
|
||||
command.value(variable);
|
||||
auto sql_result = command.query([&](int length, std::string* values, std::string* names) {
|
||||
auto entry = std::make_shared<ClientDatabaseInfo>();
|
||||
|
||||
list->push_back(entry);
|
||||
return 0;
|
||||
}
|
||||
auto index{0};
|
||||
try {
|
||||
assert(names[index] == "client_unique_id");
|
||||
entry->client_unique_id = values[index++];
|
||||
|
||||
#define MAX_QUERY 32
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfo(const std::shared_ptr<VirtualServer>& server, const std::deque<ClientDbId>& list) {
|
||||
if(list.empty()) return {};
|
||||
assert(names[index] == "client_database_id");
|
||||
entry->client_database_id = std::stoull(values[index++]);
|
||||
|
||||
deque<shared_ptr<ClientDatabaseInfo>> result;
|
||||
assert(names[index] == "client_nickname");
|
||||
entry->client_nickname = values[index++];
|
||||
|
||||
if(list.size() <= MAX_QUERY){
|
||||
std::string query = "SELECT * FROM `clients` WHERE `serverId` = :serverId AND (";
|
||||
for(auto elm : list)
|
||||
query += " `cldbid` = " + to_string(elm) + " OR";
|
||||
query = query.substr(0, query.length() - 3) + ")";
|
||||
logTrace(server ? server->getServerId() : 0, "[SQL] queryDatabseInfo(...) -> {}", query);
|
||||
auto state = sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}).query(std::function<decltype(collectData)>(collectData), &result);
|
||||
auto pf = LOG_SQL_CMD;
|
||||
pf(state);
|
||||
if(!state) return {};
|
||||
} else {
|
||||
std::deque<ClientDbId> sub;
|
||||
do {
|
||||
sub.insert(sub.begin(), list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
|
||||
list.erase(list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
|
||||
assert(names[index] == "client_created");
|
||||
entry->client_created = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])};
|
||||
|
||||
auto res = this->queryDatabaseInfo(server, sub);
|
||||
result.insert(result.end(), res.begin(), res.end());
|
||||
sub.clear();
|
||||
} while(!list.empty());
|
||||
assert(names[index] == "client_last_connected");
|
||||
entry->client_last_connected = std::chrono::system_clock::time_point{} + std::chrono::seconds{std::stoull(values[index++])};
|
||||
|
||||
assert(names[index] == "client_ip");
|
||||
entry->client_ip = values[index++];
|
||||
|
||||
assert(names[index] == "client_total_connections");
|
||||
entry->client_total_connections = std::stoull(values[index++]);
|
||||
|
||||
assert(index == length);
|
||||
} catch (std::exception& ex) {
|
||||
logError(server_id, "Failed to parse client base properties at index {}: {}. Query: {}",
|
||||
index - 1,
|
||||
ex.what(),
|
||||
query
|
||||
);
|
||||
}
|
||||
|
||||
result.push_back(std::move(entry));
|
||||
});
|
||||
if(!sql_result) {
|
||||
logError(server_id, "Failed to query client database infos: {}; Query: {}", sql_result.fmtStr(), query);
|
||||
return result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfo(const std::shared_ptr<VirtualServer>& server, const std::deque<ClientDbId>& list) {
|
||||
if(list.empty())
|
||||
return {};
|
||||
|
||||
std::string valueList{};
|
||||
for(const auto& element : list)
|
||||
valueList += ", " + std::to_string(element);
|
||||
valueList = valueList.substr(2);
|
||||
|
||||
auto serverId = server ? server->getServerId() : 0;
|
||||
return query_database_client_info(this->sql, serverId, std::string{kSqlBase} + "WHERE `server_id` = :sid AND `client_database_id` IN (" + valueList + ")", {variable{":sid", serverId}});
|
||||
}
|
||||
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> DatabaseHelper::queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &server, std::deque<std::string> list) {
|
||||
if(list.empty()) return {};
|
||||
if(list.empty())
|
||||
return {};
|
||||
|
||||
deque<shared_ptr<ClientDatabaseInfo>> result;
|
||||
std::string valueList{};
|
||||
for(size_t value_index{0}; value_index < list.size(); value_index++)
|
||||
valueList += ", :v" + std::to_string(value_index);
|
||||
valueList = valueList.substr(2);
|
||||
|
||||
if(list.size() <= MAX_QUERY){
|
||||
std::string query = "SELECT * FROM `clients` WHERE `serverId` = :serverId AND (";
|
||||
for(const auto &elm : list)
|
||||
query += " `clientUid` = '" + elm + "' OR";
|
||||
query = query.substr(0, query.length() - 3) + ")";
|
||||
logTrace(server ? server->getServerId() : 0, "[SQL] queryDatabseInfoByUid(...) -> {}", query);
|
||||
auto state = sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}).query(function<decltype(collectData)>(collectData), &result);
|
||||
auto pf = LOG_SQL_CMD;
|
||||
pf(state);
|
||||
if(!state) return {};
|
||||
} else {
|
||||
std::deque<std::string> sub;
|
||||
do {
|
||||
sub.insert(sub.begin(), list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
|
||||
list.erase(list.begin(), list.begin() + min(list.size(), (size_t) MAX_QUERY));
|
||||
auto serverId = server ? server->getServerId() : 0;
|
||||
|
||||
auto res = this->queryDatabaseInfoByUid(server, sub);
|
||||
result.insert(result.end(), res.begin(), res.end());
|
||||
sub.clear();
|
||||
} while(!list.empty());
|
||||
}
|
||||
std::vector<variable> values{};
|
||||
values.reserve(list.size() + 1);
|
||||
|
||||
return result;
|
||||
values.emplace_back(":sid", serverId);
|
||||
for(size_t value_index{0}; value_index < list.size(); value_index++)
|
||||
values.emplace_back(":v" + std::to_string(value_index), list[value_index]);
|
||||
|
||||
return query_database_client_info(this->sql, serverId, std::string{kSqlBase} + "WHERE `server_id` = :sid AND `client_unique_id` IN (" + valueList + ")", values);
|
||||
}
|
||||
|
||||
bool DatabaseHelper::validClientDatabaseId(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) { return cldbid > 0; } //TODO here check
|
||||
|
||||
void DatabaseHelper::deleteClient(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
|
||||
ServerId sid = static_cast<ServerId>(server ? server->getServerId() : 0);
|
||||
auto serverId = (ServerId) (server ? server->getServerId() : 0);
|
||||
{
|
||||
lock_guard<threads::Mutex> lock(permManagerLock);
|
||||
for(auto permMgr : this->cachedPermissionManagers)
|
||||
if(permMgr->cldbid == cldbid && permMgr->sid == sid) {
|
||||
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
|
||||
delete permMgr;
|
||||
break;
|
||||
}
|
||||
lock_guard lock{cached_permission_manager_lock};
|
||||
|
||||
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
|
||||
return entry->server_id == serverId && entry->client_database_id == cldbid;
|
||||
}), this->cached_permission_managers.end());
|
||||
}
|
||||
//TODO remove from props cache?
|
||||
|
||||
sql::result state{};
|
||||
|
||||
auto state = sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = :sid AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":sid", sid}, variable{":type1", property::PROP_TYPE_CONNECTION}, variable{":type2", property::PROP_TYPE_CLIENT}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", sid}, variable{":type", permission::SQL_PERM_USER}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `clients` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `bannedClients` WHERE `serverId` = :sid AND `invokerDbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", sid}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `properties` WHERE `serverId` = :sid AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":sid", serverId}, variable{":type1", property::PROP_TYPE_CONNECTION}, variable{":type2", property::PROP_TYPE_CLIENT}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", serverId}, variable{":type", permission::SQL_PERM_USER}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `bannedClients` WHERE `serverId` = :sid AND `invokerDbid` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
|
||||
|
||||
if(serverId == 0) {
|
||||
state = sql::command(this->sql, "DELETE FROM `clients_server` WHERE `client_database_id` = :id", variable{":id", cldbid}).execute();
|
||||
state = sql::command(this->sql, "DELETE FROM `clients` WHERE `client_database_id` = :id", variable{":id", cldbid}).execute();
|
||||
} else {
|
||||
state = sql::command(this->sql, "DELETE FROM `clients_server` WHERE `server_id` = :sid AND `client_database_id` = :id", variable{":sid", serverId}, variable{":id", cldbid}).execute();
|
||||
}
|
||||
|
||||
//TODO delete letters
|
||||
//TODO delete query
|
||||
//TODO delete complains
|
||||
}
|
||||
|
||||
inline sql::result load_permissions_v2(const std::shared_ptr<VirtualServer>& server, v2::PermissionManager* manager, sql::command& command, bool test_channel /* only used for client permissions (client channel permissions) */) {
|
||||
auto start = system_clock::now();
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
inline sql::result load_permissions_v2(
|
||||
const ServerId& server_id,
|
||||
v2::PermissionManager* manager,
|
||||
sql::command& command,
|
||||
bool test_channel, /* only used for client permissions (client channel permissions) */
|
||||
bool is_channel) {
|
||||
return command.query([&](int length, char** values, char** names){
|
||||
permission::PermissionType key = permission::PermissionType::undefined;
|
||||
permission::PermissionValue value = permNotGranted, granted = permNotGranted;
|
||||
@@ -197,66 +209,68 @@ inline sql::result load_permissions_v2(const std::shared_ptr<VirtualServer>& ser
|
||||
logError(server_id, "[SQL] Cant load permissions! Failed to parse value! Command: {} Message: {} Key: {} Value: {}", command.sqlCommand(), ex.what(),names[index], values[index]);
|
||||
return 0;
|
||||
}
|
||||
if(channel_id > 0 && test_channel) {
|
||||
if(!server)
|
||||
logError(server_id, "[SQL] Cant find channel for channel bound permission (No server given)! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
|
||||
else {
|
||||
auto channel = server->getChannelTree()->findChannel(channel_id);
|
||||
if(!channel)
|
||||
logError(server_id, "[SQL] Cant find channel for channel bound permission! Command: {} ChannelID: {}", command.sqlCommand(), values[index]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(key == permission::undefined) {
|
||||
debugMessage(server_id, "[SQL] Permission entry misses permission type! Command: {}", command.sqlCommand());
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(channel_id == 0)
|
||||
if(channel_id == 0 || is_channel) {
|
||||
manager->load_permission(key, {value, granted}, skipped, negated, value != permNotGranted, granted != permNotGranted);
|
||||
else
|
||||
} else {
|
||||
manager->load_permission(key, {value, granted}, channel_id, skipped, negated, value != permNotGranted, granted != permNotGranted);
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
auto end = system_clock::now();
|
||||
auto time = end - start;
|
||||
logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
|
||||
//auto end = system_clock::now();
|
||||
//auto time = end - start;
|
||||
//logTrace(server_id, "[SQL] load_permissions(\"{}\") took {}ms", command.sqlCommand(), duration_cast<milliseconds>(time).count());
|
||||
}
|
||||
|
||||
#define UPDATE_COMMAND "UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
|
||||
#define INSERT_COMMAND "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"
|
||||
#define DELETE_COMMAND "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"
|
||||
constexpr static std::string_view kPermissionUpdateCommand{"UPDATE `permissions` SET `value` = :value, `grant` = :grant, `flag_skip` = :flag_skip, `flag_negate` = :flag_negate WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
|
||||
constexpr static std::string_view kPermissionInsertCommand{"INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)"};
|
||||
constexpr static std::string_view kPermissionDeleteCommand{"DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `permId` = :permId AND `channelId` = :chId"};
|
||||
|
||||
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid) {
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::find_cached_permission_manager(ServerId server_id,
|
||||
ClientDbId client_database_id) {
|
||||
for(auto it = this->cached_permission_managers.begin(); it != this->cached_permission_managers.end(); it++) {
|
||||
auto& cached_manager = *it;
|
||||
|
||||
if(cached_manager->client_database_id == client_database_id && cached_manager->server_id == server_id) {
|
||||
auto manager = cached_manager->instance.lock();
|
||||
if(!manager){
|
||||
this->cached_permission_managers.erase(it);
|
||||
break;
|
||||
}
|
||||
|
||||
cached_manager->last_access = system_clock::now();
|
||||
cached_manager->instance_ref = manager;
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManager(const ServerId& server_id, ClientDbId cldbid) {
|
||||
#ifndef DISABLE_CACHING
|
||||
{
|
||||
lock_guard<threads::Mutex> lock(permManagerLock);
|
||||
for(auto permMgr : this->cachedPermissionManagers)
|
||||
if(permMgr->cldbid == cldbid && permMgr->sid == (server ? server->getServerId() : 0)) {
|
||||
auto ptr = permMgr->manager.lock();
|
||||
if(!ptr){
|
||||
this->cachedPermissionManagers.erase(std::find(this->cachedPermissionManagers.begin(), this->cachedPermissionManagers.end(), permMgr));
|
||||
delete permMgr;
|
||||
break;
|
||||
}
|
||||
permMgr->lastAccess = system_clock::now();
|
||||
return ptr;
|
||||
}
|
||||
std::lock_guard lock{cached_permission_manager_lock};
|
||||
auto manager = this->find_cached_permission_manager(server_id, cldbid);
|
||||
if(manager) return manager;
|
||||
}
|
||||
#endif
|
||||
|
||||
logTrace(server_id, "[Permission] Loading client permission manager for client {}", cldbid);
|
||||
auto permission_manager = std::make_shared<v2::PermissionManager>();
|
||||
bool loaded = false;
|
||||
if(this->use_startup_cache && server) {
|
||||
if(this->use_startup_cache && server_id > 0) {
|
||||
shared_ptr<StartupCacheEntry> entry;
|
||||
{
|
||||
threads::MutexLock lock(this->startup_lock);
|
||||
for(const auto& entries : this->startup_entries) {
|
||||
if(entries->sid == server->getServerId()) {
|
||||
if(entries->sid == server_id) {
|
||||
entry = entries;
|
||||
break;
|
||||
}
|
||||
@@ -265,12 +279,11 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
|
||||
if(entry) {
|
||||
for(const auto& perm : entry->permissions) {
|
||||
if(perm->type == permission::SQL_PERM_USER && perm->id == cldbid) {
|
||||
auto channel = perm->channelId > 0 ? server->getChannelTree()->findChannel(perm->channelId) : nullptr;
|
||||
|
||||
if(channel)
|
||||
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, channel->channelId(), perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
|
||||
else
|
||||
if(perm->channelId > 0) {
|
||||
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->channelId, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
|
||||
} else {
|
||||
permission_manager->load_permission(perm->permission->type, {perm->value, perm->grant}, perm->flag_skip, perm->flag_negate, perm->value != permNotGranted, perm->grant != permNotGranted);
|
||||
}
|
||||
}
|
||||
}
|
||||
loaded = true;
|
||||
@@ -279,23 +292,31 @@ std::shared_ptr<v2::PermissionManager> DatabaseHelper::loadClientPermissionManag
|
||||
|
||||
if(!loaded) {
|
||||
auto command = sql::command(this->sql, "SELECT `permId`, `value`, `channelId`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":serverId", server_id},
|
||||
variable{":type", permission::SQL_PERM_USER},
|
||||
variable{":id", cldbid});
|
||||
LOG_SQL_CMD(load_permissions_v2(server, permission_manager.get(), command, true));
|
||||
LOG_SQL_CMD(load_permissions_v2(server_id, permission_manager.get(), command, true, false));
|
||||
}
|
||||
|
||||
|
||||
#ifndef DISABLE_CACHING
|
||||
this->permManagerLock.lock();
|
||||
auto entry = new CachedPermissionManager();
|
||||
entry->sid = server_id;
|
||||
entry->manager = permission_manager;
|
||||
entry->ownLock = permission_manager;
|
||||
entry->cldbid = cldbid;
|
||||
entry->lastAccess = system_clock::now();
|
||||
this->cachedPermissionManagers.push_back(entry);
|
||||
this->permManagerLock.unlock();
|
||||
auto cache_entry = std::make_unique<CachedPermissionManager>();
|
||||
cache_entry->server_id = server_id;
|
||||
cache_entry->instance = permission_manager;
|
||||
cache_entry->instance_ref = permission_manager;
|
||||
cache_entry->client_database_id = cldbid;
|
||||
cache_entry->last_access = system_clock::now();
|
||||
|
||||
{
|
||||
std::lock_guard cache_lock{this->cached_permission_manager_lock};
|
||||
|
||||
/* test if we might not got a second instance */
|
||||
auto manager = this->find_cached_permission_manager(server_id, cldbid);
|
||||
if(manager) return manager;
|
||||
|
||||
this->cached_permission_managers.push_back(std::move(cache_entry));
|
||||
}
|
||||
|
||||
#endif
|
||||
return permission_manager;
|
||||
}
|
||||
@@ -308,7 +329,7 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
for(auto& update : updates) {
|
||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
||||
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||
|
||||
auto permission_data = permission::resolvePermissionData(update.permission);
|
||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||
@@ -336,14 +357,14 @@ void DatabaseHelper::saveClientPermissions(const std::shared_ptr<ts::server::Vir
|
||||
}
|
||||
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPermissions(const std::shared_ptr<VirtualServer>& server, ts::GroupId group_id) {
|
||||
std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPermissions(const ServerId& server_id, ts::GroupId group_id, uint8_t /* target */) {
|
||||
auto result = std::make_shared<v2::PermissionManager>();
|
||||
if(this->use_startup_cache && server) {
|
||||
if(this->use_startup_cache && server_id > 0) {
|
||||
shared_ptr<StartupCacheEntry> entry;
|
||||
{
|
||||
threads::MutexLock lock(this->startup_lock);
|
||||
for(const auto& entries : this->startup_entries) {
|
||||
if(entries->sid == server->getServerId()) {
|
||||
if(entries->sid == server_id) {
|
||||
entry = entries;
|
||||
break;
|
||||
}
|
||||
@@ -361,34 +382,33 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadGroupPerm
|
||||
|
||||
//7931
|
||||
auto command = sql::command(this->sql, "SELECT `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":serverId", server_id},
|
||||
variable{":type", permission::SQL_PERM_GROUP},
|
||||
variable{":id", group_id});
|
||||
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
|
||||
LOG_SQL_CMD(load_permissions_v2(server_id, result.get(), command, false, false));
|
||||
return result;
|
||||
}
|
||||
|
||||
void DatabaseHelper::saveGroupPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::GroupId group_id, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
|
||||
void DatabaseHelper::saveGroupPermissions(const ServerId &server_id, ts::GroupId group_id, uint8_t target, const std::shared_ptr<ts::permission::v2::PermissionManager> &permissions) {
|
||||
const auto updates = permissions->flush_db_updates();
|
||||
if(updates.empty())
|
||||
return;
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
for(auto& update : updates) {
|
||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
||||
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||
|
||||
auto permission_data = permission::resolvePermissionData(update.permission);
|
||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
|
||||
logTrace(server_id, "[CHANNEL] Updating group permission for group {}: {}. New value: {}. New grant: {}. Query: {}",
|
||||
group_id,
|
||||
logTrace(server_id, "Updating group permission for group {}/{}: {}. New value: {}. New grant: {}. Query: {}",
|
||||
target, group_id,
|
||||
permission_data->name,
|
||||
value,
|
||||
grant,
|
||||
query
|
||||
);
|
||||
sql::command(this->sql, query,
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":serverId", server_id},
|
||||
variable{":id", group_id},
|
||||
variable{":chId", 0},
|
||||
variable{":type", permission::SQL_PERM_GROUP},
|
||||
@@ -436,7 +456,7 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadPlaylistP
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":type", permission::SQL_PERM_PLAYLIST},
|
||||
variable{":id", playlist_id});
|
||||
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
|
||||
LOG_SQL_CMD(load_permissions_v2(server ? server->getServerId() : 0, result.get(), command, false, false));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -447,7 +467,7 @@ void DatabaseHelper::savePlaylistPermissions(const std::shared_ptr<VirtualServer
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
for(auto& update : updates) {
|
||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
||||
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||
|
||||
auto permission_data = permission::resolvePermissionData(update.permission);
|
||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||
@@ -502,7 +522,7 @@ std::shared_ptr<permission::v2::PermissionManager> DatabaseHelper::loadChannelPe
|
||||
variable{":chid", channel},
|
||||
variable{":id", 0},
|
||||
variable{":type", permission::SQL_PERM_CHANNEL});
|
||||
LOG_SQL_CMD(load_permissions_v2(server, result.get(), command, false));
|
||||
LOG_SQL_CMD(load_permissions_v2(server ? server->getServerId() : 0, result.get(), command, false, true));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -513,7 +533,7 @@ void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::Vi
|
||||
|
||||
auto server_id = server ? server->getServerId() : 0;
|
||||
for(auto& update : updates) {
|
||||
std::string query = update.flag_delete ? DELETE_COMMAND : (update.flag_db ? UPDATE_COMMAND : INSERT_COMMAND);
|
||||
std::string query{update.flag_delete ? kPermissionDeleteCommand : (update.flag_db ? kPermissionUpdateCommand : kPermissionInsertCommand)};
|
||||
|
||||
auto value = update.update_value == v2::delete_value ? permNotGranted : update.values.value;
|
||||
auto grant = update.update_grant == v2::delete_value ? permNotGranted : update.values.grant;
|
||||
@@ -540,12 +560,12 @@ void DatabaseHelper::saveChannelPermissions(const std::shared_ptr<ts::server::Vi
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Properties> DatabaseHelper::default_properties_client(std::shared_ptr<Properties> properties, ClientType type){
|
||||
if(!properties)
|
||||
properties = make_shared<Properties>();
|
||||
std::shared_ptr<PropertyManager> DatabaseHelper::default_properties_client(std::shared_ptr<PropertyManager> properties, ClientType type){
|
||||
if(!properties) {
|
||||
properties = std::make_shared<PropertyManager>();
|
||||
}
|
||||
|
||||
properties->register_property_type<property::ClientProperties>();
|
||||
properties->register_property_type<property::ConnectionProperties>();
|
||||
|
||||
if(type == ClientType::CLIENT_MUSIC || type == ClientType::CLIENT_QUERY){
|
||||
(*properties)[property::CLIENT_INPUT_HARDWARE] = true;
|
||||
@@ -555,47 +575,42 @@ std::shared_ptr<Properties> DatabaseHelper::default_properties_client(std::share
|
||||
return properties;
|
||||
}
|
||||
|
||||
bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId id, std::shared_ptr<DataClient> cl) {
|
||||
bool DatabaseHelper::assignDatabaseId(sql::SqlManager *sql, ServerId serverId, std::shared_ptr<DataClient> cl) {
|
||||
cl->loadDataForCurrentServer();
|
||||
if(cl->getClientDatabaseId() == 0){ //Client does not exist
|
||||
ClientDbId cldbid = 0;
|
||||
auto res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 AND `clientUid` = :cluid", variable{":cluid", cl->getUid()}).query([](ClientDbId* ptr, int length, char** values, char** names){
|
||||
*ptr = static_cast<ClientDbId>(stoll(values[0]));
|
||||
return 0;
|
||||
}, &cldbid);
|
||||
auto pf = LOG_SQL_CMD;
|
||||
pf(res);
|
||||
if(!res) return false;
|
||||
if(cl->getClientDatabaseId() == 0) {
|
||||
/* client does not exists, create a new one */
|
||||
|
||||
auto insertTemplate = sql::model(sql, "INSERT INTO `clients` (`serverId`, `cldbId`, `clientUid`, `lastName`,`firstConnect`,`lastConnect`, `connections`) VALUES (:serverId, :cldbid, :cluid, :name, :fconnect, :lconnect, :connections)",
|
||||
variable{":cluid", cl->getUid()}, variable{":name", cl->getDisplayName()}, variable{":fconnect", duration_cast<seconds>(system_clock::now().time_since_epoch()).count()}, variable{":lconnect", 0}, variable{":connections", 0});
|
||||
if(cldbid == 0){ //Completly new user
|
||||
res = sql::command(sql, "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([](ClientDbId* ptr, int length, char** values, char** names){
|
||||
*ptr = static_cast<ClientDbId>(stoll(values[0]));
|
||||
return 0;
|
||||
}, &cldbid);
|
||||
pf(res);
|
||||
if(!res) return false;
|
||||
sql::result sql_result{};
|
||||
|
||||
cldbid += 1;
|
||||
res = insertTemplate.command().values(variable{":serverId", 0}, variable{":cldbid", cldbid}).execute(); //Insert global
|
||||
pf(res);
|
||||
if(!res) return false;
|
||||
debugMessage(id, "Having new instance user on server {}. (Database ID: {} Name: {})", id, cldbid, cl->getDisplayName());
|
||||
} else {
|
||||
debugMessage(id, "Having new server user on server {}. (Database ID: {} Name: {})", id, cldbid, cl->getDisplayName());
|
||||
std::string insert_or_ignore{sql->getType() == sql::TYPE_SQLITE ? "INSERT OR IGNORE" : "INSERT IGNORE"};
|
||||
|
||||
auto currentTimeSeconds = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
sql_result = sql::command{sql, insert_or_ignore + " INTO `clients` (`client_unique_id`, `client_created`) VALUES (:uniqueId, :now)",
|
||||
variable{":uniqueId", cl->getUid()},
|
||||
variable{":now", currentTimeSeconds}
|
||||
}.execute();
|
||||
|
||||
if(!sql_result) {
|
||||
logCritical(LOG_INSTANCE, "Failed to execute client insert command for {}: {}", cl->getUid(), sql_result.fmtStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
if(id != 0){ //Else already inserted
|
||||
res = insertTemplate.command().values(variable{":serverId", id}, variable{":cldbid", cldbid}).execute();
|
||||
pf(res);
|
||||
if(!res) return false;
|
||||
sql_result = sql::command{sql, "INSERT INTO `clients_server` (`server_id`, `client_unique_id`, `client_database_id`, `client_created`) SELECT :serverId, :uniqueId, `client_database_id`, :now FROM `clients` WHERE `client_unique_id` = :uniqueId;",
|
||||
variable{":serverId", serverId},
|
||||
variable{":uniqueId", cl->getUid()},
|
||||
variable{":now", currentTimeSeconds}
|
||||
}.execute();
|
||||
|
||||
if(!sql_result) {
|
||||
logCritical(LOG_INSTANCE, "Failed to execute client server insert command for {}: {}", cl->getUid(), sql_result.fmtStr());
|
||||
return false;
|
||||
}
|
||||
|
||||
return assignDatabaseId(sql, id, cl);
|
||||
if(!cl->loadDataForCurrentServer())
|
||||
return false;
|
||||
|
||||
debugMessage(serverId, "Successfully registered client {} for server {} with database id {}.", cl->getUid(), serverId, cl->getClientDatabaseId());
|
||||
}
|
||||
|
||||
logTrace(id, "Loaded client from database. Database id: {} Unique id: {}", cl->getClientDatabaseId(), cl->getUid());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -617,8 +632,8 @@ inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEn
|
||||
}
|
||||
}
|
||||
|
||||
const auto &info = property::impl::info_key(type, key);
|
||||
if(info->name == "undefined") {
|
||||
const auto &info = property::find(type, key);
|
||||
if(info.name == "undefined") {
|
||||
logError(sid, "Found unknown property in database! ({})", key);
|
||||
return 0;
|
||||
}
|
||||
@@ -630,9 +645,9 @@ inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEn
|
||||
prop.setDbReference(true);
|
||||
*/
|
||||
|
||||
auto data = make_unique<FastPropertyEntry>();
|
||||
auto data = std::make_unique<FastPropertyEntry>();
|
||||
data->type = &info;
|
||||
data->value = value;
|
||||
data->type = info;
|
||||
properties.push_back(move(data));
|
||||
return 0;
|
||||
});
|
||||
@@ -643,8 +658,8 @@ inline sql::result load_properties(ServerId sid, deque<unique_ptr<FastPropertyEn
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<Properties> DatabaseHelper::loadServerProperties(const std::shared_ptr<ts::server::VirtualServer>& server) {
|
||||
auto props = std::make_shared<Properties>();
|
||||
std::shared_ptr<PropertyManager> DatabaseHelper::loadServerProperties(const std::shared_ptr<ts::server::VirtualServer>& server) {
|
||||
auto props = std::make_shared<PropertyManager>();
|
||||
|
||||
props->register_property_type<property::VirtualServerProperties>();
|
||||
(*props)[property::VIRTUALSERVER_HOST] = config::binding::DefaultVoiceHost;
|
||||
@@ -695,17 +710,19 @@ std::shared_ptr<Properties> DatabaseHelper::loadServerProperties(const std::shar
|
||||
return;
|
||||
}
|
||||
auto weak_server = weak.lock();
|
||||
if(!weak_server && serverId != 0) return;
|
||||
if(!weak_server && serverId != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
string sql;
|
||||
if(prop.hasDbReference())
|
||||
if(prop.hasDbReference()) {
|
||||
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `key` = :key";
|
||||
else {
|
||||
} else {
|
||||
prop.setDbReference(true);
|
||||
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)";
|
||||
}
|
||||
|
||||
logTrace(serverId, "Updating server property: " + prop.type().name + ". New value: " + prop.value() + ". Query: " + sql);
|
||||
logTrace(serverId, "Updating server property: " + std::string{prop.type().name} + ". New value: " + prop.value() + ". Query: " + sql);
|
||||
sql::command(this->sql, sql,
|
||||
variable{":sid", serverId},
|
||||
variable{":type", property::PropertyType::PROP_TYPE_SERVER},
|
||||
@@ -717,8 +734,8 @@ std::shared_ptr<Properties> DatabaseHelper::loadServerProperties(const std::shar
|
||||
return props;
|
||||
}
|
||||
|
||||
std::shared_ptr<Properties> DatabaseHelper::loadPlaylistProperties(const std::shared_ptr<ts::server::VirtualServer>& server, PlaylistId id) {
|
||||
auto props = std::make_shared<Properties>();
|
||||
std::shared_ptr<PropertyManager> DatabaseHelper::loadPlaylistProperties(const std::shared_ptr<ts::server::VirtualServer>& server, PlaylistId id) {
|
||||
auto props = std::make_shared<PropertyManager>();
|
||||
|
||||
props->register_property_type<property::PlaylistProperties>();
|
||||
(*props)[property::PLAYLIST_ID] = id;
|
||||
@@ -771,9 +788,9 @@ std::shared_ptr<Properties> DatabaseHelper::loadPlaylistProperties(const std::sh
|
||||
if(!weak_server && serverId != 0) return;
|
||||
|
||||
string sql;
|
||||
if(prop.hasDbReference())
|
||||
if(prop.hasDbReference()) {
|
||||
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `key` = :key";
|
||||
else {
|
||||
} else {
|
||||
prop.setDbReference(true);
|
||||
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)";
|
||||
}
|
||||
@@ -790,9 +807,9 @@ std::shared_ptr<Properties> DatabaseHelper::loadPlaylistProperties(const std::sh
|
||||
return props;
|
||||
}
|
||||
|
||||
std::shared_ptr<Properties> DatabaseHelper::loadChannelProperties(const shared_ptr<VirtualServer>& server, ChannelId channel) {
|
||||
std::shared_ptr<PropertyManager> DatabaseHelper::loadChannelProperties(const shared_ptr<VirtualServer>& server, ChannelId channel) {
|
||||
ServerId serverId = server ? server->getServerId() : 0U;
|
||||
auto props = std::make_shared<Properties>();
|
||||
auto props = std::make_shared<PropertyManager>();
|
||||
|
||||
props->register_property_type<property::ChannelProperties>();
|
||||
if(server) {
|
||||
@@ -871,11 +888,12 @@ std::shared_ptr<Properties> DatabaseHelper::loadChannelProperties(const shared_p
|
||||
return props;
|
||||
}
|
||||
|
||||
std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid, ClientType type) {
|
||||
std::shared_ptr<PropertyManager> DatabaseHelper::loadClientProperties(const std::shared_ptr<VirtualServer>& server, ClientDbId cldbid, ClientType type) {
|
||||
auto props = DatabaseHelper::default_properties_client(nullptr, type);
|
||||
if(server) {
|
||||
props->operator[](property::CLIENT_DESCRIPTION) = server->properties()[property::VIRTUALSERVER_DEFAULT_CLIENT_DESCRIPTION].value();
|
||||
}
|
||||
|
||||
bool loaded = false;
|
||||
if(use_startup_cache && server) {
|
||||
shared_ptr<StartupCacheEntry> entry;
|
||||
@@ -890,7 +908,7 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
}
|
||||
if(entry) {
|
||||
for(const auto& prop : entry->properties) {
|
||||
if(prop->id == cldbid && (prop->type == property::PROP_TYPE_CLIENT || prop->type == property::PROP_TYPE_CONNECTION)) {
|
||||
if(prop->id == cldbid && (prop->type == property::PROP_TYPE_CLIENT)) {
|
||||
auto p = (*props)[prop->info];
|
||||
p = prop->value;
|
||||
p.setModified(true);
|
||||
@@ -900,8 +918,9 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!loaded) {
|
||||
auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND (`type` = :type1 OR `type` = :type2) AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type1", property::PropertyType::PROP_TYPE_CONNECTION}, variable{":type2", property::PropertyType::PROP_TYPE_CLIENT}, variable{":id", cldbid});
|
||||
auto command = sql::command(this->sql, "SELECT `key`, `value`, `type` FROM properties WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id", variable{":serverId", server ? server->getServerId() : 0}, variable{":type", property::PropertyType::PROP_TYPE_CLIENT}, variable{":id", cldbid});
|
||||
|
||||
deque<unique_ptr<FastPropertyEntry>> property_list;
|
||||
LOG_SQL_CMD(load_properties(server ? server->getServerId() : 0, property_list, command));
|
||||
@@ -925,23 +944,26 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
|
||||
if(!prop.isModified()) return;
|
||||
if((prop.type().flags & property::FLAG_SAVE) == 0 && (type != ClientType::CLIENT_MUSIC || (prop.type().flags & property::FLAG_SAVE_MUSIC) == 0)) {
|
||||
logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + prop.type().name + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")");
|
||||
logTrace(server ? server->getServerId() : 0, "[Property] Not saving property '" + std::string{prop.type().name} + "', changed for " + to_string(cldbid) + " (New value: " + prop.value() + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
auto handle = prop.get_handle();
|
||||
if(!handle || !handle->isSaveEnabled()) {
|
||||
return;
|
||||
}
|
||||
if(!prop.get_handle()) return;
|
||||
if(!prop.get_handle()->isSaveEnabled()) return;
|
||||
if(!prop.hasDbReference() && (prop.default_value() == prop.value())) return; //No changes to default value
|
||||
prop.setModified(false);
|
||||
|
||||
std::string sql;
|
||||
std::string sqlCommand;
|
||||
if(prop.hasDbReference())
|
||||
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `key` = :key";
|
||||
sqlCommand = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id AND `key` = :key";
|
||||
else {
|
||||
prop.setDbReference(true);
|
||||
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)";
|
||||
sqlCommand = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:serverId, :type, :id, :key, :value)";
|
||||
}
|
||||
logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + prop.type().name + " value: " + prop.value());
|
||||
sql::command(this->sql, sql,
|
||||
logTrace(server ? server->getServerId() : 0, "[Property] Changed property in db key: " + std::string{prop.type().name} + " value: " + prop.value());
|
||||
sql::command(this->sql, sqlCommand,
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":type", prop.type().type_property},
|
||||
variable{":id", cldbid},
|
||||
@@ -957,21 +979,66 @@ std::shared_ptr<Properties> DatabaseHelper::loadClientProperties(const std::shar
|
||||
return;
|
||||
}
|
||||
|
||||
std::string query;
|
||||
if(prop.type() == property::CLIENT_TOTALCONNECTIONS)
|
||||
query = "UPDATE `clients` SET `connections` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
|
||||
else if(prop.type() == property::CLIENT_NICKNAME)
|
||||
query = "UPDATE `clients` SET `lastName` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
|
||||
else if(prop.type() == property::CLIENT_LASTCONNECTED)
|
||||
query = "UPDATE `clients` SET `lastConnect` = :value WHERE `serverId` = :serverId AND `cldbid` = :cldbid";
|
||||
if(query.empty()) return;
|
||||
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '" + prop.type().name + "' for " + to_string(cldbid) + " (New value: " + prop.value() + ", SQL: " + query + ")");
|
||||
sql::command(this->sql, query, variable{":serverId", server ? server->getServerId() : 0}, variable{":cldbid", cldbid}, variable{":value", prop.value()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
std::string column;
|
||||
if(prop.type().type_property == property::PROP_TYPE_CLIENT) {
|
||||
switch (prop.type().property_index) {
|
||||
case property::CLIENT_NICKNAME:
|
||||
column = "client_nickname";
|
||||
break;
|
||||
|
||||
case property::CLIENT_LASTCONNECTED:
|
||||
column = "client_last_connected";
|
||||
break;
|
||||
|
||||
case property::CLIENT_TOTALCONNECTIONS:
|
||||
column = "client_total_connections";
|
||||
break;
|
||||
|
||||
case property::CLIENT_MONTH_BYTES_UPLOADED:
|
||||
column = "client_month_upload";
|
||||
break;
|
||||
|
||||
case property::CLIENT_TOTAL_BYTES_UPLOADED:
|
||||
column = "client_total_upload";
|
||||
break;
|
||||
|
||||
case property::CLIENT_MONTH_BYTES_DOWNLOADED:
|
||||
column = "client_month_download";
|
||||
break;
|
||||
|
||||
case property::CLIENT_TOTAL_BYTES_DOWNLOADED:
|
||||
column = "client_total_download";
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
debugMessage(server ? server->getServerId() : 0, "[Property] Changing client property '{}' for {} (New value: {}, Column: {})",
|
||||
prop.type().name,
|
||||
cldbid,
|
||||
prop.value(),
|
||||
column
|
||||
);
|
||||
sql::command(this->sql, "UPDATE `clients_server` SET `" + column + "` = :value WHERE `server_id` = :serverId AND `client_database_id` = :cldbid",
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":cldbid", cldbid},
|
||||
variable{":value", prop.value()}
|
||||
).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
});
|
||||
|
||||
return props;
|
||||
}
|
||||
|
||||
void DatabaseHelper::updateClientIpAddress(const ServerId &server_id, ClientDbId client_database_id, const std::string &ip_address) {
|
||||
sql::command(this->sql, "UPDATE `clients_server` SET `client_ip` = :value WHERE `server_id` = :serverId AND `client_database_id` = :cldbid",
|
||||
variable{":serverId", server_id},
|
||||
variable{":cldbid", client_database_id},
|
||||
variable{":value", ip_address}
|
||||
).executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
}
|
||||
|
||||
void DatabaseHelper::loadStartupCache() {
|
||||
this->loadStartupPermissionCache();
|
||||
this->loadStartupPropertyCache();
|
||||
@@ -1013,6 +1080,15 @@ void DatabaseHelper::clearStartupCache(ts::ServerId sid) {
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseHelper::handleServerDelete(ServerId server_id) {
|
||||
{
|
||||
std::lock_guard pm_lock{this->cached_permission_manager_lock};
|
||||
this->cached_permission_managers.erase(std::remove_if(this->cached_permission_managers.begin(), this->cached_permission_managers.end(), [&](const auto& entry) {
|
||||
return entry->server_id == server_id;
|
||||
}), this->cached_permission_managers.end());
|
||||
}
|
||||
}
|
||||
|
||||
//SELECT `serverId`, `type`, `id`, `key`, `value` FROM properties ORDER BY `serverId`
|
||||
//SELECT `serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate` FROM permissions ORDER BY `serverId`
|
||||
struct StartupPermissionArgument {
|
||||
@@ -1119,8 +1195,8 @@ void DatabaseHelper::loadStartupPropertyCache() {
|
||||
}
|
||||
}
|
||||
|
||||
auto info = property::impl::info_key(type, key);
|
||||
if(info == property::PropertyDescription::unknown) {
|
||||
const auto& info = property::find(type, key);
|
||||
if(info.is_undefined()) {
|
||||
logError(serverId, "Invalid property ({} | {})", key, type);
|
||||
return 0;
|
||||
}
|
||||
@@ -1145,7 +1221,7 @@ void DatabaseHelper::loadStartupPropertyCache() {
|
||||
}
|
||||
|
||||
auto entry = make_unique<StartupPropertyEntry>();
|
||||
entry->info = info;
|
||||
entry->info = &info;
|
||||
entry->value = value;
|
||||
entry->id = id;
|
||||
entry->type = type;
|
||||
@@ -1154,13 +1230,19 @@ void DatabaseHelper::loadStartupPropertyCache() {
|
||||
}, &arg);
|
||||
}
|
||||
|
||||
bool DatabaseHelper::deleteGroupPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::GroupId group_id) {
|
||||
auto command = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
|
||||
variable{":serverId", server ? server->getServerId() : 0},
|
||||
variable{":type", permission::SQL_PERM_GROUP},
|
||||
variable{":id", group_id}).execute();
|
||||
LOG_SQL_CMD(command);
|
||||
return !!command;
|
||||
void DatabaseHelper::deleteGroupArtifacts(ServerId server_id, GroupId group_id) {
|
||||
sql::result result{};
|
||||
|
||||
result = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :serverId AND `type` = :type AND `id` = :id",
|
||||
variable{":serverId", server_id},
|
||||
variable{":type", permission::SQL_PERM_GROUP},
|
||||
variable{":id", group_id}).execute();
|
||||
LOG_SQL_CMD(result);
|
||||
|
||||
result = sql::command(this->sql, "DELETE FROM `tokens` WHERE `serverId` = :serverId AND `targetGroup` = :id",
|
||||
variable{":serverId", server_id},
|
||||
variable{":id", group_id}).execute();
|
||||
LOG_SQL_CMD(result);
|
||||
}
|
||||
|
||||
bool DatabaseHelper::deleteChannelPermissions(const std::shared_ptr<ts::server::VirtualServer> &server, ts::ChannelId channel_id) {
|
||||
@@ -1201,4 +1283,82 @@ bool DatabaseHelper::deletePlaylist(const std::shared_ptr<ts::server::VirtualSer
|
||||
).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed to delete playlist properties for playlist " + to_string(playlist_id)});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr static auto kDBListQuery{R"(
|
||||
SELECT `clients`.*, `properties`.`value` as `client_description` FROM (
|
||||
SELECT
|
||||
`clients_server`.`client_database_id`,
|
||||
`clients_server`.`client_unique_id`,
|
||||
`clients_server`.`client_nickname`,
|
||||
`clients_server`.`client_ip`,
|
||||
`clients_server`.`client_created`,
|
||||
`clients_server`.`client_last_connected`,
|
||||
`clients_server`.`client_total_connections`,
|
||||
`clients`.`client_login_name` FROM `clients_server`
|
||||
INNER JOIN `clients` ON `clients`.`client_database_id` = `clients_server`.`client_database_id`
|
||||
WHERE `server_id` = :serverId LIMIT :offset, :limit
|
||||
) AS `clients`
|
||||
LEFT JOIN `properties` ON `properties`.serverId = :serverId AND `properties`.key = 'client_description' AND `properties`.`id` = `clients`.`client_database_id`
|
||||
)"};
|
||||
|
||||
void DatabaseHelper::listDatabaseClients(
|
||||
ServerId server_id,
|
||||
const std::optional<int64_t>& offset,
|
||||
const std::optional<int64_t>& limit,
|
||||
void (* callback)(void *, const DatabaseClient &),
|
||||
void *user_argument) {
|
||||
|
||||
DatabaseClient client;
|
||||
size_t set_index{0};
|
||||
auto sqlResult = sql::command{this->sql, kDBListQuery,
|
||||
variable{":serverId", server_id},
|
||||
variable{":offset", offset.has_value() ? *offset : 0},
|
||||
variable{":limit", limit.has_value() ? *limit : -1}
|
||||
}.query([&](int length, std::string* values, std::string* names) {
|
||||
set_index++;
|
||||
|
||||
auto index{0};
|
||||
try {
|
||||
assert(names[index] == "client_database_id");
|
||||
client.client_database_id = std::stoull(values[index++]);
|
||||
|
||||
assert(names[index] == "client_unique_id");
|
||||
client.client_unique_id = values[index++];
|
||||
|
||||
assert(names[index] == "client_nickname");
|
||||
client.client_nickname = values[index++];
|
||||
|
||||
assert(names[index] == "client_ip");
|
||||
client.client_ip = values[index++];
|
||||
|
||||
assert(names[index] == "client_created");
|
||||
client.client_created = values[index++];
|
||||
|
||||
assert(names[index] == "client_last_connected");
|
||||
client.client_last_connected = values[index++];
|
||||
|
||||
assert(names[index] == "client_total_connections");
|
||||
client.client_total_connections = values[index++];
|
||||
|
||||
assert(names[index] == "client_login_name");
|
||||
client.client_login_name = values[index++];
|
||||
|
||||
assert(names[index] == "client_description");
|
||||
client.client_description = values[index++];
|
||||
|
||||
assert(index == length);
|
||||
} catch (std::exception& ex) {
|
||||
logError(server_id, "Failed to parse client base properties at index {}: {}. Offset: {} Limit: {} Set: {}",
|
||||
index - 1,
|
||||
ex.what(),
|
||||
offset.has_value() ? std::to_string(*offset) : "not given",
|
||||
limit.has_value() ? std::to_string(*limit) : "not given",
|
||||
set_index - 1
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(user_argument, client);
|
||||
});
|
||||
}
|
||||
+103
-99
@@ -8,125 +8,129 @@
|
||||
#include <Properties.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ts {
|
||||
namespace server {
|
||||
class VirtualServer;
|
||||
class DataClient;
|
||||
namespace ts::server {
|
||||
class VirtualServer;
|
||||
class DataClient;
|
||||
|
||||
struct ClientDatabaseInfo {
|
||||
ServerId sid;
|
||||
ClientDbId cldbid;
|
||||
std::chrono::time_point<std::chrono::system_clock> created;
|
||||
std::chrono::time_point<std::chrono::system_clock> lastjoin;
|
||||
std::string uniqueId;
|
||||
std::string lastName;
|
||||
uint32_t connections;
|
||||
};
|
||||
struct ClientDatabaseInfo {
|
||||
ServerId server_id;
|
||||
|
||||
struct CachedPermissionManager {
|
||||
ServerId sid;
|
||||
ClientDbId cldbid;
|
||||
std::weak_ptr<permission::v2::PermissionManager> manager;
|
||||
std::shared_ptr<permission::v2::PermissionManager> ownLock;
|
||||
std::chrono::time_point<std::chrono::system_clock> lastAccess;
|
||||
};
|
||||
ClientDbId client_database_id;
|
||||
std::string client_unique_id;
|
||||
std::string client_nickname;
|
||||
std::string client_ip;
|
||||
|
||||
struct CachedProperties {
|
||||
ServerId sid;
|
||||
ClientDbId cldbid;
|
||||
std::weak_ptr<Properties> properties;
|
||||
std::shared_ptr<Properties> ownLock;
|
||||
std::chrono::time_point<std::chrono::system_clock> lastAccess;
|
||||
};
|
||||
std::chrono::time_point<std::chrono::system_clock> client_created;
|
||||
std::chrono::time_point<std::chrono::system_clock> client_last_connected;
|
||||
|
||||
/*
|
||||
CREATE_TABLE("properties", "`serverId` INTEGER DEFAULT -1, `type` INTEGER, `id` INTEGER, `key` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` TEXT", command_append_utf8);
|
||||
CREATE_TABLE("permissions", "`serverId` INT NOT NULL, `type` INT, `id` INT, `channelId` INT, `permId` VARCHAR(" UNKNOWN_KEY_LENGTH "), `value` INT, `grant` INT", command_append_utf8);
|
||||
*/
|
||||
struct StartupPermissionEntry {
|
||||
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
|
||||
uint64_t id = 0;
|
||||
ChannelId channelId = 0;
|
||||
std::shared_ptr<permission::PermissionTypeEntry> permission = permission::PermissionTypeEntry::unknown;
|
||||
permission::PermissionValue value = 0;
|
||||
permission::PermissionValue grant = 0;
|
||||
uint32_t client_total_connections;
|
||||
};
|
||||
|
||||
bool flag_skip = false;
|
||||
bool flag_negate = false;
|
||||
};
|
||||
struct StartupPermissionEntry {
|
||||
permission::PermissionSqlType type = permission::SQL_PERM_CHANNEL;
|
||||
uint64_t id = 0;
|
||||
ChannelId channelId = 0;
|
||||
std::shared_ptr<permission::PermissionTypeEntry> permission = permission::PermissionTypeEntry::unknown;
|
||||
permission::PermissionValue value = 0;
|
||||
permission::PermissionValue grant = 0;
|
||||
|
||||
struct StartupPropertyEntry {
|
||||
property::PropertyType type = property::PropertyType::PROP_TYPE_UNKNOWN;
|
||||
uint64_t id = 0;
|
||||
std::shared_ptr<property::PropertyDescription> info = property::PropertyDescription::unknown;
|
||||
std::string value;
|
||||
};
|
||||
bool flag_skip = false;
|
||||
bool flag_negate = false;
|
||||
};
|
||||
|
||||
struct StartupCacheEntry {
|
||||
ServerId sid;
|
||||
struct StartupPropertyEntry {
|
||||
property::PropertyType type = property::PropertyType::PROP_TYPE_UNKNOWN;
|
||||
uint64_t id{0};
|
||||
const property::PropertyDescription* info{&property::undefined_property_description};
|
||||
std::string value;
|
||||
};
|
||||
|
||||
std::deque<std::unique_ptr<StartupPermissionEntry>> permissions;
|
||||
std::deque<std::unique_ptr<StartupPropertyEntry>> properties;
|
||||
};
|
||||
struct DatabaseClient {
|
||||
ClientDbId client_database_id;
|
||||
std::string client_unique_id;
|
||||
|
||||
struct FastPropertyEntry {
|
||||
std::shared_ptr<property::PropertyDescription> type;
|
||||
std::string value;
|
||||
};
|
||||
std::string client_nickname;
|
||||
std::string client_ip;
|
||||
|
||||
class DatabaseHelper {
|
||||
public:
|
||||
static std::shared_ptr<Properties> default_properties_client(std::shared_ptr<Properties> /* properties */, ClientType /* type */);
|
||||
static bool assignDatabaseId(sql::SqlManager *, ServerId id, std::shared_ptr<DataClient>);
|
||||
std::string client_created; /* seconds since epoch */
|
||||
std::string client_last_connected; /* seconds since epoch */
|
||||
std::string client_total_connections;
|
||||
|
||||
explicit DatabaseHelper(sql::SqlManager*);
|
||||
~DatabaseHelper();
|
||||
std::string client_login_name;
|
||||
std::string client_description; /* optional and only given sometimes */
|
||||
};
|
||||
|
||||
void loadStartupCache();
|
||||
size_t cacheBinarySize();
|
||||
void clearStartupCache(ServerId sid = 0);
|
||||
struct FastPropertyEntry {
|
||||
const property::PropertyDescription* type;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
void deleteClient(const std::shared_ptr<VirtualServer>&,ClientDbId);
|
||||
bool validClientDatabaseId(const std::shared_ptr<VirtualServer>&, ClientDbId);
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfo(const std::shared_ptr<VirtualServer>&, const std::deque<ClientDbId>&);
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &, std::deque<std::string>);
|
||||
struct CachedPermissionManager;
|
||||
struct StartupCacheEntry;
|
||||
class DatabaseHelper {
|
||||
public:
|
||||
static std::shared_ptr<PropertyManager> default_properties_client(std::shared_ptr<PropertyManager> /* properties */, ClientType /* type */);
|
||||
static bool assignDatabaseId(sql::SqlManager *, ServerId serverId, std::shared_ptr<DataClient>);
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadClientPermissionManager(const std::shared_ptr<VirtualServer>&, ClientDbId);
|
||||
void saveClientPermissions(const std::shared_ptr<VirtualServer>&, ClientDbId , const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
explicit DatabaseHelper(sql::SqlManager*);
|
||||
~DatabaseHelper();
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
|
||||
void saveChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
void loadStartupCache();
|
||||
size_t cacheBinarySize();
|
||||
void clearStartupCache(ServerId sid = 0);
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
|
||||
void saveGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
void handleServerDelete(ServerId /* server id */);
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadPlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
|
||||
void savePlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
void listDatabaseClients(
|
||||
ServerId /* server id */,
|
||||
const std::optional<int64_t>& offset,
|
||||
const std::optional<int64_t>& limit,
|
||||
void(* /* callback */)(void* /* user argument */, const DatabaseClient& /* client */),
|
||||
void* /* user argument */);
|
||||
|
||||
std::shared_ptr<Properties> loadServerProperties(const std::shared_ptr<VirtualServer>&); //Read and write
|
||||
std::shared_ptr<Properties> loadPlaylistProperties(const std::shared_ptr<VirtualServer>&, PlaylistId); //Read and write
|
||||
std::shared_ptr<Properties> loadChannelProperties(const std::shared_ptr<VirtualServer>&, ChannelId); //Read and write
|
||||
std::shared_ptr<Properties> loadClientProperties(const std::shared_ptr<VirtualServer>&, ClientDbId, ClientType);
|
||||
void deleteClient(const std::shared_ptr<VirtualServer>&,ClientDbId);
|
||||
bool validClientDatabaseId(const std::shared_ptr<VirtualServer>&, ClientDbId);
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfo(const std::shared_ptr<VirtualServer>&, const std::deque<ClientDbId>&);
|
||||
std::deque<std::shared_ptr<ClientDatabaseInfo>> queryDatabaseInfoByUid(const std::shared_ptr<VirtualServer> &, std::deque<std::string>);
|
||||
|
||||
bool deleteGroupPermissions(const std::shared_ptr<VirtualServer>&, GroupId);
|
||||
bool deleteChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
|
||||
bool deletePlaylist(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
|
||||
std::deque<std::unique_ptr<FastPropertyEntry>> query_properties(ServerId /* server */, property::PropertyType /* type */, uint64_t /* id */); /* required for server snapshots */
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadClientPermissionManager(const ServerId&, ClientDbId);
|
||||
void saveClientPermissions(const std::shared_ptr<VirtualServer>&, ClientDbId , const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
|
||||
void tick();
|
||||
private:
|
||||
void loadStartupPermissionCache();
|
||||
void loadStartupPropertyCache();
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
|
||||
void saveChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
|
||||
bool use_startup_cache = false;
|
||||
threads::Mutex startup_lock;
|
||||
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadGroupPermissions(const ServerId& server_id, GroupId, uint8_t /* target */);
|
||||
void saveGroupPermissions(const ServerId&, GroupId, uint8_t /* target */, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
|
||||
sql::SqlManager* sql = nullptr;
|
||||
threads::Mutex permManagerLock;
|
||||
std::deque<CachedPermissionManager*> cachedPermissionManagers;
|
||||
threads::Mutex propsLock;
|
||||
std::deque<CachedProperties*> cachedProperties;
|
||||
};
|
||||
}
|
||||
std::shared_ptr<permission::v2::PermissionManager> loadPlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
|
||||
void savePlaylistPermissions(const std::shared_ptr<VirtualServer>&, PlaylistId, const std::shared_ptr<permission::v2::PermissionManager>& /* permission manager */);
|
||||
|
||||
std::shared_ptr<PropertyManager> loadServerProperties(const std::shared_ptr<VirtualServer>&); //Read and write
|
||||
std::shared_ptr<PropertyManager> loadPlaylistProperties(const std::shared_ptr<VirtualServer>&, PlaylistId); //Read and write
|
||||
std::shared_ptr<PropertyManager> loadChannelProperties(const std::shared_ptr<VirtualServer>&, ChannelId); //Read and write
|
||||
std::shared_ptr<PropertyManager> loadClientProperties(const std::shared_ptr<VirtualServer>&, ClientDbId, ClientType);
|
||||
void updateClientIpAddress(const ServerId& /* server id */, ClientDbId /* target database id */, const std::string& /* ip address */);
|
||||
|
||||
void deleteGroupArtifacts(ServerId, GroupId);
|
||||
bool deleteChannelPermissions(const std::shared_ptr<VirtualServer>&, ChannelId);
|
||||
bool deletePlaylist(const std::shared_ptr<VirtualServer>&, PlaylistId /* playlist id */);
|
||||
std::deque<std::unique_ptr<FastPropertyEntry>> query_properties(ServerId /* server */, property::PropertyType /* type */, uint64_t /* id */); /* required for server snapshots */
|
||||
|
||||
void tick();
|
||||
private:
|
||||
void loadStartupPermissionCache();
|
||||
void loadStartupPropertyCache();
|
||||
|
||||
bool use_startup_cache = false;
|
||||
threads::Mutex startup_lock;
|
||||
std::deque<std::shared_ptr<StartupCacheEntry>> startup_entries;
|
||||
|
||||
sql::SqlManager* sql = nullptr;
|
||||
|
||||
threads::Mutex cached_permission_manager_lock;
|
||||
std::deque<std::unique_ptr<CachedPermissionManager>> cached_permission_managers;
|
||||
|
||||
/* Attention: cached_permission_manager_lock should be locked! */
|
||||
[[nodiscard]] inline std::shared_ptr<permission::v2::PermissionManager> find_cached_permission_manager(ServerId /* server id */, ClientDbId /* client id */);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
//
|
||||
// Created by WolverinDEV on 12/05/2020.
|
||||
//
|
||||
|
||||
#include <files/FileServer.h>
|
||||
#include <files/Config.h>
|
||||
|
||||
#include "./client/ConnectedClient.h"
|
||||
#include "FileServerHandler.h"
|
||||
|
||||
using namespace ts::server;
|
||||
using namespace ts::server::file;
|
||||
|
||||
FileServerHandler::FileServerHandler(ts::server::InstanceHandler *instance) : instance_{instance} {}
|
||||
|
||||
bool FileServerHandler::initialize(std::string &error) {
|
||||
if(!file::initialize(error,
|
||||
serverInstance->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST].value(),
|
||||
serverInstance->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT].as_or<uint16_t>(30303))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
#if 1
|
||||
file::config::ssl_option_supplier = [&]{
|
||||
return this->instance_->sslManager()->web_ssl_options();
|
||||
};
|
||||
#endif
|
||||
|
||||
auto server = file::server();
|
||||
assert(server);
|
||||
|
||||
auto& transfer = server->file_transfer();
|
||||
transfer.callback_transfer_registered = std::bind(&FileServerHandler::callback_transfer_registered, this, std::placeholders::_1);
|
||||
transfer.callback_transfer_started = std::bind(&FileServerHandler::callback_transfer_started, this, std::placeholders::_1);
|
||||
transfer.callback_transfer_finished = std::bind(&FileServerHandler::callback_transfer_finished, this, std::placeholders::_1);
|
||||
|
||||
transfer.callback_transfer_aborted = std::bind(&FileServerHandler::callback_transfer_aborted, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
transfer.callback_transfer_statistics = std::bind(&FileServerHandler::callback_transfer_statistics, this, std::placeholders::_1, std::placeholders::_2);
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileServerHandler::finalize() {
|
||||
file::finalize();
|
||||
}
|
||||
|
||||
void FileServerHandler::callback_transfer_registered(const std::shared_ptr<transfer::Transfer> &transfer) {
|
||||
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
|
||||
if(!server) return; /* well that's bad */
|
||||
|
||||
const auto bytes = transfer->expected_file_size - transfer->file_offset;
|
||||
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
|
||||
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED].increment_by<int64_t>(bytes);
|
||||
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].increment_by<int64_t>(bytes);
|
||||
} else {
|
||||
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED].increment_by<int64_t>(bytes);
|
||||
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(bytes);
|
||||
}
|
||||
|
||||
auto client = server->find_client_by_id(transfer->client_id);
|
||||
if(client && client->getUid() == transfer->client_unique_id) {
|
||||
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
|
||||
client->properties()[property::CLIENT_TOTAL_BYTES_UPLOADED].increment_by<int64_t>(bytes);
|
||||
client->properties()[property::CLIENT_MONTH_BYTES_UPLOADED].increment_by<int64_t>(bytes);
|
||||
} else {
|
||||
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(bytes);
|
||||
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileServerHandler::callback_transfer_aborted(const std::shared_ptr<transfer::Transfer> &transfer,
|
||||
const transfer::TransferStatistics &statistics,
|
||||
const ts::server::file::transfer::TransferError &error) {
|
||||
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
|
||||
if(!server) return; /* well that's bad */
|
||||
|
||||
if(statistics.file_total_size < statistics.file_current_offset)
|
||||
return;
|
||||
|
||||
const int64_t bytes_left = statistics.file_total_size - statistics.file_current_offset;
|
||||
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
|
||||
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED].increment_by<int64_t>(-bytes_left);
|
||||
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED].increment_by<int64_t>(-bytes_left);
|
||||
} else {
|
||||
server->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED].increment_by<int64_t>(-bytes_left);
|
||||
server->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(-bytes_left);
|
||||
}
|
||||
|
||||
auto client = server->find_client_by_id(transfer->client_id);
|
||||
if(client && client->getUid() == transfer->client_unique_id) {
|
||||
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
|
||||
client->properties()[property::CLIENT_TOTAL_BYTES_UPLOADED].increment_by<int64_t>(-bytes_left);
|
||||
client->properties()[property::CLIENT_MONTH_BYTES_UPLOADED].increment_by<int64_t>(-bytes_left);
|
||||
} else {
|
||||
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(-bytes_left);
|
||||
client->properties()[property::CLIENT_MONTH_BYTES_DOWNLOADED].increment_by<int64_t>(-bytes_left);
|
||||
}
|
||||
|
||||
ts::command_builder notify{"notifystatusfiletransfer"};
|
||||
|
||||
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
|
||||
notify.put(0, "size", 0);
|
||||
|
||||
ts::command_result status{};
|
||||
using ErrorType = ts::server::file::transfer::TransferError::Type;
|
||||
switch (error.error_type) {
|
||||
case ErrorType::TRANSFER_TIMEOUT:
|
||||
status.reset(ts::command_result{error::file_transfer_connection_timeout});
|
||||
break;
|
||||
|
||||
case ErrorType::DISK_IO_ERROR:
|
||||
case ErrorType::DISK_TIMEOUT:
|
||||
case ErrorType::DISK_INITIALIZE_ERROR:
|
||||
status.reset(ts::command_result{error::file_io_error});
|
||||
break;
|
||||
|
||||
case ErrorType::UNKNOWN:
|
||||
case ErrorType::NETWORK_IO_ERROR:
|
||||
status.reset(ts::command_result{error::file_connection_lost});
|
||||
break;
|
||||
|
||||
case ErrorType::UNEXPECTED_CLIENT_DISCONNECT:
|
||||
case ErrorType::UNEXPECTED_DISK_EOF:
|
||||
status.reset(ts::command_result{error::file_transfer_interrupted});
|
||||
|
||||
case ErrorType::USER_REQUEST:
|
||||
status.reset(ts::command_result{error::file_transfer_canceled});
|
||||
break;
|
||||
}
|
||||
client->writeCommandResult(notify, status, "status");
|
||||
client->sendCommand(notify);
|
||||
}
|
||||
}
|
||||
|
||||
void FileServerHandler::callback_transfer_statistics(const std::shared_ptr<transfer::Transfer> &transfer,
|
||||
const ts::server::file::transfer::TransferStatistics &statistics) {
|
||||
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
|
||||
if(!server) return; /* well that's bad */
|
||||
|
||||
auto client = server->find_client_by_id(transfer->client_id);
|
||||
if(!client || client->getUid() != transfer->client_unique_id) {
|
||||
/* client not online anymore, but we could still log this as server traffic */
|
||||
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
|
||||
server->getServerStatistics()->logFileTransferIn(statistics.delta_file_bytes_transferred);
|
||||
} else {
|
||||
server->getServerStatistics()->logFileTransferOut(statistics.delta_file_bytes_transferred);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(transfer->direction == transfer::Transfer::DIRECTION_UPLOAD) {
|
||||
client->getConnectionStatistics()->logFileTransferIn(statistics.delta_file_bytes_transferred);
|
||||
} else {
|
||||
client->getConnectionStatistics()->logFileTransferOut(statistics.delta_file_bytes_transferred);
|
||||
}
|
||||
|
||||
if(client->getType() == ClientType::CLIENT_TEAMSPEAK) {
|
||||
return; /* TS3 does not know this notify */
|
||||
}
|
||||
|
||||
ts::command_builder notify{"notifyfiletransferprogress"};
|
||||
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
|
||||
|
||||
notify.put_unchecked(0, "file_bytes_transferred", statistics.file_bytes_transferred);
|
||||
notify.put_unchecked(0, "network_bytes_send", statistics.network_bytes_send);
|
||||
notify.put_unchecked(0, "network_bytes_received", statistics.network_bytes_received);
|
||||
|
||||
notify.put_unchecked(0, "file_start_offset", statistics.file_start_offset);
|
||||
notify.put_unchecked(0, "file_current_offset", statistics.file_current_offset);
|
||||
notify.put_unchecked(0, "file_total_size", statistics.file_total_size);
|
||||
|
||||
notify.put_unchecked(0, "network_current_speed", statistics.current_speed);
|
||||
notify.put_unchecked(0, "network_average_speed", statistics.average_speed);
|
||||
|
||||
client->sendCommand(notify);
|
||||
}
|
||||
|
||||
void FileServerHandler::callback_transfer_started(const std::shared_ptr<transfer::Transfer> &transfer) {
|
||||
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
|
||||
if(!server) return; /* well that's bad */
|
||||
|
||||
auto client = server->find_client_by_id(transfer->client_id);
|
||||
if(!client || client->getUid() != transfer->client_unique_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ts::command_builder notify{"notifyfiletransferstarted"};
|
||||
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
|
||||
client->sendCommand(notify);
|
||||
}
|
||||
|
||||
void FileServerHandler::callback_transfer_finished(const std::shared_ptr<transfer::Transfer> &transfer) {
|
||||
auto server = this->instance_->getVoiceServerManager()->findServerById(transfer->server->server_id());
|
||||
if(!server) {
|
||||
return; /* well that's bad */
|
||||
}
|
||||
|
||||
auto client = server->find_client_by_id(transfer->client_id);
|
||||
if(!client || client->getUid() != transfer->client_unique_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(client->getType() == ClientType::CLIENT_TEAMSPEAK) {
|
||||
return;
|
||||
}
|
||||
|
||||
ts::command_builder notify{"notifystatusfiletransfer"};
|
||||
|
||||
notify.put_unchecked(0, "clientftfid", transfer->client_transfer_id);
|
||||
notify.put(0, "size", transfer->expected_file_size); /* not sure where TeamSpeak counts from */
|
||||
notify.put_unchecked(0, "status", (int) error::file_transfer_complete);
|
||||
notify.put_unchecked(0, "msg", findError(error::file_transfer_complete).message);
|
||||
|
||||
/* TODO: Some stats? */
|
||||
|
||||
client->sendCommand(notify);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <files/FileServer.h>
|
||||
|
||||
#include "./InstanceHandler.h"
|
||||
|
||||
namespace ts::server::file {
|
||||
class FileServerHandler {
|
||||
public:
|
||||
explicit FileServerHandler(InstanceHandler*);
|
||||
|
||||
bool initialize(std::string& /* error */);
|
||||
void finalize();
|
||||
private:
|
||||
InstanceHandler* instance_;
|
||||
|
||||
void callback_transfer_registered(const std::shared_ptr<transfer::Transfer>&);
|
||||
void callback_transfer_started(const std::shared_ptr<transfer::Transfer>&);
|
||||
void callback_transfer_finished(const std::shared_ptr<transfer::Transfer>&);
|
||||
|
||||
void callback_transfer_aborted(const std::shared_ptr<transfer::Transfer>&, const transfer::TransferStatistics&, const transfer::TransferError&);
|
||||
void callback_transfer_statistics(const std::shared_ptr<transfer::Transfer>&, const transfer::TransferStatistics&);
|
||||
};
|
||||
}
|
||||
@@ -1,920 +0,0 @@
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/memtracker.h>
|
||||
#include "Group.h"
|
||||
#include "VirtualServer.h"
|
||||
#include "src/client/ConnectedClient.h"
|
||||
#include "InstanceHandler.h"
|
||||
#include "src/server/file/FileServer.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
using namespace ts;
|
||||
using namespace ts::server;
|
||||
using namespace ts::permission;
|
||||
|
||||
extern InstanceHandler* serverInstance;
|
||||
Group::Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId groupId) : _target(target), _type(type) {
|
||||
memtrack::allocated<Group>(this);
|
||||
this->handle = handle;
|
||||
|
||||
this->_properties = new Properties();
|
||||
this->_properties->register_property_type<property::GroupProperties>();
|
||||
this->setPermissionManager(make_shared<permission::v2::PermissionManager>());
|
||||
|
||||
this->properties()[property::GROUP_ID] = groupId;
|
||||
this->properties()[property::GROUP_TYPE] = type;
|
||||
/*
|
||||
this->_properties->registerProperty("sgid", groupId, PROP_GROUP_INIT_SERVER);
|
||||
this->_properties->registerProperty("cgid", groupId, PROP_GROUP_INIT_CHANNEL);
|
||||
this->_properties->registerProperty("gid", groupId, PROP_PRIVATE_TEMP);
|
||||
|
||||
|
||||
this->_properties->registerProperty("type", (uint8_t) type, PROP_GROUP_INIT);
|
||||
this->_properties->registerProperty("name", "Undefined group #" + to_string(groupId), PROP_GROUP_INIT | PROP_SNAPSHOT);
|
||||
this->_properties->registerProperty("sortid", 0, PROP_GROUP_INIT);
|
||||
this->_properties->registerProperty("savedb", 0, PROP_GROUP_INIT);
|
||||
this->_properties->registerProperty("namemode", 0, PROP_GROUP_INIT);
|
||||
this->_properties->registerProperty("iconid", 0, PROP_GROUP_INIT);
|
||||
|
||||
this->_properties->registerProperty("default_group", false, PROP_PRIVATE_TEMP);
|
||||
*/
|
||||
|
||||
this->_properties->registerNotifyHandler([&](Property& prop){
|
||||
if((prop.type().flags & property::FLAG_SAVE) == 0) return;
|
||||
if(!this->handle) return;
|
||||
|
||||
std::string sql;
|
||||
if(prop.hasDbReference()){
|
||||
sql = "UPDATE `properties` SET `value` = :value WHERE `serverId` = :sid AND `type` = :type AND `id` = :gid AND `key` = :key";
|
||||
} else {
|
||||
prop.setDbReference(true);
|
||||
sql = "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :gid, :key, :value)";
|
||||
}
|
||||
|
||||
sql::command(this->handle->sql, sql,
|
||||
variable{":sid", this->handle->getServerId()}, variable{":type", property::PropertyType::PROP_TYPE_GROUP}, variable{":gid", this->groupId()}, variable{":key", prop.type().name}, variable{":value", prop.value()})
|
||||
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
});
|
||||
}
|
||||
|
||||
void Group::setPermissionManager(const std::shared_ptr<permission::v2::PermissionManager> &manager) {
|
||||
this->_permissions = manager;
|
||||
this->apply_properties_from_permissions();
|
||||
}
|
||||
|
||||
void Group::apply_properties_from_permissions() {
|
||||
{
|
||||
auto permission = this->_permissions->permission_value_flagged(permission::i_icon_id);
|
||||
this->properties()[property::GROUP_ICONID] = permission.has_value ? permission.value : 0;
|
||||
}
|
||||
{
|
||||
auto permission = this->_permissions->permission_value_flagged(permission::i_group_show_name_in_tree);
|
||||
this->properties()[property::GROUP_NAMEMODE] = permission.has_value ? permission.value : 0;
|
||||
}
|
||||
{
|
||||
auto permission = this->_permissions->permission_value_flagged(permission::i_group_sort_id);
|
||||
this->properties()[property::GROUP_SORTID] = permission.has_value ? permission.value : 0;
|
||||
}
|
||||
{
|
||||
auto permission = this->_permissions->permission_value_flagged(permission::b_group_is_permanent);
|
||||
this->properties()[property::GROUP_SAVEDB] = permission.has_value ? permission.value : 0;
|
||||
}
|
||||
}
|
||||
|
||||
Group::~Group() {
|
||||
delete this->_properties;
|
||||
memtrack::freed<Group>(this);
|
||||
}
|
||||
|
||||
GroupManager::GroupManager(const shared_ptr<VirtualServer> &server, sql::SqlManager *sql, std::shared_ptr<GroupManager> root) : server(server), sql(sql), root(std::move(root)) { }
|
||||
|
||||
GroupManager::~GroupManager() {}
|
||||
|
||||
bool GroupManager::loadGroupFormDatabase(GroupId id) {
|
||||
if(id == 0){
|
||||
this->groups.clear();
|
||||
|
||||
auto res = sql::command(this->sql, "SELECT * FROM `groups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).query(&GroupManager::insertGroupFromDb, this);
|
||||
LOG_SQL_CMD(res);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
for(const auto &e : this->groups)
|
||||
if(e->groupId() == id){
|
||||
this->groups.erase(find(this->groups.begin(), this->groups.end(), e));
|
||||
break;
|
||||
}
|
||||
auto res = sql::command(this->sql, "SELECT * FROM `groups` WHERE `serverId` = :sid AND `groupId` = :gid",
|
||||
variable{":sid", this->getServerId()}, variable{":gid", id}).query(&GroupManager::insertGroupFromDb, this);
|
||||
auto fn = LOG_SQL_CMD;
|
||||
fn(res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Group>> GroupManager::availableGroups(bool root) {
|
||||
std::vector<std::shared_ptr<Group>> response;
|
||||
for(const auto& group : this->groups)
|
||||
response.push_back(group);
|
||||
if(root && this->root){
|
||||
auto elm = this->root->availableGroups();
|
||||
for(const auto& e : elm)
|
||||
response.push_back(e);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Group>> GroupManager::availableServerGroups(bool root){
|
||||
std::vector<std::shared_ptr<Group>> response;
|
||||
for(const auto& group : this->groups)
|
||||
if(group->target() == GroupTarget::GROUPTARGET_SERVER)
|
||||
response.push_back(group);
|
||||
if(root && this->root){
|
||||
auto elm = this->root->availableServerGroups();
|
||||
for(const auto& e : elm)
|
||||
response.push_back(e);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Group>> GroupManager::availableChannelGroups(bool root) {
|
||||
std::vector<std::shared_ptr<Group>> response;
|
||||
for(const auto& group : this->groups)
|
||||
if(group->target() == GroupTarget::GROUPTARGET_CHANNEL)
|
||||
response.push_back(group);
|
||||
if(root && this->root){
|
||||
auto elm = this->root->availableChannelGroups(true);
|
||||
for(const auto& e : elm)
|
||||
response.push_back(e);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
int GroupManager::insertGroupFromDb(int count, char **values, char **column) {
|
||||
GroupId groupId = 0;
|
||||
auto target = (GroupTarget) 0xff;
|
||||
auto type = (GroupType) 0xff;
|
||||
std::string targetName;
|
||||
|
||||
for(int index = 0; index < count; index++){
|
||||
if(strcmp(column[index], "target") == 0)
|
||||
target = (GroupTarget) stoll(values[index]);
|
||||
else if(strcmp(column[index], "type") == 0)
|
||||
type = (GroupType) stoll(values[index]);
|
||||
else if(strcmp(column[index], "groupId") == 0)
|
||||
groupId = (GroupType) stoll(values[index]);
|
||||
else if(strcmp(column[index], "displayName") == 0)
|
||||
targetName = values[index];
|
||||
else if(strcmp(column[index], "serverId") == 0);
|
||||
else cerr << "Invalid group table row " << column[index] << endl;
|
||||
}
|
||||
|
||||
if((size_t) groupId == 0 || (size_t) target == 0xff || (size_t) type == 0xff || targetName.empty()) {
|
||||
logCritical(this->getServerId(), "Found invalid group ad database! (GroupId " + to_string(groupId) + ", Target " + to_string(target) + ", Type " + to_string(type) + ", Name '" + targetName + "')");
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
assert(groupId != 0);
|
||||
assert(target != 0xff);
|
||||
assert(type != 0xff);
|
||||
assert(!targetName.empty());
|
||||
*/
|
||||
|
||||
shared_ptr<Group> group = std::make_shared<Group>(this, target, type, groupId);
|
||||
|
||||
/*
|
||||
auto res = sql::command(this->sql, "SELECT `key`, `value` FROM `properties` WHERE `serverId` = :sid AND `type` = :type AND `id` = :gid",
|
||||
variable{":sid", this->getServerId()}, variable{":type", property::PROP_TYPE_GROUP}, variable{":gid", group->groupId()}).query([this](Group* g, int length, char** values, char** columns){
|
||||
string key, value;
|
||||
for(int index = 0; index < length; index++)
|
||||
if(strcmp(columns[index], "key") == 0)
|
||||
key = values[index];
|
||||
else if(strcmp(columns[index], "value") == 0)
|
||||
value = values[index];
|
||||
|
||||
auto info = property::info<property::GroupProperties>(key);
|
||||
if(info == property::GROUP_UNDEFINED) {
|
||||
logError(this->getServerId(), "Invalid property for group: " + key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto prop = g->properties()[info.property];
|
||||
prop.setDbReference(true);
|
||||
prop.value(value, false);
|
||||
return 0;
|
||||
}, group.get());
|
||||
auto print = LOG_SQL_CMD;
|
||||
print(res);
|
||||
*/
|
||||
//FIXME load group properties view database helper (or drop it full because no saved properties)
|
||||
|
||||
group->properties()[property::GROUP_NAME] = targetName;
|
||||
|
||||
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
|
||||
|
||||
debugMessage(this->getServerId(), "Push back group -> " + to_string(group->groupId()) + " - " + group->name());
|
||||
this->groups.push_back(group);
|
||||
|
||||
auto iconId = (IconId) group->icon_id();
|
||||
if(iconId != 0 && serverInstance && serverInstance->getFileServer() && !serverInstance->getFileServer()->iconExists(this->server.lock(), iconId)) {
|
||||
logMessage(this->getServerId(), "[FILE] Missing group icon (" + to_string(iconId) + ").");
|
||||
if(config::server::delete_missing_icon_permissions) group->permissions()->set_permission(permission::i_icon_id, {0, 0}, permission::v2::delete_value, permission::v2::do_nothing);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GroupManager::handleChannelDeleted(const ChannelId& channel_id) {
|
||||
unique_lock cache_lock(this->cacheLock);
|
||||
auto cached_clients = std::vector<shared_ptr<CachedClient>>{this->cachedClients.begin(), this->cachedClients.end()};
|
||||
cache_lock.unlock();
|
||||
|
||||
for(auto& entry : cached_clients) {
|
||||
lock_guard entry_lock(entry->lock);
|
||||
entry->channel_groups.erase(channel_id);
|
||||
}
|
||||
}
|
||||
|
||||
bool GroupManager::isLocalGroup(std::shared_ptr<Group> gr) {
|
||||
return std::find(this->groups.begin(), this->groups.end(), gr) != this->groups.end();
|
||||
}
|
||||
|
||||
std::shared_ptr<Group> GroupManager::defaultGroup(GroupTarget type, bool enforce_property) {
|
||||
threads::MutexLock lock(this->cacheLock);
|
||||
if(this->groups.empty()) return nullptr;
|
||||
|
||||
auto server = this->server.lock();
|
||||
auto id =
|
||||
server ?
|
||||
server->properties()[type == GroupTarget::GROUPTARGET_SERVER ? property::VIRTUALSERVER_DEFAULT_SERVER_GROUP : property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as_save<GroupId>() :
|
||||
serverInstance->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_save<GroupId>();
|
||||
auto group = this->findGroupLocal(id);
|
||||
if(group || enforce_property) return group;
|
||||
|
||||
for(auto elm : this->groups)
|
||||
if(elm->target() == type)
|
||||
return elm;
|
||||
|
||||
return nullptr; //Worst case!
|
||||
}
|
||||
|
||||
std::shared_ptr<Group> GroupManager::findGroup(GroupId groupId) {
|
||||
auto result = this->findGroupLocal(groupId);
|
||||
if(!result && this->root) result = this->root->findGroup(groupId);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<Group> GroupManager::findGroupLocal(GroupId groupId) {
|
||||
for(const auto& elm : this->groups)
|
||||
if(elm->groupId() == groupId) return elm;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<Group>> GroupManager::findGroup(GroupTarget target, std::string name) {
|
||||
vector<shared_ptr<Group>> res;
|
||||
for(const auto &elm : this->groups)
|
||||
if(elm->name() == name && elm->target() == target) res.push_back(elm);
|
||||
if(this->root) {
|
||||
auto r = root->findGroup(target, name);
|
||||
for(const auto &e : r) res.push_back(e);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
ServerId GroupManager::getServerId() {
|
||||
auto l = this->server.lock();
|
||||
return l ? l->getServerId() : 0;
|
||||
}
|
||||
|
||||
std::shared_ptr<Group> GroupManager::createGroup(GroupTarget target, GroupType type, std::string name) {
|
||||
if(type != GROUP_TYPE_NORMAL && this->root) return root->createGroup(target, type, name);
|
||||
|
||||
auto rawId = generateGroupId(this->sql);
|
||||
if(rawId <= 0) {
|
||||
logError(this->getServerId(), "Could not create a new group! ({})", "Could not generate group id");
|
||||
return nullptr;
|
||||
}
|
||||
auto groupId = (GroupId) rawId;
|
||||
auto res = sql::command(this->sql, "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
|
||||
variable{":sid", this->getServerId()}, variable{":gid", groupId}, variable{":target", target}, variable{":type", type}, variable{":name", name}).execute();
|
||||
auto print = LOG_SQL_CMD;
|
||||
print(res);
|
||||
if(!res) return nullptr;
|
||||
|
||||
std::shared_ptr<Group> group = std::make_shared<Group>(this, target, type, groupId);
|
||||
group->properties()[property::GROUP_NAME] = name;
|
||||
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
|
||||
this->groups.push_back(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
GroupId GroupManager::copyGroup(std::shared_ptr<Group> group, GroupType type, std::string name, ServerId targetServerId) {
|
||||
auto group_server = group->handle->getServerId();
|
||||
auto groupId = generateGroupId(this->sql);
|
||||
|
||||
auto print = LOG_SQL_CMD;
|
||||
auto res = sql::command(this->sql, "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant`,`flag_skip`, `flag_negate` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
|
||||
variable{":target", groupId}, variable{":ssid", group_server}, variable{":tsid", targetServerId}, variable{":source", group->groupId()}, variable{":type", SQL_PERM_GROUP}).execute();
|
||||
print(res);
|
||||
|
||||
//Properties not used currently
|
||||
res = sql::command(this->sql, "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, `key`, `value` FROM `properties` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
|
||||
variable{":target", groupId}, variable{":ssid", group_server}, variable{":tsid", targetServerId}, variable{":source", group->groupId()}, variable{":type", property::PropertyType::PROP_TYPE_GROUP}).execute();
|
||||
print(res);
|
||||
|
||||
res = sql::command(this->sql, "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
|
||||
variable{":sid", targetServerId}, variable{":gid", groupId}, variable{":target", group->target()}, variable{":type", type}, variable{":name", name}).execute();
|
||||
print(res);
|
||||
|
||||
if(targetServerId == (this->getServerId()))
|
||||
this->loadGroupFormDatabase(groupId);
|
||||
else if(this->root)
|
||||
this->root->loadGroupFormDatabase(groupId);
|
||||
return groupId;
|
||||
}
|
||||
|
||||
bool GroupManager::copyGroupPermissions(const shared_ptr<Group> &source, const shared_ptr<Group> &target) {
|
||||
auto targetServer = target->handle->getServerId();
|
||||
auto sourceServer = source->handle->getServerId();
|
||||
|
||||
auto res = sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type AND `id` = :id", variable{":sid", targetServer}, variable{":type", SQL_PERM_GROUP}, variable{":id", target->groupId()}).execute();
|
||||
LOG_SQL_CMD(res);
|
||||
|
||||
res = sql::command(this->sql, "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`) SELECT :tsid AS `serverId`, `type`, :target AS `id`, 0 AS `channelId`, `permId`, `value`,`grant` FROM `permissions` WHERE `serverId` = :ssid AND `type` = :type AND `id` = :source",
|
||||
variable{":ssid", sourceServer}, variable{":tsid", targetServer}, variable{":type", SQL_PERM_GROUP}, variable{":source", source->groupId()}, variable{":target", target->groupId()}).execute();
|
||||
|
||||
target->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(target->handle->server.lock(), target->groupId()));
|
||||
LOG_SQL_CMD(res);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GroupManager::reloadGroupPermissions(std::shared_ptr<Group> group) {
|
||||
if(!isLocalGroup(group)){
|
||||
if(this->root) return this->root->reloadGroupPermissions(group);
|
||||
return false;
|
||||
}
|
||||
|
||||
group->setPermissionManager(serverInstance->databaseHelper()->loadGroupPermissions(this->server.lock(), group->groupId()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GroupManager::renameGroup(std::shared_ptr<Group> group, std::string name) {
|
||||
if(!isLocalGroup(group)){
|
||||
if(this->root) return this->root->renameGroup(group, name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// CREATE_TABLE("groups", "`serverId` INT NOT NULL, `groupId` INTEGER, `target` INT, `type` INT, `displayName` TEXT");
|
||||
group->properties()[property::GROUP_NAME] = name;
|
||||
sql::command(this->sql, "UPDATE `groups` SET `displayName` = :name WHERE `serverId` = :sid AND `groupId` = :gid AND `target` = :target",
|
||||
variable{":name", name}, variable{":gid", group->groupId()}, variable{":target", group->target()}, variable{":sid", this->getServerId()}).executeLater().waitAndGetLater(LOG_SQL_CMD, {-1, "failed"});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GroupManager::deleteAllGroups() {
|
||||
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).execute());
|
||||
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid", variable{":sid", this->getServerId()}).execute());
|
||||
LOG_SQL_CMD(sql::command(this->sql, "DELETE FROM `permissions` WHERE `serverId` = :sid AND `type` = :type", variable{":sid", this->getServerId()}, variable{":type", SQL_PERM_GROUP}).execute());
|
||||
{
|
||||
lock_guard cache_lock(this->cacheLock);
|
||||
for(const auto& entry : this->cachedClients) {
|
||||
lock_guard entry_lock(entry->lock);
|
||||
entry->server_groups.clear();
|
||||
entry->channel_groups.clear();
|
||||
}
|
||||
}
|
||||
this->groups.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GroupManager::deleteGroup(std::shared_ptr<Group> group) {
|
||||
if(!isLocalGroup(group)){
|
||||
if(this->root) return this->root->deleteGroup(group);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->groups.erase(std::find(this->groups.begin(), this->groups.end(), group));
|
||||
|
||||
/* erase the group out of our cache */
|
||||
{
|
||||
lock_guard cache_lock(this->cacheLock);
|
||||
for(auto& entry : this->cachedClients) {
|
||||
lock_guard entry_lock(entry->lock);
|
||||
|
||||
entry->server_groups.erase(std::remove_if(entry->server_groups.begin(), entry->server_groups.end(), [&](const std::shared_ptr<GroupAssignment>& group_assignment) {
|
||||
return group_assignment->group == group;
|
||||
}), entry->server_groups.end());
|
||||
|
||||
for(auto it = entry->channel_groups.begin(); it != entry->channel_groups.end();) {
|
||||
if(it->second->group == group)
|
||||
it = entry->channel_groups.erase(it);
|
||||
else
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool flag_sql = false;
|
||||
auto res = sql::command(this->sql, "DELETE FROM `groups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->getServerId()}, variable{":gid", group->groupId()}).execute();
|
||||
LOG_SQL_CMD(res);
|
||||
flag_sql |= !res;
|
||||
|
||||
res = sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `groupId` = :gid", variable{":sid", this->getServerId()}, variable{":gid", group->groupId()}).execute();
|
||||
LOG_SQL_CMD(res);
|
||||
flag_sql |= !res;
|
||||
|
||||
flag_sql &= serverInstance->databaseHelper()->deleteGroupPermissions(this->server.lock(), group->groupId());
|
||||
if(flag_sql)
|
||||
logError(this->getServerId(), "Could not delete group {} ({}) from database. May leader to invalid data", group->name(), group->groupId());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t GroupManager::generateGroupId(sql::SqlManager* sql) {
|
||||
int64_t hightestGroupId = 0;
|
||||
sql::command(sql, "SELECT `groupId` FROM `groups` ORDER BY `groupId` DESC LIMIT 1").query([](int64_t* ptr, int, char** values, char**){
|
||||
*ptr = stoul(values[0]);
|
||||
return 0;
|
||||
}, &hightestGroupId);
|
||||
return hightestGroupId + 1;
|
||||
}
|
||||
|
||||
std::deque<property::ClientProperties> GroupManager::update_server_group_property(const shared_ptr<server::ConnectedClient> &client, bool channel_lock, const std::shared_ptr<BasicChannel>& channel) {
|
||||
std::deque<property::ClientProperties> changed;
|
||||
|
||||
//Server groups
|
||||
{
|
||||
auto groups = this->getServerGroups(client->getClientDatabaseId(), client->getType());
|
||||
string group_string;
|
||||
for(const auto& group : groups)
|
||||
if(group_string.empty()) group_string += to_string(group->group->groupId());
|
||||
else group_string += "," + to_string(group->group->groupId());
|
||||
if(client->properties()[property::CLIENT_SERVERGROUPS] != group_string) {
|
||||
client->properties()[property::CLIENT_SERVERGROUPS] = group_string;
|
||||
changed.push_back(property::CLIENT_SERVERGROUPS);
|
||||
|
||||
unique_lock chan_lock(client->channel_lock, defer_lock);
|
||||
if(channel_lock)
|
||||
chan_lock.lock();
|
||||
|
||||
client->cached_server_groups.clear();
|
||||
client->cached_server_groups.reserve(groups.size());
|
||||
for(const auto& group : groups)
|
||||
client->cached_server_groups.push_back(group->group->groupId());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Channel groups
|
||||
if(channel){
|
||||
shared_ptr<GroupAssignment> group = this->getChannelGroup(client->getClientDatabaseId(), channel, true);
|
||||
if(client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] != group->channelId) {
|
||||
client->properties()[property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID] = group->channelId;
|
||||
changed.push_back(property::CLIENT_CHANNEL_GROUP_INHERITED_CHANNEL_ID);
|
||||
}
|
||||
if(client->properties()[property::CLIENT_CHANNEL_GROUP_ID] != group->group->groupId()) {
|
||||
client->properties()[property::CLIENT_CHANNEL_GROUP_ID] = group->group->groupId();
|
||||
changed.push_back(property::CLIENT_CHANNEL_GROUP_ID);
|
||||
|
||||
unique_lock chan_lock(client->channel_lock, defer_lock);
|
||||
if(channel_lock)
|
||||
chan_lock.lock();
|
||||
|
||||
client->cached_channel_group = group->group->groupId();
|
||||
}
|
||||
}
|
||||
|
||||
if(!changed.empty())
|
||||
client->join_state_id++; /* groups have changed :) */
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
void GroupManager::cleanupAssignments(ClientDbId client) {
|
||||
if(this->root)
|
||||
this->root->cleanupAssignments(client);
|
||||
|
||||
for(const auto& assignment : this->getAssignedServerGroups(client)) {
|
||||
if(!assignment->group->is_permanent())
|
||||
this->removeServerGroup(client, assignment->group);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GroupManager::enableCache(const ClientDbId& client_database_id) {
|
||||
if(this->root)
|
||||
this->root->enableCache(client_database_id);
|
||||
|
||||
unique_lock cache_lock(this->cacheLock);
|
||||
/* test if we're already having the client */
|
||||
for(auto& entry : this->cachedClients)
|
||||
if(entry->client_database_id == client_database_id) {
|
||||
entry->use_count++;
|
||||
return; /* client already cached, no need to cache client */
|
||||
}
|
||||
|
||||
auto entry = std::make_shared<CachedClient>();
|
||||
entry->client_database_id = client_database_id;
|
||||
entry->use_count++;
|
||||
this->cachedClients.push_back(entry);
|
||||
|
||||
lock_guard client_cache_lock(entry->lock); /* lock the client because we're currently loading the cache. */
|
||||
cache_lock.unlock();
|
||||
|
||||
auto res = sql::command(this->sql, "SELECT `groupId`, `channelId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid", variable{":sid", this->getServerId()}, variable{":cldbid", client_database_id})
|
||||
.query([&](int length, std::string* value, std::string* column) {
|
||||
shared_ptr<Group> group = nullptr;
|
||||
time_point<system_clock> until;
|
||||
ChannelId channelId = 0;
|
||||
|
||||
for(int index = 0; index < length; index++){
|
||||
try {
|
||||
if(column[index] == "groupId"){
|
||||
group = this->findGroup(stoll(value[index]));
|
||||
} else if(column[index] == "until"){
|
||||
until = time_point<system_clock>() + milliseconds(stoll(value[index]));
|
||||
} else if(column[index] == "channelId"){
|
||||
channelId = stoll(value[index]);
|
||||
} else {
|
||||
logError(this->getServerId(), "Unknown column in group assignment query: {}", column[index]);
|
||||
continue;
|
||||
}
|
||||
} catch(std::exception& ex) {
|
||||
logError(this->getServerId(), "Failed to load group assignment from database for client {}. Column {} contains an invalid value: {}", client_database_id, column[index], value[index]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if(!group) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto assignment = std::make_shared<GroupAssignment>();
|
||||
assignment->group = group;
|
||||
assignment->until = until;
|
||||
assignment->parent = &*entry;
|
||||
assignment->channelId = channelId;
|
||||
assignment->server = this->getServerId();
|
||||
|
||||
if(channelId == 0)
|
||||
entry->server_groups.push_back(assignment);
|
||||
else
|
||||
entry->channel_groups[channelId] = assignment;
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
//FIXME: This method till get far more often then it should be. We should add a flag if the group cache is loaded for each std::shared_ptr<ConnectedClient> instance
|
||||
void GroupManager::disableCache(const ClientDbId& client_database_id) {
|
||||
if(this->root) {
|
||||
this->root->disableCache(client_database_id);
|
||||
}
|
||||
|
||||
lock_guard cache_lock(this->cacheLock);
|
||||
this->cachedClients.erase(std::remove_if(this->cachedClients.begin(), this->cachedClients.end(), [&](const std::shared_ptr<CachedClient>& client) {
|
||||
if(client->client_database_id != client_database_id)
|
||||
return false;
|
||||
|
||||
lock_guard client_lock{client->lock};
|
||||
return (--client->use_count) == 0;
|
||||
}), this->cachedClients.end());
|
||||
}
|
||||
|
||||
void GroupManager::clearCache() {
|
||||
if(this->root) this->root->clearCache();
|
||||
|
||||
lock_guard lock(this->cacheLock);
|
||||
this->cachedClients.clear();
|
||||
}
|
||||
|
||||
bool GroupManager::isClientCached(const ClientDbId& client_database_id) {
|
||||
return this->resolve_cached_client(client_database_id) == nullptr;
|
||||
}
|
||||
|
||||
typedef std::vector<std::shared_ptr<GroupMember>> ResList;
|
||||
std::vector<std::shared_ptr<GroupMember>> GroupManager::listGroupMembers(std::shared_ptr<Group> group, bool names) { //TODO juse inner join only on names = true
|
||||
if(!isLocalGroup(group)){
|
||||
if(this->root) return this->root->listGroupMembers(group, names);
|
||||
return {};
|
||||
}
|
||||
ResList result;
|
||||
|
||||
sql::command(this->sql,
|
||||
"SELECT assignedGroups.cldbid, assignedGroups.channelId, assignedGroups.until, clients.clientUid, clients.lastName FROM assignedGroups INNER JOIN clients ON (clients.cldbid = assignedGroups.cldbid AND clients.serverId = assignedGroups.serverId) WHERE assignedGroups.`serverId` = :sid AND `groupId` = :gid;",
|
||||
variable{":sid", this->getServerId()}, variable{":gid", group->groupId()})
|
||||
.query([&](ResList* list, int columnCount, char** values, char** columnName){
|
||||
std::shared_ptr<GroupMember> member = std::make_shared<GroupMember>();
|
||||
member->displayName = "undefined";
|
||||
member->uid = "undefined";
|
||||
for(int index = 0; index < columnCount; index++){
|
||||
if(values[index] == nullptr) {
|
||||
logError(this->getServerId(), string() + "Invalid value at " + columnName[index]);
|
||||
continue;
|
||||
}
|
||||
if(strcmp(columnName[index], "cldbid") == 0)
|
||||
member->cldbId = stoll(values[index]);
|
||||
else if(strcmp(columnName[index], "until") == 0)
|
||||
member->until = time_point<system_clock>() + milliseconds(stoll(values[index]));
|
||||
else if(strcmp(columnName[index], "clientUid") == 0)
|
||||
member->uid = values[index];
|
||||
else if(strcmp(columnName[index], "lastName") == 0)
|
||||
member->displayName = values[index];
|
||||
else if(strcmp(columnName[index], "channelId") == 0)
|
||||
member->channelId = stoll(values[index]);
|
||||
else cerr << "Invalid column name " << columnName[index] << endl;
|
||||
}
|
||||
list->push_back(member);
|
||||
return 0;
|
||||
}, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
vector<shared_ptr<GroupAssignment>> GroupManager::listGroupAssignments(ClientDbId cldbId) {
|
||||
vector<std::shared_ptr<GroupAssignment>> result;
|
||||
sql::result res;
|
||||
|
||||
auto cached = resolve_cached_client(cldbId);
|
||||
if(cached) {
|
||||
{
|
||||
lock_guard lock{cached->lock};
|
||||
for(const auto &serverGroup : cached->server_groups)
|
||||
result.push_back(serverGroup);
|
||||
for(auto& channelGroup : cached->channel_groups)
|
||||
result.push_back(channelGroup.second);
|
||||
}
|
||||
|
||||
if(this->root){
|
||||
auto append = this->root->listGroupAssignments(cldbId);
|
||||
for(const auto &elm : append)
|
||||
result.push_back(elm);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
res = sql::command(this->sql, "SELECT `groupId`, `until`, `channelId` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid", variable{":sid", this->getServerId()}, variable{":cldbid", cldbId})
|
||||
.query([&](int length, char** value, char** column){
|
||||
shared_ptr<Group> group = nullptr;
|
||||
time_point<system_clock> until;
|
||||
uint64_t channelId = 0;
|
||||
|
||||
for(int index = 0; index < length; index++){
|
||||
if(value[index] == nullptr) {
|
||||
logError(this->getServerId(), string() + "Invalid value at " + column[index]);
|
||||
continue;
|
||||
}
|
||||
if(strcmp(column[index], "groupId") == 0){
|
||||
group = this->findGroup(stoll(value[index]));
|
||||
} else if(strcmp(column[index], "until") == 0){
|
||||
until = time_point<system_clock>() + milliseconds(stoll(value[index]));
|
||||
} else if(strcmp(column[index], "channelId") == 0){
|
||||
channelId = stoll(value[index]);
|
||||
} else cerr << "Invalid column " << column[index] << endl;
|
||||
}
|
||||
if(!group)
|
||||
return 0;
|
||||
|
||||
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
|
||||
assignment->parent = nullptr;
|
||||
assignment->group = group;
|
||||
assignment->until = until;
|
||||
assignment->channelId = channelId;
|
||||
assignment->server = this->getServerId();
|
||||
result.push_back(assignment);
|
||||
return 0;
|
||||
});
|
||||
(LOG_SQL_CMD)(res);
|
||||
|
||||
if(this->root){
|
||||
auto append = this->root->listGroupAssignments(cldbId);
|
||||
for(const auto &elm : append)
|
||||
result.push_back(elm);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<CachedClient> GroupManager::resolve_cached_client(ClientDbId client_database_id) {
|
||||
{
|
||||
lock_guard lock(this->cacheLock);
|
||||
for(auto& entry : this->cachedClients)
|
||||
if(entry->client_database_id == client_database_id)
|
||||
return entry;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<GroupAssignment>> GroupManager::getAssignedServerGroups(ClientDbId cldbid) {
|
||||
auto cached = this->resolve_cached_client(cldbid);
|
||||
sql::result res;
|
||||
std::vector<std::shared_ptr<GroupAssignment>> result;
|
||||
if(this->root) {
|
||||
auto client_groups = this->root->getAssignedServerGroups(cldbid);
|
||||
result.insert(result.begin(), client_groups.begin(), client_groups.end());
|
||||
}
|
||||
|
||||
if(cached) {
|
||||
lock_guard cache_lock{cached->lock};
|
||||
result.insert(result.end(), cached->server_groups.begin(), cached->server_groups.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
debugMessage(this->getServerId(), "Query client groups for client {} on server {}.", cldbid, this->getServerId());
|
||||
res = sql::command(this->sql, "SELECT `groupId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = 0", variable{":sid", this->getServerId()}, variable{":cldbid", cldbid}).query([&](int length, char** value, char** column){
|
||||
shared_ptr<Group> group = nullptr;
|
||||
time_point<system_clock> until;
|
||||
|
||||
for(int index = 0; index < length; index++) {
|
||||
if(value[index] == nullptr) {
|
||||
logError(this->getServerId(), string() + "Invalid value at " + column[index]);
|
||||
continue;
|
||||
}
|
||||
if(strcmp(column[index], "groupId") == 0 && value[index] != nullptr){
|
||||
group = this->findGroup(stoll(value[index]));
|
||||
} else if(strcmp(column[index], "until") == 0){
|
||||
until = time_point<system_clock>() + milliseconds(stoll(value[index] == nullptr ? "0" : value[index]));
|
||||
} else cerr << "Invalid column " << column[index] << endl;
|
||||
}
|
||||
if(!group)
|
||||
return 0;
|
||||
|
||||
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
|
||||
assignment->parent = nullptr;
|
||||
assignment->group = group;
|
||||
assignment->until = until;
|
||||
assignment->server = this->getServerId();
|
||||
result.push_back(assignment);
|
||||
return 0;
|
||||
});
|
||||
LOG_SQL_CMD(res);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<GroupAssignment>> GroupManager::getServerGroups(ClientDbId cldbid, server::ClientType type) {
|
||||
auto result = this->getAssignedServerGroups(cldbid);
|
||||
if(result.empty()) return this->defaultServerGroupGroupAssignments(cldbid, type);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<GroupAssignment>> GroupManager::defaultServerGroupGroupAssignments(ClientDbId client, ClientType type) {
|
||||
std::vector<std::shared_ptr<GroupAssignment>> result;
|
||||
if(type == ClientType::CLIENT_QUERY && this->root) {
|
||||
auto root = this->root->defaultServerGroupGroupAssignments(client, type);
|
||||
result.insert(result.begin(), root.begin(), root.end());
|
||||
} else if(type == ClientType::CLIENT_MUSIC) {
|
||||
threads::MutexLock lock(this->cacheLock);
|
||||
|
||||
auto server = this->server.lock();
|
||||
auto id =
|
||||
server ?
|
||||
server->properties()[property::VIRTUALSERVER_DEFAULT_MUSIC_GROUP].as_save<GroupId>() :
|
||||
serverInstance->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_save<GroupId>();
|
||||
auto group = this->findGroupLocal(id);
|
||||
if(group) {
|
||||
result.push_back(std::make_shared<GroupAssignment>(nullptr, this->getServerId(), 0, group, time_point<system_clock>()));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
result.push_back(std::make_shared<GroupAssignment>(nullptr, this->getServerId(), 0, this->defaultGroup(GroupTarget::GROUPTARGET_SERVER), time_point<system_clock>()));
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<GroupAssignment> GroupManager::getChannelGroupExact(ClientDbId cldbId, const std::shared_ptr<BasicChannel>& channel, bool assign_default) {
|
||||
auto cached = resolve_cached_client(cldbId);
|
||||
if(cached) {
|
||||
lock_guard cache_lock(cached->lock);
|
||||
if(cached->channel_groups.count(channel->channelId()) > 0) {
|
||||
return cached->channel_groups[channel->channelId()];
|
||||
} else
|
||||
return assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<GroupAssignment> result;
|
||||
auto res = sql::command(this->sql, "SELECT `groupId`, `until` FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = :chid", variable{":sid", this->getServerId()}, variable{":cldbid", cldbId}, variable{":chid", channel->channelId()}).query([&](int length, char** value, char** column){
|
||||
shared_ptr<Group> group = nullptr;
|
||||
time_point<system_clock> until;
|
||||
|
||||
for(int index = 0; index < length; index++){
|
||||
if(value[index] == nullptr) {
|
||||
logError(this->getServerId(), string() + "Invalid value at " + column[index]);
|
||||
continue;
|
||||
}
|
||||
if(strcmp(column[index], "groupId") == 0){
|
||||
group = this->findGroup(stoll(value[index]));
|
||||
} else if(strcmp(column[index], "until") == 0){
|
||||
until = time_point<system_clock>() + milliseconds(stoll(value[index]));
|
||||
} else cerr << "Invalid column " << column[index] << endl;
|
||||
}
|
||||
if(!group)
|
||||
return 0;
|
||||
|
||||
shared_ptr<GroupAssignment> assignment = std::make_shared<GroupAssignment>();
|
||||
assignment->parent = nullptr;
|
||||
assignment->group = group;
|
||||
assignment->until = until;
|
||||
assignment->server = this->getServerId();
|
||||
assignment->channelId = channel->channelId();
|
||||
result = std::move(assignment);
|
||||
return 0;
|
||||
});
|
||||
(LOG_SQL_CMD)(res);
|
||||
|
||||
return !result && assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : result;
|
||||
}
|
||||
|
||||
std::shared_ptr<GroupAssignment> GroupManager::getChannelGroup(ClientDbId cldbId, const shared_ptr<BasicChannel> &channel, bool assign_default) {
|
||||
shared_ptr<GroupAssignment> group;
|
||||
std::shared_ptr<BasicChannel> inheritance_channel = channel;
|
||||
while(inheritance_channel && !group) {
|
||||
group = this->getChannelGroupExact(cldbId, inheritance_channel, false);
|
||||
if(!group) {
|
||||
auto inheritance = inheritance_channel->permissions()->permission_value_flagged(permission::b_channel_group_inheritance_end);
|
||||
if(inheritance.has_value && inheritance.value == 1)
|
||||
break;
|
||||
|
||||
inheritance_channel = inheritance_channel->parent();
|
||||
}
|
||||
}
|
||||
return !group && assign_default ? this->defaultChannelGroupAssignment(cldbId, channel) : group;
|
||||
}
|
||||
|
||||
std::shared_ptr<GroupAssignment> GroupManager::defaultChannelGroupAssignment(ClientDbId cldbId, const std::shared_ptr<BasicChannel> &channel) {
|
||||
return std::make_shared<GroupAssignment>(nullptr, this->getServerId(), channel->channelId(), this->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL), time_point<system_clock>());
|
||||
}
|
||||
|
||||
void GroupManager::addServerGroup(ClientDbId cldbId, std::shared_ptr<Group> group, time_point<system_clock> until) {
|
||||
/*
|
||||
if(!this->isLocalGroup(group)) {
|
||||
if(this->root) this->root->addServerGroup(cldbId, group, until);
|
||||
return;
|
||||
}
|
||||
if(hasServerGroup(cldbId, group)) return;
|
||||
*/
|
||||
|
||||
auto cached = resolve_cached_client(cldbId);
|
||||
if(cached) {
|
||||
lock_guard cache_lock(cached->lock);
|
||||
cached->server_groups.push_back(std::make_shared<GroupAssignment>(cached.get(), this->getServerId(), 0, group, until));
|
||||
}
|
||||
|
||||
sql::command(this->sql, "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)",
|
||||
variable{":sid", this->getServerId()},
|
||||
variable{":cldbid", cldbId},
|
||||
variable{":gid", group->groupId()},
|
||||
variable{":chid", 0},
|
||||
variable{":until", time_point_cast<milliseconds>(until).time_since_epoch().count()})
|
||||
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
}
|
||||
|
||||
void GroupManager::removeServerGroup(ClientDbId cldbId, std::shared_ptr<Group> group) {
|
||||
if(!this->hasServerGroupAssigned(cldbId, group)) return;
|
||||
auto cached = resolve_cached_client(cldbId);
|
||||
if(cached) {
|
||||
lock_guard cache_lock(cached->lock);
|
||||
cached->server_groups.erase(std::remove_if(cached->server_groups.begin(), cached->server_groups.end(), [&](const std::shared_ptr<GroupAssignment>& group_assignment) {
|
||||
return group_assignment->group == group;
|
||||
}), cached->server_groups.end());
|
||||
}
|
||||
|
||||
sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `groupId` = :gid AND `channelId` = :chid",
|
||||
variable{":sid", this->getServerId()},
|
||||
variable{":cldbid", cldbId},
|
||||
variable{":gid", group->groupId()},
|
||||
variable{":chid", 0})
|
||||
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
}
|
||||
|
||||
void GroupManager::setChannelGroup(ClientDbId cldbId, std::shared_ptr<Group> group, std::shared_ptr<BasicChannel> channel, time_point<system_clock> until) {
|
||||
auto old_group = getChannelGroupExact(cldbId, channel, false);
|
||||
if(old_group) {
|
||||
if(old_group->group == group) return;
|
||||
} else if(!group) return;
|
||||
|
||||
auto default_group = !group || group == this->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL);
|
||||
auto cached = resolve_cached_client(cldbId);
|
||||
if(cached) {
|
||||
lock_guard cache_lock(cached->lock);
|
||||
if(default_group)
|
||||
cached->channel_groups.erase(channel->channelId());
|
||||
else
|
||||
cached->channel_groups[channel->channelId()] = std::make_shared<GroupAssignment>(cached.get(), this->getServerId(), channel->channelId(), group, until);
|
||||
}
|
||||
|
||||
sql::command(this->sql, "DELETE FROM `assignedGroups` WHERE `serverId` = :sid AND `cldbid` = :cldbid AND `channelId` = :chid",
|
||||
variable{":sid", this->getServerId()},
|
||||
variable{":cldbid", cldbId},
|
||||
variable{":chid", channel->channelId()})
|
||||
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
|
||||
if(!default_group) {
|
||||
sql::command(this->sql, "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)",
|
||||
variable{":sid", this->getServerId()},
|
||||
variable{":cldbid", cldbId},
|
||||
variable{":gid", group->groupId()},
|
||||
variable{":chid", channel->channelId()},
|
||||
variable{":until", time_point_cast<milliseconds>(until).time_since_epoch().count()})
|
||||
.executeLater().waitAndGetLater(LOG_SQL_CMD, {1, "future failed"});
|
||||
}
|
||||
}
|
||||
+1
-217
@@ -12,225 +12,9 @@
|
||||
#include <sql/SqlQuery.h>
|
||||
|
||||
namespace ts {
|
||||
namespace server {
|
||||
class VirtualServer;
|
||||
class ConnectedClient;
|
||||
}
|
||||
|
||||
struct CachedClient;
|
||||
class GroupManager;
|
||||
class Group;
|
||||
|
||||
enum GroupType {
|
||||
GROUP_TYPE_TEMPLATE,
|
||||
GROUP_TYPE_NORMAL,
|
||||
GROUP_TYPE_QUERY //Only aplies for servergroups
|
||||
};
|
||||
|
||||
enum GroupTarget {
|
||||
GROUPTARGET_SERVER,
|
||||
GROUPTARGET_CHANNEL
|
||||
};
|
||||
|
||||
enum GroupNameMode {
|
||||
GROUP_NAMEMODE_HIDDEN,
|
||||
GROUP_NAMEMODE_BEFORE,
|
||||
GROUP_NAMEMODE_BEHIND
|
||||
};
|
||||
}
|
||||
|
||||
DEFINE_TRANSFORMS(ts::GroupType, uint8_t);
|
||||
DEFINE_TRANSFORMS(ts::GroupTarget, uint8_t);
|
||||
DEFINE_TRANSFORMS(ts::GroupNameMode, uint8_t);
|
||||
|
||||
namespace ts {
|
||||
struct GroupMember {
|
||||
std::string uid;
|
||||
uint64_t cldbId;
|
||||
uint64_t channelId;
|
||||
std::string displayName;
|
||||
std::chrono::time_point<std::chrono::system_clock> until;
|
||||
};
|
||||
struct GroupAssignment {
|
||||
GroupAssignment() {}
|
||||
GroupAssignment(CachedClient *parent, ServerId server, uint64_t channelId, const std::shared_ptr<Group> &group, const std::chrono::time_point<std::chrono::system_clock> &until) : parent(parent), server(server), channelId(channelId), group(group), until(until) {}
|
||||
|
||||
CachedClient* parent = nullptr; //Could be null!
|
||||
|
||||
ServerId server = 0;
|
||||
uint64_t channelId = 0;
|
||||
std::shared_ptr<Group> group;
|
||||
std::chrono::time_point<std::chrono::system_clock> until;
|
||||
|
||||
inline bool isPermanent(){ return std::chrono::time_point_cast<std::chrono::milliseconds>(until).time_since_epoch().count() == 0; }
|
||||
};
|
||||
|
||||
struct CachedClient {
|
||||
ClientDbId client_database_id;
|
||||
|
||||
std::vector<std::shared_ptr<GroupAssignment>> server_groups;
|
||||
std::map<ts::ChannelId, std::shared_ptr<GroupAssignment>> channel_groups;
|
||||
|
||||
size_t use_count = 0;
|
||||
|
||||
std::mutex lock; /* never lock this lock before the general client cache! */
|
||||
};
|
||||
|
||||
class Group {
|
||||
friend class GroupManager;
|
||||
public:
|
||||
Group(GroupManager* handle, GroupTarget target, GroupType type, GroupId groupId);
|
||||
~Group();
|
||||
|
||||
std::shared_ptr<permission::v2::PermissionManager> permissions(){ return this->_permissions; }
|
||||
Properties& properties(){ return *this->_properties; }
|
||||
|
||||
GroupNameMode nameMode(){ return (GroupNameMode) (uint8_t) properties()[property::GROUP_NAMEMODE]; }
|
||||
|
||||
std::string name(){ return properties()[property::GROUP_NAME]; }
|
||||
|
||||
GroupId groupId(){ return properties()[property::GROUP_ID]; }
|
||||
|
||||
GroupTarget target() { return _target; }
|
||||
GroupType type() { return _type; }
|
||||
|
||||
void apply_properties_from_permissions();
|
||||
|
||||
inline permission::PermissionValue updateType() {
|
||||
auto permission_manager = this->permissions(); /* copy the manager */
|
||||
assert(permission_manager);
|
||||
const auto data = permission_manager->permission_value_flagged(permission::i_group_auto_update_type);
|
||||
return data.has_value ? data.value : 0;
|
||||
}
|
||||
|
||||
inline bool permission_granted(const permission::PermissionType& permission, const permission::v2::PermissionFlaggedValue& granted_value, bool require_granted_value) {
|
||||
auto permission_manager = this->permissions(); /* copy the manager */
|
||||
assert(permission_manager);
|
||||
const auto data = permission_manager->permission_value_flagged(permission);
|
||||
if(!data.has_value) {
|
||||
return !require_granted_value || granted_value.has_value;
|
||||
}
|
||||
if(!granted_value.has_value) {
|
||||
return false;
|
||||
}
|
||||
if(data.value == -1) {
|
||||
return granted_value.value == -1;
|
||||
}
|
||||
return granted_value.value >= data.value;
|
||||
}
|
||||
|
||||
inline bool is_permanent() {
|
||||
auto permission_manager = this->permissions(); /* copy the manager */
|
||||
assert(permission_manager);
|
||||
const auto data = permission_manager->permission_value_flagged(permission::b_group_is_permanent);
|
||||
return data.has_value ? data.value == 1 : false;
|
||||
}
|
||||
|
||||
inline IconId icon_id() {
|
||||
auto permission_manager = this->permissions(); /* copy the manager */
|
||||
assert(permission_manager);
|
||||
const auto data = permission_manager->permission_value_flagged(permission::i_icon_id);
|
||||
return data.has_value ? data.value : 0;
|
||||
}
|
||||
private:
|
||||
void setPermissionManager(const std::shared_ptr<permission::v2::PermissionManager>& manager);
|
||||
|
||||
GroupManager* handle;
|
||||
std::shared_ptr<permission::v2::PermissionManager> _permissions;
|
||||
Properties* _properties;
|
||||
GroupTarget _target;
|
||||
GroupType _type;
|
||||
};
|
||||
|
||||
class GroupManager {
|
||||
friend class ServerChannelTree;
|
||||
friend class Group;
|
||||
friend class server::VirtualServer;
|
||||
public:
|
||||
static int64_t generateGroupId(sql::SqlManager* sql);
|
||||
|
||||
GroupManager(const std::shared_ptr<server::VirtualServer> &, sql::SqlManager *, std::shared_ptr<GroupManager> root = nullptr);
|
||||
~GroupManager();
|
||||
|
||||
bool loadGroupFormDatabase(GroupId id = 0);
|
||||
|
||||
std::vector<std::shared_ptr<Group>> availableGroups(bool root = true);
|
||||
std::vector<std::shared_ptr<Group>> availableServerGroups(bool root = true);
|
||||
std::vector<std::shared_ptr<Group>> availableChannelGroups(bool root = true);
|
||||
|
||||
std::vector<std::shared_ptr<GroupAssignment>> getAssignedServerGroups(ClientDbId cldbid);
|
||||
inline bool hasServerGroupAssigned(uint64_t cldbId, const std::shared_ptr<Group>& group){
|
||||
for(const auto& assign : this->getAssignedServerGroups(cldbId)) if(assign->group == group) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<GroupAssignment> get_group_assignment(const ClientDbId& client_database_id, const std::shared_ptr<Group>& group) {
|
||||
for(const auto& assign : this->getAssignedServerGroups(client_database_id))
|
||||
if(assign->group == group) return assign;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<GroupAssignment>> getServerGroups(ClientDbId cldbid, server::ClientType type);
|
||||
inline bool hasServerGroup(uint64_t cldbId, server::ClientType type, const std::shared_ptr<Group>& group){
|
||||
for(const auto& assign : this->getServerGroups(cldbId, type)) if(assign->group == group) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//Gets the channel group (may inherited)
|
||||
std::shared_ptr<GroupAssignment> getChannelGroup(ClientDbId cldbId, const std::shared_ptr<BasicChannel> &, bool assign_default);
|
||||
//Gets the channel group within the channel
|
||||
std::shared_ptr<GroupAssignment> getChannelGroupExact(ClientDbId cldbId, const std::shared_ptr<BasicChannel>&, bool assign_default);
|
||||
|
||||
std::vector<std::shared_ptr<GroupAssignment>> defaultServerGroupGroupAssignments(ClientDbId, server::ClientType);
|
||||
std::shared_ptr<GroupAssignment> defaultChannelGroupAssignment(ClientDbId cldbId, const std::shared_ptr<BasicChannel> &);
|
||||
|
||||
void addServerGroup(ClientDbId cldbId, std::shared_ptr<Group>, std::chrono::time_point<std::chrono::system_clock> until = std::chrono::time_point<std::chrono::system_clock>());
|
||||
void removeServerGroup(ClientDbId cldbId, std::shared_ptr<Group>);
|
||||
void setChannelGroup(ClientDbId cldbId, std::shared_ptr<Group>, std::shared_ptr<BasicChannel> , std::chrono::time_point<std::chrono::system_clock> until = std::chrono::time_point<std::chrono::system_clock>());
|
||||
|
||||
std::shared_ptr<Group> createGroup(GroupTarget target, GroupType type, std::string name);
|
||||
GroupId copyGroup(std::shared_ptr<Group> group, GroupType type, std::string name, ServerId targetServerId);
|
||||
bool copyGroupPermissions(const std::shared_ptr<Group>& source, const std::shared_ptr<Group>& target);
|
||||
|
||||
bool reloadGroupPermissions(std::shared_ptr<Group>);
|
||||
|
||||
bool renameGroup(std::shared_ptr<Group>, std::string);
|
||||
bool deleteGroup(std::shared_ptr<Group>);
|
||||
bool deleteAllGroups();
|
||||
std::vector<std::shared_ptr<GroupMember>> listGroupMembers(std::shared_ptr<Group>, bool names = false);
|
||||
std::vector<std::shared_ptr<GroupAssignment>> listGroupAssignments(ClientDbId client);
|
||||
|
||||
void cleanupAssignments(ClientDbId);
|
||||
|
||||
std::shared_ptr<Group> findGroup(GroupId);
|
||||
std::shared_ptr<Group> findGroupLocal(GroupId);
|
||||
std::vector<std::shared_ptr<Group>> findGroup(GroupTarget target, std::string);
|
||||
|
||||
std::shared_ptr<Group> defaultGroup(GroupTarget type, bool enforce_property = false);
|
||||
|
||||
std::deque<property::ClientProperties> update_server_group_property(const std::shared_ptr<server::ConnectedClient> &client, bool channel_lock, const std::shared_ptr<BasicChannel>& channel);
|
||||
void enableCache(const ClientDbId& /* client database id */); /* if this called disableCache(...) MUST be called to decrease the reference count */
|
||||
void disableCache(const ClientDbId& /* client database id */);
|
||||
bool isClientCached(const ClientDbId& /* client database id */);
|
||||
void clearCache();
|
||||
|
||||
|
||||
bool isLocalGroup(std::shared_ptr<Group>);
|
||||
protected:
|
||||
void handleChannelDeleted(const ChannelId& /* channel id */);
|
||||
private:
|
||||
std::shared_ptr<GroupManager> root = nullptr;
|
||||
std::weak_ptr<server::VirtualServer> server;
|
||||
ServerId getServerId();
|
||||
|
||||
sql::SqlManager* sql;
|
||||
std::vector<std::shared_ptr<Group>> groups;
|
||||
threads::Mutex cacheLock;
|
||||
std::vector<std::shared_ptr<CachedClient>> cachedClients;
|
||||
|
||||
int insertGroupFromDb(int count, char** values, char** column);
|
||||
|
||||
inline std::shared_ptr<CachedClient> resolve_cached_client(ClientDbId client_database_id);
|
||||
};
|
||||
}
|
||||
DEFINE_TRANSFORMS(ts::GroupTarget, uint8_t);
|
||||
+205
-171
@@ -3,31 +3,30 @@
|
||||
#define XFREE undefined_free
|
||||
#define XREALLOC undefined_realloc
|
||||
|
||||
#include <netdb.h>
|
||||
#include "src/weblist/WebListManager.h"
|
||||
#include <log/LogUtils.h>
|
||||
#include "InstanceHandler.h"
|
||||
#include "src/client/InternalClient.h"
|
||||
#include "src/server/QueryServer.h"
|
||||
#include "src/server/file/FileServer.h"
|
||||
#include "SignalHandler.h"
|
||||
#include "src/manager/PermissionNameMapper.h"
|
||||
#include "./FileServerHandler.h"
|
||||
#include "./server/GlobalNetworkEvents.h"
|
||||
#include <ThreadPool/Timer.h>
|
||||
#include "ShutdownHelper.h"
|
||||
#include <sys/utsname.h>
|
||||
#include "build.h"
|
||||
#include <misc/digest.h>
|
||||
#include <misc/base64.h>
|
||||
#include <misc/hex.h>
|
||||
#include <misc/rnd.h>
|
||||
#include <misc/strobf.h>
|
||||
#include <jemalloc/jemalloc.h>
|
||||
#include <protocol/buffers.h>
|
||||
#include "./groups/GroupManager.h"
|
||||
|
||||
#ifndef _POSIX_SOURCE
|
||||
#define _POSIX_SOURCE
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include "./manager/ActionLogger.h"
|
||||
#include "./client/shared/ServerCommandExecutor.h"
|
||||
|
||||
#undef _POSIX_SOURCE
|
||||
|
||||
using namespace std;
|
||||
@@ -35,14 +34,25 @@ using namespace std::chrono;
|
||||
using namespace ts;
|
||||
using namespace ts::server;
|
||||
|
||||
#define INSTANCE_TICK_NAME "instance"
|
||||
|
||||
extern bool mainThreadActive;
|
||||
InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
|
||||
serverInstance = this;
|
||||
this->tick_manager = make_shared<threads::Scheduler>(config::threads::ticking, "tick task ");
|
||||
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr, true);
|
||||
this->statistics->measure_bandwidths(true);
|
||||
|
||||
this->general_task_executor_ = std::make_shared<task_executor>(ts::config::threads::ticking, "instance tick ");
|
||||
this->general_task_executor_->set_exception_handler([](const std::string& task_name, const std::exception_ptr& exception) {
|
||||
std::string message{};
|
||||
try {
|
||||
std::rethrow_exception(exception);
|
||||
} catch (const std::exception& ex) {
|
||||
message = "std::exception::what() -> " + std::string{ex.what()};
|
||||
} catch(...) {
|
||||
message = "unknown exception";
|
||||
}
|
||||
|
||||
logCritical(LOG_INSTANCE, "Instance task executor received exception: {}", message);
|
||||
});
|
||||
|
||||
this->statistics = make_shared<stats::ConnectionStatistics>(nullptr);
|
||||
|
||||
std::string error_message{};
|
||||
this->license_service_ = std::make_shared<license::LicenseService>();
|
||||
@@ -51,7 +61,14 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
|
||||
}
|
||||
this->dbHelper = new DatabaseHelper(this->getSql());
|
||||
|
||||
this->_properties = new Properties();
|
||||
this->action_logger_ = std::make_unique<log::ActionLogger>();
|
||||
if(!this->action_logger_->initialize(error_message)) {
|
||||
logCritical(LOG_INSTANCE, "Failed to initialize instance action logs: {}", error_message);
|
||||
logCritical(LOG_INSTANCE, "Action log has been disabled.");
|
||||
this->action_logger_->finalize();
|
||||
}
|
||||
|
||||
this->_properties = std::make_shared<PropertyManager>();
|
||||
this->_properties->register_property_type<property::InstanceProperties>();
|
||||
this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT] = ts::config::binding::DefaultFilePort;
|
||||
this->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST] = ts::config::binding::DefaultFileHost;
|
||||
@@ -70,8 +87,8 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
|
||||
}
|
||||
}
|
||||
|
||||
const auto &info = property::impl::info<property::InstanceProperties>(key);
|
||||
if(*info == property::SERVERINSTANCE_UNDEFINED) {
|
||||
const auto &info = property::find<property::InstanceProperties>(key);
|
||||
if(info == property::SERVERINSTANCE_UNDEFINED) {
|
||||
logError(0, "Got an unknown instance property " + key);
|
||||
return 0;
|
||||
}
|
||||
@@ -103,18 +120,57 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
|
||||
this->properties()[property::SERVERINSTANCE_PERMISSIONS_VERSION] = this->sql->get_permissions_version();
|
||||
|
||||
|
||||
globalServerAdmin = std::make_shared<ts::server::InternalClient>(this->getSql(), nullptr, "serveradmin", true);
|
||||
static_pointer_cast<ts::server::InternalClient>(globalServerAdmin)->setSharedLock(globalServerAdmin);
|
||||
this->globalServerAdmin = std::make_shared<ts::server::InternalClient>(this->getSql(), nullptr, "serveradmin", true);
|
||||
this->globalServerAdmin->initialize_weak_reference(this->globalServerAdmin);
|
||||
ts::server::DatabaseHelper::assignDatabaseId(this->getSql(), 0, globalServerAdmin);
|
||||
|
||||
this->_musicRoot = std::make_shared<InternalClient>(this->getSql(), nullptr, "Music Manager", false);
|
||||
static_pointer_cast<InternalClient>(this->_musicRoot)->setSharedLock(this->_musicRoot);
|
||||
dynamic_pointer_cast<InternalClient>(this->_musicRoot)->initialize_weak_reference(this->_musicRoot);
|
||||
|
||||
{
|
||||
this->groupManager = std::make_shared<GroupManager>(nullptr, this->getSql());
|
||||
this->groupManager->loadGroupFormDatabase();
|
||||
using GroupLoadResult = groups::GroupLoadResult;
|
||||
|
||||
if (this->groupManager->availableServerGroups(false).empty()) {
|
||||
this->group_manager_ = std::make_shared<groups::GroupManager>(this->getSql(), 0, nullptr);
|
||||
if(!this->group_manager_->initialize(this->group_manager_, error_message)) {
|
||||
logCritical(LOG_INSTANCE, "Failed to initialize instance group manager: {}", error_message);
|
||||
mainThreadActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
bool initialize_groups{false};
|
||||
switch(this->group_manager_->server_groups()->load_data(true)) {
|
||||
case GroupLoadResult::SUCCESS:
|
||||
break;
|
||||
case GroupLoadResult::NO_GROUPS:
|
||||
initialize_groups = true;
|
||||
break;
|
||||
|
||||
case GroupLoadResult::DATABASE_ERROR:
|
||||
logCritical(LOG_INSTANCE, "Failed to load instance server groups (Database error)");
|
||||
mainThreadActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
switch(this->group_manager_->channel_groups()->load_data(true)) {
|
||||
case GroupLoadResult::SUCCESS:
|
||||
break;
|
||||
case GroupLoadResult::NO_GROUPS:
|
||||
initialize_groups = true;
|
||||
break;
|
||||
|
||||
case GroupLoadResult::DATABASE_ERROR:
|
||||
logCritical(LOG_INSTANCE, "Failed to load instance channel groups (Database error)");
|
||||
mainThreadActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this->group_manager_->assignments().load_data(error_message)) {
|
||||
logCritical(LOG_INSTANCE, "Failed to load instance group assignments: {}", error_message);
|
||||
mainThreadActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (initialize_groups) {
|
||||
if(!this->setupDefaultGroups()){
|
||||
logCritical(LOG_INSTANCE, "Could not setup server instance! Stopping...");
|
||||
mainThreadActive = false;
|
||||
@@ -122,30 +178,7 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
|
||||
}
|
||||
}
|
||||
|
||||
debugMessage(LOG_INSTANCE, "Instance admin group id " + to_string(this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP].as<GroupId>()));
|
||||
auto instance_server_admin = this->groupManager->findGroup(this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP].as<GroupId>());
|
||||
if (!instance_server_admin) {
|
||||
instance_server_admin = this->groupManager->availableServerGroups(false).front();
|
||||
logCritical(LOG_INSTANCE, "Missing instance server admin group! Using first available (" + (instance_server_admin ? instance_server_admin->name() : "nil") + ")");
|
||||
}
|
||||
if(this->groupManager->listGroupAssignments(this->globalServerAdmin->getClientDatabaseId()).empty())
|
||||
this->groupManager->addServerGroup(this->globalServerAdmin->getClientDatabaseId(), instance_server_admin);
|
||||
|
||||
debugMessage(LOG_INSTANCE, "Server guest group id " + to_string(this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as<GroupId>()));
|
||||
auto instance_server_guest = this->groupManager->findGroup(this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_save<GroupId>());
|
||||
if (!instance_server_guest) {
|
||||
instance_server_guest = this->groupManager->availableServerGroups(false).front();
|
||||
logCritical(LOG_INSTANCE, "Missing instance server guest group! Using first available (" + (instance_server_guest ? instance_server_guest->name() : "nil") + ")");
|
||||
}
|
||||
this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP] = instance_server_guest->groupId();
|
||||
|
||||
debugMessage(LOG_INSTANCE, "Server music group id " + to_string(this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as<GroupId>()));
|
||||
auto instance_server_music = this->groupManager->findGroup(this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP].as_save<GroupId>());
|
||||
if (!instance_server_music) {
|
||||
instance_server_music = instance_server_guest;
|
||||
logCritical(LOG_INSTANCE, "Missing instance server music group! Using serverguest (" + (instance_server_music ? instance_server_music->name() : "nil") + ")");
|
||||
}
|
||||
this->properties()[property::SERVERINSTANCE_TEMPLATE_MUSICDEFAULT_GROUP] = instance_server_music->groupId();
|
||||
this->validate_default_groups();
|
||||
}
|
||||
|
||||
{
|
||||
@@ -173,8 +206,12 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
|
||||
this->save_channel_permissions();
|
||||
}
|
||||
}
|
||||
if(!this->default_tree->getDefaultChannel()) this->default_tree->setDefaultChannel(this->default_tree->findChannel("[cspacer04]Default Channel", nullptr));
|
||||
if(!this->default_tree->getDefaultChannel()) this->default_tree->setDefaultChannel(*this->default_tree->channels().begin());
|
||||
if(!this->default_tree->getDefaultChannel()) {
|
||||
this->default_tree->setDefaultChannel(this->default_tree->findChannel("[cspacer04]Default Channel", nullptr));
|
||||
}
|
||||
if(!this->default_tree->getDefaultChannel()) {
|
||||
this->default_tree->setDefaultChannel(*this->default_tree->channels().begin());
|
||||
}
|
||||
assert(this->default_tree->getDefaultChannel());
|
||||
}
|
||||
|
||||
@@ -183,43 +220,24 @@ InstanceHandler::InstanceHandler(SqlDataManager *sql) : sql(sql) {
|
||||
}
|
||||
|
||||
|
||||
if(this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as<int64_t>() == 0) {
|
||||
if(this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as_or<int64_t>(0) == 0) {
|
||||
debugMessage(LOG_INSTANCE, "Setting up monthly reset timestamp!");
|
||||
this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
this->banMgr = new BanManager(this->getSql());
|
||||
this->banMgr->loadBans();
|
||||
|
||||
this->web_list = make_shared<weblist::WebListManager>();
|
||||
}
|
||||
|
||||
void InstanceHandler::executeTick(VirtualServer* server) {
|
||||
auto str = "server_" + to_string(server->getServerId());
|
||||
if(!this->tick_manager->schedule(str, std::bind(&VirtualServer::executeServerTick, server), milliseconds(500))) {
|
||||
logCritical(LOG_INSTANCE, "Could not schedule server ticking task!");
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceHandler::cancelExecute(VirtualServer* server) {
|
||||
auto str = "server_" + to_string(server->getServerId());
|
||||
if(!this->tick_manager->cancelTask(str)){
|
||||
logError(LOG_INSTANCE, "Could not stop server tick task!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
InstanceHandler::~InstanceHandler() {
|
||||
delete this->_properties;
|
||||
delete this->banMgr;
|
||||
delete this->dbHelper;
|
||||
|
||||
groupManager = nullptr;
|
||||
group_manager_ = nullptr;
|
||||
globalServerAdmin = nullptr;
|
||||
_musicRoot = nullptr;
|
||||
|
||||
statistics = nullptr;
|
||||
tick_manager = nullptr;
|
||||
}
|
||||
|
||||
inline string strip(std::string message) {
|
||||
@@ -245,12 +263,21 @@ inline vector<string> split_hosts(const std::string& message, char delimiter) {
|
||||
}
|
||||
|
||||
bool InstanceHandler::startInstance() {
|
||||
if (this->active)
|
||||
if (this->active) {
|
||||
return false;
|
||||
}
|
||||
|
||||
active = true;
|
||||
string errorMessage;
|
||||
|
||||
this->web_list->enabled = ts::config::server::enable_teamspeak_weblist;
|
||||
this->server_command_executor_ = std::make_shared<ServerCommandExecutor>(ts::config::threads::command_execute);
|
||||
this->network_event_loop_ = std::make_unique<NetworkEventLoop>(ts::config::threads::network_events);
|
||||
if(!this->network_event_loop_->initialize()) {
|
||||
this->server_command_executor_ = nullptr;
|
||||
this->network_event_loop_ = nullptr;
|
||||
logCritical(LOG_INSTANCE, "Failed to initialize network event loop");
|
||||
return false;
|
||||
}
|
||||
|
||||
this->permission_mapper = make_shared<permission::PermissionNameMapper>();
|
||||
if(!this->permission_mapper->initialize(config::permission_mapping_file, errorMessage)) {
|
||||
@@ -282,31 +309,11 @@ bool InstanceHandler::startInstance() {
|
||||
}
|
||||
|
||||
this->loadWebCertificate();
|
||||
fileServer = new ts::server::FileServer();
|
||||
{
|
||||
auto bindings_string = this->properties()[property::SERVERINSTANCE_FILETRANSFER_HOST].as<string>();
|
||||
auto port = this->properties()[property::SERVERINSTANCE_FILETRANSFER_PORT].as<uint16_t>();
|
||||
auto ft_bindings = net::resolve_bindings(bindings_string, port);
|
||||
deque<shared_ptr<FileServer::Binding>> bindings;
|
||||
|
||||
for(auto& binding : ft_bindings) {
|
||||
if(!get<2>(binding).empty()) {
|
||||
logError(LOG_FT, "Failed to resolve binding for {}: {}", get<0>(binding), get<2>(binding));
|
||||
continue;
|
||||
}
|
||||
auto entry = make_shared<FileServer::Binding>();
|
||||
memcpy(&entry->address, &get<1>(binding), sizeof(sockaddr_storage));
|
||||
|
||||
entry->file_descriptor = -1;
|
||||
entry->event_accept = nullptr;
|
||||
bindings.push_back(entry);
|
||||
}
|
||||
|
||||
logMessage(LOG_FT, "Starting server on {}:{}", bindings_string, port);
|
||||
if(!fileServer->start(bindings, errorMessage)) {
|
||||
logCritical(LOG_FT, "Failed to start server: {}", errorMessage);
|
||||
return false;
|
||||
}
|
||||
this->file_server_handler_ = new file::FileServerHandler{this};
|
||||
if(!this->file_server_handler_->initialize(errorMessage)) {
|
||||
logCritical(LOG_FT, "Failed to initialize server: {}", errorMessage);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(config::query::sslMode > 0) {
|
||||
@@ -334,8 +341,8 @@ bool InstanceHandler::startInstance() {
|
||||
}
|
||||
|
||||
{
|
||||
auto query_bindings_string = this->properties()[property::SERVERINSTANCE_QUERY_HOST].as<string>();
|
||||
auto query_port = this->properties()[property::SERVERINSTANCE_QUERY_PORT].as<uint16_t>();
|
||||
auto query_bindings_string = this->properties()[property::SERVERINSTANCE_QUERY_HOST].value();
|
||||
auto query_port = this->properties()[property::SERVERINSTANCE_QUERY_PORT].as_or<uint16_t>(0);
|
||||
auto query_bindings = net::resolve_bindings(query_bindings_string, query_port);
|
||||
deque<shared_ptr<QueryServer::Binding>> bindings;
|
||||
|
||||
@@ -377,7 +384,6 @@ FwIDAQAB
|
||||
logCritical(LOG_GENERAL, "Failed to initialize WebClient TeaForum key! ({})", error);
|
||||
return false;
|
||||
}
|
||||
this->web_event_loop = make_shared<webio::LoopManager>();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -414,7 +420,14 @@ FwIDAQAB
|
||||
startTimestamp = system_clock::now();
|
||||
this->voiceServerManager->executeAutostart();
|
||||
|
||||
this->scheduler()->schedule(INSTANCE_TICK_NAME, bind(&InstanceHandler::tickInstance, this), milliseconds(100));
|
||||
this->general_task_executor()->schedule_repeating(
|
||||
this->tick_task_id,
|
||||
"instance ticker",
|
||||
std::chrono::milliseconds{500},
|
||||
[&](const auto&) {
|
||||
this->tickInstance();
|
||||
}
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -425,10 +438,13 @@ void InstanceHandler::stopInstance() {
|
||||
this->active = false;
|
||||
this->activeCon.notify_all();
|
||||
}
|
||||
this->web_list->enabled = false;
|
||||
this->server_command_executor_->shutdown();
|
||||
|
||||
/* TODO: Block on canceling. */
|
||||
this->general_task_executor()->cancel_task(this->tick_task_id);
|
||||
this->tick_task_id = 0;
|
||||
|
||||
threads::MutexLock lock_tick(this->lock_tick);
|
||||
this->scheduler()->cancelTask(INSTANCE_TICK_NAME);
|
||||
|
||||
debugMessage(LOG_INSTANCE, "Stopping all virtual servers");
|
||||
if (this->voiceServerManager)
|
||||
@@ -444,25 +460,32 @@ void InstanceHandler::stopInstance() {
|
||||
debugMessage(LOG_QUERY, "Query server stopped");
|
||||
|
||||
debugMessage(LOG_FT, "Stopping file server");
|
||||
if (this->fileServer) this->fileServer->stop();
|
||||
delete this->fileServer;
|
||||
this->fileServer = nullptr;
|
||||
file::finalize();
|
||||
debugMessage(LOG_FT, "File server stopped");
|
||||
|
||||
this->save_channel_permissions();
|
||||
this->save_group_permissions();
|
||||
|
||||
if(this->file_server_handler_) {
|
||||
this->file_server_handler_->finalize();
|
||||
delete std::exchange(this->file_server_handler_, nullptr);
|
||||
}
|
||||
|
||||
delete this->sslMgr;
|
||||
this->sslMgr = nullptr;
|
||||
|
||||
this->web_event_loop = nullptr;
|
||||
|
||||
this->license_service_->shutdown();
|
||||
this->server_command_executor_ = nullptr;
|
||||
|
||||
this->network_event_loop_->shutdown();
|
||||
this->network_event_loop_ = nullptr;
|
||||
}
|
||||
|
||||
void InstanceHandler::tickInstance() {
|
||||
threads::MutexLock lock(this->lock_tick);
|
||||
if(!this->active) return;
|
||||
if(!this->active) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto now = system_clock::now();
|
||||
|
||||
@@ -482,16 +505,17 @@ void InstanceHandler::tickInstance() {
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> flush", milliseconds(5));
|
||||
//logger::flush();
|
||||
}
|
||||
if(statisticsUpdateTimestamp + seconds(5) < now) {
|
||||
{
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick", milliseconds(5));
|
||||
this->statistics->tick();
|
||||
}
|
||||
if(statisticsUpdateTimestamp + seconds(1) < now) {
|
||||
statisticsUpdateTimestamp = now;
|
||||
{
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick", milliseconds(5));
|
||||
this->statistics->tick();
|
||||
}
|
||||
|
||||
{
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> statistics tick [monthly]", milliseconds(2));
|
||||
auto month_timestamp = system_clock::time_point() + seconds(this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as<int64_t>());
|
||||
auto month_timestamp = system_clock::time_point() + seconds(
|
||||
this->properties()[property::SERVERINSTANCE_MONTHLY_TIMESTAMP].as_or<int64_t>(0));
|
||||
auto time_t_old = system_clock::to_time_t(month_timestamp);
|
||||
auto time_t_new = system_clock::to_time_t(system_clock::now());
|
||||
|
||||
@@ -512,18 +536,13 @@ void InstanceHandler::tickInstance() {
|
||||
if(memcleanTimestamp + minutes(10) < now) {
|
||||
memcleanTimestamp = now;
|
||||
{
|
||||
{
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> mem cleanup -> buffer cleanup", milliseconds(5));
|
||||
auto info = buffer::cleanup_buffers(buffer::cleanmode::CHUNKS_BLOCKS);
|
||||
if(info.bytes_freed_buffer != 0 || info.bytes_freed_internal != 0)
|
||||
logMessage(LOG_INSTANCE, "Cleanupped buffers. (Buffer: {}, Internal: {})", info.bytes_freed_buffer, info.bytes_freed_internal);
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> mem cleanup -> buffer cleanup", milliseconds(5));
|
||||
auto info = buffer::cleanup_buffers(buffer::cleanmode::CHUNKS_BLOCKS);
|
||||
if(info.bytes_freed_buffer != 0 || info.bytes_freed_internal != 0) {
|
||||
logMessage(LOG_INSTANCE, "Cleanupped buffers. (Buffer: {}, Internal: {})", info.bytes_freed_buffer, info.bytes_freed_internal);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> fileserver tick", milliseconds(5));
|
||||
if(this->fileServer) this->fileServer->instanceTick();
|
||||
}
|
||||
{
|
||||
ALARM_TIMER(t, "InstanceHandler::tickInstance -> sql_test tick", milliseconds(5));
|
||||
if(this->sql && this->active) {
|
||||
@@ -533,7 +552,7 @@ void InstanceHandler::tickInstance() {
|
||||
auto command = this->sql->sql()->getType() == sql::TYPE_SQLITE ? "SELECT * FROM `sqlite_master`" : "SHOW TABLES";
|
||||
auto result = sql::command(this->getSql(), command).query([command](int, string*, string*){ return 0; });
|
||||
if(!result) {
|
||||
logCritical(LOG_INSTANCE, "Dummy sql connection test faild! (Failed to execute command \"{}\". Error message: {})", command, result.fmtStr());
|
||||
logCritical(LOG_INSTANCE, "Dummy sql connection test failed! (Failed to execute command \"{}\". Error message: {})", command, result.fmtStr());
|
||||
logCritical(LOG_INSTANCE, "Stopping instance!");
|
||||
ts::server::shutdownInstance("invalid sql connection!");
|
||||
}
|
||||
@@ -555,25 +574,14 @@ void InstanceHandler::tickInstance() {
|
||||
|
||||
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE] = this->calculateSpokenTime().count();
|
||||
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL] =
|
||||
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>() +
|
||||
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>() +
|
||||
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
|
||||
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as_or<uint64_t>(0) +
|
||||
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as_or<uint64_t>(0) +
|
||||
this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as_or<uint64_t>(0);
|
||||
}
|
||||
|
||||
this->web_list->tick();
|
||||
}
|
||||
|
||||
void InstanceHandler::save_group_permissions() {
|
||||
auto groups = this->getGroupManager()->availableGroups(false);
|
||||
for(auto& group : groups) {
|
||||
auto permissions = group->permissions();
|
||||
if(permissions->require_db_updates()) {
|
||||
auto begin = system_clock::now();
|
||||
serverInstance->databaseHelper()->saveGroupPermissions(nullptr, group->groupId(), permissions);
|
||||
auto end = system_clock::now();
|
||||
debugMessage(0, "Saved instance group permissions for group {} ({}) in {}ms", group->groupId(), group->name(), duration_cast<milliseconds>(end - begin).count());
|
||||
}
|
||||
}
|
||||
this->group_manager()->save_permissions();
|
||||
}
|
||||
|
||||
void InstanceHandler::save_channel_permissions() {
|
||||
@@ -646,15 +654,15 @@ std::shared_ptr<ts::server::license::InstanceLicenseInfo> InstanceHandler::gener
|
||||
auto request = std::make_shared<license::InstanceLicenseInfo>();
|
||||
request->license = config::license;
|
||||
request->metrics.servers_online = this->voiceServerManager->runningServers();
|
||||
auto report = this->voiceServerManager->clientReport();
|
||||
request->metrics.client_online = report.clients_ts;
|
||||
request->metrics.web_clients_online = report.clients_web;
|
||||
request->metrics.bots_online = report.bots;
|
||||
auto report = this->voiceServerManager->instanceSlotUsageReport();
|
||||
request->metrics.client_online = report.clients_teamspeak;
|
||||
request->metrics.web_clients_online = report.clients_teaweb;
|
||||
request->metrics.bots_online = report.music_bots;
|
||||
request->metrics.queries_online = report.queries;
|
||||
request->metrics.speech_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as<uint64_t>();
|
||||
request->metrics.speech_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as<uint64_t>();
|
||||
request->metrics.speech_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as<uint64_t>();
|
||||
request->metrics.speech_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as<uint64_t>();
|
||||
request->metrics.speech_total = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_TOTAL].as_or<uint64_t>(0);
|
||||
request->metrics.speech_varianz = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_VARIANZ].as_or<uint64_t>(0);
|
||||
request->metrics.speech_online = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_ALIVE].as_or<uint64_t>(0);
|
||||
request->metrics.speech_dead = this->properties()[property::SERVERINSTANCE_SPOKEN_TIME_DELETED].as_or<uint64_t>(0);
|
||||
|
||||
static std::string null_str{"\0\0\0\0\0\0\0\0", 8}; /* we need at least some characters */
|
||||
request->web_certificate_revision = this->web_cert_revision.empty() ? null_str : this->web_cert_revision;
|
||||
@@ -677,11 +685,11 @@ std::shared_ptr<ts::server::license::InstanceLicenseInfo> InstanceHandler::gener
|
||||
|
||||
{ /* unique id */
|
||||
auto property_unique_id = this->properties()[property::SERVERINSTANCE_UNIQUE_ID];
|
||||
if(property_unique_id.as<string>().empty())
|
||||
if(property_unique_id.value().empty())
|
||||
property_unique_id = rnd_string(64);
|
||||
|
||||
auto hash = digest::sha256(request->info.uname);
|
||||
hash = digest::sha256(hash + property_unique_id.as<string>() + get_mac_address());
|
||||
hash = digest::sha256(hash + property_unique_id.value() + get_mac_address());
|
||||
request->info.unique_id = base64::encode(hash);
|
||||
}
|
||||
}
|
||||
@@ -894,35 +902,61 @@ void InstanceHandler::loadWebCertificate() {
|
||||
this->web_cert_revision = revision;
|
||||
}
|
||||
|
||||
bool InstanceHandler::validate_default_groups() {
|
||||
using groups::GroupCalculateMode;
|
||||
using groups::GroupAssignmentCalculateMode;
|
||||
|
||||
permission::v2::PermissionFlaggedValue InstanceHandler::calculate_permission(permission::PermissionType permission,
|
||||
ClientDbId cldbid, ClientType type, ChannelId channel, bool granted, std::shared_ptr<CalculateCache> cache) {
|
||||
auto result = this->calculate_permissions({permission}, cldbid, type, channel, granted, cache);
|
||||
if(result.empty()) return {0, false};
|
||||
return result.front().second;
|
||||
}
|
||||
{
|
||||
auto property = this->properties()[property::SERVERINSTANCE_ADMIN_SERVERQUERY_GROUP];
|
||||
auto group_id = property.as_or<GroupId>(0);
|
||||
debugMessage(LOG_INSTANCE, "Instance admin query group id {}", group_id);
|
||||
|
||||
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue> > InstanceHandler::calculate_permissions(
|
||||
const std::deque<permission::PermissionType> &permissions, ClientDbId cldbid, ClientType type, ChannelId channel, bool granted, std::shared_ptr<CalculateCache> cache) {
|
||||
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> result{};
|
||||
auto group_instance = this->group_manager_->server_groups()->find_group(GroupCalculateMode::GLOBAL, group_id);
|
||||
if(!group_instance) {
|
||||
auto available_groups = this->group_manager_->server_groups()->available_groups(GroupCalculateMode::GLOBAL);
|
||||
if(available_groups.empty()) {
|
||||
logCritical(LOG_INSTANCE, "Missing instance server groups.");
|
||||
return false;
|
||||
}
|
||||
|
||||
//TODO: Negate?
|
||||
//TODO: May move this part to the instance?
|
||||
|
||||
auto server_groups = this->getGroupManager()->getServerGroups(cldbid, type);
|
||||
for(const auto& permission : permissions) {
|
||||
permission::v2::PermissionFlaggedValue value{0, false};
|
||||
|
||||
for(const auto &gr : server_groups){
|
||||
auto group_permissions = gr->group->permissions();
|
||||
auto flagged_permissions = granted ? group_permissions->permission_granted_flagged(permission) : group_permissions->permission_value_flagged(permission);
|
||||
if(flagged_permissions.has_value)
|
||||
if(!value.has_value || flagged_permissions.value > value.value || flagged_permissions.value == -1)
|
||||
value = flagged_permissions;
|
||||
group_instance = available_groups.front();
|
||||
logCritical(LOG_INSTANCE, "Missing instance server admin query group. Using first available ({})", group_instance->group_id());
|
||||
}
|
||||
|
||||
result.emplace_back(permission, value);
|
||||
property.update_value(group_instance->group_id());
|
||||
|
||||
{
|
||||
auto& assignments = this->group_manager_->assignments();
|
||||
auto client_assignments = assignments.server_groups_of_client(GroupAssignmentCalculateMode::GLOBAL, this->globalServerAdmin->getClientDatabaseId());
|
||||
if(client_assignments.empty()) {
|
||||
assignments.add_server_group(this->globalServerAdmin->getClientDatabaseId(), group_instance->group_id(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
{
|
||||
auto property = this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP];
|
||||
auto group_id = property.as_or<GroupId>(0);
|
||||
debugMessage(LOG_INSTANCE, "Instance guest query group id {}", group_id);
|
||||
|
||||
auto group_instance = this->group_manager_->server_groups()->find_group(GroupCalculateMode::GLOBAL, group_id);
|
||||
if(!group_instance) {
|
||||
auto available_groups = this->group_manager_->server_groups()->available_groups(GroupCalculateMode::GLOBAL);
|
||||
if(available_groups.empty()) {
|
||||
logCritical(LOG_INSTANCE, "Missing instance server groups.");
|
||||
return false;
|
||||
}
|
||||
|
||||
group_instance = available_groups.front();
|
||||
logCritical(LOG_INSTANCE, "Missing instance server guest query group. Using first available ({})", group_instance->group_id());
|
||||
}
|
||||
|
||||
property.update_value(group_instance->group_id());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::shared_ptr<groups::ServerGroup> InstanceHandler::guest_query_group() {
|
||||
auto group_id = this->properties()[property::SERVERINSTANCE_GUEST_SERVERQUERY_GROUP].as_or(0);
|
||||
return this->group_manager_->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id);
|
||||
}
|
||||
@@ -7,13 +7,9 @@
|
||||
#include <src/lincense/LicenseService.h>
|
||||
#include "manager/SqlDataManager.h"
|
||||
#include "lincense/TeamSpeakLicense.h"
|
||||
#include "server/WebIoManager.h"
|
||||
#include <misc/task_executor.h>
|
||||
|
||||
namespace ts {
|
||||
namespace weblist {
|
||||
class WebListManager;
|
||||
}
|
||||
|
||||
namespace permission {
|
||||
class PermissionNameMapper;
|
||||
}
|
||||
@@ -23,6 +19,22 @@ namespace ts {
|
||||
class LicenseService;
|
||||
}
|
||||
|
||||
namespace file {
|
||||
class FileServerHandler;
|
||||
}
|
||||
|
||||
namespace log {
|
||||
class ActionLogger;
|
||||
}
|
||||
|
||||
namespace groups {
|
||||
class GroupManager;
|
||||
}
|
||||
|
||||
class NetworkEventLoop;
|
||||
class ServerCommandExecutor;
|
||||
class InstanceHandler;
|
||||
|
||||
class InstanceHandler {
|
||||
public:
|
||||
explicit InstanceHandler(SqlDataManager*);
|
||||
@@ -31,29 +43,32 @@ namespace ts {
|
||||
bool startInstance();
|
||||
void stopInstance();
|
||||
|
||||
ts::Properties& properties(){
|
||||
return *_properties;
|
||||
}
|
||||
inline PropertyWrapper properties() { return PropertyWrapper{this->_properties}; }
|
||||
inline const PropertyWrapper properties() const { return PropertyWrapper{this->_properties}; }
|
||||
|
||||
std::shared_ptr<ts::server::InternalClient> getInitialServerAdmin(){ return globalServerAdmin; }
|
||||
std::shared_ptr<ts::GroupManager> getGroupManager(){ return groupManager; }
|
||||
const auto& group_manager(){ return this->group_manager_; }
|
||||
|
||||
/**
|
||||
* Get the default instance server query group.
|
||||
* @return the default group or `nullptr` if the group doesn't exists any more.
|
||||
*/
|
||||
[[nodiscard]] std::shared_ptr<groups::ServerGroup> guest_query_group();
|
||||
|
||||
std::shared_ptr<ts::ServerChannelTree> getChannelTree() { return this->default_tree; }
|
||||
std::shared_mutex& getChannelTreeLock() { return this->default_tree_lock; }
|
||||
|
||||
VirtualServerManager* getVoiceServerManager(){ return this->voiceServerManager; }
|
||||
FileServer* getFileServer(){ return fileServer; }
|
||||
QueryServer* getQueryServer(){ return queryServer; }
|
||||
DatabaseHelper* databaseHelper(){ return this->dbHelper; }
|
||||
BanManager* banManager(){ return this->banMgr; }
|
||||
ssl::SSLManager* sslManager(){ return this->sslMgr; }
|
||||
sql::SqlManager* getSql(){ return sql->sql(); }
|
||||
log::ActionLogger* action_logger() { return &*this->action_logger_; }
|
||||
file::FileServerHandler* getFileServerHandler() { return this->file_server_handler_; }
|
||||
|
||||
std::chrono::time_point<std::chrono::system_clock> getStartTimestamp(){ return startTimestamp; }
|
||||
|
||||
void executeTick(VirtualServer*);
|
||||
void cancelExecute(VirtualServer*);
|
||||
|
||||
bool reloadConfig(std::vector<std::string>& /* errors */, bool /* reload file */);
|
||||
void setWebCertRoot(const std::string& /* key */, const std::string& /* certificate */, const std::string& /* revision */);
|
||||
|
||||
@@ -64,35 +79,19 @@ namespace ts {
|
||||
|
||||
bool resetMonthlyStats();
|
||||
|
||||
std::shared_ptr<stats::ConnectionStatistics> getStatistics(){ return statistics; }
|
||||
std::shared_ptr<threads::Scheduler> scheduler(){ return this->tick_manager; }
|
||||
std::shared_ptr<license::InstanceLicenseInfo> generateLicenseData();
|
||||
[[nodiscard]] inline const auto& general_task_executor(){ return this->general_task_executor_; }
|
||||
[[nodiscard]] inline const auto& network_event_loop(){ return this->network_event_loop_; }
|
||||
|
||||
std::shared_ptr<TeamSpeakLicense> getTeamSpeakLicense() { return this->teamspeak_license; }
|
||||
std::shared_ptr<ts::Properties> getDefaultServerProperties() { return this->default_server_properties; }
|
||||
std::shared_ptr<webio::LoopManager> getWebIoLoop() { return this->web_event_loop; }
|
||||
std::shared_ptr<weblist::WebListManager> getWebList() { return this->web_list; }
|
||||
[[nodiscard]] inline std::shared_ptr<stats::ConnectionStatistics> getStatistics(){ return statistics; }
|
||||
[[nodiscard]] std::shared_ptr<license::InstanceLicenseInfo> generateLicenseData();
|
||||
|
||||
std::shared_ptr<permission::PermissionNameMapper> getPermissionMapper() { return this->permission_mapper; }
|
||||
std::shared_ptr<ts::event::EventExecutor> getConversationIo() { return this->conversation_io; }
|
||||
[[nodiscard]] inline std::shared_ptr<TeamSpeakLicense> getTeamSpeakLicense() { return this->teamspeak_license; }
|
||||
[[nodiscard]] inline PropertyWrapper getDefaultServerProperties() { return PropertyWrapper{this->default_server_properties}; }
|
||||
|
||||
permission::v2::PermissionFlaggedValue calculate_permission(
|
||||
permission::PermissionType,
|
||||
ClientDbId,
|
||||
ClientType type,
|
||||
ChannelId channel,
|
||||
bool granted = false,
|
||||
std::shared_ptr<CalculateCache> cache = nullptr
|
||||
);
|
||||
[[nodiscard]] inline std::shared_ptr<permission::PermissionNameMapper> getPermissionMapper() { return this->permission_mapper; }
|
||||
[[nodiscard]] inline std::shared_ptr<ts::event::EventExecutor> getConversationIo() { return this->conversation_io; }
|
||||
|
||||
std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> calculate_permissions(
|
||||
const std::deque<permission::PermissionType>&,
|
||||
ClientDbId,
|
||||
ClientType type,
|
||||
ChannelId channel,
|
||||
bool granted = false,
|
||||
std::shared_ptr<CalculateCache> cache = nullptr
|
||||
);
|
||||
[[nodiscard]] inline const auto& server_command_executor() { return this->server_command_executor_; }
|
||||
|
||||
[[nodiscard]] inline std::shared_ptr<license::LicenseService> license_service() { return this->license_service_; }
|
||||
private:
|
||||
@@ -100,6 +99,8 @@ namespace ts {
|
||||
std::condition_variable activeCon;
|
||||
bool active = false;
|
||||
|
||||
task_id tick_task_id{};
|
||||
|
||||
std::chrono::system_clock::time_point startTimestamp;
|
||||
std::chrono::system_clock::time_point sqlTestTimestamp;
|
||||
std::chrono::system_clock::time_point speachUpdateTimestamp;
|
||||
@@ -110,30 +111,34 @@ namespace ts {
|
||||
std::chrono::system_clock::time_point memcleanTimestamp;
|
||||
SqlDataManager* sql;
|
||||
|
||||
FileServer* fileServer = nullptr;
|
||||
QueryServer* queryServer = nullptr;
|
||||
VirtualServerManager* voiceServerManager = nullptr;
|
||||
DatabaseHelper* dbHelper = nullptr;
|
||||
BanManager* banMgr = nullptr;
|
||||
ssl::SSLManager* sslMgr = nullptr;
|
||||
file::FileServerHandler* file_server_handler_{nullptr};
|
||||
std::unique_ptr<log::ActionLogger> action_logger_{nullptr};
|
||||
std::unique_ptr<NetworkEventLoop> network_event_loop_{nullptr};
|
||||
|
||||
ts::Properties* _properties = nullptr;
|
||||
std::shared_ptr<ts::PropertyManager> _properties{};
|
||||
|
||||
std::shared_ptr<ServerCommandExecutor> server_command_executor_{};
|
||||
|
||||
std::shared_ptr<ts::event::EventExecutor> conversation_io = nullptr;
|
||||
std::shared_ptr<webio::LoopManager> web_event_loop = nullptr;
|
||||
std::shared_ptr<weblist::WebListManager> web_list = nullptr;
|
||||
|
||||
std::shared_ptr<ts::Properties> default_server_properties = nullptr;
|
||||
std::shared_ptr<ts::ServerChannelTree> default_tree = nullptr;
|
||||
std::shared_ptr<ts::PropertyManager> default_server_properties = nullptr;
|
||||
std::shared_mutex default_tree_lock;
|
||||
std::shared_ptr<ts::GroupManager> groupManager = nullptr;
|
||||
std::shared_ptr<ts::ServerChannelTree> default_tree = nullptr;
|
||||
|
||||
std::shared_ptr<groups::GroupManager> group_manager_{nullptr};
|
||||
|
||||
std::shared_ptr<ts::server::InternalClient> globalServerAdmin = nullptr;
|
||||
std::shared_ptr<ConnectedClient> _musicRoot = nullptr;
|
||||
|
||||
std::shared_ptr<license::LicenseService> license_service_{nullptr};
|
||||
std::shared_ptr<stats::ConnectionStatistics> statistics = nullptr;
|
||||
std::shared_ptr<threads::Scheduler> tick_manager = nullptr;
|
||||
|
||||
std::shared_ptr<task_executor> general_task_executor_{nullptr};
|
||||
|
||||
std::shared_ptr<permission::PermissionNameMapper> permission_mapper = nullptr;
|
||||
std::shared_ptr<TeamSpeakLicense> teamspeak_license = nullptr;
|
||||
@@ -149,6 +154,7 @@ namespace ts {
|
||||
void save_channel_permissions();
|
||||
|
||||
void loadWebCertificate();
|
||||
bool validate_default_groups();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by WolverinDEV on 26/02/2021.
|
||||
//
|
||||
|
||||
#include "./InstanceHandler.h"
|
||||
#include "./groups/GroupManager.h"
|
||||
#include "./PermissionCalculator.h"
|
||||
|
||||
using namespace ts;
|
||||
using namespace ts::server;
|
||||
@@ -1,12 +1,10 @@
|
||||
#include <netdb.h>
|
||||
#include <deque>
|
||||
#include <tuple>
|
||||
#include <fstream>
|
||||
#include <log/LogUtils.h>
|
||||
#include "InstanceHandler.h"
|
||||
#include "src/client/InternalClient.h"
|
||||
#include "src/server/QueryServer.h"
|
||||
#include "src/server/file/FileServer.h"
|
||||
#include "./groups/GroupManager.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@@ -24,9 +22,9 @@ struct GroupInfo {
|
||||
*/
|
||||
int target;
|
||||
std::deque<string> properties;
|
||||
string name;
|
||||
std::string name;
|
||||
/* permission type, value, granted, skip, negate */
|
||||
deque<tuple<permission::PermissionType, permission::PermissionValue, permission::PermissionValue, bool, bool>> permissions;
|
||||
std::deque<std::tuple<permission::PermissionType, permission::PermissionValue, permission::PermissionValue, bool, bool>> permissions;
|
||||
};
|
||||
|
||||
/* TODO may use a transaction here? */
|
||||
@@ -120,25 +118,53 @@ bool InstanceHandler::setupDefaultGroups() {
|
||||
groups.push_back(group);
|
||||
}
|
||||
|
||||
debugMessage(LOG_INSTANCE, "Read " + to_string(groups.size()) + " default groups");
|
||||
debugMessage(LOG_INSTANCE, "Read {} default groups", groups.size());
|
||||
for(const auto& info : groups) {
|
||||
debugMessage(LOG_INSTANCE, "Creating default group {} with type {}", info->name, to_string(info->target));
|
||||
//Query groups
|
||||
auto group = this->groupManager->createGroup(
|
||||
info->target != 2 ? GroupTarget::GROUPTARGET_SERVER : GroupTarget::GROUPTARGET_CHANNEL,
|
||||
info->target == 0 ? GroupType::GROUP_TYPE_QUERY : GroupType::GROUP_TYPE_TEMPLATE,
|
||||
info->name
|
||||
);
|
||||
|
||||
std::shared_ptr<groups::Group> created_group{};
|
||||
groups::GroupCreateResult create_result{};
|
||||
|
||||
if(info->target == 2) {
|
||||
std::shared_ptr<groups::ChannelGroup> c_group{};
|
||||
create_result = serverInstance->group_manager()->channel_groups()->create_group(groups::GroupType::GROUP_TYPE_TEMPLATE, info->name, c_group);
|
||||
created_group = c_group;
|
||||
} else {
|
||||
std::shared_ptr<groups::ServerGroup> s_group{};
|
||||
create_result = serverInstance->group_manager()->server_groups()->create_group(info->target == 0 ? groups::GroupType::GROUP_TYPE_QUERY : groups::GroupType::GROUP_TYPE_TEMPLATE, info->name, s_group);
|
||||
created_group = s_group;
|
||||
}
|
||||
|
||||
switch(create_result) {
|
||||
case groups::GroupCreateResult::SUCCESS:
|
||||
break;
|
||||
|
||||
case groups::GroupCreateResult::DATABASE_ERROR:
|
||||
logCritical(LOG_INSTANCE, "Failed to insert template group {} (Database error)", info->name);
|
||||
return false;
|
||||
|
||||
case groups::GroupCreateResult::NAME_TOO_LONG:
|
||||
case groups::GroupCreateResult::NAME_ALREADY_IN_USED:
|
||||
case groups::GroupCreateResult::NAME_TOO_SHORT:
|
||||
logCritical(LOG_INSTANCE, "Failed to insert template group {} (Name issue)", info->name);
|
||||
return false;
|
||||
|
||||
default:
|
||||
logCritical(LOG_INSTANCE, "Failed to insert template group {} (Unkown error)", info->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
for(auto perm : info->permissions) {
|
||||
group->permissions()->set_permission(get<0>(perm), {get<1>(perm), get<2>(perm)}, permission::v2::set_value, permission::v2::set_value, get<3>(perm), get<4>(perm));
|
||||
created_group->permissions()->set_permission(get<0>(perm), {get<1>(perm), get<2>(perm)}, get<1>(perm) == permNotGranted ? permission::v2::do_nothing : permission::v2::set_value, get<2>(perm) == permNotGranted ? permission::v2::do_nothing : permission::v2::set_value, get<3>(perm), get<4>(perm));
|
||||
}
|
||||
|
||||
for(const auto& property : info->properties) {
|
||||
const auto& prop = property::impl::info<property::InstanceProperties>(property);
|
||||
if(*prop == property::SERVERINSTANCE_UNDEFINED) {
|
||||
const auto& prop = property::find<property::InstanceProperties>(property);
|
||||
if(prop.is_undefined()) {
|
||||
logCritical(LOG_INSTANCE, "Invalid template property name: " + property);
|
||||
} else {
|
||||
this->properties()[prop] = group->groupId();
|
||||
this->properties()[prop] = created_group->group_id();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,408 @@
|
||||
//
|
||||
// Created by WolverinDEV on 27/02/2021.
|
||||
//
|
||||
|
||||
#include "./PermissionCalculator.h"
|
||||
#include "./InstanceHandler.h"
|
||||
#include "./groups/Group.h"
|
||||
#include "./client/DataClient.h"
|
||||
#include <PermissionManager.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <src/groups/GroupManager.h>
|
||||
|
||||
using namespace ts::server;
|
||||
using ts::permission::PermissionType;
|
||||
using ts::permission::v2::PermissionFlaggedValue;
|
||||
|
||||
ClientPermissionCalculator::ClientPermissionCalculator(DataClient *client, ChannelId channel_id) {
|
||||
/* Note: Order matters! */
|
||||
this->initialize_client(client);
|
||||
this->initialize_default_groups(client->getServer());
|
||||
|
||||
auto server = client->getServer();
|
||||
if(server && channel_id > 0) {
|
||||
std::shared_ptr<BasicChannel> channel{};
|
||||
try {
|
||||
std::shared_lock channel_lock{server->get_channel_tree_lock()};
|
||||
this->channel_ = server->getChannelTree()->findChannel(channel_id);
|
||||
} catch (std::system_error& e) {
|
||||
if(e.code() != std::errc::resource_deadlock_would_occur) {
|
||||
throw;
|
||||
}
|
||||
|
||||
/* tree already write locked, no need to lock it again */
|
||||
this->channel_ = server->getChannelTree()->findChannel(channel_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClientPermissionCalculator::ClientPermissionCalculator(DataClient *client, const std::shared_ptr<BasicChannel> &channel) {
|
||||
/* Note: Order matters! */
|
||||
this->initialize_client(client);
|
||||
this->initialize_default_groups(client->getServer());
|
||||
this->channel_ = channel;
|
||||
}
|
||||
|
||||
ClientPermissionCalculator::ClientPermissionCalculator(
|
||||
const std::shared_ptr<VirtualServer> &server,
|
||||
ClientDbId client_database_id,
|
||||
ClientType client_type,
|
||||
ChannelId channel_id) {
|
||||
|
||||
this->client_database_id = client_database_id;
|
||||
this->client_type = client_type;
|
||||
|
||||
if(server) {
|
||||
this->virtual_server_id = server->getServerId();
|
||||
this->group_manager_ = server->group_manager();
|
||||
|
||||
try {
|
||||
std::shared_lock channel_lock{server->get_channel_tree_lock()};
|
||||
this->channel_ = server->getChannelTree()->findChannel(channel_id);
|
||||
} catch (std::system_error& e) {
|
||||
if(e.code() != std::errc::resource_deadlock_would_occur) {
|
||||
throw;
|
||||
}
|
||||
|
||||
/* tree already write locked, no need to lock it again */
|
||||
this->channel_ = server->getChannelTree()->findChannel(channel_id);
|
||||
}
|
||||
} else {
|
||||
this->virtual_server_id = 0;
|
||||
this->group_manager_ = serverInstance->group_manager();
|
||||
}
|
||||
|
||||
this->initialize_default_groups(server);
|
||||
}
|
||||
|
||||
void ClientPermissionCalculator::initialize_client(DataClient* client) {
|
||||
this->virtual_server_id = client->getServerId();
|
||||
this->client_database_id = client->getClientDatabaseId();
|
||||
this->client_type = client->getType();
|
||||
this->client_permissions_ = client->permissions();
|
||||
|
||||
auto server = client->getServer();
|
||||
if(server) {
|
||||
this->group_manager_ = server->group_manager();
|
||||
} else {
|
||||
this->group_manager_ = serverInstance->group_manager();
|
||||
}
|
||||
}
|
||||
|
||||
void ClientPermissionCalculator::initialize_default_groups(const std::shared_ptr<VirtualServer> &server) {
|
||||
if(this->client_type == ClientType::CLIENT_QUERY) {
|
||||
this->default_server_group = []{ return serverInstance->guest_query_group(); };
|
||||
} else if(server) {
|
||||
this->default_server_group = [server]{ return server->default_server_group(); };
|
||||
}
|
||||
|
||||
if(server) {
|
||||
this->default_channel_group = [server]{ return server->default_channel_group(); };
|
||||
}
|
||||
}
|
||||
|
||||
PermissionFlaggedValue ClientPermissionCalculator::calculate_permission(permission::PermissionType permission,
|
||||
bool granted) {
|
||||
auto result = this->calculate_permissions({permission}, granted);
|
||||
if(result.empty()) {
|
||||
return { permNotGranted, false };
|
||||
}
|
||||
|
||||
return result.front().second;
|
||||
}
|
||||
|
||||
std::vector<std::pair<PermissionType, PermissionFlaggedValue>> ClientPermissionCalculator::calculate_permissions(
|
||||
const std::deque<permission::PermissionType> &permissions,
|
||||
bool calculate_granted
|
||||
) {
|
||||
if(permissions.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::pair<PermissionType, PermissionFlaggedValue>> result;
|
||||
result.reserve(permissions.size());
|
||||
|
||||
auto client_permissions = this->client_permissions();
|
||||
assert(client_permissions);
|
||||
|
||||
/*
|
||||
* server_group_data[0] := Server group id
|
||||
* server_group_data[1] := Skip flag
|
||||
* server_group_data[2] := Negate flag
|
||||
* server_group_data[3] := Permission value
|
||||
*/
|
||||
typedef std::tuple<GroupId, bool, bool, permission::PermissionValue> GroupData;
|
||||
bool server_group_data_initialized = false;
|
||||
std::vector<GroupData> server_group_data;
|
||||
GroupData* active_server_group;
|
||||
|
||||
auto initialize_group_data = [&](const permission::PermissionType& permission_type) {
|
||||
server_group_data_initialized = true;
|
||||
active_server_group = nullptr;
|
||||
|
||||
auto assigned_groups = this->assigned_server_groups();
|
||||
server_group_data.resize(assigned_groups.size());
|
||||
auto it = server_group_data.begin();
|
||||
for(auto& group : assigned_groups) {
|
||||
auto group_permissions = group->permissions();
|
||||
auto permission_flags = group_permissions->permission_flags(permission_type);
|
||||
|
||||
auto flag_set = calculate_granted ? permission_flags.grant_set : permission_flags.value_set;
|
||||
if(!flag_set) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//TODO: Test if there is may a group channel permissions
|
||||
auto value = group_permissions->permission_values(permission_type);
|
||||
*it = std::make_tuple(group->group_id(), (bool) permission_flags.skip, (bool) permission_flags.negate, calculate_granted ? value.grant : value.value);
|
||||
it++;
|
||||
}
|
||||
if(it == server_group_data.begin()) {
|
||||
return; /* no server group has that permission */
|
||||
}
|
||||
|
||||
server_group_data.erase(it, server_group_data.end()); /* remove unneeded */
|
||||
|
||||
auto found_negate = false;
|
||||
for(auto& group : server_group_data) {
|
||||
if(std::get<2>(group)) {
|
||||
found_negate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(found_negate) {
|
||||
server_group_data.erase(remove_if(server_group_data.begin(), server_group_data.end(), [](auto data) { return !std::get<2>(data); }), server_group_data.end());
|
||||
logTrace(this->virtual_server_id, "[Permission] Found negate flag within server groups. Groups left: {}", server_group_data.size());
|
||||
if(server_group_data.empty()) {
|
||||
logTrace(this->virtual_server_id, "[Permission] After non negated groups have been kicked out the negated groups are empty! This should not happen! Permission: {}, Client ID: {}", permission_type, this->client_database_id);
|
||||
}
|
||||
permission::PermissionValue current_lowest = 0;
|
||||
for(auto& group : server_group_data) {
|
||||
if(!active_server_group || (std::get<3>(group) < current_lowest && std::get<3>(group) != -1)) {
|
||||
current_lowest = std::get<3>(group);
|
||||
active_server_group = &group;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
permission::PermissionValue current_highest = 0;
|
||||
for(auto& group : server_group_data) {
|
||||
if(!active_server_group || (std::get<3>(group) > current_highest || std::get<3>(group) == -1)) {
|
||||
current_highest = std::get<3>(group);
|
||||
active_server_group = &group;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for(const auto& permission : permissions) {
|
||||
server_group_data_initialized = false; /* reset all group data */
|
||||
auto client_permission_flags = client_permissions->permission_flags(permission);
|
||||
/* lets try to resolve the channel specific permission */
|
||||
if(this->channel_ && client_permission_flags.channel_specific) {
|
||||
auto data = client_permissions->channel_permission(permission, this->channel_->channelId());
|
||||
if(calculate_granted ? data.flags.grant_set : data.flags.value_set) {
|
||||
result.push_back({permission, {calculate_granted ? data.values.grant : data.values.value, true}});
|
||||
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Client channel permission)", this->client_database_id, permission::resolvePermissionData(permission)->name, data.values.value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool skip_channel_permissions = !this->channel_ || this->has_global_skip_permission();
|
||||
if(!skip_channel_permissions) {
|
||||
/* We dont have a global skip flag. Lets see if the target permission has skip enabled */
|
||||
if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) {
|
||||
/* okey the client has the permission, this counts */
|
||||
skip_channel_permissions = client_permission_flags.skip;
|
||||
} else {
|
||||
if(!server_group_data_initialized) {
|
||||
initialize_group_data(permission);
|
||||
}
|
||||
|
||||
if(active_server_group) {
|
||||
skip_channel_permissions = std::get<1>(*active_server_group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!skip_channel_permissions) {
|
||||
/* lookup the channel group */
|
||||
{
|
||||
auto channel_assignment = this->assigned_channel_group();
|
||||
if(channel_assignment) {
|
||||
auto group_permissions = channel_assignment->permissions();
|
||||
auto permission_flags = group_permissions->permission_flags(permission);
|
||||
|
||||
auto flag_set = calculate_granted ? permission_flags.grant_set : permission_flags.value_set;
|
||||
if(flag_set) {
|
||||
auto value = group_permissions->permission_values(permission);
|
||||
result.push_back({permission, {calculate_granted ? value.grant : value.value, true}});
|
||||
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Channel group permission)", this->client_database_id, permission::resolvePermissionData(permission)->name, calculate_granted ? value.grant : value.value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* lookup the channel permissions. Whyever? */
|
||||
if(this->channel_) {
|
||||
auto channel_permissions = this->channel_->permissions();
|
||||
auto data = calculate_granted ? channel_permissions->permission_granted_flagged(permission) : channel_permissions->permission_value_flagged(permission);
|
||||
if(data.has_value) {
|
||||
result.push_back({permission, {data.value, true}});
|
||||
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Channel permission)", this->client_database_id, permission::resolvePermissionData(permission)->name, data.value);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(calculate_granted ? client_permission_flags.grant_set : client_permission_flags.value_set) {
|
||||
auto client_value = client_permissions->permission_values(permission);
|
||||
result.push_back({permission, {calculate_granted ? client_value.grant : client_value.value, true}});
|
||||
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Client permission)", this->client_database_id, permission::resolvePermissionData(permission)->name, client_value.value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!server_group_data_initialized) {
|
||||
initialize_group_data(permission);
|
||||
}
|
||||
|
||||
if(active_server_group) {
|
||||
result.push_back({permission, {get<3>(*active_server_group), true}});
|
||||
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned {} (Server group permission of group {})", this->client_database_id, permission::resolvePermissionData(permission)->name, get<3>(*active_server_group), get<0>(*active_server_group));
|
||||
continue;
|
||||
}
|
||||
|
||||
logTrace(this->virtual_server_id, "[Permission] Calculation for client {} of permission {} returned in no permission.", this->client_database_id, permission::resolvePermissionData(permission)->name);
|
||||
result.push_back({permission, { permNotGranted, false }});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::shared_ptr<ts::permission::v2::PermissionManager> & ClientPermissionCalculator::client_permissions() {
|
||||
if(!this->client_permissions_) {
|
||||
this->client_permissions_ = serverInstance->databaseHelper()->loadClientPermissionManager(this->virtual_server_id, this->client_database_id);
|
||||
|
||||
if(!this->client_permissions_) {
|
||||
logCritical(this->virtual_server_id, "Failed to load client permissions for client {}. Using empty permission set.", this->client_database_id);
|
||||
this->client_permissions_ = std::make_shared<permission::v2::PermissionManager>();
|
||||
}
|
||||
}
|
||||
|
||||
return this->client_permissions_;
|
||||
}
|
||||
|
||||
const std::vector<std::shared_ptr<groups::ServerGroup>>& ClientPermissionCalculator::assigned_server_groups() {
|
||||
if(this->assigned_server_groups_.has_value()) {
|
||||
return *this->assigned_server_groups_;
|
||||
}
|
||||
|
||||
auto assignments = this->group_manager_->assignments().server_groups_of_client(groups::GroupAssignmentCalculateMode::GLOBAL, this->client_database_id);
|
||||
|
||||
this->assigned_server_groups_.emplace();
|
||||
this->assigned_server_groups_->reserve(assignments.size());
|
||||
|
||||
for(const auto& group_id : assignments) {
|
||||
auto group = this->group_manager_->server_groups()->find_group(groups::GroupCalculateMode::GLOBAL, group_id);
|
||||
if(group) {
|
||||
this->assigned_server_groups_->push_back(group);
|
||||
}
|
||||
}
|
||||
if(this->assigned_server_groups_->empty() && this->default_server_group) {
|
||||
auto default_group = this->default_server_group();
|
||||
if(default_group) {
|
||||
this->assigned_server_groups_->push_back(default_group);
|
||||
}
|
||||
}
|
||||
|
||||
return *this->assigned_server_groups_;
|
||||
}
|
||||
|
||||
const std::shared_ptr<groups::ChannelGroup>& ClientPermissionCalculator::assigned_channel_group() {
|
||||
if(this->assigned_channel_group_.has_value()) {
|
||||
return *this->assigned_channel_group_;
|
||||
}
|
||||
|
||||
this->assigned_channel_group_.emplace();
|
||||
if(!this->channel_) {
|
||||
return *this->assigned_channel_group_;
|
||||
}
|
||||
|
||||
std::shared_ptr<BasicChannel> inherited_channel{this->channel_};
|
||||
auto channel_group_assignment = this->group_manager_->assignments().calculate_channel_group_of_client(
|
||||
groups::GroupAssignmentCalculateMode::GLOBAL,
|
||||
this->client_database_id,
|
||||
inherited_channel
|
||||
);
|
||||
|
||||
if(channel_group_assignment.has_value()) {
|
||||
assert(inherited_channel);
|
||||
auto channel_group = this->group_manager_->channel_groups()->find_group(groups::GroupCalculateMode::GLOBAL, *channel_group_assignment);
|
||||
if(channel_group) {
|
||||
this->assigned_channel_group_.emplace(std::move(channel_group));
|
||||
logTrace(this->virtual_server_id, "[Permission] Using calculated channel group with id {} (Channel id: {}, Inherited channel id: {}).",
|
||||
*channel_group_assignment, this->channel_->channelId(), inherited_channel->channelId());
|
||||
} else {
|
||||
logTrace(this->virtual_server_id, "[Permission] Missing calculated channel group with id {} (Channel id: {}, Inherited channel id: {}). Using default channel group.",
|
||||
*channel_group_assignment, this->channel_->channelId(), inherited_channel->channelId());
|
||||
}
|
||||
} else {
|
||||
logTrace(this->virtual_server_id, "[Permission] Using default channel group.");
|
||||
}
|
||||
|
||||
if(!this->assigned_channel_group_.has_value()) {
|
||||
assigned_channel_group_.emplace(
|
||||
this->default_channel_group()
|
||||
);
|
||||
}
|
||||
|
||||
return *this->assigned_channel_group_;
|
||||
}
|
||||
|
||||
bool ClientPermissionCalculator::has_global_skip_permission() {
|
||||
if(this->skip_enabled.has_value()) {
|
||||
return *this->skip_enabled;
|
||||
}
|
||||
|
||||
/* test for skip permission within the client permission manager */
|
||||
{
|
||||
auto client_permissions = this->client_permissions();
|
||||
auto skip_value = client_permissions->permission_value_flagged(permission::b_client_skip_channelgroup_permissions);
|
||||
if(skip_value.has_value) {
|
||||
this->skip_enabled = std::make_optional(permission::v2::permission_granted(1, skip_value));
|
||||
logTrace(this->virtual_server_id, "[Permission] Found skip permission in client permissions. Value: {}", *this->skip_enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/* test for skip permission within all server groups */
|
||||
if(!this->skip_enabled.has_value()) {
|
||||
for(const auto& assignment : this->assigned_server_groups()) {
|
||||
auto group_permissions = assignment->permissions();
|
||||
auto flagged_value = group_permissions->permission_value_flagged(permission::b_client_skip_channelgroup_permissions);
|
||||
if(flagged_value.has_value) {
|
||||
this->skip_enabled = std::make_optional(permission::v2::permission_granted(1, flagged_value));
|
||||
if(*this->skip_enabled) {
|
||||
logTrace(this->virtual_server_id, "[Permission] Found skip permission in client server group. Group: {} ({})", assignment->group_id(), assignment->display_name());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!this->skip_enabled.has_value()) {
|
||||
this->skip_enabled = std::make_optional(false);
|
||||
}
|
||||
|
||||
return *this->skip_enabled;
|
||||
}
|
||||
|
||||
bool ClientPermissionCalculator::permission_granted(const permission::PermissionType &permission,
|
||||
const permission::PermissionValue &required_value, bool granted) {
|
||||
return this->permission_granted(permission, { required_value, true }, granted);
|
||||
}
|
||||
|
||||
bool ClientPermissionCalculator::permission_granted(const permission::PermissionType &permission,
|
||||
const permission::v2::PermissionFlaggedValue &required_value, bool granted) {
|
||||
return permission::v2::permission_granted(required_value, this->calculate_permission(permission, granted));
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
#include <Definitions.h>
|
||||
#include <PermissionManager.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace ts {
|
||||
class BasicChannel;
|
||||
}
|
||||
|
||||
namespace ts::server {
|
||||
class DataClient;
|
||||
class VirtualServer;
|
||||
|
||||
namespace groups {
|
||||
class ChannelGroup;
|
||||
class ServerGroup;
|
||||
class GroupManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for calculating the client permissions for a certain channel.
|
||||
* Note: All functions are not thread save!
|
||||
*/
|
||||
class ClientPermissionCalculator {
|
||||
public:
|
||||
/* When providing the pointer to the channel the channel tree **should** not be locked in any way! */
|
||||
explicit ClientPermissionCalculator(DataClient* /* client */, const std::shared_ptr<BasicChannel>& /* target channel */);
|
||||
explicit ClientPermissionCalculator(DataClient* /* client */, ChannelId /* target channel id */);
|
||||
explicit ClientPermissionCalculator(
|
||||
const std::shared_ptr<VirtualServer>& /* server */,
|
||||
ClientDbId /* client database id */,
|
||||
ClientType /* client type */,
|
||||
ChannelId /* target channel id */
|
||||
);
|
||||
|
||||
/**
|
||||
* Calculate the given permissions.
|
||||
* This method can be called from everywhere without any locking needed.
|
||||
* @param granted
|
||||
* @return
|
||||
*/
|
||||
[[nodiscard]] permission::v2::PermissionFlaggedValue calculate_permission(
|
||||
permission::PermissionType,
|
||||
bool granted = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Calculate the given permissions.
|
||||
* This method can be called from everywhere without any locking needed.
|
||||
* @param channel
|
||||
* @param calculate_granted
|
||||
* @return
|
||||
*/
|
||||
[[nodiscard]] std::vector<std::pair<permission::PermissionType, permission::v2::PermissionFlaggedValue>> calculate_permissions(
|
||||
const std::deque<permission::PermissionType>&,
|
||||
bool calculate_granted = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Test if the target has the target permission granted.
|
||||
* If the client does not have any value assigned for the permission `false` will be returned.
|
||||
*/
|
||||
[[nodiscard]] bool permission_granted(
|
||||
const permission::PermissionType& /* target permission */,
|
||||
const permission::PermissionValue& /* required value */,
|
||||
bool /* granted permission */ = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Test if the target has the target permission granted.
|
||||
* If the client does not have any value assigned for the permission,
|
||||
* `true` will be returned if the `required value` contains no value
|
||||
* otherwise false will be returned.
|
||||
*
|
||||
* This method should be used when testing permissions which are allowed by default except if they're
|
||||
* specified otherwise. An example permission would be if we're testing against the channel needed join power.
|
||||
*/
|
||||
[[nodiscard]] bool permission_granted(
|
||||
const permission::PermissionType& /* target permission */,
|
||||
const permission::v2::PermissionFlaggedValue& /* required value */,
|
||||
bool /* granted permission */ = false
|
||||
);
|
||||
|
||||
//const PermissionValue& required, const PermissionFlaggedValue& given, bool requires_given = true
|
||||
private:
|
||||
/* given fields */
|
||||
ServerId virtual_server_id;
|
||||
ClientDbId client_database_id;
|
||||
ClientType client_type;
|
||||
std::shared_ptr<BasicChannel> channel_;
|
||||
|
||||
std::shared_ptr<groups::GroupManager> group_manager_{};
|
||||
std::function<std::shared_ptr<groups::ChannelGroup>()> default_channel_group{[]{ return nullptr; }};
|
||||
std::function<std::shared_ptr<groups::ServerGroup>()> default_server_group{[]{ return nullptr; }};
|
||||
|
||||
/* fields which will be set when calculating permissions */
|
||||
std::shared_ptr<permission::v2::PermissionManager> client_permissions_{};
|
||||
|
||||
std::optional<bool> skip_enabled{};
|
||||
|
||||
std::optional<std::shared_ptr<groups::ChannelGroup>> assigned_channel_group_{};
|
||||
std::optional<std::vector<std::shared_ptr<groups::ServerGroup>>> assigned_server_groups_{};
|
||||
|
||||
void initialize_client(DataClient* /* client */);
|
||||
void initialize_default_groups(const std::shared_ptr<VirtualServer>& /* server */);
|
||||
|
||||
[[nodiscard]] const std::vector<std::shared_ptr<groups::ServerGroup>>& assigned_server_groups();
|
||||
[[nodiscard]] const std::shared_ptr<groups::ChannelGroup>& assigned_channel_group();
|
||||
[[nodiscard]] const std::shared_ptr<permission::v2::PermissionManager>& client_permissions();
|
||||
[[nodiscard]] bool has_global_skip_permission();
|
||||
};
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/std_unique_ptr.h>
|
||||
#include <Properties.h>
|
||||
#include "VirtualServerManager.h"
|
||||
#include "src/server/VoiceServer.h"
|
||||
#include "InstanceHandler.h"
|
||||
#include "InstanceHandler.h"
|
||||
#include "./groups/GroupManager.h"
|
||||
|
||||
//TODO: When using the new command builder make sure you're using a std::deque as the underlying bulk type!
|
||||
|
||||
@@ -16,13 +15,6 @@ using namespace ts::server;
|
||||
|
||||
#define PREFIX string("[SNAPSHOT] ")
|
||||
|
||||
inline std::string avArguments(const ts::Command& cmd, int index){
|
||||
stringstream res;
|
||||
for(const auto &key : cmd[index].keys())
|
||||
res << key << "=" << cmd[index][key].as<std::string>() << " ";
|
||||
return res.str();
|
||||
}
|
||||
|
||||
//TeamSpeak: permid=i_channel_needed_permission_modify_power permvalue=75 permskip=0 permnegated=0
|
||||
//TeaSpeak: perm=i_channel_needed_permission_modify_power value=75 granted=75 flag_skip=0 flag_negated=0
|
||||
struct SnapshotPermissionEntry {
|
||||
@@ -32,104 +24,9 @@ struct SnapshotPermissionEntry {
|
||||
bool negated = false;
|
||||
bool skipped = false;
|
||||
|
||||
SnapshotPermissionEntry(const std::shared_ptr<permission::PermissionTypeEntry>& type, permission::PermissionValue value, permission::PermissionValue grant) : type(std::move(type)), value(value), grant(grant) {}
|
||||
SnapshotPermissionEntry(const std::shared_ptr<permission::PermissionTypeEntry>& type, permission::PermissionValue value, permission::PermissionValue grant, bool negated, bool skipped) : type(type), value(value), grant(grant), negated(negated), skipped(skipped) {}
|
||||
SnapshotPermissionEntry() = default;
|
||||
|
||||
static deque<std::unique_ptr<SnapshotPermissionEntry>> parse(const Command& cmd, int& index, permission::teamspeak::GroupType type, int version, const std::vector<std::string>& ends, bool ignore_first = false) {
|
||||
deque<std::unique_ptr<SnapshotPermissionEntry>> result{};
|
||||
|
||||
while(true) {
|
||||
bool end_reached = false;
|
||||
for(const auto& e : ends)
|
||||
if(cmd[index].has(e)) { end_reached = true; break; }
|
||||
if(end_reached || cmd.bulkCount() < index) {
|
||||
if(ignore_first) ignore_first = false;
|
||||
else break;
|
||||
}
|
||||
|
||||
if(version == 0) {
|
||||
auto permission_name = cmd[index]["permid"].string();
|
||||
for(const auto& mapped : permission::teamspeak::map_key(permission_name, type)) {
|
||||
auto permission = permission::resolvePermissionData(mapped);
|
||||
if(permission->type == permission::unknown) {
|
||||
logError(0, "Failed to parse permission {}. Original: {}", mapped, permission_name);
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for(auto& e : result)
|
||||
if(e->type->type == permission->type) {
|
||||
found = true;
|
||||
if(permission->grantName() == mapped)
|
||||
e->grant = cmd[index]["permvalue"];
|
||||
else {
|
||||
e->value = cmd[index]["permvalue"];
|
||||
e->negated = cmd[index]["permnegated"];
|
||||
e->skipped = cmd[index]["permskip"];
|
||||
}
|
||||
}
|
||||
if(!found) {
|
||||
auto entry = make_unique<SnapshotPermissionEntry>();
|
||||
entry->type = permission;
|
||||
if(permission->grantName() == mapped)
|
||||
entry->grant = cmd[index]["permvalue"];
|
||||
else {
|
||||
entry->value = cmd[index]["permvalue"];
|
||||
entry->negated = cmd[index]["permnegated"];
|
||||
entry->skipped = cmd[index]["permskip"];
|
||||
}
|
||||
result.push_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
} else if(version >= 1) {
|
||||
auto permission_name = cmd[index]["perm"].string();
|
||||
|
||||
auto permission = permission::resolvePermissionData(permission_name);
|
||||
if(permission->type == permission::unknown) {
|
||||
logError(0, "Failed to parse permission {}", permission_name);
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto entry = make_unique<SnapshotPermissionEntry>();
|
||||
entry->type = permission;
|
||||
entry->value = cmd[index]["value"];
|
||||
entry->grant = cmd[index]["grant"];
|
||||
entry->skipped = cmd[index]["flag_skip"];
|
||||
entry->negated = cmd[index]["flag_negated"];
|
||||
result.push_back(std::move(entry));
|
||||
} else {
|
||||
logError(0, "Failed to parse snapshot permission entry! Invalid version!");
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
|
||||
deque<std::unique_ptr<SnapshotPermissionEntry>> addings{};
|
||||
for(const auto& perm : result) {
|
||||
if(perm->type->type == permission::i_group_auto_update_type) { //Migrate this type
|
||||
for(const auto& e : permission::update::migrate)
|
||||
if(e.type == perm->value) {
|
||||
auto _type = permission::resolvePermissionData(e.permission.name);
|
||||
if(_type->type == permission::unknown) {
|
||||
logError(0, "Invalid update permission for update group {}. Permission name: {}", e.type, e.permission.name);
|
||||
} else {
|
||||
addings.push_back(make_unique<SnapshotPermissionEntry>(_type, e.permission.value, e.permission.granted, e.permission.negated, e.permission.skipped));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for(auto& a : addings)
|
||||
result.push_back(move(a));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void write(Command& cmd, int& index, permission::teamspeak::GroupType type, int version) {
|
||||
if(version == 0) {
|
||||
if(this->value != permNotGranted) {
|
||||
@@ -163,542 +60,6 @@ struct SnapshotPermissionEntry {
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<VirtualServer> VirtualServerManager::createServerFromSnapshot(shared_ptr<VirtualServer> old, std::string host,
|
||||
uint16_t port, const ts::Command &arguments,
|
||||
std::string &error) {
|
||||
ServerId serverId = 0;
|
||||
map<ClientDbId, map<ChannelId, ClientDbId>> channelGroupRelations; //cid is the new cgid
|
||||
map<GroupId, GroupId> channelGroupMapping;
|
||||
map<ClientDbId, vector<GroupId>> serverGroupRelations; //gid is the new gid
|
||||
map<GroupId, GroupId> serverGroupMapping;
|
||||
map<ClientDbId, ClientDbId> db_mapping_client_id;
|
||||
|
||||
deque<pair<std::string, threads::Future<sql::result>>> futures;
|
||||
|
||||
bool sid_success = false;
|
||||
serverId = this->next_available_server_id(sid_success);
|
||||
if(!sid_success) {
|
||||
error = "failed to generate new sid";
|
||||
return nullptr;
|
||||
}
|
||||
ServerId log_server_id = old ? old->getServerId() : serverId;
|
||||
|
||||
futures.emplace_back(
|
||||
"server id register",
|
||||
sql::command(this->handle->getSql(), "INSERT INTO `servers` (`serverId`, `host`, `port`) VALUES (:sid, :host, :port)", variable{":sid", serverId}, variable{":host", host}, variable{":port", port}).executeLater()
|
||||
);
|
||||
sql::model sql_insert_channel(this->handle->getSql(), "INSERT INTO `channels` (`serverId`, `channelId`, `type`, `parentId`) VALUES (:sid, :id, :type, :pid)", variable{":sid", serverId});
|
||||
sql::model sql_insert_group_assignment(this->handle->getSql(), "INSERT INTO `assignedGroups` (`serverId`, `cldbid`, `groupId`, `channelId`, `until`) VALUES (:sid, :cldbid, :gid, :chid, :until)", variable{":sid", serverId}, variable{":until", 0});
|
||||
sql::model sql_insert_permission(this->handle->getSql(), "INSERT INTO `permissions` (`serverId`, `type`, `id`, `channelId`, `permId`, `value`, `grant`, `flag_skip`, `flag_negate`) VALUES (:serverId, :type, :id, :chId, :permId, :value, :grant, :flag_skip, :flag_negate)",
|
||||
variable{":serverId", serverId});
|
||||
sql::model sql_update_permission_grant(this->handle->getSql(), "UPDATE `permissions` SET `grant` = :grant WHERE `serverId` = :sid AND `type` = :type AND `id` = :id AND `channelId` = :chid AND `permId` = :key", variable{":sid", serverId});
|
||||
sql::model sql_insert_property(this->handle->getSql(), "INSERT INTO `properties` (`serverId`, `type`, `id`, `key`, `value`) VALUES (:sid, :type, :id, :key, :value)", variable{":sid", serverId});
|
||||
sql::model sql_insert_bot(this->handle->getSql(), "INSERT INTO `musicbots` (`serverId`, `botId`, `uniqueId`, `owner`) VALUES (:server_id, :bot_id, :unique_id, :owner)", variable{":server_id", serverId});
|
||||
sql::model sql_insert_playlist(this->handle->getSql(), "INSERT INTO `playlists` (`serverId`, `playlist_id`) VALUES (:server_id, :playlist_id)", variable{":server_id", serverId});
|
||||
sql::model sql_insert_playlist_song(this->handle->getSql(), "INSERT INTO `playlist_songs` (`serverId`, `playlist_id`, `song_id`, `order_id`, `invoker_dbid`, `url`, `url_loader`, `loaded`, `metadata`) VALUES (:server_id, :playlist_id, :song_id, :order_id, :invoker_dbid, :url, :url_loader, :loaded, :metadata)", variable{":server_id", serverId});
|
||||
|
||||
int index = 0;
|
||||
auto snapshot_version = arguments[index].has("snapshot_version") ? arguments[index++]["snapshot_version"] : 0;
|
||||
debugMessage(0, "Got server snapshot with version {}", snapshot_version);
|
||||
while(true){
|
||||
for(auto &key : arguments[index].keys()){
|
||||
if(key == "end_virtualserver") continue;
|
||||
if(key == "begin_virtualserver") continue;
|
||||
if(snapshot_version == 0) {
|
||||
if(key == "virtualserver_download_quota" || key == "virtualserver_upload_quota" || key == "virtualserver_max_download_total_bandwidth" || key == "virtualserver_max_upload_total_bandwidth") {
|
||||
auto value = arguments[index][key].string();
|
||||
try {
|
||||
arguments[index][key] = to_string((int64_t) std::stoull(value));
|
||||
} catch(const std::exception& ex) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
debugMessage(LOG_GENERAL, PREFIX + " Having server key {} = {}", key, arguments[index][key].as<std::string>());
|
||||
futures.emplace_back(
|
||||
"server property import proprty=" + key,
|
||||
sql_insert_property.command().values(
|
||||
variable{":type", property::PropertyType::PROP_TYPE_SERVER},
|
||||
variable{":id", 0},
|
||||
variable{":key", key},
|
||||
variable{":value", arguments[index][key].string()}
|
||||
).executeLater()
|
||||
);
|
||||
}
|
||||
|
||||
if(arguments[index].has("end_virtualserver")) break;
|
||||
index++;
|
||||
}
|
||||
|
||||
|
||||
//`serverId` INT NOT NULL, `playlist_id` INT, `song_id` INT, `order_id` INT, `invoker_dbid` INT, `url` TEXT, `url_loader` TEXT
|
||||
/*
|
||||
* sql::command(this->handle->getSql(), "INSERT INTO `playlists` (`serverId`, `playlist_id`) VALUES (:server_id, :playlist_id)",
|
||||
variable{":server_id", this->ref_server()->getServerId()},
|
||||
variable{":playlist_id", playlist_id}
|
||||
)
|
||||
*/
|
||||
index++;
|
||||
for(;index < arguments.bulkCount(); index++) {
|
||||
if (arguments[index].has("begin_channels")) {
|
||||
while (true) {
|
||||
if (arguments[index].has("end_channels")) break;
|
||||
|
||||
auto channel_id = arguments[index]["channel_id"].string();
|
||||
futures.emplace_back(
|
||||
"channel register cid=" + channel_id,
|
||||
sql_insert_channel.command().values(variable{":id", channel_id}, variable{":type", -1}, variable{":pid", arguments[index]["channel_pid"].string()}).executeLater()
|
||||
); //TODO findout type!
|
||||
for (const auto &key : arguments[index].keys()) {
|
||||
if(key == "channel_id") continue;
|
||||
if(key == "channel_pid") continue;
|
||||
if(key == "channel_security_salt") continue;
|
||||
if(key == "channel_filepath") continue; //TODO implement channel_filepath?
|
||||
if(key == "begin_channels") continue;
|
||||
|
||||
debugMessage(log_server_id, PREFIX + "Having channel key " + key + "=" + arguments[index][key].as<std::string>() + " for channel " + arguments[index]["channel_id"].as<std::string>());
|
||||
|
||||
futures.push_back({
|
||||
"channel prop register cid=" + channel_id + " property=" + key,
|
||||
sql_insert_property.command().values(variable{":sid", serverId},
|
||||
variable{":type", property::PropertyType::PROP_TYPE_CHANNEL},
|
||||
variable{":id", channel_id},
|
||||
variable{":key", key},
|
||||
variable{":value", arguments[index][key].as<std::string>()}
|
||||
).executeLater()
|
||||
});
|
||||
}
|
||||
index++;
|
||||
}
|
||||
} else if (arguments[index].has("begin_clients")) {
|
||||
while (true) {
|
||||
if (arguments[index].has("end_clients")) break;
|
||||
debugMessage(log_server_id, PREFIX + "Create client {}/{} -> end: {}",arguments[index]["client_id"].as<std::string>(), arguments[index]["client_nickname"].as<std::string>(), to_string(arguments[index].has("end_clients")));
|
||||
|
||||
ClientDbId oldId = 0;
|
||||
auto res = sql::command(this->handle->getSql(), "SELECT `cldbid` FROM `clients` WHERE `clientUid` = :uid AND `serverId` = 0", variable{":uid", arguments[index]["client_unique_id"].as<std::string>()}).query([](ClientDbId* ptr, int, char** v, char**){ *ptr = stoll(v[0]); return 0;}, &oldId);
|
||||
LOG_SQL_CMD(res);
|
||||
if(oldId == 0){
|
||||
res = sql::command(this->handle->getSql(), "SELECT `cldbid` FROM `clients` WHERE `serverId` = 0 ORDER BY `cldbid` DESC LIMIT 1").query([](ClientDbId* ptr, int length, char** values, char** names){
|
||||
*ptr = static_cast<ClientDbId>(stoll(values[0]));
|
||||
return 0;
|
||||
}, &oldId);
|
||||
LOG_SQL_CMD(res);
|
||||
|
||||
oldId += 1;
|
||||
//Create a new client
|
||||
sql::command(this->handle->getSql(), "INSERT INTO `clients` (`serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName`) VALUES (:sid, :cldbid, :cluid, :firstConnect, :lastConnect, :connections, :name)",
|
||||
variable{":sid", 0}, variable{":cldbid", oldId}, variable{":cluid", arguments[index]["client_unique_id"].as<std::string>()},
|
||||
variable{":firstConnect", arguments[index]["client_created"].as<std::string>()},
|
||||
variable{":lastConnect", "0"}, variable{":connections", "0"}, variable{":name", arguments[index]["client_nickname"].as<std::string>()}).execute(); //TODO log error
|
||||
}
|
||||
db_mapping_client_id[arguments[index]["client_id"].as<ClientDbId>()] = oldId;
|
||||
sql::command(this->handle->getSql(), "INSERT INTO `clients` (`serverId`, `cldbid`, `clientUid`, `firstConnect`, `lastConnect`, `connections`, `lastName`) VALUES (:sid, :cldbid, :cluid, :firstConnect, :lastConnect, :connections, :name)",
|
||||
variable{":sid", serverId}, variable{":cldbid", oldId}, variable{":cluid", arguments[index]["client_unique_id"].as<std::string>()},
|
||||
variable{":firstConnect", arguments[index]["client_created"].as<std::string>()},
|
||||
variable{":lastConnect", "0"}, variable{":connections", "0"}, variable{":name", arguments[index]["client_nickname"].as<std::string>()}).execute(); //TODO log error
|
||||
//TODO here
|
||||
index++;
|
||||
}
|
||||
} else if (arguments[index].has("begin_permissions")) {
|
||||
while (true) {
|
||||
index++;
|
||||
if (arguments[index].has("end_permissions")) break;
|
||||
|
||||
else if (arguments[index].has("server_groups")) {
|
||||
GroupId currentGroupId = 1;
|
||||
while (true) {
|
||||
if (arguments[index].has("end_groups")) break;
|
||||
|
||||
if(!arguments[index].has("id")){ //new group
|
||||
logError(0, "Invalid server group permission entry! Missing group id!");
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
currentGroupId = static_cast<GroupId>(GroupManager::generateGroupId(this->handle->getSql()));
|
||||
debugMessage(log_server_id, PREFIX + "Insert group: " + to_string(currentGroupId) + " - " + arguments[index]["name"].as<string>());
|
||||
serverGroupMapping[arguments[index]["id"].as<GroupId>()] = currentGroupId;
|
||||
|
||||
LOG_SQL_CMD(sql::command(this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
|
||||
variable{":sid", serverId},
|
||||
variable{":gid", currentGroupId},
|
||||
variable{":target", GROUPTARGET_SERVER},
|
||||
variable{":type", GroupType::GROUP_TYPE_NORMAL},
|
||||
variable{":name", arguments[index]["name"].as<string>()})
|
||||
.execute());
|
||||
|
||||
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::SERVER, snapshot_version, {"end_group"});
|
||||
for(const auto& entry : permissions) {
|
||||
futures.push_back({
|
||||
"server group permission sgid=" + to_string(currentGroupId) + " permId=" + entry->type->name,
|
||||
sql_insert_permission.command().values(
|
||||
variable{":id", currentGroupId},
|
||||
variable{":type", permission::SQL_PERM_GROUP},
|
||||
variable{":chId", 0},
|
||||
|
||||
variable{":permId", entry->type->name},
|
||||
variable{":value", entry->value},
|
||||
variable{":grant", entry->grant},
|
||||
variable{":flag_skip", entry->skipped},
|
||||
variable{":flag_negate", entry->negated}
|
||||
).executeLater()
|
||||
});
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
//Relations for sgroups after list
|
||||
index++;
|
||||
while (true) {
|
||||
if (arguments[index].has("end_relations")) break;
|
||||
serverGroupRelations[db_mapping_client_id[arguments[index]["cldbid"].as<ClientDbId>()]].push_back(serverGroupMapping[arguments[index]["gid"].as<GroupId>()]);
|
||||
futures.push_back({
|
||||
"server group relation",
|
||||
sql_insert_group_assignment.command().values(variable{":cldbid", db_mapping_client_id[arguments[index]["cldbid"].as<ClientDbId>()]}, variable{":gid", serverGroupMapping[arguments[index]["gid"].as<GroupId>()]}, variable{":chid", "0"}).executeLater()
|
||||
});
|
||||
index++;
|
||||
}
|
||||
} else if(arguments[index].has("channel_groups")){
|
||||
GroupId currentGroupId = 1;
|
||||
while (true) {
|
||||
if (arguments[index].has("end_groups")) break;
|
||||
|
||||
if(!arguments[index].has("id")){ //new group
|
||||
logError(0, "Invalid server group permission entry! Missing group id!");
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
currentGroupId = static_cast<GroupId>(GroupManager::generateGroupId(this->handle->getSql()));
|
||||
debugMessage(log_server_id, PREFIX + "Insert channel group: " + to_string(currentGroupId) + " - " + arguments[index]["name"].as<string>());
|
||||
channelGroupMapping[arguments[index]["id"].as<GroupId>()] = currentGroupId;
|
||||
|
||||
LOG_SQL_CMD(sql::command(this->handle->getSql(), "INSERT INTO `groups` (`serverId`, `groupId`, `target`, `type`, `displayName`) VALUES (:sid, :gid, :target, :type, :name)",
|
||||
variable{":sid", serverId},
|
||||
variable{":gid", currentGroupId},
|
||||
variable{":target", GROUPTARGET_CHANNEL},
|
||||
variable{":type", GroupType::GROUP_TYPE_NORMAL},
|
||||
variable{":name", arguments[index]["name"].as<string>()})
|
||||
.execute());
|
||||
|
||||
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::SERVER, snapshot_version, {"end_group"});
|
||||
for(const auto& entry : permissions) {
|
||||
futures.push_back({
|
||||
"channel group property insert cgid=" + to_string(currentGroupId) + " property=" + entry->type->name,
|
||||
sql_insert_permission.command().values(
|
||||
variable{":id", currentGroupId},
|
||||
variable{":type", permission::SQL_PERM_GROUP},
|
||||
variable{":chId", 0},
|
||||
|
||||
variable{":permId", entry->type->name},
|
||||
variable{":value", entry->value},
|
||||
variable{":grant", entry->grant},
|
||||
variable{":flag_skip", entry->skipped},
|
||||
variable{":flag_negate", entry->negated}
|
||||
).executeLater()
|
||||
});
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
//Relations for sgroups after list
|
||||
ChannelId chId = 0;
|
||||
index++;
|
||||
while (true) {
|
||||
if (arguments[index].has("end_relations")) break;
|
||||
if(arguments[index].has("iid")) chId = arguments[index]["iid"];
|
||||
channelGroupRelations[db_mapping_client_id[arguments[index]["cldbid"].as<ClientDbId>()]][chId] = channelGroupMapping[arguments[index]["gid"].as<GroupId>()];
|
||||
futures.push_back({
|
||||
"channel group relation",
|
||||
sql_insert_group_assignment.command().values(variable{":cldbid", db_mapping_client_id[arguments[index]["cldbid"].as<ClientDbId>()]}, variable{":gid", channelGroupMapping[arguments[index]["gid"].as<GroupId>()]}, variable{":chid", chId}).executeLater()
|
||||
});
|
||||
index++;
|
||||
}
|
||||
} else if (arguments[index].has("client_flat")) {
|
||||
ClientDbId currentId = 0;
|
||||
while (true) {
|
||||
if (arguments[index].has("end_flat")) break;
|
||||
if(arguments[index].has("id1"))
|
||||
currentId = arguments[index]["id1"];
|
||||
|
||||
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::CLIENT, snapshot_version, {"id1", "end_flat"}, true);
|
||||
for(const auto& entry : permissions) {
|
||||
futures.push_back({
|
||||
"client permission insert clientId=" + to_string(currentId) + " permId=" + entry->type->name,
|
||||
sql_insert_permission.command().values(
|
||||
variable{":id", currentId},
|
||||
variable{":type", permission::SQL_PERM_USER},
|
||||
variable{":chId", 0},
|
||||
|
||||
variable{":permId", entry->type->name},
|
||||
variable{":value", entry->value},
|
||||
variable{":grant", entry->grant},
|
||||
variable{":flag_skip", entry->skipped},
|
||||
variable{":flag_negate", entry->negated}
|
||||
).executeLater()
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (arguments[index].has("channel_flat")) {
|
||||
ChannelId currentChannelId = 0;
|
||||
while (true) {
|
||||
if (arguments[index].has("end_flat")) break;
|
||||
|
||||
if (arguments[index].has("id1"))
|
||||
currentChannelId = arguments[index]["id1"]; //We also have id2 unknown for what this is. Maybe parent?
|
||||
|
||||
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::CHANNEL, snapshot_version, {"id1", "end_flat"}, true);
|
||||
for(const auto& entry : permissions) {
|
||||
futures.push_back({
|
||||
"channel permission insert channelId=" + to_string(currentChannelId) + " permId=" + entry->type->name,
|
||||
sql_insert_permission.command().values(
|
||||
variable{":id", 0},
|
||||
variable{":type", permission::SQL_PERM_CHANNEL},
|
||||
variable{":chId", currentChannelId},
|
||||
|
||||
variable{":permId", entry->type->name},
|
||||
variable{":value", entry->value},
|
||||
variable{":grant", entry->grant},
|
||||
variable{":flag_skip", entry->skipped},
|
||||
variable{":flag_negate", entry->negated}
|
||||
).executeLater()
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (arguments[index].has("channel_client_flat")) {
|
||||
ClientDbId currentClientId = 0;
|
||||
ChannelId currentChannelId = 0;
|
||||
while (true) {
|
||||
if (arguments[index].has("end_flat")) break;
|
||||
//id1 = channel id
|
||||
//id2 = client id
|
||||
if(arguments[index].has("id1"))
|
||||
currentChannelId = arguments[index]["id1"];
|
||||
if(arguments[index].has("id2"))
|
||||
currentClientId = db_mapping_client_id[arguments[index]["id2"]];
|
||||
if(currentChannelId > 0 && currentClientId > 0){
|
||||
auto permissions = SnapshotPermissionEntry::parse(arguments, index, permission::teamspeak::CLIENT, snapshot_version, {"id1", "id2", "end_flat"}, true);
|
||||
for(const auto& entry : permissions) {
|
||||
futures.push_back({
|
||||
"client channel permission insert",
|
||||
sql_insert_permission.command().values(
|
||||
variable{":id", currentClientId},
|
||||
variable{":type", permission::SQL_PERM_CHANNEL},
|
||||
variable{":chId", currentChannelId},
|
||||
|
||||
variable{":permId", entry->type->name},
|
||||
variable{":value", entry->value},
|
||||
variable{":grant", entry->grant},
|
||||
variable{":flag_skip", entry->skipped},
|
||||
variable{":flag_negate", entry->negated}
|
||||
).executeLater()
|
||||
});
|
||||
}
|
||||
} else index++;
|
||||
}
|
||||
} else {
|
||||
error = "Invalid tag in permissions. Available: " + avArguments(arguments, index);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
} else if(arguments[index].has("begin_music")) {
|
||||
PlaylistId playlist_index = 0;
|
||||
PlaylistId playlist_error = 0xFFFFFFFF;
|
||||
map<PlaylistId, PlaylistId> db_mapping_playlist;
|
||||
|
||||
/*
|
||||
//gather static info (not needed because its a new server)
|
||||
{
|
||||
auto sql_result = sql::command(this->handle->getSql(), "SELECT `playlist_id` FROM `playlists` WHERE `serverId` = :server_id ORDER BY `playlist_id` DESC", variable{":server_id", serverId}).query([&](int length, string* values, string* names){
|
||||
if(length != 1) return;
|
||||
|
||||
try {
|
||||
playlist_index = (PlaylistId) stoll(values[0]);
|
||||
} catch(const std::exception& ex) {
|
||||
error = "Failed to parse playlist id from database. ID: " + values[0];
|
||||
return;
|
||||
}
|
||||
});
|
||||
LOG_SQL_CMD(sql_result);
|
||||
|
||||
if(playlist_index == 0) {
|
||||
error = "failed to gather info";
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
while(!arguments[index].has("end_music")) {
|
||||
if(arguments[index].has("begin_bots")) {
|
||||
while(!arguments[index].has("end_bots")) {
|
||||
ClientDbId bot_id = arguments[index]["bot_id"];
|
||||
if(db_mapping_client_id.find(bot_id) == db_mapping_client_id.end()) {
|
||||
logError(log_server_id, PREFIX + "Failed to insert music bot within id {}. (Mapping not found)", bot_id);
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
ClientDbId new_bot_id = db_mapping_client_id[bot_id];
|
||||
|
||||
//begin_bots
|
||||
auto sql_result = sql_insert_bot.command().values(
|
||||
variable{":bot_id", new_bot_id},
|
||||
variable{":unique_id", arguments[index]["bot_unique_id"].string()},
|
||||
variable{":owner", arguments[index]["bot_owner_id"].string()}
|
||||
).execute();
|
||||
if(!sql_result) {
|
||||
logError(log_server_id, PREFIX + "Failed to insert music bot with id {} (old: {}). ({})", new_bot_id, bot_id, sql_result.fmtStr());
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
for(const auto& key : arguments[index].keys()) {
|
||||
if(key == "begin_music") continue;
|
||||
if(key == "begin_bots") continue;
|
||||
if(key == "bot_unique_id") continue;
|
||||
if(key == "bot_owner_id") continue;
|
||||
if(key == "bot_id") continue;
|
||||
|
||||
const auto& property = property::info<property::ClientProperties>(key);
|
||||
if(property->property_index == property::CLIENT_UNDEFINED) {
|
||||
debugMessage(log_server_id, PREFIX + "Failed to parse give music bot property {} for bot {} (old: {}). Value: {}", key, new_bot_id, bot_id, arguments[index][key].string());
|
||||
continue;
|
||||
}
|
||||
|
||||
futures.push_back({
|
||||
"music bot insert",
|
||||
sql_insert_property.command().values(
|
||||
variable{":type", property::PropertyType::PROP_TYPE_CLIENT},
|
||||
variable{":id", new_bot_id},
|
||||
variable{":key", key},
|
||||
variable{":value", arguments[index][key].string()}
|
||||
).executeLater()
|
||||
});
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
} else if(arguments[index].has("begin_playlist")) {
|
||||
while(!arguments[index].has("end_playlist")) {
|
||||
PlaylistId playlist_id = arguments[index]["playlist_id"];
|
||||
db_mapping_playlist[++playlist_index] = playlist_id;
|
||||
|
||||
auto sql_result = sql_insert_playlist.command().values(variable{":playlist_id", playlist_index}).execute();
|
||||
if(!sql_result) {
|
||||
db_mapping_playlist[playlist_index] = playlist_error;
|
||||
logError(log_server_id, PREFIX + "Failed to insert playlist with id {} (old: {}). ({})", playlist_index, playlist_id, sql_result.fmtStr());
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
for(const auto& key : arguments[index].keys()) {
|
||||
if(key == "begin_music") continue;
|
||||
if(key == "begin_playlist") continue;
|
||||
if(key == "playlist_id") continue;
|
||||
|
||||
const auto& property = property::info<property::ClientProperties>(key);
|
||||
if(property->property_index == property::CLIENT_UNDEFINED) {
|
||||
debugMessage(log_server_id, PREFIX + "Failed to parse given playlist property {} for playlist {} (old: {}). Value: {}", key, playlist_index, playlist_id, arguments[index][key].string());
|
||||
continue;
|
||||
}
|
||||
|
||||
futures.push_back({
|
||||
"playlist insert",
|
||||
sql_insert_property.command().values(
|
||||
variable{":type", property::PropertyType::PROP_TYPE_PLAYLIST},
|
||||
variable{":id", playlist_index},
|
||||
variable{":key", key},
|
||||
variable{":value", arguments[index][key].string()}
|
||||
).executeLater()
|
||||
});
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
} else if(arguments[index].has("begin_playlist_songs")) {
|
||||
PlaylistId current_playlist = 0;
|
||||
PlaylistId current_playlist_mapped = 0;
|
||||
while(!arguments[index].has("end_playlist_songs")) {
|
||||
if(arguments[index].has("song_playlist_id")) {
|
||||
current_playlist = arguments[index]["song_playlist_id"];
|
||||
current_playlist_mapped = (PlaylistId) db_mapping_client_id[current_playlist];
|
||||
if(current_playlist_mapped == 0) {
|
||||
logError(log_server_id, PREFIX + "Failed to insert playlist song entry {}. Playlist id {} hasn't been mapped", arguments[index]["song_id"].value(), current_playlist);
|
||||
current_playlist_mapped = playlist_error;
|
||||
}
|
||||
}
|
||||
if(current_playlist_mapped == playlist_error) { /* current list is an error */
|
||||
index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto sql_future = sql_insert_playlist_song.command().values(
|
||||
variable{":playlist_id", current_playlist_mapped},
|
||||
variable{":song_id", arguments[index]["song_id"].value()},
|
||||
variable{":order_id", arguments[index]["song_order"].value()},
|
||||
variable{":invoker_dbid", arguments[index]["song_invoker"].value()},
|
||||
variable{":url", arguments[index]["song_url"].value()},
|
||||
variable{":url_loader", arguments[index]["song_url_loader"].value()},
|
||||
variable{":loaded", arguments[index]["song_loaded"].value()},
|
||||
variable{":metadata", arguments[index]["song_metadata"].value()}
|
||||
).executeLater();
|
||||
futures.push_back({
|
||||
"song insert",
|
||||
sql_future
|
||||
});
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
} else {
|
||||
error = "Invalid root tag. Available: " + avArguments(arguments, index);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
debugMessage(serverId, "Wait for success!");
|
||||
|
||||
for(const auto& future : futures) {
|
||||
auto result = future.second.waitAndGet({-1, "timeout"}, chrono::system_clock::now() + chrono::seconds(5));
|
||||
if(!result) {
|
||||
logError(serverId, "Failed to execute snapshotdeploy command {}: {}", future.first, result.fmtStr());
|
||||
}
|
||||
}
|
||||
|
||||
if(old)
|
||||
this->deleteServer(old);
|
||||
//Now we load the server
|
||||
auto server = make_shared<VirtualServer>(serverId, this->handle->getSql());
|
||||
server->self = server;
|
||||
if(!server->initialize(false)) {
|
||||
//FIXME Error handling!
|
||||
}
|
||||
server->properties()[property::VIRTUALSERVER_HOST] = host;
|
||||
server->properties()[property::VIRTUALSERVER_PORT] = port;
|
||||
server->properties()[property::VIRTUALSERVER_ASK_FOR_PRIVILEGEKEY] = false;
|
||||
|
||||
{
|
||||
threads::MutexLock l(this->instanceLock);
|
||||
this->instances.push_back(server);
|
||||
}
|
||||
this->adjust_executor_threads();
|
||||
|
||||
server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP] = serverGroupMapping[server->properties()[property::VIRTUALSERVER_DEFAULT_SERVER_GROUP].as<GroupId>()];
|
||||
server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP] = channelGroupMapping[server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_GROUP].as<GroupId>()];
|
||||
server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP] = channelGroupMapping[server->properties()[property::VIRTUALSERVER_DEFAULT_CHANNEL_ADMIN_GROUP].as<GroupId>()];
|
||||
server->ensureValidDefaultGroups();
|
||||
return server;
|
||||
}
|
||||
|
||||
struct CommandTuple {
|
||||
Command& cmd;
|
||||
int& index;
|
||||
int version;
|
||||
};
|
||||
|
||||
struct PermissionCommandTuple {
|
||||
Command& cmd;
|
||||
int& index;
|
||||
@@ -771,8 +132,8 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
|
||||
int index = 0;
|
||||
|
||||
if(version == -1) version = 2; //Auto versioned
|
||||
if(version < 0 || version > 2) {
|
||||
if(version == -1) version = 3; //Auto versioned
|
||||
if(version < 0 || version > 3) {
|
||||
error = "Invalid snapshot version!";
|
||||
return false;
|
||||
}
|
||||
@@ -780,19 +141,19 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
//Server
|
||||
{
|
||||
cmd[index]["begin_virtualserver"] = "";
|
||||
for(const auto& serverProperty : server->properties().list_properties(property::FLAG_SNAPSHOT)) {
|
||||
for(const auto& serverProperty : server->properties()->list_properties(property::FLAG_SNAPSHOT)) {
|
||||
if(version == 0) {
|
||||
switch (serverProperty.type().property_index) {
|
||||
case property::VIRTUALSERVER_DOWNLOAD_QUOTA:
|
||||
case property::VIRTUALSERVER_UPLOAD_QUOTA:
|
||||
case property::VIRTUALSERVER_MAX_DOWNLOAD_TOTAL_BANDWIDTH:
|
||||
case property::VIRTUALSERVER_MAX_UPLOAD_TOTAL_BANDWIDTH:
|
||||
cmd[index][serverProperty.type().name] = (uint64_t) serverProperty.as_save<int64_t>();
|
||||
cmd[index][std::string{serverProperty.type().name}] = (uint64_t) serverProperty.as_or<int64_t>(0);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
cmd[index][serverProperty.type().name] = serverProperty.value();
|
||||
cmd[index][std::string{serverProperty.type().name}] = serverProperty.value();
|
||||
}
|
||||
cmd[index++]["end_virtualserver"] = "";
|
||||
}
|
||||
@@ -801,13 +162,14 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
{
|
||||
cmd[index]["begin_channels"] = "";
|
||||
for(const auto& channel : server->getChannelTree()->channels()) {
|
||||
for(const auto& channelProperty : channel->properties().list_properties(property::FLAG_SNAPSHOT)) {
|
||||
if(channelProperty.type() == property::CHANNEL_ID)
|
||||
cmd[index]["channel_id"] = channelProperty.as<string>();
|
||||
else if(channelProperty.type() == property::CHANNEL_PID)
|
||||
cmd[index]["channel_pid"] = channelProperty.as<string>();
|
||||
else
|
||||
cmd[index][channelProperty.type().name] = channelProperty.as<string>();
|
||||
for(const auto& channelProperty : channel->properties()->list_properties(property::FLAG_SNAPSHOT)) {
|
||||
if(channelProperty.type() == property::CHANNEL_ID) {
|
||||
cmd[index]["channel_id"] = channelProperty.value();
|
||||
} else if(channelProperty.type() == property::CHANNEL_PID) {
|
||||
cmd[index]["channel_pid"] = channelProperty.value();
|
||||
} else {
|
||||
cmd[index][std::string{channelProperty.type().name}] = channelProperty.value();
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
@@ -817,44 +179,26 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
//Clients
|
||||
{
|
||||
cmd[index]["begin_clients"] = "";
|
||||
CommandTuple parm{cmd, index, version};
|
||||
auto res = sql::command(server->getSql(), "SELECT `cldbid`, `clientUid`, `firstConnect`, `lastName` FROM `clients` WHERE `serverId` = :sid",
|
||||
variable{":sid", server->getServerId()}
|
||||
).query([&](CommandTuple* commandIndex, int length, char** value, char** name) {
|
||||
ClientDbId cldbid = 0;
|
||||
int64_t clientCreated = 0;
|
||||
string clientUid;
|
||||
string lastName;
|
||||
string description; //TODO description
|
||||
|
||||
for(int idx = 0; idx < length; idx++) {
|
||||
try {
|
||||
if(strcmp(name[idx], "cldbid") == 0)
|
||||
cldbid = value[idx] && strlen(value[idx]) > 0 ? stoul(value[idx]) : 0;
|
||||
else if(strcmp(name[idx], "clientUid") == 0)
|
||||
clientUid = value[idx];
|
||||
else if(strcmp(name[idx], "firstConnect") == 0)
|
||||
clientCreated = value[idx] && strlen(value[idx]) > 0 ? stoll(value[idx]) : 0L;
|
||||
else if(strcmp(name[idx], "lastName") == 0)
|
||||
lastName = value[idx];
|
||||
} catch (std::exception& ex) {
|
||||
logError(0, "Failed to write snapshot client (Skipping it)! Message: {} @ {} => {}", ex.what(), name[idx], value[idx]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
struct CallbackArgument {
|
||||
Command& command;
|
||||
int& index;
|
||||
int version;
|
||||
};
|
||||
|
||||
CallbackArgument callback_argument{cmd, index, version};
|
||||
this->handle->databaseHelper()->listDatabaseClients(server->getServerId(), std::nullopt, std::nullopt, [](void* ptr_argument, const DatabaseClient& client) {
|
||||
auto argument = (CallbackArgument*) ptr_argument;
|
||||
|
||||
commandIndex->cmd[commandIndex->index]["client_id"] = cldbid;
|
||||
commandIndex->cmd[commandIndex->index]["client_unique_id"] = clientUid;
|
||||
commandIndex->cmd[commandIndex->index]["client_nickname"] = lastName;
|
||||
commandIndex->cmd[commandIndex->index]["client_created"] = clientCreated;
|
||||
commandIndex->cmd[commandIndex->index]["client_description"] = description;
|
||||
if(commandIndex->version == 0)
|
||||
commandIndex->cmd[commandIndex->index]["client_unread_messages"] = 0;
|
||||
commandIndex->index++;
|
||||
return 0;
|
||||
}, &parm);
|
||||
LOG_SQL_CMD(res);
|
||||
argument->command[argument->index]["client_id"] = client.client_database_id;
|
||||
argument->command[argument->index]["client_unique_id"] = client.client_unique_id;
|
||||
argument->command[argument->index]["client_nickname"] = client.client_nickname;
|
||||
argument->command[argument->index]["client_created"] = client.client_created;
|
||||
argument->command[argument->index]["client_description"] = client.client_description;
|
||||
if(argument->version == 0)
|
||||
argument->command[argument->index]["client_unread_messages"] = 0;
|
||||
argument->index++;
|
||||
}, &callback_argument);
|
||||
cmd[index++]["end_clients"] = "";
|
||||
}
|
||||
|
||||
@@ -899,7 +243,7 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
if((property->type->flags & (property::FLAG_SAVE_MUSIC | property::FLAG_SAVE)) == 0) continue;
|
||||
if(property->value == property->type->default_value) continue;
|
||||
|
||||
cmd[index][property->type->name] = property->value;
|
||||
cmd[index][std::string{property->type->name}] = property->value;
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -931,7 +275,7 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
if((property->type->flags & (property::FLAG_SAVE_MUSIC | property::FLAG_SAVE)) == 0) continue;
|
||||
if(property->value == property->type->default_value) continue;
|
||||
|
||||
cmd[index][property->type->name] = property->value;
|
||||
cmd[index][std::string{property->type->name}] = property->value;
|
||||
}
|
||||
|
||||
index++;
|
||||
@@ -994,10 +338,13 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
//List groups
|
||||
{
|
||||
cmd[index]["server_groups"] = "";
|
||||
for(const auto& group : server->getGroupManager()->availableServerGroups(false)) {
|
||||
cmd[index]["id"] = group->groupId();
|
||||
cmd[index]["name"] = group->name();
|
||||
if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) break;
|
||||
auto server_groups = server->group_manager()->server_groups();
|
||||
for(const auto& group : server_groups->available_groups(groups::GroupCalculateMode::LOCAL)) {
|
||||
cmd[index]["id"] = group->group_id();
|
||||
cmd[index]["name"] = group->display_name();
|
||||
if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) {
|
||||
break;
|
||||
}
|
||||
cmd[index++]["end_group"] = "";
|
||||
}
|
||||
cmd[index++]["end_groups"] = "";
|
||||
@@ -1016,10 +363,13 @@ bool VirtualServerManager::createServerSnapshot(Command &cmd, shared_ptr<Virtual
|
||||
//List groups
|
||||
{
|
||||
cmd[index]["channel_groups"] = "";
|
||||
for(const auto& group : server->getGroupManager()->availableChannelGroups(false)) {
|
||||
cmd[index]["id"] = group->groupId();
|
||||
cmd[index]["name"] = group->name();
|
||||
if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) break;
|
||||
auto server_groups = server->group_manager()->channel_groups();
|
||||
for(const auto& group : server_groups->available_groups(groups::GroupCalculateMode::LOCAL)) {
|
||||
cmd[index]["id"] = group->group_id();
|
||||
cmd[index]["name"] = group->display_name();
|
||||
if(!writePermissions(group->permissions(), cmd, index, version, permission::teamspeak::SERVER, error)) {
|
||||
break;
|
||||
}
|
||||
cmd[index++]["end_group"] = "";
|
||||
}
|
||||
cmd[index++]["end_groups"] = "";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <log/LogUtils.h>
|
||||
#include <StringVariable.h>
|
||||
#include <ThreadPool/ThreadHelper.h>
|
||||
#include <src/terminal/PipedTerminal.h>
|
||||
#include "ShutdownHelper.h"
|
||||
#include "InstanceHandler.h"
|
||||
|
||||
@@ -21,6 +22,7 @@ void ts::server::shutdownInstance(const std::string& message) {
|
||||
logCriticalFmt(true, 0, "Could not shutdown server within 30 seconds! (Hangup!)");
|
||||
logCriticalFmt(true, 0, "Killing server!");
|
||||
|
||||
terminal::finalize_pipe();
|
||||
auto force_kill = std::thread([]{
|
||||
threads::self::sleep_for(chrono::seconds(5));
|
||||
logCriticalFmt(true, 0, "Failed to exit normally!");
|
||||
@@ -31,6 +33,7 @@ void ts::server::shutdownInstance(const std::string& message) {
|
||||
force_kill.detach();
|
||||
|
||||
exit(2);
|
||||
//kill(0, SIGKILL);
|
||||
});
|
||||
threads::name(hangup_controller, "stop controller");
|
||||
hangup_controller.detach();
|
||||
@@ -42,6 +45,10 @@ void ts::server::shutdownInstance(const std::string& message) {
|
||||
mainThreadActive = false;
|
||||
}
|
||||
|
||||
bool ts::server::isShuttingDown() {
|
||||
return shuttingDown;
|
||||
}
|
||||
|
||||
std::shared_ptr<server::ShutdownData> currentShutdown = nullptr;
|
||||
std::shared_ptr<server::ShutdownData> server::scheduledShutdown() { return currentShutdown; }
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
namespace ts {
|
||||
namespace server {
|
||||
extern void shutdownInstance(const std::string& reason = ts::config::messages::applicationStopped);
|
||||
|
||||
extern bool isShuttingDown();
|
||||
|
||||
struct ShutdownData {
|
||||
std::string reason;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
#include <breakpad/client/linux/handler/exception_handler.h>
|
||||
#include "VirtualServer.h"
|
||||
#include "SignalHandler.h"
|
||||
#include "VirtualServerManager.h"
|
||||
@@ -8,29 +7,26 @@
|
||||
#include <csignal>
|
||||
#include <log/LogUtils.h>
|
||||
#include <experimental/filesystem>
|
||||
#include <src/terminal/PipedTerminal.h>
|
||||
|
||||
#include <iterator>
|
||||
|
||||
#define BREAKPAD_EXCEPTION_HANDLER 1
|
||||
#ifdef BREAKPAD_EXCEPTION_HANDLER
|
||||
#include <breakpad/client/linux/handler/exception_handler.h>
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
namespace fs = std::experimental::filesystem;
|
||||
|
||||
#ifdef BREAKPAD_EXCEPTION_HANDLER
|
||||
google_breakpad::ExceptionHandler* globalExceptionHandler = nullptr;
|
||||
#endif
|
||||
#define SIG(s, c) \
|
||||
if(signal(s, c) != nullptr) logError(LOG_GENERAL, "Cant setup signal handler for " #s);
|
||||
|
||||
|
||||
extern bool mainThreadDone;
|
||||
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) {
|
||||
logCritical(LOG_GENERAL, "The server crashed!");
|
||||
try {
|
||||
if(!fs::exists(fs::u8path(ts::config::crash_path)))
|
||||
fs::create_directories(fs::u8path(ts::config::crash_path));
|
||||
|
||||
auto path = fs::u8path(descriptor.path());
|
||||
path = fs::u8path(ts::config::crash_path + "crash_dump_" + path.filename().string());
|
||||
fs::rename(fs::u8path(descriptor.path()), path);
|
||||
logCritical(LOG_GENERAL, "Wrote crash dump to " + path.relative_path().string());
|
||||
} catch (...) {
|
||||
logCritical(LOG_GENERAL, "Failed to write/move crash dump!");
|
||||
}
|
||||
void print_current_exception() {
|
||||
if(std::current_exception()) {
|
||||
logCritical(LOG_GENERAL, "Exception reached stack root and cause the server to crash!");
|
||||
logCritical(LOG_GENERAL, " Type: {}", std::current_exception().__cxa_exception_type()->name());
|
||||
@@ -40,23 +36,61 @@ static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor,
|
||||
logCritical(LOG_GENERAL, " Message: {}", ex.what());
|
||||
} catch(...) {}
|
||||
}
|
||||
}
|
||||
|
||||
extern bool mainThreadDone;
|
||||
#ifdef BREAKPAD_EXCEPTION_HANDLER
|
||||
static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) {
|
||||
if(ts::server::isShuttingDown()) {
|
||||
/* We don't care about this crash dump. Remove it. */
|
||||
std::error_code error_code{};
|
||||
fs::remove(fs::u8path(descriptor.path()), error_code);
|
||||
return true;
|
||||
}
|
||||
|
||||
logCritical(LOG_GENERAL, "The server crashed!");
|
||||
try {
|
||||
if(!fs::exists(fs::u8path(ts::config::crash_path))) {
|
||||
fs::create_directories(fs::u8path(ts::config::crash_path));
|
||||
}
|
||||
|
||||
auto path = fs::u8path(descriptor.path());
|
||||
path = fs::u8path(ts::config::crash_path + "crash_dump_" + path.filename().string());
|
||||
fs::rename(fs::u8path(descriptor.path()), path);
|
||||
logCritical(LOG_GENERAL, "Wrote crash dump to " + path.relative_path().string());
|
||||
} catch (...) {
|
||||
logCritical(LOG_GENERAL, "Failed to write/move crash dump!");
|
||||
}
|
||||
print_current_exception();
|
||||
|
||||
logCritical(LOG_GENERAL, "Please report this crash to the TeaSpeak maintainer WolverinDEV");
|
||||
logCritical(LOG_GENERAL, "Official issue and bug tracker url: https://github.com/TeaSpeak/TeaSpeak/issues");
|
||||
logCritical(LOG_GENERAL, "Any reports of crashes are useless if you not provide the above generated crashlog!");
|
||||
logCritical(LOG_GENERAL, "Stopping server");
|
||||
|
||||
terminal::finalize_pipe();
|
||||
ts::server::shutdownInstance(ts::config::messages::applicationCrashed);
|
||||
while(!mainThreadDone) threads::self::sleep_for(chrono::seconds(1));
|
||||
while(!mainThreadDone) {
|
||||
threads::self::sleep_for(chrono::seconds(1));
|
||||
}
|
||||
return succeeded;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::atomic spawn_failed_count = 0;
|
||||
bool ts::syssignal::setup() {
|
||||
logMessage(LOG_GENERAL, "Setting up exception handler");
|
||||
#ifdef BREAKPAD_EXCEPTION_HANDLER
|
||||
globalExceptionHandler = new google_breakpad::ExceptionHandler(google_breakpad::MinidumpDescriptor("."), nullptr, dumpCallback, nullptr, true, -1);
|
||||
#endif
|
||||
|
||||
SIG(SIGTERM, &ts::syssignal::handleStopSignal);
|
||||
if(isatty(fileno(stdin))) //We cant listen for this signal if stdin ist a atty
|
||||
if(isatty(fileno(stdin))) {
|
||||
//We cant listen for this signal if stdin ist a atty
|
||||
SIG(SIGINT, &ts::syssignal::handleStopSignal);
|
||||
}
|
||||
//SIG(SIGABRT, &ts::syssignal::handleAbortSignal);
|
||||
std::set_terminate(ts::syssignal::handleTerminate);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -98,4 +132,14 @@ void ts::syssignal::handleStopSignal(int signal) {
|
||||
raise(SIGKILL);
|
||||
}
|
||||
ts::server::shutdownInstance();
|
||||
}
|
||||
|
||||
void ts::syssignal::handleAbortSignal(int) {
|
||||
logCritical(0, "The server crashed (Abort signal received)!");
|
||||
print_current_exception();
|
||||
}
|
||||
|
||||
void ts::syssignal::handleTerminate() {
|
||||
logCritical(0, "The server crashed (Received a terminate signal)!");
|
||||
print_current_exception();
|
||||
}
|
||||
@@ -8,5 +8,7 @@ namespace ts {
|
||||
extern bool setup();
|
||||
extern bool setup_threads();
|
||||
extern void handleStopSignal(int);
|
||||
extern void handleAbortSignal(int);
|
||||
extern void handleTerminate();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
#include <cstring>
|
||||
#include <protocol/buffers.h>
|
||||
#include "./PermissionCalculator.h"
|
||||
#include "client/voice/VoiceClient.h"
|
||||
#include "client/InternalClient.h"
|
||||
#include "VirtualServer.h"
|
||||
#include <misc/timer.h>
|
||||
#include <log/LogUtils.h>
|
||||
#include <misc/sassert.h>
|
||||
#include <src/manager/ActionLogger.h>
|
||||
#include "InstanceHandler.h"
|
||||
#include "./groups/GroupManager.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace ts::server;
|
||||
@@ -19,192 +22,234 @@ bool VirtualServer::registerClient(shared_ptr<ConnectedClient> client) {
|
||||
sassert(client);
|
||||
|
||||
{
|
||||
lock_guard lock(this->clients.lock);
|
||||
std::lock_guard clients_lock{this->clients_mutex};
|
||||
if(client->getClientId() > 0) {
|
||||
logCritical(this->getServerId(), "Client {} ({}|{}) has been already registered!", client->getDisplayName(), client->getClientId(), client->getUid());
|
||||
return false;
|
||||
}
|
||||
|
||||
ClientId client_id = 0;
|
||||
ClientId max_client_id = this->clients.clients.size();
|
||||
|
||||
while(client_id < max_client_id && this->clients.clients[client_id])
|
||||
ClientId client_id{0};
|
||||
while(this->clients.count(client_id)) {
|
||||
client_id++;
|
||||
if(client_id == max_client_id)
|
||||
this->clients.clients.push_back(client);
|
||||
else
|
||||
this->clients.clients[client_id] = client;
|
||||
this->clients.count++;
|
||||
}
|
||||
|
||||
this->clients.emplace(client_id, client);
|
||||
client->setClientId(client_id);
|
||||
}
|
||||
|
||||
{
|
||||
lock_guard lock(this->client_nickname_lock);
|
||||
std::lock_guard lock{this->client_nickname_lock};
|
||||
|
||||
auto login_name = client->getDisplayName();
|
||||
while(login_name.length() < 3)
|
||||
login_name += ".";
|
||||
|
||||
if(client->getExternalType() == ClientType::CLIENT_TEAMSPEAK)
|
||||
if(client->getExternalType() == ClientType::CLIENT_TEAMSPEAK) {
|
||||
client->properties()[property::CLIENT_LOGIN_NAME] = login_name;
|
||||
}
|
||||
|
||||
std::shared_ptr<ConnectedClient> found_client = nullptr;
|
||||
while(login_name.length() < 3) {
|
||||
login_name += ".";
|
||||
}
|
||||
|
||||
auto client_name = login_name;
|
||||
size_t counter = 0;
|
||||
std::shared_ptr<ConnectedClient> found_client{nullptr};
|
||||
auto registered_clients = this->getClients();
|
||||
|
||||
auto client_name{login_name};
|
||||
size_t counter{0};
|
||||
|
||||
{
|
||||
lock_guard clients_lock(this->clients.lock);
|
||||
while(true) {
|
||||
for(auto& _client : this->clients.clients) {
|
||||
if(!_client) continue;
|
||||
|
||||
if(_client->getDisplayName() == client_name && _client != client)
|
||||
for(auto& _client : registered_clients) {
|
||||
if(_client->getDisplayName() == client_name && _client != client) {
|
||||
goto increase_name;
|
||||
}
|
||||
}
|
||||
goto nickname_valid;
|
||||
|
||||
increase_name:
|
||||
client_name = login_name + to_string(++counter);
|
||||
client_name = login_name + std::to_string(++counter);
|
||||
}
|
||||
}
|
||||
|
||||
nickname_valid:
|
||||
client->setDisplayName(client_name);
|
||||
}
|
||||
|
||||
switch(client->getType()) {
|
||||
case ClientType::CLIENT_TEAMSPEAK:
|
||||
case ClientType::CLIENT_TEASPEAK:
|
||||
case ClientType::CLIENT_WEB:
|
||||
this->properties()[property::VIRTUALSERVER_CLIENT_CONNECTIONS].increment_by<uint64_t>(1); //increase manager connections
|
||||
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
break;
|
||||
|
||||
case ClientType::CLIENT_QUERY:
|
||||
this->properties()[property::VIRTUALSERVER_LAST_QUERY_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
this->properties()[property::VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS].increment_by<uint64_t>(1); //increase manager connections
|
||||
break;
|
||||
|
||||
case ClientType::CLIENT_MUSIC:
|
||||
case ClientType::CLIENT_INTERNAL:
|
||||
break;
|
||||
|
||||
|
||||
if(client->getType() == ClientType::CLIENT_TEAMSPEAK || client->getType() == ClientType::CLIENT_WEB) {
|
||||
this->properties()[property::VIRTUALSERVER_CLIENT_CONNECTIONS] ++; //increase manager connections
|
||||
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
else if(client->getType() == ClientType::CLIENT_QUERY) {
|
||||
this->properties()[property::VIRTUALSERVER_LAST_QUERY_CONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
this->properties()[property::VIRTUALSERVER_QUERY_CLIENT_CONNECTIONS] ++; //increase manager connections
|
||||
case ClientType::MAX:
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VirtualServer::unregisterClient(shared_ptr<ConnectedClient> cl, std::string reason, std::unique_lock<std::shared_mutex>& chan_tree_lock) {
|
||||
if(cl->getType() == ClientType::CLIENT_TEAMSPEAK && cl->getType() == ClientType::CLIENT_WEB) {
|
||||
sassert(cl->state == ConnectionState::DISCONNECTED);
|
||||
bool VirtualServer::unregisterClient(shared_ptr<ConnectedClient> client, std::string reason, std::unique_lock<std::shared_mutex>& chan_tree_lock) {
|
||||
/* FIXME: Reenable this for the web client as soon we've fixed the web client disconnect method */
|
||||
if(client->getType() == ClientType::CLIENT_TEAMSPEAK || client->getType() == ClientType::CLIENT_TEASPEAK/* || client->getType() == ClientType::CLIENT_WEB */) {
|
||||
sassert(client->state == ConnectionState::DISCONNECTED);
|
||||
}
|
||||
|
||||
auto client_id = cl->getClientId();
|
||||
if(client_id == 0) return false; /* not registered */
|
||||
{
|
||||
|
||||
lock_guard lock(this->clients.lock);
|
||||
if(client_id >= this->clients.clients.size()) {
|
||||
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but client id exceed client id! Failed to unregister client.", cl->getDisplayName(), client_id, cl->getUid());
|
||||
} else {
|
||||
auto& client_container = this->clients.clients[client_id];
|
||||
if(client_container != cl) {
|
||||
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but container hasn't client set! Failed to unregister client.", cl->getDisplayName(), client_id, cl->getUid());
|
||||
} else {
|
||||
client_container.reset();
|
||||
this->clients.count--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(cl->getType() == ClientType::CLIENT_TEAMSPEAK || cl->getType() == ClientType::CLIENT_WEB)
|
||||
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_DISCONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
else if(cl->getType() == ClientType::CLIENT_QUERY)
|
||||
this->properties()[property::VIRTUALSERVER_LAST_QUERY_DISCONNECT] = duration_cast<seconds>(system_clock::now().time_since_epoch()).count();
|
||||
|
||||
{
|
||||
if(!chan_tree_lock.owns_lock())
|
||||
if(!chan_tree_lock.owns_lock()) {
|
||||
chan_tree_lock.lock();
|
||||
}
|
||||
|
||||
if(cl->currentChannel) //We dont have to make him invisible if he hasnt even a channel
|
||||
this->client_move(cl, nullptr, nullptr, reason, ViewReasonId::VREASON_SERVER_LEFT, false, chan_tree_lock);
|
||||
if(client->currentChannel) {
|
||||
//We dont have to make him invisible if he hasnt even a channel
|
||||
this->client_move(client, nullptr, nullptr, reason, ViewReasonId::VREASON_SERVER_LEFT, false, chan_tree_lock);
|
||||
}
|
||||
|
||||
chan_tree_lock.unlock();
|
||||
}
|
||||
|
||||
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions);
|
||||
cl->setClientId(0);
|
||||
{
|
||||
std::lock_guard clients_lock{this->clients_mutex};
|
||||
auto client_id = client->getClientId();
|
||||
if(client_id == 0) {
|
||||
return false; /* not registered */
|
||||
}
|
||||
|
||||
if(!this->clients.erase(client_id)) {
|
||||
client->setClientId(0);
|
||||
logError(this->getServerId(), "Tried to unregister a not registered client {}/{} ({})", client->getDisplayName(), client->getUid(), client_id);
|
||||
return false;
|
||||
}
|
||||
client->setClientId(0);
|
||||
}
|
||||
|
||||
auto current_time_seconds = std::chrono::duration_cast<seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
switch(client->getType()) {
|
||||
case ClientType::CLIENT_TEAMSPEAK:
|
||||
case ClientType::CLIENT_TEASPEAK:
|
||||
case ClientType::CLIENT_WEB:
|
||||
this->properties()[property::VIRTUALSERVER_LAST_CLIENT_DISCONNECT] = current_time_seconds;
|
||||
break;
|
||||
|
||||
case ClientType::CLIENT_QUERY:
|
||||
this->properties()[property::VIRTUALSERVER_LAST_QUERY_DISCONNECT] = current_time_seconds;
|
||||
break;
|
||||
|
||||
|
||||
case ClientType::CLIENT_MUSIC:
|
||||
case ClientType::CLIENT_INTERNAL:
|
||||
case ClientType::MAX:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), client->getClientDatabaseId(), client->clientPermissions);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VirtualServer::registerInternalClient(std::shared_ptr<ConnectedClient> client) {
|
||||
client->state = ConnectionState::CONNECTED;
|
||||
{
|
||||
lock_guard lock(this->clients.lock);
|
||||
if(client->getClientId() > 0) {
|
||||
logCritical(this->getServerId(), "Internal client {} ({}|{}) has been already registered!", client->getDisplayName(), client->getClientId(), client->getUid());
|
||||
return;
|
||||
}
|
||||
|
||||
ClientId client_id = 0;
|
||||
ClientId max_client_id = this->clients.clients.size();
|
||||
while(client_id < max_client_id && this->clients.clients[client_id])
|
||||
client_id++;
|
||||
if(client_id == max_client_id)
|
||||
this->clients.clients.push_back(client);
|
||||
else
|
||||
this->clients.clients[client_id] = client;
|
||||
|
||||
this->clients.clients[client_id] = client;
|
||||
this->clients.count++;
|
||||
client->setClientId(client_id);
|
||||
}
|
||||
this->registerClient(client);
|
||||
}
|
||||
|
||||
void VirtualServer::unregisterInternalClient(std::shared_ptr<ConnectedClient> client) {
|
||||
client->state = ConnectionState::DISCONNECTED;
|
||||
|
||||
{
|
||||
auto client_id = client->getClientId();
|
||||
|
||||
lock_guard lock(this->clients.lock);
|
||||
if(client_id >= this->clients.clients.size()) {
|
||||
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but client id exceed client id! Failed to unregister internal client.", client->getDisplayName(), client_id, client->getUid());
|
||||
} else {
|
||||
auto& client_container = this->clients.clients[client_id];
|
||||
if(client_container != client) {
|
||||
logCritical(this->getServerId(), "Client {} ({}|{}) has been registered, but container hasn't client set! Failed to unregister internal client.", client->getDisplayName(), client_id, client->getUid());
|
||||
} else {
|
||||
this->clients.count--;
|
||||
client_container.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
std::unique_lock tree_lock{this->channel_tree_mutex};
|
||||
this->unregisterClient(client, "internal disconnect", tree_lock);
|
||||
}
|
||||
|
||||
bool VirtualServer::assignDefaultChannel(const shared_ptr<ConnectedClient>& client, bool join) {
|
||||
shared_lock server_channel_lock(this->channel_tree_lock);
|
||||
std::shared_ptr<BasicChannel> channel = nullptr;
|
||||
if(client->properties()->hasProperty(property::CLIENT_DEFAULT_CHANNEL) && !client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<string>().empty()) {
|
||||
auto str = client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<std::string>();
|
||||
if(str[0] == '/' && str.find_first_not_of("0123456789", 1) == string::npos)
|
||||
channel = this->channelTree->findChannel(static_cast<ChannelId>(stoll(str.substr(1))));
|
||||
else
|
||||
channel = this->channelTree->findChannelByPath(str);
|
||||
if (channel) {
|
||||
if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) {
|
||||
logMessage(this->serverId, "{} Client tried to connect to a channel which he hasn't permission for. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name());
|
||||
channel = nullptr;
|
||||
} else if (!channel->passwordMatch(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD], true) && !permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
|
||||
logMessage(this->serverId, "{} Client tried to connect to a channel which is password protected and he hasn't the right password. Channel: {} ({})", CLIENT_STR_LOG_PREFIX_(client), channel->channelId(), channel->name());
|
||||
channel = nullptr;
|
||||
}
|
||||
} else
|
||||
logMessage(this->serverId, "{} Client {}/{} tried to join on a not existing channel. Name: {}",
|
||||
CLIENT_STR_LOG_PREFIX_(client),
|
||||
client->getDisplayName(), client->getUid(),
|
||||
client->properties()[property::CLIENT_DEFAULT_CHANNEL].as<std::string>());
|
||||
}
|
||||
if(!channel) channel = this->channelTree->getDefaultChannel();
|
||||
if(!channel) return false;
|
||||
std::shared_ptr<BasicChannel> channel{};
|
||||
|
||||
std::unique_lock server_channel_lock{this->channel_tree_mutex};
|
||||
auto requested_channel_path = client->properties()[property::CLIENT_DEFAULT_CHANNEL].value();
|
||||
if(!requested_channel_path.empty()) {
|
||||
if (requested_channel_path[0] == '/' && requested_channel_path.find_first_not_of("0123456789", 1) == std::string::npos) {
|
||||
ChannelId channel_id{0};
|
||||
try {
|
||||
channel_id = std::stoull(requested_channel_path.substr(1));
|
||||
} catch (std::exception&) {
|
||||
logTrace(this->getServerId(), "{} Failed to parse provided channel path as channel id.");
|
||||
}
|
||||
|
||||
if(channel_id > 0) {
|
||||
channel = this->channelTree->findChannel(channel_id);
|
||||
}
|
||||
} else {
|
||||
channel = this->channelTree->findChannelByPath(requested_channel_path);
|
||||
}
|
||||
}
|
||||
|
||||
if(channel) {
|
||||
/* Client proposes a target channel */
|
||||
auto& channel_whitelist = client->join_whitelisted_channel;
|
||||
auto whitelist_entry = std::find_if(channel_whitelist.begin(), channel_whitelist.end(), [&](const auto& entry) { return entry.first == channel->channelId(); });
|
||||
|
||||
auto client_channel_password = client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD].value();
|
||||
if(whitelist_entry != channel_whitelist.end()) {
|
||||
debugMessage(this->getServerId(), "{} Allowing client to join channel {} because the token he used explicitly allowed it.", client->getLoggingPrefix(), channel->channelId());
|
||||
|
||||
if(whitelist_entry->second != "ignore") {
|
||||
if (!channel->verify_password(std::make_optional(client_channel_password), true)) {
|
||||
if (!permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
|
||||
channel = nullptr;
|
||||
goto skip_permissions;
|
||||
}
|
||||
}
|
||||
}
|
||||
goto skip_permissions;
|
||||
}
|
||||
|
||||
if(!channel->permission_granted(permission::i_channel_needed_join_power, client->calculate_permission(permission::i_channel_join_power, channel->channelId()), false)) {
|
||||
debugMessage(this->getServerId(), "{} Tried to join channel {} but hasn't enough join power.", client->getLoggingPrefix(), channel->channelId());
|
||||
channel = nullptr;
|
||||
goto skip_permissions;
|
||||
}
|
||||
|
||||
if (!channel->verify_password(std::make_optional(client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD].value()), true)) {
|
||||
if(!permission::v2::permission_granted(1, client->calculate_permission(permission::b_channel_join_ignore_password, channel->channelId()))) {
|
||||
debugMessage(this->getServerId(), "{} Tried to join channel {} but hasn't given the right channel password.", client->getLoggingPrefix(), channel->channelId());
|
||||
channel = nullptr;
|
||||
goto skip_permissions;
|
||||
}
|
||||
}
|
||||
|
||||
skip_permissions:;
|
||||
}
|
||||
|
||||
/* Clear these parameters. We don't need them any more after we initially payed attention. */
|
||||
client->properties()[property::CLIENT_DEFAULT_CHANNEL] = "";
|
||||
client->properties()[property::CLIENT_DEFAULT_CHANNEL_PASSWORD] = "";
|
||||
|
||||
if(!channel) {
|
||||
/* Client did not propose a channel or the proposed channel got rejected */
|
||||
channel = this->channelTree->getDefaultChannel();
|
||||
if(!channel) {
|
||||
logCritical(this->getServerId(), "Channel tree is missing the default channel.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
debugMessage(this->getServerId(), "{} Using channel {} as default client channel.", client->getLoggingPrefix(), channel->channelId());
|
||||
if(join) {
|
||||
server_channel_lock.unlock();
|
||||
unique_lock server_channel_w_lock(this->channel_tree_lock);
|
||||
this->client_move(client, channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_w_lock);
|
||||
this->client_move(client, channel, nullptr, "", ViewReasonId::VREASON_USER_ACTION, false, server_channel_lock);
|
||||
} else {
|
||||
client->currentChannel = channel;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -220,51 +265,17 @@ void VirtualServer::testBanStateChange(const std::shared_ptr<ConnectedClient>& i
|
||||
});
|
||||
}
|
||||
|
||||
bool VirtualServer::could_default_create_channel() {
|
||||
{
|
||||
|
||||
auto default_group = this->getGroupManager()->defaultGroup(GroupTarget::GROUPTARGET_SERVER);
|
||||
if(default_group) {
|
||||
auto flag = default_group->permissions()->permission_value_flagged(permission::b_channel_create_temporary).value == 1;
|
||||
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_semi_permanent).value == 1;
|
||||
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_permanent).value == 1;
|
||||
if(flag)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
{
|
||||
|
||||
auto default_group = this->getGroupManager()->defaultGroup(GroupTarget::GROUPTARGET_CHANNEL);
|
||||
if(default_group) {
|
||||
auto flag = default_group->permissions()->permission_value_flagged(permission::b_channel_create_temporary).value == 1;
|
||||
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_semi_permanent).value == 1;
|
||||
flag = flag ? flag : default_group->permissions()->permission_value_flagged(permission::b_channel_create_permanent).value == 1;
|
||||
if(flag)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
for (auto &cl : this->server->getClients())
|
||||
if (cl->isClientVisible(client) || client == cl)
|
||||
cl->notifyClientLeftViewKicked(client, client->currentChannel, nullptr, cmd["reasonmsg"].as<std::string>(), this);
|
||||
*/
|
||||
|
||||
void VirtualServer::notify_client_ban(const shared_ptr<ConnectedClient> &target, const std::shared_ptr<ts::server::ConnectedClient> &invoker, const std::string &reason, size_t time) {
|
||||
/* the target is not allowed to execute anything; Must before channel tree lock because the target may waits for us to finish the channel stuff */
|
||||
lock_guard command_lock(target->command_lock);
|
||||
unique_lock server_channel_lock(this->channel_tree_lock); /* we're "moving" a client! */
|
||||
unique_lock server_channel_lock(this->channel_tree_mutex); /* we're "moving" a client! */
|
||||
|
||||
if(target->currentChannel) {
|
||||
for(const auto& client : this->getClients()) {
|
||||
if(!client || client == target)
|
||||
continue;
|
||||
|
||||
unique_lock client_channel_lock(client->channel_lock);
|
||||
unique_lock client_channel_lock(client->channel_tree_mutex);
|
||||
if(client->isClientVisible(target, false))
|
||||
client->notifyClientLeftViewBanned(target, reason, invoker, time, false);
|
||||
}
|
||||
@@ -274,7 +285,7 @@ void VirtualServer::notify_client_ban(const shared_ptr<ConnectedClient> &target,
|
||||
}
|
||||
|
||||
/* now disconnect the target itself */
|
||||
unique_lock client_channel_lock(target->channel_lock);
|
||||
unique_lock client_channel_lock(target->channel_tree_mutex);
|
||||
target->notifyClientLeftViewBanned(target, reason, invoker, time, false);
|
||||
target->currentChannel = nullptr;
|
||||
}
|
||||
@@ -287,29 +298,32 @@ void VirtualServer::notify_client_kick(
|
||||
|
||||
if(target_channel) {
|
||||
/* use the move! */
|
||||
unique_lock server_channel_lock(this->channel_tree_lock, defer_lock);
|
||||
unique_lock server_channel_lock(this->channel_tree_mutex, defer_lock);
|
||||
this->client_move(target, target_channel, invoker, reason, ViewReasonId::VREASON_CHANNEL_KICK, true, server_channel_lock);
|
||||
} else {
|
||||
/* the target is not allowed to execute anything; Must before channel tree lock because the target may waits for us to finish the channel stuff */
|
||||
lock_guard command_lock(target->command_lock);
|
||||
unique_lock server_channel_lock(this->channel_tree_lock); /* we're "moving" a client! */
|
||||
unique_lock server_channel_lock(this->channel_tree_mutex); /* we're "moving" a client! */
|
||||
|
||||
if(target->currentChannel) {
|
||||
for(const auto& client : this->getClients()) {
|
||||
if(!client || client == target)
|
||||
continue;
|
||||
|
||||
unique_lock client_channel_lock(client->channel_lock);
|
||||
unique_lock client_channel_lock(client->channel_tree_mutex);
|
||||
if(client->isClientVisible(target, false))
|
||||
client->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false);
|
||||
}
|
||||
|
||||
auto s_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
|
||||
s_channel->unregister_client(target);
|
||||
if(auto client{dynamic_pointer_cast<SpeakingClient>(target)}; client) {
|
||||
this->rtc_server().assign_channel(client->rtc_client_id, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* now disconnect the target itself */
|
||||
unique_lock client_channel_lock(target->channel_lock);
|
||||
unique_lock client_channel_lock(target->channel_tree_mutex);
|
||||
target->notifyClientLeftViewKicked(target, nullptr, reason, invoker, false);
|
||||
target->currentChannel = nullptr;
|
||||
}
|
||||
@@ -323,11 +337,14 @@ void VirtualServer::notify_client_kick(
|
||||
*
|
||||
* Note: channel cant be a ref because the channel itself gets deleted!
|
||||
*/
|
||||
void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const shared_ptr<ConnectedClient> &invoker, const std::string& kick_message, unique_lock<std::shared_mutex> &tree_lock) {
|
||||
if(!tree_lock.owns_lock())
|
||||
void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const shared_ptr<ConnectedClient> &invoker, const std::string& kick_message, unique_lock<std::shared_mutex> &tree_lock, bool temp_delete) {
|
||||
if(!tree_lock.owns_lock()) {
|
||||
tree_lock.lock();
|
||||
if(channel->deleted)
|
||||
}
|
||||
|
||||
if(channel->deleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
deque<std::shared_ptr<ConnectedClient>> clients;
|
||||
{
|
||||
@@ -344,29 +361,61 @@ void VirtualServer::delete_channel(shared_ptr<ts::ServerChannel> channel, const
|
||||
channel->deleted = true;
|
||||
}
|
||||
auto default_channel = this->channelTree->getDefaultChannel();
|
||||
tree_lock.unlock();
|
||||
|
||||
deque<unique_lock<threads::Mutex>> command_locks;
|
||||
for(const auto& client : clients) {
|
||||
command_locks.push_back(move(unique_lock(client->command_lock)));
|
||||
}
|
||||
|
||||
for(const auto& client : clients)
|
||||
for(const auto& client : clients) {
|
||||
this->client_move(client, default_channel, invoker, kick_message, ViewReasonId::VREASON_CHANNEL_KICK, true, tree_lock);
|
||||
}
|
||||
|
||||
if(!tree_lock.owns_lock())
|
||||
tree_lock.lock(); /* no clients left within that tree */
|
||||
if(!tree_lock.owns_lock()) {
|
||||
/* This case should never happen. client_move should never unlock the tree lock! */
|
||||
tree_lock.lock();
|
||||
}
|
||||
command_locks.clear();
|
||||
|
||||
auto channel_ids = this->channelTree->delete_channel_root(channel);
|
||||
auto deleted_channels = this->channelTree->delete_channel_root(channel);
|
||||
log::ChannelDeleteReason delete_reason{temp_delete ? log::ChannelDeleteReason::EMPTY : log::ChannelDeleteReason::USER_ACTION};
|
||||
for(const auto& deleted_channel : deleted_channels) {
|
||||
serverInstance->action_logger()->channel_logger.log_channel_delete(this->serverId, invoker, deleted_channel->channelId(), channel == deleted_channel ? delete_reason : log::ChannelDeleteReason::PARENT_DELETED);
|
||||
}
|
||||
|
||||
this->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
|
||||
unique_lock client_channel_lock(client->channel_lock);
|
||||
client->notifyChannelDeleted(client->channels->delete_channel_root(channel), invoker);
|
||||
unique_lock client_channel_lock(client->channel_tree_mutex);
|
||||
client->notifyChannelDeleted(client->channel_tree->delete_channel_root(channel), invoker);
|
||||
});
|
||||
|
||||
{
|
||||
std::vector<ChannelId> deleted_channel_ids{};
|
||||
deleted_channel_ids.reserve(deleted_channels.size());
|
||||
for(const auto& deleted_channel : deleted_channels) {
|
||||
deleted_channel_ids.push_back(deleted_channel->channelId());
|
||||
}
|
||||
|
||||
auto ref_self = this->ref();
|
||||
task_id task_id{};
|
||||
serverInstance->general_task_executor()->schedule(task_id, "database cleanup after channel delete", [ref_self, deleted_channel_ids]{
|
||||
for(const auto& deleted_channel_id : deleted_channel_ids) {
|
||||
ref_self->tokenManager->handle_channel_deleted(deleted_channel_id);
|
||||
}
|
||||
|
||||
for(const auto& deleted_channel_id : deleted_channel_ids) {
|
||||
ref_self->group_manager()->assignments().handle_channel_deleted(deleted_channel_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This method had previously owned the clients command lock but that's not really needed.
|
||||
* Everything which is related to the server channel tree or the client channel tree should be locked with
|
||||
* the appropriate mutexes.
|
||||
*/
|
||||
void VirtualServer::client_move(
|
||||
const shared_ptr<ts::server::ConnectedClient> &target,
|
||||
const shared_ptr<ts::server::ConnectedClient> &target_client,
|
||||
shared_ptr<ts::BasicChannel> target_channel,
|
||||
const std::shared_ptr<ts::server::ConnectedClient> &invoker,
|
||||
const std::string &reason_message,
|
||||
@@ -375,171 +424,183 @@ void VirtualServer::client_move(
|
||||
std::unique_lock<std::shared_mutex> &server_channel_write_lock) {
|
||||
|
||||
TIMING_START(timings);
|
||||
if(server_channel_write_lock.owns_lock())
|
||||
server_channel_write_lock.unlock();
|
||||
if(!server_channel_write_lock.owns_lock()) {
|
||||
server_channel_write_lock.lock();
|
||||
}
|
||||
|
||||
lock_guard client_command_lock(target->command_lock);
|
||||
server_channel_write_lock.lock();
|
||||
TIMING_STEP(timings, "chan tree l");
|
||||
if(target->currentChannel == target_channel)
|
||||
if(target_client->currentChannel == target_channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* first step: resolve the target channel / or fix missing */
|
||||
/* first step: verify thew source and target channel */
|
||||
auto s_target_channel = dynamic_pointer_cast<ServerChannel>(target_channel);
|
||||
auto s_source_channel = dynamic_pointer_cast<ServerChannel>(target->currentChannel);
|
||||
assert(!target->currentChannel || s_source_channel != nullptr);
|
||||
auto s_source_channel = dynamic_pointer_cast<ServerChannel>(target_client->currentChannel);
|
||||
assert(!target_client->currentChannel || s_source_channel != nullptr);
|
||||
|
||||
deque<property::ClientProperties> client_updates;
|
||||
std::deque<property::ClientProperties> changed_groups{};
|
||||
std::deque<property::ClientProperties> updated_client_properties{};
|
||||
if(target_channel) {
|
||||
assert(s_target_channel);
|
||||
if(s_target_channel->deleted) {
|
||||
target_channel = this->channelTree->getDefaultChannel();
|
||||
s_target_channel = dynamic_pointer_cast<ServerChannel>(target_channel);
|
||||
assert(s_target_channel);
|
||||
return;
|
||||
}
|
||||
|
||||
/* update the group properties here already, so for all enter views we could just send the new props directly */
|
||||
changed_groups = this->groups->update_server_group_property(target, true, s_target_channel);
|
||||
client_updates.insert(client_updates.end(), changed_groups.begin(), changed_groups.end()); //TODO: Only update for clients which have no enter?
|
||||
}
|
||||
|
||||
auto l_target_channel = s_target_channel ? this->channelTree->findLinkedChannel(s_target_channel->channelId()) : nullptr;
|
||||
auto l_source_channel = s_source_channel ? this->channelTree->findLinkedChannel(s_source_channel->channelId()) : nullptr;
|
||||
TIMING_STEP(timings, "channel res");
|
||||
|
||||
/* second step: show the target channel to the client if its not shown and let him subscibe to the channel */
|
||||
/* second step: show the target channel to the client if its not shown and let him subscribe to the channel */
|
||||
if(target_channel && notify_client) {
|
||||
unique_lock client_channel_lock(target->channel_lock);
|
||||
std::unique_lock client_channel_lock{target_client->channel_tree_mutex};
|
||||
|
||||
bool success = false;
|
||||
bool success{false};
|
||||
/* TODO: Use a bunk here and not a notify for every single */
|
||||
for(const auto& channel : target->channels->show_channel(l_target_channel, success))
|
||||
target->notifyChannelShow(channel->channel(), channel->previous_channel);
|
||||
sassert(success);
|
||||
if(!success)
|
||||
return;
|
||||
for(const auto& channel : target_client->channel_tree->show_channel(l_target_channel, success)) {
|
||||
target_client->notifyChannelShow(channel->channel(), channel->previous_channel);
|
||||
}
|
||||
|
||||
target->subscribeChannel({target_channel}, false, true);
|
||||
sassert(success);
|
||||
if(!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
target_client->subscribeChannel({ target_channel }, false, true);
|
||||
}
|
||||
TIMING_STEP(timings, "target show");
|
||||
|
||||
if(s_source_channel) {
|
||||
s_source_channel->unregister_client(target_client);
|
||||
}
|
||||
|
||||
if(target_channel) {
|
||||
this->forEachClient([&](const shared_ptr<ConnectedClient>& client) {
|
||||
if (!notify_client && client == target) return;
|
||||
ClientPermissionCalculator target_client_permissions{&*target_client, target_channel};
|
||||
auto needed_view_power = target_client_permissions.calculate_permission(permission::i_client_needed_serverquery_view_power);
|
||||
|
||||
unique_lock client_channel_lock(client->channel_lock);
|
||||
auto chan_target = client->channels->find_channel(target_channel);
|
||||
/* ct_... is for client channel tree */
|
||||
this->forEachClient([&](const std::shared_ptr<ConnectedClient>& client) {
|
||||
if (!notify_client && client == target_client) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(chan_target) {
|
||||
auto chan_source = client->channels->find_channel(s_source_channel);
|
||||
if(chan_source) {
|
||||
if (chan_target->subscribed || client == target) {
|
||||
if (client == target || client->isClientVisible(target, false)) {
|
||||
client->notifyClientMoved(target, s_target_channel, reason_id, reason_message, invoker, false);
|
||||
bool move_target_client_visible{true};
|
||||
if(target_client->getType() == ClientType::CLIENT_QUERY) {
|
||||
auto query_view_power = client->calculate_permission(permission::i_client_serverquery_view_power, target_channel->channelId());
|
||||
move_target_client_visible = permission::v2::permission_granted(needed_view_power, query_view_power);
|
||||
}
|
||||
|
||||
std::unique_lock client_channel_lock{client->channel_tree_mutex};
|
||||
auto ct_target_channel = move_target_client_visible ? client->channel_tree->find_channel(target_channel) : nullptr;
|
||||
|
||||
if(ct_target_channel) {
|
||||
auto ct_source_channel = client->channel_tree->find_channel(s_source_channel);
|
||||
if(ct_source_channel) {
|
||||
/* Source and target channel are visible for the client. Just a "normal" move. */
|
||||
if (ct_target_channel->subscribed || client == target_client) {
|
||||
if (client == target_client || client->isClientVisible(target_client, false)) {
|
||||
client->notifyClientMoved(target_client, s_target_channel, reason_id, reason_message, invoker, false);
|
||||
} else {
|
||||
client->notifyClientEnterView(target, invoker, reason_message, s_target_channel, reason_id, s_source_channel, false);
|
||||
client->notifyClientEnterView(target_client, invoker, reason_message, s_target_channel, reason_id, s_source_channel, false);
|
||||
}
|
||||
} else if(client->isClientVisible(target, false)){
|
||||
//Client got out of view
|
||||
client->notifyClientLeftView(target, s_target_channel, reason_id, reason_message.empty() ? string("view left") : reason_message, invoker, false);
|
||||
} else if(client->isClientVisible(target_client, false)){
|
||||
/* Client has been moved into an unsubscribed channel */
|
||||
client->notifyClientLeftView(target_client, s_target_channel, reason_id, reason_message.empty() ? string("view left") : reason_message, invoker, false);
|
||||
}
|
||||
} else {
|
||||
if(client == target && client->getType() != ClientType::CLIENT_INTERNAL && client->getType() != ClientType::CLIENT_MUSIC)
|
||||
logCritical(this->getServerId(), "{} Client enters visibility twice!", CLIENT_STR_LOG_PREFIX_(client));
|
||||
|
||||
//Client entered view
|
||||
if(chan_target->subscribed)
|
||||
client->notifyClientEnterView(target, invoker, reason_message, s_target_channel, ViewReasonId::VREASON_USER_ACTION, nullptr, false);
|
||||
} else if(ct_target_channel->subscribed) {
|
||||
/* Target client entered the view from an invisible channel */
|
||||
client->notifyClientEnterView(target_client, invoker, reason_message, s_target_channel, ViewReasonId::VREASON_USER_ACTION, nullptr, false);
|
||||
}
|
||||
} else {
|
||||
/* target channel isn't visible => so client gone out of view */
|
||||
if(client == target && client->getType() != ClientType::CLIENT_INTERNAL && client->getType() != ClientType::CLIENT_MUSIC)
|
||||
logCritical(this->getServerId(), "{} Moving own client into a not visible channel! This shall not happen!", CLIENT_STR_LOG_PREFIX_(client));
|
||||
//Test for in view? (Notify already does but nvm)
|
||||
|
||||
if(client->isClientVisible(target, false)){
|
||||
//Client got out of view
|
||||
if(reason_id == ViewReasonId::VREASON_USER_ACTION)
|
||||
client->notifyClientLeftView(target, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "joined a hidden channel" : reason_message, invoker, false);
|
||||
else
|
||||
client->notifyClientLeftView(target, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "moved to a hidden channel" : reason_message, invoker, false);
|
||||
if(client->isClientVisible(target_client, false)) {
|
||||
/* Client has been moved out of view into an invisible channel */
|
||||
if(reason_id == ViewReasonId::VREASON_USER_ACTION) {
|
||||
client->notifyClientLeftView(target_client, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "joined a hidden channel" : reason_message, invoker, false);
|
||||
} else {
|
||||
client->notifyClientLeftView(target_client, nullptr, ViewReasonId::VREASON_SERVER_LEFT, reason_message.empty() ? "moved to a hidden channel" : reason_message, invoker, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(s_source_channel)
|
||||
s_source_channel->unregister_client(target);
|
||||
s_target_channel->register_client(target);
|
||||
s_target_channel->register_client(target_client);
|
||||
if(auto client{dynamic_pointer_cast<SpeakingClient>(target_client)}; client) {
|
||||
this->rtc_server().assign_channel(client->rtc_client_id, s_target_channel->rtc_channel_id);
|
||||
}
|
||||
|
||||
if(auto client{dynamic_pointer_cast<VoiceClient>(target_client)}; client) {
|
||||
/* Start normal broadcasting, what the client expects */
|
||||
this->rtc_server().start_broadcast_audio(client->rtc_client_id, 1);
|
||||
client->clear_video_unsupported_message_flag();
|
||||
}
|
||||
} else {
|
||||
/* client left the server */
|
||||
if(target->currentChannel) {
|
||||
if(target_client->currentChannel) {
|
||||
for(const auto& client : this->getClients()) {
|
||||
if(!client || client == target)
|
||||
if(!client || client == target_client)
|
||||
continue;
|
||||
|
||||
unique_lock client_channel_lock(client->channel_lock);
|
||||
if(client->isClientVisible(target, false))
|
||||
client->notifyClientLeftView(target, nullptr, reason_id, reason_message, invoker, false);
|
||||
unique_lock client_channel_lock(client->channel_tree_mutex);
|
||||
if(client->isClientVisible(target_client, false)) {
|
||||
client->notifyClientLeftView(target_client, nullptr, reason_id, reason_message, invoker, false);
|
||||
}
|
||||
}
|
||||
|
||||
s_source_channel->unregister_client(target);
|
||||
if(auto client{dynamic_pointer_cast<SpeakingClient>(target_client)}; client) {
|
||||
this->rtc_server().assign_channel(client->rtc_client_id, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
TIMING_STEP(timings, "notify view");
|
||||
|
||||
target->currentChannel = target_channel;
|
||||
server_channel_write_lock.unlock();
|
||||
target_client->currentChannel = target_channel;
|
||||
|
||||
/* third step: update stuff for the client (remember: the client cant execute anything at the moment!) */
|
||||
shared_lock server_channel_read_lock(this->channel_tree_lock);
|
||||
unique_lock client_channel_lock(target->channel_lock);
|
||||
unique_lock client_channel_lock{target_client->channel_tree_mutex};
|
||||
TIMING_STEP(timings, "lock own tr");
|
||||
|
||||
if (s_source_channel) {
|
||||
s_source_channel->properties()[property::CHANNEL_LAST_LEFT] = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now().time_since_epoch()).count();
|
||||
auto source_channel_group = this->groups->getChannelGroupExact(target->getClientDatabaseId(), s_source_channel, false);
|
||||
if(source_channel_group) {
|
||||
auto default_data = source_channel_group->group->permissions()->permission_value_flagged(permission::b_group_is_permanent);
|
||||
if(!default_data.has_value || default_data.value != 1)
|
||||
this->groups->setChannelGroup(target->getClientDatabaseId(), nullptr, s_source_channel);
|
||||
s_source_channel->properties()[property::CHANNEL_LAST_LEFT] = std::chrono::duration_cast<chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
this->group_manager()->assignments().cleanup_temporary_channel_assignment(target_client->getClientDatabaseId(), s_source_channel->channelId());
|
||||
|
||||
if(target_client->properties()[property::CLIENT_IS_TALKER].update_value("0")) {
|
||||
updated_client_properties.push_back(property::CLIENT_IS_TALKER);
|
||||
}
|
||||
|
||||
auto update = target->properties()[property::CLIENT_IS_TALKER].as<bool>() || target->properties()[property::CLIENT_TALK_REQUEST].as<int64_t>() > 0;
|
||||
if(update) {
|
||||
target->properties()[property::CLIENT_IS_TALKER] = 0;
|
||||
target->properties()[property::CLIENT_TALK_REQUEST] = 0;
|
||||
target->properties()[property::CLIENT_TALK_REQUEST_MSG] = "";
|
||||
client_updates.push_back(property::CLIENT_IS_TALKER);
|
||||
client_updates.push_back(property::CLIENT_TALK_REQUEST);
|
||||
client_updates.push_back(property::CLIENT_TALK_REQUEST_MSG);
|
||||
if(target_client->properties()[property::CLIENT_TALK_REQUEST].update_value("0")) {
|
||||
updated_client_properties.push_back(property::CLIENT_TALK_REQUEST);
|
||||
}
|
||||
|
||||
if(target_client->properties()[property::CLIENT_TALK_REQUEST_MSG].update_value("")) {
|
||||
updated_client_properties.push_back(property::CLIENT_TALK_REQUEST_MSG);
|
||||
}
|
||||
TIMING_STEP(timings, "src chan up");
|
||||
}
|
||||
|
||||
if (s_target_channel) {
|
||||
if(target->update_cached_permissions()) /* update cached calculated permissions */
|
||||
target->sendNeededPermissions(false);
|
||||
target_client->task_update_needed_permissions.enqueue();
|
||||
target_client->task_update_displayed_groups.enqueue();
|
||||
TIMING_STEP(timings, "perm gr upd");
|
||||
|
||||
if(s_source_channel) {
|
||||
deque<ChannelId> deleted;
|
||||
for(const auto& channel : target->channels->test_channel(l_source_channel, l_target_channel))
|
||||
for(const auto& channel : target_client->channel_tree->test_channel(l_source_channel, l_target_channel)) {
|
||||
deleted.push_back(channel->channelId());
|
||||
}
|
||||
|
||||
if(!deleted.empty())
|
||||
target->notifyChannelHide(deleted, false);
|
||||
if(!deleted.empty()) {
|
||||
target_client->notifyChannelHide(deleted, false);
|
||||
}
|
||||
|
||||
auto i_source_channel = s_source_channel->channelId();
|
||||
if(std::find(deleted.begin(), deleted.end(), i_source_channel) == deleted.end()) {
|
||||
auto source_channel_sub_power = target->calculate_permission(permission::i_channel_subscribe_power, i_source_channel);
|
||||
auto source_channel_sub_power = target_client->calculate_permission(permission::i_channel_subscribe_power, i_source_channel);
|
||||
if(!s_source_channel->permission_granted(permission::i_channel_needed_subscribe_power, source_channel_sub_power, false)) {
|
||||
auto source_channel_sub_power_ignore = target->calculate_permission(permission::b_channel_ignore_subscribe_power, i_source_channel);
|
||||
if(!permission::v2::permission_granted(1, source_channel_sub_power_ignore, true)) {
|
||||
auto source_channel_sub_power_ignore = target_client->calculate_permission(permission::b_channel_ignore_subscribe_power, i_source_channel);
|
||||
if(!permission::v2::permission_granted(1, source_channel_sub_power_ignore)) {
|
||||
logTrace(this->serverId, "Force unsubscribing of client {} for channel {}/{}. (Channel switch and no permissions)",
|
||||
CLIENT_STR_LOG_PREFIX_(target), s_source_channel->name(),
|
||||
CLIENT_STR_LOG_PREFIX_(target_client), s_source_channel->name(),
|
||||
i_source_channel
|
||||
);
|
||||
target->unsubscribeChannel({s_source_channel}, false); //Unsubscribe last channel (hasn't permissions)
|
||||
target_client->unsubscribeChannel({ s_source_channel }, false); //Unsubscribe last channel (hasn't permissions)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -547,12 +608,13 @@ void VirtualServer::client_move(
|
||||
}
|
||||
}
|
||||
client_channel_lock.unlock();
|
||||
|
||||
/* both methods lock if they require stuff */
|
||||
this->notifyClientPropertyUpdates(target, client_updates, s_source_channel ? true : false);
|
||||
this->notifyClientPropertyUpdates(target_client, updated_client_properties, s_source_channel ? true : false);
|
||||
TIMING_STEP(timings, "notify cpro");
|
||||
if(s_target_channel) {
|
||||
target->updateChannelClientProperties(false, s_source_channel ? true : false);
|
||||
target_client->updateChannelClientProperties(false, s_source_channel ? true : false);
|
||||
TIMING_STEP(timings, "notify_t_pr");
|
||||
}
|
||||
debugMessage(this->getServerId(), "{} Client move timings: {}", CLIENT_STR_LOG_PREFIX_(target), TIMING_FINISH(timings));
|
||||
debugMessage(this->getServerId(), "{} Client move timings: {}", CLIENT_STR_LOG_PREFIX_(target_client), TIMING_FINISH(timings));
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "VirtualServer.h"
|
||||
#include "./manager/ConversationManager.h"
|
||||
#include "./music/MusicBotManager.h"
|
||||
#include "./groups/GroupManager.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace std::chrono;
|
||||
@@ -16,10 +17,10 @@ using namespace ts::buffer;
|
||||
inline void banClientFlood(VirtualServer* server, const shared_ptr<ConnectedClient>& cl, time_point<system_clock> until){
|
||||
auto time = until.time_since_epoch().count() == 0 ? 0L : chrono::ceil<chrono::seconds>(until - system_clock::now()).count();
|
||||
|
||||
std::string reason = "You're flooding too much";
|
||||
std::string reason{"You're flooding too much"};
|
||||
serverInstance->banManager()->registerBan(server->getServerId(), cl->getClientDatabaseId(), reason, cl->getUid(), cl->getLoggingPeerIp(), "", "", until);
|
||||
|
||||
for(const auto &client : server->findClientsByUid(cl->getUid())) {
|
||||
for(const auto &client : server->findClientsByUid(cl->getUid())) {
|
||||
server->notify_client_ban(client, server->getServerRoot(), reason, time);
|
||||
client->close_connection(system_clock::now() + seconds(1));
|
||||
}
|
||||
@@ -31,29 +32,27 @@ timing_end = system_clock::now(); \
|
||||
variable = duration_cast<decltype(variable)>(timing_end - timing_begin);
|
||||
|
||||
void VirtualServer::executeServerTick() {
|
||||
threads::MutexTryLock l(this->stateLock); //Should not attempt to shutdown or start the server while ticking
|
||||
if(!l) {
|
||||
if(this->running())
|
||||
logError(this->getServerId(), "Failed to lock tick mutex!");
|
||||
std::shared_lock state_lock{this->state_mutex};
|
||||
if(!this->running()) {
|
||||
return;
|
||||
}
|
||||
if(!this->running()) return;
|
||||
|
||||
auto tick_timestamp = std::chrono::system_clock::now();
|
||||
try {
|
||||
if(lastTick.time_since_epoch().count() != 0) {
|
||||
auto delay = system_clock::now() - lastTick;
|
||||
auto delay_ms = duration_cast<milliseconds>(delay).count();
|
||||
if(this->lastTick.time_since_epoch().count() > 0) {
|
||||
auto delay = tick_timestamp - this->lastTick;
|
||||
auto delay_ms = std::chrono::duration_cast<std::chrono::milliseconds>(delay).count();
|
||||
if(delay_ms > 510) {
|
||||
if(delay_ms < 750) {
|
||||
logWarning(this->getServerId(),
|
||||
"Found varianzes within the server tick! (Supposed: 500ms Hold: {}ms)", delay_ms);
|
||||
"Found variances within the server tick! (Supposed: 500ms Hold: {}ms)", delay_ms);
|
||||
} else {
|
||||
logError(this->getServerId(),
|
||||
"Found varianzes within the server tick! This long delay could be an issue. (Supposed: 500ms Hold: {}ms)", delay_ms);
|
||||
"Found variances within the server tick! This long delay could be an issue. (Supposed: 500ms Hold: {}ms)", delay_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
lastTick = system_clock::now();
|
||||
this->lastTick = tick_timestamp;
|
||||
|
||||
system_clock::time_point timing_begin, timing_end;
|
||||
milliseconds timing_update_states, timing_client_tick, timing_channel, timing_statistic, timing_groups, timing_ccache, music_manager;
|
||||
@@ -63,40 +62,62 @@ void VirtualServer::executeServerTick() {
|
||||
{
|
||||
BEGIN_TIMINGS();
|
||||
|
||||
size_t clientOnline = 0;
|
||||
size_t queryOnline = 0;
|
||||
size_t clientOnline{0};
|
||||
size_t queryOnline{0};
|
||||
for(const auto& conn : client_list){
|
||||
switch (conn->getType()){
|
||||
switch(conn->connectionState()) {
|
||||
case ConnectionState::CONNECTED:
|
||||
case ConnectionState::INIT_HIGH:
|
||||
break;
|
||||
|
||||
case ConnectionState::INIT_LOW:
|
||||
case ConnectionState::DISCONNECTING:
|
||||
case ConnectionState::DISCONNECTED:
|
||||
continue;
|
||||
|
||||
case ConnectionState::UNKNWON:
|
||||
default:
|
||||
assert(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (conn->getType()) {
|
||||
case ClientType::CLIENT_TEAMSPEAK:
|
||||
case ClientType::CLIENT_TEASPEAK:
|
||||
case ClientType::CLIENT_WEB:
|
||||
clientOnline++;
|
||||
break;
|
||||
|
||||
case ClientType::CLIENT_QUERY:
|
||||
case ClientType::CLIENT_MUSIC:
|
||||
queryOnline++;
|
||||
break;
|
||||
|
||||
case ClientType::CLIENT_INTERNAL:
|
||||
case ClientType::MAX:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
properties()[property::VIRTUALSERVER_UPTIME] = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - this->startTimestamp).count();
|
||||
properties()[property::VIRTUALSERVER_UPTIME] = std::chrono::duration_cast<std::chrono::seconds>(tick_timestamp - this->startTimestamp).count();
|
||||
properties()[property::VIRTUALSERVER_CLIENTS_ONLINE] = clientOnline + queryOnline;
|
||||
properties()[property::VIRTUALSERVER_QUERYCLIENTS_ONLINE] = queryOnline;
|
||||
if(clientOnline + queryOnline == 0) //We don't need to tick, when server is empty!
|
||||
if(clientOnline + queryOnline == 0) {
|
||||
//We don't need to tick, when server is empty!
|
||||
return;
|
||||
properties()[property::VIRTUALSERVER_CHANNELS_ONLINE] = this->channelTree->channel_count();
|
||||
properties()[property::VIRTUALSERVER_TOTAL_PING] = this->averagePing();
|
||||
}
|
||||
|
||||
properties()[property::VIRTUALSERVER_CHANNELS_ONLINE] = this->channelTree->channel_count();
|
||||
properties()[property::VIRTUALSERVER_TOTAL_PING] = this->generate_network_report().average_ping;
|
||||
END_TIMINGS(timing_update_states);
|
||||
}
|
||||
|
||||
{
|
||||
BEGIN_TIMINGS();
|
||||
|
||||
auto flood_decrease = this->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_TICK_REDUCE].as<FloodPoints>();
|
||||
auto flood_block = this->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_IP_BLOCK].as<FloodPoints>();
|
||||
auto flood_decrease = this->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_TICK_REDUCE].as_or<FloodPoints>(0);
|
||||
auto flood_block = this->properties()[property::VIRTUALSERVER_ANTIFLOOD_POINTS_NEEDED_IP_BLOCK].as_or<FloodPoints>(0);
|
||||
|
||||
bool flag_update_spoken = this->spoken_time_timestamp + seconds(30) < system_clock::now();
|
||||
|
||||
@@ -105,37 +126,34 @@ void VirtualServer::executeServerTick() {
|
||||
tick_client_begin = tick_client_end;
|
||||
if(cl->server != this) {
|
||||
logError(this->getServerId(), "Got registered client, but client does not think hes bound to this server!");
|
||||
|
||||
{
|
||||
lock_guard lock(this->clients.lock);
|
||||
for(auto& client : this->clients.clients) {
|
||||
if(client != cl) continue;
|
||||
|
||||
client.reset();
|
||||
this->clients.count--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
continue; //Fully ha?
|
||||
std::unique_lock tree_lock{this->channel_tree_mutex};
|
||||
this->unregisterClient(cl, "invalid server handle", tree_lock);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(cl->floodPoints > flood_block){
|
||||
if(!cl->ignoresFlood()) {
|
||||
banClientFlood(this, cl, system_clock::now() + minutes(10));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(cl->floodPoints > flood_decrease)
|
||||
|
||||
if(cl->floodPoints > flood_decrease) {
|
||||
cl->floodPoints -= flood_decrease;
|
||||
else cl->floodPoints = 0;
|
||||
cl->tick(tick_client_end);
|
||||
} else {
|
||||
cl->floodPoints = 0;
|
||||
}
|
||||
|
||||
cl->tick_server(tick_client_end);
|
||||
auto voice = dynamic_pointer_cast<SpeakingClient>(cl);
|
||||
if(flag_update_spoken && voice)
|
||||
if(flag_update_spoken && voice) {
|
||||
this->spoken_time += voice->takeSpokenTime();
|
||||
}
|
||||
tick_client_end = system_clock::now();
|
||||
|
||||
auto passed_time = tick_client_end - tick_client_begin;
|
||||
if(passed_time > microseconds(2500)) {
|
||||
if(passed_time > milliseconds(10)) {
|
||||
if(passed_time > microseconds{2500}) {
|
||||
if(passed_time > milliseconds{10}) {
|
||||
logError(this->serverId, "Ticking of client {:1} ({:2}) needs more that 2500 microseconds! ({:3} microseconds)",
|
||||
cl->getLoggingPeerIp() + ":" + to_string(cl->getPeerPort()),
|
||||
cl->getDisplayName(),
|
||||
@@ -150,15 +168,18 @@ void VirtualServer::executeServerTick() {
|
||||
}
|
||||
}
|
||||
|
||||
if(cl->clientPermissions->require_db_updates()) {
|
||||
auto client_permissions = cl->clientPermissions;
|
||||
if(client_permissions->require_db_updates()) {
|
||||
auto begin = system_clock::now();
|
||||
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), cl->clientPermissions);
|
||||
serverInstance->databaseHelper()->saveClientPermissions(this->ref(), cl->getClientDatabaseId(), client_permissions);
|
||||
auto end = system_clock::now();
|
||||
debugMessage(this->serverId, "Saved client permissions for client {} ({}) in {}ms", cl->getClientDatabaseId(), cl->getDisplayName(), duration_cast<milliseconds>(end - begin).count());
|
||||
}
|
||||
}
|
||||
if(flag_update_spoken)
|
||||
|
||||
if(flag_update_spoken) {
|
||||
this->spoken_time_timestamp = system_clock::now();
|
||||
}
|
||||
|
||||
END_TIMINGS(timing_client_tick);
|
||||
}
|
||||
@@ -167,31 +188,33 @@ void VirtualServer::executeServerTick() {
|
||||
{
|
||||
BEGIN_TIMINGS();
|
||||
|
||||
unique_lock channel_lock(this->channel_tree_lock);
|
||||
std::unique_lock channel_lock{this->channel_tree_mutex};
|
||||
|
||||
auto channels = this->channelTree->channels();
|
||||
channel_lock.unlock();
|
||||
for(const auto& channel : channels) {
|
||||
auto server_channel = dynamic_pointer_cast<ServerChannel>(channel);
|
||||
assert(server_channel);
|
||||
|
||||
for(const auto& channel : this->channelTree->channels()){
|
||||
if(channel->channelType() == ChannelType::temporary) {
|
||||
auto server_channel = dynamic_pointer_cast<ServerChannel>(channel);
|
||||
assert(server_channel);
|
||||
if(server_channel->client_count() > 0 || !this->getClientsByChannelRoot(channel, true).empty())
|
||||
if(server_channel->client_count() > 0 || !this->isChannelRootEmpty(channel, false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
seconds deleteTimeout(0);
|
||||
if(channel->properties().hasProperty(property::CHANNEL_DELETE_DELAY))
|
||||
deleteTimeout = seconds(channel->properties()[property::CHANNEL_DELETE_DELAY].as<uint64_t>());
|
||||
/* seconds */
|
||||
auto channel_delete_timeout = channel->properties()[property::CHANNEL_DELETE_DELAY].as_or<uint64_t>(0);
|
||||
auto empty_seconds = channel->empty_seconds();
|
||||
|
||||
auto last_left = time_point<system_clock>() + milliseconds(channel->properties()[property::CHANNEL_LAST_LEFT].as<int64_t>());
|
||||
auto channel_created = channel->createdTimestamp();
|
||||
if(empty_seconds > channel_delete_timeout) {
|
||||
this->delete_channel(server_channel, this->serverRoot, "temporary auto delete", channel_lock, true);
|
||||
if(!channel_lock.owns_lock()) {
|
||||
channel_lock.lock();
|
||||
}
|
||||
|
||||
if(system_clock::now() - last_left < deleteTimeout) continue; //One second stay
|
||||
if(system_clock::now() - channel_created < deleteTimeout + seconds(1)) continue; //One second stay
|
||||
|
||||
this->delete_channel(server_channel, this->serverRoot, "temporary autodelete", channel_lock);
|
||||
if(channel_lock.owns_lock())
|
||||
channel_lock.unlock();
|
||||
/* no need to tick the channel any more since it has been deleted */
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto permission_manager = channel->permissions();
|
||||
if(permission_manager->require_db_updates()) {
|
||||
@@ -209,34 +232,15 @@ void VirtualServer::executeServerTick() {
|
||||
{
|
||||
BEGIN_TIMINGS();
|
||||
|
||||
this->serverStatistics->tick();
|
||||
|
||||
if(fileStatisticsTimestamp + seconds(5) < system_clock::now()) {
|
||||
fileStatisticsTimestamp = system_clock::now();
|
||||
auto update = this->serverStatistics->mark_file_bytes();
|
||||
if(update.first > 0) {
|
||||
this->properties()[property::VIRTUALSERVER_MONTH_BYTES_DOWNLOADED] += update.first;
|
||||
this->properties()[property::VIRTUALSERVER_TOTAL_BYTES_DOWNLOADED] += update.first;
|
||||
}
|
||||
if(update.second > 0) {
|
||||
this->properties()[property::VIRTUALSERVER_MONTH_BYTES_UPLOADED] += update.second;
|
||||
this->properties()[property::VIRTUALSERVER_TOTAL_BYTES_UPLOADED] += update.second;
|
||||
}
|
||||
}
|
||||
|
||||
this->server_statistics_->tick();
|
||||
{
|
||||
lock_guard<threads::Mutex> lock(this->join_attempts_lock);
|
||||
if(system_clock::now() > this->join_last_decrease + seconds(5)) {
|
||||
for(auto& elm : this->join_attempts)
|
||||
if(elm.second > 0) elm.second--;
|
||||
lock_guard lock{this->join_attempts_lock};
|
||||
if(tick_timestamp > this->join_last_decrease + seconds(5)) {
|
||||
std::erase_if(this->join_attempts, [](auto& entry) {
|
||||
return --entry.second <= 0;
|
||||
});
|
||||
|
||||
auto copy = this->join_attempts;
|
||||
for(const auto& elm : copy)
|
||||
if(elm.second == 0){
|
||||
auto found = find(this->join_attempts.begin(), this->join_attempts.end(), elm);
|
||||
if(found != this->join_attempts.end()) this->join_attempts.erase(found);
|
||||
}
|
||||
this->join_last_decrease = system_clock::now();
|
||||
this->join_last_decrease = tick_timestamp;
|
||||
}
|
||||
}
|
||||
END_TIMINGS(timing_statistic);
|
||||
@@ -244,18 +248,7 @@ void VirtualServer::executeServerTick() {
|
||||
|
||||
{
|
||||
BEGIN_TIMINGS();
|
||||
|
||||
auto groups = this->getGroupManager()->availableGroups(false);
|
||||
for(auto& group : groups) {
|
||||
auto permissions = group->permissions();
|
||||
if(permissions->require_db_updates()) {
|
||||
auto begin = system_clock::now();
|
||||
serverInstance->databaseHelper()->saveGroupPermissions(this->ref(), group->groupId(), permissions);
|
||||
auto end = system_clock::now();
|
||||
debugMessage(this->serverId, "Saved group permissions for group {} ({}) in {}ms", group->groupId(), group->name(), duration_cast<milliseconds>(end - begin).count());
|
||||
}
|
||||
}
|
||||
|
||||
this->group_manager()->save_permissions();
|
||||
END_TIMINGS(timing_groups);
|
||||
}
|
||||
|
||||
@@ -263,7 +256,7 @@ void VirtualServer::executeServerTick() {
|
||||
BEGIN_TIMINGS();
|
||||
if(this->conversation_cache_cleanup_timestamp + minutes(15) < system_clock::now()) {
|
||||
debugMessage(this->serverId, "Cleaning up conversation cache.");
|
||||
this->_conversation_manager->cleanup_cache();
|
||||
this->conversation_manager_->cleanup_cache();
|
||||
conversation_cache_cleanup_timestamp = system_clock::now();
|
||||
}
|
||||
END_TIMINGS(timing_ccache);
|
||||
@@ -271,13 +264,13 @@ void VirtualServer::executeServerTick() {
|
||||
|
||||
{
|
||||
BEGIN_TIMINGS();
|
||||
this->musicManager->execute_tick();
|
||||
this->music_manager_->execute_tick();
|
||||
END_TIMINGS(music_manager);
|
||||
}
|
||||
|
||||
if(system_clock::now() - lastTick > milliseconds(100)) {
|
||||
//milliseconds timing_update_states, timing_client_tick, timing_channel, timing_statistic;
|
||||
logError(this->serverId, "Server tick tooks to long ({}ms => Status updates: {}ms Client tick: {}ms, Channel tick: {}ms, Statistic tick: {}ms, Groups: {}ms, Conversation cache: {}ms)",
|
||||
logError(this->serverId, "Server tick took to long ({}ms => Status updates: {}ms Client tick: {}ms, Channel tick: {}ms, Statistic tick: {}ms, Groups: {}ms, Conversation cache: {}ms)",
|
||||
duration_cast<milliseconds>(system_clock::now() - lastTick).count(),
|
||||
timing_update_states.count(),
|
||||
timing_client_tick.count(),
|
||||
|
||||
+567
-564
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user