mirror of
https://github.com/RetroShare/RetroShare.git
synced 2024-10-01 02:35:48 -04:00
added group update feature
git-svn-id: http://svn.code.sf.net/p/retroshare/code/branches/v0.5-gxs_finale@6733 b45a01b8-16f6-495d-af2f-9b41ad6348cc
This commit is contained in:
parent
750dd526c2
commit
33eb99d812
@ -177,6 +177,8 @@ RsDataService::RsDataService(const std::string &serviceDir, const std::string &d
|
|||||||
// for retrieving msg offsets
|
// for retrieving msg offsets
|
||||||
mMsgOffSetColumns.push_back(KEY_MSG_ID); mMsgOffSetColumns.push_back(KEY_NXS_FILE_OFFSET);
|
mMsgOffSetColumns.push_back(KEY_MSG_ID); mMsgOffSetColumns.push_back(KEY_NXS_FILE_OFFSET);
|
||||||
mMsgOffSetColumns.push_back(KEY_NXS_FILE_LEN);
|
mMsgOffSetColumns.push_back(KEY_NXS_FILE_LEN);
|
||||||
|
|
||||||
|
grpIdColumn.push_back(KEY_GRP_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
RsDataService::~RsDataService(){
|
RsDataService::~RsDataService(){
|
||||||
@ -641,6 +643,99 @@ int RsDataService::storeGroup(std::map<RsNxsGrp *, RsGxsGrpMetaData *> &grp)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int RsDataService::updateGroup(std::map<RsNxsGrp *, RsGxsGrpMetaData *> &grp)
|
||||||
|
{
|
||||||
|
|
||||||
|
RsStackMutex stack(mDbMutex);
|
||||||
|
|
||||||
|
std::map<RsNxsGrp*, RsGxsGrpMetaData* >::iterator sit = grp.begin();
|
||||||
|
|
||||||
|
// begin transaction
|
||||||
|
mDb->execSQL("BEGIN;");
|
||||||
|
|
||||||
|
for(; sit != grp.end(); sit++)
|
||||||
|
{
|
||||||
|
|
||||||
|
RsNxsGrp* grpPtr = sit->first;
|
||||||
|
RsGxsGrpMetaData* grpMetaPtr = sit->second;
|
||||||
|
|
||||||
|
// if data is larger than max item size do not add
|
||||||
|
if(!validSize(grpPtr)) continue;
|
||||||
|
|
||||||
|
std::string grpFile = mServiceDir + "/" + grpPtr->grpId;
|
||||||
|
std::fstream ostrm(grpFile.c_str(), std::ios::binary | std::ios::app | std::ios::out);
|
||||||
|
ostrm.seekg(0, std::ios::end); // go to end to append
|
||||||
|
uint32_t offset = ostrm.tellg(); // get fill offset
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* STORE file offset, file length, file name,
|
||||||
|
* grpId, flags, publish time stamp, identity,
|
||||||
|
* id signature, admin signatue, key set, last posting ts
|
||||||
|
* and meta data
|
||||||
|
**/
|
||||||
|
ContentValue cv;
|
||||||
|
cv.put(KEY_NXS_FILE_OFFSET, (int32_t)offset);
|
||||||
|
cv.put(KEY_NXS_FILE_LEN, (int32_t)grpPtr->grp.TlvSize());
|
||||||
|
cv.put(KEY_NXS_FILE, grpFile);
|
||||||
|
cv.put(KEY_GRP_ID, grpPtr->grpId);
|
||||||
|
cv.put(KEY_GRP_NAME, grpMetaPtr->mGroupName);
|
||||||
|
cv.put(KEY_ORIG_GRP_ID, grpMetaPtr->mOrigGrpId);
|
||||||
|
cv.put(KEY_NXS_SERV_STRING, grpMetaPtr->mServiceString);
|
||||||
|
cv.put(KEY_NXS_FLAGS, (int32_t)grpMetaPtr->mGroupFlags);
|
||||||
|
cv.put(KEY_TIME_STAMP, (int32_t)grpMetaPtr->mPublishTs);
|
||||||
|
cv.put(KEY_GRP_SIGN_FLAGS, (int32_t)grpMetaPtr->mSignFlags);
|
||||||
|
cv.put(KEY_GRP_CIRCLE_ID, grpMetaPtr->mCircleId);
|
||||||
|
cv.put(KEY_GRP_CIRCLE_TYPE, (int32_t)grpMetaPtr->mCircleType);
|
||||||
|
cv.put(KEY_GRP_INTERNAL_CIRCLE, grpMetaPtr->mInternalCircle);
|
||||||
|
cv.put(KEY_GRP_ORIGINATOR, grpMetaPtr->mOriginator);
|
||||||
|
cv.put(KEY_GRP_AUTHEN_FLAGS, (int32_t)grpMetaPtr->mAuthenFlags);
|
||||||
|
cv.put(KEY_NXS_HASH, grpMetaPtr->mHash);
|
||||||
|
|
||||||
|
if(! (grpMetaPtr->mAuthorId.empty()) ){
|
||||||
|
cv.put(KEY_NXS_IDENTITY, grpMetaPtr->mAuthorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = 0;
|
||||||
|
char keySetData[grpMetaPtr->keys.TlvSize()];
|
||||||
|
grpMetaPtr->keys.SetTlv(keySetData, grpMetaPtr->keys.TlvSize(), &offset);
|
||||||
|
cv.put(KEY_KEY_SET, grpMetaPtr->keys.TlvSize(), keySetData);
|
||||||
|
|
||||||
|
offset = 0;
|
||||||
|
char metaData[grpPtr->meta.TlvSize()];
|
||||||
|
grpPtr->meta.SetTlv(metaData, grpPtr->meta.TlvSize(), &offset);
|
||||||
|
cv.put(KEY_NXS_META, grpPtr->meta.TlvSize(), metaData);
|
||||||
|
|
||||||
|
// local meta data
|
||||||
|
cv.put(KEY_GRP_SUBCR_FLAG, (int32_t)grpMetaPtr->mSubscribeFlags);
|
||||||
|
cv.put(KEY_GRP_POP, (int32_t)grpMetaPtr->mPop);
|
||||||
|
cv.put(KEY_MSG_COUNT, (int32_t)grpMetaPtr->mMsgCount);
|
||||||
|
cv.put(KEY_GRP_STATUS, (int32_t)grpMetaPtr->mGroupStatus);
|
||||||
|
cv.put(KEY_GRP_LAST_POST, (int32_t)grpMetaPtr->mLastPost);
|
||||||
|
|
||||||
|
offset = 0;
|
||||||
|
char grpData[grpPtr->grp.TlvSize()];
|
||||||
|
grpPtr->grp.SetTlv(grpData, grpPtr->grp.TlvSize(), &offset);
|
||||||
|
ostrm.write(grpData, grpPtr->grp.TlvSize());
|
||||||
|
ostrm.close();
|
||||||
|
|
||||||
|
mDb->sqlUpdate(GRP_TABLE_NAME, "grpId='" + grpPtr->grpId + "'", cv);
|
||||||
|
}
|
||||||
|
// finish transaction
|
||||||
|
bool ret = mDb->execSQL("COMMIT;");
|
||||||
|
|
||||||
|
for(sit = grp.begin(); sit != grp.end(); sit++)
|
||||||
|
{
|
||||||
|
//TODO: API encourages aliasing, remove this abomination
|
||||||
|
if(sit->second != sit->first->metaData)
|
||||||
|
delete sit->second;
|
||||||
|
delete sit->first;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool RsDataService::validSize(RsNxsGrp* grp) const
|
bool RsDataService::validSize(RsNxsGrp* grp) const
|
||||||
{
|
{
|
||||||
if((grp->grp.TlvSize() + grp->meta.TlvSize()) <= GXS_MAX_ITEM_SIZE) return true;
|
if((grp->grp.TlvSize() + grp->meta.TlvSize()) <= GXS_MAX_ITEM_SIZE) return true;
|
||||||
@ -1177,6 +1272,31 @@ int RsDataService::removeGroups(const std::vector<std::string> &grpIds)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int RsDataService::retrieveGroupIds(std::vector<std::string> &grpIds)
|
||||||
|
{
|
||||||
|
RsStackMutex stack(mDbMutex);
|
||||||
|
|
||||||
|
RetroCursor* c = mDb->sqlQuery(GRP_TABLE_NAME, grpIdColumn, "", "");
|
||||||
|
|
||||||
|
if(c)
|
||||||
|
{
|
||||||
|
bool valid = c->moveToFirst();
|
||||||
|
|
||||||
|
while(valid)
|
||||||
|
{
|
||||||
|
std::string grpId;
|
||||||
|
c->getString(0, grpId);
|
||||||
|
grpIds.push_back(grpId);
|
||||||
|
valid = c->moveToNext();
|
||||||
|
}
|
||||||
|
delete c;
|
||||||
|
}else
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
bool RsDataService::locked_updateMessageEntries(const MsgUpdates& updates)
|
bool RsDataService::locked_updateMessageEntries(const MsgUpdates& updates)
|
||||||
{
|
{
|
||||||
|
@ -106,6 +106,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
int removeGroups(const std::vector<RsGxsGroupId>& grpIds);
|
int removeGroups(const std::vector<RsGxsGroupId>& grpIds);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Retrieves all group ids in store
|
||||||
|
* @param grpIds all grpids in store is inserted into this vector
|
||||||
|
* @return error code
|
||||||
|
*/
|
||||||
|
int RsDataService::retrieveGroupIds(std::vector<std::string> &grpIds);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @return the cache size set for this RsGeneralDataService in bytes
|
* @return the cache size set for this RsGeneralDataService in bytes
|
||||||
*/
|
*/
|
||||||
@ -130,6 +137,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
int storeGroup(std::map<RsNxsGrp*, RsGxsGrpMetaData*>& grp);
|
int storeGroup(std::map<RsNxsGrp*, RsGxsGrpMetaData*>& grp);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Updates group entries in Db
|
||||||
|
* @param grp map of group and decoded meta data
|
||||||
|
* @return error code
|
||||||
|
*/
|
||||||
|
int updateGroup(std::map<RsNxsGrp*, RsGxsGrpMetaData*>& grsp);
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @param metaData The meta data item to update
|
* @param metaData The meta data item to update
|
||||||
* @return error code
|
* @return error code
|
||||||
@ -238,6 +252,7 @@ private:
|
|||||||
|
|
||||||
std::list<std::string> grpColumns;
|
std::list<std::string> grpColumns;
|
||||||
std::list<std::string> grpMetaColumns;
|
std::list<std::string> grpMetaColumns;
|
||||||
|
std::list<std::string> grpIdColumn;
|
||||||
|
|
||||||
std::string mServiceDir, mDbName;
|
std::string mServiceDir, mDbName;
|
||||||
uint16_t mServType;
|
uint16_t mServType;
|
||||||
|
@ -167,7 +167,14 @@ public:
|
|||||||
* @param grpIds ids of groups to be removed
|
* @param grpIds ids of groups to be removed
|
||||||
* @return error code
|
* @return error code
|
||||||
*/
|
*/
|
||||||
virtual int removeGroups(const std::vector<RsGxsGroupId>& grpIds) = 0;
|
virtual int removeGroups(std::vector<RsGxsGroupId>& grpIds) = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Retrieves all group ids in store
|
||||||
|
* @param grpIds all grpids in store is inserted into this vector
|
||||||
|
* @return error code
|
||||||
|
*/
|
||||||
|
virtual int retrieveGroupIds(const std::vector<RsGxsGroupId>& grpIds) = 0;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @return the cache size set for this RsGeneralDataService in bytes
|
* @return the cache size set for this RsGeneralDataService in bytes
|
||||||
@ -186,6 +193,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual int storeMessage(std::map<RsNxsMsg*, RsGxsMsgMetaData*>& msgs) = 0;
|
virtual int storeMessage(std::map<RsNxsMsg*, RsGxsMsgMetaData*>& msgs) = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Stores a list of signed messages into data store
|
||||||
|
* @param msg map of message and decoded meta data information
|
||||||
|
* @return error code
|
||||||
|
*/
|
||||||
|
virtual int storeMessage(std::map<RsNxsMsg*, RsGxsMsgMetaData*>& msgs) = 0;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Stores a list of groups in data store
|
* Stores a list of groups in data store
|
||||||
* @param grp map of group and decoded meta data
|
* @param grp map of group and decoded meta data
|
||||||
@ -194,6 +208,13 @@ public:
|
|||||||
virtual int storeGroup(std::map<RsNxsGrp*, RsGxsGrpMetaData*>& grsp) = 0;
|
virtual int storeGroup(std::map<RsNxsGrp*, RsGxsGrpMetaData*>& grsp) = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Updates group entries in Db
|
||||||
|
* @param grp map of group and decoded meta data
|
||||||
|
* @return error code
|
||||||
|
*/
|
||||||
|
virtual int updateGroup(std::map<RsNxsGrp*, RsGxsGrpMetaData*>& grsp) = 0;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* @param metaData
|
* @param metaData
|
||||||
*/
|
*/
|
||||||
|
@ -2038,6 +2038,8 @@ void RsGenExchange::processRecvdData()
|
|||||||
processRecvdGroups();
|
processRecvdGroups();
|
||||||
|
|
||||||
processRecvdMessages();
|
processRecvdMessages();
|
||||||
|
|
||||||
|
performUpdateValidation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -2193,9 +2195,11 @@ void RsGenExchange::processRecvdGroups()
|
|||||||
RsStackMutex stack(mGenMtx);
|
RsStackMutex stack(mGenMtx);
|
||||||
|
|
||||||
NxsGrpPendValidVect::iterator vit = mReceivedGrps.begin();
|
NxsGrpPendValidVect::iterator vit = mReceivedGrps.begin();
|
||||||
|
std::vector<std::string> existingGrpIds, grpIds;
|
||||||
|
|
||||||
std::map<RsNxsGrp*, RsGxsGrpMetaData*> grps;
|
std::map<RsNxsGrp*, RsGxsGrpMetaData*> grps;
|
||||||
|
|
||||||
std::list<RsGxsGroupId> grpIds;
|
mDataStore->retrieveGroupIds(existingGrpIds);
|
||||||
|
|
||||||
while( vit != mReceivedGrps.end())
|
while( vit != mReceivedGrps.end())
|
||||||
{
|
{
|
||||||
@ -2224,9 +2228,19 @@ void RsGenExchange::processRecvdGroups()
|
|||||||
meta->mOriginator = grp->PeerId();
|
meta->mOriginator = grp->PeerId();
|
||||||
|
|
||||||
computeHash(grp->grp, meta->mHash);
|
computeHash(grp->grp, meta->mHash);
|
||||||
grps.insert(std::make_pair(grp, meta));
|
|
||||||
grpIds.push_back(grp->grpId);
|
|
||||||
|
|
||||||
|
// now check if group already existss
|
||||||
|
if(std::find(existingGrpIds.begin(), existingGrpIds.end(), grp->grpId) == existingGrpIds.end())
|
||||||
|
{
|
||||||
|
grps.insert(std::make_pair(grp, meta));
|
||||||
|
grpIds.push_back(grp->grpId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GroupUpdate update;
|
||||||
|
update.newGrp = grp;
|
||||||
|
mGroupUpdates.push_back(update);
|
||||||
|
}
|
||||||
erase = true;
|
erase = true;
|
||||||
}
|
}
|
||||||
else if(ret == VALIDATE_FAIL)
|
else if(ret == VALIDATE_FAIL)
|
||||||
@ -2278,3 +2292,78 @@ void RsGenExchange::processRecvdGroups()
|
|||||||
mDataStore->storeGroup(grps);
|
mDataStore->storeGroup(grps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RsGenExchange::performUpdateValidation()
|
||||||
|
{
|
||||||
|
#ifdef GXS_GENX_DEBUG
|
||||||
|
std::cerr << "RsGenExchange::performUpdateValidation() " << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::map<std::string, RsGxsGrpMetaData*> grpMetas;
|
||||||
|
|
||||||
|
std::vector<GroupUpdate>::iterator vit = mGroupUpdates.begin();
|
||||||
|
for(; vit != mGroupUpdates.end(); vit++)
|
||||||
|
grpMetas.insert(std::make_pair(vit->newGrp->grpId, (RsGxsGrpMetaData*)NULL));
|
||||||
|
|
||||||
|
mDataStore->retrieveGxsGrpMetaData(grpMetas);
|
||||||
|
|
||||||
|
vit = mGroupUpdates.begin();
|
||||||
|
for(; vit != mGroupUpdates.end(); vit++)
|
||||||
|
{
|
||||||
|
GroupUpdate& gu = *vit;
|
||||||
|
std::map<std::string, RsGxsGrpMetaData*>::iterator mit =
|
||||||
|
grpMetas.find(gu.newGrp->grpId);
|
||||||
|
gu.oldGrpMeta = mit->second;
|
||||||
|
gu.validUpdate = updateValid(*(gu.oldGrpMeta), *(gu.newGrp));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef GXS_GENX_DEBUG
|
||||||
|
std::cerr << "RsGenExchange::performUpdateValidation() " << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
vit = mGroupUpdates.begin();
|
||||||
|
std::map<RsNxsGrp*, RsGxsGrpMetaData*> grps;
|
||||||
|
for(; vit != mGroupUpdates.end(); vit++)
|
||||||
|
{
|
||||||
|
GroupUpdate& gu = *vit;
|
||||||
|
grps.insert(std::make_pair(gu.newGrp, gu.newGrp->metaData));
|
||||||
|
delete gu.oldGrpMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
mDataStore->updateGroup(grps);
|
||||||
|
mGroupUpdates.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RsGenExchange::updateValid(RsGxsGrpMetaData& oldGrpMeta, RsNxsGrp& newGrp) const
|
||||||
|
{
|
||||||
|
std::map<SignType, RsTlvKeySignature>& signSet = newGrp.metaData->signSet.keySignSet;
|
||||||
|
std::map<SignType, RsTlvKeySignature>::iterator mit = signSet.find(GXS_SERV::FLAG_AUTHEN_ADMIN);
|
||||||
|
|
||||||
|
if(mit == signSet.end())
|
||||||
|
{
|
||||||
|
#ifdef GXS_GENX_DEBUG
|
||||||
|
std::cerr << "RsGenExchange::updateValid() new admin sign not found! " << std::endl;
|
||||||
|
std::cerr << "RsGenExchange::updateValid() grpId: " << oldGrp.grpId << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RsTlvKeySignature& adminSign = mit->second;
|
||||||
|
|
||||||
|
std::map<std::string, RsTlvSecurityKey>& keys = oldGrpMeta.keys.keys;
|
||||||
|
std::map<std::string, RsTlvSecurityKey>::iterator keyMit = keys.find(oldGrpMeta.mGroupId);
|
||||||
|
|
||||||
|
if(keyMit == keys.end())
|
||||||
|
{
|
||||||
|
#ifdef GXS_GENX_DEBUG
|
||||||
|
std::cerr << "RsGenExchange::updateValid() admin key not found! " << std::endl;
|
||||||
|
#endif
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// also check this is the latest published group
|
||||||
|
bool latest = newGrp.metaData->mPublishTs > oldGrpMeta.mPublishTs;
|
||||||
|
|
||||||
|
return GxsSecurity::validateNxsGrp(newGrp, adminSign, keyMit->second) && latest;
|
||||||
|
}
|
||||||
|
@ -736,6 +736,20 @@ private:
|
|||||||
|
|
||||||
static void computeHash(const RsTlvBinaryData& data, std::string& hash);
|
static void computeHash(const RsTlvBinaryData& data, std::string& hash);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Checks validation of recently received groups to be
|
||||||
|
* updated
|
||||||
|
*/
|
||||||
|
void performUpdateValidation();
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* Checks if the update is valid (i.e. the new admin signature is by the old admin key)
|
||||||
|
* @param oldGrp the old group to be updated (must have meta data member initialised)
|
||||||
|
* @param newGrp the new group that updates the old group (must have meta data member initialised)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
bool updateValid(RsGxsGrpMetaData& oldGrp, RsNxsGrp& newGrp) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
RsMutex mGenMtx;
|
RsMutex mGenMtx;
|
||||||
@ -798,6 +812,10 @@ private:
|
|||||||
const uint8_t CREATE_FAIL, CREATE_SUCCESS, CREATE_FAIL_TRY_LATER, SIGN_MAX_ATTEMPTS;
|
const uint8_t CREATE_FAIL, CREATE_SUCCESS, CREATE_FAIL_TRY_LATER, SIGN_MAX_ATTEMPTS;
|
||||||
const uint8_t SIGN_FAIL, SIGN_SUCCESS, SIGN_FAIL_TRY_LATER;
|
const uint8_t SIGN_FAIL, SIGN_SUCCESS, SIGN_FAIL_TRY_LATER;
|
||||||
const uint8_t VALIDATE_FAIL, VALIDATE_SUCCESS, VALIDATE_FAIL_TRY_LATER, VALIDATE_MAX_ATTEMPTS;
|
const uint8_t VALIDATE_FAIL, VALIDATE_SUCCESS, VALIDATE_FAIL_TRY_LATER, VALIDATE_MAX_ATTEMPTS;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::vector<GroupUpdate> mGroupUpdates;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // RSGENEXCHANGE_H
|
#endif // RSGENEXCHANGE_H
|
||||||
|
@ -129,4 +129,14 @@ private:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class GroupUpdate
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
GroupUpdate() : oldGrpMeta(NULL), newGrp(NULL), validUpdate(false)
|
||||||
|
{}
|
||||||
|
RsGxsGrpMetaData* oldGrpMeta;
|
||||||
|
RsNxsGrp* newGrp;
|
||||||
|
bool validUpdate;
|
||||||
|
};
|
||||||
|
|
||||||
#endif /* GXSUTIL_H_ */
|
#endif /* GXSUTIL_H_ */
|
||||||
|
Loading…
Reference in New Issue
Block a user