RetroShare/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp

494 lines
17 KiB
C++

/*******************************************************************************
* gui/statistics/GxsTransportStatistics.cpp *
* *
* Copyright (c) 2011 Retroshare Team <retroshare.project@gmail.com> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include <iostream>
#include <time.h>
#include <memory>
#include <QDateTime>
#include <QFontMetrics>
#include <QHeaderView>
#include <QLayout>
#include <QMenu>
#include <QObject>
#include <QPainter>
#include <QStylePainter>
#include <QTimer>
#include <QTreeWidget>
#include <QWheelEvent>
#include <retroshare/rsgxstrans.h>
#include <retroshare/rspeers.h>
#include <retroshare/rsidentity.h>
#include <retroshare/rsgxstrans.h>
#include "GxsTransportStatistics.h"
#include "gui/Identity/IdDetailsDialog.h"
#include "gui/settings/rsharesettings.h"
#include "util/QtVersion.h"
#include "gui/common/UIStateHelper.h"
#include "util/misc.h"
#include "util/qtthreadsutils.h"
#include "gui/gxs/GxsIdLabel.h"
#include "gui/gxs/GxsIdDetails.h"
#include "gui/gxs/GxsIdTreeWidgetItem.h"
#include "gui/Identity/IdDialog.h"
#include "gui/MainWindow.h"
#include "gui/common/FilesDefs.h"
#define COL_PENDING_ID 0
#define COL_PENDING_DESTINATION 1
#define COL_PENDING_DATASTATUS 2
#define COL_PENDING_DATASIZE 3
#define COL_PENDING_DATAHASH 4
#define COL_PENDING_SEND 5
#define COL_PENDING_GROUP_ID 6
#define COL_PENDING_SENDTIME 7
#define COL_PENDING_DESTINATION_ID 8
#define COL_GROUP_GRP_ID 0
#define COL_GROUP_PUBLISHTS 1
#define COL_GROUP_NUM_MSGS 2
#define COL_GROUP_SIZE_MSGS 3
#define COL_GROUP_SUBSCRIBED 4
#define COL_GROUP_POPULARITY 5
#define COL_GROUP_UNIQUE_ID 6
#define COL_GROUP_AUTHOR_ID 7
//static const int PARTIAL_VIEW_SIZE = 9 ;
//static const int MAX_TUNNEL_REQUESTS_DISPLAY = 10 ;
//static const int GXSTRANS_STATISTICS_DELAY_BETWEEN_GROUP_REQ = 30 ; // never request more than every 30 secs.
#define GXSTRANS_GROUP_META 0x01
#define GXSTRANS_GROUP_DATA 0x02
#define GXSTRANS_GROUP_STAT 0x03
#define GXSTRANS_MSG_META 0x04
//#define DEBUG_GXSTRANS_STATS 1
GxsTransportStatistics::GxsTransportStatistics(QWidget *parent)
: MainPage(parent)
{
setupUi(this) ;
mStateHelper = new UIStateHelper(this);
mStateHelper->addWidget(GXSTRANS_GROUP_META, treeWidget);
m_bProcessSettings = false;
mLastGroupReqTS = 0 ;
/* Set header resize modes and initial section sizes Uploads TreeView*/
QHeaderView_setSectionResizeMode(treeWidget->header(), QHeaderView::ResizeToContents);
QHeaderView_setSectionResizeMode(groupTreeWidget->header(), QHeaderView::ResizeToContents);
connect(treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(CustomPopupMenu(QPoint)));
connect(groupTreeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(CustomPopupMenuGroups(QPoint)));
treeWidget->setColumnHidden(COL_PENDING_DESTINATION_ID,true);
groupTreeWidget->setColumnHidden(COL_GROUP_AUTHOR_ID,true);
// load settings
processSettings(true);
updateDisplay(true);
}
GxsTransportStatistics::~GxsTransportStatistics()
{
// save settings
processSettings(false);
}
void GxsTransportStatistics::processSettings(bool bLoad)
{
m_bProcessSettings = true;
Settings->beginGroup(QString("GxsTransportStatistics"));
if (bLoad) {
// load settings
// state of splitter
//splitter->restoreState(Settings->value("Splitter").toByteArray());
} else {
// save settings
// state of splitter
//Settings->setValue("Splitter", splitter->saveState());
}
Settings->endGroup();
m_bProcessSettings = false;
}
void GxsTransportStatistics::CustomPopupMenu( QPoint )
{
QMenu contextMnu( this );
QTreeWidgetItem *item = treeWidget->currentItem();
if (item) {
contextMnu.addAction(FilesDefs::getIconFromQtResourcePath(":/images/info16.png"), tr("View details"), this, SLOT(personDetails()));
}
contextMnu.exec(QCursor::pos());
}
void GxsTransportStatistics::CustomPopupMenuGroups( QPoint )
{
QMenu contextMnu( this );
QTreeWidgetItem *item = groupTreeWidget->currentItem();
if (item) {
contextMnu.addAction(FilesDefs::getIconFromQtResourcePath(":/images/info16.png"), tr("View details"), this, SLOT(showAuthorInPeople()));
}
contextMnu.exec(QCursor::pos());
}
void GxsTransportStatistics::updateDisplay(bool)
{
time_t now = time(NULL) ;
#ifdef DEBUG_GXSTRANS_STATS
std::cerr << "GxsTransportStatistics::updateDisplay()" << std::endl;
#endif
loadGroups();
mLastGroupReqTS = now ;
}
QString GxsTransportStatistics::getPeerName(const RsPeerId &peer_id)
{
static std::map<RsPeerId, QString> names ;
std::map<RsPeerId,QString>::const_iterator it = names.find(peer_id) ;
if( it != names.end())
return it->second ;
else
{
RsPeerDetails detail ;
if(!rsPeers->getPeerDetails(peer_id,detail))
return tr("Unknown Peer");
return (names[peer_id] = QString::fromUtf8(detail.name.c_str())) ;
}
}
static QString getStatusString(GxsTransSendStatus status)
{
switch(status)
{
case GxsTransSendStatus::PENDING_PROCESSING : return QObject::tr("Processing") ;
case GxsTransSendStatus::PENDING_PREFERRED_GROUP : return QObject::tr("Choosing group") ;
case GxsTransSendStatus::PENDING_RECEIPT_CREATE : return QObject::tr("Creating receipt") ;
case GxsTransSendStatus::PENDING_RECEIPT_SIGNATURE : return QObject::tr("Signing receipt") ;
case GxsTransSendStatus::PENDING_SERIALIZATION : return QObject::tr("Serializing") ;
case GxsTransSendStatus::PENDING_PAYLOAD_CREATE : return QObject::tr("Creating payload") ;
case GxsTransSendStatus::PENDING_PAYLOAD_ENCRYPT : return QObject::tr("Encrypting payload") ;
case GxsTransSendStatus::PENDING_PUBLISH : return QObject::tr("Publishing") ;
case GxsTransSendStatus::PENDING_RECEIPT_RECEIVE : return QObject::tr("Waiting for receipt") ;
case GxsTransSendStatus::RECEIPT_RECEIVED : return QObject::tr("Receipt received") ;
case GxsTransSendStatus::FAILED_RECEIPT_SIGNATURE : return QObject::tr("Receipt signature failed") ;
case GxsTransSendStatus::FAILED_ENCRYPTION : return QObject::tr("Encryption failed") ;
case GxsTransSendStatus::UNKNOWN :
default : return QObject::tr("Unknown") ;
}
}
void GxsTransportStatistics::updateContent()
{
RsGxsTrans::GxsTransStatistics transinfo ;
rsGxsTrans->getDataStatistics(transinfo) ;
// clear
treeWidget->clear();
//time_t now = time(NULL) ;
// 1 - fill the table for pending packets
time_t now = time(NULL) ;
groupBox->setTitle(tr("Pending data items")+": " + QString::number(transinfo.outgoing_records.size()) );
for(uint32_t i=0;i<transinfo.outgoing_records.size();++i)
{
const RsGxsTransOutgoingRecord& rec(transinfo.outgoing_records[i]) ;
//QTreeWidgetItem *item = new QTreeWidgetItem();
GxsIdRSTreeWidgetItem *item = new GxsIdRSTreeWidgetItem(NULL,GxsIdDetails::ICON_TYPE_AVATAR) ;
treeWidget->addTopLevelItem(item);
RsIdentityDetails details ;
rsIdentity->getIdDetails(rec.recipient,details);
QString nickname = QString::fromUtf8(details.mNickname.c_str());
if(nickname.isEmpty())
nickname = tr("Unknown");
item -> setId(rec.recipient,COL_PENDING_DESTINATION, false) ;
item -> setData(COL_PENDING_ID, Qt::DisplayRole, QString::number(rec.trans_id,16).rightJustified(8,'0'));
item -> setData(COL_PENDING_DATASTATUS, Qt::DisplayRole, getStatusString(rec.status));
item -> setData(COL_PENDING_DATASIZE, Qt::DisplayRole, misc::friendlyUnit(rec.data_size));
item -> setData(COL_PENDING_DATAHASH, Qt::DisplayRole, QString::fromStdString(rec.data_hash.toStdString()));
item -> setData(COL_PENDING_SEND, Qt::DisplayRole, QDateTime::fromTime_t(rec.send_TS).toString());
item -> setData(COL_PENDING_GROUP_ID, Qt::DisplayRole, QString::fromStdString(rec.group_id.toStdString()));
item -> setData(COL_PENDING_DESTINATION_ID, Qt::DisplayRole, QString::fromStdString(rec.recipient.toStdString()));
item -> setData(COL_PENDING_SENDTIME, Qt::DisplayRole, QString::number(now - rec.send_TS));
item->setTextAlignment(COL_PENDING_DATASIZE, Qt::AlignRight );
item->setTextAlignment(COL_PENDING_SEND, Qt::AlignRight );
}
// 2 - fill the table for pending group data
// record openned groups
std::set<RsGxsGroupId> openned_groups ;
for(int i=0; i<groupTreeWidget->topLevelItemCount(); ++i)
if( groupTreeWidget->isItemExpanded(groupTreeWidget->topLevelItem(i)) )
openned_groups.insert(RsGxsGroupId(groupTreeWidget->topLevelItem(i)->data(COL_GROUP_GRP_ID, Qt::DisplayRole).toString().toStdString()));
groupTreeWidget->clear();
for(std::map<RsGxsGroupId,RsGxsTransGroupStatistics>::const_iterator it(mGroupStats.begin());it!=mGroupStats.end();++it)
{
const RsGxsTransGroupStatistics& stat(it->second) ;
QTreeWidgetItem *item ;
{
QString unique_id = QString::fromStdString(stat.mGrpId.toStdString());
QList<QTreeWidgetItem*> iteml = groupTreeWidget->findItems(unique_id,Qt::MatchExactly,COL_GROUP_UNIQUE_ID) ;
if(iteml.empty())
item = new QTreeWidgetItem;
else
item = *iteml.begin();
}
groupTreeWidget->addTopLevelItem(item);
groupTreeWidget->setItemExpanded(item,openned_groups.find(it->first) != openned_groups.end());
QString msg_time_string = (stat.last_publish_TS>0)?QString("(Last msg: %1)").arg(QDateTime::fromTime_t((uint)stat.last_publish_TS).toString()):"" ;
item->setData(COL_GROUP_PUBLISHTS, Qt::DisplayRole, msg_time_string) ;
item->setData(COL_GROUP_NUM_MSGS, Qt::DisplayRole, QString::number(stat.mNumMsgs) ) ;
item->setData(COL_GROUP_GRP_ID, Qt::DisplayRole, QString::fromStdString(it->first.toStdString())) ;
item->setData(COL_GROUP_SIZE_MSGS, Qt::DisplayRole, QString::number(stat.mTotalSizeOfMsgs)) ;
item->setData(COL_GROUP_SUBSCRIBED,Qt::DisplayRole, stat.subscribed?tr("Yes"):tr("No")) ;
item->setData(COL_GROUP_POPULARITY,Qt::DisplayRole, QString::number(stat.popularity)) ;
item->setData(COL_GROUP_UNIQUE_ID, Qt::DisplayRole, QString::fromStdString(it->first.toStdString())) ;
for(std::map<RsGxsMessageId,RsMsgMetaData>::const_iterator msgIt(stat.messages_metas.begin());msgIt!=stat.messages_metas.end();++msgIt)
{
const RsMsgMetaData& meta(msgIt->second);
QTreeWidgetItem *sitem ;
{
QString unique_id = QString::fromStdString(meta.mMsgId.toStdString());
QList<QTreeWidgetItem*> iteml = groupTreeWidget->findItems(unique_id,Qt::MatchExactly,COL_GROUP_UNIQUE_ID) ;
if(iteml.empty())
sitem = new QTreeWidgetItem(item) ;
else
sitem = *iteml.begin();
}
GxsIdLabel *label = new GxsIdLabel();
label->setId(meta.mAuthorId) ;
groupTreeWidget->setItemWidget(sitem,COL_GROUP_GRP_ID,label) ;
RsIdentityDetails idDetails ;
rsIdentity->getIdDetails(meta.mAuthorId,idDetails);
QPixmap pixmap ;
QDateTime qdatetime;
qdatetime.setTime_t(meta.mPublishTs);
if(idDetails.mAvatar.mSize == 0 || !GxsIdDetails::loadPixmapFromData(idDetails.mAvatar.mData, idDetails.mAvatar.mSize, pixmap,GxsIdDetails::SMALL))
pixmap = GxsIdDetails::makeDefaultIcon(meta.mAuthorId,GxsIdDetails::SMALL);
sitem->setIcon(COL_GROUP_GRP_ID, QIcon(pixmap));
sitem->setData(COL_GROUP_UNIQUE_ID, Qt::DisplayRole,QString::fromStdString(meta.mMsgId.toStdString()));
sitem->setData(COL_GROUP_AUTHOR_ID, Qt::DisplayRole, QString::fromStdString(meta.mAuthorId.toStdString())) ;
sitem->setText(COL_GROUP_PUBLISHTS, QDateTime::fromTime_t(meta.mPublishTs).toString());
sitem->setData(COL_GROUP_PUBLISHTS, Qt::UserRole, qdatetime);
}
}
}
void GxsTransportStatistics::personDetails()
{
QTreeWidgetItem *item = treeWidget->currentItem();
std::string id = item->text(COL_PENDING_DESTINATION_ID).toStdString();
if (id.empty()) {
return;
}
IdDetailsDialog *dialog = new IdDetailsDialog(RsGxsGroupId(id));
dialog->show();
}
void GxsTransportStatistics::showAuthorInPeople()
{
QTreeWidgetItem *item = groupTreeWidget->currentItem();
std::string id = item->text(COL_GROUP_AUTHOR_ID).toStdString();
if (id.empty()) {
return;
}
/* window will destroy itself! */
IdDialog *idDialog = dynamic_cast<IdDialog*>(MainWindow::getPage(MainWindow::People));
if (!idDialog)
return ;
MainWindow::showWindow(MainWindow::People);
idDialog->navigate(RsGxsId(id));
}
#ifdef TO_REMOVE
void GxsTransportStatistics::loadGroupMeta(const std::vector<RsGroupMetaData>& groupInfo)
{
mStateHelper->setLoading(GXSTRANS_GROUP_META, false);
#ifdef DEBUG_GXSTRANS_STATS
std::cerr << "GxsTransportStatisticsWidget::loadGroupMeta()";
std::cerr << std::endl;
#endif
mStateHelper->setActive(GXSTRANS_GROUP_META, true);
std::set<RsGxsGroupId> existing_groups ;
for(auto vit = groupInfo.begin(); vit != groupInfo.end(); ++vit)
{
existing_groups.insert(vit->mGroupId) ;
/* Add Widget, and request Pages */
#ifdef DEBUG_GXSTRANS_STATS
std::cerr << "GxsTransportStatisticsWidget::loadGroupMeta() GroupId: " << vit->mGroupId << " Group: " << vit->mGroupName << std::endl;
#endif
loadGroupStats(vit->mGroupId) ;
loadMsgMetas(vit->mGroupId) ;
RsGxsTransGroupStatistics& s(mGroupStats[vit->mGroupId]);
s.popularity = vit->mPop ;
s.subscribed = IS_GROUP_SUBSCRIBED(vit->mSubscribeFlags) ;
s.mGrpId = vit->mGroupId ;
}
// remove group stats for group that do not exist anymore
for(std::map<RsGxsGroupId,RsGxsTransGroupStatistics>::iterator it(mGroupStats.begin());it!=mGroupStats.end();)
if(existing_groups.find(it->first) == existing_groups.end())
it = mGroupStats.erase(it);
else
++it;
}
void GxsTransportStatistics::loadGroupStats(const RsGxsGroupId& groupId)
{
#ifdef DEBUG_GXSTRANS_STATS
std::cerr << "Loading group stats: " << stats.mGrpId << ", num msgs=" << stats.mNumMsgs << ", total size=" << stats.mTotalSizeOfMsgs << std::endl;
#endif
RsThread::async([this]()
{
// 1 - get message data from p3GxsForums
#ifdef DEBUG_FORUMS
std::cerr << "Retrieving post data for post " << mThreadId << std::endl;
#endif
GxsGroupStatistic stats;
rsGxsTrans->getGroupStatistic(groupId,stats);
RsQThreadUtils::postToObject( [stats,this]()
{
/* Here it goes any code you want to be executed on the Qt Gui
* thread, for example to update the data model with new information
* after a blocking call to RetroShare API complete */
dynamic_cast<GxsGroupStatistic&>(mGroupStats[stats.mGrpId]) = stats ;
mStateHelper->setLoading(GXSTRANS_GROUP_STAT, false);
}, this );
});
}
#endif
void GxsTransportStatistics::loadGroups()
{
mStateHelper->setLoading(GXSTRANS_GROUP_META, true);
RsThread::async([this]()
{
// 1 - get message data from p3GxsForums
#ifdef DEBUG_FORUMS
std::cerr << "Retrieving post data for post " << mThreadId << std::endl;
#endif
auto stats = std::make_unique<
std::map<RsGxsGroupId,RsGxsTransGroupStatistics> >();
if(!rsGxsTrans->getGroupStatistics(*stats))
{
RS_ERR("Cannot retrieve group statistics in GxsTransportStatistics");
return;
}
RsQThreadUtils::postToObject(
[stats = std::move(stats), this]()
{
/* Here it goes any code you want to be executed on the Qt Gui
* thread, for example to update the data model with new information
* after a blocking call to RetroShare API complete */
// TODO: consider making mGroupStats an unique_ptr to avoid copying
mGroupStats = *stats;
updateContent();
mStateHelper->setLoading(GXSTRANS_GROUP_META, false);
}, this );
});
}