Port filesystem to ubuntu, add filesystem tests
This commit is contained in:
@@ -205,6 +205,7 @@ Strs operator+(Str a, Str b) {
|
||||
return c;
|
||||
}
|
||||
|
||||
//@todo: split on any whitespace instead!
|
||||
Strs Split(char *str) {
|
||||
Str s = S8_MakeFromChar(str);
|
||||
S8_List list = S8_Split(Perm, s, S8_Lit(" "), 0);
|
||||
@@ -240,6 +241,8 @@ Str Merge(Strs list, Str separator = " "_s) {
|
||||
}
|
||||
|
||||
bool CodeWasModified(char *str) { return SRC_WasModified(S8_MakeFromChar(str)); }
|
||||
bool CodeWasModified(S8_String str) { return SRC_WasModified(str); }
|
||||
S8_String FilenameWithoutExt(S8_String it) { return S8_SkipToLastSlash(S8_ChopLastPeriod(it)); }
|
||||
Strs IfCodeWasModified(char *cfile, char *objfile) {
|
||||
Strs result = {};
|
||||
S8_String s = S8_MakeFromChar(cfile);
|
||||
@@ -258,7 +261,7 @@ int Run(Strs s) {
|
||||
#ifndef BLD_MAIN
|
||||
int main() {
|
||||
int Main();
|
||||
SRC_InitCache(Perm, S8_Lit("project.cache"));
|
||||
SRC_InitCache(Perm, S8_Lit("buildfile.cache"));
|
||||
int result = Main();
|
||||
if (result == 0) SRC_SaveCache();
|
||||
}
|
||||
|
||||
17
build.cpp
17
build.cpp
@@ -8,8 +8,8 @@ int Main() {
|
||||
Strs cc = "cl.exe";
|
||||
Strs flags = Split("-WX -W3 -wd4200 -diagnostics:column -nologo -Z7 -FC -GF -Gm- -Oi -Zo -D_CRT_SECURE_NO_WARNINGS");
|
||||
Strs link = Split("-link -incremental:no");
|
||||
Strs files = Split("../test/main_core_as_header.cpp");
|
||||
files += IfCodeWasModified("../core.c", "../core.obj");
|
||||
Strs files = Split("../test/main_core_as_header.cpp ../core.c");
|
||||
// files += IfCodeWasModified("../core.c", "../core.obj");
|
||||
if (use_std) {
|
||||
flags += Split("-GR- -EHa-");
|
||||
}
|
||||
@@ -24,5 +24,16 @@ int Main() {
|
||||
flags += Split("-Od -D_DEBUG -MTd -fsanitize=address -MTd -RTC1");
|
||||
link += Split("-NODEFAULTLIB:LIBCMT");
|
||||
}
|
||||
return Run(cc + files + flags + link);
|
||||
|
||||
Strs objs = {};
|
||||
For(files) {
|
||||
if (CodeWasModified(it)) {
|
||||
int error = Run(cc + it + flags + "-c" + link);
|
||||
if (error != 0) return error;
|
||||
}
|
||||
|
||||
objs += S8_Format(Perm, "../build/%Q.obj", FilenameWithoutExt(it));
|
||||
}
|
||||
int error = Run(cc + objs + flags + link);
|
||||
return error;
|
||||
}
|
||||
14
build.sh
14
build.sh
@@ -1,10 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
mkdir build
|
||||
cd build
|
||||
clang -o test_array ../test/test_array.cpp -fno-exceptions -fno-rtti -Wno-writable-strings
|
||||
set -e
|
||||
|
||||
gcc -o test_filesystem ../test/test_filesystem.c -Wno-write-strings
|
||||
./test_filesystem
|
||||
gcc -o test_array ../test/test_array.cpp -fno-exceptions -fno-rtti -Wno-write-strings
|
||||
./test_array
|
||||
clang -o test_table ../test/test_table.cpp -fno-exceptions -fno-rtti -Wno-writable-strings
|
||||
gcc -o test_table ../test/test_table.cpp -fno-exceptions -fno-rtti -Wno-write-strings
|
||||
./test_table
|
||||
gcc -o compile_as_c ../test/main.c
|
||||
./compile_as_c
|
||||
|
||||
#-Wno-writable-strings
|
||||
cd ..
|
||||
273
filesystem.c
273
filesystem.c
@@ -344,16 +344,6 @@ OS_API S8_String OS_ReadFile(MA_Arena *arena, S8_String path) {
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API int OS_SystemF(const char *string, ...) {
|
||||
MA_Checkpoint scratch = MA_GetScratch();
|
||||
S8_FORMAT(scratch.arena, string, result);
|
||||
IO_Printf("Executing: %s\n", result.str);
|
||||
fflush(stdout);
|
||||
int error_code = system(result.str);
|
||||
MA_ReleaseScratch(scratch);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
OS_API int64_t OS_GetFileModTime(S8_String file) {
|
||||
FILETIME time = {0};
|
||||
WIN32_FIND_DATAW data;
|
||||
@@ -387,51 +377,260 @@ OS_API OS_Date OS_GetDate(void) {
|
||||
}
|
||||
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <sys/stat.h>
|
||||
#include <time.h>
|
||||
#include <dirent.h>
|
||||
|
||||
OS_API bool OS_EnableTerminalColors(void) { return true; }
|
||||
OS_API bool OS_IsAbsolute(S8_String path) { return false; }
|
||||
|
||||
OS_API bool OS_IsAbsolute(S8_String path) {
|
||||
bool result = path.len >= 1 && path.str[0] == '/';
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API S8_String OS_GetExePath(MA_Arena *arena) {
|
||||
S8_String s = {0};
|
||||
return s;
|
||||
char buffer[PATH_MAX] = {};
|
||||
if (readlink("/proc/self/exe", buffer, PATH_MAX) == -1) {
|
||||
return S8_MakeEmpty();
|
||||
}
|
||||
S8_String result = S8_Copy(arena, S8_MakeFromChar(buffer));
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API S8_String OS_GetExeDir(MA_Arena *arena) {
|
||||
S8_String s = {0};
|
||||
return s;
|
||||
S8_String path = OS_GetExePath(arena);
|
||||
S8_String dir = S8_ChopLastSlash(path);
|
||||
S8_String copy = S8_Copy(arena, dir);
|
||||
return copy;
|
||||
}
|
||||
|
||||
OS_API S8_String OS_GetWorkingDir(MA_Arena *arena) {
|
||||
S8_String s = {0};
|
||||
return s;
|
||||
char *buffer = (char *)MA_PushSizeNonZeroed(arena, PATH_MAX);
|
||||
char *cwd = getcwd(buffer, PATH_MAX);
|
||||
S8_String result = S8_MakeFromChar(cwd);
|
||||
return result;
|
||||
}
|
||||
OS_API void OS_SetWorkingDir(S8_String path) {}
|
||||
|
||||
OS_API void OS_SetWorkingDir(S8_String path) {
|
||||
IO_Assert(path.str[path.len] == 0);
|
||||
chdir(path.str);
|
||||
}
|
||||
|
||||
OS_API S8_String OS_GetAbsolutePath(MA_Arena *arena, S8_String relative) {
|
||||
S8_String s = {0};
|
||||
return s;
|
||||
IO_Assert(relative.str[relative.len] == 0);
|
||||
|
||||
char *buffer = (char *)MA_PushSizeNonZeroed(arena, PATH_MAX);
|
||||
realpath((char *)relative.str, buffer);
|
||||
S8_String result = S8_MakeFromChar(buffer);
|
||||
return result;
|
||||
}
|
||||
OS_API bool OS_FileExists(S8_String path) { return false; }
|
||||
OS_API bool OS_IsDir(S8_String path) { return false; }
|
||||
OS_API bool OS_IsFile(S8_String path) { return false; }
|
||||
OS_API double OS_GetTime(void) { return 0.0; }
|
||||
|
||||
OS_API bool OS_FileExists(S8_String path) {
|
||||
IO_Assert(path.str[path.len] == 0);
|
||||
|
||||
bool result = false;
|
||||
if (access((char *)path.str, F_OK) == 0) {
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API bool OS_IsDir(S8_String path) {
|
||||
IO_Assert(path.str[path.len] == 0);
|
||||
struct stat s;
|
||||
if (stat(path.str, &s) != 0)
|
||||
return false;
|
||||
bool result = S_ISDIR(s.st_mode);
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API bool OS_IsFile(S8_String path) {
|
||||
IO_Assert(path.str[path.len] == 0);
|
||||
struct stat s;
|
||||
if (stat(path.str, &s) != 0)
|
||||
return false;
|
||||
bool result = S_ISREG(s.st_mode);
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API double OS_GetTime(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
uint64_t timeu64 = (((uint64_t)ts.tv_sec) * 1000000ull) + ((uint64_t)ts.tv_nsec) / 1000ull;
|
||||
double timef = (double)timeu64;
|
||||
double result = timef / 1000000.0; // Microseconds to seconds
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API S8_List OS_ListDir(MA_Arena *arena, S8_String path, unsigned flags) {
|
||||
S8_List s = {0};
|
||||
return s;
|
||||
IO_Assert((flags & OS_RECURSIVE) == 0);
|
||||
IO_Assert(path.str[path.len] == 0);
|
||||
|
||||
S8_List result = {0};
|
||||
struct dirent *dir = 0;
|
||||
DIR *d = opendir((char *)path.str);
|
||||
if (d) {
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if (dir->d_name[0] == '.') {
|
||||
if (dir->d_name[1] == '.') {
|
||||
if (dir->d_name[2] == 0)
|
||||
continue;
|
||||
}
|
||||
OS_API OS_Result OS_MakeDir(S8_String path) { return OS_FAILURE; }
|
||||
OS_API OS_Result OS_CopyFile(S8_String from, S8_String to, bool overwrite) { return OS_FAILURE; }
|
||||
OS_API OS_Result OS_DeleteFile(S8_String path) { return OS_FAILURE; }
|
||||
OS_API OS_Result OS_DeleteDir(S8_String path, unsigned flags) { return OS_FAILURE; }
|
||||
OS_API OS_Result OS_AppendFile(S8_String path, S8_String string) { return OS_FAILURE; }
|
||||
OS_API OS_Result OS_WriteFile(S8_String path, S8_String string) { return OS_FAILURE; }
|
||||
OS_API S8_String OS_ReadFile(MA_Arena *arena, S8_String path) {
|
||||
S8_String s = {0};
|
||||
return s;
|
||||
|
||||
if (dir->d_name[1] == 0)
|
||||
continue;
|
||||
}
|
||||
OS_API int OS_SystemF(const char *string, ...) { return 0; }
|
||||
OS_API int64_t OS_GetFileModTime(S8_String file) { return 0; }
|
||||
|
||||
S8_String n = S8_Format(arena, "%Q/%s", path, dir->d_name);
|
||||
if ((flags & OS_RELATIVE_PATHS) == 0) {
|
||||
n = OS_GetAbsolutePath(arena, n);
|
||||
}
|
||||
if (dir->d_type == DT_DIR) {
|
||||
n = S8_Format(arena, "%Q/", n);
|
||||
}
|
||||
|
||||
S8_AddNode(arena, &result, n);
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_MakeDir(S8_String path) {
|
||||
IO_Assert(path.str[path.len]);
|
||||
int error = mkdir(path.str, 0755);
|
||||
return error == 0 ? OS_SUCCESS : OS_FAILURE;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_CopyFile(S8_String from, S8_String to, bool overwrite) {
|
||||
const char *ow = overwrite ? "-n" : "";
|
||||
int result = OS_SystemF("cp %s %Q %Q", ow, from, to);
|
||||
return result == 0 ? OS_SUCCESS : OS_FAILURE;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_DeleteFile(S8_String path) {
|
||||
int result = OS_SystemF("rm %Q");
|
||||
return result == 0 ? OS_SUCCESS : OS_FAILURE;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_DeleteDir(S8_String path, unsigned flags) {
|
||||
IO_Assert(flags & OS_RECURSIVE);
|
||||
int result = OS_SystemF("rm -r %Q");
|
||||
return result == 0 ? OS_SUCCESS : OS_FAILURE;
|
||||
}
|
||||
|
||||
OS_API int64_t OS_GetFileModTime(S8_String file) {
|
||||
IO_Assert(file.str[file.len] == 0);
|
||||
|
||||
struct stat attrib = {};
|
||||
stat(file.str, &attrib);
|
||||
struct timespec ts = attrib.st_mtim;
|
||||
int64_t result = (((int64_t)ts.tv_sec) * 1000000ll) + ((int64_t)ts.tv_nsec) / 1000ll;
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API OS_Date OS_GetDate(void) {
|
||||
OS_Date s = {0};
|
||||
return s;
|
||||
}
|
||||
|
||||
OS_API OS_Result OS_AppendFile(S8_String path, S8_String string) {
|
||||
IO_Assert(path.str[path.len] == 0);
|
||||
|
||||
OS_Result result = OS_FAILURE;
|
||||
FILE *f = fopen((const char *)path.str, "a");
|
||||
if (f) {
|
||||
result = OS_SUCCESS;
|
||||
|
||||
size_t written = fwrite(string.str, 1, string.len, f);
|
||||
if (written < string.len) {
|
||||
result = OS_FAILURE;
|
||||
}
|
||||
|
||||
int error = fclose(f);
|
||||
if (error != 0) {
|
||||
result = OS_FAILURE;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
OS_API S8_String OS_ReadFile(MA_Arena *arena, S8_String path) {
|
||||
IO_Assert(path.str[path.len] == 0);
|
||||
|
||||
S8_String result = {};
|
||||
FILE *f = fopen(path.str, "rb");
|
||||
if (f) {
|
||||
fseek(f, 0, SEEK_END);
|
||||
result.len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
result.str = (char *)MA_PushSizeNonZeroed(arena, result.len + 1);
|
||||
fread(result.str, result.len, 1, f);
|
||||
result.str[result.len] = 0;
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#if 0
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
OS_API OS_Result OS_WriteFile(S8_String path, S8_String string) {
|
||||
IO_Assert(path.str[path.len] == 0);
|
||||
|
||||
OS_Result result = OS_FAILURE;
|
||||
int fd = open(path.str, O_CREAT | O_WRONLY, 0644);
|
||||
if (fd != -1) {
|
||||
result = OS_SUCCESS;
|
||||
|
||||
ssize_t written = write(fd, string.str, string.len);
|
||||
if (written != string.len) result = OS_FAILURE;
|
||||
|
||||
int err = close(fd);
|
||||
if (err != 0) result = OS_FAILURE;
|
||||
}
|
||||
return OS_SUCCESS;
|
||||
}
|
||||
#else
|
||||
OS_API OS_Result OS_WriteFile(S8_String path, S8_String string) {
|
||||
IO_Assert(path.str[path.len] == 0);
|
||||
|
||||
OS_Result result = OS_FAILURE;
|
||||
FILE *f = fopen((const char *)path.str, "w");
|
||||
if (f) {
|
||||
result = OS_SUCCESS;
|
||||
|
||||
size_t written = fwrite(string.str, 1, string.len, f);
|
||||
if (written < string.len) {
|
||||
result = OS_FAILURE;
|
||||
}
|
||||
|
||||
int error = fclose(f);
|
||||
if (error != 0) {
|
||||
result = OS_FAILURE;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
OS_API int OS_SystemF(const char *string, ...) {
|
||||
MA_Checkpoint scratch = MA_GetScratch();
|
||||
S8_FORMAT(scratch.arena, string, result);
|
||||
IO_Printf("Executing: %s\n", result.str);
|
||||
fflush(stdout);
|
||||
int error_code = system(result.str);
|
||||
MA_ReleaseScratch(scratch);
|
||||
return error_code;
|
||||
}
|
||||
|
||||
OS_API S8_String UTF_CreateStringFromWidechar(MA_Arena *arena, wchar_t *wstr, int64_t wsize) {
|
||||
int64_t buffer_size = (wsize + 1) * 2;
|
||||
char *buffer = (char *)MA_PushSizeNonZeroed(arena, buffer_size);
|
||||
|
||||
@@ -43,7 +43,7 @@ OS_API bool OS_FileExists(S8_String path);
|
||||
OS_API bool OS_IsDir(S8_String path);
|
||||
OS_API bool OS_IsFile(S8_String path);
|
||||
OS_API double OS_GetTime(void);
|
||||
OS_API S8_List OS_ListDir(MA_Arena *arena, S8_String path, unsigned flags);
|
||||
OS_API S8_List OS_ListDir(MA_Arena *arena, S8_String path, unsigned flags); // @todo: this is poor API, we want absolute, relative, bool dir
|
||||
OS_API OS_Result OS_MakeDir(S8_String path);
|
||||
OS_API OS_Result OS_CopyFile(S8_String from, S8_String to, bool overwrite);
|
||||
OS_API OS_Result OS_DeleteFile(S8_String path);
|
||||
|
||||
@@ -596,7 +596,7 @@ STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback,
|
||||
case 'Q':
|
||||
str = va_arg(va, struct STB_STRING);
|
||||
if (str.str == 0 && str.len != 0) {
|
||||
str.str = "null";
|
||||
str.str = (char *)"null";
|
||||
str.len = 4;
|
||||
}
|
||||
pr = (int)str.len;
|
||||
|
||||
1
test/data/append_file
Normal file
1
test/data/append_file
Normal file
@@ -0,0 +1 @@
|
||||
WRITE OK APPEND OK
|
||||
1
test/data/read_file
Normal file
1
test/data/read_file
Normal file
@@ -0,0 +1 @@
|
||||
OK
|
||||
1
test/data/write_file
Normal file
1
test/data/write_file
Normal file
@@ -0,0 +1 @@
|
||||
WRITE2 OK
|
||||
105
test/test_filesystem.c
Normal file
105
test/test_filesystem.c
Normal file
@@ -0,0 +1,105 @@
|
||||
#include "../core.c"
|
||||
|
||||
int main() {
|
||||
MA_Arena arena = {};
|
||||
S8_String read_file_path = S8_Lit("../test/data/read_file");
|
||||
|
||||
// Read file test
|
||||
{
|
||||
S8_String file = OS_ReadFile(&arena, read_file_path);
|
||||
IO_Assert(S8_AreEqual(file, S8_Lit("OK"), 0));
|
||||
}
|
||||
|
||||
// Write file test
|
||||
{
|
||||
S8_String path = S8_Lit("../test/data/write_file");
|
||||
{
|
||||
S8_String data = S8_Lit("WRITE1 OK");
|
||||
OS_Result result = OS_WriteFile(path, data);
|
||||
IO_Assert(result == OS_SUCCESS);
|
||||
|
||||
S8_String read = OS_ReadFile(&arena, path);
|
||||
IO_Assert(S8_AreEqual(read, data, 0));
|
||||
}
|
||||
S8_String data = S8_Lit("WRITE2 OK");
|
||||
|
||||
OS_Result result = OS_WriteFile(path, data);
|
||||
IO_Assert(result == OS_SUCCESS);
|
||||
|
||||
S8_String read = OS_ReadFile(&arena, path);
|
||||
IO_Assert(S8_AreEqual(read, data, 0));
|
||||
}
|
||||
|
||||
// Append file test
|
||||
{
|
||||
S8_String path = S8_Lit("../test/data/append_file");
|
||||
{
|
||||
S8_String data = S8_Lit("WRITE OK");
|
||||
OS_Result result = OS_WriteFile(path, data);
|
||||
IO_Assert(result == OS_SUCCESS);
|
||||
}
|
||||
S8_String data = S8_Lit(" APPEND OK");
|
||||
OS_Result result = OS_AppendFile(path, data);
|
||||
IO_Assert(result == OS_SUCCESS);
|
||||
|
||||
S8_String read = OS_ReadFile(&arena, path);
|
||||
IO_Assert(S8_AreEqual(read, S8_Lit("WRITE OK APPEND OK"), 0));
|
||||
}
|
||||
|
||||
IO_Assert(OS_FileExists(read_file_path));
|
||||
IO_Assert(!OS_FileExists(S8_Lit("121ffsadasd.random")));
|
||||
|
||||
// Fetching dirs
|
||||
{
|
||||
S8_String exe_path = OS_GetExePath(&arena);
|
||||
S8_String dir_path = OS_GetExeDir(&arena);
|
||||
S8_String work_path = OS_GetWorkingDir(&arena);
|
||||
S8_String abs_path = OS_GetAbsolutePath(&arena, read_file_path);
|
||||
// IO_Printf("Exe path = %Q\n", exe_path);
|
||||
// IO_Printf("Dir path = %Q\n", dir_path);
|
||||
// IO_Printf("Working path = %Q\n", work_path);
|
||||
// IO_Printf("Abs path = %Q\n", abs_path);
|
||||
|
||||
IO_Assert(OS_IsDir(dir_path));
|
||||
IO_Assert(!OS_IsFile(dir_path));
|
||||
|
||||
IO_Assert(OS_IsFile(exe_path));
|
||||
IO_Assert(!OS_IsDir(exe_path));
|
||||
|
||||
IO_Assert(OS_IsAbsolute(exe_path));
|
||||
IO_Assert(OS_IsAbsolute(dir_path));
|
||||
IO_Assert(OS_IsAbsolute(work_path));
|
||||
IO_Assert(OS_IsAbsolute(abs_path));
|
||||
|
||||
IO_Assert(S8_Find(exe_path, S8_Lit("/test_filesystem"), 0, 0));
|
||||
IO_Assert(S8_Find(exe_path, S8_Lit("/build"), 0, 0));
|
||||
IO_Assert(S8_Find(dir_path, S8_Lit("/build"), 0, 0));
|
||||
IO_Assert(S8_Find(work_path, S8_Lit("/build"), 0, 0));
|
||||
IO_Assert(S8_Find(abs_path, S8_Lit("/test/data"), 0, 0));
|
||||
IO_Assert(!S8_Find(abs_path, S8_Lit("../"), 0, 0));
|
||||
}
|
||||
|
||||
// List dir test
|
||||
{
|
||||
S8_List list = OS_ListDir(&arena, S8_Lit("../test"), 0);
|
||||
IO_Assert(list.node_count > 4);
|
||||
int dir_count = 0;
|
||||
S8_For(it, list) {
|
||||
if (OS_IsDir(it->string)) {
|
||||
IO_Assert(it->string.str[it->string.len - 1] == '/');
|
||||
dir_count += 1;
|
||||
}
|
||||
IO_Assert(S8_Find(it->string, S8_Lit("/test"), 0, 0));
|
||||
IO_Assert(!S8_Find(it->string, S8_Lit("../test"), 0, 0));
|
||||
}
|
||||
IO_Assert(dir_count > 0);
|
||||
|
||||
// relative
|
||||
{
|
||||
S8_List list = OS_ListDir(&arena, S8_Lit("../test"), OS_RELATIVE_PATHS);
|
||||
S8_For(it, list) {
|
||||
IO_Assert(S8_Find(it->string, S8_Lit("../test"), 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user