forked from blue/lmdbal
Doc fixes, testing for QVariant
This commit is contained in:
parent
275406df61
commit
fbcf94d1c2
@ -1,9 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## LMDBAL 0.5.0 (UNRELEASED, 2023)
|
||||
## LMDBAL 0.5.0 (October 15, 2023)
|
||||
### New Features
|
||||
- duplicates support
|
||||
- duplicates support (only for table)
|
||||
|
||||
### Improvements
|
||||
- some more documentation
|
||||
- more tests
|
||||
|
||||
## LMDBAL 0.4.0 (August 13, 2023)
|
||||
### New Features
|
||||
@ -17,7 +20,6 @@
|
||||
- some more documentation
|
||||
- more tests
|
||||
|
||||
|
||||
## LMDBAL 0.3.1 (April 14, 2023)
|
||||
### Bug fixes
|
||||
- build with qt5 now is possible again
|
||||
|
@ -321,7 +321,7 @@ void LMDBAL::Storage<K, V>::changeRecord(const K& key, const V& value, Transacti
|
||||
* If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb.
|
||||
* It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result.
|
||||
* Anyway:
|
||||
* - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as </b>UNSIGNED</b>. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by </b>UNSIGNED</b> comparison.
|
||||
* - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as <b>UNSIGNED</b>. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by <b>UNSIGNED</b> comparison.
|
||||
* - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50"
|
||||
*
|
||||
* \param[in] key key of the record you look for
|
||||
@ -348,7 +348,7 @@ V LMDBAL::Storage<K, V>::getRecord(const K& key) const {
|
||||
* If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb.
|
||||
* It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result.
|
||||
* Anyway:
|
||||
* - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as </b>UNSIGNED</b>. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by </b>UNSIGNED</b> comparison.
|
||||
* - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as <b>UNSIGNED</b>. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by <b>UNSIGNED</b> comparison.
|
||||
* - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50"
|
||||
*
|
||||
* \param[in] key key of the record you look for
|
||||
@ -384,7 +384,7 @@ void LMDBAL::Storage<K, V>::getRecord(const K& key, V& value) const {
|
||||
* If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb.
|
||||
* It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result.
|
||||
* Anyway:
|
||||
* - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as </b>UNSIGNED</b>. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by </b>UNSIGNED</b> comparison.
|
||||
* - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as <b>UNSIGNED</b>. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by <b>UNSIGNED</b> comparison.
|
||||
* - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50"
|
||||
*
|
||||
* \param[in] key key of the record you look for
|
||||
@ -415,7 +415,7 @@ V LMDBAL::Storage<K, V>::getRecord(const K& key, TransactionID txn) const {
|
||||
* If the storage supports duplicates the exact value returned from it depends on comparison function of lmdb.
|
||||
* It's not very straight forward, so, you shouldn't really use this method if you use duplicates and you rely on exact result.
|
||||
* Anyway:
|
||||
* - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as </b>UNSIGNED</b>. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by </b>UNSIGNED</b> comparison.
|
||||
* - if your values are signed or unsigned integer of any size the LOWEST value is returned compared as <b>UNSIGNED</b>. For example for storage with int32_t as value, from the same key, from the set of values {-33, -1, 5573, 77753} 5573 is returned as it is the lowest by <b>UNSIGNED</b> comparison.
|
||||
* - if your values are anything else - they are compared byte by byte as if they are strings, it makes it especially complicated to predict the exact value for float or double templated storages. For strings if makes a bit more sence: if the choise is from "50" and "100" - "100" is returned, because the first byte of the "100" is lower than the first byte of the "50"
|
||||
*
|
||||
* \param[in] key key of the record you look for
|
||||
|
109
test/basic.cpp
109
test/basic.cpp
@ -12,13 +12,15 @@ protected:
|
||||
::testing::Test(),
|
||||
t1(db->getStorage<uint32_t, uint32_t>("table1")),
|
||||
t2(db->getStorage<QString, QString>("table2")),
|
||||
c1(db->getCache<int8_t, std::string>("cache1")) {}
|
||||
c1(db->getCache<int8_t, std::string>("cache1")),
|
||||
c2(db->getCache<std::string, QVariant>("cache2")) {}
|
||||
|
||||
~BaseTest() {}
|
||||
|
||||
uint32_t getT1Flags() const {return t1->flags();}
|
||||
uint32_t getT2Flags() const {return t2->flags();}
|
||||
uint32_t getC1Flags() const {return c1->flags();}
|
||||
uint32_t getC2Flags() const {return c2->flags();}
|
||||
|
||||
static void SetUpTestSuite() {
|
||||
if (db == nullptr) {
|
||||
@ -26,6 +28,7 @@ protected:
|
||||
db->addStorage<uint32_t, uint32_t>("table1");
|
||||
db->addStorage<QString, QString>("table2");
|
||||
db->addCache<int8_t, std::string>("cache1");
|
||||
db->addCache<std::string, QVariant>("cache2");
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,6 +44,7 @@ protected:
|
||||
LMDBAL::Storage<uint32_t, uint32_t>* t1;
|
||||
LMDBAL::Storage<QString, QString>* t2;
|
||||
LMDBAL::Cache<int8_t, std::string>* c1;
|
||||
LMDBAL::Cache<std::string, QVariant>* c2;
|
||||
};
|
||||
|
||||
|
||||
@ -64,6 +68,7 @@ TEST_F(BaseTest, Flags) {
|
||||
uint32_t t1Flags = getT1Flags();
|
||||
uint32_t t2Flags = getT2Flags();
|
||||
uint32_t c1Flags = getC1Flags();
|
||||
uint32_t c2Flags = getC2Flags();
|
||||
|
||||
EXPECT_TRUE(t1Flags & MDB_INTEGERKEY);
|
||||
EXPECT_FALSE(t1Flags & MDB_DUPSORT);
|
||||
@ -79,6 +84,11 @@ TEST_F(BaseTest, Flags) {
|
||||
EXPECT_FALSE(c1Flags & MDB_DUPSORT);
|
||||
EXPECT_FALSE(c1Flags & MDB_DUPFIXED);
|
||||
EXPECT_FALSE(c1Flags & MDB_INTEGERDUP);
|
||||
|
||||
EXPECT_FALSE(c2Flags & MDB_INTEGERKEY);
|
||||
EXPECT_FALSE(c2Flags & MDB_DUPSORT);
|
||||
EXPECT_FALSE(c2Flags & MDB_DUPFIXED);
|
||||
EXPECT_FALSE(c2Flags & MDB_INTEGERDUP);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, AddingIntegerKey) {
|
||||
@ -108,6 +118,19 @@ TEST_F(BaseTest, AddingKeysToCache) {
|
||||
EXPECT_EQ(c1->getRecord(-116), "whatever");
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, AddingKeysToVariableCache) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
c2->addRecord("regrets", "blah balah");
|
||||
c2->addRecord("fossil fingers", 842);
|
||||
c2->addRecord("preloaded cut", 539.75);
|
||||
c2->addRecord("dihotomy", false);
|
||||
|
||||
EXPECT_EQ(c2->getRecord("regrets"), "blah balah");
|
||||
EXPECT_EQ(c2->getRecord("fossil fingers"), 842);
|
||||
EXPECT_EQ(c2->getRecord("preloaded cut"), 539.75);
|
||||
EXPECT_EQ(c2->getRecord("dihotomy"), false);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, AddingRepeatingKey) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
|
||||
@ -119,6 +142,9 @@ TEST_F(BaseTest, AddingRepeatingKey) {
|
||||
|
||||
EXPECT_THROW(c1->addRecord(-4, "world"), LMDBAL::Exist);
|
||||
EXPECT_EQ(c1->getRecord(-4), "testing goes brrr");
|
||||
|
||||
EXPECT_THROW(c2->addRecord("dihotomy", "pired"), LMDBAL::Exist);
|
||||
EXPECT_EQ(c2->getRecord("dihotomy"), false);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, GettingNotExistingKeys) {
|
||||
@ -127,6 +153,7 @@ TEST_F(BaseTest, GettingNotExistingKeys) {
|
||||
EXPECT_THROW(t2->getRecord("almonds"), LMDBAL::NotFound);
|
||||
EXPECT_THROW(t1->getRecord(64), LMDBAL::NotFound);
|
||||
EXPECT_THROW(c1->getRecord(21), LMDBAL::NotFound);
|
||||
EXPECT_THROW(c2->getRecord("praising"), LMDBAL::NotFound);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, Persistence) {
|
||||
@ -134,6 +161,7 @@ TEST_F(BaseTest, Persistence) {
|
||||
uint32_t t1Size = t1->count();
|
||||
uint32_t t2Size = t2->count();
|
||||
uint32_t c1Size = c1->count();
|
||||
uint32_t c2Size = c2->count();
|
||||
|
||||
db->close();
|
||||
delete db;
|
||||
@ -142,6 +170,7 @@ TEST_F(BaseTest, Persistence) {
|
||||
t1 = db->addStorage<uint32_t, uint32_t>("table1");
|
||||
t2 = db->addStorage<QString, QString>("table2");
|
||||
c1 = db->addCache<int8_t, std::string>("cache1");
|
||||
c2 = db->addCache<std::string, QVariant>("cache2");
|
||||
db->open();
|
||||
|
||||
EXPECT_EQ(t1->count(), t1Size);
|
||||
@ -170,9 +199,16 @@ TEST_F(BaseTest, Persistence) {
|
||||
EXPECT_EQ(c1->getRecord(2), "blah balah");
|
||||
EXPECT_EQ(c1->count(), c1Size);
|
||||
|
||||
EXPECT_EQ(c2->count(), c2Size);
|
||||
EXPECT_EQ(c2->getRecord("regrets"), "blah balah");
|
||||
EXPECT_EQ(c2->getRecord("fossil fingers"), 842);
|
||||
EXPECT_EQ(c2->getRecord("preloaded cut"), 539.75);
|
||||
EXPECT_EQ(c2->getRecord("dihotomy"), false);
|
||||
|
||||
EXPECT_THROW(t2->getRecord("cats"), LMDBAL::NotFound);
|
||||
EXPECT_THROW(t1->getRecord(7893), LMDBAL::NotFound);
|
||||
EXPECT_THROW(c1->getRecord(89), LMDBAL::NotFound);
|
||||
EXPECT_THROW(c2->getRecord("marathons"), LMDBAL::NotFound);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, CountAndDrop) {
|
||||
@ -180,21 +216,24 @@ TEST_F(BaseTest, CountAndDrop) {
|
||||
EXPECT_EQ(t1->count(), 3);
|
||||
EXPECT_EQ(t2->count(), 4);
|
||||
EXPECT_EQ(c1->count(), 4);
|
||||
|
||||
EXPECT_EQ(c2->count(), 4);
|
||||
db->drop();
|
||||
|
||||
EXPECT_EQ(t1->count(), 0);
|
||||
EXPECT_EQ(t2->count(), 0);
|
||||
EXPECT_EQ(c1->count(), 0);
|
||||
EXPECT_EQ(c2->count(), 0);
|
||||
|
||||
t1->addRecord(2, 2);
|
||||
t2->addRecord("sdfhga", "world");
|
||||
c1->addRecord(15, "world");
|
||||
c1->addRecord(12, "grr grr");
|
||||
c2->addRecord("blues", -749);
|
||||
|
||||
EXPECT_EQ(t1->count(), 1);
|
||||
EXPECT_EQ(t2->count(), 1);
|
||||
EXPECT_EQ(c1->count(), 2);
|
||||
EXPECT_EQ(c2->count(), 1);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, Change) {
|
||||
@ -202,37 +241,45 @@ TEST_F(BaseTest, Change) {
|
||||
EXPECT_EQ(t1->count(), 1);
|
||||
EXPECT_EQ(t2->count(), 1);
|
||||
EXPECT_EQ(c1->count(), 2);
|
||||
EXPECT_EQ(c2->count(), 1);
|
||||
|
||||
EXPECT_EQ(t1->getRecord(2), 2);
|
||||
EXPECT_EQ(t2->getRecord("sdfhga"), "world");
|
||||
EXPECT_EQ(c1->getRecord(15), "world");
|
||||
EXPECT_EQ(c1->getRecord(12), "grr grr");
|
||||
EXPECT_EQ(c2->getRecord("blues"), -749);
|
||||
|
||||
t1->addRecord(58, 39);
|
||||
t2->addRecord("lawfirm", "stumble");
|
||||
c1->addRecord(89, "answer");
|
||||
c2->addRecord("wielders", QStringList({"calcium", "eagles"}));
|
||||
|
||||
t1->changeRecord(2, 49);
|
||||
t2->changeRecord("sdfhga", "void");
|
||||
c1->changeRecord(15, "recording");
|
||||
c1->changeRecord(12, "thermal");
|
||||
c2->changeRecord("blues", true);
|
||||
|
||||
EXPECT_THROW(t1->changeRecord(37, 49), LMDBAL::NotFound);
|
||||
EXPECT_THROW(t2->changeRecord("precision", "cryoplastics"), LMDBAL::NotFound);
|
||||
EXPECT_THROW(c1->changeRecord(-62, "sharks"), LMDBAL::NotFound);
|
||||
EXPECT_THROW(c2->changeRecord("visions", 90), LMDBAL::NotFound);
|
||||
|
||||
EXPECT_EQ(t1->getRecord(2), 49);
|
||||
EXPECT_EQ(t2->getRecord("sdfhga"), "void");
|
||||
EXPECT_EQ(c1->getRecord(15), "recording");
|
||||
EXPECT_EQ(c1->getRecord(12), "thermal");
|
||||
EXPECT_EQ(c2->getRecord("blues"), true);
|
||||
|
||||
EXPECT_EQ(t1->getRecord(58), 39);
|
||||
EXPECT_EQ(t2->getRecord("lawfirm"), "stumble");
|
||||
EXPECT_EQ(c1->getRecord(89), "answer");
|
||||
EXPECT_EQ(c2->getRecord("wielders"), QStringList({"calcium", "eagles"}));
|
||||
|
||||
EXPECT_EQ(t1->count(), 2);
|
||||
EXPECT_EQ(t2->count(), 2);
|
||||
EXPECT_EQ(c1->count(), 3);
|
||||
EXPECT_EQ(c2->count(), 2);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, Force) {
|
||||
@ -244,21 +291,30 @@ TEST_F(BaseTest, Force) {
|
||||
EXPECT_EQ(t2->forceRecord("lawfirm", "paracetamol"), false); //changing
|
||||
EXPECT_EQ(c1->forceRecord(89, "canine"), false); //changing
|
||||
EXPECT_EQ(c1->forceRecord(98, "duration"), true); //adding
|
||||
EXPECT_EQ(c2->forceRecord("wielders", "charm"), false); //changing
|
||||
EXPECT_EQ(c2->forceRecord("tedious", 5732.89), true); //adding
|
||||
|
||||
EXPECT_EQ(t1->getRecord(2), 49);
|
||||
EXPECT_EQ(t1->getRecord(58), 35);
|
||||
EXPECT_EQ(t1->getRecord(68), 36);
|
||||
|
||||
EXPECT_EQ(t2->getRecord("sdfhga"), "void");
|
||||
EXPECT_EQ(t2->getRecord("prophecy"), "dumpling");
|
||||
EXPECT_EQ(t2->getRecord("lawfirm"), "paracetamol");
|
||||
|
||||
EXPECT_EQ(c1->getRecord(15), "recording");
|
||||
EXPECT_EQ(c1->getRecord(12), "thermal");
|
||||
EXPECT_EQ(c1->getRecord(89), "canine");
|
||||
EXPECT_EQ(c1->getRecord(98), "duration");
|
||||
|
||||
EXPECT_EQ(c2->getRecord("blues"), true);
|
||||
EXPECT_EQ(c2->getRecord("tedious"), 5732.89);
|
||||
EXPECT_EQ(c2->getRecord("wielders"), "charm");
|
||||
|
||||
EXPECT_EQ(t1->count(), 3);
|
||||
EXPECT_EQ(t2->count(), 3);
|
||||
EXPECT_EQ(c1->count(), 4);
|
||||
EXPECT_EQ(c2->count(), 3);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, ReadAll) {
|
||||
@ -267,21 +323,29 @@ TEST_F(BaseTest, ReadAll) {
|
||||
std::map<uint32_t, uint32_t> m1 = t1->readAll();
|
||||
std::map<QString, QString> m2 = t2->readAll();
|
||||
std::map<int8_t, std::string> m3 = c1->readAll();
|
||||
std::map<std::string, QVariant> m4 = c2->readAll();
|
||||
|
||||
EXPECT_EQ(m1.at(2), 49);
|
||||
EXPECT_EQ(m1.at(58), 35);
|
||||
EXPECT_EQ(m1.at(68), 36);
|
||||
|
||||
EXPECT_EQ(m2.at("sdfhga"), "void");
|
||||
EXPECT_EQ(m2.at("prophecy"), "dumpling");
|
||||
EXPECT_EQ(m2.at("lawfirm"), "paracetamol");
|
||||
|
||||
EXPECT_EQ(m3.at(15), "recording");
|
||||
EXPECT_EQ(m3.at(12), "thermal");
|
||||
EXPECT_EQ(m3.at(89), "canine");
|
||||
EXPECT_EQ(m3.at(98), "duration");
|
||||
|
||||
EXPECT_EQ(m4.at("blues"), true);
|
||||
EXPECT_EQ(m4.at("tedious"), 5732.89);
|
||||
EXPECT_EQ(m4.at("wielders"), "charm");
|
||||
|
||||
EXPECT_EQ(m1.size(), 3);
|
||||
EXPECT_EQ(m2.size(), 3);
|
||||
EXPECT_EQ(m3.size(), 4);
|
||||
EXPECT_EQ(m4.size(), 3);
|
||||
}
|
||||
|
||||
TEST_F(BaseTest, ReplaceAll) {
|
||||
@ -299,10 +363,14 @@ TEST_F(BaseTest, ReplaceAll) {
|
||||
{"ronin", "cheese"}
|
||||
});
|
||||
c1->replaceAll({});
|
||||
c2->replaceAll({
|
||||
{"kind", 73}
|
||||
});
|
||||
|
||||
EXPECT_EQ(t1->count(), 4);
|
||||
EXPECT_EQ(t2->count(), 3);
|
||||
EXPECT_EQ(c1->count(), 0);
|
||||
EXPECT_EQ(c2->count(), 1);
|
||||
|
||||
EXPECT_FALSE(t1->checkRecord(2));
|
||||
EXPECT_FALSE(t1->checkRecord(58));
|
||||
@ -317,6 +385,10 @@ TEST_F(BaseTest, ReplaceAll) {
|
||||
EXPECT_FALSE(c1->checkRecord(89));
|
||||
EXPECT_FALSE(c1->checkRecord(98));
|
||||
|
||||
EXPECT_FALSE(c2->checkRecord("blues"));
|
||||
EXPECT_FALSE(c2->checkRecord("tedious"));
|
||||
EXPECT_FALSE(c2->checkRecord("wielders"));
|
||||
|
||||
EXPECT_EQ(t1->getRecord(7), 48);
|
||||
EXPECT_EQ(t1->getRecord(194), 582);
|
||||
EXPECT_EQ(t1->getRecord(857), 39);
|
||||
@ -326,6 +398,8 @@ TEST_F(BaseTest, ReplaceAll) {
|
||||
EXPECT_EQ(t2->getRecord("cluster"), "throttle");
|
||||
EXPECT_EQ(t2->getRecord("ronin"), "cheese");
|
||||
|
||||
EXPECT_EQ(c2->getRecord("kind"), 73);
|
||||
|
||||
c1->replaceAll({
|
||||
{68, "quality"},
|
||||
{31, "ridgid body"},
|
||||
@ -342,8 +416,6 @@ TEST_F(BaseTest, ReplaceAll) {
|
||||
EXPECT_EQ(c1->getRecord(-117), "lance of Michael");
|
||||
}
|
||||
|
||||
|
||||
|
||||
TEST_F(BaseTest, AddRecords) {
|
||||
EXPECT_EQ(db->ready(), true);
|
||||
|
||||
@ -472,4 +544,33 @@ TEST_F(BaseTest, AddRecords) {
|
||||
EXPECT_EQ(c1->getRecord(-32), "stitched");
|
||||
EXPECT_EQ(c1->getRecord(123), "horibly unforseen");
|
||||
|
||||
LMDBAL::SizeType s4 = c2->addRecords({
|
||||
{"and wholesome", "preaching"},
|
||||
{"ginger", false}
|
||||
});
|
||||
|
||||
EXPECT_EQ(c2->count(), 3);
|
||||
EXPECT_EQ(s4, 3);
|
||||
|
||||
EXPECT_THROW(
|
||||
s4 = c2->addRecords({
|
||||
{"returning favor", 'c'},
|
||||
{"ginger", 23},
|
||||
{"frames", true}
|
||||
}), LMDBAL::Exist
|
||||
);
|
||||
EXPECT_EQ(c2->count(), 3);
|
||||
|
||||
s4 = c2->addRecords({
|
||||
{"and wholesome", -1},
|
||||
{"orcid", 89.9}
|
||||
}, true);
|
||||
|
||||
EXPECT_EQ(c2->count(), 4);
|
||||
EXPECT_EQ(s4, 4);
|
||||
|
||||
EXPECT_EQ(c2->getRecord("and wholesome"), -1);
|
||||
EXPECT_EQ(c2->getRecord("orcid"), 89.9);
|
||||
EXPECT_EQ(c2->getRecord("ginger"), false);
|
||||
EXPECT_EQ(c2->getRecord("kind"), 73);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user