mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-24 10:56:38 +01:00
Core/Misc: Added compatibility layer for boost 1.66 and future std:: networking stuff
* Based on work done by @dimiandre in PR #21173 Closes #21171 Closes #21173
This commit is contained in:
@@ -18,6 +18,11 @@
|
||||
#ifndef AsioHacksFwd_h__
|
||||
#define AsioHacksFwd_h__
|
||||
|
||||
#include <boost/version.hpp>
|
||||
|
||||
/**
|
||||
Collection of forward declarations to improve compile time
|
||||
*/
|
||||
namespace boost
|
||||
{
|
||||
namespace posix_time
|
||||
@@ -27,6 +32,9 @@ namespace boost
|
||||
|
||||
namespace asio
|
||||
{
|
||||
template <typename Time>
|
||||
struct time_traits;
|
||||
|
||||
namespace ip
|
||||
{
|
||||
class address;
|
||||
@@ -37,7 +45,32 @@ namespace boost
|
||||
class basic_endpoint;
|
||||
|
||||
typedef basic_endpoint<tcp> tcp_endpoint;
|
||||
}
|
||||
|
||||
#if BOOST_VERSION >= 106600
|
||||
template <typename Time, typename TimeTraits>
|
||||
class basic_deadline_timer;
|
||||
|
||||
typedef basic_deadline_timer<posix_time::ptime, time_traits<posix_time::ptime>> deadline_timer;
|
||||
|
||||
namespace ip
|
||||
{
|
||||
template <typename InternetProtocol>
|
||||
class basic_resolver;
|
||||
|
||||
typedef basic_resolver<tcp> tcp_resolver;
|
||||
}
|
||||
#else
|
||||
template <typename TimeType, typename TimeTraits>
|
||||
class deadline_timer_service;
|
||||
|
||||
template <typename Time, typename TimeTraits, typename TimerService>
|
||||
class basic_deadline_timer;
|
||||
|
||||
typedef basic_deadline_timer<posix_time::ptime, time_traits<posix_time::ptime>, deadline_timer_service<posix_time::ptime, time_traits<posix_time::ptime>>> deadline_timer;
|
||||
|
||||
namespace ip
|
||||
{
|
||||
template <typename InternetProtocol>
|
||||
class resolver_service;
|
||||
|
||||
@@ -46,23 +79,16 @@ namespace boost
|
||||
|
||||
typedef basic_resolver<tcp, resolver_service<tcp>> tcp_resolver;
|
||||
}
|
||||
|
||||
template <typename Time>
|
||||
struct time_traits;
|
||||
|
||||
template <typename TimeType, typename TimeTraits>
|
||||
class deadline_timer_service;
|
||||
|
||||
template <typename Time, typename TimeTraits, typename TimerService>
|
||||
class basic_deadline_timer;
|
||||
|
||||
typedef basic_deadline_timer<posix_time::ptime, time_traits<posix_time::ptime>, deadline_timer_service<posix_time::ptime, time_traits<posix_time::ptime>>> deadline_timer;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
namespace Trinity
|
||||
{
|
||||
class AsioStrand;
|
||||
namespace Asio
|
||||
{
|
||||
class Strand;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // AsioHacksFwd_h__
|
||||
65
src/common/Asio/IoContext.h
Normal file
65
src/common/Asio/IoContext.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef IoContext_h__
|
||||
#define IoContext_h__
|
||||
|
||||
#include <boost/version.hpp>
|
||||
|
||||
#if BOOST_VERSION >= 106600
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/post.hpp>
|
||||
#define IoContextBaseNamespace boost::asio
|
||||
#define IoContextBase io_context
|
||||
#else
|
||||
#include <boost/asio/io_service.hpp>
|
||||
#define IoContextBaseNamespace boost::asio
|
||||
#define IoContextBase io_service
|
||||
#endif
|
||||
|
||||
namespace Trinity
|
||||
{
|
||||
namespace Asio
|
||||
{
|
||||
class IoContext : public IoContextBaseNamespace::IoContextBase
|
||||
{
|
||||
using IoContextBaseNamespace::IoContextBase::IoContextBase;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline decltype(auto) post(IoContextBaseNamespace::IoContextBase& ioContext, T&& t)
|
||||
{
|
||||
#if BOOST_VERSION >= 106600
|
||||
return boost::asio::post(ioContext, std::forward<T>(t));
|
||||
#else
|
||||
return ioContext.post(std::forward<T>(t));
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline decltype(auto) get_io_context(T&& ioObject)
|
||||
{
|
||||
#if BOOST_VERSION >= 106600
|
||||
return ioObject.get_executor().context();
|
||||
#else
|
||||
return ioObject.get_io_service();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // IoContext_h__
|
||||
46
src/common/Asio/IpAddress.h
Normal file
46
src/common/Asio/IpAddress.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef IpAddress_h__
|
||||
#define IpAddress_h__
|
||||
|
||||
#include "Define.h"
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
|
||||
namespace Trinity
|
||||
{
|
||||
namespace Net
|
||||
{
|
||||
#if BOOST_VERSION >= 106600
|
||||
using boost::asio::ip::make_address;
|
||||
using boost::asio::ip::make_address_v4;
|
||||
inline uint32 address_to_uint(boost::asio::ip::address_v4 const& address) { return address.to_uint(); }
|
||||
#else
|
||||
inline boost::asio::ip::address make_address(char const* str) { return boost::asio::ip::address::from_string(str); }
|
||||
inline boost::asio::ip::address make_address(char const* str, boost::system::error_code& ec) { return boost::asio::ip::address::from_string(str, ec); }
|
||||
inline boost::asio::ip::address make_address(std::string const& str) { return boost::asio::ip::address::from_string(str); }
|
||||
inline boost::asio::ip::address make_address(std::string const& str, boost::system::error_code& ec) { return boost::asio::ip::address::from_string(str, ec); }
|
||||
inline boost::asio::ip::address_v4 make_address_v4(char const* str) { return boost::asio::ip::address_v4::from_string(str); }
|
||||
inline boost::asio::ip::address_v4 make_address_v4(char const* str, boost::system::error_code& ec) { return boost::asio::ip::address_v4::from_string(str, ec); }
|
||||
inline boost::asio::ip::address_v4 make_address_v4(std::string const& str) { return boost::asio::ip::address_v4::from_string(str); }
|
||||
inline boost::asio::ip::address_v4 make_address_v4(std::string const& str, boost::system::error_code& ec) { return boost::asio::ip::address_v4::from_string(str, ec); }
|
||||
inline uint32 address_to_uint(boost::asio::ip::address_v4 const& address) { return address.to_ulong(); }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif // IpAddress_h__
|
||||
71
src/common/Asio/IpNetwork.h
Normal file
71
src/common/Asio/IpNetwork.h
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef IpNetwork_h__
|
||||
#define IpNetwork_h__
|
||||
|
||||
#include "Define.h"
|
||||
#include "IpAddress.h"
|
||||
#include <boost/version.hpp>
|
||||
#if BOOST_VERSION >= 106600
|
||||
#include <boost/asio/ip/network_v4.hpp>
|
||||
#include <boost/asio/ip/network_v6.hpp>
|
||||
#endif
|
||||
|
||||
namespace Trinity
|
||||
{
|
||||
namespace Net
|
||||
{
|
||||
inline bool IsInNetwork(boost::asio::ip::address_v4 const& networkAddress, boost::asio::ip::address_v4 const& mask, boost::asio::ip::address_v4 const& clientAddress)
|
||||
{
|
||||
#if BOOST_VERSION >= 106600
|
||||
boost::asio::ip::network_v4 network = boost::asio::ip::make_network_v4(networkAddress, mask);
|
||||
boost::asio::ip::address_v4_range hosts = network.hosts();
|
||||
return hosts.find(clientAddress) != hosts.end();
|
||||
#else
|
||||
return (clientAddress.to_ulong() & mask.to_ulong()) == (networkAddress.to_ulong() & mask.to_ulong());
|
||||
#endif
|
||||
}
|
||||
|
||||
inline boost::asio::ip::address_v4 GetDefaultNetmaskV4(boost::asio::ip::address_v4 const& networkAddress)
|
||||
{
|
||||
if ((address_to_uint(networkAddress) & 0x80000000) == 0)
|
||||
return boost::asio::ip::address_v4(0xFF000000);
|
||||
if ((address_to_uint(networkAddress) & 0xC0000000) == 0x80000000)
|
||||
return boost::asio::ip::address_v4(0xFFFF0000);
|
||||
if ((address_to_uint(networkAddress) & 0xE0000000) == 0xC0000000)
|
||||
return boost::asio::ip::address_v4(0xFFFFFF00);
|
||||
return boost::asio::ip::address_v4(0xFFFFFFFF);
|
||||
}
|
||||
|
||||
inline bool IsInNetwork(boost::asio::ip::address_v6 const& networkAddress, uint16 prefixLength, boost::asio::ip::address_v6 const& clientAddress)
|
||||
{
|
||||
#if BOOST_VERSION >= 106600
|
||||
boost::asio::ip::network_v6 network = boost::asio::ip::make_network_v6(networkAddress, prefixLength);
|
||||
boost::asio::ip::address_v6_range hosts = network.hosts();
|
||||
return hosts.find(clientAddress) != hosts.end();
|
||||
#else
|
||||
(void)networkAddress;
|
||||
(void)prefixLength;
|
||||
(void)clientAddress;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // IpNetwork_h__
|
||||
52
src/common/Asio/Resolver.h
Normal file
52
src/common/Asio/Resolver.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef Resolver_h__
|
||||
#define Resolver_h__
|
||||
|
||||
#include "Optional.h"
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <string>
|
||||
|
||||
namespace Trinity
|
||||
{
|
||||
namespace Net
|
||||
{
|
||||
inline Optional<boost::asio::ip::tcp::endpoint> Resolve(boost::asio::ip::tcp::resolver& resolver, boost::asio::ip::tcp const& protocol,
|
||||
std::string const& host, std::string const& service)
|
||||
{
|
||||
boost::system::error_code ec;
|
||||
#if BOOST_VERSION >= 106600
|
||||
boost::asio::ip::tcp::resolver::results_type results = resolver.resolve(protocol, host, service, ec);
|
||||
if (results.empty() || ec)
|
||||
return {};
|
||||
|
||||
return results.begin()->endpoint();
|
||||
#else
|
||||
boost::asio::ip::tcp::resolver::query query(std::move(protocol), std::move(host), std::move(service));
|
||||
boost::asio::ip::tcp::resolver::iterator itr = resolver.resolve(query, ec);
|
||||
boost::asio::ip::tcp::resolver::iterator end;
|
||||
if (itr == end || ec)
|
||||
return {};
|
||||
|
||||
return itr->endpoint();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // Resolver_h__
|
||||
@@ -15,18 +15,39 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AsioHacksImpl_h__
|
||||
#define AsioHacksImpl_h__
|
||||
#ifndef Strand_h__
|
||||
#define Strand_h__
|
||||
|
||||
#include "IoContext.h"
|
||||
#include <boost/asio/strand.hpp>
|
||||
|
||||
#if BOOST_VERSION >= 106600
|
||||
#include <boost/asio/bind_executor.hpp>
|
||||
#endif
|
||||
|
||||
namespace Trinity
|
||||
{
|
||||
class AsioStrand : public boost::asio::io_service::strand
|
||||
namespace Asio
|
||||
{
|
||||
public:
|
||||
AsioStrand(boost::asio::io_service& io_service) : boost::asio::io_service::strand(io_service) { }
|
||||
};
|
||||
/**
|
||||
Hack to make it possible to forward declare strand (which is a inner class)
|
||||
*/
|
||||
class Strand : public IoContextBaseNamespace::IoContextBase::strand
|
||||
{
|
||||
public:
|
||||
Strand(IoContext& ioContext) : IoContextBaseNamespace::IoContextBase::strand(ioContext) { }
|
||||
};
|
||||
|
||||
#if BOOST_VERSION >= 106600
|
||||
using boost::asio::bind_executor;
|
||||
#else
|
||||
template<typename T>
|
||||
inline decltype(auto) bind_executor(Strand& strand, T&& t)
|
||||
{
|
||||
return strand.wrap(std::forward<T>(t));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#endif // AsioHacksImpl_h__
|
||||
#endif // Strand_h__
|
||||
@@ -19,18 +19,18 @@
|
||||
#include "Log.h"
|
||||
#include "AppenderConsole.h"
|
||||
#include "AppenderFile.h"
|
||||
#include "AsioHacksImpl.h"
|
||||
#include "Common.h"
|
||||
#include "Config.h"
|
||||
#include "Errors.h"
|
||||
#include "Logger.h"
|
||||
#include "LogMessage.h"
|
||||
#include "LogOperation.h"
|
||||
#include "Strand.h"
|
||||
#include "Util.h"
|
||||
#include <chrono>
|
||||
#include <sstream>
|
||||
|
||||
Log::Log() : AppenderId(0), lowestLogLevel(LOG_LEVEL_FATAL), _ioService(nullptr), _strand(nullptr)
|
||||
Log::Log() : AppenderId(0), lowestLogLevel(LOG_LEVEL_FATAL), _ioContext(nullptr), _strand(nullptr)
|
||||
{
|
||||
m_logsTimestamp = "_" + GetTimestampStr();
|
||||
RegisterAppender<AppenderConsole>();
|
||||
@@ -228,11 +228,10 @@ void Log::write(std::unique_ptr<LogMessage>&& msg) const
|
||||
{
|
||||
Logger const* logger = GetLoggerByType(msg->type);
|
||||
|
||||
if (_ioService)
|
||||
if (_ioContext)
|
||||
{
|
||||
auto logOperation = std::make_shared<LogOperation>(logger, std::move(msg));
|
||||
|
||||
_ioService->post(_strand->wrap([logOperation](){ logOperation->call(); }));
|
||||
std::shared_ptr<LogOperation> logOperation = std::make_shared<LogOperation>(logger, std::move(msg));
|
||||
Trinity::Asio::post(*_ioContext, Trinity::Asio::bind_executor(*_strand, [logOperation]() { logOperation->call(); }));
|
||||
}
|
||||
else
|
||||
logger->write(msg.get());
|
||||
@@ -367,12 +366,12 @@ Log* Log::instance()
|
||||
return &instance;
|
||||
}
|
||||
|
||||
void Log::Initialize(boost::asio::io_service* ioService)
|
||||
void Log::Initialize(Trinity::Asio::IoContext* ioContext)
|
||||
{
|
||||
if (ioService)
|
||||
if (ioContext)
|
||||
{
|
||||
_ioService = ioService;
|
||||
_strand = new Trinity::AsioStrand(*ioService);
|
||||
_ioContext = ioContext;
|
||||
_strand = new Trinity::Asio::Strand(*ioContext);
|
||||
}
|
||||
|
||||
LoadFromConfig();
|
||||
@@ -382,7 +381,7 @@ void Log::SetSynchronous()
|
||||
{
|
||||
delete _strand;
|
||||
_strand = nullptr;
|
||||
_ioService = nullptr;
|
||||
_ioContext = nullptr;
|
||||
}
|
||||
|
||||
void Log::LoadFromConfig()
|
||||
|
||||
@@ -31,11 +31,11 @@ class Appender;
|
||||
class Logger;
|
||||
struct LogMessage;
|
||||
|
||||
namespace boost
|
||||
namespace Trinity
|
||||
{
|
||||
namespace asio
|
||||
namespace Asio
|
||||
{
|
||||
class io_service;
|
||||
class IoContext;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class TC_COMMON_API Log
|
||||
public:
|
||||
static Log* instance();
|
||||
|
||||
void Initialize(boost::asio::io_service* ioService);
|
||||
void Initialize(Trinity::Asio::IoContext* ioContext);
|
||||
void SetSynchronous(); // Not threadsafe - should only be called from main() after all threads are joined
|
||||
void LoadFromConfig();
|
||||
void Close();
|
||||
@@ -122,8 +122,8 @@ class TC_COMMON_API Log
|
||||
std::string m_logsDir;
|
||||
std::string m_logsTimestamp;
|
||||
|
||||
boost::asio::io_service* _ioService;
|
||||
Trinity::AsioStrand* _strand;
|
||||
Trinity::Asio::IoContext* _ioContext;
|
||||
Trinity::Asio::Strand* _strand;
|
||||
};
|
||||
|
||||
#define sLog Log::instance()
|
||||
|
||||
@@ -16,21 +16,21 @@
|
||||
*/
|
||||
|
||||
#include "Metric.h"
|
||||
#include "AsioHacksImpl.h"
|
||||
#include "Common.h"
|
||||
#include "Config.h"
|
||||
#include "Log.h"
|
||||
#include "Strand.h"
|
||||
#include "Util.h"
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
#include <boost/asio/deadline_timer.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
void Metric::Initialize(std::string const& realmName, boost::asio::io_service& ioService, std::function<void()> overallStatusLogger)
|
||||
void Metric::Initialize(std::string const& realmName, Trinity::Asio::IoContext& ioContext, std::function<void()> overallStatusLogger)
|
||||
{
|
||||
_dataStream = Trinity::make_unique<boost::asio::ip::tcp::iostream>();
|
||||
_realmName = FormatInfluxDBTagValue(realmName);
|
||||
_batchTimer = Trinity::make_unique<boost::asio::deadline_timer>(ioService);
|
||||
_overallStatusTimer = Trinity::make_unique<boost::asio::deadline_timer>(ioService);
|
||||
_batchTimer = Trinity::make_unique<boost::asio::deadline_timer>(ioContext);
|
||||
_overallStatusTimer = Trinity::make_unique<boost::asio::deadline_timer>(ioContext);
|
||||
_overallStatusLogger = overallStatusLogger;
|
||||
LoadFromConfigs();
|
||||
}
|
||||
@@ -215,8 +215,8 @@ void Metric::ScheduleSend()
|
||||
|
||||
void Metric::ForceSend()
|
||||
{
|
||||
// Send what's queued only if io_service is stopped (so only on shutdown)
|
||||
if (_enabled && _batchTimer->get_io_service().stopped())
|
||||
// Send what's queued only if IoContext is stopped (so only on shutdown)
|
||||
if (_enabled && Trinity::Asio::get_io_context(*_batchTimer).stopped())
|
||||
SendBatch();
|
||||
}
|
||||
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace boost
|
||||
namespace Trinity
|
||||
{
|
||||
namespace asio
|
||||
namespace Asio
|
||||
{
|
||||
class io_service;
|
||||
class IoContext;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
~Metric();
|
||||
static Metric* instance();
|
||||
|
||||
void Initialize(std::string const& realmName, boost::asio::io_service& ioService, std::function<void()> overallStatusLogger);
|
||||
void Initialize(std::string const& realmName, Trinity::Asio::IoContext& ioContext, std::function<void()> overallStatusLogger);
|
||||
void LoadFromConfigs();
|
||||
void Update();
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
#include "Util.h"
|
||||
#include "Common.h"
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
#include "IpAddress.h"
|
||||
#include <utf8.h>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
@@ -216,7 +216,7 @@ bool IsIPAddress(char const* ipaddress)
|
||||
return false;
|
||||
|
||||
boost::system::error_code error;
|
||||
boost::asio::ip::address::from_string(ipaddress, error);
|
||||
Trinity::Net::make_address(ipaddress, error);
|
||||
return !error;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user