Changes in restart TimeMap and Schedule::iterateSection
This commit changes two aspects of restarts where well and group information is assembled from the restart file: o We require the restart date to be present in the Schedule file as a DATES keyword. o SKIPREST behavior is implemented unconditionally for restarts with restart files, even if the SKIPREST keyword is not present.
This commit is contained in:
parent
1ae7026a4b
commit
42f1278db3
@ -51,12 +51,12 @@ namespace Opm {
|
|||||||
double getTotalTime() const;
|
double getTotalTime() const;
|
||||||
double seconds(size_t timeStep) const;
|
double seconds(size_t timeStep) const;
|
||||||
std::size_t restart_offset() const;
|
std::size_t restart_offset() const;
|
||||||
|
std::time_t restart_time() const;
|
||||||
|
|
||||||
std::time_t operator[] (size_t index) const;
|
std::time_t operator[] (size_t index) const;
|
||||||
/// Return the date and time where a given time step starts.
|
/// Return the date and time where a given time step starts.
|
||||||
std::time_t getStartTime(size_t tStepIdx) const;
|
std::time_t getStartTime(size_t tStepIdx) const;
|
||||||
std::time_t getEndTime() const;
|
std::time_t getEndTime() const;
|
||||||
bool skiprest() const;
|
|
||||||
/// Return the period of time in seconds which passed between the start of the simulation and a given point in time.
|
/// Return the period of time in seconds which passed between the start of the simulation and a given point in time.
|
||||||
double getTimePassedUntil(size_t tLevelIdx) const;
|
double getTimePassedUntil(size_t tLevelIdx) const;
|
||||||
/// Return the length of a given time step in seconds.
|
/// Return the length of a given time step in seconds.
|
||||||
@ -83,7 +83,6 @@ namespace Opm {
|
|||||||
serializer(m_timeList);
|
serializer(m_timeList);
|
||||||
serializer.vector(m_first_timestep_years);
|
serializer.vector(m_first_timestep_years);
|
||||||
serializer.vector(m_first_timestep_months);
|
serializer.vector(m_first_timestep_months);
|
||||||
serializer(m_skiprest);
|
|
||||||
serializer(m_restart_offset);
|
serializer(m_restart_offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,8 +116,8 @@ namespace Opm {
|
|||||||
std::vector<std::time_t> m_timeList;
|
std::vector<std::time_t> m_timeList;
|
||||||
std::vector<StepData> m_first_timestep_years; // A list of the first timestep of every year
|
std::vector<StepData> m_first_timestep_years; // A list of the first timestep of every year
|
||||||
std::vector<StepData> m_first_timestep_months; // A list of the first timestep of every month
|
std::vector<StepData> m_first_timestep_months; // A list of the first timestep of every month
|
||||||
bool m_skiprest = false;
|
|
||||||
std::size_t m_restart_offset = 0;
|
std::size_t m_restart_offset = 0;
|
||||||
|
std::time_t m_restart_time;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& stream, const TimeMap& tm);
|
std::ostream& operator<<(std::ostream& stream, const TimeMap& tm);
|
||||||
|
@ -311,15 +311,60 @@ namespace {
|
|||||||
all.
|
all.
|
||||||
*/
|
*/
|
||||||
std::unordered_set<std::string> skiprest_whitelist = {"VFPPROD", "VFPINJ", "RPTSCHED", "RPTRST", "TUNING", "MESSAGES"};
|
std::unordered_set<std::string> skiprest_whitelist = {"VFPPROD", "VFPINJ", "RPTSCHED", "RPTRST", "TUNING", "MESSAGES"};
|
||||||
|
std::size_t currentStep = 0;
|
||||||
|
const auto restart_offset = this->m_timeMap.restart_offset();
|
||||||
|
|
||||||
std::size_t currentStep;
|
/*
|
||||||
if (this->m_timeMap.skiprest())
|
The behavior of variable restart_skip is more lenient than the
|
||||||
currentStep = 0;
|
SKIPREST keyword. If this is a restarted[1] run the loop iterating
|
||||||
else
|
over keywords will skip the all keywords[2] until DATES keyword with
|
||||||
currentStep = this->m_timeMap.restart_offset();
|
the restart date is encountered - irrespective of whether the SKIPREST
|
||||||
|
keyword is present in the deck or not.
|
||||||
|
|
||||||
|
[1]: opm/flow can restart in a mode where all the keywords from the
|
||||||
|
historical part of the Schedule section is internalized, and only
|
||||||
|
the solution fields are read from the restart file. In this case
|
||||||
|
we will have TimeMap::restart_offset() == 0.
|
||||||
|
|
||||||
|
[2]: With the exception of the keywords in the skiprest_whitelist;
|
||||||
|
these keywords will be assigned to report step 0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
auto restart_skip = restart_offset > 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
if (keywordIdx == section.size())
|
||||||
|
break;
|
||||||
|
|
||||||
const auto& keyword = section.getKeyword(keywordIdx);
|
const auto& keyword = section.getKeyword(keywordIdx);
|
||||||
|
if (keyword.name() == "DATES") {
|
||||||
|
checkIfAllConnectionsIsShut(currentStep);
|
||||||
|
for (const auto& record : keyword) {
|
||||||
|
if (restart_skip) {
|
||||||
|
auto deck_time = TimeMap::timeFromEclipse(record);
|
||||||
|
if (deck_time == this->m_timeMap.restart_time()) {
|
||||||
|
restart_skip = false;
|
||||||
|
currentStep = this->m_timeMap.restart_offset();
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
currentStep += 1;
|
||||||
|
}
|
||||||
|
keywordIdx++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyword.name() == "TSTEP") {
|
||||||
|
checkIfAllConnectionsIsShut(currentStep);
|
||||||
|
currentStep += keyword.getRecord(0).getItem(0).data_size();
|
||||||
|
keywordIdx++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restart_skip && skiprest_whitelist.count(keyword.name()) == 0) {
|
||||||
|
OpmLog::info("Skipping keyword: " + keyword.name() + " while loading SCHEDULE section");
|
||||||
|
keywordIdx++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (keyword.name() == "ACTIONX") {
|
if (keyword.name() == "ACTIONX") {
|
||||||
Action::ActionX action(keyword, this->m_timeMap.getStartTime(currentStep));
|
Action::ActionX action(keyword, this->m_timeMap.getStartTime(currentStep));
|
||||||
while (true) {
|
while (true) {
|
||||||
@ -334,39 +379,32 @@ namespace {
|
|||||||
if (Action::ActionX::valid_keyword(action_keyword.name()))
|
if (Action::ActionX::valid_keyword(action_keyword.name()))
|
||||||
action.addKeyword(action_keyword);
|
action.addKeyword(action_keyword);
|
||||||
else {
|
else {
|
||||||
std::string msg = "The keyword {0} is not supported in a ACTIONX block. file: {1} line: {2}";
|
|
||||||
std::string msg_fmt = "The keyword {keyword} is not supported in the ACTIONX block\n"
|
std::string msg_fmt = "The keyword {keyword} is not supported in the ACTIONX block\n"
|
||||||
"In {file} line {line}.";
|
"In {file} line {line}.";
|
||||||
parseContext.handleError( ParseContext::ACTIONX_ILLEGAL_KEYWORD, msg, action_keyword.location(), errors);
|
parseContext.handleError( ParseContext::ACTIONX_ILLEGAL_KEYWORD, msg_fmt, action_keyword.location(), errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this->addACTIONX(action, currentStep);
|
this->addACTIONX(action, currentStep);
|
||||||
|
keywordIdx++;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (keyword.name() == "DATES") {
|
this->handleKeyword(python,
|
||||||
checkIfAllConnectionsIsShut(currentStep);
|
input_path,
|
||||||
currentStep += keyword.size();
|
currentStep,
|
||||||
}
|
section,
|
||||||
|
keywordIdx,
|
||||||
else if (keyword.name() == "TSTEP") {
|
keyword,
|
||||||
checkIfAllConnectionsIsShut(currentStep);
|
parseContext,
|
||||||
currentStep += keyword.getRecord(0).getItem(0).data_size();
|
errors,
|
||||||
}
|
grid,
|
||||||
|
fp,
|
||||||
else {
|
rftProperties);
|
||||||
if (currentStep >= this->m_timeMap.restart_offset() || skiprest_whitelist.count(keyword.name()))
|
|
||||||
this->handleKeyword(python, input_path, currentStep, section, keywordIdx, keyword, parseContext, errors, grid, fp, rftProperties);
|
|
||||||
else
|
|
||||||
OpmLog::info("Skipping keyword: " + keyword.name() + " while loading SCHEDULE section");
|
|
||||||
}
|
|
||||||
|
|
||||||
keywordIdx++;
|
keywordIdx++;
|
||||||
if (keywordIdx == section.size())
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkIfAllConnectionsIsShut(currentStep);
|
checkIfAllConnectionsIsShut(currentStep);
|
||||||
|
|
||||||
|
|
||||||
for (auto rftPair = rftProperties.begin(); rftPair != rftProperties.end(); ++rftPair) {
|
for (auto rftPair = rftProperties.begin(); rftPair != rftProperties.end(); ++rftPair) {
|
||||||
const DeckKeyword& keyword = *rftPair->first;
|
const DeckKeyword& keyword = *rftPair->first;
|
||||||
std::size_t timeStep = rftPair->second;
|
std::size_t timeStep = rftPair->second;
|
||||||
|
@ -85,41 +85,31 @@ namespace {
|
|||||||
this->m_restart_offset += 1;
|
this->m_restart_offset += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeMap::TimeMap( const Deck& deck, const std::pair<std::time_t, std::size_t>& restart) {
|
|
||||||
bool skiprest = deck.hasKeyword<ParserKeywords::SKIPREST>();
|
|
||||||
{
|
|
||||||
std::time_t start_time;
|
|
||||||
if (deck.hasKeyword("START")) {
|
|
||||||
// Use the 'START' keyword to find out the start date (if the
|
|
||||||
// keyword was specified)
|
|
||||||
const auto& keyword = deck.getKeyword("START");
|
|
||||||
start_time = timeFromEclipse(keyword.getRecord(0));
|
|
||||||
} else {
|
|
||||||
// The default start date is not specified in the Eclipse
|
|
||||||
// reference manual. We hence just assume it is same as for
|
|
||||||
// the START keyword for Eclipse R100, i.e., January 1st,
|
|
||||||
// 1983...
|
|
||||||
start_time = mkdate(1983, 1, 1);
|
|
||||||
}
|
|
||||||
this->init_start(start_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto restart_time = restart.first;
|
TimeMap::TimeMap( const Deck& deck, const std::pair<std::time_t, std::size_t>& restart) {
|
||||||
|
std::time_t start_time;
|
||||||
|
if (deck.hasKeyword("START")) {
|
||||||
|
// Use the 'START' keyword to find out the start date (if the
|
||||||
|
// keyword was specified)
|
||||||
|
const auto& keyword = deck.getKeyword("START");
|
||||||
|
start_time = timeFromEclipse(keyword.getRecord(0));
|
||||||
|
} else {
|
||||||
|
// The default start date is not specified in the Eclipse
|
||||||
|
// reference manual. We hence just assume it is same as for
|
||||||
|
// the START keyword for Eclipse R100, i.e., January 1st,
|
||||||
|
// 1983...
|
||||||
|
start_time = mkdate(1983, 1, 1);
|
||||||
|
}
|
||||||
|
this->init_start(start_time);
|
||||||
|
|
||||||
|
this->m_restart_time = restart.first;
|
||||||
this->m_restart_offset = restart.second;
|
this->m_restart_offset = restart.second;
|
||||||
bool skip = false;
|
|
||||||
|
|
||||||
for (std::size_t it = 1; it < this->m_restart_offset; it++)
|
for (std::size_t it = 1; it < this->m_restart_offset; it++)
|
||||||
this->m_timeList.push_back(invalid_time);
|
this->m_timeList.push_back(invalid_time);
|
||||||
|
|
||||||
if (this->m_restart_offset > 0) {
|
bool skip = (this->m_restart_offset > 0);
|
||||||
if (skiprest)
|
bool restart_found = false;
|
||||||
skip = true;
|
|
||||||
else {
|
|
||||||
this->m_timeList.push_back(restart_time);
|
|
||||||
skip = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for( const auto& keyword : SCHEDULESection(deck)) {
|
for( const auto& keyword : SCHEDULESection(deck)) {
|
||||||
// We're only interested in "TSTEP" and "DATES" keywords,
|
// We're only interested in "TSTEP" and "DATES" keywords,
|
||||||
// so we ignore everything else here...
|
// so we ignore everything else here...
|
||||||
@ -130,11 +120,13 @@ namespace {
|
|||||||
for (size_t recordIndex = 0; recordIndex < keyword.size(); recordIndex++) {
|
for (size_t recordIndex = 0; recordIndex < keyword.size(); recordIndex++) {
|
||||||
const auto &record = keyword.getRecord(recordIndex);
|
const auto &record = keyword.getRecord(recordIndex);
|
||||||
const std::time_t nextTime = TimeMap::timeFromEclipse(record);
|
const std::time_t nextTime = TimeMap::timeFromEclipse(record);
|
||||||
if (nextTime == restart_time)
|
if (nextTime == this->m_restart_time) {
|
||||||
skip = false;
|
skip = false;
|
||||||
|
restart_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!skip)
|
if (!skip)
|
||||||
addTime(nextTime);
|
this->addTime(nextTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@ -143,36 +135,25 @@ namespace {
|
|||||||
if (skip)
|
if (skip)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
addFromTSTEPKeyword(keyword);
|
this->addFromTSTEPKeyword(keyword);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
There is a coupling between the presence of the SKIPREST keyword and
|
It is a hard requirement that the restart date is found as a DATES
|
||||||
the restart argument: The restart argument indicates whether this is
|
keyword, although it is technically possible to create a valid
|
||||||
deck should be parsed as restarted deck. If m_restart_offset == 0 we do
|
restarted case using TSTEP we do not accept that.
|
||||||
not interpret this as restart situation and the presence of SKIPREST
|
|
||||||
is ignored. In the opposite case we verify - post loading - that we
|
|
||||||
have actually located the restart date - otherwise "something is
|
|
||||||
broken".
|
|
||||||
*/
|
*/
|
||||||
if (this->m_restart_offset != 0) {
|
if (this->m_restart_offset != 0 && !restart_found) {
|
||||||
if (skiprest) {
|
TimeStampUTC ts(this->m_restart_time);
|
||||||
const auto iter = std::find(this->m_timeList.begin(), this->m_timeList.end(), restart_time);
|
throw std::invalid_argument("Could not find restart date " + std::to_string(ts.year()) + "-" + std::to_string(ts.month()) + "-" + std::to_string(ts.day()));
|
||||||
if (iter == this->m_timeList.end()) {
|
|
||||||
TimeStampUTC ts(restart_time);
|
|
||||||
throw std::invalid_argument("Could not find restart date " + std::to_string(ts.year()) + "-" + std::to_string(ts.month()) + "-" + std::to_string(ts.day()));
|
|
||||||
}
|
|
||||||
this->m_skiprest = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeMap TimeMap::serializeObject()
|
TimeMap TimeMap::serializeObject()
|
||||||
{
|
{
|
||||||
TimeMap result({123});
|
TimeMap result({123});
|
||||||
result.m_skiprest = true;
|
|
||||||
result.m_restart_offset = 4;
|
result.m_restart_offset = 4;
|
||||||
|
result.m_restart_time = 19867234;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,7 +210,7 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void TimeMap::addTStep(int64_t step) {
|
void TimeMap::addTStep(int64_t step) {
|
||||||
addTime(forward(m_timeList.back(), step));
|
this->addTime(forward(m_timeList.back(), step));
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t TimeMap::size() const {
|
size_t TimeMap::size() const {
|
||||||
@ -279,7 +260,7 @@ namespace {
|
|||||||
|
|
||||||
for (size_t itemIndex = 0; itemIndex < item.data_size(); itemIndex++) {
|
for (size_t itemIndex = 0; itemIndex < item.data_size(); itemIndex++) {
|
||||||
const int64_t seconds = static_cast<int64_t>(item.getSIDouble(itemIndex));
|
const int64_t seconds = static_cast<int64_t>(item.getSIDouble(itemIndex));
|
||||||
addTStep(seconds);
|
this->addTStep(seconds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -444,10 +425,11 @@ namespace {
|
|||||||
return this->m_restart_offset;
|
return this->m_restart_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TimeMap::skiprest() const {
|
std::time_t TimeMap::restart_time() const {
|
||||||
return this->m_skiprest;
|
return this->m_restart_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& stream, const TimeMap& tm) {
|
std::ostream& operator<<(std::ostream& stream, const TimeMap& tm) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "{";
|
ss << "{";
|
||||||
|
@ -449,7 +449,14 @@ TSTEP
|
|||||||
28 31 30 31 30 31 31 30 31 30 31
|
28 31 30 31 30 31 31 30 31 30 31
|
||||||
31 28 31 30 31 30 31 31 30 31 30 31
|
31 28 31 30 31 30 31 31 30 31 30 31
|
||||||
31 28 31 30 31 30 31 31 30 31 30 31
|
31 28 31 30 31 30 31 31 30 31 30 31
|
||||||
31 28 31 30 31 30 31 31 30 31 30 31
|
31 /
|
||||||
|
|
||||||
|
DATES
|
||||||
|
28 'FEB' 2019 /
|
||||||
|
/
|
||||||
|
|
||||||
|
TSTEP
|
||||||
|
31 30 31 30 31 31 30 31 30 31
|
||||||
31 28 31 30 31 30 31 31 30 31 30 31
|
31 28 31 30 31 30 31 31 30 31 30 31
|
||||||
31 28 31 30 31 30 31 31 30 31 30 31
|
31 28 31 30 31 30 31 31 30 31 30 31
|
||||||
31 28 31 30 31 30 31 31 30 31 30 31
|
31 28 31 30 31 30 31 31 30 31 30 31
|
||||||
|
@ -223,7 +223,6 @@ BOOST_AUTO_TEST_CASE(TimeStepsCorrect) {
|
|||||||
|
|
||||||
BOOST_CHECK_EQUAL(tmap.getTimeStepLength(8), 6*24*60*60);
|
BOOST_CHECK_EQUAL(tmap.getTimeStepLength(8), 6*24*60*60);
|
||||||
BOOST_CHECK_EQUAL(tmap.getTimeStepLength(9), 7*24*60*60);
|
BOOST_CHECK_EQUAL(tmap.getTimeStepLength(9), 7*24*60*60);
|
||||||
BOOST_CHECK(!tmap.skiprest());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -663,7 +662,7 @@ SCHEDULE
|
|||||||
--/
|
--/
|
||||||
|
|
||||||
DATES
|
DATES
|
||||||
1 JUL 2005 /
|
1 JAN 2005 /
|
||||||
/
|
/
|
||||||
|
|
||||||
DATES
|
DATES
|
||||||
@ -675,25 +674,10 @@ DATES
|
|||||||
/
|
/
|
||||||
)";
|
)";
|
||||||
|
|
||||||
std::string deck_string3 = R"(
|
|
||||||
START
|
|
||||||
1 JAN 2000 /
|
|
||||||
|
|
||||||
RESTART
|
|
||||||
'CASE' 5 /
|
|
||||||
|
|
||||||
SCHEDULE
|
|
||||||
|
|
||||||
-- This test does not have SKIPREST
|
|
||||||
|
|
||||||
TSTEP
|
|
||||||
1 1 1 /
|
|
||||||
)";
|
|
||||||
|
|
||||||
Opm::Parser parser;
|
Opm::Parser parser;
|
||||||
const auto deck1 = parser.parseString(deck_string1);
|
const auto deck1 = parser.parseString(deck_string1);
|
||||||
const auto deck2 = parser.parseString(deck_string2);
|
const auto deck2 = parser.parseString(deck_string2);
|
||||||
const auto deck3 = parser.parseString(deck_string3);
|
|
||||||
|
|
||||||
// The date 2005-01-02 is not present as a DATES in the deck; invalid input.
|
// The date 2005-01-02 is not present as a DATES in the deck; invalid input.
|
||||||
auto invalid_restart = std::make_pair(Opm::asTimeT(Opm::TimeStampUTC(2005, 1, 2)), 5);
|
auto invalid_restart = std::make_pair(Opm::asTimeT(Opm::TimeStampUTC(2005, 1, 2)), 5);
|
||||||
@ -706,15 +690,9 @@ TSTEP
|
|||||||
auto start = tm1[0];
|
auto start = tm1[0];
|
||||||
BOOST_CHECK_EQUAL(start , Opm::asTimeT(Opm::TimeStampUTC(2000,1,1)));
|
BOOST_CHECK_EQUAL(start , Opm::asTimeT(Opm::TimeStampUTC(2000,1,1)));
|
||||||
BOOST_CHECK_EQUAL(tm1[5] , Opm::asTimeT(Opm::TimeStampUTC(2005,1,1)));
|
BOOST_CHECK_EQUAL(tm1[5] , Opm::asTimeT(Opm::TimeStampUTC(2005,1,1)));
|
||||||
BOOST_CHECK(tm1.skiprest());
|
|
||||||
|
|
||||||
Opm::TimeMap tm2(deck2, valid_restart);
|
Opm::TimeMap tm2(deck2, valid_restart);
|
||||||
BOOST_CHECK_EQUAL(tm2[5], Opm::asTimeT(Opm::TimeStampUTC(2005,1,1)));
|
BOOST_CHECK_EQUAL(tm2[5], Opm::asTimeT(Opm::TimeStampUTC(2005,1,1)));
|
||||||
BOOST_CHECK_EQUAL(tm2[6], Opm::asTimeT(Opm::TimeStampUTC(2005,7,1)));
|
BOOST_CHECK_EQUAL(tm2[6], Opm::asTimeT(Opm::TimeStampUTC(2006,1,1)));
|
||||||
|
|
||||||
Opm::TimeMap tm3(deck3, valid_restart);
|
|
||||||
BOOST_CHECK_EQUAL(tm3[5], Opm::asTimeT(Opm::TimeStampUTC(2005,1,1)));
|
|
||||||
BOOST_CHECK_EQUAL(tm3[6], Opm::asTimeT(Opm::TimeStampUTC(2005,1,2)));
|
|
||||||
BOOST_CHECK_EQUAL(tm3[7], Opm::asTimeT(Opm::TimeStampUTC(2005,1,3)));
|
|
||||||
BOOST_CHECK_EQUAL(tm3[8], Opm::asTimeT(Opm::TimeStampUTC(2005,1,4)));
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user