mirror of
https://github.com/slsdetectorgroup/slsDetectorPackage.git
synced 2025-04-26 16:20:03 +02:00
working implementation
This commit is contained in:
parent
0803f1bc1f
commit
589124845a
@ -1,43 +1,45 @@
|
|||||||
# SPDX-License-Identifier: LGPL-3.0-or-other
|
# SPDX-License-Identifier: LGPL-3.0-or-other
|
||||||
# Copyright (C) 2021 Contributors to the SLS Detector Package
|
# Copyright (C) 2021 Contributors to the SLS Detector Package
|
||||||
from .detector import Detector
|
from .detector import Detector, freeze
|
||||||
from .utils import element_if_equal
|
from .utils import element_if_equal
|
||||||
from .dacs import DetectorDacs
|
from .dacs import DetectorDacs, NamedDacs
|
||||||
import _slsdet
|
import _slsdet
|
||||||
dacIndex = _slsdet.slsDetectorDefs.dacIndex
|
dacIndex = _slsdet.slsDetectorDefs.dacIndex
|
||||||
from .detector_property import DetectorProperty
|
from .detector_property import DetectorProperty
|
||||||
|
|
||||||
class CtbDacs(DetectorDacs):
|
# class CtbDacs(DetectorDacs):
|
||||||
"""
|
# """
|
||||||
Ctb dacs
|
# Ctb dacs
|
||||||
"""
|
# """
|
||||||
_dacs = [('dac0', dacIndex(0), 0, 4000, 1400),
|
# _dacs = [('dac0', dacIndex(0), 0, 4000, 1400),
|
||||||
('dac1', dacIndex(1), 0, 4000, 1200),
|
# ('dac1', dacIndex(1), 0, 4000, 1200),
|
||||||
('dac2', dacIndex(2), 0, 4000, 900),
|
# ('dac2', dacIndex(2), 0, 4000, 900),
|
||||||
('dac3', dacIndex(3), 0, 4000, 1050),
|
# ('dac3', dacIndex(3), 0, 4000, 1050),
|
||||||
('dac4', dacIndex(4), 0, 4000, 1400),
|
# ('dac4', dacIndex(4), 0, 4000, 1400),
|
||||||
('dac5', dacIndex(5), 0, 4000, 655),
|
# ('dac5', dacIndex(5), 0, 4000, 655),
|
||||||
('dac6', dacIndex(6), 0, 4000, 2000),
|
# ('dac6', dacIndex(6), 0, 4000, 2000),
|
||||||
('dac7', dacIndex(7), 0, 4000, 1400),
|
# ('dac7', dacIndex(7), 0, 4000, 1400),
|
||||||
('dac8', dacIndex(8), 0, 4000, 850),
|
# ('dac8', dacIndex(8), 0, 4000, 850),
|
||||||
('dac9', dacIndex(9), 0, 4000, 2000),
|
# ('dac9', dacIndex(9), 0, 4000, 2000),
|
||||||
('dac10', dacIndex(10), 0, 4000, 2294),
|
# ('dac10', dacIndex(10), 0, 4000, 2294),
|
||||||
('dac11', dacIndex(11), 0, 4000, 983),
|
# ('dac11', dacIndex(11), 0, 4000, 983),
|
||||||
('dac12', dacIndex(12), 0, 4000, 1475),
|
# ('dac12', dacIndex(12), 0, 4000, 1475),
|
||||||
('dac13', dacIndex(13), 0, 4000, 1200),
|
# ('dac13', dacIndex(13), 0, 4000, 1200),
|
||||||
('dac14', dacIndex(14), 0, 4000, 1600),
|
# ('dac14', dacIndex(14), 0, 4000, 1600),
|
||||||
('dac15', dacIndex(15), 0, 4000, 1455),
|
# ('dac15', dacIndex(15), 0, 4000, 1455),
|
||||||
('dac16', dacIndex(16), 0, 4000, 0),
|
# ('dac16', dacIndex(16), 0, 4000, 0),
|
||||||
('dac17', dacIndex(17), 0, 4000, 1000),
|
# ('dac17', dacIndex(17), 0, 4000, 1000),
|
||||||
]
|
# ]
|
||||||
_dacnames = [_d[0] for _d in _dacs]
|
# _dacnames = [_d[0] for _d in _dacs]
|
||||||
|
|
||||||
from .utils import element
|
from .utils import element
|
||||||
|
@freeze
|
||||||
class Ctb(Detector):
|
class Ctb(Detector):
|
||||||
def __init__(self, id = 0):
|
def __init__(self, id = 0):
|
||||||
super().__init__(id)
|
super().__init__(id)
|
||||||
self._frozen = False
|
self._frozen = False
|
||||||
self._dacs = CtbDacs(self)
|
# self._dacs = CtbDacs(self)
|
||||||
|
self._dacs = NamedDacs(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dacs(self):
|
def dacs(self):
|
||||||
|
@ -36,6 +36,79 @@ class Dac(DetectorProperty):
|
|||||||
dacstr = ''.join([f'{item:5d}' for item in self.get()])
|
dacstr = ''.join([f'{item:5d}' for item in self.get()])
|
||||||
return f'{self.__name__:15s}:{dacstr}'
|
return f'{self.__name__:15s}:{dacstr}'
|
||||||
|
|
||||||
|
class NamedDacs:
|
||||||
|
"""
|
||||||
|
New implementation of the detector dacs. Used at the momen for
|
||||||
|
Ctb but should replace the old one for all detectors
|
||||||
|
"""
|
||||||
|
_frozen = False
|
||||||
|
_direct_access = ['_detector', '_current', '_dacnames']
|
||||||
|
def __init__(self, detector):
|
||||||
|
self._detector = detector
|
||||||
|
self._current = 0
|
||||||
|
|
||||||
|
self._dacnames = [n.replace(" ", "") for n in detector.getDacNames()]
|
||||||
|
# # Populate the dacs
|
||||||
|
for i,name in enumerate(self._dacnames):
|
||||||
|
#name, enum, low, high, default, detector
|
||||||
|
setattr(self, name, Dac(name, dacIndex(i), 0, 4000, 1000, detector))
|
||||||
|
|
||||||
|
self._frozen = True
|
||||||
|
|
||||||
|
# def __getattr__(self, name):
|
||||||
|
# return self.__getattribute__('_' + name)
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
if not self._frozen:
|
||||||
|
#durining init we need to be able to set up the class
|
||||||
|
super().__setattr__(name, value)
|
||||||
|
else:
|
||||||
|
#Later we restrict us to manipulate dacs and a few fields
|
||||||
|
if name in self._direct_access:
|
||||||
|
super().__setattr__(name, value)
|
||||||
|
elif name in self._dacnames:
|
||||||
|
return self.__getattribute__(name).__setitem__(slice(None, None, None), value)
|
||||||
|
else:
|
||||||
|
raise AttributeError(f'Dac not found: {name}')
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
if self._current >= len(self._dacnames):
|
||||||
|
self._current = 0
|
||||||
|
raise StopIteration
|
||||||
|
else:
|
||||||
|
self._current += 1
|
||||||
|
return self.__getattribute__(self._dacnames[self._current-1])
|
||||||
|
# return self.__getattr__(self._dacnames[self._current-1])
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
r_str = ['========== DACS =========']
|
||||||
|
r_str += [repr(dac) for dac in self]
|
||||||
|
return '\n'.join(r_str)
|
||||||
|
def get_asarray(self):
|
||||||
|
"""
|
||||||
|
Read the dacs into a numpy array with dimensions [ndacs, nmodules]
|
||||||
|
"""
|
||||||
|
dac_array = np.zeros((len(self._dacnames), len(self._detector)))
|
||||||
|
for i, _d in enumerate(self):
|
||||||
|
dac_array[i,:] = _d[:]
|
||||||
|
return dac_array
|
||||||
|
|
||||||
|
def to_array(self):
|
||||||
|
return self.get_asarray()
|
||||||
|
|
||||||
|
def set_from_array(self, dac_array):
|
||||||
|
"""
|
||||||
|
Set the dacs from an numpy array with dac values. [ndacs, nmodules]
|
||||||
|
"""
|
||||||
|
dac_array = dac_array.astype(np.int)
|
||||||
|
for i, _d in enumerate(self):
|
||||||
|
_d[:] = dac_array[i]
|
||||||
|
|
||||||
|
def from_array(self, dac_array):
|
||||||
|
self.set_from_array(dac_array)
|
||||||
|
|
||||||
class DetectorDacs:
|
class DetectorDacs:
|
||||||
_dacs = []
|
_dacs = []
|
||||||
@ -50,7 +123,7 @@ class DetectorDacs:
|
|||||||
# Index to support iteration
|
# Index to support iteration
|
||||||
self._current = 0
|
self._current = 0
|
||||||
|
|
||||||
# Populate the dacs
|
# Name the attributes?
|
||||||
for _d in self._dacs:
|
for _d in self._dacs:
|
||||||
setattr(self, '_'+_d[0], Dac(*_d, detector))
|
setattr(self, '_'+_d[0], Dac(*_d, detector))
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ set(SOURCES
|
|||||||
src/CmdProxy.cpp
|
src/CmdProxy.cpp
|
||||||
src/CmdParser.cpp
|
src/CmdParser.cpp
|
||||||
src/Pattern.cpp
|
src/Pattern.cpp
|
||||||
|
src/ctb_named_dacs.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(slsDetectorObject OBJECT
|
add_library(slsDetectorObject OBJECT
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "CmdProxy.h"
|
#include "CmdProxy.h"
|
||||||
#include "DetectorImpl.h"
|
#include "DetectorImpl.h"
|
||||||
#include "Module.h"
|
#include "Module.h"
|
||||||
|
#include "ctb_named_dacs.h"
|
||||||
#include "sls/Pattern.h"
|
#include "sls/Pattern.h"
|
||||||
#include "sls/container_utils.h"
|
#include "sls/container_utils.h"
|
||||||
#include "sls/file_utils.h"
|
#include "sls/file_utils.h"
|
||||||
@ -21,6 +22,9 @@
|
|||||||
namespace sls {
|
namespace sls {
|
||||||
|
|
||||||
void freeSharedMemory(int detectorIndex, int moduleIndex) {
|
void freeSharedMemory(int detectorIndex, int moduleIndex) {
|
||||||
|
//
|
||||||
|
remove_ctb_dacnames(detectorIndex);
|
||||||
|
|
||||||
// single module
|
// single module
|
||||||
if (moduleIndex >= 0) {
|
if (moduleIndex >= 0) {
|
||||||
SharedMemory<sharedModule> moduleShm(detectorIndex, moduleIndex);
|
SharedMemory<sharedModule> moduleShm(detectorIndex, moduleIndex);
|
||||||
@ -54,7 +58,10 @@ Detector::Detector(int shm_id)
|
|||||||
Detector::~Detector() = default;
|
Detector::~Detector() = default;
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
void Detector::freeSharedMemory() { pimpl->freeSharedMemory(); }
|
void Detector::freeSharedMemory() {
|
||||||
|
remove_ctb_dacnames(pimpl->getDetectorIndex());
|
||||||
|
pimpl->freeSharedMemory();
|
||||||
|
}
|
||||||
|
|
||||||
void Detector::loadConfig(const std::string &fname) {
|
void Detector::loadConfig(const std::string &fname) {
|
||||||
int shm_id = getShmId();
|
int shm_id = getShmId();
|
||||||
@ -2070,47 +2077,19 @@ void Detector::setLEDEnable(bool enable, Positions pos) {
|
|||||||
void Detector::setDacNames(const std::vector<std::string> names) {
|
void Detector::setDacNames(const std::vector<std::string> names) {
|
||||||
if (getDetectorType().squash() != defs::CHIPTESTBOARD)
|
if (getDetectorType().squash() != defs::CHIPTESTBOARD)
|
||||||
throw RuntimeError("Named dacs only for CTB");
|
throw RuntimeError("Named dacs only for CTB");
|
||||||
if (names.size() != 18)
|
set_ctb_dac_names(names, pimpl->getDetectorIndex());
|
||||||
throw RuntimeError("Need to set all 18 dacs when naming dacs");
|
|
||||||
|
|
||||||
std::ofstream ofs("/dev/shm/slsDetectorPackage_ctbdacnames");
|
|
||||||
if (!ofs)
|
|
||||||
throw RuntimeError("Failed to open dacnames file in shared memory");
|
|
||||||
|
|
||||||
std::string s = sls::ToString(names);
|
|
||||||
ofs.write(&s[0], s.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> Detector::getDacNames() const {
|
std::vector<std::string> Detector::getDacNames() const {
|
||||||
|
std::vector<std::string> names;
|
||||||
auto type = getDetectorType().squash();
|
auto type = getDetectorType().squash();
|
||||||
if (type == defs::CHIPTESTBOARD) {
|
if (type == defs::CHIPTESTBOARD) {
|
||||||
try {
|
names = get_ctb_dac_names(pimpl->getDetectorIndex());
|
||||||
std::ifstream ifs("/dev/shm/slsDetectorPackage_ctbdacnames");
|
|
||||||
if (!ifs)
|
|
||||||
throw RuntimeError("Could not read dacnames form shm");
|
|
||||||
std::string dacnames;
|
|
||||||
ifs.seekg(0, std::ios::end);
|
|
||||||
dacnames.resize(ifs.tellg());
|
|
||||||
ifs.seekg(0, std::ios::beg);
|
|
||||||
ifs.read(&dacnames[0], dacnames.size());
|
|
||||||
|
|
||||||
std::string chars = "[] ";
|
|
||||||
|
|
||||||
dacnames.erase(std::remove_if(dacnames.begin(), dacnames.end(),
|
|
||||||
[&chars](const char &c) {
|
|
||||||
return chars.find(c) !=
|
|
||||||
std::string::npos;
|
|
||||||
}),
|
|
||||||
dacnames.end());
|
|
||||||
auto names = sls::split(dacnames, ',');
|
|
||||||
return names;
|
|
||||||
} catch (...) {
|
|
||||||
}
|
}
|
||||||
}
|
if (names.empty()) {
|
||||||
std::vector<std::string> names;
|
for (const auto &index : getDacList())
|
||||||
for (const auto& index : getDacList())
|
|
||||||
names.push_back(ToString(index));
|
names.push_back(ToString(index));
|
||||||
|
}
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
65
slsDetectorSoftware/src/ctb_named_dacs.cpp
Normal file
65
slsDetectorSoftware/src/ctb_named_dacs.cpp
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
|
||||||
|
#include "SharedMemory.h"
|
||||||
|
#include "ctb_named_dacs.h"
|
||||||
|
#include "sls/string_utils.h"
|
||||||
|
#include "sls/ToString.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace sls {
|
||||||
|
|
||||||
|
std::vector<std::string> get_ctb_dac_names(int det_id) {
|
||||||
|
std::ifstream ifs(ctb_dac_fname(det_id));
|
||||||
|
if (!ifs)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::string dacnames;
|
||||||
|
ifs.seekg(0, std::ios::end);
|
||||||
|
dacnames.resize(ifs.tellg());
|
||||||
|
ifs.seekg(0, std::ios::beg);
|
||||||
|
ifs.read(&dacnames[0], dacnames.size());
|
||||||
|
|
||||||
|
std::string chars = "[] ";
|
||||||
|
|
||||||
|
dacnames.erase(std::remove_if(dacnames.begin(), dacnames.end(),
|
||||||
|
[&chars](const char &c) {
|
||||||
|
return chars.find(c) != std::string::npos;
|
||||||
|
}),
|
||||||
|
dacnames.end());
|
||||||
|
auto names = sls::split(dacnames, ',');
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ctb_dac_fname(int det_id) {
|
||||||
|
std::string sEnvPath;
|
||||||
|
char *envpath = getenv(SHM_ENV_NAME);
|
||||||
|
if (envpath != nullptr) {
|
||||||
|
sEnvPath.assign(envpath);
|
||||||
|
sEnvPath.append("_");
|
||||||
|
}
|
||||||
|
sEnvPath.insert(0, "_");
|
||||||
|
std::stringstream oss;
|
||||||
|
oss << "/dev/shm" << SHM_DETECTOR_PREFIX << det_id << sEnvPath << "ctbdacs";
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_ctb_dac_names(const std::vector<std::string>& names, int det_id){
|
||||||
|
if (names.size() != 18)
|
||||||
|
throw RuntimeError("Need to set all 18 dacs when naming dacs");
|
||||||
|
|
||||||
|
|
||||||
|
std::ofstream ofs(ctb_dac_fname(det_id));
|
||||||
|
if(!ofs)
|
||||||
|
throw RuntimeError("Could not open dacnames file for writing");
|
||||||
|
std::string s = sls::ToString(names);
|
||||||
|
ofs.write(&s[0], s.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void remove_ctb_dacnames(int det_id){
|
||||||
|
unlink(ctb_dac_fname(det_id).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sls
|
13
slsDetectorSoftware/src/ctb_named_dacs.h
Normal file
13
slsDetectorSoftware/src/ctb_named_dacs.h
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
namespace sls{
|
||||||
|
|
||||||
|
std::vector<std::string> get_ctb_dac_names(int det_id);
|
||||||
|
void set_ctb_dac_names(const std::vector<std::string>& names, int det_id);
|
||||||
|
std::string ctb_dac_fname(int det_id);
|
||||||
|
void remove_ctb_dacnames(int det_id);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ target_sources(tests PRIVATE
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/test-CmdParser.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test-CmdParser.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test-Module.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test-Module.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/test-Pattern.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/test-Pattern.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/test_ctb_named_dacs.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(tests PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../src>")
|
target_include_directories(tests PUBLIC "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../src>")
|
@ -590,13 +590,13 @@ TEST_CASE("master", "[.cmd]") {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::ostringstream oss1;
|
std::ostringstream oss1;
|
||||||
proxy.Call("master", {"0"}, 0, PUT, oss3);
|
proxy.Call("master", {"0"}, 0, PUT, oss1);
|
||||||
REQUIRE(oss3.str() == "master 0\n");
|
REQUIRE(oss1.str() == "master 0\n");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
std::ostringstream oss1;
|
std::ostringstream oss1;
|
||||||
proxy.Call("master", {"1"}, 0, PUT, oss3);
|
proxy.Call("master", {"1"}, 0, PUT, oss1);
|
||||||
REQUIRE(oss3.str() == "master 1\n");
|
REQUIRE(oss1.str() == "master 1\n");
|
||||||
}
|
}
|
||||||
REQUIRE_THROWS(proxy.Call("master", {"1"}, -1, PUT));
|
REQUIRE_THROWS(proxy.Call("master", {"1"}, -1, PUT));
|
||||||
// set all to slaves, and then master
|
// set all to slaves, and then master
|
||||||
|
61
slsDetectorSoftware/tests/test_ctb_named_dacs.cpp
Normal file
61
slsDetectorSoftware/tests/test_ctb_named_dacs.cpp
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
#include "catch.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "SharedMemory.h"
|
||||||
|
#include "ctb_named_dacs.h"
|
||||||
|
using namespace sls;
|
||||||
|
#include <fstream>
|
||||||
|
TEST_CASE("Create name for shm dac file") {
|
||||||
|
// if SLSDETNAME is already set we unset it but
|
||||||
|
// save the value
|
||||||
|
std::string old_slsdetname;
|
||||||
|
if (getenv(SHM_ENV_NAME))
|
||||||
|
old_slsdetname = getenv(SHM_ENV_NAME);
|
||||||
|
unsetenv(SHM_ENV_NAME);
|
||||||
|
|
||||||
|
int det_id = 0;
|
||||||
|
REQUIRE(ctb_dac_fname(det_id) == "/dev/shm/slsDetectorPackage_detector_0_ctbdacs");
|
||||||
|
|
||||||
|
setenv(SHM_ENV_NAME, "myprefix", 1);
|
||||||
|
REQUIRE(ctb_dac_fname(det_id) ==
|
||||||
|
"/dev/shm/slsDetectorPackage_detector_0_myprefix_ctbdacs");
|
||||||
|
|
||||||
|
// Clean up after us
|
||||||
|
if (old_slsdetname.empty())
|
||||||
|
unsetenv(SHM_ENV_NAME);
|
||||||
|
else
|
||||||
|
setenv(SHM_ENV_NAME, old_slsdetname.c_str(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Get ctb dac names returns an empty vector when not set"){
|
||||||
|
int large_unlikely_number = 123203;
|
||||||
|
auto vec = get_ctb_dac_names(large_unlikely_number);
|
||||||
|
REQUIRE(vec.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Remove file fails silently when file is not present"){
|
||||||
|
int large_unlikely_number = 123203;
|
||||||
|
REQUIRE_NOTHROW(remove_ctb_dacnames(large_unlikely_number));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Read dacs from file then remove file"){
|
||||||
|
int large_unlikely_number = 998765;
|
||||||
|
std::ofstream out(ctb_dac_fname(large_unlikely_number));
|
||||||
|
std::string names = "[first, second, third]";
|
||||||
|
out.write(&names[0], names.size());
|
||||||
|
out.close();
|
||||||
|
|
||||||
|
auto dacnames = get_ctb_dac_names(large_unlikely_number);
|
||||||
|
REQUIRE(dacnames.size() == 3);
|
||||||
|
REQUIRE(dacnames[0] == "first");
|
||||||
|
REQUIRE(dacnames[1] == "second");
|
||||||
|
REQUIRE(dacnames[2] == "third");
|
||||||
|
|
||||||
|
remove_ctb_dacnames(large_unlikely_number);
|
||||||
|
|
||||||
|
std::ifstream in(ctb_dac_fname(large_unlikely_number));
|
||||||
|
REQUIRE_FALSE(in);
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user