mirror of
https://github.com/lifegpc/c-utils.git
synced 2026-07-08 01:30:39 +08:00
Update
This commit is contained in:
@@ -34,6 +34,7 @@ set(SOURCE_FILE
|
|||||||
wchar_util.cpp
|
wchar_util.cpp
|
||||||
memfile.c
|
memfile.c
|
||||||
cmath.c
|
cmath.c
|
||||||
|
time_util.cpp
|
||||||
)
|
)
|
||||||
set(SOURCE_FILE_HEADERS
|
set(SOURCE_FILE_HEADERS
|
||||||
cfileop.h
|
cfileop.h
|
||||||
@@ -45,6 +46,7 @@ set(SOURCE_FILE_HEADERS
|
|||||||
wchar_util.h
|
wchar_util.h
|
||||||
memfile.h
|
memfile.h
|
||||||
cmath.h
|
cmath.h
|
||||||
|
time_util.h
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(utils STATIC ${SOURCE_FILE} ${SOURCE_FILE_HEADERS})
|
add_library(utils STATIC ${SOURCE_FILE} ${SOURCE_FILE_HEADERS})
|
||||||
|
|||||||
30
cfileop.cpp
30
cfileop.cpp
@@ -49,3 +49,33 @@ int fileop_open(const char* fn, int* fd, int oflag, int shflag, int pmode) {
|
|||||||
*fd = tfd;
|
*fd = tfd;
|
||||||
return re;
|
return re;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int fileop_isabs(const char* path) {
|
||||||
|
if (!path) return 0;
|
||||||
|
return fileop::isabs(path) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* fileop_join(const char* path, const char* path2) {
|
||||||
|
if (!path || !path2) return nullptr;
|
||||||
|
auto re = fileop::join(path, path2);
|
||||||
|
char* tmp = nullptr;
|
||||||
|
return cpp2c::string2char(re, tmp) ? tmp : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fileop_isdir(const char* path, int* result) {
|
||||||
|
if (!path || !result) return 0;
|
||||||
|
bool re;
|
||||||
|
auto r = fileop::isdir(path, re);
|
||||||
|
if (r) *result = re ? 1 : 0;
|
||||||
|
return r ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fileop_mkdir(const char* path, int mode) {
|
||||||
|
if (!path) return 0;
|
||||||
|
return fileop::mkdir(path, mode) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fileop_set_file_time(const char* path, time_t ctime, time_t actime, time_t modtime) {
|
||||||
|
if (!path) return 0;
|
||||||
|
return fileop::set_file_time(path, ctime, actime, modtime) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|||||||
51
cfileop.h
51
cfileop.h
@@ -4,6 +4,10 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <fcntl.h>
|
||||||
/**
|
/**
|
||||||
* @brief Check file exists
|
* @brief Check file exists
|
||||||
* @param fn File name
|
* @param fn File name
|
||||||
@@ -53,6 +57,53 @@ int fileop_parse_size(const char* size, size_t* fs, int is_byte);
|
|||||||
* @return errno
|
* @return errno
|
||||||
*/
|
*/
|
||||||
int fileop_open(const char* fn, int* fd, int oflag, int shflag, int pmode);
|
int fileop_open(const char* fn, int* fd, int oflag, int shflag, int pmode);
|
||||||
|
/**
|
||||||
|
* @brief Check if path is absoulte path.
|
||||||
|
* @param path The path.
|
||||||
|
* @return 1 if path is absoulte path otherwise 0
|
||||||
|
*/
|
||||||
|
int fileop_isabs(const char* path);
|
||||||
|
/**
|
||||||
|
* @brief Join two more pathname components
|
||||||
|
* @param path
|
||||||
|
* @param path2
|
||||||
|
* @return Result. Need free memory by using free.
|
||||||
|
*/
|
||||||
|
char* fileop_join(const char* path, const char* path2);
|
||||||
|
/**
|
||||||
|
* @brief check if path is an existing directory.
|
||||||
|
* @param path Path
|
||||||
|
* @param result 1 if path is an existing directory otherwise 0
|
||||||
|
* @return 0 if error occured otherwise 1
|
||||||
|
*/
|
||||||
|
int fileop_isdir(const char* path, int* result);
|
||||||
|
/**
|
||||||
|
* @brief Creates a new directory.
|
||||||
|
* @param path Path for a new directory.
|
||||||
|
* @param mode Directory permission. (Linux only)
|
||||||
|
* @return 1 if created successfully otherwise 0
|
||||||
|
*/
|
||||||
|
int fileop_mkdir(const char* path, int mode);
|
||||||
|
/**
|
||||||
|
* @brief Sets the date and time that the specified file or directory was created, last accessed, or last modified.
|
||||||
|
* @param path The path of directory or file
|
||||||
|
* @param ctime creation date (Windows Only)
|
||||||
|
* @param actime Last access date
|
||||||
|
* @param modtime Last modification date
|
||||||
|
* @return 1 if successed otherwise 0.
|
||||||
|
*/
|
||||||
|
int fileop_set_file_time(const char* path, time_t ctime, time_t actime, time_t modtime);
|
||||||
|
#if _WIN32
|
||||||
|
#define fileop_fdopen _fdopen
|
||||||
|
#else
|
||||||
|
#define fileop_fdopen fdopen
|
||||||
|
#endif
|
||||||
|
#if _WIN32
|
||||||
|
#define fileop_close(fd) (!_close(fd))
|
||||||
|
#else
|
||||||
|
#define fileop_close(fd) (!close(fd))
|
||||||
|
#endif
|
||||||
|
#define fileop_fclose(stream) (!fclose(stream))
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
12
cstr_util.c
12
cstr_util.c
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
int cstr_util_copy_str(char** dest, const char* str) {
|
int cstr_util_copy_str(char** dest, const char* str) {
|
||||||
if (!dest || !str) return 1;
|
if (!dest || !str) return 1;
|
||||||
@@ -15,3 +16,14 @@ int cstr_util_copy_str(char** dest, const char* str) {
|
|||||||
*dest = temp;
|
*dest = temp;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int cstr_is_integer(const char* str, int allow_sign) {
|
||||||
|
if (!str) return 0;
|
||||||
|
size_t le = strlen(str), i = 0;
|
||||||
|
if (!le) return 0;
|
||||||
|
if (allow_sign && le > 1 && (str[0] == '+' || str[0] == '-')) i++;
|
||||||
|
for (; i < le; i++) {
|
||||||
|
if (!isdigit(str[i])) return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ extern "C" {
|
|||||||
* @return 0 if successed. `1` - args contains NULL. `2` - Out of memory.
|
* @return 0 if successed. `1` - args contains NULL. `2` - Out of memory.
|
||||||
*/
|
*/
|
||||||
int cstr_util_copy_str(char** dest, const char* str);
|
int cstr_util_copy_str(char** dest, const char* str);
|
||||||
|
/**
|
||||||
|
* @brief Check if a string is a integer.
|
||||||
|
* @param str String
|
||||||
|
* @param allow_sign Allow +/- at the first.
|
||||||
|
* @return 1 if is a interger otherwise 0
|
||||||
|
*/
|
||||||
|
int cstr_is_integer(const char* str, int allow_sign);
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
148
fileop.cpp
148
fileop.cpp
@@ -4,16 +4,22 @@
|
|||||||
|
|
||||||
#include "fileop.h"
|
#include "fileop.h"
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#include <io.h>
|
#include <io.h>
|
||||||
|
#include <direct.h>
|
||||||
|
#include <fileapi.h>
|
||||||
#else
|
#else
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/stat.h>
|
#include <utime.h>
|
||||||
#endif
|
#endif
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <ctype.h>
|
||||||
#include "err.h"
|
#include "err.h"
|
||||||
#include "wchar_util.h"
|
#include "wchar_util.h"
|
||||||
|
#include "time_util.h"
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
@@ -52,12 +58,29 @@ int open_internal(wchar_t* fn, int* fd, int oflag, int shflag, int pmode) {
|
|||||||
return _wsopen_s(fd, fn, oflag, shflag, pmode);
|
return _wsopen_s(fd, fn, oflag, shflag, pmode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isdir_internal(wchar_t* fn, bool& result) {
|
||||||
|
struct __stat64 stats;
|
||||||
|
if (_wstat64(fn, &stats)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result = stats.st_mode & S_IFDIR;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mkdir_internal(wchar_t* fn) {
|
||||||
|
return !_wmkdir(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE set_file_time_internal(wchar_t* fn) {
|
||||||
|
return CreateFileW(fn, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, typename ... Args>
|
template <typename T, typename ... Args>
|
||||||
T fileop_internal(const char* fname, UINT codePage, T(*callback)(wchar_t* fn, Args... args), T failed, Args... args) {
|
T fileop_internal(const char* fname, UINT codePage, T(*callback)(wchar_t* fn, Args... args), T failed, Args... args) {
|
||||||
int wlen;
|
int wlen;
|
||||||
wchar_t* fn;
|
wchar_t* fn;
|
||||||
DWORD opt = wchar_util::getMultiByteToWideCharOptions(MB_ERR_INVALID_CHARS, codePage);
|
DWORD opt = wchar_util::getMultiByteToWideCharOptions(MB_ERR_INVALID_CHARS, codePage);
|
||||||
wlen = MultiByteToWideChar(codePage, opt, fname, -1, NULL, 0);
|
wlen = MultiByteToWideChar(codePage, opt, fname, -1, nullptr, 0);
|
||||||
if (!wlen) return failed;
|
if (!wlen) return failed;
|
||||||
fn = (wchar_t*)malloc(sizeof(wchar_t) * wlen);
|
fn = (wchar_t*)malloc(sizeof(wchar_t) * wlen);
|
||||||
if (!MultiByteToWideChar(codePage, opt, fname, -1, fn, wlen)) {
|
if (!MultiByteToWideChar(codePage, opt, fname, -1, fn, wlen)) {
|
||||||
@@ -203,3 +226,124 @@ int fileop::open(std::string fn, int& fd, int oflag, int shflag, int pmode) {
|
|||||||
return fd == -1 ? errno : 0;
|
return fd == -1 ? errno : 0;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool fileop::isabs(std::string path) {
|
||||||
|
if (!path.length()) return false;
|
||||||
|
#if _WIN32
|
||||||
|
if (path.length() <= 2) return false;
|
||||||
|
if (isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) return true; else return false;
|
||||||
|
#else
|
||||||
|
return path[0] == '/' ? true : false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string fileop::join(std::string path, std::string path2) {
|
||||||
|
auto l1 = path.length(), l2 = path2.length();
|
||||||
|
if (!l1) return path2;
|
||||||
|
if (!l2) return path;
|
||||||
|
if (isabs(path2)) return path2;
|
||||||
|
#if _WIN32
|
||||||
|
if (l2 >= 2 && isalpha(path2[0]) && path2[1] == ':') return path2;
|
||||||
|
if (l1 >= 2 && isalpha(path[0]) && path[1] == ':') {
|
||||||
|
if (path2[0] == '/' || path2[0] == '\\') return path.substr(0, 2) + path2;
|
||||||
|
return (path[l1 - 1] == '/' || path[l1 - 1] == '\\') ? path + path2 : path + "\\" + path2;
|
||||||
|
}
|
||||||
|
if (path2[0] == '/' || path2[0] == '\\') return path2;
|
||||||
|
return (path[l1 - 1] == '/' || path[l1 - 1] == '\\') ? path + path2 : path + "\\" + path2;
|
||||||
|
#else
|
||||||
|
return path[l1 - 1] == '/' ? path + path2 : path + "/" + path2;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fileop::isdir(std::string path, bool& result) {
|
||||||
|
if (!exists(path)) {
|
||||||
|
result = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#if _WIN32
|
||||||
|
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
if (fileop_internal<bool, bool&>(path.c_str(), cp[i], &isdir_internal, false, result)) return true;
|
||||||
|
}
|
||||||
|
struct __stat64 stats;
|
||||||
|
if (_stat64(path.c_str(), &stats)) {
|
||||||
|
#else
|
||||||
|
struct stat stats;
|
||||||
|
if (stat(path.c_str(), &stats)) {
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result = stats.st_mode & S_IFDIR;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
bool fileop::isdrive(std::string path) {
|
||||||
|
auto l = path.length();
|
||||||
|
if (l == 2 && isalpha(path[0]) && path[1] == ':') return true;
|
||||||
|
if (l == 3 && isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool fileop::mkdir(std::string path, int mode) {
|
||||||
|
#if _WIN32
|
||||||
|
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
if (fileop_internal(path.c_str(), cp[i], &mkdir_internal, false)) return true;
|
||||||
|
}
|
||||||
|
return !::_mkdir(path.c_str());
|
||||||
|
#else
|
||||||
|
return !::mkdir(path.c_str(), mode);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fileop::set_file_time(std::string path, time_t ctime, time_t actime, time_t modtime) {
|
||||||
|
#if _WIN32
|
||||||
|
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
|
||||||
|
int i;
|
||||||
|
HANDLE file;
|
||||||
|
for (i = 0; i < 3; i++) {
|
||||||
|
if ((file = fileop_internal(path.c_str(), cp[i], &set_file_time_internal, INVALID_HANDLE_VALUE)) != INVALID_HANDLE_VALUE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (file == INVALID_HANDLE_VALUE) {
|
||||||
|
file = CreateFileA(path.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
|
||||||
|
if (file == INVALID_HANDLE_VALUE) return false;
|
||||||
|
}
|
||||||
|
FILETIME c, ac, mod;
|
||||||
|
time_util::time_t_to_file_time(ctime, &c);
|
||||||
|
time_util::time_t_to_file_time(actime, &ac);
|
||||||
|
time_util::time_t_to_file_time(modtime, &mod);
|
||||||
|
auto re = SetFileTime(file, &c, &ac, &mod);
|
||||||
|
CloseHandle(file);
|
||||||
|
return re ? true : false;
|
||||||
|
#else
|
||||||
|
struct utimbuf t = { actime, modtime };
|
||||||
|
return !utime(path.c_str(), &t);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE* fileop::fdopen(int fd, std::string mode) {
|
||||||
|
#if _WIN32
|
||||||
|
return ::_fdopen(fd, mode.c_str());
|
||||||
|
#else
|
||||||
|
return ::fdopen(fd, mode.c_str());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fileop::close(int fd) {
|
||||||
|
#if _WIN32
|
||||||
|
return !::_close(fd);
|
||||||
|
#else
|
||||||
|
return !::close(fd);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fileop::fclose(FILE* f) {
|
||||||
|
if (!f) return false;
|
||||||
|
return !::fclose(f);
|
||||||
|
}
|
||||||
|
|||||||
65
fileop.h
65
fileop.h
@@ -1,6 +1,8 @@
|
|||||||
#ifndef _UTIL_FILEOP_H
|
#ifndef _UTIL_FILEOP_H
|
||||||
#define _UTIL_FILEOP_H
|
#define _UTIL_FILEOP_H
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <time.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
namespace fileop {
|
namespace fileop {
|
||||||
/**
|
/**
|
||||||
@@ -52,5 +54,68 @@ namespace fileop {
|
|||||||
* @return errno
|
* @return errno
|
||||||
*/
|
*/
|
||||||
int open(std::string fn, int& fd, int oflag, int shflag = 0x10, int pmode = 0);
|
int open(std::string fn, int& fd, int oflag, int shflag = 0x10, int pmode = 0);
|
||||||
|
/**
|
||||||
|
* @brief Check if path is absoulte path.
|
||||||
|
* @param path The path.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
bool isabs(std::string path);
|
||||||
|
/**
|
||||||
|
* @brief Join two more pathname components
|
||||||
|
* @param path
|
||||||
|
* @param path2
|
||||||
|
* @return result
|
||||||
|
*/
|
||||||
|
std::string join(std::string path, std::string path2);
|
||||||
|
/**
|
||||||
|
* @brief check if path is an existing directory.
|
||||||
|
* @param path Path
|
||||||
|
* @param result true if path is an existing directory.
|
||||||
|
* @return false if error occured
|
||||||
|
*/
|
||||||
|
bool isdir(std::string path, bool& result);
|
||||||
|
#if _WIN32
|
||||||
|
/**
|
||||||
|
* @brief Check if a path only contains drive.
|
||||||
|
* @param path Path
|
||||||
|
* @return Result
|
||||||
|
*/
|
||||||
|
bool isdrive(std::string path);
|
||||||
|
#endif
|
||||||
|
/**
|
||||||
|
* @brief Creates a new directory.
|
||||||
|
* @param path Path for a new directory.
|
||||||
|
* @param mode Directory permission. (Linux only)
|
||||||
|
* @return true if successed.
|
||||||
|
*/
|
||||||
|
bool mkdir(std::string path, int mode);
|
||||||
|
/**
|
||||||
|
* @brief Sets the date and time that the specified file or directory was created, last accessed, or last modified.
|
||||||
|
* @param path The path of directory or file
|
||||||
|
* @param ctime creation date (Windows Only)
|
||||||
|
* @param actime Last access date
|
||||||
|
* @param modtime Last modification date
|
||||||
|
* @return true if successed.
|
||||||
|
*/
|
||||||
|
bool set_file_time(std::string path, time_t ctime, time_t actime, time_t modtime);
|
||||||
|
/**
|
||||||
|
* @brief Associates a stream with a file that was previously opened.
|
||||||
|
* @param fd File descriptor of the open file.
|
||||||
|
* @param mode Type of file access.
|
||||||
|
* @return A pointer to the open stream.
|
||||||
|
*/
|
||||||
|
FILE* fdopen(int fd, std::string mode);
|
||||||
|
/**
|
||||||
|
* @brief Closes a file.
|
||||||
|
* @param fd File descriptor referring to the open file.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
bool close(int fd);
|
||||||
|
/**
|
||||||
|
* @brief Closes a stream
|
||||||
|
* @param f Pointer to FILE structure.
|
||||||
|
* @return true if the stream is successfully closed
|
||||||
|
*/
|
||||||
|
bool fclose(FILE* f);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
14
time_util.cpp
Normal file
14
time_util.cpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#include "time_util.h"
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if _WIN32
|
||||||
|
void time_util::time_t_to_file_time(time_t t, LPFILETIME pft) {
|
||||||
|
ULARGE_INTEGER time_value;
|
||||||
|
time_value.QuadPart = (t * 10000000LL) + 116444736000000000LL;
|
||||||
|
pft->dwLowDateTime = time_value.LowPart;
|
||||||
|
pft->dwHighDateTime = time_value.HighPart;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
18
time_util.h
Normal file
18
time_util.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef _UTIL_TIME_UTIL_H
|
||||||
|
#define _UTIL_TIME_UTIL_H
|
||||||
|
#include <time.h>
|
||||||
|
#if _WIN32
|
||||||
|
#include <Windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace time_util {
|
||||||
|
#if _WIN32
|
||||||
|
/**
|
||||||
|
* @brief Convert from time_t to FILETIME
|
||||||
|
* @param t UNIX Timestamp
|
||||||
|
* @param pft Result
|
||||||
|
*/
|
||||||
|
void time_t_to_file_time(time_t t, LPFILETIME pft);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user