merged with master

This commit is contained in:
csoler 2020-06-25 16:53:18 +02:00
commit 500f572b98
No known key found for this signature in database
GPG Key ID: 7BCA522266C0804C
53 changed files with 4912 additions and 593 deletions

View File

@ -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
@ -517,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\

View File

@ -1803,6 +1803,7 @@ void p3NetMgrIMPL::updateNatSetting()
#endif
#ifdef RS_USE_DHT_STUNNER
if (mProxyStunner) {
switch(natType)
{
case RSNET_NATTYPE_RESTRICTED_CONE:
@ -1827,6 +1828,7 @@ void p3NetMgrIMPL::updateNatSetting()
mProxyStunner->setRefreshPeriod(NET_STUNNER_PERIOD_SLOW);
break;
}
}
#endif // RS_USE_DHT_STUNNER
/* This controls the Attach mode of the DHT...

View File

@ -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 */

View File

@ -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.

View File

@ -923,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<std::string> sslIds;
@ -1649,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) ;
@ -1724,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;
@ -1795,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.

View File

@ -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<uint8_t> bin = Radix64::decode(copy);
// hash
std::vector<uint8_t> 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);
}
@ -285,9 +248,7 @@ 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_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<RsItem *> &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<RsItem *> &lst)
bool p3I2pBob::loadList(std::list<RsItem *> &load)
{
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "loadList");
RS_DBG4("");
for(std::list<RsItem*>::const_iterator it = load.begin(); it!=load.end(); ++it) {
RsConfigKeyValueSet *vitem = dynamic_cast<RsConfigKeyValueSet*>(*it);
@ -808,11 +765,11 @@ bool p3I2pBob::loadList(std::list<RsItem *> &load)
RS_STACK_MUTEX(mLock);
for(std::list<RsTlvKeyValue>::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<RsItem *> &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<uint8_t> 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<char> buffer(bufferSize);
uint16_t retry = 10;
do {
memset(buffer, 0, bufferSize);
// peek at data
auto length = ::recv(mSocket, buffer, bufferSize, MSG_PEEK);
if (length <= 0) {
if (length < 0) {
// error
perror(__PRETTY_FUNCTION__);
}
retry--;
doSleep(sleepTimeRecv);
// there is only one thread that touches mSocket - no need for a lock
length = ::recv(mSocket, buffer.data(), buffer.size(), 0);
if (length < 0)
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;
}

View File

@ -30,9 +30,10 @@
#include <sys/socket.h>
#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:

View File

@ -22,6 +22,7 @@
#include "rsautoproxymonitor.h"
#include <unistd.h> /* 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<autoProxyType::autoProxyType_enum>::const_iterator it;
@ -168,7 +174,11 @@ void rsAutoProxyMonitor::task(taskTicket *ticket)
*tt = *ticket;
tt->types.clear();
tt->types.push_back(*it);
// it's async!
RsThread::async([s, tt] {
s->processTaskAsync(tt);
});
} else {
s->processTaskSync(ticket);
}
@ -187,7 +197,8 @@ void rsAutoProxyMonitor::taskAsync(std::vector<autoProxyType::autoProxyType_enum
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::taskAsync called with a synchronous task!" << std::endl;
RS_ERR("called with a synchronous task!");
print_stacktrace();
}
taskTicket *tt = getTicket();
@ -215,7 +226,8 @@ void rsAutoProxyMonitor::taskSync(std::vector<autoProxyType::autoProxyType_enum>
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;
}

View File

@ -1997,7 +1997,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;i<it->second.size();++i)
{

View File

@ -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<uint8_t> bin;
RsBase64::decode(copy, bin);
// hash
std::vector<uint8_t> 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<uint8_t>::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<uint8_t> 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<typename std::underlying_type<CertType>::type>(CertType::Null)) {
/*
* CertType.Null
* type null is followed by 0x00 0x00 <END>
* 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<typename std::underlying_type<CertType>::type>(CertType::Key)) {
// unsupported
RS_DBG("cert type ", certType, " is unsupported");
return std::string();
}
RS_DBG("cert is CertType.Key");
publicKeyLen += 7; // <type 1B> <len 2B> <keyType1 2B> <keyType2 2B> = 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<uint8_t>(dataPriv.cbegin(), dataPriv.cbegin() + publicKeyLen);
RsBase64::encode(data2.data(), data2.size(), pub, false, false);
return pub;
}
} // namespace i2p

View File

@ -0,0 +1,211 @@
#ifndef I2PCOMMON_H
#define I2PCOMMON_H
#include <algorithm>
#include <map>
#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<std::pair<uint16_t, uint16_t>, 5> cryptoKeyLengths {
/*CryptoKeyType::ElGamal*/ std::make_pair<uint16_t, uint16_t>(256, 256),
/*CryptoKeyType::P256, */ std::make_pair<uint16_t, uint16_t>( 64, 32),
/*CryptoKeyType::P384, */ std::make_pair<uint16_t, uint16_t>( 96, 48),
/*CryptoKeyType::P521, */ std::make_pair<uint16_t, uint16_t>(132, 66),
/*CryptoKeyType::X25519,*/ std::make_pair<uint16_t, uint16_t>( 32, 32),
};
static const std::array<std::pair<uint16_t, uint16_t>, 12> signingKeyLengths {
/*SigningKeyType::DSA_SHA1, */ std::make_pair<uint16_t, uint16_t>(128, 128),
/*SigningKeyType::ECDSA_SHA256_P256, */ std::make_pair<uint16_t, uint16_t>( 64, 32),
/*SigningKeyType::ECDSA_SHA384_P384, */ std::make_pair<uint16_t, uint16_t>( 96, 48),
/*SigningKeyType::ECDSA_SHA512_P521, */ std::make_pair<uint16_t, uint16_t>(132, 66),
/*SigningKeyType::RSA_SHA256_2048, */ std::make_pair<uint16_t, uint16_t>(256, 512),
/*SigningKeyType::RSA_SHA384_3072, */ std::make_pair<uint16_t, uint16_t>(384, 768),
/*SigningKeyType::RSA_SHA512_4096, */ std::make_pair<uint16_t, uint16_t>(512,1024),
/*SigningKeyType::EdDSA_SHA512_Ed25519 */ std::make_pair<uint16_t, uint16_t>( 32, 32),
/*SigningKeyType::EdDSA_SHA512_Ed25519ph */ std::make_pair<uint16_t, uint16_t>( 32, 32),
/*reserved (GOST) */ std::make_pair<uint16_t, uint16_t>( 64, 0),
/*reserved (GOST) */ std::make_pair<uint16_t, uint16_t>(128, 0),
/*SigningKeyType::RedDSA_SHA512_Ed25519 */ std::make_pair<uint16_t, uint16_t>( 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

View File

@ -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<char>(rChar);
}
for(uint32_t i=0;i<len;++i)
s += (char)( (random_u32()%94) + 33) ;
return s ;
return s;
}
/*static*/ std::string RsRandom::printable(uint32_t length)
{
std::string ret(length, 0);
random_bytes(reinterpret_cast<uint8_t*>(&ret[0]), length);
for(uint32_t i=0; i<length; ++i) ret[i] = (ret[i] % 94) + 33;
return ret;
}

View File

@ -46,9 +46,19 @@ public:
static bool seed(uint32_t s);
static std::string random_alphaNumericString(uint32_t length);
static void random_bytes(uint8_t* data, uint32_t length);
/// Return a random alphanumeric *[0-9,A-Z,a-z] string of the given lenght
static std::string alphaNumeric(uint32_t length);
/** Return a random printable string of the given lenght */
static std::string printable(uint32_t length);
/** This return a printable string not an alphanumeric one @deprecated */
RS_DEPRECATED_FOR("RsRandom::printable")
static inline std::string random_alphaNumericString(uint32_t length)
{ return printable(length); }
private:
static RsMutex rndMtx;

View File

@ -24,6 +24,7 @@
#include <QMenu>
#include <algorithm>
#include <memory>
#include <retroshare/rspeers.h>
#include <retroshare/rsidentity.h>
@ -700,30 +701,28 @@ void CreateCircleDialog::loadIdentities()
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<RsGxsId> ids;
for(auto& meta:ids_meta) ids.insert(RsGxsId(meta.mGroupId));
for(auto& meta:ids_meta)
ids.insert(RsGxsId(meta.mGroupId)) ;
std::vector<RsGxsIdGroup> id_groups;
if(!rsIdentity->getIdentitiesInfo(ids,id_groups))
auto id_groups = std::make_unique<std::vector<RsGxsIdGroup>>();
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 );
});

View File

@ -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)
{

View File

@ -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 <iostream>
#include <algorithm>
#include <memory>
/******
* #define ID_DEBUG 1
@ -506,21 +508,24 @@ void IdDialog::updateCircles()
std::cerr << "Retrieving post data for post " << mThreadId << std::endl;
#endif
std::list<RsGroupMetaData> 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<std::list<RsGroupMetaData>>();
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<RsGxsGroupId,RsGxsIdGroup> ids_set;
auto ids_set = std::make_unique<std::map<RsGxsGroupId,RsGxsIdGroup>>();
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 );
});

View File

@ -210,8 +210,8 @@ void IdEditDialog::setupExistingId(const RsGxsGroupId& keyId)
RsThread::async([this,keyId]()
{
std::vector<RsGxsIdGroup> datavector;
bool res = rsIdentity->getIdentitiesInfo(std::set<RsGxsId>({(RsGxsId)keyId}),datavector);
bool res = rsIdentity->getIdentitiesInfo(
std::set<RsGxsId>({(RsGxsId)keyId}), datavector );
RsQThreadUtils::postToObject( [this,keyId,res,datavector]()
{

View File

@ -36,6 +36,7 @@
#include <retroshare/rsstatus.h>
#include <algorithm>
#include <memory>
#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<RsGxsGroupId> ids;
for(auto& meta:ids_meta)
ids.push_back(meta.mGroupId) ;
auto ids = std::make_unique<std::vector<RsGxsGroupId>>();
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 );
});
}

View File

@ -19,10 +19,43 @@
*******************************************************************************/
#include <QPainter>
#include <QResizeEvent>
#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)

View File

@ -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;
};

View File

@ -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);

View File

@ -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<RsGxsMessageId> &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<RsGxsMessageId> mMsgVersions;

View File

@ -89,7 +89,6 @@ GxsGroupFrameDialog::GxsGroupFrameDialog(RsGxsIfaceHelper *ifaceImpl, QWidget *p
mSubscribedGroups = NULL;
mPopularGroups = NULL;
mOtherGroups = NULL;
mMessageWidget = NULL;
/* Setup Queue */
mInterface = ifaceImpl;
@ -251,6 +250,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 +268,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);
}
}
@ -383,6 +387,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
@ -425,12 +430,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)) {
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()));
@ -666,7 +669,7 @@ bool GxsGroupFrameDialog::getCurrentGroupName(QString& name)
void GxsGroupFrameDialog::markMsgAsRead()
{
GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId, false);
GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId);
if (msgWidget) {
msgWidget->setAllMessagesRead(true);
}
@ -674,7 +677,7 @@ void GxsGroupFrameDialog::markMsgAsRead()
void GxsGroupFrameDialog::markMsgAsUnread()
{
GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId, false);
GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId);
if (msgWidget) {
msgWidget->setAllMessagesRead(false);
}
@ -752,7 +755,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;
}
@ -764,18 +767,17 @@ 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<GxsMessageFrameWidget*>(ui->messageTabWidget->widget(index));
if (ownTab && mMessageWidget && childWidget == mMessageWidget) {
continue;
}
if (childWidget && childWidget->groupId() == groupId) {
if (childWidget && childWidget->groupId() == groupId)
return childWidget;
}
}
return NULL;
}
@ -783,9 +785,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());
@ -810,40 +812,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);
}
}
void GxsGroupFrameDialog::groupTreeMiddleButtonClicked(QTreeWidgetItem *item)
@ -863,37 +869,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<GxsMessageFrameWidget*>(widget);
if (msgWidget && msgWidget == mMessageWidget) {
/* Don't close single tab */
return;
}
GxsMessageFrameWidget *msgWidget = dynamic_cast<GxsMessageFrameWidget*>(ui->messageTabWidget->widget(index));
delete msgWidget ;
}
delete(widget);
GxsMessageFrameWidget *GxsGroupFrameDialog::currentWidget() const
{
return dynamic_cast<GxsMessageFrameWidget*>(ui->messageTabWidget->widget(ui->messageTabWidget->currentIndex()));
}
void GxsGroupFrameDialog::messageTabChanged(int index)
{
GxsMessageFrameWidget *msgWidget = dynamic_cast<GxsMessageFrameWidget*>(ui->messageTabWidget->widget(index));
if (!msgWidget) {
if (!msgWidget)
return;
}
ui->groupTreeWidget->activateId(QString::fromStdString(msgWidget->groupId().toStdString()), false);
}

View File

@ -181,7 +181,7 @@ 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);
@ -193,13 +193,15 @@ protected:
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;

View File

@ -55,6 +55,7 @@ signals:
void groupChanged(QWidget *widget);
void waitingChanged(QWidget *widget);
void loadComment(const RsGxsGroupId &groupId, const QVector<RsGxsMessageId>& msg_versions,const RsGxsMessageId &msgId, const QString &title);
void groupDataLoaded();
protected:
virtual void setAllMessagesReadDo(bool read, uint32_t &token) = 0;

View File

@ -72,6 +72,7 @@ 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)));
@ -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);
}

View File

@ -58,6 +58,7 @@ private slots:
void addExtraFile();
void checkAttachmentReady();
void deleteAttachment();
void updatePreviewText(const QString &);
void cancelMsg();
void sendMsg();

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>671</width>
<height>513</height>
<width>736</width>
<height>271</height>
</rect>
</property>
<property name="acceptDrops">
@ -20,7 +20,7 @@
<iconset resource="../images.qrc">
<normaloff>:/images/logo/logo_16.png</normaloff>:/images/logo/logo_16.png</iconset>
</property>
<layout class="QGridLayout" name="CreateGxsChannelMsgGLayout">
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
@ -33,7 +33,10 @@
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<property name="horizontalSpacing">
<number>6</number>
</property>
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="0">
@ -52,53 +55,15 @@
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QPushButton" name="channelpostButton">
<property name="text">
<string>Channel Post</string>
<property name="topMargin">
<number>3</number>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/png/comment.png</normaloff>:/icons/png/comment.png</iconset>
<property name="horizontalSpacing">
<number>6</number>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
<property name="verticalSpacing">
<number>0</number>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="attachmentsButton">
<property name="text">
<string>Attachments</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/png/attachements.png</normaloff>:/icons/png/attachements.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>486</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" colspan="3">
<widget class="QStackedWidget" name="stackedWidget">
<property name="mouseTracking">
@ -114,7 +79,10 @@
<number>0</number>
</property>
<widget class="QWidget" name="stackedWidgetPage1">
<layout class="QGridLayout" name="gridLayout_3">
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
@ -127,116 +95,27 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="1" column="0">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0" rowspan="2">
<widget class="QLabel" name="thumbnail_label">
<property name="maximumSize">
<size>
<width>156</width>
<height>107</height>
</size>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>6</number>
</property>
<property name="sizeIncrement">
<size>
<width>0</width>
<height>0</height>
</size>
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<property name="pixmap">
<pixmap resource="../images.qrc">:/images/thumb-default-video.png</pixmap>
</property>
</widget>
<item>
<widget class="ChannelPostThumbnailView" name="preview_W" native="true"/>
</item>
<item row="0" column="1" colspan="4">
<widget class="QLabel" name="channelAttachLabel">
<property name="text">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; font-weight:600;&quot;&gt;Attachments:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;img src=&quot;:/images/feedback_arrow.png&quot; /&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt; Use Drag and Drop / Add Files button, to Hash new files.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;img src=&quot;:/images/feedback_arrow.png&quot; /&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt; Copy/Paste RetroShare links from your shares&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>6</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="addThumbnailButton">
<property name="text">
<string>Add Channel Thumbnail</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/png/add-image.png</normaloff>:/icons/png/add-image.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="addfilepushButton">
<property name="text">
<string>Add File to Attach</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/png/add-file.png</normaloff>:/icons/png/add-file.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item row="1" column="3" colspan="2">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="messageGBox">
<property name="title">
<string>Message</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLineEdit" name="subjectEdit">
<property name="placeholderText">
<string>Title</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="RichTextEdit" name="RichTextEditWidget" native="true"/>
</item>
</layout>
</widget>
</item>
<item row="0" column="0">
<item>
<layout class="QHBoxLayout" name="channelNameHLayout">
<property name="topMargin">
<number>6</number>
</property>
<item>
<widget class="QLabel" name="channelNameLabel">
<property name="sizePolicy">
@ -268,6 +147,107 @@ p, li { white-space: pre-wrap; }
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="channelAttachLabel">
<property name="text">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:10pt; font-weight:600;&quot;&gt;Attachments:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;img src=&quot;:/images/feedback_arrow.png&quot; /&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt; Use Drag and Drop / Add Files button, to Hash new files.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;img src=&quot;:/images/feedback_arrow.png&quot; /&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt; Copy/Paste RetroShare links from your shares&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="addThumbnailButton">
<property name="text">
<string>Add Channel Thumbnail</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/png/add-image.png</normaloff>:/icons/png/add-image.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="addfilepushButton">
<property name="text">
<string>Add File to Attach</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/png/add-file.png</normaloff>:/icons/png/add-file.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="topMargin">
<number>9</number>
</property>
<item>
<widget class="QLineEdit" name="subjectEdit">
<property name="placeholderText">
<string>Title</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>1</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="RichTextEdit" name="RichTextEditWidget" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="stackedWidgetPage2">
@ -276,7 +256,7 @@ p, li { white-space: pre-wrap; }
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
<number>6</number>
</property>
<property name="rightMargin">
<number>0</number>
@ -329,7 +309,7 @@ p, li { white-space: pre-wrap; }
<rect>
<x>0</x>
<y>0</y>
<width>632</width>
<width>81</width>
<height>24</height>
</rect>
</property>
@ -403,6 +383,53 @@ p, li { white-space: pre-wrap; }
</widget>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="channelpostButton">
<property name="text">
<string>Channel Post</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/png/comment.png</normaloff>:/icons/png/comment.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>486</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="attachmentsButton">
<property name="text">
<string>Attachments</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/png/attachements.png</normaloff>:/icons/png/attachements.png</iconset>
</property>
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
</widget>
</item>
<item row="2" column="0" colspan="3">
<layout class="QHBoxLayout" name="buttonHLayout">
<item>
@ -455,10 +482,16 @@ p, li { white-space: pre-wrap; }
<header>util/RichTextEdit.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ChannelPostThumbnailView</class>
<extends>QWidget</extends>
<header>gui/gxschannels/GxsChannelPostThumbnail.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../icons.qrc"/>
<include location="../images.qrc"/>
<include location="../icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -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"
@ -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()

View File

@ -18,6 +18,7 @@
* *
*******************************************************************************/
#include <QMenu>
#include <QTimer>
#include <QMessageBox>
#include <QFileInfo>
@ -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;
}
}

View File

@ -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);

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>367</width>
<height>27</height>
<width>421</width>
<height>29</height>
</rect>
</property>
<property name="windowTitle">
@ -48,7 +48,7 @@
<number>2</number>
</property>
<item>
<widget class="QToolButton" name="downloadToolButton">
<widget class="QPushButton" name="downloadPushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -126,18 +126,31 @@
</widget>
</item>
<item>
<widget class="QToolButton" name="openFolderToolButton">
<widget class="QPushButton" name="openFilePushButton">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Play</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="openFolderToolButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="text">
<string>Open folder</string>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
@ -148,6 +161,7 @@
</widget>
<resources>
<include location="../images.qrc"/>
<include location="../icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -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);

View File

@ -0,0 +1,476 @@
/*******************************************************************************
* retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.cpp *
* *
* Copyright 2020 by Cyril Soler <csoler@users.sourceforge.net> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include <QApplication>
#include <QFontMetrics>
#include <QModelIndex>
#include <QIcon>
#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;i<mFiles.size();++i)
mFilteredFiles.push_back(i);
}
else
{
mFilteredFiles.clear();
//mFilteredPosts.push_back(0);
for(int i=0;i<mFiles.size();++i)
{
bool passes_strings = true;
for(auto& s:strings)
passes_strings = passes_strings && QString::fromStdString(mFiles[i].mName).contains(s,Qt::CaseInsensitive);
if(passes_strings)
mFilteredFiles.push_back(i);
}
}
count = mFilteredFiles.size();
std::cerr << "After filtering: " << count << " posts remain." << std::endl;
beginInsertRows(QModelIndex(),0,rowCount()-1);
endInsertRows();
postMods();
}
class compareOperator
{
public:
compareOperator(int column,Qt::SortOrder order): col(column),ord(order) {}
bool operator()(const ChannelPostFileInfo& f1,const ChannelPostFileInfo& f2) const
{
switch(col)
{
default:
case RsGxsChannelPostFilesModel::COLUMN_FILES_NAME: return (ord==Qt::AscendingOrder)?(f1.mName<f2.mName):(f1.mName>f2.mName);
case RsGxsChannelPostFilesModel::COLUMN_FILES_SIZE: return (ord==Qt::AscendingOrder)?(f1.mSize<f2.mSize):(f1.mSize>f2.mSize);
case RsGxsChannelPostFilesModel::COLUMN_FILES_DATE: return (ord==Qt::AscendingOrder)?(f1.mPublishTime<f2.mPublishTime):(f1.mPublishTime>f2.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.transfered<fi2.transfered):(fi1.transfered>fi2.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<ChannelPostFileInfo> &files)
{
preMods();
beginRemoveRows(QModelIndex(),0,mFilteredFiles.size()-1);
endRemoveRows();
initEmptyHierarchy();
for(auto& file:files)
mFiles.push_back(file);
for(uint32_t i=0;i<mFiles.size();++i)
mFilteredFiles.push_back(i);
#ifdef DEBUG_CHANNEL_MODEL
// debug_dump();
#endif
beginInsertRows(QModelIndex(),0,mFilteredFiles.size()-1);
endInsertRows();
postMods();
emit channelLoaded();
if(!files.empty())
mTimer->start(5000);
else
mTimer->stop();
}

View File

@ -0,0 +1,173 @@
/*******************************************************************************
* retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.h *
* *
* Copyright 2020 by Cyril Soler <csoler@users.sourceforge.net> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#pragma once
#include "retroshare/rsfiles.h"
#include "retroshare/rsgxscommon.h"
#include <QModelIndex>
#include <QColor>
// 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<ChannelPostFileInfo>& 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<int> mFilteredFiles ; // store the list of files for the post
std::vector<ChannelPostFileInfo> mFiles ; // store the list of files for the post
QTimer *mTimer;
};

View File

@ -0,0 +1,125 @@
/*******************************************************************************
* retroshare-gui/src/gui/gxschannels/GxsChannelPostThumbnail.h *
* *
* Copyright 2020 by Retroshare Team <retroshare.project@gmail.com> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#pragma once
#include <string>
#include <QWidget>
#include <QLabel>
#include <QLayout>
#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;
};

View File

@ -0,0 +1,736 @@
/*******************************************************************************
* retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.cpp *
* *
* Copyright 2020 by Cyril Soler <csoler@users.sourceforge.net> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include <QApplication>
#include <QFontMetrics>
#include <QModelIndex>
#include <QIcon>
#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<const RsEvent> 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<const RsEvent> event)
{
const RsGxsChannelEvent *e = dynamic_cast<const RsGxsChannelEvent*>(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<RsGxsChannelPost> posts;
std::vector<RsGxsComment> comments;
std::vector<RsGxsVote> votes;
if(!rsGxsChannels->getChannelContent(mChannelGroup.mMeta.mGroupId,std::set<RsGxsMessageId>{ 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<posts.size();++i)
{
// linear search. Not good at all, but normally this is for a single post.
for(uint32_t j=0;j<mPosts.size();++j)
if(mPosts[j].mMeta.mMsgId == posts[i].mMeta.mMsgId)
{
mPosts[j] = posts[i];
emit dataChanged(createIndex(0,0,(void*)NULL), createIndex(mFilteredPosts.size(),mColumns-1,(void*)NULL));
}
}
},this);
});
default:
break;
}
}
}
void RsGxsChannelPostsModel::initEmptyHierarchy()
{
preMods();
mPosts.clear();
mFilteredPosts.clear();
// mPosts.resize(1); // adds a sentinel item
// mPosts[0].mMeta.mMsgName = "Root sentinel post" ;
// mFilteredPosts.resize(1);
// mFilteredPosts[0] = 1;
postMods();
}
void RsGxsChannelPostsModel::preMods()
{
//emit layoutAboutToBeChanged(); //Generate SIGSEGV when click on button move next/prev.
beginResetModel();
}
void RsGxsChannelPostsModel::postMods()
{
endResetModel();
emit dataChanged(createIndex(0,0,(void*)NULL), createIndex(mFilteredPosts.size(),mColumns-1,(void*)NULL));
}
void RsGxsChannelPostsModel::getFilesList(std::list<ChannelPostFileInfo>& files)
{
// We use an intermediate map so as to remove duplicates
std::map<RsFileHash,ChannelPostFileInfo> files_map;
for(uint32_t i=0;i<mPosts.size();++i)
for(auto& file:mPosts[i].mFiles)
files_map.insert(std::make_pair(file.mHash,ChannelPostFileInfo(file,mPosts[i].mMeta.mPublishTs)));
files.clear();
for(auto& it:files_map)
files.push_back(it.second);
}
void RsGxsChannelPostsModel::setFilter(const QStringList& strings, uint32_t& count)
{
preMods();
beginRemoveRows(QModelIndex(),0,rowCount()-1);
endRemoveRows();
if(strings.empty())
{
mFilteredPosts.clear();
for(int i=0;i<mPosts.size();++i)
mFilteredPosts.push_back(i);
}
else
{
mFilteredPosts.clear();
//mFilteredPosts.push_back(0);
for(int i=0;i<mPosts.size();++i)
{
bool passes_strings = true;
for(auto& s:strings)
passes_strings = passes_strings && QString::fromStdString(mPosts[i].mMeta.mMsgName).contains(s,Qt::CaseInsensitive);
if(passes_strings)
mFilteredPosts.push_back(i);
}
}
count = mFilteredPosts.size();
std::cerr << "After filtering: " << count << " posts remain." << std::endl;
beginInsertRows(QModelIndex(),0,rowCount()-1);
endInsertRows();
postMods();
}
int RsGxsChannelPostsModel::rowCount(const QModelIndex& parent) const
{
if(parent.column() > 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<RsGxsChannelPost>& 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<mPosts.size();++i)
mFilteredPosts.push_back(i);
#ifdef DEBUG_CHANNEL_MODEL
// debug_dump();
#endif
beginInsertRows(QModelIndex(),0,rowCount()-1);
endInsertRows();
postMods();
emit channelPostsLoaded();
}
void RsGxsChannelPostsModel::update_posts(const RsGxsGroupId& group_id)
{
if(group_id.isNull())
return;
RsThread::async([this, group_id]()
{
// 1 - get message data from p3GxsChannels
std::list<RsGxsGroupId> channelIds;
std::vector<RsMsgMetaData> msg_metas;
std::vector<RsGxsChannelGroup> 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<RsGxsChannelPost> *posts = new std::vector<RsGxsChannelPost>();
std::vector<RsGxsComment> *comments = new std::vector<RsGxsComment>();
std::vector<RsGxsVote> *votes = new std::vector<RsGxsVote>();
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<time_t,RsGxsMessageId>& e1,const std::pair<time_t,RsGxsMessageId>& e2) { return e2.first < e1.first ; }
void RsGxsChannelPostsModel::createPostsArray(std::vector<RsGxsChannelPost>& posts)
{
// collect new versions of posts if any
#ifdef DEBUG_CHANNEL_MODEL
std::cerr << "Inserting channel posts" << std::endl;
#endif
std::vector<uint32_t> new_versions ;
for (uint32_t i=0;i<posts.size();++i)
{
if(posts[i].mMeta.mOrigMsgId == posts[i].mMeta.mMsgId)
posts[i].mMeta.mOrigMsgId.clear();
#ifdef DEBUG_CHANNEL_MODEL
std::cerr << " " << i << ": name=\"" << posts[i].mMeta.mMsgName << "\" msg_id=" << posts[i].mMeta.mMsgId << ": orig msg id = " << posts[i].mMeta.mOrigMsgId << std::endl;
#endif
if(!posts[i].mMeta.mOrigMsgId.isNull())
new_versions.push_back(i) ;
}
#ifdef DEBUG_CHANNEL_MODEL
std::cerr << "New versions: " << new_versions.size() << std::endl;
#endif
if(!new_versions.empty())
{
#ifdef DEBUG_CHANNEL_MODEL
std::cerr << " New versions present. Replacing them..." << std::endl;
std::cerr << " Creating search map." << std::endl;
#endif
// make a quick search map
std::map<RsGxsMessageId,uint32_t> search_map ;
for (uint32_t i=0;i<posts.size();++i)
search_map[posts[i].mMeta.mMsgId] = i ;
for(uint32_t i=0;i<new_versions.size();++i)
{
#ifdef DEBUG_CHANNEL_MODEL
std::cerr << " Taking care of new version at index " << new_versions[i] << std::endl;
#endif
uint32_t current_index = new_versions[i] ;
uint32_t source_index = new_versions[i] ;
#ifdef DEBUG_CHANNEL_MODEL
RsGxsMessageId source_msg_id = posts[source_index].mMeta.mMsgId ;
#endif
// What we do is everytime we find a replacement post, we climb up the replacement graph until we find the original post
// (or the most recent version of it). When we reach this post, we replace it with the data of the source post.
// In the mean time, all other posts have their MsgId cleared, so that the posts are removed from the list.
//std::vector<uint32_t> versions ;
std::map<RsGxsMessageId,uint32_t>::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<RsGxsMessageId>::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<RsGxsChannelPost>::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<mFilteredPosts.size();++i)
{
// First look into msg versions, in case the msg is a version of an existing message
for(auto& msg_id:mPosts[mFilteredPosts[i]].mOlderVersions)
if(msg_id == postId)
{
quintptr ref ;
convertTabEntryToRefPointer(i,ref); // we dont use i+1 here because i is not a row, but an index in the mPosts tab
return createIndex(i/mColumns,i%mColumns, ref);
}
if(mPosts[mFilteredPosts[i]].mMeta.mMsgId == postId)
{
quintptr ref ;
convertTabEntryToRefPointer(i,ref); // we dont use i+1 here because i is not a row, but an index in the mPosts tab
return createIndex(i/mColumns,i%mColumns, ref);
}
}
return QModelIndex();
}

View File

@ -0,0 +1,239 @@
/*******************************************************************************
* retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.h *
* *
* Copyright 2020 by Cyril Soler <csoler@users.sourceforge.net> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include "retroshare/rsgxschannels.h"
#include "retroshare/rsgxsifacetypes.h"
#include "retroshare/rsevents.h"
#include <QModelIndex>
#include <QColor>
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<std::pair<time_t,RsGxsMessageId> > 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<ChannelPostFileInfo> &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<RsGxsForumMsg>& 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<RsGxsChannelPost>& 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<RsMsgMetaData> &msgs_array, std::vector<ChannelPostsModelPostEntry> &posts, std::map<RsGxsMessageId, std::vector<std::pair<time_t, RsGxsMessageId> > > &mPostVersions);
void createPostsArray(std::vector<RsGxsChannelPost> &posts);
void setPosts(const RsGxsChannelGroup& group, std::vector<RsGxsChannelPost> &posts);
void initEmptyHierarchy();
void handleEvent_main_thread(std::shared_ptr<const RsEvent> event);
std::vector<int> mFilteredPosts; // stores the list of displayes indices due to filtering.
std::vector<RsGxsChannelPost> mPosts ; // store the list of posts updated from rsForums.
//std::map<RsGxsMessageId,std::vector<std::pair<time_t,RsGxsMessageId> > > mPostVersions; // stores versions of posts
QColor mTextColorRead ;
QColor mTextColorUnread ;
QColor mTextColorUnreadChildren;
QColor mTextColorNotSubscribed ;
QColor mTextColorMissing ;
RsEventsHandlerId_t mEventHandlerId ;
friend class const_iterator;
};

View File

@ -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 */

View File

@ -98,7 +98,7 @@ private:
int viewMode();
void insertChannelDetails(const RsGxsChannelGroup &group);
void insertChannelPosts(std::vector<RsGxsChannelPost> &posts, GxsMessageFramePostThread *thread, bool related);
void insertChannelPosts(std::vector<RsGxsChannelPost>& posts, GxsMessageFramePostThread *thread, bool related);
void createPostItem(const RsGxsChannelPost &post, bool related);
void handleEvent_main_thread(std::shared_ptr<const RsEvent> event);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,184 @@
/*******************************************************************************
* retroshare-gui/src/gui/gxschannels/GxsChannelPostsWidget.h *
* *
* Copyright 2013 by Robert Fernie <retroshare.project@gmail.com> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Affero General Public License as *
* published by the Free Software Foundation, either version 3 of the *
* License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Affero General Public License for more details. *
* *
* You should have received a copy of the GNU Affero General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#ifndef _GXS_CHANNELPOSTSWIDGET_H
#define _GXS_CHANNELPOSTSWIDGET_H
#include <map>
#include <QStyledItemDelegate>
#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<RsGxsMessageId> &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<RsGxsMessageId>& msgIds,std::vector<RsGxsGenericMsgData*>& posts) override;
virtual void getAllMsgData(std::vector<RsGxsGenericMsgData*>& posts) override;
virtual void insertPosts(const std::vector<RsGxsGenericMsgData*>& posts) override;
virtual void insertAllPosts(const std::vector<RsGxsGenericMsgData*>& 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<const RsEvent> 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

View File

@ -0,0 +1,601 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>GxsChannelPostsWidgetWithModel</class>
<widget class="QWidget" name="GxsChannelPostsWidgetWithModel">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>977</width>
<height>628</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>4</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QFrame" name="toolBarFrame">
<property name="frameShape">
<enum>QFrame::Box</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="SubscribeToolButton" name="subscribeToolButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string notr="true">Subscribe</string>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
<property name="popupMode">
<enum>QToolButton::MenuButtonPopup</enum>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="postButton">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Post to Channel</string>
</property>
<property name="text">
<string>Add new post</string>
</property>
<property name="icon">
<iconset resource="../icons.qrc">
<normaloff>:/icons/png/add.png</normaloff>:/icons/png/add.png</iconset>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>16</height>
</size>
</property>
<property name="toolButtonStyle">
<enum>Qt::ToolButtonTextBesideIcon</enum>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="viewModeLayout">
<property name="spacing">
<number>0</number>
</property>
</layout>
</item>
<item>
<widget class="LineEditClear" name="filterLineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Search channels</string>
</property>
<property name="frame">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTabWidget" name="channel_TW">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>Channel details</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="StyledElidedLabel" name="channelName_LB">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
<strikeout>false</strikeout>
</font>
</property>
<property name="text">
<string>Channel title</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="logoLabel">
<property name="text">
<string>TextLabel</string>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="3">
<widget class="QLabel" name="infoLastPost">
<property name="text">
<string notr="true">unknown</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Created:</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="GxsIdLabel" name="infoAdministrator">
<property name="text">
<string>unknown</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Distribution:</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="infoPosts">
<property name="text">
<string notr="true">0</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="2">
<widget class="QLabel" name="infoLastPostLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Last Post:</string>
</property>
</widget>
</item>
<item row="3" column="3">
<widget class="QLabel" name="infoDistribution">
<property name="text">
<string>unknown</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLabel" name="label">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Administrator:</string>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QLabel" name="infoCreated">
<property name="text">
<string>unknown</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QLabel" name="infoPostsLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Posts:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTextBrowser" name="infoDescription">
<property name="html">
<string notr="true">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;Description&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="openLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_4">
<attribute name="title">
<string>Posts</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="RSTreeView" name="postsTree">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectItems</enum>
</property>
<property name="textElideMode">
<enum>Qt::ElideNone</enum>
</property>
<property name="indentation">
<number>0</number>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="headerHidden">
<bool>true</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
<widget class="QTabWidget" name="details_TW">
<property name="font">
<font>
<kerning>true</kerning>
</font>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Details</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="postLogo_LB">
<property name="text">
<string>TextLabel</string>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="postName_LB">
<property name="text">
<string>TextLabel</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="postTime_LB">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QTextBrowser" name="postDetails_TE">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="channelPostFiles_tab">
<attribute name="title">
<string>Files</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="RSTreeView" name="channelPostFiles_TV">
<property name="editTriggers">
<set>QAbstractItemView::CurrentChanged|QAbstractItemView::SelectedClicked</set>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="indentation">
<number>5</number>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Comments</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="GxsCommentDialog" name="commentsDialog" native="true"/>
</item>
</layout>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_5">
<attribute name="title">
<string>Files</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<widget class="RSTreeView" name="channelFiles_TV">
<property name="editTriggers">
<set>QAbstractItemView::CurrentChanged|QAbstractItemView::SelectedClicked</set>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<property name="expandsOnDoubleClick">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
<action name="actionFeeds">
<property name="text">
<string>Feeds</string>
</property>
</action>
<action name="actionFiles">
<property name="text">
<string>Files</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>GxsIdLabel</class>
<extends>QLabel</extends>
<header>gui/gxs/GxsIdLabel.h</header>
</customwidget>
<customwidget>
<class>SubscribeToolButton</class>
<extends>QToolButton</extends>
<header>gui/common/SubscribeToolButton.h</header>
</customwidget>
<customwidget>
<class>StyledElidedLabel</class>
<extends>QLabel</extends>
<header>gui/common/StyledElidedLabel.h</header>
</customwidget>
<customwidget>
<class>RSTreeView</class>
<extends>QTreeView</extends>
<header>gui/common/RSTreeView.h</header>
</customwidget>
<customwidget>
<class>GxsCommentDialog</class>
<extends>QWidget</extends>
<header>gui/gxs/GxsCommentDialog.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>LineEditClear</class>
<extends>QLineEdit</extends>
<header>gui/common/LineEditClear.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../icons.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -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());
@ -172,9 +172,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());
@ -462,7 +462,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 +498,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 +561,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<uint32_t>(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<uint32_t>(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<uint32_t>(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 +599,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 +664,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 +756,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);

View File

@ -60,6 +60,7 @@
<file>icons/png/anonymous.png</file>
<file>icons/png/attach-image.png</file>
<file>icons/png/attach.png</file>
<file>icons/png/arrow.png</file>
<file>icons/png/cert.png</file>
<file>icons/png/channels-notify.png</file>
<file>icons/png/channels.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 935 B

View File

@ -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;

View File

@ -84,7 +84,7 @@ ConfCertDialog QLabel#servicePermissionsLabel
qproperty-fontSizeFactor: 125;
}
GxsChannelPostsWidget QLabel#nameLabel
GxsChannelPostsWidgetWithModel QLabel#channelName_LB
{
qproperty-fontSizeFactor: 250;
}

View File

@ -23,6 +23,7 @@
#include <gui/notifyqt.h>
#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;

View File

@ -20,6 +20,7 @@
#include <iostream>
#include <time.h>
#include <memory>
#include <QDateTime>
#include <QFontMetrics>
@ -463,24 +464,25 @@ void GxsTransportStatistics::loadGroups()
#ifdef DEBUG_FORUMS
std::cerr << "Retrieving post data for post " << mThreadId << std::endl;
#endif
std::map<RsGxsGroupId,RsGxsTransGroupStatistics> stats;
auto stats = std::make_unique<
std::map<RsGxsGroupId,RsGxsTransGroupStatistics> >();
if(!rsGxsTrans->getGroupStatistics(stats))
if(!rsGxsTrans->getGroupStatistics(*stats))
{
RsErr() << "Cannot retrieve group statistics in GxsTransportStatistics" << std::endl;
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;
// TODO: consider making mGroupStats an unique_ptr to avoid copying
mGroupStats = *stats;
updateContent();
mStateHelper->setLoading(GXSTRANS_GROUP_META, false);
}, this );

View File

@ -1337,13 +1337,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 \
@ -1352,6 +1358,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 \

View File

@ -1,7 +1,7 @@
/*******************************************************************************
* util/qthreadutils.h *
* *
* Copyright (C) 2018 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2018-2020 Gioacchino Mazzurco <gio@eigenlab.org> *
* *
* 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 <QtGlobal>
#include <QtCore>
#include <type_traits>
#include <utility>
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();

View File

@ -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