2012-12-09 02:32:47 +00:00
/****************************************************************
* RetroShare is distributed under the following license :
*
* Copyright ( C ) 2012 , RetroShare Team
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version 2
* 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 General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor ,
* Boston , MA 02110 - 1301 , USA .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include <QApplication>
# include <QTreeWidgetItem>
# include "GxsForumsFillThread.h"
# include "GxsForumThreadWidget.h"
2013-02-28 21:58:38 +00:00
# include "retroshare/rsgxsflags.h"
2012-12-09 02:32:47 +00:00
# include <retroshare/rsgxsforums.h>
# include <iostream>
2013-02-28 21:58:38 +00:00
# include <algorithm>
2012-12-09 02:32:47 +00:00
2017-02-11 10:45:30 +01:00
//#define DEBUG_FORUMS
2013-07-12 17:24:11 +00:00
# define PROGRESSBAR_MAX 100
2012-12-09 02:32:47 +00:00
GxsForumsFillThread : : GxsForumsFillThread ( GxsForumThreadWidget * parent )
: QThread ( parent ) , mParent ( parent )
{
mStopped = false ;
mCompareRole = NULL ;
mExpandNewMessages = true ;
mFillComplete = false ;
mFilterColumn = 0 ;
mViewType = 0 ;
mFlatView = false ;
mUseChildTS = false ;
}
GxsForumsFillThread : : ~ GxsForumsFillThread ( )
{
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::~GxsForumsFillThread " < < std : : endl ;
# endif
// remove all items (when items are available, the thread was terminated)
QList < QTreeWidgetItem * > : : iterator item ;
2014-10-21 22:33:02 +00:00
for ( item = mItems . begin ( ) ; item ! = mItems . end ( ) ; + + item ) {
2012-12-09 02:32:47 +00:00
if ( * item ) {
delete ( * item ) ;
}
}
mItems . clear ( ) ;
mItemToExpand . clear ( ) ;
}
void GxsForumsFillThread : : stop ( )
{
disconnect ( ) ;
mStopped = true ;
QApplication : : processEvents ( ) ;
wait ( ) ;
}
2013-01-07 22:11:16 +00:00
void GxsForumsFillThread : : calculateExpand ( const RsGxsForumMsg & msg , QTreeWidgetItem * item )
{
if ( mFillComplete & & mExpandNewMessages & & IS_MSG_UNREAD ( msg . mMeta . mMsgStatus ) ) {
QTreeWidgetItem * parentItem = item ;
while ( ( parentItem = parentItem - > parent ( ) ) ! = NULL ) {
if ( std : : find ( mItemToExpand . begin ( ) , mItemToExpand . end ( ) , parentItem ) = = mItemToExpand . end ( ) ) {
mItemToExpand . push_back ( parentItem ) ;
}
}
}
}
2017-02-09 16:15:35 +01:00
static bool decreasing_time_comp ( const QPair < time_t , RsGxsMessageId > & e1 , const QPair < time_t , RsGxsMessageId > & e2 ) { return e2 . first < e1 . first ; }
2012-12-09 02:32:47 +00:00
void GxsForumsFillThread : : run ( )
{
RsTokenService * service = rsGxsForums - > getTokenService ( ) ;
emit status ( tr ( " Waiting " ) ) ;
/* get all messages of the forum */
RsTokReqOptions opts ;
opts . mReqType = GXS_REQUEST_TYPE_MSG_DATA ;
2014-03-17 20:56:06 +00:00
std : : list < RsGxsGroupId > grpIds ;
2012-12-09 02:32:47 +00:00
grpIds . push_back ( mForumId ) ;
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() forum id " < < mForumId < < std : : endl ;
# endif
uint32_t token ;
service - > requestMsgInfo ( token , RS_TOKREQ_ANSTYPE_DATA , opts , grpIds ) ;
/* wait for the answer */
2014-11-29 18:45:01 +00:00
uint32_t requestStatus = RsTokenService : : GXS_REQUEST_V2_STATUS_PENDING ;
2012-12-09 02:32:47 +00:00
while ( ! wasStopped ( ) ) {
requestStatus = service - > requestStatus ( token ) ;
if ( requestStatus = = RsTokenService : : GXS_REQUEST_V2_STATUS_FAILED | |
requestStatus = = RsTokenService : : GXS_REQUEST_V2_STATUS_COMPLETE ) {
break ;
}
msleep ( 100 ) ;
}
if ( wasStopped ( ) ) {
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() thread stopped, cancel request " < < std : : endl ;
# endif
/* cancel request */
service - > cancelRequest ( token ) ;
return ;
}
if ( requestStatus = = RsTokenService : : GXS_REQUEST_V2_STATUS_FAILED ) {
//#TODO
return ;
}
//#TODO
// if (failed) {
// mService->cancelRequest(token);
// return;
// }
emit status ( tr ( " Retrieving " ) ) ;
/* get messages */
2017-02-06 00:11:26 +01:00
std : : map < RsGxsMessageId , RsGxsForumMsg > msgs ;
{ // This forces to delete msgs_array after the conversion to std::map.
std : : vector < RsGxsForumMsg > msgs_array ;
if ( ! rsGxsForums - > getMsgData ( token , msgs_array ) ) {
return ;
}
// now put everything into a map in order to make search log(n)
for ( uint32_t i = 0 ; i < msgs_array . size ( ) ; + + i )
{
2017-02-06 23:46:01 +01:00
# ifdef DEBUG_FORUMS
2017-02-06 00:11:26 +01:00
std : : cerr < < " Adding message " < < msgs_array [ i ] . mMeta . mMsgId < < " with parent " < < msgs_array [ i ] . mMeta . mParentId < < " to message map " < < std : : endl ;
2017-02-06 23:46:01 +01:00
# endif
2017-02-06 00:11:26 +01:00
msgs [ msgs_array [ i ] . mMeta . mMsgId ] = msgs_array [ i ] ;
}
2012-12-09 02:32:47 +00:00
}
emit status ( tr ( " Loading " ) ) ;
int count = msgs . size ( ) ;
int pos = 0 ;
2013-07-12 17:24:11 +00:00
int steps = count / PROGRESSBAR_MAX ;
int step = 0 ;
2017-02-09 14:49:43 +01:00
2017-02-06 00:11:26 +01:00
// ThreadList contains the list of parent threads. The algorithm below iterates through all messages
// and tries to establish parenthood relationships between them, given that we only know the
// immediate parent of a message and now its children. Some messages have a missing parent and for them
// a fake top level parent is generated.
// In order to be efficient, we first create a structure that lists the children of every mesage ID in the list.
// Then the hierarchy of message is build by attaching the kids to every message until all of them have been processed.
// The messages with missing parents will be the last ones remaining in the list.
std : : list < std : : pair < RsGxsMessageId , QTreeWidgetItem * > > threadStack ;
std : : map < RsGxsMessageId , std : : list < RsGxsMessageId > > kids_array ;
std : : set < RsGxsMessageId > missing_parents ;
2017-02-09 14:49:43 +01:00
// First of all, remove all older versions of posts. This is done by first adding all posts into a hierarchy structure
// and then removing all posts which have a new versions available. The older versions are kept appart.
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() Collecting post versions " < < std : : endl ;
# endif
mPostVersions . clear ( ) ;
std : : list < RsGxsMessageId > msg_stack ;
for ( std : : map < RsGxsMessageId , RsGxsForumMsg > : : iterator msgIt = msgs . begin ( ) ; msgIt ! = msgs . end ( ) ; + + msgIt )
if ( ! msgIt - > second . mMeta . mOrigMsgId . isNull ( ) & & msgIt - > second . mMeta . mOrigMsgId ! = msgIt - > second . mMeta . mMsgId )
2017-02-28 18:05:44 +01:00
{
2017-02-09 14:49:43 +01:00
# ifdef DEBUG_FORUMS
std : : cerr < < " Post " < < msgIt - > second . mMeta . mMsgId < < " is a new version of " < < msgIt - > second . mMeta . mOrigMsgId < < std : : endl ;
# endif
2017-02-09 16:15:35 +01:00
std : : map < RsGxsMessageId , RsGxsForumMsg > : : iterator msgIt2 = msgs . find ( msgIt - > second . mMeta . mOrigMsgId ) ;
2017-02-28 18:05:44 +01:00
// Ensuring that the post exists allows to only collect the existing data.
2017-02-09 16:15:35 +01:00
2017-02-28 18:05:44 +01:00
if ( msgIt2 = = msgs . end ( ) )
continue ;
2017-02-09 16:15:35 +01:00
2017-02-28 18:05:44 +01:00
// Make sure that the author is the same than the original message. This should always happen, but nothing can prevent someone to
// craft a new version of a message with his own signature.
2017-02-09 21:49:24 +01:00
2017-02-28 18:05:44 +01:00
if ( msgIt2 - > second . mMeta . mAuthorId ! = msgIt - > second . mMeta . mAuthorId )
continue ;
// always add the post a self version
if ( mPostVersions [ msgIt - > second . mMeta . mOrigMsgId ] . empty ( ) )
mPostVersions [ msgIt - > second . mMeta . mOrigMsgId ] . push_back ( QPair < time_t , RsGxsMessageId > ( msgIt2 - > second . mMeta . mPublishTs , msgIt2 - > second . mMeta . mMsgId ) ) ;
mPostVersions [ msgIt - > second . mMeta . mOrigMsgId ] . push_back ( QPair < time_t , RsGxsMessageId > ( msgIt - > second . mMeta . mPublishTs , msgIt - > second . mMeta . mMsgId ) ) ;
}
2017-02-09 14:49:43 +01:00
2017-02-09 16:15:35 +01:00
// The following code assembles all new versions of a given post into the same array, indexed by the oldest version of the post.
2017-02-09 14:49:43 +01:00
for ( QMap < RsGxsMessageId , QVector < QPair < time_t , RsGxsMessageId > > > : : iterator it ( mPostVersions . begin ( ) ) ; it ! = mPostVersions . end ( ) ; + + it )
{
QVector < QPair < time_t , RsGxsMessageId > > & v ( * it ) ;
for ( int32_t i = 0 ; i < v . size ( ) ; + + i )
2017-02-09 16:15:35 +01:00
if ( v [ i ] . second ! = it . key ( ) )
{
RsGxsMessageId sub_msg_id = v [ i ] . second ;
2017-02-09 14:49:43 +01:00
2017-02-09 16:15:35 +01:00
QMap < RsGxsMessageId , QVector < QPair < time_t , RsGxsMessageId > > > : : iterator it2 = mPostVersions . find ( sub_msg_id ) ;
2017-02-09 14:49:43 +01:00
2017-02-09 16:15:35 +01:00
if ( it2 ! = mPostVersions . end ( ) )
{
2017-02-11 10:45:30 +01:00
for ( int32_t j = 0 ; j < ( * it2 ) . size ( ) ; + + j )
2017-02-09 16:15:35 +01:00
if ( ( * it2 ) [ j ] . second ! = sub_msg_id ) // dont copy it, since it is already present at slot i
v . append ( ( * it2 ) [ j ] ) ;
2017-02-09 14:49:43 +01:00
2017-02-09 16:15:35 +01:00
mPostVersions . erase ( it2 ) ; // it2 is never equal to it
}
}
2017-02-09 14:49:43 +01:00
}
2017-05-09 21:33:02 +02:00
2017-02-09 16:15:35 +01:00
// Now remove from msg ids, all posts except the most recent one. And make the mPostVersion be indexed by the most recent version of the post,
// which corresponds to the item in the tree widget.
2017-02-09 14:49:43 +01:00
# ifdef DEBUG_FORUMS
std : : cerr < < " Final post versions: " < < std : : endl ;
# endif
2017-02-09 16:15:35 +01:00
QMap < RsGxsMessageId , QVector < QPair < time_t , RsGxsMessageId > > > mTmp ;
2017-05-09 21:33:02 +02:00
std : : map < RsGxsMessageId , RsGxsMessageId > most_recent_versions ;
2017-02-09 16:15:35 +01:00
2017-02-09 14:49:43 +01:00
for ( QMap < RsGxsMessageId , QVector < QPair < time_t , RsGxsMessageId > > > : : iterator it ( mPostVersions . begin ( ) ) ; it ! = mPostVersions . end ( ) ; + + it )
{
# ifdef DEBUG_FORUMS
std : : cerr < < " Original post: " < < it . key ( ) < < std : : endl ;
# endif
2017-02-09 16:15:35 +01:00
// Finally, sort the posts from newer to older
qSort ( ( * it ) . begin ( ) , ( * it ) . end ( ) , decreasing_time_comp ) ;
2017-02-09 14:49:43 +01:00
2017-02-09 16:15:35 +01:00
# ifdef DEBUG_FORUMS
std : : cerr < < " most recent version " < < ( * it ) [ 0 ] . first < < " " < < ( * it ) [ 0 ] . second < < std : : endl ;
# endif
2017-02-11 10:45:30 +01:00
for ( int32_t i = 1 ; i < ( * it ) . size ( ) ; + + i )
2017-02-09 14:49:43 +01:00
{
2017-02-09 16:15:35 +01:00
msgs . erase ( ( * it ) [ i ] . second ) ;
2017-02-09 14:49:43 +01:00
# ifdef DEBUG_FORUMS
2017-02-09 16:15:35 +01:00
std : : cerr < < " older version " < < ( * it ) [ i ] . first < < " " < < ( * it ) [ i ] . second < < std : : endl ;
2017-02-09 14:49:43 +01:00
# endif
}
2017-02-09 16:15:35 +01:00
mTmp [ ( * it ) [ 0 ] . second ] = * it ; // index the versions map by the ID of the most recent post.
2017-05-09 21:33:02 +02:00
// Now make sure that message parents are consistent. Indeed, an old post may have the old version of a post as parent. So we need to change that parent
// to the newest version. So we create a map of which is the most recent version of each message, so that parent messages can be searched in it.
2017-07-15 13:20:46 +02:00
for ( int i = 1 ; i < ( * it ) . size ( ) ; + + i )
2017-05-09 21:33:02 +02:00
most_recent_versions [ ( * it ) [ i ] . second ] = ( * it ) [ 0 ] . second ;
2017-02-09 14:49:43 +01:00
}
2017-02-09 16:15:35 +01:00
mPostVersions = mTmp ;
2017-02-09 14:49:43 +01:00
2017-05-09 21:33:02 +02:00
// The next step is to find the top level thread messages. These are defined as the messages without
2017-02-06 00:11:26 +01:00
// any parent message ID.
// this trick is needed because while we remove messages, the parents a given msg may already have been removed
// and wrongly understand as a missing parent.
std : : map < RsGxsMessageId , RsGxsForumMsg > kept_msgs ;
for ( std : : map < RsGxsMessageId , RsGxsForumMsg > : : iterator msgIt = msgs . begin ( ) ; msgIt ! = msgs . end ( ) ; + + msgIt )
2017-02-09 14:49:43 +01:00
{
2017-02-06 00:11:26 +01:00
if ( mFlatView | | msgIt - > second . mMeta . mParentId . isNull ( ) )
{
/* add all threads */
if ( wasStopped ( ) )
return ;
const RsGxsForumMsg & msg = msgIt - > second ;
2012-12-09 02:32:47 +00:00
# ifdef DEBUG_FORUMS
2017-02-06 00:11:26 +01:00
std : : cerr < < " GxsForumsFillThread::run() Adding TopLevel Thread: mId: " < < msg . mMeta . mMsgId < < std : : endl ;
2012-12-09 02:32:47 +00:00
# endif
2017-02-06 00:11:26 +01:00
QTreeWidgetItem * item = mParent - > convertMsgToThreadWidget ( msg , mUseChildTS , mFilterColumn , NULL ) ;
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
if ( ! mFlatView )
threadStack . push_back ( std : : make_pair ( msg . mMeta . mMsgId , item ) ) ;
2013-07-12 17:24:11 +00:00
2017-02-06 00:11:26 +01:00
calculateExpand ( msg , item ) ;
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
mItems . append ( item ) ;
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
if ( + + step > = steps ) {
step = 0 ;
emit progress ( + + pos , PROGRESSBAR_MAX ) ;
2012-12-09 02:32:47 +00:00
}
2017-02-06 00:11:26 +01:00
}
else
{
2012-12-09 02:32:47 +00:00
# ifdef DEBUG_FORUMS
2017-02-06 00:11:26 +01:00
std : : cerr < < " GxsForumsFillThread::run() Storing kid " < < msgIt - > first < < " of message " < < msgIt - > second . mMeta . mParentId < < std : : endl ;
2012-12-09 02:32:47 +00:00
# endif
2017-02-06 00:11:26 +01:00
// The same missing parent may appear multiple times, so we first store them into a unique container.
2012-12-09 02:32:47 +00:00
2017-05-09 21:33:02 +02:00
RsGxsMessageId parent_msg = msgIt - > second . mMeta . mParentId ;
if ( msgs . find ( parent_msg ) = = msgs . end ( ) )
{
// also check that the message is not versionned
std : : map < RsGxsMessageId , RsGxsMessageId > : : const_iterator mrit = most_recent_versions . find ( parent_msg ) ;
if ( mrit ! = most_recent_versions . end ( ) )
parent_msg = mrit - > second ;
else
missing_parents . insert ( parent_msg ) ;
}
2012-12-09 02:32:47 +00:00
2017-05-09 21:33:02 +02:00
kids_array [ parent_msg ] . push_back ( msgIt - > first ) ;
2017-02-06 00:11:26 +01:00
kept_msgs . insert ( * msgIt ) ;
}
2017-02-09 14:49:43 +01:00
}
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
msgs = kept_msgs ;
2012-12-09 02:32:47 +00:00
2017-02-09 14:49:43 +01:00
// Also create a list of posts by time, when they are new versions of existing posts. Only the last one will have an item created.
2017-02-06 00:11:26 +01:00
// Add a fake toplevel item for the parent IDs that we dont actually have.
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
for ( std : : set < RsGxsMessageId > : : const_iterator it ( missing_parents . begin ( ) ) ; it ! = missing_parents . end ( ) ; + + it )
{
// add dummy parent item
QTreeWidgetItem * parent = mParent - > generateMissingItem ( * it ) ;
mItems . append ( parent ) ;
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
threadStack . push_back ( std : : make_pair ( * it , parent ) ) ;
}
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() Processing stack: " < < std : : endl ;
# endif
// Now use a stack to go down the hierarchy
2013-07-12 17:24:11 +00:00
2017-02-06 00:11:26 +01:00
while ( ! threadStack . empty ( ) )
{
std : : pair < RsGxsMessageId , QTreeWidgetItem * > threadPair = threadStack . front ( ) ;
threadStack . pop_front ( ) ;
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
std : : map < RsGxsMessageId , std : : list < RsGxsMessageId > > : : iterator it = kids_array . find ( threadPair . first ) ;
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() Node: " < < threadPair . first < < std : : endl ;
# endif
if ( it = = kids_array . end ( ) )
continue ;
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
if ( wasStopped ( ) )
return ;
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
for ( std : : list < RsGxsMessageId > : : const_iterator it2 ( it - > second . begin ( ) ) ; it2 ! = it - > second . end ( ) ; + + it2 )
{
// We iterate through the top level thread items, and look for which message has the current item as parent.
// When found, the item is put in the thread list itself, as a potential new parent.
std : : map < RsGxsMessageId , RsGxsForumMsg > : : iterator mit = msgs . find ( * it2 ) ;
if ( mit = = msgs . end ( ) )
{
std : : cerr < < " GxsForumsFillThread::run() Cannot find submessage " < < * it2 < < " !!! " < < std : : endl ;
continue ;
}
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
const RsGxsForumMsg & msg ( mit - > second ) ;
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() adding sub_item " < < msg . mMeta . mMsgId < < std : : endl ;
# endif
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
QTreeWidgetItem * item = mParent - > convertMsgToThreadWidget ( msg , mUseChildTS , mFilterColumn , threadPair . second ) ;
calculateExpand ( msg , item ) ;
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
/* add item to process list */
threadStack . push_back ( std : : make_pair ( msg . mMeta . mMsgId , item ) ) ;
2012-12-09 02:32:47 +00:00
2017-02-06 00:11:26 +01:00
if ( + + step > = steps ) {
step = 0 ;
emit progress ( + + pos , PROGRESSBAR_MAX ) ;
2012-12-09 02:32:47 +00:00
}
2017-02-06 00:11:26 +01:00
msgs . erase ( mit ) ;
2012-12-09 02:32:47 +00:00
}
2017-02-06 00:11:26 +01:00
# ifdef DEBUG_FORUMS
std : : cerr < < " GxsForumsFillThread::run() Erasing entry " < < it - > first < < " from kids tab. " < < std : : endl ;
# endif
kids_array . erase ( it ) ; // This is not strictly needed, but it improves performance by reducing the search space.
2012-12-09 02:32:47 +00:00
}
# ifdef DEBUG_FORUMS
2017-02-06 00:11:26 +01:00
std : : cerr < < " Kids array now has " < < kids_array . size ( ) < < " elements " < < std : : endl ;
for ( std : : map < RsGxsMessageId , std : : list < RsGxsMessageId > > : : const_iterator it ( kids_array . begin ( ) ) ; it ! = kids_array . end ( ) ; + + it )
{
std : : cerr < < " Node " < < it - > first < < std : : endl ;
for ( std : : list < RsGxsMessageId > : : const_iterator it2 ( it - > second . begin ( ) ) ; it2 ! = it - > second . end ( ) ; + + it2 )
std : : cerr < < " " < < * it2 < < std : : endl ;
}
2012-12-09 02:32:47 +00:00
std : : cerr < < " GxsForumsFillThread::run() stopped: " < < ( wasStopped ( ) ? " yes " : " no " ) < < std : : endl ;
# endif
}
2017-02-06 00:11:26 +01:00