diff --git a/.travis.yml b/.travis.yml index 4fec84f99..cfe2eac21 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,7 +57,7 @@ before_script: - if [ $TRAVIS_OS_NAME == linux ]; then qmake; fi - > if [ $TRAVIS_OS_NAME == osx ]; then - qmake CONFIG+=rs_macos10.14 + qmake CONFIG+=rs_macos10.14 CONFIG+=c++14 INCLUDEPATH+=$(find /usr/local/Cellar/miniupnpc/*/include | head -n 1) QMAKE_LIBDIR+=$(find /usr/local/Cellar/miniupnpc/*/lib/ | head -n 1) INCLUDEPATH+=$(find /usr/local/Cellar/openssl*/*/include/ | head -n 1) diff --git a/build_scripts/OBS b/build_scripts/OBS index 879d5dfe8..b0d7ae39f 160000 --- a/build_scripts/OBS +++ b/build_scripts/OBS @@ -1 +1 @@ -Subproject commit 879d5dfe8dcd8995be753120cf1b8bab4dd2ec82 +Subproject commit b0d7ae39fe9d9192848bbf7b8902d2188da895c9 diff --git a/libretroshare/src/chat/distributedchat.cc b/libretroshare/src/chat/distributedchat.cc index 3f5ecc74e..ca24cac1e 100644 --- a/libretroshare/src/chat/distributedchat.cc +++ b/libretroshare/src/chat/distributedchat.cc @@ -219,7 +219,13 @@ bool DistributedChatService::checkSignature(RsChatLobbyBouncingObject *obj,const // network pre-request key to allow message authentication. - mGixs->requestKey(obj->signature.keyId,peer_list,RsIdentityUsage(RS_SERVICE_TYPE_CHAT,RsIdentityUsage::CHAT_LOBBY_MSG_VALIDATION,RsGxsGroupId(),RsGxsMessageId(),obj->lobby_id)); + mGixs->requestKey(obj->signature.keyId,peer_list,RsIdentityUsage(RsServiceType::CHAT, + RsIdentityUsage::CHAT_LOBBY_MSG_VALIDATION, + RsGxsGroupId(), + RsGxsMessageId(), + RsGxsMessageId(), + RsGxsMessageId(), + obj->lobby_id)); uint32_t size = RsChatSerialiser(RsSerializationFlags::SIGNATURE) .size(dynamic_cast(obj)); @@ -238,7 +244,13 @@ bool DistributedChatService::checkSignature(RsChatLobbyBouncingObject *obj,const } uint32_t error_status ; - RsIdentityUsage use_info(RS_SERVICE_TYPE_CHAT,RsIdentityUsage::CHAT_LOBBY_MSG_VALIDATION,RsGxsGroupId(),RsGxsMessageId(),obj->lobby_id) ; + RsIdentityUsage use_info(RsServiceType::CHAT, + RsIdentityUsage::CHAT_LOBBY_MSG_VALIDATION, + RsGxsGroupId(), + RsGxsMessageId(), + RsGxsMessageId(), + RsGxsMessageId(), + obj->lobby_id) ; if(!mGixs->validateData(memory,size,obj->signature,false,use_info,error_status)) { diff --git a/libretroshare/src/gossipdiscovery/p3gossipdiscovery.cc b/libretroshare/src/gossipdiscovery/p3gossipdiscovery.cc index c4f80436a..fc1c17f85 100644 --- a/libretroshare/src/gossipdiscovery/p3gossipdiscovery.cc +++ b/libretroshare/src/gossipdiscovery/p3gossipdiscovery.cc @@ -450,7 +450,7 @@ void p3discovery2::recvIdentityList(const RsPeerId& pid,const std::list std::cerr << "p3discovery2::recvIdentityList(): from peer " << pid << ": " << ids.size() << " identities" << std::endl; #endif - RsIdentityUsage use_info(RS_SERVICE_TYPE_DISC,RsIdentityUsage::IDENTITY_DATA_UPDATE); + RsIdentityUsage use_info(RsServiceType::GOSSIP_DISCOVERY,RsIdentityUsage::IDENTITY_NEW_FROM_DISCOVERY); for(auto it(ids.begin());it!=ids.end();++it) { diff --git a/libretroshare/src/grouter/p3grouter.cc b/libretroshare/src/grouter/p3grouter.cc index d34a1349f..16148d508 100644 --- a/libretroshare/src/grouter/p3grouter.cc +++ b/libretroshare/src/grouter/p3grouter.cc @@ -2108,7 +2108,7 @@ bool p3GRouter::verifySignedDataItem(const RsGRouterAbstractMsgItem *item,const if(!signature_serializer.serialise(const_cast(item),data,&data_size)) throw std::runtime_error("Cannot serialise signed data."); - RsIdentityUsage use(RS_SERVICE_TYPE_GROUTER,info); + RsIdentityUsage use(RsServiceType::GROUTER,info); if(!mGixs->validateData( data, data_size, item->signature, true, use, error_status )) { diff --git a/libretroshare/src/gxs/gxssecurity.cc b/libretroshare/src/gxs/gxssecurity.cc index 6dedf1dff..d5b377328 100644 --- a/libretroshare/src/gxs/gxssecurity.cc +++ b/libretroshare/src/gxs/gxssecurity.cc @@ -651,6 +651,9 @@ bool GxsSecurity::encrypt(uint8_t *& out, uint32_t &outlen, const uint8_t *in, u try { + if(keys.empty()) + throw std::runtime_error("EVP_SealInit will not be called with 0 keys. GxsSecurity::encrypt() was called with an empty set of destination keys!") ; + for(uint32_t i=0;i &list, const std::string &attribute) { list.push_back(attribute); @@ -123,7 +121,6 @@ RsDataService::RsDataService(const std::string &serviceDir, const std::string &d : RsGeneralDataService(), mDbMutex("RsDataService"), mServiceDir(serviceDir), mDbName(dbName), mDbPath(mServiceDir + "/" + dbName), mServType(serviceType), mDb(NULL) { bool isNewDatabase = !RsDirUtil::fileExists(mDbPath); - mGrpMetaDataCache_ContainsAllDatabase = false ; mDb = new RetroDb(mDbPath, RetroDb::OPEN_READWRITE_CREATE, key); @@ -488,8 +485,7 @@ bool RsDataService::finishReleaseUpdate(int release, bool result) RsGxsGrpMetaData* RsDataService::locked_getGrpMeta(RetroCursor &c, int colOffset,bool use_cache) { #ifdef RS_DATA_SERVICE_DEBUG - std::cerr << "RsDataService::locked_getGrpMeta()"; - std::cerr << std::endl; + std::cerr << "RsDataService::locked_getGrpMeta()" << std::endl; #endif bool ok = true; @@ -506,21 +502,17 @@ RsGxsGrpMetaData* RsDataService::locked_getGrpMeta(RetroCursor &c, int colOffset RsGxsGrpMetaData* grpMeta ; RsGxsGroupId grpId(tempId) ; - if(use_cache) - { - auto it = mGrpMetaDataCache.find(grpId) ; + if(grpId.isNull()) // not in the DB! + return nullptr; - if(it != mGrpMetaDataCache.end()) - grpMeta = it->second ; - else - { - grpMeta = new RsGxsGrpMetaData(); - mGrpMetaDataCache[grpId] = grpMeta ; - } - } + if(use_cache) + grpMeta = mGrpMetaDataCache.getOrCreateMeta(grpId); else grpMeta = new RsGxsGrpMetaData(); + if(!grpMeta->mGroupId.isNull()) // the grpMeta is already initialized because it comes from the cache + return grpMeta; + grpMeta->mGroupId = RsGxsGroupId(tempId); c.getString(mColGrpMeta_NxsIdentity + colOffset, tempId); grpMeta->mAuthorId = RsGxsId(tempId); @@ -653,24 +645,40 @@ RsNxsGrp* RsDataService::locked_getGroup(RetroCursor &c) return NULL; } -RsGxsMsgMetaData* RsDataService::locked_getMsgMeta(RetroCursor &c, int colOffset) +RsGxsMsgMetaData* RsDataService::locked_getMsgMeta(RetroCursor &c, int colOffset,bool use_cache) { - RsGxsMsgMetaData* msgMeta = new RsGxsMsgMetaData(); - bool ok = true; uint32_t data_len = 0, offset = 0; char* data = NULL; + RsGxsGroupId group_id; + RsGxsMessageId msg_id; + std::string gId; c.getString(mColMsgMeta_GrpId + colOffset, gId); - msgMeta->mGroupId = RsGxsGroupId(gId); + group_id = RsGxsGroupId(gId); std::string temp; c.getString(mColMsgMeta_MsgId + colOffset, temp); - msgMeta->mMsgId = RsGxsMessageId(temp); + msg_id = RsGxsMessageId(temp); + // without these, a msg is meaningless - ok &= (!msgMeta->mGroupId.isNull()) && (!msgMeta->mMsgId.isNull()); + if(group_id.isNull() || msg_id.isNull()) + return nullptr; + + RsGxsMsgMetaData* msgMeta = nullptr; + + if(use_cache) + msgMeta = mMsgMetaDataCache[group_id].getOrCreateMeta(msg_id); + else + msgMeta = new RsGxsMsgMetaData(); + + if(!msgMeta->mGroupId.isNull()) // we cannot do that because the cursor needs to advance. Is there a method to skip some data in the db? + return msgMeta; + + msgMeta->mGroupId = group_id; + msgMeta->mMsgId = msg_id; c.getString(mColMsgMeta_OrigMsgId + colOffset, temp); msgMeta->mOrigMsgId = RsGxsMessageId(temp); @@ -704,7 +712,7 @@ RsGxsMsgMetaData* RsDataService::locked_getMsgMeta(RetroCursor &c, int colOffset if(ok) return msgMeta; - else + else if(!use_cache) delete msgMeta; return NULL; @@ -834,7 +842,8 @@ int RsDataService::storeMessage(const std::list& msg) // This is needed so that mLastPost is correctly updated in the group meta when it is re-loaded. - locked_clearGrpMetaCache(msgMetaPtr->mGroupId); + mGrpMetaDataCache.clear(msgMetaPtr->mGroupId); + mMsgMetaDataCache[msgMetaPtr->mGroupId].updateMeta(msgMetaPtr->mMsgId,*msgMetaPtr); } // finish transaction @@ -926,7 +935,7 @@ int RsDataService::storeGroup(const std::list& grp) cv.put(KEY_GRP_STATUS, (int32_t)grpMetaPtr->mGroupStatus); cv.put(KEY_GRP_LAST_POST, (int32_t)grpMetaPtr->mLastPost); - locked_updateGrpMetaCache(*grpMetaPtr); + mGrpMetaDataCache.updateMeta(grpMetaPtr->mGroupId,*grpMetaPtr); if (!mDb->sqlInsert(GRP_TABLE_NAME, "", cv)) { @@ -942,54 +951,6 @@ int RsDataService::storeGroup(const std::list& grp) return ret; } -void RsDataService::locked_updateGrpMetaCache(const RsGxsGrpMetaData& meta) -{ - auto it = mGrpMetaDataCache.find(meta.mGroupId) ; - - if(it != mGrpMetaDataCache.end()) - *(it->second) = meta ; - else - mGrpMetaDataCache[meta.mGroupId] = new RsGxsGrpMetaData(meta) ; -} - -void RsDataService::locked_clearGrpMetaCache(const RsGxsGroupId& gid) -{ - rstime_t now = time(NULL) ; - auto it = mGrpMetaDataCache.find(gid) ; - - // We dont actually delete the item, because it might be used by a calling client. - // In this case, the memory will not be used for long, so we keep it into a list for a safe amount - // of time and delete it later. Using smart pointers here would be more elegant, but that would need - // to be implemented thread safe, which is difficult in this case. - - if(it != mGrpMetaDataCache.end()) - { -#ifdef RS_DATA_SERVICE_DEBUG - std::cerr << "(II) moving database cache entry " << (void*)(*it).second << " to dead list." << std::endl; -#endif - - mOldCachedItems.push_back(std::make_pair(now,it->second)) ; - - mGrpMetaDataCache.erase(it) ; - mGrpMetaDataCache_ContainsAllDatabase = false; - } - - // We also take that opportunity to delete old entries. - - auto it2(mOldCachedItems.begin()); - - while(it2!=mOldCachedItems.end() && (*it2).first + CACHE_ENTRY_GRACE_PERIOD < now) - { -#ifdef RS_DATA_SERVICE_DEBUG - std::cerr << "(II) deleting old GXS database cache entry " << (void*)(*it2).second << ", " << now - (*it2).first << " seconds old." << std::endl; -#endif - - delete (*it2).second ; - it2 = mOldCachedItems.erase(it2) ; - } - -} - int RsDataService::updateGroup(const std::list &grp) { @@ -1058,7 +1019,7 @@ int RsDataService::updateGroup(const std::list &grp) mDb->sqlUpdate(GRP_TABLE_NAME, "grpId='" + grpPtr->grpId.toStdString() + "'", cv); - locked_updateGrpMetaCache(*grpMetaPtr); + mGrpMetaDataCache.updateMeta(grpMetaPtr->mGroupId,*grpMetaPtr); } // finish transaction bool ret = mDb->commitTransaction(); @@ -1275,7 +1236,7 @@ void RsDataService::locked_retrieveMessages(RetroCursor *c, std::vectormetaData = locked_getMsgMeta(*c, metaOffset); + m->metaData = locked_getMsgMeta(*c, metaOffset,false); } msgs.push_back(m); } @@ -1285,7 +1246,7 @@ void RsDataService::locked_retrieveMessages(RetroCursor *c, std::vectorfirst; + const std::set& msgIdV = mit->second; // if vector empty then request all messages - const std::set& msgIdV = mit->second; - std::vector metaSet; - if(msgIdV.empty()){ - RetroCursor* c = mDb->sqlQuery(MSG_TABLE_NAME, mMsgMetaColumns, KEY_GRP_ID+ "='" + grpId.toStdString() + "'", ""); + t_MetaDataCache& cache(mMsgMetaDataCache[grpId]); - if (c) - { - locked_retrieveMsgMeta(c, metaSet); + if(msgIdV.empty()) + { + if(cache.isCacheUpToDate()) + cache.getFullMetaList(msgMeta[grpId]); + else + { + RetroCursor* c = mDb->sqlQuery(MSG_TABLE_NAME, mMsgMetaColumns, KEY_GRP_ID+ "='" + grpId.toStdString() + "'", ""); + + if (c) + { + locked_retrieveMsgMetaList(c, msgMeta[grpId]); + cache.setCacheUpToDate(true); + } + delete c; + } #ifdef RS_DATA_SERVICE_DEBUG_CACHE - std::cerr << mDbName << ": Retrieving (all) Msg metadata grpId=" << grpId << ", " << std::dec << metaSet.size() << " messages" << std::endl; + std::cerr << mDbName << ": Retrieving (all) Msg metadata grpId=" << grpId << ", " << std::dec << metaSet.size() << " messages" << std::endl; #endif - } - }else{ - - // request each grp - std::set::const_iterator sit = msgIdV.begin(); - - for(; sit!=msgIdV.end(); ++sit){ - const RsGxsMessageId& msgId = *sit; - RetroCursor* c = mDb->sqlQuery(MSG_TABLE_NAME, mMsgMetaColumns, KEY_GRP_ID+ "='" + grpId.toStdString() - + "' AND " + KEY_MSG_ID + "='" + msgId.toStdString() + "'", ""); - - if (c) - { - locked_retrieveMsgMeta(c, metaSet); -#ifdef RS_DATA_SERVICE_DEBUG_CACHE - std::cerr << mDbName << ": Retrieving Msg metadata grpId=" << grpId << ", " << std::dec << metaSet.size() << " messages" << std::endl; -#endif - } - } } + else + { + // request each msg meta + auto& metaSet(msgMeta[grpId]); -#ifdef RS_DATA_SERVICE_DEBUG_TIME - resultCount += metaSet.size(); + for(auto sit(msgIdV.begin()); sit!=msgIdV.end(); ++sit) + { + const RsGxsMessageId& msgId = *sit; + + RsGxsMsgMetaData *meta = cache.getMeta(msgId); + + if(meta) + metaSet.push_back(meta); + else + { + RetroCursor* c = mDb->sqlQuery(MSG_TABLE_NAME, mMsgMetaColumns, KEY_GRP_ID+ "='" + grpId.toStdString() + "' AND " + KEY_MSG_ID + "='" + msgId.toStdString() + "'", ""); + + c->moveToFirst(); + RsGxsMsgMetaData* meta = locked_getMsgMeta(*c, 0,true); + + if(meta) + metaSet.push_back(meta); + + delete c; + } + } +#ifdef RS_DATA_SERVICE_DEBUG_CACHE + std::cerr << mDbName << ": Retrieving Msg metadata grpId=" << grpId << ", " << std::dec << metaSet.size() << " messages" << std::endl; #endif - - msgMeta[grpId] = metaSet; + } } #ifdef RS_DATA_SERVICE_DEBUG_TIME @@ -1350,22 +1324,43 @@ int RsDataService::retrieveGxsMsgMetaData(const GxsMsgReq& reqIds, GxsMsgMetaRes return 1; } -void RsDataService::locked_retrieveMsgMeta(RetroCursor *c, std::vector &msgMeta) +void RsDataService::locked_retrieveGrpMetaList(RetroCursor *c, std::map& grpMeta) { + if(!c) + { + RsErr() << __PRETTY_FUNCTION__ << ": attempt to retrieve Group Meta data from the DB with null cursor!" << std::endl; + return; + } - if(c) - { - bool valid = c->moveToFirst(); - while(valid){ - RsGxsMsgMetaData* m = locked_getMsgMeta(*c, 0); + bool valid = c->moveToFirst(); - if(m != NULL) - msgMeta.push_back(m); + while(valid) + { + RsGxsGrpMetaData* m = locked_getGrpMeta(*c, 0,true); - valid = c->moveToNext(); - } - delete c; - } + if(m) + grpMeta[m->mGroupId] = m; + + valid = c->moveToNext(); + } +} +void RsDataService::locked_retrieveMsgMetaList(RetroCursor *c, std::vector& msgMeta) +{ + if(!c) + { + RsErr() << __PRETTY_FUNCTION__ << ": attempt to retrieve Msg Meta data from the DB with null cursor!" << std::endl; + return; + } + + bool valid = c->moveToFirst(); + while(valid){ + const RsGxsMsgMetaData* m = locked_getMsgMeta(*c, 0,true); + + if(m != NULL) + msgMeta.push_back(m); + + valid = c->moveToNext(); + } } int RsDataService::retrieveGxsGrpMetaData(RsGxsGrpMetaTemporaryMap& grp) @@ -1385,13 +1380,13 @@ int RsDataService::retrieveGxsGrpMetaData(RsGxsGrpMetaTemporaryMap& grp) if(grp.empty()) { - if(mGrpMetaDataCache_ContainsAllDatabase) // grab all the stash from the cache, so as to avoid decryption costs. + if(mGrpMetaDataCache.isCacheUpToDate()) // grab all the stash from the cache, so as to avoid decryption costs. { #ifdef RS_DATA_SERVICE_DEBUG_CACHE std::cerr << (void*)this << ": RsDataService::retrieveGxsGrpMetaData() retrieving all from cache!" << std::endl; #endif - grp = mGrpMetaDataCache ; + mGrpMetaDataCache.getFullMetaList(grp) ; } else { @@ -1402,93 +1397,103 @@ int RsDataService::retrieveGxsGrpMetaData(RsGxsGrpMetaTemporaryMap& grp) RetroCursor* c = mDb->sqlQuery(GRP_TABLE_NAME, mGrpMetaColumns, "", ""); - if(c) + if(c) { - bool valid = c->moveToFirst(); + locked_retrieveGrpMetaList(c,grp); - while(valid) - { - RsGxsGrpMetaData* g = locked_getGrpMeta(*c, 0,true); - - if(g) - { - grp[g->mGroupId] = g; -#ifdef RS_DATA_SERVICE_DEBUG_CACHE - std::cerr << (void *)this << " " << mDbName << ": Retrieving (all) Grp metadata grpId=" << g->mGroupId << std::endl; -#endif - } - valid = c->moveToNext(); - -#ifdef RS_DATA_SERVICE_DEBUG_TIME - ++resultCount; -#endif - } - delete c; + mGrpMetaDataCache.setCacheUpToDate(true); } + delete c; +#ifdef RS_DATA_SERVICE_DEBUG_TIME + resultCount += grp.size(); +#endif - mGrpMetaDataCache_ContainsAllDatabase = true ; +// if(c) +// { +// bool valid = c->moveToFirst(); +// +// while(valid) +// { +// RsGxsGrpMetaData* g = locked_getGrpMeta(*c, 0,true); +// +// if(g) +// { +// grp[g->mGroupId] = g; +//#ifdef RS_DATA_SERVICE_DEBUG_CACHE +// std::cerr << (void *)this << " " << mDbName << ": Retrieving (all) Grp metadata grpId=" << g->mGroupId << std::endl; +//#endif +// } +// valid = c->moveToNext(); +// +// } +// delete c; +// } } - } else - { - std::map::iterator mit = grp.begin(); + { + for(auto mit(grp.begin()); mit != grp.end(); ++mit) + { + RsGxsGrpMetaData *meta = mGrpMetaDataCache.getMeta(mit->first) ; - for(; mit != grp.end(); ++mit) - { - std::map::const_iterator itt = mGrpMetaDataCache.find(mit->first) ; - - if(itt != mGrpMetaDataCache.end()) - { + if(meta) + mit->second = meta; + else + { #ifdef RS_DATA_SERVICE_DEBUG_CACHE - std::cerr << mDbName << ": Retrieving Grp metadata grpId=" << mit->first << " from cache!" << std::endl; -#endif - grp[mit->first] = itt->second ; - } - else - { -#ifdef RS_DATA_SERVICE_DEBUG_CACHE - std::cerr << mDbName << ": Retrieving Grp metadata grpId=" << mit->first ; + std::cerr << mDbName << ": Retrieving Grp metadata grpId=" << mit->first ; #endif - const RsGxsGroupId& grpId = mit->first; - RetroCursor* c = mDb->sqlQuery(GRP_TABLE_NAME, mGrpMetaColumns, "grpId='" + grpId.toStdString() + "'", ""); + const RsGxsGroupId& grpId = mit->first; + RetroCursor* c = mDb->sqlQuery(GRP_TABLE_NAME, mGrpMetaColumns, "grpId='" + grpId.toStdString() + "'", ""); - if(c) - { - bool valid = c->moveToFirst(); + c->moveToFirst(); + RsGxsGrpMetaData* meta = locked_getGrpMeta(*c, 0,true); -#ifdef RS_DATA_SERVICE_DEBUG_CACHE - if(!valid) - std::cerr << " Empty query! GrpId " << grpId << " is not in database" << std::endl; -#endif - while(valid) - { - RsGxsGrpMetaData* g = locked_getGrpMeta(*c, 0,true); - - if(g) - { - grp[g->mGroupId] = g; -#ifdef RS_DATA_SERVICE_DEBUG_CACHE - std::cerr << ". Got it. Updating cache." << std::endl; -#endif - } - valid = c->moveToNext(); + if(meta) + mit->second = meta; #ifdef RS_DATA_SERVICE_DEBUG_TIME - ++resultCount; + ++resultCount; #endif - } - delete c; - } -#ifdef RS_DATA_SERVICE_DEBUG_CACHE - else - std::cerr << ". not found!" << std::endl; -#endif - } - } - } + delete c; + + // if(c) + // { + // bool valid = c->moveToFirst(); + // + //#ifdef RS_DATA_SERVICE_DEBUG_CACHE + // if(!valid) + // std::cerr << " Empty query! GrpId " << grpId << " is not in database" << std::endl; + //#endif + // while(valid) + // { + // RsGxsGrpMetaData* g = locked_getGrpMeta(*c, 0,true); + // + // if(g) + // { + // grp[g->mGroupId] = g; + //#ifdef RS_DATA_SERVICE_DEBUG_CACHE + // std::cerr << ". Got it. Updating cache." << std::endl; + //#endif + // } + // valid = c->moveToNext(); + // + //#ifdef RS_DATA_SERVICE_DEBUG_TIME + // ++resultCount; + //#endif + // } + // delete c; + // } +#ifdef RS_DATA_SERVICE_DEBUG_CACHE + else + std::cerr << ". not found!" << std::endl; +#endif + } + } + + } #ifdef RS_DATA_SERVICE_DEBUG_TIME std::cerr << "RsDataService::retrieveGxsGrpMetaData() " << mDbName << ", Requests: " << requestedGroups << ", Results: " << resultCount << ", Time: " << timer.duration() << std::endl; @@ -1525,35 +1530,37 @@ int RsDataService::resetDataStore() return 1; } -int RsDataService::updateGroupMetaData(GrpLocMetaData &meta) +int RsDataService::updateGroupMetaData(const GrpLocMetaData& meta) { #ifdef RS_DATA_SERVICE_DEBUG_CACHE std::cerr << (void*)this << ": Updating Grp Meta data: grpId = " << meta.grpId << std::endl; #endif RsStackMutex stack(mDbMutex); - RsGxsGroupId& grpId = meta.grpId; + const RsGxsGroupId& grpId = meta.grpId; #ifdef RS_DATA_SERVICE_DEBUG_CACHE std::cerr << (void*)this << ": erasing old entry from cache." << std::endl; #endif - locked_clearGrpMetaCache(meta.grpId); + mGrpMetaDataCache.clear(meta.grpId); return mDb->sqlUpdate(GRP_TABLE_NAME, KEY_GRP_ID+ "='" + grpId.toStdString() + "'", meta.val) ? 1 : 0; } -int RsDataService::updateMessageMetaData(MsgLocMetaData &metaData) +int RsDataService::updateMessageMetaData(const MsgLocMetaData& metaData) { #ifdef RS_DATA_SERVICE_DEBUG_CACHE std::cerr << (void*)this << ": Updating Msg Meta data: grpId = " << metaData.msgId.first << " msgId = " << metaData.msgId.second << std::endl; #endif RsStackMutex stack(mDbMutex); - RsGxsGroupId& grpId = metaData.msgId.first; - RsGxsMessageId& msgId = metaData.msgId.second; - return mDb->sqlUpdate(MSG_TABLE_NAME, KEY_GRP_ID+ "='" + grpId.toStdString() - + "' AND " + KEY_MSG_ID + "='" + msgId.toStdString() + "'", metaData.val) ? 1 : 0; + const RsGxsGroupId& grpId = metaData.msgId.first; + const RsGxsMessageId& msgId = metaData.msgId.second; + + mMsgMetaDataCache[grpId].clear(msgId); + + return mDb->sqlUpdate(MSG_TABLE_NAME, KEY_GRP_ID+ "='" + grpId.toStdString() + "' AND " + KEY_MSG_ID + "='" + msgId.toStdString() + "'", metaData.val) ? 1 : 0; } int RsDataService::removeMsgs(const GxsMsgReq& msgIds) @@ -1678,13 +1685,13 @@ bool RsDataService::locked_removeMessageEntries(const GxsMsgReq& msgIds) { const RsGxsGroupId& grpId = mit->first; const std::set& msgsV = mit->second; - std::set::const_iterator vit = msgsV.begin(); + auto& cache(mMsgMetaDataCache[grpId]); - for(; vit != msgsV.end(); ++vit) + for(auto& msgId:msgsV) { - const RsGxsMessageId& msgId = *vit; - mDb->sqlDelete(MSG_TABLE_NAME, KEY_GRP_ID+ "='" + grpId.toStdString() - + "' AND " + KEY_MSG_ID + "='" + msgId.toStdString() + "'", ""); + mDb->sqlDelete(MSG_TABLE_NAME, KEY_GRP_ID+ "='" + grpId.toStdString() + "' AND " + KEY_MSG_ID + "='" + msgId.toStdString() + "'", ""); + + cache.clear(msgId); } } @@ -1698,23 +1705,18 @@ bool RsDataService::locked_removeGroupEntries(const std::vector& g // start a transaction bool ret = mDb->beginTransaction(); - std::vector::const_iterator vit = grpIds.begin(); - - for(; vit != grpIds.end(); ++vit) + for(auto grpId:grpIds) { - - const RsGxsGroupId& grpId = *vit; mDb->sqlDelete(GRP_TABLE_NAME, KEY_GRP_ID+ "='" + grpId.toStdString() + "'", ""); // also remove the group meta from cache. - locked_clearGrpMetaCache(*vit) ; + mGrpMetaDataCache.clear(grpId) ; } ret &= mDb->commitTransaction(); - - mGrpMetaDataCache_ContainsAllDatabase = false ; return ret; } + uint32_t RsDataService::cacheSize() const { return 0; } @@ -1724,3 +1726,38 @@ int RsDataService::setCacheSize(uint32_t /* size */) return 0; } +void RsDataService::debug_printCacheSize() const +{ + uint32_t nb_items,nb_items_on_deadlist; + uint64_t total_size,total_size_of_deadlist; + + mGrpMetaDataCache.debug_computeSize(nb_items, nb_items_on_deadlist, total_size,total_size_of_deadlist); + + RsDbg() << "Cache size: " << std::endl; + RsDbg() << " Groups: " << " total: " << nb_items << " (dead: " << nb_items_on_deadlist << "), size: " << total_size << " (Dead: " << total_size_of_deadlist << ")" << std::endl; + + nb_items = 0,nb_items_on_deadlist = 0; + total_size = 0,total_size_of_deadlist = 0; + + for(auto it:mMsgMetaDataCache) + { + uint32_t tmp_nb_items,tmp_nb_items_on_deadlist; + uint64_t tmp_total_size,tmp_total_size_of_deadlist; + + it.second.debug_computeSize(tmp_nb_items, tmp_nb_items_on_deadlist, tmp_total_size,tmp_total_size_of_deadlist); + + nb_items += tmp_nb_items; + nb_items_on_deadlist += tmp_nb_items_on_deadlist; + total_size += tmp_total_size; + total_size_of_deadlist += tmp_total_size_of_deadlist; + } + RsDbg() << " Msgs: " << " total: " << nb_items << " (dead: " << nb_items_on_deadlist << "), size: " << total_size << " (Dead: " << total_size_of_deadlist << ")" << std::endl; +} + + + + + + + + diff --git a/libretroshare/src/gxs/rsdataservice.h b/libretroshare/src/gxs/rsdataservice.h index 8b4e2aac9..56dc8dea6 100644 --- a/libretroshare/src/gxs/rsdataservice.h +++ b/libretroshare/src/gxs/rsdataservice.h @@ -35,6 +35,116 @@ public: ContentValue cv; }; +template class t_MetaDataCache +{ +public: + t_MetaDataCache() : mCache_ContainsAllMetas(false) {} + + bool isCacheUpToDate() const { return mCache_ContainsAllMetas ; } + void setCacheUpToDate(bool b) { mCache_ContainsAllMetas = b; } + + void getFullMetaList(std::map& mp) const { mp = mMetas ; } + void getFullMetaList(std::vector& mp) const { for(auto& m:mMetas) mp.push_back(m.second) ; } + + MetaDataClass *getMeta(const ID& id) + { + auto itt = mMetas.find(id); + + if(itt != mMetas.end()) + return itt->second ; + else + return NULL; + } + + MetaDataClass *getOrCreateMeta(const ID& id) + { + MetaDataClass *meta = nullptr; + auto it = mMetas.find(id) ; + + if(it != mMetas.end()) + { +#ifdef RS_DATA_SERVICE_DEBUG + RsDbg() << __PRETTY_FUNCTION__ << ": getting group meta " << grpId << " from cache." << std::endl; +#endif + meta = it->second ; + } + else + { +#ifdef RS_DATA_SERVICE_DEBUG + RsDbg() << __PRETTY_FUNCTION__ << ": group meta " << grpId << " not in cache. Loading it from DB..." << std::endl; +#endif + meta = new MetaDataClass(); + mMetas[id] = meta ; + } + + return meta; + } + + void updateMeta(const ID& id,const MetaDataClass& meta) + { + auto it = mMetas.find(id) ; + + if(it != mMetas.end()) + *(it->second) = meta ; + else + mMetas[id] = new MetaDataClass(meta) ; + } + + void clear(const ID& id) + { + rstime_t now = time(NULL) ; + auto it = mMetas.find(id) ; + + // We dont actually delete the item, because it might be used by a calling client. + // In this case, the memory will not be used for long, so we keep it into a list for a safe amount + // of time and delete it later. Using smart pointers here would be more elegant, but that would need + // to be implemented thread safe, which is difficult in this case. + + if(it != mMetas.end()) + { +#ifdef RS_DATA_SERVICE_DEBUG + std::cerr << "(II) moving database cache entry " << (void*)(*it).second << " to dead list." << std::endl; +#endif + + mOldCachedItems.push_back(std::make_pair(now,it->second)) ; + + mMetas.erase(it) ; + mCache_ContainsAllMetas = false; + } + + // We also take that opportunity to delete old entries. + + auto it2(mOldCachedItems.begin()); + + while(it2!=mOldCachedItems.end() && (*it2).first + CACHE_ENTRY_GRACE_PERIOD < now) + { +#ifdef RS_DATA_SERVICE_DEBUG + std::cerr << "(II) deleting old GXS database cache entry " << (void*)(*it2).second << ", " << now - (*it2).first << " seconds old." << std::endl; +#endif + delete (*it2).second ; + it2 = mOldCachedItems.erase(it2) ; + } + } + + void debug_computeSize(uint32_t& nb_items, uint32_t& nb_items_on_deadlist, uint64_t& total_size,uint64_t& total_size_of_deadlist) const + { + nb_items = mMetas.size(); + nb_items_on_deadlist = mOldCachedItems.size(); + total_size = 0; + total_size_of_deadlist = 0; + + for(auto it:mMetas) total_size += it.second->serial_size(); + for(auto it:mOldCachedItems) total_size_of_deadlist += it.second->serial_size(); + } +private: + std::map mMetas; + std::list > mOldCachedItems ; // dead list, where items get deleted after being unused for a while. This is due to not using smart ptrs. + + static const uint32_t CACHE_ENTRY_GRACE_PERIOD = 600 ; // Unused items are deleted 10 minutes after last usage. + + bool mCache_ContainsAllMetas ; +}; + class RsDataService : public RsGeneralDataService { public: @@ -147,13 +257,13 @@ public: * @param metaData The meta data item to update * @return error code */ - int updateMessageMetaData(MsgLocMetaData& metaData); + int updateMessageMetaData(const MsgLocMetaData& metaData); /*! * @param metaData The meta data item to update * @return error code */ - int updateGroupMetaData(GrpLocMetaData& meta); + int updateGroupMetaData(const GrpLocMetaData &meta); /*! * Completely clear out data stored in @@ -174,6 +284,8 @@ public: int updateGroupKeys(const RsGxsGroupId& grpId,const RsTlvSecurityKeySet& keys, uint32_t subscribe_flags) ; + void debug_printCacheSize() const; + private: /*! @@ -194,15 +306,22 @@ private: /*! * Retrieves all the msg meta results from a cursor * @param c cursor to result set - * @param metaSet message metadata retrieved from cursor are stored here + * @param msgMeta message metadata retrieved from cursor are stored here */ - void locked_retrieveMsgMeta(RetroCursor* c, std::vector& msgMeta); + void locked_retrieveMsgMetaList(RetroCursor* c, std::vector& msgMeta); + + /*! + * Retrieves all the grp meta results from a cursor + * @param c cursor to result set + * @param grpMeta group metadata retrieved from cursor are stored here + */ + void locked_retrieveGrpMetaList(RetroCursor *c, std::map& grpMeta); /*! * extracts a msg meta item from a cursor at its * current position */ - RsGxsMsgMetaData* locked_getMsgMeta(RetroCursor& c, int colOffset); + RsGxsMsgMetaData* locked_getMsgMeta(RetroCursor& c, int colOffset, bool use_cache); /*! * extracts a grp meta item from a cursor at its @@ -348,10 +467,8 @@ private: void locked_clearGrpMetaCache(const RsGxsGroupId& gid); void locked_updateGrpMetaCache(const RsGxsGrpMetaData& meta); - std::map mGrpMetaDataCache ; - std::list > mOldCachedItems ; - - bool mGrpMetaDataCache_ContainsAllDatabase ; + t_MetaDataCache mGrpMetaDataCache; + std::map > mMsgMetaDataCache; }; #endif // RSDATASERVICE_H diff --git a/libretroshare/src/gxs/rsgds.h b/libretroshare/src/gxs/rsgds.h index 04c2b17e7..fb985b9f7 100644 --- a/libretroshare/src/gxs/rsgds.h +++ b/libretroshare/src/gxs/rsgds.h @@ -239,12 +239,12 @@ public: /*! * @param metaData */ - virtual int updateMessageMetaData(MsgLocMetaData& metaData) = 0; + virtual int updateMessageMetaData(const MsgLocMetaData& metaData) = 0; /*! * @param metaData */ - virtual int updateGroupMetaData(GrpLocMetaData& meta) = 0; + virtual int updateGroupMetaData(const GrpLocMetaData& meta) = 0; virtual int updateGroupKeys(const RsGxsGroupId& grpId,const RsTlvSecurityKeySet& keys,uint32_t subscribed_flags) = 0 ; diff --git a/libretroshare/src/gxs/rsgenexchange.cc b/libretroshare/src/gxs/rsgenexchange.cc index 8debf99f4..bd51875c8 100644 --- a/libretroshare/src/gxs/rsgenexchange.cc +++ b/libretroshare/src/gxs/rsgenexchange.cc @@ -512,7 +512,7 @@ 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,RsIdentityUsage(mServType,RsIdentityUsage::GROUP_AUTHOR_SIGNATURE_CREATION,grpMeta.mGroupId)) ; + mGixs->timeStampKey(grpMeta.mAuthorId,RsIdentityUsage(RsServiceType(mServType),RsIdentityUsage::GROUP_AUTHOR_SIGNATURE_CREATION,grpMeta.mGroupId)) ; signSet.keySignSet[INDEX_AUTHEN_IDENTITY] = sign; } else @@ -680,7 +680,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,RsIdentityUsage(mServType,RsIdentityUsage::MESSAGE_AUTHOR_SIGNATURE_CREATION,msgMeta.mGroupId,msgMeta.mMsgId)) ; + mGixs->timeStampKey(msgMeta.mAuthorId,RsIdentityUsage(RsServiceType(mServType),RsIdentityUsage::MESSAGE_AUTHOR_SIGNATURE_CREATION,msgMeta.mGroupId,msgMeta.mMsgId,msgMeta.mParentId,msgMeta.mThreadId)) ; signSet.keySignSet[INDEX_AUTHEN_IDENTITY] = sign; } else @@ -905,7 +905,11 @@ 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,RsIdentityUsage(mServType,RsIdentityUsage::MESSAGE_AUTHOR_SIGNATURE_VALIDATION,metaData.mGroupId,metaData.mMsgId)) ; + mGixs->timeStampKey(metaData.mAuthorId,RsIdentityUsage(RsServiceType(mServType),RsIdentityUsage::MESSAGE_AUTHOR_SIGNATURE_VALIDATION, + metaData.mGroupId, + metaData.mMsgId, + metaData.mParentId, + metaData.mThreadId)) ; } else { @@ -949,7 +953,12 @@ int RsGenExchange::validateMsg(RsNxsMsg *msg, const uint32_t& grpFlag, const uin { std::list peers; peers.push_back(msg->PeerId()); - mGixs->requestKey(metaData.mAuthorId, peers, RsIdentityUsage(serviceType(),RsIdentityUsage::MESSAGE_AUTHOR_SIGNATURE_VALIDATION,metaData.mGroupId,metaData.mMsgId)); + mGixs->requestKey(metaData.mAuthorId, peers, RsIdentityUsage((RsServiceType)serviceType(), + RsIdentityUsage::MESSAGE_AUTHOR_SIGNATURE_VALIDATION, + metaData.mGroupId, + metaData.mMsgId, + metaData.mParentId, + metaData.mThreadId)); #ifdef GEN_EXCH_DEBUG std::cerr << ", Key missing. Retry later." << std::endl; @@ -1026,7 +1035,7 @@ int RsGenExchange::validateGrp(RsNxsGrp* grp) #ifdef GEN_EXCH_DEBUG std::cerr << " key ID validation result: " << idValidate << std::endl; #endif - mGixs->timeStampKey(metaData.mAuthorId,RsIdentityUsage(mServType,RsIdentityUsage::GROUP_AUTHOR_SIGNATURE_VALIDATION,metaData.mGroupId)); + mGixs->timeStampKey(metaData.mAuthorId,RsIdentityUsage(RsServiceType(mServType),RsIdentityUsage::GROUP_AUTHOR_SIGNATURE_VALIDATION,metaData.mGroupId)); } else { @@ -1044,7 +1053,7 @@ int RsGenExchange::validateGrp(RsNxsGrp* grp) #endif std::list peers; peers.push_back(grp->PeerId()); - mGixs->requestKey(metaData.mAuthorId, peers,RsIdentityUsage(mServType,RsIdentityUsage::GROUP_AUTHOR_SIGNATURE_VALIDATION,metaData.mGroupId)); + mGixs->requestKey(metaData.mAuthorId, peers,RsIdentityUsage(RsServiceType(mServType),RsIdentityUsage::GROUP_AUTHOR_SIGNATURE_VALIDATION,metaData.mGroupId)); return VALIDATE_FAIL_TRY_LATER; } } @@ -1274,18 +1283,18 @@ bool RsGenExchange::getMsgMeta(const uint32_t &token, for(; mit != result.end(); ++mit) { - std::vector& metaV = mit->second; + std::vector& metaV = mit->second; std::vector& msgInfoV = msgInfo[mit->first]; - std::vector::iterator vit = metaV.begin(); + std::vector::iterator vit = metaV.begin(); RsMsgMetaData meta; for(; vit != metaV.end(); ++vit) { - RsGxsMsgMetaData& m = *(*vit); + const RsGxsMsgMetaData& m = *(*vit); meta = m; msgInfoV.push_back(meta); - delete *vit; + //delete *vit; } metaV.clear(); } @@ -1302,18 +1311,18 @@ bool RsGenExchange::getMsgRelatedMeta(const uint32_t &token, GxsMsgRelatedMetaMa for(; mit != result.end(); ++mit) { - std::vector& metaV = mit->second; + std::vector& metaV = mit->second; std::vector& msgInfoV = msgMeta[mit->first]; - std::vector::iterator vit = metaV.begin(); + std::vector::iterator vit = metaV.begin(); RsMsgMetaData meta; for(; vit != metaV.end(); ++vit) { - RsGxsMsgMetaData& m = *(*vit); + const RsGxsMsgMetaData& m = *(*vit); meta = m; msgInfoV.push_back(meta); - delete *vit; + //delete *vit; } metaV.clear(); } @@ -1675,13 +1684,7 @@ void RsGenExchange::receiveNewMessages(std::vector& messages) void RsGenExchange::receiveDistantSearchResults(TurtleRequestId id,const RsGxsGroupId &grpId) { - std::cerr << __PRETTY_FUNCTION__ << " received result for request " - << std::hex << id << std::dec << std::endl; - - RS_STACK_MUTEX(mGenMtx); - - RsGxsDistantSearchResultChange* gc = new RsGxsDistantSearchResultChange(id,grpId); - mNotifications.push_back(gc); + std::cerr << __PRETTY_FUNCTION__ << " received result for request " << std::hex << id << std::dec << ": this method should be overloaded in the client service, but it is not. This is a bug!" << std::endl; } void RsGenExchange::notifyReceivePublishKey(const RsGxsGroupId &grpId) @@ -2016,15 +2019,15 @@ void RsGenExchange::processMsgMetaChanges() if(mit != result.end()) { - std::vector& msgMetaV = mit->second; + std::vector& msgMetaV = mit->second; if(!msgMetaV.empty()) { - RsGxsMsgMetaData* meta = *(msgMetaV.begin()); + const RsGxsMsgMetaData* meta = *(msgMetaV.begin()); value = (meta->mMsgStatus & ~mask) | (mask & value); changed = (static_cast(meta->mMsgStatus) != value); m.val.put(RsGeneralDataService::MSG_META_STATUS, value); - delete meta; + //delete meta; ok = true; } } @@ -3352,7 +3355,7 @@ bool RsGenExchange::updateValid(const RsGxsGrpMetaData& oldGrpMeta, const RsNxsG // also check this is the latest published group bool latest = newGrp.metaData->mPublishTs > oldGrpMeta.mPublishTs; - mGixs->timeStampKey(newGrp.metaData->mAuthorId, RsIdentityUsage(mServType,RsIdentityUsage::GROUP_ADMIN_SIGNATURE_CREATION, oldGrpMeta.mGroupId)) ; + mGixs->timeStampKey(newGrp.metaData->mAuthorId, RsIdentityUsage(RsServiceType(mServType),RsIdentityUsage::GROUP_ADMIN_SIGNATURE_CREATION, oldGrpMeta.mGroupId)) ; return GxsSecurity::validateNxsGrp(newGrp, adminSign, keyMit->second) && latest; } diff --git a/libretroshare/src/gxs/rsgxs.h b/libretroshare/src/gxs/rsgxs.h index 3f25b087b..fd7f693f9 100644 --- a/libretroshare/src/gxs/rsgxs.h +++ b/libretroshare/src/gxs/rsgxs.h @@ -29,8 +29,8 @@ /* data types used throughout Gxs from netservice to genexchange */ -typedef std::map > GxsMsgMetaResult; -typedef std::map > MsgRelatedMetaResult; +typedef std::map > GxsMsgMetaResult; +typedef std::map > MsgRelatedMetaResult; // Default values that are used throughout GXS code diff --git a/libretroshare/src/gxs/rsgxsdata.cc b/libretroshare/src/gxs/rsgxsdata.cc index 0ec61de8e..c3c9b9f08 100644 --- a/libretroshare/src/gxs/rsgxsdata.cc +++ b/libretroshare/src/gxs/rsgxsdata.cc @@ -209,7 +209,7 @@ RsGxsMsgMetaData::~RsGxsMsgMetaData(){ return; } -uint32_t RsGxsMsgMetaData::serial_size() +uint32_t RsGxsMsgMetaData::serial_size() const { uint32_t s = 8; // header size diff --git a/libretroshare/src/gxs/rsgxsdata.h b/libretroshare/src/gxs/rsgxsdata.h index 282f063fb..2102a8254 100644 --- a/libretroshare/src/gxs/rsgxsdata.h +++ b/libretroshare/src/gxs/rsgxsdata.h @@ -48,6 +48,7 @@ public: bool deserialise(void *data, uint32_t &pktsize); bool serialise(void* data, uint32_t &pktsize, uint32_t api_version); uint32_t serial_size(uint32_t api_version) const; + uint32_t serial_size() const { return serial_size(RS_GXS_GRP_META_DATA_CURRENT_API_VERSION); } void clear(); void operator =(const RsGroupMetaData& rMeta); @@ -94,7 +95,7 @@ public: ~RsGxsMsgMetaData(); bool deserialise(void *data, uint32_t *size); bool serialise(void* data, uint32_t *size); - uint32_t serial_size(); + uint32_t serial_size() const; void clear(); void operator =(const RsMsgMetaData& rMeta); diff --git a/libretroshare/src/gxs/rsgxsdataaccess.cc b/libretroshare/src/gxs/rsgxsdataaccess.cc index 6104b0b56..a695672f9 100644 --- a/libretroshare/src/gxs/rsgxsdataaccess.cc +++ b/libretroshare/src/gxs/rsgxsdataaccess.cc @@ -1028,7 +1028,7 @@ bool RsGxsDataAccess::getMsgMetaDataList( const GxsMsgReq& msgIds, const RsTokRe //auto& filter( metaFilter[grpId] ); // does the initialization of metaFilter[grpId] and avoids further O(log(n)) calls - std::vector& metaV = meta_it->second; + std::vector& metaV = meta_it->second; if (onlyLatestMsgs) // if we only consider latest messages, we need to first filter out messages with "children" { @@ -1062,7 +1062,7 @@ bool RsGxsDataAccess::getMsgMetaDataList( const GxsMsgReq& msgIds, const RsTokRe for(uint32_t i=0;imParentId.isNull()) { - delete msgMeta; + //delete msgMeta; metaV[i] = nullptr; continue; } if (onlyOrigMsgs && !msgMeta->mOrigMsgId.isNull() && msgMeta->mMsgId != msgMeta->mOrigMsgId) { - delete msgMeta; + //delete msgMeta; metaV[i] = nullptr; continue; } @@ -1187,7 +1187,7 @@ bool RsGxsDataAccess::getMsgIdList( const GxsMsgReq& msgIds, const RsTokReqOptio } // delete meta data - cleanseMsgMetaMap(result); + //cleanseMsgMetaMap(result); return true; } @@ -1296,9 +1296,7 @@ bool RsGxsDataAccess::getMsgRelatedInfo(MsgRelatedInfoReq *req) return true; } - std::vector::iterator vit_msgIds = req->mMsgIds.begin(); - - for(; vit_msgIds != req->mMsgIds.end(); ++vit_msgIds) + for(auto vit_msgIds(req->mMsgIds.begin()); vit_msgIds != req->mMsgIds.end(); ++vit_msgIds) { MsgMetaFilter filterMap; @@ -1310,8 +1308,8 @@ bool RsGxsDataAccess::getMsgRelatedInfo(MsgRelatedInfoReq *req) GxsMsgReq msgIds; msgIds.insert(std::make_pair(grpMsgIdPair.first, std::set())); mDataStore->retrieveGxsMsgMetaData(msgIds, result); - std::vector& metaV = result[grpMsgIdPair.first]; - std::vector::iterator vit_meta; + std::vector& metaV = result[grpMsgIdPair.first]; + std::vector::iterator vit_meta; // msg id to relate to const RsGxsMessageId& msgId = grpMsgIdPair.second; @@ -1319,10 +1317,11 @@ bool RsGxsDataAccess::getMsgRelatedInfo(MsgRelatedInfoReq *req) std::set outMsgIds; - RsGxsMsgMetaData* origMeta = nullptr; + const RsGxsMsgMetaData* origMeta = nullptr; + for(vit_meta = metaV.begin(); vit_meta != metaV.end(); ++vit_meta) { - RsGxsMsgMetaData* meta = *vit_meta; + const RsGxsMsgMetaData* meta = *vit_meta; if(msgId == meta->mMsgId) { @@ -1337,12 +1336,11 @@ bool RsGxsDataAccess::getMsgRelatedInfo(MsgRelatedInfoReq *req) RsDbg() << "RsGxsDataAccess::getMsgRelatedInfo(): Cannot find meta of msgId (to relate to)!" << std::endl; #endif - cleanseMsgMetaMap(result); return false; } const RsGxsMessageId& origMsgId = origMeta->mOrigMsgId; - std::map& metaMap = filterMap[grpId]; + std::map& metaMap = filterMap[grpId]; if (onlyLatestMsgs) { @@ -1354,7 +1352,7 @@ bool RsGxsDataAccess::getMsgRelatedInfo(MsgRelatedInfoReq *req) for(vit_meta = metaV.begin(); vit_meta != metaV.end(); ++vit_meta) { - RsGxsMsgMetaData* meta = *vit_meta; + const RsGxsMsgMetaData* meta = *vit_meta; // skip msgs that aren't children. if (onlyChildMsgs) @@ -1422,11 +1420,11 @@ bool RsGxsDataAccess::getMsgRelatedInfo(MsgRelatedInfoReq *req) /* first guess is potentially better than Orig (can't be worse!) */ rstime_t latestTs = 0; RsGxsMessageId latestMsgId; - RsGxsMsgMetaData* latestMeta=nullptr; + const RsGxsMsgMetaData* latestMeta=nullptr; for(vit_meta = metaV.begin(); vit_meta != metaV.end(); ++vit_meta) { - RsGxsMsgMetaData* meta = *vit_meta; + const RsGxsMsgMetaData* meta = *vit_meta; if (meta->mOrigMsgId == origMsgId) { @@ -1446,7 +1444,7 @@ bool RsGxsDataAccess::getMsgRelatedInfo(MsgRelatedInfoReq *req) { for(vit_meta = metaV.begin(); vit_meta != metaV.end(); ++vit_meta) { - RsGxsMsgMetaData* meta = *vit_meta; + const RsGxsMsgMetaData* meta = *vit_meta; if (meta->mOrigMsgId == origMsgId) { @@ -1482,8 +1480,6 @@ bool RsGxsDataAccess::getMsgRelatedInfo(MsgRelatedInfoReq *req) outMsgIds.clear(); filteredOutMsgIds.clear(); - - cleanseMsgMetaMap(result); } return true; } @@ -1496,7 +1492,7 @@ bool RsGxsDataAccess::getGroupStatistic(GroupStatisticRequest *req) GxsMsgMetaResult metaResult; mDataStore->retrieveGxsMsgMetaData(metaReq, metaResult); - const std::vector& msgMetaV = metaResult[req->mGrpId]; + const std::vector& msgMetaV = metaResult[req->mGrpId]; req->mGroupStatistic.mGrpId = req->mGrpId; req->mGroupStatistic.mNumMsgs = msgMetaV.size(); @@ -1514,7 +1510,7 @@ bool RsGxsDataAccess::getGroupStatistic(GroupStatisticRequest *req) for(uint32_t i = 0; i < msgMetaV.size(); ++i) { - RsGxsMsgMetaData* m = msgMetaV[i]; + const RsGxsMsgMetaData* m = msgMetaV[i]; req->mGroupStatistic.mTotalSizeOfMsgs += m->mMsgSize + m->serial_size(); if(obsolete_msgs.find(m->mMsgId) != obsolete_msgs.end()) // skip obsolete messages. @@ -1540,7 +1536,7 @@ bool RsGxsDataAccess::getGroupStatistic(GroupStatisticRequest *req) } } - cleanseMsgMetaMap(metaResult); + //cleanseMsgMetaMap(metaResult); return true; } @@ -1595,21 +1591,19 @@ bool RsGxsDataAccess::getMsgIdList(MsgIdReq* req) mDataStore->retrieveGxsMsgMetaData(req->mMsgIds, result); - GxsMsgMetaResult::iterator mit = result.begin(), mit_end = result.end(); for(; mit != mit_end; ++mit) { const RsGxsGroupId grpId = mit->first; - std::vector& metaV = mit->second; - std::vector::iterator vit = metaV.begin(), + std::vector& metaV = mit->second; + std::vector::iterator vit = metaV.begin(), vit_end = metaV.end(); for(; vit != vit_end; ++vit) { - RsGxsMsgMetaData* meta = *vit; + const RsGxsMsgMetaData* meta = *vit; req->mMsgIdResult[grpId].insert(meta->mMsgId); - delete meta; // discard meta data mem } } @@ -1622,24 +1616,24 @@ bool RsGxsDataAccess::getMsgIdList(MsgIdReq* req) return true; } -void RsGxsDataAccess::cleanseMsgMetaMap(GxsMsgMetaResult& result) -{ - GxsMsgMetaResult::iterator mit = result.begin(); - - for(; mit !=result.end(); ++mit) - { - - std::vector& msgMetaV = mit->second; - std::vector::iterator vit = msgMetaV.begin(); - for(; vit != msgMetaV.end(); ++vit) - { - delete *vit; - } - } - - result.clear(); - return; -} +// void RsGxsDataAccess::cleanseMsgMetaMap(GxsMsgMetaResult& result) +// { +// GxsMsgMetaResult::iterator mit = result.begin(); +// +// for(; mit !=result.end(); ++mit) +// { +// +// std::vector& msgMetaV = mit->second; +// std::vector::iterator vit = msgMetaV.begin(); +// for(; vit != msgMetaV.end(); ++vit) +// { +// delete *vit; +// } +// } +// +// result.clear(); +// return; +// } void RsGxsDataAccess::filterMsgIdList( GxsMsgIdResult& resultsMap, const RsTokReqOptions& opts, const MsgMetaFilter& msgMetas ) const { @@ -1659,11 +1653,11 @@ void RsGxsDataAccess::filterMsgIdList( GxsMsgIdResult& resultsMap, const RsTokRe for( std::set::iterator msgIdIt = msgsIdSet.begin(); msgIdIt != msgsIdSet.end(); ) { const RsGxsMessageId& msgId(*msgIdIt); - const std::map& msgsMetaMap = + const std::map& msgsMetaMap = cit->second; bool keep = false; - std::map::const_iterator msgsMetaMapIt; + std::map::const_iterator msgsMetaMapIt; if( (msgsMetaMapIt = msgsMetaMap.find(msgId)) != msgsMetaMap.end() ) { diff --git a/libretroshare/src/gxs/rsgxsdataaccess.h b/libretroshare/src/gxs/rsgxsdataaccess.h index 21cb89b24..727ebe605 100644 --- a/libretroshare/src/gxs/rsgxsdataaccess.h +++ b/libretroshare/src/gxs/rsgxsdataaccess.h @@ -28,7 +28,7 @@ #include "rsgds.h" -typedef std::map< RsGxsGroupId, std::map > MsgMetaFilter; +typedef std::map< RsGxsGroupId, std::map > MsgMetaFilter; typedef std::map< RsGxsGroupId, RsGxsGrpMetaData* > GrpMetaFilter; bool operator<(const std::pair& p1,const std::pair& p2); @@ -328,11 +328,11 @@ private: */ void tokenList(std::list &tokens); - /*! - * Convenience function to delete the ids - * @param filter the meta filter to clean - */ - void cleanseMsgMetaMap(GxsMsgMetaResult& result); + // /*! + // * Convenience function to delete the ids + // * @param filter the meta filter to clean + // */ + // void cleanseMsgMetaMap(GxsMsgMetaResult& result); public: diff --git a/libretroshare/src/gxs/rsgxsnetservice.cc b/libretroshare/src/gxs/rsgxsnetservice.cc index 3259a060c..8ed657bfb 100644 --- a/libretroshare/src/gxs/rsgxsnetservice.cc +++ b/libretroshare/src/gxs/rsgxsnetservice.cc @@ -272,6 +272,7 @@ NXS_NET_DEBUG_6 group sync statistics (e.g. number of posts at nighbour nodes, etc) NXS_NET_DEBUG_7 encryption/decryption of transactions NXS_NET_DEBUG_8 gxs distant sync + NXS_NET_DEBUG_9 gxs distant search ***/ //#define NXS_NET_DEBUG_0 1 @@ -283,6 +284,7 @@ //#define NXS_NET_DEBUG_6 1 //#define NXS_NET_DEBUG_7 1 //#define NXS_NET_DEBUG_8 1 +//#define NXS_NET_DEBUG_9 1 //#define NXS_FRAG @@ -306,6 +308,7 @@ static const uint32_t GROUP_STATS_UPDATE_DELAY = 240; // static const uint32_t GROUP_STATS_UPDATE_NB_PEERS = 2; // number of peers to which the group stats are asked static const uint32_t MAX_ALLOWED_GXS_MESSAGE_SIZE = 199000; // 200,000 bytes including signature and headers static const uint32_t MIN_DELAY_BETWEEN_GROUP_SEARCH = 40; // dont search same group more than every 40 secs. +static const uint32_t SAFETY_DELAY_FOR_UNSUCCESSFUL_UPDATE = 1800; // avoid re-sending the same msg list to a peer who asks twice for the same update in less than this time static const uint32_t RS_NXS_ITEM_ENCRYPTION_STATUS_UNKNOWN = 0x00 ; static const uint32_t RS_NXS_ITEM_ENCRYPTION_STATUS_NO_ERROR = 0x01 ; @@ -318,11 +321,11 @@ static const uint32_t RS_NXS_ITEM_ENCRYPTION_STATUS_GXS_KEY_MISSING = 0x05 ; #if defined(NXS_NET_DEBUG_0) || defined(NXS_NET_DEBUG_1) || defined(NXS_NET_DEBUG_2) || defined(NXS_NET_DEBUG_3) \ || defined(NXS_NET_DEBUG_4) || defined(NXS_NET_DEBUG_5) || defined(NXS_NET_DEBUG_6) || defined(NXS_NET_DEBUG_7) \ - || defined(NXS_NET_DEBUG_8) + || defined(NXS_NET_DEBUG_8) || defined(NXS_NET_DEBUG_9) static const RsPeerId peer_to_print = RsPeerId();//std::string("a97fef0e2dc82ddb19200fb30f9ac575")) ; -static const RsGxsGroupId group_id_to_print = RsGxsGroupId(std::string("66052380f5d1d0c5992e2b55dc402ce6")) ; // use this to allow to this group id only, or "" for all IDs -static const uint32_t service_to_print = RS_SERVICE_GXS_TYPE_GXSCIRCLE; // use this to allow to this service id only, or 0 for all services +static const RsGxsGroupId group_id_to_print = RsGxsGroupId();//std::string("66052380f5d1d0c5992e2b55dc402ce6")) ; // use this to allow to this group id only, or "" for all IDs +static const uint32_t service_to_print = RS_SERVICE_GXS_TYPE_GXSID; // use this to allow to this service id only, or 0 for all services // warning. Numbers should be SERVICE IDS (see serialiser/rsserviceids.h. E.g. 0x0215 for forums) class nullstream: public std::ostream {}; @@ -590,32 +593,30 @@ void RsGxsNetService::syncWithPeers() return; } - std::set::iterator sit = peers.begin(); + // for now just grps + for(auto sit = peers.begin(); sit != peers.end(); ++sit) + { - // for now just grps - for(; sit != peers.end(); ++sit) - { + const RsPeerId peerId = *sit; - const RsPeerId peerId = *sit; + ClientGrpMap::const_iterator cit = mClientGrpUpdateMap.find(peerId); + uint32_t updateTS = 0; - ClientGrpMap::const_iterator cit = mClientGrpUpdateMap.find(peerId); - uint32_t updateTS = 0; - - if(cit != mClientGrpUpdateMap.end()) - { - const RsGxsGrpUpdate *gui = &cit->second; - updateTS = gui->grpUpdateTS; - } - RsNxsSyncGrpReqItem *grp = new RsNxsSyncGrpReqItem(mServType); - grp->clear(); - grp->PeerId(*sit); - grp->updateTS = updateTS; + if(cit != mClientGrpUpdateMap.end()) + { + const RsGxsGrpUpdate *gui = &cit->second; + updateTS = gui->grpUpdateTS; + } + RsNxsSyncGrpReqItem *grp = new RsNxsSyncGrpReqItem(mServType); + grp->clear(); + grp->PeerId(*sit); + grp->updateTS = updateTS; #ifdef NXS_NET_DEBUG_5 - GXSNETDEBUG_P_(*sit) << "Service "<< std::hex << ((mServiceInfo.mServiceType >> 8)& 0xffff) << std::dec << " sending global group TS of peer id: " << *sit << " ts=" << nice_time_stamp(time(NULL),updateTS) << " (secs ago) to himself" << std::endl; + GXSNETDEBUG_P_(*sit) << "Service "<< std::hex << ((mServiceInfo.mServiceType >> 8)& 0xffff) << std::dec << " sending global group TS of peer id: " << *sit << " ts=" << nice_time_stamp(time(NULL),updateTS) << " (secs ago) to himself" << std::endl; #endif - generic_sendItem(grp); - } + generic_sendItem(grp); + } if(!mAllowMsgSync) return ; @@ -641,15 +642,13 @@ void RsGxsNetService::syncWithPeers() } } - sit = peers.begin(); - // Synchronise group msg for groups which we're subscribed to // For each peer and each group, we send to the peer the time stamp of the most // recent modification the peer has sent. If the peer has more recent messages he will send them, because its latest // modifications will be more recent. This ensures that we always compare timestamps all taken in the same // computer (the peer's computer in this case) - for(; sit != peers.end(); ++sit) + for(auto sit = peers.begin(); sit != peers.end(); ++sit) { const RsPeerId& peerId = *sit; @@ -953,7 +952,7 @@ void RsGxsNetService::handleRecvSyncGrpStatistics(RsNxsSyncGrpStatsItem *grs) #endif mDataStore->retrieveGxsMsgMetaData(reqIds, result); - const std::vector& vec(result[grs->grpId]) ; + const std::vector& vec(result[grs->grpId]) ; if(vec.empty()) // that means we don't have any, or there isn't any, but since the default is always 0, no need to send. return ; @@ -970,12 +969,9 @@ void RsGxsNetService::handleRecvSyncGrpStatistics(RsNxsSyncGrpStatsItem *grs) // be used to discard groups that are not used. for(uint32_t i=0;ilast_post_TS < vec[i]->mPublishTs) grs_resp->last_post_TS = vec[i]->mPublishTs; - delete vec[i] ; - } #ifdef NXS_NET_DEBUG_6 GXSNETDEBUG_PG(grs->PeerId(),grs->grpId) << " sending back statistics item with " << vec.size() << " elements." << std::endl; #endif @@ -2953,21 +2949,19 @@ void RsGxsNetService::locked_genReqMsgTransaction(NxsTransaction* tr) reqIds[grpId] = std::set(); GxsMsgMetaResult result; mDataStore->retrieveGxsMsgMetaData(reqIds, result); - std::vector &msgMetaV = result[grpId]; + std::vector &msgMetaV = result[grpId]; #ifdef NXS_NET_DEBUG_1 GXSNETDEBUG_PG(item->PeerId(),grpId) << " retrieving grp message list..." << std::endl; GXSNETDEBUG_PG(item->PeerId(),grpId) << " grp locally contains " << msgMetaV.size() << " messsages." << std::endl; #endif - std::vector::const_iterator vit = msgMetaV.begin(); + std::vector::const_iterator vit = msgMetaV.begin(); std::set msgIdSet; // put ids in set for each searching for(; vit != msgMetaV.end(); ++vit) - { msgIdSet.insert((*vit)->mMsgId); - delete(*vit); - } + msgMetaV.clear(); #ifdef NXS_NET_DEBUG_1 @@ -3185,7 +3179,8 @@ void RsGxsNetService::locked_genReqGrpTransaction(NxsTransaction* tr) { grpItemL.push_back(item); grpMetaMap[item->grpId] = NULL; - }else + } + else { #ifdef NXS_NET_DEBUG_0 GXSNETDEBUG_PG(tr->mTransaction->PeerId(),item->grpId) << "RsGxsNetService::genReqGrpTransaction(): item failed to caste to RsNxsSyncMsgItem* " << std::endl; @@ -3228,7 +3223,7 @@ void RsGxsNetService::locked_genReqGrpTransaction(NxsTransaction* tr) RsNxsSyncGrpItem*& grpSyncItem = *llit; const RsGxsGroupId& grpId = grpSyncItem->grpId; - std::map::const_iterator metaIter = grpMetaMap.find(grpId); + std::map::const_iterator metaIter = grpMetaMap.find(grpId); bool haveItem = false; bool latestVersion = false; @@ -3239,19 +3234,21 @@ void RsGxsNetService::locked_genReqGrpTransaction(NxsTransaction* tr) } // FIXTESTS global variable rsReputations not available in unittests! -#warning csoler 2016-12-23: Update the code below to correctly send/recv dependign on reputation - if( !grpSyncItem->authorId.isNull() && - mReputations->overallReputationLevel(grpSyncItem->authorId) == - RsReputationLevel::LOCALLY_NEGATIVE ) + if( mReputations->overallReputationLevel(RsGxsId(grpSyncItem->grpId)) == RsReputationLevel::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->grpId << " is banned. Not GXS-syncing group." << std::endl; #endif continue ; } if( (mGrpAutoSync && !haveItem) || latestVersion) + { +#ifdef NXS_NET_DEBUG_0 + GXSNETDEBUG_PG(tr->mTransaction->PeerId(),grpId) << " Identity " << grpId << " will be sync-ed using GXS. mGrpAutoSync:" << mGrpAutoSync << " haveItem:" << haveItem << " latest_version: " << latestVersion << std::endl; +#endif addGroupItemToList(tr, grpId, transN, reqList); + } } if(!reqList.empty()) @@ -3998,7 +3995,7 @@ bool RsGxsNetService::locked_CanReceiveUpdate(const RsNxsSyncGrpReqItem *item) GXSNETDEBUG_P_(item->PeerId()) << " local modification time stamp: " << std::dec<< time(NULL) - mGrpServerUpdate.grpUpdateTS << " secs ago. Update sent: " << ((item->updateTS < mGrpServerUpdate.grpUpdateTS)?"YES":"NO") << std::endl; #endif - return item->updateTS < mGrpServerUpdate.grpUpdateTS; + return item->updateTS < mGrpServerUpdate.grpUpdateTS && locked_checkResendingOfUpdates(item->PeerId(),RsGxsGroupId(),item->updateTS,mGrpServerUpdate.grpUpdateTsRecords[item->PeerId()]) ; } void RsGxsNetService::handleRecvSyncGroup(RsNxsSyncGrpReqItem *item) @@ -4243,10 +4240,33 @@ bool RsGxsNetService::checkCanRecvMsgFromPeer(const RsPeerId& sslId, const RsGxs return true; } +bool RsGxsNetService::locked_checkResendingOfUpdates(const RsPeerId& pid,const RsGxsGroupId& grpId,rstime_t incoming_ts,RsPeerUpdateTsRecord& rec) +{ + rstime_t now = time(NULL); + + // Now we check if the peer is sending the same outdated TS for the same time in a short while. This would mean the peer + // hasn't finished processing the updates we're sending and we shouldn't send new data anymore. Of course the peer might + // have disconnected or so, which means that we need to be careful about not sending. As a compromise we still send, but + // after waiting for a while (See + + if(rec.mLastTsReceived == incoming_ts && rec.mTs + SAFETY_DELAY_FOR_UNSUCCESSFUL_UPDATE > now) + { +#ifdef NXS_NET_DEBUG_0 + GXSNETDEBUG_PG(pid,grpId) << "(II) peer " << pid << " already sent the same TS " << (long int)now-(long int)rec.mTs << " secs ago for that group ID. Will not send msg list again for a while to prevent clogging..." << std::endl; +#endif + return false; + } + + rec.mLastTsReceived = incoming_ts; + rec.mTs = now; + + return true; +} + bool RsGxsNetService::locked_CanReceiveUpdate(RsNxsSyncMsgReqItem *item,bool& grp_is_known) { // Do we have new updates for this peer? - // Here we compare times in the same clock: the friend's clock, so it should be fine. + // Here we compare times in the same clock: our own clock, so it should be fine. grp_is_known = false ; @@ -4255,7 +4275,7 @@ bool RsGxsNetService::locked_CanReceiveUpdate(RsNxsSyncMsgReqItem *item,bool& gr // Item contains the hashed group ID in order to protect is from friends who don't know it. So we de-hash it using bruteforce over known group IDs for this peer. // We could save the de-hash result. But the cost is quite light, since the number of encrypted groups per service is usually low. - for(ServerMsgMap::const_iterator it(mServerMsgUpdateMap.begin());it!=mServerMsgUpdateMap.end();++it) + for(ServerMsgMap::iterator it(mServerMsgUpdateMap.begin());it!=mServerMsgUpdateMap.end();++it) if(item->grpId == hashGrpId(it->first,item->PeerId())) { item->grpId = it->first ; @@ -4265,20 +4285,25 @@ bool RsGxsNetService::locked_CanReceiveUpdate(RsNxsSyncMsgReqItem *item,bool& gr #endif grp_is_known = true ; - return item->updateTS < it->second.msgUpdateTS ; + // The order of tests below is important because we want to only modify the map of requests records if the request actually is a valid requests instead of + // a simple check that nothing's changed. + + return item->updateTS < it->second.msgUpdateTS && locked_checkResendingOfUpdates(item->PeerId(),item->grpId,item->updateTS,it->second.msgUpdateTsRecords[item->PeerId()]) ; } return false ; } - ServerMsgMap::const_iterator cit = mServerMsgUpdateMap.find(item->grpId); + ServerMsgMap::iterator cit = mServerMsgUpdateMap.find(item->grpId); + if(cit != mServerMsgUpdateMap.end()) { #ifdef NXS_NET_DEBUG_0 GXSNETDEBUG_PG(item->PeerId(),item->grpId) << " local time stamp: " << std::dec<< time(NULL) - cit->second.msgUpdateTS << " secs ago. Update sent: " << (item->updateTS < cit->second.msgUpdateTS) << std::endl; #endif grp_is_known = true ; - return item->updateTS < cit->second.msgUpdateTS ; + + return item->updateTS < cit->second.msgUpdateTS && locked_checkResendingOfUpdates(item->PeerId(),item->grpId,item->updateTS,cit->second.msgUpdateTsRecords[item->PeerId()]) ; } #ifdef NXS_NET_DEBUG_0 @@ -4299,6 +4324,9 @@ void RsGxsNetService::handleRecvSyncMessage(RsNxsSyncMsgReqItem *item,bool item_ bool grp_is_known = false; bool was_circle_protected = item_was_encrypted || bool(item->flag & RsNxsSyncMsgReqItem::FLAG_USE_HASHED_GROUP_ID); + // This call determines if the peer can receive updates from us, meaning that our last TS is larger than what the peer sent. + // It also changes the items' group id into the un-hashed group ID if the group is a distant group. + bool peer_can_receive_update = locked_CanReceiveUpdate(item, grp_is_known); if(item_was_encrypted) @@ -4314,7 +4342,7 @@ void RsGxsNetService::handleRecvSyncMessage(RsNxsSyncMsgReqItem *item,bool item_ // We update suppliers in two cases: // Case 1: the grp is known because it is the hash of an existing group, but it's not yet in the server config map - // Case 2: the gtp is not known, possibly because it was deleted, but there's an entry in mServerGrpConfigMap due to statistics gathering. Still, statistics are only + // Case 2: the grp is not known, possibly because it was deleted, but there's an entry in mServerGrpConfigMap due to statistics gathering. Still, statistics are only // gathered from known suppliers. So statistics never add new suppliers. These are only added here. if(grp_is_known || mServerGrpConfigMap.find(item->grpId)!=mServerGrpConfigMap.end()) @@ -4367,7 +4395,7 @@ void RsGxsNetService::handleRecvSyncMessage(RsNxsSyncMsgReqItem *item,bool item_ GxsMsgMetaResult metaResult; mDataStore->retrieveGxsMsgMetaData(req, metaResult); - std::vector& msgMetas = metaResult[item->grpId]; + std::vector& msgMetas = metaResult[item->grpId]; #ifdef NXS_NET_DEBUG_0 GXSNETDEBUG_PG(item->PeerId(),item->grpId) << " retrieving message meta data." << std::endl; @@ -4395,9 +4423,9 @@ void RsGxsNetService::handleRecvSyncMessage(RsNxsSyncMsgReqItem *item,bool item_ if(canSendMsgIds(msgMetas, *grpMeta, peer, should_encrypt_to_this_circle_id)) { - for(std::vector::iterator vit = msgMetas.begin();vit != msgMetas.end(); ++vit) + for(auto vit = msgMetas.begin();vit != msgMetas.end(); ++vit) { - RsGxsMsgMetaData* m = *vit; + const RsGxsMsgMetaData* m = *vit; // Check reputation @@ -4497,8 +4525,8 @@ void RsGxsNetService::handleRecvSyncMessage(RsNxsSyncMsgReqItem *item,bool item_ // release meta resource - for(std::vector::iterator vit = msgMetas.begin(); vit != msgMetas.end(); ++vit) - delete *vit; + // for(std::vector::iterator vit = msgMetas.begin(); vit != msgMetas.end(); ++vit) + // delete *vit; } void RsGxsNetService::locked_pushMsgRespFromList(std::list& itemL, const RsPeerId& sslId, const RsGxsGroupId& grp_id,const uint32_t& transN) @@ -4542,7 +4570,7 @@ void RsGxsNetService::locked_pushMsgRespFromList(std::list& itemL, c } } -bool RsGxsNetService::canSendMsgIds(std::vector& msgMetas, const RsGxsGrpMetaData& grpMeta, const RsPeerId& sslId,RsGxsCircleId& should_encrypt_id) +bool RsGxsNetService::canSendMsgIds(std::vector& msgMetas, const RsGxsGrpMetaData& grpMeta, const RsPeerId& sslId,RsGxsCircleId& should_encrypt_id) { #ifdef NXS_NET_DEBUG_4 GXSNETDEBUG_PG(sslId,grpMeta.mGroupId) << "RsGxsNetService::canSendMsgIds() CIRCLE VETTING" << std::endl; @@ -4609,7 +4637,7 @@ bool RsGxsNetService::canSendMsgIds(std::vector& msgMetas, co GXSNETDEBUG_PG(sslId,grpMeta.mGroupId) << " deleting MsgMeta entry for msg ID " << msgMetas[i]->mMsgId << " signed by " << msgMetas[i]->mAuthorId << " who is not in group circle " << circleId << std::endl; #endif - delete msgMetas[i] ; + //delete msgMetas[i] ; msgMetas[i] = msgMetas[msgMetas.size()-1] ; msgMetas.pop_back() ; } @@ -5160,7 +5188,7 @@ static bool termSearch(const std::string& src, const std::string& substring) } #endif // ndef RS_DEEP_CHANNEL_INDEX -bool RsGxsNetService::retrieveDistantSearchResults(TurtleRequestId req,std::map& group_infos) +bool RsGxsNetService::retrieveDistantSearchResults(TurtleRequestId req,std::map& group_infos) { RS_STACK_MUTEX(mNxsMutex) ; @@ -5172,7 +5200,7 @@ bool RsGxsNetService::retrieveDistantSearchResults(TurtleRequestId req,std::map< group_infos = it->second; return true ; } -bool RsGxsNetService::retrieveDistantGroupSummary(const RsGxsGroupId& group_id,RsGxsGroupSummary& gs) +bool RsGxsNetService::retrieveDistantGroupSummary(const RsGxsGroupId& group_id,RsGxsGroupSearchResults& gs) { RS_STACK_MUTEX(mNxsMutex) ; for(auto it(mDistantSearchResults.begin());it!=mDistantSearchResults.end();++it) @@ -5194,8 +5222,7 @@ bool RsGxsNetService::clearDistantSearchResults(const TurtleRequestId& id) return true ; } -void RsGxsNetService::receiveTurtleSearchResults( - TurtleRequestId req, const std::list& group_infos ) +void RsGxsNetService::receiveTurtleSearchResults( TurtleRequestId req, const std::list& group_infos ) { std::set groupsToNotifyResults; @@ -5203,20 +5230,43 @@ void RsGxsNetService::receiveTurtleSearchResults( RS_STACK_MUTEX(mNxsMutex); RsGxsGrpMetaTemporaryMap grpMeta; - std::map& - search_results_map(mDistantSearchResults[req]); + std::map& search_results_map(mDistantSearchResults[req]); + +#ifdef NXS_NET_DEBUG_9 + std::cerr << "Received group summary through turtle search for the following groups:" << std::endl; +#endif for(const RsGxsGroupSummary& gps : group_infos) - if(search_results_map.find(gps.mGroupId) == search_results_map.end()) - grpMeta[gps.mGroupId] = nullptr; + { + std::cerr <<" " << gps.mGroupId << " \"" << gps.mGroupName << "\"" << std::endl; + grpMeta[gps.mGroupId] = nullptr; + } + mDataStore->retrieveGxsGrpMetaData(grpMeta); +#ifdef NXS_NET_DEBUG_9 + std::cerr << "Retrieved data store group data for the following groups:" <mGroupName << std::endl; +#endif + for (const RsGxsGroupSummary& gps : group_infos) { #ifndef RS_DEEP_CHANNEL_INDEX /* Only keep groups that are not locally known, and groups that are - * not already in the mDistantSearchResults structure. */ - if(grpMeta[gps.mGroupId]) continue; + * not already in the mDistantSearchResults structure. + * mDataStore may in some situations allocate an empty group meta data, so it's important + * to test that the group meta is both non null and actually corresponds to the group id we seek. */ + + auto& meta(grpMeta[gps.mGroupId]); + + if(meta != nullptr && meta->mGroupId == gps.mGroupId) + continue; + +#ifdef NXS_NET_DEBUG_9 + std::cerr << " group " << gps.mGroupId << " is not known. Adding it to search results..." << std::endl; +#endif + #else // ndef RS_DEEP_CHANNEL_INDEX /* When deep search is enabled search results may bring more info * then we already have also about post that are indexed by xapian, @@ -5225,22 +5275,32 @@ void RsGxsNetService::receiveTurtleSearchResults( const RsGxsGroupId& grpId(gps.mGroupId); groupsToNotifyResults.insert(grpId); - auto it2 = search_results_map.find(grpId); - if(it2 != search_results_map.end()) - { - // update existing data - RsGxsGroupSummary& eGpS(it2->second); - eGpS.mPopularity++; - eGpS.mNumberOfMessages = std::max( - eGpS.mNumberOfMessages, - gps.mNumberOfMessages ); - } - else - { - search_results_map[grpId] = gps; - // number of results so far - search_results_map[grpId].mPopularity = 1; - } + + // Find search results place for this particular group + +#ifdef NXS_NET_DEBUG_9 + std::cerr << " Adding gps=" << gps.mGroupId << " name=\"" << gps.mGroupName << "\" gps.mSearchContext=\"" << gps.mSearchContext << "\"" << std::endl; +#endif + RsGxsGroupSearchResults& eGpS(search_results_map[grpId]); + + if(eGpS.mGroupId != grpId) // not initialized yet. So we do it now. + { + eGpS.mGroupId = gps.mGroupId; + eGpS.mGroupName = gps.mGroupName; + eGpS.mAuthorId = gps.mAuthorId; + eGpS.mPublishTs = gps.mPublishTs; + eGpS.mSignFlags = gps.mSignFlags; + } + // We should check that the above values are always the same for all info that is received. In the end, we'll + // request the group meta and check the signature, but it may be misleading to receive a forged information + // that is not the real one. + + ++eGpS.mPopularity; // increase popularity. This is not a real counting, but therefore some heuristic estimate. + eGpS.mNumberOfMessages = std::max( eGpS.mNumberOfMessages, gps.mNumberOfMessages ); + eGpS.mLastMessageTs = std::max( eGpS.mLastMessageTs, gps.mLastMessageTs ); + + if(gps.mSearchContext != gps.mGroupName) // this is a bit of a hack. We should have flags to tell where the search hit happens + eGpS.mSearchContexts.insert(gps.mSearchContext); } } // end RS_STACK_MUTEX(mNxsMutex); diff --git a/libretroshare/src/gxs/rsgxsnetservice.h b/libretroshare/src/gxs/rsgxsnetservice.h index 39fd2982e..6b3e39d30 100644 --- a/libretroshare/src/gxs/rsgxsnetservice.h +++ b/libretroshare/src/gxs/rsgxsnetservice.h @@ -140,9 +140,9 @@ public: virtual void receiveTurtleSearchResults(TurtleRequestId req,const std::list& group_infos); virtual void receiveTurtleSearchResults(TurtleRequestId req,const unsigned char *encrypted_group_data,uint32_t encrypted_group_data_len); - virtual bool retrieveDistantSearchResults(TurtleRequestId req, std::map &group_infos); + virtual bool retrieveDistantSearchResults(TurtleRequestId req, std::map &group_infos); virtual bool clearDistantSearchResults(const TurtleRequestId& id); - virtual bool retrieveDistantGroupSummary(const RsGxsGroupId&,RsGxsGroupSummary&); + virtual bool retrieveDistantGroupSummary(const RsGxsGroupId&, RsGxsGroupSearchResults &); /*! * pauses synchronisation of subscribed groups and request for group id @@ -395,7 +395,7 @@ private: * @return false, if you cannot send to this peer, true otherwise */ bool canSendGrpId(const RsPeerId& sslId, const RsGxsGrpMetaData& grpMeta, std::vector& toVet, bool &should_encrypt); - bool canSendMsgIds(std::vector& msgMetas, const RsGxsGrpMetaData&, const RsPeerId& sslId, RsGxsCircleId &should_encrypt_id); + bool canSendMsgIds(std::vector& msgMetas, const RsGxsGrpMetaData&, const RsPeerId& sslId, RsGxsCircleId &should_encrypt_id); /*! * \brief checkPermissionsForFriendGroup @@ -439,6 +439,7 @@ private: bool locked_CanReceiveUpdate(const RsNxsSyncGrpReqItem *item); bool locked_CanReceiveUpdate(RsNxsSyncMsgReqItem *item, bool &grp_is_known); void locked_resetClientTS(const RsGxsGroupId& grpId); + bool locked_checkResendingOfUpdates(const RsPeerId& pid, const RsGxsGroupId &grpId, rstime_t incoming_ts, RsPeerUpdateTsRecord& rec); static RsGxsGroupId hashGrpId(const RsGxsGroupId& gid,const RsPeerId& pid) ; @@ -609,7 +610,7 @@ private: std::set mNewPublishKeysToNotify ; // Distant search result map - std::map > mDistantSearchResults ; + std::map > mDistantSearchResults ; void debugDump(); diff --git a/libretroshare/src/gxs/rsgxsnettunnel.cc b/libretroshare/src/gxs/rsgxsnettunnel.cc index a58d20f5f..eabf21e7e 100644 --- a/libretroshare/src/gxs/rsgxsnettunnel.cc +++ b/libretroshare/src/gxs/rsgxsnettunnel.cc @@ -1090,8 +1090,10 @@ void RsGxsNetTunnelService::receiveSearchResult(TurtleSearchRequestId request_id { GXS_NET_TUNNEL_DEBUG() << " : result is of type group summary result for service " << result_gs->service << std::dec << ": " << std::endl; +#ifdef DEBUG_RSGXSNETTUNNEL for(auto it(result_gs->group_infos.begin());it!=result_gs->group_infos.end();++it) std::cerr << " group " << (*it).mGroupId << ": " << (*it).mGroupName << ", " << (*it).mNumberOfMessages << " messages, last is " << time(NULL)-(*it).mLastMessageTs << " secs ago." << std::endl; +#endif auto it = mSearchableServices.find(result_gs->service) ; diff --git a/libretroshare/src/gxs/rsgxsnotify.h b/libretroshare/src/gxs/rsgxsnotify.h index 4640f133a..a6fd4c6fb 100644 --- a/libretroshare/src/gxs/rsgxsnotify.h +++ b/libretroshare/src/gxs/rsgxsnotify.h @@ -76,16 +76,6 @@ protected: bool mMetaChange; }; -class RsGxsDistantSearchResultChange: public RsGxsNotify -{ -public: - RsGxsDistantSearchResultChange(TurtleRequestId id,const RsGxsGroupId& gid) : RsGxsNotify(gid), mRequestId(id){} - - NotifyType getType() { return TYPE_RECEIVED_DISTANT_SEARCH_RESULTS ; } - - TurtleRequestId mRequestId ; -}; - /*! * Relevant to message changes */ diff --git a/libretroshare/src/gxs/rsgxsutil.cc b/libretroshare/src/gxs/rsgxsutil.cc index 2396618a8..79ae187bd 100644 --- a/libretroshare/src/gxs/rsgxsutil.cc +++ b/libretroshare/src/gxs/rsgxsutil.cc @@ -85,7 +85,7 @@ bool RsGxsMessageCleanUp::clean() for(; mit != result.end(); ++mit) { - std::vector& metaV = mit->second; + std::vector& metaV = mit->second; // First, make a map of which message have a child message. This allows to only delete messages that dont have child messages. // A more accurate way to go would be to compute the time of the oldest message and possibly delete all the branch, but in the @@ -99,7 +99,7 @@ bool RsGxsMessageCleanUp::clean() for( uint32_t i=0;imMsgId)!=messages_with_kids.end()); @@ -132,7 +132,7 @@ bool RsGxsMessageCleanUp::clean() std::cerr << std::endl; #endif - delete meta; + //delete meta; } } @@ -215,7 +215,8 @@ bool RsGxsIntegrityCheck::check() rsReputations->overallReputationLevel( grp->metaData->mAuthorId ) > RsReputationLevel::LOCALLY_NEGATIVE ) - used_gxs_ids.insert(std::make_pair(grp->metaData->mAuthorId, RsIdentityUsage(mGenExchangeClient->serviceType(), RsIdentityUsage::GROUP_AUTHOR_KEEP_ALIVE,grp->grpId))); + used_gxs_ids.insert(std::make_pair(grp->metaData->mAuthorId, RsIdentityUsage(RsServiceType(mGenExchangeClient->serviceType()), + RsIdentityUsage::GROUP_AUTHOR_KEEP_ALIVE,grp->grpId))); } } } @@ -404,7 +405,12 @@ bool RsGxsIntegrityCheck::check() rsReputations->overallReputationLevel( msg->metaData->mAuthorId ) > RsReputationLevel::LOCALLY_NEGATIVE ) - used_gxs_ids.insert(std::make_pair(msg->metaData->mAuthorId,RsIdentityUsage(mGenExchangeClient->serviceType(),RsIdentityUsage::MESSAGE_AUTHOR_KEEP_ALIVE,msg->metaData->mGroupId,msg->metaData->mMsgId))) ; + used_gxs_ids.insert(std::make_pair(msg->metaData->mAuthorId,RsIdentityUsage(RsServiceType(mGenExchangeClient->serviceType()), + RsIdentityUsage::MESSAGE_AUTHOR_KEEP_ALIVE, + msg->metaData->mGroupId, + msg->metaData->mMsgId, + msg->metaData->mParentId, + msg->metaData->mThreadId))) ; } } diff --git a/libretroshare/src/gxs/rsnxs.h b/libretroshare/src/gxs/rsnxs.h index 2752fa6bf..7276f3421 100644 --- a/libretroshare/src/gxs/rsnxs.h +++ b/libretroshare/src/gxs/rsnxs.h @@ -128,7 +128,7 @@ public: * \return * false when the request is unknown. */ - virtual bool retrieveDistantSearchResults(TurtleRequestId req, std::map &group_infos)=0; + virtual bool retrieveDistantSearchResults(TurtleRequestId req, std::map &group_infos)=0; /*! * \brief getDistantSearchResults * \param id @@ -136,7 +136,7 @@ public: * \return */ virtual bool clearDistantSearchResults(const TurtleRequestId& id)=0; - virtual bool retrieveDistantGroupSummary(const RsGxsGroupId&,RsGxsGroupSummary&)=0; + virtual bool retrieveDistantGroupSummary(const RsGxsGroupId&,RsGxsGroupSearchResults&)=0; virtual bool search(const std::string& substring,std::list& group_infos) =0; virtual bool search(const Sha1CheckSum& hashed_group_id,unsigned char *& encrypted_group_data,uint32_t& encrypted_group_data_len)=0; diff --git a/libretroshare/src/gxstunnel/p3gxstunnel.cc b/libretroshare/src/gxstunnel/p3gxstunnel.cc index 543817ffc..2909238ea 100644 --- a/libretroshare/src/gxstunnel/p3gxstunnel.cc +++ b/libretroshare/src/gxstunnel/p3gxstunnel.cc @@ -976,7 +976,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,RsIdentityUsage(RS_SERVICE_TYPE_GXS_TUNNEL,RsIdentityUsage::GXS_TUNNEL_DH_SIGNATURE_CHECK)); + mGixs->timeStampKey(item->signature.keyId,RsIdentityUsage(RsServiceType::GXS_TUNNEL,RsIdentityUsage::GXS_TUNNEL_DH_SIGNATURE_CHECK)); #ifdef DEBUG_GXS_TUNNEL std::cerr << " Signature checks! Sender's ID = " << senders_id << std::endl; diff --git a/libretroshare/src/jsonapi/jsonapi.cpp b/libretroshare/src/jsonapi/jsonapi.cpp index 338782a01..3dcde2a27 100644 --- a/libretroshare/src/jsonapi/jsonapi.cpp +++ b/libretroshare/src/jsonapi/jsonapi.cpp @@ -165,6 +165,7 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), RsThread::setStopTimeout(10); #endif +#if !RS_VERSION_AT_LEAST(0,6,6) registerHandler("/rsLoginHelper/createLocation", [this](const std::shared_ptr session) { @@ -180,6 +181,7 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), std::string errorMessage; bool makeHidden = false; bool makeAutoTor = false; + std::string createToken; // deserialize input parameters from JSON { @@ -189,6 +191,7 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), RS_SERIAL_PROCESS(password); RS_SERIAL_PROCESS(makeHidden); RS_SERIAL_PROCESS(makeAutoTor); + RS_SERIAL_PROCESS(createToken); } // call retroshare C++ API @@ -196,8 +199,9 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), location, password, errorMessage, makeHidden, makeAutoTor ); - if(retval) - authorizeUser(location.mLocationId.toStdString(),password); + std::string tokenUser, tokenPw; + if(retval && parseToken(createToken, tokenUser, tokenPw)) + authorizeUser(tokenUser,tokenPw); // serialize out parameters and return value to JSON { @@ -212,8 +216,9 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), DEFAULT_API_CALL_JSON_RETURN(rb::OK); } ); }, false); +#endif // !RS_VERSION_AT_LEAST(0,6,6) - registerHandler("/rsLoginHelper/attemptLogin", + registerHandler("/rsLoginHelper/createLocationV2", [this](const std::shared_ptr session) { auto reqSize = session->get_request()->get_header("Content-Length", 0); @@ -223,28 +228,51 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), { INITIALIZE_API_CALL_JSON_CONTEXT; - RsPeerId account; + RsPeerId locationId; + RsPgpId pgpId; + std::string locationName; + std::string pgpName; std::string password; + // JSON API only + std::string apiUser; + std::string apiPass; + // deserialize input parameters from JSON { RsGenericSerializer::SerializeContext& ctx(cReq); RsGenericSerializer::SerializeJob j(RsGenericSerializer::FROM_JSON); - RS_SERIAL_PROCESS(account); + RS_SERIAL_PROCESS(locationId); + RS_SERIAL_PROCESS(pgpId); + RS_SERIAL_PROCESS(locationName); + RS_SERIAL_PROCESS(pgpName); RS_SERIAL_PROCESS(password); + + // JSON API only + RS_SERIAL_PROCESS(apiUser); + RS_SERIAL_PROCESS(apiPass); } - // call retroshare C++ API - RsInit::LoadCertificateStatus retval = - rsLoginHelper->attemptLogin(account, password); + std::error_condition retval; - if( retval == RsInit::OK ) - authorizeUser(account.toStdString(), password); + if(apiUser.empty()) + retval = RsJsonApiErrorNum::TOKEN_FORMAT_INVALID; + + if(!retval) + retval = badApiCredientalsFormat(apiUser, apiPass); + + if(!retval) // call retroshare C++ API + retval = rsLoginHelper->createLocationV2( + locationId, pgpId, locationName, pgpName, password ); + + if(!retval) retval = authorizeUser(apiUser, apiPass); // serialize out parameters and return value to JSON { RsGenericSerializer::SerializeContext& ctx(cAns); RsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON); + RS_SERIAL_PROCESS(locationId); + RS_SERIAL_PROCESS(pgpId); RS_SERIAL_PROCESS(retval); } @@ -467,18 +495,18 @@ void JsonApiServer::registerHandler( const std::function)>& callback ) { /* Declare outside the lambda to avoid returning a dangling - * reference on Android */ + * reference */ RsWarn tWarn; const auto authFail = - [&](int status) -> RsWarn::stream_type& + [&](int status) -> std::ostream& { /* Capture session by reference as it is cheaper then copying * shared_ptr by value which is not needed in this case */ session->close(status, corsOptionsHeaders); return tWarn << "JsonApiServer authentication handler " - "blocked an attempt to call JSON API " - "authenticated method: " << path; + "blocked an attempt to call JSON API " + "authenticated method: " << path; }; if(session->get_request()->get_method() == "OPTIONS") diff --git a/libretroshare/src/libretroshare.pro b/libretroshare/src/libretroshare.pro index 3f9dde680..837c1f715 100644 --- a/libretroshare/src/libretroshare.pro +++ b/libretroshare/src/libretroshare.pro @@ -154,6 +154,7 @@ rs_webui { HEADERS += plugins/pluginmanager.h \ plugins/dlfcn_win32.h \ rsitems/rspluginitems.h \ + util/i2pcommon.h \ util/rsinitedptr.h HEADERS += $$PUBLIC_HEADERS @@ -327,6 +328,8 @@ INCLUDEPATH *= $${OPENPGPSDK_DIR} PRE_TARGETDEPS *= $${OPENPGPSDK_DIR}/lib/libops.a LIBS *= $${OPENPGPSDK_DIR}/lib/libops.a -lbz2 +################################### HEADERS & SOURCES ############################# + HEADERS += ft/ftchunkmap.h \ ft/ftcontroller.h \ ft/ftdata.h \ @@ -468,7 +471,12 @@ HEADERS += turtle/p3turtle.h \ turtle/turtleclientservice.h HEADERS += util/folderiterator.h \ - util/rsdebug.h \ + util/rsdebug.h \ + util/rsdebuglevel0.h \ + util/rsdebuglevel1.h \ + util/rsdebuglevel2.h \ + util/rsdebuglevel3.h \ + util/rsdebuglevel4.h \ util/rskbdinput.h \ util/rsmemory.h \ util/smallobject.h \ @@ -510,7 +518,8 @@ SOURCES += ft/ftchunkmap.cc \ ft/ftfilesearch.cc \ ft/ftserver.cc \ ft/fttransfermodule.cc \ - ft/ftturtlefiletransferitem.cc + ft/ftturtlefiletransferitem.cc \ + util/i2pcommon.cpp SOURCES += crypto/chacha20.cpp \ crypto/hashstream.cc\ diff --git a/libretroshare/src/pqi/p3netmgr.cc b/libretroshare/src/pqi/p3netmgr.cc index 38007c309..cf4ab36fc 100644 --- a/libretroshare/src/pqi/p3netmgr.cc +++ b/libretroshare/src/pqi/p3netmgr.cc @@ -1803,15 +1803,16 @@ void p3NetMgrIMPL::updateNatSetting() #endif #ifdef RS_USE_DHT_STUNNER - switch(natType) - { - case RSNET_NATTYPE_RESTRICTED_CONE: + if (mProxyStunner) { + switch(natType) + { + case RSNET_NATTYPE_RESTRICTED_CONE: { if ((natHole == RSNET_NATHOLE_NONE) || (natHole == RSNET_NATHOLE_UNKNOWN)) { mProxyStunner->setRefreshPeriod(NET_STUNNER_PERIOD_FAST); } - else + else { mProxyStunner->setRefreshPeriod(NET_STUNNER_PERIOD_SLOW); } @@ -1826,6 +1827,7 @@ void p3NetMgrIMPL::updateNatSetting() mProxyStunner->setRefreshPeriod(NET_STUNNER_PERIOD_SLOW); break; + } } #endif // RS_USE_DHT_STUNNER diff --git a/libretroshare/src/pqi/p3netmgr.h b/libretroshare/src/pqi/p3netmgr.h index f7775757d..05c9b743a 100644 --- a/libretroshare/src/pqi/p3netmgr.h +++ b/libretroshare/src/pqi/p3netmgr.h @@ -291,8 +291,8 @@ private: //p3BitDht *mBitDht; #ifdef RS_USE_DHT_STUNNER - pqiAddrAssist *mDhtStunner; - pqiAddrAssist *mProxyStunner; + pqiAddrAssist *mDhtStunner = nullptr; + pqiAddrAssist *mProxyStunner = nullptr; #endif // RS_USE_DHT_STUNNER RsMutex mNetMtx; /* protects below */ diff --git a/libretroshare/src/pqi/pqinetwork.cc b/libretroshare/src/pqi/pqinetwork.cc index e2251ed6a..18fe71fc0 100644 --- a/libretroshare/src/pqi/pqinetwork.cc +++ b/libretroshare/src/pqi/pqinetwork.cc @@ -413,9 +413,10 @@ int unix_fcntl_nonblock(int fd) { int ret; -/******************* WINDOWS SPECIFIC PART ******************/ +/******************* OS SPECIFIC PART ******************/ #ifndef WINDOWS_SYS // ie UNIX - ret = fcntl(fd, F_SETFL, O_NONBLOCK); + int flags = fcntl(fd, F_GETFL); + ret = fcntl(fd, F_SETFL, flags | O_NONBLOCK); #ifdef NET_DEBUG std::cerr << "unix_fcntl_nonblock():" << ret << " errno:" << errno << std::endl; diff --git a/libretroshare/src/pqi/pqiservice.cc b/libretroshare/src/pqi/pqiservice.cc index 35a4fac64..86336631a 100644 --- a/libretroshare/src/pqi/pqiservice.cc +++ b/libretroshare/src/pqi/pqiservice.cc @@ -1,5 +1,5 @@ /******************************************************************************* - * libretroshare/src/pqi: pqiservice.h * + * libretroshare/src/pqi: pqiservice.cc * * * * libretroshare: retroshare core library * * * @@ -23,6 +23,22 @@ #include "util/rsdebug.h" #include "util/rsstring.h" +#include +#include +static double getCurrentTS() +{ +#ifndef WINDOWS_SYS + struct timeval cts_tmp; + gettimeofday(&cts_tmp, NULL); + double cts = (cts_tmp.tv_sec) + ((double) cts_tmp.tv_usec) / 1000000.0; +#else + struct _timeb timebuf; + _ftime( &timebuf); + double cts = (timebuf.time) + ((double) timebuf.millitm) / 1000.0; +#endif + return cts; +} + #ifdef SERVICE_DEBUG const int pqiservicezone = 60478; #endif @@ -44,7 +60,7 @@ bool pqiService::send(RsRawItem *item) p3ServiceServer::p3ServiceServer(pqiPublisher *pub, p3ServiceControl *ctrl) : mPublisher(pub), mServiceControl(ctrl), srvMtx("p3ServiceServer") { - RsStackMutex stack(srvMtx); /********* LOCKED *********/ + RS_STACK_MUTEX(srvMtx); /********* LOCKED *********/ #ifdef SERVICE_DEBUG pqioutput(PQL_DEBUG_BASIC, pqiservicezone, @@ -56,7 +72,7 @@ p3ServiceServer::p3ServiceServer(pqiPublisher *pub, p3ServiceControl *ctrl) : mP int p3ServiceServer::addService(pqiService *ts, bool defaultOn) { - RsStackMutex stack(srvMtx); /********* LOCKED *********/ + RS_STACK_MUTEX(srvMtx); /********* LOCKED *********/ #ifdef SERVICE_DEBUG pqioutput(PQL_DEBUG_BASIC, pqiservicezone, @@ -84,7 +100,7 @@ int p3ServiceServer::addService(pqiService *ts, bool defaultOn) bool p3ServiceServer::getServiceItemNames(uint32_t service_type,std::map& names) { - RsStackMutex stack(srvMtx); /********* LOCKED *********/ + RS_STACK_MUTEX(srvMtx); /********* LOCKED *********/ std::map::iterator it=services.find(service_type) ; @@ -99,7 +115,7 @@ bool p3ServiceServer::getServiceItemNames(uint32_t service_type,std::map PacketId(), (item -> PacketId() & 0xffffff00)); - item -> print_string(out); - std::cerr << out; - std::cerr << std::endl; - } -#endif - // Packet Filtering. // This doesn't need to be in Mutex. if (!mServiceControl->checkFilter(item->PacketId() & 0xffffff00, item->PeerId())) { -#ifdef SERVICE_DEBUG - std::cerr << "p3ServiceServer::recvItem() Fails Filtering " << std::endl; -#endif delete item; return false; } + pqiService *s = NULL; - std::map::iterator it; - it = services.find(item -> PacketId() & 0xffffff00); - if (it == services.end()) + // access the service map under mutex lock { -#ifdef SERVICE_DEBUG - std::cerr << "p3ServiceServer::incoming() Service: No Service - deleting"; - std::cerr << std::endl; -#endif - delete item; - return false; + RS_STACK_MUTEX(srvMtx); + auto it = services.find(item -> PacketId() & 0xffffff00); + if (it == services.end()) + { + delete item; + return false; + } + s = it->second; } - { -#ifdef SERVICE_DEBUG - std::cerr << "p3ServiceServer::incoming() Sending to : " << (void *) it -> second; - std::cerr << std::endl; -#endif - - return (it->second) -> recv(item); - } - - delete item; - return false; + // then call recv off mutex + bool result = s->recv(item); + return result; } - - bool p3ServiceServer::sendItem(RsRawItem *item) { #ifdef SERVICE_DEBUG @@ -204,40 +193,27 @@ bool p3ServiceServer::sendItem(RsRawItem *item) } mPublisher->sendItem(item); + return true; } - - int p3ServiceServer::tick() { - mServiceControl->tick(); - RsStackMutex stack(srvMtx); /********* LOCKED *********/ - -#ifdef SERVICE_DEBUG - pqioutput(PQL_DEBUG_ALL, pqiservicezone, - "p3ServiceServer::tick()"); -#endif - - std::map::iterator it; - - // from the beginning to where we started. - for(it = services.begin();it != services.end(); ++it) - { - -#ifdef SERVICE_DEBUG - std::string out; - rs_sprintf(out, "p3ServiceServer::service id: %u -> Service: %p", it -> first, it -> second); - pqioutput(PQL_DEBUG_ALL, pqiservicezone, out); -#endif - - // now we should actually tick the service. - (it -> second) -> tick(); + // make a copy of the service map + std::map local_map; + { + RS_STACK_MUTEX(srvMtx); + local_map=services; } + + // tick all services off mutex + for(auto it(local_map.begin());it!=local_map.end();++it) + { + (it->second)->tick(); + } + return 1; + } - - - diff --git a/libretroshare/src/pqi/pqissl.cc b/libretroshare/src/pqi/pqissl.cc index db8d32cf7..734fc9aba 100644 --- a/libretroshare/src/pqi/pqissl.cc +++ b/libretroshare/src/pqi/pqissl.cc @@ -372,9 +372,11 @@ int pqissl::status() // tick...... int pqissl::tick() { - RsStackMutex stack(mSslMtx); /**** LOCKED MUTEX ****/ + // there is no reason to lock pqissl mutex now + // we will lock the mutex later if we actually need to call to ConnectAttempt + // RsStackMutex stack(mSslMtx); /**** LOCKED MUTEX ****/ - //pqistreamer::tick(); + // pqistreamer::tick(); // continue existing connection attempt. if (!active) @@ -385,7 +387,8 @@ int pqissl::tick() #ifdef PQISSL_LOG_DEBUG rslog(RSL_DEBUG_BASIC, pqisslzone, "pqissl::tick() Continuing Connection Attempt!"); #endif - + // now lock pqissl mutex, that will take up to 10 ms + RsStackMutex stack(mSslMtx); /**** LOCKED MUTEX ****/ ConnectAttempt(); return 1; } diff --git a/libretroshare/src/pqi/pqistreamer.cc b/libretroshare/src/pqi/pqistreamer.cc index dc9ceaabc..62b4d7f84 100644 --- a/libretroshare/src/pqi/pqistreamer.cc +++ b/libretroshare/src/pqi/pqistreamer.cc @@ -1,5 +1,5 @@ /******************************************************************************* - * libretroshare/src/pqi: pqistreamer.h * + * libretroshare/src/pqi: pqistreamer.cc * * * * libretroshare: retroshare core library * * * @@ -102,38 +102,39 @@ pqistreamer::pqistreamer(RsSerialiser *rss, const RsPeerId& id, BinInterface *bi mAvgDtOut(0), mAvgDtIn(0) { - // 100 B/s (minimal) - setMaxRate(true, 0.1); - setMaxRate(false, 0.1); - setRate(true, 0); // needs to be off-mutex - setRate(false, 0); + // 100 B/s (minimal) + setMaxRate(true, 0.1); + setMaxRate(false, 0.1); + setRate(true, 0); // needs to be off-mutex + setRate(false, 0); - RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ + RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ - mAcceptsPacketSlicing = false ; // by default. Will be turned into true when everyone's ready. - mLastSentPacketSlicingProbe = 0 ; + mAcceptsPacketSlicing = false ; // by default. Will be turned into true when everyone's ready. + mLastSentPacketSlicingProbe = 0 ; - mAvgLastUpdate = mCurrSentTS = mCurrReadTS = getCurrentTS(); + mAvgLastUpdate = mCurrSentTS = mCurrReadTS = getCurrentTS(); - mIncomingSize = 0 ; + mIncomingSize = 0 ; + mIncomingSize_bytes = 0; - mStatisticsTimeStamp = 0 ; - /* allocated once */ - mPkt_rpend_size = 0; - mPkt_rpending = 0; - mReading_state = reading_state_initial ; + mStatisticsTimeStamp = 0 ; + /* allocated once */ + mPkt_rpend_size = 0; + mPkt_rpending = 0; + mReading_state = reading_state_initial ; - pqioutput(PQL_DEBUG_ALL, pqistreamerzone, "pqistreamer::pqistreamer() Initialisation!"); + pqioutput(PQL_DEBUG_ALL, pqistreamerzone, "pqistreamer::pqistreamer() Initialisation!"); - if (!bio_in) - { - pqioutput(PQL_ALERT, pqistreamerzone, "pqistreamer::pqistreamer() NULL bio, FATAL ERROR!"); - exit(1); - } + if (!bio_in) + { + pqioutput(PQL_ALERT, pqistreamerzone, "pqistreamer::pqistreamer() NULL bio, FATAL ERROR!"); + exit(1); + } - mFailed_read_attempts = 0; // reset failed read, as no packet is still read. + mFailed_read_attempts = 0; // reset failed read, as no packet is still read. - return; + return; } pqistreamer::~pqistreamer() @@ -159,7 +160,7 @@ pqistreamer::~pqistreamer() if (mRsSerialiser) delete mRsSerialiser; - free_pend_locked() ; + free_pend() ; // clean up incoming. while (!mIncoming.empty()) @@ -177,6 +178,7 @@ pqistreamer::~pqistreamer() // Get/Send Items. +// This is the entry poing for methods willing to send items through our out queue int pqistreamer::SendItem(RsItem *si,uint32_t& out_size) { #ifdef RSITEM_DEBUG @@ -199,18 +201,30 @@ RsItem *pqistreamer::GetItem() pqioutput(PQL_DEBUG_ALL, pqistreamerzone, "pqistreamer::GetItem()"); #endif - RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ - if(mIncoming.empty()) return NULL; RsItem *osr = mIncoming.front() ; - mIncoming.pop_front() ; - --mIncomingSize; + mIncoming.pop_front() ; + --mIncomingSize; +// for future use +// mIncomingSize_bytes -= return osr; } + +float pqistreamer::getMaxRate(bool b) +{ + RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ + return getMaxRate_locked(b); +} + +float pqistreamer::getMaxRate_locked(bool b) +{ + return RateInterface::getMaxRate(b) ; +} + float pqistreamer::getRate(bool b) { RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ @@ -219,26 +233,28 @@ float pqistreamer::getRate(bool b) void pqistreamer::setMaxRate(bool b,float f) { - RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ - RateInterface::setMaxRate(b,f) ; + RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ + setMaxRate_locked(b,f); } + +void pqistreamer::setMaxRate_locked(bool b,float f) +{ + RateInterface::setMaxRate(b,f) ; +} + void pqistreamer::setRate(bool b,float f) { RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ RateInterface::setRate(b,f) ; } + void pqistreamer::updateRates() { - // update rates both ways. + // update actual rates both ways. double t = getCurrentTS(); // get current timestamp. - double diff ; - - { - RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ - diff = t - mAvgLastUpdate ; - } + double diff = t - mAvgLastUpdate; if (diff > PQISTREAM_AVG_PERIOD) { @@ -263,10 +279,11 @@ void pqistreamer::updateRates() setRate(false, 0); } + mAvgLastUpdate = t; + mAvgReadCount = 0; + { RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ - mAvgLastUpdate = t; - mAvgReadCount = 0; mAvgSentCount = 0; } } @@ -277,7 +294,7 @@ int pqistreamer::tick_bio() RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ mBio->tick(); - /* short circuit everything is bio isn't active */ + /* short circuit everything if bio isn't active */ if (!(mBio->isactive())) { return 0; @@ -285,36 +302,36 @@ int pqistreamer::tick_bio() return 1; } - int pqistreamer::tick_recv(uint32_t timeout) { - RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ +// Apart from a few exceptions that are atomic (mLastIncomingTs, mIncomingSize), only this pqi thread reads/writes mIncoming queue and related counters. +// The lock of pqistreamer mutex is thus not needed here. +// The mutex lock is still needed before calling locked_addTrafficClue because this method is also used by the thread pushing packets in mOutPkts. +// Locks around rates are provided internally. if (mBio->moretoread(timeout)) { - handleincoming_locked(); + handleincoming(); + } + if(!(mBio->isactive())) + { + free_pend(); } - if(!(mBio->isactive())) - { - free_pend_locked(); - } return 1; } - int pqistreamer::tick_send(uint32_t timeout) { - RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ - - /* short circuit everything is bio isn't active */ + /* short circuit everything if bio isn't active */ if (!(mBio->isactive())) { - free_pend_locked(); + free_pend(); return 0; } if (mBio->cansend(timeout)) { + RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ handleoutgoing_locked(); } @@ -340,12 +357,11 @@ int pqistreamer::status() return 0; } +// this method is overloaded by pqiqosstreamer void pqistreamer::locked_storeInOutputQueue(void *ptr,int,int) { mOutPkts.push_back(ptr); } -// -/**************** HANDLE OUTGOING TRANSLATION + TRANSMISSION ******/ int pqistreamer::queue_outpqi_locked(RsItem *pqi,uint32_t& pktsize) { @@ -354,7 +370,6 @@ int pqistreamer::queue_outpqi_locked(RsItem *pqi,uint32_t& pktsize) std::cerr << "pqistreamer::queue_outpqi() called." << std::endl; #endif - /* decide which type of packet it is */ pktsize = mRsSerialiser->size(pqi); @@ -362,7 +377,6 @@ int pqistreamer::queue_outpqi_locked(RsItem *pqi,uint32_t& pktsize) if(ptr == NULL) return 0 ; - #ifdef DEBUG_PQISTREAMER std::cerr << "pqistreamer::queue_outpqi() serializing packet with packet size : " << pktsize << std::endl; @@ -403,27 +417,31 @@ int pqistreamer::queue_outpqi_locked(RsItem *pqi,uint32_t& pktsize) return 1; // keep error internal. } -int pqistreamer::handleincomingitem_locked(RsItem *pqi,int len) +int pqistreamer::handleincomingitem(RsItem *pqi,int len) { #ifdef DEBUG_PQISTREAMER - pqioutput(PQL_DEBUG_ALL, pqistreamerzone, "pqistreamer::handleincomingitem_locked()"); + pqioutput(PQL_DEBUG_ALL, pqistreamerzone, "pqistreamer::handleincomingitem()"); #endif // timestamp last received packet. mLastIncomingTs = time(NULL); // Use overloaded Contact function pqi -> PeerId(PeerId()); - mIncoming.push_back(pqi); - ++mIncomingSize ; - /*******************************************************************************************/ - // keep info for stats for a while. Only keep the items for the last two seconds. sec n is ongoing and second n-1 - // is a full statistics chunk that can be used in the GUI + mIncoming.push_back(pqi); + ++mIncomingSize; + // for future use + // mIncomingSize_bytes += len; - locked_addTrafficClue(pqi,len,mCurrentStatsChunk_In) ; - - /*******************************************************************************************/ + /*******************************************************************************************/ + // keep info for stats for a while. Only keep the items for the last two seconds. sec n is ongoing and second n-1 + // is a full statistics chunk that can be used in the GUI + { + RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ + locked_addTrafficClue(pqi,len,mCurrentStatsChunk_In) ; + } + /*******************************************************************************************/ return 1; } @@ -456,8 +474,8 @@ void pqistreamer::locked_addTrafficClue(const RsItem *pqi,uint32_t pktsize,std:: rstime_t pqistreamer::getLastIncomingTS() { - RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ - + // This is the only case where another thread (rs main for pqiperson) will access our data + // Still a mutex lock is not needed because the operation is atomic return mLastIncomingTs; } @@ -693,23 +711,23 @@ int pqistreamer::handleoutgoing_locked() /* Handles reading from input stream. */ -int pqistreamer::handleincoming_locked() +int pqistreamer::handleincoming() { int readbytes = 0; static const int max_failed_read_attempts = 2000 ; #ifdef DEBUG_PQISTREAMER - pqioutput(PQL_DEBUG_ALL, pqistreamerzone, "pqistreamer::handleincoming_locked()"); + pqioutput(PQL_DEBUG_ALL, pqistreamerzone, "pqistreamer::handleincoming()"); #endif if(!(mBio->isactive())) { mReading_state = reading_state_initial ; - free_pend_locked(); + free_pend(); return 0; } else - allocate_rpend_locked(); + allocate_rpend(); // enough space to read any packet. uint32_t maxlen = mPkt_rpend_size; @@ -718,7 +736,7 @@ int pqistreamer::handleincoming_locked() // initial read size: basic packet. int blen = getRsPktBaseSize(); // this is valid for both packet slices and normal un-sliced packets (same header size) - int maxin = inAllowedBytes_locked(); + int maxin = inAllowedBytes(); #ifdef DEBUG_PQISTREAMER std::cerr << "[" << (void*)pthread_self() << "] " << "reading state = " << mReading_state << std::endl ; @@ -967,19 +985,19 @@ continue_packet: std::cerr << "Inputing partial packet " << RsUtil::BinToHex((char*)block,8) << std::endl; #endif uint32_t packet_length = 0 ; - pkt = addPartialPacket_locked(block,pktlen,slice_packet_id,is_packet_starting,is_packet_ending,packet_length) ; + pkt = addPartialPacket(block,pktlen,slice_packet_id,is_packet_starting,is_packet_ending,packet_length) ; pktlen = packet_length ; } else pkt = mRsSerialiser->deserialise(block, &pktlen); - if ((pkt != NULL) && (0 < handleincomingitem_locked(pkt,pktlen))) + if ((pkt != NULL) && (0 < handleincomingitem(pkt,pktlen))) { #ifdef DEBUG_PQISTREAMER pqioutput(PQL_DEBUG_BASIC, pqistreamerzone, "Successfully Read a Packet!"); #endif - inReadBytes_locked(pktlen); // only count deserialised packets, because that's what is actually been transfered. + inReadBytes(pktlen); // only count deserialised packets, because that's what is actually been transfered. } else if (!is_partial_packet) { @@ -1012,7 +1030,7 @@ continue_packet: return 0; } -RsItem *pqistreamer::addPartialPacket_locked(const void *block, uint32_t len, uint32_t slice_packet_id, bool is_packet_starting, bool is_packet_ending, uint32_t &total_len) +RsItem *pqistreamer::addPartialPacket(const void *block, uint32_t len, uint32_t slice_packet_id, bool is_packet_starting, bool is_packet_ending, uint32_t &total_len) { #ifdef DEBUG_PACKET_SLICING std::cerr << "Receiving partial packet. size=" << len << ", ID=" << std::hex << slice_packet_id << std::dec << ", starting:" << is_packet_starting << ", ending:" << is_packet_ending ; @@ -1134,7 +1152,7 @@ int pqistreamer::outAllowedBytes_locked() // low pass filter on mAvgDtOut mAvgDtOut = PQISTREAM_AVG_DT_FRAC * mAvgDtOut + (1 - PQISTREAM_AVG_DT_FRAC) * dt; - double maxout = getMaxRate(false) * 1024.0; + double maxout = getMaxRate_locked(false) * 1024.0; // this is used to take into account a possible excess of data sent during the previous round mCurrSent -= int(dt * maxout); @@ -1156,7 +1174,7 @@ int pqistreamer::outAllowedBytes_locked() return quota; } -int pqistreamer::inAllowedBytes_locked() +int pqistreamer::inAllowedBytes() { double t = getCurrentTS(); // in sec, with high accuracy @@ -1194,7 +1212,7 @@ int pqistreamer::inAllowedBytes_locked() #ifdef DEBUG_PQISTREAMER uint64_t t_now = 1000 * getCurrentTS(); - std::cerr << std::dec << t_now << " DEBUG_PQISTREAMER pqistreamer::inAllowedBytes_locked PeerId " << this->PeerId().toStdString() << " dt " << (int)(1000 * dt) << "ms, mAvgDtIn " << (int)(1000 * mAvgDtIn) << "ms, maxin " << (int)(maxin) << " bytes/s, mCurrRead " << mCurrRead << " bytes, quota " << (int)(quota) << " bytes" << std::endl; + std::cerr << std::dec << t_now << " DEBUG_PQISTREAMER pqistreamer::inAllowedBytes PeerId " << this->PeerId().toStdString() << " dt " << (int)(1000 * dt) << "ms, mAvgDtIn " << (int)(1000 * mAvgDtIn) << "ms, maxin " << (int)(maxin) << " bytes/s, mCurrRead " << mCurrRead << " bytes, quota " << (int)(quota) << " bytes" << std::endl; #endif return quota; @@ -1231,7 +1249,7 @@ void pqistreamer::outSentBytes_locked(uint32_t outb) return; } -void pqistreamer::inReadBytes_locked(uint32_t inb) +void pqistreamer::inReadBytes(uint32_t inb) { #ifdef DEBUG_PQISTREAMER { @@ -1248,7 +1266,7 @@ void pqistreamer::inReadBytes_locked(uint32_t inb) return; } -void pqistreamer::allocate_rpend_locked() +void pqistreamer::allocate_rpend() { if(mPkt_rpending) return; @@ -1271,17 +1289,17 @@ int pqistreamer::reset() #ifdef DEBUG_PQISTREAMER std::cerr << "pqistreamer::reset()" << std::endl; #endif - free_pend_locked(); + free_pend(); return 1 ; } -void pqistreamer::free_pend_locked() +void pqistreamer::free_pend() { if(mPkt_rpending) { #ifdef DEBUG_PQISTREAMER - std::cerr << "pqistreamer::free_pend_locked(): pending input packet buffer" << std::endl; + std::cerr << "pqistreamer::free_pend(): pending input packet buffer" << std::endl; #endif free(mPkt_rpending); mPkt_rpending = 0; @@ -1291,7 +1309,7 @@ void pqistreamer::free_pend_locked() if (mPkt_wpending) { #ifdef DEBUG_PQISTREAMER - std::cerr << "pqistreamer::free_pend_locked(): pending output packet buffer" << std::endl; + std::cerr << "pqistreamer::free_pend(): pending output packet buffer" << std::endl; #endif free(mPkt_wpending); mPkt_wpending = NULL; @@ -1300,7 +1318,7 @@ void pqistreamer::free_pend_locked() #ifdef DEBUG_PQISTREAMER if(!mPartialPackets.empty()) - std::cerr << "pqistreamer::free_pend_locked(): " << mPartialPackets.size() << " pending input partial packets" << std::endl; + std::cerr << "pqistreamer::free_pend(): " << mPartialPackets.size() << " pending input partial packets" << std::endl; #endif // also delete any incoming partial packet for(std::map::iterator it(mPartialPackets.begin());it!=mPartialPackets.end();++it) @@ -1318,26 +1336,47 @@ int pqistreamer::gatherStatistics(std::list& outqueue_lst,std return locked_gatherStatistics(outqueue_lst,inqueue_lst); } + +// this method is overloaded by pqiqosstreamer int pqistreamer::getQueueSize(bool in) { - RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ - if (in) - return mIncomingSize; - else - return locked_out_queue_size(); +// no mutex is needed here because this is atomic + return mIncomingSize; + else + { + RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ + return locked_out_queue_size(); + } +} + +int pqistreamer::getQueueSize_bytes(bool in) +{ + if (in) +// no mutex is needed here because this is atomic +// for future use, mIncomingSize_bytes is not updated yet + return mIncomingSize_bytes; + else + { + RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ + return locked_compute_out_pkt_size(); + } } void pqistreamer::getRates(RsBwRates &rates) { RateInterface::getRates(rates); - RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ +// no mutex is needed here because this is atomic + rates.mQueueIn = mIncomingSize; - rates.mQueueIn = mIncomingSize; - rates.mQueueOut = locked_out_queue_size(); + { + RsStackMutex stack(mStreamerMtx); /**** LOCKED MUTEX ****/ + rates.mQueueOut = locked_out_queue_size(); + } } +// this method is overloaded by pqiqosstreamer int pqistreamer::locked_out_queue_size() const { // Warning: because out_pkt is a list, calling size @@ -1347,6 +1386,7 @@ int pqistreamer::locked_out_queue_size() const return mOutPkts.size() ; } +// this method is overloaded by pqiqosstreamer void pqistreamer::locked_clear_out_queue() { for(std::list::iterator it = mOutPkts.begin(); it != mOutPkts.end(); ) @@ -1361,6 +1401,7 @@ void pqistreamer::locked_clear_out_queue() } } +// this method is overloaded by pqiqosstreamer int pqistreamer::locked_compute_out_pkt_size() const { int total = 0 ; @@ -1379,6 +1420,7 @@ int pqistreamer::locked_gatherStatistics(std::list& out_lst,std:: return 1 ; } +// this method is overloaded by pqiqosstreamer void *pqistreamer::locked_pop_out_data(uint32_t /*max_slice_size*/, uint32_t &size, bool &starts, bool &ends, uint32_t &packet_id) { size = 0 ; @@ -1400,4 +1442,3 @@ void *pqistreamer::locked_pop_out_data(uint32_t /*max_slice_size*/, uint32_t &si return res ; } - diff --git a/libretroshare/src/pqi/pqistreamer.h b/libretroshare/src/pqi/pqistreamer.h index c5b2bc983..0e7f6b815 100644 --- a/libretroshare/src/pqi/pqistreamer.h +++ b/libretroshare/src/pqi/pqistreamer.h @@ -38,8 +38,8 @@ class RsSerialiser; struct PartialPacketRecord { - void *mem ; - uint32_t size ; + void *mem ; + uint32_t size ; }; /** @@ -65,18 +65,23 @@ class pqistreamer: public PQInterface virtual RsItem *GetItem(); virtual int status(); - rstime_t getLastIncomingTS(); // Time of last data packet, for checking a connection is alive. + rstime_t getLastIncomingTS(); // Time of last data packet, for checking a connection is alive. virtual void getRates(RsBwRates &rates); virtual int getQueueSize(bool in); // extracting data. + virtual int getQueueSize_bytes(bool in); // size of incoming queue in bytes virtual int gatherStatistics(std::list& outqueue_stats,std::list& inqueue_stats); // extracting data. // mutex protected versions of RateInterface calls. virtual void setRate(bool b,float f) ; virtual void setMaxRate(bool b,float f) ; - virtual float getRate(bool b) ; + virtual void setMaxRate_locked(bool b,float f) ; - protected: - virtual int reset() ; + virtual float getRate(bool b) ; + virtual float getMaxRate(bool b) ; + virtual float getMaxRate_locked(bool b); + + protected: + virtual int reset() ; int tick_bio(); int tick_send(uint32_t timeout); @@ -104,12 +109,12 @@ class pqistreamer: public PQInterface private: int queue_outpqi_locked(RsItem *i,uint32_t& serialized_size); - int handleincomingitem_locked(RsItem *i, int len); + int handleincomingitem(RsItem *i, int len); // ticked regularly (manages out queues and sending // via above interfaces. virtual int handleoutgoing_locked(); - virtual int handleincoming_locked(); + virtual int handleincoming(); // Bandwidth/Streaming Management. float outTimeSlice_locked(); @@ -117,11 +122,11 @@ class pqistreamer: public PQInterface int outAllowedBytes_locked(); void outSentBytes_locked(uint32_t ); - int inAllowedBytes_locked(); - void inReadBytes_locked(uint32_t ); + int inAllowedBytes(); + void inReadBytes(uint32_t ); // cleans up everything that's pending / half finished. - void free_pend_locked(); + void free_pend(); // RsSerialiser - determines which packets can be serialised. RsSerialiser *mRsSerialiser; @@ -129,13 +134,12 @@ class pqistreamer: public PQInterface void *mPkt_wpending; // storage for pending packet to write. uint32_t mPkt_wpending_size; // ... and its size. - void allocate_rpend_locked(); // use these two functions to allocate/free the buffer below + void allocate_rpend(); // use these two functions to allocate/free the buffer below int mPkt_rpend_size; // size of pkt_rpending. void *mPkt_rpending; // storage for read in pending packets. - enum {reading_state_packet_started=1, - reading_state_initial=0 } ; + enum {reading_state_packet_started=1, reading_state_initial=0 } ; int mReading_state ; int mFailed_read_attempts ; @@ -144,7 +148,8 @@ class pqistreamer: public PQInterface std::list mOutPkts; // Cntrl / Search / Results queue std::list mIncoming; - uint32_t mIncomingSize; // size of mIncoming. To avoid calling linear cost std::list::size() + uint32_t mIncomingSize; // size of mIncoming. To avoid calling linear cost std::list::size() + uint32_t mIncomingSize_bytes; // size of Incoming in btyes // data for network stats. int mTotalRead; @@ -154,8 +159,8 @@ class pqistreamer: public PQInterface int mCurrRead; int mCurrSent; - double mCurrReadTS; // TS from which these are measured. - double mCurrSentTS; + double mCurrReadTS; // TS from which these are measured. + double mCurrSentTS; double mAvgLastUpdate; // TS from which these are measured. uint32_t mAvgReadCount; @@ -174,12 +179,12 @@ class pqistreamer: public PQInterface std::list mCurrentStatsChunk_Out ; rstime_t mStatisticsTimeStamp ; - bool mAcceptsPacketSlicing ; - rstime_t mLastSentPacketSlicingProbe ; - void locked_addTrafficClue(const RsItem *pqi, uint32_t pktsize, std::list &lst); - RsItem *addPartialPacket_locked(const void *block, uint32_t len, uint32_t slice_packet_id,bool packet_starting,bool packet_ending,uint32_t& total_len); + bool mAcceptsPacketSlicing ; + rstime_t mLastSentPacketSlicingProbe ; + void locked_addTrafficClue(const RsItem *pqi, uint32_t pktsize, std::list &lst); + RsItem *addPartialPacket(const void *block, uint32_t len, uint32_t slice_packet_id,bool packet_starting,bool packet_ending,uint32_t& total_len); - std::map mPartialPackets ; + std::map mPartialPackets ; }; #endif //MRK_PQI_STREAMER_HEADER diff --git a/libretroshare/src/pqi/pqithreadstreamer.cc b/libretroshare/src/pqi/pqithreadstreamer.cc index 857075ef3..6745dc864 100644 --- a/libretroshare/src/pqi/pqithreadstreamer.cc +++ b/libretroshare/src/pqi/pqithreadstreamer.cc @@ -23,17 +23,17 @@ #include "pqi/pqithreadstreamer.h" #include -#define DEFAULT_STREAMER_TIMEOUT 10000 // 10 ms. -#define DEFAULT_STREAMER_SLEEP 1000 // 1 ms. +#define DEFAULT_STREAMER_TIMEOUT 10000 // 10 ms +#define DEFAULT_STREAMER_SLEEP 30000 // 30 ms #define DEFAULT_STREAMER_IDLE_SLEEP 1000000 // 1 sec -//#define PQISTREAMER_DEBUG +// #define PQISTREAMER_DEBUG pqithreadstreamer::pqithreadstreamer(PQInterface *parent, RsSerialiser *rss, const RsPeerId& id, BinInterface *bio_in, int bio_flags_in) :pqistreamer(rss, id, bio_in, bio_flags_in), mParent(parent), mTimeout(0), mThreadMutex("pqithreadstreamer") { - mTimeout = DEFAULT_STREAMER_TIMEOUT; - mSleepPeriod = DEFAULT_STREAMER_SLEEP; + mTimeout = DEFAULT_STREAMER_TIMEOUT; + mSleepPeriod = DEFAULT_STREAMER_SLEEP; } bool pqithreadstreamer::RecvItem(RsItem *item) @@ -43,55 +43,59 @@ bool pqithreadstreamer::RecvItem(RsItem *item) int pqithreadstreamer::tick() { - RsStackMutex stack(mThreadMutex); - tick_bio(); + // pqithreadstreamer mutex lock is not needed here + // we will only check if the connection is active, and if not we will try to establish it + tick_bio(); return 0; } void pqithreadstreamer::threadTick() { - uint32_t recv_timeout = 0; - uint32_t sleep_period = 0; - bool isactive = false; - { - RsStackMutex stack(mStreamerMtx); - recv_timeout = mTimeout; - sleep_period = mSleepPeriod; - isactive = mBio->isactive(); - } + uint32_t recv_timeout = 0; + uint32_t sleep_period = 0; + bool isactive = false; + + { + RsStackMutex stack(mStreamerMtx); + recv_timeout = mTimeout; + sleep_period = mSleepPeriod; + isactive = mBio->isactive(); + } - updateRates() ; + // update the connection rates + updateRates() ; - if (!isactive) - { - rstime::rs_usleep(DEFAULT_STREAMER_IDLE_SLEEP); - return ; - } + // if the connection est not active, long sleep then return + if (!isactive) + { + rstime::rs_usleep(DEFAULT_STREAMER_IDLE_SLEEP); + return ; + } - { - RsStackMutex stack(mThreadMutex); - tick_recv(recv_timeout); - } + // fill incoming queue with items from SSL + { + RsStackMutex stack(mThreadMutex); + tick_recv(recv_timeout); + } - // Push Items, Outside of Mutex. - RsItem *incoming = NULL; - while((incoming = GetItem())) - { - RecvItem(incoming); - } + // move items to appropriate service queue or shortcut to fast service + RsItem *incoming = NULL; + while((incoming = GetItem())) + { + RecvItem(incoming); + } - { - RsStackMutex stack(mThreadMutex); - tick_send(0); - } + // parse the outgoing queue and send items to SSL + { + RsStackMutex stack(mThreadMutex); + tick_send(0); + } - if (sleep_period) - { - rstime::rs_usleep(sleep_period); - } + // sleep + if (sleep_period) + { + rstime::rs_usleep(sleep_period); + } } - - - diff --git a/libretroshare/src/retroshare/rsevents.h b/libretroshare/src/retroshare/rsevents.h index 47a499497..65e0e8557 100644 --- a/libretroshare/src/retroshare/rsevents.h +++ b/libretroshare/src/retroshare/rsevents.h @@ -33,6 +33,7 @@ #include "serialiser/rsserializable.h" #include "serialiser/rstypeserializer.h" #include "util/rstime.h" +#include "util/rsdebug.h" class RsEvents; @@ -126,8 +127,7 @@ struct RsEventsErrorCategory: std::error_category case RsEventsErrorNum::INVALID_HANDLER_ID: return "Invalid handler id"; default: - return "Error message for error: " + std::to_string(ev) + - " not available in category: " + name(); + return rsErrorNotInCategory(ev, name()); } } diff --git a/libretroshare/src/retroshare/rsfiles.h b/libretroshare/src/retroshare/rsfiles.h index 92c017cef..fa6b37e29 100644 --- a/libretroshare/src/retroshare/rsfiles.h +++ b/libretroshare/src/retroshare/rsfiles.h @@ -37,6 +37,7 @@ #include "util/rstime.h" #include "retroshare/rsevents.h" #include "util/rsmemory.h" +#include "util/rsdebug.h" class RsFiles; @@ -63,8 +64,7 @@ struct RsFilesErrorCategory: std::error_category case RsFilesErrorNum::FILES_HANDLE_NOT_FOUND: return "Files handle not found"; default: - return "Error message for error: " + std::to_string(ev) + - " not available in category: " + name(); + return rsErrorNotInCategory(ev, name()); } } diff --git a/libretroshare/src/retroshare/rsgxschannels.h b/libretroshare/src/retroshare/rsgxschannels.h index 4b188ad01..106147dc6 100644 --- a/libretroshare/src/retroshare/rsgxschannels.h +++ b/libretroshare/src/retroshare/rsgxschannels.h @@ -117,14 +117,11 @@ enum class RsChannelEventCode: uint8_t struct RsGxsChannelEvent: RsEvent { - RsGxsChannelEvent(): - RsEvent(RsEventType::GXS_CHANNELS), - mChannelEventCode(RsChannelEventCode::UNKNOWN) {} + RsGxsChannelEvent(): RsEvent(RsEventType::GXS_CHANNELS), mChannelEventCode(RsChannelEventCode::UNKNOWN) {} RsChannelEventCode mChannelEventCode; RsGxsGroupId mChannelGroupId; RsGxsMessageId mChannelMsgId; - TurtleRequestId mDistantSearchRequestId; ///* @see RsEvent @see RsSerializable void serial_process( RsGenericSerializer::SerializeJob j,RsGenericSerializer::SerializeContext& ctx) override @@ -134,8 +131,28 @@ struct RsGxsChannelEvent: RsEvent RS_SERIAL_PROCESS(mChannelEventCode); RS_SERIAL_PROCESS(mChannelGroupId); RS_SERIAL_PROCESS(mChannelMsgId); - RS_SERIAL_PROCESS(mDistantSearchRequestId); - } + } +}; + +// This event is used to factor multiple search results notifications in a single event. + +struct RsGxsChannelSearchResultEvent: RsEvent +{ + RsGxsChannelSearchResultEvent(): + RsEvent(RsEventType::GXS_CHANNELS), + mChannelEventCode(RsChannelEventCode::RECEIVED_DISTANT_SEARCH_RESULT) {} + + RsChannelEventCode mChannelEventCode; + std::map > mSearchResultsMap; + + ///* @see RsEvent @see RsSerializable + void serial_process( RsGenericSerializer::SerializeJob j,RsGenericSerializer::SerializeContext& ctx) override + { + RsEvent::serial_process(j, ctx); + + RS_SERIAL_PROCESS(mChannelEventCode); + RS_SERIAL_PROCESS(mSearchResultsMap); + } }; class RsGxsChannels: public RsGxsIfaceHelper, public RsGxsCommentService @@ -407,48 +424,6 @@ public: */ virtual bool getChannelStatistics(const RsGxsGroupId& channelId,GxsGroupStatistic& stat) =0; - - /** - * @brief Request remote channels search - * @jsonapi{development} - * @param[in] matchString string to look for in the search - * @param multiCallback function that will be called each time a search - * result is received - * @param[in] maxWait maximum wait time in seconds for search results - * @return false on error, true otherwise - */ - virtual bool turtleSearchRequest( - const std::string& matchString, - const std::function& multiCallback, - rstime_t maxWait = 300 ) = 0; - - /** - * @brief Request remote channel - * @jsonapi{development} - * @param[in] channelId id of the channel to request to distants peers - * @param multiCallback function that will be called each time a result is - * received - * @param[in] maxWait maximum wait time in seconds for search results - * @return false on error, true otherwise - */ - virtual bool turtleChannelRequest( - const RsGxsGroupId& channelId, - const std::function& multiCallback, - rstime_t maxWait = 300 ) = 0; - - /** - * @brief Search local channels - * @jsonapi{development} - * @param[in] matchString string to look for in the search - * @param multiCallback function that will be called for each result - * @param[in] maxWait maximum wait time in seconds for search results - * @return false on error, true otherwise - */ - virtual bool localSearchRequest( - const std::string& matchString, - const std::function& multiCallback, - rstime_t maxWait = 30 ) = 0; - /// default base URL used for channels links @see exportChannelLink static const std::string DEFAULT_CHANNEL_BASE_URL; @@ -493,6 +468,7 @@ public: /** * @brief Import channel from full link + * @jsonapi{development} * @param[in] link channel link either in radix or link format * @param[out] chanId optional storage for parsed channel id * @param[out] errMsg optional storage for error message, meaningful only in @@ -504,7 +480,58 @@ public: RsGxsGroupId& chanId = RS_DEFAULT_STORAGE_PARAM(RsGxsGroupId), std::string& errMsg = RS_DEFAULT_STORAGE_PARAM(std::string) ) = 0; + /** + * @brief Search the turtle reachable network for matching channels + * @jsonapi{development} + * An @see RsGxsChannelSearchResultEvent is emitted when matching channels + * arrives from the network + * @param[in] matchString string to search into the channels + * @return search id + */ + virtual TurtleRequestId turtleSearchRequest(const std::string& matchString)=0; + /** + * @brief Retrieve available search results + * @jsonapi{development} + * @param[in] searchId search id + * @param[out] results storage for search results + * @return false on error, true otherwise + */ + virtual bool retrieveDistantSearchResults( + TurtleRequestId searchId, + std::map& results ) = 0; + + /** + * @brief Request distant channel details + * @jsonapi{development} + * An @see RsGxsChannelSearchResultEvent is emitted once details are + * retrieved from the network + * @param[in] groupId if of the group to request to the network + * @return search id + */ + virtual TurtleRequestId turtleGroupRequest(const RsGxsGroupId& groupId) = 0; + + /** + * @brief Retrieve previously requested distant group + * @jsonapi{development} + * @param[in] groupId if of teh group + * @param[out] distantGroup storage for group data + * @return false on error, true otherwise + */ + virtual bool getDistantSearchResultGroupData( + const RsGxsGroupId& groupId, RsGxsChannelGroup& distantGroup ) = 0; + + /** + * @brief Clear accumulated search results + * @jsonapi{development} + * @param[in] reqId search id + * @return false on error, true otherwise + */ + virtual bool clearDistantSearchResults(TurtleRequestId reqId) = 0; + + ~RsGxsChannels() override; + + //////////////////////////////////////////////////////////////////////////// /* Following functions are deprecated and should not be considered a safe to * use API */ @@ -690,22 +717,4 @@ public: */ RS_DEPRECATED_FOR(editChannel) virtual bool updateGroup(uint32_t& token, RsGxsChannelGroup& group) = 0; - - ////////////////////////////////////////////////////////////////////////////// - /// Distant synchronisation methods /// - ////////////////////////////////////////////////////////////////////////////// - /// - RS_DEPRECATED_FOR(turtleChannelRequest) - virtual TurtleRequestId turtleGroupRequest(const RsGxsGroupId& group_id)=0; - RS_DEPRECATED - virtual TurtleRequestId turtleSearchRequest(const std::string& match_string)=0; - RS_DEPRECATED_FOR(turtleSearchRequest) - virtual bool retrieveDistantSearchResults(TurtleRequestId req, std::map &results) =0; - RS_DEPRECATED - virtual bool clearDistantSearchResults(TurtleRequestId req)=0; - RS_DEPRECATED_FOR(turtleChannelRequest) - virtual bool retrieveDistantGroup(const RsGxsGroupId& group_id,RsGxsChannelGroup& distant_group)=0; - ////////////////////////////////////////////////////////////////////////////// - - ~RsGxsChannels() override; }; diff --git a/libretroshare/src/retroshare/rsgxsiface.h b/libretroshare/src/retroshare/rsgxsiface.h index 36bee517e..74e67008f 100644 --- a/libretroshare/src/retroshare/rsgxsiface.h +++ b/libretroshare/src/retroshare/rsgxsiface.h @@ -72,6 +72,46 @@ struct RsGxsGroupSummary : RsSerializable ~RsGxsGroupSummary(); }; +/*! + * This structure is used to locally store group search results for a given service. + * It contains the group information as well as a context + * strings to tell where the information was found. It is more compact than a + * GroupMeta object, so as to make search responses as light as possible. + */ +struct RsGxsGroupSearchResults : RsSerializable +{ + RsGxsGroupSearchResults() + : mPublishTs(0), mNumberOfMessages(0),mLastMessageTs(0), mSignFlags(0),mPopularity(0) + {} + + RsGxsGroupId mGroupId; + std::string mGroupName; + RsGxsId mAuthorId; + rstime_t mPublishTs; + uint32_t mNumberOfMessages; + rstime_t mLastMessageTs; + uint32_t mSignFlags; + uint32_t mPopularity; + + std::set mSearchContexts; + + /// @see RsSerializable::serial_process + void serial_process( RsGenericSerializer::SerializeJob j, + RsGenericSerializer::SerializeContext& ctx ) + { + RS_SERIAL_PROCESS(mGroupId); + RS_SERIAL_PROCESS(mGroupName); + RS_SERIAL_PROCESS(mAuthorId); + RS_SERIAL_PROCESS(mPublishTs); + RS_SERIAL_PROCESS(mNumberOfMessages); + RS_SERIAL_PROCESS(mLastMessageTs); + RS_SERIAL_PROCESS(mSignFlags); + RS_SERIAL_PROCESS(mPopularity); + RS_SERIAL_PROCESS(mSearchContexts); + } + + virtual ~RsGxsGroupSearchResults() = default; +}; /*! * Stores ids of changed gxs groups and messages. diff --git a/libretroshare/src/retroshare/rsgxsifacehelper.h b/libretroshare/src/retroshare/rsgxsifacehelper.h index d2f588623..7db596873 100644 --- a/libretroshare/src/retroshare/rsgxsifacehelper.h +++ b/libretroshare/src/retroshare/rsgxsifacehelper.h @@ -507,25 +507,27 @@ private: std::map mActiveTokens; +#ifdef DEBUG_GXSIFACEHELPER void locked_dumpTokens() { const uint16_t service_id = mGxs.serviceType(); const auto countSize = static_cast(TokenRequestType::__MAX); uint32_t count[countSize] = {0}; - RsDbg() << __PRETTY_FUNCTION__ << "Service 0x" << std::hex << service_id - << " (" << rsServiceControl->getServiceName( - RsServiceInfo::RsServiceInfoUIn16ToFullServiceId(service_id) ) - << ") this=0x" << static_cast(this) - << ") Active tokens (per type): "; + RsDbg rsdbg; + rsdbg << __PRETTY_FUNCTION__ << " Service 0x" << std::hex << service_id + << " (" << rsServiceControl->getServiceName( + RsServiceInfo::RsServiceInfoUIn16ToFullServiceId(service_id) ) + << ") this=0x" << static_cast(this) + << ") Active tokens (per type): "; // let's count how many token of each type we've got. for(auto& it: mActiveTokens) ++count[static_cast(it.second)]; for(uint32_t i=0; i < countSize; ++i) - RsDbg().uStream() /* << i << ":" */ << count[i] << " "; - RsDbg().uStream() << std::endl; + rsdbg /* << i << ":" */ << count[i] << " "; } +#endif // def DEBUG_GXSIFACEHELPER RS_SET_CONTEXT_DEBUG_LEVEL(1) }; diff --git a/libretroshare/src/retroshare/rsidentity.h b/libretroshare/src/retroshare/rsidentity.h index d1345461e..7fa23697a 100644 --- a/libretroshare/src/retroshare/rsidentity.h +++ b/libretroshare/src/retroshare/rsidentity.h @@ -234,31 +234,30 @@ struct RsIdentityUsage : RsSerializable GXS_TUNNEL_DH_SIGNATURE_CHECK = 0x0c, GXS_TUNNEL_DH_SIGNATURE_CREATION = 0x0d, + /// Identity received through GXS sync + IDENTITY_NEW_FROM_GXS_SYNC = 0x0e, /// Group update on that identity data. Can be avatar, name, etc. - IDENTITY_DATA_UPDATE = 0x0e, + IDENTITY_NEW_FROM_DISCOVERY = 0x0f, + /// Explicit request to friend + IDENTITY_NEW_FROM_EXPLICIT_REQUEST = 0x10, /// Any signature verified for that identity - IDENTITY_GENERIC_SIGNATURE_CHECK = 0x0f, + IDENTITY_GENERIC_SIGNATURE_CHECK = 0x11, /// Any signature made by that identity - IDENTITY_GENERIC_SIGNATURE_CREATION = 0x10, + IDENTITY_GENERIC_SIGNATURE_CREATION = 0x12, - IDENTITY_GENERIC_ENCRYPTION = 0x11, - IDENTITY_GENERIC_DECRYPTION = 0x12, - CIRCLE_MEMBERSHIP_CHECK = 0x13 + IDENTITY_GENERIC_ENCRYPTION = 0x13, + IDENTITY_GENERIC_DECRYPTION = 0x14, + CIRCLE_MEMBERSHIP_CHECK = 0x15 } ; - RS_DEPRECATED - RsIdentityUsage( uint16_t service, const RsIdentityUsage::UsageCode& code, - const RsGxsGroupId& gid = RsGxsGroupId(), - const RsGxsMessageId& mid = RsGxsMessageId(), - uint64_t additional_id=0, - const std::string& comment = std::string() ); - RsIdentityUsage( RsServiceType service, RsIdentityUsage::UsageCode code, const RsGxsGroupId& gid = RsGxsGroupId(), - const RsGxsMessageId& mid = RsGxsMessageId(), + const RsGxsMessageId& message_id = RsGxsMessageId(), + const RsGxsMessageId& parent_id = RsGxsMessageId(), + const RsGxsMessageId& thread_id = RsGxsMessageId(), uint64_t additional_id=0, const std::string& comment = std::string() ); @@ -275,6 +274,12 @@ struct RsIdentityUsage : RsSerializable /// Message ID using the identity RsGxsMessageId mMsgId; + /// Reference message ID. Useful for votes/comments + RsGxsMessageId mParentId; + + /// Reference message ID. Useful for votes/comments + RsGxsMessageId mThreadId; + /// Some additional ID. Can be used for e.g. chat lobbies. uint64_t mAdditionalId; diff --git a/libretroshare/src/retroshare/rsinit.h b/libretroshare/src/retroshare/rsinit.h index 165649f16..19c0349bb 100644 --- a/libretroshare/src/retroshare/rsinit.h +++ b/libretroshare/src/retroshare/rsinit.h @@ -20,8 +20,7 @@ *******************************************************************************/ #pragma once -/// RetroShare initialization and login API - +/// @file RetroShare initialization and login API // Initialize ok, result >= 0 #define RS_INIT_OK 0 // Initialize ok @@ -32,11 +31,15 @@ #define RS_INIT_NO_KEYRING -3 // Keyring is empty. Need to import it. #define RS_INIT_NO_EXECUTABLE -4 // executable path hasn't been set in config options -#include #include #include #include -#include +#include +#include + +#include "retroshare/rstypes.h" +#include "retroshare/rsversion.h" + class RsLoginHelper; @@ -46,6 +49,71 @@ class RsLoginHelper; */ extern RsLoginHelper* rsLoginHelper; + +enum class RsInitErrorNum : int32_t +{ + ALREADY_LOGGED_IN = 6000, + CANT_ACQUIRE_LOCK = 6001, + INVALID_LOCATION_NAME = 6002, + PGP_NAME_OR_ID_NEEDED = 6003, + PGP_KEY_CREATION_FAILED = 6004, + SSL_KEY_CREATION_FAILED = 6005, + INVALID_SSL_ID = 6006, + LOGIN_FAILED = 6007 +}; + +struct RsInitErrorCategory: std::error_category +{ + const char* name() const noexcept override + { return "RetroShare init"; } + + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case RsInitErrorNum::ALREADY_LOGGED_IN: + return "Already logged in"; + case RsInitErrorNum::CANT_ACQUIRE_LOCK: + return "Cannot aquire lock on location data. Another instance is " + "already running with this profile?"; + case RsInitErrorNum::INVALID_LOCATION_NAME: + return "Invalid location name"; + case RsInitErrorNum::PGP_NAME_OR_ID_NEEDED: + return "Either PGP name or PGP id is needed"; + case RsInitErrorNum::PGP_KEY_CREATION_FAILED: + return "Failure creating PGP key"; + case RsInitErrorNum::SSL_KEY_CREATION_FAILED: + return "Failure creating SSL key"; + case RsInitErrorNum::INVALID_SSL_ID: + return "Invalid SSL id"; + case RsInitErrorNum::LOGIN_FAILED: + return "Generic login failure"; + default: + return rsErrorNotInCategory(ev, name()); + } + } + + const static RsInitErrorCategory instance; +}; + + +namespace std +{ +/** Register RsJsonApiErrorNum as an error condition enum, must be in std + * namespace */ +template<> struct is_error_condition_enum : true_type {}; +} + +/** Provide RsInitErrorNum conversion to std::error_condition, must be in + * same namespace of RsInitErrorNum */ +inline std::error_condition make_error_condition(RsInitErrorNum e) noexcept +{ + return std::error_condition( + static_cast(e), RsInitErrorCategory::instance ); +}; + + + /** * @brief The RsInitConfig struct * This class contains common configuration options, that executables using libretroshare may want to @@ -85,7 +153,7 @@ struct RsConfigOptions class RsInit { public: - enum LoadCertificateStatus : uint8_t + enum RS_DEPRECATED_FOR(RsInitErrorNum) LoadCertificateStatus : uint8_t { OK, /// Everything go as expected, no error occurred ERR_ALREADY_RUNNING, /// Another istance is running already @@ -317,7 +385,7 @@ public: /** * @brief Normal way to attempt login - * @jsonapi{development,manualwrapper} + * @jsonapi{development,unauthenticated} * @param[in] account Id of the account to which attempt login * @param[in] password Password for the given account * @return RsInit::OK if login attempt success, error code otherwhise @@ -353,6 +421,44 @@ public: void getLocations(std::vector& locations); /** + * @brief Creates a new RetroShare location, and log in once is created + * @jsonapi{development,manualwrapper} + * @param[out] locationId storage for generated location SSL id + * @param[inout] pgpId specify PGP id to use to sign the location, if a null + * id is passed the PGP key is created too and this param is used as + * storage for its id. + * @param[in] password to protect and unlock the associated PGP key + * param[in] apiUser (JSON API only) string containing username for JSON API + * so it can be later used to authenticate JSON API calls. It is passed + * down to @see RsJsonApi::authorizeUser under the hood. + * param[in] apiPass (JSON API only) string containing password for JSON API + * so it can be later used to authenticate JSON API calls. It is passed + * down to @see RsJsonApi::authorizeUser under the hood. + * To improve security we strongly advise to not use the same as the + * password used for the PGP key. + * @return Success or error information + */ + std::error_condition createLocationV2( + RsPeerId& locationId, + RsPgpId& pgpId, + const std::string& locationName, + const std::string& pgpName, + const std::string& password + /* JSON API only + * const std::string& apiUser + * const std::string& apiPass */ ); + + /** + * @brief Check if RetroShare is already logged in, this usually return true + * after a successfull attemptLogin() and before closeSession() + * @jsonapi{development,unauthenticated} + * @return true if already logged in, false otherwise + */ + bool isLoggedIn(); + +#if !RS_VERSION_AT_LEAST(0,6,6) + /** + * @deprecated Use @see createLocationV2 instead * @brief Creates a new RetroShare location, and log in once is created * @jsonapi{development,manualwrapper} * @param[inout] location provide input information to generate the location @@ -365,15 +471,9 @@ public: * Tor hidden location. UNTESTED! * @return true if success, false otherwise */ + RS_DEPRECATED_FOR(createLocationV2) bool createLocation( RsLoginHelper::Location& location, const std::string& password, std::string& errorMessage, bool makeHidden = false, bool makeAutoTor = false ); - - /** - * @brief Check if RetroShare is already logged in, this usually return true - * after a successfull attemptLogin() and before closeSession() - * @jsonapi{development,unauthenticated} - * @return true if already logged in, false otherwise - */ - bool isLoggedIn(); +#endif // !RS_VERSION_AT_LEAST(0,6,6) }; diff --git a/libretroshare/src/retroshare/rsjsonapi.h b/libretroshare/src/retroshare/rsjsonapi.h index 147011787..38e33490e 100644 --- a/libretroshare/src/retroshare/rsjsonapi.h +++ b/libretroshare/src/retroshare/rsjsonapi.h @@ -29,6 +29,7 @@ #include #include +#include "util/rsdebug.h" #include "util/rsmemory.h" class RsJsonApi; @@ -74,8 +75,7 @@ struct RsJsonApiErrorCategory: std::error_category case RsJsonApiErrorNum::NOT_A_MACHINE_GUN: return "Method must not be called in burst"; default: - return "Error message for error: " + std::to_string(ev) + - " not available in category: " + name(); + return rsErrorNotInCategory(ev, name()); } } diff --git a/libretroshare/src/rsitems/rsgxsupdateitems.h b/libretroshare/src/rsitems/rsgxsupdateitems.h index 9e7a194ac..03bd21800 100644 --- a/libretroshare/src/rsitems/rsgxsupdateitems.h +++ b/libretroshare/src/rsitems/rsgxsupdateitems.h @@ -106,12 +106,22 @@ public: RsPeerId peerID; }; +struct RsPeerUpdateTsRecord +{ + RsPeerUpdateTsRecord() : mLastTsReceived(0), mTs(0) {} + + rstime_t mLastTsReceived; // last TS that was sent for this group by this peer ID. + rstime_t mTs; // time at which this TS was sent. +}; + class RsGxsServerGrpUpdate { public: RsGxsServerGrpUpdate() { grpUpdateTS = 0 ; } uint32_t grpUpdateTS; + + std::map grpUpdateTsRecords; }; class RsGxsServerGrpUpdateItem : public RsGxsNetServiceItem, public RsGxsServerGrpUpdate @@ -168,7 +178,13 @@ class RsGxsServerMsgUpdate public: RsGxsServerMsgUpdate() { msgUpdateTS = 0 ;} - uint32_t msgUpdateTS; // local time stamp this group last received a new msg + uint32_t msgUpdateTS; // local time stamp at which this group last received a new msg + + // Now we also store for each peer the last own TS the peer sent and when it did so. This allows to detect when transactions are stuck because of + // outqueues clogging. If that happens, we receive multiple times the same TS from the friend, in which case we do not send the list of msgs + // again until a significant amount of time has passed. These values are obviously initialized to 0. + + std::map msgUpdateTsRecords; }; class RsGxsServerMsgUpdateItem : public RsGxsNetServiceItem, public RsGxsServerMsgUpdate diff --git a/libretroshare/src/rsserver/p3face-server.cc b/libretroshare/src/rsserver/p3face-server.cc index 0ed2f698c..9426b0471 100644 --- a/libretroshare/src/rsserver/p3face-server.cc +++ b/libretroshare/src/rsserver/p3face-server.cc @@ -143,13 +143,14 @@ void RsServer::threadTick() // if there is time left, we sleep double timeToSleep = mTickInterval - mAvgRunDuration; - if (timeToSleep > 0) - { +// never sleep less than 50 ms + if (timeToSleep < 0.050) + timeToSleep = 0.050; + #ifdef TICK_DEBUG - RsDbg() << "TICK_DEBUG will sleep " << timeToSleep << " ms" << std::endl; + RsDbg() << "TICK_DEBUG will sleep " << (int) (1000 * timeToSleep) << " ms" << std::endl; #endif - rstime::rs_usleep(timeToSleep * 1000000); - } + rstime::rs_usleep(timeToSleep * 1000000); double ts = getCurrentTS(); mLastts = ts; @@ -229,12 +230,16 @@ void RsServer::threadTick() // ticking is done, now compute new values of mLastRunDuration, mAvgRunDuration and mTickInterval ts = getCurrentTS(); mLastRunDuration = ts - mLastts; + +// low-pass filter and don't let mAvgRunDuration exceeds maxTickInterval mAvgRunDuration = 0.1 * mLastRunDuration + 0.9 * mAvgRunDuration; + if (mAvgRunDuration > maxTickInterval) + mAvgRunDuration = maxTickInterval; #ifdef TICK_DEBUG RsDbg() << "TICK_DEBUG new mLastRunDuration " << mLastRunDuration << " mAvgRunDuration " << mAvgRunDuration << std::endl; if (mLastRunDuration > WARN_BIG_CYCLE_TIME) - RsDbg() << "TICK_DEBUG excessively long lycle time " << mLastRunDuration << std::endl; + RsDbg() << "TICK_DEBUG excessively long cycle time " << mLastRunDuration << std::endl; #endif // if the core has returned that there is more to tick we decrease the ticking interval, else we increse it @@ -250,7 +255,7 @@ void RsServer::threadTick() RsDbg() << "TICK_DEBUG new tick interval " << mTickInterval << std::endl; #endif -// keep the tick interval within allowed limits +// keep the tick interval target within allowed limits if (mTickInterval < minTickInterval) mTickInterval = minTickInterval; else if (mTickInterval > maxTickInterval) diff --git a/libretroshare/src/rsserver/p3face.h b/libretroshare/src/rsserver/p3face.h index a2637ceaf..661cb244f 100644 --- a/libretroshare/src/rsserver/p3face.h +++ b/libretroshare/src/rsserver/p3face.h @@ -161,7 +161,9 @@ public: p3ChatService *chatSrv; p3StatusService *mStatusSrv; p3GxsTunnelService *mGxsTunnels; +#ifdef RS_USE_I2P_BOB p3I2pBob *mI2pBob; +#endif // This list contains all threaded services. It will be used to shut them down properly. diff --git a/libretroshare/src/rsserver/rsinit.cc b/libretroshare/src/rsserver/rsinit.cc index 25391fcbe..fa21b82e7 100644 --- a/libretroshare/src/rsserver/rsinit.cc +++ b/libretroshare/src/rsserver/rsinit.cc @@ -114,6 +114,8 @@ RsLoginHelper* rsLoginHelper = nullptr; RsAccounts* rsAccounts = nullptr; +const RsInitErrorCategory RsInitErrorCategory::instance; + RsConfigOptions::RsConfigOptions() : autoLogin(false), @@ -921,8 +923,10 @@ int RsServer::StartupRetroShare() mNetMgr->setManagers(mPeerMgr, mLinkMgr); rsAutoProxyMonitor *autoProxy = rsAutoProxyMonitor::instance(); +#ifdef RS_USE_I2P_BOB mI2pBob = new p3I2pBob(mPeerMgr); autoProxy->addProxy(autoProxyType::I2PBOB, mI2pBob); +#endif //load all the SSL certs as friends // std::list sslIds; @@ -1647,7 +1651,9 @@ int RsServer::StartupRetroShare() mConfigMgr->addConfiguration("wire.cfg", wire_ns); #endif #endif //RS_ENABLE_GXS +#ifdef RS_USE_I2P_BOB mConfigMgr->addConfiguration("I2PBOB.cfg", mI2pBob); +#endif mPluginsManager->addConfigurations(mConfigMgr) ; @@ -1722,7 +1728,7 @@ int RsServer::StartupRetroShare() // now enable bob bobSettings bs; autoProxy->taskSync(autoProxyType::I2PBOB, autoProxyTask::getSettings, &bs); - bs.enableBob = true; + bs.enable = true; autoProxy->taskSync(autoProxyType::I2PBOB, autoProxyTask::setSettings, &bs); } else { std::cerr << "RsServer::StartupRetroShare failed to receive keys" << std::endl; @@ -1793,7 +1799,9 @@ int RsServer::StartupRetroShare() /**************************************************************************/ // auto proxy threads +#ifdef RS_USE_I2P_BOB startServiceThread(mI2pBob, "I2P-BOB"); +#endif #ifdef RS_ENABLE_GXS // Must Set the GXS pointers before starting threads. @@ -1950,6 +1958,47 @@ void RsLoginHelper::getLocations(std::vector& store) } } +std::error_condition RsLoginHelper::createLocationV2( + RsPeerId& locationId, RsPgpId& pgpId, + const std::string& locationName, const std::string& pgpName, + const std::string& password ) +{ + if(isLoggedIn()) return RsInitErrorNum::ALREADY_LOGGED_IN; + if(locationName.empty()) return RsInitErrorNum::INVALID_LOCATION_NAME; + if(pgpId.isNull() && pgpName.empty()) + return RsInitErrorNum::PGP_NAME_OR_ID_NEEDED; + + std::string errorMessage; + if(pgpId.isNull() && !RsAccounts::GeneratePGPCertificate( + pgpName, "", password, pgpId, 4096, errorMessage ) ) + { + RS_ERR("Failure creating PGP key: ", errorMessage); + return RsInitErrorNum::PGP_KEY_CREATION_FAILED; + } + + std::string sslPassword = + RsRandom::random_alphaNumericString(RsInit::getSslPwdLen()); + + rsNotify->cachePgpPassphrase(password); + rsNotify->setDisableAskPassword(true); + + bool ret = RsAccounts::createNewAccount( + pgpId, "", locationName, "", false, false, sslPassword, + locationId, errorMessage ); + if(!ret) + { + RS_ERR("Failure creating SSL key: ", errorMessage); + return RsInitErrorNum::SSL_KEY_CREATION_FAILED; + } + + RsInit::LoadPassword(sslPassword); + ret = (RsInit::OK == attemptLogin(locationId, password)); + rsNotify->setDisableAskPassword(false); + + return (ret ? std::error_condition() : RsInitErrorNum::LOGIN_FAILED); +} + +#if !RS_VERSION_AT_LEAST(0,6,6) bool RsLoginHelper::createLocation( RsLoginHelper::Location& l, const std::string& password, std::string& errorMessage, bool makeHidden, bool makeAutoTor ) @@ -1991,6 +2040,7 @@ bool RsLoginHelper::createLocation( rsNotify->setDisableAskPassword(false); return ret; } +#endif // !RS_VERSION_AT_LEAST(0,6,6) bool RsLoginHelper::isLoggedIn() { diff --git a/libretroshare/src/rsserver/rsloginhandler.cc b/libretroshare/src/rsserver/rsloginhandler.cc index 8a0469e1d..dbe023235 100644 --- a/libretroshare/src/rsserver/rsloginhandler.cc +++ b/libretroshare/src/rsserver/rsloginhandler.cc @@ -26,6 +26,7 @@ #include "rsloginhandler.h" #include "util/rsdir.h" #include "retroshare/rsinit.h" +#include "util/rsdebug.h" //#define DEBUG_RSLOGINHANDLER 1 @@ -497,8 +498,15 @@ bool RsLoginHandler::enableAutoLogin(const RsPeerId& ssl_id,const std::string& s NULL); if (error) { + RsErr() << __PRETTY_FUNCTION__ + << " Could not store passwd using libsecret with" + << " error.code=" << error->code + << " error.domain=" << error->domain + << " error.message=\"" << error->message << "\"" << std::endl; + if (error->code == 2) + RsErr() << "Do have a key wallet installed?" << std::endl + << "Like gnome-keyring or other using \"Secret Service\" by DBus." << std::endl; g_error_free (error); - std::cerr << "Could not store passwd using libsecret" << std::endl; return false; } std::cout << "Stored passwd " << "************************" << " using libsecret" << std::endl; diff --git a/libretroshare/src/serialiser/rstypeserializer.cc b/libretroshare/src/serialiser/rstypeserializer.cc index 7cf799d0f..0c7bfc435 100644 --- a/libretroshare/src/serialiser/rstypeserializer.cc +++ b/libretroshare/src/serialiser/rstypeserializer.cc @@ -235,8 +235,7 @@ template<> bool RsTypeSerializer::from_JSON( \ \ if(!ret) \ { \ - Dbg3() << __PRETTY_FUNCTION__ << " " << memberName << " not found" \ - << std::endl; \ + RS_DBG3(memberName, " not found"); \ return false; \ } \ \ diff --git a/libretroshare/src/serialiser/rstypeserializer.h b/libretroshare/src/serialiser/rstypeserializer.h index 3c8af510e..6474f6971 100644 --- a/libretroshare/src/serialiser/rstypeserializer.h +++ b/libretroshare/src/serialiser/rstypeserializer.h @@ -39,7 +39,7 @@ #include "serialiser/rsserializer.h" #include "serialiser/rsserializable.h" #include "util/rsjson.h" -#include "util/rsdebug.h" +#include "util/rsdebuglevel1.h" #include "util/cxx14retrocompat.h" @@ -715,12 +715,9 @@ struct RsTypeSerializer E& member, const std::string& memberName ) { -#ifdef RSSERIAL_DEBUG - std::cerr << __PRETTY_FUNCTION__ << " processing enum: " - << typeid(E).name() << " as " - << typeid(typename std::underlying_type::type).name() - << std::endl; -#endif + RS_DBG4( "processing enum: ", typeid(E).name(), " as ", + typeid(typename std::underlying_type::type).name() ); + serial_process( j, ctx, reinterpret_cast::type&>(member), @@ -1004,14 +1001,16 @@ protected: uint8_t data[], uint32_t size, uint32_t &offset, T member ) { std::decay_t backupMember = member; +#if RS_DEBUG_LEVEL >= 3 uint32_t offsetBackup = offset; +#endif bool ok = true; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wbool-compare" /* Check with < and not with <= here as we write last byte after * the loop. Order of && operands very important here! */ - while(member > 127 && (ok = offset < size)) + while(member > 127 && (ok = (offset < size))) { // | 128: Set the next byte flag data[offset++] = (static_cast(member & 127)) | 128; @@ -1036,13 +1035,13 @@ protected: data[offset++] = static_cast(member & 127); - Dbg3() << __PRETTY_FUNCTION__ << " backupMember: " << backupMember - << " offsetBackup: " << offsetBackup << " offeset: " << offset - << " serialized as: "; +#if RS_DEBUG_LEVEL >= 3 + RsDbg tdbg( __PRETTY_FUNCTION__, " backupMember: ", backupMember, + " offsetBackup: ", offsetBackup, " offeset: ", offset, + " serialized as: " ); for(; offsetBackup < offset; ++offsetBackup) - Dbg3().uStream() << " " << std::bitset<8>(data[offsetBackup]); - Dbg3().uStream() << std::endl; - + tdbg << " " << std::bitset<8>(data[offsetBackup]); +#endif return ok; } @@ -1082,13 +1081,13 @@ protected: /* If return is not triggered inside the for loop, either the buffer * ended before we encountered the end of the number, or the number * is VLQ encoded improperly */ - RsErr() << __PRETTY_FUNCTION__ << std::errc::illegal_byte_sequence - << " size: " << size - << " offsetBackup: " << offsetBackup - << " offset: " << offset << " bytes: "; + RsErr rserr; + rserr << __PRETTY_FUNCTION__ << std::errc::illegal_byte_sequence + << " size: " << size + << " offsetBackup: " << offsetBackup + << " offset: " << offset << " bytes: "; for(; offsetBackup < offset; ++offsetBackup) - RsErr().uStream() << " " << std::bitset<8>(data[offsetBackup]); - RsErr().uStream() << std::endl; + rserr << " " << std::bitset<8>(data[offsetBackup]); print_stacktrace(); return false; @@ -1151,7 +1150,7 @@ protected: struct ErrConditionWrapper : RsSerializable { - ErrConditionWrapper(const std::error_condition& ec): mec(ec) {} + explicit ErrConditionWrapper(const std::error_condition& ec): mec(ec) {} /** supports only TO_JSON if a different SerializeJob is passed it will * explode at runtime */ diff --git a/libretroshare/src/services/autoproxy/p3i2pbob.cc b/libretroshare/src/services/autoproxy/p3i2pbob.cc index 693570ac2..f9eb3d9b3 100644 --- a/libretroshare/src/services/autoproxy/p3i2pbob.cc +++ b/libretroshare/src/services/autoproxy/p3i2pbob.cc @@ -43,21 +43,14 @@ static const std::string kConfigKeyOutLength = "OUT_LENGTH"; static const std::string kConfigKeyOutQuantity = "OUT_QUANTITY"; static const std::string kConfigKeyOutVariance = "OUT_VARIANCE"; -static const bool kDefaultBOBEnable = false; -static const int8_t kDefaultLength = 3; -static const int8_t kDefaultQuantity = 4; -static const int8_t kDefaultVariance = 0; - -/// Sleep duration for receiving loop -static const useconds_t sleepTimeRecv = 10; // times 1000 = 10ms +/// Sleep duration for receiving loop in error/no-data case +static const useconds_t sleepTimeRecv = 250; // times 1000 = 250ms /// Sleep duration for everything else static const useconds_t sleepTimeWait = 50; // times 1000 = 50ms or 0.05s static const int sleepFactorDefault = 10; // 0.5s static const int sleepFactorFast = 1; // 0.05s static const int sleepFactorSlow = 20; // 1s -static struct RsLog::logInfo i2pBobLogInfo = {RsLog::Default, "p3I2pBob"}; - static const rstime_t selfCheckPeroid = 30; void doSleep(useconds_t timeToSleepMS) { @@ -74,15 +67,7 @@ p3I2pBob::p3I2pBob(p3PeerMgr *peerMgr) mProcessing(NULL), mLock("I2P-BOB") { // set defaults - mSetting.enableBob = kDefaultBOBEnable; - mSetting.keys = ""; - mSetting.addr = ""; - mSetting.inLength = kDefaultLength; - mSetting.inQuantity = kDefaultQuantity; - mSetting.inVariance = kDefaultVariance; - mSetting.outLength = kDefaultLength; - mSetting.outQuantity = kDefaultQuantity; - mSetting.outVariance = kDefaultVariance; + mSetting.initDefault(); mCommands.clear(); } @@ -90,12 +75,12 @@ p3I2pBob::p3I2pBob(p3PeerMgr *peerMgr) bool p3I2pBob::isEnabled() { RS_STACK_MUTEX(mLock); - return mSetting.enableBob; + return mSetting.enable; } bool p3I2pBob::initialSetup(std::string &addr, uint16_t &/*port*/) { - std::cout << "p3I2pBob::initialSetup" << std::endl; + RS_DBG(""); // update config { @@ -108,7 +93,7 @@ bool p3I2pBob::initialSetup(std::string &addr, uint16_t &/*port*/) } } - std::cout << "p3I2pBob::initialSetup config updated" << std::endl; + RS_DBG("config updated"); // request keys // p3I2pBob::stateMachineBOB expects mProcessing to be set therefore @@ -118,12 +103,12 @@ bool p3I2pBob::initialSetup(std::string &addr, uint16_t &/*port*/) fakeTicket->task = autoProxyTask::receiveKey; processTaskAsync(fakeTicket); - std::cout << "p3I2pBob::initialSetup fakeTicket requested" << std::endl; + RS_DBG("fakeTicket requested"); // now start thread start("I2P-BOB gen key"); - std::cout << "p3I2pBob::initialSetup thread started" << std::endl; + RS_DBG("thread started"); int counter = 0; // wait for keys @@ -137,24 +122,24 @@ bool p3I2pBob::initialSetup(std::string &addr, uint16_t &/*port*/) break; if (++counter > 30) { - std::cout << "p3I2pBob::initialSetup timeout!" << std::endl; + RS_DBG4("timeout!"); return false; } } - std::cout << "p3I2pBob::initialSetup got keys" << std::endl; + RS_DBG("got keys"); // stop thread fullstop(); - std::cout << "p3I2pBob::initialSetup thread stopped" << std::endl; + RS_DBG("thread stopped"); { RS_STACK_MUTEX(mLock); - addr = mSetting.addr; + addr = mSetting.address.base32; } - std::cout << "p3I2pBob::initialSetup addr '" << addr << "'" << std::endl; + RS_DBG4("addr ", addr); return true; } @@ -172,7 +157,7 @@ void p3I2pBob::processTaskAsync(taskTicket *ticket) } break; default: - rslog(RsLog::Warning, &i2pBobLogInfo, "p3I2pBob::processTaskAsync unknown task"); + RS_DBG("unknown task"); rsAutoProxyMonitor::taskError(ticket); break; } @@ -187,7 +172,7 @@ void p3I2pBob::processTaskSync(taskTicket *ticket) case autoProxyTask::status: // check if everything needed is set if (!data) { - rslog(RsLog::Warning, &i2pBobLogInfo, "p3I2pBob::status autoProxyTask::status data is missing"); + RS_DBG("autoProxyTask::status data is missing"); rsAutoProxyMonitor::taskError(ticket); break; } @@ -201,7 +186,7 @@ void p3I2pBob::processTaskSync(taskTicket *ticket) case autoProxyTask::getSettings: // check if everything needed is set if (!data) { - rslog(RsLog::Warning, &i2pBobLogInfo, "p3I2pBob::data_tick autoProxyTask::getSettings data is missing"); + RS_DBG("autoProxyTask::getSettings data is missing"); rsAutoProxyMonitor::taskError(ticket); break; } @@ -215,7 +200,7 @@ void p3I2pBob::processTaskSync(taskTicket *ticket) case autoProxyTask::setSettings: // check if everything needed is set if (!data) { - rslog(RsLog::Warning, &i2pBobLogInfo, "p3I2pBob::data_tick autoProxyTask::setSettings data is missing"); + RS_DBG("autoProxyTask::setSettings data is missing"); rsAutoProxyMonitor::taskError(ticket); break; } @@ -235,7 +220,7 @@ void p3I2pBob::processTaskSync(taskTicket *ticket) break; case autoProxyTask::getErrorInfo: if (!data) { - rslog(RsLog::Warning, &i2pBobLogInfo, "p3I2pBob::data_tick autoProxyTask::getErrorInfo data is missing"); + RS_DBG("autoProxyTask::getErrorInfo data is missing"); rsAutoProxyMonitor::taskError(ticket); } else { RS_STACK_MUTEX(mLock); @@ -244,34 +229,12 @@ void p3I2pBob::processTaskSync(taskTicket *ticket) } break; default: - rslog(RsLog::Warning, &i2pBobLogInfo, "p3I2pBob::processTaskSync unknown task"); + RS_DBG("unknown task"); rsAutoProxyMonitor::taskError(ticket); break; } } -std::string p3I2pBob::keyToBase32Addr(const std::string &key) -{ - std::string copy(key); - - // replace I2P specific chars - std::replace(copy.begin(), copy.end(), '~', '/'); - std::replace(copy.begin(), copy.end(), '-', '+'); - - // decode - std::vector bin = Radix64::decode(copy); - // hash - std::vector sha256 = RsUtil::BinToSha256(bin); - // encode - std::string out = Radix32::encode(sha256); - - // i2p uses lowercase - std::transform(out.begin(), out.end(), out.begin(), ::tolower); - out.append(".b32.i2p"); - - return out; -} - bool inline isAnswerOk(const std::string &answer) { return (answer.compare(0, 2, "OK") == 0); } @@ -284,10 +247,8 @@ void p3I2pBob::threadTick() { int sleepTime = 0; { - RS_STACK_MUTEX(mLock); - std::stringstream ss; - ss << "data_tick mState: " << mState << " mTask: " << mTask << " mBOBState: " << mBOBState << " mPending: " << mPending.size(); - rslog(RsLog::Debug_All, &i2pBobLogInfo, ss.str()); + RS_STACK_MUTEX(mLock); + RS_DBG4("data_tick mState: ", mState, " mTask: ", mTask, " mBOBState: ", mBOBState, " mPending: ", mPending.size()); } sleepTime += stateMachineController(); @@ -326,15 +287,13 @@ int p3I2pBob::stateMachineBOB() if (mBOBState == bsList) { int counter = 0; while (answer.find("OK Listing done") == std::string::npos) { - std::stringstream ss; - ss << "stateMachineBOB status check: read loop, counter: " << counter; - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, ss.str()); + RS_DBG3("stateMachineBOB status check: read loop, counter: ", counter); answer += recv(); counter++; } if (answer.find(mTunnelName) == std::string::npos) { - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineBOB status check: tunnel down!"); + RS_DBG("status check: tunnel down!"); // signal error *((bool *)mProcessing->data) = true; } @@ -346,12 +305,12 @@ int p3I2pBob::stateMachineBOB() switch (mBOBState) { case bsNewkeysN: key = answer.substr(3, answer.length()-3); - mSetting.addr = keyToBase32Addr(key); + mSetting.address.base32 = i2p::keyToBase32Addr(key); IndicateConfigChanged(); break; case bsGetkeys: key = answer.substr(3, answer.length()-3); - mSetting.keys = key; + mSetting.address.privateKey = key; IndicateConfigChanged(); break; default: @@ -374,8 +333,8 @@ int p3I2pBob::stateMachineBOB_locked_failure(const std::string &answer, const bo return sleepFactorDefault; } - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineBOB FAILED to run command '" + currentState.command + "'"); - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineBOB '" + answer + "'"); + RS_DBG("FAILED to run command: ", currentState.command); + RS_DBG("answer: ", answer); mErrorMsg.append("FAILED to run command '" + currentState.command + "'" + '\n'); mErrorMsg.append("reason '" + answer + "'" + '\n'); @@ -422,14 +381,14 @@ int p3I2pBob::stateMachineController() return stateMachineController_locked_idle(); case csDoConnect: if (!connectI2P()) { - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController doConnect: unable to connect"); + RS_DBG("doConnect: unable to connect"); mStateOld = mState; mState = csError; mErrorMsg = "unable to connect to BOB port"; return sleepFactorSlow; } - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController doConnect: connected"); + RS_DBG4("doConnect: connected"); mState = csConnected; break; case csConnected: @@ -437,7 +396,7 @@ int p3I2pBob::stateMachineController() case csWaitForBob: // check connection problems if (mSocket == 0) { - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController waitForBob: conection lost"); + RS_DBG("waitForBob: conection lost"); mStateOld = mState; mState = csError; mErrorMsg = "connection lost to BOB"; @@ -447,21 +406,21 @@ int p3I2pBob::stateMachineController() // check for finished BOB protocol if (mBOBState == bsCleared) { // done - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController waitForBob: mBOBState == bsCleared"); + RS_DBG4("waitForBob: mBOBState == bsCleared"); mState = csDoDisconnect; } break; case csDoDisconnect: if (!disconnectI2P() || mSocket != 0) { // just in case - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController doDisconnect: can't disconnect"); + RS_DBG("doDisconnect: can't disconnect"); mStateOld = mState; mState = csError; mErrorMsg = "unable to disconnect from BOB"; return sleepFactorDefault; } - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController doDisconnect: disconnected"); + RS_DBG4("doDisconnect: disconnected"); mState = csDisconnected; break; case csDisconnected: @@ -487,12 +446,12 @@ int p3I2pBob::stateMachineController_locked_idle() mProcessing = mPending.front(); mPending.pop(); - if (!mSetting.enableBob && ( + if (!mSetting.enable && ( mProcessing->task == autoProxyTask::start || mProcessing->task == autoProxyTask::stop || mProcessing->task == autoProxyTask::proxyStatusCheck)) { // skip since we are not enabled - rslog(RsLog::Debug_Alert, &i2pBobLogInfo, "stateMachineController_locked_idle: disabled -> skipping ticket"); + RS_DBG1("disabled -> skipping ticket"); rsAutoProxyMonitor::taskDone(mProcessing, autoProxyStatus::disabled); mProcessing = NULL; } else { @@ -514,7 +473,7 @@ int p3I2pBob::stateMachineController_locked_idle() mTask = ctRunCheck; break; default: - rslog(RsLog::Debug_Alert, &i2pBobLogInfo, "stateMachineController_locked_idle unknown async task"); + RS_DBG1("unknown async task"); rsAutoProxyMonitor::taskError(mProcessing); mProcessing = NULL; break; @@ -561,29 +520,29 @@ int p3I2pBob::stateMachineController_locked_connected() switch (mTask) { case ctRunSetUp: // when we have a key use it for server tunnel! - if(mSetting.keys.empty()) { - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_connected: setting mBOBState = setnickC"); + if(mSetting.address.privateKey.empty()) { + RS_DBG4("setting mBOBState = setnickC"); mBOBState = bsSetnickC; } else { - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_connected: setting mBOBState = setnickS"); + RS_DBG4("setting mBOBState = setnickS"); mBOBState = bsSetnickS; } break; case ctRunShutDown: // shut down existing tunnel - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_connected: setting mBOBState = getnick"); + RS_DBG4("setting mBOBState = getnick"); mBOBState = bsGetnick; break; case ctRunCheck: - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_connected: setting mBOBState = list"); + RS_DBG4("setting mBOBState = list"); mBOBState = bsList; break; case ctRunGetKeys: - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_connected: setting mBOBState = setnickN"); + RS_DBG4("setting mBOBState = setnickN"); mBOBState = bsSetnickN; break; case ctIdle: - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController_locked_connected: task is idle. This should not happen!"); + RS_DBG("task is idle. This should not happen!"); break; } @@ -599,7 +558,7 @@ int p3I2pBob::stateMachineController_locked_disconnected() if(errorHappened) { // reset old state mStateOld = csIdel; - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController_locked_disconnected: error during process!"); + RS_DBG("error during process!"); } // answer ticket @@ -628,12 +587,12 @@ int p3I2pBob::stateMachineController_locked_disconnected() mTask = mTaskOld; if (!errorHappened) { - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_disconnected: run check result: ok"); + RS_DBG4("run check result: ok"); break; } // switch to error newState = csError; - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController_locked_disconnected: run check result: error"); + RS_DBG("run check result: error"); mErrorMsg = "Connection check failed. Will try to restart tunnel."; break; @@ -656,7 +615,7 @@ int p3I2pBob::stateMachineController_locked_disconnected() mTask = mTaskOld; break; case ctIdle: - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController_locked_disconnected: task is idle. This should not happen!"); + RS_DBG("task is idle. This should not happen!"); rsAutoProxyMonitor::taskError(mProcessing); } mProcessing = NULL; @@ -672,14 +631,12 @@ int p3I2pBob::stateMachineController_locked_error() { // wait for bob protocoll if (mBOBState != bsCleared) { - rslog(RsLog::Debug_All, &i2pBobLogInfo, "stateMachineController_locked_error: waiting for BOB"); + RS_DBG4("waiting for BOB"); return sleepFactorFast; } #if 0 - std::stringstream ss; - ss << "stateMachineController_locked_error: mProcessing: " << (mProcessing ? "not null" : "null"); - rslog(RsLog::Debug_All, &i2pBobLogInfo, ss.str()); + RS_DBG4("stateMachineController_locked_error: mProcessing: ", (mProcessing ? "not null" : "null")); #endif // try to finish ticket @@ -687,7 +644,7 @@ int p3I2pBob::stateMachineController_locked_error() switch (mTask) { case ctRunCheck: // connection check failed at some point - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController_locked_error: failed to check proxy status (it's likely dead)!"); + RS_DBG("failed to check proxy status (it's likely dead)!"); *((bool *)mProcessing->data) = true; mState = csDoDisconnect; mStateOld = csIdel; @@ -695,7 +652,7 @@ int p3I2pBob::stateMachineController_locked_error() break; case ctRunShutDown: // not a big deal though - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController_locked_error: failed to shut down tunnel (it's likely dead though)!"); + RS_DBG("failed to shut down tunnel (it's likely dead though)!"); mState = csDoDisconnect; mStateOld = csIdel; mErrorMsg.clear(); @@ -703,14 +660,14 @@ int p3I2pBob::stateMachineController_locked_error() case ctIdle: // should not happen but we need to deal with it // this will produce some error messages in the log and finish the task (marked as failed) - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController_locked_error: task is idle. This should not happen!"); + RS_DBG("task is idle. This should not happen!"); mState = csDoDisconnect; mStateOld = csIdel; mErrorMsg.clear(); break; case ctRunGetKeys: case ctRunSetUp: - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController_locked_error: failed to receive key / start up"); + RS_DBG("failed to receive key / start up"); mStateOld = csError; mState = csDoDisconnect; // keep the error message @@ -721,7 +678,7 @@ int p3I2pBob::stateMachineController_locked_error() // periodically retry if (mLastProxyCheck < time(NULL) - (selfCheckPeroid >> 1) && mTask == ctRunSetUp) { - rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController_locked_error: retrying"); + RS_DBG("retrying"); mLastProxyCheck = time(NULL); mErrorMsg.clear(); @@ -734,7 +691,7 @@ int p3I2pBob::stateMachineController_locked_error() // check for new tickets if (!mPending.empty()) { - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_error: processing new ticket"); + RS_DBG4("processing new ticket"); // reset and try new task mTask = ctIdle; @@ -765,16 +722,16 @@ RsSerialiser *p3I2pBob::setupSerialiser() bool p3I2pBob::saveList(bool &cleanup, std::list &lst) { - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "saveList"); + RS_DBG4(""); cleanup = true; RsConfigKeyValueSet *vitem = new RsConfigKeyValueSet; RsTlvKeyValue kv; RS_STACK_MUTEX(mLock); - addKVS(vitem, kv, kConfigKeyBOBEnable, mSetting.enableBob ? "TRUE" : "FALSE") - addKVS(vitem, kv, kConfigKeyBOBKey, mSetting.keys) - addKVS(vitem, kv, kConfigKeyBOBAddr, mSetting.addr) + addKVS(vitem, kv, kConfigKeyBOBEnable, mSetting.enable ? "TRUE" : "FALSE") + addKVS(vitem, kv, kConfigKeyBOBKey, mSetting.address.privateKey) + addKVS(vitem, kv, kConfigKeyBOBAddr, mSetting.address.base32) addKVSInt(vitem, kv, kConfigKeyInLength, mSetting.inLength) addKVSInt(vitem, kv, kConfigKeyInQuantity, mSetting.inQuantity) addKVSInt(vitem, kv, kConfigKeyInVariance, mSetting.inVariance) @@ -800,7 +757,7 @@ bool p3I2pBob::saveList(bool &cleanup, std::list &lst) bool p3I2pBob::loadList(std::list &load) { - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "loadList"); + RS_DBG4(""); for(std::list::const_iterator it = load.begin(); it!=load.end(); ++it) { RsConfigKeyValueSet *vitem = dynamic_cast(*it); @@ -808,11 +765,11 @@ bool p3I2pBob::loadList(std::list &load) RS_STACK_MUTEX(mLock); for(std::list::const_iterator kit = vitem->tlvkvs.pairs.begin(); kit != vitem->tlvkvs.pairs.end(); ++kit) { if (kit->key == kConfigKeyBOBEnable) - mSetting.enableBob = kit->value == "TRUE"; + mSetting.enable = kit->value == "TRUE"; else if (kit->key == kConfigKeyBOBKey) - mSetting.keys = kit->value; + mSetting.address.privateKey = kit->value; else if (kit->key == kConfigKeyBOBAddr) - mSetting.addr = kit->value; + mSetting.address.base32 = kit->value; getKVSUInt(kit, kConfigKeyInLength, mSetting.inLength) getKVSUInt(kit, kConfigKeyInQuantity, mSetting.inQuantity) getKVSUInt(kit, kConfigKeyInVariance, mSetting.inVariance) @@ -820,7 +777,7 @@ bool p3I2pBob::loadList(std::list &load) getKVSUInt(kit, kConfigKeyOutQuantity, mSetting.outQuantity) getKVSUInt(kit, kConfigKeyOutVariance, mSetting.outVariance) else - rslog(RsLog::Warning, &i2pBobLogInfo, "loadList unknown key: " + kit->key); + RS_DBG("unknown key: ", kit->key); } } delete vitem; @@ -884,7 +841,7 @@ void p3I2pBob::getStates(bobStates *bs) std::string p3I2pBob::executeCommand(const std::string &command) { - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "executeCommand_locked running '" + command + "'"); + RS_DBG4("running: ", command); std::string copy = command; copy.push_back('\n'); @@ -896,7 +853,7 @@ std::string p3I2pBob::executeCommand(const std::string &command) // receive answer (trailing new line is already removed!) std::string ans = recv(); - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "executeCommand_locked answer '" + ans + "'"); + RS_DBG4("answer: ", ans); return ans; } @@ -906,7 +863,7 @@ bool p3I2pBob::connectI2P() // there is only one thread that touches mSocket - no need for a lock if (mSocket != 0) { - rslog(RsLog::Warning, &i2pBobLogInfo, "connectI2P_locked mSocket != 0"); + RS_DBG("mSocket != 0"); return false; } @@ -914,21 +871,21 @@ bool p3I2pBob::connectI2P() mSocket = unix_socket(PF_INET, SOCK_STREAM, 0); if (mSocket < 0) { - rslog(RsLog::Warning, &i2pBobLogInfo, "connectI2P_locked Failed to open socket! Socket Error: " + socket_errorType(errno)); + RS_DBG("Failed to open socket! Socket Error: ", socket_errorType(errno)); return false; } // connect int err = unix_connect(mSocket, mI2PProxyAddr); if (err != 0) { - rslog(RsLog::Warning, &i2pBobLogInfo, "connectI2P_locked Failed to connect to BOB! Socket Error: " + socket_errorType(errno)); + RS_DBG("Failed to connect to BOB! Socket Error: ", socket_errorType(errno)); return false; } // receive hello msg recv(); - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "connectI2P_locked done"); + RS_DBG4("done"); return true; } @@ -937,17 +894,17 @@ bool p3I2pBob::disconnectI2P() // there is only one thread that touches mSocket - no need for a lock if (mSocket == 0) { - rslog(RsLog::Warning, &i2pBobLogInfo, "disconnectI2P_locked mSocket == 0"); + RS_DBG("mSocket == 0"); return true; } int err = unix_close(mSocket); if (err != 0) { - rslog(RsLog::Warning, &i2pBobLogInfo, "disconnectI2P_locked Failed to close socket! Socket Error: " + socket_errorType(errno)); + RS_DBG("Failed to close socket! Socket Error: ", socket_errorType(errno)); return false; } - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "disconnectI2P_locked done"); + RS_DBG4("done"); mSocket = 0; return true; } @@ -968,7 +925,7 @@ std::string toString(const std::string &a, const int8_t b) { void p3I2pBob::finalizeSettings_locked() { - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "finalizeSettings_locked"); + RS_DBG4(""); sockaddr_storage_clear(mI2PProxyAddr); // get i2p proxy addr @@ -979,8 +936,8 @@ void p3I2pBob::finalizeSettings_locked() sockaddr_storage_setipv4(mI2PProxyAddr, (sockaddr_in*)&proxy); sockaddr_storage_setport(mI2PProxyAddr, 2827); - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "finalizeSettings_locked using " + sockaddr_storage_tostring(mI2PProxyAddr)); - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "finalizeSettings_locked using " + mSetting.addr); + RS_DBG4("using ", mI2PProxyAddr); + RS_DBG4("using ", mSetting.address.base32); peerState ps; mPeerMgr->getOwnNetStatus(ps); @@ -988,21 +945,17 @@ void p3I2pBob::finalizeSettings_locked() // setup commands // new lines are appended later! - // generate random suffix for name - // RSRandom::random_alphaNumericString can return very weird looking strings like: ,,@z+M - // use base32 instead - size_t len = 5; // 5 characters = 8 base32 symbols - std::vector tmp(len); - RSRandom::random_bytes(tmp.data(), len); - const std::string location = Radix32::encode(tmp.data(), len); - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "finalizeSettings_locked using suffix " + location); + // generate 8 characater long random suffix for name + constexpr size_t len = 8; + const std::string location = RsRandom::alphaNumeric(len); + RS_DBG4("using suffix ", location); mTunnelName = "RetroShare-" + location; const std::string setnick = "setnick RetroShare-" + location; const std::string getnick = "getnick RetroShare-" + location; const std::string newkeys = "newkeys"; const std::string getkeys = "getkeys"; - const std::string setkeys = "setkeys " + mSetting.keys; + const std::string setkeys = "setkeys " + mSetting.address.privateKey; const std::string inhost = "inhost " + sockaddr_storage_iptostring(proxy); const std::string inport = toString("inport ", sockaddr_storage_port(proxy)); const std::string outhost = "outhost " + sockaddr_storage_iptostring(ps.localaddr); @@ -1063,7 +1016,7 @@ void p3I2pBob::finalizeSettings_locked() void p3I2pBob::updateSettings_locked() { - rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "updateSettings_locked"); + RS_DBG4(""); sockaddr_storage proxy; mPeerMgr->getProxyServerAddress(RS_HIDDEN_TYPE_I2P, proxy); @@ -1071,7 +1024,7 @@ void p3I2pBob::updateSettings_locked() peerState ps; mPeerMgr->getOwnNetStatus(ps); - const std::string setkeys = "setkeys " + mSetting.keys; + const std::string setkeys = "setkeys " + mSetting.address.privateKey; const std::string inhost = "inhost " + sockaddr_storage_iptostring(proxy); const std::string inport = toString("inport ", sockaddr_storage_port(proxy)); const std::string outhost = "outhost " + sockaddr_storage_iptostring(ps.localaddr); @@ -1103,38 +1056,62 @@ void p3I2pBob::updateSettings_locked() std::string p3I2pBob::recv() { + // BOB works line based + // -> \n indicates and of the line + + constexpr uint16_t bufferSize = 128; + char buffer[bufferSize]; + std::string ans; - ssize_t length; - const uint16_t bufferSize = 128; - std::vector buffer(bufferSize); + uint16_t retry = 10; do { - doSleep(sleepTimeRecv); + memset(buffer, 0, bufferSize); - // there is only one thread that touches mSocket - no need for a lock - length = ::recv(mSocket, buffer.data(), buffer.size(), 0); - if (length < 0) + // peek at data + auto length = ::recv(mSocket, buffer, bufferSize, MSG_PEEK); + if (length <= 0) { + if (length < 0) { + // error + perror(__PRETTY_FUNCTION__); + } + retry--; + doSleep(sleepTimeRecv); continue; + } - ans.append(buffer.begin(), buffer.end()); + // at least one byte was read - // clean received string - ans.erase(std::remove(ans.begin(), ans.end(), '\0'), ans.end()); - ans.erase(std::remove(ans.begin(), ans.end(), '\n'), ans.end()); + // search for new line + auto bufferStr = std::string(buffer); + size_t pos = bufferStr.find('\n'); -#if 0 - std::stringstream ss; - ss << "recv length: " << length << " (bufferSize: " << bufferSize << ") ans: " << ans.length(); - rslog(RsLog::Debug_All, &i2pBobLogInfo, ss.str()); -#endif + if (pos == std::string::npos) { + // no new line found -> more to read - // clear and resize buffer again - buffer.clear(); - buffer.resize(bufferSize); + // sanity check + if (length != bufferSize) { + // expectation: a full buffer was peeked) + RS_DBG1("peeked less than bufferSize but also didn't found a new line character"); + } + // this should never happen + assert(length <= bufferSize); + } else { + // new line found -> end of message - if (this->shouldStop()) - break; - } while(length == bufferSize || ans.size() < 4); + // calculate how much there is to read, read the \n, too! + length = pos + 1; + + // end loop + retry = 0; + } + + // now read for real + memset(buffer, 0, bufferSize); + length = ::recv(mSocket, buffer, length, 0); + bufferStr = std::string(buffer); + ans.append(bufferStr); + } while(retry > 0); return ans; } diff --git a/libretroshare/src/services/autoproxy/p3i2pbob.h b/libretroshare/src/services/autoproxy/p3i2pbob.h index e6ef60bae..29bcbcb61 100644 --- a/libretroshare/src/services/autoproxy/p3i2pbob.h +++ b/libretroshare/src/services/autoproxy/p3i2pbob.h @@ -30,9 +30,10 @@ #include #endif +#include "pqi/p3cfgmgr.h" #include "services/autoproxy/rsautoproxymonitor.h" #include "util/rsthreads.h" -#include "pqi/p3cfgmgr.h" +#include "util/i2pcommon.h" /* * This class implements I2P BOB (BASIC OPEN BRIDGE) communication to allow RS @@ -49,7 +50,7 @@ * * Note 3: * BOB needs a unique name as an ID for each tunnel. - * We use 'RetroShare-' + 8 base32 characters. + * We use 'RetroShare-' + 8 random base32 characters. * * Design: * The service uses three state machines to manage its task: @@ -72,7 +73,7 @@ * mCommands[bobState::quit] = {quit, bobState::cleared}; * * stateMachineController: - * This state machone manages the high level tasks. + * This state machine manages the high level tasks. * It is controlled by mState and mTask. * * mTast: @@ -162,19 +163,7 @@ struct bobStateInfo { bobState nextState; }; -struct bobSettings { - bool enableBob; ///< This field is used by the pqi subsystem to determinine whether SOCKS proxy or BOB is used for I2P connections - std::string keys; ///< (optional) server keys - std::string addr; ///< (optional) hidden service addr. in base32 form - - int8_t inLength; - int8_t inQuantity; - int8_t inVariance; - - int8_t outLength; - int8_t outQuantity; - int8_t outVariance; -}; +struct bobSettings : i2p::settings {}; /// /// \brief The bobStates struct @@ -203,8 +192,6 @@ public: void processTaskAsync(taskTicket *ticket); void processTaskSync(taskTicket *ticket); - static std::string keyToBase32Addr(const std::string &key); - void threadTick() override; /// @see RsTickingThread private: diff --git a/libretroshare/src/services/autoproxy/rsautoproxymonitor.cc b/libretroshare/src/services/autoproxy/rsautoproxymonitor.cc index d198bd207..d58c871e3 100644 --- a/libretroshare/src/services/autoproxy/rsautoproxymonitor.cc +++ b/libretroshare/src/services/autoproxy/rsautoproxymonitor.cc @@ -22,6 +22,7 @@ #include "rsautoproxymonitor.h" #include /* for usleep() */ +#include "util/rsdebug.h" #include "util/rstime.h" rsAutoProxyMonitor *rsAutoProxyMonitor::mInstance = NULL; @@ -42,8 +43,10 @@ rsAutoProxyMonitor *rsAutoProxyMonitor::instance() void rsAutoProxyMonitor::addProxy(autoProxyType::autoProxyType_enum type, autoProxyService *service) { RS_STACK_MUTEX(mLock); - if (mProxies.find(type) != mProxies.end()) - std::cerr << "sAutoProxyMonitor::addProxy type " << type << " already added - OVERWRITING" << std::endl; + if (mProxies.find(type) != mProxies.end()) { + RS_ERR("type ", type, " already added - OVERWRITING"); + print_stacktrace(); + } mProxies[type] = service; } @@ -117,7 +120,7 @@ void rsAutoProxyMonitor::stopAllRSShutdown() do { rstime::rs_usleep(1000 * 1000); RS_STACK_MUTEX(mLock); - std::cout << "(II) waiting for auto proxy service(s) to shut down " << t << "/" << timeout << " (remaining: " << mProxies.size() << ")" << std::endl; + RS_DBG("waiting for auto proxy service(s) to shut down ", t, "/", timeout, " (remaining: ", mProxies.size(), ")"); if (mProxies.empty()) break; t++; @@ -146,13 +149,16 @@ void rsAutoProxyMonitor::task(taskTicket *ticket) { // sanity checks if (!ticket->async && ticket->types.size() > 1) { - std::cerr << "(WW) rsAutoProxyMonitor::task synchronous call to multiple services. This can cause problems!" << std::endl; + RS_ERR("synchronous call to multiple services. This can cause problems!"); + print_stacktrace(); } if (ticket->async && !ticket->cb && ticket->data) { - std::cerr << "(WW) rsAutoProxyMonitor::task asynchronous call with data but no callback. This will likely causes memory leak!" << std::endl; + RS_ERR("asynchronous call with data but no callback. This will likely causes memory leak!"); + print_stacktrace(); } if (ticket->types.size() > 1 && ticket->data) { - std::cerr << "(WW) rsAutoProxyMonitor::task call with data to multiple services. This will likely causes memory leak!" << std::endl; + RS_ERR("call with data to multiple services. This will likely causes memory leak!"); + print_stacktrace(); } std::vector::const_iterator it; @@ -168,7 +174,11 @@ void rsAutoProxyMonitor::task(taskTicket *ticket) *tt = *ticket; tt->types.clear(); tt->types.push_back(*it); - s->processTaskAsync(tt); + + // it's async! + RsThread::async([s, tt] { + s->processTaskAsync(tt); + }); } else { s->processTaskSync(ticket); } @@ -187,7 +197,8 @@ void rsAutoProxyMonitor::taskAsync(std::vector if (isAsyncTask(task)) { // Usually the services will reject this ticket. // Just print a warning - maybe there is some special case where this is a good idea. - std::cerr << "(WW) rsAutoProxyMonitor::taskSync called with an asynchronous task!" << std::endl; + RS_ERR("called with an asynchronous task!"); + print_stacktrace(); } taskTicket *tt = getTicket(); @@ -244,7 +256,8 @@ void rsAutoProxyMonitor::taskDone(taskTicket *t, autoProxyStatus::autoProxyStatu t->cb->taskFinished(t); if (t != NULL) { // callack did not clean up properly - std::cerr << "(WW) rsAutoProxyMonitor::taskFinish callback did not clean up!" << std::endl; + RS_ERR("callback did not clean up!"); + print_stacktrace(); cleanUp = true; } } else if (t->async){ @@ -252,12 +265,13 @@ void rsAutoProxyMonitor::taskDone(taskTicket *t, autoProxyStatus::autoProxyStatu // we must take care of deleting cleanUp = true; if(t->data) - std::cerr << "(WW) rsAutoProxyMonitor::taskFinish async call with data attached but no callback set!" << std::endl; + RS_ERR("async call with data attached but no callback set!"); } if (cleanUp) { if (t->data) { - std::cerr << "(WW) rsAutoProxyMonitor::taskFinish will try to delete void pointer!" << std::endl; + RS_ERR("will try to delete void pointer!"); + print_stacktrace(); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdelete-incomplete" delete t->data; @@ -290,7 +304,8 @@ void rsAutoProxyMonitor::taskFinished(taskTicket *&ticket) // clean up if (ticket->data) { - std::cerr << "rsAutoProxyMonitor::taskFinished data set. Will try to delete void pointer" << std::endl; + RS_ERR(" data set. Will try to delete void pointer"); + print_stacktrace(); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdelete-incomplete" delete ticket->data; @@ -308,7 +323,7 @@ autoProxyService *rsAutoProxyMonitor::lookUpService(autoProxyType::autoProxyType if ((itService = mProxies.find(t)) != mProxies.end()) { return itService->second; } - std::cerr << "sAutoProxyMonitor::lookUpService no service for type " << t << " found!" << std::endl; + RS_DBG("no service for type ", t, " found!"); return NULL; } diff --git a/libretroshare/src/services/p3gxschannels.cc b/libretroshare/src/services/p3gxschannels.cc index 2a699e662..bdb1f9981 100644 --- a/libretroshare/src/services/p3gxschannels.cc +++ b/libretroshare/src/services/p3gxschannels.cc @@ -80,12 +80,15 @@ p3GxsChannels::p3GxsChannels( RS_SERVICE_GXS_TYPE_CHANNELS, gixs, channelsAuthenPolicy() ), RsGxsChannels(static_cast(*this)), GxsTokenQueue(this), mSubscribedGroupsMutex("GXS channels subscribed groups cache"), - mKnownChannelsMutex("GXS channels known channels timestamp cache"), + mKnownChannelsMutex("GXS channels known channels timestamp cache") +#ifdef TO_REMOVE mSearchCallbacksMapMutex("GXS channels search callbacks map"), mDistantChannelsCallbacksMapMutex("GXS channels distant channels callbacks map") +#endif { // For Dummy Msgs. mGenActive = false; + mLastDistantSearchNotificationTS = 0; mCommentService = new p3GxsCommentService(this, RS_SERVICE_GXS_TYPE_CHANNELS); RsTickEvent::schedule_in(CHANNEL_PROCESS, 0); @@ -352,18 +355,6 @@ void p3GxsChannels::notifyChanges(std::vector &changes) } - RsGxsDistantSearchResultChange *dsrChange = dynamic_cast(*it); - - if(dsrChange && rsEvents) - { - auto ev = std::make_shared(); - ev->mChannelGroupId = dsrChange->mGroupId; - ev->mChannelEventCode = RsChannelEventCode::RECEIVED_DISTANT_SEARCH_RESULT; - ev->mDistantSearchRequestId = dsrChange->mRequestId; - - rsEvents->postEvent(ev); - } - /* shouldn't need to worry about groups - as they need to be subscribed to */ delete *it; } @@ -383,17 +374,32 @@ void p3GxsChannels::notifyChanges(std::vector &changes) void p3GxsChannels::service_tick() { static rstime_t last_dummy_tick = 0; + rstime_t now = time(NULL); if (time(NULL) > last_dummy_tick + 5) { dummy_tick(); - last_dummy_tick = time(NULL); + last_dummy_tick = now; } RsTickEvent::tick_events(); GxsTokenQueue::checkRequests(); mCommentService->comment_tick(); + + // Notify distant search results, not more than once per sec. Normally we should + // rather send one item for all, but that needs another class type + + if(now > mLastDistantSearchNotificationTS+2 && !mSearchResultsToNotify.empty()) + { + auto ev = std::make_shared(); + ev->mSearchResultsMap = mSearchResultsToNotify; + + mLastDistantSearchNotificationTS = now; + mSearchResultsToNotify.clear(); + + rsEvents->postEvent(ev); + } } bool p3GxsChannels::getGroupData(const uint32_t &token, std::vector &groups) @@ -2210,7 +2216,9 @@ void p3GxsChannels::dummy_tick() } +#ifdef TO_REMOVE cleanTimedOutCallbacks(); +#endif } @@ -2389,18 +2397,18 @@ bool p3GxsChannels::clearDistantSearchResults(TurtleRequestId req) { return netService()->clearDistantSearchResults(req); } -bool p3GxsChannels::retrieveDistantSearchResults(TurtleRequestId req,std::map& results) +bool p3GxsChannels::retrieveDistantSearchResults(TurtleRequestId req,std::map& results) { return netService()->retrieveDistantSearchResults(req,results); } -bool p3GxsChannels::retrieveDistantGroup(const RsGxsGroupId& group_id,RsGxsChannelGroup& distant_group) +bool p3GxsChannels::getDistantSearchResultGroupData(const RsGxsGroupId& group_id,RsGxsChannelGroup& distant_group) { - RsGxsGroupSummary gs; + RsGxsGroupSearchResults gs; if(netService()->retrieveDistantGroupSummary(group_id,gs)) { - // This is a placeholder information by the time we receive the full group meta data. + // This is a placeholder information by the time we receive the full group meta data and check the signature. distant_group.mMeta.mGroupId = gs.mGroupId ; distant_group.mMeta.mGroupName = gs.mGroupName; distant_group.mMeta.mGroupFlags = GXS_SERV::FLAG_PRIVACY_PUBLIC ; @@ -2426,6 +2434,7 @@ bool p3GxsChannels::retrieveDistantGroup(const RsGxsGroupId& group_id,RsGxsChann return false ; } +#ifdef TO_REMOVE bool p3GxsChannels::turtleSearchRequest( const std::string& matchString, const std::function& multiCallback, @@ -2505,17 +2514,24 @@ bool p3GxsChannels::localSearchRequest( return true; } +#endif -void p3GxsChannels::receiveDistantSearchResults( - TurtleRequestId id, const RsGxsGroupId& grpId ) +void p3GxsChannels::receiveDistantSearchResults( TurtleRequestId id, const RsGxsGroupId& grpId ) { - std::cerr << __PRETTY_FUNCTION__ << "(" << id << ", " << grpId << ")" - << std::endl; + if(!rsEvents) + return; + + // We temporise here, in order to avoid notifying clients with many events + // So we put some data in there and will send an event with all of them at once every 1 sec at most. + + mSearchResultsToNotify[id].insert(grpId); + +#ifdef TO_REMOVE + std::cerr << __PRETTY_FUNCTION__ << "(" << id << ", " << grpId << ")" << std::endl; { RsGenExchange::receiveDistantSearchResults(id, grpId); - RsGxsGroupSummary gs; - gs.mGroupId = grpId; + RsGxsGroupSearchResults gs; netService()->retrieveDistantGroupSummary(grpId, gs); { @@ -2556,8 +2572,10 @@ void p3GxsChannels::receiveDistantSearchResults( return; } } // RS_STACK_MUTEX(mDistantChannelsCallbacksMapMutex); +#endif } +#ifdef TO_REMOVE void p3GxsChannels::cleanTimedOutCallbacks() { auto now = std::chrono::system_clock::now(); @@ -2586,6 +2604,7 @@ void p3GxsChannels::cleanTimedOutCallbacks() else ++cbpt; } // RS_STACK_MUTEX(mDistantChannelsCallbacksMapMutex) } +#endif bool p3GxsChannels::exportChannelLink( std::string& link, const RsGxsGroupId& chanId, bool includeGxsData, diff --git a/libretroshare/src/services/p3gxschannels.h b/libretroshare/src/services/p3gxschannels.h index e6917e252..da274690b 100644 --- a/libretroshare/src/services/p3gxschannels.h +++ b/libretroshare/src/services/p3gxschannels.h @@ -68,9 +68,9 @@ protected: virtual TurtleRequestId turtleGroupRequest(const RsGxsGroupId& group_id); virtual TurtleRequestId turtleSearchRequest(const std::string& match_string); - virtual bool retrieveDistantSearchResults(TurtleRequestId req, std::map &results) ; + virtual bool retrieveDistantSearchResults(TurtleRequestId req, std::map &results) ; virtual bool clearDistantSearchResults(TurtleRequestId req); - virtual bool retrieveDistantGroup(const RsGxsGroupId& group_id,RsGxsChannelGroup& distant_group); + virtual bool getDistantSearchResultGroupData(const RsGxsGroupId& group_id,RsGxsChannelGroup& distant_group); // Overloaded to cache new groups. virtual RsGenExchange::ServiceCreate_Return service_CreateGroup(RsGxsGrpItem* grpItem, RsTlvSecurityKeySet& keySet); @@ -109,6 +109,7 @@ virtual bool getChannelAutoDownload(const RsGxsGroupId &groupid, bool& enabled); virtual bool setChannelDownloadDirectory(const RsGxsGroupId &groupId, const std::string& directory); virtual bool getChannelDownloadDirectory(const RsGxsGroupId &groupId, std::string& directory); +#ifdef TO_REMOVE /// @see RsGxsChannels::turtleSearchRequest virtual bool turtleSearchRequest(const std::string& matchString, const std::function& multiCallback, @@ -124,6 +125,7 @@ virtual bool getChannelDownloadDirectory(const RsGxsGroupId &groupId, std::strin virtual bool localSearchRequest(const std::string& matchString, const std::function& multiCallback, rstime_t maxWait = 30 ) override; +#endif /** * Receive results from turtle search @see RsGenExchange @see RsNxsObserver @@ -374,6 +376,9 @@ bool generateGroup(uint32_t &token, std::string groupName); std::map mKnownChannels; RsMutex mKnownChannelsMutex; + rstime_t mLastDistantSearchNotificationTS; + std::map > mSearchResultsToNotify; +#ifdef TO_REMOVE /** Store search callbacks with timeout*/ std::map< TurtleRequestId, @@ -394,4 +399,5 @@ bool generateGroup(uint32_t &token, std::string groupName); /// Cleanup mSearchCallbacksMap and mDistantChannelsCallbacksMap void cleanTimedOutCallbacks(); +#endif }; diff --git a/libretroshare/src/services/p3gxscircles.cc b/libretroshare/src/services/p3gxscircles.cc index 92b0c669e..80f5fa290 100644 --- a/libretroshare/src/services/p3gxscircles.cc +++ b/libretroshare/src/services/p3gxscircles.cc @@ -1463,12 +1463,14 @@ bool p3GxsCircles::locked_processLoadingCacheEntry(RsGxsCircleCache& cache) } else { +#ifdef DEBUG_CIRCLES std::cerr << " (WW) cache entry for circle " << cache.mCircleId << " has empty originator. Asking info for GXS id " << pit->first << " to all connected friends." << std::endl; +#endif rsPeers->getOnlineList(peers) ; } - mIdentities->requestKey(pit->first, peers,RsIdentityUsage(serviceType(),RsIdentityUsage::CIRCLE_MEMBERSHIP_CHECK,RsGxsGroupId(cache.mCircleId))); + mIdentities->requestKey(pit->first, peers,RsIdentityUsage(RsServiceType::GXSCIRCLE,RsIdentityUsage::CIRCLE_MEMBERSHIP_CHECK,RsGxsGroupId(cache.mCircleId))); all_ids_here = false; } @@ -1997,7 +1999,9 @@ bool p3GxsCircles::processMembershipRequests(uint32_t token) // now do another sweep and remove all msgs that are older than the latest +#ifdef DEBUG_CIRCLES std::cerr << " Cleaning old messages..." << std::endl; +#endif for(uint32_t i=0;isecond.size();++i) { diff --git a/libretroshare/src/services/p3idservice.cc b/libretroshare/src/services/p3idservice.cc index 3ee089a97..609dd8ad6 100644 --- a/libretroshare/src/services/p3idservice.cc +++ b/libretroshare/src/services/p3idservice.cc @@ -55,7 +55,7 @@ #define ID_REQUEST_REPUTATION 0x0003 #define ID_REQUEST_OPINION 0x0004 -#define GXSID_MAX_CACHE_SIZE 5000 +#define GXSID_MAX_CACHE_SIZE 15000 // unused keys are deleted according to some heuristic that should favor known keys, signed keys etc. @@ -616,11 +616,11 @@ void p3IdService::notifyChanges(std::vector &changes) std::cerr << "p3IdService::notifyChanges() Found Group Change Notification"; std::cerr << std::endl; #endif + const RsGxsGroupId& gid(groupChange->mGroupId); #ifdef DEBUG_IDS - std::cerr << "p3IdService::notifyChanges() Auto Subscribe to Incoming Groups: " << *git; + std::cerr << "p3IdService::notifyChanges() Auto Subscribe to Incoming Groups: " << gid; std::cerr << std::endl; #endif - const RsGxsGroupId& gid(groupChange->mGroupId); if(!rsReputations->isIdentityBanned(RsGxsId(gid))) { @@ -641,7 +641,7 @@ void p3IdService::notifyChanges(std::vector &changes) rsEvents->postEvent(ev); // also time_stamp the key that this group represents - timeStampKey(RsGxsId(gid),RsIdentityUsage(serviceType(),RsIdentityUsage::IDENTITY_DATA_UPDATE)) ; + timeStampKey(RsGxsId(gid),RsIdentityUsage(RsServiceType(serviceType()),RsIdentityUsage::IDENTITY_NEW_FROM_GXS_SYNC)) ; should_subscribe = true; } break; @@ -654,8 +654,10 @@ void p3IdService::notifyChanges(std::vector &changes) rsEvents->postEvent(ev); // also time_stamp the key that this group represents - timeStampKey(RsGxsId(gid),RsIdentityUsage(serviceType(),RsIdentityUsage::IDENTITY_DATA_UPDATE)) ; + timeStampKey(RsGxsId(gid),RsIdentityUsage(RsServiceType(serviceType()),RsIdentityUsage::IDENTITY_NEW_FROM_GXS_SYNC)) ; should_subscribe = true; + + std::cerr << "Received new identity " << gid << " and subscribing to it" << std::endl; } break; @@ -1214,8 +1216,7 @@ bool p3IdService::requestIdentity( return false; } - RsIdentityUsage usageInfo( RsServiceType::GXSID, - RsIdentityUsage::IDENTITY_DATA_UPDATE ); + RsIdentityUsage usageInfo( RsServiceType::GXSID, RsIdentityUsage::IDENTITY_NEW_FROM_EXPLICIT_REQUEST ); return requestKey(id, askPeersList, usageInfo); } @@ -1250,8 +1251,9 @@ bool p3IdService::requestKey(const RsGxsId &id, const std::list& peers if( info.mOverallReputationLevel == RsReputationLevel::LOCALLY_NEGATIVE ) { - RsInfo() << __PRETTY_FUNCTION__ << " not requesting Key " << id - << " because it has been banned." << std::endl; +#ifdef DEBUG_IDS + RsInfo() << __PRETTY_FUNCTION__ << " not requesting Key " << id << " because it has been banned." << std::endl; +#endif RS_STACK_MUTEX(mIdMtx); mIdsNotPresent.erase(id); @@ -1360,7 +1362,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,RsIdentityUsage(serviceType(),RsIdentityUsage::IDENTITY_GENERIC_SIGNATURE_CREATION)) ; + timeStampKey(own_gxs_id,RsIdentityUsage(RsServiceType(serviceType()),RsIdentityUsage::IDENTITY_GENERIC_SIGNATURE_CREATION)) ; return true ; } @@ -1433,7 +1435,7 @@ bool p3IdService::encryptData( const uint8_t *decrypted_data, return false ; } error_status = RS_GIXS_ERROR_NO_ERROR ; - timeStampKey(encryption_key_id,RsIdentityUsage(serviceType(),RsIdentityUsage::IDENTITY_GENERIC_ENCRYPTION)) ; + timeStampKey(encryption_key_id,RsIdentityUsage(RsServiceType::GXSID,RsIdentityUsage::IDENTITY_GENERIC_ENCRYPTION)) ; return true ; } @@ -1522,7 +1524,7 @@ bool p3IdService::encryptData( const uint8_t* decrypted_data, { timeStampKey( *it, RsIdentityUsage( - serviceType(), + RsServiceType::GXSID, RsIdentityUsage::IDENTITY_GENERIC_ENCRYPTION ) ); } @@ -1563,7 +1565,7 @@ bool p3IdService::decryptData( const uint8_t *encrypted_data, error_status = RS_GIXS_ERROR_NO_ERROR; timeStampKey( key_id, RsIdentityUsage( - serviceType(), + RsServiceType::GXSID, RsIdentityUsage::IDENTITY_GENERIC_DECRYPTION) ); return true ; @@ -1656,7 +1658,7 @@ bool p3IdService::decryptData( const uint8_t* encrypted_data, { timeStampKey( *it, RsIdentityUsage( - serviceType(), + RsServiceType::GXSID, RsIdentityUsage::IDENTITY_GENERIC_DECRYPTION ) ); } @@ -2292,7 +2294,7 @@ bool SSGxsIdGroup::load(const std::string &input) char scorestr[RSGXSID_MAX_SERVICE_STRING]; // split into parts. - if (3 != sscanf(input.c_str(), "v2 {P:%[^}]} {T:%[^}]} {R:%[^}]}", pgpstr, recognstr, scorestr)) + if (3 != sscanf(input.c_str(), "v2 {P:%[^}]}{T:%[^}]}{R:%[^}]}", pgpstr, recognstr, scorestr)) { #ifdef DEBUG_IDS std::cerr << "SSGxsIdGroup::load() Failed to extract 4 Parts"; @@ -2954,9 +2956,21 @@ void p3IdService::requestIdsFromNet() for(cit = mIdsNotPresent.begin(); cit != mIdsNotPresent.end();) { #ifdef DEBUG_IDS - Dbg2() << __PRETTY_FUNCTION__ << " Processing missing key RsGxsId: " - << cit->first << std::endl; + Dbg2() << __PRETTY_FUNCTION__ << " Processing missing key RsGxsId: " << cit->first << std::endl; #endif + RsGxsIdCache data; + + if(mKeyCache.fetch(cit->first,data)) + { +#ifdef DEBUG_IDS + std::cerr << __PRETTY_FUNCTION__ << ". Dropping request for ID " << cit->first << " at last minute, because it was found in cache"<< std::endl; +#endif + auto tmp(cit); + ++tmp; + mIdsNotPresent.erase(cit); + cit = tmp; + continue; + } const RsGxsId& gxsId = cit->first; const std::list& peers = cit->second; @@ -3011,8 +3025,7 @@ void p3IdService::requestIdsFromNet() { const RsPeerId& peer = cit2->first; std::list grpIds; - for( std::list::const_iterator gxs_id_it = cit2->second.begin(); - gxs_id_it != cit2->second.end(); ++gxs_id_it ) + for( std::list::const_iterator gxs_id_it = cit2->second.begin(); gxs_id_it != cit2->second.end(); ++gxs_id_it ) { #ifdef DEBUG_IDS Dbg2() << __PRETTY_FUNCTION__ << " passing RsGxsId: " << *gxs_id_it @@ -3223,7 +3236,7 @@ bool p3IdService::cachetest_handlerequest(uint32_t token) if (!haveKey(*vit)) { std::list nullpeers; - requestKey(*vit, nullpeers,RsIdentityUsage(serviceType(),RsIdentityUsage::UNKNOWN_USAGE)); + requestKey(*vit, nullpeers,RsIdentityUsage(RsServiceType::GXSID,RsIdentityUsage::UNKNOWN_USAGE)); #ifdef DEBUG_IDS std::cerr << "p3IdService::cachetest_request() Requested Key Id: " << *vit; @@ -4819,11 +4832,10 @@ void RsGxsIdGroup::serial_process( RS_SERIAL_PROCESS(mReputation); } -RsIdentityUsage::RsIdentityUsage( - RsServiceType service, RsIdentityUsage::UsageCode code, - const RsGxsGroupId& gid, const RsGxsMessageId& mid, +RsIdentityUsage::RsIdentityUsage(RsServiceType service, RsIdentityUsage::UsageCode code, + const RsGxsGroupId& gid, const RsGxsMessageId& mid, const RsGxsMessageId &pid, const RsGxsMessageId &tid, uint64_t additional_id, const std::string& comment ) : - mServiceId(service), mUsageCode(code), mGrpId(gid), mMsgId(mid), + mServiceId(service), mUsageCode(code), mGrpId(gid), mMsgId(mid),mParentId(pid),mThreadId(tid), mAdditionalId(additional_id), mComment(comment) { /* This is a hack, since it will hash also mHash, but because it is @@ -4841,42 +4853,6 @@ RsIdentityUsage::RsIdentityUsage( mHash = hs.hash(); } -RsIdentityUsage::RsIdentityUsage( - uint16_t service, const RsIdentityUsage::UsageCode& code, - const RsGxsGroupId& gid, const RsGxsMessageId& mid, - uint64_t additional_id,const std::string& comment ) : - mServiceId(static_cast(service)), mUsageCode(code), - mGrpId(gid), mMsgId(mid), mAdditionalId(additional_id), mComment(comment) -{ -#ifdef DEBUG_IDS - std::cerr << "New identity usage: " << std::endl; - std::cerr << " service=" << std::hex << service << std::endl; - std::cerr << " code =" << std::hex << code << std::endl; - std::cerr << " grpId =" << std::hex << gid << std::endl; - std::cerr << " msgId =" << std::hex << mid << std::endl; - std::cerr << " add id =" << std::hex << additional_id << std::endl; - std::cerr << " commnt =\"" << std::hex << comment << "\"" << std::endl; -#endif - - /* This is a hack, since it will hash also mHash, but because it is - * initialized to 0, and only computed in the constructor here, it should - * be ok. */ - librs::crypto::HashStream hs(librs::crypto::HashStream::SHA1) ; - - hs << (uint32_t)service ; // G10h4ck: Why uint32 if it's 16 bits? - hs << (uint8_t)code ; - hs << gid ; - hs << mid ; - hs << (uint64_t)additional_id ; - hs << comment ; - - mHash = hs.hash(); - -#ifdef DEBUG_IDS - std::cerr << " hash =\"" << std::hex << mHash << "\"" << std::endl; -#endif -} - RsIdentityUsage::RsIdentityUsage() : mServiceId(RsServiceType::NONE), mUsageCode(UNKNOWN_USAGE), mAdditionalId(0) {} diff --git a/libretroshare/src/use_libretroshare.pri b/libretroshare/src/use_libretroshare.pri index 681805a7f..eb7a4e470 100644 --- a/libretroshare/src/use_libretroshare.pri +++ b/libretroshare/src/use_libretroshare.pri @@ -102,3 +102,8 @@ android-* { CONFIG *= qt QT *= network } + +################################### Pkg-Config Stuff ############################# + +LIBS *= $$system(pkg-config --libs $$PKGCONFIG) + diff --git a/libretroshare/src/util/i2pcommon.cpp b/libretroshare/src/util/i2pcommon.cpp new file mode 100644 index 000000000..ec2ebfd6b --- /dev/null +++ b/libretroshare/src/util/i2pcommon.cpp @@ -0,0 +1,163 @@ +#include "i2pcommon.h" + +#include "util/rsbase64.h" +#include "util/rsdebug.h" + +namespace i2p { + +std::string keyToBase32Addr(const std::string &key) +{ + std::string copy(key); + + // replace I2P specific chars + std::replace(copy.begin(), copy.end(), '~', '/'); + // replacing the - with a + is not necessary, as RsBase64 can handle base64url encoding, too + // std::replace(copy.begin(), copy.end(), '-', '+'); + + // decode + std::vector bin; + RsBase64::decode(copy, bin); + + // hash + std::vector sha256 = RsUtil::BinToSha256(bin); + // encode + std::string out = Radix32::encode(sha256); + + // i2p uses lowercase + std::transform(out.begin(), out.end(), out.begin(), ::tolower); + out.append(".b32.i2p"); + + return out; +} + +const std::string makeOption(const std::string &lhs, const int8_t &rhs) { + return lhs + "=" + std::to_string(rhs); +} + +uint16_t readTwoBytesBE(std::vector::const_iterator &p) +{ + uint16_t val = 0; + val += *p++; + val <<= 8; + val += *p++; + return val; +} + +std::string publicKeyFromPrivate(std::string const &priv) +{ + /* + * https://geti2p.net/spec/common-structures#destination + * https://geti2p.net/spec/common-structures#keysandcert + * https://geti2p.net/spec/common-structures#certificate + */ + if (priv.length() < 884) // base64 ( = 663 bytes = KeyCert + priv Keys) + return std::string(); + + // creat a copy to work on, need to convert it to standard base64 + auto priv_copy(priv); + std::replace(priv_copy.begin(), priv_copy.end(), '~', '/'); + // replacing the - with a + is not necessary, as RsBase64 can handle base64url encoding, too + // std::replace(copy.begin(), copy.end(), '-', '+'); + + // get raw data + std::vector dataPriv; + RsBase64::decode(priv_copy, dataPriv); + + auto p = dataPriv.cbegin(); + RS_DBG("dataPriv.size ", dataPriv.size()); + + size_t publicKeyLen = 256 + 128; // default length (bytes) + uint8_t certType = 0; + uint16_t len = 0; + uint16_t signingKeyType = 0; + uint16_t cryptKey = 0; + + // only used for easy break + do { + try { + // jump to certificate + p += publicKeyLen; + // try to read type and length + certType = *p++; + len = readTwoBytesBE(p); + + // only 0 and 5 are used / valid at this point + // check for == 0 + if (certType == static_cast::type>(CertType::Null)) { + /* + * CertType.Null + * type null is followed by 0x00 0x00 + * so has to be 0! + */ + RS_DBG("cert is CertType.Null"); + publicKeyLen += 3; // add 0x00 0x00 0x00 + + if (len != 0) + // weird + RS_DBG("cert is CertType.Null but len != 0"); + + break; + } + + // check for != 5 + if (certType != static_cast::type>(CertType::Key)) { + // unsupported + RS_DBG("cert type ", certType, " is unsupported"); + return std::string(); + } + + RS_DBG("cert is CertType.Key"); + publicKeyLen += 7; // = 1 + 2 + 2 + 2 = 7 bytes + + /* + * "Key certificates were introduced in release 0.9.12. Prior to that release, all PublicKeys were 256-byte ElGamal keys, and all SigningPublicKeys were 128-byte DSA-SHA1 keys." + * --> there is space for 256+128 bytes, longer keys are splitted and appended to the certificate + * We don't need to bother with the splitting here as only the lenght is important! + */ + + // Signing Public Key + // likely 7 + signingKeyType = readTwoBytesBE(p); + + RS_DBG("signing pubkey type ", certType); + if (signingKeyType >= 3 && signingKeyType <= 6) { + RS_DBG("signing pubkey type ", certType, " has oversize"); + // calculate oversize + + if (signingKeyType >= signingKeyLengths.size()) { + // just in case + RS_DBG("signing pubkey type ", certType, " cannot be found in size data!"); + return std::string(); + } + + auto values = signingKeyLengths[signingKeyType]; + if (values.first <= 128) { + // just in case, it's supposed to be larger! + RS_DBG("signing pubkey type ", certType, " is oversize but size calculation would underflow!"); + return std::string(); + } + + publicKeyLen += values.first - 128; // 128 = default DSA key length = the space than can be used before the key must be splitted + } + + // Crypto Public Key + // likely 0 + cryptKey = readTwoBytesBE(p); + RS_DBG("crypto pubkey type ", cryptKey); + // info: these are all smaller than the default 256 bytes, so no oversize calculation is needed + + break; + } catch (const std::out_of_range &e) { + RS_DBG("hit exception! ", e.what()); + return std::string(); + } + } while(false); + + std::string pub; + auto data2 = std::vector(dataPriv.cbegin(), dataPriv.cbegin() + publicKeyLen); + RsBase64::encode(data2.data(), data2.size(), pub, false, false); + + return pub; +} + +} // namespace i2p diff --git a/libretroshare/src/util/i2pcommon.h b/libretroshare/src/util/i2pcommon.h new file mode 100644 index 000000000..1fd152079 --- /dev/null +++ b/libretroshare/src/util/i2pcommon.h @@ -0,0 +1,213 @@ +#ifndef I2PCOMMON_H +#define I2PCOMMON_H + +#include +#include +#include +#include + +#include "util/rsrandom.h" +#include "util/radix32.h" +#include "util/rsbase64.h" +#include "util/rsprint.h" +#include "util/rsdebug.h" + +/* + * This header provides common code for i2p related code, namely BOB and SAM3 support. + */ + +namespace i2p { + +static constexpr int8_t kDefaultLength = 3; // i2p default +static constexpr int8_t kDefaultQuantity = 3; // i2p default + 1 +static constexpr int8_t kDefaultVariance = 0; +static constexpr int8_t kDefaultBackupQuantity = 0; + +/** + * @brief The address struct + * This structure is a container for any i2p address/key. The public key is used for addressing and can be (optionally) hashed to generate the .b32.i2p address. + */ +struct address { + std::string base32; + std::string publicKey; + std::string privateKey; + + void clear() { + base32.clear(); + publicKey.clear(); + privateKey.clear(); + } +}; + +/** + * @brief The settings struct + * Common structure with all settings that are shared between any i2p backends + */ +struct settings { + bool enable; + struct address address; + + // connection parameter + int8_t inLength; + int8_t inQuantity; + int8_t inVariance; + int8_t inBackupQuantity; + + int8_t outLength; + int8_t outQuantity; + int8_t outVariance; + int8_t outBackupQuantity; + + void initDefault() { + enable = false; + address.clear(); + + inLength = kDefaultLength; + inQuantity = kDefaultQuantity; + inVariance = kDefaultVariance; + inBackupQuantity = kDefaultBackupQuantity; + + outLength = kDefaultLength; + outQuantity = kDefaultQuantity; + outVariance = kDefaultVariance; + outBackupQuantity = kDefaultBackupQuantity; + } +}; + +/* + Type Type Code Payload Length Total Length Notes + Null 0 0 3 + HashCash 1 varies varies Experimental, unused. Payload contains an ASCII colon-separated hashcash string. + Hidden 2 0 3 Experimental, unused. Hidden routers generally do not announce that they are hidden. + Signed 3 40 or 72 43 or 75 Experimental, unused. Payload contains a 40-byte DSA signature, optionally followed by the 32-byte Hash of the signing Destination. + Multiple 4 varies varies Experimental, unused. Payload contains multiple certificates. + Key 5 4+ 7+ Since 0.9.12. See below for details. +*/ +enum class CertType : uint8_t { + Null = 0, + HashCash = 1, + Hidden = 2, + Signed = 3, + Multiple = 4, + Key = 5 +}; + +/* + * public + Type Type Code Total Public Key Length Since Usage + DSA_SHA1 0 128 0.9.12 Legacy Router Identities and Destinations, never explicitly set + ECDSA_SHA256_P256 1 64 0.9.12 Older Destinations + ECDSA_SHA384_P384 2 96 0.9.12 Rarely if ever used for Destinations + ECDSA_SHA512_P521 3 132 0.9.12 Rarely if ever used for Destinations + RSA_SHA256_2048 4 256 0.9.12 Offline only; never used in Key Certificates for Router Identities or Destinations + RSA_SHA384_3072 5 384 0.9.12 Offline only; never used in Key Certificates for Router Identities or Destinations + RSA_SHA512_4096 6 512 0.9.12 Offline only; never used in Key Certificates for Router Identities or Destinations + EdDSA_SHA512_Ed25519 7 32 0.9.15 Recent Router Identities and Destinations + EdDSA_SHA512_Ed25519ph 8 32 0.9.25 Offline only; never used in Key Certificates for Router Identities or Destinations + reserved (GOST) 9 64 Reserved, see proposal 134 + reserved (GOST) 10 128 Reserved, see proposal 134 + RedDSA_SHA512_Ed25519 11 32 0.9.39 For Destinations and encrypted leasesets only; never used for Router Identities + reserved 65280-65534 Reserved for experimental use + reserved 65535 Reserved for future expansion + + * private + Type Length (bytes) Since Usage + DSA_SHA1 20 Legacy Router Identities and Destinations + ECDSA_SHA256_P256 32 0.9.12 Recent Destinations + ECDSA_SHA384_P384 48 0.9.12 Rarely used for Destinations + ECDSA_SHA512_P521 66 0.9.12 Rarely used for Destinations + RSA_SHA256_2048 512 0.9.12 Offline signing, never used for Router Identities or Destinations + RSA_SHA384_3072 768 0.9.12 Offline signing, never used for Router Identities or Destinations + RSA_SHA512_4096 1024 0.9.12 Offline signing, never used for Router Identities or Destinations + EdDSA_SHA512_Ed25519 32 0.9.15 Recent Router Identities and Destinations + EdDSA_SHA512_Ed25519ph 32 0.9.25 Offline signing, never used for Router Identities or Destinations + RedDSA_SHA512_Ed25519 32 0.9.39 For Destinations and encrypted leasesets only, never used for Router Identities + */ +enum class SigningKeyType : uint16_t { + DSA_SHA1 = 0, + ECDSA_SHA256_P256 = 1, + ECDSA_SHA384_P384 = 2, + ECDSA_SHA512_P521 = 3, + RSA_SHA256_2048 = 4, + RSA_SHA384_3072 = 5, + RSA_SHA512_4096 = 6, + EdDSA_SHA512_Ed25519 = 7, + EdDSA_SHA512_Ed25519ph = 8, + RedDSA_SHA512_Ed25519 = 11 +}; + +/* + * public + Type Type Code Total Public Key Length Usage + ElGamal 0 256 All Router Identities and Destinations + P256 1 64 Reserved, see proposal 145 + P384 2 96 Reserved, see proposal 145 + P521 3 132 Reserved, see proposal 145 + X25519 4 32 Not for use in key certs. See proposal 144 + reserved 65280-65534 Reserved for experimental use + reserved 65535 Reserved for future expansion + + * private + Type Length (bytes) Since Usage + ElGamal 256 All Router Identities and Destinations + P256 32 TBD Reserved, see proposal 145 + P384 48 TBD Reserved, see proposal 145 + P521 66 TBD Reserved, see proposal 145 + X25519 32 0.9.38 Little-endian. See proposal 144 +*/ +enum class CryptoKeyType : uint16_t { + ElGamal = 0, + P256 = 1, + P384 = 2, + P521 = 3, + X25519 = 4 +}; + +static const std::array, 5> cryptoKeyLengths { + /*CryptoKeyType::ElGamal*/ std::make_pair(256, 256), + /*CryptoKeyType::P256, */ std::make_pair( 64, 32), + /*CryptoKeyType::P384, */ std::make_pair( 96, 48), + /*CryptoKeyType::P521, */ std::make_pair(132, 66), + /*CryptoKeyType::X25519,*/ std::make_pair( 32, 32), +}; + +static const std::array, 12> signingKeyLengths { + /*SigningKeyType::DSA_SHA1, */ std::make_pair(128, 128), + /*SigningKeyType::ECDSA_SHA256_P256, */ std::make_pair( 64, 32), + /*SigningKeyType::ECDSA_SHA384_P384, */ std::make_pair( 96, 48), + /*SigningKeyType::ECDSA_SHA512_P521, */ std::make_pair(132, 66), + /*SigningKeyType::RSA_SHA256_2048, */ std::make_pair(256, 512), + /*SigningKeyType::RSA_SHA384_3072, */ std::make_pair(384, 768), + /*SigningKeyType::RSA_SHA512_4096, */ std::make_pair(512,1024), + /*SigningKeyType::EdDSA_SHA512_Ed25519 */ std::make_pair( 32, 32), + /*SigningKeyType::EdDSA_SHA512_Ed25519ph */ std::make_pair( 32, 32), + /*reserved (GOST) */ std::make_pair( 64, 0), + /*reserved (GOST) */ std::make_pair(128, 0), + /*SigningKeyType::RedDSA_SHA512_Ed25519 */ std::make_pair( 32, 32), +}; + +/** + * @brief makeOption Creates the string "lhs=rhs" used by BOB and SAM. Converts rhs + * @param lhs option to set + * @param rhs value to set + * @return concatenated string + */ +const std::string makeOption(const std::string &lhs, const int8_t &rhs); + +/** + * @brief keyToBase32Addr generated a base32 address (.b32.i2p) from a given public key + * @param key public key + * @return generated base32 address + */ +std::string keyToBase32Addr(const std::string &key); + +/** + * @brief publicKeyFromPrivate parses the private key and calculates the lenght of the public key + * @param priv private key (which includes the public key) to read + * @return public key used for addressing + */ +std::string publicKeyFromPrivate(const std::string &priv); + +} // namespace i2p + +#endif // I2PCOMMON_H diff --git a/libretroshare/src/util/rsdebug.cc b/libretroshare/src/util/rsdebug.cc index 93049e1b9..0a4777ab0 100644 --- a/libretroshare/src/util/rsdebug.cc +++ b/libretroshare/src/util/rsdebug.cc @@ -30,6 +30,12 @@ std::ostream &operator<<(std::ostream& out, const std::error_condition& err) << " category: " << err.category().name(); } +std::string rsErrorNotInCategory(int errNum, const std::string& categoryName) +{ + return "Error message for error: " + std::to_string(errNum) + + " not available in category: " + categoryName; +} + //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// diff --git a/libretroshare/src/util/rsdebug.h b/libretroshare/src/util/rsdebug.h index 73d5a2132..6409e9492 100644 --- a/libretroshare/src/util/rsdebug.h +++ b/libretroshare/src/util/rsdebug.h @@ -21,19 +21,23 @@ *******************************************************************************/ #pragma once -#include +#include +#include #include -/** Stream helper for std::error_condition */ -std::ostream &operator<<(std::ostream& out, const std::error_condition& err); - #ifdef __ANDROID__ # include -# include -# include +#else // def __ANDROID__ +# include +# include +# include +#endif // def __ANDROID__ -# include "util/rsjson.h" +#include "util/rsjson.h" + + +#ifdef __ANDROID__ enum class RsLoggerCategories { DEBUG = ANDROID_LOG_DEBUG, @@ -42,72 +46,7 @@ enum class RsLoggerCategories ERROR = ANDROID_LOG_ERROR, FATAL = ANDROID_LOG_FATAL }; - -template -struct t_RsLogger -{ - inline t_RsLogger() = default; - - /** Offer variadic style too, as a benefit this has better atomicity then - * << style, but doesn't supports manipulators and things like std::endl - * @see https://stackoverflow.com/a/27375675 */ - template - inline t_RsLogger(Arg&& arg, Args&&... args) - { - ostr << std::forward(arg); - using expander = int[]; - (void)expander{0, (void(ostr << std::forward(args)), 0)...}; - mFlush(); - } - - /** On other platforms expose the type of underlying stream. - * On Android it cannot work like that so return the class type itself - * just for code compatibility with other platforms */ - using stream_type = t_RsLogger; - - template - inline stream_type& operator<<(const T& val) - { ostr << val; return *this; } - - template - inline stream_type& operator<<(const RsJson& val) - { ostr << val; return *this; } - - /// needed for manipulators and things like std::endl - stream_type& operator<<(std::ostream& (*pf)(std::ostream&)) - { - if(pf == static_cast( - &std::endl< char, std::char_traits > )) - mFlush(); - else ostr << pf; - - return *this; - } - - /** On other platforms return underlying stream to write avoiding additional - * prefixes. On Android it cannot work like that so return the object itself - * just for code compatibility with other platforms */ - inline stream_type& uStream() { return *this; } - -private: - std::ostringstream ostr; - - void mFlush() - { - __android_log_write( - static_cast(CATEGORY), - "RetroShare", ostr.str().c_str() ); - ostr.str() = ""; - } -}; - #else // def __ANDROID__ - -#include -#include -#include -#include - enum class RsLoggerCategories { DEBUG = 'D', @@ -116,89 +55,105 @@ enum class RsLoggerCategories ERROR = 'E', FATAL = 'F' }; +#endif // def __ANDROID__ + + +/** Stream helper for std::error_condition */ +std::ostream &operator<<(std::ostream& out, const std::error_condition& err); + +/** Provide unkown error message for all error categories to avoid duplicating + * the message around */ +std::string rsErrorNotInCategory(int errNum, const std::string& categoryName); + template -struct t_RsLogger +struct t_RsLogger : std::ostringstream { - /// Expose the type of underlying stream - using stream_type = decltype(std::cerr); + t_RsLogger() { setPrefix(); } + ~t_RsLogger() { flush(); } - /// Return underlying stream to write avoiding additional prefixes - static inline stream_type& uStream() { return std::cerr; } - - inline t_RsLogger() = default; - - /** Offer variadic style too, as a benefit this has better atomicity then - * << style, but doesn't supports manipulators and things like std::endl - * @see https://stackoverflow.com/a/27375675 */ - template - inline t_RsLogger(Arg&& arg, Args&&... args) + /** Offer variadic style, this doesn't supports things like std::endl as + * paramether but when used toghether with conditional debugging macros + * reduces binary size as paramethers of suppressed calls are not evaluated + * and literally disappear in preprocessing fase @see RsDbg */ + template + explicit inline t_RsLogger(Args&&... args) { - std::ostringstream ostr; - ostr << getPrefix() << std::forward(arg); + setPrefix(); + + /* Combine initializer list and comma operator so the compiler unpack + * template arguments and feed our own stream without recursion + * see https://stackoverflow.com/a/27375675 */ using expander = int[]; - (void)expander{0, (void(ostr << std::forward(args)), 0)...}; - ostr << std::endl; - uStream() << ostr.str(); + (void) expander {0, (void((*this) << std::forward(args)), 0)...}; } - template - inline stream_type& operator<<(const T& val) - { return uStream() << getPrefix() << val; } - - /// needed for manipulators and things like std::endl - stream_type& operator<<(std::ostream& (*pf)(std::ostream&)) - { return uStream() << pf; } + /** Dump buffer stream to log */ + void flush() + { +#ifdef __ANDROID__ + __android_log_write( + static_cast(CATEGORY), + "RetroShare", str().c_str() ); +#else // def __ANDROID__ + (*this) << std::endl; + std::cerr << str(); +#endif // def __ANDROID__ + str() = ""; + } private: - std::string getPrefix() +#ifdef __ANDROID__ + inline void setPrefix() {} +#else // def __ANDROID__ + void setPrefix() { using namespace std::chrono; const auto now = system_clock::now(); const auto sec = time_point_cast(now); const auto msec = duration_cast(now - sec); - std::ostringstream tstream; - tstream << static_cast(CATEGORY) << " " + (*this) << static_cast(CATEGORY) << " " << sec.time_since_epoch().count() << "." - << std::setfill('0') << std::setw(3) << msec.count() - << " "; - return tstream.str(); + << std::setfill('0') << std::setw(3) << msec.count() << " "; } -}; #endif // def __ANDROID__ +}; /** - * Comfortable debug message logging, supports chaining like std::cerr but can - * be easly and selectively disabled at compile time to reduce generated binary - * size and performance impact without too many \#ifdef around. + * Comfortable debug message logging, supports both variadic style and chaining + * style like std::cerr. + * Can be easly and selectively disabled at compile time. + * To reduce generated binary size and performance impact when debugging is + * disabled without too many \#ifdef around the code combining the variadic + * style with the leveled debugging macros is the way to go. * - * To selectively debug your context you can just add something like this in - * in that context, as an example for a class you can just add a line like this - * inside class declaration: + * To selectively debug your file you just need to include the header of desired + * debugging level (0 to 4) @code{.cpp} -RS_SET_CONTEXT_DEBUG_LEVEL(2) +#include "util/rsdebuglevel2.h" @endcode - * And the you can write debug messages around the code of the class like this: + * Then where you want to print debug messages use @code{.cpp} -Dbg1() << "Level 1 debug message example, this will be compiled and " - << "printed" << std::endl; -Dbg2() << "Level 2 debug message example, this will be compiled and " - << "printed" << std::endl; -Dbg3() << "Level 3 debug message example, this will not be compiled and " - << "printed, and without #ifdef around!!" << std::endl; -Dbg4() << "Level 4 debug message example, this will not be compiled and " - << "printed, and without #ifdef around!!" << std::endl; +RS_DBG0("Hello 0 ", "my debug ", my_variable) << " message " << variable2; +RS_DBG1("Hello 1 ", "my debug ", my_variable) << " message " << variable2; +RS_DBG2("Hello 2 ", "my debug ", my_variable) << " message " << variable2; +RS_DBG3("Hello 3 ", "my debug ", my_variable) << " message " << variable2; +RS_DBG4("Hello 4 ", "my debug ", my_variable) << " message " << variable2; @endcode - * To change the debugging level, for example to completely disable debug - * messages you can change it to 0 -@code{.cpp} -RS_SET_CONTEXT_DEBUG_LEVEL(0) -@endcode - * While to set it to maximim level you have to pass 4. + * To change the debugging level just include a different level header like + * `util/rsdebuglevel1.h`, debug messages with lower or equal level then the + * included header will be printed, the others will not. + * Remember then on messages with debug level higher then the included the + * paramethers you pass as macro arguments (variadic style) will disappear in + * the preprocessing phase, so their evaluation will not be included in the + * final binary and not executed at runtime, instead the paramether passed with + * `<<` (chaining style) will be in the compiled binary and evaluated at runtime + * even if are not printed, due to how C++ is made it is not possible to avoid + * this, so we suggest to use variadic style for debug messages. */ -using RsDbg = t_RsLogger; - +using RsDbg = t_RsLogger; +#define RS_DBG(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) /** * Comfortable log information reporting helper, supports chaining like @@ -208,17 +163,22 @@ using RsDbg = t_RsLogger; RsInfo() << __PRETTY_FUNCTION__ << "My information message" << std::cerr; @endcode */ -using RsInfo = t_RsLogger; +using RsInfo = t_RsLogger; +#define RS_INFO(...) RsInfo(__PRETTY_FUNCTION__, " ", __VA_ARGS__) /// Similar to @see RsInfo but for warning messages -using RsWarn = t_RsLogger; +using RsWarn = t_RsLogger; +#define RS_WARN(...) RsWarn(__PRETTY_FUNCTION__, " ", __VA_ARGS__) /// Similar to @see RsInfo but for error messages -using RsErr = t_RsLogger; +using RsErr = t_RsLogger; +#define RS_ERR(...) RsErr(__PRETTY_FUNCTION__, " ", __VA_ARGS__) /** Similar to @see RsInfo but for fatal errors (the ones which cause RetroShare * to terminate) messages */ -using RsFatal = t_RsLogger; +using RsFatal = t_RsLogger; +#define RS_FATAL(...) RsFatal(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + /** * Keeps compatible syntax with RsDbg but explicitely do nothing in a way that @@ -228,27 +188,31 @@ using RsFatal = t_RsLogger; struct RsNoDbg { inline RsNoDbg() = default; - - template inline RsNoDbg(T, Args...) {} - - /** Defined as the type itself just for code compatibility with other - * logging classes */ - using stream_type = RsNoDbg; + template inline explicit RsNoDbg(Args...) {} /** This match most of the types, but might be not enough for templated * types */ template - inline stream_type& operator<<(const T&) { return *this; } + inline RsNoDbg& operator<<(const T&) { return *this; } /// needed for manipulators and things like std::endl - inline stream_type& operator<<(std::ostream& (*/*pf*/)(std::ostream&)) + inline RsNoDbg& operator<<(std::ostream& (*/*pf*/)(std::ostream&)) { return *this; } - /** Return the object itself just for code compatibility with other - * logging classes */ - inline stream_type& uStream() { return *this; } + /** Do nothing. Just for code compatibility with other logging classes */ + inline void flush() {} }; + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/// All the following lines are DEPRECATED!! + +#include "util/rsdeprecate.h" + /** * Concatenate preprocessor tokens A and B without expanding macro definitions * (however, if invoked from a macro, macro arguments are expanded). @@ -267,41 +231,30 @@ struct RsNoDbg // A bunch of boilerplate, but just in one place #define RS_SET_CONTEXT_DEBUG_LEVEL0 \ - using Dbg1 = RsNoDbg; \ - using Dbg2 = RsNoDbg; \ - using Dbg3 = RsNoDbg; \ - using Dbg4 = RsNoDbg; + using Dbg1 RS_DEPRECATED_FOR(RS_DBG1) = RsNoDbg; \ + using Dbg2 RS_DEPRECATED_FOR(RS_DBG2) = RsNoDbg; \ + using Dbg3 RS_DEPRECATED_FOR(RS_DBG3) = RsNoDbg; \ + using Dbg4 RS_DEPRECATED_FOR(RS_DBG4) = RsNoDbg; #define RS_SET_CONTEXT_DEBUG_LEVEL1 \ - using Dbg1 = RsDbg; \ - using Dbg2 = RsNoDbg; \ - using Dbg3 = RsNoDbg; \ - using Dbg4 = RsNoDbg; + using Dbg1 RS_DEPRECATED_FOR(RS_DBG1) = RsDbg; \ + using Dbg2 RS_DEPRECATED_FOR(RS_DBG2) = RsNoDbg; \ + using Dbg3 RS_DEPRECATED_FOR(RS_DBG3) = RsNoDbg; \ + using Dbg4 RS_DEPRECATED_FOR(RS_DBG4) = RsNoDbg; #define RS_SET_CONTEXT_DEBUG_LEVEL2 \ - using Dbg1 = RsDbg; \ - using Dbg2 = RsDbg; \ - using Dbg3 = RsNoDbg; \ - using Dbg4 = RsNoDbg; + using Dbg1 RS_DEPRECATED_FOR(RS_DBG1) = RsDbg; \ + using Dbg2 RS_DEPRECATED_FOR(RS_DBG2) = RsDbg; \ + using Dbg3 RS_DEPRECATED_FOR(RS_DBG3) = RsNoDbg; \ + using Dbg4 RS_DEPRECATED_FOR(RS_DBG4) = RsNoDbg; #define RS_SET_CONTEXT_DEBUG_LEVEL3 \ - using Dbg1 = RsDbg; \ - using Dbg2 = RsDbg; \ - using Dbg3 = RsDbg; \ - using Dbg4 = RsNoDbg; + using Dbg1 RS_DEPRECATED_FOR(RS_DBG1) = RsDbg; \ + using Dbg2 RS_DEPRECATED_FOR(RS_DBG2) = RsDbg; \ + using Dbg3 RS_DEPRECATED_FOR(RS_DBG3) = RsDbg; \ + using Dbg4 RS_DEPRECATED_FOR(RS_DBG4) = RsNoDbg; #define RS_SET_CONTEXT_DEBUG_LEVEL4 \ - using Dbg1 = RsDbg; \ - using Dbg2 = RsDbg; \ - using Dbg3 = RsDbg; \ - using Dbg4 = RsDbg; - - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -/// All the following lines are DEPRECATED!! - -#include - -#include "util/rsdeprecate.h" + using Dbg1 RS_DEPRECATED_FOR(RS_DBG1) = RsDbg; \ + using Dbg2 RS_DEPRECATED_FOR(RS_DBG2) = RsDbg; \ + using Dbg3 RS_DEPRECATED_FOR(RS_DBG3) = RsDbg; \ + using Dbg4 RS_DEPRECATED_FOR(RS_DBG4) = RsDbg; namespace RsLog { enum RS_DEPRECATED_FOR("RsErr, RsDbg, RsNoDbg") logLvl { diff --git a/libretroshare/src/util/rsdebuglevel0.h b/libretroshare/src/util/rsdebuglevel0.h new file mode 100644 index 000000000..5c68ec0be --- /dev/null +++ b/libretroshare/src/util/rsdebuglevel0.h @@ -0,0 +1,42 @@ +/******************************************************************************* + * RetroShare debugging level * + * * + * Copyright (C) 2020 Gioacchino Mazzurco * + * Copyright (C) 2020 Asociación Civil Altermundi * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this program. If not, see . * + * * + *******************************************************************************/ + +// #pragma once // This is commented out on purpose! + +#include + +#undef RS_DEBUG_LEVEL +#define RS_DEBUG_LEVEL 0 + +#undef RS_DBG0 +#define RS_DBG0(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG1 +#define RS_DBG1(...) RsNoDbg("") + +#undef RS_DBG2 +#define RS_DBG2(...) RsNoDbg("") + +#undef RS_DBG3 +#define RS_DBG3(...) RsNoDbg("") + +#undef RS_DBG4 +#define RS_DBG4(...) RsNoDbg("") diff --git a/libretroshare/src/util/rsdebuglevel1.h b/libretroshare/src/util/rsdebuglevel1.h new file mode 100644 index 000000000..7e968e402 --- /dev/null +++ b/libretroshare/src/util/rsdebuglevel1.h @@ -0,0 +1,42 @@ +/******************************************************************************* + * RetroShare debugging level * + * * + * Copyright (C) 2020 Gioacchino Mazzurco * + * Copyright (C) 2020 Asociación Civil Altermundi * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this program. If not, see . * + * * + *******************************************************************************/ + +// #pragma once // This is commented out on purpose! + +#include + +#undef RS_DEBUG_LEVEL +#define RS_DEBUG_LEVEL 1 + +#undef RS_DBG0 +#define RS_DBG0(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG1 +#define RS_DBG1(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG2 +#define RS_DBG2(...) RsNoDbg() + +#undef RS_DBG3 +#define RS_DBG3(...) RsNoDbg() + +#undef RS_DBG4 +#define RS_DBG4(...) RsNoDbg() diff --git a/libretroshare/src/util/rsdebuglevel2.h b/libretroshare/src/util/rsdebuglevel2.h new file mode 100644 index 000000000..2cbf1a224 --- /dev/null +++ b/libretroshare/src/util/rsdebuglevel2.h @@ -0,0 +1,42 @@ +/******************************************************************************* + * RetroShare debugging level * + * * + * Copyright (C) 2020 Gioacchino Mazzurco * + * Copyright (C) 2020 Asociación Civil Altermundi * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this program. If not, see . * + * * + *******************************************************************************/ + +// #pragma once // This is commented out on purpose! + +#include + +#undef RS_DEBUG_LEVEL +#define RS_DEBUG_LEVEL 2 + +#undef RS_DBG0 +#define RS_DBG0(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG1 +#define RS_DBG1(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG2 +#define RS_DBG2(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG3 +#define RS_DBG3(...) RsNoDbg() + +#undef RS_DBG4 +#define RS_DBG4(...) RsNoDbg() diff --git a/libretroshare/src/util/rsdebuglevel3.h b/libretroshare/src/util/rsdebuglevel3.h new file mode 100644 index 000000000..53bcbfe9f --- /dev/null +++ b/libretroshare/src/util/rsdebuglevel3.h @@ -0,0 +1,42 @@ +/******************************************************************************* + * RetroShare debugging level * + * * + * Copyright (C) 2020 Gioacchino Mazzurco * + * Copyright (C) 2020 Asociación Civil Altermundi * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this program. If not, see . * + * * + *******************************************************************************/ + +// #pragma once // This is commented out on purpose! + +#include + +#undef RS_DEBUG_LEVEL +#define RS_DEBUG_LEVEL 3 + +#undef RS_DBG0 +#define RS_DBG0(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG1 +#define RS_DBG1(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG2 +#define RS_DBG2(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG3 +#define RS_DBG3(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG4 +#define RS_DBG4(...) RsNoDbg() diff --git a/libretroshare/src/util/rsdebuglevel4.h b/libretroshare/src/util/rsdebuglevel4.h new file mode 100644 index 000000000..f8697cad8 --- /dev/null +++ b/libretroshare/src/util/rsdebuglevel4.h @@ -0,0 +1,42 @@ +/******************************************************************************* + * RetroShare debugging level * + * * + * Copyright (C) 2020 Gioacchino Mazzurco * + * Copyright (C) 2020 Asociación Civil Altermundi * + * * + * This program is free software: you can redistribute it and/or modify * + * it under the terms of the GNU Lesser 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 Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public License * + * along with this program. If not, see . * + * * + *******************************************************************************/ + +// #pragma once // This is commented out on purpose! + +#include + +#undef RS_DEBUG_LEVEL +#define RS_DEBUG_LEVEL 4 + +#undef RS_DBG0 +#define RS_DBG0(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG1 +#define RS_DBG1(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG2 +#define RS_DBG2(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG3 +#define RS_DBG3(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) + +#undef RS_DBG4 +#define RS_DBG4(...) RsDbg(__PRETTY_FUNCTION__, " ", __VA_ARGS__) diff --git a/libretroshare/src/util/rsrandom.cc b/libretroshare/src/util/rsrandom.cc index 7ea8eb968..dc7d9ac67 100644 --- a/libretroshare/src/util/rsrandom.cc +++ b/libretroshare/src/util/rsrandom.cc @@ -121,13 +121,26 @@ double RsRandom::random_f64() return random_u64() / (double)(~(uint64_t)0) ; } -std::string RsRandom::random_alphaNumericString(uint32_t len) +/*static*/ std::string RsRandom::alphaNumeric(uint32_t length) { - std::string s = "" ; + std::string s; + while(s.size() < length) + { + uint8_t rChar; random_bytes(&rChar, 1); rChar = rChar % 123; + /* if(isalnum(val)) isalnum result may vary depend on locale!! */ + if( (rChar >= 48 && rChar <= 57) /* 0-9 */ || + (rChar >= 65 && rChar <= 90) /* A-Z */ || + (rChar >= 97 && rChar <= 122) /* a-z */ ) + s += static_cast(rChar); + } - for(uint32_t i=0;i(&ret[0]), length); + for(uint32_t i=0; i #include #include #include diff --git a/retroshare-gui/src/gui/Circles/CreateCircleDialog.cpp b/retroshare-gui/src/gui/Circles/CreateCircleDialog.cpp index 59b0da95d..3733c0f1a 100644 --- a/retroshare-gui/src/gui/Circles/CreateCircleDialog.cpp +++ b/retroshare-gui/src/gui/Circles/CreateCircleDialog.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -696,34 +697,32 @@ void CreateCircleDialog::loadIdentities() { RsThread::async([this]() { - std::list ids_meta; + std::list ids_meta; if(!rsIdentity->getIdentitiesSummaries(ids_meta)) { - std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve identities ids for all identities" << std::endl; + RS_ERR("failed to retrieve identities ids for all identities"); return; - } - std::set ids; + } - for(auto& meta:ids_meta) - ids.insert(RsGxsId(meta.mGroupId)) ; + std::set ids; + for(auto& meta:ids_meta) ids.insert(RsGxsId(meta.mGroupId)); - std::vector id_groups; - - if(!rsIdentity->getIdentitiesInfo(ids,id_groups)) + auto id_groups = std::make_unique>(); + if(!rsIdentity->getIdentitiesInfo(ids, *id_groups)) { - std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve identities group info for all identities" << std::endl; + RS_ERR("failed to retrieve identities group info for all identities"); return; - } + } - RsQThreadUtils::postToObject( [id_groups,this]() + RsQThreadUtils::postToObject( + [id_groups = std::move(id_groups), 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 */ - fillIdentitiesList(id_groups) ; - + fillIdentitiesList(*id_groups); }, this ); }); diff --git a/retroshare-gui/src/gui/GenCertDialog.cpp b/retroshare-gui/src/gui/GenCertDialog.cpp index 4fdb9f714..aae80b753 100644 --- a/retroshare-gui/src/gui/GenCertDialog.cpp +++ b/retroshare-gui/src/gui/GenCertDialog.cpp @@ -340,6 +340,10 @@ void GenCertDialog::setupState() ui.hiddenport_spinBox->setVisible(hidden_state && !tor_auto); ui.cbUseBob->setVisible(hidden_state && !tor_auto); +#ifndef RS_USE_I2P_BOB + ui.cbUseBob->setDisabled(true); + ui.cbUseBob->setToolTip(tr("BOB support is not available")); +#endif if(!mAllFieldsOk) { diff --git a/retroshare-gui/src/gui/Identity/IdDialog.cpp b/retroshare-gui/src/gui/Identity/IdDialog.cpp index 2fd243c20..d87f1d6e7 100644 --- a/retroshare-gui/src/gui/Identity/IdDialog.cpp +++ b/retroshare-gui/src/gui/Identity/IdDialog.cpp @@ -47,6 +47,7 @@ #include "util/misc.h" #include "util/QtVersion.h" #include "util/rstime.h" +#include "util/rsdebug.h" #include "retroshare/rsgxsflags.h" #include "retroshare/rsmsgs.h" @@ -55,6 +56,7 @@ #include #include +#include /****** * #define ID_DEBUG 1 @@ -506,21 +508,24 @@ void IdDialog::updateCircles() std::cerr << "Retrieving post data for post " << mThreadId << std::endl; #endif - std::list circle_metas ; + /* This can be big so use a smart pointer to just copy the pointer + * instead of copying the whole list accross the lambdas */ + auto circle_metas = std::make_unique>(); - if(!rsGxsCircles->getCirclesSummaries(circle_metas)) + if(!rsGxsCircles->getCirclesSummaries(*circle_metas)) { - std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve circles group info list" << std::endl; + RS_ERR("failed to retrieve circles group info list"); return; - } + } - RsQThreadUtils::postToObject( [circle_metas,this]() + RsQThreadUtils::postToObject( + [circle_metas = std::move(circle_metas), 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 */ - loadCircles(circle_metas); + loadCircles(*circle_metas); }, this ); @@ -1305,19 +1310,17 @@ void IdDialog::updateIdList() return; } - std::map ids_set; + auto ids_set = std::make_unique>(); + for(auto it(groups.begin()); it!=groups.end(); ++it) + (*ids_set)[(*it).mMeta.mGroupId] = *it; - for(auto it(groups.begin());it!=groups.end();++it) - ids_set[(*it).mMeta.mGroupId] = *it; - - RsQThreadUtils::postToObject( [ids_set,this]() + RsQThreadUtils::postToObject( + [ids_set = std::move(ids_set), 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 */ - - loadIdentities(ids_set); - + loadIdentities(*ids_set); }, this ); }); @@ -1575,6 +1578,14 @@ void IdDialog::loadIdentities(const std::map& ids_set /* count items */ int itemCount = contactsItem->childCount() + allItem->childCount() + ownItem->childCount(); ui->label_count->setText( "(" + QString::number( itemCount ) + ")" ); + + int contactsCount = contactsItem->childCount() ; + int allCount = allItem->childCount() ; + int ownCount = ownItem->childCount(); + + contactsItem->setText(0, tr("My contacts") + " (" + QString::number( contactsCount ) + ")" ); + allItem->setText(0, tr("All") + " (" + QString::number( allCount ) + ")" ); + ownItem->setText(0, tr("My own identities") + " (" + QString::number( ownCount ) + ")" ); navigate(RsGxsId(oldCurrentId)); filterIds(); @@ -1851,10 +1862,17 @@ QString IdDialog::createUsageString(const RsIdentityUsage& u) const { case RsServiceType::CHANNELS: service_name = tr("Channels") ;service_type = RetroShareLink::TYPE_CHANNEL ; break ; case RsServiceType::FORUMS: service_name = tr("Forums") ; service_type = RetroShareLink::TYPE_FORUM ; break ; - case RsServiceType::POSTED: service_name = tr("Posted") ; service_type = RetroShareLink::TYPE_POSTED ; break ; + case RsServiceType::POSTED: service_name = tr("Boards") ; service_type = RetroShareLink::TYPE_POSTED ; break ; case RsServiceType::CHAT: service_name = tr("Chat") ; service_type = RetroShareLink::TYPE_CHAT_ROOM ; break ; + + case RsServiceType::GXS_TRANS: return tr("GxsMail author "); +#ifdef TODO + // We need a RS link for circles if we want to do that. + // + case RsServiceType::GXSCIRCLE: service_name = tr("GxsCircles"); service_type = RetroShareLink::TYPE_CIRCLES; break ; +#endif default: - service_name = tr("Unknown"); service_type = RetroShareLink::TYPE_UNKNOWN ; + service_name = tr("Unknown (service=")+QString::number((int)u.mServiceId,16)+")"; service_type = RetroShareLink::TYPE_UNKNOWN ; } switch(u.mUsageCode) @@ -1874,10 +1892,25 @@ QString IdDialog::createUsageString(const RsIdentityUsage& u) const return tr("Group author for group %1 in service %2").arg(QString::fromStdString(u.mGrpId.toStdString())).arg(service_name); break ; case RsIdentityUsage::MESSAGE_AUTHOR_SIGNATURE_VALIDATION: - case RsIdentityUsage::MESSAGE_AUTHOR_KEEP_ALIVE: // Identities are stamped regularly by crawlign the set of messages for all groups. That helps keepign the useful identities in hand. + case RsIdentityUsage::MESSAGE_AUTHOR_KEEP_ALIVE: // Identities are stamped regularly by crawling the set of messages for all groups. That helps keepign the useful identities in hand. { - RetroShareLink l = RetroShareLink::createGxsMessageLink(service_type,u.mGrpId,u.mMsgId,tr("Message/vote/comment")); - return tr("%1 in %2 tab").arg(l.toHtml()).arg(service_name) ; + RetroShareLink l; + + std::cerr << "Signature validation/keep alive signature:" << std::endl; + std::cerr << " service ID = " << std::hex << (uint16_t)u.mServiceId << std::dec << std::endl; + std::cerr << " u.mGrpId = " << u.mGrpId << std::endl; + std::cerr << " u.mMsgId = " << u.mMsgId << std::endl; + std::cerr << " u.mParentId = " << u.mParentId << std::endl; + std::cerr << " u.mThreadId = " << u.mThreadId << std::endl; + + if(service_type == RetroShareLink::TYPE_CHANNEL && !u.mThreadId.isNull()) + l = RetroShareLink::createGxsMessageLink(service_type,u.mGrpId,u.mThreadId,tr("Vote/comment")); + else if(service_type == RetroShareLink::TYPE_POSTED && !u.mThreadId.isNull()) + l = RetroShareLink::createGxsMessageLink(service_type,u.mGrpId,u.mThreadId,tr("Vote")); + else + l = RetroShareLink::createGxsMessageLink(service_type,u.mGrpId,u.mMsgId,tr("Message")); + + return tr("%1 in %2 service").arg(l.toHtml()).arg(service_name) ; } case RsIdentityUsage::CHAT_LOBBY_MSG_VALIDATION: // Chat lobby msgs are signed, so each time one comes, or a chat lobby event comes, a signature verificaiton happens. { @@ -1903,9 +1936,13 @@ QString IdDialog::createUsageString(const RsIdentityUsage& u) const { return tr("Signature in distant tunnel system."); } - case RsIdentityUsage::IDENTITY_DATA_UPDATE: // Group update on that identity data. Can be avatar, name, etc. + case RsIdentityUsage::IDENTITY_NEW_FROM_GXS_SYNC: // Group update on that identity data. Can be avatar, name, etc. { - return tr("Update of identity data."); + return tr("Received from GXS sync."); + } + case RsIdentityUsage::IDENTITY_NEW_FROM_DISCOVERY: // Own friend sended his own ids + { + return tr("Friend node identity received through discovery."); } case RsIdentityUsage::IDENTITY_GENERIC_SIGNATURE_CHECK: // Any signature verified for that identity { @@ -1913,11 +1950,18 @@ QString IdDialog::createUsageString(const RsIdentityUsage& u) const } case RsIdentityUsage::IDENTITY_GENERIC_SIGNATURE_CREATION: // Any signature made by that identity { - return tr("Generic signature."); + return tr("Generic signature creation (e.g. chat room message, global router,...)."); } case RsIdentityUsage::IDENTITY_GENERIC_ENCRYPTION: return tr("Generic encryption."); case RsIdentityUsage::IDENTITY_GENERIC_DECRYPTION: return tr("Generic decryption."); - case RsIdentityUsage::CIRCLE_MEMBERSHIP_CHECK: return tr("Membership verification in circle %1.").arg(QString::fromStdString(u.mGrpId.toStdString())); + case RsIdentityUsage::CIRCLE_MEMBERSHIP_CHECK: + { + RsGxsCircleDetails det; + if(rsGxsCircles->getCircleDetails(RsGxsCircleId(u.mGrpId),det)) + return tr("Membership verification in circle \"%1\" (%2).").arg(QString::fromUtf8(det.mCircleName.c_str())).arg(QString::fromStdString(u.mGrpId.toStdString())); + else + return tr("Membership verification in circle (ID=%1).").arg(QString::fromStdString(u.mGrpId.toStdString())); + } #warning TODO! csoler 2017-01-03: Add the different strings and translations here. default: diff --git a/retroshare-gui/src/gui/Identity/IdEditDialog.cpp b/retroshare-gui/src/gui/Identity/IdEditDialog.cpp index bb1ee139a..0f432ba2d 100644 --- a/retroshare-gui/src/gui/Identity/IdEditDialog.cpp +++ b/retroshare-gui/src/gui/Identity/IdEditDialog.cpp @@ -210,8 +210,8 @@ void IdEditDialog::setupExistingId(const RsGxsGroupId& keyId) RsThread::async([this,keyId]() { std::vector datavector; - - bool res = rsIdentity->getIdentitiesInfo(std::set({(RsGxsId)keyId}),datavector); + bool res = rsIdentity->getIdentitiesInfo( + std::set({(RsGxsId)keyId}), datavector ); RsQThreadUtils::postToObject( [this,keyId,res,datavector]() { diff --git a/retroshare-gui/src/gui/Posted/PostedCardView.cpp b/retroshare-gui/src/gui/Posted/PostedCardView.cpp index e75c2fb62..0aafaeeb5 100644 --- a/retroshare-gui/src/gui/Posted/PostedCardView.cpp +++ b/retroshare-gui/src/gui/Posted/PostedCardView.cpp @@ -146,6 +146,8 @@ void PostedCardView::setup() QAction *CopyLinkAction = new QAction(QIcon(""),tr("Copy RetroShare Link"), this); connect(CopyLinkAction, SIGNAL(triggered()), this, SLOT(copyMessageLink())); + QAction *showInPeopleAct = new QAction(QIcon(), tr("Show author in people tab"), this); + connect(showInPeopleAct, SIGNAL(triggered()), this, SLOT(showAuthorInPeople())); int S = QFontMetricsF(font()).height() ; @@ -157,6 +159,8 @@ void PostedCardView::setup() QMenu *menu = new QMenu(); menu->addAction(CopyLinkAction); + menu->addSeparator(); + menu->addAction(showInPeopleAct); ui->shareButton->setMenu(menu); ui->clearButton->hide(); @@ -172,90 +176,106 @@ void PostedCardView::fill() // return; // } - QPixmap sqpixmap2 = QPixmap(":/images/thumb-default.png"); + RsReputationLevel overall_reputation = rsReputations->overallReputationLevel(mPost.mMeta.mAuthorId); + bool redacted = (overall_reputation == RsReputationLevel::LOCALLY_NEGATIVE); - mInFill = true; - int desired_height = 1.5*(ui->voteDownButton->height() + ui->voteUpButton->height() + ui->scoreLabel->height()); - int desired_width = sqpixmap2.width()*desired_height/(float)sqpixmap2.height(); - - QDateTime qtime; - qtime.setTime_t(mPost.mMeta.mPublishTs); - QString timestamp = qtime.toString("hh:mm dd-MMM-yyyy"); - QString timestamp2 = misc::timeRelativeToNow(mPost.mMeta.mPublishTs); - ui->dateLabel->setText(timestamp2); - ui->dateLabel->setToolTip(timestamp); - - ui->fromLabel->setId(mPost.mMeta.mAuthorId); - - // Use QUrl to check/parse our URL - // The only combination that seems to work: load as EncodedUrl, extract toEncoded(). - QByteArray urlarray(mPost.mLink.c_str()); - QUrl url = QUrl::fromEncoded(urlarray.trimmed()); - QString urlstr = "Invalid Link"; - QString sitestr = "Invalid Link"; - - bool urlOkay = url.isValid(); - if (urlOkay) - { - QString scheme = url.scheme(); - if ((scheme != "https") - && (scheme != "http") - && (scheme != "ftp") - && (scheme != "retroshare")) - { - urlOkay = false; - sitestr = "Invalid Link Scheme"; - } - } - - if (urlOkay) - { - urlstr = QString(" "); - urlstr += messageName(); - urlstr += QString(" "); - - QString siteurl = url.toEncoded(); - sitestr = QString(" %2 ").arg(siteurl).arg(siteurl); - - ui->titleLabel->setText(urlstr); - }else - { - ui->titleLabel->setText(messageName()); - - } - - if (urlarray.isEmpty()) - { - ui->siteLabel->hide(); - } - - ui->siteLabel->setText(sitestr); - - if(mPost.mImage.mData != NULL) - { - QPixmap pixmap; - GxsIdDetails::loadPixmapFromData(mPost.mImage.mData, mPost.mImage.mSize, pixmap,GxsIdDetails::ORIGINAL); - // Wiping data - as its been passed to thumbnail. - - QPixmap scaledpixmap; - if(pixmap.width() > 800){ - QPixmap scaledpixmap = pixmap.scaledToWidth(800, Qt::SmoothTransformation); - ui->pictureLabel->setPixmap(scaledpixmap); - }else{ - ui->pictureLabel->setPixmap(pixmap); - } - } - else if (mPost.mImage.mData == NULL) - { + if(redacted) { + ui->commentButton->setDisabled(true); + ui->voteUpButton->setDisabled(true); + ui->voteDownButton->setDisabled(true); ui->picture_frame->hide(); - } - else - { - ui->picture_frame->show(); - } + ui->fromLabel->setId(mPost.mMeta.mAuthorId); + ui->titleLabel->setText(tr( "

The author of this message (with ID %1) is banned.").arg(QString::fromStdString(mPost.mMeta.mAuthorId.toStdString()))) ; + QDateTime qtime; + qtime.setTime_t(mPost.mMeta.mPublishTs); + QString timestamp = qtime.toString("hh:mm dd-MMM-yyyy"); + ui->dateLabel->setText(timestamp); + } else { + QPixmap sqpixmap2 = FilesDefs::getPixmapFromQtResourcePath(":/images/thumb-default.png"); + + mInFill = true; + int desired_height = 1.5*(ui->voteDownButton->height() + ui->voteUpButton->height() + ui->scoreLabel->height()); + int desired_width = sqpixmap2.width()*desired_height/(float)sqpixmap2.height(); + + QDateTime qtime; + qtime.setTime_t(mPost.mMeta.mPublishTs); + QString timestamp = qtime.toString("hh:mm dd-MMM-yyyy"); + QString timestamp2 = misc::timeRelativeToNow(mPost.mMeta.mPublishTs); + ui->dateLabel->setText(timestamp2); + ui->dateLabel->setToolTip(timestamp); + + ui->fromLabel->setId(mPost.mMeta.mAuthorId); + + // Use QUrl to check/parse our URL + // The only combination that seems to work: load as EncodedUrl, extract toEncoded(). + QByteArray urlarray(mPost.mLink.c_str()); + QUrl url = QUrl::fromEncoded(urlarray.trimmed()); + QString urlstr = "Invalid Link"; + QString sitestr = "Invalid Link"; + + bool urlOkay = url.isValid(); + if (urlOkay) + { + QString scheme = url.scheme(); + if ((scheme != "https") + && (scheme != "http") + && (scheme != "ftp") + && (scheme != "retroshare")) + { + urlOkay = false; + sitestr = "Invalid Link Scheme"; + } + } + + if (urlOkay) + { + urlstr = QString(" "); + urlstr += messageName(); + urlstr += QString(" "); + + QString siteurl = url.toEncoded(); + sitestr = QString(" %2 ").arg(siteurl).arg(siteurl); + + ui->titleLabel->setText(urlstr); + }else + { + ui->titleLabel->setText(messageName()); + + } + + if (urlarray.isEmpty()) + { + ui->siteLabel->hide(); + } + + ui->siteLabel->setText(sitestr); + + if(mPost.mImage.mData != NULL) + { + QPixmap pixmap; + GxsIdDetails::loadPixmapFromData(mPost.mImage.mData, mPost.mImage.mSize, pixmap,GxsIdDetails::ORIGINAL); + // Wiping data - as its been passed to thumbnail. + + QPixmap scaledpixmap; + if(pixmap.width() > 800){ + QPixmap scaledpixmap = pixmap.scaledToWidth(800, Qt::SmoothTransformation); + ui->pictureLabel->setPixmap(scaledpixmap); + }else{ + ui->pictureLabel->setPixmap(pixmap); + } + } + else if (mPost.mImage.mData == NULL) + { + ui->picture_frame->hide(); + } + else + { + ui->picture_frame->show(); + } + } //QString score = "Hot" + QString::number(post.mHotScore); //score += " Top" + QString::number(post.mTopScore); diff --git a/retroshare-gui/src/gui/Posted/PostedItem.cpp b/retroshare-gui/src/gui/Posted/PostedItem.cpp index 751f68ee5..b4cfa2d4a 100644 --- a/retroshare-gui/src/gui/Posted/PostedItem.cpp +++ b/retroshare-gui/src/gui/Posted/PostedItem.cpp @@ -32,6 +32,8 @@ #include "gui/common/FilesDefs.h" #include "util/qtthreadsutils.h" #include "util/HandleRichText.h" +#include "gui/MainWindow.h" +#include "gui/Identity/IdDialog.h" #include "PhotoView.h" #include "ui_PostedItem.h" @@ -338,6 +340,24 @@ void BasePostedItem::viewPicture() /* window will destroy itself! */ } +void BasePostedItem::showAuthorInPeople() +{ + if(mPost.mMeta.mAuthorId.isNull()) + { + std::cerr << "(EE) 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(mPost.mMeta.mAuthorId)); +} + //======================================================================================== // PostedItem // //======================================================================================== @@ -394,6 +414,8 @@ void PostedItem::setup() QAction *CopyLinkAction = new QAction(QIcon(""),tr("Copy RetroShare Link"), this); connect(CopyLinkAction, SIGNAL(triggered()), this, SLOT(copyMessageLink())); + QAction *showInPeopleAct = new QAction(QIcon(), tr("Show author in people tab"), this); + connect(showInPeopleAct, SIGNAL(triggered()), this, SLOT(showAuthorInPeople())); int S = QFontMetricsF(font()).height() ; @@ -407,6 +429,8 @@ void PostedItem::setup() QMenu *menu = new QMenu(); menu->addAction(CopyLinkAction); + menu->addSeparator(); + menu->addAction(showInPeopleAct); ui->shareButton->setMenu(menu); ui->clearButton->hide(); @@ -438,8 +462,6 @@ void PostedItem::makeUpVote() emit vote(msgId, true); } - - void PostedItem::setComment(const RsGxsComment& cmt) { ui->newCommentLabel->show(); @@ -459,97 +481,115 @@ void PostedItem::setCommentsSize(int comNb) void PostedItem::fill() { - RetroShareLink link = RetroShareLink::createGxsGroupLink(RetroShareLink::TYPE_POSTED, mGroupMeta.mGroupId, groupName()); - ui->nameLabel->setText(link.toHtml()); + RsReputationLevel overall_reputation = rsReputations->overallReputationLevel(mPost.mMeta.mAuthorId); + bool redacted = (overall_reputation == RsReputationLevel::LOCALLY_NEGATIVE); - QPixmap sqpixmap2 = QPixmap(":/images/thumb-default.png"); + if(redacted) { + ui->expandButton->setDisabled(true); + ui->commentButton->setDisabled(true); + ui->voteUpButton->setDisabled(true); + ui->voteDownButton->setDisabled(true); - mInFill = true; - int desired_height = 1.5*(ui->voteDownButton->height() + ui->voteUpButton->height() + ui->scoreLabel->height()); - int desired_width = sqpixmap2.width()*desired_height/(float)sqpixmap2.height(); + ui->thumbnailLabel->setPixmap( QPixmap(":/images/thumb-default.png")); + ui->fromLabel->setId(mPost.mMeta.mAuthorId); + ui->titleLabel->setText(tr( "

The author of this message (with ID %1) is banned.").arg(QString::fromStdString(mPost.mMeta.mAuthorId.toStdString()))) ; + QDateTime qtime; + qtime.setTime_t(mPost.mMeta.mPublishTs); + QString timestamp = qtime.toString("hh:mm dd-MMM-yyyy"); + ui->dateLabel->setText(timestamp); + } else { + RetroShareLink link = RetroShareLink::createGxsGroupLink(RetroShareLink::TYPE_POSTED, mGroupMeta.mGroupId, groupName()); + ui->nameLabel->setText(link.toHtml()); - QDateTime qtime; - qtime.setTime_t(mPost.mMeta.mPublishTs); - QString timestamp = qtime.toString("hh:mm dd-MMM-yyyy"); - QString timestamp2 = misc::timeRelativeToNow(mPost.mMeta.mPublishTs); - ui->dateLabel->setText(timestamp2); - ui->dateLabel->setToolTip(timestamp); + QPixmap sqpixmap2 = FilesDefs::getPixmapFromQtResourcePath(":/images/thumb-default.png"); - ui->fromLabel->setId(mPost.mMeta.mAuthorId); + mInFill = true; + int desired_height = 1.5*(ui->voteDownButton->height() + ui->voteUpButton->height() + ui->scoreLabel->height()); + int desired_width = sqpixmap2.width()*desired_height/(float)sqpixmap2.height(); - // Use QUrl to check/parse our URL - // The only combination that seems to work: load as EncodedUrl, extract toEncoded(). - QByteArray urlarray(mPost.mLink.c_str()); - QUrl url = QUrl::fromEncoded(urlarray.trimmed()); - QString urlstr = "Invalid Link"; - QString sitestr = "Invalid Link"; + QDateTime qtime; + qtime.setTime_t(mPost.mMeta.mPublishTs); + QString timestamp = qtime.toString("hh:mm dd-MMM-yyyy"); + QString timestamp2 = misc::timeRelativeToNow(mPost.mMeta.mPublishTs); + ui->dateLabel->setText(timestamp2); + ui->dateLabel->setToolTip(timestamp); - bool urlOkay = url.isValid(); - if (urlOkay) - { - QString scheme = url.scheme(); - if ((scheme != "https") - && (scheme != "http") - && (scheme != "ftp") - && (scheme != "retroshare")) + ui->fromLabel->setId(mPost.mMeta.mAuthorId); + + // Use QUrl to check/parse our URL + // The only combination that seems to work: load as EncodedUrl, extract toEncoded(). + QByteArray urlarray(mPost.mLink.c_str()); + QUrl url = QUrl::fromEncoded(urlarray.trimmed()); + QString urlstr = "Invalid Link"; + QString sitestr = "Invalid Link"; + + bool urlOkay = url.isValid(); + if (urlOkay) { - urlOkay = false; - sitestr = "Invalid Link Scheme"; + QString scheme = url.scheme(); + if ((scheme != "https") + && (scheme != "http") + && (scheme != "ftp") + && (scheme != "retroshare")) + { + urlOkay = false; + sitestr = "Invalid Link Scheme"; + } } - } - if (urlOkay) - { - urlstr = QString(" "); - urlstr += messageName(); - urlstr += QString(" "); + if (urlOkay) + { + urlstr = QString(" "); + urlstr += messageName(); + urlstr += QString(" "); - QString siteurl = url.toEncoded(); - sitestr = QString(" %2 ").arg(siteurl).arg(siteurl); + QString siteurl = url.toEncoded(); + sitestr = QString(" %2 ").arg(siteurl).arg(siteurl); - ui->titleLabel->setText(urlstr); - }else - { - ui->titleLabel->setText(messageName()); + ui->titleLabel->setText(urlstr); + }else + { + ui->titleLabel->setText(messageName()); - } - - if (urlarray.isEmpty()) - { - ui->siteLabel->hide(); - } - - ui->siteLabel->setText(sitestr); - - if(mPost.mImage.mData != NULL) - { - QPixmap pixmap; - GxsIdDetails::loadPixmapFromData(mPost.mImage.mData, mPost.mImage.mSize, pixmap,GxsIdDetails::ORIGINAL); - // Wiping data - as its been passed to thumbnail. - - QPixmap sqpixmap = pixmap.scaled(desired_width,desired_height, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); - ui->thumbnailLabel->setPixmap(sqpixmap); - ui->thumbnailLabel->setToolTip(tr("Click to view Picture")); - - QPixmap scaledpixmap; - if(pixmap.width() > 800){ - QPixmap scaledpixmap = pixmap.scaledToWidth(800, Qt::SmoothTransformation); - ui->pictureLabel->setPixmap(scaledpixmap); - }else{ - ui->pictureLabel->setPixmap(pixmap); } - } - else if (urlOkay && (mPost.mImage.mData == NULL)) - { - ui->expandButton->setDisabled(true); - ui->thumbnailLabel->setPixmap(FilesDefs::getPixmapFromQtResourcePath(LINK_IMAGE)); - } - else - { - ui->expandButton->setDisabled(true); - ui->thumbnailLabel->setPixmap(sqpixmap2); + + if (urlarray.isEmpty()) + { + ui->siteLabel->hide(); + } + + ui->siteLabel->setText(sitestr); + + if(mPost.mImage.mData != NULL) + { + QPixmap pixmap; + GxsIdDetails::loadPixmapFromData(mPost.mImage.mData, mPost.mImage.mSize, pixmap,GxsIdDetails::ORIGINAL); + // Wiping data - as its been passed to thumbnail. + + QPixmap sqpixmap = pixmap.scaled(desired_width,desired_height, Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); + ui->thumbnailLabel->setPixmap(sqpixmap); + ui->thumbnailLabel->setToolTip(tr("Click to view Picture")); + + QPixmap scaledpixmap; + if(pixmap.width() > 800){ + QPixmap scaledpixmap = pixmap.scaledToWidth(800, Qt::SmoothTransformation); + ui->pictureLabel->setPixmap(scaledpixmap); + }else{ + ui->pictureLabel->setPixmap(pixmap); + } + } + else if (urlOkay && (mPost.mImage.mData == NULL)) + { + ui->expandButton->setDisabled(true); + ui->thumbnailLabel->setPixmap(FilesDefs::getPixmapFromQtResourcePath(LINK_IMAGE)); + } + else + { + ui->expandButton->setDisabled(true); + ui->thumbnailLabel->setPixmap(sqpixmap2); + } } @@ -701,5 +741,3 @@ void PostedItem::toggleNotes() } } - - diff --git a/retroshare-gui/src/gui/Posted/PostedItem.h b/retroshare-gui/src/gui/Posted/PostedItem.h index 7debd9d75..d726cf830 100644 --- a/retroshare-gui/src/gui/Posted/PostedItem.h +++ b/retroshare-gui/src/gui/Posted/PostedItem.h @@ -55,6 +55,7 @@ private slots: void readAndClearItem(); void copyMessageLink(); void viewPicture(); + void showAuthorInPeople(); signals: void vote(const RsGxsGrpMsgIdPair& msgId, bool up); diff --git a/retroshare-gui/src/gui/Posted/PostedListWidget.ui b/retroshare-gui/src/gui/Posted/PostedListWidget.ui index c4a6d51cd..3fa7069e6 100644 --- a/retroshare-gui/src/gui/Posted/PostedListWidget.ui +++ b/retroshare-gui/src/gui/Posted/PostedListWidget.ui @@ -298,199 +298,10 @@ false - - - - - - - 64 - 64 - - - - - 64 - 64 - - - - :/icons/png/postedlinks.png - - - true - - - - - - - - 14 - - - - TextLabel - - - - - - - - - 6 - - - - - - 75 - true - - - - Popularity - - - - - - - - 75 - true - - - - 0 - - - - - - - - 0 - 0 - - - - - 75 - true - - - - Posts - - - - - - - - 75 - true - - - - 0 - - - - - - - - 75 - true - - - - Created - - - - - - - unknown - - - - - - - - 75 - true - - - - Administrator: - - - - - - - unknown - - - true - - - - - - - - 75 - true - - - - Distribution: - - - - - - - unknown - - - - - - - - 0 - 0 - - - - - 75 - true - - - - Last Post: - - - - - - - unknown - - - - - - + + 6 + + Qt::Horizontal @@ -503,7 +314,7 @@ - + @@ -526,6 +337,194 @@ p, li { white-space: pre-wrap; } + + + + 0 + + + + + + 75 + true + + + + Popularity + + + + + + + + 75 + true + + + + 0 + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Posts + + + + + + + + 75 + true + + + + 0 + + + + + + + + 75 + true + + + + Created + + + + + + + unknown + + + + + + + + 75 + true + + + + Administrator: + + + + + + + unknown + + + true + + + + + + + + 75 + true + + + + Distribution: + + + + + + + unknown + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Last Post: + + + + + + + unknown + + + + + + + + 64 + 64 + + + + + 64 + 64 + + + + :/icons/png/postedlinks.png + + + true + + + + + + + + 14 + + + + TextLabel + + + + + diff --git a/retroshare-gui/src/gui/common/FriendSelectionWidget.cpp b/retroshare-gui/src/gui/common/FriendSelectionWidget.cpp index fa5c439f1..4043fe424 100644 --- a/retroshare-gui/src/gui/common/FriendSelectionWidget.cpp +++ b/retroshare-gui/src/gui/common/FriendSelectionWidget.cpp @@ -36,6 +36,7 @@ #include #include +#include #define COLUMN_NAME 0 #define COLUMN_CHECK 0 @@ -250,24 +251,21 @@ void FriendSelectionWidget::loadIdentities() if(!rsIdentity->getIdentitiesSummaries(ids_meta)) { - std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve identities group info for all identities" << std::endl; + RS_ERR("failed to retrieve identities group info for all identities"); return; - } - std::vector ids; + } - for(auto& meta:ids_meta) - ids.push_back(meta.mGroupId) ; + auto ids = std::make_unique>(); + for(auto& meta: ids_meta) ids->push_back(meta.mGroupId); - RsQThreadUtils::postToObject( [ids,this]() + RsQThreadUtils::postToObject( + [ids = std::move(ids), 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 */ - - gxsIds = ids; // we do that is the GUI thread. Dont try it on another thread! - - fillList() ; - + // We do that is the GUI thread. Dont try it on another thread! + gxsIds = *ids; + /* TODO: To furter optimize away a copy gxsIds could be a unique_ptr + * too */ + fillList(); }, this ); }); } diff --git a/retroshare-gui/src/gui/common/GroupTreeWidget.cpp b/retroshare-gui/src/gui/common/GroupTreeWidget.cpp index fa12ebf54..db9b487b4 100644 --- a/retroshare-gui/src/gui/common/GroupTreeWidget.cpp +++ b/retroshare-gui/src/gui/common/GroupTreeWidget.cpp @@ -397,6 +397,19 @@ bool GroupTreeWidget::isSearchRequestResult(QPoint &point,QString& group_id,uint return search_req_id > 0; } +bool GroupTreeWidget::isSearchRequestResultItem(QTreeWidgetItem *item,QString& group_id,uint32_t& search_req_id) +{ + QTreeWidgetItem *parent = item->parent(); + + if(parent == NULL) + return false ; + + search_req_id = parent->data(COLUMN_DATA, ROLE_REQUEST_ID).toUInt(); + group_id = itemId(item) ; + + return search_req_id > 0; +} + bool GroupTreeWidget::isSearchRequestItem(QPoint &point,uint32_t& search_req_id) { QTreeWidgetItem *item = ui->treeWidget->itemAt(point); @@ -463,6 +476,17 @@ void GroupTreeWidget::fillGroupItems(QTreeWidgetItem *categoryItem, const QList< item->setData(COLUMN_DATA, ROLE_NAME, itemInfo.name); item->setData(COLUMN_DATA, ROLE_DESCRIPTION, itemInfo.description); + // Add children for context strings. This happens in the search. + while(nullptr != item->takeChild(0)); + + for(auto str:itemInfo.context_strings) + if(!str.empty()) + { + QTreeWidgetItem *it = new QTreeWidgetItem(QStringList(QString::fromUtf8(str.c_str()))); + it->setData(COLUMN_DATA,ROLE_ID,itemInfo.id); + item->addChild(it); + } + /* Set last post */ qlonglong lastPost = itemInfo.lastpost.toTime_t(); item->setData(COLUMN_DATA, ROLE_LASTPOST, -lastPost); // negative for correct sorting diff --git a/retroshare-gui/src/gui/common/GroupTreeWidget.h b/retroshare-gui/src/gui/common/GroupTreeWidget.h index 23c90a292..5b40a9487 100644 --- a/retroshare-gui/src/gui/common/GroupTreeWidget.h +++ b/retroshare-gui/src/gui/common/GroupTreeWidget.h @@ -21,6 +21,8 @@ #ifndef GROUPTREEWIDGET_H #define GROUPTREEWIDGET_H +#include + #include #include @@ -47,16 +49,17 @@ public: {} public: - QString id; - QString name; - QString description; - int popularity; - QDateTime lastpost; - QIcon icon; - bool publishKey; - bool adminKey; - quint32 subscribeFlags; - quint32 max_visible_posts ; + QString id; + QString name; + QString description; + int popularity; + QDateTime lastpost; + QIcon icon; + bool publishKey; + bool adminKey; + quint32 subscribeFlags; + quint32 max_visible_posts ; + std::set context_strings; }; //cppcheck-suppress noConstructor @@ -94,6 +97,7 @@ public: bool isSearchRequestItem(QPoint &point,uint32_t& search_req_id); bool isSearchRequestResult(QPoint &point, QString &group_id, uint32_t& search_req_id); + bool isSearchRequestResultItem(QTreeWidgetItem *item,QString& group_id,uint32_t& search_req_id); QTreeWidgetItem *getItemFromId(const QString &id); QTreeWidgetItem *activateId(const QString &id, bool focus); diff --git a/retroshare-gui/src/gui/common/RSTreeView.cpp b/retroshare-gui/src/gui/common/RSTreeView.cpp index 918fbc558..d54987c3e 100644 --- a/retroshare-gui/src/gui/common/RSTreeView.cpp +++ b/retroshare-gui/src/gui/common/RSTreeView.cpp @@ -19,10 +19,43 @@ *******************************************************************************/ #include +#include #include "RSTreeView.h" RSTreeView::RSTreeView(QWidget *parent) : QTreeView(parent) { + setMouseTracking(false); // normally the default, but who knows if it's not goign to change in the future. +} + +void RSTreeView::wheelEvent(QWheelEvent *e) +{ + if(e->modifiers() == Qt::ControlModifier) + emit zoomRequested(e->delta() > 0); + else + QTreeView::wheelEvent(e); +} + +void RSTreeView::mouseMoveEvent(QMouseEvent *e) +{ + QModelIndex idx = indexAt(e->pos()); + + if(idx != selectionModel()->currentIndex()) + selectionModel()->setCurrentIndex(idx,QItemSelectionModel::ClearAndSelect); + + QTreeView::mouseMoveEvent(e); +} + +void RSTreeView::setAutoSelect(bool b) +{ + if(b) + setMouseTracking(true); + else + setMouseTracking(false); +} + +void RSTreeView::resizeEvent(QResizeEvent *e) +{ + emit sizeChanged(e->size()); } void RSTreeView::setPlaceholderText(const QString &text) diff --git a/retroshare-gui/src/gui/common/RSTreeView.h b/retroshare-gui/src/gui/common/RSTreeView.h index 022a643db..116ccac31 100644 --- a/retroshare-gui/src/gui/common/RSTreeView.h +++ b/retroshare-gui/src/gui/common/RSTreeView.h @@ -33,8 +33,20 @@ public: void setPlaceholderText(const QString &text); + // Use this to make selection automatic based on mouse position. This is useful to trigger selection and therefore editing mode + // in trees that show editing widgets using a QStyledItemDelegate + + void setAutoSelect(bool b); + +signals: + void sizeChanged(QSize); + void zoomRequested(bool zoom_or_unzoom); + protected: - void paintEvent(QPaintEvent *event); + virtual void mouseMoveEvent(QMouseEvent *e) override; // overriding so as to manage auto-selection + virtual void wheelEvent(QWheelEvent *e) override; // overriding so as to manage zoom + virtual void resizeEvent(QResizeEvent *e) override; + virtual void paintEvent(QPaintEvent *event) override; QString placeholderText; }; diff --git a/retroshare-gui/src/gui/elastic/elnode.h b/retroshare-gui/src/gui/elastic/elnode.h index 1e0edd1a8..33043ddab 100644 --- a/retroshare-gui/src/gui/elastic/elnode.h +++ b/retroshare-gui/src/gui/elastic/elnode.h @@ -23,6 +23,10 @@ #ifndef ELNODE_H #define ELNODE_H +#include "graphwidget.h" + +#include + #include #if QT_VERSION >= 0x040600 #include @@ -30,9 +34,7 @@ #include #endif #include - -#include -#include "graphwidget.h" +#include class Edge; QT_BEGIN_NAMESPACE diff --git a/retroshare-gui/src/gui/gxs/GxsCommentDialog.cpp b/retroshare-gui/src/gui/gxs/GxsCommentDialog.cpp index f9b8b1dbc..6512c0c54 100644 --- a/retroshare-gui/src/gui/gxs/GxsCommentDialog.cpp +++ b/retroshare-gui/src/gui/gxs/GxsCommentDialog.cpp @@ -36,10 +36,12 @@ GxsCommentDialog::GxsCommentDialog(QWidget *parent, RsTokenService *token_servic /* Invoke the Qt Designer generated QObject setup routine */ ui->setupUi(this); - //ui->postFrame->setVisible(false); - - ui->treeWidget->setup(token_service, comment_service); + setTokenService(token_service,comment_service); + init(); +} +void GxsCommentDialog::init() +{ /* Set header resize modes and initial section sizes */ QHeaderView * ttheader = ui->treeWidget->header () ; ttheader->resizeSection (0, 440); @@ -62,6 +64,20 @@ GxsCommentDialog::GxsCommentDialog(QWidget *parent, RsTokenService *token_servic ui->sortBox->setIconSize(QSize(S*1.5,S*1.5)); } +void GxsCommentDialog::setTokenService(RsTokenService *token_service, RsGxsCommentService *comment_service) +{ + ui->treeWidget->setup(token_service, comment_service); +} + +GxsCommentDialog::GxsCommentDialog(QWidget *parent) + : QWidget(parent), ui(new Ui::GxsCommentDialog) +{ + /* Invoke the Qt Designer generated QObject setup routine */ + ui->setupUi(this); + + init(); +} + GxsCommentDialog::~GxsCommentDialog() { delete(ui); diff --git a/retroshare-gui/src/gui/gxs/GxsCommentDialog.h b/retroshare-gui/src/gui/gxs/GxsCommentDialog.h index 82d2fdbd1..4c506dd21 100644 --- a/retroshare-gui/src/gui/gxs/GxsCommentDialog.h +++ b/retroshare-gui/src/gui/gxs/GxsCommentDialog.h @@ -32,9 +32,11 @@ class GxsCommentDialog: public QWidget Q_OBJECT public: + GxsCommentDialog(QWidget *parent); GxsCommentDialog(QWidget *parent, RsTokenService *token_service, RsGxsCommentService *comment_service); virtual ~GxsCommentDialog(); + void setTokenService(RsTokenService *token_service, RsGxsCommentService *comment_service); void setCommentHeader(QWidget *header); void commentLoad(const RsGxsGroupId &grpId, const std::set &msg_versions, const RsGxsMessageId &most_recent_msgId); @@ -48,6 +50,8 @@ private slots: void sortComments(int); private: + void init(); + RsGxsGroupId mGrpId; RsGxsMessageId mMostRecentMsgId; std::set mMsgVersions; diff --git a/retroshare-gui/src/gui/gxs/GxsCommentTreeWidget.cpp b/retroshare-gui/src/gui/gxs/GxsCommentTreeWidget.cpp index 14413963f..a1f824e9a 100644 --- a/retroshare-gui/src/gui/gxs/GxsCommentTreeWidget.cpp +++ b/retroshare-gui/src/gui/gxs/GxsCommentTreeWidget.cpp @@ -18,6 +18,14 @@ * * *******************************************************************************/ +#include "GxsCommentTreeWidget.h" + +#include "gui/common/FilesDefs.h" +#include "gui/common/RSElidedItemDelegate.h" +#include "gui/common/RSTreeWidgetItem.h" +#include "gui/gxs/GxsCreateCommentDialog.h" +#include "gui/gxs/GxsIdTreeWidgetItem.h" + #include #include #include @@ -25,15 +33,9 @@ #include #include #include +#include #include -#include "gui/common/RSElidedItemDelegate.h" -#include "gui/common/FilesDefs.h" -#include "gui/gxs/GxsCommentTreeWidget.h" -#include "gui/gxs/GxsCreateCommentDialog.h" -#include "gui/gxs/GxsIdTreeWidgetItem.h" -#include "gui/common/RSTreeWidgetItem.h" - #include #define PCITEM_COLUMN_COMMENT 0 diff --git a/retroshare-gui/src/gui/gxs/GxsGroupFrameDialog.cpp b/retroshare-gui/src/gui/gxs/GxsGroupFrameDialog.cpp index 1245c80b2..b7f3fcf20 100644 --- a/retroshare-gui/src/gui/gxs/GxsGroupFrameDialog.cpp +++ b/retroshare-gui/src/gui/gxs/GxsGroupFrameDialog.cpp @@ -29,6 +29,7 @@ #include "gui/settings/rsharesettings.h" #include "gui/RetroShareLink.h" #include "gui/gxs/GxsGroupShareKey.h" +#include "gui/common/GroupTreeWidget.h" #include "gui/common/RSTreeWidget.h" #include "gui/notifyqt.h" #include "gui/common/UIStateHelper.h" @@ -89,7 +90,6 @@ GxsGroupFrameDialog::GxsGroupFrameDialog(RsGxsIfaceHelper *ifaceImpl, QWidget *p mSubscribedGroups = NULL; mPopularGroups = NULL; mOtherGroups = NULL; - mMessageWidget = NULL; /* Setup Queue */ mInterface = ifaceImpl; @@ -251,6 +251,13 @@ void GxsGroupFrameDialog::processSettings(bool load) Settings->endGroup(); } +bool GxsGroupFrameDialog::useTabs() +{ + GroupFrameSettings groupFrameSettings; + + return Settings->getGroupFrameSettings(groupFrameSettingsType(), groupFrameSettings) && groupFrameSettings.mOpenAllInNewTab; +} + void GxsGroupFrameDialog::settingsChanged() { GroupFrameSettings groupFrameSettings; @@ -262,17 +269,15 @@ void GxsGroupFrameDialog::settingsChanged() void GxsGroupFrameDialog::setSingleTab(bool singleTab) { - if (singleTab) { - if (!mMessageWidget) { - mMessageWidget = createMessageWidget(RsGxsGroupId()); - // remove close button of the the first tab - ui->messageTabWidget->hideCloseButton(ui->messageTabWidget->indexOf(mMessageWidget)); - } - } else { - if (mMessageWidget) { - delete(mMessageWidget); - mMessageWidget = NULL; - } + if (singleTab) + { + while(ui->messageTabWidget->count() > 1) + { + auto w = ui->messageTabWidget->widget(0) ; + ui->messageTabWidget->removeTab(0); + delete w; + } + ui->messageTabWidget->hideCloseButton(0); } } @@ -286,55 +291,50 @@ void GxsGroupFrameDialog::updateDisplay(bool complete) if(complete) // || !getGrpIds().empty() || !getGrpIdsMeta().empty()) { updateGroupSummary(); /* Update group list */ - updateSearchResults() ; +// updateSearchResults() ; } void GxsGroupFrameDialog::updateSearchResults() { - const std::set& reqs = getSearchRequests(); + for(auto& it:mSearchGroupsItems) + updateSearchResults(it.first); +} - for(auto it(reqs.begin());it!=reqs.end();++it) - { - std::cerr << "updating search ID " << std::hex << *it << std::dec << std::endl; +void GxsGroupFrameDialog::updateSearchResults(const TurtleRequestId& sid) +{ + std::cerr << "updating search ID " << std::hex << sid << std::dec << std::endl; - std::map group_infos; + std::map group_infos; - getDistantSearchResults(*it,group_infos) ; + getDistantSearchResults(sid,group_infos) ; - std::cerr << "retrieved " << std::endl; + std::cerr << "retrieved " << std::endl; - auto it2 = mSearchGroupsItems.find(*it); + auto it2 = mSearchGroupsItems.find(sid); - if(mSearchGroupsItems.end() == it2) - { - std::cerr << "GxsGroupFrameDialog::updateSearchResults(): received result notification for req " << std::hex << *it << std::dec << " but no item present!" << std::endl; - continue ; // we could create the item just as well but since this situation is not supposed to happen, I prefer to make this a failure case. - } + QList group_items ; - QList group_items ; + for(auto it3(group_infos.begin());it3!=group_infos.end();++it3) + { + std::cerr << " adding group " << it3->first << " " << it3->second.mGroupId << " \"" << it3->second.mGroupName << "\"" << std::endl; + for(auto s:it3->second.mSearchContexts) + std::cerr << " Context string \"" << s << "\"" << std::endl; - for(auto it3(group_infos.begin());it3!=group_infos.end();++it3) - if(mCachedGroupMetas.find(it3->first) == mCachedGroupMetas.end()) - { - std::cerr << " adding new group " << it3->first << " " - << it3->second.mGroupId << " \"" - << it3->second.mGroupName << "\"" << std::endl; + GroupItemInfo i; + i.id = QString(it3->second.mGroupId.toStdString().c_str()); + i.name = QString::fromUtf8(it3->second.mGroupName.c_str()); + i.popularity = 0; // could be set to the number of hits + i.lastpost = QDateTime::fromTime_t(it3->second.mLastMessageTs); + i.subscribeFlags = 0; // irrelevant here + i.publishKey = false ; // IS_GROUP_PUBLISHER(groupInfo.mSubscribeFlags); + i.adminKey = false ; // IS_GROUP_ADMIN(groupInfo.mSubscribeFlags); + i.max_visible_posts = it3->second.mNumberOfMessages; + i.context_strings = it3->second.mSearchContexts; - GroupItemInfo i; - i.id = QString(it3->second.mGroupId.toStdString().c_str()); - i.name = QString::fromUtf8(it3->second.mGroupName.c_str()); - i.popularity = 0; // could be set to the number of hits - i.lastpost = QDateTime::fromTime_t(it3->second.mLastMessageTs); - i.subscribeFlags = 0; // irrelevant here - i.publishKey = false ; // IS_GROUP_PUBLISHER(groupInfo.mSubscribeFlags); - i.adminKey = false ; // IS_GROUP_ADMIN(groupInfo.mSubscribeFlags); - i.max_visible_posts = it3->second.mNumberOfMessages; + group_items.push_back(i); + } - group_items.push_back(i); - } - - ui->groupTreeWidget->fillGroupItems(it2->second, group_items); - } + ui->groupTreeWidget->fillGroupItems(it2->second, group_items); } void GxsGroupFrameDialog::todo() @@ -360,13 +360,22 @@ void GxsGroupFrameDialog::removeCurrentSearch() mSearchGroupsItems.erase(it); mKnownGroups.erase(search_request_id); + + clearDistantSearchResults(search_request_id); } void GxsGroupFrameDialog::removeAllSearches() { for(auto it(mSearchGroupsItems.begin());it!=mSearchGroupsItems.end();++it) - ui->groupTreeWidget->removeSearchItem(it->second) ; + { + QString group_id; + TurtleRequestId search_request_id; + if(ui->groupTreeWidget->isSearchRequestResultItem(it->second,group_id,search_request_id)) + clearDistantSearchResults(search_request_id); + + ui->groupTreeWidget->removeSearchItem(it->second) ; + } mSearchGroupsItems.clear(); mKnownGroups.clear(); } @@ -390,6 +399,7 @@ static uint32_t checkDelay(uint32_t time_in_secs) return 365 * 86400; } + void GxsGroupFrameDialog::groupTreeCustomPopupMenu(QPoint point) { // First separately handle the case of search top level items @@ -432,12 +442,10 @@ void GxsGroupFrameDialog::groupTreeCustomPopupMenu(QPoint point) QMenu contextMnu(this); QAction *action; - if (mMessageWidget) { - action = contextMnu.addAction(QIcon(IMAGE_TABNEW), tr("Open in new tab"), this, SLOT(openInNewTab())); - if (mGroupId.isNull() || messageWidget(mGroupId, true)) { - action->setEnabled(false); - } - } + action = contextMnu.addAction(QIcon(IMAGE_TABNEW), tr("Open in new tab"), this, SLOT(openInNewTab())); + + if(mGroupId.isNull()) // dont enable the open in tab if a tab is already here + action->setEnabled(false); if (isSubscribed) { action = contextMnu.addAction(QIcon(IMAGE_UNSUBSCRIBE), tr("Unsubscribe"), this, SLOT(unsubscribeGroup())); @@ -673,7 +681,7 @@ bool GxsGroupFrameDialog::getCurrentGroupName(QString& name) void GxsGroupFrameDialog::markMsgAsRead() { - GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId, false); + GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId); if (msgWidget) { msgWidget->setAllMessagesRead(true); } @@ -681,7 +689,7 @@ void GxsGroupFrameDialog::markMsgAsRead() void GxsGroupFrameDialog::markMsgAsUnread() { - GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId, false); + GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId); if (msgWidget) { msgWidget->setAllMessagesRead(false); } @@ -759,7 +767,7 @@ bool GxsGroupFrameDialog::navigate(const RsGxsGroupId &groupId, const RsGxsMessa changedCurrentGroup(groupIdString); /* search exisiting tab */ - GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId, false); + GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId); if (!msgWidget) { return false; } @@ -771,17 +779,16 @@ bool GxsGroupFrameDialog::navigate(const RsGxsGroupId &groupId, const RsGxsMessa return msgWidget->navigate(msgId); } -GxsMessageFrameWidget *GxsGroupFrameDialog::messageWidget(const RsGxsGroupId &groupId, bool ownTab) +GxsMessageFrameWidget *GxsGroupFrameDialog::messageWidget(const RsGxsGroupId &groupId) { int tabCount = ui->messageTabWidget->count(); - for (int index = 0; index < tabCount; ++index) { + + for (int index = 0; index < tabCount; ++index) + { GxsMessageFrameWidget *childWidget = dynamic_cast(ui->messageTabWidget->widget(index)); - if (ownTab && mMessageWidget && childWidget == mMessageWidget) { - continue; - } - if (childWidget && childWidget->groupId() == groupId) { + + if (childWidget && childWidget->groupId() == groupId) return childWidget; - } } return NULL; @@ -790,9 +797,9 @@ GxsMessageFrameWidget *GxsGroupFrameDialog::messageWidget(const RsGxsGroupId &gr GxsMessageFrameWidget *GxsGroupFrameDialog::createMessageWidget(const RsGxsGroupId &groupId) { GxsMessageFrameWidget *msgWidget = createMessageFrameWidget(groupId); - if (!msgWidget) { + + if (!msgWidget) return NULL; - } int index = ui->messageTabWidget->addTab(msgWidget, msgWidget->groupName(true)); ui->messageTabWidget->setTabIcon(index, msgWidget->groupIcon()); @@ -817,40 +824,44 @@ GxsCommentDialog *GxsGroupFrameDialog::commentWidget(const RsGxsMessageId& msgId return NULL; } -void GxsGroupFrameDialog::changedCurrentGroup(const QString &groupId) +void GxsGroupFrameDialog::changedCurrentGroup(const QString& groupId) { if (mInFill) { return; } - if (groupId.isEmpty()) { - if (mMessageWidget) { - mMessageWidget->setGroupId(RsGxsGroupId()); - ui->messageTabWidget->setCurrentWidget(mMessageWidget); - } + if (groupId.isEmpty()) + { + auto w = currentWidget(); + + if(w) + w->setGroupId(RsGxsGroupId()); + return; } mGroupId = RsGxsGroupId(groupId.toStdString()); - if (mGroupId.isNull()) { + + if (mGroupId.isNull()) return; - } /* search exisiting tab */ - GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId, true); + GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId); - if (!msgWidget) { - if (mMessageWidget) { - /* not found, use standard tab */ - msgWidget = mMessageWidget; - msgWidget->setGroupId(mGroupId); - } else { - /* create new tab */ - msgWidget = createMessageWidget(mGroupId); + // check that we have at least one tab + + if(msgWidget) + ui->messageTabWidget->setCurrentWidget(msgWidget); + else + { + if(useTabs() || ui->messageTabWidget->count()==0) + { + msgWidget = createMessageWidget(RsGxsGroupId(groupId.toStdString())); + ui->messageTabWidget->setCurrentWidget(msgWidget); } + else + currentWidget()->setGroupId(mGroupId); } - - ui->messageTabWidget->setCurrentWidget(msgWidget); } void GxsGroupFrameDialog::groupTreeMiddleButtonClicked(QTreeWidgetItem *item) @@ -870,37 +881,31 @@ void GxsGroupFrameDialog::openGroupInNewTab(const RsGxsGroupId &groupId) } /* search exisiting tab */ - GxsMessageFrameWidget *msgWidget = messageWidget(groupId, true); - if (!msgWidget) { - /* not found, create new tab */ - msgWidget = createMessageWidget(groupId); - } + GxsMessageFrameWidget *msgWidget = createMessageWidget(groupId); ui->messageTabWidget->setCurrentWidget(msgWidget); } void GxsGroupFrameDialog::messageTabCloseRequested(int index) { - QWidget *widget = ui->messageTabWidget->widget(index); - if (!widget) { + if(ui->messageTabWidget->count() == 1) /* Don't close single tab */ return; - } - GxsMessageFrameWidget *msgWidget = dynamic_cast(widget); - if (msgWidget && msgWidget == mMessageWidget) { - /* Don't close single tab */ - return; - } + GxsMessageFrameWidget *msgWidget = dynamic_cast(ui->messageTabWidget->widget(index)); + delete msgWidget ; +} - delete(widget); +GxsMessageFrameWidget *GxsGroupFrameDialog::currentWidget() const +{ + return dynamic_cast(ui->messageTabWidget->widget(ui->messageTabWidget->currentIndex())); } void GxsGroupFrameDialog::messageTabChanged(int index) { GxsMessageFrameWidget *msgWidget = dynamic_cast(ui->messageTabWidget->widget(index)); - if (!msgWidget) { + + if (!msgWidget) return; - } ui->groupTreeWidget->activateId(QString::fromStdString(msgWidget->groupId().toStdString()), false); } @@ -1074,15 +1079,20 @@ void GxsGroupFrameDialog::updateGroupSummary() { RsThread::async([this]() { - std::list groupInfo; + auto groupInfo = new std::list() ; - if(!getGroupData(groupInfo)) + if(!getGroupData(*groupInfo)) { - std::cerr << __PRETTY_FUNCTION__ << " failed to collect group info " << std::endl; + std::cerr << __PRETTY_FUNCTION__ << " failed to collect group info." << std::endl; + delete groupInfo; return; } - if(groupInfo.empty()) + if(groupInfo->empty()) + { + std::cerr << __PRETTY_FUNCTION__ << " no group info collected." << std::endl; + delete groupInfo; return; + } RsQThreadUtils::postToObject( [this,groupInfo]() { @@ -1092,7 +1102,7 @@ void GxsGroupFrameDialog::updateGroupSummary() * Qt::QueuedConnection is important! */ - insertGroupsData(groupInfo); + insertGroupsData(*groupInfo); updateSearchResults(); mStateHelper->setLoading(TOKEN_TYPE_GROUP_SUMMARY, false); @@ -1111,12 +1121,14 @@ void GxsGroupFrameDialog::updateGroupSummary() // now delete the data that is not used anymore - for(auto& g:groupInfo) + for(auto& g:*groupInfo) { mCachedGroupMetas[g->mMeta.mGroupId] = g->mMeta; delete g; } + delete groupInfo; + }, this ); }); } diff --git a/retroshare-gui/src/gui/gxs/GxsGroupFrameDialog.h b/retroshare-gui/src/gui/gxs/GxsGroupFrameDialog.h index 902e49087..006f593a4 100644 --- a/retroshare-gui/src/gui/gxs/GxsGroupFrameDialog.h +++ b/retroshare-gui/src/gui/gxs/GxsGroupFrameDialog.h @@ -161,7 +161,9 @@ private: virtual void groupTreeCustomActions(RsGxsGroupId /*grpId*/, int /*subscribeFlags*/, QList &/*actions*/) {} virtual RsGxsCommentService *getCommentService() { return NULL; } virtual QWidget *createCommentHeaderWidget(const RsGxsGroupId &/*grpId*/, const RsGxsMessageId &/*msgId*/) { return NULL; } - virtual bool getDistantSearchResults(TurtleRequestId /* id */, std::map& /* group_infos */){ return false ;} + virtual bool getDistantSearchResults(TurtleRequestId /* id */, std::map& /* group_infos */){ return false ;} + virtual void clearDistantSearchResults(TurtleRequestId /* id */) {} + virtual RsGxsGenericGroupData *getDistantSearchResultGroupData(const RsGxsGroupId& group_id){ return nullptr ;} void initUi(); @@ -181,24 +183,27 @@ private: // subscribe/unsubscribe ack. - GxsMessageFrameWidget *messageWidget(const RsGxsGroupId &groupId, bool ownTab); + GxsMessageFrameWidget *messageWidget(const RsGxsGroupId &groupId); GxsMessageFrameWidget *createMessageWidget(const RsGxsGroupId &groupId); GxsCommentDialog *commentWidget(const RsGxsMessageId &msgId); protected: - void updateSearchResults(); + void updateSearchResults(const TurtleRequestId &sid); + void updateSearchResults(); // update all searches bool mCountChildMsgs; // Count unread child messages? private: + GxsMessageFrameWidget *currentWidget() const; + bool useTabs(); + bool mInitialized; bool mInFill; bool mDistSyncAllowed; QString mSettingsName; RsGxsGroupId mGroupId; RsGxsIfaceHelper *mInterface; - GxsMessageFrameWidget *mMessageWidget; QTreeWidgetItem *mYourGroups; QTreeWidgetItem *mSubscribedGroups; diff --git a/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp b/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp index 22469575f..701a9153d 100644 --- a/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp +++ b/retroshare-gui/src/gui/gxs/GxsIdDetails.cpp @@ -18,21 +18,23 @@ * * *******************************************************************************/ -#include -#include -#include -#include - -#include -#include -#include "gui/common/AvatarDialog.h" #include "GxsIdDetails.h" + +#include "gui/common/AvatarDialog.h" #include "retroshare-gui/RsAutoUpdatePage.h" #include +#include + +#include +#include +#include +#include +#include +#include #include -#include +#include /* Images for tag icons */ #define IMAGE_LOADING ":/images/folder-draft.png" diff --git a/retroshare-gui/src/gui/gxs/GxsMessageFrameWidget.h b/retroshare-gui/src/gui/gxs/GxsMessageFrameWidget.h index 9b9a8e34e..36c2f4f67 100644 --- a/retroshare-gui/src/gui/gxs/GxsMessageFrameWidget.h +++ b/retroshare-gui/src/gui/gxs/GxsMessageFrameWidget.h @@ -55,6 +55,7 @@ signals: void groupChanged(QWidget *widget); void waitingChanged(QWidget *widget); void loadComment(const RsGxsGroupId &groupId, const QVector& msg_versions,const RsGxsMessageId &msgId, const QString &title); + void groupDataLoaded(); protected: virtual void setAllMessagesReadDo(bool read, uint32_t &token) = 0; diff --git a/retroshare-gui/src/gui/gxschannels/CreateGxsChannelMsg.cpp b/retroshare-gui/src/gui/gxschannels/CreateGxsChannelMsg.cpp index 56c910237..ef1a6271e 100644 --- a/retroshare-gui/src/gui/gxschannels/CreateGxsChannelMsg.cpp +++ b/retroshare-gui/src/gui/gxschannels/CreateGxsChannelMsg.cpp @@ -72,7 +72,8 @@ CreateGxsChannelMsg::CreateGxsChannelMsg(const RsGxsGroupId &cId, RsGxsMessageId connect(addFileButton, SIGNAL(clicked() ), this , SLOT(addExtraFile())); connect(addfilepushButton, SIGNAL(clicked() ), this , SLOT(addExtraFile())); - + connect(subjectEdit,SIGNAL(textChanged(const QString&)),this,SLOT(updatePreviewText(const QString&))); + connect(addThumbnailButton, SIGNAL(clicked() ), this , SLOT(addThumbnail())); connect(thumbNailCb, SIGNAL(toggled(bool)), this, SLOT(allowAutoMediaThumbNail(bool))); connect(stackedWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenu(QPoint))); @@ -605,6 +606,11 @@ void CreateGxsChannelMsg::saveChannelInfo(const RsGroupMetaData &meta) subjectEdit->setFocus(); } +void CreateGxsChannelMsg::updatePreviewText(const QString& s) +{ + preview_W->setText(s); +} + void CreateGxsChannelMsg::sendMsg() { #ifdef DEBUG_CREATE_GXS_MSG @@ -717,7 +723,7 @@ void CreateGxsChannelMsg::sendMessage(const std::string &subject, const std::str void CreateGxsChannelMsg::addThumbnail() { - QPixmap img = misc::getOpenThumbnailedPicture(this, tr("Load thumbnail picture"), 156, 107); + QPixmap img = misc::getOpenThumbnailedPicture(this, tr("Load thumbnail picture"), 107,156); // these absolute sizes are terrible if (img.isNull()) return; @@ -725,7 +731,7 @@ void CreateGxsChannelMsg::addThumbnail() picture = img; // to show the selected - thumbnail_label->setPixmap(picture); + preview_W->setPixmap(picture); } void CreateGxsChannelMsg::loadOriginalChannelPostInfo() @@ -769,7 +775,7 @@ void CreateGxsChannelMsg::loadOriginalChannelPostInfo() if(post.mThumbnail.mData != NULL) { GxsIdDetails::loadPixmapFromData(post.mThumbnail.mData,post.mThumbnail.mSize,picture,GxsIdDetails::ORIGINAL); - thumbnail_label->setPixmap(picture); + preview_W->setPixmap(picture); } diff --git a/retroshare-gui/src/gui/gxschannels/CreateGxsChannelMsg.h b/retroshare-gui/src/gui/gxschannels/CreateGxsChannelMsg.h index 7c1058695..44d867ba2 100644 --- a/retroshare-gui/src/gui/gxschannels/CreateGxsChannelMsg.h +++ b/retroshare-gui/src/gui/gxschannels/CreateGxsChannelMsg.h @@ -58,6 +58,7 @@ private slots: void addExtraFile(); void checkAttachmentReady(); void deleteAttachment(); + void updatePreviewText(const QString &); void cancelMsg(); void sendMsg(); diff --git a/retroshare-gui/src/gui/gxschannels/CreateGxsChannelMsg.ui b/retroshare-gui/src/gui/gxschannels/CreateGxsChannelMsg.ui index 703f43bb1..c0d28ac5f 100644 --- a/retroshare-gui/src/gui/gxschannels/CreateGxsChannelMsg.ui +++ b/retroshare-gui/src/gui/gxschannels/CreateGxsChannelMsg.ui @@ -6,8 +6,8 @@ 0 0 - 671 - 513 + 736 + 271 @@ -20,7 +20,7 @@ :/images/logo/logo_16.png:/images/logo/logo_16.png - + 0 @@ -33,7 +33,10 @@ 0 - + + 6 + + 0 @@ -52,53 +55,15 @@ QFrame::Raised - - - - Channel Post - - - - :/icons/png/comment.png:/icons/png/comment.png - - - - 24 - 24 - - - - - - - - Attachments - - - - :/icons/png/attachements.png:/icons/png/attachements.png - - - - 24 - 24 - - - - - - - - Qt::Horizontal - - - - 486 - 20 - - - - + + 3 + + + 6 + + + 0 + @@ -114,7 +79,10 @@ 0 - + + + 6 + 0 @@ -127,146 +95,158 @@ 0 - - - - - - - 156 - 107 - - - - - 0 - 0 - - - - :/images/thumb-default-video.png - - + + + + 6 + + + QLayout::SetFixedSize + + + - - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + + + + 6 + + + + + 6 + + + + + + 0 + 0 + + + + + 75 + true + + + + Channel Post to: + + + + + + + true + + + true + + + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; font-weight:600;">Attachments:</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/images/feedback_arrow.png" /><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt;"> Use Drag and Drop / Add Files button, to Hash new files.</span></p> <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/images/feedback_arrow.png" /><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt;"> Copy/Paste RetroShare links from your shares</span></p></body></html> - - - - - - - Add Channel Thumbnail - - - - :/icons/png/add-image.png:/icons/png/add-image.png - - - - 24 - 24 - - - - - - - - Add File to Attach - - - - :/icons/png/add-file.png:/icons/png/add-file.png - - - - 24 - 24 - - - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 40 - 20 - - - + + + + + + + + + Add Channel Thumbnail + + + + :/icons/png/add-image.png:/icons/png/add-image.png + + + + 24 + 24 + + + + + + + + Add File to Attach + + + + :/icons/png/add-file.png:/icons/png/add-file.png + + + + 24 + 24 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + + 9 + + + + + Title + + + + + + + + + Qt::Vertical + + + + 20 + 1 + + + + + - - - - Message - - - - 0 - - - - - Title - - - - - - - - - - - - - - - - 0 - 0 - - - - - 75 - true - - - - Channel Post to: - - - - - - - true - - - true - - - - + + @@ -276,7 +256,7 @@ p, li { white-space: pre-wrap; } 0 - 0 + 6 0 @@ -329,7 +309,7 @@ p, li { white-space: pre-wrap; } 0 0 - 632 + 81 24 @@ -403,6 +383,53 @@ p, li { white-space: pre-wrap; } + + + + Channel Post + + + + :/icons/png/comment.png:/icons/png/comment.png + + + + 24 + 24 + + + + + + + + Qt::Horizontal + + + + 486 + 20 + + + + + + + + Attachments + + + + :/icons/png/attachements.png:/icons/png/attachements.png + + + + 24 + 24 + + + + @@ -455,10 +482,16 @@ p, li { white-space: pre-wrap; }

util/RichTextEdit.h
1 + + ChannelPostThumbnailView + QWidget +
gui/gxschannels/GxsChannelPostThumbnail.h
+ 1 +
- + diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelDialog.cpp b/retroshare-gui/src/gui/gxschannels/GxsChannelDialog.cpp index 8e25e4ea0..5e1c40fe4 100644 --- a/retroshare-gui/src/gui/gxschannels/GxsChannelDialog.cpp +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelDialog.cpp @@ -26,7 +26,7 @@ #include "GxsChannelDialog.h" #include "GxsChannelGroupDialog.h" -#include "GxsChannelPostsWidget.h" +#include "GxsChannelPostsWidgetWithModel.h" #include "CreateGxsChannelMsg.h" #include "GxsChannelUserNotify.h" #include "gui/gxs/GxsGroupShareKey.h" @@ -61,9 +61,7 @@ void GxsChannelDialog::handleEvent_main_thread(std::shared_ptr ev { const RsGxsChannelEvent *e = dynamic_cast(event.get()); - if(!e) - return; - + if(e) switch(e->mChannelEventCode) { case RsChannelEventCode::NEW_MESSAGE: // [[fallthrough]]; @@ -72,11 +70,6 @@ void GxsChannelDialog::handleEvent_main_thread(std::shared_ptr ev updateGroupStatisticsReal(e->mChannelGroupId); // update the list immediately break; - case RsChannelEventCode::RECEIVED_DISTANT_SEARCH_RESULT: - mSearchResults.insert(e->mDistantSearchRequestId); - updateSearchResults(); - break; - case RsChannelEventCode::NEW_CHANNEL: // [[fallthrough]]; case RsChannelEventCode::SUBSCRIBE_STATUS_CHANGED: updateDisplay(true); @@ -89,6 +82,13 @@ void GxsChannelDialog::handleEvent_main_thread(std::shared_ptr ev default: break; } + + + const RsGxsChannelSearchResultEvent*f = dynamic_cast(event.get()); + + if(nullptr != f) + for(auto it:f->mSearchResultsMap) + updateSearchResults(it.first); } GxsChannelDialog::~GxsChannelDialog() @@ -204,7 +204,7 @@ int GxsChannelDialog::shareKeyType() GxsMessageFrameWidget *GxsChannelDialog::createMessageFrameWidget(const RsGxsGroupId &groupId) { - return new GxsChannelPostsWidget(groupId); + return new GxsChannelPostsWidgetWithModel(groupId,this); } void GxsChannelDialog::setDefaultDirectory() @@ -396,21 +396,36 @@ void GxsChannelDialog::groupInfoToGroupItemInfo(const RsGxsGenericGroupData *gro groupItemInfo.description = QString::fromUtf8(channelGroupData->mDescription.c_str()); } +void GxsChannelDialog::clearDistantSearchResults(TurtleRequestId id) +{ + rsGxsChannels->clearDistantSearchResults(id); +} + TurtleRequestId GxsChannelDialog::distantSearch(const QString& search_string) { return rsGxsChannels->turtleSearchRequest(search_string.toStdString()) ; } -bool GxsChannelDialog::getDistantSearchResults(TurtleRequestId id, std::map& group_infos) +bool GxsChannelDialog::getDistantSearchResults(TurtleRequestId id, std::map& group_infos) { return rsGxsChannels->retrieveDistantSearchResults(id,group_infos); } +RsGxsGenericGroupData *GxsChannelDialog::getDistantSearchResultGroupData(const RsGxsGroupId& group_id) +{ + RsGxsChannelGroup channel_group; + + if(rsGxsChannels->getDistantSearchResultGroupData(group_id,channel_group)) + return new RsGxsGenericGroupData(channel_group); + else + return nullptr; +} + void GxsChannelDialog::checkRequestGroup(const RsGxsGroupId& grpId) { RsGxsChannelGroup distant_group; - if( rsGxsChannels->retrieveDistantGroup(grpId,distant_group)) // normally we should also check that the group meta is not already here. + if( rsGxsChannels->getDistantSearchResultGroupData(grpId,distant_group)) // normally we should also check that the group meta is not already here. { std::cerr << "GxsChannelDialog::checkRequestGroup() sending turtle request for group data for group " << grpId << std::endl; rsGxsChannels->turtleGroupRequest(grpId); diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelDialog.h b/retroshare-gui/src/gui/gxschannels/GxsChannelDialog.h index b0b46205d..63b6ba38a 100644 --- a/retroshare-gui/src/gui/gxschannels/GxsChannelDialog.h +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelDialog.h @@ -43,10 +43,12 @@ public: protected: /* GxsGroupFrameDialog */ - virtual bool getDistantSearchResults(TurtleRequestId id, std::map& group_infos); + virtual bool getDistantSearchResults(TurtleRequestId id, std::map &group_infos) override; + virtual RsGxsGenericGroupData *getDistantSearchResultGroupData(const RsGxsGroupId& group_id) override; - virtual TurtleRequestId distantSearch(const QString& search_string) ; - virtual void checkRequestGroup(const RsGxsGroupId& grpId) ; + virtual TurtleRequestId distantSearch(const QString& search_string) override; + virtual void checkRequestGroup(const RsGxsGroupId& grpId) override ; + virtual void clearDistantSearchResults(TurtleRequestId id) override; // Implementation of some abstract methods in GxsGroupFrameDialog diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelFilesStatusWidget.cpp b/retroshare-gui/src/gui/gxschannels/GxsChannelFilesStatusWidget.cpp index 6dfe8a673..bc019e426 100644 --- a/retroshare-gui/src/gui/gxschannels/GxsChannelFilesStatusWidget.cpp +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelFilesStatusWidget.cpp @@ -18,6 +18,7 @@ * * *******************************************************************************/ +#include #include #include #include @@ -27,11 +28,13 @@ #include "GxsChannelFilesStatusWidget.h" #include "ui_GxsChannelFilesStatusWidget.h" #include "gui/common/RsUrlHandler.h" +#include "gui/common/FilesDefs.h" +#include "util/misc.h" #include "retroshare/rsfiles.h" -GxsChannelFilesStatusWidget::GxsChannelFilesStatusWidget(const RsGxsGroupId &groupId, const RsGxsMessageId &messageId, const RsGxsFile &file, QWidget *parent) : - QWidget(parent), mGroupId(groupId), mMessageId(messageId), mFile(file), ui(new Ui::GxsChannelFilesStatusWidget) +GxsChannelFilesStatusWidget::GxsChannelFilesStatusWidget(const RsGxsFile &file, QWidget *parent) : + QWidget(parent), mFile(file), ui(new Ui::GxsChannelFilesStatusWidget) { ui->setupUi(this); @@ -40,11 +43,21 @@ GxsChannelFilesStatusWidget::GxsChannelFilesStatusWidget(const RsGxsGroupId &gro setSize(mFile.mSize); /* Connect signals */ - connect(ui->downloadToolButton, SIGNAL(clicked()), this, SLOT(download())); + connect(ui->downloadPushButton, SIGNAL(clicked()), this, SLOT(download())); connect(ui->resumeToolButton, SIGNAL(clicked()), this, SLOT(resume())); connect(ui->pauseToolButton, SIGNAL(clicked()), this, SLOT(pause())); connect(ui->cancelToolButton, SIGNAL(clicked()), this, SLOT(cancel())); - connect(ui->openFolderToolButton, SIGNAL(clicked()), this, SLOT(openFolder())); + connect(ui->openFilePushButton, SIGNAL(clicked()), this, SLOT(openFile())); + + ui->downloadPushButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/png/download.png")); + ui->openFolderToolButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/png/arrow.png")); + + QAction *openfolder = new QAction(tr("Open folder"), this); + connect(openfolder, SIGNAL(triggered()), this, SLOT(openFolder())); + + QMenu *menu = new QMenu(); + menu->addAction(openfolder); + ui->openFolderToolButton->setMenu(menu); check(); } @@ -80,6 +93,17 @@ void GxsChannelFilesStatusWidget::check() if (rsFiles->alreadyHaveFile(mFile.mHash, fileInfo)) { mState = STATE_LOCAL; setSize(fileInfo.size); + + /* check if the file is a media file */ + if (!misc::isPreviewable(QFileInfo(QString::fromUtf8(fileInfo.path.c_str())).suffix())) + { + /* check if the file is not a media file and change text */ + ui->openFilePushButton->setText(tr("Open file")); + } else { + ui->openFilePushButton->setText(tr("Play")); + ui->openFilePushButton->setIcon(FilesDefs::getIconFromQtResourcePath(":/icons/png/play.png")); + } + } else { FileInfo fileInfo; bool detailsOk = rsFiles->FileDetails(mFile.mHash, RS_FILE_HINTS_DOWNLOAD | RS_FILE_HINTS_SPEC_ONLY, fileInfo); @@ -126,11 +150,12 @@ void GxsChannelFilesStatusWidget::check() case STATE_ERROR: repeat = 0; - ui->downloadToolButton->hide(); + ui->downloadPushButton->hide(); ui->resumeToolButton->hide(); ui->pauseToolButton->hide(); ui->cancelToolButton->hide(); ui->progressBar->hide(); + ui->openFilePushButton->hide(); ui->openFolderToolButton->hide(); statusText = tr("Error"); @@ -140,11 +165,12 @@ void GxsChannelFilesStatusWidget::check() case STATE_REMOTE: repeat = 30000; - ui->downloadToolButton->show(); + ui->downloadPushButton->show(); ui->resumeToolButton->hide(); ui->pauseToolButton->hide(); ui->cancelToolButton->hide(); ui->progressBar->hide(); + ui->openFilePushButton->hide(); ui->openFolderToolButton->hide(); break; @@ -152,11 +178,12 @@ void GxsChannelFilesStatusWidget::check() case STATE_DOWNLOAD: repeat = 1000; - ui->downloadToolButton->hide(); + ui->downloadPushButton->hide(); ui->resumeToolButton->hide(); ui->pauseToolButton->show(); ui->cancelToolButton->show(); ui->progressBar->show(); + ui->openFilePushButton->hide(); ui->openFolderToolButton->hide(); break; @@ -164,11 +191,12 @@ void GxsChannelFilesStatusWidget::check() case STATE_PAUSED: repeat = 1000; - ui->downloadToolButton->hide(); + ui->downloadPushButton->hide(); ui->resumeToolButton->show(); ui->pauseToolButton->hide(); ui->cancelToolButton->show(); ui->progressBar->hide(); + ui->openFilePushButton->hide(); ui->openFolderToolButton->hide(); statusText = tr("Paused"); @@ -178,11 +206,12 @@ void GxsChannelFilesStatusWidget::check() case STATE_WAITING: repeat = 1000; - ui->downloadToolButton->hide(); + ui->downloadPushButton->hide(); ui->resumeToolButton->hide(); ui->pauseToolButton->show(); ui->cancelToolButton->show(); ui->progressBar->hide(); + ui->openFilePushButton->hide(); ui->openFolderToolButton->hide(); statusText = tr("Waiting"); @@ -192,11 +221,12 @@ void GxsChannelFilesStatusWidget::check() case STATE_CHECKING: repeat = 1000; - ui->downloadToolButton->hide(); + ui->downloadPushButton->hide(); ui->resumeToolButton->hide(); ui->pauseToolButton->hide(); ui->cancelToolButton->show(); ui->progressBar->hide(); + ui->openFilePushButton->hide(); ui->openFolderToolButton->hide(); statusText = tr("Checking"); @@ -206,11 +236,12 @@ void GxsChannelFilesStatusWidget::check() case STATE_LOCAL: repeat = 60000; - ui->downloadToolButton->hide(); + ui->downloadPushButton->hide(); ui->resumeToolButton->hide(); ui->pauseToolButton->hide(); ui->cancelToolButton->hide(); ui->progressBar->hide(); + ui->openFilePushButton->show(); ui->openFolderToolButton->show(); break; @@ -296,3 +327,24 @@ void GxsChannelFilesStatusWidget::openFolder() } } } + +void GxsChannelFilesStatusWidget::openFile() +{ + FileInfo fileInfo; + if (!rsFiles->alreadyHaveFile(mFile.mHash, fileInfo)) { + return; + } + + /* open file with a suitable application */ + QFileInfo qinfo; + qinfo.setFile(QString::fromUtf8(fileInfo.path.c_str())); + if (qinfo.exists()) { + if (!RsUrlHandler::openUrl(QUrl::fromLocalFile(qinfo.absoluteFilePath()))) { + std::cerr << "GxsChannelFilesStatusWidget(): can't open file " << fileInfo.path << std::endl; + } + }else{ + QMessageBox::information(this, tr("Play File"), + tr("File %1 does not exist at location.").arg(fileInfo.path.c_str())); + return; + } +} diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelFilesStatusWidget.h b/retroshare-gui/src/gui/gxschannels/GxsChannelFilesStatusWidget.h index 1fc24437f..1effe0549 100644 --- a/retroshare-gui/src/gui/gxschannels/GxsChannelFilesStatusWidget.h +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelFilesStatusWidget.h @@ -34,7 +34,7 @@ class GxsChannelFilesStatusWidget : public QWidget Q_OBJECT public: - explicit GxsChannelFilesStatusWidget(const RsGxsGroupId &groupId, const RsGxsMessageId &messageId, const RsGxsFile &file, QWidget *parent = 0); + explicit GxsChannelFilesStatusWidget(const RsGxsFile &file, QWidget *parent = 0); ~GxsChannelFilesStatusWidget(); private slots: @@ -44,6 +44,7 @@ private slots: void pause(); void resume(); void openFolder(); + void openFile(); private: void setSize(uint64_t size); diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelFilesStatusWidget.ui b/retroshare-gui/src/gui/gxschannels/GxsChannelFilesStatusWidget.ui index 59fa846da..c1b02ab83 100644 --- a/retroshare-gui/src/gui/gxschannels/GxsChannelFilesStatusWidget.ui +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelFilesStatusWidget.ui @@ -6,8 +6,8 @@ 0 0 - 367 - 27 + 421 + 29 @@ -48,7 +48,7 @@ 2 - + 0 @@ -126,18 +126,31 @@ - + 0 0 + + Play + + + + + + + + 0 + 0 + + Qt::NoFocus - - Open folder + + QToolButton::InstantPopup @@ -148,6 +161,7 @@ + diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelFilesWidget.cpp b/retroshare-gui/src/gui/gxschannels/GxsChannelFilesWidget.cpp index 3aa6ccae9..6a975df64 100644 --- a/retroshare-gui/src/gui/gxschannels/GxsChannelFilesWidget.cpp +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelFilesWidget.cpp @@ -86,7 +86,7 @@ GxsChannelFilesWidget::~GxsChannelFilesWidget() delete ui; } -void GxsChannelFilesWidget::addFiles(const RsGxsChannelPost &post, bool related) +void GxsChannelFilesWidget::addFiles(const RsGxsChannelPost& post, bool related) { if (related) { removeItems(post.mMeta.mGroupId, post.mMeta.mMsgId); @@ -113,7 +113,7 @@ void GxsChannelFilesWidget::addFiles(const RsGxsChannelPost &post, bool related) ui->treeWidget->addTopLevelItem(treeItem); - QWidget *statusWidget = new GxsChannelFilesStatusWidget(post.mMeta.mGroupId, post.mMeta.mMsgId, file); + QWidget *statusWidget = new GxsChannelFilesStatusWidget(file); ui->treeWidget->setItemWidget(treeItem, COLUMN_STATUS, statusWidget); filterItem(treeItem); diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelPostFilesModel.cpp b/retroshare-gui/src/gui/gxschannels/GxsChannelPostFilesModel.cpp new file mode 100644 index 000000000..987144162 --- /dev/null +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelPostFilesModel.cpp @@ -0,0 +1,476 @@ +/******************************************************************************* + * retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.cpp * + * * + * Copyright 2020 by Cyril Soler * + * * + * 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 +#include +#include + +#include "gui/common/FilesDefs.h" +#include "util/qtthreadsutils.h" +#include "util/HandleRichText.h" +#include "util/DateTime.h" +#include "retroshare/rsgxsflags.h" +#include "retroshare/rsgxschannels.h" +#include "retroshare/rsexpr.h" + +#include "GxsChannelPostFilesModel.h" + +//#define DEBUG_CHANNEL_MODEL + +Q_DECLARE_METATYPE(ChannelPostFileInfo) + +static std::ostream& operator<<(std::ostream& o, const QModelIndex& i);// defined elsewhere + +RsGxsChannelPostFilesModel::RsGxsChannelPostFilesModel(QObject *parent) + : QAbstractItemModel(parent) +{ + initEmptyHierarchy(); + + mTimer = new QTimer; + connect(mTimer,SIGNAL(timeout()),this,SLOT(update())); +} + +void RsGxsChannelPostFilesModel::initEmptyHierarchy() +{ + preMods(); + + mFiles.clear(); + mFilteredFiles.clear(); + + postMods(); +} + +void RsGxsChannelPostFilesModel::preMods() +{ + //emit layoutAboutToBeChanged(); //Generate SIGSEGV when click on button move next/prev. + + beginResetModel(); +} +void RsGxsChannelPostFilesModel::postMods() +{ + endResetModel(); + + emit dataChanged(createIndex(0,0,(void*)NULL), createIndex(mFilteredFiles.size(),COLUMN_FILES_NB_COLUMNS-1,(void*)NULL)); +} + +void RsGxsChannelPostFilesModel::update() +{ + emit dataChanged(createIndex(0,0,(void*)NULL), createIndex(mFilteredFiles.size(),COLUMN_FILES_NB_COLUMNS-1,(void*)NULL)); +} + +int RsGxsChannelPostFilesModel::rowCount(const QModelIndex& parent) const +{ + if(parent.column() > 0) + return 0; + + if(mFilteredFiles.empty()) // security. Should never happen. + return 0; + + if(!parent.isValid()) + return mFilteredFiles.size(); // mFilteredPosts always has an item at 0, so size()>=1, and mColumn>=1 + + RsErr() << __PRETTY_FUNCTION__ << " rowCount cannot figure out the porper number of rows." << std::endl; + return 0; +} + +int RsGxsChannelPostFilesModel::columnCount(const QModelIndex &/*parent*/) const +{ + return COLUMN_FILES_NB_COLUMNS ; +} + +bool RsGxsChannelPostFilesModel::getFileData(const QModelIndex& i,ChannelPostFileInfo& fmpe) const +{ + if(!i.isValid()) + return true; + + quintptr ref = i.internalId(); + uint32_t entry = 0; + + if(!convertRefPointerToTabEntry(ref,entry) || entry >= mFiles.size()) + return false ; + + fmpe = mFiles[mFilteredFiles[entry]]; + + return true; + +} + +bool RsGxsChannelPostFilesModel::hasChildren(const QModelIndex &parent) const +{ + if(!parent.isValid()) + return true; + + return false; // by default, no channel post has children +} + +bool RsGxsChannelPostFilesModel::convertTabEntryToRefPointer(uint32_t entry,quintptr& ref) +{ + // the pointer is formed the following way: + // + // [ 32 bits ] + // + // This means that the whole software has the following build-in limitation: + // * 4 B simultaenous posts. Should be enough ! + + ref = (intptr_t)(entry+1); + + return true; +} + +bool RsGxsChannelPostFilesModel::convertRefPointerToTabEntry(quintptr ref, uint32_t& entry) +{ + intptr_t val = (intptr_t)ref; + + if(val > (1<<30)) // make sure the pointer is an int that fits in 32bits and not too big which would look suspicious + { + RsErr() << "(EE) trying to make a ChannelPostsModelIndex out of a number that is larger than 2^32-1 !" << std::endl; + return false ; + } + if(val==0) + { + RsErr() << "(EE) trying to make a ChannelPostsFileModelIndex out of index 0." << std::endl; + return false; + } + entry = val-1; + + return true; +} + +QModelIndex RsGxsChannelPostFilesModel::index(int row, int column, const QModelIndex & parent) const +{ + if(row < 0 || column < 0 || column >= COLUMN_FILES_NB_COLUMNS) + return QModelIndex(); + + quintptr ref = getChildRef(parent.internalId(),row); + +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << "index-3(" << row << "," << column << " parent=" << parent << ") : " << createIndex(row,column,ref) << std::endl; +#endif + return createIndex(row,column,ref) ; +} + +QModelIndex RsGxsChannelPostFilesModel::parent(const QModelIndex& index) const +{ + if(!index.isValid()) + return QModelIndex(); + + return QModelIndex(); // there's no hierarchy here. So nothing to do! +} + +Qt::ItemFlags RsGxsChannelPostFilesModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return 0; + + if(index.column() == COLUMN_FILES_FILE) + return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; + else + return QAbstractItemModel::flags(index); +} + +quintptr RsGxsChannelPostFilesModel::getChildRef(quintptr ref,int index) const +{ + if (index < 0) + return 0; + + if(ref == quintptr(0)) + { + quintptr new_ref; + convertTabEntryToRefPointer(index,new_ref); + return new_ref; + } + else + return 0 ; +} + +quintptr RsGxsChannelPostFilesModel::getParentRow(quintptr ref,int& row) const +{ + ChannelPostFilesModelIndex ref_entry; + + if(!convertRefPointerToTabEntry(ref,ref_entry) || ref_entry >= mFilteredFiles.size()) + return 0 ; + + if(ref_entry == 0) + { + RsErr() << "getParentRow() shouldn't be asked for the parent of NULL" << std::endl; + row = 0; + } + else + row = ref_entry-1; + + return 0; +} + +int RsGxsChannelPostFilesModel::getChildrenCount(quintptr ref) const +{ + uint32_t entry = 0 ; + + if(ref == quintptr(0)) + return rowCount()-1; + + return 0; +} + +QVariant RsGxsChannelPostFilesModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + switch(section) + { + case COLUMN_FILES_FILE: return QString("Status"); + case COLUMN_FILES_SIZE: return QString("Size"); + case COLUMN_FILES_NAME: return QString("File"); + case COLUMN_FILES_DATE: return QString("Published"); + default: + return QString("[No data]"); + } +} + +QVariant RsGxsChannelPostFilesModel::data(const QModelIndex &index, int role) const +{ +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << "calling data(" << index << ") role=" << role << std::endl; +#endif + + if(!index.isValid()) + return QVariant(); + + switch(role) + { + case Qt::SizeHintRole: return sizeHintRole(index.column()) ; + case Qt::StatusTipRole:return QVariant(); + default: break; + } + + quintptr ref = (index.isValid())?index.internalId():0 ; + uint32_t entry = 0; + +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << "data(" << index << ")" ; +#endif + + if(!ref) + { +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << " [empty]" << std::endl; +#endif + return QVariant() ; + } + + if(!convertRefPointerToTabEntry(ref,entry) || entry >= mFilteredFiles.size()) + { +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << "Bad pointer: " << (void*)ref << std::endl; +#endif + return QVariant() ; + } + + const ChannelPostFileInfo& fmpe(mFiles[mFilteredFiles[entry]]); + + switch(role) + { + case Qt::DisplayRole: return displayRole (fmpe,index.column()) ; + case Qt::UserRole: return userRole (fmpe,index.column()) ; + case SortRole: return sortRole (fmpe,index.column()) ; + default: + return QVariant(); + } +} + +void RsGxsChannelPostFilesModel::setFilter(const QStringList& strings, uint32_t& count) +{ + preMods(); + + beginRemoveRows(QModelIndex(),0,rowCount()-1); + endRemoveRows(); + + if(strings.empty()) + { + mFilteredFiles.clear(); + for(int i=0;if2.mName); + case RsGxsChannelPostFilesModel::COLUMN_FILES_SIZE: return (ord==Qt::AscendingOrder)?(f1.mSizef2.mSize); + case RsGxsChannelPostFilesModel::COLUMN_FILES_DATE: return (ord==Qt::AscendingOrder)?(f1.mPublishTimef2.mPublishTime); + case RsGxsChannelPostFilesModel::COLUMN_FILES_FILE: + { + FileInfo fi1,fi2; + rsFiles->FileDetails(f1.mHash,RS_FILE_HINTS_DOWNLOAD,fi1); + rsFiles->FileDetails(f2.mHash,RS_FILE_HINTS_DOWNLOAD,fi2); + + return (ord==Qt::AscendingOrder)?(fi1.transferedfi2.transfered); + } + } + + } + +private: + int col; + Qt::SortOrder ord; +}; + +void RsGxsChannelPostFilesModel::sort(int column, Qt::SortOrder order) +{ + std::sort(mFiles.begin(),mFiles.end(),compareOperator(column,order)); + + update(); +} + +QVariant RsGxsChannelPostFilesModel::sizeHintRole(int col) const +{ + float factor = QFontMetricsF(QApplication::font()).height()/14.0f ; + + return QVariant( QSize(factor * 170, factor*14 )); +} + +QVariant RsGxsChannelPostFilesModel::sortRole(const ChannelPostFileInfo& fmpe,int column) const +{ + switch(column) + { + case COLUMN_FILES_NAME: return QVariant(QString::fromUtf8(fmpe.mName.c_str())); + case COLUMN_FILES_SIZE: return QVariant(qulonglong(fmpe.mSize)); + case COLUMN_FILES_DATE: return QVariant(qulonglong(fmpe.mPublishTime)); + case COLUMN_FILES_FILE: + { + FileInfo finfo; + if(rsFiles->FileDetails(fmpe.mHash,RS_FILE_HINTS_DOWNLOAD,finfo)) + return qulonglong(finfo.transfered); + + return QVariant(qulonglong(fmpe.mSize)); + } + break; + + default: + return displayRole(fmpe,column); + } +} + +QVariant RsGxsChannelPostFilesModel::displayRole(const ChannelPostFileInfo& fmpe,int col) const +{ + switch(col) + { + case COLUMN_FILES_NAME: return QString::fromUtf8(fmpe.mName.c_str()); + case COLUMN_FILES_SIZE: return QString::number(fmpe.mSize); + case COLUMN_FILES_DATE: return QString::number(fmpe.mPublishTime); + case COLUMN_FILES_FILE: { + FileInfo finfo; + if(rsFiles->FileDetails(fmpe.mHash,RS_FILE_HINTS_DOWNLOAD,finfo)) + return qulonglong(finfo.transfered); + else + return 0; + } + default: + return QString(); + + } + + + return QVariant("[ERROR]"); +} + +QVariant RsGxsChannelPostFilesModel::userRole(const ChannelPostFileInfo& fmpe,int col) const +{ + switch(col) + { + default: + return QVariant::fromValue(fmpe); + } +} + +void RsGxsChannelPostFilesModel::clear() +{ + preMods(); + + initEmptyHierarchy(); + + postMods(); + emit channelLoaded(); +} + +void RsGxsChannelPostFilesModel::setFiles(const std::list &files) +{ + preMods(); + + beginRemoveRows(QModelIndex(),0,mFilteredFiles.size()-1); + endRemoveRows(); + + initEmptyHierarchy(); + + for(auto& file:files) + mFiles.push_back(file); + + for(uint32_t i=0;istart(5000); + else + mTimer->stop(); +} diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelPostFilesModel.h b/retroshare-gui/src/gui/gxschannels/GxsChannelPostFilesModel.h new file mode 100644 index 000000000..d2d4fabf8 --- /dev/null +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelPostFilesModel.h @@ -0,0 +1,173 @@ +/******************************************************************************* + * retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.h * + * * + * Copyright 2020 by Cyril Soler * + * * + * 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 . * + * * + *******************************************************************************/ + +#pragma once + +#include "retroshare/rsfiles.h" +#include "retroshare/rsgxscommon.h" + +#include +#include + +// This class holds the actual hierarchy of posts, represented by identifiers +// It is responsible for auto-updating when necessary and holds a mutex to allow the Model to +// safely access the data. + +// The model contains a post in place 0 that is the parent of all posts. + +typedef uint32_t ChannelPostFilesModelIndex; + +class QTimer; + +// This class contains the info for a file as well as additional info such as publication date + +struct ChannelPostFileInfo: public RsGxsFile +{ + ChannelPostFileInfo(const RsGxsFile& gxs_file,rstime_t t) + : RsGxsFile(gxs_file),mPublishTime(t) + {} + + ChannelPostFileInfo() : mPublishTime(0) {} + + rstime_t mPublishTime; +}; + +// This class is the item model used by Qt to display the information + +class RsGxsChannelPostFilesModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit RsGxsChannelPostFilesModel(QObject *parent = NULL); + ~RsGxsChannelPostFilesModel(){} + + enum Columns { + COLUMN_FILES_NAME = 0x00, + COLUMN_FILES_SIZE = 0x01, + COLUMN_FILES_FILE = 0x02, + COLUMN_FILES_DATE = 0x03, + COLUMN_FILES_NB_COLUMNS = 0x04 + }; + + enum Roles{ SortRole = Qt::UserRole+1, + FilterRole = Qt::UserRole+2, + }; + +#ifdef TODO + enum SortMode{ SORT_MODE_PUBLISH_TS = 0x00, + SORT_MODE_CHILDREN_PUBLISH_TS = 0x01, + }; +#endif + + QModelIndex root() const{ return createIndex(0,0,(void*)NULL) ;} + + // This method will asynchroneously update the data + void setFiles(const std::list& files); + void setFilter(const QStringList &strings, uint32_t &count) ; + +#ifdef TODO + QModelIndex getIndexOfFile(const RsFileHash& hash) const; + void setSortMode(SortMode mode) ; + + void setTextColorRead (QColor color) { mTextColorRead = color;} + void setTextColorUnread (QColor color) { mTextColorUnread = color;} + void setTextColorUnreadChildren(QColor color) { mTextColorUnreadChildren = color;} + void setTextColorNotSubscribed (QColor color) { mTextColorNotSubscribed = color;} + void setTextColorMissing (QColor color) { mTextColorMissing = color;} + void setAuthorOpinion(const QModelIndex& indx,RsOpinion op); +#endif + + // Helper functions + + void clear() ; + + // AbstractItemModel functions. + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; + + QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& child) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + + // Custom item roles + + QVariant sizeHintRole (int col) const; + QVariant displayRole (const ChannelPostFileInfo& fmpe, int col) const; + QVariant toolTipRole (const ChannelPostFileInfo& fmpe, int col) const; + QVariant userRole (const ChannelPostFileInfo& fmpe, int col) const; + QVariant sortRole (const ChannelPostFileInfo& fmpe, int col) const; + QVariant filterRole (const ChannelPostFileInfo& fmpe, int col) const; +#ifdef TODO + QVariant decorationRole(const ForumModelPostEntry& fmpe, int col) const; + QVariant pinnedRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant missingRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant statusRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant authorRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant fontRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant textColorRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant backgroundRole(const ForumModelPostEntry& fmpe, int col) const; +#endif + + /*! + * \brief debug_dump + * Dumps the hierarchy of posts in the terminal, to allow checking whether the internal representation is correct. + */ + void debug_dump(); + +signals: + void channelLoaded(); // emitted after the posts have been set. Can be used to updated the UI. + +private slots: + void update(); + +private: +#ifdef TODO + bool mUseChildTS; + bool mFilteringEnabled; + SortMode mSortMode; +#endif + void preMods() ; + void postMods() ; + + quintptr getParentRow(quintptr ref,int& row) const; + quintptr getChildRef(quintptr ref, int index) const; + int getChildrenCount(quintptr ref) const; + bool getFileData(const QModelIndex& i, ChannelPostFileInfo &fmpe) const; + + static bool convertTabEntryToRefPointer(uint32_t entry, quintptr& ref); + static bool convertRefPointerToTabEntry(quintptr ref,uint32_t& entry); + +#ifdef TODO + static void generateMissingItem(const RsGxsMessageId &msgId,ChannelPostsModelPostEntry& entry); +#endif + void initEmptyHierarchy(); + + std::vector mFilteredFiles ; // store the list of files for the post + std::vector mFiles ; // store the list of files for the post + + QTimer *mTimer; +}; diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelPostThumbnail.h b/retroshare-gui/src/gui/gxschannels/GxsChannelPostThumbnail.h new file mode 100644 index 000000000..4a0ae5855 --- /dev/null +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelPostThumbnail.h @@ -0,0 +1,125 @@ +/******************************************************************************* + * retroshare-gui/src/gui/gxschannels/GxsChannelPostThumbnail.h * + * * + * Copyright 2020 by Retroshare Team * + * * + * 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 . * + * * + *******************************************************************************/ + +#pragma once + +#include + +#include +#include +#include + +#include "retroshare/rsgxschannels.h" +#include "retroshare/rsidentity.h" + +#include "gui/gxs/GxsIdDetails.h" +#include "gui/common/FilesDefs.h" + +// Class to paint the thumbnails with title + +class ChannelPostThumbnailView: public QWidget +{ + Q_OBJECT + +public: + // This variable determines the zoom factor on the text below thumbnails. 2.0 is mostly correct for all screen. + static constexpr float THUMBNAIL_OVERSAMPLE_FACTOR = 2.0; + + // Size of thumbnails as a function of the height of the font. An aspect ratio of 3/4 is good. + + static const int THUMBNAIL_W = 4; + static const int THUMBNAIL_H = 6; + + static constexpr char *CHAN_DEFAULT_IMAGE = ":images/thumb-default-video.png"; + + virtual ~ChannelPostThumbnailView() + { + delete lb; + delete lt; + } + + ChannelPostThumbnailView(QWidget *parent=NULL): QWidget(parent) + { + init(FilesDefs::getPixmapFromQtResourcePath(CHAN_DEFAULT_IMAGE), QString("New post"),false); + } + + ChannelPostThumbnailView(const RsGxsChannelPost& post,QWidget *parent=NULL) + : QWidget(parent) + { + // now fill the data + + QPixmap thumbnail; + + if(post.mThumbnail.mSize > 0) + GxsIdDetails::loadPixmapFromData(post.mThumbnail.mData, post.mThumbnail.mSize, thumbnail,GxsIdDetails::ORIGINAL); + else if(post.mMeta.mPublishTs > 0) // this is for testing that the post is not an empty post (happens at the end of the last row) + thumbnail = FilesDefs::getPixmapFromQtResourcePath(CHAN_DEFAULT_IMAGE); + + init(thumbnail, QString::fromUtf8(post.mMeta.mMsgName.c_str()), IS_MSG_UNREAD(post.mMeta.mMsgStatus) || IS_MSG_NEW(post.mMeta.mMsgStatus) ); + + } + + void init(const QPixmap& thumbnail,const QString& msg,bool is_msg_new) + { + QVBoxLayout *layout = new QVBoxLayout(this); + + lb = new QLabel(this); + lb->setScaledContents(true); + layout->addWidget(lb); + + lt = new QLabel(this); + layout->addWidget(lt); + + setLayout(layout); + + setSizePolicy(QSizePolicy::Maximum,QSizePolicy::Maximum); + + QFontMetricsF fm(font()); + int W = THUMBNAIL_OVERSAMPLE_FACTOR * THUMBNAIL_W * fm.height() ; + int H = THUMBNAIL_OVERSAMPLE_FACTOR * THUMBNAIL_H * fm.height() ; + + lb->setFixedSize(W,H); + lb->setPixmap(thumbnail); + + lt->setText(msg); + + QFont font = lt->font(); + + if(is_msg_new) + { + font.setBold(true); + lt->setFont(font); + } + + lt->setMaximumWidth(W); + lt->setWordWrap(true); + + adjustSize(); + update(); + } + + void setPixmap(const QPixmap& p) { lb->setPixmap(p); } + void setText(const QString& s) { lt->setText(s); } + +private: + QLabel *lb; + QLabel *lt; +}; + diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.cpp b/retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.cpp new file mode 100644 index 000000000..47f46bfe9 --- /dev/null +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.cpp @@ -0,0 +1,736 @@ +/******************************************************************************* + * retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.cpp * + * * + * Copyright 2020 by Cyril Soler * + * * + * 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 +#include +#include + +#include "retroshare/rsgxsflags.h" +#include "retroshare/rsgxschannels.h" +#include "retroshare/rsexpr.h" + +#include "gui/common/FilesDefs.h" +#include "util/qtthreadsutils.h" +#include "util/HandleRichText.h" +#include "util/DateTime.h" + +#include "GxsChannelPostsModel.h" +#include "GxsChannelPostFilesModel.h" + +//#define DEBUG_CHANNEL_MODEL + +Q_DECLARE_METATYPE(RsMsgMetaData) + +Q_DECLARE_METATYPE(RsGxsChannelPost) + +std::ostream& operator<<(std::ostream& o, const QModelIndex& i);// defined elsewhere + +RsGxsChannelPostsModel::RsGxsChannelPostsModel(QObject *parent) + : QAbstractItemModel(parent), mTreeMode(TREE_MODE_PLAIN), mColumns(6) +{ + initEmptyHierarchy(); + + mEventHandlerId = 0; + // Needs to be asynced because this function is called by another thread! + + rsEvents->registerEventsHandler( [this](std::shared_ptr event) + { + RsQThreadUtils::postToObject([=](){ handleEvent_main_thread(event); }, this ); + }, mEventHandlerId, RsEventType::GXS_CHANNELS ); +} + +RsGxsChannelPostsModel::~RsGxsChannelPostsModel() +{ + rsEvents->unregisterEventsHandler(mEventHandlerId); +} + +void RsGxsChannelPostsModel::handleEvent_main_thread(std::shared_ptr event) +{ + const RsGxsChannelEvent *e = dynamic_cast(event.get()); + + if(!e) + return; + + switch(e->mChannelEventCode) + { + case RsChannelEventCode::UPDATED_MESSAGE: + case RsChannelEventCode::READ_STATUS_CHANGED: + { + // Normally we should just emit dataChanged() on the index of the data that has changed: + // + // We need to update the data! + + if(e->mChannelGroupId == mChannelGroup.mMeta.mGroupId) + RsThread::async([this, e]() + { + // 1 - get message data from p3GxsChannels + + std::vector posts; + std::vector comments; + std::vector votes; + + if(!rsGxsChannels->getChannelContent(mChannelGroup.mMeta.mGroupId,std::set{ e->mChannelMsgId }, posts,comments,votes)) + { + std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve channel message data for channel/msg " << e->mChannelGroupId << "/" << e->mChannelMsgId << std::endl; + return; + } + + // 2 - update the model in the UI thread. + + RsQThreadUtils::postToObject( [posts,comments,votes,this]() + { + for(uint32_t i=0;i& files) +{ + // We use an intermediate map so as to remove duplicates + + std::map files_map; + + for(uint32_t i=0;i 0) + return 0; + + if(mFilteredPosts.empty()) // security. Should never happen. + return 0; + + if(!parent.isValid()) + return (mFilteredPosts.size() + mColumns-1)/mColumns; // mFilteredPosts always has an item at 0, so size()>=1, and mColumn>=1 + + RsErr() << __PRETTY_FUNCTION__ << " rowCount cannot figure out the porper number of rows." << std::endl; + return 0; +} + +int RsGxsChannelPostsModel::columnCount(const QModelIndex &/*parent*/) const +{ + return std::min((int)mFilteredPosts.size(),(int)mColumns) ; +} + +bool RsGxsChannelPostsModel::getPostData(const QModelIndex& i,RsGxsChannelPost& fmpe) const +{ + if(!i.isValid()) + return true; + + quintptr ref = i.internalId(); + uint32_t entry = 0; + + if(!convertRefPointerToTabEntry(ref,entry) || entry >= mFilteredPosts.size()) + return false ; + + fmpe = mPosts[mFilteredPosts[entry]]; + + return true; + +} + +bool RsGxsChannelPostsModel::hasChildren(const QModelIndex &parent) const +{ + if(!parent.isValid()) + return true; + + return false; // by default, no channel post has children +} + +bool RsGxsChannelPostsModel::convertTabEntryToRefPointer(uint32_t entry,quintptr& ref) +{ + // the pointer is formed the following way: + // + // [ 32 bits ] + // + // This means that the whole software has the following build-in limitation: + // * 4 B simultaenous posts. Should be enough ! + + ref = (intptr_t)(entry+1); + + return true; +} + +bool RsGxsChannelPostsModel::convertRefPointerToTabEntry(quintptr ref, uint32_t& entry) +{ + intptr_t val = (intptr_t)ref; + + if(val > (1<<30)) // make sure the pointer is an int that fits in 32bits and not too big which would look suspicious + { + RsErr() << "(EE) trying to make a ChannelPostsModelIndex out of a number that is larger than 2^32-1 !" << std::endl; + return false ; + } + if(val==0) + { + RsErr() << "(EE) trying to make a ChannelPostsModelIndex out of index 0." << std::endl; + return false; + } + + entry = val - 1; + + return true; +} + +QModelIndex RsGxsChannelPostsModel::index(int row, int column, const QModelIndex & parent) const +{ + if(row < 0 || column < 0 || column >= mColumns) + return QModelIndex(); + + quintptr ref = getChildRef(parent.internalId(),column + row*mColumns); + +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << "index-3(" << row << "," << column << " parent=" << parent << ") : " << createIndex(row,column,ref) << std::endl; +#endif + return createIndex(row,column,ref) ; +} + +QModelIndex RsGxsChannelPostsModel::parent(const QModelIndex& index) const +{ + if(!index.isValid()) + return QModelIndex(); + + return QModelIndex(); // there's no hierarchy here. So nothing to do! +} + +Qt::ItemFlags RsGxsChannelPostsModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return 0; + + return QAbstractItemModel::flags(index); +} + +void RsGxsChannelPostsModel::setNumColumns(int n) +{ + if(n < 1) + { + RsErr() << __PRETTY_FUNCTION__ << " Attempt to set a number of column of 0. This is wrong." << std::endl; + return; + } + preMods(); + + beginRemoveRows(QModelIndex(),0,rowCount()-1); + endRemoveRows(); + + mColumns = n; + + beginInsertRows(QModelIndex(),0,rowCount()-1); + endInsertRows(); + + postMods(); +} + +quintptr RsGxsChannelPostsModel::getChildRef(quintptr ref,int index) const +{ + if (index < 0) + return 0; + + if(ref == quintptr(0)) + { + quintptr new_ref; + convertTabEntryToRefPointer(index,new_ref); + return new_ref; + } + else + return 0 ; +} + +quintptr RsGxsChannelPostsModel::getParentRow(quintptr ref,int& row) const +{ + ChannelPostsModelIndex ref_entry; + + if(!convertRefPointerToTabEntry(ref,ref_entry) || ref_entry >= mFilteredPosts.size()) + return 0 ; + + if(ref_entry == 0) + { + RsErr() << "getParentRow() shouldn't be asked for the parent of NULL" << std::endl; + row = 0; + } + else + row = ref_entry-1; + + return 0; +} + +int RsGxsChannelPostsModel::getChildrenCount(quintptr ref) const +{ + uint32_t entry = 0 ; + + if(ref == quintptr(0)) + return rowCount()-1; + + return 0; +} + +QVariant RsGxsChannelPostsModel::data(const QModelIndex &index, int role) const +{ +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << "calling data(" << index << ") role=" << role << std::endl; +#endif + + if(!index.isValid()) + return QVariant(); + + switch(role) + { + case Qt::SizeHintRole: return sizeHintRole(index.column()) ; + case Qt::StatusTipRole:return QVariant(); + default: break; + } + + quintptr ref = (index.isValid())?index.internalId():0 ; + uint32_t entry = 0; + +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << "data(" << index << ")" ; +#endif + + if(!ref) + { +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << " [empty]" << std::endl; +#endif + return QVariant() ; + } + + if(!convertRefPointerToTabEntry(ref,entry) || entry >= mFilteredPosts.size()) + { +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << "Bad pointer: " << (void*)ref << std::endl; +#endif + return QVariant() ; + } + + const RsGxsChannelPost& fmpe(mPosts[mFilteredPosts[entry]]); + + switch(role) + { + case Qt::DisplayRole: return displayRole (fmpe,index.column()) ; + case Qt::UserRole: return userRole (fmpe,index.column()) ; + default: + return QVariant(); + } +} + +QVariant RsGxsChannelPostsModel::sizeHintRole(int col) const +{ + float factor = QFontMetricsF(QApplication::font()).height()/14.0f ; + + return QVariant( QSize(factor * 170, factor*14 )); +} + +QVariant RsGxsChannelPostsModel::displayRole(const RsGxsChannelPost& fmpe,int col) const +{ + switch(col) + { + default: + return QString::fromUtf8(fmpe.mMeta.mMsgName.c_str()); + } + + + return QVariant("[ERROR]"); +} + +QVariant RsGxsChannelPostsModel::userRole(const RsGxsChannelPost& fmpe,int col) const +{ + switch(col) + { + default: + return QVariant::fromValue(fmpe); + } +} + +const RsGxsGroupId& RsGxsChannelPostsModel::currentGroupId() const +{ + return mChannelGroup.mMeta.mGroupId; +} + +void RsGxsChannelPostsModel::updateChannel(const RsGxsGroupId& channel_group_id) +{ + if(channel_group_id.isNull()) + return; + + update_posts(channel_group_id); +} + +void RsGxsChannelPostsModel::clear() +{ + preMods(); + + mPosts.clear(); + initEmptyHierarchy(); + + postMods(); + emit channelPostsLoaded(); +} + +bool operator<(const RsGxsChannelPost& p1,const RsGxsChannelPost& p2) +{ + return p1.mMeta.mPublishTs > p2.mMeta.mPublishTs; +} + +void RsGxsChannelPostsModel::setPosts(const RsGxsChannelGroup& group, std::vector& posts) +{ + preMods(); + + beginRemoveRows(QModelIndex(),0,rowCount()-1); + endRemoveRows(); + + mPosts.clear(); + mChannelGroup = group; + + createPostsArray(posts); + + std::sort(mPosts.begin(),mPosts.end()); + + mFilteredPosts.clear(); + for(int i=0;i channelIds; + std::vector msg_metas; + std::vector groups; + + channelIds.push_back(group_id); + + if(!rsGxsChannels->getChannelsInfo(channelIds,groups) || groups.size() != 1) + { + std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve channel group info for channel " << group_id << std::endl; + return; + } + + RsGxsChannelGroup group = groups[0]; + + // We use the heap because the arrays need to be stored accross async + + std::vector *posts = new std::vector(); + std::vector *comments = new std::vector(); + std::vector *votes = new std::vector(); + + if(!rsGxsChannels->getChannelAllContent(group_id, *posts,*comments,*votes)) + { + std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve channel messages for channel " << group_id << std::endl; + return; + } + + // 2 - update the model in the UI thread. + + RsQThreadUtils::postToObject( [group,posts,comments,votes,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, note that + * Qt::QueuedConnection is important! + */ + + setPosts(group,*posts) ; + + delete posts; + delete comments; + delete votes; + + }, this ); + + }); +} + +static bool decreasing_time_comp(const std::pair& e1,const std::pair& e2) { return e2.first < e1.first ; } + +void RsGxsChannelPostsModel::createPostsArray(std::vector& posts) +{ + // collect new versions of posts if any + +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << "Inserting channel posts" << std::endl; +#endif + + std::vector new_versions ; + for (uint32_t i=0;i search_map ; + for (uint32_t i=0;i versions ; + std::map::const_iterator vit ; + + while(search_map.end() != (vit=search_map.find(posts[current_index].mMeta.mOrigMsgId))) + { +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << " post at index " << current_index << " replaces a post at position " << vit->second ; +#endif + + // Now replace the post only if the new versionis more recent. It may happen indeed that the same post has been corrected multiple + // times. In this case, we only need to replace the post with the newest version + + //uint32_t prev_index = current_index ; + current_index = vit->second ; + + if(posts[current_index].mMeta.mMsgId.isNull()) // This handles the branching situation where this post has been already erased. No need to go down further. + { +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << " already erased. Stopping." << std::endl; +#endif + break ; + } + + if(posts[current_index].mMeta.mPublishTs < posts[source_index].mMeta.mPublishTs) + { +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << " and is more recent => following" << std::endl; +#endif + for(std::set::const_iterator itt(posts[current_index].mOlderVersions.begin());itt!=posts[current_index].mOlderVersions.end();++itt) + posts[source_index].mOlderVersions.insert(*itt); + + posts[source_index].mOlderVersions.insert(posts[current_index].mMeta.mMsgId); + posts[current_index].mMeta.mMsgId.clear(); // clear the msg Id so the post will be ignored + } +#ifdef DEBUG_CHANNEL_MODEL + else + std::cerr << " but is older -> Stopping" << std::endl; +#endif + } + } + } + +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << "Now adding " << posts.size() << " posts into array structure..." << std::endl; +#endif + + mPosts.clear(); + + for (std::vector::const_reverse_iterator it = posts.rbegin(); it != posts.rend(); ++it) + { + if(!(*it).mMeta.mMsgId.isNull()) + { +#ifdef DEBUG_CHANNEL_MODEL + std::cerr << " adding post \"" << (*it).mMeta.mMsgName << "\"" << std::endl; +#endif + mPosts.push_back(*it); + } +#ifdef DEBUG_CHANNEL_MODEL + else + std::cerr << " skipped older version post \"" << (*it).mMeta.mMsgName << "\"" << std::endl; +#endif + } +} + +void RsGxsChannelPostsModel::setMsgReadStatus(const QModelIndex& i,bool read_status,bool with_children) +{ + if(!i.isValid()) + return ; + + // no need to call preMods()/postMods() here because we'renot changing the model + + quintptr ref = i.internalId(); + uint32_t entry = 0; + + if(!convertRefPointerToTabEntry(ref,entry) || entry >= mFilteredPosts.size()) + return ; + +#warning TODO +// bool has_unread_below, has_read_below; +// recursSetMsgReadStatus(entry,read_status,with_children) ; +// recursUpdateReadStatusAndTimes(0,has_unread_below,has_read_below); +} + +QModelIndex RsGxsChannelPostsModel::getIndexOfMessage(const RsGxsMessageId& mid) const +{ + // Brutal search. This is not so nice, so dont call that in a loop! If too costly, we'll use a map. + + RsGxsMessageId postId = mid; + + for(uint32_t i=0;i * + * * + * 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 "retroshare/rsgxschannels.h" +#include "retroshare/rsgxsifacetypes.h" +#include "retroshare/rsevents.h" + +#include +#include + +struct ChannelPostFileInfo; + +// This class holds the actual hierarchy of posts, represented by identifiers +// It is responsible for auto-updating when necessary and holds a mutex to allow the Model to +// safely access the data. + +// The model contains a post in place 0 that is the parent of all posts. + +typedef uint32_t ChannelPostsModelIndex; + +// struct ChannelPostsModelPostEntry +// { +// ChannelPostsModelPostEntry() : mPublishTs(0),mPostFlags(0),mMsgStatus(0),prow(0) {} +// +// enum { // flags for display of posts. To be used in mPostFlags +// FLAG_POST_IS_PINNED = 0x0001, +// FLAG_POST_IS_MISSING = 0x0002, +// FLAG_POST_IS_REDACTED = 0x0004, +// FLAG_POST_HAS_UNREAD_CHILDREN = 0x0008, +// FLAG_POST_HAS_READ_CHILDREN = 0x0010, +// FLAG_POST_PASSES_FILTER = 0x0020, +// FLAG_POST_CHILDREN_PASSES_FILTER = 0x0040, +// }; +// +// std::string mTitle ; +// RsGxsMessageId mMsgId; +// uint32_t mPublishTs; +// uint32_t mPostFlags; +// int mMsgStatus; +// +// int prow ;// parent row, which basically means position in the array of posts +// }; + +// This class is the item model used by Qt to display the information + +class RsGxsChannelPostsModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit RsGxsChannelPostsModel(QObject *parent = NULL); + virtual ~RsGxsChannelPostsModel() override; + + static const int COLUMN_THREAD_NB_COLUMNS = 0x01; + +#ifdef TODO + enum Columns { + COLUMN_THREAD_TITLE =0x00, + COLUMN_THREAD_READ =0x01, + COLUMN_THREAD_DATE =0x02, + COLUMN_THREAD_DISTRIBUTION =0x03, + COLUMN_THREAD_AUTHOR =0x04, + COLUMN_THREAD_CONTENT =0x05, + COLUMN_THREAD_MSGID =0x06, + COLUMN_THREAD_DATA =0x07, + }; + + enum Roles{ SortRole = Qt::UserRole+1, + ThreadPinnedRole = Qt::UserRole+2, + MissingRole = Qt::UserRole+3, + StatusRole = Qt::UserRole+4, + UnreadChildrenRole = Qt::UserRole+5, + FilterRole = Qt::UserRole+6, + }; +#endif + + enum TreeMode{ TREE_MODE_UNKWN = 0x00, + TREE_MODE_PLAIN = 0x01, + TREE_MODE_FILES = 0x02, + }; + +#ifdef TODO + enum SortMode{ SORT_MODE_PUBLISH_TS = 0x00, + SORT_MODE_CHILDREN_PUBLISH_TS = 0x01, + }; +#endif + + QModelIndex root() const{ return createIndex(0,0,(void*)NULL) ;} + QModelIndex getIndexOfMessage(const RsGxsMessageId& mid) const; + + std::vector > getPostVersions(const RsGxsMessageId& mid) const; + + // This method will asynchroneously update the data + void updateChannel(const RsGxsGroupId& channel_group_id); + const RsGxsGroupId& currentGroupId() const; + + void setNumColumns(int n); + + // Retrieve the full list of files for all posts. + + void getFilesList(std::list &files); + +#ifdef TODO + void setSortMode(SortMode mode) ; + + void setTextColorRead (QColor color) { mTextColorRead = color;} + void setTextColorUnread (QColor color) { mTextColorUnread = color;} + void setTextColorUnreadChildren(QColor color) { mTextColorUnreadChildren = color;} + void setTextColorNotSubscribed (QColor color) { mTextColorNotSubscribed = color;} + void setTextColorMissing (QColor color) { mTextColorMissing = color;} +#endif + + void setMsgReadStatus(const QModelIndex &i, bool read_status, bool with_children); + void setFilter(const QStringList &strings, uint32_t &count) ; + +#ifdef TODO + void setAuthorOpinion(const QModelIndex& indx,RsOpinion op); +#endif + + // Helper functions + + bool getPostData(const QModelIndex& i,RsGxsChannelPost& fmpe) const ; + void clear() ; + + // AbstractItemModel functions. + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + bool hasChildren(const QModelIndex &parent = QModelIndex()) const override; + + QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& child) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + // Custom item roles + + QVariant sizeHintRole (int col) const; + QVariant displayRole (const RsGxsChannelPost& fmpe, int col) const; + QVariant toolTipRole (const RsGxsChannelPost& fmpe, int col) const; + QVariant userRole (const RsGxsChannelPost& fmpe, int col) const; +#ifdef TODO + QVariant decorationRole(const ForumModelPostEntry& fmpe, int col) const; + QVariant pinnedRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant missingRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant statusRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant authorRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant sortRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant fontRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant filterRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant textColorRole (const ForumModelPostEntry& fmpe, int col) const; + QVariant backgroundRole(const ForumModelPostEntry& fmpe, int col) const; +#endif + + /*! + * \brief debug_dump + * Dumps the hierarchy of posts in the terminal, to allow checking whether the internal representation is correct. + */ + void debug_dump(); + +signals: + void channelPostsLoaded(); // emitted after the posts have been loaded. + +private: + RsGxsChannelGroup mChannelGroup; + +#ifdef TODO + bool mUseChildTS; + bool mFilteringEnabled; + SortMode mSortMode; +#endif + TreeMode mTreeMode; + + uint32_t mColumns; + + void preMods() ; + void postMods() ; + + quintptr getParentRow(quintptr ref,int& row) const; + quintptr getChildRef(quintptr ref, int index) const; + int getChildrenCount(quintptr ref) const; + + static bool convertTabEntryToRefPointer(uint32_t entry, quintptr &ref); + static bool convertRefPointerToTabEntry(quintptr ref,uint32_t& entry); + static void computeReputationLevel(uint32_t forum_sign_flags, RsGxsChannelPost& entry); + + void update_posts(const RsGxsGroupId& group_id); + +#ifdef TODO + void setForumMessageSummary(const std::vector& messages); +#endif + void recursUpdateReadStatusAndTimes(ChannelPostsModelIndex i,bool& has_unread_below,bool& has_read_below); + uint32_t recursUpdateFilterStatus(ChannelPostsModelIndex i,int column,const QStringList& strings); + void recursSetMsgReadStatus(ChannelPostsModelIndex i,bool read_status,bool with_children); + +#ifdef TODO + static void generateMissingItem(const RsGxsMessageId &msgId,ChannelPostsModelPostEntry& entry); +#endif + //static ChannelModelIndex addEntry(std::vector& posts,const ChannelModelPostEntry& entry,ChannelModelIndex parent); + //static void convertMsgToPostEntry(const RsGxsChannelGroup &mChannelGroup, const RsMsgMetaData &msg, bool useChildTS, ChannelModelPostEntry& fentry); + + //void computeMessagesHierarchy(const RsGxsChannelGroup& forum_group, const std::vector &msgs_array, std::vector &posts, std::map > > &mPostVersions); + void createPostsArray(std::vector &posts); + void setPosts(const RsGxsChannelGroup& group, std::vector &posts); + void initEmptyHierarchy(); + void handleEvent_main_thread(std::shared_ptr event); + + std::vector mFilteredPosts; // stores the list of displayes indices due to filtering. + std::vector mPosts ; // store the list of posts updated from rsForums. + + //std::map > > mPostVersions; // stores versions of posts + + QColor mTextColorRead ; + QColor mTextColorUnread ; + QColor mTextColorUnreadChildren; + QColor mTextColorNotSubscribed ; + QColor mTextColorMissing ; + + RsEventsHandlerId_t mEventHandlerId ; + friend class const_iterator; +}; diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidget.cpp b/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidget.cpp index d4e6192e2..bbbb26a42 100644 --- a/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidget.cpp +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidget.cpp @@ -551,13 +551,9 @@ void GxsChannelPostsWidget::createPostItem(const RsGxsChannelPost& post, bool re ui->feedWidget->addFeedItem(item, ROLE_PUBLISH, QDateTime::fromTime_t(meta.mPublishTs)); } -#ifdef TODO ui->fileWidget->addFiles(post, related); -#endif } - - void GxsChannelPostsWidget::fillThreadCreatePost(const QVariant &post, bool related, int current, int count) { /* show fill progress */ @@ -862,11 +858,12 @@ bool GxsChannelPostsWidget::getGroupData(RsGxsGenericGroupData *& data) { RsGxsChannelGroup distant_group; - if(rsGxsChannels->retrieveDistantGroup(groupId(),distant_group)) + if(rsGxsChannels->getDistantSearchResultGroupData(groupId(),distant_group)) { insertChannelDetails(distant_group); - data = new RsGxsChannelGroup(distant_group); + data = new RsGxsChannelGroup(distant_group); mGroup = distant_group; // make a local copy to pass on to items + return true ; } } diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidget.h b/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidget.h index 025d933bf..dded41491 100644 --- a/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidget.h +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidget.h @@ -98,7 +98,7 @@ private: int viewMode(); void insertChannelDetails(const RsGxsChannelGroup &group); - void insertChannelPosts(std::vector &posts, GxsMessageFramePostThread *thread, bool related); + void insertChannelPosts(std::vector& posts, GxsMessageFramePostThread *thread, bool related); void createPostItem(const RsGxsChannelPost &post, bool related); void handleEvent_main_thread(std::shared_ptr event); diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidgetWithModel.cpp b/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidgetWithModel.cpp new file mode 100644 index 000000000..cc60e003b --- /dev/null +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidgetWithModel.cpp @@ -0,0 +1,1179 @@ +/******************************************************************************* + * retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidget.cpp * + * * + * Copyright 2013 by 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 +#include +#include +#include + +#include "retroshare/rsgxscircles.h" + +#include "ui_GxsChannelPostsWidgetWithModel.h" +#include "gui/feeds/GxsChannelPostItem.h" +#include "gui/gxs/GxsIdDetails.h" +#include "util/misc.h" +#include "gui/gxschannels/CreateGxsChannelMsg.h" +#include "gui/common/UIStateHelper.h" +#include "gui/settings/rsharesettings.h" +#include "gui/feeds/SubFileItem.h" +#include "gui/notifyqt.h" +#include "gui/RetroShareLink.h" +#include "util/HandleRichText.h" +#include "util/DateTime.h" +#include "util/qtthreadsutils.h" +#include "gui/common/FilesDefs.h" + +#include "GxsChannelPostsWidgetWithModel.h" +#include "GxsChannelPostsModel.h" +#include "GxsChannelPostFilesModel.h" +#include "GxsChannelFilesStatusWidget.h" +#include "GxsChannelPostThumbnail.h" + +#include + +#define ROLE_PUBLISH FEED_TREEWIDGET_SORTROLE + +/**** + * #define DEBUG_CHANNEL + ***/ + +static const int mTokenTypeGroupData = 1; + +static const int CHANNEL_TABS_DETAILS= 0; +static const int CHANNEL_TABS_POSTS = 1; +static const int CHANNEL_TABS_FILES = 2; + +/* View mode */ +#define VIEW_MODE_FEEDS 1 +#define VIEW_MODE_FILES 2 + +// Determine the Shape and size of cells as a factor of the font height. An aspect ratio of 3/4 is what's needed +// for the image, so it's important that the height is a bit larger so as to leave some room for the text. +// +// +#define COLUMN_SIZE_FONT_FACTOR_W 6 +#define COLUMN_SIZE_FONT_FACTOR_H 10 + +#define STAR_OVERLAY_IMAGE ":icons/star_overlay_128.png" +#define IMAGE_COPYLINK ":/images/copyrslink.png" + +Q_DECLARE_METATYPE(ChannelPostFileInfo) + +// Delegate used to paint into the table of thumbnails + +int ChannelPostDelegate::cellSize(const QFont& font) const +{ + return mZoom*COLUMN_SIZE_FONT_FACTOR_W*QFontMetricsF(font).height(); +} + +void ChannelPostDelegate::zoom(bool zoom_or_unzoom) +{ + if(zoom_or_unzoom) + mZoom *= 1.02; + else + mZoom /= 1.02; + + std::cerr << "zoom factor: " << mZoom << std::endl; +} + +void ChannelPostDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const +{ + // prepare + painter->save(); + painter->setClipRect(option.rect); + + RsGxsChannelPost post = index.data(Qt::UserRole).value() ; + + painter->save(); + painter->fillRect( option.rect, option.backgroundBrush); + painter->restore(); + + ChannelPostThumbnailView w(post); + + QPixmap pixmap(w.size()); + + if((option.state & QStyle::State_Selected) && post.mMeta.mPublishTs > 0) // check if post is selected and is not empty (end of last row) + pixmap.fill(QRgb(0xff308dc7)); // I dont know how to grab the backgroud color for selected objects automatically. + else + pixmap.fill(QRgb(0x00ffffff)); // choose a fully transparent background + + w.render(&pixmap,QPoint(),QRegion(),QWidget::DrawChildren );// draw the widgets, not the background + + if(mZoom != 1.0) + pixmap = pixmap.scaled(mZoom*pixmap.size(),Qt::KeepAspectRatio,Qt::SmoothTransformation); + + if(IS_MSG_UNREAD(post.mMeta.mMsgStatus) || IS_MSG_NEW(post.mMeta.mMsgStatus)) + { + QPainter p(&pixmap); + QFontMetricsF fm(option.font); + p.drawPixmap(mZoom*QPoint(6.2*fm.height(),6.9*fm.height()),FilesDefs::getPixmapFromQtResourcePath(STAR_OVERLAY_IMAGE).scaled(mZoom*7*fm.height(),mZoom*7*fm.height(),Qt::KeepAspectRatio,Qt::SmoothTransformation)); + } + + // debug + // if(index.row()==0 && index.column()==0) + // { + // QFile file("yourFile.png"); + // file.open(QIODevice::WriteOnly); + // pixmap.save(&file, "PNG"); + // std::cerr << "Saved pxmap to png" << std::endl; + // } + //std::cerr << "option.rect = " << option.rect.width() << "x" << option.rect.height() << ". fm.height()=" << QFontMetricsF(option.font).height() << std::endl; + + painter->drawPixmap(option.rect.topLeft(), + pixmap.scaled(option.rect.width(),option.rect.width()*w.height()/(float)w.width(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation)); +} + +QSize ChannelPostDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + // This is the only place where we actually set the size of cells + + QFontMetricsF fm(option.font); + + return QSize(mZoom*COLUMN_SIZE_FONT_FACTOR_W*fm.height(),mZoom*COLUMN_SIZE_FONT_FACTOR_H*fm.height()); +} + +QWidget *ChannelPostFilesDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex& index) const +{ + ChannelPostFileInfo file = index.data(Qt::UserRole).value() ; + + if(index.column() == RsGxsChannelPostFilesModel::COLUMN_FILES_FILE) + return new GxsChannelFilesStatusWidget(file,parent); + else + return NULL; +} +void ChannelPostFilesDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const +{ + editor->setGeometry(option.rect); +} + +void ChannelPostFilesDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const +{ + ChannelPostFileInfo file = index.data(Qt::UserRole).value() ; + + // prepare + painter->save(); + painter->setClipRect(option.rect); + + painter->save(); + + painter->fillRect( option.rect, option.backgroundBrush); + //optionFocusRect.backgroundColor = option.palette.color(colorgroup, (option.state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Background); + painter->restore(); + + switch(index.column()) + { + case RsGxsChannelPostFilesModel::COLUMN_FILES_NAME: painter->drawText(option.rect,Qt::AlignLeft | Qt::AlignVCenter," " + QString::fromUtf8(file.mName.c_str())); + break; + case RsGxsChannelPostFilesModel::COLUMN_FILES_SIZE: painter->drawText(option.rect,Qt::AlignRight | Qt::AlignVCenter,misc::friendlyUnit(qulonglong(file.mSize))); + break; + case RsGxsChannelPostFilesModel::COLUMN_FILES_DATE: painter->drawText(option.rect,Qt::AlignLeft | Qt::AlignVCenter,QDateTime::fromMSecsSinceEpoch(file.mPublishTime*1000).toString("MM/dd/yyyy, hh:mm")); + break; + case RsGxsChannelPostFilesModel::COLUMN_FILES_FILE: { + + GxsChannelFilesStatusWidget w(file); + + w.setFixedWidth(option.rect.width()); + w.setFixedHeight(option.rect.height()); + + QPixmap pixmap(w.size()); + pixmap.fill(QRgb(0x00ffffff)); // choose a fully transparent background + w.render(&pixmap,QPoint(),QRegion(),QWidget::DrawChildren );// draw the widgets, not the background + + painter->drawPixmap(option.rect.topLeft(),pixmap); + } + break; + + default: + painter->drawText(option.rect,Qt::AlignLeft,QString("[No data]")); + break; + } +} + +QSize ChannelPostFilesDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + ChannelPostFileInfo file = index.data(Qt::UserRole).value() ; + + QFontMetricsF fm(option.font); + + switch(index.column()) + { + case RsGxsChannelPostFilesModel::COLUMN_FILES_NAME: return QSize(1.1*fm.width(QString::fromUtf8(file.mName.c_str())),fm.height()); + case RsGxsChannelPostFilesModel::COLUMN_FILES_SIZE: return QSize(1.1*fm.width(misc::friendlyUnit(qulonglong(file.mSize))),fm.height()); + case RsGxsChannelPostFilesModel::COLUMN_FILES_DATE: return QSize(1.1*fm.width(QDateTime::fromMSecsSinceEpoch(file.mPublishTime*1000).toString("MM/dd/yyyy, hh:mm")),fm.height()); + default: + case RsGxsChannelPostFilesModel::COLUMN_FILES_FILE: return QSize(option.rect.width(),GxsChannelFilesStatusWidget(file).height()); + } +} + +/** Constructor */ +GxsChannelPostsWidgetWithModel::GxsChannelPostsWidgetWithModel(const RsGxsGroupId &channelId, QWidget *parent) : + GxsMessageFrameWidget(rsGxsChannels, parent), + ui(new Ui::GxsChannelPostsWidgetWithModel) +{ + /* Invoke the Qt Designer generated object setup routine */ + ui->setupUi(this); + + ui->postsTree->setModel(mChannelPostsModel = new RsGxsChannelPostsModel()); + ui->postsTree->setItemDelegate(mChannelPostsDelegate = new ChannelPostDelegate()); + ui->postsTree->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // prevents bug on w10, since row size depends on widget width + ui->postsTree->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);// more beautiful if we scroll at pixel level + ui->postsTree->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + + connect(ui->postsTree,SIGNAL(zoomRequested(bool)),this,SLOT(updateZoomFactor(bool))); + + ui->channelPostFiles_TV->setModel(mChannelPostFilesModel = new RsGxsChannelPostFilesModel(this)); + ui->channelPostFiles_TV->setItemDelegate(new ChannelPostFilesDelegate()); + ui->channelPostFiles_TV->setPlaceholderText(tr("No files in this post, or no post selected")); + ui->channelPostFiles_TV->setSortingEnabled(true); + ui->channelPostFiles_TV->sortByColumn(0, Qt::AscendingOrder); + + ui->channelFiles_TV->setModel(mChannelFilesModel = new RsGxsChannelPostFilesModel()); + ui->channelFiles_TV->setItemDelegate(new ChannelPostFilesDelegate()); + ui->channelFiles_TV->setPlaceholderText(tr("No files in the channel, or no channel selected")); + ui->channelFiles_TV->setSortingEnabled(true); + ui->channelFiles_TV->sortByColumn(0, Qt::AscendingOrder); + + connect(ui->channelPostFiles_TV->header(),SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(sortColumnPostFiles(int,Qt::SortOrder))); + connect(ui->channelFiles_TV->header(),SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(sortColumnFiles(int,Qt::SortOrder))); + + connect(ui->postsTree->selectionModel(),SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)),this,SLOT(showPostDetails())); + connect(ui->postsTree,SIGNAL(customContextMenuRequested(const QPoint&)),this,SLOT(postContextMenu(const QPoint&))); + + connect(mChannelPostsModel,SIGNAL(channelPostsLoaded()),this,SLOT(postChannelPostLoad())); + + ui->postName_LB->hide(); + ui->postTime_LB->hide(); + ui->postLogo_LB->hide(); + + ui->postDetails_TE->setPlaceholderText(tr("No post selected")); + + // Set initial size of the splitter + ui->splitter->setStretchFactor(0, 1); + ui->splitter->setStretchFactor(1, 0); + + QFontMetricsF fm(font()); + + for(int i=0;icolumnCount();++i) + ui->postsTree->setColumnWidth(i,mChannelPostsDelegate->cellSize(font())); + + /* Setup UI helper */ + + /* Connect signals */ + connect(ui->postButton, SIGNAL(clicked()), this, SLOT(createMsg())); + connect(ui->subscribeToolButton, SIGNAL(subscribe(bool)), this, SLOT(subscribeGroup(bool))); + connect(NotifyQt::getInstance(), SIGNAL(settingsChanged()), this, SLOT(settingsChanged())); + + ui->postButton->setText(tr("Add new post")); + + /* add filter actions */ + ui->filterLineEdit->setPlaceholderText(tr("Search...")); + connect(ui->filterLineEdit, SIGNAL(textChanged(QString)), this, SLOT(filterChanged(QString))); + + ui->postsTree->setPlaceholderText(tr("Thumbnails")); + ui->postsTree->setMinimumWidth(COLUMN_SIZE_FONT_FACTOR_W*QFontMetricsF(font()).height()+1); + + connect(ui->postsTree,SIGNAL(sizeChanged(QSize)),this,SLOT(handlePostsTreeSizeChange(QSize))); + + /* Set initial section sizes */ + QHeaderView * channelpostfilesheader = ui->channelPostFiles_TV->header () ; + QHeaderView * channelfilesheader = ui->channelFiles_TV->header () ; + + channelpostfilesheader->resizeSection (RsGxsChannelPostFilesModel::COLUMN_FILES_NAME, fm.width("RetroShare-v0.6.5-1487-g6714648e5-Windows-x64-portable-20200518-Qt-5.14.2.7z")); + channelfilesheader->resizeSection (RsGxsChannelPostFilesModel::COLUMN_FILES_NAME, fm.width("RetroShare-v0.6.5-1487-g6714648e5-Windows-x64-portable-20200518-Qt-5.14.2.7z")); + + /* Initialize feed widget */ + //ui->feedWidget->setSortRole(ROLE_PUBLISH, Qt::DescendingOrder); + //ui->feedWidget->setFilterCallback(filterItem); + + /* load settings */ + processSettings(true); + + ui->channelPostFiles_TV->setColumnHidden(RsGxsChannelPostFilesModel::COLUMN_FILES_DATE, true); // no need to show this here. + + /* Initialize subscribe button */ + QIcon icon; + icon.addPixmap(QPixmap(":/images/redled.png"), QIcon::Normal, QIcon::On); + icon.addPixmap(QPixmap(":/images/start.png"), QIcon::Normal, QIcon::Off); + mAutoDownloadAction = new QAction(icon, "", this); + mAutoDownloadAction->setCheckable(true); + connect(mAutoDownloadAction, SIGNAL(triggered()), this, SLOT(toggleAutoDownload())); + + ui->subscribeToolButton->addSubscribedAction(mAutoDownloadAction); + + ui->commentsDialog->setTokenService(rsGxsChannels->getTokenService(),rsGxsChannels); + + /* Initialize GUI */ + setAutoDownload(false); + settingsChanged(); + + setGroupId(channelId); + mChannelPostsModel->updateChannel(channelId); + + mEventHandlerId = 0; + // Needs to be asynced because this function is called by another thread! + rsEvents->registerEventsHandler( [this](std::shared_ptr event) + { + RsQThreadUtils::postToObject([=](){ handleEvent_main_thread(event); }, this ); + }, mEventHandlerId, RsEventType::GXS_CHANNELS ); +} + +void GxsChannelPostsWidgetWithModel::updateZoomFactor(bool zoom_or_unzoom) +{ + mChannelPostsDelegate->zoom(zoom_or_unzoom); + + for(int i=0;icolumnCount();++i) + ui->postsTree->setColumnWidth(i,mChannelPostsDelegate->cellSize(font())); + + QSize s = ui->postsTree->size(); + + int n_columns = std::max(1,(int)floor(s.width() / (mChannelPostsDelegate->cellSize(font())))); + std::cerr << "nb columns: " << n_columns << std::endl; + + mChannelPostsModel->setNumColumns(n_columns); // forces the update + + ui->postsTree->dataChanged(QModelIndex(),QModelIndex()); +} + +void GxsChannelPostsWidgetWithModel::sortColumnPostFiles(int col,Qt::SortOrder so) +{ + std::cerr << "Sorting post files according to col " << col << std::endl; + mChannelPostFilesModel->sort(col,so); +} +void GxsChannelPostsWidgetWithModel::sortColumnFiles(int col,Qt::SortOrder so) +{ + std::cerr << "Sorting channel files according to col " << col << std::endl; + mChannelFilesModel->sort(col,so); +} + +void GxsChannelPostsWidgetWithModel::postContextMenu(const QPoint&) +{ + QMenu menu(this); + + menu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_COPYLINK), tr("Copy RetroShare Link"), this, SLOT(copyMessageLink())); + + if(IS_GROUP_PUBLISHER(mGroup.mMeta.mSubscribeFlags)) + menu.addAction(FilesDefs::getIconFromQtResourcePath(":/images/edit_16.png"), tr("Edit"), this, SLOT(editPost())); + + menu.exec(QCursor::pos()); +} + +void GxsChannelPostsWidgetWithModel::copyMessageLink() +{ + try + { + if (groupId().isNull()) + throw std::runtime_error("No channel currently selected!"); + + QModelIndex index = ui->postsTree->selectionModel()->currentIndex(); + + if(!index.isValid()) + throw std::runtime_error("No post under mouse!"); + + RsGxsChannelPost post = index.data(Qt::UserRole).value() ; + + if(post.mMeta.mMsgId.isNull()) + throw std::runtime_error("Post has empty MsgId!"); + + RetroShareLink link = RetroShareLink::createGxsMessageLink(RetroShareLink::TYPE_CHANNEL, groupId(), post.mMeta.mMsgId, QString::fromUtf8(post.mMeta.mMsgName.c_str())); + + if (!link.valid()) + throw std::runtime_error("Link is not valid"); + + QList urls; + urls.push_back(link); + RSLinkClipboard::copyLinks(urls); + } + catch(std::exception& e) + { + QMessageBox::critical(NULL,tr("Link creation error"),tr("Link could not be created: ")+e.what()); + } +} + +void GxsChannelPostsWidgetWithModel::editPost() +{ + QModelIndex index = ui->postsTree->selectionModel()->currentIndex(); + RsGxsChannelPost post = index.data(Qt::UserRole).value() ; + + CreateGxsChannelMsg *msgDialog = new CreateGxsChannelMsg(post.mMeta.mGroupId,post.mMeta.mMsgId); + msgDialog->show(); +} + +void GxsChannelPostsWidgetWithModel::handlePostsTreeSizeChange(QSize s) +{ + int n_columns = std::max(1,(int)floor(s.width() / (mChannelPostsDelegate->cellSize(font())))); + std::cerr << "nb columns: " << n_columns << std::endl; + + if(n_columns != mChannelPostsModel->columnCount()) + mChannelPostsModel->setNumColumns(n_columns); +} + +void GxsChannelPostsWidgetWithModel::handleEvent_main_thread(std::shared_ptr event) +{ + const RsGxsChannelEvent *e = dynamic_cast(event.get()); + + if(!e) + return; + + switch(e->mChannelEventCode) + { + case RsChannelEventCode::NEW_CHANNEL: // [[fallthrough]]; + case RsChannelEventCode::UPDATED_CHANNEL: // [[fallthrough]]; + case RsChannelEventCode::NEW_MESSAGE: // [[fallthrough]]; + case RsChannelEventCode::UPDATED_MESSAGE: + { + if(e->mChannelGroupId == groupId()) + updateDisplay(true); + } + + default: + break; + } +} + +void GxsChannelPostsWidgetWithModel::showPostDetails() +{ + QModelIndex index = ui->postsTree->selectionModel()->currentIndex(); + RsGxsChannelPost post = index.data(Qt::UserRole).value() ; + + QTextDocument doc; + doc.setHtml(post.mMsg.c_str()); + + if(post.mMeta.mPublishTs == 0) + { + ui->postDetails_TE->clear(); + ui->postLogo_LB->hide(); + ui->postName_LB->hide(); + ui->postTime_LB->hide(); + mChannelPostFilesModel->clear(); + + return; + } + + ui->postLogo_LB->show(); + ui->postName_LB->show(); + ui->postTime_LB->show(); + + if(index.row()==0 && index.column()==0) + std::cerr << "here" << std::endl; + + std::cerr << "showPostDetails: setting mSelectedPost to current post Id " << post.mMeta.mMsgId << ". Previous value: " << mSelectedPost << std::endl; + mSelectedPost = post.mMeta.mMsgId; + + std::list files; + for(auto& file:post.mFiles) + files.push_back(ChannelPostFileInfo(file,post.mMeta.mPublishTs)); + + mChannelPostFilesModel->setFiles(files); + + auto all_msgs_versions(post.mOlderVersions); + all_msgs_versions.insert(post.mMeta.mMsgId); + + ui->commentsDialog->commentLoad(post.mMeta.mGroupId, all_msgs_versions, post.mMeta.mMsgId); + + std::cerr << "Showing details about selected index : "<< index.row() << "," << index.column() << std::endl; + + ui->postDetails_TE->setText(RsHtml().formatText(NULL, QString::fromUtf8(post.mMsg.c_str()), RSHTML_FORMATTEXT_EMBED_SMILEYS | RSHTML_FORMATTEXT_EMBED_LINKS)); + + QPixmap postImage; + + if (post.mThumbnail.mData != NULL) + GxsIdDetails::loadPixmapFromData(post.mThumbnail.mData, post.mThumbnail.mSize, postImage,GxsIdDetails::ORIGINAL); + else + postImage = FilesDefs::getPixmapFromQtResourcePath(ChannelPostThumbnailView::CHAN_DEFAULT_IMAGE); + + int W = QFontMetricsF(font()).height() * 8; + + // Using fixed width so that the post will not displace the text when we browse. + + ui->postLogo_LB->setPixmap(postImage); + ui->postLogo_LB->setFixedSize(W,postImage.height()/(float)postImage.width()*W); + + ui->postName_LB->setText(QString::fromUtf8(post.mMeta.mMsgName.c_str())); + ui->postName_LB->setFixedWidth(W); + ui->postTime_LB->setText(QDateTime::fromMSecsSinceEpoch(post.mMeta.mPublishTs*1000).toString("MM/dd/yyyy, hh:mm")); + ui->postTime_LB->setFixedWidth(W); + + //ui->channelPostFiles_TV->resizeColumnToContents(RsGxsChannelPostFilesModel::COLUMN_FILES_FILE); + //ui->channelPostFiles_TV->resizeColumnToContents(RsGxsChannelPostFilesModel::COLUMN_FILES_SIZE); + ui->channelPostFiles_TV->setAutoSelect(true); + + // Now also set the post as read + + if(IS_MSG_UNREAD(post.mMeta.mMsgStatus) || IS_MSG_NEW(post.mMeta.mMsgStatus)) + { + RsGxsGrpMsgIdPair postId; + postId.second = post.mMeta.mMsgId; + postId.first = post.mMeta.mGroupId; + + RsThread::async([postId]() { rsGxsChannels->markRead(postId, true) ; } ); + } +} + +void GxsChannelPostsWidgetWithModel::updateGroupData() +{ + if(groupId().isNull()) + { + // clear post, files and comment widgets + + showPostDetails(); + return; + } + + RsThread::async([this]() + { + RsGxsChannelGroup group; + std::vector groups; + + if(rsGxsChannels->getChannelsInfo(std::list{ groupId() }, groups) && groups.size()==1) + group = groups[0]; + else if(!rsGxsChannels->getDistantSearchResultGroupData(groupId(),group)) + { + std::cerr << __PRETTY_FUNCTION__ << " failed to get group data for channel: " << groupId() << std::endl; + return; + } + + RsQThreadUtils::postToObject( [this,group]() + { + mGroup = group; + mChannelPostsModel->updateChannel(groupId()); + + insertChannelDetails(mGroup); + + emit groupDataLoaded(); + emit groupChanged(this); // signals the parent widget to e.g. update the group tab name + } ); + }); +} + +void GxsChannelPostsWidgetWithModel::postChannelPostLoad() +{ + std::cerr << "Post channel load..." << std::endl; + + if(!mSelectedPost.isNull()) + { + QModelIndex index = mChannelPostsModel->getIndexOfMessage(mSelectedPost); + + std::cerr << "Setting current index to " << index.row() << ","<< index.column() << " for current post " + << mSelectedPost.toStdString() << std::endl; + + ui->postsTree->selectionModel()->setCurrentIndex(index,QItemSelectionModel::ClearAndSelect); + ui->postsTree->scrollTo(index);//May change if model reloaded + ui->postsTree->setFocus(); + } + else + std::cerr << "No pre-selected channel post." << std::endl; + + std::list files; + + mChannelPostsModel->getFilesList(files); + mChannelFilesModel->setFiles(files); + + //ui->channelFiles_TV->resizeColumnToContents(RsGxsChannelPostFilesModel::COLUMN_FILES_FILE); + //ui->channelFiles_TV->resizeColumnToContents(RsGxsChannelPostFilesModel::COLUMN_FILES_SIZE); + ui->channelFiles_TV->setAutoSelect(true); + +} + +void GxsChannelPostsWidgetWithModel::updateDisplay(bool complete) +{ +#ifdef DEBUG_CHANNEL + std::cerr << "udateDisplay: groupId()=" << groupId()<< std::endl; +#endif + if(groupId().isNull()) + { +#ifdef DEBUG_CHANNEL + std::cerr << " group_id=0. Return!"<< std::endl; +#endif + return; + } + + if(mGroup.mMeta.mGroupId.isNull() && !groupId().isNull()) + { +#ifdef DEBUG_FORUMS + std::cerr << " inconsistent group data. Reloading!"<< std::endl; +#endif + complete = true; + } + if(complete) // need to update the group data, reload the messages etc. + { +#warning todo + //saveExpandedItems(mSavedExpandedMessages); + + //if(mGroupId != mChannelPostsModel->currentGroupId()) + // mThreadId.clear(); + + updateGroupData(); + + return; + } +} +GxsChannelPostsWidgetWithModel::~GxsChannelPostsWidgetWithModel() +{ + rsEvents->unregisterEventsHandler(mEventHandlerId); + // save settings + processSettings(false); + + delete(mAutoDownloadAction); + delete ui; +} + +void GxsChannelPostsWidgetWithModel::processSettings(bool load) +{ + QHeaderView *channelpostfilesheader = ui->channelPostFiles_TV->header () ; + QHeaderView *channelfilesheader = ui->channelFiles_TV->header () ; + + Settings->beginGroup(QString("ChannelPostsWidget")); + + if (load) { +#ifdef TO_REMOVE + // load settings + + /* Filter */ + //ui->filterLineEdit->setCurrentFilter(Settings->value("filter", FILTER_TITLE).toInt()); + + /* View mode */ + //setViewMode(Settings->value("viewMode", VIEW_MODE_FEEDS).toInt()); +#endif + // state of files tree + channelpostfilesheader->restoreState(Settings->value("PostFilesTree").toByteArray()); + channelfilesheader->restoreState(Settings->value("FilesTree").toByteArray()); + + // state of splitter + ui->splitter->restoreState(Settings->value("SplitterChannelPosts").toByteArray()); + } else { +#ifdef TO_REMOVE + // save settings + + /* Filter */ + //Settings->setValue("filter", ui->filterLineEdit->currentFilter()); + + /* View mode */ + //Settings->setValue("viewMode", viewMode()); +#endif + // state of files tree + Settings->setValue("PostFilesTree", channelpostfilesheader->saveState()); + Settings->setValue("FilesTree", channelfilesheader->saveState()); + + // state of splitter + Settings->setValue("SplitterChannelPosts", ui->splitter->saveState()); + } + + Settings->endGroup(); +} + +void GxsChannelPostsWidgetWithModel::settingsChanged() +{ + mUseThread = Settings->getChannelLoadThread(); + + //mStateHelper->setWidgetVisible(ui->progressBar, mUseThread); +} + +QString GxsChannelPostsWidgetWithModel::groupName(bool) +{ + return QString::fromUtf8(mGroup.mMeta.mGroupName.c_str()); +} + +void GxsChannelPostsWidgetWithModel::groupNameChanged(const QString &name) +{ +// if (groupId().isNull()) { +// ui->nameLabel->setText(tr("No Channel Selected")); +// ui->logoLabel->setPixmap(QPixmap(":/icons/png/channels.png")); +// } else { +// ui->nameLabel->setText(name); +// } +} + +QIcon GxsChannelPostsWidgetWithModel::groupIcon() +{ + /* CHANNEL IMAGE */ + QPixmap chanImage; + if (mGroup.mImage.mData != NULL) { + GxsIdDetails::loadPixmapFromData(mGroup.mImage.mData, mGroup.mImage.mSize, chanImage,GxsIdDetails::ORIGINAL); + } else { + chanImage = FilesDefs::getPixmapFromQtResourcePath(ChannelPostThumbnailView::CHAN_DEFAULT_IMAGE); + } + + return QIcon(chanImage); +} + +/*************************************************************************************/ +/*************************************************************************************/ +/*************************************************************************************/ + +// Callback from Widget->FeedHolder->ServiceDialog->CommentContainer->CommentDialog, +void GxsChannelPostsWidgetWithModel::openComments(uint32_t /*type*/, const RsGxsGroupId &groupId, const QVector& msg_versions,const RsGxsMessageId &msgId, const QString &title) +{ + emit loadComment(groupId, msg_versions,msgId, title); +} + +void GxsChannelPostsWidgetWithModel::createMsg() +{ + if (groupId().isNull()) { + return; + } + + if (!IS_GROUP_SUBSCRIBED(mGroup.mMeta.mSubscribeFlags)) { + return; + } + + CreateGxsChannelMsg *msgDialog = new CreateGxsChannelMsg(groupId()); + msgDialog->show(); + + /* window will destroy itself! */ +} + +void GxsChannelPostsWidgetWithModel::insertChannelDetails(const RsGxsChannelGroup &group) +{ + // save selection if needed + + /* IMAGE */ + QPixmap chanImage; + if (group.mImage.mData != NULL) { + GxsIdDetails::loadPixmapFromData(group.mImage.mData, group.mImage.mSize, chanImage,GxsIdDetails::ORIGINAL); + } else { + chanImage = QPixmap(ChannelPostThumbnailView::CHAN_DEFAULT_IMAGE); + } + if(group.mMeta.mGroupName.empty()) + ui->channelName_LB->setText(tr("[No name]")); + else + ui->channelName_LB->setText(QString::fromUtf8(group.mMeta.mGroupName.c_str())); + + ui->logoLabel->setPixmap(chanImage); + ui->logoLabel->setFixedSize(QSize(ui->logoLabel->height()*chanImage.width()/(float)chanImage.height(),ui->logoLabel->height())); // make the logo have the same aspect ratio than the original image + + ui->postButton->setEnabled(bool(group.mMeta.mSubscribeFlags & GXS_SERV::GROUP_SUBSCRIBE_PUBLISH)); + + ui->subscribeToolButton->setSubscribed(IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags)); + ui->subscribeToolButton->setEnabled(true); + + + bool autoDownload ; + rsGxsChannels->getChannelAutoDownload(group.mMeta.mGroupId,autoDownload); + setAutoDownload(autoDownload); + + RetroShareLink link; + + if (IS_GROUP_SUBSCRIBED(group.mMeta.mSubscribeFlags)) + { + ui->subscribeToolButton->setText(tr("Subscribed") + " " + QString::number(group.mMeta.mPop) ); + //ui->feedToolButton->setEnabled(true); + //ui->fileToolButton->setEnabled(true); + ui->channel_TW->setTabEnabled(CHANNEL_TABS_POSTS,true); + ui->channel_TW->setTabEnabled(CHANNEL_TABS_FILES,true); + ui->details_TW->setEnabled(true); + } + else + { + ui->details_TW->setEnabled(false); + ui->channel_TW->setTabEnabled(CHANNEL_TABS_POSTS,false); + ui->channel_TW->setTabEnabled(CHANNEL_TABS_FILES,false); + } + + + ui->infoPosts->setText(QString::number(group.mMeta.mVisibleMsgCount)); + if(group.mMeta.mLastPost==0) + ui->infoLastPost->setText(tr("Never")); + else + ui->infoLastPost->setText(DateTime::formatLongDateTime(group.mMeta.mLastPost)); + QString formatDescription = QString::fromUtf8(group.mDescription.c_str()); + + unsigned int formatFlag = RSHTML_FORMATTEXT_EMBED_LINKS; + + // embed smileys ? + if (Settings->valueFromGroup(QString("ChannelPostsWidget"), QString::fromUtf8("Emoteicons_ChannelDecription"), true).toBool()) { + formatFlag |= RSHTML_FORMATTEXT_EMBED_SMILEYS; + } + + formatDescription = RsHtml().formatText(NULL, formatDescription, formatFlag); + + ui->infoDescription->setText(formatDescription); + + ui->infoAdministrator->setId(group.mMeta.mAuthorId) ; + + link = RetroShareLink::createMessage(group.mMeta.mAuthorId, ""); + ui->infoAdministrator->setText(link.toHtml()); + + ui->infoCreated->setText(DateTime::formatLongDateTime(group.mMeta.mPublishTs)); + + QString distrib_string ( "[unknown]" ); + + switch(group.mMeta.mCircleType) + { + case GXS_CIRCLE_TYPE_PUBLIC: distrib_string = tr("Public") ; + break ; + case GXS_CIRCLE_TYPE_EXTERNAL: + { + RsGxsCircleDetails det ; + + // !! What we need here is some sort of CircleLabel, which loads the circle and updates the label when done. + + if(rsGxsCircles->getCircleDetails(group.mMeta.mCircleId,det)) + distrib_string = tr("Restricted to members of circle \"")+QString::fromUtf8(det.mCircleName.c_str()) +"\""; + else + distrib_string = tr("Restricted to members of circle ")+QString::fromStdString(group.mMeta.mCircleId.toStdString()) ; + } + break ; + case GXS_CIRCLE_TYPE_YOUR_EYES_ONLY: distrib_string = tr("Your eyes only"); + break ; + case GXS_CIRCLE_TYPE_LOCAL: distrib_string = tr("You and your friend nodes"); + break ; + default: + std::cerr << "(EE) badly initialised group distribution ID = " << group.mMeta.mCircleType << std::endl; + } + + ui->infoDistribution->setText(distrib_string); +#ifdef TODO + ui->infoWidget->show(); + ui->feedWidget->hide(); + ui->fileWidget->hide(); + + //ui->feedToolButton->setEnabled(false); + //ui->fileToolButton->setEnabled(false); +#endif + ui->subscribeToolButton->setText(tr("Subscribe ") + " " + QString::number(group.mMeta.mPop) ); + +} + +#ifdef TODO +int GxsChannelPostsWidgetWithModel::viewMode() +{ + if (ui->feedToolButton->isChecked()) { + return VIEW_MODE_FEEDS; + } else if (ui->fileToolButton->isChecked()) { + return VIEW_MODE_FILES; + } + + /* Default */ + return VIEW_MODE_FEEDS; +} +#endif + +void GxsChannelPostsWidgetWithModel::setViewMode(int viewMode) +{ +#ifdef TODO + switch (viewMode) { + case VIEW_MODE_FEEDS: + ui->feedWidget->show(); + ui->fileWidget->hide(); + + ui->feedToolButton->setChecked(true); + ui->fileToolButton->setChecked(false); + + break; + case VIEW_MODE_FILES: + ui->feedWidget->hide(); + ui->fileWidget->show(); + + ui->feedToolButton->setChecked(false); + ui->fileToolButton->setChecked(true); + + break; + default: + setViewMode(VIEW_MODE_FEEDS); + return; + } +#endif +} + +void GxsChannelPostsWidgetWithModel::filterChanged(QString s) +{ + QStringList ql = s.split(' ',QString::SkipEmptyParts); + uint32_t count; + mChannelPostsModel->setFilter(ql,count); + mChannelFilesModel->setFilter(ql,count); + + //mChannelPostFilesProxyModel->setFilterKeyColumn(RsGxsChannelPostFilesModel::COLUMN_FILES_NAME); + //mChannelPostFilesProxyModel->setFilterRegExp(s) ;// triggers a re-display. s is actually not used. +} + +#ifdef TODO +/*static*/ bool GxsChannelPostsWidgetWithModel::filterItem(FeedItem *feedItem, const QString &text, int filter) +{ + GxsChannelPostItem *item = dynamic_cast(feedItem); + if (!item) { + return true; + } + + bool bVisible = text.isEmpty(); + + if (!bVisible) + { + switch(filter) + { + case FILTER_TITLE: + bVisible = item->getTitleLabel().contains(text,Qt::CaseInsensitive); + break; + case FILTER_MSG: + bVisible = item->getMsgLabel().contains(text,Qt::CaseInsensitive); + break; + case FILTER_FILE_NAME: + { + std::list fileItems = item->getFileItems(); + std::list::iterator lit; + for(lit = fileItems.begin(); lit != fileItems.end(); ++lit) + { + SubFileItem *fi = *lit; + QString fileName = QString::fromUtf8(fi->FileName().c_str()); + bVisible = (bVisible || fileName.contains(text,Qt::CaseInsensitive)); + } + break; + } + default: + bVisible = true; + break; + } + } + + return bVisible; +} + +void GxsChannelPostsWidget::createPostItemFromMetaData(const RsGxsMsgMetaData& meta,bool related) +{ + GxsChannelPostItem *item = NULL; + RsGxsChannelPost post; + + if(!meta.mOrigMsgId.isNull()) + { + FeedItem *feedItem = ui->feedWidget->findFeedItem(GxsChannelPostItem::computeIdentifier(meta.mOrigMsgId)) ; + item = dynamic_cast(feedItem); + + if(item) + { + post = feedItem->post(); + ui->feedWidget->removeFeedItem(item) ; + + post.mOlderVersions.insert(post.mMeta.mMsgId); + + GxsChannelPostItem *item = new GxsChannelPostItem(this, 0, post, true, false,post.mOlderVersions); + ui->feedWidget->addFeedItem(item, ROLE_PUBLISH, QDateTime::fromTime_t(post.mMeta.mPublishTs)); + + return ; + } + } + + if (related) + { + FeedItem *feedItem = ui->feedWidget->findFeedItem(GxsChannelPostItem::computeIdentifier(meta.mMsgId)) ; + item = dynamic_cast(feedItem); + } + if (item) + { + item->setPost(post); + ui->feedWidget->setSort(item, ROLE_PUBLISH, QDateTime::fromTime_t(meta.mPublishTs)); + } + else + { + GxsChannelPostItem *item = new GxsChannelPostItem(this, 0, meta.mGroupId,meta.mMsgId, true, true); + ui->feedWidget->addFeedItem(item, ROLE_PUBLISH, QDateTime::fromTime_t(post.mMeta.mPublishTs)); + } +#ifdef TODO + ui->fileWidget->addFiles(post, related); +#endif +} + +void GxsChannelPostsWidget::createPostItem(const RsGxsChannelPost& post, bool related) +{ + GxsChannelPostItem *item = NULL; + + const RsMsgMetaData& meta(post.mMeta); + + if(!meta.mOrigMsgId.isNull()) + { + FeedItem *feedItem = ui->feedWidget->findFeedItem(GxsChannelPostItem::computeIdentifier(meta.mOrigMsgId)) ; + item = dynamic_cast(feedItem); + + if(item) + { + std::set older_versions(item->olderVersions()); // we make a copy because the item will be deleted + ui->feedWidget->removeFeedItem(item) ; + + older_versions.insert(meta.mMsgId); + + GxsChannelPostItem *item = new GxsChannelPostItem(this, 0, mGroup.mMeta,meta.mMsgId, true, false,older_versions); + ui->feedWidget->addFeedItem(item, ROLE_PUBLISH, QDateTime::fromTime_t(meta.mPublishTs)); + + return ; + } + } + + if (related) + { + FeedItem *feedItem = ui->feedWidget->findFeedItem(GxsChannelPostItem::computeIdentifier(meta.mMsgId)) ; + item = dynamic_cast(feedItem); + } + if (item) + { + item->setPost(post); + ui->feedWidget->setSort(item, ROLE_PUBLISH, QDateTime::fromTime_t(meta.mPublishTs)); + } + else + { + GxsChannelPostItem *item = new GxsChannelPostItem(this, 0, mGroup.mMeta,meta.mMsgId, true, true); + ui->feedWidget->addFeedItem(item, ROLE_PUBLISH, QDateTime::fromTime_t(meta.mPublishTs)); + } + + ui->fileWidget->addFiles(post, related); +} + +void GxsChannelPostsWidget::fillThreadCreatePost(const QVariant &post, bool related, int current, int count) +{ + /* show fill progress */ + if (count) { + ui->progressBar->setValue(current * ui->progressBar->maximum() / count); + } + + if (!post.canConvert()) { + return; + } + + createPostItem(post.value(), related); +} +#endif + +void GxsChannelPostsWidgetWithModel::blank() +{ + ui->postButton->setEnabled(false); + ui->subscribeToolButton->setEnabled(false); + + ui->channelName_LB->setText(tr("No Channel Selected")); + ui->logoLabel->setPixmap(FilesDefs::getPixmapFromQtResourcePath(":/icons/png/channels.png")); + ui->infoPosts->setText(""); + ui->infoLastPost->setText(""); + ui->infoAdministrator->setText(""); + ui->infoDistribution->setText(""); + ui->infoCreated->setText(""); + ui->infoDescription->setText(""); + + mChannelPostsModel->clear(); + mChannelPostFilesModel->clear(); + ui->postDetails_TE->clear(); + ui->postLogo_LB->hide(); + ui->postName_LB->hide(); + ui->postTime_LB->hide(); + groupNameChanged(QString()); + +} + +bool GxsChannelPostsWidgetWithModel::navigate(const RsGxsMessageId& msgId) +{ + QModelIndex index = mChannelPostsModel->getIndexOfMessage(msgId); + + if(!index.isValid()) + { + std::cerr << "(EE) Cannot navigate to msg " << msgId << " in channel " << mGroup.mMeta.mGroupId << ": index unknown. Setting mNavigatePendingMsgId." << std::endl; + + mSelectedPost = msgId; // not found. That means the forum may not be loaded yet. So we keep that post in mind, for after loading. + return true; // we have to return true here, otherwise the caller will intepret the async loading as an error. + } + + ui->postsTree->selectionModel()->setCurrentIndex(index,QItemSelectionModel::ClearAndSelect); + ui->postsTree->scrollTo(index);//May change if model reloaded + ui->postsTree->setFocus(); + + return true; +} + +void GxsChannelPostsWidgetWithModel::subscribeGroup(bool subscribe) +{ + RsGxsGroupId grpId(groupId()); + if (grpId.isNull()) return; + + RsThread::async([=]() + { + rsGxsChannels->subscribeToChannel(grpId, subscribe); + } ); +} + +void GxsChannelPostsWidgetWithModel::setAutoDownload(bool autoDl) +{ + mAutoDownloadAction->setChecked(autoDl); + mAutoDownloadAction->setText(autoDl ? tr("Disable Auto-Download") : tr("Enable Auto-Download")); +} + +void GxsChannelPostsWidgetWithModel::toggleAutoDownload() +{ + RsGxsGroupId grpId = groupId(); + if (grpId.isNull()) { + return; + } + + bool autoDownload; + if(!rsGxsChannels->getChannelAutoDownload(grpId, autoDownload)) + { + std::cerr << __PRETTY_FUNCTION__ << " failed to get autodownload value " + << "for channel: " << grpId.toStdString() << std::endl; + return; + } + + RsThread::async([this, grpId, autoDownload]() + { + if(!rsGxsChannels->setChannelAutoDownload(grpId, !autoDownload)) + { + std::cerr << __PRETTY_FUNCTION__ << " failed to set autodownload " + << "for channel: " << grpId.toStdString() << std::endl; + return; + } + }); +} + +class GxsChannelPostsReadData +{ +public: + GxsChannelPostsReadData(bool read) + { + mRead = read; + mLastToken = 0; + } + +public: + bool mRead; + uint32_t mLastToken; +}; + +static void setAllMessagesReadCallback(FeedItem *feedItem, void *data) +{ + GxsChannelPostItem *channelPostItem = dynamic_cast(feedItem); + if (!channelPostItem) { + return; + } + + GxsChannelPostsReadData *readData = (GxsChannelPostsReadData*) data; + bool isRead = !channelPostItem->isUnread() ; + + if(channelPostItem->isLoaded() && (isRead == readData->mRead)) + return ; + + RsGxsGrpMsgIdPair msgPair = std::make_pair(channelPostItem->groupId(), channelPostItem->messageId()); + rsGxsChannels->setMessageReadStatus(readData->mLastToken, msgPair, readData->mRead); +} + +void GxsChannelPostsWidgetWithModel::setAllMessagesReadDo(bool read, uint32_t &token) +{ + if (groupId().isNull() || !IS_GROUP_SUBSCRIBED(mGroup.mMeta.mSubscribeFlags)) { + return; + } + + GxsChannelPostsReadData data(read); + //ui->feedWidget->withAll(setAllMessagesReadCallback, &data); + + token = data.mLastToken; +} + diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidgetWithModel.h b/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidgetWithModel.h new file mode 100644 index 000000000..be2ccdf6c --- /dev/null +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidgetWithModel.h @@ -0,0 +1,184 @@ +/******************************************************************************* + * retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidget.h * + * * + * Copyright 2013 by 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 . * + * * + *******************************************************************************/ + +#ifndef _GXS_CHANNELPOSTSWIDGET_H +#define _GXS_CHANNELPOSTSWIDGET_H + +#include + +#include + +#include "gui/gxs/GxsMessageFramePostWidget.h" +#include "gui/feeds/FeedHolder.h" + +namespace Ui { +class GxsChannelPostsWidgetWithModel; +} + +class GxsChannelPostItem; +class QTreeWidgetItem; +class QSortFilterProxyModel; +class FeedItem; +class RsGxsChannelPostsModel; +class RsGxsChannelPostFilesModel; +class RsGxsChannelPostFilesProxyModel; + +class ChannelPostFilesDelegate: public QStyledItemDelegate +{ + Q_OBJECT + + public: + ChannelPostFilesDelegate(QObject *parent=0) : QStyledItemDelegate(parent){} + virtual ~ChannelPostFilesDelegate(){} + + void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const override; + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const override; + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + +class ChannelPostDelegate: public QAbstractItemDelegate +{ + Q_OBJECT + + public: + ChannelPostDelegate(QObject *parent=0) : QAbstractItemDelegate(parent), mZoom(1.0){} + virtual ~ChannelPostDelegate(){} + + void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const override; + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; + + int cellSize(const QFont& font) const; + void zoom(bool zoom_or_unzoom) ; + private: + static constexpr float IMAGE_MARGIN_FACTOR = 1.0; + static constexpr float IMAGE_SIZE_FACTOR_W = 4.0 ; + static constexpr float IMAGE_SIZE_FACTOR_H = 6.0 ; + static constexpr float IMAGE_ZOOM_FACTOR = 1.0; + + float mZoom; // zoom factor for the whole thumbnail +}; + +class GxsChannelPostsWidgetWithModel: public GxsMessageFrameWidget +{ + Q_OBJECT + +public: + /* Filters */ + enum Filter { + FILTER_TITLE = 1, + FILTER_MSG = 2, + FILTER_FILE_NAME = 3 + }; + +public: + /** Default Constructor */ + GxsChannelPostsWidgetWithModel(const RsGxsGroupId &channelId, QWidget *parent = 0); + /** Default Destructor */ + ~GxsChannelPostsWidgetWithModel(); + + /* GxsMessageFrameWidget */ + virtual QIcon groupIcon(); + virtual void groupIdChanged() { updateDisplay(true); } + virtual QString groupName(bool) override; + virtual bool navigate(const RsGxsMessageId&) override; + + void updateDisplay(bool complete); + +#ifdef TODO + /* FeedHolder */ + virtual QScrollArea *getScrollArea(); + virtual void deleteFeedItem(FeedItem *feedItem, uint32_t type); + virtual void openChat(const RsPeerId& peerId); +#endif + virtual void openComments(uint32_t type, const RsGxsGroupId &groupId, const QVector &msg_versions, const RsGxsMessageId &msgId, const QString &title); + +protected: + /* GxsMessageFramePostWidget */ + virtual void groupNameChanged(const QString &name); + +#ifdef TODO + virtual bool insertGroupData(const RsGxsGenericGroupData *data) override; +#endif + virtual bool useThread() { return mUseThread; } + virtual void blank() ; + +#ifdef TODO + virtual bool getGroupData(RsGxsGenericGroupData *& data) override; + virtual void getMsgData(const std::set& msgIds,std::vector& posts) override; + virtual void getAllMsgData(std::vector& posts) override; + virtual void insertPosts(const std::vector& posts) override; + virtual void insertAllPosts(const std::vector& posts, GxsMessageFramePostThread *thread) override; +#endif + + /* GxsMessageFrameWidget */ + virtual void setAllMessagesReadDo(bool read, uint32_t &token); + +private slots: + void showPostDetails(); + void updateGroupData(); + void createMsg(); + void toggleAutoDownload(); + void subscribeGroup(bool subscribe); + void filterChanged(QString); + void setViewMode(int viewMode); + void settingsChanged(); + void handlePostsTreeSizeChange(QSize s); + void postChannelPostLoad(); + void editPost(); + void postContextMenu(const QPoint&); + void copyMessageLink(); + void updateZoomFactor(bool zoom_or_unzoom); + +public slots: + void sortColumnFiles(int col,Qt::SortOrder so); + void sortColumnPostFiles(int col,Qt::SortOrder so); + +private: + void processSettings(bool load); + + void setAutoDownload(bool autoDl); + static bool filterItem(FeedItem *feedItem, const QString &text, int filter); + + int viewMode(); + + void insertChannelDetails(const RsGxsChannelGroup &group); + void handleEvent_main_thread(std::shared_ptr event); + +private: + QAction *mAutoDownloadAction; + + RsGxsChannelGroup mGroup; + bool mUseThread; + RsEventsHandlerId_t mEventHandlerId ; + + RsGxsChannelPostsModel *mChannelPostsModel; + RsGxsChannelPostFilesModel *mChannelPostFilesModel; + RsGxsChannelPostFilesModel *mChannelFilesModel; + ChannelPostDelegate *mChannelPostsDelegate; + + RsGxsMessageId mSelectedPost; + + /* UI - from Designer */ + Ui::GxsChannelPostsWidgetWithModel *ui; +}; + +#endif diff --git a/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidgetWithModel.ui b/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidgetWithModel.ui new file mode 100644 index 000000000..2886e8dbe --- /dev/null +++ b/retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidgetWithModel.ui @@ -0,0 +1,601 @@ + + + GxsChannelPostsWidgetWithModel + + + + 0 + 0 + 977 + 628 + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::Box + + + QFrame::Sunken + + + + 4 + + + 2 + + + 6 + + + 2 + + + + + + 0 + 0 + + + + Subscribe + + + + 16 + 16 + + + + QToolButton::MenuButtonPopup + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + Qt::NoFocus + + + Post to Channel + + + Add new post + + + + :/icons/png/add.png:/icons/png/add.png + + + + 32 + 16 + + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 0 + 0 + + + + + + + + 0 + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + + 200 + 16777215 + + + + Search channels + + + true + + + + + + + + + + 0 + + + + Channel details + + + + + + + 75 + true + false + + + + Channel title + + + + + + + + + TextLabel + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + unknown + + + + + + + + 75 + true + + + + Created: + + + + + + + unknown + + + true + + + + + + + + 75 + true + + + + Distribution: + + + + + + + 0 + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Last Post: + + + + + + + unknown + + + + + + + + 75 + true + + + + Administrator: + + + + + + + unknown + + + + + + + + 0 + 0 + + + + + 75 + true + + + + Posts: + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt;">Description</span></p></body></html> + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + true + + + true + + + + + + + + Posts + + + + 0 + + + + + Qt::Vertical + + + + Qt::CustomContextMenu + + + QAbstractScrollArea::AdjustToContents + + + QAbstractItemView::SelectItems + + + Qt::ElideNone + + + 0 + + + false + + + false + + + true + + + true + + + false + + + + + + true + + + + 0 + + + + Details + + + + 3 + + + + + + + TextLabel + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + TextLabel + + + true + + + + + + + TextLabel + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + true + + + + + + + + Files + + + + + + QAbstractItemView::CurrentChanged|QAbstractItemView::SelectedClicked + + + true + + + 5 + + + false + + + + + + + + Comments + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + Files + + + + + + QAbstractItemView::CurrentChanged|QAbstractItemView::SelectedClicked + + + false + + + true + + + false + + + + + + + + + + + Feeds + + + + + Files + + + + + + GxsIdLabel + QLabel +
gui/gxs/GxsIdLabel.h
+
+ + SubscribeToolButton + QToolButton +
gui/common/SubscribeToolButton.h
+
+ + StyledElidedLabel + QLabel +
gui/common/StyledElidedLabel.h
+
+ + RSTreeView + QTreeView +
gui/common/RSTreeView.h
+
+ + GxsCommentDialog + QWidget +
gui/gxs/GxsCommentDialog.h
+ 1 +
+ + LineEditClear + QLineEdit +
gui/common/LineEditClear.h
+
+
+ + + + +
diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumModel.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumModel.cpp index f72649dfc..e4d6f13e9 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumModel.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumModel.cpp @@ -669,8 +669,11 @@ QVariant RsGxsForumModel::displayRole(const ForumModelPostEntry& fmpe,int col) c return QVariant(DateTime::formatDateTime(qtime)); } - case COLUMN_THREAD_DISTRIBUTION: - case COLUMN_THREAD_AUTHOR:{ + case COLUMN_THREAD_DISTRIBUTION: // passthrough // handled by delegate. + case COLUMN_THREAD_MSGID: + return QVariant(); + case COLUMN_THREAD_AUTHOR: + { QString name; RsGxsId id = RsGxsId(fmpe.mAuthorId.toStdString()); @@ -680,7 +683,6 @@ QVariant RsGxsForumModel::displayRole(const ForumModelPostEntry& fmpe,int col) c return name; return QVariant(tr("[Unknown]")); } - case COLUMN_THREAD_MSGID: return QVariant(); #ifdef TODO if (filterColumn == COLUMN_THREAD_CONTENT) { // need content for filter diff --git a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp index 02eb74334..fd9c591ca 100644 --- a/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp +++ b/retroshare-gui/src/gui/gxsforums/GxsForumThreadWidget.cpp @@ -123,9 +123,9 @@ public: { default: case 3: - case 0: icon = QIcon(IMAGE_VOID); break; - case 1: icon = QIcon(IMAGE_WARNING_YELLOW); break; - case 2: icon = QIcon(IMAGE_WARNING_RED); break; + case 0: icon = FilesDefs::getIconFromQtResourcePath(IMAGE_VOID); break; + case 1: icon = FilesDefs::getIconFromQtResourcePath(IMAGE_WARNING_YELLOW); break; + case 2: icon = FilesDefs::getIconFromQtResourcePath(IMAGE_WARNING_RED); break; } QPixmap pix = icon.pixmap(r.size()); @@ -134,6 +134,13 @@ public: const QPoint p = QPoint((r.width() - pix.width())/2, (r.height() - pix.height())/2); painter->drawPixmap(r.topLeft() + p, pix); } + + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override + { + static auto img(FilesDefs::getPixmapFromQtResourcePath(IMAGE_WARNING_YELLOW)); + + return QSize(img.width()*1.2,option.rect.height()); + } }; class ReadStatusItemDelegate: public QStyledItemDelegate @@ -172,9 +179,9 @@ public: else { if (unread) - icon = QIcon(":/images/message-state-unread.png"); + icon = FilesDefs::getIconFromQtResourcePath(":/images/message-state-unread.png"); else - icon = QIcon(":/images/message-state-read.png"); + icon = FilesDefs::getIconFromQtResourcePath(":/images/message-state-read.png"); } QPixmap pix = icon.pixmap(r.size()); @@ -183,6 +190,13 @@ public: const QPoint p = QPoint((r.width() - pix.width())/2, (r.height() - pix.height())/2); painter->drawPixmap(r.topLeft() + p, pix); } + + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override + { + static auto img(FilesDefs::getPixmapFromQtResourcePath(":/images/message-state-unread.png")); + + return QSize(img.width()*1.2,option.rect.height()); + } }; class ForumPostSortFilterProxyModel: public QSortFilterProxyModel @@ -299,9 +313,10 @@ GxsForumThreadWidget::GxsForumThreadWidget(const RsGxsGroupId &forumId, QWidget QHeaderView * ttheader = ui->threadTreeWidget->header () ; ttheader->resizeSection (RsGxsForumModel::COLUMN_THREAD_DATE, 140*f); ttheader->resizeSection (RsGxsForumModel::COLUMN_THREAD_TITLE, 440*f); - ttheader->resizeSection (RsGxsForumModel::COLUMN_THREAD_DISTRIBUTION, 24*f); ttheader->resizeSection (RsGxsForumModel::COLUMN_THREAD_AUTHOR, 150*f); - ttheader->resizeSection (RsGxsForumModel::COLUMN_THREAD_READ, 24*f); + + ui->threadTreeWidget->resizeColumnToContents(RsGxsForumModel::COLUMN_THREAD_DISTRIBUTION); + ui->threadTreeWidget->resizeColumnToContents(RsGxsForumModel::COLUMN_THREAD_READ); QHeaderView_setSectionResizeModeColumn(ttheader, RsGxsForumModel::COLUMN_THREAD_TITLE, QHeaderView::Interactive); QHeaderView_setSectionResizeModeColumn(ttheader, RsGxsForumModel::COLUMN_THREAD_DATE, QHeaderView::Interactive); @@ -462,7 +477,7 @@ QString GxsForumThreadWidget::groupName(bool withUnreadCount) QIcon GxsForumThreadWidget::groupIcon() { if (mNewCount) { - return QIcon(":/images/message-state-new.png"); + return FilesDefs::getIconFromQtResourcePath(":/images/message-state-new.png"); } return QIcon(); @@ -498,6 +513,7 @@ void GxsForumThreadWidget::recursRestoreExpandedItems(const QModelIndex& /*index ui->threadTreeWidget->setExpanded( mThreadProxyModel->mapFromSource(mThreadModel->getIndexOfMessage(*it)) ,true) ; } + void GxsForumThreadWidget::updateDisplay(bool complete) { #ifdef DEBUG_FORUMS @@ -560,35 +576,35 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) #ifdef DEBUG_FORUMS std::cerr << "Clicked on msg " << current_post.mMsgId << std::endl; #endif - QAction *editAct = new QAction(QIcon(IMAGE_MESSAGEEDIT), tr("Edit"), &contextMnu); + QAction *editAct = new QAction(FilesDefs::getIconFromQtResourcePath(IMAGE_MESSAGEEDIT), tr("Edit"), &contextMnu); connect(editAct, SIGNAL(triggered()), this, SLOT(editforummessage())); bool is_pinned = mForumGroup.mPinnedPosts.ids.find(mThreadId) != mForumGroup.mPinnedPosts.ids.end(); - QAction *pinUpPostAct = new QAction(QIcon(IMAGE_PINPOST), (is_pinned?tr("Un-pin this post"):tr("Pin this post up")), &contextMnu); + QAction *pinUpPostAct = new QAction(FilesDefs::getIconFromQtResourcePath(IMAGE_PINPOST), (is_pinned?tr("Un-pin this post"):tr("Pin this post up")), &contextMnu); connect(pinUpPostAct , SIGNAL(triggered()), this, SLOT(togglePinUpPost())); - QAction *replyAct = new QAction(QIcon(IMAGE_REPLY), tr("Reply"), &contextMnu); + QAction *replyAct = new QAction(FilesDefs::getIconFromQtResourcePath(IMAGE_REPLY), tr("Reply"), &contextMnu); connect(replyAct, SIGNAL(triggered()), this, SLOT(replytoforummessage())); - QAction *replyauthorAct = new QAction(QIcon(IMAGE_MESSAGEREPLY), tr("Reply to author with private message"), &contextMnu); + QAction *replyauthorAct = new QAction(FilesDefs::getIconFromQtResourcePath(IMAGE_MESSAGEREPLY), tr("Reply to author with private message"), &contextMnu); connect(replyauthorAct, SIGNAL(triggered()), this, SLOT(reply_with_private_message())); - QAction *flagaspositiveAct = new QAction(QIcon(IMAGE_POSITIVE_OPINION), tr("Give positive opinion"), &contextMnu); + QAction *flagaspositiveAct = new QAction(FilesDefs::getIconFromQtResourcePath(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(static_cast(RsOpinion::POSITIVE)); connect(flagaspositiveAct, SIGNAL(triggered()), this, SLOT(flagperson())); - QAction *flagasneutralAct = new QAction(QIcon(IMAGE_NEUTRAL_OPINION), tr("Give neutral opinion"), &contextMnu); + QAction *flagasneutralAct = new QAction(FilesDefs::getIconFromQtResourcePath(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(static_cast(RsOpinion::NEUTRAL)); connect(flagasneutralAct, SIGNAL(triggered()), this, SLOT(flagperson())); - QAction *flagasnegativeAct = new QAction(QIcon(IMAGE_NEGATIVE_OPINION), tr("Give negative opinion"), &contextMnu); + QAction *flagasnegativeAct = new QAction(FilesDefs::getIconFromQtResourcePath(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(static_cast(RsOpinion::NEGATIVE)); connect(flagasnegativeAct, SIGNAL(triggered()), this, SLOT(flagperson())); - QAction *newthreadAct = new QAction(QIcon(IMAGE_MESSAGE), tr("Start New Thread"), &contextMnu); + QAction *newthreadAct = new QAction(FilesDefs::getIconFromQtResourcePath(IMAGE_MESSAGE), tr("Start New Thread"), &contextMnu); newthreadAct->setEnabled (IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags)); connect(newthreadAct , SIGNAL(triggered()), this, SLOT(createthread())); @@ -598,19 +614,19 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) QAction* collapseAll = new QAction(tr( "Collapse all"), &contextMnu); connect(collapseAll, SIGNAL(triggered()), ui->threadTreeWidget, SLOT(collapseAll())); - QAction *markMsgAsRead = new QAction(QIcon(":/images/message-mail-read.png"), tr("Mark as read"), &contextMnu); + QAction *markMsgAsRead = new QAction(FilesDefs::getIconFromQtResourcePath(":/images/message-mail-read.png"), tr("Mark as read"), &contextMnu); connect(markMsgAsRead, SIGNAL(triggered()), this, SLOT(markMsgAsRead())); - QAction *markMsgAsReadChildren = new QAction(QIcon(":/images/message-mail-read.png"), tr("Mark as read") + " (" + tr ("with children") + ")", &contextMnu); + QAction *markMsgAsReadChildren = new QAction(FilesDefs::getIconFromQtResourcePath(":/images/message-mail-read.png"), tr("Mark as read") + " (" + tr ("with children") + ")", &contextMnu); connect(markMsgAsReadChildren, SIGNAL(triggered()), this, SLOT(markMsgAsReadChildren())); - QAction *markMsgAsUnread = new QAction(QIcon(":/images/message-mail.png"), tr("Mark as unread"), &contextMnu); + QAction *markMsgAsUnread = new QAction(FilesDefs::getIconFromQtResourcePath(":/images/message-mail.png"), tr("Mark as unread"), &contextMnu); connect(markMsgAsUnread, SIGNAL(triggered()), this, SLOT(markMsgAsUnread())); - QAction *markMsgAsUnreadChildren = new QAction(QIcon(":/images/message-mail.png"), tr("Mark as unread") + " (" + tr ("with children") + ")", &contextMnu); + QAction *markMsgAsUnreadChildren = new QAction(FilesDefs::getIconFromQtResourcePath(":/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/info16.png"), tr("Show author in people tab"), &contextMnu); + QAction *showinpeopleAct = new QAction(FilesDefs::getIconFromQtResourcePath(":/images/info16.png"), tr("Show author in people tab"), &contextMnu); connect(showinpeopleAct, SIGNAL(triggered()), this, SLOT(showInPeopleTab())); if (IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags)) @@ -663,7 +679,7 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/) contextMnu.addAction(replyAct); contextMnu.addAction(newthreadAct); - QAction* action = contextMnu.addAction(QIcon(IMAGE_COPYLINK), tr("Copy RetroShare Link"), this, SLOT(copyMessageLink())); + QAction* action = contextMnu.addAction(FilesDefs::getIconFromQtResourcePath(IMAGE_COPYLINK), tr("Copy RetroShare Link"), this, SLOT(copyMessageLink())); action->setEnabled(!groupId().isNull() && !mThreadId.isNull()); contextMnu.addSeparator(); contextMnu.addAction(markMsgAsRead); @@ -755,7 +771,7 @@ void GxsForumThreadWidget::togglethreadview_internal() { // if (ui->expandButton->isChecked()) { ui->postText->setVisible(true); - ui->expandButton->setIcon(QIcon(QString(":/images/edit_remove24.png"))); + ui->expandButton->setIcon(FilesDefs::getIconFromQtResourcePath(QString(":/images/edit_remove24.png"))); ui->expandButton->setToolTip(tr("Hide")); // } else { // ui->postText->setVisible(false); diff --git a/retroshare-gui/src/gui/icons.qrc b/retroshare-gui/src/gui/icons.qrc index 6e4bd7569..656cdc7fd 100644 --- a/retroshare-gui/src/gui/icons.qrc +++ b/retroshare-gui/src/gui/icons.qrc @@ -60,6 +60,7 @@ icons/png/anonymous.png icons/png/attach-image.png icons/png/attach.png + icons/png/arrow.png icons/png/cert.png icons/png/channels-notify.png icons/png/channels.png diff --git a/retroshare-gui/src/gui/icons/png/arrow.png b/retroshare-gui/src/gui/icons/png/arrow.png new file mode 100644 index 000000000..e23853802 Binary files /dev/null and b/retroshare-gui/src/gui/icons/png/arrow.png differ diff --git a/retroshare-gui/src/gui/qss/stylesheet/Standard.qss b/retroshare-gui/src/gui/qss/stylesheet/Standard.qss index 00569eef2..eb12e6551 100644 --- a/retroshare-gui/src/gui/qss/stylesheet/Standard.qss +++ b/retroshare-gui/src/gui/qss/stylesheet/Standard.qss @@ -745,12 +745,7 @@ GxsForumMsgItem QFrame#frame{ background-color: white; } -GxsChannelPostsWidget QFrame#infoFrame -{ - -} - -GxsChannelPostsWidget QToolButton#subscribeToolButton { +GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton { font: bold; font-size: 14px; color: white; @@ -759,18 +754,18 @@ GxsChannelPostsWidget QToolButton#subscribeToolButton { max-height: 27px; } -GxsChannelPostsWidget QToolButton#subscribeToolButton:hover { +GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton:hover { background: #03b1f3; border-radius: 4px; } -GxsChannelPostsWidget QToolButton#subscribeToolButton:pressed { +GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton:pressed { background: #03b1f3; border-radius: 4px; border: 1px solid gray; } -GxsChannelPostsWidget QToolButton#subscribeToolButton:disabled { +GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton:disabled { background: gray; border-radius: 4px; border: 1px solid gray; @@ -778,19 +773,35 @@ GxsChannelPostsWidget QToolButton#subscribeToolButton:disabled { } /* only for MenuButtonPopup */ -GxsChannelPostsWidget QToolButton#subscribeToolButton[popupMode="1"] { +GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton[popupMode="1"] { padding-right: 0px; } -GxsChannelPostsWidget QToolButton#subscribeToolButton::menu-arrow { +GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton::menu-arrow { image: none; } -GxsChannelPostsWidget QToolButton#subscribeToolButton::menu-button { +GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton::menu-button { image: none; } +GxsChannelFilesStatusWidget QToolButton#openFolderToolButton::menu-indicator { + image: none; +} + +GxsChannelFilesStatusWidget QToolButton#openFolderToolButton[popupMode="0"] { + padding-right: 0px; +} + +GxsChannelFilesStatusWidget QToolButton#openFolderToolButton::menu-indicator { + image: none; +} + +GxsChannelFilesStatusWidget QToolButton#openFolderToolButton[popupMode="0"] { + padding-right: 0px; +} + GxsGroupDialog QLabel#groupLogo{ border: 2px solid #CCCCCC; border-radius: 3px; diff --git a/retroshare-gui/src/gui/qss/stylesheet/qss.default b/retroshare-gui/src/gui/qss/stylesheet/qss.default index f71a4b435..b4e20120a 100644 --- a/retroshare-gui/src/gui/qss/stylesheet/qss.default +++ b/retroshare-gui/src/gui/qss/stylesheet/qss.default @@ -84,7 +84,7 @@ ConfCertDialog QLabel#servicePermissionsLabel qproperty-fontSizeFactor: 125; } -GxsChannelPostsWidget QLabel#nameLabel +GxsChannelPostsWidgetWithModel QLabel#channelName_LB { qproperty-fontSizeFactor: 250; } diff --git a/retroshare-gui/src/gui/settings/ServerPage.cpp b/retroshare-gui/src/gui/settings/ServerPage.cpp index 2881acd60..37291be0f 100755 --- a/retroshare-gui/src/gui/settings/ServerPage.cpp +++ b/retroshare-gui/src/gui/settings/ServerPage.cpp @@ -23,6 +23,7 @@ #include #include "rshare.h" #include "rsharesettings.h" +#include "util/i2pcommon.h" #include "util/RsNetUtil.h" #include "util/misc.h" @@ -82,6 +83,10 @@ ServerPage::ServerPage(QWidget * parent, Qt::WindowFlags flags) manager = NULL ; mOngoingConnectivityCheck = -1; +#ifndef RS_USE_I2P_BOB + ui.hiddenServiceTab->removeTab(TAB_HIDDEN_SERVICE_I2P_BOB); // warning: the order of operation here is very important. +#endif + if(RsAccounts::isHiddenNode()) { if(RsAccounts::isTorAuto()) @@ -1352,7 +1357,7 @@ void ServerPage::updateInProxyIndicator() ui.iconlabel_service_incoming->setMovie(movie); movie->start(); - if (mHiddenType == RS_HIDDEN_TYPE_I2P && mBobSettings.enableBob) { + if (mHiddenType == RS_HIDDEN_TYPE_I2P && mBobSettings.enable) { QTcpSocket tcpSocket; @@ -1439,15 +1444,16 @@ void ServerPage::getNewKey() void ServerPage::loadKey() { - mBobSettings.keys = ui.pteBobServerKey->toPlainText().toStdString(); - mBobSettings.addr = p3I2pBob::keyToBase32Addr(mBobSettings.keys); + mBobSettings.address.privateKey = ui.pteBobServerKey->toPlainText().toStdString(); + mBobSettings.address.publicKey = i2p::publicKeyFromPrivate(mBobSettings.address.privateKey); + mBobSettings.address.base32 = i2p::keyToBase32Addr(mBobSettings.address.publicKey); rsAutoProxyMonitor::taskSync(autoProxyType::I2PBOB, autoProxyTask::setSettings, &mBobSettings); } void ServerPage::enableBob(bool checked) { - mBobSettings.enableBob = checked; + mBobSettings.enable = checked; rsAutoProxyMonitor::taskSync(autoProxyType::I2PBOB, autoProxyTask::setSettings, &mBobSettings); @@ -1487,7 +1493,7 @@ void ServerPage::toggleBobAdvancedSettings(bool checked) { ui.swBobAdvanced->setCurrentIndex(checked ? 1 : 0); - if (!mBobSettings.keys.empty()) { + if (!mBobSettings.address.privateKey.empty()) { if (checked) { ui.pbBobGenAddr->show(); } else { @@ -1578,9 +1584,9 @@ void ServerPage::loadCommon() whileBlocking(ui.hiddenpage_proxyPort_i2p_2)->setValue(proxyport); // this one is for bob tab // don't use whileBlocking here - ui.cb_enableBob->setChecked(mBobSettings.enableBob); + ui.cb_enableBob->setChecked(mBobSettings.enable); - if (!mBobSettings.keys.empty()) { + if (!mBobSettings.address.privateKey.empty()) { ui.lBobB32Addr->show(); ui.leBobB32Addr->show(); } @@ -1623,13 +1629,13 @@ void ServerPage::saveBob() void ServerPage::updateStatusBob() { - QString addr = QString::fromStdString(mBobSettings.addr); + QString addr = QString::fromStdString(mBobSettings.address.base32); if (ui.leBobB32Addr->text() != addr) { ui.leBobB32Addr->setText(addr); ui.hiddenpage_serviceAddress->setText(addr); - ui.pteBobServerKey->setPlainText(QString::fromStdString(mBobSettings.keys)); + ui.pteBobServerKey->setPlainText(QString::fromStdString(mBobSettings.address.privateKey)); - if (!mBobSettings.keys.empty()) { + if (!mBobSettings.address.privateKey.empty()) { // we have an addr -> show fields ui.lBobB32Addr->show(); ui.leBobB32Addr->show(); @@ -1655,7 +1661,7 @@ void ServerPage::updateStatusBob() QString bobSimpleText = QString(); bobSimpleText.append(tr("RetroShare uses BOB to set up a %1 tunnel at %2:%3 (named %4)\n\n" "When changing options (e.g. port) use the buttons at the bottom to restart BOB.\n\n"). - arg(mBobSettings.keys.empty() ? tr("client") : tr("server"), + arg(mBobSettings.address.privateKey.empty() ? tr("client") : tr("server"), ui.hiddenpage_proxyAddress_i2p_2->text(), ui.hiddenpage_proxyPort_i2p_2->text(), bs.tunnelName.empty() ? tr("unknown") : @@ -1777,15 +1783,15 @@ void ServerPage::updateStatusBob() void ServerPage::setUpBobElements() { - ui.gbBob->setEnabled(mBobSettings.enableBob); - if (mBobSettings.enableBob) { + ui.gbBob->setEnabled(mBobSettings.enable); + if (mBobSettings.enable) { ui.hiddenpage_proxyAddress_i2p->setEnabled(false); ui.hiddenpage_proxyAddress_i2p->setToolTip("Use I2P/BOB settings to change this value"); ui.hiddenpage_proxyPort_i2p->setEnabled(false); ui.hiddenpage_proxyPort_i2p->setToolTip("Use I2P/BOB settings to change this value"); - ui.leBobB32Addr->setText(QString::fromStdString(mBobSettings.addr)); - ui.pteBobServerKey->setPlainText(QString::fromStdString(mBobSettings.keys)); + ui.leBobB32Addr->setText(QString::fromStdString(mBobSettings.address.base32)); + ui.pteBobServerKey->setPlainText(QString::fromStdString(mBobSettings.address.privateKey)); // cast to int to avoid problems int li, lo, qi, qo, vi, vo; diff --git a/retroshare-gui/src/gui/statistics/GxsIdStatistics.cpp b/retroshare-gui/src/gui/statistics/GxsIdStatistics.cpp new file mode 100644 index 000000000..a09053deb --- /dev/null +++ b/retroshare-gui/src/gui/statistics/GxsIdStatistics.cpp @@ -0,0 +1,588 @@ +/******************************************************************************* + * gui/statistics/GlobalRouterStatistics.cpp * + * * + * Copyright (c) 2011 Retroshare Team * + * * + * 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 + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "GxsIdStatistics.h" + +#include "util/DateTime.h" +#include "util/QtVersion.h" +#include "util/misc.h" +#include "util/qtthreadsutils.h" + +static QColor colorScale(float f) +{ + if(f == 0) + return QColor::fromHsv(0,0,192) ; + else + return QColor::fromHsv((int)((1.0-f)*280),200,255) ; +} + +GxsIdStatistics::GxsIdStatistics(QWidget *parent) + : RsAutoUpdatePage(4000,parent) +{ + setupUi(this) ; + + _stats_F->setWidget(_tst_CW = new GxsIdStatisticsWidget); + m_bProcessSettings = false; + + // load settings + processSettings(true); +} + +GxsIdStatistics::~GxsIdStatistics() +{ + // save settings + processSettings(false); +} + +void GxsIdStatistics::processSettings(bool bLoad) +{ + m_bProcessSettings = true; + + Settings->beginGroup(QString("GlobalRouterStatistics")); + + 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 GxsIdStatistics::updateDisplay() +{ + _tst_CW->updateContent() ; + + static rstime_t last_data_update_time = 0; + rstime_t now = time(NULL); + + if(now > last_data_update_time + 60) + { + last_data_update_time = now; + _tst_CW->updateData(); + } +} + +static QString getServiceName(uint32_t s) +{ + switch(s) + { + default: + case 0x0011 /* GOSSIP_DISCOVERY */ : return QObject::tr("Discovery"); + case 0x0012 /* CHAT */ : return QObject::tr("Chat"); + case 0x0013 /* MSG */ : return QObject::tr("Messages"); + case 0x0014 /* TURTLE */ : return QObject::tr("Turtle"); + case 0x0016 /* HEARTBEAT */ : return QObject::tr("Heartbeat"); + case 0x0017 /* FILE_TRANSFER */ : return QObject::tr("File transfer"); + case 0x0018 /* GROUTER */ : return QObject::tr("Global router"); + case 0x0019 /* FILE_DATABASE */ : return QObject::tr("File database"); + case 0x0020 /* SERVICEINFO */ : return QObject::tr("Service info"); + case 0x0021 /* BANDWIDTH_CONTROL */ : return QObject::tr("Bandwidth control"); + case 0x0022 /* MAIL */ : return QObject::tr("Mail"); + case 0x0023 /* DIRECT_MAIL */ : return QObject::tr("Mail"); + case 0x0024 /* DISTANT_MAIL */ : return QObject::tr("Distant mail"); + case 0x0026 /* SERVICE_CONTROL */ : return QObject::tr("Service control"); + case 0x0027 /* DISTANT_CHAT */ : return QObject::tr("Distant chat"); + case 0x0028 /* GXS_TUNNEL */ : return QObject::tr("GXS Tunnel"); + case 0x0101 /* BANLIST */ : return QObject::tr("Ban list"); + case 0x0102 /* STATUS */ : return QObject::tr("Status"); + case 0x0200 /* NXS */ : return QObject::tr("NXS"); + case 0x0211 /* GXSID */ : return QObject::tr("Identities"); + case 0x0212 /* PHOTO */ : return QObject::tr("GXS Photo"); + case 0x0213 /* WIKI */ : return QObject::tr("GXS Wiki"); + case 0x0214 /* WIRE */ : return QObject::tr("GXS TheWire"); + case 0x0215 /* FORUMS */ : return QObject::tr("Forums"); + case 0x0216 /* POSTED */ : return QObject::tr("Boards"); + case 0x0217 /* CHANNELS */ : return QObject::tr("Channels"); + case 0x0218 /* GXSCIRCLE */ : return QObject::tr("Circles"); + /// entiti not gxs, but used with :dentities. + case 0x0219 /* REPUTATION */ : return QObject::tr("Reputation"); + case 0x0220 /* GXS_RECOGN */ : return QObject::tr("Recogn"); + case 0x0230 /* GXS_TRANS */ : return QObject::tr("GXS Transport"); + case 0x0240 /* JSONAPI */ : return QObject::tr("JSon API"); + + } +} + +static QString getUsageStatisticsName(RsIdentityUsage::UsageCode code) +{ + switch(code) + { + default: + case RsIdentityUsage::UNKNOWN_USAGE : return QObject::tr("Unknown"); + case RsIdentityUsage::GROUP_ADMIN_SIGNATURE_CREATION : return QObject::tr("Group admin signature creation"); + case RsIdentityUsage::GROUP_ADMIN_SIGNATURE_VALIDATION : return QObject::tr("Group admin signature validation"); + case RsIdentityUsage::GROUP_AUTHOR_SIGNATURE_CREATION : return QObject::tr("Group author signature creation"); + case RsIdentityUsage::GROUP_AUTHOR_SIGNATURE_VALIDATION : return QObject::tr("Group author signature validation"); + case RsIdentityUsage::MESSAGE_AUTHOR_SIGNATURE_CREATION : return QObject::tr("Message author signature creation"); + case RsIdentityUsage::MESSAGE_AUTHOR_SIGNATURE_VALIDATION : return QObject::tr("Message author signature validation"); + case RsIdentityUsage::GROUP_AUTHOR_KEEP_ALIVE : return QObject::tr("Routine group author signature check."); + case RsIdentityUsage::MESSAGE_AUTHOR_KEEP_ALIVE : return QObject::tr("Routine message author signature check"); + case RsIdentityUsage::CHAT_LOBBY_MSG_VALIDATION : return QObject::tr("Chat room signature validation"); + case RsIdentityUsage::GLOBAL_ROUTER_SIGNATURE_CHECK : return QObject::tr("Global router message validation"); + case RsIdentityUsage::GLOBAL_ROUTER_SIGNATURE_CREATION : return QObject::tr("Global router message creation"); + case RsIdentityUsage::GXS_TUNNEL_DH_SIGNATURE_CHECK : return QObject::tr("DH Key exchange validation for GXS tunnel"); + case RsIdentityUsage::GXS_TUNNEL_DH_SIGNATURE_CREATION : return QObject::tr("DH Key exchange creation for GXS tunnel"); + case RsIdentityUsage::IDENTITY_NEW_FROM_GXS_SYNC : return QObject::tr("New identity from GXS sync"); + case RsIdentityUsage::IDENTITY_NEW_FROM_DISCOVERY : return QObject::tr("New friend identity from discovery"); + case RsIdentityUsage::IDENTITY_NEW_FROM_EXPLICIT_REQUEST : return QObject::tr("New identity requested from friend node"); + case RsIdentityUsage::IDENTITY_GENERIC_SIGNATURE_CHECK : return QObject::tr("Generic signature validation"); + case RsIdentityUsage::IDENTITY_GENERIC_SIGNATURE_CREATION : return QObject::tr("Generic signature creation"); + case RsIdentityUsage::IDENTITY_GENERIC_ENCRYPTION : return QObject::tr("Generic data decryption"); + case RsIdentityUsage::IDENTITY_GENERIC_DECRYPTION : return QObject::tr("Generic data encryption"); + case RsIdentityUsage::CIRCLE_MEMBERSHIP_CHECK : return QObject::tr("Circle membership checking"); + } +} + +void GxsIdStatisticsWidget::updateData() +{ + // get the info, stats, histograms and pass them + + RsThread::async([this]() + { + // 1 - get group data + +#ifdef DEBUG_FORUMS + std::cerr << "Retrieving post data for post " << mThreadId << std::endl; +#endif + + auto pids = new std::list() ; + rsIdentity->getIdentitiesSummaries(*pids) ; + + RsQThreadUtils::postToObject( [pids,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 */ + + const auto& ids(*pids); + + time_t now = time(NULL); + mPublishDateHist = Histogram(now - mNbWeeks*7*86400,now,mNbWeeks); + mLastUsedHist = Histogram(now - 3600*mNbHours,now,mNbHours); + mTotalIdentities = 0; + mUsageMap.clear(); + mPerServiceUsageMap.clear(); + + for(auto& meta:ids) + { + RsIdentityDetails det; + + if(!rsIdentity->getIdDetails(RsGxsId(meta.mGroupId),det)) + continue; + + mPublishDateHist.insert((double)meta.mPublishTs); + mLastUsedHist.insert((double)det.mLastUsageTS); + + for(auto it:det.mUseCases) + { + auto it2 = mUsageMap.find(it.first.mUsageCode); + if(it2 == mUsageMap.end()) + mUsageMap[it.first.mUsageCode] = 0 ; + + ++mUsageMap[it.first.mUsageCode]; + + uint32_t s = static_cast(it.first.mServiceId); + auto it3 = mPerServiceUsageMap.find(s); + if(it3 == mPerServiceUsageMap.end()) + mPerServiceUsageMap[s] = 0; + + ++mPerServiceUsageMap[s]; + } + + ++mTotalIdentities; + } + + delete pids; + + }, this ); + }); +} + +void GxsIdStatisticsWidget::updateContent() +{ + // Now draw the info int the widget's pixmap + + float size = QFontMetricsF(font()).height() ; + float fact = size/14.0 ; + + QPixmap tmppixmap(mMaxWidth, mMaxHeight); + tmppixmap.fill(Qt::transparent); + setFixedHeight(mMaxHeight); + + QPainter painter(&tmppixmap); + painter.initFrom(this); + painter.setPen(QColor::fromRgb(0,0,0)) ; + + QFont times_f(font());//"Times") ; + QFont monospace_f("Monospace") ; + monospace_f.setStyleHint(QFont::TypeWriter) ; + monospace_f.setPointSize(font().pointSize()) ; + + QFontMetricsF fm_monospace(monospace_f) ; + QFontMetricsF fm_times(times_f) ; + + int cellx = fm_monospace.width(QString(" ")) ; + int celly = fm_monospace.height() ; + + // Display general statistics + + int ox=5*fact,oy=15*fact ; + + painter.setFont(times_f) ; + painter.drawText(ox,oy,tr("Total identities: ")+QString::number(mTotalIdentities)) ; oy += celly*2 ; + + uint32_t total_per_type = 0; + for(auto it:mUsageMap) + total_per_type += it.second; + + painter.setFont(times_f) ; + painter.drawText(ox,oy,tr("Usage types") + "(" + QString::number(total_per_type) + " hits): ") ; oy += 2*celly; + + for(auto it:mUsageMap) + { + painter.drawText(ox+2*cellx,oy, getUsageStatisticsName(it.first) + ": " + QString::number(it.second)) ; + oy += celly ; + } + oy += celly ; + + // Display per-service statistics + + uint32_t total_per_service = 0; + for(auto it:mPerServiceUsageMap) + total_per_service += it.second; + + painter.setFont(times_f) ; + painter.drawText(ox,oy,tr("Usage per service") + "(" + QString::number(total_per_service) + " hits): ") ; oy += 2*celly; + + for(auto it:mPerServiceUsageMap) + { + painter.drawText(ox+2*cellx,oy, getServiceName(it.first) + ": " + QString::number(it.second)) ; + oy += celly ; + } + oy += celly ; + + // Draw the creation time histogram + + painter.setFont(times_f) ; + painter.drawText(ox,oy,tr("Identity age (in weeks):")) ; oy += celly ; + + uint32_t hist_height = 10; + oy += hist_height*celly; + + painter.drawLine(QPoint(ox+4*cellx,oy),QPoint(ox+4*cellx+cellx*mNbWeeks*2,oy)); + painter.drawLine(QPoint(ox+4*cellx,oy),QPoint(ox+4*cellx,oy-celly*hist_height)); + + uint32_t max_entry=0; + for(int i=0;i each [] shows a square (one per friend node) that is the routing probabilities for all connected friends + // computed using the "computeRoutingProbabilitites()" method. + // + // Own key ids + // key service id description + // + // Data items + // Msg id Local origin Destination Time Status + // + QPixmap tmppixmap(maxWidth, maxHeight); + tmppixmap.fill(Qt::transparent); + setFixedHeight(maxHeight); + + QPainter painter(&tmppixmap); + painter.initFrom(this); + painter.setPen(QColor::fromRgb(0,0,0)) ; + + QFont times_f(font());//"Times") ; + QFont monospace_f("Monospace") ; + monospace_f.setStyleHint(QFont::TypeWriter) ; + monospace_f.setPointSize(font().pointSize()) ; + + QFontMetricsF fm_monospace(monospace_f) ; + QFontMetricsF fm_times(times_f) ; + + static const int cellx = fm_monospace.width(QString(" ")) ; + static const int celly = fm_monospace.height() ; + + maxHeight = 500*fact ; + + // std::cerr << "Drawing into pixmap of size " << maxWidth << "x" << maxHeight << std::endl; + // draw... + int ox=5*fact,oy=5*fact ; + + + painter.setFont(times_f) ; + painter.drawText(ox,oy+celly,tr("Managed keys")+":" + QString::number(matrix_info.published_keys.size())) ; oy += celly*2 ; + + painter.setFont(monospace_f) ; + for(std::map::const_iterator it(matrix_info.published_keys.begin());it!=matrix_info.published_keys.end();++it) + { + QString packet_string ; + packet_string += QString::fromStdString(it->second.authentication_key.toStdString()) ; + packet_string += tr(" : Service ID =")+" "+QString::number(it->second.service_id,16) ; + packet_string += " \""+QString::fromUtf8(it->second.description_string.c_str()) + "\"" ; + + painter.drawText(ox+2*cellx,oy+celly,packet_string ) ; oy += celly ; + } + oy += celly ; + + + std::map > tos ; + + // Now draw the matrix + + QString prob_string ; + painter.setFont(times_f) ; + QString Q = tr("Routing matrix (") ; + + painter.drawText(ox+0*cellx,oy+fm_times.height(),Q) ; + + // draw scale + + for(int i=0;i<100*fact;++i) + { + painter.setPen(colorScale(i/100.0/fact)) ; + painter.drawLine(ox+fm_times.width(Q)+i,oy+fm_times.height()*0.5,ox+fm_times.width(Q)+i,oy+fm_times.height()) ; + } + painter.setPen(QColor::fromRgb(0,0,0)) ; + + painter.drawText(ox+fm_times.width(Q) + 102*fact,oy+celly,")") ; + + oy += celly ; + oy += celly ; + + //static const int MaxKeySize = 20*fact ; + painter.setFont(monospace_f) ; + + int n=0; + QString ids; + std::vector current_probs ; + int current_oy = 0 ; + + mMinWheelZoneX = ox+2*cellx ; + mMinWheelZoneY = oy ; + + RsGxsId current_id ; + float current_width=0 ; + + for(std::map >::const_iterator it(matrix_info.per_friend_probabilities.begin());it!=matrix_info.per_friend_probabilities.end();++it,++n) + if(n >= mCurrentN-PARTIAL_VIEW_SIZE/2 && n <= mCurrentN+PARTIAL_VIEW_SIZE/2) + { + ids = QString::fromStdString(it->first.toStdString())+" : " ; + painter.drawText(ox+2*cellx,oy+celly,ids) ; + + for(uint32_t i=0;isecond[i])) ; + + if(n == mCurrentN) + { + current_probs = it->second ; + current_oy = oy ; + current_id = it->first ; + current_width = ox+matrix_info.friend_ids.size()*cellx+fm_monospace.width(ids); + } + + oy += celly ; + } + mMaxWheelZoneX = ox+matrix_info.friend_ids.size()*cellx + fm_monospace.width(ids); + + RsIdentityDetails iddetails ; + if(rsIdentity->getIdDetails(current_id,iddetails)) + painter.drawText(current_width+cellx, current_oy+celly, QString::fromUtf8(iddetails.mNickname.c_str())) ; + else + painter.drawText(current_width+cellx, current_oy+celly, tr("[Unknown identity]")) ; + + mMaxWheelZoneY = oy+celly ; + + painter.setPen(QColor::fromRgb(0,0,0)) ; + + painter.setPen(QColor::fromRgb(127,127,127)); + painter.drawRect(ox+2*cellx,current_oy+0.15*celly,fm_monospace.width(ids)+cellx*matrix_info.friend_ids.size()- 2*cellx,celly) ; + + float total_length = (matrix_info.friend_ids.size()+2)*cellx ; + + if(!current_probs.empty()) + for(uint32_t i=0;igetPeerDetails(matrix_info.friend_ids[i], peer_ssl_details); + + painter.drawLine(x1,y1,x1,y2); + painter.drawLine(x1,y2,x1 + total_length - i*cellx,y2) ; + painter.drawText(cellx+ x1 + total_length - i*cellx,y2+(0.35)*celly, QString::fromUtf8(peer_ssl_details.name.c_str()) + " - " + QString::fromUtf8(peer_ssl_details.location.c_str()) + " ("+QString::number(current_probs[i])+")"); + } + oy += celly * (2+matrix_info.friend_ids.size()); + + oy += celly ; + oy += celly ; + + // update the pixmap + // + pixmap = tmppixmap; + maxHeight = oy ; +} +#endif + +void GxsIdStatisticsWidget::paintEvent(QPaintEvent */*event*/) +{ + QStylePainter(this).drawPixmap(0, 0, pixmap); +} + +void GxsIdStatisticsWidget::resizeEvent(QResizeEvent *event) +{ + QRect rect = geometry(); + + mMaxWidth = rect.width(); + mMaxHeight = rect.height() ; + + QWidget::resizeEvent(event); + updateContent(); +} diff --git a/retroshare-gui/src/gui/statistics/GxsIdStatistics.h b/retroshare-gui/src/gui/statistics/GxsIdStatistics.h new file mode 100644 index 000000000..9d101aa88 --- /dev/null +++ b/retroshare-gui/src/gui/statistics/GxsIdStatistics.h @@ -0,0 +1,94 @@ +/******************************************************************************* + * gui/statistics/GxsIdStatistics.h * + * * + * Copyright (c) 2011 Retroshare Team * + * * + * 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 . * + * * + *******************************************************************************/ + +#pragma once + +#include +#include +#include + +#include "RsAutoUpdatePage.h" +#include "Histogram.h" +#include "ui_GxsIdStatistics.h" + +// In this statistics panel we show: +// +// - histograms +// * age histogram of GXS ids (creation time) +// * last usage histogram +// * number of IDs used in each service as reported by UsageStatistics +// +// (note: we could use that histogram class for packets statistics, so we made a separate class) +// +// And general statistics: +// +// - total number of IDs +// - total number of signed IDs +// - total number of own IDs +// +class GxsIdStatisticsWidget ; + +class GxsIdStatistics: public RsAutoUpdatePage, public Ui::GxsIdStatistics +{ + Q_OBJECT + + public: + GxsIdStatistics(QWidget *parent = NULL) ; + ~GxsIdStatistics(); + + void updateContent() ; + + private: + + void processSettings(bool bLoad); + bool m_bProcessSettings; + + virtual void updateDisplay() ; + + GxsIdStatisticsWidget *_tst_CW ; +} ; + +class GxsIdStatisticsWidget: public QWidget +{ + Q_OBJECT + + public: + GxsIdStatisticsWidget(QWidget *parent = NULL) ; + + virtual void paintEvent(QPaintEvent *event) ; + virtual void resizeEvent(QResizeEvent *event); + + void updateContent() ; + void updateData(); + private: + static QString speedString(float f) ; + + QPixmap pixmap ; + int mMaxWidth,mMaxHeight ; + uint32_t mNbWeeks; + uint32_t mNbHours; + uint32_t mTotalIdentities; + Histogram mPublishDateHist ; + Histogram mLastUsedHist ; + + std::map mUsageMap; + std::map mPerServiceUsageMap; +}; + diff --git a/retroshare-gui/src/gui/statistics/GxsIdStatistics.ui b/retroshare-gui/src/gui/statistics/GxsIdStatistics.ui new file mode 100644 index 000000000..0c42ab780 --- /dev/null +++ b/retroshare-gui/src/gui/statistics/GxsIdStatistics.ui @@ -0,0 +1,49 @@ + + + GxsIdStatistics + + + + 0 + 0 + 1468 + 659 + + + + Router Statistics + + + + + + Qt::Vertical + + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + true + + + + + 0 + 0 + 1450 + 641 + + + + + + + + + + + diff --git a/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp b/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp index 10663a735..c258cb4bc 100644 --- a/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp +++ b/retroshare-gui/src/gui/statistics/GxsTransportStatistics.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -49,6 +50,9 @@ #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 @@ -61,11 +65,13 @@ #define COL_PENDING_DESTINATION_ID 8 #define COL_GROUP_GRP_ID 0 -#define COL_GROUP_NUM_MSGS 1 -#define COL_GROUP_SIZE_MSGS 2 -#define COL_GROUP_SUBSCRIBED 3 -#define COL_GROUP_POPULARITY 4 -#define COL_GROUP_UNIQUE_ID 5 +#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 ; @@ -94,8 +100,10 @@ GxsTransportStatistics::GxsTransportStatistics(QWidget *parent) QHeaderView_setSectionResizeMode(groupTreeWidget->header(), QHeaderView::ResizeToContents); connect(treeWidget, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(CustomPopupMenu(QPoint))); - - treeWidget->setColumnHidden(COL_PENDING_DESTINATION_ID,true); + 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); @@ -139,7 +147,20 @@ void GxsTransportStatistics::CustomPopupMenu( QPoint ) QTreeWidgetItem *item = treeWidget->currentItem(); if (item) { - contextMnu.addAction(QIcon(":/images/info16.png"), tr("Details"), this, SLOT(personDetails())); + 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())); } @@ -275,9 +296,10 @@ void GxsTransportStatistics::updateContent() 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()):"" ; + 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_NUM_MSGS, Qt::DisplayRole, QString::number(stat.mNumMsgs) + msg_time_string) ; + 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")) ; @@ -308,6 +330,8 @@ void GxsTransportStatistics::updateContent() 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); @@ -315,7 +339,9 @@ void GxsTransportStatistics::updateContent() 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_NUM_MSGS,Qt::DisplayRole, QDateTime::fromTime_t(meta.mPublishTs).toString()); + 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); } } } @@ -333,6 +359,25 @@ void GxsTransportStatistics::personDetails() 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(MainWindow::getPage(MainWindow::People)); + + if (!idDialog) + return ; + + MainWindow::showWindow(MainWindow::People); + idDialog->navigate(RsGxsId(id)); +} + #ifdef TO_REMOVE void GxsTransportStatistics::loadGroupMeta(const std::vector& groupInfo) { @@ -419,24 +464,25 @@ void GxsTransportStatistics::loadGroups() #ifdef DEBUG_FORUMS std::cerr << "Retrieving post data for post " << mThreadId << std::endl; #endif - std::map stats; + auto stats = std::make_unique< + std::map >(); - if(!rsGxsTrans->getGroupStatistics(stats)) - { - RsErr() << "Cannot retrieve group statistics in GxsTransportStatistics" << std::endl; - return; - } + if(!rsGxsTrans->getGroupStatistics(*stats)) + { + RS_ERR("Cannot retrieve group statistics in GxsTransportStatistics"); + return; + } - RsQThreadUtils::postToObject( [stats,this]() + 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 */ - mGroupStats = stats; - - updateContent(); - + // TODO: consider making mGroupStats an unique_ptr to avoid copying + mGroupStats = *stats; + updateContent(); mStateHelper->setLoading(GXSTRANS_GROUP_META, false); }, this ); diff --git a/retroshare-gui/src/gui/statistics/GxsTransportStatistics.h b/retroshare-gui/src/gui/statistics/GxsTransportStatistics.h index 01a31e776..d23fdaa0f 100644 --- a/retroshare-gui/src/gui/statistics/GxsTransportStatistics.h +++ b/retroshare-gui/src/gui/statistics/GxsTransportStatistics.h @@ -51,7 +51,10 @@ public: private slots: /** Create the context popup menu and it's submenus */ void CustomPopupMenu( QPoint point ); + void CustomPopupMenuGroups( QPoint point ) ; + void personDetails(); + void showAuthorInPeople(); private: void updateDisplay(bool complete) ; diff --git a/retroshare-gui/src/gui/statistics/GxsTransportStatistics.ui b/retroshare-gui/src/gui/statistics/GxsTransportStatistics.ui index e2c7449c0..8ce780e81 100644 --- a/retroshare-gui/src/gui/statistics/GxsTransportStatistics.ui +++ b/retroshare-gui/src/gui/statistics/GxsTransportStatistics.ui @@ -41,7 +41,7 @@ true - false + true @@ -108,6 +108,12 @@ Qt::CustomContextMenu + + true + + + true + Group ID / Author @@ -115,7 +121,12 @@ - Number of messages / Publish TS + Publish TS + + + + + Number of messages diff --git a/retroshare-gui/src/gui/statistics/Histogram.cpp b/retroshare-gui/src/gui/statistics/Histogram.cpp new file mode 100644 index 000000000..6cd1c4906 --- /dev/null +++ b/retroshare-gui/src/gui/statistics/Histogram.cpp @@ -0,0 +1,55 @@ +/******************************************************************************* + * gui/statistics/Histogram.cpp * + * * + * Copyright (c) 2020 Retroshare Team * + * * + * 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 "Histogram.h" + +Histogram::Histogram() + : mStart(0),mEnd(1.0),mBins(10,0) +{} + +Histogram::Histogram(double start, double end, int bins) + : mStart(start),mEnd(end),mBins(bins,0) +{ + if(mEnd <= mStart) + std::cerr << "Null histogram created! Please check your parameters" << std::endl; +} + +void Histogram::draw(QPainter *painter) const +{ +} + +void Histogram::insert(double val) +{ + long int bin = (uint32_t)floor((val - mStart)/(mEnd - mStart) * mBins.size()); + + if(bin >= 0 && bin < mBins.size()) + ++mBins[bin]; +} + +std::ostream& operator<<(std::ostream& o,const Histogram& h) +{ + o << "Histogram: [" << h.mStart << "..." << h.mEnd << "] " << h.mBins.size() << " bins." << std::endl; + for(uint32_t i=0;i * + * * + * 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 + +class QPainter; + +class Histogram +{ + public: + Histogram(); + Histogram(double start, double end, int bins); + + void draw(QPainter *painter) const ; + void insert(double val); + + const std::vector& entries() const { return mBins; } + + private: + double mStart; + double mEnd; + + std::vector mBins; + + friend std::ostream& operator<<(std::ostream& o,const Histogram& h); +}; + diff --git a/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp b/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp index bbda5101a..50c947822 100644 --- a/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp +++ b/retroshare-gui/src/gui/statistics/StatisticsWindow.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -52,6 +53,7 @@ #define IMAGE_DHT ":/icons/DHT128.png" #define IMAGE_TURTLE ":/icons/turtle128.png" +#define IMAGE_IDENTITIES ":/icons/avatar_128.png" #define IMAGE_BWGRAPH ":/icons/bandwidth128.png" #define IMAGE_GLOBALROUTER ":/icons/GRouter128.png" #define IMAGE_GXSTRANSPORT ":/icons/transport128.png" @@ -143,6 +145,9 @@ void StatisticsWindow::initStackedPage() ui->stackPages->add(trsdlg = new TurtleRouterStatistics(ui->stackPages), action = createPageAction(QIcon(IMAGE_TURTLE), tr("Turtle Router"), grp)); + ui->stackPages->add(gxsiddlg = new GxsIdStatistics(ui->stackPages), + action = createPageAction(QIcon(IMAGE_IDENTITIES), tr("Identities"), grp)); + ui->stackPages->add(grsdlg = new GlobalRouterStatistics(ui->stackPages), action = createPageAction(QIcon(IMAGE_GLOBALROUTER), tr("Global Router"), grp)); @@ -150,7 +155,7 @@ void StatisticsWindow::initStackedPage() action = createPageAction(QIcon(IMAGE_GXSTRANSPORT), tr("Gxs Transport"), grp)); ui->stackPages->add(rttdlg = new RttStatistics(ui->stackPages), - action = createPageAction(QIcon(IMAGE_RTT), tr("RTT Statistics"), grp)); + action = createPageAction(QIcon(IMAGE_RTT), tr("RTT Statistics"), grp)); bool showdht = true; RsPeerDetails detail; diff --git a/retroshare-gui/src/gui/statistics/StatisticsWindow.h b/retroshare-gui/src/gui/statistics/StatisticsWindow.h index db2d75939..2450812d7 100644 --- a/retroshare-gui/src/gui/statistics/StatisticsWindow.h +++ b/retroshare-gui/src/gui/statistics/StatisticsWindow.h @@ -38,6 +38,7 @@ class TurtleRouterStatistics; class GlobalRouterStatistics; class GxsTransportStatistics; class RttStatistics; +class GxsIdStatistics; class StatisticsWindow : public QMainWindow { Q_OBJECT @@ -57,6 +58,7 @@ public: BwCtrlWindow *bwdlg; TurtleRouterStatistics *trsdlg; RttStatistics *rttdlg; + GxsIdStatistics *gxsiddlg; public slots: diff --git a/retroshare-gui/src/retroshare-gui.pro b/retroshare-gui/src/retroshare-gui.pro index 42f737005..d3ccaeb44 100644 --- a/retroshare-gui/src/retroshare-gui.pro +++ b/retroshare-gui/src/retroshare-gui.pro @@ -344,12 +344,16 @@ openbsd-* { LIBS *= -rdynamic } +################################### COMMON stuff ################################## + wikipoos { PRE_TARGETDEPS *= $$OUT_PWD/../../supportlibs/pegmarkdown/lib/libpegmarkdown.a LIBS *= $$OUT_PWD/../../supportlibs/pegmarkdown/lib/libpegmarkdown.a LIBS *= -lglib-2.0 } +################################### HEADERS & SOURCES ############################# + # Tor controller HEADERS += TorControl/AddOnionCommand.h \ @@ -433,7 +437,9 @@ HEADERS += rshare.h \ gui/FileTransfer/BannedFilesDialog.h \ gui/statistics/TurtleRouterDialog.h \ gui/statistics/TurtleRouterStatistics.h \ + gui/statistics/GxsIdStatistics.h \ gui/statistics/dhtgraph.h \ + gui/statistics/Histogram.h \ gui/statistics/BandwidthGraphWindow.h \ gui/statistics/turtlegraph.h \ gui/statistics/BandwidthStatsWidget.h \ @@ -751,6 +757,7 @@ FORMS += gui/StartDialog.ui \ gui/statistics/DhtWindow.ui \ gui/statistics/TurtleRouterDialog.ui \ gui/statistics/TurtleRouterStatistics.ui \ + gui/statistics/GxsIdStatistics.ui \ gui/statistics/GlobalRouterStatistics.ui \ gui/statistics/GxsTransportStatistics.ui \ gui/statistics/StatisticsWindow.ui \ @@ -991,8 +998,10 @@ SOURCES += main.cpp \ gui/statistics/BandwidthGraphWindow.cpp \ gui/statistics/BandwidthStatsWidget.cpp \ gui/statistics/DhtWindow.cpp \ + gui/statistics/Histogram.cpp \ gui/statistics/TurtleRouterDialog.cpp \ gui/statistics/TurtleRouterStatistics.cpp \ + gui/statistics/GxsIdStatistics.cpp \ gui/statistics/GlobalRouterStatistics.cpp \ gui/statistics/GxsTransportStatistics.cpp \ gui/statistics/StatisticsWindow.cpp \ @@ -1353,13 +1362,19 @@ gxschannels { gui/gxschannels/GxsChannelGroupDialog.h \ gui/gxschannels/CreateGxsChannelMsg.h \ gui/gxschannels/GxsChannelPostsWidget.h \ + gui/gxschannels/GxsChannelPostsWidgetWithModel.h \ + gui/gxschannels/GxsChannelPostsModel.h \ + gui/gxschannels/GxsChannelPostFilesModel.h \ gui/gxschannels/GxsChannelFilesWidget.h \ + gui/gxschannels/GxsChannelPostThumbnail.h \ gui/gxschannels/GxsChannelFilesStatusWidget.h \ gui/feeds/GxsChannelGroupItem.h \ gui/feeds/GxsChannelPostItem.h \ gui/gxschannels/GxsChannelUserNotify.h - FORMS += gui/gxschannels/GxsChannelPostsWidget.ui \ + FORMS += \ + gui/gxschannels/GxsChannelPostsWidgetWithModel.ui \ + gui/gxschannels/GxsChannelPostsWidget.ui \ gui/gxschannels/GxsChannelFilesWidget.ui \ gui/gxschannels/GxsChannelFilesStatusWidget.ui \ gui/gxschannels/CreateGxsChannelMsg.ui \ @@ -1368,6 +1383,9 @@ gxschannels { SOURCES += gui/gxschannels/GxsChannelDialog.cpp \ gui/gxschannels/GxsChannelPostsWidget.cpp \ + gui/gxschannels/GxsChannelPostsWidgetWithModel.cpp \ + gui/gxschannels/GxsChannelPostsModel.cpp \ + gui/gxschannels/GxsChannelPostFilesModel.cpp \ gui/gxschannels/GxsChannelFilesWidget.cpp \ gui/gxschannels/GxsChannelFilesStatusWidget.cpp \ gui/gxschannels/GxsChannelGroupDialog.cpp \ diff --git a/retroshare-gui/src/util/qtthreadsutils.h b/retroshare-gui/src/util/qtthreadsutils.h index 155b71b7e..1c377b35d 100644 --- a/retroshare-gui/src/util/qtthreadsutils.h +++ b/retroshare-gui/src/util/qtthreadsutils.h @@ -1,7 +1,7 @@ /******************************************************************************* * util/qthreadutils.h * * * - * Copyright (C) 2018 Gioacchino Mazzurco * + * Copyright (C) 2018-2020 Gioacchino Mazzurco * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU Affero General Public License as * @@ -27,7 +27,9 @@ #include #include + #include +#include namespace RsQThreadUtils { @@ -44,7 +46,7 @@ void postToObject(F &&fun, QObject *obj = qApp) QObject src; auto type = obj->metaObject(); QObject::connect( &src, &QObject::destroyed, obj, - [fun, type, obj] + [fun = std::move(fun), type, obj] { // ensure that the object is not being destructed if (obj->metaObject()->inherits(type)) fun(); diff --git a/retroshare-service/src/retroshare-service.pro b/retroshare-service/src/retroshare-service.pro index 75cb0b581..ae3b38049 100644 --- a/retroshare-service/src/retroshare-service.pro +++ b/retroshare-service/src/retroshare-service.pro @@ -29,6 +29,8 @@ QT -= gui SOURCES += retroshare-service.cc +################################# Linux ########################################## + android-* { QT += androidextras @@ -56,11 +58,14 @@ appimage { INSTALLS += desktop_files } + unix { target.path = "$${RS_BIN_DIR}" INSTALLS += target } +################################# MacOSX ########################################## + macx { # ENABLE THIS OPTION FOR Univeral Binary BUILD. #CONFIG += ppc x86 @@ -81,6 +86,8 @@ macx { INCLUDEPATH += . $$INC_DIR } +################################# Windows ########################################## + win32-g++ { CONFIG(debug, debug|release) { # show console output @@ -130,3 +137,6 @@ win32-g++ { QMAKE_PRE_LINK = $(CHK_DIR_EXISTS) lib || $(MKDIR) lib } } + +################################### COMMON stuff ################################## + diff --git a/retroshare.pri b/retroshare.pri index 6bccc1f5a..994ced0f2 100644 --- a/retroshare.pri +++ b/retroshare.pri @@ -140,6 +140,11 @@ rs_macos10.15:CONFIG -= rs_macos10.11 CONFIG *= no_rs_jsonapi rs_jsonapi:CONFIG -= no_rs_jsonapi +# Disable i2p BOB support for automatically setting up an i2p tunnel for RS +# "CONFIG+=no_rs_bob" +CONFIG *= rs_bob +no_rs_bob:CONFIG -= rs_bob + # To enable channel indexing append the following assignation to qmake command # line "CONFIG+=rs_deep_channel_index" CONFIG *= no_rs_deep_channel_index @@ -550,6 +555,10 @@ rs_webui { DEFINES *= RS_WEBUI } +rs_bob { + DEFINES *= RS_USE_I2P_BOB +} + rs_deep_channels_index:DEFINES *= RS_DEEP_CHANNEL_INDEX rs_deep_files_index:DEFINES *= RS_DEEP_FILES_INDEX