From 338fcee86502549952280ea27140f68647fc8790 Mon Sep 17 00:00:00 2001 From: csoler Date: Fri, 9 Oct 2015 18:51:16 -0400 Subject: [PATCH] added button in forums to flag poster identity as bad --- libretroshare/src/retroshare/rsreputations.h | 1 + libretroshare/src/services/p3gxsreputation.cc | 14 +++++++++++--- libretroshare/src/services/p3gxsreputation.h | 1 + retroshare-gui/src/gui/Identity/IdDialog.cpp | 11 +++++------ retroshare-gui/src/gui/Identity/IdDialog.ui | 12 +++++++++++- .../gui/gxsforums/GxsForumThreadWidget.cpp | 18 ++++++++++++++++++ .../src/gui/gxsforums/GxsForumThreadWidget.h | 3 ++- retroshare-gui/src/gui/icons.qrc | 1 + .../src/gui/icons/yellow_biohazard64.png | Bin 0 -> 10182 bytes 9 files changed, 50 insertions(+), 11 deletions(-) create mode 100644 retroshare-gui/src/gui/icons/yellow_biohazard64.png diff --git a/libretroshare/src/retroshare/rsreputations.h b/libretroshare/src/retroshare/rsreputations.h index 7b227e70f..ee09458a8 100644 --- a/libretroshare/src/retroshare/rsreputations.h +++ b/libretroshare/src/retroshare/rsreputations.h @@ -40,6 +40,7 @@ public: { RsReputations::Opinion mOwnOpinion ; float mOverallReputationScore ; + float mFriendAverage ; RsReputations::Assessment mAssessment; // this should help clients in taking decisions }; diff --git a/libretroshare/src/services/p3gxsreputation.cc b/libretroshare/src/services/p3gxsreputation.cc index d7e815cae..7de4165d2 100644 --- a/libretroshare/src/services/p3gxsreputation.cc +++ b/libretroshare/src/services/p3gxsreputation.cc @@ -524,6 +524,7 @@ bool p3GxsReputation::getReputationInfo(const RsGxsId& gxsid, RsReputations::Rep if (it == mReputations.end()) { info.mOwnOpinion = RsReputations::OPINION_NEUTRAL ; + info.mFriendAverage = RsReputations::OPINION_NEUTRAL ; info.mOverallReputationScore = float(RsReputations::OPINION_NEUTRAL) ; info.mAssessment = RsReputations::ASSESSMENT_OK ; #ifdef DEBUG_REPUTATION @@ -533,7 +534,8 @@ bool p3GxsReputation::getReputationInfo(const RsGxsId& gxsid, RsReputations::Rep else { info.mOwnOpinion = RsReputations::Opinion(it->second.mOwnOpinion) ; - info.mOverallReputationScore = float(it->second.mReputation) ; + info.mOverallReputationScore = it->second.mReputation ; + info.mFriendAverage = it->second.mFriendAverage ; if(info.mOverallReputationScore > REPUTATION_ASSESSMENT_THRESHOLD_X1) info.mAssessment = RsReputations::ASSESSMENT_OK ; @@ -874,9 +876,15 @@ float Reputation::updateReputation(uint32_t average_active_friends) friend_total += it->second - 1; if(mOpinions.empty()) // includes the case of no friends! + { mReputation = 1.0f; + mFriendAverage = 1.0f ; + } else - mReputation = 1.0f + friend_total / float(std::max(average_active_friends,(uint32_t)mOpinions.size())) ; + { + mFriendAverage = 1.0+friend_total / float(std::max(average_active_friends,(uint32_t)mOpinions.size())); + mReputation = mFriendAverage ; + } } else mReputation = (float)mOwnOpinion ; @@ -893,7 +901,7 @@ void p3GxsReputation::debug_print() for(std::map::const_iterator it(mReputations.begin());it!=mReputations.end();++it) { - std::cerr << " ID=" << it->first << ", own: " << it->second.mOwnOpinion << ", global_score: " << it->second.mReputation + std::cerr << " ID=" << it->first << ", own: " << it->second.mOwnOpinion << ", Friend average: " << it->second.mFriendAverage << ", global_score: " << it->second.mReputation << ", last own update: " << now - it->second.mOwnOpinionTs << " secs ago." << std::endl; 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 d69c8da89..82f954b4d 100644 --- a/libretroshare/src/services/p3gxsreputation.h +++ b/libretroshare/src/services/p3gxsreputation.h @@ -70,6 +70,7 @@ public: int32_t mOwnOpinion; time_t mOwnOpinionTs; + float mFriendAverage ; float mReputation; }; diff --git a/retroshare-gui/src/gui/Identity/IdDialog.cpp b/retroshare-gui/src/gui/Identity/IdDialog.cpp index afdd3ceec..2d26c55b4 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.cpp +++ b/retroshare-gui/src/gui/Identity/IdDialog.cpp @@ -680,8 +680,11 @@ void IdDialog::insertIdDetails(uint32_t token) RsReputations::ReputationInfo info ; rsReputations->getReputationInfo(RsGxsId(data.mMeta.mGroupId),info) ; - ui->overallOpinion_TF->setText(QString::number(info.mOverallReputationScore)); + 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")) +")" ) ; + switch(info.mOwnOpinion) { case RsReputations::OPINION_NEGATIVE: ui->ownOpinion_CB->setCurrentIndex(0); break ; @@ -690,8 +693,6 @@ void IdDialog::insertIdDetails(uint32_t token) default: std::cerr << "Unexpected value in own opinion: " << info.mOwnOpinion << std::endl; } - - //ui->neighborNodesOpinion_TF->setText(QString::number(info.m)); } void IdDialog::modifyReputation() @@ -740,11 +741,9 @@ void IdDialog::modifyReputation() std::cerr << std::endl; #endif -#ifdef SUSPENDED // trigger refresh when finished. // basic / anstype are not needed. - mIdQueue->queueRequest(token, 0, 0, IDDIALOG_REFRESH); -#endif + requestIdDetails(); return; } diff --git a/retroshare-gui/src/gui/Identity/IdDialog.ui b/retroshare-gui/src/gui/Identity/IdDialog.ui index 06462cca3..b6649e413 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.ui +++ b/retroshare-gui/src/gui/Identity/IdDialog.ui @@ -485,6 +485,9 @@ + + <html><head/><body><p>Average opinion of neighbor nodes about this identity. Negative is bad,</p><p>positive is good. Zero is neutral.</p></body></html> + true @@ -530,12 +533,16 @@ p, li { white-space: pre-wrap; } <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - 1 + 0 Negative + + + ../icons/yellow_biohazard64.png../icons/yellow_biohazard64.png + @@ -551,6 +558,9 @@ p, li { white-space: pre-wrap; } + + <html><head/><body><p>Overall reputation score, accounting for yours and your friends'.</p><p>Negative is bad, positive is good. Zero is neutral. If the score is too low,</p><p>the identity is flagged as bad, and will be filtered out in forums, chat lobbies,</p><p>channels, etc.</p></body></html> + true diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index e51527860..0f2f656d3 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -41,6 +41,7 @@ #include "util/QtVersion.h" #include +#include #include // These should be in retroshare/ folder. #include "retroshare/rsgxsflags.h" @@ -57,6 +58,7 @@ #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 VIEW_LAST_POST 0 #define VIEW_THREADED 1 @@ -406,6 +408,9 @@ 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("Flag this person as bad"), &contextMnu); + connect(flagasbadAct, SIGNAL(triggered()), this, SLOT(flagpersonasbad())); + QAction *newthreadAct = new QAction(QIcon(IMAGE_MESSAGE), tr("Start New Thread"), &contextMnu); newthreadAct->setEnabled (IS_GROUP_SUBSCRIBED(mSubscribeFlags)); connect(newthreadAct , SIGNAL(triggered()), this, SLOT(createthread())); @@ -484,6 +489,8 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) contextMnu.addAction(expandAll); contextMnu.addAction(collapseAll); + contextMnu.addSeparator(); + contextMnu.addAction(flagasbadAct); contextMnu.addSeparator(); contextMnu.addAction(replyauthorAct); @@ -1685,6 +1692,17 @@ static QString buildReplyHeader(const RsMsgMetaData &meta) return header; } +void GxsForumThreadWidget::flagpersonasbad() +{ + if (groupId().isNull() || mThreadId.isNull()) { + QMessageBox::information(this, tr("RetroShare"),tr("You cant reply to a non-existant Message")); + return; + } + + // Get Message ... then complete replyMessageData(). + rsReputations->setOwnOpinion(RsGxsId(groupId()),RsReputations::OPINION_NEGATIVE) ; +} + void GxsForumThreadWidget::replytomessage() { if (groupId().isNull() || mThreadId.isNull()) { diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h index 311024f07..293a8fb7b 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.h @@ -101,6 +101,7 @@ private slots: void downloadAllFiles(); void changedViewBox(); + void flagpersonasbad(); void filterColumnChanged(int column); void filterItems(const QString &text); @@ -170,7 +171,7 @@ private: RsGxsMessageId mNavigatePendingMsgId; QList mIgnoredMsgId; - Ui::GxsForumThreadWidget *ui; + Ui::GxsForumThreadWidget *ui; }; #endif // GXSFORUMTHREADWIDGET_H diff --git a/retroshare-gui/src/gui/icons.qrc b/retroshare-gui/src/gui/icons.qrc index 8840c6087..3cee5d1d2 100644 --- a/retroshare-gui/src/gui/icons.qrc +++ b/retroshare-gui/src/gui/icons.qrc @@ -60,5 +60,6 @@ icons/user-busy_64.png icons/user-offline_64.png icons/user-online_64.png + icons/yellow_biohazard64.png diff --git a/retroshare-gui/src/gui/icons/yellow_biohazard64.png b/retroshare-gui/src/gui/icons/yellow_biohazard64.png new file mode 100644 index 0000000000000000000000000000000000000000..edb6fb3f24b36b67d7267e4d0d83fdd0bbcdc876 GIT binary patch literal 10182 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