/******************************************************************************* * gui/NewsFeed.cpp * * * * Copyright (c) 2008 Robert Fernie * * * * 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 . * * * *******************************************************************************/ #include #include "NewsFeed.h" #include "ui_NewsFeed.h" #include #include #include #include #include #include #include #include #include "util/qtthreadsutils.h" #include "feeds/ChatMsgItem.h" #include "feeds/GxsCircleItem.h" #include "feeds/GxsChannelGroupItem.h" #include "feeds/GxsChannelPostItem.h" #include "feeds/GxsForumGroupItem.h" #include "feeds/GxsForumMsgItem.h" #include "feeds/MsgItem.h" #include "feeds/NewsFeedUserNotify.h" #include "feeds/PeerItem.h" #include "feeds/PostedGroupItem.h" #include "feeds/SecurityItem.h" #include "feeds/SecurityIpItem.h" #include "settings/rsettingswin.h" #include "settings/rsharesettings.h" #include "chat/ChatDialog.h" #include "Posted/PostedItem.h" #include "msgs/MessageComposer.h" #include "msgs/MessageInterface.h" #include "common/FeedNotify.h" #include "notifyqt.h" #define ROLE_RECEIVED FEED_TREEWIDGET_SORTROLE #define TOKEN_TYPE_GROUP 1 #define TOKEN_TYPE_MESSAGE 2 #define TOKEN_TYPE_PUBLISHKEY 3 /***** * #define NEWS_DEBUG 1 ****/ static NewsFeed *instance = NULL; /** Constructor */ NewsFeed::NewsFeed(QWidget *parent) : MainPage(parent), ui(new Ui::NewsFeed) { mEventTypes = { RsEventType::AUTHSSL_CONNECTION_AUTENTICATION, RsEventType::PEER_CONNECTION , RsEventType::GXS_CIRCLES , RsEventType::GXS_CHANNELS , RsEventType::GXS_FORUMS , RsEventType::GXS_POSTED , RsEventType::MAIL_STATUS }; for(uint32_t i=0;iregisterEventsHandler(mEventTypes[i], [this](std::shared_ptr event) { handleEvent(event); }, mEventHandlerIds.back() ); } /* Invoke the Qt Designer generated object setup routine */ ui->setupUi(this); if (!instance) { instance = this; } ui->feedWidget->enableRemove(true); ui->sortComboBox->addItem(tr("Newest on top"), Qt::DescendingOrder); ui->sortComboBox->addItem(tr("Oldest on top"), Qt::AscendingOrder); connect(ui->sortComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(sortChanged(int))); connect(ui->removeAllButton, SIGNAL(clicked()), ui->feedWidget, SLOT(clear())); connect(ui->feedWidget, SIGNAL(feedCountChanged()), this, SLOT(sendNewsFeedChanged()),Qt::QueuedConnection); connect(ui->feedOptionsButton, SIGNAL(clicked()), this, SLOT(feedoptions())); ui->feedOptionsButton->hide(); // (csoler) Hidden until we repare the system to display a specific settings page. QString hlp_str = tr( "

  News Feed

\

The Log Feed displays the last events on your network, sorted by the time you received them. \ This gives you a summary of the activity of your friends. \ You can configure which events to show by pressing on Options.

\

The various events shown are: \

    \
  • Connection attempts (useful to make friends with new people and control who's trying to reach you)
  • \
  • Channel and Forum posts
  • \
  • New Channels and Forums you can subscribe to
  • \
  • Private messages from your friends
  • \

\ ") ; registerHelpButton(ui->helpButton,hlp_str,"NewFeed") ; // load settings processSettings(true); } NewsFeed::~NewsFeed() { for(uint32_t i=0;iunregisterEventsHandler(mEventHandlerIds[i]); // save settings processSettings(false); if (instance == this) { instance = NULL; } } UserNotify *NewsFeed::getUserNotify(QObject *parent) { return new NewsFeedUserNotify(this, parent); } void NewsFeed::processSettings(bool load) { Settings->beginGroup("NewsFeed"); if (load) { // load settings // state of sort order Qt::SortOrder sortOrder = (Qt::SortOrder) Settings->value("SortOrder", Qt::AscendingOrder).toInt(); ui->sortComboBox->setCurrentIndex(ui->sortComboBox->findData(sortOrder)); sortChanged(ui->sortComboBox->currentIndex()); } else { // save settings // state of sort order Settings->setValue("SortOrder", ui->sortComboBox->itemData(ui->sortComboBox->currentIndex()).toInt()); } Settings->endGroup(); } void NewsFeed::sortChanged(int index) { Qt::SortOrder sortOrder = (Qt::SortOrder) ui->sortComboBox->itemData(index).toInt(); ui->feedWidget->setSortRole(ROLE_RECEIVED, sortOrder); } // handler for the new notification system in libretroshare. void NewsFeed::handleEvent(std::shared_ptr event) { // /!\ Absolutely no access to Qt structures (such as Settings) should happen here!!! RsQThreadUtils::postToObject( [=]() { handleEvent_main_thread(event); }, this ); } void NewsFeed::handleEvent_main_thread(std::shared_ptr event) { uint flags = Settings->getNewsFeedFlags(); if(event->mType == RsEventType::AUTHSSL_CONNECTION_AUTENTICATION && (flags & RS_FEED_TYPE_SECURITY)) handleSecurityEvent(event); if(event->mType == RsEventType::PEER_CONNECTION && (flags & RS_FEED_TYPE_PEER)) handleConnectionEvent(event); if(event->mType == RsEventType::GXS_CIRCLES && (flags & RS_FEED_TYPE_CIRCLE)) handleCircleEvent(event); if(event->mType == RsEventType::GXS_CHANNELS && (flags & RS_FEED_TYPE_CHANNEL)) handleChannelEvent(event); if(event->mType == RsEventType::GXS_FORUMS && (flags & RS_FEED_TYPE_FORUM)) handleForumEvent(event); if(event->mType == RsEventType::GXS_POSTED && (flags & RS_FEED_TYPE_POSTED)) handleMailEvent(event); if(event->mType == RsEventType::MAIL_STATUS && (flags & RS_FEED_TYPE_MSG)) handlePostedEvent(event); } void NewsFeed::handleMailEvent(std::shared_ptr event) { const RsMailStatusEvent *pe = dynamic_cast(event.get()); if(!pe) return; switch(pe->mMailStatusEventCode) { case RsMailStatusEventType::NEW_MESSAGE: for(auto msgid: pe->mChangedMsgIds) addFeedItem( new MsgItem(this, NEWSFEED_MESSAGELIST, msgid, false)); break; default: break; } } void NewsFeed::handlePostedEvent(std::shared_ptr event) { const RsGxsPostedEvent *pe = dynamic_cast(event.get()); if(!pe) return; switch(pe->mPostedEventCode) { case RsGxsPostedEvent::PostedEventCode::NEW_POSTED_GROUP: addFeedItem( new PostedGroupItem(this, NEWSFEED_POSTEDNEWLIST, pe->mPostedGroupId, false, true)); break; case RsGxsPostedEvent::PostedEventCode::NEW_MESSAGE: addFeedItem( new PostedItem(this, NEWSFEED_POSTEDMSGLIST, pe->mPostedGroupId, pe->mPostedMsgId, false, true)); break; default: break; } } void NewsFeed::handleForumEvent(std::shared_ptr event) { const RsGxsForumEvent *pe = dynamic_cast(event.get()); if(!pe) return; switch(pe->mForumEventCode) { case RsGxsForumEvent::ForumEventCode::UPDATED_FORUM: case RsGxsForumEvent::ForumEventCode::NEW_FORUM: addFeedItem(new GxsForumGroupItem(this, NEWSFEED_FORUMNEWLIST, pe->mForumGroupId, false, true)); break; case RsGxsForumEvent::ForumEventCode::UPDATED_MESSAGE: case RsGxsForumEvent::ForumEventCode::NEW_MESSAGE: addFeedItem(new GxsForumMsgItem(this, NEWSFEED_FORUMNEWLIST, pe->mForumGroupId, pe->mForumMsgId, false, true)); break; default: break; } } void NewsFeed::handleChannelEvent(std::shared_ptr event) { const RsGxsChannelEvent *pe = dynamic_cast(event.get()); if(!pe) return; switch(pe->mChannelEventCode) { case RsGxsChannelEvent::ChannelEventCode::UPDATED_CHANNEL: case RsGxsChannelEvent::ChannelEventCode::NEW_CHANNEL: addFeedItem(new GxsChannelGroupItem(this, NEWSFEED_CHANNELNEWLIST, pe->mChannelGroupId, false, true)); break; case RsGxsChannelEvent::ChannelEventCode::UPDATED_MESSAGE: case RsGxsChannelEvent::ChannelEventCode::NEW_MESSAGE: addFeedItem(new GxsChannelPostItem(this, NEWSFEED_CHANNELNEWLIST, pe->mChannelGroupId, pe->mChannelMsgId, false, true)); break; case RsGxsChannelEvent::ChannelEventCode::RECEIVED_PUBLISH_KEY: addFeedItem(new GxsChannelGroupItem(this, NEWSFEED_CHANNELPUBKEYLIST, pe->mChannelGroupId, false, true)); break; default: break; } } void NewsFeed::handleCircleEvent(std::shared_ptr event) { const RsGxsCircleEvent *pe = dynamic_cast(event.get()); if(!pe) return; RsGxsCircleDetails details; if(!rsGxsCircles->getCircleDetails(pe->mCircleId,details)) { std::cerr << "(EE) Cannot get information about circle " << pe->mCircleId << ". Not in cache?" << std::endl; return; } // Check if the circle is one of which we belong to. If so, then notify in the GUI about other members leaving/subscribing if(details.mAmIAllowed || details.mAmIAdmin) { switch(pe->mCircleEventType) { case RsGxsCircleEvent::CircleEventCode::CIRCLE_MEMBERSHIP_REQUEST: // only show membership requests if we're an admin of that circle if(details.mAmIAdmin) addFeedItemIfUnique(new GxsCircleItem(this, NEWSFEED_CIRCLELIST, pe->mCircleId, pe->mGxsId, RS_FEED_ITEM_CIRCLE_MEMB_REQ),true); break; case RsGxsCircleEvent::CircleEventCode::CIRCLE_MEMBERSHIP_JOIN: addFeedItemIfUnique(new GxsCircleItem(this, NEWSFEED_CIRCLELIST, pe->mCircleId, pe->mGxsId, RS_FEED_ITEM_CIRCLE_MEMB_JOIN),true); break; case RsGxsCircleEvent::CircleEventCode::CIRCLE_MEMBERSHIP_LEAVE: addFeedItemIfUnique(new GxsCircleItem(this, NEWSFEED_CIRCLELIST, pe->mCircleId, pe->mGxsId, RS_FEED_ITEM_CIRCLE_MEMB_LEAVE),true); break; case RsGxsCircleEvent::CircleEventCode::CIRCLE_MEMBERSHIP_INVITE: addFeedItemIfUnique(new GxsCircleItem(this, NEWSFEED_CIRCLELIST, pe->mCircleId, pe->mGxsId, RS_FEED_ITEM_CIRCLE_INVIT_REC),true); break; case RsGxsCircleEvent::CircleEventCode::CIRCLE_MEMBERSHIP_REVOQUED: addFeedItemIfUnique(new GxsCircleItem(this, NEWSFEED_CIRCLELIST, pe->mCircleId, pe->mGxsId, RS_FEED_ITEM_CIRCLE_MEMB_REVOQUED),true); break; default: break; } } } void NewsFeed::handleConnectionEvent(std::shared_ptr event) { const RsConnectionEvent *pe = dynamic_cast(event.get()); if(!pe) return; auto& e(*pe); #ifdef NEWS_DEBUG std::cerr << "NotifyQt: handling connection event from peer " << e.mSslId << std::endl; #endif switch(e.mConnectionInfoCode) { case RsConnectionEvent::ConnectionEventCode::PEER_CONNECTED: addFeedItemIfUnique(new PeerItem(this, NEWSFEED_PEERLIST, e.mSslId, PEER_TYPE_CONNECT, false), true); break; case RsConnectionEvent::ConnectionEventCode::PEER_DISCONNECTED: // not handled yet break; case RsConnectionEvent::ConnectionEventCode::PEER_TIME_SHIFT:addFeedItemIfUnique(new PeerItem(this, NEWSFEED_PEERLIST, e.mSslId, PEER_TYPE_OFFSET, false),false); break; case RsConnectionEvent::ConnectionEventCode::PEER_REPORTS_WRONG_IP: addFeedItemIfUnique(new SecurityIpItem(this, e.mSslId, e.mStrInfo2, e.mStrInfo1, RS_FEED_ITEM_SEC_IP_WRONG_EXTERNAL_IP_REPORTED, false), false); break; default: break; } } void NewsFeed::handleSecurityEvent(std::shared_ptr event) { const RsAuthSslConnectionAutenticationEvent *pe = dynamic_cast(event.get()); if(!pe) return; auto& e(*pe); #ifdef NEWS_DEBUG std::cerr << "NotifyQt: handling security event from (" << e.mSslId << "," << e.mPgpId << ") error code: " << (int)e.mErrorCode << std::endl; #endif uint flags = Settings->getNewsFeedFlags(); if(e.mErrorCode == RsAuthSslConnectionAutenticationEvent::AuthenticationCode::PEER_REFUSED_CONNECTION) { addFeedItemIfUnique(new PeerItem(this, NEWSFEED_PEERLIST, e.mSslId, PEER_TYPE_HELLO, false), true ); return; } uint32_t FeedItemType=0; switch(e.mErrorCode) { case RsAuthSslConnectionAutenticationEvent::AuthenticationCode::NO_CERTIFICATE_SUPPLIED: case RsAuthSslConnectionAutenticationEvent::AuthenticationCode::MISMATCHED_PGP_ID: // fallthrough case RsAuthSslConnectionAutenticationEvent::AuthenticationCode::MISSING_AUTHENTICATION_INFO: FeedItemType = RS_FEED_ITEM_SEC_BAD_CERTIFICATE; break; case RsAuthSslConnectionAutenticationEvent::AuthenticationCode::PGP_SIGNATURE_VALIDATION_FAILED: FeedItemType = RS_FEED_ITEM_SEC_WRONG_SIGNATURE; break; case RsAuthSslConnectionAutenticationEvent::AuthenticationCode::NOT_A_FRIEND: FeedItemType = RS_FEED_ITEM_SEC_CONNECT_ATTEMPT; break; case RsAuthSslConnectionAutenticationEvent::AuthenticationCode::IP_IS_BLACKLISTED: FeedItemType = RS_FEED_ITEM_SEC_IP_BLACKLISTED; break; case RsAuthSslConnectionAutenticationEvent::AuthenticationCode::MISSING_CERTIFICATE: FeedItemType = RS_FEED_ITEM_SEC_MISSING_CERTIFICATE; break; default: return; // display nothing } RsPeerDetails det; rsPeers->getPeerDetails(e.mSslId,det) || rsPeers->getGPGDetails(e.mPgpId,det); addFeedItemIfUnique(new SecurityItem(this, NEWSFEED_SECLIST, e.mPgpId, e.mSslId, det.location, e.mLocator.toString(), FeedItemType, false), true ); if (Settings->getMessageFlags() & RS_MESSAGE_CONNECT_ATTEMPT) MessageComposer::addConnectAttemptMsg(e.mPgpId, e.mSslId, QString::fromStdString(det.name + "(" + det.location + ")")); } void NewsFeed::testFeeds(uint notifyFlags) { #ifdef TO_REMOVE if (!instance) { return; } instance->ui->feedWidget->enableCountChangedSignal(false); uint pos = 0; while (notifyFlags) { uint type = notifyFlags & (1 << pos); notifyFlags &= ~(1 << pos); ++pos; RsFeedItem fi; } instance->ui->feedWidget->enableCountChangedSignal(true); instance->sendNewsFeedChanged(); #endif std::cerr << "(EE) testFeeds() is currently disabled" << std::endl; } void NewsFeed::testFeed(FeedNotify *feedNotify) { if (!instance) { return; } if (!feedNotify) { return; } FeedItem *feedItem = feedNotify->testFeedItem(instance); if (!feedItem) { return; } instance->addFeedItem(feedItem); } void NewsFeed::addFeedItem(FeedItem *item) { static const int MAX_FEEDITEM_COUNT = 500 ; item->setAttribute(Qt::WA_DeleteOnClose, true); // costly, but not really a problem here int feedItemCount; bool fromTop = (ui->sortComboBox->itemData(ui->sortComboBox->currentIndex()).toInt() == Qt::AscendingOrder); while ((feedItemCount = ui->feedWidget->feedItemCount()) >= MAX_FEEDITEM_COUNT) { FeedItem *feedItem = ui->feedWidget->feedItem(fromTop ? 0 : feedItemCount - 1); if (!feedItem) { break; } ui->feedWidget->removeFeedItem(feedItem); } ui->feedWidget->addFeedItem(item, ROLE_RECEIVED, QDateTime::currentDateTime()); sendNewsFeedChanged(); } void NewsFeed::addFeedItemIfUnique(FeedItem *item, bool replace) { FeedItem *feedItem = ui->feedWidget->findFeedItem(item->uniqueIdentifier()); if (feedItem) { if (!replace) { delete item; return; } ui->feedWidget->removeFeedItem(feedItem); } addFeedItem(item); sendNewsFeedChanged(); } void NewsFeed::remUniqueFeedItem(FeedItem *item) { FeedItem *feedItem = ui->feedWidget->findFeedItem(item->uniqueIdentifier()); if (feedItem) { delete item; ui->feedWidget->removeFeedItem(feedItem); } } /* FeedHolder Functions (for FeedItem functionality) */ QScrollArea *NewsFeed::getScrollArea() { return NULL; } void NewsFeed::deleteFeedItem(QWidget *item, uint32_t /*type*/) { #ifdef NEWS_DEBUG std::cerr << "NewsFeed::deleteFeedItem()"; std::cerr << std::endl; #endif if (item) { item->close (); } } void NewsFeed::openChat(const RsPeerId &peerId) { #ifdef NEWS_DEBUG std::cerr << "NewsFeed::openChat()"; std::cerr << std::endl; #endif ChatDialog::chatFriend(ChatId(peerId)); } void NewsFeed::openComments(uint32_t /*type*/, const RsGxsGroupId &/*groupId*/, const QVector &/*versions*/,const RsGxsMessageId &/*msgId*/, const QString &/*title*/) { std::cerr << "NewsFeed::openComments() Not Handled Yet"; std::cerr << std::endl; } static void sendNewsFeedChangedCallback(FeedItem *feedItem, void *data) { if (dynamic_cast(feedItem) == NULL) { /* don't count PeerItem's */ ++(*((int*) data)); } } void NewsFeed::sendNewsFeedChanged() { int count = 0; ui->feedWidget->withAll(sendNewsFeedChangedCallback, &count); emit newsFeedChanged(count); } void NewsFeed::feedoptions() { SettingsPage::showYourself(this, SettingsPage::Notify); }