From ef0850e65b997dd0e0968e9725d6773bb54a26b8 Mon Sep 17 00:00:00 2001 From: csoler Date: Mon, 19 Dec 2016 20:44:15 +0100 Subject: [PATCH 01/23] added recording system for usage cases of GXS ids --- libretroshare/src/gxs/rsgenexchange.cc | 12 ++--- libretroshare/src/gxs/rsgixs.h | 2 +- libretroshare/src/gxs/rsgxsutil.cc | 2 +- libretroshare/src/gxstunnel/p3gxstunnel.cc | 2 +- libretroshare/src/services/p3idservice.cc | 53 ++++++++++++++-------- libretroshare/src/services/p3idservice.h | 13 +++++- 6 files changed, 54 insertions(+), 30 deletions(-) diff --git a/libretroshare/src/gxs/rsgenexchange.cc b/libretroshare/src/gxs/rsgenexchange.cc index 46d0fd98a..3d1f2e109 100644 --- a/libretroshare/src/gxs/rsgenexchange.cc +++ b/libretroshare/src/gxs/rsgenexchange.cc @@ -470,8 +470,8 @@ int RsGenExchange::createGroupSignatures(RsTlvKeySignatureSet& signSet, RsTlvBin if(GxsSecurity::getSignature((char*)grpData.bin_data, grpData.bin_len, authorKey, sign)) { id_ret = SIGN_SUCCESS; - mGixs->timeStampKey(grpMeta.mAuthorId) ; - signSet.keySignSet[INDEX_AUTHEN_IDENTITY] = sign; + mGixs->timeStampKey(grpMeta.mAuthorId,"Creation of group author signature for GrpId" + grpMeta.mGroupId.toStdString()) ; + signSet.keySignSet[INDEX_AUTHEN_IDENTITY] = sign; } else id_ret = SIGN_FAIL; @@ -638,7 +638,7 @@ int RsGenExchange::createMsgSignatures(RsTlvKeySignatureSet& signSet, RsTlvBinar if(GxsSecurity::getSignature((char*)msgData.bin_data, msgData.bin_len, authorKey, sign)) { id_ret = SIGN_SUCCESS; - mGixs->timeStampKey(msgMeta.mAuthorId) ; + mGixs->timeStampKey(msgMeta.mAuthorId,"Creating author signature in group " + msgMeta.mGroupId.toStdString() + ", msg " + msgMeta.mMsgId.toStdString()) ; signSet.keySignSet[INDEX_AUTHEN_IDENTITY] = sign; } else @@ -855,7 +855,7 @@ int RsGenExchange::validateMsg(RsNxsMsg *msg, const uint32_t& grpFlag, const uin { RsTlvKeySignature sign = metaData.signSet.keySignSet[INDEX_AUTHEN_IDENTITY]; idValidate &= GxsSecurity::validateNxsMsg(*msg, sign, authorKey); - mGixs->timeStampKey(metaData.mAuthorId) ; + mGixs->timeStampKey(metaData.mAuthorId,"Validation of author signature. Grp="+metaData.mGroupId.toStdString()+", msg="+metaData.mMsgId.toStdString()) ; } else { @@ -986,7 +986,7 @@ int RsGenExchange::validateGrp(RsNxsGrp* grp) #ifdef GEN_EXCH_DEBUG std::cerr << " key ID validation result: " << idValidate << std::endl; #endif - mGixs->timeStampKey(metaData.mAuthorId) ; + mGixs->timeStampKey(metaData.mAuthorId,"Group author signature validation. GrpId=" + metaData.mGroupId.toStdString()) ; } else { @@ -3145,7 +3145,7 @@ bool RsGenExchange::updateValid(RsGxsGrpMetaData& oldGrpMeta, RsNxsGrp& newGrp) // also check this is the latest published group bool latest = newGrp.metaData->mPublishTs > oldGrpMeta.mPublishTs; - mGixs->timeStampKey(newGrp.metaData->mAuthorId) ; + mGixs->timeStampKey(newGrp.metaData->mAuthorId,"Validation of signature for updated grp " + oldGrpMeta.mGroupId.toStdString()) ; return GxsSecurity::validateNxsGrp(newGrp, adminSign, keyMit->second) && latest; } diff --git a/libretroshare/src/gxs/rsgixs.h b/libretroshare/src/gxs/rsgixs.h index 5b12aa873..21c46d04d 100644 --- a/libretroshare/src/gxs/rsgixs.h +++ b/libretroshare/src/gxs/rsgixs.h @@ -125,7 +125,7 @@ public: virtual bool getOwnIds(std::list& ids) = 0; virtual bool isOwnId(const RsGxsId& key_id) = 0 ; - virtual void timeStampKey(const RsGxsId& key_id) = 0 ; + virtual void timeStampKey(const RsGxsId& key_id,const std::string& reason) = 0 ; // Key related interface - used for validating msgs and groups. /*! diff --git a/libretroshare/src/gxs/rsgxsutil.cc b/libretroshare/src/gxs/rsgxsutil.cc index 29c9ae337..0b679fd41 100644 --- a/libretroshare/src/gxs/rsgxsutil.cc +++ b/libretroshare/src/gxs/rsgxsutil.cc @@ -339,7 +339,7 @@ bool RsGxsIntegrityCheck::check() // Note: we could time_stamp even in the case where the id is not cached. Anyway, it's not really a problem here, since IDs have a high chance of // behing eventually stamped. - mGixs->timeStampKey(gxs_ids[n]) ; + mGixs->timeStampKey(gxs_ids[n],"Used in service (Integrity check)") ; } gxs_ids[n] = gxs_ids[gxs_ids.size()-1] ; diff --git a/libretroshare/src/gxstunnel/p3gxstunnel.cc b/libretroshare/src/gxstunnel/p3gxstunnel.cc index 0838bef50..c64cf6572 100644 --- a/libretroshare/src/gxstunnel/p3gxstunnel.cc +++ b/libretroshare/src/gxstunnel/p3gxstunnel.cc @@ -937,7 +937,7 @@ void p3GxsTunnelService::handleRecvDHPublicKey(RsGxsTunnelDHPublicKeyItem *item) std::cerr << "(SS) Signature was verified and it doesn't check! This is a security issue!" << std::endl; return ; } - mGixs->timeStampKey(item->signature.keyId) ; + mGixs->timeStampKey(item->signature.keyId,"Used to validate GXS tunnel DH half-key.") ; #ifdef DEBUG_GXS_TUNNEL std::cerr << " Signature checks! Sender's ID = " << senders_id << std::endl; diff --git a/libretroshare/src/services/p3idservice.cc b/libretroshare/src/services/p3idservice.cc index 6914ebaa5..c4ae86f52 100644 --- a/libretroshare/src/services/p3idservice.cc +++ b/libretroshare/src/services/p3idservice.cc @@ -128,8 +128,9 @@ RsIdentity *rsIdentity = NULL; /* delays */ -#define CACHETEST_PERIOD 60 +#define CACHETEST_PERIOD 60 #define DELAY_BETWEEN_CONFIG_UPDATES 300 +#define GXS_MAX_KEY_TS_USAGE_MAP_SIZE 5 #define OWNID_RELOAD_DELAY 10 @@ -255,23 +256,33 @@ void p3IdService::slowIndicateConfigChanged() } time_t p3IdService::locked_getLastUsageTS(const RsGxsId& gxs_id) { - std::map::const_iterator it = mKeysTS.find(gxs_id) ; + std::map::const_iterator it = mKeysTS.find(gxs_id) ; if(it == mKeysTS.end()) return 0 ; else - return it->second ; + return it->second.TS ; } -void p3IdService::timeStampKey(const RsGxsId& gxs_id) +void p3IdService::timeStampKey(const RsGxsId& gxs_id, const std::string& reason) { if(rsReputations->isIdentityBanned(gxs_id) ) { std::cerr << "(II) p3IdService:timeStampKey(): refusing to time stamp key " << gxs_id << " because it is banned." << std::endl; return ; } + std::cerr << "(II) time stamping key " << gxs_id << " for the following reason: " << reason << std::endl; RS_STACK_MUTEX(mIdMtx) ; - mKeysTS[gxs_id] = time(NULL) ; + + time_t now = time(NULL) ; + + keyTSInfo& info(mKeysTS[gxs_id]) ; + + info.TS = now ; + info.usage_map[now] = reason ; + + while(info.usage_map.size() > GXS_MAX_KEY_TS_USAGE_MAP_SIZE) + info.usage_map.erase(info.usage_map.begin()); slowIndicateConfigChanged() ; } @@ -286,7 +297,7 @@ bool p3IdService::loadList(std::list& items) if( (lii = dynamic_cast(*it)) != NULL) { for(std::map::const_iterator it2 = lii->mTimeStamps.begin();it2!=lii->mTimeStamps.end();++it2) - mKeysTS.insert(*it2) ; + mKeysTS[it2->first].TS = it2->second; mContacts = lii->mContacts ; } @@ -307,7 +318,10 @@ bool p3IdService::saveList(bool& cleanup,std::list& items) RS_STACK_MUTEX(mIdMtx) ; cleanup = true ; RsGxsIdLocalInfoItem *item = new RsGxsIdLocalInfoItem ; - item->mTimeStamps = mKeysTS ; + + for(std::map::const_iterator it(mKeysTS.begin());it!=mKeysTS.end();++it) + item->mTimeStamps[it->first] = it->second.TS; + item->mContacts = mContacts ; items.push_back(item) ; @@ -317,7 +331,7 @@ bool p3IdService::saveList(bool& cleanup,std::list& items) class IdCacheEntryCleaner { public: - IdCacheEntryCleaner(const std::map& last_usage_TSs) : mLastUsageTS(last_usage_TSs) {} + IdCacheEntryCleaner(const std::map& last_usage_TSs) : mLastUsageTS(last_usage_TSs) {} bool processEntry(RsGxsIdCache& entry) { @@ -338,11 +352,11 @@ public: return true ; } - std::map::const_iterator it = mLastUsageTS.find(gxs_id) ; + std::map::const_iterator it = mLastUsageTS.find(gxs_id) ; bool no_ts = (it == mLastUsageTS.end()) ; - time_t last_usage_ts = no_ts?0:(it->second); + time_t last_usage_ts = no_ts?0:(it->second.TS); time_t max_keep_time ; if(no_ts) @@ -370,7 +384,7 @@ public: } std::list ids_to_delete ; - const std::map& mLastUsageTS; + const std::map& mLastUsageTS; }; void p3IdService::cleanUnusedKeys() @@ -495,7 +509,7 @@ void p3IdService::notifyChanges(std::vector &changes) // also time_stamp the key that this group represents - timeStampKey(RsGxsId(*git)) ; + timeStampKey(RsGxsId(*git),"Group info changed") ; ++git; } @@ -892,7 +906,7 @@ bool p3IdService::signData(const uint8_t *data,uint32_t data_size,const RsGxsId& return false ; } error_status = RS_GIXS_ERROR_NO_ERROR ; - timeStampKey(own_gxs_id) ; + timeStampKey(own_gxs_id,"own GXS id") ; return true ; } bool p3IdService::validateData(const uint8_t *data,uint32_t data_size,const RsTlvKeySignature& signature,bool force_load,uint32_t& signing_error) @@ -929,7 +943,7 @@ bool p3IdService::validateData(const uint8_t *data,uint32_t data_size,const RsTl } signing_error = RS_GIXS_ERROR_NO_ERROR ; - timeStampKey(signature.keyId) ; + timeStampKey(signature.keyId,"Used in signature checking." ) ; return true ; } bool p3IdService::encryptData(const uint8_t *decrypted_data,uint32_t decrypted_data_size,uint8_t *& encrypted_data,uint32_t& encrypted_data_size,const RsGxsId& encryption_key_id,bool force_load,uint32_t& error_status) @@ -957,7 +971,7 @@ bool p3IdService::encryptData(const uint8_t *decrypted_data,uint32_t decrypted_d return false ; } error_status = RS_GIXS_ERROR_NO_ERROR ; - timeStampKey(encryption_key_id) ; + timeStampKey(encryption_key_id,"Used to encrypt data") ; return true ; } @@ -989,7 +1003,7 @@ bool p3IdService::decryptData(const uint8_t *encrypted_data,uint32_t encrypted_d return false ; } error_status = RS_GIXS_ERROR_NO_ERROR ; - timeStampKey(key_id) ; + timeStampKey(key_id,"Used to decrypt data") ; return true ; } @@ -2399,7 +2413,8 @@ bool p3IdService::cache_load_ownids(uint32_t token) // This prevents automatic deletion to get rid of them. // In other words, own ids are always used. - mKeysTS[RsGxsId(item->meta.mGroupId)] = time(NULL) ; + + mKeysTS[RsGxsId(item->meta.mGroupId)].TS = time(NULL) ; } delete item ; } @@ -2691,7 +2706,7 @@ RsGenExchange::ServiceCreate_Return p3IdService::service_CreateGroup(RsGxsGrpIte std::cerr << std::endl; return SERVICE_CREATE_FAIL; } - mKeysTS[RsGxsId(item->meta.mGroupId)] = time(NULL) ; + mKeysTS[RsGxsId(item->meta.mGroupId)].TS = time(NULL) ; /********************* TEMP HACK UNTIL GXS FILLS IN GROUP_ID *****************/ @@ -2851,7 +2866,7 @@ RsGenExchange::ServiceCreate_Return p3IdService::service_CreateGroup(RsGxsGrpIte if (std::find(mOwnIds.begin(), mOwnIds.end(), gxsId) == mOwnIds.end()) { mOwnIds.push_back(gxsId); - mKeysTS[gxsId] = time(NULL) ; + mKeysTS[gxsId].TS = time(NULL) ; } } diff --git a/libretroshare/src/services/p3idservice.h b/libretroshare/src/services/p3idservice.h index f31bbaf50..f06042172 100644 --- a/libretroshare/src/services/p3idservice.h +++ b/libretroshare/src/services/p3idservice.h @@ -467,7 +467,7 @@ private: void cleanUnusedKeys() ; void slowIndicateConfigChanged() ; - virtual void timeStampKey(const RsGxsId& id) ; + virtual void timeStampKey(const RsGxsId& id,const std::string& reason) ; time_t locked_getLastUsageTS(const RsGxsId& gxs_id); std::string genRandomId(int len = 20); @@ -507,10 +507,19 @@ private: private: + struct keyTSInfo + { + keyTSInfo() : TS(0) {} + + time_t TS ; + std::map usage_map ; + }; + friend class IdCacheEntryCleaner; + std::map > mIdsPendingCache; std::map > mGroupNotPresent; std::map > mIdsNotPresent; - std::map mKeysTS ; + std::map mKeysTS ; // keep a list of regular contacts. This is useful to sort IDs, and allow some services to priviledged ids only. std::set mContacts; From d3051eff1a121e965b479771487d209b17c9d50b Mon Sep 17 00:00:00 2001 From: csoler Date: Wed, 21 Dec 2016 00:34:07 +0100 Subject: [PATCH 02/23] added display of usage statistics for GXS identities --- libretroshare/src/gxs/rsgenexchange.h | 2 ++ libretroshare/src/gxs/rsgxsutil.cc | 20 +++++-------- libretroshare/src/pqi/p3servicecontrol.cc | 14 +++++++++ libretroshare/src/pqi/p3servicecontrol.h | 1 + libretroshare/src/retroshare/rsidentity.h | 3 +- .../src/retroshare/rsservicecontrol.h | 1 + libretroshare/src/services/p3idservice.cc | 29 +++++++++++++++++-- retroshare-gui/src/gui/Identity/IdDialog.cpp | 18 ++++++++++++ retroshare-gui/src/gui/Identity/IdDialog.ui | 14 ++++++++- 9 files changed, 85 insertions(+), 17 deletions(-) diff --git a/libretroshare/src/gxs/rsgenexchange.h b/libretroshare/src/gxs/rsgenexchange.h index a391d47e0..2fc2ef6f7 100644 --- a/libretroshare/src/gxs/rsgenexchange.h +++ b/libretroshare/src/gxs/rsgenexchange.h @@ -656,6 +656,8 @@ public: virtual void setSyncPeriod(const RsGxsGroupId& grpId,uint32_t age_in_secs) ; uint16_t serviceType() const { return mServType ; } + uint16_t serviceFullType() const { return ((uint32_t)mServType << 8) + (((uint32_t) RS_PKT_VERSION_SERVICE) << 24); } + protected: /** Notifications **/ diff --git a/libretroshare/src/gxs/rsgxsutil.cc b/libretroshare/src/gxs/rsgxsutil.cc index 0b679fd41..7fa83e3b0 100644 --- a/libretroshare/src/gxs/rsgxsutil.cc +++ b/libretroshare/src/gxs/rsgxsutil.cc @@ -139,7 +139,7 @@ bool RsGxsIntegrityCheck::check() GxsMsgReq msgIds; GxsMsgReq grps; - std::set used_gxs_ids ; + std::map used_gxs_ids ; std::set subscribed_groups ; // compute hash and compare to stored value, if it fails then simply add it @@ -172,7 +172,7 @@ bool RsGxsIntegrityCheck::check() #endif if(rsIdentity!=NULL && !rsIdentity->isBanned(grp->metaData->mAuthorId)) - used_gxs_ids.insert(grp->metaData->mAuthorId) ; + used_gxs_ids.insert(std::make_pair(grp->metaData->mAuthorId,grp->grpId)) ; } } } @@ -270,7 +270,7 @@ bool RsGxsIntegrityCheck::check() GXSUTIL_DEBUG() << "TimeStamping message authors' key ID " << msg->metaData->mAuthorId << " in message " << msg->msgId << ", group ID " << msg->grpId<< std::endl; #endif if(rsIdentity!=NULL && !rsIdentity->isBanned(msg->metaData->mAuthorId)) - used_gxs_ids.insert(msg->metaData->mAuthorId) ; + used_gxs_ids.insert(std::make_pair(msg->metaData->mAuthorId,msg->metaData->mGroupId)) ; } delete msg; @@ -297,9 +297,9 @@ bool RsGxsIntegrityCheck::check() std::list connected_friends ; rsPeers->getOnlineList(connected_friends) ; - std::vector gxs_ids ; + std::vector > gxs_ids ; - for(std::set::const_iterator it(used_gxs_ids.begin());it!=used_gxs_ids.end();++it) + for(std::map::const_iterator it(used_gxs_ids.begin());it!=used_gxs_ids.end();++it) { gxs_ids.push_back(*it) ; #ifdef DEBUG_GXSUTIL @@ -321,9 +321,9 @@ bool RsGxsIntegrityCheck::check() GXSUTIL_DEBUG() << " requesting ID " << gxs_ids[n] ; #endif - if(!mGixs->haveKey(gxs_ids[n])) // checks if we have it already in the cache (conservative way to ensure that we atually have it) + if(!mGixs->haveKey(gxs_ids[n].first)) // checks if we have it already in the cache (conservative way to ensure that we atually have it) { - mGixs->requestKey(gxs_ids[n],connected_friends); + mGixs->requestKey(gxs_ids[n].first,connected_friends); ++nb_requested_not_in_cache ; #ifdef DEBUG_GXSUTIL @@ -335,12 +335,8 @@ bool RsGxsIntegrityCheck::check() #ifdef DEBUG_GXSUTIL GXSUTIL_DEBUG() << " ... already in cache" << std::endl; #endif - - // Note: we could time_stamp even in the case where the id is not cached. Anyway, it's not really a problem here, since IDs have a high chance of - // behing eventually stamped. - - mGixs->timeStampKey(gxs_ids[n],"Used in service (Integrity check)") ; } + mGixs->timeStampKey(gxs_ids[n].first,"Author in group " + gxs_ids[n].second.toStdString() + " of service \"" + rsServiceControl->getServiceName(mGenExchangeClient->serviceFullType())) ; gxs_ids[n] = gxs_ids[gxs_ids.size()-1] ; gxs_ids.pop_back() ; diff --git a/libretroshare/src/pqi/p3servicecontrol.cc b/libretroshare/src/pqi/p3servicecontrol.cc index 11dd01520..a397a4e1c 100644 --- a/libretroshare/src/pqi/p3servicecontrol.cc +++ b/libretroshare/src/pqi/p3servicecontrol.cc @@ -362,6 +362,20 @@ void p3ServiceControl::getServiceChanges(std::set &updateSet) mUpdatedSet.clear(); } +std::string p3ServiceControl::getServiceName(uint32_t service_id) +{ + RsStackMutex stack(mCtrlMtx); /***** LOCK STACK MUTEX ****/ + + std::map::const_iterator it = mOwnServices.find(service_id) ; + + if(it == mOwnServices.end()) + { + std::cerr << "(EE) Cannot find own service for ID = " << std::hex << service_id << std::dec << std::endl; + return std::string(); + } + + return it->second.mServiceName; +} bool p3ServiceControl::getOwnServices(RsPeerServiceInfo &info) { diff --git a/libretroshare/src/pqi/p3servicecontrol.h b/libretroshare/src/pqi/p3servicecontrol.h index e2a27ddc3..948481a2b 100644 --- a/libretroshare/src/pqi/p3servicecontrol.h +++ b/libretroshare/src/pqi/p3servicecontrol.h @@ -85,6 +85,7 @@ virtual const RsPeerId& getOwnId(); */ virtual bool getOwnServices(RsPeerServiceInfo &info); +virtual std::string getServiceName(uint32_t service_id) ; // This is what is passed to peers, can be displayed by GUI too. virtual bool getServicesAllowed(const RsPeerId &peerId, RsPeerServiceInfo &info); diff --git a/libretroshare/src/retroshare/rsidentity.h b/libretroshare/src/retroshare/rsidentity.h index e7a7abbe8..4a48b747c 100644 --- a/libretroshare/src/retroshare/rsidentity.h +++ b/libretroshare/src/retroshare/rsidentity.h @@ -184,7 +184,7 @@ public: // Cyril: Reputation details. At some point we might want to merge information // between the two into a single global score. Since the old reputation system // is not finished yet, I leave this in place. We should decide what to do with it. - +#warning (cyril) possibly remove this old reputation field. GxsReputation mReputation_oldSystem; // this is the old "mReputation" field, which apparently is not used. RsReputations::ReputationInfo mReputation; @@ -193,6 +193,7 @@ public: // last usage time_t mLastUsageTS ; + std::map mUseCases ; }; diff --git a/libretroshare/src/retroshare/rsservicecontrol.h b/libretroshare/src/retroshare/rsservicecontrol.h index 76c2ee811..a6d8fde7c 100644 --- a/libretroshare/src/retroshare/rsservicecontrol.h +++ b/libretroshare/src/retroshare/rsservicecontrol.h @@ -108,6 +108,7 @@ class RsServiceControl virtual ~RsServiceControl() { return; } virtual bool getOwnServices(RsPeerServiceInfo &info) = 0; +virtual std::string getServiceName(uint32_t service_id) = 0; virtual bool getServicesAllowed(const RsPeerId &peerId, RsPeerServiceInfo &info) = 0; virtual bool getServicesProvided(const RsPeerId &peerId, RsPeerServiceInfo &info) = 0; diff --git a/libretroshare/src/services/p3idservice.cc b/libretroshare/src/services/p3idservice.cc index c4ae86f52..429562a18 100644 --- a/libretroshare/src/services/p3idservice.cc +++ b/libretroshare/src/services/p3idservice.cc @@ -279,10 +279,24 @@ void p3IdService::timeStampKey(const RsGxsId& gxs_id, const std::string& reason) keyTSInfo& info(mKeysTS[gxs_id]) ; info.TS = now ; - info.usage_map[now] = reason ; + info.usage_map[reason] = now; while(info.usage_map.size() > GXS_MAX_KEY_TS_USAGE_MAP_SIZE) - info.usage_map.erase(info.usage_map.begin()); + { + // This is very costly, but normally the outerloop should never be rolled more than once. + + std::map::iterator best_it ; + time_t best_time = now+1; + + for(std::map::iterator it(info.usage_map.begin());it!=info.usage_map.end();++it) + if(it->second < best_time) + { + best_time = it->second ; + best_it = it; + } + + info.usage_map.erase(best_it) ; + } slowIndicateConfigChanged() ; } @@ -566,7 +580,16 @@ bool p3IdService::getIdDetails(const RsGxsId &id, RsIdentityDetails &details) rsReputations->setOwnOpinion(id,RsReputations::OPINION_POSITIVE) ; details = data.details; - details.mLastUsageTS = locked_getLastUsageTS(id) ; + + std::map::const_iterator it = mKeysTS.find(id) ; + + if(it == mKeysTS.end()) + details.mLastUsageTS = 0 ; + else + { + details.mLastUsageTS = it->second.TS ; + details.mUseCases = it->second.usage_map ; + } // one utf8 symbol can be at most 4 bytes long - would be better to measure real unicode length !!! if(details.mNickname.length() > RSID_MAXIMUM_NICKNAME_SIZE*4) diff --git a/retroshare-gui/src/gui/Identity/IdDialog.cpp b/retroshare-gui/src/gui/Identity/IdDialog.cpp index 20bef5ccf..0ca6be250 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.cpp +++ b/retroshare-gui/src/gui/Identity/IdDialog.cpp @@ -1849,6 +1849,24 @@ void IdDialog::insertIdDetails(uint32_t token) default: std::cerr << "Unexpected value in own opinion: " << info.mOwnOpinion << std::endl; } + + // now fill in usage cases + + RsIdentityDetails det ; + rsIdentity->getIdDetails(RsGxsId(data.mMeta.mGroupId),det) ; + + QString usage_txt ; + std::map rmap ; + for(std::map::const_iterator it(det.mUseCases.begin());it!=det.mUseCases.end();++it) + rmap.insert(std::make_pair(it->second,it->first)) ; + + for(std::map::const_iterator it(rmap.begin());it!=rmap.end();++it) + usage_txt += QString("")+ getHumanReadableDuration(now - data.mLastUsageTS) + " \t: " + QString::fromStdString(it->second) + "
" ; + + if(usage_txt.isNull()) + usage_txt = tr("[Unused]") ; + + ui->usageStatistics_TB->setText(usage_txt) ; } void IdDialog::modifyReputation() diff --git a/retroshare-gui/src/gui/Identity/IdDialog.ui b/retroshare-gui/src/gui/Identity/IdDialog.ui index bb075b56f..3dceaa373 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.ui +++ b/retroshare-gui/src/gui/Identity/IdDialog.ui @@ -7,7 +7,7 @@ 0 0 1269 - 911 + 1040 @@ -595,6 +595,18 @@ p, li { white-space: pre-wrap; } + + + + Usage statistics + + + + + + + + From 0a3ec9706d73bf6bd8f9caa2d2a199c131388398 Mon Sep 17 00:00:00 2001 From: csoler Date: Thu, 22 Dec 2016 00:14:16 +0100 Subject: [PATCH 03/23] added statistics info for key requests --- libretroshare/src/chat/distributedchat.cc | 4 ++-- libretroshare/src/grouter/p3grouter.cc | 12 ++++++------ libretroshare/src/grouter/p3grouter.h | 2 +- libretroshare/src/gxs/rsgenexchange.cc | 6 +++--- libretroshare/src/gxs/rsgenexchange.h | 2 +- libretroshare/src/gxs/rsgixs.h | 4 ++-- libretroshare/src/gxs/rsgxsutil.cc | 4 ++-- libretroshare/src/services/p3gxscircles.cc | 2 +- libretroshare/src/services/p3idservice.cc | 18 +++++++++++------- libretroshare/src/services/p3idservice.h | 4 ++-- libretroshare/src/util/rsprint.cc | 6 +++++- libretroshare/src/util/rsprint.h | 2 +- 12 files changed, 37 insertions(+), 29 deletions(-) diff --git a/libretroshare/src/chat/distributedchat.cc b/libretroshare/src/chat/distributedchat.cc index 9f4a4274e..dc4da67c0 100644 --- a/libretroshare/src/chat/distributedchat.cc +++ b/libretroshare/src/chat/distributedchat.cc @@ -220,7 +220,7 @@ bool DistributedChatService::checkSignature(RsChatLobbyBouncingObject *obj,const // network pre-request key to allow message authentication. - mGixs->requestKey(obj->signature.keyId,peer_list); + mGixs->requestKey(obj->signature.keyId,peer_list,"Needed for chat lobby "+RsUtil::NumberToString(obj->lobby_id,true)); uint32_t size = obj->signed_serial_size() ; RsTemporaryMemory memory(size) ; @@ -238,7 +238,7 @@ bool DistributedChatService::checkSignature(RsChatLobbyBouncingObject *obj,const uint32_t error_status ; - if(!mGixs->validateData(memory,obj->signed_serial_size(),obj->signature,false,error_status)) + if(!mGixs->validateData(memory,obj->signed_serial_size(),obj->signature,false,"Chat lobby "+RsUtil::NumberToString(obj->lobby_id,true),error_status)) { bool res = false ; diff --git a/libretroshare/src/grouter/p3grouter.cc b/libretroshare/src/grouter/p3grouter.cc index 5c51a196d..c9bc1fb13 100644 --- a/libretroshare/src/grouter/p3grouter.cc +++ b/libretroshare/src/grouter/p3grouter.cc @@ -1555,7 +1555,7 @@ void p3GRouter::handleIncomingReceiptItem(RsGRouterSignedReceiptItem *receipt_it uint32_t error_status ; - if(! verifySignedDataItem(receipt_item,error_status)) + if(! verifySignedDataItem(receipt_item,"GRouter incoming receipt item",error_status)) if( (it->second.routing_flags & GRouterRoutingInfo::ROUTING_FLAGS_IS_ORIGIN) || (error_status != RsGixs::RS_GIXS_ERROR_KEY_NOT_AVAILABLE)) { std::cerr << " checking receipt signature : FAILED. Receipt is dropped. Error status=" << error_status << std::endl; @@ -1711,7 +1711,7 @@ void p3GRouter::handleIncomingDataItem(RsGRouterGenericDataItem *data_item) #endif uint32_t error_status ; - if(!verifySignedDataItem(data_item,error_status)) // we should get proper flags out of this + if(!verifySignedDataItem(data_item,"Incoming distant message",error_status)) // we should get proper flags out of this { std::cerr << " verifying item signature: FAILED! Droping that item" ; std::cerr << " You probably received a message from a person you don't have key." << std::endl; @@ -1980,7 +1980,7 @@ bool p3GRouter::signDataItem(RsGRouterAbstractMsgItem *item,const RsGxsId& signi return false ; } } -bool p3GRouter::verifySignedDataItem(RsGRouterAbstractMsgItem *item,uint32_t& error_status) +bool p3GRouter::verifySignedDataItem(RsGRouterAbstractMsgItem *item,const std::string& info,uint32_t& error_status) { try { @@ -1999,7 +1999,7 @@ bool p3GRouter::verifySignedDataItem(RsGRouterAbstractMsgItem *item,uint32_t& er if(!item->serialise_signed_data(data,data_size)) throw std::runtime_error("Cannot serialise signed data.") ; - if(!mGixs->validateData(data,data_size,item->signature,true,error_status)) + if(!mGixs->validateData(data,data_size,item->signature,true,info, error_status)) { switch(error_status) { @@ -2010,7 +2010,7 @@ bool p3GRouter::verifySignedDataItem(RsGRouterAbstractMsgItem *item,uint32_t& er std::cerr << "(EE) Key for GXS Id " << item->signature.keyId << " is not available. Cannot verify. Asking key to peer " << item->PeerId() << std::endl; - mGixs->requestKey(item->signature.keyId,peer_ids) ; // request the key around + mGixs->requestKey(item->signature.keyId,peer_ids,info) ; // request the key around } break ; case RsGixs::RS_GIXS_ERROR_SIGNATURE_MISMATCH: std::cerr << "(EE) Signature mismatch. Spoofing/Corrupted/MITM?." << std::endl; @@ -2116,7 +2116,7 @@ bool p3GRouter::sendData(const RsGxsId& destination,const GRouterServiceId& clie // Verify the signature. If that fails, there's a bug somewhere!! uint32_t error_status; - if(!verifySignedDataItem(data_item,error_status)) + if(!verifySignedDataItem(data_item,"GRouter own signature check for outgoing msg",error_status)) { std::cerr << "Cannot verify data item that was just signed. Some error occured!" << std::endl; delete data_item; diff --git a/libretroshare/src/grouter/p3grouter.h b/libretroshare/src/grouter/p3grouter.h index 9ddf50149..19d704add 100644 --- a/libretroshare/src/grouter/p3grouter.h +++ b/libretroshare/src/grouter/p3grouter.h @@ -254,7 +254,7 @@ private: // signs an item with the given key. bool signDataItem(RsGRouterAbstractMsgItem *item,const RsGxsId& id) ; - bool verifySignedDataItem(RsGRouterAbstractMsgItem *item, uint32_t &error_status) ; + bool verifySignedDataItem(RsGRouterAbstractMsgItem *item, const std::string &info, uint32_t &error_status) ; bool encryptDataItem(RsGRouterGenericDataItem *item,const RsGxsId& destination_key) ; bool decryptDataItem(RsGRouterGenericDataItem *item) ; diff --git a/libretroshare/src/gxs/rsgenexchange.cc b/libretroshare/src/gxs/rsgenexchange.cc index 3d1f2e109..2075bc7c1 100644 --- a/libretroshare/src/gxs/rsgenexchange.cc +++ b/libretroshare/src/gxs/rsgenexchange.cc @@ -855,7 +855,7 @@ int RsGenExchange::validateMsg(RsNxsMsg *msg, const uint32_t& grpFlag, const uin { RsTlvKeySignature sign = metaData.signSet.keySignSet[INDEX_AUTHEN_IDENTITY]; idValidate &= GxsSecurity::validateNxsMsg(*msg, sign, authorKey); - mGixs->timeStampKey(metaData.mAuthorId,"Validation of author signature. Grp="+metaData.mGroupId.toStdString()+", msg="+metaData.mMsgId.toStdString()) ; + mGixs->timeStampKey(metaData.mAuthorId,"Validation of author signature, service: " + rsServiceControl->getServiceName(serviceFullType()) + ". Grp="+metaData.mGroupId.toStdString()+", msg="+metaData.mMsgId.toStdString()) ; } else { @@ -909,7 +909,7 @@ int RsGenExchange::validateMsg(RsNxsMsg *msg, const uint32_t& grpFlag, const uin { std::list peers; peers.push_back(msg->PeerId()); - mGixs->requestKey(metaData.mAuthorId, peers); + mGixs->requestKey(metaData.mAuthorId, peers,"Validation of author signature, service: " + rsServiceControl->getServiceName(serviceFullType()) + ". Grp="+metaData.mGroupId.toStdString()+", msg="+metaData.mMsgId.toStdString()); #ifdef GEN_EXCH_DEBUG std::cerr << ", Key missing. Retry later." << std::endl; @@ -1004,7 +1004,7 @@ int RsGenExchange::validateGrp(RsNxsGrp* grp) #endif std::list peers; peers.push_back(grp->PeerId()); - mGixs->requestKey(metaData.mAuthorId, peers); + mGixs->requestKey(metaData.mAuthorId, peers,"Group author signature validation. GrpId=" + metaData.mGroupId.toStdString()); return VALIDATE_FAIL_TRY_LATER; } } diff --git a/libretroshare/src/gxs/rsgenexchange.h b/libretroshare/src/gxs/rsgenexchange.h index 2fc2ef6f7..0614ed2b1 100644 --- a/libretroshare/src/gxs/rsgenexchange.h +++ b/libretroshare/src/gxs/rsgenexchange.h @@ -656,7 +656,7 @@ public: virtual void setSyncPeriod(const RsGxsGroupId& grpId,uint32_t age_in_secs) ; uint16_t serviceType() const { return mServType ; } - uint16_t serviceFullType() const { return ((uint32_t)mServType << 8) + (((uint32_t) RS_PKT_VERSION_SERVICE) << 24); } + uint32_t serviceFullType() const { return ((uint32_t)mServType << 8) + (((uint32_t) RS_PKT_VERSION_SERVICE) << 24); } protected: diff --git a/libretroshare/src/gxs/rsgixs.h b/libretroshare/src/gxs/rsgixs.h index 21c46d04d..a210064c2 100644 --- a/libretroshare/src/gxs/rsgixs.h +++ b/libretroshare/src/gxs/rsgixs.h @@ -117,7 +117,7 @@ public: */ virtual bool signData(const uint8_t *data,uint32_t data_size,const RsGxsId& signer_id,RsTlvKeySignature& signature,uint32_t& signing_error) = 0 ; - virtual bool validateData(const uint8_t *data,uint32_t data_size,const RsTlvKeySignature& signature,bool force_load,uint32_t& signing_error) = 0 ; + virtual bool validateData(const uint8_t *data,uint32_t data_size,const RsTlvKeySignature& signature,bool force_load,const std::string& info_string,uint32_t& signing_error) = 0 ; virtual bool encryptData(const uint8_t *clear_data,uint32_t clear_data_size,uint8_t *& encrypted_data,uint32_t& encrypted_data_size,const RsGxsId& encryption_key_id,bool force_load,uint32_t& encryption_error) = 0 ; virtual bool decryptData(const uint8_t *encrypted_data,uint32_t encrypted_data_size,uint8_t *& clear_data,uint32_t& clear_data_size,const RsGxsId& encryption_key_id,uint32_t& encryption_error) = 0 ; @@ -149,7 +149,7 @@ public: * @param keyref the KeyRef of the key being requested * @return will */ - virtual bool requestKey(const RsGxsId &id, const std::list &peers) = 0; + virtual bool requestKey(const RsGxsId &id, const std::list &peers,const std::string& info) = 0; virtual bool requestPrivateKey(const RsGxsId &id) = 0; diff --git a/libretroshare/src/gxs/rsgxsutil.cc b/libretroshare/src/gxs/rsgxsutil.cc index 7fa83e3b0..c4dc5304f 100644 --- a/libretroshare/src/gxs/rsgxsutil.cc +++ b/libretroshare/src/gxs/rsgxsutil.cc @@ -323,7 +323,7 @@ bool RsGxsIntegrityCheck::check() if(!mGixs->haveKey(gxs_ids[n].first)) // checks if we have it already in the cache (conservative way to ensure that we atually have it) { - mGixs->requestKey(gxs_ids[n].first,connected_friends); + mGixs->requestKey(gxs_ids[n].first,connected_friends,"Author in group " + gxs_ids[n].second.toStdString() + " of service \"" + rsServiceControl->getServiceName(mGenExchangeClient->serviceFullType())+"\"") ; ++nb_requested_not_in_cache ; #ifdef DEBUG_GXSUTIL @@ -336,7 +336,7 @@ bool RsGxsIntegrityCheck::check() GXSUTIL_DEBUG() << " ... already in cache" << std::endl; #endif } - mGixs->timeStampKey(gxs_ids[n].first,"Author in group " + gxs_ids[n].second.toStdString() + " of service \"" + rsServiceControl->getServiceName(mGenExchangeClient->serviceFullType())) ; + mGixs->timeStampKey(gxs_ids[n].first,"Author in group " + gxs_ids[n].second.toStdString() + " of service \"" + rsServiceControl->getServiceName(mGenExchangeClient->serviceFullType())+"\"") ; gxs_ids[n] = gxs_ids[gxs_ids.size()-1] ; gxs_ids.pop_back() ; diff --git a/libretroshare/src/services/p3gxscircles.cc b/libretroshare/src/services/p3gxscircles.cc index b36dc141e..c9087cea1 100644 --- a/libretroshare/src/services/p3gxscircles.cc +++ b/libretroshare/src/services/p3gxscircles.cc @@ -1094,7 +1094,7 @@ bool p3GxsCircles::locked_processLoadingCacheEntry(RsGxsCircleCache& cache) rsPeers->getOnlineList(peers) ; } - mIdentities->requestKey(pit->first, peers); + mIdentities->requestKey(pit->first, peers,"Membership status check in Circle "+cache.mCircleName+" ("+cache.mCircleId.toStdString()+")"); //isUnprocessedPeers = true; } } diff --git a/libretroshare/src/services/p3idservice.cc b/libretroshare/src/services/p3idservice.cc index 429562a18..72cd3231e 100644 --- a/libretroshare/src/services/p3idservice.cc +++ b/libretroshare/src/services/p3idservice.cc @@ -523,7 +523,7 @@ void p3IdService::notifyChanges(std::vector &changes) // also time_stamp the key that this group represents - timeStampKey(RsGxsId(*git),"Group info changed") ; + timeStampKey(RsGxsId(*git),"Group meta data changed") ; ++git; } @@ -789,7 +789,7 @@ static void mergeIds(std::map >& idmap,const RsGxsId old_peers.push_back(*it) ; } -bool p3IdService::requestKey(const RsGxsId &id, const std::list& peers) +bool p3IdService::requestKey(const RsGxsId &id, const std::list& peers,const std::string& info) { if(id.isNull()) { @@ -815,7 +815,7 @@ bool p3IdService::requestKey(const RsGxsId &id, const std::list& peers std::cerr << "(II) not requesting Key " << id << " because it has been banned." << std::endl; { - RsStackMutex stack(mIdMtx); /********** STACK LOCKED MTX ******/ + RS_STACK_MUTEX(mIdMtx); /********** STACK LOCKED MTX ******/ mIdsNotPresent.erase(id) ; } return true; @@ -833,6 +833,10 @@ bool p3IdService::requestKey(const RsGxsId &id, const std::list& peers return true ; } } + { + RS_STACK_MUTEX(mIdMtx); /********** STACK LOCKED MTX ******/ + mKeysTS[id].usage_map["Requested to friends: "+info] = time(NULL) ; + } return cache_request_load(id, peers); } @@ -929,10 +933,10 @@ bool p3IdService::signData(const uint8_t *data,uint32_t data_size,const RsGxsId& return false ; } error_status = RS_GIXS_ERROR_NO_ERROR ; - timeStampKey(own_gxs_id,"own GXS id") ; + timeStampKey(own_gxs_id,"Own GXS id") ; return true ; } -bool p3IdService::validateData(const uint8_t *data,uint32_t data_size,const RsTlvKeySignature& signature,bool force_load,uint32_t& signing_error) +bool p3IdService::validateData(const uint8_t *data,uint32_t data_size,const RsTlvKeySignature& signature,bool force_load,const std::string& info_string,uint32_t& signing_error) { // RsIdentityDetails details ; // getIdDetails(signature.keyId,details); @@ -966,7 +970,7 @@ bool p3IdService::validateData(const uint8_t *data,uint32_t data_size,const RsTl } signing_error = RS_GIXS_ERROR_NO_ERROR ; - timeStampKey(signature.keyId,"Used in signature checking." ) ; + timeStampKey(signature.keyId,"Used in signature checking: "+info_string ) ; return true ; } bool p3IdService::encryptData(const uint8_t *decrypted_data,uint32_t decrypted_data_size,uint8_t *& encrypted_data,uint32_t& encrypted_data_size,const RsGxsId& encryption_key_id,bool force_load,uint32_t& error_status) @@ -2531,7 +2535,7 @@ bool p3IdService::cachetest_handlerequest(uint32_t token) if (!haveKey(*vit)) { std::list nullpeers; - requestKey(*vit, nullpeers); + requestKey(*vit, nullpeers,"Cache test in p3IdService"); #ifdef DEBUG_IDS std::cerr << "p3IdService::cachetest_request() Requested Key Id: " << *vit; diff --git a/libretroshare/src/services/p3idservice.h b/libretroshare/src/services/p3idservice.h index f06042172..5ca5d1c75 100644 --- a/libretroshare/src/services/p3idservice.h +++ b/libretroshare/src/services/p3idservice.h @@ -287,7 +287,7 @@ public: virtual bool isOwnId(const RsGxsId& key_id) ; virtual bool signData(const uint8_t *data,uint32_t data_size,const RsGxsId& signer_id,RsTlvKeySignature& signature,uint32_t& signing_error) ; - virtual bool validateData(const uint8_t *data,uint32_t data_size,const RsTlvKeySignature& signature,bool force_load,uint32_t& signing_error) ; + virtual bool validateData(const uint8_t *data,uint32_t data_size,const RsTlvKeySignature& signature,bool force_load,const std::string& info_string,uint32_t& signing_error) ; virtual bool encryptData(const uint8_t *decrypted_data,uint32_t decrypted_data_size,uint8_t *& encrypted_data,uint32_t& encrypted_data_size,const RsGxsId& encryption_key_id,bool force_load,uint32_t& encryption_error) ; virtual bool decryptData(const uint8_t *encrypted_data,uint32_t encrypted_data_size,uint8_t *& decrypted_data,uint32_t& decrypted_data_size,const RsGxsId& encryption_key_id,uint32_t& encryption_error) ; @@ -298,7 +298,7 @@ public: virtual bool getKey(const RsGxsId &id, RsTlvPublicRSAKey &key); virtual bool getPrivateKey(const RsGxsId &id, RsTlvPrivateRSAKey &key); - virtual bool requestKey(const RsGxsId &id, const std::list &peers); + virtual bool requestKey(const RsGxsId &id, const std::list &peers, const std::string &info); virtual bool requestPrivateKey(const RsGxsId &id); diff --git a/libretroshare/src/util/rsprint.cc b/libretroshare/src/util/rsprint.cc index 5735088f4..e692f6c26 100644 --- a/libretroshare/src/util/rsprint.cc +++ b/libretroshare/src/util/rsprint.cc @@ -36,9 +36,13 @@ #include #endif -std::string RsUtil::NumberToString(uint64_t n) +std::string RsUtil::NumberToString(uint64_t n,bool hex) { std::ostringstream os ; + + if(hex) + os << std::hex ; + os << n ; os.flush() ; diff --git a/libretroshare/src/util/rsprint.h b/libretroshare/src/util/rsprint.h index ebcccb8ef..37207ebde 100644 --- a/libretroshare/src/util/rsprint.h +++ b/libretroshare/src/util/rsprint.h @@ -36,7 +36,7 @@ namespace RsUtil { std::string BinToHex(const std::string &bin); std::string BinToHex(const char *arr, const uint32_t len); std::string BinToHex(const unsigned char *arr, const uint32_t len); -std::string NumberToString(uint64_t n); +std::string NumberToString(uint64_t n, bool hex=false); std::string HashId(const std::string &id, bool reverse = false); //std::string AccurateTimeString(); From 8b9038a028e4fe8c4432c03e9f15a6eeca12019f Mon Sep 17 00:00:00 2001 From: csoler Date: Thu, 22 Dec 2016 11:21:49 +0100 Subject: [PATCH 04/23] improved display/wording of usage statstics in People --- libretroshare/src/gxs/rsgxsutil.cc | 4 ++-- retroshare-gui/src/gui/Identity/IdDialog.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libretroshare/src/gxs/rsgxsutil.cc b/libretroshare/src/gxs/rsgxsutil.cc index c4dc5304f..8b2b25a5d 100644 --- a/libretroshare/src/gxs/rsgxsutil.cc +++ b/libretroshare/src/gxs/rsgxsutil.cc @@ -323,7 +323,7 @@ bool RsGxsIntegrityCheck::check() if(!mGixs->haveKey(gxs_ids[n].first)) // checks if we have it already in the cache (conservative way to ensure that we atually have it) { - mGixs->requestKey(gxs_ids[n].first,connected_friends,"Author in group " + gxs_ids[n].second.toStdString() + " of service \"" + rsServiceControl->getServiceName(mGenExchangeClient->serviceFullType())+"\"") ; + mGixs->requestKey(gxs_ids[n].first,connected_friends,"Author in service \"" + rsServiceControl->getServiceName(mGenExchangeClient->serviceFullType())+"\" (group ID " + gxs_ids[n].second.toStdString() + ")" ) ; ++nb_requested_not_in_cache ; #ifdef DEBUG_GXSUTIL @@ -336,7 +336,7 @@ bool RsGxsIntegrityCheck::check() GXSUTIL_DEBUG() << " ... already in cache" << std::endl; #endif } - mGixs->timeStampKey(gxs_ids[n].first,"Author in group " + gxs_ids[n].second.toStdString() + " of service \"" + rsServiceControl->getServiceName(mGenExchangeClient->serviceFullType())+"\"") ; + mGixs->timeStampKey(gxs_ids[n].first,"Author in service \"" + rsServiceControl->getServiceName(mGenExchangeClient->serviceFullType())+"\" (group ID " + gxs_ids[n].second.toStdString() + ")"); gxs_ids[n] = gxs_ids[gxs_ids.size()-1] ; gxs_ids.pop_back() ; diff --git a/retroshare-gui/src/gui/Identity/IdDialog.cpp b/retroshare-gui/src/gui/Identity/IdDialog.cpp index 0ca6be250..5f7a20010 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.cpp +++ b/retroshare-gui/src/gui/Identity/IdDialog.cpp @@ -1864,7 +1864,7 @@ void IdDialog::insertIdDetails(uint32_t token) usage_txt += QString("")+ getHumanReadableDuration(now - data.mLastUsageTS) + " \t: " + QString::fromStdString(it->second) + "
" ; if(usage_txt.isNull()) - usage_txt = tr("[Unused]") ; + usage_txt = tr("[No record in current session]") ; ui->usageStatistics_TB->setText(usage_txt) ; } From 72fb8f17a99cfd81e4333a32c0e4827dc5baae35 Mon Sep 17 00:00:00 2001 From: csoler Date: Fri, 23 Dec 2016 17:52:02 +0100 Subject: [PATCH 05/23] changed the reputation system to output a level that differentiate your own opinion to your friends opinion --- libretroshare/src/chat/distributedchat.cc | 4 +- libretroshare/src/grouter/p3grouter.cc | 2 +- libretroshare/src/gxs/rsgenexchange.cc | 19 ++--- libretroshare/src/gxs/rsgxsnetservice.cc | 51 +++++++------ libretroshare/src/gxs/rsgxsutil.cc | 4 +- libretroshare/src/retroshare/rsidentity.h | 11 ++- libretroshare/src/retroshare/rsreputations.h | 22 ++++-- libretroshare/src/services/p3gxsreputation.cc | 75 +++++++++++++++---- libretroshare/src/services/p3gxsreputation.h | 17 +++-- libretroshare/src/services/p3idservice.cc | 9 +-- libretroshare/src/services/p3idservice.h | 2 +- .../src/gui/Identity/IdDetailsDialog.cpp | 7 +- retroshare-gui/src/gui/Identity/IdDialog.cpp | 25 +++++-- retroshare-gui/src/gui/gxs/GxsIdDetails.cpp | 6 +- .../src/gui/gxs/GxsIdTreeWidgetItem.cpp | 6 +- .../gui/gxsforums/GxsForumThreadWidget.cpp | 7 +- 16 files changed, 173 insertions(+), 94 deletions(-) diff --git a/libretroshare/src/chat/distributedchat.cc b/libretroshare/src/chat/distributedchat.cc index dc4da67c0..8fbc8f624 100644 --- a/libretroshare/src/chat/distributedchat.cc +++ b/libretroshare/src/chat/distributedchat.cc @@ -138,7 +138,7 @@ bool DistributedChatService::handleRecvChatLobbyMsgItem(RsChatMsgItem *ci) return false ; } - if(rsIdentity->isBanned(cli->signature.keyId)) + if(rsIdentity->overallReputationLevel(cli->signature.keyId) == RsReputations::REPUTATION_LOCALLY_NEGATIVE) { std::cerr << "(WW) Received lobby msg/item from banned identity " << cli->signature.keyId << ". Dropping it." << std::endl; return false ; @@ -647,7 +647,7 @@ void DistributedChatService::handleRecvChatLobbyEventItem(RsChatLobbyEventItem * #endif time_t now = time(NULL) ; - if(rsIdentity->isBanned(item->signature.keyId)) + if(rsIdentity->overallReputationLevel(item->signature.keyId) == RsReputations::REPUTATION_LOCALLY_NEGATIVE) { std::cerr << "(WW) Received lobby msg/item from banned identity " << item->signature.keyId << ". Dropping it." << std::endl; return ; diff --git a/libretroshare/src/grouter/p3grouter.cc b/libretroshare/src/grouter/p3grouter.cc index c9bc1fb13..1cb8cb360 100644 --- a/libretroshare/src/grouter/p3grouter.cc +++ b/libretroshare/src/grouter/p3grouter.cc @@ -1984,7 +1984,7 @@ bool p3GRouter::verifySignedDataItem(RsGRouterAbstractMsgItem *item,const std::s { try { - if(rsIdentity->isBanned(item->signature.keyId)) + if(rsIdentity->overallReputationLevel(item->signature.keyId) == RsReputations::REPUTATION_LOCALLY_NEGATIVE) { std::cerr << "(WW) received global router message from banned identity " << item->signature.keyId << ". Rejecting the message." << std::endl; return false ; diff --git a/libretroshare/src/gxs/rsgenexchange.cc b/libretroshare/src/gxs/rsgenexchange.cc index 2075bc7c1..25d4e52d8 100644 --- a/libretroshare/src/gxs/rsgenexchange.cc +++ b/libretroshare/src/gxs/rsgenexchange.cc @@ -880,20 +880,13 @@ int RsGenExchange::validateMsg(RsNxsMsg *msg, const uint32_t& grpFlag, const uin else { - // now check reputation of the message author - float reputation_threshold = RsReputations::REPUTATION_THRESHOLD_DEFAULT; - - if( (signFlag & GXS_SERV::FLAG_AUTHOR_AUTHENTICATION_GPG_KNOWN) && !(details.mFlags & RS_IDENTITY_FLAGS_PGP_KNOWN)) - reputation_threshold = RsReputations::REPUTATION_THRESHOLD_ANTI_SPAM; - else if( (signFlag & GXS_SERV::FLAG_AUTHOR_AUTHENTICATION_GPG) && !(details.mFlags & RS_IDENTITY_FLAGS_PGP_LINKED)) - reputation_threshold = RsReputations::REPUTATION_THRESHOLD_ANTI_SPAM; - else - reputation_threshold = RsReputations::REPUTATION_THRESHOLD_DEFAULT; - - if(details.mReputation.mOverallReputationScore < reputation_threshold) + // now check reputation of the message author. The reputation will need to be at least as high as this value for the msg to validate. + // At validation step, we accept all messages, except the ones signed by locally rejected identities. + + if(details.mReputation.mOverallReputationLevel == RsReputations::REPUTATION_LOCALLY_NEGATIVE) { #ifdef GEN_EXCH_DEBUG - std::cerr << "RsGenExchange::validateMsg(): message from " << metaData.mAuthorId << ", rejected because reputation score (" << details.mReputation.mOverallReputationScore <<") is below the accepted threshold (" << reputation_threshold << ")" << std::endl; + std::cerr << "RsGenExchange::validateMsg(): message from " << metaData.mAuthorId << ", rejected because reputation score (" << details.mReputation.mOverallReputationLevel <<") is below the accepted threshold (" << reputation_threshold << ")" << std::endl; #endif idValidate = false ; } @@ -903,7 +896,7 @@ int RsGenExchange::validateMsg(RsNxsMsg *msg, const uint32_t& grpFlag, const uin #endif } - } + } } else { diff --git a/libretroshare/src/gxs/rsgxsnetservice.cc b/libretroshare/src/gxs/rsgxsnetservice.cc index e19052257..dd7ffebf7 100644 --- a/libretroshare/src/gxs/rsgxsnetservice.cc +++ b/libretroshare/src/gxs/rsgxsnetservice.cc @@ -2810,16 +2810,13 @@ void RsGxsNetService::locked_genReqMsgTransaction(NxsTransaction* tr) if(reqListSize < MAX_REQLIST_SIZE && msgIdSet.find(msgId) == msgIdSet.end()) { - // if reputation is in reputations cache then proceed - // or if there isn't an author (note as author requirement is - // enforced at service level, if no author is needed then reputation - // filtering is optional) bool noAuthor = syncItem->authorId.isNull(); #ifdef NXS_NET_DEBUG_1 GXSNETDEBUG_PG(item->PeerId(),grpId) << ", reqlist size=" << reqListSize << ", message not present." ; #endif // grp meta must be present if author present + if(!noAuthor && grpMeta == NULL) { #ifdef NXS_NET_DEBUG_1 @@ -2828,7 +2825,14 @@ void RsGxsNetService::locked_genReqMsgTransaction(NxsTransaction* tr) continue; } - if(rsIdentity && rsIdentity->isBanned(syncItem->authorId)) + // The algorithm on request of message is: + // + // - always re-check for author ban level + // - if author is locally banned, do not download. + // - if author is not locally banned, download, whatever friends' opinion might be. + +#warning Update the code below to correctly send/recv dependign on reputation + if(rsIdentity && rsIdentity->overallReputationLevel(syncItem->authorId) == RsReputations::REPUTATION_LOCALLY_NEGATIVE) { #ifdef NXS_NET_DEBUG_1 GXSNETDEBUG_PG(item->PeerId(),grpId) << ", Identity " << syncItem->authorId << " is banned. Not requesting message!" << std::endl; @@ -2844,7 +2848,7 @@ void RsGxsNetService::locked_genReqMsgTransaction(NxsTransaction* tr) continue ; } - +#ifdef TO_BE_REMOVED if(mReputations->haveReputation(syncItem->authorId) || noAuthor) { GixsReputation rep; @@ -2859,17 +2863,20 @@ void RsGxsNetService::locked_genReqMsgTransaction(NxsTransaction* tr) // at genexchange side of things if(rep.score >= (int)grpMeta->mReputationCutOff || noAuthor) { -#ifdef NXS_NET_DEBUG_1 - GXSNETDEBUG_PG(item->PeerId(),grpId) << ", passed! Adding message to req list." << std::endl; #endif - RsNxsSyncMsgItem* msgItem = new RsNxsSyncMsgItem(mServType); - msgItem->grpId = grpId; - msgItem->msgId = msgId; - msgItem->flag = RsNxsSyncMsgItem::FLAG_REQUEST; - msgItem->transactionNumber = transN; - msgItem->PeerId(peerFrom); - reqList.push_back(msgItem); - ++reqListSize ; +#ifdef NXS_NET_DEBUG_1 + GXSNETDEBUG_PG(item->PeerId(),grpId) << ", passed! Adding message to req list." << std::endl; +#endif + RsNxsSyncMsgItem* msgItem = new RsNxsSyncMsgItem(mServType); + msgItem->grpId = grpId; + msgItem->msgId = msgId; + msgItem->flag = RsNxsSyncMsgItem::FLAG_REQUEST; + msgItem->transactionNumber = transN; + msgItem->PeerId(peerFrom); + reqList.push_back(msgItem); + ++reqListSize ; + +#ifdef TO_BE_REMOVED } #ifdef NXS_NET_DEBUG_1 else @@ -2889,6 +2896,7 @@ void RsGxsNetService::locked_genReqMsgTransaction(NxsTransaction* tr) entry.mMsgId = syncItem->msgId; toVet.push_back(entry); } +#endif } #ifdef NXS_NET_DEBUG_1 else @@ -3061,13 +3069,14 @@ void RsGxsNetService::locked_genReqGrpTransaction(NxsTransaction* tr) } // FIXTESTS global variable rsReputations not available in unittests! - if(!grpSyncItem->authorId.isNull() && rsIdentity && rsIdentity->isBanned(grpSyncItem->authorId)) - { +#warning Update the code below to correctly send/recv dependign on reputation + if(!grpSyncItem->authorId.isNull() && rsIdentity && rsIdentity->overallReputationLevel(grpSyncItem->authorId) == RsReputations::REPUTATION_LOCALLY_NEGATIVE) + { #ifdef NXS_NET_DEBUG_0 - GXSNETDEBUG_PG(tr->mTransaction->PeerId(),grpId) << " Identity " << grpSyncItem->authorId << " is banned. Not syncing group." << std::endl; + GXSNETDEBUG_PG(tr->mTransaction->PeerId(),grpId) << " Identity " << grpSyncItem->authorId << " is banned. Not syncing group." << std::endl; #endif - continue ; - } + continue ; + } if( (mGrpAutoSync && !haveItem) || latestVersion) { diff --git a/libretroshare/src/gxs/rsgxsutil.cc b/libretroshare/src/gxs/rsgxsutil.cc index 8b2b25a5d..694f34972 100644 --- a/libretroshare/src/gxs/rsgxsutil.cc +++ b/libretroshare/src/gxs/rsgxsutil.cc @@ -171,7 +171,7 @@ bool RsGxsIntegrityCheck::check() GXSUTIL_DEBUG() << "TimeStamping group authors' key ID " << grp->metaData->mAuthorId << " in group ID " << grp->grpId << std::endl; #endif - if(rsIdentity!=NULL && !rsIdentity->isBanned(grp->metaData->mAuthorId)) + if(rsIdentity!=NULL && rsIdentity->overallReputationLevel(grp->metaData->mAuthorId) > RsReputations::REPUTATION_LOCALLY_NEGATIVE) used_gxs_ids.insert(std::make_pair(grp->metaData->mAuthorId,grp->grpId)) ; } } @@ -269,7 +269,7 @@ bool RsGxsIntegrityCheck::check() #ifdef DEBUG_GXSUTIL GXSUTIL_DEBUG() << "TimeStamping message authors' key ID " << msg->metaData->mAuthorId << " in message " << msg->msgId << ", group ID " << msg->grpId<< std::endl; #endif - if(rsIdentity!=NULL && !rsIdentity->isBanned(msg->metaData->mAuthorId)) + if(rsIdentity!=NULL && rsIdentity->overallReputationLevel(msg->metaData->mAuthorId) > RsReputations::REPUTATION_LOCALLY_NEGATIVE) used_gxs_ids.insert(std::make_pair(msg->metaData->mAuthorId,msg->metaData->mGroupId)) ; } diff --git a/libretroshare/src/retroshare/rsidentity.h b/libretroshare/src/retroshare/rsidentity.h index 4a48b747c..dff973204 100644 --- a/libretroshare/src/retroshare/rsidentity.h +++ b/libretroshare/src/retroshare/rsidentity.h @@ -184,8 +184,6 @@ public: // Cyril: Reputation details. At some point we might want to merge information // between the two into a single global score. Since the old reputation system // is not finished yet, I leave this in place. We should decide what to do with it. -#warning (cyril) possibly remove this old reputation field. - GxsReputation mReputation_oldSystem; // this is the old "mReputation" field, which apparently is not used. RsReputations::ReputationInfo mReputation; // avatar @@ -255,7 +253,14 @@ public: virtual bool setAsRegularContact(const RsGxsId& id,bool is_a_contact) = 0 ; virtual bool isARegularContact(const RsGxsId& id) = 0 ; - virtual bool isBanned(const RsGxsId& id) =0; + + /*! + * \brief overallReputationLevel + * Returns the overall reputation level of the supplied identity. See rsreputations.h + * \param id + * \return + */ + virtual RsReputations::ReputationLevel overallReputationLevel(const RsGxsId& id)=0; virtual time_t getLastUsageTS(const RsGxsId &id) =0; // Specific RsIdentity Functions.... diff --git a/libretroshare/src/retroshare/rsreputations.h b/libretroshare/src/retroshare/rsreputations.h index dac28ad38..ce8e46401 100644 --- a/libretroshare/src/retroshare/rsreputations.h +++ b/libretroshare/src/retroshare/rsreputations.h @@ -36,17 +36,27 @@ public: // This is the interface file for the reputation system // - enum Opinion { OPINION_NEGATIVE = 0, OPINION_NEUTRAL = 1, OPINION_POSITIVE = 2 } ; - enum Assessment { ASSESSMENT_BAD = 0, ASSESSMENT_OK = 1 } ; + enum Opinion { OPINION_NEGATIVE = 0, OPINION_NEUTRAL = 1, OPINION_POSITIVE = 2 } ; + + enum ReputationLevel { REPUTATION_LOCALLY_NEGATIVE = 0x00, // local opinion is positive + REPUTATION_REMOTELY_NEGATIVE = 0x01, // local opinion is neutral and friends are positive in average + REPUTATION_NEUTRAL = 0x02, // no reputation information ; + REPUTATION_REMOTELY_POSITIVE = 0x03, // local opinion is neutral and friends are negative in average + REPUTATION_LOCALLY_POSITIVE = 0x04 // local opinion is negative + }; struct ReputationInfo { - ReputationInfo() : mOwnOpinion(OPINION_NEUTRAL), mOverallReputationScore(REPUTATION_THRESHOLD_DEFAULT), mFriendAverage(REPUTATION_THRESHOLD_DEFAULT),mAssessment(ASSESSMENT_OK){} + ReputationInfo() : mOwnOpinion(OPINION_NEUTRAL), mFriendAverageScore(REPUTATION_THRESHOLD_DEFAULT),mOverallReputationLevel(REPUTATION_NEUTRAL){} RsReputations::Opinion mOwnOpinion ; - float mOverallReputationScore ; - float mFriendAverage ; - RsReputations::Assessment mAssessment; // this should help clients in taking decisions + + uint32_t mFriendsPositiveVotes ; + uint32_t mFriendsNegativeVotes ; + + float mFriendAverageScore ; + + RsReputations::ReputationLevel mOverallReputationLevel; // this should help clients in taking decisions }; virtual bool setOwnOpinion(const RsGxsId& key_id, const Opinion& op) =0; diff --git a/libretroshare/src/services/p3gxsreputation.cc b/libretroshare/src/services/p3gxsreputation.cc index 68933e4a8..f47dd6f2c 100644 --- a/libretroshare/src/services/p3gxsreputation.cc +++ b/libretroshare/src/services/p3gxsreputation.cc @@ -795,8 +795,9 @@ bool p3GxsReputation::getReputationInfo(const RsGxsId& gxsid, const RsPgpId& own if(it == mReputations.end()) { info.mOwnOpinion = RsReputations::OPINION_NEUTRAL ; - info.mOverallReputationScore = RsReputations::REPUTATION_THRESHOLD_DEFAULT ; - info.mFriendAverage = REPUTATION_THRESHOLD_DEFAULT ; + info.mFriendAverageScore = REPUTATION_THRESHOLD_DEFAULT ; + info.mFriendsNegativeVotes = 0 ; + info.mFriendsPositiveVotes = 0 ; owner_id = ownerNode ; } @@ -805,8 +806,9 @@ bool p3GxsReputation::getReputationInfo(const RsGxsId& gxsid, const RsPgpId& own Reputation& rep(it->second) ; info.mOwnOpinion = RsReputations::Opinion(rep.mOwnOpinion) ; - info.mOverallReputationScore = rep.mReputation ; - info.mFriendAverage = rep.mFriendAverage ; + info.mFriendAverageScore = rep.mFriendAverage ; + info.mFriendsNegativeVotes = rep.mFriendsNegative ; + info.mFriendsPositiveVotes = rep.mFriendsPositive ; if(rep.mOwnerNode.isNull()) rep.mOwnerNode = ownerNode ; @@ -814,33 +816,67 @@ bool p3GxsReputation::getReputationInfo(const RsGxsId& gxsid, const RsPgpId& own owner_id = rep.mOwnerNode ; } + // now compute overall score and reputation + + // 0 - check for own opinion. If positive or negative, it decides on the result + + if(info.mOwnOpinion == RsReputations::OPINION_NEGATIVE) + { + // own opinion is always read in priority + + info.mOverallReputationLevel = RsReputations::REPUTATION_LOCALLY_NEGATIVE ; + return true ; + } + if(info.mOwnOpinion == RsReputations::OPINION_POSITIVE) + { + // own opinion is always read in priority + + info.mOverallReputationLevel = RsReputations::REPUTATION_LOCALLY_POSITIVE ; + return true ; + } + + // 1 - check for banned PGP ids. + std::map::iterator it2 ; if(!owner_id.isNull() && (it2 = mBannedPgpIds.find(owner_id))!=mBannedPgpIds.end()) { + // Check if current identity is present in the list of known identities for this banned node. + if(it2->second.known_identities.find(gxsid) == it2->second.known_identities.end()) { it2->second.known_identities.insert(gxsid) ; it2->second.last_activity_TS = now ; + + // if so, update + mBannedNodesProxyNeedsUpdate = true ; } - info.mAssessment = RsReputations::ASSESSMENT_BAD ; #ifdef DEBUG_REPUTATION2 std::cerr << "p3GxsReputations: identity " << gxsid << " is banned because owner node ID " << owner_id << " is banned (found in banned nodes list)." << std::endl; #endif + info.mOverallReputationLevel = RsReputations::REPUTATION_LOCALLY_NEGATIVE ; + return true ; } - else if(mPerNodeBannedIdsProxy.find(gxsid) != mPerNodeBannedIdsProxy.end()) + // also check the proxy + + if(mPerNodeBannedIdsProxy.find(gxsid) != mPerNodeBannedIdsProxy.end()) { #ifdef DEBUG_REPUTATION2 std::cerr << "p3GxsReputations: identity " << gxsid << " is banned because owner node ID " << owner_id << " is banned (found in proxy)." << std::endl; #endif - info.mAssessment = RsReputations::ASSESSMENT_BAD ; + info.mOverallReputationLevel = RsReputations::REPUTATION_LOCALLY_NEGATIVE ; + return true; } - else if(info.mOverallReputationScore <= mAutoBanIdentitiesLimit) - info.mAssessment = RsReputations::ASSESSMENT_BAD ; + // 2 - now, our own opinion is neutral, which means we rely on what our friends tell + + if(info.mFriendsPositiveVotes > info.mFriendsNegativeVotes) + info.mOverallReputationLevel = RsReputations::REPUTATION_REMOTELY_POSITIVE ; + else if(info.mFriendsPositiveVotes < info.mFriendsNegativeVotes) + info.mOverallReputationLevel = RsReputations::REPUTATION_REMOTELY_NEGATIVE ; else - info.mAssessment = RsReputations::ASSESSMENT_OK ; + info.mOverallReputationLevel = RsReputations::REPUTATION_NEUTRAL ; #ifdef DEBUG_REPUTATION2 std::cerr << " information present. OwnOp = " << info.mOwnOpinion << ", owner node=" << owner_id << ", overall score=" << info.mAssessment << std::endl; @@ -887,7 +923,7 @@ bool p3GxsReputation::isIdentityBanned(const RsGxsId &id) #ifdef DEBUG_REPUTATION std::cerr << "isIdentityBanned(): returning " << (info.mAssessment == RsReputations::ASSESSMENT_BAD) << " for GXS id " << id << std::endl; #endif - return info.mAssessment == RsReputations::ASSESSMENT_BAD ; + return info.mOverallReputationLevel == RsReputations::REPUTATION_LOCALLY_NEGATIVE ; } bool p3GxsReputation::setOwnOpinion(const RsGxsId& gxsid, const RsReputations::Opinion& opinion) @@ -1292,11 +1328,22 @@ void Reputation::updateReputation() int friend_total = 0; + mFriendsNegative = 0 ; + mFriendsPositive = 0 ; + // accounts for all friends. Neutral opinions count for 1-1=0 // because the average is performed over only accessible peers (not the total number) we need to shift to 1 for(std::map::const_iterator it(mOpinions.begin());it!=mOpinions.end();++it) + { + if(it->second == RsReputations::OPINION_NEGATIVE) + ++mFriendsNegative ; + + if(it->second == RsReputations::OPINION_POSITIVE) + ++mFriendsPositive ; + friend_total += it->second - 1; + } if(mOpinions.empty()) // includes the case of no friends! mFriendAverage = 1.0f ; @@ -1351,9 +1398,9 @@ void Reputation::updateReputation() // now compute a bias for PGP-signed ids. if(mOwnOpinion == RsReputations::OPINION_NEUTRAL) - mReputation = mFriendAverage ; + mReputationScore = mFriendAverage ; else - mReputation = (float)mOwnOpinion ; + mReputationScore = (float)mOwnOpinion ; } void p3GxsReputation::debug_print() @@ -1366,7 +1413,7 @@ void p3GxsReputation::debug_print() for(std::map::const_iterator it(mReputations.begin());it!=mReputations.end();++it) { - std::cerr << " " << it->first << ": own: " << it->second.mOwnOpinion << ", Friend average: " << it->second.mFriendAverage << ", global_score: " << it->second.mReputation + std::cerr << " " << it->first << ": own: " << it->second.mOwnOpinion << ", Friend average: " << it->second.mFriendAverage << ", global_score: " << it->second.mReputationScore << ", last own update: " << now - it->second.mOwnOpinionTs << " secs ago." << std::endl; #ifdef DEBUG_REPUTATION2 for(std::map::const_iterator it2(it->second.mOpinions.begin());it2!=it->second.mOpinions.end();++it2) diff --git a/libretroshare/src/services/p3gxsreputation.h b/libretroshare/src/services/p3gxsreputation.h index 94fb356e1..8016ed48d 100644 --- a/libretroshare/src/services/p3gxsreputation.h +++ b/libretroshare/src/services/p3gxsreputation.h @@ -69,10 +69,10 @@ class Reputation { public: Reputation() - :mOwnOpinion(RsReputations::OPINION_NEUTRAL), mOwnOpinionTs(0),mFriendAverage(1.0f), mReputation(RsReputations::OPINION_NEUTRAL),mIdentityFlags(REPUTATION_IDENTITY_FLAG_NEEDS_UPDATE){ } + :mOwnOpinion(RsReputations::OPINION_NEUTRAL), mOwnOpinionTs(0),mFriendAverage(1.0f), mReputationScore(RsReputations::OPINION_NEUTRAL),mIdentityFlags(REPUTATION_IDENTITY_FLAG_NEEDS_UPDATE){ } Reputation(const RsGxsId& /*about*/) - :mOwnOpinion(RsReputations::OPINION_NEUTRAL), mOwnOpinionTs(0),mFriendAverage(1.0f), mReputation(RsReputations::OPINION_NEUTRAL),mIdentityFlags(REPUTATION_IDENTITY_FLAG_NEEDS_UPDATE){ } + :mOwnOpinion(RsReputations::OPINION_NEUTRAL), mOwnOpinionTs(0),mFriendAverage(1.0f), mReputationScore(RsReputations::OPINION_NEUTRAL),mIdentityFlags(REPUTATION_IDENTITY_FLAG_NEEDS_UPDATE){ } void updateReputation(); @@ -80,12 +80,15 @@ public: int32_t mOwnOpinion; time_t mOwnOpinionTs; - float mFriendAverage ; - float mReputation; - - RsPgpId mOwnerNode; + float mFriendAverage ; + uint32_t mFriendsPositive ; // number of positive vites from friends + uint32_t mFriendsNegative ; // number of negative vites from friends + + float mReputationScore; + + RsPgpId mOwnerNode; - uint32_t mIdentityFlags; + uint32_t mIdentityFlags; }; diff --git a/libretroshare/src/services/p3idservice.cc b/libretroshare/src/services/p3idservice.cc index 72cd3231e..d0154be6e 100644 --- a/libretroshare/src/services/p3idservice.cc +++ b/libretroshare/src/services/p3idservice.cc @@ -607,15 +607,12 @@ bool p3IdService::getIdDetails(const RsGxsId &id, RsIdentityDetails &details) return false; } -bool p3IdService::isBanned(const RsGxsId &id) +RsReputations::ReputationLevel p3IdService::overallReputationLevel(const RsGxsId &id) { RsIdentityDetails det ; getIdDetails(id,det) ; -#ifdef DEBUG_REPUTATION - std::cerr << "isIdentityBanned(): returning " << (det.mReputation.mAssessment == RsReputations::ASSESSMENT_BAD) << " for GXS id " << id << std::endl; -#endif - return det.mReputation.mAssessment == RsReputations::ASSESSMENT_BAD ; + return det.mReputation.mOverallReputationLevel ; } bool p3IdService::isOwnId(const RsGxsId& id) @@ -810,7 +807,7 @@ bool p3IdService::requestKey(const RsGxsId &id, const std::list& peers RsReputations::ReputationInfo info ; rsReputations->getReputationInfo(id,RsPgpId(),info) ; - if(info.mAssessment == RsReputations::ASSESSMENT_BAD) + if(info.mOverallReputationLevel == RsReputations::REPUTATION_LOCALLY_NEGATIVE) { std::cerr << "(II) not requesting Key " << id << " because it has been banned." << std::endl; diff --git a/libretroshare/src/services/p3idservice.h b/libretroshare/src/services/p3idservice.h index 5ca5d1c75..638a9d2e0 100644 --- a/libretroshare/src/services/p3idservice.h +++ b/libretroshare/src/services/p3idservice.h @@ -274,7 +274,7 @@ public: virtual bool setAsRegularContact(const RsGxsId& id,bool is_a_contact) ; virtual bool isARegularContact(const RsGxsId& id) ; - virtual bool isBanned(const RsGxsId& id) ; + virtual RsReputations::ReputationLevel overallReputationLevel(const RsGxsId& id); virtual time_t getLastUsageTS(const RsGxsId &id) ; /**************** RsGixs Implementation ***************/ diff --git a/retroshare-gui/src/gui/Identity/IdDetailsDialog.cpp b/retroshare-gui/src/gui/Identity/IdDetailsDialog.cpp index f1419b650..a988a8563 100644 --- a/retroshare-gui/src/gui/Identity/IdDetailsDialog.cpp +++ b/retroshare-gui/src/gui/Identity/IdDetailsDialog.cpp @@ -264,10 +264,9 @@ void IdDetailsDialog::insertIdDetails(uint32_t token) RsReputations::ReputationInfo info ; rsReputations->getReputationInfo(RsGxsId(data.mMeta.mGroupId),data.mPgpId,info) ; - ui->neighborNodesOpinion_TF->setText(QString::number(info.mOverallReputationScore-1.0f)); - - ui->overallOpinion_TF->setText(QString::number(info.mOverallReputationScore-1.0f) +" ("+ - ((info.mAssessment == RsReputations::ASSESSMENT_OK)? tr("OK") : tr("Banned")) +")" ) ; +#warning (csoler) Do we need to do this? This code is apparently not used. + ui->neighborNodesOpinion_TF->setText(QString::number(info.mFriendAverageScore)); + ui->overallOpinion_TF->setText(QString::number(info.mOverallReputationLevel)); switch(info.mOwnOpinion) { diff --git a/retroshare-gui/src/gui/Identity/IdDialog.cpp b/retroshare-gui/src/gui/Identity/IdDialog.cpp index 5f7a20010..67025fff5 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.cpp +++ b/retroshare-gui/src/gui/Identity/IdDialog.cpp @@ -1392,7 +1392,8 @@ bool IdDialog::fillIdListItem(const RsGxsIdGroup& data, QTreeWidgetItem *&item, bool isOwnId = (data.mMeta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_ADMIN); RsIdentityDetails idd ; rsIdentity->getIdDetails(RsGxsId(data.mMeta.mGroupId),idd) ; - bool isBanned = idd.mReputation.mAssessment == RsReputations::ASSESSMENT_BAD; + + bool isBanned = idd.mReputation.mOverallReputationLevel == RsReputations::REPUTATION_LOCALLY_NEGATIVE; uint32_t item_flags = 0 ; /* do filtering */ @@ -1462,7 +1463,7 @@ bool IdDialog::fillIdListItem(const RsGxsIdGroup& data, QTreeWidgetItem *&item, item->setData(RSID_COL_KEYID, Qt::UserRole,QVariant(item_flags)) ; item->setTextAlignment(RSID_COL_VOTES, Qt::AlignRight | Qt::AlignVCenter); - item->setData(RSID_COL_VOTES,Qt::DisplayRole, QString::number(idd.mReputation.mOverallReputationScore - 1.0f,'f',3)); + item->setData(RSID_COL_VOTES,Qt::DisplayRole, idd.mReputation.mOverallReputationLevel); if(isOwnId) { @@ -1836,10 +1837,24 @@ void IdDialog::insertIdDetails(uint32_t token) RsReputations::ReputationInfo info ; rsReputations->getReputationInfo(RsGxsId(data.mMeta.mGroupId),data.mPgpId,info) ; - ui->neighborNodesOpinion_TF->setText(QString::number(info.mFriendAverage - 1.0f)); + QString frep_string ; + if(info.mFriendsPositiveVotes > 0) frep_string += QString::number(info.mFriendsPositiveVotes) + tr(" positive, ") ; + if(info.mFriendsNegativeVotes > 0) frep_string += QString::number(info.mFriendsNegativeVotes) + tr(" negative, ") ; - ui->overallOpinion_TF->setText(QString::number(info.mOverallReputationScore - 1.0f) +" ("+ - ((info.mAssessment == RsReputations::ASSESSMENT_OK)? tr("OK") : tr("Banned")) +")" ) ; + if(info.mFriendsPositiveVotes==0 && info.mFriendsNegativeVotes==0) + frep_string = tr("No votes from friends, ") ; + + ui->neighborNodesOpinion_TF->setText(frep_string) ; + + switch(info.mOverallReputationLevel) + { + case RsReputations::REPUTATION_LOCALLY_POSITIVE: ui->overallOpinion_TF->setText(tr("Positive")) ; break ; + case RsReputations::REPUTATION_LOCALLY_NEGATIVE: ui->overallOpinion_TF->setText(tr("Negative (Banned)")) ; break ; + case RsReputations::REPUTATION_REMOTELY_POSITIVE: ui->overallOpinion_TF->setText(tr("Positive, according to your friends")) ; break ; + case RsReputations::REPUTATION_REMOTELY_NEGATIVE: ui->overallOpinion_TF->setText(tr("Negative, according to your friends")) ; break ; + default: + case RsReputations::REPUTATION_NEUTRAL: ui->overallOpinion_TF->setText(tr("Neutral")) ; break ; + } switch(info.mOwnOpinion) { diff --git a/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp b/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp index 5c7d00dd7..3fee9346c 100644 --- a/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp +++ b/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp @@ -904,7 +904,7 @@ bool GxsIdDetails::MakeIdDesc(const RsGxsId &id, bool doIcons, QString &str, QLi QString GxsIdDetails::getName(const RsIdentityDetails &details) { - if(rsIdentity->isBanned(details.mId)) + if(details.mReputation.mOverallReputationLevel == RsReputations::REPUTATION_LOCALLY_NEGATIVE) return tr("[Banned]") ; QString name = QString::fromUtf8(details.mNickname.c_str()).left(RSID_MAXIMUM_NICKNAME_SIZE); @@ -923,7 +923,7 @@ QString GxsIdDetails::getComment(const RsIdentityDetails &details) QString comment; QString nickname ; - bool banned = rsIdentity->isBanned(details.mId) ; + bool banned = (details.mReputation.mOverallReputationLevel == RsReputations::REPUTATION_LOCALLY_NEGATIVE); if(details.mNickname.empty()) nickname = tr("[Unknown]") ; @@ -962,7 +962,7 @@ void GxsIdDetails::getIcons(const RsIdentityDetails &details, QList &icon { QPixmap pix ; - if(rsIdentity->isBanned(details.mId)) + if(details.mReputation.mOverallReputationLevel == RsReputations::REPUTATION_LOCALLY_NEGATIVE) { icons.clear() ; icons.push_back(QIcon(IMAGE_BANNED)) ; diff --git a/retroshare-gui/src/gui/gxs/GxsIdTreeWidgetItem.cpp b/retroshare-gui/src/gui/gxs/GxsIdTreeWidgetItem.cpp index 0177f892f..aa5edc91e 100644 --- a/retroshare-gui/src/gui/gxs/GxsIdTreeWidgetItem.cpp +++ b/retroshare-gui/src/gui/gxs/GxsIdTreeWidgetItem.cpp @@ -116,14 +116,14 @@ void GxsIdRSTreeWidgetItem::setId(const RsGxsId &id, int column, bool retryWhenF void GxsIdRSTreeWidgetItem::updateBannedState() { - if(mBannedState != rsIdentity->isBanned(mId)) + if(mBannedState != (rsIdentity->overallReputationLevel(mId) == RsReputations::REPUTATION_LOCALLY_NEGATIVE)) forceUpdate() ; } void GxsIdRSTreeWidgetItem::forceUpdate() { mIdFound = false; - mBannedState = rsIdentity->isBanned(mId) ; + mBannedState = (rsIdentity->overallReputationLevel(mId) == RsReputations::REPUTATION_LOCALLY_NEGATIVE); startProcess(); } @@ -169,7 +169,7 @@ QVariant GxsIdRSTreeWidgetItem::data(int column, int role) const if(mId.isNull()) return RSTreeWidgetItem::data(column, role); - else if(rsIdentity->isBanned(mId)) + else if(rsIdentity->overallReputationLevel(mId) == RsReputations::REPUTATION_LOCALLY_NEGATIVE) pix = QImage(BANNED_IMAGE) ; else if (mAvatar.mSize == 0 || !pix.loadFromData(mAvatar.mData, mAvatar.mSize, "PNG")) pix = GxsIdDetails::makeDefaultIcon(mId); diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index 8dea116fe..8bf70fe48 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -996,13 +996,13 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum // Early check for a message that should be hidden because its author // is flagged with a bad reputation + uint32_t reputation_level = rsIdentity->overallReputationLevel(msg.mMeta.mAuthorId) ; - bool redacted = rsIdentity->isBanned(msg.mMeta.mAuthorId) ; + bool redacted = (reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE) ; GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(mThreadCompareRole,GxsIdDetails::ICON_TYPE_ALL || (redacted?(GxsIdDetails::ICON_TYPE_REDACTED):0)); item->moveToThread(ui->threadTreeWidget->thread()); - if(redacted) item->setText(COLUMN_THREAD_TITLE, tr("[ ... Redacted message ... ]")); else @@ -1431,7 +1431,8 @@ void GxsForumThreadWidget::insertMessageData(const RsGxsForumMsg &msg) return; } - bool redacted = rsIdentity->isBanned(msg.mMeta.mAuthorId) ; + uint32_t overall_reputation = rsIdentity->overallReputationLevel(msg.mMeta.mAuthorId) ; + bool redacted = (overall_reputation == RsReputations::REPUTATION_LOCALLY_NEGATIVE) ; mStateHelper->setActive(mTokenTypeMessageData, true); From e5e59098ac3a09f208d85bb6e4b54e5a59c6a007 Mon Sep 17 00:00:00 2001 From: csoler Date: Fri, 23 Dec 2016 22:53:57 +0100 Subject: [PATCH 06/23] added proper display of reputation as an icon in IDDialog --- retroshare-gui/src/gui/Identity/IdDialog.cpp | 69 +++- retroshare-gui/src/gui/Identity/IdDialog.ui | 2 +- retroshare-gui/src/gui/icons.qrc | 366 +++++++++++------- .../icons/bullet_green_yellow_star_128.png | Bin 0 -> 15443 bytes .../src/gui/icons/red_biohazard64.png | Bin 0 -> 10084 bytes .../src/gui/icons/yellow_biohazard64.png | Bin 10182 -> 10182 bytes 6 files changed, 287 insertions(+), 150 deletions(-) create mode 100644 retroshare-gui/src/gui/icons/bullet_green_yellow_star_128.png create mode 100644 retroshare-gui/src/gui/icons/red_biohazard64.png diff --git a/retroshare-gui/src/gui/Identity/IdDialog.cpp b/retroshare-gui/src/gui/Identity/IdDialog.cpp index 67025fff5..636329ebd 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.cpp +++ b/retroshare-gui/src/gui/Identity/IdDialog.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include "IdDialog.h" #include "ui_IdDialog.h" @@ -104,6 +106,12 @@ #define IMAGE_ADMIN ":/icons/bullet_blue_128.png" #define IMAGE_INFO ":/images/info16.png" +#define REPUTATION_LOCALLY_POSITIVE_ICON ":/icons/bullet_green_yellow_star_128.png" +#define REPUTATION_REMOTELY_POSITIVE_ICON ":/icons/bullet_green_128.png" +#define REPUTATION_NEUTRAL_ICON ":/icons/bullet_grey_128.png" +#define REPUTATION_REMOTELY_NEGATIVE_ICON ":/icons/yellow_biohazard64.png" +#define REPUTATION_LOCALLY_NEGATIVE_ICON ":/icons/red_biohazard64.png" + // comment this out in order to remove the sorting of circles into "belong to" and "other visible circles" #define CIRCLE_MEMBERSHIP_CATEGORIES 1 @@ -127,6 +135,54 @@ class TreeWidgetItem : public QTreeWidgetItem { } } }; + +// This class allows to draw the item in the share flags column using an appropriate size + +class ReputationItemDelegate: public QStyledItemDelegate +{ +public: + ReputationItemDelegate() {} + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_ASSERT(index.isValid()); + + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + // disable default icon + opt.icon = QIcon(); + // draw default item + QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, 0); + + const QRect r = option.rect; + + // get pixmap + unsigned int icon_index = qvariant_cast(index.data(Qt::DecorationRole)); + + if(icon_index > 4) + return ; + + QIcon icon ; + + switch(icon_index) + { + case RsReputations::REPUTATION_LOCALLY_NEGATIVE: icon = QIcon(REPUTATION_LOCALLY_NEGATIVE_ICON) ; break ; + case RsReputations::REPUTATION_LOCALLY_POSITIVE: icon = QIcon(REPUTATION_LOCALLY_POSITIVE_ICON) ; break ; + case RsReputations::REPUTATION_REMOTELY_POSITIVE: icon = QIcon(REPUTATION_REMOTELY_POSITIVE_ICON) ; break ; + case RsReputations::REPUTATION_REMOTELY_NEGATIVE: icon = QIcon(REPUTATION_REMOTELY_NEGATIVE_ICON) ; break ; + case RsReputations::REPUTATION_NEUTRAL: icon = QIcon(REPUTATION_NEUTRAL_ICON) ; break ; + default: + return ; // dont draw anything + } + + QPixmap pix = icon.pixmap(r.size()); + + // draw pixmap at center of item + const QPoint p = QPoint((r.width() - pix.width())/2, (r.height() - pix.height())/2); + painter->drawPixmap(r.topLeft() + p, pix); + } +}; + /** Constructor */ IdDialog::IdDialog(QWidget *parent) : RsGxsUpdateBroadcastPage(rsIdentity, parent), @@ -142,12 +198,15 @@ IdDialog::IdDialog(QWidget *parent) : ownItem = new QTreeWidgetItem(); ownItem->setText(0, tr("My own identities")); + ownItem->setData(RSID_COL_VOTES, Qt::DecorationRole,0xff); allItem = new QTreeWidgetItem(); allItem->setText(0, tr("All")); + allItem->setData(RSID_COL_VOTES, Qt::DecorationRole,0xff); contactsItem = new QTreeWidgetItem(); contactsItem->setText(0, tr("My contacts")); + contactsItem->setData(RSID_COL_VOTES, Qt::DecorationRole,0xff); ui->treeWidget_membership->clear(); @@ -318,6 +377,8 @@ IdDialog::IdDialog(QWidget *parent) : ui->idTreeWidget->setColumnWidth(RSID_COL_IDTYPE, 18 * fontWidth); ui->idTreeWidget->setColumnWidth(RSID_COL_VOTES, 7 * fontWidth); + ui->idTreeWidget->setItemDelegateForColumn(RSID_COL_VOTES,new ReputationItemDelegate()) ; + //QHeaderView_setSectionResizeMode(ui->idTreeWidget->header(), QHeaderView::ResizeToContents); mIdQueue = new TokenQueue(rsIdentity->getTokenService(), this); @@ -1463,7 +1524,7 @@ bool IdDialog::fillIdListItem(const RsGxsIdGroup& data, QTreeWidgetItem *&item, item->setData(RSID_COL_KEYID, Qt::UserRole,QVariant(item_flags)) ; item->setTextAlignment(RSID_COL_VOTES, Qt::AlignRight | Qt::AlignVCenter); - item->setData(RSID_COL_VOTES,Qt::DisplayRole, idd.mReputation.mOverallReputationLevel); + item->setData(RSID_COL_VOTES,Qt::DecorationRole, idd.mReputation.mOverallReputationLevel); if(isOwnId) { @@ -1838,11 +1899,11 @@ void IdDialog::insertIdDetails(uint32_t token) rsReputations->getReputationInfo(RsGxsId(data.mMeta.mGroupId),data.mPgpId,info) ; QString frep_string ; - if(info.mFriendsPositiveVotes > 0) frep_string += QString::number(info.mFriendsPositiveVotes) + tr(" positive, ") ; - if(info.mFriendsNegativeVotes > 0) frep_string += QString::number(info.mFriendsNegativeVotes) + tr(" negative, ") ; + if(info.mFriendsPositiveVotes > 0) frep_string += QString::number(info.mFriendsPositiveVotes) + tr(" positive ") ; + if(info.mFriendsNegativeVotes > 0) frep_string += QString::number(info.mFriendsNegativeVotes) + tr(" negative ") ; if(info.mFriendsPositiveVotes==0 && info.mFriendsNegativeVotes==0) - frep_string = tr("No votes from friends, ") ; + frep_string = tr("No votes from friends") ; ui->neighborNodesOpinion_TF->setText(frep_string) ; diff --git a/retroshare-gui/src/gui/Identity/IdDialog.ui b/retroshare-gui/src/gui/Identity/IdDialog.ui index 3dceaa373..57fbc3649 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.ui +++ b/retroshare-gui/src/gui/Identity/IdDialog.ui @@ -492,7 +492,7 @@ - Neighbor nodes: + Friend votes: diff --git a/retroshare-gui/src/gui/icons.qrc b/retroshare-gui/src/gui/icons.qrc index c2506c851..1a63b784d 100644 --- a/retroshare-gui/src/gui/icons.qrc +++ b/retroshare-gui/src/gui/icons.qrc @@ -1,147 +1,223 @@ - - icons/svg/profile.svg - icons/svg/download.svg - icons/svg/folders.svg - icons/svg/folders1.svg - icons/svg/magnifying-glass.svg - icons/svg/upload.svg - icons/settings/appearance.svg - icons/settings/channels.svg - icons/settings/chat.svg - icons/settings/directories.svg - icons/settings/filesharing.svg - icons/settings/forums.svg - icons/settings/general.svg - icons/settings/messages.svg - icons/settings/network.svg - icons/settings/notify.svg - icons/settings/people.svg - icons/settings/permissions.svg - icons/settings/plugins.svg - icons/settings/posted.svg - icons/settings/profile.svg - icons/settings/server.svg - icons/settings/sound.svg - icons/settings/webinterface.svg - icons/add_user_256.png - icons/aol.png - icons/avatar_128.png - icons/avatar_grey_128.png - icons/blank_red_128.png - icons/void_128.png - icons/blank_green_128.png - icons/blank_blue_128.png - icons/browsable_green_128.png - icons/search_red_128.png - icons/anonymous_blue_128.png - icons/bullet_blue_128.png - icons/bullet_green_128.png - icons/bullet_grey_128.png - icons/bullet_red_128.png - icons/bullet_yellow_128.png - icons/channels_128.png - icons/channels_red_128.png - icons/chat_128.png - icons/chat_red_128.png - icons/circles_128.png - icons/circles_new_128.png - icons/friends_128.png - icons/global_switch_off_128.png - icons/global_switch_on_128.png - icons/gmail.png - icons/help_128.png - icons/help_64.png - icons/information_128.png - icons/internet_128.png - icons/invite64.png - icons/knews_128.png - icons/knews_red_128.png - icons/konversation_128.png - icons/konversation128.png - icons/konversation_red_128.png - icons/ktorrent_128.png - icons/ktorrent_red_128.png - icons/logo_0_connected_128.png - icons/logo_128.png - icons/logo_1_connected_128.png - icons/logo_2_connected_128.png - icons/mail_128.png - icons/mail_old_128.png - icons/mail_red_128.png - icons/newsfeed128.png - icons/outlook.png - icons/plugins_128.png - icons/posted_128.png - icons/posted_red_128.png - icons/quit_128.png - icons/security_high_128.png - icons/security_low_128.png - icons/security_medium_128.png - icons/star_overlay_128.png - icons/switch00_128.png - icons/switch01_128.png - icons/switch10_128.png - icons/switch11_128.png - icons/system_128.png - icons/tile_checking_48.png - icons/tile_downloaded_48.png - icons/tile_downloading_48.png - icons/tile_inactive_48.png - icons/tor-logo.png - icons/tor-off.png - icons/tor-on.png - icons/tor-starting.png - icons/tor-stopping.png - icons/user-away_64.png - icons/user-away-extended_64.png - icons/user-busy_64.png - icons/user-offline_64.png - icons/user-online_64.png - icons/yahoo.png - icons/yandex.png - icons/yellow_biohazard64.png - icons/png/attach.png - icons/png/attach-image.png - icons/png/highlight.png - icons/png/invite.png - icons/png/leave.png - icons/png/search.png - icons/png/send-message.png - icons/png/settings.png - icons/png/smiley.png - icons/png/font.png - icons/png/send-message-blocked.png - icons/png/chat-bubble-notify.png - icons/png/channels.png - icons/png/chat-lobbies.png - icons/png/forums.png - icons/png/info.png - icons/png/messages.png - icons/png/network.png - icons/png/newsfeed.png - icons/png/people.png - icons/png/posted.png - icons/png/exit.png - icons/png/options.png - icons/png/filesharing.png - icons/png/channels-notify.png - icons/png/chat-lobbies-notify.png - icons/png/forums-notify.png - icons/png/messages-notify.png - icons/png/network-notify.png - icons/png/newsfeed-notify.png - icons/png/people-notify.png - icons/png/posted-notify.png - icons/png/filesharing-notify.png - icons/png/thumbs-up.png - icons/png/thumbs-down.png - icons/png/thumbs-neutral.png - icons/png/add.png - icons/png/netgraph.png - icons/png/network-puplic.png - icons/png/circles.png - icons/png/person.png - icons/png/keyring.png - + + icons/add_user_256.png + icons/anonymous_blue_128.png + icons/anonymous_green_128.png + icons/aol.png + icons/avatar_128.png + icons/avatar_grey_128.png + icons/blank_blue_128.png + icons/blank_green_128.png + icons/blank_red_128.png + icons/browsable_blue_128.png + icons/browsable_green_128.png + icons/bullet_blue_128.png + icons/bullet_green_128.png + icons/bullet_green_yellow_star_128.png + icons/bullet_grey_128.png + icons/bullet_red_128.png + icons/bullet_yellow_128.png + icons/channels_128.png + icons/channels_red_128.png + icons/chat_128.png + icons/chat_red_128.png + icons/circles_128.png + icons/circles_new_128.png + icons/friends_128.png + icons/global_switch_off_128.png + icons/global_switch_on_128.png + icons/gmail.png + icons/help_128.png + icons/help_64.png + icons/information_128.png + icons/internet_128.png + icons/invite64.png + icons/knews_128.png + icons/knews_red_128.png + icons/konversation_128.png + icons/konversation128.png + icons/konversation_red_128.png + icons/ktorrent_128.png + icons/ktorrent_red_128.png + icons/logo_0_connected_128.png + icons/logo_128.png + icons/logo_1_connected_128.png + icons/logo_2_connected_128.png + icons/mail_128.png + icons/mail_old_128.png + icons/mail_red_128.png + icons/newsfeed128.png + icons/outlook.png + icons/plugins_128.png + icons/png/add.png + icons/png/attach-image.png + icons/png/attach.png + icons/png/channels-notify.png + icons/png/channels.png + icons/png/chat-bubble-notify.png + icons/png/chat-bubble.png + icons/png/chat-lobbies-notify.png + icons/png/chat-lobbies.png + icons/png/circles.png + icons/png/empty-circle.png + icons/png/exit.png + icons/png/feedreader-notify.png + icons/png/feedreader.png + icons/png/filesharing-notify.png + icons/png/filesharing.png + icons/png/font.png + icons/png/forums-notify.png + icons/png/forums.png + icons/png/fullscreen_arrows.png + icons/png/highlight.png + icons/png/info.png + icons/png/invite.png + icons/png/keyring.png + icons/png/leave.png + icons/png/messages-notify.png + icons/png/messages.png + icons/png/microphone_mute.png + icons/png/microphone.png + icons/png/netgraph.png + icons/png/network-notify.png + icons/png/network.png + icons/png/network-puplic.png + icons/png/newsfeed-notify.png + icons/png/newsfeed.png + icons/png/options.png + icons/png/people-notify.png + icons/png/people.png + icons/png/person.png + icons/png/phone_hangup.png + icons/png/phone.png + icons/png/posted-notify.png + icons/png/posted.png + icons/png/search.png + icons/png/send-message-blocked.png + icons/png/send-message.png + icons/png/settings.png + icons/png/smiley.png + icons/png/speaker_mute.png + icons/png/speaker.png + icons/png/thumbs-down.png + icons/png/thumbs-neutral.png + icons/png/thumbs-up.png + icons/png/video.png + icons/posted_128.png + icons/posted_red_128.png + icons/quit_128.png + icons/red_biohazard64.png + icons/search_red_128.png + icons/security_high_128.png + icons/security_low_128.png + icons/security_medium_128.png + icons/settings/appearance.svg + icons/settings/channels.svg + icons/settings/chat.svg + icons/settings/directories.svg + icons/settings/filesharing.svg + icons/settings/forums.svg + icons/settings/general.svg + icons/settings/messages.svg + icons/settings/network.svg + icons/settings/notify.svg + icons/settings/people.svg + icons/settings/permissions.svg + icons/settings/plugins.svg + icons/settings/posted.svg + icons/settings/profile.svg + icons/settings/server.svg + icons/settings/sound.svg + icons/settings/webinterface.svg + icons/star_overlay_128.png + icons/svg/add.svg + icons/svg/attach-image.svg + icons/svg/attach.svg + icons/svg/channels-notify.svg + icons/svg/channels.svg + icons/svg/chat-bubble-notify.svg + icons/svg/chat-bubble.svg + icons/svg/chat-lobbies-notify.svg + icons/svg/chat-lobbies.svg + icons/svg/circles.svg + icons/svg/download.svg + icons/svg/empty-circle.svg + icons/svg/exit-red.svg + icons/svg/exit.svg + icons/svg/feedreader-notify.svg + icons/svg/feedreader.svg + icons/svg/filesharing-notify.svg + icons/svg/filesharing.svg + icons/svg/folders1.svg + icons/svg/folders.svg + icons/svg/font.svg + icons/svg/forums-notify.svg + icons/svg/forums.svg + icons/svg/fullscreen_arrows.svg + icons/svg/highlight.svg + icons/svg/info.svg + icons/svg/invite.svg + icons/svg/keyring.svg + icons/svg/leave.svg + icons/svg/magnifying-glass.svg + icons/svg/messages-notify.svg + icons/svg/messages.svg + icons/svg/microphone_mute.svg + icons/svg/microphone.svg + icons/svg/netgraph.svg + icons/svg/network-notify.svg + icons/svg/network-puplic.svg + icons/svg/network.svg + icons/svg/newsfeed-notify.svg + icons/svg/newsfeed.svg + icons/svg/options.svg + icons/svg/people-notify.svg + icons/svg/people.svg + icons/svg/person.svg + icons/svg/phone_hangup.svg + icons/svg/phone.svg + icons/svg/posted-notify.svg + icons/svg/posted.svg + icons/svg/profile.svg + icons/svg/retroshare-info.svg + icons/svg/retroshare-splash.svg + icons/svg/retroshare-symbol_black_bg.svg + icons/svg/retroshare-symbol_gradients.svg + icons/svg/retroshare-symbol-minimal.svg + icons/svg/search.svg + icons/svg/send-message-blocked.svg + icons/svg/send-message.svg + icons/svg/settings.svg + icons/svg/smiley.svg + icons/svg/speaker_mute.svg + icons/svg/speaker.svg + icons/svg/thumbs-down.svg + icons/svg/thumbs-neutral.svg + icons/svg/thumbs-up.svg + icons/svg/upload.svg + icons/svg/video.svg + icons/switch00_128.png + icons/switch01_128.png + icons/switch10_128.png + icons/switch11_128.png + icons/system_128.png + icons/tile_checking_48.png + icons/tile_downloaded_48.png + icons/tile_downloading_48.png + icons/tile_inactive_48.png + icons/tor-logo.png + icons/tor-off.png + icons/tor-on.png + icons/tor-starting.png + icons/tor-stopping.png + icons/user-away_64.png + icons/user-away-extended_64.png + icons/user-busy_64.png + icons/user-offline_64.png + icons/user-online_64.png + icons/void_128.png + icons/yahoo.png + icons/yandex.png + icons/yellow_biohazard64.png + diff --git a/retroshare-gui/src/gui/icons/bullet_green_yellow_star_128.png b/retroshare-gui/src/gui/icons/bullet_green_yellow_star_128.png new file mode 100644 index 0000000000000000000000000000000000000000..2b49ec38414d5238c6cc21e3d634b0a0c22550c5 GIT binary patch literal 15443 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 JPDHLkV1oRb(K7%5 literal 0 HcmV?d00001 diff --git a/retroshare-gui/src/gui/icons/red_biohazard64.png b/retroshare-gui/src/gui/icons/red_biohazard64.png new file mode 100644 index 0000000000000000000000000000000000000000..eacbf9f8d3506cec2841648f9fc5fc9b8490fd24 GIT binary patch literal 10084 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 literal 0 HcmV?d00001 diff --git a/retroshare-gui/src/gui/icons/yellow_biohazard64.png b/retroshare-gui/src/gui/icons/yellow_biohazard64.png index edb6fb3f24b36b67d7267e4d0d83fdd0bbcdc876..09accbf83969471ade76ae2ef9ea0aba2f77984a 100644 GIT binary patch delta 23 fcmX@+f6RZw8ZNE}JmR9P7IV$yTsLpy5|9M|Y%~YP delta 23 fcmX@+f6RZw8ZNH;T%2Njns??|25#QQB_Im`ai|D> From 873c0e60df3f6c7872f7ba8a5def6562d5605ed7 Mon Sep 17 00:00:00 2001 From: csoler Date: Sat, 24 Dec 2016 14:13:02 +0100 Subject: [PATCH 07/23] added display of reputation in forums --- retroshare-gui/src/gui/Identity/IdDialog.cpp | 21 ++-------------- .../src/gui/gxs/GxsCommentTreeWidget.cpp | 2 +- retroshare-gui/src/gui/gxs/GxsIdDetails.cpp | 25 +++++++++++++++++++ retroshare-gui/src/gui/gxs/GxsIdDetails.h | 11 ++++---- .../src/gui/gxs/GxsIdTreeWidgetItem.cpp | 6 ----- .../src/gui/gxs/GxsIdTreeWidgetItem.h | 3 +-- .../gui/gxsforums/GxsForumThreadWidget.cpp | 4 +-- 7 files changed, 37 insertions(+), 35 deletions(-) diff --git a/retroshare-gui/src/gui/Identity/IdDialog.cpp b/retroshare-gui/src/gui/Identity/IdDialog.cpp index 636329ebd..584c71d33 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.cpp +++ b/retroshare-gui/src/gui/Identity/IdDialog.cpp @@ -106,12 +106,6 @@ #define IMAGE_ADMIN ":/icons/bullet_blue_128.png" #define IMAGE_INFO ":/images/info16.png" -#define REPUTATION_LOCALLY_POSITIVE_ICON ":/icons/bullet_green_yellow_star_128.png" -#define REPUTATION_REMOTELY_POSITIVE_ICON ":/icons/bullet_green_128.png" -#define REPUTATION_NEUTRAL_ICON ":/icons/bullet_grey_128.png" -#define REPUTATION_REMOTELY_NEGATIVE_ICON ":/icons/yellow_biohazard64.png" -#define REPUTATION_LOCALLY_NEGATIVE_ICON ":/icons/red_biohazard64.png" - // comment this out in order to remove the sorting of circles into "belong to" and "other visible circles" #define CIRCLE_MEMBERSHIP_CATEGORIES 1 @@ -162,18 +156,7 @@ public: if(icon_index > 4) return ; - QIcon icon ; - - switch(icon_index) - { - case RsReputations::REPUTATION_LOCALLY_NEGATIVE: icon = QIcon(REPUTATION_LOCALLY_NEGATIVE_ICON) ; break ; - case RsReputations::REPUTATION_LOCALLY_POSITIVE: icon = QIcon(REPUTATION_LOCALLY_POSITIVE_ICON) ; break ; - case RsReputations::REPUTATION_REMOTELY_POSITIVE: icon = QIcon(REPUTATION_REMOTELY_POSITIVE_ICON) ; break ; - case RsReputations::REPUTATION_REMOTELY_NEGATIVE: icon = QIcon(REPUTATION_REMOTELY_NEGATIVE_ICON) ; break ; - case RsReputations::REPUTATION_NEUTRAL: icon = QIcon(REPUTATION_NEUTRAL_ICON) ; break ; - default: - return ; // dont draw anything - } + QIcon icon = GxsIdDetails::getReputationIcon(RsReputations::ReputationLevel(icon_index)); QPixmap pix = icon.pixmap(r.size()); @@ -198,7 +181,7 @@ IdDialog::IdDialog(QWidget *parent) : ownItem = new QTreeWidgetItem(); ownItem->setText(0, tr("My own identities")); - ownItem->setData(RSID_COL_VOTES, Qt::DecorationRole,0xff); + ownItem->setData(RSID_COL_VOTES, Qt::DecorationRole,0xff); // this is in order to prevent displaying a reputaiton icon next to these items. allItem = new QTreeWidgetItem(); allItem->setText(0, tr("All")); diff --git a/retroshare-gui/src/gui/gxs/GxsCommentTreeWidget.cpp b/retroshare-gui/src/gui/gxs/GxsCommentTreeWidget.cpp index 7631ad23f..2fa1f3b1e 100644 --- a/retroshare-gui/src/gui/gxs/GxsCommentTreeWidget.cpp +++ b/retroshare-gui/src/gui/gxs/GxsCommentTreeWidget.cpp @@ -418,7 +418,7 @@ void GxsCommentTreeWidget::service_loadThread(const uint32_t &token) std::cerr << "GxsCommentTreeWidget::service_loadThread() Got Comment: " << comment.mMeta.mMsgId; std::cerr << std::endl; - GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(NULL) ; + GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(NULL,GxsIdDetails::ICON_TYPE_ALL) ; QString text; { diff --git a/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp b/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp index 3fee9346c..a3f4b680b 100644 --- a/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp +++ b/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp @@ -48,6 +48,12 @@ #define IMAGE_DEV_PATCHER ":/images/tags/dev-patcher.png" #define IMAGE_DEV_DEVELOPER ":/images/tags/developer.png" +#define REPUTATION_LOCALLY_POSITIVE_ICON ":/icons/bullet_green_yellow_star_128.png" +#define REPUTATION_REMOTELY_POSITIVE_ICON ":/icons/bullet_green_128.png" +#define REPUTATION_NEUTRAL_ICON ":/icons/bullet_grey_128.png" +#define REPUTATION_REMOTELY_NEGATIVE_ICON ":/icons/yellow_biohazard64.png" +#define REPUTATION_LOCALLY_NEGATIVE_ICON ":/icons/red_biohazard64.png" + #define TIMER_INTERVAL 500 #define MAX_ATTEMPTS 10 #define MAX_PROCESS_COUNT_PER_TIMER 50 @@ -958,6 +964,21 @@ QString nickname ; return comment; } +QIcon GxsIdDetails::getReputationIcon(RsReputations::ReputationLevel icon_index) +{ + switch(icon_index) + { + case RsReputations::REPUTATION_LOCALLY_NEGATIVE: return QIcon(REPUTATION_LOCALLY_NEGATIVE_ICON) ; break ; + case RsReputations::REPUTATION_LOCALLY_POSITIVE: return QIcon(REPUTATION_LOCALLY_POSITIVE_ICON) ; break ; + case RsReputations::REPUTATION_REMOTELY_POSITIVE: return QIcon(REPUTATION_REMOTELY_POSITIVE_ICON) ; break ; + case RsReputations::REPUTATION_REMOTELY_NEGATIVE: return QIcon(REPUTATION_REMOTELY_NEGATIVE_ICON) ; break ; + case RsReputations::REPUTATION_NEUTRAL: return QIcon(REPUTATION_NEUTRAL_ICON) ; break ; + default: + std::cerr << "Asked for unidentified icon index " << icon_index << std::endl; + return QIcon(); // dont draw anything + } +} + void GxsIdDetails::getIcons(const RsIdentityDetails &details, QList &icons,uint32_t icon_types) { QPixmap pix ; @@ -969,6 +990,10 @@ void GxsIdDetails::getIcons(const RsIdentityDetails &details, QList &icon return ; } + + if(icon_types & ICON_TYPE_REPUTATION) + icons.push_back(getReputationIcon(details.mReputation.mOverallReputationLevel)) ; + if(icon_types & ICON_TYPE_AVATAR) { if(details.mAvatar.mSize == 0 || !pix.loadFromData(details.mAvatar.mData, details.mAvatar.mSize, "PNG")) diff --git a/retroshare-gui/src/gui/gxs/GxsIdDetails.h b/retroshare-gui/src/gui/gxs/GxsIdDetails.h index ef15c5fdd..7199f0565 100644 --- a/retroshare-gui/src/gui/gxs/GxsIdDetails.h +++ b/retroshare-gui/src/gui/gxs/GxsIdDetails.h @@ -50,11 +50,11 @@ class GxsIdDetails : public QObject Q_OBJECT public: - static const int ICON_TYPE_AVATAR = 0x0001 ; - static const int ICON_TYPE_PGP = 0x0002 ; - static const int ICON_TYPE_RECOGN = 0x0004 ; - static const int ICON_TYPE_ALL = 0x0007 ; - static const int ICON_TYPE_REDACTED= 0x0008 ; + static const int ICON_TYPE_AVATAR = 0x0001 ; + static const int ICON_TYPE_PGP = 0x0002 ; + static const int ICON_TYPE_RECOGN = 0x0004 ; + static const int ICON_TYPE_REPUTATION = 0x0008 ; + static const int ICON_TYPE_ALL = 0x000f ; GxsIdDetails(); virtual ~GxsIdDetails(); @@ -76,6 +76,7 @@ public: static QString getNameForType(GxsIdDetailsType type, const RsIdentityDetails &details); static QIcon getLoadingIcon(const RsGxsId &id); + static QIcon getReputationIcon(RsReputations::ReputationLevel icon_index); static void GenerateCombinedPixmap(QPixmap &pixmap, const QList &icons, int iconSize); diff --git a/retroshare-gui/src/gui/gxs/GxsIdTreeWidgetItem.cpp b/retroshare-gui/src/gui/gxs/GxsIdTreeWidgetItem.cpp index aa5edc91e..a2535c38e 100644 --- a/retroshare-gui/src/gui/gxs/GxsIdTreeWidgetItem.cpp +++ b/retroshare-gui/src/gui/gxs/GxsIdTreeWidgetItem.cpp @@ -35,12 +35,6 @@ GxsIdRSTreeWidgetItem::GxsIdRSTreeWidgetItem(const RSTreeWidgetItemCompareRole * init(); } -GxsIdRSTreeWidgetItem::GxsIdRSTreeWidgetItem(const RSTreeWidgetItemCompareRole *compareRole, uint32_t icon_mask,QTreeWidgetItem *parent) - : QObject(NULL), RSTreeWidgetItem(compareRole, parent), mColumn(0), mIconTypeMask(icon_mask) -{ - init(); -} - void GxsIdRSTreeWidgetItem::init() { mIdFound = false; diff --git a/retroshare-gui/src/gui/gxs/GxsIdTreeWidgetItem.h b/retroshare-gui/src/gui/gxs/GxsIdTreeWidgetItem.h index 472477841..dde64fdc6 100644 --- a/retroshare-gui/src/gui/gxs/GxsIdTreeWidgetItem.h +++ b/retroshare-gui/src/gui/gxs/GxsIdTreeWidgetItem.h @@ -40,8 +40,7 @@ class GxsIdRSTreeWidgetItem : public QObject, public RSTreeWidgetItem Q_OBJECT public: - GxsIdRSTreeWidgetItem(const RSTreeWidgetItemCompareRole *compareRole, uint32_t icon_mask=GxsIdDetails::ICON_TYPE_ALL,QTreeWidget *parent = NULL); - GxsIdRSTreeWidgetItem(const RSTreeWidgetItemCompareRole *compareRole, uint32_t icon_mask,QTreeWidgetItem *parent); + GxsIdRSTreeWidgetItem(const RSTreeWidgetItemCompareRole *compareRole, uint32_t icon_mask,QTreeWidget *parent = NULL); void setId(const RsGxsId &id, int column, bool retryWhenFailed); bool getId(RsGxsId &id); diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index 8bf70fe48..fe36f9305 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -1000,7 +1000,7 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum bool redacted = (reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE) ; - GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(mThreadCompareRole,GxsIdDetails::ICON_TYPE_ALL || (redacted?(GxsIdDetails::ICON_TYPE_REDACTED):0)); + GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(mThreadCompareRole,GxsIdDetails::ICON_TYPE_REPUTATION | GxsIdDetails::ICON_TYPE_AVATAR ); item->moveToThread(ui->threadTreeWidget->thread()); if(redacted) @@ -1096,7 +1096,7 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum QTreeWidgetItem *GxsForumThreadWidget::generateMissingItem(const RsGxsMessageId &msgId) { - GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(mThreadCompareRole,GxsIdDetails::ICON_TYPE_ALL); + GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(mThreadCompareRole,GxsIdDetails::ICON_TYPE_REPUTATION | GxsIdDetails::ICON_TYPE_AVATAR); item->setText(COLUMN_THREAD_TITLE, tr("[ ... Missing Message ... ]")); item->setData(COLUMN_THREAD_DATA, ROLE_THREAD_MSGID, QString::fromStdString(msgId.toStdString())); From d4237450641f0588bb0b16de414e7219500d0fde Mon Sep 17 00:00:00 2001 From: csoler Date: Sat, 24 Dec 2016 15:04:08 +0100 Subject: [PATCH 08/23] added column to show distribution data and moved ReputationItemDelegate to GxsIdDetails --- retroshare-gui/src/gui/Identity/IdDialog.cpp | 38 +----------------- retroshare-gui/src/gui/gxs/GxsIdDetails.cpp | 40 +++++++++++++++++-- retroshare-gui/src/gui/gxs/GxsIdDetails.h | 26 +++++++++++- .../gui/gxsforums/GxsForumThreadWidget.cpp | 22 +++++----- .../src/gui/gxsforums/GxsForumThreadWidget.ui | 7 +++- 5 files changed, 80 insertions(+), 53 deletions(-) diff --git a/retroshare-gui/src/gui/Identity/IdDialog.cpp b/retroshare-gui/src/gui/Identity/IdDialog.cpp index 584c71d33..5dc948599 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.cpp +++ b/retroshare-gui/src/gui/Identity/IdDialog.cpp @@ -130,42 +130,6 @@ class TreeWidgetItem : public QTreeWidgetItem { } }; -// This class allows to draw the item in the share flags column using an appropriate size - -class ReputationItemDelegate: public QStyledItemDelegate -{ -public: - ReputationItemDelegate() {} - - virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const - { - Q_ASSERT(index.isValid()); - - QStyleOptionViewItemV4 opt = option; - initStyleOption(&opt, index); - // disable default icon - opt.icon = QIcon(); - // draw default item - QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, 0); - - const QRect r = option.rect; - - // get pixmap - unsigned int icon_index = qvariant_cast(index.data(Qt::DecorationRole)); - - if(icon_index > 4) - return ; - - QIcon icon = GxsIdDetails::getReputationIcon(RsReputations::ReputationLevel(icon_index)); - - QPixmap pix = icon.pixmap(r.size()); - - // draw pixmap at center of item - const QPoint p = QPoint((r.width() - pix.width())/2, (r.height() - pix.height())/2); - painter->drawPixmap(r.topLeft() + p, pix); - } -}; - /** Constructor */ IdDialog::IdDialog(QWidget *parent) : RsGxsUpdateBroadcastPage(rsIdentity, parent), @@ -360,7 +324,7 @@ IdDialog::IdDialog(QWidget *parent) : ui->idTreeWidget->setColumnWidth(RSID_COL_IDTYPE, 18 * fontWidth); ui->idTreeWidget->setColumnWidth(RSID_COL_VOTES, 7 * fontWidth); - ui->idTreeWidget->setItemDelegateForColumn(RSID_COL_VOTES,new ReputationItemDelegate()) ; + ui->idTreeWidget->setItemDelegateForColumn(RSID_COL_VOTES,new ReputationItemDelegate(RsReputations::ReputationLevel(0xff))) ; //QHeaderView_setSectionResizeMode(ui->idTreeWidget->header(), QHeaderView::ResizeToContents); diff --git a/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp b/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp index a3f4b680b..0ccc4c2a2 100644 --- a/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp +++ b/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp @@ -53,6 +53,7 @@ #define REPUTATION_NEUTRAL_ICON ":/icons/bullet_grey_128.png" #define REPUTATION_REMOTELY_NEGATIVE_ICON ":/icons/yellow_biohazard64.png" #define REPUTATION_LOCALLY_NEGATIVE_ICON ":/icons/red_biohazard64.png" +#define REPUTATION_VOID ":/icons/void_128.png" #define TIMER_INTERVAL 500 #define MAX_ATTEMPTS 10 @@ -66,6 +67,35 @@ const int kRecognTagType_Dev_Translator = 3; const int kRecognTagType_Dev_Patcher = 4; const int kRecognTagType_Dev_Developer = 5; + +void ReputationItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_ASSERT(index.isValid()); + + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + // disable default icon + opt.icon = QIcon(); + // draw default item + QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, 0); + + const QRect r = option.rect; + + // get pixmap + unsigned int icon_index = qvariant_cast(index.data(Qt::DecorationRole)); + + if(icon_index > mMaxLevelToDisplay) + return ; + + QIcon icon = GxsIdDetails::getReputationIcon(RsReputations::ReputationLevel(icon_index),0xff); + + QPixmap pix = icon.pixmap(r.size()); + + // draw pixmap at center of item + const QPoint p = QPoint((r.width() - pix.width())/2, (r.height() - pix.height())/2); + painter->drawPixmap(r.topLeft() + p, pix); +} + /* The global object */ GxsIdDetails *GxsIdDetails::mInstance = NULL ; @@ -964,8 +994,11 @@ QString nickname ; return comment; } -QIcon GxsIdDetails::getReputationIcon(RsReputations::ReputationLevel icon_index) +QIcon GxsIdDetails::getReputationIcon(RsReputations::ReputationLevel icon_index,uint32_t min_reputation) { + if(icon_index >= min_reputation) + return QIcon(REPUTATION_VOID); + switch(icon_index) { case RsReputations::REPUTATION_LOCALLY_NEGATIVE: return QIcon(REPUTATION_LOCALLY_NEGATIVE_ICON) ; break ; @@ -979,7 +1012,7 @@ QIcon GxsIdDetails::getReputationIcon(RsReputations::ReputationLevel icon_index) } } -void GxsIdDetails::getIcons(const RsIdentityDetails &details, QList &icons,uint32_t icon_types) +void GxsIdDetails::getIcons(const RsIdentityDetails &details, QList &icons,uint32_t icon_types,uint32_t minimal_required_reputation) { QPixmap pix ; @@ -989,10 +1022,9 @@ void GxsIdDetails::getIcons(const RsIdentityDetails &details, QList &icon icons.push_back(QIcon(IMAGE_BANNED)) ; return ; } - if(icon_types & ICON_TYPE_REPUTATION) - icons.push_back(getReputationIcon(details.mReputation.mOverallReputationLevel)) ; + icons.push_back(getReputationIcon(details.mReputation.mOverallReputationLevel,minimal_required_reputation)) ; if(icon_types & ICON_TYPE_AVATAR) { diff --git a/retroshare-gui/src/gui/gxs/GxsIdDetails.h b/retroshare-gui/src/gui/gxs/GxsIdDetails.h index 7199f0565..39a6ad29d 100644 --- a/retroshare-gui/src/gui/gxs/GxsIdDetails.h +++ b/retroshare-gui/src/gui/gxs/GxsIdDetails.h @@ -29,6 +29,7 @@ #include #include #include +#include #include @@ -45,6 +46,21 @@ enum GxsIdDetailsType typedef void (*GxsIdDetailsCallbackFunction)(GxsIdDetailsType type, const RsIdentityDetails &details, QObject *object, const QVariant &data); +// This class allows to draw the item in a reputation column using an appropriate size. The max_level_to_display parameter allows to replace +// the icon by an empty icon when needed. This allows to keep the focus on the critical icons only. + +class ReputationItemDelegate: public QStyledItemDelegate +{ +public: + ReputationItemDelegate(RsReputations::ReputationLevel max_level_to_display) : mMaxLevelToDisplay(max_level_to_display) {} + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + +private: + uint32_t mMaxLevelToDisplay ; +}; + + class GxsIdDetails : public QObject { Q_OBJECT @@ -67,7 +83,13 @@ public: static QString getName(const RsIdentityDetails &details); static QString getComment(const RsIdentityDetails &details); - static void getIcons(const RsIdentityDetails &details, QList &icons,uint32_t icon_types=ICON_TYPE_ALL); + + /*! + * \brief getIcons + * Returns the list of icons to display along with the ID name. The types of icons to show is a compound of the ICON_TYPE_* flags. + * If reputation is needed and exceeds the minimal reputation, an empty/void icon is showsn . This allow to only show reputation for IDs for which a problem exists. + */ + static void getIcons(const RsIdentityDetails &details, QList &icons, uint32_t icon_types=ICON_TYPE_ALL, uint32_t minimal_required_reputation=0xff); static QString getEmptyIdText(); static QString getLoadingText(const RsGxsId &id); @@ -76,7 +98,7 @@ public: static QString getNameForType(GxsIdDetailsType type, const RsIdentityDetails &details); static QIcon getLoadingIcon(const RsGxsId &id); - static QIcon getReputationIcon(RsReputations::ReputationLevel icon_index); + static QIcon getReputationIcon(RsReputations::ReputationLevel icon_index, uint32_t min_reputation); static void GenerateCombinedPixmap(QPixmap &pixmap, const QList &icons, int iconSize); diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index fe36f9305..2171c1012 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -67,13 +67,14 @@ #define VIEW_FLAT 2 /* Thread constants */ -#define COLUMN_THREAD_COUNT 6 -#define COLUMN_THREAD_TITLE 0 -#define COLUMN_THREAD_READ 1 -#define COLUMN_THREAD_DATE 2 -#define COLUMN_THREAD_AUTHOR 3 -#define COLUMN_THREAD_SIGNED 4 -#define COLUMN_THREAD_CONTENT 5 +#define COLUMN_THREAD_TITLE 0 +#define COLUMN_THREAD_READ 1 +#define COLUMN_THREAD_DATE 2 +#define COLUMN_THREAD_DISTRIBUTION 3 +#define COLUMN_THREAD_AUTHOR 4 +#define COLUMN_THREAD_SIGNED 5 +#define COLUMN_THREAD_CONTENT 6 +#define COLUMN_THREAD_COUNT 7 #define COLUMN_THREAD_DATA 0 // column for storing the userdata like msgid and parentid @@ -137,6 +138,8 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget mThreadCompareRole = new RSTreeWidgetItemCompareRole; mThreadCompareRole->setRole(COLUMN_THREAD_DATE, ROLE_THREAD_SORT); + ui->threadTreeWidget->setItemDelegateForColumn(COLUMN_THREAD_DISTRIBUTION,new ReputationItemDelegate(RsReputations::REPUTATION_NEUTRAL)) ; + connect(ui->threadTreeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(threadListCustomPopupMenu(QPoint))); connect(ui->postText, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuTextBrowser(QPoint))); @@ -1000,7 +1003,7 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum bool redacted = (reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE) ; - GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(mThreadCompareRole,GxsIdDetails::ICON_TYPE_REPUTATION | GxsIdDetails::ICON_TYPE_AVATAR ); + GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(mThreadCompareRole,GxsIdDetails::ICON_TYPE_AVATAR ); item->moveToThread(ui->threadTreeWidget->thread()); if(redacted) @@ -1008,6 +1011,7 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum else item->setText(COLUMN_THREAD_TITLE, QString::fromUtf8(msg.mMeta.mMsgName.c_str())); + item->setData(COLUMN_THREAD_DISTRIBUTION,Qt::DecorationRole, reputation_level) ; //msg.mMeta.mChildTs Was not updated when received new child // so do it here. @@ -1096,7 +1100,7 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum QTreeWidgetItem *GxsForumThreadWidget::generateMissingItem(const RsGxsMessageId &msgId) { - GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(mThreadCompareRole,GxsIdDetails::ICON_TYPE_REPUTATION | GxsIdDetails::ICON_TYPE_AVATAR); + GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(mThreadCompareRole,GxsIdDetails::ICON_TYPE_AVATAR); item->setText(COLUMN_THREAD_TITLE, tr("[ ... Missing Message ... ]")); item->setData(COLUMN_THREAD_DATA, ROLE_THREAD_MSGID, QString::fromStdString(msgId.toStdString())); diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.ui b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.ui index a086d110c..ba0dd9640 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.ui +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.ui @@ -6,7 +6,7 @@ 0 0 - 851 + 1217 721 @@ -238,6 +238,11 @@ Date + + + Distribution + + Author From b34ab447da5f1dd3817e7639caefb82fa476d86a Mon Sep 17 00:00:00 2001 From: csoler Date: Sat, 24 Dec 2016 17:29:53 +0100 Subject: [PATCH 09/23] added menu entries to vote for/against a forum poster --- retroshare-gui/src/gui/Identity/IdDialog.ui | 2 +- .../gui/gxsforums/GxsForumThreadWidget.cpp | 47 +++++++++++++++---- .../src/gui/gxsforums/GxsForumThreadWidget.h | 6 ++- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/retroshare-gui/src/gui/Identity/IdDialog.ui b/retroshare-gui/src/gui/Identity/IdDialog.ui index 57fbc3649..a4e096b5f 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.ui +++ b/retroshare-gui/src/gui/Identity/IdDialog.ui @@ -485,7 +485,7 @@ - Auto-Ban all identities from this node + Auto-Ban all identities signed by the same node diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index 2171c1012..33fa60e7c 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -61,6 +61,9 @@ #define IMAGE_DOWNLOADALL ":/images/startall.png" #define IMAGE_COPYLINK ":/images/copyrslink.png" #define IMAGE_BIOHAZARD ":/icons/yellow_biohazard64.png" +#define IMAGE_POSITIVE_OPINION ":/icons/png/thumbs-up.png" +#define IMAGE_NEUTRAL_OPINION ":/icons/png/thumbs-neutral.png" +#define IMAGE_NEGATIVE_OPINION ":/icons/png/thumbs-down.png" #define VIEW_LAST_POST 0 #define VIEW_THREADED 1 @@ -100,7 +103,9 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget mTokenTypeMessageData = nextTokenType(); mTokenTypeReplyMessage = nextTokenType(); mTokenTypeReplyForumMessage = nextTokenType(); - mTokenTypeBanAuthor = nextTokenType(); + mTokenTypeNegativeAuthor = nextTokenType(); + mTokenTypeNeutralAuthor = nextTokenType(); + mTokenTypePositiveAuthor = nextTokenType(); setUpdateWhenInvisible(true); @@ -421,9 +426,20 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) QAction *replyauthorAct = new QAction(QIcon(IMAGE_MESSAGEREPLY), tr("Reply with private message"), &contextMnu); connect(replyauthorAct, SIGNAL(triggered()), this, SLOT(replytomessage())); - QAction *flagasbadAct = new QAction(QIcon(IMAGE_BIOHAZARD), tr("Ban this author"), &contextMnu); - flagasbadAct->setToolTip(tr("This will block/hide messages from this person, and notify neighbor nodes.")) ; - connect(flagasbadAct, SIGNAL(triggered()), this, SLOT(flagpersonasbad())); + QAction *flagaspositiveAct = new QAction(QIcon(IMAGE_POSITIVE_OPINION), tr("Give positive opinion"), &contextMnu); + flagaspositiveAct->setToolTip(tr("This will block/hide messages from this person, and notify friend nodes.")) ; + flagaspositiveAct->setData(mTokenTypePositiveAuthor) ; + connect(flagaspositiveAct, SIGNAL(triggered()), this, SLOT(flagperson())); + + QAction *flagasneutralAct = new QAction(QIcon(IMAGE_NEUTRAL_OPINION), tr("Give neutral opinion to this author"), &contextMnu); + flagasneutralAct->setToolTip(tr("Doing this, you trust your friends to decide to forward this message or not.")) ; + flagasneutralAct->setData(mTokenTypeNeutralAuthor) ; + connect(flagasneutralAct, SIGNAL(triggered()), this, SLOT(flagperson())); + + QAction *flagasnegativeAct = new QAction(QIcon(IMAGE_NEGATIVE_OPINION), tr("Give negative opinion"), &contextMnu); + flagasnegativeAct->setToolTip(tr("This will block/hide messages from this person, and notify friend nodes.")) ; + flagasnegativeAct->setData(mTokenTypeNegativeAuthor) ; + connect(flagasnegativeAct, SIGNAL(triggered()), this, SLOT(flagperson())); QAction *newthreadAct = new QAction(QIcon(IMAGE_MESSAGE), tr("Start New Thread"), &contextMnu); newthreadAct->setEnabled (IS_GROUP_SUBSCRIBED(mSubscribeFlags)); @@ -504,7 +520,9 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) contextMnu.addAction(collapseAll); contextMnu.addSeparator(); - contextMnu.addAction(flagasbadAct); + contextMnu.addAction(flagaspositiveAct); + contextMnu.addAction(flagasneutralAct); + contextMnu.addAction(flagasnegativeAct); contextMnu.addSeparator(); contextMnu.addAction(replyauthorAct); @@ -1013,6 +1031,17 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum item->setData(COLUMN_THREAD_DISTRIBUTION,Qt::DecorationRole, reputation_level) ; + QString rep_tooltip_str ; + switch(reputation_level) + { + case RsReputations::REPUTATION_LOCALLY_NEGATIVE: rep_tooltip_str = tr("You have banned this ID. The message will not be\ndisplayed nor forwarded to your friends.") ; break; + case RsReputations::REPUTATION_REMOTELY_NEGATIVE: rep_tooltip_str = tr("You have not set an opinion for this person,\n and your friends vote negatively: Spam regulation \nprevents the message to be forwarded to your friends.") ; break; + case RsReputations::REPUTATION_NEUTRAL: rep_tooltip_str = tr("You have not set an opinion for this person,\n and neither have your friends: Spam regulation\nprevents the message to be forwarded to your friends.") ; break; + default: + rep_tooltip_str = tr("Message will be forwarded to your friends.") ; break; + } + item->setData(COLUMN_THREAD_DISTRIBUTION,Qt::ToolTipRole,rep_tooltip_str) ; + //msg.mMeta.mChildTs Was not updated when received new child // so do it here. QDateTime qtime; @@ -1832,7 +1861,7 @@ static QString buildReplyHeader(const RsMsgMetaData &meta) return header; } -void GxsForumThreadWidget::flagpersonasbad() +void GxsForumThreadWidget::flagperson() { // no need to use the token system for that, since we just need to find out the author's name, which is in the item. @@ -1841,6 +1870,8 @@ void GxsForumThreadWidget::flagpersonasbad() return; } + uint32_t token_type = qobject_cast(sender())->data().toUInt(); + // Get Message ... then complete replyMessageData(). RsGxsGrpMsgIdPair postId = std::make_pair(groupId(), mThreadId); @@ -1857,7 +1888,7 @@ void GxsForumThreadWidget::flagpersonasbad() vect.push_back(postId.second); uint32_t token; - mTokenQueue->requestMsgInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts, msgIds, mTokenTypeBanAuthor); + mTokenQueue->requestMsgInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts, msgIds, token_type); } void GxsForumThreadWidget::replytomessage() @@ -2296,7 +2327,7 @@ void GxsForumThreadWidget::loadRequest(const TokenQueue *queue, const TokenReque return; } - if (req.mUserType == mTokenTypeBanAuthor) { + if (req.mUserType == mTokenTypeNegativeAuthor) { loadMsgData_BanAuthor(req.mToken); return; } diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h index 1cf109a81..2a3073f00 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h @@ -108,7 +108,7 @@ private slots: void downloadAllFiles(); void changedViewBox(); - void flagpersonasbad(); + void flagperson(); void filterColumnChanged(int column); void filterItems(const QString &text); @@ -173,7 +173,9 @@ private: uint32_t mTokenTypeMessageData; uint32_t mTokenTypeReplyMessage; uint32_t mTokenTypeReplyForumMessage; - uint32_t mTokenTypeBanAuthor; + uint32_t mTokenTypeNegativeAuthor; + uint32_t mTokenTypePositiveAuthor; + uint32_t mTokenTypeNeutralAuthor; /* Color definitions (for standard see qss.default) */ QColor mTextColorRead; From c80d3ec1a498e360e0d85ee4b660246ef65e1af4 Mon Sep 17 00:00:00 2001 From: csoler Date: Sun, 25 Dec 2016 23:12:07 +0100 Subject: [PATCH 10/23] added check for reputation before sending message ids --- libretroshare/src/gxs/rsgxsnetservice.cc | 134 +++++++++++------- .../src/serialiser/rsgxsupdateitems.h | 2 +- 2 files changed, 84 insertions(+), 52 deletions(-) diff --git a/libretroshare/src/gxs/rsgxsnetservice.cc b/libretroshare/src/gxs/rsgxsnetservice.cc index dd7ffebf7..a9c55eae0 100644 --- a/libretroshare/src/gxs/rsgxsnetservice.cc +++ b/libretroshare/src/gxs/rsgxsnetservice.cc @@ -1954,8 +1954,6 @@ void RsGxsNetService::updateServerSyncTS() for(std::map::const_iterator mit = gxsMap.begin();mit != gxsMap.end(); ++mit) { - const RsGxsGroupId& grpId = mit->first; - // Check if the group is subscribed and restricted to a circle. If the circle has changed, update the // global TS to reflect that change to clients who may be able to see/subscribe to that particular group. @@ -2798,7 +2796,7 @@ void RsGxsNetService::locked_genReqMsgTransaction(NxsTransaction* tr) #ifdef NXS_NET_DEBUG_1 GXSNETDEBUG_PG(item->PeerId(),grpId) << " msg ID = " << msgId ; #endif - if(reqListSize >= MAX_REQLIST_SIZE) + if(reqListSize >= (int)MAX_REQLIST_SIZE) { #ifdef NXS_NET_DEBUG_1 GXSNETDEBUG_PG(item->PeerId(),grpId) << ". reqlist too big. Pruning out this item for now." << std::endl; @@ -2807,7 +2805,7 @@ void RsGxsNetService::locked_genReqMsgTransaction(NxsTransaction* tr) continue ; // we should actually break, but we need to print some debug info. } - if(reqListSize < MAX_REQLIST_SIZE && msgIdSet.find(msgId) == msgIdSet.end()) + if(reqListSize < (int)MAX_REQLIST_SIZE && msgIdSet.find(msgId) == msgIdSet.end()) { bool noAuthor = syncItem->authorId.isNull(); @@ -2831,7 +2829,6 @@ void RsGxsNetService::locked_genReqMsgTransaction(NxsTransaction* tr) // - if author is locally banned, do not download. // - if author is not locally banned, download, whatever friends' opinion might be. -#warning Update the code below to correctly send/recv dependign on reputation if(rsIdentity && rsIdentity->overallReputationLevel(syncItem->authorId) == RsReputations::REPUTATION_LOCALLY_NEGATIVE) { #ifdef NXS_NET_DEBUG_1 @@ -4213,57 +4210,92 @@ void RsGxsNetService::handleRecvSyncMessage(RsNxsSyncMsgReqItem *item,bool item_ if(canSendMsgIds(msgMetas, *grpMeta, peer, should_encrypt_to_this_circle_id)) { + RsReputations::ReputationLevel min_rep_for_anonymous ; + RsReputations::ReputationLevel min_rep_for_unknown_signed ; + + min_rep_for_anonymous = (grpMeta->mSignFlags & GXS_SERV::FLAG_AUTHOR_AUTHENTICATION_GPG )?RsReputations::REPUTATION_REMOTELY_POSITIVE: RsReputations::REPUTATION_REMOTELY_NEGATIVE; + min_rep_for_unknown_signed = (grpMeta->mSignFlags & GXS_SERV::FLAG_AUTHOR_AUTHENTICATION_GPG_KNOWN)?RsReputations::REPUTATION_REMOTELY_POSITIVE: RsReputations::REPUTATION_REMOTELY_NEGATIVE; + for(std::vector::iterator vit = msgMetas.begin();vit != msgMetas.end(); ++vit) - if(item->createdSinceTS < (*vit)->mPublishTs) + { + RsGxsMsgMetaData* m = *vit; + + // if anti-spam is enabled, do not send messages from authors with bad reputation + + RsIdentityDetails details ; + + if(!rsIdentity->getIdDetails(m->mAuthorId,details)) { - RsGxsMsgMetaData* m = *vit; - - RsNxsSyncMsgItem* mItem = new RsNxsSyncMsgItem(mServType); - mItem->flag = RsNxsSyncGrpItem::FLAG_RESPONSE; - mItem->grpId = m->mGroupId; - mItem->msgId = m->mMsgId; - mItem->authorId = m->mAuthorId; - mItem->PeerId(peer); - mItem->transactionNumber = transN; - - if(!should_encrypt_to_this_circle_id.isNull()) - { -#ifdef NXS_NET_DEBUG_7 - GXSNETDEBUG_PG(item->PeerId(),item->grpId) << " sending info item for msg id " << mItem->msgId << ". Transaction will be encrypted for group " << should_encrypt_to_this_circle_id << std::endl; -#endif - RsNxsItem *encrypted_item = NULL ; - uint32_t status = RS_NXS_ITEM_ENCRYPTION_STATUS_UNKNOWN ; - - if(encryptSingleNxsItem(mItem, grpMeta->mCircleId,m->mGroupId, encrypted_item,status)) - { - itemL.push_back(encrypted_item) ; - delete mItem ; - } - else - { - // Something's not ready (probably the circle content. We could put on a vetting list, but actually the client will re-ask the list asap. - - std::cerr << " (EE) Cannot encrypt msg meta data. MsgId=" << mItem->msgId << ", grpId=" << mItem->grpId << ", circleId=" << should_encrypt_to_this_circle_id << ". Dropping the whole list." << std::endl; - - for(std::list::const_iterator it(itemL.begin());it!=itemL.end();++it) - delete *it ; - - itemL.clear() ; - break ; - } - } - else - { -#ifdef NXS_NET_DEBUG_7 - GXSNETDEBUG_PG(item->PeerId(),item->grpId) << " sending info item for msg id " << mItem->msgId << " in clear." << std::endl; -#endif - itemL.push_back(mItem); - } + std::cerr << /* GXSNETDEBUG_PG(item->PeerId(),item->grpId) << */ " not sending grp message ID " << (*vit)->mMsgId << ", because the identity of the author is not accessible (unknown/not cached)" << std::endl; + continue ; } + if(!(details.mFlags & RS_IDENTITY_FLAGS_PGP_LINKED) && details.mReputation.mOverallReputationLevel < min_rep_for_anonymous) + { +//#ifdef NXS_NET_DEBUG_0 + std::cerr << /* GXSNETDEBUG_PG(item->PeerId(),item->grpId) << */ " not sending item ID " << (*vit)->mMsgId << ", because the author is anonymous has reputation level " << details.mReputation.mOverallReputationLevel << std::endl; +//#endif + continue ; + } + if(!(details.mFlags & RS_IDENTITY_FLAGS_PGP_KNOWN) && details.mReputation.mOverallReputationLevel < min_rep_for_unknown_signed) + { +//#ifdef NXS_NET_DEBUG_0 + std::cerr << /* GXSNETDEBUG_PG(item->PeerId(),item->grpId) << */ " not sending item ID " << (*vit)->mMsgId << ", because the author is signed by unknown key, and has reputation level " << details.mReputation.mOverallReputationLevel << std::endl; +//#endif + continue ; + } + + // Check publish TS + + if(item->createdSinceTS > (*vit)->mPublishTs) + { #ifdef NXS_NET_DEBUG_0 - else - GXSNETDEBUG_PG(item->PeerId(),item->grpId) << " not sending item ID " << (*vit)->mMsgId << ", because it is too old (publishTS = " << (time(NULL)-(*vit)->mPublishTs)/86400 << " days ago" << std::endl; + GXSNETDEBUG_PG(item->PeerId(),item->grpId) << " not sending item ID " << (*vit)->mMsgId << ", because it is too old (publishTS = " << (time(NULL)-(*vit)->mPublishTs)/86400 << " days ago" << std::endl; #endif + continue ; + } + + RsNxsSyncMsgItem* mItem = new RsNxsSyncMsgItem(mServType); + mItem->flag = RsNxsSyncGrpItem::FLAG_RESPONSE; + mItem->grpId = m->mGroupId; + mItem->msgId = m->mMsgId; + mItem->authorId = m->mAuthorId; + mItem->PeerId(peer); + mItem->transactionNumber = transN; + + if(!should_encrypt_to_this_circle_id.isNull()) + { +#ifdef NXS_NET_DEBUG_7 + GXSNETDEBUG_PG(item->PeerId(),item->grpId) << " sending info item for msg id " << mItem->msgId << ". Transaction will be encrypted for group " << should_encrypt_to_this_circle_id << std::endl; +#endif + RsNxsItem *encrypted_item = NULL ; + uint32_t status = RS_NXS_ITEM_ENCRYPTION_STATUS_UNKNOWN ; + + if(encryptSingleNxsItem(mItem, grpMeta->mCircleId,m->mGroupId, encrypted_item,status)) + { + itemL.push_back(encrypted_item) ; + delete mItem ; + } + else + { + // Something's not ready (probably the circle content. We could put on a vetting list, but actually the client will re-ask the list asap. + + std::cerr << " (EE) Cannot encrypt msg meta data. MsgId=" << mItem->msgId << ", grpId=" << mItem->grpId << ", circleId=" << should_encrypt_to_this_circle_id << ". Dropping the whole list." << std::endl; + + for(std::list::const_iterator it(itemL.begin());it!=itemL.end();++it) + delete *it ; + + itemL.clear() ; + break ; + } + } + else + { +#ifdef NXS_NET_DEBUG_7 + GXSNETDEBUG_PG(item->PeerId(),item->grpId) << " sending info item for msg id " << mItem->msgId << " in clear." << std::endl; +#endif + itemL.push_back(mItem); + } + } } #ifdef NXS_NET_DEBUG_0 else diff --git a/libretroshare/src/serialiser/rsgxsupdateitems.h b/libretroshare/src/serialiser/rsgxsupdateitems.h index c32f6d3f4..117cc54d9 100644 --- a/libretroshare/src/serialiser/rsgxsupdateitems.h +++ b/libretroshare/src/serialiser/rsgxsupdateitems.h @@ -96,7 +96,7 @@ public: virtual ~RsGxsGrpConfigItem() {} virtual void clear() {} - virtual std::ostream &print(std::ostream &out, uint16_t indent) { return out;} + virtual std::ostream &print(std::ostream &out, uint16_t /* indent */) { return out;} virtual bool serialise(void *data,uint32_t& size) const ; virtual uint32_t serial_size() const ; From 811d084dfaca8f43c09749c0ff2cd2a5f4a51640 Mon Sep 17 00:00:00 2001 From: csoler Date: Sun, 25 Dec 2016 23:43:29 +0100 Subject: [PATCH 11/23] only show distribution column if anti-spam is enabled --- retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index 33fa60e7c..dff9e9607 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -227,6 +227,7 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget available posts from your subscribed friends, and make the \ forum visible to all other friends.

Afterwards you can unsubscribe from the context menu of the forum list at left.

")); ui->threadTreeWidget->enableColumnCustomize(true); + } GxsForumThreadWidget::~GxsForumThreadWidget() @@ -815,8 +816,8 @@ static QString getDurationString(uint32_t days) tw->ui->forumName->setText(QString::fromUtf8(group.mMeta.mGroupName.c_str())); QString anti_spam_features1 ; - if(IS_GROUP_PGP_KNOWN_AUTHED(tw->mSignFlags)) anti_spam_features1 = tr("Anonymous/unknown node IDs reputation threshold set to 0.4"); - else if(IS_GROUP_PGP_AUTHED(tw->mSignFlags)) anti_spam_features1 = tr("Anonymous IDs reputation threshold set to 0.4"); + if(IS_GROUP_PGP_KNOWN_AUTHED(tw->mSignFlags)) anti_spam_features1 = tr("Anonymous/unknown posts forwarded if reputation is positive"); + else if(IS_GROUP_PGP_AUTHED(tw->mSignFlags)) anti_spam_features1 = tr("Anonymous posts forwarded if reputation is positive"); tw->mForumDescription = QString("%1: \t%2
").arg(tr("Forum name"), QString::fromUtf8( group.mMeta.mGroupName.c_str())); tw->mForumDescription += QString("%1: \t%2
").arg(tr("Subscribers")).arg(group.mMeta.mPop); @@ -2105,6 +2106,8 @@ void GxsForumThreadWidget::loadGroupData(const uint32_t &token) mStateHelper->setActive(mTokenTypeGroupData, true); + // Don't show the distribution column if the forum has no anti-spam + ui->threadTreeWidget->setColumnHidden(COLUMN_THREAD_DISTRIBUTION, !IS_GROUP_PGP_KNOWN_AUTHED(mForumGroup.mMeta.mSignFlags) && !(IS_GROUP_PGP_AUTHED(mForumGroup.mMeta.mSignFlags))); ui->subscribeToolButton->setHidden(IS_GROUP_SUBSCRIBED(mSubscribeFlags)) ; } else From 742a7648a4b7d0999670f4fbfaf07935c0f07417 Mon Sep 17 00:00:00 2001 From: csoler Date: Mon, 26 Dec 2016 15:59:53 +0100 Subject: [PATCH 12/23] added display of warning for non forwarded posts --- libretroshare/src/gxs/rsgenexchange.cc | 4 + libretroshare/src/gxs/rsgenexchange.h | 1 + libretroshare/src/gxs/rsgxsnetservice.cc | 29 ++---- libretroshare/src/gxs/rsnxs.h | 46 ++++++++++ libretroshare/src/retroshare/rsgxsforums.h | 35 ++++---- libretroshare/src/retroshare/rsgxsiface.h | 3 + .../src/retroshare/rsgxsifacehelper.h | 5 ++ retroshare-gui/src/gui/Identity/IdDialog.cpp | 6 +- .../gui/gxsforums/GxsForumThreadWidget.cpp | 83 +++++++++++++++--- retroshare-gui/src/gui/icons.qrc | 2 + .../src/gui/icons/warning_red_128.png | Bin 0 -> 27339 bytes .../src/gui/icons/warning_yellow_128.png | Bin 0 -> 25848 bytes 12 files changed, 160 insertions(+), 54 deletions(-) create mode 100644 retroshare-gui/src/gui/icons/warning_red_128.png create mode 100644 retroshare-gui/src/gui/icons/warning_yellow_128.png diff --git a/libretroshare/src/gxs/rsgenexchange.cc b/libretroshare/src/gxs/rsgenexchange.cc index 25d4e52d8..b002638da 100644 --- a/libretroshare/src/gxs/rsgenexchange.cc +++ b/libretroshare/src/gxs/rsgenexchange.cc @@ -1614,6 +1614,10 @@ uint32_t RsGenExchange::getDefaultSyncPeriod() } } +RsReputations::ReputationLevel RsGenExchange::minReputationForForwardingMessages(uint32_t group_sign_flags,uint32_t identity_sign_flags) +{ + return RsNetworkExchangeService::minReputationForForwardingMessages(group_sign_flags,identity_sign_flags); +} uint32_t RsGenExchange::getSyncPeriod(const RsGxsGroupId& grpId) { RS_STACK_MUTEX(mGenMtx) ; diff --git a/libretroshare/src/gxs/rsgenexchange.h b/libretroshare/src/gxs/rsgenexchange.h index 0614ed2b1..95efbf2e1 100644 --- a/libretroshare/src/gxs/rsgenexchange.h +++ b/libretroshare/src/gxs/rsgenexchange.h @@ -658,6 +658,7 @@ public: uint16_t serviceType() const { return mServType ; } uint32_t serviceFullType() const { return ((uint32_t)mServType << 8) + (((uint32_t) RS_PKT_VERSION_SERVICE) << 24); } + virtual RsReputations::ReputationLevel minReputationForForwardingMessages(uint32_t group_sign_flags,uint32_t identity_flags); protected: /** Notifications **/ diff --git a/libretroshare/src/gxs/rsgxsnetservice.cc b/libretroshare/src/gxs/rsgxsnetservice.cc index a9c55eae0..f8fa8ecc2 100644 --- a/libretroshare/src/gxs/rsgxsnetservice.cc +++ b/libretroshare/src/gxs/rsgxsnetservice.cc @@ -4210,18 +4210,10 @@ void RsGxsNetService::handleRecvSyncMessage(RsNxsSyncMsgReqItem *item,bool item_ if(canSendMsgIds(msgMetas, *grpMeta, peer, should_encrypt_to_this_circle_id)) { - RsReputations::ReputationLevel min_rep_for_anonymous ; - RsReputations::ReputationLevel min_rep_for_unknown_signed ; - - min_rep_for_anonymous = (grpMeta->mSignFlags & GXS_SERV::FLAG_AUTHOR_AUTHENTICATION_GPG )?RsReputations::REPUTATION_REMOTELY_POSITIVE: RsReputations::REPUTATION_REMOTELY_NEGATIVE; - min_rep_for_unknown_signed = (grpMeta->mSignFlags & GXS_SERV::FLAG_AUTHOR_AUTHENTICATION_GPG_KNOWN)?RsReputations::REPUTATION_REMOTELY_POSITIVE: RsReputations::REPUTATION_REMOTELY_NEGATIVE; - for(std::vector::iterator vit = msgMetas.begin();vit != msgMetas.end(); ++vit) { RsGxsMsgMetaData* m = *vit; - // if anti-spam is enabled, do not send messages from authors with bad reputation - RsIdentityDetails details ; if(!rsIdentity->getIdDetails(m->mAuthorId,details)) @@ -4229,21 +4221,14 @@ void RsGxsNetService::handleRecvSyncMessage(RsNxsSyncMsgReqItem *item,bool item_ std::cerr << /* GXSNETDEBUG_PG(item->PeerId(),item->grpId) << */ " not sending grp message ID " << (*vit)->mMsgId << ", because the identity of the author is not accessible (unknown/not cached)" << std::endl; continue ; } - if(!(details.mFlags & RS_IDENTITY_FLAGS_PGP_LINKED) && details.mReputation.mOverallReputationLevel < min_rep_for_anonymous) - { -//#ifdef NXS_NET_DEBUG_0 - std::cerr << /* GXSNETDEBUG_PG(item->PeerId(),item->grpId) << */ " not sending item ID " << (*vit)->mMsgId << ", because the author is anonymous has reputation level " << details.mReputation.mOverallReputationLevel << std::endl; -//#endif - continue ; - } - if(!(details.mFlags & RS_IDENTITY_FLAGS_PGP_KNOWN) && details.mReputation.mOverallReputationLevel < min_rep_for_unknown_signed) - { -//#ifdef NXS_NET_DEBUG_0 - std::cerr << /* GXSNETDEBUG_PG(item->PeerId(),item->grpId) << */ " not sending item ID " << (*vit)->mMsgId << ", because the author is signed by unknown key, and has reputation level " << details.mReputation.mOverallReputationLevel << std::endl; -//#endif - continue ; - } + if(details.mReputation.mOverallReputationLevel < minReputationForForwardingMessages(grpMeta->mSignFlags, details.mFlags)) + { +//#ifdef NXS_NET_DEBUG_0 + std::cerr << /* GXSNETDEBUG_PG(item->PeerId(),item->grpId) << */ " not sending item ID " << (*vit)->mMsgId << ", because the author is flags " << std::hex << details.mFlags << std::dec << " and reputation level " << details.mReputation.mOverallReputationLevel << std::endl; +//#endif + continue ; + } // Check publish TS if(item->createdSinceTS > (*vit)->mPublishTs) diff --git a/libretroshare/src/gxs/rsnxs.h b/libretroshare/src/gxs/rsnxs.h index 0613560b0..995377ed3 100644 --- a/libretroshare/src/gxs/rsnxs.h +++ b/libretroshare/src/gxs/rsnxs.h @@ -34,6 +34,8 @@ #include #include "services/p3service.h" +#include "retroshare/rsreputations.h" +#include "retroshare/rsidentity.h" #include "rsgds.h" /*! @@ -159,6 +161,50 @@ public: * \return */ virtual bool stampMsgServerUpdateTS(const RsGxsGroupId& gid) =0; + + /*! + * \brief minReputationForForwardingMessages + * Encodes the policy for sending/requesting messages depending on anti-spam settings. + * + * \param group_sign_flags Sign flags from the group meta data + * \param identity_flags Flags of the identity + * \return + */ + static RsReputations::ReputationLevel minReputationForRequestingMessages(uint32_t /* group_sign_flags */, uint32_t /* identity_flags */) + { + // We always request messages, except if the author identity is locally banned. + + return RsReputations::REPUTATION_REMOTELY_NEGATIVE; + } + static RsReputations::ReputationLevel minReputationForForwardingMessages(uint32_t group_sign_flags, uint32_t identity_flags) + { + // If anti-spam is enabled, do not send messages from authors with bad reputation. The policy is to only forward messages if the reputation of the author is at least + // equal to the minimal reputation in the table below (R=remotely, L=locally, P=positive, N=negative) : + // + // | Anonymous Signed Signed+Known + // -----------+----------------------------------------------------- + // NONE | RN RN RN + // GPG_AUTHED | RP RN RN + // GPG_KNOWN | RP RP RN + // + + if(identity_flags & RS_IDENTITY_FLAGS_PGP_KNOWN) + return RsReputations::REPUTATION_REMOTELY_NEGATIVE; + else if(identity_flags & RS_IDENTITY_FLAGS_PGP_LINKED) + { + if(group_sign_flags & GXS_SERV::FLAG_AUTHOR_AUTHENTICATION_GPG_KNOWN) + return RsReputations::REPUTATION_REMOTELY_POSITIVE; + else + return RsReputations::REPUTATION_REMOTELY_NEGATIVE; + } + else + { + if( (group_sign_flags & GXS_SERV::FLAG_AUTHOR_AUTHENTICATION_GPG_KNOWN) || (group_sign_flags & GXS_SERV::FLAG_AUTHOR_AUTHENTICATION_GPG)) + return RsReputations::REPUTATION_REMOTELY_POSITIVE; + else + return RsReputations::REPUTATION_REMOTELY_NEGATIVE; + } + } }; #endif // RSGNP_H diff --git a/libretroshare/src/retroshare/rsgxsforums.h b/libretroshare/src/retroshare/rsgxsforums.h index cf7ca26ef..e1835928e 100644 --- a/libretroshare/src/retroshare/rsgxsforums.h +++ b/libretroshare/src/retroshare/rsgxsforums.h @@ -61,37 +61,36 @@ std::ostream &operator<<(std::ostream &out, const RsGxsForumMsg &msg); class RsGxsForums: public RsGxsIfaceHelper { - public: +public: RsGxsForums(RsGxsIface *gxs) - :RsGxsIfaceHelper(gxs) { return; } -virtual ~RsGxsForums() { return; } + :RsGxsIfaceHelper(gxs) { return; } + virtual ~RsGxsForums() { return; } /* Specific Service Data */ -virtual bool getGroupData(const uint32_t &token, std::vector &groups) = 0; -virtual bool getMsgData(const uint32_t &token, std::vector &msgs) = 0; -//Not currently used -//virtual bool getRelatedMessages(const uint32_t &token, std::vector &msgs) = 0; + virtual bool getGroupData(const uint32_t &token, std::vector &groups) = 0; + virtual bool getMsgData(const uint32_t &token, std::vector &msgs) = 0; + //Not currently used + //virtual bool getRelatedMessages(const uint32_t &token, std::vector &msgs) = 0; - ////////////////////////////////////////////////////////////////////////////// -virtual void setMessageReadStatus(uint32_t& token, const RsGxsGrpMsgIdPair& msgId, bool read) = 0; + ////////////////////////////////////////////////////////////////////////////// + virtual void setMessageReadStatus(uint32_t& token, const RsGxsGrpMsgIdPair& msgId, bool read) = 0; -//virtual bool setMessageStatus(const std::string &msgId, const uint32_t status, const uint32_t statusMask); -//virtual bool setGroupSubscribeFlags(const std::string &groupId, uint32_t subscribeFlags, uint32_t subscribeMask); + //virtual bool setMessageStatus(const std::string &msgId, const uint32_t status, const uint32_t statusMask); + //virtual bool setGroupSubscribeFlags(const std::string &groupId, uint32_t subscribeFlags, uint32_t subscribeMask); -//virtual bool groupRestoreKeys(const std::string &groupId); -//virtual bool groupShareKeys(const std::string &groupId, std::list& peers); + //virtual bool groupRestoreKeys(const std::string &groupId); + //virtual bool groupShareKeys(const std::string &groupId, std::list& peers); -virtual bool createGroup(uint32_t &token, RsGxsForumGroup &group) = 0; -virtual bool createMsg(uint32_t &token, RsGxsForumMsg &msg) = 0; - -/*! + virtual bool createGroup(uint32_t &token, RsGxsForumGroup &group) = 0; + virtual bool createMsg(uint32_t &token, RsGxsForumMsg &msg) = 0; + /*! * To update forum group with new information * @param token the token used to check completion status of update * @param group group to be updated, groupId element must be set or will be rejected * @return false groupId not set, true if set and accepted (still check token for completion) */ -virtual bool updateGroup(uint32_t &token, RsGxsForumGroup &group) = 0; + virtual bool updateGroup(uint32_t &token, RsGxsForumGroup &group) = 0; }; diff --git a/libretroshare/src/retroshare/rsgxsiface.h b/libretroshare/src/retroshare/rsgxsiface.h index 8eb1ac6ae..3e2ae55ae 100644 --- a/libretroshare/src/retroshare/rsgxsiface.h +++ b/libretroshare/src/retroshare/rsgxsiface.h @@ -27,6 +27,7 @@ #ifndef RSGXSIFACE_H_ #define RSGXSIFACE_H_ +#include "retroshare/rsreputations.h" #include "retroshare/rsgxsservice.h" #include "gxs/rsgxsdata.h" #include "retroshare/rsgxsifacetypes.h" @@ -181,6 +182,8 @@ public: virtual uint32_t getDefaultSyncPeriod() = 0; virtual uint32_t getSyncPeriod(const RsGxsGroupId& grpId) = 0; virtual void setSyncPeriod(const RsGxsGroupId& grpId,uint32_t age_in_secs) = 0; + + virtual RsReputations::ReputationLevel minReputationForForwardingMessages(uint32_t group_sign_flags,uint32_t identity_flags)=0; }; diff --git a/libretroshare/src/retroshare/rsgxsifacehelper.h b/libretroshare/src/retroshare/rsgxsifacehelper.h index e23f1aeb1..fbe12570e 100644 --- a/libretroshare/src/retroshare/rsgxsifacehelper.h +++ b/libretroshare/src/retroshare/rsgxsifacehelper.h @@ -27,6 +27,7 @@ */ #include "retroshare/rsgxsiface.h" +#include "retroshare/rsreputations.h" #include "rsgxsflags.h" /*! @@ -236,6 +237,10 @@ public: mGxs->setSyncPeriod(grpId,age_in_secs); } + RsReputations::ReputationLevel minReputationForForwardingMessages(uint32_t group_sign_flags,uint32_t identity_flags) + { + return mGxs->minReputationForForwardingMessages(group_sign_flags,identity_flags); + } private: RsGxsIface* mGxs; diff --git a/retroshare-gui/src/gui/Identity/IdDialog.cpp b/retroshare-gui/src/gui/Identity/IdDialog.cpp index 5dc948599..ef1841375 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.cpp +++ b/retroshare-gui/src/gui/Identity/IdDialog.cpp @@ -1857,9 +1857,9 @@ void IdDialog::insertIdDetails(uint32_t token) switch(info.mOverallReputationLevel) { case RsReputations::REPUTATION_LOCALLY_POSITIVE: ui->overallOpinion_TF->setText(tr("Positive")) ; break ; - case RsReputations::REPUTATION_LOCALLY_NEGATIVE: ui->overallOpinion_TF->setText(tr("Negative (Banned)")) ; break ; - case RsReputations::REPUTATION_REMOTELY_POSITIVE: ui->overallOpinion_TF->setText(tr("Positive, according to your friends")) ; break ; - case RsReputations::REPUTATION_REMOTELY_NEGATIVE: ui->overallOpinion_TF->setText(tr("Negative, according to your friends")) ; break ; + case RsReputations::REPUTATION_LOCALLY_NEGATIVE: ui->overallOpinion_TF->setText(tr("Negative (Banned by you)")) ; break ; + case RsReputations::REPUTATION_REMOTELY_POSITIVE: ui->overallOpinion_TF->setText(tr("Positive (according to your friends)")) ; break ; + case RsReputations::REPUTATION_REMOTELY_NEGATIVE: ui->overallOpinion_TF->setText(tr("Negative (according to your friends)")) ; break ; default: case RsReputations::REPUTATION_NEUTRAL: ui->overallOpinion_TF->setText(tr("Neutral")) ; break ; } diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index dff9e9607..7c115aecc 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "GxsForumThreadWidget.h" #include "ui_GxsForumThreadWidget.h" @@ -61,6 +62,9 @@ #define IMAGE_DOWNLOADALL ":/images/startall.png" #define IMAGE_COPYLINK ":/images/copyrslink.png" #define IMAGE_BIOHAZARD ":/icons/yellow_biohazard64.png" +#define IMAGE_WARNING_YELLOW ":/icons/warning_yellow_128.png" +#define IMAGE_WARNING_RED ":/icons/warning_red_128.png" +#define IMAGE_VOID ":/icons/void_128.png" #define IMAGE_POSITIVE_OPINION ":/icons/png/thumbs-up.png" #define IMAGE_NEUTRAL_OPINION ":/icons/png/thumbs-neutral.png" #define IMAGE_NEGATIVE_OPINION ":/icons/png/thumbs-down.png" @@ -92,6 +96,45 @@ #define ROLE_THREAD_COUNT 4 +class DistributionItemDelegate: public QStyledItemDelegate +{ +public: + DistributionItemDelegate() {} + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const + { + Q_ASSERT(index.isValid()); + + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + // disable default icon + opt.icon = QIcon(); + // draw default item + QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter, 0); + + const QRect r = option.rect; + + QIcon icon ; + + // get pixmap + unsigned int warning_level = qvariant_cast(index.data(Qt::DecorationRole)); + + switch(warning_level) + { + default: + case 0: icon = QIcon(IMAGE_VOID); break; + case 1: icon = QIcon(IMAGE_WARNING_YELLOW); break; + case 2: icon = QIcon(IMAGE_WARNING_RED); break; + } + + QPixmap pix = icon.pixmap(r.size()); + + // draw pixmap at center of item + const QPoint p = QPoint((r.width() - pix.width())/2, (r.height() - pix.height())/2); + painter->drawPixmap(r.topLeft() + p, pix); + } +}; + GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget *parent) : GxsMessageFrameWidget(rsGxsForums, parent), ui(new Ui::GxsForumThreadWidget) @@ -143,7 +186,7 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget mThreadCompareRole = new RSTreeWidgetItemCompareRole; mThreadCompareRole->setRole(COLUMN_THREAD_DATE, ROLE_THREAD_SORT); - ui->threadTreeWidget->setItemDelegateForColumn(COLUMN_THREAD_DISTRIBUTION,new ReputationItemDelegate(RsReputations::REPUTATION_NEUTRAL)) ; + ui->threadTreeWidget->setItemDelegateForColumn(COLUMN_THREAD_DISTRIBUTION,new DistributionItemDelegate()) ; connect(ui->threadTreeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(threadListCustomPopupMenu(QPoint))); connect(ui->postText, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuTextBrowser(QPoint))); @@ -1018,9 +1061,18 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum // Early check for a message that should be hidden because its author // is flagged with a bad reputation - uint32_t reputation_level = rsIdentity->overallReputationLevel(msg.mMeta.mAuthorId) ; + RsIdentityDetails iddetails ; - bool redacted = (reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE) ; + RsReputations::ReputationLevel reputation_level = RsReputations::REPUTATION_NEUTRAL ; + bool redacted = false ; + + if(!rsIdentity->getIdDetails(msg.mMeta.mAuthorId,iddetails)) + std::cerr << "(WW) Cannot grab identity details for " << msg.mMeta.mAuthorId.toStdString() << std::endl; + else + { + reputation_level = iddetails.mReputation.mOverallReputationLevel ; + redacted = (reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE) ; + } GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(mThreadCompareRole,GxsIdDetails::ICON_TYPE_AVATAR ); item->moveToThread(ui->threadTreeWidget->thread()); @@ -1030,18 +1082,27 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum else item->setText(COLUMN_THREAD_TITLE, QString::fromUtf8(msg.mMeta.mMsgName.c_str())); - item->setData(COLUMN_THREAD_DISTRIBUTION,Qt::DecorationRole, reputation_level) ; - QString rep_tooltip_str ; - switch(reputation_level) + uint32_t rep_warning_level ; + + if(reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE) { - case RsReputations::REPUTATION_LOCALLY_NEGATIVE: rep_tooltip_str = tr("You have banned this ID. The message will not be\ndisplayed nor forwarded to your friends.") ; break; - case RsReputations::REPUTATION_REMOTELY_NEGATIVE: rep_tooltip_str = tr("You have not set an opinion for this person,\n and your friends vote negatively: Spam regulation \nprevents the message to be forwarded to your friends.") ; break; - case RsReputations::REPUTATION_NEUTRAL: rep_tooltip_str = tr("You have not set an opinion for this person,\n and neither have your friends: Spam regulation\nprevents the message to be forwarded to your friends.") ; break; - default: - rep_tooltip_str = tr("Message will be forwarded to your friends.") ; break; + rep_warning_level = 2 ; + rep_tooltip_str = tr("You have banned this ID. The message will not be\ndisplayed nor forwarded to your friends.") ; } + else if(reputation_level < rsGxsForums->minReputationForForwardingMessages(mForumGroup.mMeta.mSignFlags,iddetails.mFlags)) + { + rep_warning_level = 1 ; + rep_tooltip_str = tr("You have not set an opinion for this person,\n and your friends do not vote positively: Spam regulation \nprevents the message to be forwarded to your friends.") ; + } + else + { + rep_warning_level = 0 ; + rep_tooltip_str = tr("Message will be forwarded to your friends.") ; + } + item->setData(COLUMN_THREAD_DISTRIBUTION,Qt::ToolTipRole,rep_tooltip_str) ; + item->setData(COLUMN_THREAD_DISTRIBUTION,Qt::DecorationRole,rep_warning_level) ; //msg.mMeta.mChildTs Was not updated when received new child // so do it here. diff --git a/retroshare-gui/src/gui/icons.qrc b/retroshare-gui/src/gui/icons.qrc index 1a63b784d..8b57ae68a 100644 --- a/retroshare-gui/src/gui/icons.qrc +++ b/retroshare-gui/src/gui/icons.qrc @@ -216,6 +216,8 @@ icons/user-offline_64.png icons/user-online_64.png icons/void_128.png + icons/warning_red_128.png + icons/warning_yellow_128.png icons/yahoo.png icons/yandex.png icons/yellow_biohazard64.png diff --git a/retroshare-gui/src/gui/icons/warning_red_128.png b/retroshare-gui/src/gui/icons/warning_red_128.png new file mode 100644 index 0000000000000000000000000000000000000000..3399fee0c54c14f18dc826fdf42198a5345ab13a GIT binary patch literal 27339 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 F{{fkZ(R2U+ literal 0 HcmV?d00001 diff --git a/retroshare-gui/src/gui/icons/warning_yellow_128.png b/retroshare-gui/src/gui/icons/warning_yellow_128.png new file mode 100644 index 0000000000000000000000000000000000000000..0d809ce654653bb1f3d508a1ed63f15204dbb654 GIT binary patch literal 25848 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 literal 0 HcmV?d00001 From 5ed879c035dbfcf9306161af3fa4538384a9bc87 Mon Sep 17 00:00:00 2001 From: csoler Date: Mon, 26 Dec 2016 17:00:21 +0100 Subject: [PATCH 13/23] added small debug output in GxsForumThreadWidget --- retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index 7c115aecc..e1ba6e54a 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -1100,6 +1100,7 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum rep_warning_level = 0 ; rep_tooltip_str = tr("Message will be forwarded to your friends.") ; } + std::cerr << "Inserting post from ID " << msg.mMeta.mAuthorId << ", group flags=" << std::hex << mForumGroup.mMeta.mSignFlags << " Identity flags = " << iddetails.mFlags << ": warning level = " << rep_warning_level << std::dec << std::endl; item->setData(COLUMN_THREAD_DISTRIBUTION,Qt::ToolTipRole,rep_tooltip_str) ; item->setData(COLUMN_THREAD_DISTRIBUTION,Qt::DecorationRole,rep_warning_level) ; From 64737827a14d0b7603521fa8e626a7a7c4ddf455 Mon Sep 17 00:00:00 2001 From: csoler Date: Tue, 27 Dec 2016 17:17:23 +0100 Subject: [PATCH 14/23] added an icon for when information is missing for an ID in forum distribution --- libretroshare/src/retroshare/rsreputations.h | 3 ++- .../src/gui/gxsforums/GxsForumThreadWidget.cpp | 15 +++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/libretroshare/src/retroshare/rsreputations.h b/libretroshare/src/retroshare/rsreputations.h index ce8e46401..bbe86b21b 100644 --- a/libretroshare/src/retroshare/rsreputations.h +++ b/libretroshare/src/retroshare/rsreputations.h @@ -42,7 +42,8 @@ public: REPUTATION_REMOTELY_NEGATIVE = 0x01, // local opinion is neutral and friends are positive in average REPUTATION_NEUTRAL = 0x02, // no reputation information ; REPUTATION_REMOTELY_POSITIVE = 0x03, // local opinion is neutral and friends are negative in average - REPUTATION_LOCALLY_POSITIVE = 0x04 // local opinion is negative + REPUTATION_LOCALLY_POSITIVE = 0x04, // local opinion is negative + REPUTATION_UNKNOWN = 0x05 // missing info }; struct ReputationInfo diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index e1ba6e54a..b1b4e05bb 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -64,6 +64,7 @@ #define IMAGE_BIOHAZARD ":/icons/yellow_biohazard64.png" #define IMAGE_WARNING_YELLOW ":/icons/warning_yellow_128.png" #define IMAGE_WARNING_RED ":/icons/warning_red_128.png" +#define IMAGE_WARNING_UNKNOWN":/icons/bullet_grey_128.png" #define IMAGE_VOID ":/icons/void_128.png" #define IMAGE_POSITIVE_OPINION ":/icons/png/thumbs-up.png" #define IMAGE_NEUTRAL_OPINION ":/icons/png/thumbs-neutral.png" @@ -125,6 +126,7 @@ public: case 0: icon = QIcon(IMAGE_VOID); break; case 1: icon = QIcon(IMAGE_WARNING_YELLOW); break; case 2: icon = QIcon(IMAGE_WARNING_RED); break; + case 3: icon = QIcon(IMAGE_WARNING_UNKNOWN); break; } QPixmap pix = icon.pixmap(r.size()); @@ -1066,13 +1068,13 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum RsReputations::ReputationLevel reputation_level = RsReputations::REPUTATION_NEUTRAL ; bool redacted = false ; - if(!rsIdentity->getIdDetails(msg.mMeta.mAuthorId,iddetails)) - std::cerr << "(WW) Cannot grab identity details for " << msg.mMeta.mAuthorId.toStdString() << std::endl; - else + if(rsIdentity->getIdDetails(msg.mMeta.mAuthorId,iddetails)) { reputation_level = iddetails.mReputation.mOverallReputationLevel ; redacted = (reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE) ; } + else + reputation_level = RsReputations::REPUTATION_UNKNOWN ; GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(mThreadCompareRole,GxsIdDetails::ICON_TYPE_AVATAR ); item->moveToThread(ui->threadTreeWidget->thread()); @@ -1085,7 +1087,12 @@ QTreeWidgetItem *GxsForumThreadWidget::convertMsgToThreadWidget(const RsGxsForum QString rep_tooltip_str ; uint32_t rep_warning_level ; - if(reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE) + if(reputation_level == RsReputations::REPUTATION_UNKNOWN) + { + rep_warning_level = 3 ; + rep_tooltip_str = tr("Information for this identity is currently missing.") ; + } + else if(reputation_level == RsReputations::REPUTATION_LOCALLY_NEGATIVE) { rep_warning_level = 2 ; rep_tooltip_str = tr("You have banned this ID. The message will not be\ndisplayed nor forwarded to your friends.") ; From 3a92124aac2c77508271110811367d7562a17f27 Mon Sep 17 00:00:00 2001 From: csoler Date: Tue, 27 Dec 2016 20:42:47 +0100 Subject: [PATCH 15/23] added method to show a given post author into the people tab --- retroshare-gui/src/gui/Identity/IdDialog.cpp | 16 ++ retroshare-gui/src/gui/Identity/IdDialog.h | 1 + retroshare-gui/src/gui/MainWindow.cpp | 6 +- retroshare-gui/src/gui/MainWindow.h | 3 + .../gui/gxsforums/GxsForumThreadWidget.cpp | 141 ++++++++++++++---- .../src/gui/gxsforums/GxsForumThreadWidget.h | 15 +- 6 files changed, 151 insertions(+), 31 deletions(-) diff --git a/retroshare-gui/src/gui/Identity/IdDialog.cpp b/retroshare-gui/src/gui/Identity/IdDialog.cpp index ef1841375..dfba6030a 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.cpp +++ b/retroshare-gui/src/gui/Identity/IdDialog.cpp @@ -1941,6 +1941,22 @@ void IdDialog::modifyReputation() return; } +void IdDialog::navigate(const RsGxsId& gxs_id) +{ + std::cerr << "IdDialog::navigate to " << gxs_id.toStdString() << std::endl; + + // in order to do this, we just select the correct ID in the ID list + + QList select = ui->idTreeWidget->findItems(QString::fromStdString(gxs_id.toStdString()),Qt::MatchExactly | Qt::MatchRecursive | Qt::MatchWrap,RSID_COL_KEYID) ; + + if(select.empty()) + { + std::cerr << "Cannot find item with ID " << gxs_id << " in ID list." << std::endl; + return ; + } + ui->idTreeWidget->setCurrentItem(*select.begin(),true); + +} void IdDialog::updateDisplay(bool complete) { /* Update identity list */ diff --git a/retroshare-gui/src/gui/Identity/IdDialog.h b/retroshare-gui/src/gui/Identity/IdDialog.h index 6c849def4..0d8886279 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.h +++ b/retroshare-gui/src/gui/Identity/IdDialog.h @@ -62,6 +62,7 @@ public: void loadRequest(const TokenQueue *queue, const TokenRequest &req); + void navigate(const RsGxsId& gxs_id) ; // shows the info about this particular ID protected: virtual void updateDisplay(bool complete); diff --git a/retroshare-gui/src/gui/MainWindow.cpp b/retroshare-gui/src/gui/MainWindow.cpp index c653503b1..807b43667 100644 --- a/retroshare-gui/src/gui/MainWindow.cpp +++ b/retroshare-gui/src/gui/MainWindow.cpp @@ -352,7 +352,6 @@ void MainWindow::initStackedPage() addPage(peopleDialog = new PeopleDialog(ui->stackPages), grp, ¬ify); #endif - IdDialog *idDialog = NULL; addPage(idDialog = new IdDialog(ui->stackPages), grp, ¬ify); //#ifdef RS_USE_CIRCLES @@ -878,6 +877,9 @@ void SetForegroundWindowInternal(HWND hWnd) case Friends: _instance->ui->stackPages->setCurrentPage( _instance->friendsDialog ); break; + case People: + _instance->ui->stackPages->setCurrentPage( _instance->idDialog ); + break; case ChatLobby: _instance->ui->stackPages->setCurrentPage( _instance->chatLobbyDialog ); break; @@ -974,6 +976,8 @@ void SetForegroundWindowInternal(HWND hWnd) return _instance->friendsDialog->networkDialog; case Friends: return _instance->friendsDialog; + case People: + return _instance->idDialog; case ChatLobby: return _instance->chatLobbyDialog; case Transfers: diff --git a/retroshare-gui/src/gui/MainWindow.h b/retroshare-gui/src/gui/MainWindow.h index 98c17b3eb..51fc4835b 100644 --- a/retroshare-gui/src/gui/MainWindow.h +++ b/retroshare-gui/src/gui/MainWindow.h @@ -51,6 +51,7 @@ class GxsChannelDialog ; class GxsForumsDialog ; class PostedDialog; class FriendsDialog; +class IdDialog; class ChatLobbyWidget; class ChatDialog; class NetworkDialog; @@ -103,6 +104,7 @@ public: Links = 10, /** Links page. */ #endif Posted = 11, /** Posted links */ + People = 12 /** People page. */ }; /** Create main window */ @@ -137,6 +139,7 @@ public: NewsFeed *newsFeed; FriendsDialog *friendsDialog; TransfersDialog *transfersDialog; + IdDialog *idDialog ; ChatLobbyWidget *chatLobbyDialog; MessagesDialog *messagesDialog; SharedFilesDialog *sharedfilesDialog; diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index b1b4e05bb..62a756b49 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -34,9 +34,11 @@ #include "gui/common/RSElidedItemDelegate.h" #include "gui/settings/rsharesettings.h" #include "gui/gxs/GxsIdTreeWidgetItem.h" +#include "gui/Identity/IdDialog.h" #include "gui/gxs/GxsIdDetails.h" #include "util/HandleRichText.h" #include "CreateGxsForumMsg.h" +#include "gui/MainWindow.h" #include "gui/msgs/MessageComposer.h" #include "util/DateTime.h" #include "gui/common/UIStateHelper.h" @@ -55,19 +57,19 @@ //#define DEBUG_FORUMS /* Images for context menu icons */ -#define IMAGE_MESSAGE ":/images/mail_new.png" -#define IMAGE_MESSAGEREPLY ":/images/mail_reply.png" -#define IMAGE_MESSAGEREMOVE ":/images/mail_delete.png" -#define IMAGE_DOWNLOAD ":/images/start.png" -#define IMAGE_DOWNLOADALL ":/images/startall.png" -#define IMAGE_COPYLINK ":/images/copyrslink.png" -#define IMAGE_BIOHAZARD ":/icons/yellow_biohazard64.png" -#define IMAGE_WARNING_YELLOW ":/icons/warning_yellow_128.png" -#define IMAGE_WARNING_RED ":/icons/warning_red_128.png" -#define IMAGE_WARNING_UNKNOWN":/icons/bullet_grey_128.png" -#define IMAGE_VOID ":/icons/void_128.png" +#define IMAGE_MESSAGE ":/images/mail_new.png" +#define IMAGE_MESSAGEREPLY ":/images/mail_reply.png" +#define IMAGE_MESSAGEREMOVE ":/images/mail_delete.png" +#define IMAGE_DOWNLOAD ":/images/start.png" +#define IMAGE_DOWNLOADALL ":/images/startall.png" +#define IMAGE_COPYLINK ":/images/copyrslink.png" +#define IMAGE_BIOHAZARD ":/icons/yellow_biohazard64.png" +#define IMAGE_WARNING_YELLOW ":/icons/warning_yellow_128.png" +#define IMAGE_WARNING_RED ":/icons/warning_red_128.png" +#define IMAGE_WARNING_UNKNOWN ":/icons/bullet_grey_128.png" +#define IMAGE_VOID ":/icons/void_128.png" #define IMAGE_POSITIVE_OPINION ":/icons/png/thumbs-up.png" -#define IMAGE_NEUTRAL_OPINION ":/icons/png/thumbs-neutral.png" +#define IMAGE_NEUTRAL_OPINION ":/icons/png/thumbs-neutral.png" #define IMAGE_NEGATIVE_OPINION ":/icons/png/thumbs-down.png" #define VIEW_LAST_POST 0 @@ -122,10 +124,10 @@ public: switch(warning_level) { - default: case 0: icon = QIcon(IMAGE_VOID); break; case 1: icon = QIcon(IMAGE_WARNING_YELLOW); break; case 2: icon = QIcon(IMAGE_WARNING_RED); break; + default: case 3: icon = QIcon(IMAGE_WARNING_UNKNOWN); break; } @@ -143,14 +145,15 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget { ui->setupUi(this); - mTokenTypeGroupData = nextTokenType(); - mTokenTypeInsertThreads = nextTokenType(); - mTokenTypeMessageData = nextTokenType(); - mTokenTypeReplyMessage = nextTokenType(); - mTokenTypeReplyForumMessage = nextTokenType(); - mTokenTypeNegativeAuthor = nextTokenType(); - mTokenTypeNeutralAuthor = nextTokenType(); - mTokenTypePositiveAuthor = nextTokenType(); + mTokenTypeGroupData = nextTokenType(); + mTokenTypeInsertThreads = nextTokenType(); + mTokenTypeMessageData = nextTokenType(); + mTokenTypeReplyMessage = nextTokenType(); + mTokenTypeReplyForumMessage = nextTokenType(); + mTokenTypeShowAuthorInPeople = nextTokenType(); + mTokenTypeNegativeAuthor = nextTokenType(); + mTokenTypeNeutralAuthor = nextTokenType(); + mTokenTypePositiveAuthor = nextTokenType(); setUpdateWhenInvisible(true); @@ -509,6 +512,9 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) QAction *markMsgAsUnreadChildren = new QAction(QIcon(":/images/message-mail.png"), tr("Mark as unread") + " (" + tr ("with children") + ")", &contextMnu); connect(markMsgAsUnreadChildren, SIGNAL(triggered()), this, SLOT(markMsgAsUnreadChildren())); + QAction *showinpeopleAct = new QAction(QIcon(":/images/message-mail.png"), tr("Show in people tab"), &contextMnu); + connect(showinpeopleAct, SIGNAL(triggered()), this, SLOT(showInPeopleTab())); + if (IS_GROUP_SUBSCRIBED(mSubscribeFlags)) { QList rows; QList rowsRead; @@ -566,10 +572,13 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) contextMnu.addAction(collapseAll); contextMnu.addSeparator(); - contextMnu.addAction(flagaspositiveAct); - contextMnu.addAction(flagasneutralAct); - contextMnu.addAction(flagasnegativeAct); - contextMnu.addSeparator(); + + QMenu *submenu1 = contextMnu.addMenu(tr("Reputation")) ; + submenu1->addAction(flagaspositiveAct); + submenu1->addAction(flagasneutralAct); + submenu1->addAction(flagasnegativeAct); + contextMnu.addAction(showinpeopleAct); + contextMnu.addAction(replyauthorAct); contextMnu.exec(QCursor::pos()); @@ -1748,6 +1757,17 @@ void GxsForumThreadWidget::setMsgReadStatus(QList &rows, bool } } +void GxsForumThreadWidget::showInPeopleTab() +{ + if (groupId().isNull() || mThreadId.isNull()) { + QMessageBox::information(this, tr("RetroShare"),tr("You cant act on the author to a non-existant Message")); + return; + } + + RsGxsGrpMsgIdPair postId = std::make_pair(groupId(), mThreadId); + requestMsgData_ShowAuthorInPeople(postId) ; +} + void GxsForumThreadWidget::markMsgAsReadUnread (bool read, bool children, bool forum) { if (groupId().isNull() || !IS_GROUP_SUBSCRIBED(mSubscribeFlags)) { @@ -2013,6 +2033,18 @@ void GxsForumThreadWidget::replyMessageData(const RsGxsForumMsg &msg) } } +void GxsForumThreadWidget::showAuthorInPeople(const RsGxsForumMsg& msg) +{ + if ((msg.mMeta.mGroupId != groupId()) || (msg.mMeta.mMsgId != mThreadId)) + { + std::cerr << "GxsForumThreadWidget::replyMessageData() ERROR Message Ids have changed!"; + std::cerr << std::endl; + return; + } + RsGxsGrpMsgIdPair postId = std::make_pair(groupId(), mThreadId); + requestMsgData_ShowAuthorInPeople(postId); +} + void GxsForumThreadWidget::replyForumMessageData(const RsGxsForumMsg &msg) { if ((msg.mMeta.mGroupId != groupId()) || (msg.mMeta.mMsgId != mThreadId)) @@ -2266,6 +2298,24 @@ void GxsForumThreadWidget::requestMsgData_ReplyMessage(const RsGxsGrpMsgIdPair & mTokenQueue->requestMsgInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts, msgIds, mTokenTypeReplyMessage); } +void GxsForumThreadWidget::requestMsgData_ShowAuthorInPeople(const RsGxsGrpMsgIdPair& msgId) +{ + RsTokReqOptions opts; + opts.mReqType = GXS_REQUEST_TYPE_MSG_DATA; + +#ifdef DEBUG_FORUMS + std::cerr << "GxsForumThreadWidget::requestMsgData_ReplyMessage(" << msgId.first << "," << msgId.second << ")"; + std::cerr << std::endl; +#endif + + GxsMsgReq msgIds; + std::vector &vect = msgIds[msgId.first]; + vect.push_back(msgId.second); + + uint32_t token; + mTokenQueue->requestMsgInfo(token, RS_TOKREQ_ANSTYPE_DATA, opts, msgIds, mTokenTypeShowAuthorInPeople); +} + void GxsForumThreadWidget::requestMsgData_ReplyForumMessage(const RsGxsGrpMsgIdPair &msgId) { RsTokReqOptions opts; @@ -2335,6 +2385,42 @@ void GxsForumThreadWidget::loadMsgData_ReplyForumMessage(const uint32_t &token) } } +void GxsForumThreadWidget::loadMsgData_ShowAuthorInPeople(const uint32_t &token) +{ +#ifdef DEBUG_FORUMS + std::cerr << "GxsForumThreadWidget::loadMsgData_ReplyMessage()"; + std::cerr << std::endl; +#endif + + std::vector msgs; + if (rsGxsForums->getMsgData(token, msgs)) + { + if (msgs.size() != 1) + { + std::cerr << "GxsForumThreadWidget::loadMsgData_showAuthorInPeople() ERROR Wrong number of answers"; + std::cerr << std::endl; + return; + } + + if(msgs[0].mMeta.mAuthorId.isNull()) + { + std::cerr << "GxsForumThreadWidget::loadMsgData_showAuthorInPeople() ERROR Missing Message Data..."; + std::cerr << std::endl; + } + + /* window will destroy itself! */ + IdDialog *idDialog = dynamic_cast(MainWindow::getPage(MainWindow::People)); + + if (!idDialog) + return ; + + MainWindow::showWindow(MainWindow::People); + idDialog->navigate(RsGxsId(msgs[0].mMeta.mAuthorId)); + } + else + std::cerr << "GxsForumThreadWidget::loadMsgData_showAuthorInPeople() ERROR Missing Message Data..."; +} + void GxsForumThreadWidget::loadMsgData_BanAuthor(const uint32_t &token) { #ifdef DEBUG_FORUMS @@ -2399,6 +2485,11 @@ void GxsForumThreadWidget::loadRequest(const TokenQueue *queue, const TokenReque return; } + if (req.mUserType == mTokenTypeShowAuthorInPeople) { + loadMsgData_ShowAuthorInPeople(req.mToken); + return; + } + if (req.mUserType == mTokenTypeNegativeAuthor) { loadMsgData_BanAuthor(req.mToken); return; diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h index 2a3073f00..dfd5322e4 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h @@ -80,7 +80,8 @@ private slots: void replyMessageData(const RsGxsForumMsg &msg); void replyForumMessageData(const RsGxsForumMsg &msg); - + void showAuthorInPeople(const RsGxsForumMsg& msg); + void saveImage(); @@ -94,6 +95,7 @@ private slots: void markMsgAsUnreadChildren(); void copyMessageLink(); + void showInPeopleTab(); /* handle splitter */ void togglethreadview(); @@ -145,13 +147,15 @@ private: static void loadAuthorIdCallback(GxsIdDetailsType type, const RsIdentityDetails &details, QObject *object, const QVariant &/*data*/); void requestMessageData(const RsGxsGrpMsgIdPair &msgId); - void loadMessageData(const uint32_t &token); void requestMsgData_ReplyMessage(const RsGxsGrpMsgIdPair &msgId); - void loadMsgData_ReplyMessage(const uint32_t &token); - + void requestMsgData_ShowAuthorInPeople(const RsGxsGrpMsgIdPair &msgId); void requestMsgData_ReplyForumMessage(const RsGxsGrpMsgIdPair &msgId); + + void loadMessageData(const uint32_t &token); + void loadMsgData_ReplyMessage(const uint32_t &token); void loadMsgData_ReplyForumMessage(const uint32_t &token); - void loadMsgData_BanAuthor(const uint32_t &token); + void loadMsgData_ShowAuthorInPeople(const uint32_t &token); + void loadMsgData_BanAuthor(const uint32_t &token); private: RsGxsGroupId mLastForumID; @@ -173,6 +177,7 @@ private: uint32_t mTokenTypeMessageData; uint32_t mTokenTypeReplyMessage; uint32_t mTokenTypeReplyForumMessage; + uint32_t mTokenTypeShowAuthorInPeople; uint32_t mTokenTypeNegativeAuthor; uint32_t mTokenTypePositiveAuthor; uint32_t mTokenTypeNeutralAuthor; From da881e1f5b9dec20ec1405c85cb97538ad8057f4 Mon Sep 17 00:00:00 2001 From: csoler Date: Tue, 27 Dec 2016 21:13:37 +0100 Subject: [PATCH 16/23] fixed setting of author reputation from forum thread menu --- .../gui/gxsforums/GxsForumThreadWidget.cpp | 39 ++++++++++++------- .../src/gui/gxsforums/GxsForumThreadWidget.h | 2 +- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index 62a756b49..428dda5e0 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -472,7 +472,7 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) QAction *replyAct = new QAction(QIcon(IMAGE_MESSAGEREPLY), tr("Reply"), &contextMnu); connect(replyAct, SIGNAL(triggered()), this, SLOT(replytoforummessage())); - QAction *replyauthorAct = new QAction(QIcon(IMAGE_MESSAGEREPLY), tr("Reply with private message"), &contextMnu); + QAction *replyauthorAct = new QAction(QIcon(IMAGE_MESSAGEREPLY), tr("Reply to author with private message"), &contextMnu); connect(replyauthorAct, SIGNAL(triggered()), this, SLOT(replytomessage())); QAction *flagaspositiveAct = new QAction(QIcon(IMAGE_POSITIVE_OPINION), tr("Give positive opinion"), &contextMnu); @@ -480,7 +480,7 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) flagaspositiveAct->setData(mTokenTypePositiveAuthor) ; connect(flagaspositiveAct, SIGNAL(triggered()), this, SLOT(flagperson())); - QAction *flagasneutralAct = new QAction(QIcon(IMAGE_NEUTRAL_OPINION), tr("Give neutral opinion to this author"), &contextMnu); + QAction *flagasneutralAct = new QAction(QIcon(IMAGE_NEUTRAL_OPINION), tr("Give neutral opinion"), &contextMnu); flagasneutralAct->setToolTip(tr("Doing this, you trust your friends to decide to forward this message or not.")) ; flagasneutralAct->setData(mTokenTypeNeutralAuthor) ; connect(flagasneutralAct, SIGNAL(triggered()), this, SLOT(flagperson())); @@ -512,7 +512,7 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) QAction *markMsgAsUnreadChildren = new QAction(QIcon(":/images/message-mail.png"), tr("Mark as unread") + " (" + tr ("with children") + ")", &contextMnu); connect(markMsgAsUnreadChildren, SIGNAL(triggered()), this, SLOT(markMsgAsUnreadChildren())); - QAction *showinpeopleAct = new QAction(QIcon(":/images/message-mail.png"), tr("Show in people tab"), &contextMnu); + QAction *showinpeopleAct = new QAction(QIcon(":/images/message-mail.png"), tr("Show author in people tab"), &contextMnu); connect(showinpeopleAct, SIGNAL(triggered()), this, SLOT(showInPeopleTab())); if (IS_GROUP_SUBSCRIBED(mSubscribeFlags)) { @@ -573,7 +573,7 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) contextMnu.addSeparator(); - QMenu *submenu1 = contextMnu.addMenu(tr("Reputation")) ; + QMenu *submenu1 = contextMnu.addMenu(tr("Author's reputation")) ; submenu1->addAction(flagaspositiveAct); submenu1->addAction(flagasneutralAct); submenu1->addAction(flagasnegativeAct); @@ -1953,24 +1953,24 @@ static QString buildReplyHeader(const RsMsgMetaData &meta) void GxsForumThreadWidget::flagperson() { - // no need to use the token system for that, since we just need to find out the author's name, which is in the item. - + // no need to use the token system for that, since we just need to find out the author's name, which is in the item. + if (groupId().isNull() || mThreadId.isNull()) { QMessageBox::information(this, tr("RetroShare"),tr("You cant reply to a non-existant Message")); return; } - uint32_t token_type = qobject_cast(sender())->data().toUInt(); + uint32_t token_type = qobject_cast(sender())->data().toUInt(); // Get Message ... then complete replyMessageData(). RsGxsGrpMsgIdPair postId = std::make_pair(groupId(), mThreadId); - - RsTokReqOptions opts; + + RsTokReqOptions opts; opts.mReqType = GXS_REQUEST_TYPE_MSG_DATA; #ifdef DEBUG_FORUMS - std::cerr << "GxsForumThreadWidget::requestMsgData_BanAuthor(" << postId.first << "," << postId.second << ")"; - std::cerr << std::endl; + std::cerr << "GxsForumThreadWidget::requestMsgData_BanAuthor(" << postId.first << "," << postId.second << ")"; + std::cerr << std::endl; #endif GxsMsgReq msgIds; @@ -2421,7 +2421,7 @@ void GxsForumThreadWidget::loadMsgData_ShowAuthorInPeople(const uint32_t &token) std::cerr << "GxsForumThreadWidget::loadMsgData_showAuthorInPeople() ERROR Missing Message Data..."; } -void GxsForumThreadWidget::loadMsgData_BanAuthor(const uint32_t &token) +void GxsForumThreadWidget::loadMsgData_SetAuthorOpinion(const uint32_t &token,RsReputations::Opinion opinion) { #ifdef DEBUG_FORUMS std::cerr << "GxsForumThreadWidget::loadMsgData_BanAuthor()"; @@ -2439,7 +2439,8 @@ void GxsForumThreadWidget::loadMsgData_BanAuthor(const uint32_t &token) } std::cerr << " banning author id " << msgs[0].mMeta.mAuthorId << std::endl; - rsReputations->setOwnOpinion(msgs[0].mMeta.mAuthorId,RsReputations::OPINION_NEGATIVE) ; + + rsReputations->setOwnOpinion(msgs[0].mMeta.mAuthorId,opinion) ; } else { @@ -2490,8 +2491,18 @@ void GxsForumThreadWidget::loadRequest(const TokenQueue *queue, const TokenReque return; } + if (req.mUserType == mTokenTypePositiveAuthor) { + loadMsgData_SetAuthorOpinion(req.mToken,RsReputations::OPINION_POSITIVE); + return; + } + if (req.mUserType == mTokenTypeNegativeAuthor) { - loadMsgData_BanAuthor(req.mToken); + loadMsgData_SetAuthorOpinion(req.mToken,RsReputations::OPINION_NEGATIVE); + return; + } + + if (req.mUserType == mTokenTypeNeutralAuthor) { + loadMsgData_SetAuthorOpinion(req.mToken,RsReputations::OPINION_NEUTRAL); return; } } diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h index dfd5322e4..a2a6eecee 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h @@ -155,7 +155,7 @@ private: void loadMsgData_ReplyMessage(const uint32_t &token); void loadMsgData_ReplyForumMessage(const uint32_t &token); void loadMsgData_ShowAuthorInPeople(const uint32_t &token); - void loadMsgData_BanAuthor(const uint32_t &token); + void loadMsgData_SetAuthorOpinion(const uint32_t &token, RsReputations::Opinion opinion); private: RsGxsGroupId mLastForumID; From fb733916ef72f4025739f59376116531a847f47c Mon Sep 17 00:00:00 2001 From: csoler Date: Wed, 28 Dec 2016 18:58:49 +0100 Subject: [PATCH 17/23] added two fields in settings/people to setup the thresholds to decide between neutral and remotely positive/negative reputation --- libretroshare/src/retroshare/rsreputations.h | 9 +++- libretroshare/src/services/p3gxsreputation.cc | 39 ++++++++++++++++- libretroshare/src/services/p3gxsreputation.h | 8 ++++ .../src/gui/settings/PeoplePage.cpp | 9 ++-- retroshare-gui/src/gui/settings/PeoplePage.ui | 42 ++++++++++++------- 5 files changed, 86 insertions(+), 21 deletions(-) diff --git a/libretroshare/src/retroshare/rsreputations.h b/libretroshare/src/retroshare/rsreputations.h index bbe86b21b..b188ceb20 100644 --- a/libretroshare/src/retroshare/rsreputations.h +++ b/libretroshare/src/retroshare/rsreputations.h @@ -73,8 +73,13 @@ public: virtual float nodeAutoBanIdentitiesLimit() =0; virtual void setNodeAutoBanIdentitiesLimit(float f) =0; - // This one is a proxy designed to allow fast checking of a GXS id. - // it basically returns true if assessment is not ASSESSMENT_OK + virtual uint32_t thresholdForRemotelyNegativeReputation()=0; + virtual uint32_t thresholdForRemotelyPositiveReputation()=0; + virtual void setThresholdForRemotelyNegativeReputation(uint32_t thresh)=0; + virtual void setThresholdForRemotelyPositiveReputation(uint32_t thresh)=0; + + // This one is a proxy designed to allow fast checking of a GXS id. + // it basically returns true if assessment is not ASSESSMENT_OK virtual bool isIdentityBanned(const RsGxsId& id) =0; diff --git a/libretroshare/src/services/p3gxsreputation.cc b/libretroshare/src/services/p3gxsreputation.cc index f47dd6f2c..51d9c4df3 100644 --- a/libretroshare/src/services/p3gxsreputation.cc +++ b/libretroshare/src/services/p3gxsreputation.cc @@ -141,6 +141,9 @@ static const uint32_t BANNED_NODES_UPDATE_DELAY = 313 ; // update static const uint32_t REPUTATION_INFO_KEEP_DELAY = 86400*35; // remove old reputation info 5 days after last usage limit, in case the ID would come back.. static const uint32_t BANNED_NODES_INACTIVITY_KEEP = 86400*60; // remove all info about banned nodes after 2 months of inactivity +static const uint32_t REPUTATION_DEFAULT_MIN_VOTES_FOR_REMOTELY_POSITIVE = 1; // min difference in votes that makes friends opinion globally positive +static const uint32_t REPUTATION_DEFAULT_MIN_VOTES_FOR_REMOTELY_NEGATIVE = 1; // min difference in votes that makes friends opinion globally negative + p3GxsReputation::p3GxsReputation(p3LinkMgr *lm) :p3Service(), p3Config(), mReputationMtx("p3GxsReputation"), mLinkMgr(lm) @@ -159,6 +162,8 @@ p3GxsReputation::p3GxsReputation(p3LinkMgr *lm) mAutoBanIdentitiesLimit = REPUTATION_ASSESSMENT_THRESHOLD_X1; mAutoSetPositiveOptionToContacts = true; // default + mMinVotesForRemotelyPositive = REPUTATION_DEFAULT_MIN_VOTES_FOR_REMOTELY_POSITIVE; + mMinVotesForRemotelyNegative = REPUTATION_DEFAULT_MIN_VOTES_FOR_REMOTELY_NEGATIVE; } const std::string GXS_REPUTATION_APP_NAME = "gxsreputation"; @@ -871,9 +876,9 @@ bool p3GxsReputation::getReputationInfo(const RsGxsId& gxsid, const RsPgpId& own } // 2 - now, our own opinion is neutral, which means we rely on what our friends tell - if(info.mFriendsPositiveVotes > info.mFriendsNegativeVotes) + if(info.mFriendsPositiveVotes >= info.mFriendsNegativeVotes + mMinVotesForRemotelyPositive) info.mOverallReputationLevel = RsReputations::REPUTATION_REMOTELY_POSITIVE ; - else if(info.mFriendsPositiveVotes < info.mFriendsNegativeVotes) + else if(info.mFriendsPositiveVotes + mMinVotesForRemotelyNegative <= info.mFriendsNegativeVotes) info.mOverallReputationLevel = RsReputations::REPUTATION_REMOTELY_NEGATIVE ; else info.mOverallReputationLevel = RsReputations::REPUTATION_NEUTRAL ; @@ -885,6 +890,36 @@ bool p3GxsReputation::getReputationInfo(const RsGxsId& gxsid, const RsPgpId& own return true ; } +uint32_t p3GxsReputation::thresholdForRemotelyNegativeReputation() +{ + RsStackMutex stack(mReputationMtx); /****** LOCKED MUTEX *******/ + return mMinVotesForRemotelyNegative ; +} +uint32_t p3GxsReputation::thresholdForRemotelyPositiveReputation() +{ + RsStackMutex stack(mReputationMtx); /****** LOCKED MUTEX *******/ + return mMinVotesForRemotelyPositive ; +} +void p3GxsReputation::setThresholdForRemotelyPositiveReputation(uint32_t thresh) +{ + RsStackMutex stack(mReputationMtx); /****** LOCKED MUTEX *******/ + if(mMinVotesForRemotelyPositive == thresh || thresh==0) + return ; + + mMinVotesForRemotelyPositive = thresh ; + IndicateConfigChanged(); +} + +void p3GxsReputation::setThresholdForRemotelyNegativeReputation(uint32_t thresh) +{ + RsStackMutex stack(mReputationMtx); /****** LOCKED MUTEX *******/ + if(mMinVotesForRemotelyNegative == thresh || thresh==0) + return ; + + mMinVotesForRemotelyNegative = thresh ; + IndicateConfigChanged(); +} + void p3GxsReputation::banNode(const RsPgpId& id,bool b) { RsStackMutex stack(mReputationMtx); /****** LOCKED MUTEX *******/ diff --git a/libretroshare/src/services/p3gxsreputation.h b/libretroshare/src/services/p3gxsreputation.h index 8016ed48d..04b1b646d 100644 --- a/libretroshare/src/services/p3gxsreputation.h +++ b/libretroshare/src/services/p3gxsreputation.h @@ -120,6 +120,11 @@ public: virtual float nodeAutoBanIdentitiesLimit() ; virtual void setNodeAutoBanIdentitiesLimit(float f) ; + uint32_t thresholdForRemotelyNegativeReputation(); + uint32_t thresholdForRemotelyPositiveReputation(); + void setThresholdForRemotelyNegativeReputation(uint32_t thresh); + void setThresholdForRemotelyPositiveReputation(uint32_t thresh); + /***** overloaded from p3Service *****/ virtual int tick(); virtual int status(); @@ -185,6 +190,9 @@ private: std::set mPerNodeBannedIdsProxy ; //uint32_t mPgpAutoBanThreshold ; bool mBannedNodesProxyNeedsUpdate ; + + uint32_t mMinVotesForRemotelyPositive ; + uint32_t mMinVotesForRemotelyNegative ; }; #endif //SERVICE_RSGXSREPUTATION_HEADER diff --git a/retroshare-gui/src/gui/settings/PeoplePage.cpp b/retroshare-gui/src/gui/settings/PeoplePage.cpp index 6ce71233f..9a52cdb35 100644 --- a/retroshare-gui/src/gui/settings/PeoplePage.cpp +++ b/retroshare-gui/src/gui/settings/PeoplePage.cpp @@ -42,7 +42,8 @@ bool PeoplePage::save(QString &/*errmsg*/) else rsReputations->setNodeAutoPositiveOpinionForContacts(false) ; - rsReputations->setNodeAutoBanIdentitiesLimit(ui.autoBanIdentitiesLimit_SB->value()); + rsReputations->setThresholdForRemotelyPositiveReputation(ui.thresholdForPositive_SB->value()); + rsReputations->setThresholdForRemotelyNegativeReputation(ui.thresholdForNegative_SB->value()); return true; } @@ -51,8 +52,10 @@ bool PeoplePage::save(QString &/*errmsg*/) void PeoplePage::load() { bool auto_positive_contacts = rsReputations->nodeAutoPositiveOpinionForContacts() ; - float node_auto_ban_identities_limit = rsReputations->nodeAutoBanIdentitiesLimit(); + uint32_t threshold_for_positive = rsReputations->thresholdForRemotelyPositiveReputation(); + uint32_t threshold_for_negative = rsReputations->thresholdForRemotelyNegativeReputation(); ui.autoPositiveOpinion_CB->setChecked(auto_positive_contacts); - ui.autoBanIdentitiesLimit_SB->setValue(node_auto_ban_identities_limit); + ui.thresholdForPositive_SB->setValue(threshold_for_positive); + ui.thresholdForNegative_SB->setValue(threshold_for_negative); } diff --git a/retroshare-gui/src/gui/settings/PeoplePage.ui b/retroshare-gui/src/gui/settings/PeoplePage.ui index 08a5c4967..133634c0c 100644 --- a/retroshare-gui/src/gui/settings/PeoplePage.ui +++ b/retroshare-gui/src/gui/settings/PeoplePage.ui @@ -10,13 +10,13 @@ 441
- + Identities handling - + @@ -31,30 +31,44 @@ - - - + + + - Friend average opinion below which identities are banned: + Difference in votes to make friends globally negative: - - + + <html><head/><body><p>The default value of -0.6 needs 3 to 4 friends to set a negative opinion in order for that identity to be banned at your own node. This is a pretty conservative value. If you want to more easily get rid of banned identities, set the value to e.g. -0.2</p></body></html> - -1.000000000000000 + 1 - -0.010000000000000 + 100 - - 0.010000000000000 + + + + + + <html><head/><body><p>The default value of -0.6 needs 3 to 4 friends to set a negative opinion in order for that identity to be banned at your own node. This is a pretty conservative value. If you want to more easily get rid of banned identities, set the value to e.g. -0.2</p></body></html> - - -0.600000000000000 + + 1 + + + 100 + + + + + + + Difference in votes to make friends globally positive: From f3f0fcea05d1b3b94e8b548e7b8266523e2c3e89 Mon Sep 17 00:00:00 2001 From: csoler Date: Thu, 29 Dec 2016 10:34:57 +0100 Subject: [PATCH 18/23] added load/save of new thresholds in p3gxsreputation. Removed some unused variables --- libretroshare/src/retroshare/rsreputations.h | 5 -- libretroshare/src/services/p3gxsreputation.cc | 60 ++++++++----------- libretroshare/src/services/p3gxsreputation.h | 5 -- 3 files changed, 26 insertions(+), 44 deletions(-) diff --git a/libretroshare/src/retroshare/rsreputations.h b/libretroshare/src/retroshare/rsreputations.h index b188ceb20..1102a45be 100644 --- a/libretroshare/src/retroshare/rsreputations.h +++ b/libretroshare/src/retroshare/rsreputations.h @@ -65,13 +65,8 @@ public: // parameters - // virtual void setNodeAutoBanThreshold(uint32_t n) =0; - // virtual uint32_t nodeAutoBanThreshold() =0; - virtual void setNodeAutoPositiveOpinionForContacts(bool b) =0; virtual bool nodeAutoPositiveOpinionForContacts() =0; - virtual float nodeAutoBanIdentitiesLimit() =0; - virtual void setNodeAutoBanIdentitiesLimit(float f) =0; virtual uint32_t thresholdForRemotelyNegativeReputation()=0; virtual uint32_t thresholdForRemotelyPositiveReputation()=0; diff --git a/libretroshare/src/services/p3gxsreputation.cc b/libretroshare/src/services/p3gxsreputation.cc index 51d9c4df3..70aa58ab2 100644 --- a/libretroshare/src/services/p3gxsreputation.cc +++ b/libretroshare/src/services/p3gxsreputation.cc @@ -261,27 +261,6 @@ bool p3GxsReputation::nodeAutoPositiveOpinionForContacts() RsStackMutex stack(mReputationMtx); /****** LOCKED MUTEX *******/ return mAutoSetPositiveOptionToContacts ; } -float p3GxsReputation::nodeAutoBanIdentitiesLimit() -{ - RsStackMutex stack(mReputationMtx); /****** LOCKED MUTEX *******/ - return mAutoBanIdentitiesLimit - 1.0f; -} -void p3GxsReputation::setNodeAutoBanIdentitiesLimit(float f) -{ - RsStackMutex stack(mReputationMtx); /****** LOCKED MUTEX *******/ - - if(f < -1.0 || f >= 0.0) - { - std::cerr << "(EE) Unexpected value for auto ban identities limit: " << f << std::endl; - return ; - } - if(f != mAutoBanIdentitiesLimit) - { - mLastBannedNodesUpdate = 0 ; - mAutoBanIdentitiesLimit = f+1.0 ; - IndicateConfigChanged() ; - } -} int p3GxsReputation::status() { @@ -1097,9 +1076,14 @@ bool p3GxsReputation::saveList(bool& cleanup, std::list &savelist) RsConfigKeyValueSet *vitem = new RsConfigKeyValueSet ; RsTlvKeyValue kv; -// kv.key = "AUTO_BAN_NODES_THRESHOLD" ; -// rs_sprintf(kv.value, "%d", mPgpAutoBanThreshold); -// vitem->tlvkvs.pairs.push_back(kv) ; + + kv.key = "AUTO_REMOTELY_POSITIVE_THRESHOLD" ; + rs_sprintf(kv.value, "%d", mMinVotesForRemotelyPositive); + vitem->tlvkvs.pairs.push_back(kv) ; + + kv.key = "AUTO_REMOTELY_NEGATIVE_THRESHOLD" ; + rs_sprintf(kv.value, "%d", mMinVotesForRemotelyNegative); + vitem->tlvkvs.pairs.push_back(kv) ; kv.key = "AUTO_BAN_IDENTITIES_THRESHOLD" ; rs_sprintf(kv.value, "%f", mAutoBanIdentitiesLimit); @@ -1164,16 +1148,24 @@ bool p3GxsReputation::loadList(std::list& loadList) if(vitem) for(std::list::const_iterator kit = vitem->tlvkvs.pairs.begin(); kit != vitem->tlvkvs.pairs.end(); ++kit) { -// if(kit->key == "AUTO_BAN_NODES_THRESHOLD") -// { -// int val ; -// if (sscanf(kit->value.c_str(), "%d", &val) == 1) -// { -// mPgpAutoBanThreshold = val ; -// std::cerr << "Setting AutoBanNode threshold to " << val << std::endl ; -// mLastBannedNodesUpdate = 0 ; // force update -// } -// }; + if(kit->key == "AUTO_REMOTELY_POSITIVE_THRESHOLD") + { + int val ; + if (sscanf(kit->value.c_str(), "%d", &val) == 1) + { + mMinVotesForRemotelyPositive = val ; + std::cerr << "Setting mMinVotesForRemotelyPositive threshold to " << val << std::endl ; + } + }; + if(kit->key == "AUTO_REMOTELY_NEGATIVE_THRESHOLD") + { + int val ; + if (sscanf(kit->value.c_str(), "%d", &val) == 1) + { + mMinVotesForRemotelyNegative = val ; + std::cerr << "Setting mMinVotesForRemotelyNegative threshold to " << val << std::endl ; + } + }; if(kit->key == "AUTO_BAN_IDENTITIES_THRESHOLD") { float val ; diff --git a/libretroshare/src/services/p3gxsreputation.h b/libretroshare/src/services/p3gxsreputation.h index 04b1b646d..d5b9a54e6 100644 --- a/libretroshare/src/services/p3gxsreputation.h +++ b/libretroshare/src/services/p3gxsreputation.h @@ -112,13 +112,8 @@ public: virtual bool isNodeBanned(const RsPgpId& id); virtual void banNode(const RsPgpId& id,bool b) ; - //virtual void setNodeAutoBanThreshold(uint32_t n) ; - //virtual uint32_t nodeAutoBanThreshold() ; - virtual void setNodeAutoPositiveOpinionForContacts(bool b) ; virtual bool nodeAutoPositiveOpinionForContacts() ; - virtual float nodeAutoBanIdentitiesLimit() ; - virtual void setNodeAutoBanIdentitiesLimit(float f) ; uint32_t thresholdForRemotelyNegativeReputation(); uint32_t thresholdForRemotelyPositiveReputation(); From 3a45bac8e746346dd2dd54394b4ca8d233ff8161 Mon Sep 17 00:00:00 2001 From: csoler Date: Thu, 29 Dec 2016 15:12:58 +0100 Subject: [PATCH 19/23] removed unused autoBanIdentitiesLimit variable --- libretroshare/src/services/p3gxsreputation.cc | 16 ---------------- libretroshare/src/services/p3gxsreputation.h | 1 - 2 files changed, 17 deletions(-) diff --git a/libretroshare/src/services/p3gxsreputation.cc b/libretroshare/src/services/p3gxsreputation.cc index 70aa58ab2..ea0c7f519 100644 --- a/libretroshare/src/services/p3gxsreputation.cc +++ b/libretroshare/src/services/p3gxsreputation.cc @@ -160,7 +160,6 @@ p3GxsReputation::p3GxsReputation(p3LinkMgr *lm) mLastBannedNodesUpdate = 0 ; mBannedNodesProxyNeedsUpdate = false; - mAutoBanIdentitiesLimit = REPUTATION_ASSESSMENT_THRESHOLD_X1; mAutoSetPositiveOptionToContacts = true; // default mMinVotesForRemotelyPositive = REPUTATION_DEFAULT_MIN_VOTES_FOR_REMOTELY_POSITIVE; mMinVotesForRemotelyNegative = REPUTATION_DEFAULT_MIN_VOTES_FOR_REMOTELY_NEGATIVE; @@ -1085,10 +1084,6 @@ bool p3GxsReputation::saveList(bool& cleanup, std::list &savelist) rs_sprintf(kv.value, "%d", mMinVotesForRemotelyNegative); vitem->tlvkvs.pairs.push_back(kv) ; - kv.key = "AUTO_BAN_IDENTITIES_THRESHOLD" ; - rs_sprintf(kv.value, "%f", mAutoBanIdentitiesLimit); - vitem->tlvkvs.pairs.push_back(kv) ; - kv.key = "AUTO_POSITIVE_CONTACTS" ; kv.value = mAutoSetPositiveOptionToContacts?"YES":"NO"; vitem->tlvkvs.pairs.push_back(kv) ; @@ -1166,17 +1161,6 @@ bool p3GxsReputation::loadList(std::list& loadList) std::cerr << "Setting mMinVotesForRemotelyNegative threshold to " << val << std::endl ; } }; - if(kit->key == "AUTO_BAN_IDENTITIES_THRESHOLD") - { - float val ; - - if (sscanf(kit->value.c_str(), "%f", &val) == 1) - { - mAutoBanIdentitiesLimit = val ; - std::cerr << "Setting AutoBanIdentity threshold to " << val << std::endl ; - mLastBannedNodesUpdate = 0 ; // force update - } - }; if(kit->key == "AUTO_POSITIVE_CONTACTS") { mAutoSetPositiveOptionToContacts = (kit->value == "YES"); diff --git a/libretroshare/src/services/p3gxsreputation.h b/libretroshare/src/services/p3gxsreputation.h index d5b9a54e6..da7831987 100644 --- a/libretroshare/src/services/p3gxsreputation.h +++ b/libretroshare/src/services/p3gxsreputation.h @@ -183,7 +183,6 @@ private: // PGP Ids auto-banned. This is updated regularly. std::map mBannedPgpIds ; std::set mPerNodeBannedIdsProxy ; - //uint32_t mPgpAutoBanThreshold ; bool mBannedNodesProxyNeedsUpdate ; uint32_t mMinVotesForRemotelyPositive ; From 9d82a3ff165568872e5c405bcec64a1a84eab8b4 Mon Sep 17 00:00:00 2001 From: csoler Date: Thu, 29 Dec 2016 15:24:31 +0100 Subject: [PATCH 20/23] replace mNbAttempts in signature and validation of posts by a timeout, which allows to wait for missing keys more robustly --- libretroshare/src/gxs/rsgenexchange.cc | 50 ++++++++++---------------- libretroshare/src/gxs/rsgenexchange.h | 17 +++------ 2 files changed, 24 insertions(+), 43 deletions(-) diff --git a/libretroshare/src/gxs/rsgenexchange.cc b/libretroshare/src/gxs/rsgenexchange.cc index 49906301b..7e1331396 100644 --- a/libretroshare/src/gxs/rsgenexchange.cc +++ b/libretroshare/src/gxs/rsgenexchange.cc @@ -86,14 +86,14 @@ RsGenExchange::RsGenExchange(RsGeneralDataService *gds, RsNetworkExchangeService CREATE_FAIL(0), CREATE_SUCCESS(1), CREATE_FAIL_TRY_LATER(2), - SIGN_MAX_ATTEMPTS(5), + SIGN_MAX_WAITING_TIME(60), SIGN_FAIL(0), SIGN_SUCCESS(1), SIGN_FAIL_TRY_LATER(2), VALIDATE_FAIL(0), VALIDATE_SUCCESS(1), VALIDATE_FAIL_TRY_LATER(2), - VALIDATE_MAX_ATTEMPTS(5) + VALIDATE_MAX_WAITING_TIME(60) { mDataAccess = new RsGxsDataAccess(gds); @@ -1478,7 +1478,7 @@ void RsGenExchange::notifyNewGroups(std::vector &groups) std::cerr << std::endl; #endif - GxsPendingItem gpsi(grp, grp->grpId); + GxsPendingItem gpsi(grp, grp->grpId,time(NULL)); mReceivedGrps.push_back(gpsi); } else @@ -1923,7 +1923,9 @@ bool RsGenExchange::processGrpMask(const RsGxsGroupId& grpId, ContentValue &grpC void RsGenExchange::publishMsgs() { - RS_STACK_MUTEX(mGenMtx) ; + RS_STACK_MUTEX(mGenMtx) ; + + time_t now = time(NULL); // stick back msgs pending signature typedef std::map > PendSignMap; @@ -1992,22 +1994,20 @@ void RsGenExchange::publishMsgs() // sign attempt if(pit == mMsgPendingSign.end()) { - GxsPendingItem gsi(msgItem, token); + GxsPendingItem gsi(msgItem, token,time(NULL)); mMsgPendingSign.insert(std::make_pair(token, gsi)); } else { // remove from attempts queue if over sign // attempts limit - if(pit->second.mAttempts == SIGN_MAX_ATTEMPTS) + if(pit->second.mFirstTryTS + SIGN_MAX_WAITING_TIME < now) { + std::cerr << "Pending signature grp=" << pit->second.mItem->meta.mGroupId << ", msg=" << pit->second.mItem->meta.mMsgId << ", has exceeded validation time limit. The author's key can probably not be obtained. This is unexpected." << std::endl; + mMsgPendingSign.erase(token); tryLater = false; } - else - { - ++pit->second.mAttempts; - } } createOk = false; @@ -2653,28 +2653,22 @@ void RsGenExchange::processRecvdMessages() { RS_STACK_MUTEX(mGenMtx) ; + time_t now = time(NULL); + #ifdef GEN_EXCH_DEBUG if(!mMsgPendingValidate.empty()) std::cerr << "processing received messages" << std::endl; #endif NxsMsgPendingVect::iterator pend_it = mMsgPendingValidate.begin(); -#ifdef GEN_EXCH_DEBUG - if(!mMsgPendingValidate.empty()) - std::cerr << " pending validation" << std::endl; -#endif for(; pend_it != mMsgPendingValidate.end();) { GxsPendingItem& gpsi = *pend_it; -#ifdef GEN_EXCH_DEBUG - std::cerr << " grp=" << gpsi.mId.first << ", msg=" << gpsi.mId.second << ", attempts=" << gpsi.mAttempts ; -#endif - if(gpsi.mAttempts == VALIDATE_MAX_ATTEMPTS) + if(gpsi.mFirstTryTS + VALIDATE_MAX_WAITING_TIME < now) { -#ifdef GEN_EXCH_DEBUG - std::cerr << " = max! deleting." << std::endl; -#endif + std::cerr << "Pending validation grp=" << gpsi.mId.first << ", msg=" << gpsi.mId.second << ", has exceeded validation time limit. The author's key can probably not be obtained. This is unexpected." << std::endl; + delete gpsi.mItem; pend_it = mMsgPendingValidate.erase(pend_it); } @@ -2836,16 +2830,12 @@ void RsGenExchange::processRecvdMessages() // first check you haven't made too many attempts - NxsMsgPendingVect::iterator vit = std::find( - mMsgPendingValidate.begin(), mMsgPendingValidate.end(), id); + NxsMsgPendingVect::iterator vit = std::find(mMsgPendingValidate.begin(), mMsgPendingValidate.end(), id); if(vit == mMsgPendingValidate.end()) { - GxsPendingItem item(msg, id); + GxsPendingItem item(msg, id,time(NULL)); mMsgPendingValidate.push_back(item); - }else - { - vit->mAttempts++; } } } @@ -2978,18 +2968,16 @@ void RsGenExchange::processRecvdGroups() std::cerr << " failed to validate incoming grp, trying again. grpId: " << grp->grpId << std::endl; #endif - if(gpsi.mAttempts == VALIDATE_MAX_ATTEMPTS) + if(gpsi.mFirstTryTS + VALIDATE_MAX_WAITING_TIME < time(NULL)) { #ifdef GEN_EXCH_DEBUG - std::cerr << " max attempts " << VALIDATE_MAX_ATTEMPTS << " reached. Will delete group " << grp->grpId << std::endl; + std::cerr << " validation time got group " << grp->grpId << " exceeded maximum. Will delete group " << std::endl; #endif delete grp; erase = true; } else - { erase = false; - } } } else diff --git a/libretroshare/src/gxs/rsgenexchange.h b/libretroshare/src/gxs/rsgenexchange.h index 95efbf2e1..735e8ec38 100644 --- a/libretroshare/src/gxs/rsgenexchange.h +++ b/libretroshare/src/gxs/rsgenexchange.h @@ -43,17 +43,10 @@ template class GxsPendingItem { public: - GxsPendingItem(GxsItem item, Identity id) : - mItem(item), mId(id), mAttempts(0) + GxsPendingItem(GxsItem item, Identity id,time_t ts) : + mItem(item), mId(id), mFirstTryTS(ts) {} - GxsPendingItem(const GxsPendingItem& gpsi) - { - this->mItem = gpsi.mItem; - this->mId = gpsi.mId; - this->mAttempts = gpsi.mAttempts; - } - bool operator==(const Identity& id) { return this->mId == id; @@ -61,7 +54,7 @@ public: GxsItem mItem; Identity mId; - uint8_t mAttempts; + time_t mFirstTryTS; }; class GxsGrpPendingSign @@ -883,9 +876,9 @@ private: 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_WAITING_TIME; 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_WAITING_TIME; private: From 057e3ebea2f963fcc7756e5a4a3ee614136d57ea Mon Sep 17 00:00:00 2001 From: csoler Date: Thu, 29 Dec 2016 18:10:21 +0100 Subject: [PATCH 21/23] extended GXS sync limit to sending instead of just requesting, in order to make backward compatibility less painful --- libretroshare/src/gxs/rsgxsnetservice.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libretroshare/src/gxs/rsgxsnetservice.cc b/libretroshare/src/gxs/rsgxsnetservice.cc index 2c92c6892..089f97a8c 100644 --- a/libretroshare/src/gxs/rsgxsnetservice.cc +++ b/libretroshare/src/gxs/rsgxsnetservice.cc @@ -4208,6 +4208,10 @@ void RsGxsNetService::handleRecvSyncMessage(RsNxsSyncMsgReqItem *item,bool item_ uint32_t transN = locked_getTransactionId(); RsGxsCircleId should_encrypt_to_this_circle_id ; + time_t now = time(NULL) ; + + uint32_t max_send_delay = mServerGrpConfigMap[item->grpId].msg_req_delay; // we should use "sync" but there's only one variable used in the GUI: the req one. + if(canSendMsgIds(msgMetas, *grpMeta, peer, should_encrypt_to_this_circle_id)) { for(std::vector::iterator vit = msgMetas.begin();vit != msgMetas.end(); ++vit) @@ -4231,7 +4235,7 @@ void RsGxsNetService::handleRecvSyncMessage(RsNxsSyncMsgReqItem *item,bool item_ } // Check publish TS - if(item->createdSinceTS > (*vit)->mPublishTs) + if(item->createdSinceTS > (*vit)->mPublishTs || (*vit)->mPublishTs + max_send_delay < now) { #ifdef NXS_NET_DEBUG_0 GXSNETDEBUG_PG(item->PeerId(),item->grpId) << " not sending item ID " << (*vit)->mMsgId << ", because it is too old (publishTS = " << (time(NULL)-(*vit)->mPublishTs)/86400 << " days ago" << std::endl; From 924e3bf54bf60498dd32a742948a9a7b562df99f Mon Sep 17 00:00:00 2001 From: csoler Date: Thu, 29 Dec 2016 21:54:50 +0100 Subject: [PATCH 22/23] changed anti-spam policy to require a minimum of NEUTRAL to forward posts of trusted people --- libretroshare/src/gxs/rsnxs.h | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libretroshare/src/gxs/rsnxs.h b/libretroshare/src/gxs/rsnxs.h index 995377ed3..5ef659d23 100644 --- a/libretroshare/src/gxs/rsnxs.h +++ b/libretroshare/src/gxs/rsnxs.h @@ -179,30 +179,30 @@ public: static RsReputations::ReputationLevel minReputationForForwardingMessages(uint32_t group_sign_flags, uint32_t identity_flags) { // If anti-spam is enabled, do not send messages from authors with bad reputation. The policy is to only forward messages if the reputation of the author is at least - // equal to the minimal reputation in the table below (R=remotely, L=locally, P=positive, N=negative) : + // equal to the minimal reputation in the table below (R=remotely, L=locally, P=positive, N=negative, O=neutral) : // // | Anonymous Signed Signed+Known // -----------+----------------------------------------------------- - // NONE | RN RN RN - // GPG_AUTHED | RP RN RN - // GPG_KNOWN | RP RP RN + // NONE | O O O + // GPG_AUTHED | RP O O + // GPG_KNOWN | RP RP O // if(identity_flags & RS_IDENTITY_FLAGS_PGP_KNOWN) - return RsReputations::REPUTATION_REMOTELY_NEGATIVE; + return RsReputations::REPUTATION_NEUTRAL; else if(identity_flags & RS_IDENTITY_FLAGS_PGP_LINKED) { if(group_sign_flags & GXS_SERV::FLAG_AUTHOR_AUTHENTICATION_GPG_KNOWN) return RsReputations::REPUTATION_REMOTELY_POSITIVE; else - return RsReputations::REPUTATION_REMOTELY_NEGATIVE; + return RsReputations::REPUTATION_NEUTRAL; } else { if( (group_sign_flags & GXS_SERV::FLAG_AUTHOR_AUTHENTICATION_GPG_KNOWN) || (group_sign_flags & GXS_SERV::FLAG_AUTHOR_AUTHENTICATION_GPG)) return RsReputations::REPUTATION_REMOTELY_POSITIVE; else - return RsReputations::REPUTATION_REMOTELY_NEGATIVE; + return RsReputations::REPUTATION_NEUTRAL; } } }; From f8ac391a28d4b7aa2d64263a63dd98086f74c449 Mon Sep 17 00:00:00 2001 From: csoler Date: Sat, 31 Dec 2016 13:42:29 +0100 Subject: [PATCH 23/23] better documentation of anti-spam policy --- libretroshare/src/gxs/rsnxs.h | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/libretroshare/src/gxs/rsnxs.h b/libretroshare/src/gxs/rsnxs.h index 5ef659d23..9b879d2d0 100644 --- a/libretroshare/src/gxs/rsnxs.h +++ b/libretroshare/src/gxs/rsnxs.h @@ -181,11 +181,16 @@ public: // If anti-spam is enabled, do not send messages from authors with bad reputation. The policy is to only forward messages if the reputation of the author is at least // equal to the minimal reputation in the table below (R=remotely, L=locally, P=positive, N=negative, O=neutral) : // - // | Anonymous Signed Signed+Known - // -----------+----------------------------------------------------- - // NONE | O O O - // GPG_AUTHED | RP O O - // GPG_KNOWN | RP RP O + // + // +----------------------------------------------------+ + // | Identity flags | + // +----------------------------------------------------+ + // | Anonymous Signed Signed+Known | + // +-------------+-----------+----------------------------------------------------+ + // | |NONE | O O O | + // | Forum flags |GPG_AUTHED | RP O O | + // | |GPG_KNOWN | RP RP O | + // +-------------+-----------+----------------------------------------------------+ // if(identity_flags & RS_IDENTITY_FLAGS_PGP_KNOWN)