Merge branch 'dev' into 'master'
Sprint - Unit Tests sind cool See merge request link/projekte/ws19/vkvm-new/shared-memory!2
This commit is contained in:
commit
27fa718aeb
32
.ci/clang-tidy.sh
Executable file
32
.ci/clang-tidy.sh
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "Doing clang-tidy..."
|
||||||
|
bool=false
|
||||||
|
# explicitly set IFS to contain only a line feed
|
||||||
|
IFS='
|
||||||
|
'
|
||||||
|
filelist="$(find . -not \( -path './*build*' -prune \) -not \( -path './include' -prune \) -not \( -path './lib' -prune \) -type f ! -name "$(printf "*\n*")")"
|
||||||
|
for file in $filelist; do
|
||||||
|
if echo "$file" | grep -q -E ".*(\.cpp|\.h|\.hpp)$" ; then
|
||||||
|
#Extra check missing dependencies due to clang-tidy doesn't toggle exit code.
|
||||||
|
clang_tidy_lib_check="$(clang-tidy -warnings-as-errors='*' -header-filter='.*,-cpptoml.hpp' "$file" -- -I. -std=c++14 2>&1)"
|
||||||
|
for tidy_line in $clang_tidy_lib_check; do
|
||||||
|
echo "$tidy_line" | grep -q -v -E "^Error while processing*"
|
||||||
|
if [ $? -eq 1 ]; then
|
||||||
|
bool=true
|
||||||
|
fi
|
||||||
|
echo "$tidy_line" | grep -q -v -E ".* error: .*"
|
||||||
|
if [ $? -eq 1 ]; then
|
||||||
|
bool=true
|
||||||
|
fi
|
||||||
|
echo "$tidy_line"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if $bool; then
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "No clang-tidy errors found."
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
2
.clang-tidy
Normal file
2
.clang-tidy
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Checks: '*,-llvm-header-guard,-fuchsia*,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-pro-bounds-constant-array-index,-bugprone-narrowing-conversions,-cppcoreguidelines-narrowing-conversions,-cppcoreguidelines-pro-type-const-cast,-cppcoreguidelines-pro-type-reinterpret-cast,-hicpp-signed-bitwise,-bugprone-exception-escape,-cppcoreguidelines-pro-type-member-init,-cppcoreguidelines-pro-type-cstyle-cast,-hicpp-member-init,-google-readability-namespace-comments,-llvm-namespace-comment,-cppcoreguidelines-pro-type-vararg,-hicpp-vararg,-modernize-use-trailing-return-type,-readability-magic-numbers,-cppcoreguidelines-avoid-magic-numbers'
|
||||||
|
WarningsAsErrors: 'true'
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -10,6 +10,5 @@ CmakeCache.txt
|
|||||||
|
|
||||||
|
|
||||||
bin/
|
bin/
|
||||||
lib/
|
|
||||||
include/
|
include/
|
||||||
build/
|
build/
|
77
.gitlab-ci.yml
Normal file
77
.gitlab-ci.yml
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
|
||||||
|
image: samueldebruyn/debian-git:latest
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- style
|
||||||
|
- test
|
||||||
|
- build
|
||||||
|
|
||||||
|
|
||||||
|
clang_tidy:
|
||||||
|
image: joethei/clang_tidy
|
||||||
|
stage: style
|
||||||
|
tags:
|
||||||
|
- docker-ci
|
||||||
|
script:
|
||||||
|
- mkdir current
|
||||||
|
- ls -d .[!.]* | grep -v current | xargs mv -t current
|
||||||
|
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.repo.digitech.hs-emden-leer.de/link/projekte/ws19/vkvm-new/library.git
|
||||||
|
- mkdir library/build
|
||||||
|
- cd library/build
|
||||||
|
- cmake ..
|
||||||
|
- make
|
||||||
|
- cd ../../current/.ci
|
||||||
|
- sh clang-tidy.sh
|
||||||
|
|
||||||
|
|
||||||
|
make_test:
|
||||||
|
stage: test
|
||||||
|
tags:
|
||||||
|
- docker-ci
|
||||||
|
script:
|
||||||
|
- apt-get update
|
||||||
|
- apt-get install -y clang make cmake clang-tidy
|
||||||
|
- mkdir current
|
||||||
|
- ls | grep -v current | xargs mv -t current
|
||||||
|
- git clone https://github.com/catchorg/Catch2.git
|
||||||
|
- cd Catch2
|
||||||
|
- cmake -Bbuild -H. -DBUILD_TESTING=OFF
|
||||||
|
- cmake --build build/ --target install
|
||||||
|
- cd ..
|
||||||
|
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.repo.digitech.hs-emden-leer.de/link/projekte/ws19/vkvm-new/library.git
|
||||||
|
- mkdir library/build
|
||||||
|
- cd library/build
|
||||||
|
- cmake ..
|
||||||
|
- make
|
||||||
|
- cd ../../current
|
||||||
|
- mkdir build
|
||||||
|
- cd build
|
||||||
|
- cmake ..
|
||||||
|
- make
|
||||||
|
- make test
|
||||||
|
|
||||||
|
cmake_build:
|
||||||
|
stage: build
|
||||||
|
tags:
|
||||||
|
- docker-ci
|
||||||
|
script:
|
||||||
|
- apt-get update
|
||||||
|
- apt-get install -y clang make cmake clang-tidy
|
||||||
|
- mkdir current
|
||||||
|
- ls | grep -v current | xargs mv -t current
|
||||||
|
- git clone https://github.com/catchorg/Catch2.git
|
||||||
|
- cd Catch2
|
||||||
|
- cmake -Bbuild -H. -DBUILD_TESTING=OFF
|
||||||
|
- cmake --build build/ --target install
|
||||||
|
- cd ..
|
||||||
|
- git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.repo.digitech.hs-emden-leer.de/link/projekte/ws19/vkvm-new/library.git
|
||||||
|
- mkdir library/build
|
||||||
|
- cd library/build
|
||||||
|
- cmake ..
|
||||||
|
- make
|
||||||
|
- cd ../../current
|
||||||
|
- mkdir build
|
||||||
|
- cd build
|
||||||
|
- cmake ..
|
||||||
|
- make
|
@ -8,6 +8,12 @@ if ("${CMAKE_BINARY_DIR}" STREQUAL "${CMAKE_SOURCE_DIR}")
|
|||||||
endif()
|
endif()
|
||||||
set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "bin" "doc" "CMakeFiles" "lib" "include")
|
set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "bin" "doc" "CMakeFiles" "lib" "include")
|
||||||
|
|
||||||
|
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||||
|
#set(CMAKE_CXX_COMPILER "clang++")
|
||||||
|
|
||||||
|
set(CMAKE_C_COMPILER "clang")
|
||||||
|
set(CMAKE_CXX_COMPILER "clang++")
|
||||||
|
|
||||||
project(Shared_Memory)
|
project(Shared_Memory)
|
||||||
set(CMAKE_CXX_STANDARD 14)
|
set(CMAKE_CXX_STANDARD 14)
|
||||||
|
|
||||||
@ -16,7 +22,7 @@ set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-checks=*")
|
|||||||
set(CMAKE_CXX_CLANG_TIDY clang-tidy;-header-filter=.;-checks=*;)
|
set(CMAKE_CXX_CLANG_TIDY clang-tidy;-header-filter=.;-checks=*;)
|
||||||
|
|
||||||
file(GLOB_RECURSE SOURCES src/*.cpp)
|
file(GLOB_RECURSE SOURCES src/*.cpp)
|
||||||
file(GLOB_RECURSE HEADERS src/*.h)
|
file(GLOB_RECURSE HEADERS src/*.hpp)
|
||||||
file(GLOB_RECURSE TESTS test/*.cpp)
|
file(GLOB_RECURSE TESTS test/*.cpp)
|
||||||
|
|
||||||
set(LIB_PATH "${CMAKE_SOURCE_DIR}/../library")
|
set(LIB_PATH "${CMAKE_SOURCE_DIR}/../library")
|
||||||
@ -24,6 +30,7 @@ set(LIB_PATH "${CMAKE_SOURCE_DIR}/../library")
|
|||||||
include_directories(${LIB_PATH}/include)
|
include_directories(${LIB_PATH}/include)
|
||||||
add_executable(SharedMemory ${SOURCES} ${HEADERS} main/main.cpp)
|
add_executable(SharedMemory ${SOURCES} ${HEADERS} main/main.cpp)
|
||||||
|
|
||||||
|
|
||||||
target_link_libraries(SharedMemory ${LIB_PATH}/lib/liblibrary.a)
|
target_link_libraries(SharedMemory ${LIB_PATH}/lib/liblibrary.a)
|
||||||
|
|
||||||
|
|
||||||
@ -31,7 +38,11 @@ enable_testing()
|
|||||||
find_package(Catch2 REQUIRED)
|
find_package(Catch2 REQUIRED)
|
||||||
add_executable(UnitTests ${SOURCES} ${HEADERS} ${TESTS})
|
add_executable(UnitTests ${SOURCES} ${HEADERS} ${TESTS})
|
||||||
target_link_libraries(UnitTests Catch2::Catch2)
|
target_link_libraries(UnitTests Catch2::Catch2)
|
||||||
|
target_link_libraries(UnitTests ${LIB_PATH}/lib/liblibrary.a)
|
||||||
|
|
||||||
include(CTest)
|
include(CTest)
|
||||||
include(Catch)
|
include(Catch)
|
||||||
catch_discover_tests(UnitTests)
|
catch_discover_tests(UnitTests)
|
||||||
|
|
||||||
|
#toml
|
||||||
|
include_directories(lib/toml)
|
||||||
|
17
README.md
17
README.md
@ -1,5 +1,15 @@
|
|||||||
# vKVM Shared Memory
|
# vKVM Shared Memory
|
||||||
|
|
||||||
|
Shared memory is a kernel-managed memory area that can be read and written by the Library.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Manage the shared memory
|
||||||
|
- Create new key for the shared memory.
|
||||||
|
- Ask the user if he want to change the key of shared memory.
|
||||||
|
- Create shared memory and make it available to use it
|
||||||
|
- Delete the shared memory.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Use the installation script provided in the Scripts repository
|
Use the installation script provided in the Scripts repository
|
||||||
@ -7,3 +17,10 @@ to install the full package.
|
|||||||
|
|
||||||
Installing a single component is currently not supported.
|
Installing a single component is currently not supported.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake ..
|
||||||
|
make
|
||||||
|
|
||||||
|
3668
lib/toml/cpptoml.h
Normal file
3668
lib/toml/cpptoml.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,54 @@
|
|||||||
#include "../src/demo.h"
|
#include "../src/SharedMemory.h"
|
||||||
|
#include "vkvm.hpp"
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sys/shm.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
return test();
|
vkvm::initialize(0);
|
||||||
|
initSharedMemory();
|
||||||
|
vkvm::setDefaultValues();
|
||||||
|
std::atexit(deleteSharedMemory);
|
||||||
|
std::string eingabe;
|
||||||
|
getConfig();
|
||||||
|
while(eingabe != "exit") {
|
||||||
|
std::cout << "cmd# ";
|
||||||
|
std::cin >> eingabe;
|
||||||
|
//if verschieden information ausgaben
|
||||||
|
if(eingabe == "info")
|
||||||
|
{
|
||||||
|
std::cout << "OUTPUT ------------------------ OUTPUT\n" << std::endl;
|
||||||
|
std::cout << "memory ID: " << shmget(vkvm::impl.sharedMemoryKey, 0, 0) << std::endl;
|
||||||
|
std::cout << "Max memory Size: " << Max_Memory_Size << std::endl;
|
||||||
|
std::cout << "OUTPUT ------------------------ OUTPUT\n" << std::endl;
|
||||||
|
//Memory that is used now
|
||||||
|
//outprint of all memory
|
||||||
|
} else if(eingabe == "write") {
|
||||||
|
std::cout << "text: ";
|
||||||
|
std::cin >> eingabe;
|
||||||
|
vkvm::setText(eingabe);
|
||||||
|
//write a bitMap for example 2 (width) * 2 (high) * 3 (color) in Schared Memory
|
||||||
|
/*
|
||||||
|
char bitMap[12] = {0};
|
||||||
|
for(int i = 0; i < sizeof(bitMap); i++)
|
||||||
|
bitMap[i] = 'y';
|
||||||
|
writeSharedMemory(bitMap, sizeof(bitMap),1);
|
||||||
|
*/
|
||||||
|
} else if(eingabe == "read") {
|
||||||
|
auto str = vkvm::getText();
|
||||||
|
std::cout << str << std::endl;
|
||||||
|
/*
|
||||||
|
char content[12] = {0};
|
||||||
|
std::cout << "key "<< impl.sharedMemoryKey << std::endl;
|
||||||
|
getSharedMemory(content, sizeof(content), 1);
|
||||||
|
|
||||||
|
std::cout << content << std::endl;
|
||||||
|
*/
|
||||||
|
} else if (eingabe != "exit"){
|
||||||
|
std::cout << eingabe << " <- is not a known command" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
15
res/config.toml
Normal file
15
res/config.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#defaultKey = 12345
|
||||||
|
#newKey = 0
|
||||||
|
layoutVersion = 0
|
||||||
|
width = 800
|
||||||
|
height = 600
|
||||||
|
mode = 4
|
||||||
|
redrawInterval = 500
|
||||||
|
timerInterruptInterval = 1000
|
||||||
|
backgroundColor = 0
|
||||||
|
foregroundColor = 0xffffff
|
||||||
|
charactersPerRow = 60
|
||||||
|
charactersPerColumn = 20
|
||||||
|
font = 4
|
||||||
|
mousePositionX = 0
|
||||||
|
mousePositionY = 0
|
113
src/SharedMemory.cpp
Normal file
113
src/SharedMemory.cpp
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#include "SharedMemory.h"
|
||||||
|
#include "../lib/toml/cpptoml.h"
|
||||||
|
#include "vkvm.hpp"
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
key_t changedKey() {
|
||||||
|
std::ofstream keyFile;
|
||||||
|
keyFile.open("/tmp/vkvmKey.log", std::ofstream::out | std::ofstream::trunc);
|
||||||
|
keyFile.clear();
|
||||||
|
int newKey;
|
||||||
|
std::cout << "Type in a new Key for the Shared Memory Segment" << std::endl;
|
||||||
|
std::cout << "new Key#";
|
||||||
|
std::cin >> newKey;
|
||||||
|
std::cout << "New Key is -> " << newKey << std::endl;
|
||||||
|
keyFile << newKey;
|
||||||
|
keyFile.close();
|
||||||
|
|
||||||
|
return key_t(newKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getAnswerFromUser() {
|
||||||
|
std::cout << "answer# ";
|
||||||
|
std::string answer;
|
||||||
|
std::cin >> answer;
|
||||||
|
if (answer == "y" || answer == "n" || answer == "override") {
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
std::cout << "wrong input: pls answer with (y) for yes, (n) for no, or (override)\n";
|
||||||
|
getAnswerFromUser();
|
||||||
|
return "-0";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void initSharedMemory() {
|
||||||
|
if (vkvm::impl.sharedMemoryKey != key_t(0)) {
|
||||||
|
if (shmget(vkvm::impl.sharedMemoryKey, Max_Memory_Size, IPC_CREAT | IPC_EXCL | 0666) < 0) {
|
||||||
|
std::cout << "Shared Memory with Key: " << vkvm::impl.sharedMemoryKey << " is already in use\n"
|
||||||
|
<< "You can change the Key( type -> y or n ), or you can override the shared memory Segment(type -> override)\n";
|
||||||
|
std::string answer = getAnswerFromUser();
|
||||||
|
if (answer == "y") {
|
||||||
|
vkvm::impl.sharedMemoryKey = changedKey();
|
||||||
|
std::cout << "new Try with Key: " << vkvm::impl.sharedMemoryKey << std::endl;
|
||||||
|
initSharedMemory();
|
||||||
|
} else if (answer == "n") {
|
||||||
|
std::cout << "This will end shared memory" << std::endl;
|
||||||
|
exit(0);
|
||||||
|
} else if (answer == "override") {
|
||||||
|
deleteSharedMemory();
|
||||||
|
initSharedMemory();
|
||||||
|
vkvm::reset();
|
||||||
|
getConfig();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::cout << "Shared Memory is (Re-) allocated with Key: " << vkvm::impl.sharedMemoryKey << std::endl;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::cerr << "key -> " << vkvm::impl.sharedMemoryKey << " is not allowed here " << std::endl;
|
||||||
|
vkvm::impl.sharedMemoryKey = changedKey();
|
||||||
|
initSharedMemory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteSharedMemory() {
|
||||||
|
int id = shmctl(shmget(vkvm::impl.sharedMemoryKey, 0, 0), IPC_RMID, 0);
|
||||||
|
if (id < 0) {
|
||||||
|
std::cerr << "Failed to remove shared Memory, maybe not existing: try cmd: ipcs " << std::endl;
|
||||||
|
} else {
|
||||||
|
int x =0;
|
||||||
|
std::cout << "shared Memory deleted" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void getConfig() {
|
||||||
|
auto config = cpptoml::parse_file(configFile);
|
||||||
|
|
||||||
|
int layoutVersion = config->get_as<int>("layoutVersion").value_or(0);
|
||||||
|
int width = config->get_as<int>("width").value_or(0);
|
||||||
|
int height = config->get_as<int>("height").value_or(0);
|
||||||
|
int mode = config->get_as<int>("mode").value_or(0);
|
||||||
|
int timerInterruptInterval = config->get_as<int>("timerInterruptInterval").value_or(0);
|
||||||
|
int redrawInterval = config->get_as<int>("redrawInterval").value_or(0);
|
||||||
|
int backgroundColor = config->get_as<int>("backgroundColor").value_or(0);
|
||||||
|
int foregroundColor = config->get_as<int>("foregroundColor").value_or(0);
|
||||||
|
int charactersPerRow = config->get_as<int>("charactersPerRow").value_or(0);
|
||||||
|
int charactersPerColumn = config->get_as<int>("charactersPerColumn").value_or(0);
|
||||||
|
int font = config->get_as<int>("font").value_or(0);
|
||||||
|
int mousePositionX = config->get_as<int>("mousePositionX").value_or(0);
|
||||||
|
int mousePositionY = config->get_as<int>("mousePositionY").value_or(0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
//convert to hex
|
||||||
|
std::string result;
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex << result;
|
||||||
|
ss >> result;
|
||||||
|
*/
|
||||||
|
|
||||||
|
vkvm::setLayoutVersion((vkvm::LayoutVersion) layoutVersion);
|
||||||
|
vkvm::setWidth(width);
|
||||||
|
vkvm::setHeight(height);
|
||||||
|
vkvm::setMode((vkvm::GraphicMode) mode);
|
||||||
|
vkvm::setTimerInterruptInterval(timerInterruptInterval);
|
||||||
|
vkvm::setRedrawInterval(redrawInterval);
|
||||||
|
vkvm::setBackgroundColor(vkvm::Color(backgroundColor));
|
||||||
|
vkvm::setForegroundColor(vkvm::Color(foregroundColor));
|
||||||
|
vkvm::setCharactersPerRow(charactersPerRow);
|
||||||
|
vkvm::setCharactersPerColumn(charactersPerColumn);
|
||||||
|
vkvm::setFont(static_cast<vkvm::FontType>(font));
|
||||||
|
vkvm::setMousePosition(mousePositionX, mousePositionY);
|
||||||
|
}
|
||||||
|
|
21
src/SharedMemory.h
Normal file
21
src/SharedMemory.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#ifndef SHARED_MEMORY_H
|
||||||
|
#define SHARED_MEMORY_H
|
||||||
|
|
||||||
|
#include "internal.hpp"
|
||||||
|
#include <string>
|
||||||
|
#include <sys/shm.h>
|
||||||
|
|
||||||
|
//ID-Speicherbereich
|
||||||
|
//Größe des Speicherbereichs hier 8MB (8000)
|
||||||
|
//Shared-Memory-Segment erstellen oder öffnen – shmget()
|
||||||
|
constexpr int Max_Memory_Size (8000 * 512);
|
||||||
|
const std::string configFile ("../res/config.toml");
|
||||||
|
void deleteSharedMemory(int s);
|
||||||
|
void deleteSharedMemory();
|
||||||
|
void initSharedMemory();
|
||||||
|
void getConfig();
|
||||||
|
key_t changedKey();
|
||||||
|
std::string getAnswerFromUser();
|
||||||
|
//Config File
|
||||||
|
|
||||||
|
#endif //SHARED_MEMORY_H
|
@ -1,5 +0,0 @@
|
|||||||
#include "demo.h"
|
|
||||||
|
|
||||||
int test() {
|
|
||||||
return 42;
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
#ifndef SHARED_MEMORY_DEMO_H
|
|
||||||
#define SHARED_MEMORY_DEMO_H
|
|
||||||
|
|
||||||
|
|
||||||
int test();
|
|
||||||
|
|
||||||
|
|
||||||
#endif //SHARED_MEMORY_DEMO_H
|
|
1
src/key.log
Normal file
1
src/key.log
Normal file
@ -0,0 +1 @@
|
|||||||
|
12345
|
@ -1,6 +1,7 @@
|
|||||||
#include <catch2/catch.hpp>
|
#include <catch2/catch.hpp>
|
||||||
#include "../src/demo.h"
|
#include "../src/SharedMemory.h"
|
||||||
|
|
||||||
|
TEST_CASE("Demo test"){
|
||||||
|
REQUIRE(42 == 42);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Demo test") {
|
|
||||||
REQUIRE(test() == 42);
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user