mirror of
https://github.com/RetroShare/RetroShare.git
synced 2024-12-25 23:49:35 -05:00
merged with master
This commit is contained in:
commit
500f572b98
@ -154,6 +154,7 @@ rs_webui {
|
|||||||
HEADERS += plugins/pluginmanager.h \
|
HEADERS += plugins/pluginmanager.h \
|
||||||
plugins/dlfcn_win32.h \
|
plugins/dlfcn_win32.h \
|
||||||
rsitems/rspluginitems.h \
|
rsitems/rspluginitems.h \
|
||||||
|
util/i2pcommon.h \
|
||||||
util/rsinitedptr.h
|
util/rsinitedptr.h
|
||||||
|
|
||||||
HEADERS += $$PUBLIC_HEADERS
|
HEADERS += $$PUBLIC_HEADERS
|
||||||
@ -517,7 +518,8 @@ SOURCES += ft/ftchunkmap.cc \
|
|||||||
ft/ftfilesearch.cc \
|
ft/ftfilesearch.cc \
|
||||||
ft/ftserver.cc \
|
ft/ftserver.cc \
|
||||||
ft/fttransfermodule.cc \
|
ft/fttransfermodule.cc \
|
||||||
ft/ftturtlefiletransferitem.cc
|
ft/ftturtlefiletransferitem.cc \
|
||||||
|
util/i2pcommon.cpp
|
||||||
|
|
||||||
SOURCES += crypto/chacha20.cpp \
|
SOURCES += crypto/chacha20.cpp \
|
||||||
crypto/hashstream.cc\
|
crypto/hashstream.cc\
|
||||||
|
@ -1803,6 +1803,7 @@ void p3NetMgrIMPL::updateNatSetting()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef RS_USE_DHT_STUNNER
|
#ifdef RS_USE_DHT_STUNNER
|
||||||
|
if (mProxyStunner) {
|
||||||
switch(natType)
|
switch(natType)
|
||||||
{
|
{
|
||||||
case RSNET_NATTYPE_RESTRICTED_CONE:
|
case RSNET_NATTYPE_RESTRICTED_CONE:
|
||||||
@ -1827,6 +1828,7 @@ void p3NetMgrIMPL::updateNatSetting()
|
|||||||
mProxyStunner->setRefreshPeriod(NET_STUNNER_PERIOD_SLOW);
|
mProxyStunner->setRefreshPeriod(NET_STUNNER_PERIOD_SLOW);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#endif // RS_USE_DHT_STUNNER
|
#endif // RS_USE_DHT_STUNNER
|
||||||
|
|
||||||
/* This controls the Attach mode of the DHT...
|
/* This controls the Attach mode of the DHT...
|
||||||
|
@ -291,8 +291,8 @@ private:
|
|||||||
|
|
||||||
//p3BitDht *mBitDht;
|
//p3BitDht *mBitDht;
|
||||||
#ifdef RS_USE_DHT_STUNNER
|
#ifdef RS_USE_DHT_STUNNER
|
||||||
pqiAddrAssist *mDhtStunner;
|
pqiAddrAssist *mDhtStunner = nullptr;
|
||||||
pqiAddrAssist *mProxyStunner;
|
pqiAddrAssist *mProxyStunner = nullptr;
|
||||||
#endif // RS_USE_DHT_STUNNER
|
#endif // RS_USE_DHT_STUNNER
|
||||||
|
|
||||||
RsMutex mNetMtx; /* protects below */
|
RsMutex mNetMtx; /* protects below */
|
||||||
|
@ -161,7 +161,9 @@ public:
|
|||||||
p3ChatService *chatSrv;
|
p3ChatService *chatSrv;
|
||||||
p3StatusService *mStatusSrv;
|
p3StatusService *mStatusSrv;
|
||||||
p3GxsTunnelService *mGxsTunnels;
|
p3GxsTunnelService *mGxsTunnels;
|
||||||
|
#ifdef RS_USE_I2P_BOB
|
||||||
p3I2pBob *mI2pBob;
|
p3I2pBob *mI2pBob;
|
||||||
|
#endif
|
||||||
|
|
||||||
// This list contains all threaded services. It will be used to shut them down properly.
|
// This list contains all threaded services. It will be used to shut them down properly.
|
||||||
|
|
||||||
|
@ -923,8 +923,10 @@ int RsServer::StartupRetroShare()
|
|||||||
mNetMgr->setManagers(mPeerMgr, mLinkMgr);
|
mNetMgr->setManagers(mPeerMgr, mLinkMgr);
|
||||||
|
|
||||||
rsAutoProxyMonitor *autoProxy = rsAutoProxyMonitor::instance();
|
rsAutoProxyMonitor *autoProxy = rsAutoProxyMonitor::instance();
|
||||||
|
#ifdef RS_USE_I2P_BOB
|
||||||
mI2pBob = new p3I2pBob(mPeerMgr);
|
mI2pBob = new p3I2pBob(mPeerMgr);
|
||||||
autoProxy->addProxy(autoProxyType::I2PBOB, mI2pBob);
|
autoProxy->addProxy(autoProxyType::I2PBOB, mI2pBob);
|
||||||
|
#endif
|
||||||
|
|
||||||
//load all the SSL certs as friends
|
//load all the SSL certs as friends
|
||||||
// std::list<std::string> sslIds;
|
// std::list<std::string> sslIds;
|
||||||
@ -1649,7 +1651,9 @@ int RsServer::StartupRetroShare()
|
|||||||
mConfigMgr->addConfiguration("wire.cfg", wire_ns);
|
mConfigMgr->addConfiguration("wire.cfg", wire_ns);
|
||||||
#endif
|
#endif
|
||||||
#endif //RS_ENABLE_GXS
|
#endif //RS_ENABLE_GXS
|
||||||
|
#ifdef RS_USE_I2P_BOB
|
||||||
mConfigMgr->addConfiguration("I2PBOB.cfg", mI2pBob);
|
mConfigMgr->addConfiguration("I2PBOB.cfg", mI2pBob);
|
||||||
|
#endif
|
||||||
|
|
||||||
mPluginsManager->addConfigurations(mConfigMgr) ;
|
mPluginsManager->addConfigurations(mConfigMgr) ;
|
||||||
|
|
||||||
@ -1724,7 +1728,7 @@ int RsServer::StartupRetroShare()
|
|||||||
// now enable bob
|
// now enable bob
|
||||||
bobSettings bs;
|
bobSettings bs;
|
||||||
autoProxy->taskSync(autoProxyType::I2PBOB, autoProxyTask::getSettings, &bs);
|
autoProxy->taskSync(autoProxyType::I2PBOB, autoProxyTask::getSettings, &bs);
|
||||||
bs.enableBob = true;
|
bs.enable = true;
|
||||||
autoProxy->taskSync(autoProxyType::I2PBOB, autoProxyTask::setSettings, &bs);
|
autoProxy->taskSync(autoProxyType::I2PBOB, autoProxyTask::setSettings, &bs);
|
||||||
} else {
|
} else {
|
||||||
std::cerr << "RsServer::StartupRetroShare failed to receive keys" << std::endl;
|
std::cerr << "RsServer::StartupRetroShare failed to receive keys" << std::endl;
|
||||||
@ -1795,7 +1799,9 @@ int RsServer::StartupRetroShare()
|
|||||||
/**************************************************************************/
|
/**************************************************************************/
|
||||||
|
|
||||||
// auto proxy threads
|
// auto proxy threads
|
||||||
|
#ifdef RS_USE_I2P_BOB
|
||||||
startServiceThread(mI2pBob, "I2P-BOB");
|
startServiceThread(mI2pBob, "I2P-BOB");
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef RS_ENABLE_GXS
|
#ifdef RS_ENABLE_GXS
|
||||||
// Must Set the GXS pointers before starting threads.
|
// Must Set the GXS pointers before starting threads.
|
||||||
|
@ -43,21 +43,14 @@ static const std::string kConfigKeyOutLength = "OUT_LENGTH";
|
|||||||
static const std::string kConfigKeyOutQuantity = "OUT_QUANTITY";
|
static const std::string kConfigKeyOutQuantity = "OUT_QUANTITY";
|
||||||
static const std::string kConfigKeyOutVariance = "OUT_VARIANCE";
|
static const std::string kConfigKeyOutVariance = "OUT_VARIANCE";
|
||||||
|
|
||||||
static const bool kDefaultBOBEnable = false;
|
/// Sleep duration for receiving loop in error/no-data case
|
||||||
static const int8_t kDefaultLength = 3;
|
static const useconds_t sleepTimeRecv = 250; // times 1000 = 250ms
|
||||||
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 everything else
|
/// Sleep duration for everything else
|
||||||
static const useconds_t sleepTimeWait = 50; // times 1000 = 50ms or 0.05s
|
static const useconds_t sleepTimeWait = 50; // times 1000 = 50ms or 0.05s
|
||||||
static const int sleepFactorDefault = 10; // 0.5s
|
static const int sleepFactorDefault = 10; // 0.5s
|
||||||
static const int sleepFactorFast = 1; // 0.05s
|
static const int sleepFactorFast = 1; // 0.05s
|
||||||
static const int sleepFactorSlow = 20; // 1s
|
static const int sleepFactorSlow = 20; // 1s
|
||||||
|
|
||||||
static struct RsLog::logInfo i2pBobLogInfo = {RsLog::Default, "p3I2pBob"};
|
|
||||||
|
|
||||||
static const rstime_t selfCheckPeroid = 30;
|
static const rstime_t selfCheckPeroid = 30;
|
||||||
|
|
||||||
void doSleep(useconds_t timeToSleepMS) {
|
void doSleep(useconds_t timeToSleepMS) {
|
||||||
@ -74,15 +67,7 @@ p3I2pBob::p3I2pBob(p3PeerMgr *peerMgr)
|
|||||||
mProcessing(NULL), mLock("I2P-BOB")
|
mProcessing(NULL), mLock("I2P-BOB")
|
||||||
{
|
{
|
||||||
// set defaults
|
// set defaults
|
||||||
mSetting.enableBob = kDefaultBOBEnable;
|
mSetting.initDefault();
|
||||||
mSetting.keys = "";
|
|
||||||
mSetting.addr = "";
|
|
||||||
mSetting.inLength = kDefaultLength;
|
|
||||||
mSetting.inQuantity = kDefaultQuantity;
|
|
||||||
mSetting.inVariance = kDefaultVariance;
|
|
||||||
mSetting.outLength = kDefaultLength;
|
|
||||||
mSetting.outQuantity = kDefaultQuantity;
|
|
||||||
mSetting.outVariance = kDefaultVariance;
|
|
||||||
|
|
||||||
mCommands.clear();
|
mCommands.clear();
|
||||||
}
|
}
|
||||||
@ -90,12 +75,12 @@ p3I2pBob::p3I2pBob(p3PeerMgr *peerMgr)
|
|||||||
bool p3I2pBob::isEnabled()
|
bool p3I2pBob::isEnabled()
|
||||||
{
|
{
|
||||||
RS_STACK_MUTEX(mLock);
|
RS_STACK_MUTEX(mLock);
|
||||||
return mSetting.enableBob;
|
return mSetting.enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool p3I2pBob::initialSetup(std::string &addr, uint16_t &/*port*/)
|
bool p3I2pBob::initialSetup(std::string &addr, uint16_t &/*port*/)
|
||||||
{
|
{
|
||||||
std::cout << "p3I2pBob::initialSetup" << std::endl;
|
RS_DBG("");
|
||||||
|
|
||||||
// update config
|
// 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
|
// request keys
|
||||||
// p3I2pBob::stateMachineBOB expects mProcessing to be set therefore
|
// 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;
|
fakeTicket->task = autoProxyTask::receiveKey;
|
||||||
processTaskAsync(fakeTicket);
|
processTaskAsync(fakeTicket);
|
||||||
|
|
||||||
std::cout << "p3I2pBob::initialSetup fakeTicket requested" << std::endl;
|
RS_DBG("fakeTicket requested");
|
||||||
|
|
||||||
// now start thread
|
// now start thread
|
||||||
start("I2P-BOB gen key");
|
start("I2P-BOB gen key");
|
||||||
|
|
||||||
std::cout << "p3I2pBob::initialSetup thread started" << std::endl;
|
RS_DBG("thread started");
|
||||||
|
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
// wait for keys
|
// wait for keys
|
||||||
@ -137,24 +122,24 @@ bool p3I2pBob::initialSetup(std::string &addr, uint16_t &/*port*/)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
if (++counter > 30) {
|
if (++counter > 30) {
|
||||||
std::cout << "p3I2pBob::initialSetup timeout!" << std::endl;
|
RS_DBG4("timeout!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "p3I2pBob::initialSetup got keys" << std::endl;
|
RS_DBG("got keys");
|
||||||
|
|
||||||
// stop thread
|
// stop thread
|
||||||
fullstop();
|
fullstop();
|
||||||
|
|
||||||
std::cout << "p3I2pBob::initialSetup thread stopped" << std::endl;
|
RS_DBG("thread stopped");
|
||||||
|
|
||||||
{
|
{
|
||||||
RS_STACK_MUTEX(mLock);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
@ -172,7 +157,7 @@ void p3I2pBob::processTaskAsync(taskTicket *ticket)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "p3I2pBob::processTaskAsync unknown task");
|
RS_DBG("unknown task");
|
||||||
rsAutoProxyMonitor::taskError(ticket);
|
rsAutoProxyMonitor::taskError(ticket);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -187,7 +172,7 @@ void p3I2pBob::processTaskSync(taskTicket *ticket)
|
|||||||
case autoProxyTask::status:
|
case autoProxyTask::status:
|
||||||
// check if everything needed is set
|
// check if everything needed is set
|
||||||
if (!data) {
|
if (!data) {
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "p3I2pBob::status autoProxyTask::status data is missing");
|
RS_DBG("autoProxyTask::status data is missing");
|
||||||
rsAutoProxyMonitor::taskError(ticket);
|
rsAutoProxyMonitor::taskError(ticket);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -201,7 +186,7 @@ void p3I2pBob::processTaskSync(taskTicket *ticket)
|
|||||||
case autoProxyTask::getSettings:
|
case autoProxyTask::getSettings:
|
||||||
// check if everything needed is set
|
// check if everything needed is set
|
||||||
if (!data) {
|
if (!data) {
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "p3I2pBob::data_tick autoProxyTask::getSettings data is missing");
|
RS_DBG("autoProxyTask::getSettings data is missing");
|
||||||
rsAutoProxyMonitor::taskError(ticket);
|
rsAutoProxyMonitor::taskError(ticket);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -215,7 +200,7 @@ void p3I2pBob::processTaskSync(taskTicket *ticket)
|
|||||||
case autoProxyTask::setSettings:
|
case autoProxyTask::setSettings:
|
||||||
// check if everything needed is set
|
// check if everything needed is set
|
||||||
if (!data) {
|
if (!data) {
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "p3I2pBob::data_tick autoProxyTask::setSettings data is missing");
|
RS_DBG("autoProxyTask::setSettings data is missing");
|
||||||
rsAutoProxyMonitor::taskError(ticket);
|
rsAutoProxyMonitor::taskError(ticket);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -235,7 +220,7 @@ void p3I2pBob::processTaskSync(taskTicket *ticket)
|
|||||||
break;
|
break;
|
||||||
case autoProxyTask::getErrorInfo:
|
case autoProxyTask::getErrorInfo:
|
||||||
if (!data) {
|
if (!data) {
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "p3I2pBob::data_tick autoProxyTask::getErrorInfo data is missing");
|
RS_DBG("autoProxyTask::getErrorInfo data is missing");
|
||||||
rsAutoProxyMonitor::taskError(ticket);
|
rsAutoProxyMonitor::taskError(ticket);
|
||||||
} else {
|
} else {
|
||||||
RS_STACK_MUTEX(mLock);
|
RS_STACK_MUTEX(mLock);
|
||||||
@ -244,34 +229,12 @@ void p3I2pBob::processTaskSync(taskTicket *ticket)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "p3I2pBob::processTaskSync unknown task");
|
RS_DBG("unknown task");
|
||||||
rsAutoProxyMonitor::taskError(ticket);
|
rsAutoProxyMonitor::taskError(ticket);
|
||||||
break;
|
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) {
|
bool inline isAnswerOk(const std::string &answer) {
|
||||||
return (answer.compare(0, 2, "OK") == 0);
|
return (answer.compare(0, 2, "OK") == 0);
|
||||||
}
|
}
|
||||||
@ -285,9 +248,7 @@ void p3I2pBob::threadTick()
|
|||||||
int sleepTime = 0;
|
int sleepTime = 0;
|
||||||
{
|
{
|
||||||
RS_STACK_MUTEX(mLock);
|
RS_STACK_MUTEX(mLock);
|
||||||
std::stringstream ss;
|
RS_DBG4("data_tick mState: ", mState, " mTask: ", mTask, " mBOBState: ", mBOBState, " mPending: ", mPending.size());
|
||||||
ss << "data_tick mState: " << mState << " mTask: " << mTask << " mBOBState: " << mBOBState << " mPending: " << mPending.size();
|
|
||||||
rslog(RsLog::Debug_All, &i2pBobLogInfo, ss.str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sleepTime += stateMachineController();
|
sleepTime += stateMachineController();
|
||||||
@ -326,15 +287,13 @@ int p3I2pBob::stateMachineBOB()
|
|||||||
if (mBOBState == bsList) {
|
if (mBOBState == bsList) {
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
while (answer.find("OK Listing done") == std::string::npos) {
|
while (answer.find("OK Listing done") == std::string::npos) {
|
||||||
std::stringstream ss;
|
RS_DBG3("stateMachineBOB status check: read loop, counter: ", counter);
|
||||||
ss << "stateMachineBOB status check: read loop, counter: " << counter;
|
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, ss.str());
|
|
||||||
answer += recv();
|
answer += recv();
|
||||||
counter++;
|
counter++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (answer.find(mTunnelName) == std::string::npos) {
|
if (answer.find(mTunnelName) == std::string::npos) {
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineBOB status check: tunnel down!");
|
RS_DBG("status check: tunnel down!");
|
||||||
// signal error
|
// signal error
|
||||||
*((bool *)mProcessing->data) = true;
|
*((bool *)mProcessing->data) = true;
|
||||||
}
|
}
|
||||||
@ -346,12 +305,12 @@ int p3I2pBob::stateMachineBOB()
|
|||||||
switch (mBOBState) {
|
switch (mBOBState) {
|
||||||
case bsNewkeysN:
|
case bsNewkeysN:
|
||||||
key = answer.substr(3, answer.length()-3);
|
key = answer.substr(3, answer.length()-3);
|
||||||
mSetting.addr = keyToBase32Addr(key);
|
mSetting.address.base32 = i2p::keyToBase32Addr(key);
|
||||||
IndicateConfigChanged();
|
IndicateConfigChanged();
|
||||||
break;
|
break;
|
||||||
case bsGetkeys:
|
case bsGetkeys:
|
||||||
key = answer.substr(3, answer.length()-3);
|
key = answer.substr(3, answer.length()-3);
|
||||||
mSetting.keys = key;
|
mSetting.address.privateKey = key;
|
||||||
IndicateConfigChanged();
|
IndicateConfigChanged();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -374,8 +333,8 @@ int p3I2pBob::stateMachineBOB_locked_failure(const std::string &answer, const bo
|
|||||||
return sleepFactorDefault;
|
return sleepFactorDefault;
|
||||||
}
|
}
|
||||||
|
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineBOB FAILED to run command '" + currentState.command + "'");
|
RS_DBG("FAILED to run command: ", currentState.command);
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineBOB '" + answer + "'");
|
RS_DBG("answer: ", answer);
|
||||||
|
|
||||||
mErrorMsg.append("FAILED to run command '" + currentState.command + "'" + '\n');
|
mErrorMsg.append("FAILED to run command '" + currentState.command + "'" + '\n');
|
||||||
mErrorMsg.append("reason '" + answer + "'" + '\n');
|
mErrorMsg.append("reason '" + answer + "'" + '\n');
|
||||||
@ -422,14 +381,14 @@ int p3I2pBob::stateMachineController()
|
|||||||
return stateMachineController_locked_idle();
|
return stateMachineController_locked_idle();
|
||||||
case csDoConnect:
|
case csDoConnect:
|
||||||
if (!connectI2P()) {
|
if (!connectI2P()) {
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController doConnect: unable to connect");
|
RS_DBG("doConnect: unable to connect");
|
||||||
mStateOld = mState;
|
mStateOld = mState;
|
||||||
mState = csError;
|
mState = csError;
|
||||||
mErrorMsg = "unable to connect to BOB port";
|
mErrorMsg = "unable to connect to BOB port";
|
||||||
return sleepFactorSlow;
|
return sleepFactorSlow;
|
||||||
}
|
}
|
||||||
|
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController doConnect: connected");
|
RS_DBG4("doConnect: connected");
|
||||||
mState = csConnected;
|
mState = csConnected;
|
||||||
break;
|
break;
|
||||||
case csConnected:
|
case csConnected:
|
||||||
@ -437,7 +396,7 @@ int p3I2pBob::stateMachineController()
|
|||||||
case csWaitForBob:
|
case csWaitForBob:
|
||||||
// check connection problems
|
// check connection problems
|
||||||
if (mSocket == 0) {
|
if (mSocket == 0) {
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController waitForBob: conection lost");
|
RS_DBG("waitForBob: conection lost");
|
||||||
mStateOld = mState;
|
mStateOld = mState;
|
||||||
mState = csError;
|
mState = csError;
|
||||||
mErrorMsg = "connection lost to BOB";
|
mErrorMsg = "connection lost to BOB";
|
||||||
@ -447,21 +406,21 @@ int p3I2pBob::stateMachineController()
|
|||||||
// check for finished BOB protocol
|
// check for finished BOB protocol
|
||||||
if (mBOBState == bsCleared) {
|
if (mBOBState == bsCleared) {
|
||||||
// done
|
// done
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController waitForBob: mBOBState == bsCleared");
|
RS_DBG4("waitForBob: mBOBState == bsCleared");
|
||||||
mState = csDoDisconnect;
|
mState = csDoDisconnect;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case csDoDisconnect:
|
case csDoDisconnect:
|
||||||
if (!disconnectI2P() || mSocket != 0) {
|
if (!disconnectI2P() || mSocket != 0) {
|
||||||
// just in case
|
// just in case
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController doDisconnect: can't disconnect");
|
RS_DBG("doDisconnect: can't disconnect");
|
||||||
mStateOld = mState;
|
mStateOld = mState;
|
||||||
mState = csError;
|
mState = csError;
|
||||||
mErrorMsg = "unable to disconnect from BOB";
|
mErrorMsg = "unable to disconnect from BOB";
|
||||||
return sleepFactorDefault;
|
return sleepFactorDefault;
|
||||||
}
|
}
|
||||||
|
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController doDisconnect: disconnected");
|
RS_DBG4("doDisconnect: disconnected");
|
||||||
mState = csDisconnected;
|
mState = csDisconnected;
|
||||||
break;
|
break;
|
||||||
case csDisconnected:
|
case csDisconnected:
|
||||||
@ -487,12 +446,12 @@ int p3I2pBob::stateMachineController_locked_idle()
|
|||||||
mProcessing = mPending.front();
|
mProcessing = mPending.front();
|
||||||
mPending.pop();
|
mPending.pop();
|
||||||
|
|
||||||
if (!mSetting.enableBob && (
|
if (!mSetting.enable && (
|
||||||
mProcessing->task == autoProxyTask::start ||
|
mProcessing->task == autoProxyTask::start ||
|
||||||
mProcessing->task == autoProxyTask::stop ||
|
mProcessing->task == autoProxyTask::stop ||
|
||||||
mProcessing->task == autoProxyTask::proxyStatusCheck)) {
|
mProcessing->task == autoProxyTask::proxyStatusCheck)) {
|
||||||
// skip since we are not enabled
|
// 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);
|
rsAutoProxyMonitor::taskDone(mProcessing, autoProxyStatus::disabled);
|
||||||
mProcessing = NULL;
|
mProcessing = NULL;
|
||||||
} else {
|
} else {
|
||||||
@ -514,7 +473,7 @@ int p3I2pBob::stateMachineController_locked_idle()
|
|||||||
mTask = ctRunCheck;
|
mTask = ctRunCheck;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
rslog(RsLog::Debug_Alert, &i2pBobLogInfo, "stateMachineController_locked_idle unknown async task");
|
RS_DBG1("unknown async task");
|
||||||
rsAutoProxyMonitor::taskError(mProcessing);
|
rsAutoProxyMonitor::taskError(mProcessing);
|
||||||
mProcessing = NULL;
|
mProcessing = NULL;
|
||||||
break;
|
break;
|
||||||
@ -561,29 +520,29 @@ int p3I2pBob::stateMachineController_locked_connected()
|
|||||||
switch (mTask) {
|
switch (mTask) {
|
||||||
case ctRunSetUp:
|
case ctRunSetUp:
|
||||||
// when we have a key use it for server tunnel!
|
// when we have a key use it for server tunnel!
|
||||||
if(mSetting.keys.empty()) {
|
if(mSetting.address.privateKey.empty()) {
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_connected: setting mBOBState = setnickC");
|
RS_DBG4("setting mBOBState = setnickC");
|
||||||
mBOBState = bsSetnickC;
|
mBOBState = bsSetnickC;
|
||||||
} else {
|
} else {
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_connected: setting mBOBState = setnickS");
|
RS_DBG4("setting mBOBState = setnickS");
|
||||||
mBOBState = bsSetnickS;
|
mBOBState = bsSetnickS;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ctRunShutDown:
|
case ctRunShutDown:
|
||||||
// shut down existing tunnel
|
// shut down existing tunnel
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_connected: setting mBOBState = getnick");
|
RS_DBG4("setting mBOBState = getnick");
|
||||||
mBOBState = bsGetnick;
|
mBOBState = bsGetnick;
|
||||||
break;
|
break;
|
||||||
case ctRunCheck:
|
case ctRunCheck:
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_connected: setting mBOBState = list");
|
RS_DBG4("setting mBOBState = list");
|
||||||
mBOBState = bsList;
|
mBOBState = bsList;
|
||||||
break;
|
break;
|
||||||
case ctRunGetKeys:
|
case ctRunGetKeys:
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_connected: setting mBOBState = setnickN");
|
RS_DBG4("setting mBOBState = setnickN");
|
||||||
mBOBState = bsSetnickN;
|
mBOBState = bsSetnickN;
|
||||||
break;
|
break;
|
||||||
case ctIdle:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,7 +558,7 @@ int p3I2pBob::stateMachineController_locked_disconnected()
|
|||||||
if(errorHappened) {
|
if(errorHappened) {
|
||||||
// reset old state
|
// reset old state
|
||||||
mStateOld = csIdel;
|
mStateOld = csIdel;
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController_locked_disconnected: error during process!");
|
RS_DBG("error during process!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// answer ticket
|
// answer ticket
|
||||||
@ -628,12 +587,12 @@ int p3I2pBob::stateMachineController_locked_disconnected()
|
|||||||
mTask = mTaskOld;
|
mTask = mTaskOld;
|
||||||
|
|
||||||
if (!errorHappened) {
|
if (!errorHappened) {
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_disconnected: run check result: ok");
|
RS_DBG4("run check result: ok");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// switch to error
|
// switch to error
|
||||||
newState = csError;
|
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.";
|
mErrorMsg = "Connection check failed. Will try to restart tunnel.";
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -656,7 +615,7 @@ int p3I2pBob::stateMachineController_locked_disconnected()
|
|||||||
mTask = mTaskOld;
|
mTask = mTaskOld;
|
||||||
break;
|
break;
|
||||||
case ctIdle:
|
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);
|
rsAutoProxyMonitor::taskError(mProcessing);
|
||||||
}
|
}
|
||||||
mProcessing = NULL;
|
mProcessing = NULL;
|
||||||
@ -672,14 +631,12 @@ int p3I2pBob::stateMachineController_locked_error()
|
|||||||
{
|
{
|
||||||
// wait for bob protocoll
|
// wait for bob protocoll
|
||||||
if (mBOBState != bsCleared) {
|
if (mBOBState != bsCleared) {
|
||||||
rslog(RsLog::Debug_All, &i2pBobLogInfo, "stateMachineController_locked_error: waiting for BOB");
|
RS_DBG4("waiting for BOB");
|
||||||
return sleepFactorFast;
|
return sleepFactorFast;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
std::stringstream ss;
|
RS_DBG4("stateMachineController_locked_error: mProcessing: ", (mProcessing ? "not null" : "null"));
|
||||||
ss << "stateMachineController_locked_error: mProcessing: " << (mProcessing ? "not null" : "null");
|
|
||||||
rslog(RsLog::Debug_All, &i2pBobLogInfo, ss.str());
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// try to finish ticket
|
// try to finish ticket
|
||||||
@ -687,7 +644,7 @@ int p3I2pBob::stateMachineController_locked_error()
|
|||||||
switch (mTask) {
|
switch (mTask) {
|
||||||
case ctRunCheck:
|
case ctRunCheck:
|
||||||
// connection check failed at some point
|
// 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;
|
*((bool *)mProcessing->data) = true;
|
||||||
mState = csDoDisconnect;
|
mState = csDoDisconnect;
|
||||||
mStateOld = csIdel;
|
mStateOld = csIdel;
|
||||||
@ -695,7 +652,7 @@ int p3I2pBob::stateMachineController_locked_error()
|
|||||||
break;
|
break;
|
||||||
case ctRunShutDown:
|
case ctRunShutDown:
|
||||||
// not a big deal though
|
// 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;
|
mState = csDoDisconnect;
|
||||||
mStateOld = csIdel;
|
mStateOld = csIdel;
|
||||||
mErrorMsg.clear();
|
mErrorMsg.clear();
|
||||||
@ -703,14 +660,14 @@ int p3I2pBob::stateMachineController_locked_error()
|
|||||||
case ctIdle:
|
case ctIdle:
|
||||||
// should not happen but we need to deal with it
|
// 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)
|
// 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;
|
mState = csDoDisconnect;
|
||||||
mStateOld = csIdel;
|
mStateOld = csIdel;
|
||||||
mErrorMsg.clear();
|
mErrorMsg.clear();
|
||||||
break;
|
break;
|
||||||
case ctRunGetKeys:
|
case ctRunGetKeys:
|
||||||
case ctRunSetUp:
|
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;
|
mStateOld = csError;
|
||||||
mState = csDoDisconnect;
|
mState = csDoDisconnect;
|
||||||
// keep the error message
|
// keep the error message
|
||||||
@ -721,7 +678,7 @@ int p3I2pBob::stateMachineController_locked_error()
|
|||||||
|
|
||||||
// periodically retry
|
// periodically retry
|
||||||
if (mLastProxyCheck < time(NULL) - (selfCheckPeroid >> 1) && mTask == ctRunSetUp) {
|
if (mLastProxyCheck < time(NULL) - (selfCheckPeroid >> 1) && mTask == ctRunSetUp) {
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "stateMachineController_locked_error: retrying");
|
RS_DBG("retrying");
|
||||||
|
|
||||||
mLastProxyCheck = time(NULL);
|
mLastProxyCheck = time(NULL);
|
||||||
mErrorMsg.clear();
|
mErrorMsg.clear();
|
||||||
@ -734,7 +691,7 @@ int p3I2pBob::stateMachineController_locked_error()
|
|||||||
|
|
||||||
// check for new tickets
|
// check for new tickets
|
||||||
if (!mPending.empty()) {
|
if (!mPending.empty()) {
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "stateMachineController_locked_error: processing new ticket");
|
RS_DBG4("processing new ticket");
|
||||||
|
|
||||||
// reset and try new task
|
// reset and try new task
|
||||||
mTask = ctIdle;
|
mTask = ctIdle;
|
||||||
@ -765,16 +722,16 @@ RsSerialiser *p3I2pBob::setupSerialiser()
|
|||||||
|
|
||||||
bool p3I2pBob::saveList(bool &cleanup, std::list<RsItem *> &lst)
|
bool p3I2pBob::saveList(bool &cleanup, std::list<RsItem *> &lst)
|
||||||
{
|
{
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "saveList");
|
RS_DBG4("");
|
||||||
|
|
||||||
cleanup = true;
|
cleanup = true;
|
||||||
RsConfigKeyValueSet *vitem = new RsConfigKeyValueSet;
|
RsConfigKeyValueSet *vitem = new RsConfigKeyValueSet;
|
||||||
RsTlvKeyValue kv;
|
RsTlvKeyValue kv;
|
||||||
|
|
||||||
RS_STACK_MUTEX(mLock);
|
RS_STACK_MUTEX(mLock);
|
||||||
addKVS(vitem, kv, kConfigKeyBOBEnable, mSetting.enableBob ? "TRUE" : "FALSE")
|
addKVS(vitem, kv, kConfigKeyBOBEnable, mSetting.enable ? "TRUE" : "FALSE")
|
||||||
addKVS(vitem, kv, kConfigKeyBOBKey, mSetting.keys)
|
addKVS(vitem, kv, kConfigKeyBOBKey, mSetting.address.privateKey)
|
||||||
addKVS(vitem, kv, kConfigKeyBOBAddr, mSetting.addr)
|
addKVS(vitem, kv, kConfigKeyBOBAddr, mSetting.address.base32)
|
||||||
addKVSInt(vitem, kv, kConfigKeyInLength, mSetting.inLength)
|
addKVSInt(vitem, kv, kConfigKeyInLength, mSetting.inLength)
|
||||||
addKVSInt(vitem, kv, kConfigKeyInQuantity, mSetting.inQuantity)
|
addKVSInt(vitem, kv, kConfigKeyInQuantity, mSetting.inQuantity)
|
||||||
addKVSInt(vitem, kv, kConfigKeyInVariance, mSetting.inVariance)
|
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)
|
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) {
|
for(std::list<RsItem*>::const_iterator it = load.begin(); it!=load.end(); ++it) {
|
||||||
RsConfigKeyValueSet *vitem = dynamic_cast<RsConfigKeyValueSet*>(*it);
|
RsConfigKeyValueSet *vitem = dynamic_cast<RsConfigKeyValueSet*>(*it);
|
||||||
@ -808,11 +765,11 @@ bool p3I2pBob::loadList(std::list<RsItem *> &load)
|
|||||||
RS_STACK_MUTEX(mLock);
|
RS_STACK_MUTEX(mLock);
|
||||||
for(std::list<RsTlvKeyValue>::const_iterator kit = vitem->tlvkvs.pairs.begin(); kit != vitem->tlvkvs.pairs.end(); ++kit) {
|
for(std::list<RsTlvKeyValue>::const_iterator kit = vitem->tlvkvs.pairs.begin(); kit != vitem->tlvkvs.pairs.end(); ++kit) {
|
||||||
if (kit->key == kConfigKeyBOBEnable)
|
if (kit->key == kConfigKeyBOBEnable)
|
||||||
mSetting.enableBob = kit->value == "TRUE";
|
mSetting.enable = kit->value == "TRUE";
|
||||||
else if (kit->key == kConfigKeyBOBKey)
|
else if (kit->key == kConfigKeyBOBKey)
|
||||||
mSetting.keys = kit->value;
|
mSetting.address.privateKey = kit->value;
|
||||||
else if (kit->key == kConfigKeyBOBAddr)
|
else if (kit->key == kConfigKeyBOBAddr)
|
||||||
mSetting.addr = kit->value;
|
mSetting.address.base32 = kit->value;
|
||||||
getKVSUInt(kit, kConfigKeyInLength, mSetting.inLength)
|
getKVSUInt(kit, kConfigKeyInLength, mSetting.inLength)
|
||||||
getKVSUInt(kit, kConfigKeyInQuantity, mSetting.inQuantity)
|
getKVSUInt(kit, kConfigKeyInQuantity, mSetting.inQuantity)
|
||||||
getKVSUInt(kit, kConfigKeyInVariance, mSetting.inVariance)
|
getKVSUInt(kit, kConfigKeyInVariance, mSetting.inVariance)
|
||||||
@ -820,7 +777,7 @@ bool p3I2pBob::loadList(std::list<RsItem *> &load)
|
|||||||
getKVSUInt(kit, kConfigKeyOutQuantity, mSetting.outQuantity)
|
getKVSUInt(kit, kConfigKeyOutQuantity, mSetting.outQuantity)
|
||||||
getKVSUInt(kit, kConfigKeyOutVariance, mSetting.outVariance)
|
getKVSUInt(kit, kConfigKeyOutVariance, mSetting.outVariance)
|
||||||
else
|
else
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "loadList unknown key: " + kit->key);
|
RS_DBG("unknown key: ", kit->key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete vitem;
|
delete vitem;
|
||||||
@ -884,7 +841,7 @@ void p3I2pBob::getStates(bobStates *bs)
|
|||||||
|
|
||||||
std::string p3I2pBob::executeCommand(const std::string &command)
|
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;
|
std::string copy = command;
|
||||||
copy.push_back('\n');
|
copy.push_back('\n');
|
||||||
@ -896,7 +853,7 @@ std::string p3I2pBob::executeCommand(const std::string &command)
|
|||||||
// receive answer (trailing new line is already removed!)
|
// receive answer (trailing new line is already removed!)
|
||||||
std::string ans = recv();
|
std::string ans = recv();
|
||||||
|
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "executeCommand_locked answer '" + ans + "'");
|
RS_DBG4("answer: ", ans);
|
||||||
|
|
||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
@ -906,7 +863,7 @@ bool p3I2pBob::connectI2P()
|
|||||||
// there is only one thread that touches mSocket - no need for a lock
|
// there is only one thread that touches mSocket - no need for a lock
|
||||||
|
|
||||||
if (mSocket != 0) {
|
if (mSocket != 0) {
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "connectI2P_locked mSocket != 0");
|
RS_DBG("mSocket != 0");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -914,21 +871,21 @@ bool p3I2pBob::connectI2P()
|
|||||||
mSocket = unix_socket(PF_INET, SOCK_STREAM, 0);
|
mSocket = unix_socket(PF_INET, SOCK_STREAM, 0);
|
||||||
if (mSocket < 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// connect
|
// connect
|
||||||
int err = unix_connect(mSocket, mI2PProxyAddr);
|
int err = unix_connect(mSocket, mI2PProxyAddr);
|
||||||
if (err != 0) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// receive hello msg
|
// receive hello msg
|
||||||
recv();
|
recv();
|
||||||
|
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "connectI2P_locked done");
|
RS_DBG4("done");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -937,17 +894,17 @@ bool p3I2pBob::disconnectI2P()
|
|||||||
// there is only one thread that touches mSocket - no need for a lock
|
// there is only one thread that touches mSocket - no need for a lock
|
||||||
|
|
||||||
if (mSocket == 0) {
|
if (mSocket == 0) {
|
||||||
rslog(RsLog::Warning, &i2pBobLogInfo, "disconnectI2P_locked mSocket == 0");
|
RS_DBG("mSocket == 0");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int err = unix_close(mSocket);
|
int err = unix_close(mSocket);
|
||||||
if (err != 0) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "disconnectI2P_locked done");
|
RS_DBG4("done");
|
||||||
mSocket = 0;
|
mSocket = 0;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -968,7 +925,7 @@ std::string toString(const std::string &a, const int8_t b) {
|
|||||||
|
|
||||||
void p3I2pBob::finalizeSettings_locked()
|
void p3I2pBob::finalizeSettings_locked()
|
||||||
{
|
{
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "finalizeSettings_locked");
|
RS_DBG4("");
|
||||||
|
|
||||||
sockaddr_storage_clear(mI2PProxyAddr);
|
sockaddr_storage_clear(mI2PProxyAddr);
|
||||||
// get i2p proxy addr
|
// get i2p proxy addr
|
||||||
@ -979,8 +936,8 @@ void p3I2pBob::finalizeSettings_locked()
|
|||||||
sockaddr_storage_setipv4(mI2PProxyAddr, (sockaddr_in*)&proxy);
|
sockaddr_storage_setipv4(mI2PProxyAddr, (sockaddr_in*)&proxy);
|
||||||
sockaddr_storage_setport(mI2PProxyAddr, 2827);
|
sockaddr_storage_setport(mI2PProxyAddr, 2827);
|
||||||
|
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "finalizeSettings_locked using " + sockaddr_storage_tostring(mI2PProxyAddr));
|
RS_DBG4("using ", mI2PProxyAddr);
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "finalizeSettings_locked using " + mSetting.addr);
|
RS_DBG4("using ", mSetting.address.base32);
|
||||||
|
|
||||||
peerState ps;
|
peerState ps;
|
||||||
mPeerMgr->getOwnNetStatus(ps);
|
mPeerMgr->getOwnNetStatus(ps);
|
||||||
@ -988,21 +945,17 @@ void p3I2pBob::finalizeSettings_locked()
|
|||||||
// setup commands
|
// setup commands
|
||||||
// new lines are appended later!
|
// new lines are appended later!
|
||||||
|
|
||||||
// generate random suffix for name
|
// generate 8 characater long random suffix for name
|
||||||
// RSRandom::random_alphaNumericString can return very weird looking strings like: ,,@z+M
|
constexpr size_t len = 8;
|
||||||
// use base32 instead
|
const std::string location = RsRandom::alphaNumeric(len);
|
||||||
size_t len = 5; // 5 characters = 8 base32 symbols
|
RS_DBG4("using suffix ", location);
|
||||||
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);
|
|
||||||
mTunnelName = "RetroShare-" + location;
|
mTunnelName = "RetroShare-" + location;
|
||||||
|
|
||||||
const std::string setnick = "setnick RetroShare-" + location;
|
const std::string setnick = "setnick RetroShare-" + location;
|
||||||
const std::string getnick = "getnick RetroShare-" + location;
|
const std::string getnick = "getnick RetroShare-" + location;
|
||||||
const std::string newkeys = "newkeys";
|
const std::string newkeys = "newkeys";
|
||||||
const std::string getkeys = "getkeys";
|
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 inhost = "inhost " + sockaddr_storage_iptostring(proxy);
|
||||||
const std::string inport = toString("inport ", sockaddr_storage_port(proxy));
|
const std::string inport = toString("inport ", sockaddr_storage_port(proxy));
|
||||||
const std::string outhost = "outhost " + sockaddr_storage_iptostring(ps.localaddr);
|
const std::string outhost = "outhost " + sockaddr_storage_iptostring(ps.localaddr);
|
||||||
@ -1063,7 +1016,7 @@ void p3I2pBob::finalizeSettings_locked()
|
|||||||
|
|
||||||
void p3I2pBob::updateSettings_locked()
|
void p3I2pBob::updateSettings_locked()
|
||||||
{
|
{
|
||||||
rslog(RsLog::Debug_Basic, &i2pBobLogInfo, "updateSettings_locked");
|
RS_DBG4("");
|
||||||
|
|
||||||
sockaddr_storage proxy;
|
sockaddr_storage proxy;
|
||||||
mPeerMgr->getProxyServerAddress(RS_HIDDEN_TYPE_I2P, proxy);
|
mPeerMgr->getProxyServerAddress(RS_HIDDEN_TYPE_I2P, proxy);
|
||||||
@ -1071,7 +1024,7 @@ void p3I2pBob::updateSettings_locked()
|
|||||||
peerState ps;
|
peerState ps;
|
||||||
mPeerMgr->getOwnNetStatus(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 inhost = "inhost " + sockaddr_storage_iptostring(proxy);
|
||||||
const std::string inport = toString("inport ", sockaddr_storage_port(proxy));
|
const std::string inport = toString("inport ", sockaddr_storage_port(proxy));
|
||||||
const std::string outhost = "outhost " + sockaddr_storage_iptostring(ps.localaddr);
|
const std::string outhost = "outhost " + sockaddr_storage_iptostring(ps.localaddr);
|
||||||
@ -1103,38 +1056,62 @@ void p3I2pBob::updateSettings_locked()
|
|||||||
|
|
||||||
std::string p3I2pBob::recv()
|
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;
|
std::string ans;
|
||||||
ssize_t length;
|
uint16_t retry = 10;
|
||||||
const uint16_t bufferSize = 128;
|
|
||||||
std::vector<char> buffer(bufferSize);
|
|
||||||
|
|
||||||
do {
|
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);
|
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;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
ans.append(buffer.begin(), buffer.end());
|
// at least one byte was read
|
||||||
|
|
||||||
// clean received string
|
// search for new line
|
||||||
ans.erase(std::remove(ans.begin(), ans.end(), '\0'), ans.end());
|
auto bufferStr = std::string(buffer);
|
||||||
ans.erase(std::remove(ans.begin(), ans.end(), '\n'), ans.end());
|
size_t pos = bufferStr.find('\n');
|
||||||
|
|
||||||
#if 0
|
if (pos == std::string::npos) {
|
||||||
std::stringstream ss;
|
// no new line found -> more to read
|
||||||
ss << "recv length: " << length << " (bufferSize: " << bufferSize << ") ans: " << ans.length();
|
|
||||||
rslog(RsLog::Debug_All, &i2pBobLogInfo, ss.str());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// clear and resize buffer again
|
// sanity check
|
||||||
buffer.clear();
|
if (length != bufferSize) {
|
||||||
buffer.resize(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())
|
// calculate how much there is to read, read the \n, too!
|
||||||
break;
|
length = pos + 1;
|
||||||
} while(length == bufferSize || ans.size() < 4);
|
|
||||||
|
// 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;
|
return ans;
|
||||||
}
|
}
|
||||||
|
@ -30,9 +30,10 @@
|
|||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "pqi/p3cfgmgr.h"
|
||||||
#include "services/autoproxy/rsautoproxymonitor.h"
|
#include "services/autoproxy/rsautoproxymonitor.h"
|
||||||
#include "util/rsthreads.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
|
* This class implements I2P BOB (BASIC OPEN BRIDGE) communication to allow RS
|
||||||
@ -49,7 +50,7 @@
|
|||||||
*
|
*
|
||||||
* Note 3:
|
* Note 3:
|
||||||
* BOB needs a unique name as an ID for each tunnel.
|
* 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:
|
* Design:
|
||||||
* The service uses three state machines to manage its task:
|
* The service uses three state machines to manage its task:
|
||||||
@ -72,7 +73,7 @@
|
|||||||
* mCommands[bobState::quit] = {quit, bobState::cleared};
|
* mCommands[bobState::quit] = {quit, bobState::cleared};
|
||||||
*
|
*
|
||||||
* stateMachineController:
|
* stateMachineController:
|
||||||
* This state machone manages the high level tasks.
|
* This state machine manages the high level tasks.
|
||||||
* It is controlled by mState and mTask.
|
* It is controlled by mState and mTask.
|
||||||
*
|
*
|
||||||
* mTast:
|
* mTast:
|
||||||
@ -162,19 +163,7 @@ struct bobStateInfo {
|
|||||||
bobState nextState;
|
bobState nextState;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct bobSettings {
|
struct bobSettings : i2p::settings {};
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
///
|
||||||
/// \brief The bobStates struct
|
/// \brief The bobStates struct
|
||||||
@ -203,8 +192,6 @@ public:
|
|||||||
void processTaskAsync(taskTicket *ticket);
|
void processTaskAsync(taskTicket *ticket);
|
||||||
void processTaskSync(taskTicket *ticket);
|
void processTaskSync(taskTicket *ticket);
|
||||||
|
|
||||||
static std::string keyToBase32Addr(const std::string &key);
|
|
||||||
|
|
||||||
void threadTick() override; /// @see RsTickingThread
|
void threadTick() override; /// @see RsTickingThread
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
#include "rsautoproxymonitor.h"
|
#include "rsautoproxymonitor.h"
|
||||||
|
|
||||||
#include <unistd.h> /* for usleep() */
|
#include <unistd.h> /* for usleep() */
|
||||||
|
#include "util/rsdebug.h"
|
||||||
#include "util/rstime.h"
|
#include "util/rstime.h"
|
||||||
|
|
||||||
rsAutoProxyMonitor *rsAutoProxyMonitor::mInstance = NULL;
|
rsAutoProxyMonitor *rsAutoProxyMonitor::mInstance = NULL;
|
||||||
@ -42,8 +43,10 @@ rsAutoProxyMonitor *rsAutoProxyMonitor::instance()
|
|||||||
void rsAutoProxyMonitor::addProxy(autoProxyType::autoProxyType_enum type, autoProxyService *service)
|
void rsAutoProxyMonitor::addProxy(autoProxyType::autoProxyType_enum type, autoProxyService *service)
|
||||||
{
|
{
|
||||||
RS_STACK_MUTEX(mLock);
|
RS_STACK_MUTEX(mLock);
|
||||||
if (mProxies.find(type) != mProxies.end())
|
if (mProxies.find(type) != mProxies.end()) {
|
||||||
std::cerr << "sAutoProxyMonitor::addProxy type " << type << " already added - OVERWRITING" << std::endl;
|
RS_ERR("type ", type, " already added - OVERWRITING");
|
||||||
|
print_stacktrace();
|
||||||
|
}
|
||||||
|
|
||||||
mProxies[type] = service;
|
mProxies[type] = service;
|
||||||
}
|
}
|
||||||
@ -117,7 +120,7 @@ void rsAutoProxyMonitor::stopAllRSShutdown()
|
|||||||
do {
|
do {
|
||||||
rstime::rs_usleep(1000 * 1000);
|
rstime::rs_usleep(1000 * 1000);
|
||||||
RS_STACK_MUTEX(mLock);
|
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())
|
if (mProxies.empty())
|
||||||
break;
|
break;
|
||||||
t++;
|
t++;
|
||||||
@ -146,13 +149,16 @@ void rsAutoProxyMonitor::task(taskTicket *ticket)
|
|||||||
{
|
{
|
||||||
// sanity checks
|
// sanity checks
|
||||||
if (!ticket->async && ticket->types.size() > 1) {
|
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) {
|
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) {
|
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;
|
std::vector<autoProxyType::autoProxyType_enum>::const_iterator it;
|
||||||
@ -168,7 +174,11 @@ void rsAutoProxyMonitor::task(taskTicket *ticket)
|
|||||||
*tt = *ticket;
|
*tt = *ticket;
|
||||||
tt->types.clear();
|
tt->types.clear();
|
||||||
tt->types.push_back(*it);
|
tt->types.push_back(*it);
|
||||||
|
|
||||||
|
// it's async!
|
||||||
|
RsThread::async([s, tt] {
|
||||||
s->processTaskAsync(tt);
|
s->processTaskAsync(tt);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
s->processTaskSync(ticket);
|
s->processTaskSync(ticket);
|
||||||
}
|
}
|
||||||
@ -187,7 +197,8 @@ void rsAutoProxyMonitor::taskAsync(std::vector<autoProxyType::autoProxyType_enum
|
|||||||
if (!isAsyncTask(task)) {
|
if (!isAsyncTask(task)) {
|
||||||
// Usually the services will reject this ticket.
|
// Usually the services will reject this ticket.
|
||||||
// Just print a warning - maybe there is some special case where this is a good idea.
|
// 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();
|
taskTicket *tt = getTicket();
|
||||||
@ -215,7 +226,8 @@ void rsAutoProxyMonitor::taskSync(std::vector<autoProxyType::autoProxyType_enum>
|
|||||||
if (isAsyncTask(task)) {
|
if (isAsyncTask(task)) {
|
||||||
// Usually the services will reject this ticket.
|
// Usually the services will reject this ticket.
|
||||||
// Just print a warning - maybe there is some special case where this is a good idea.
|
// 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();
|
taskTicket *tt = getTicket();
|
||||||
@ -244,7 +256,8 @@ void rsAutoProxyMonitor::taskDone(taskTicket *t, autoProxyStatus::autoProxyStatu
|
|||||||
t->cb->taskFinished(t);
|
t->cb->taskFinished(t);
|
||||||
if (t != NULL) {
|
if (t != NULL) {
|
||||||
// callack did not clean up properly
|
// 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;
|
cleanUp = true;
|
||||||
}
|
}
|
||||||
} else if (t->async){
|
} else if (t->async){
|
||||||
@ -252,12 +265,13 @@ void rsAutoProxyMonitor::taskDone(taskTicket *t, autoProxyStatus::autoProxyStatu
|
|||||||
// we must take care of deleting
|
// we must take care of deleting
|
||||||
cleanUp = true;
|
cleanUp = true;
|
||||||
if(t->data)
|
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 (cleanUp) {
|
||||||
if (t->data) {
|
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 push
|
||||||
#pragma GCC diagnostic ignored "-Wdelete-incomplete"
|
#pragma GCC diagnostic ignored "-Wdelete-incomplete"
|
||||||
delete t->data;
|
delete t->data;
|
||||||
@ -290,7 +304,8 @@ void rsAutoProxyMonitor::taskFinished(taskTicket *&ticket)
|
|||||||
|
|
||||||
// clean up
|
// clean up
|
||||||
if (ticket->data) {
|
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 push
|
||||||
#pragma GCC diagnostic ignored "-Wdelete-incomplete"
|
#pragma GCC diagnostic ignored "-Wdelete-incomplete"
|
||||||
delete ticket->data;
|
delete ticket->data;
|
||||||
@ -308,7 +323,7 @@ autoProxyService *rsAutoProxyMonitor::lookUpService(autoProxyType::autoProxyType
|
|||||||
if ((itService = mProxies.find(t)) != mProxies.end()) {
|
if ((itService = mProxies.find(t)) != mProxies.end()) {
|
||||||
return itService->second;
|
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;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1997,7 +1997,9 @@ bool p3GxsCircles::processMembershipRequests(uint32_t token)
|
|||||||
|
|
||||||
// now do another sweep and remove all msgs that are older than the latest
|
// now do another sweep and remove all msgs that are older than the latest
|
||||||
|
|
||||||
|
#ifdef DEBUG_CIRCLES
|
||||||
std::cerr << " Cleaning old messages..." << std::endl;
|
std::cerr << " Cleaning old messages..." << std::endl;
|
||||||
|
#endif
|
||||||
|
|
||||||
for(uint32_t i=0;i<it->second.size();++i)
|
for(uint32_t i=0;i<it->second.size();++i)
|
||||||
{
|
{
|
||||||
|
163
libretroshare/src/util/i2pcommon.cpp
Normal file
163
libretroshare/src/util/i2pcommon.cpp
Normal 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
|
211
libretroshare/src/util/i2pcommon.h
Normal file
211
libretroshare/src/util/i2pcommon.h
Normal 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
|
@ -121,13 +121,26 @@ double RsRandom::random_f64()
|
|||||||
return random_u64() / (double)(~(uint64_t)0) ;
|
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)
|
return s;
|
||||||
s += (char)( (random_u32()%94) + 33) ;
|
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
@ -46,9 +46,19 @@ public:
|
|||||||
|
|
||||||
static bool seed(uint32_t s);
|
static bool seed(uint32_t s);
|
||||||
|
|
||||||
static std::string random_alphaNumericString(uint32_t length);
|
|
||||||
static void random_bytes(uint8_t* data, 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:
|
private:
|
||||||
static RsMutex rndMtx;
|
static RsMutex rndMtx;
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <retroshare/rspeers.h>
|
#include <retroshare/rspeers.h>
|
||||||
#include <retroshare/rsidentity.h>
|
#include <retroshare/rsidentity.h>
|
||||||
@ -700,30 +701,28 @@ void CreateCircleDialog::loadIdentities()
|
|||||||
|
|
||||||
if(!rsIdentity->getIdentitiesSummaries(ids_meta))
|
if(!rsIdentity->getIdentitiesSummaries(ids_meta))
|
||||||
{
|
{
|
||||||
std::cerr << __PRETTY_FUNCTION__ << " failed to retrieve identities ids for all identities" << std::endl;
|
RS_ERR("failed to retrieve identities ids for all identities");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<RsGxsId> ids;
|
std::set<RsGxsId> ids;
|
||||||
|
for(auto& meta:ids_meta) ids.insert(RsGxsId(meta.mGroupId));
|
||||||
|
|
||||||
for(auto& meta:ids_meta)
|
auto id_groups = std::make_unique<std::vector<RsGxsIdGroup>>();
|
||||||
ids.insert(RsGxsId(meta.mGroupId)) ;
|
if(!rsIdentity->getIdentitiesInfo(ids, *id_groups))
|
||||||
|
|
||||||
std::vector<RsGxsIdGroup> id_groups;
|
|
||||||
|
|
||||||
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;
|
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
|
/* 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
|
* thread, for example to update the data model with new information
|
||||||
* after a blocking call to RetroShare API complete */
|
* after a blocking call to RetroShare API complete */
|
||||||
|
|
||||||
fillIdentitiesList(id_groups) ;
|
fillIdentitiesList(*id_groups);
|
||||||
|
|
||||||
}, this );
|
}, this );
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -340,6 +340,10 @@ void GenCertDialog::setupState()
|
|||||||
ui.hiddenport_spinBox->setVisible(hidden_state && !tor_auto);
|
ui.hiddenport_spinBox->setVisible(hidden_state && !tor_auto);
|
||||||
|
|
||||||
ui.cbUseBob->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)
|
if(!mAllFieldsOk)
|
||||||
{
|
{
|
||||||
|
@ -47,6 +47,7 @@
|
|||||||
#include "util/misc.h"
|
#include "util/misc.h"
|
||||||
#include "util/QtVersion.h"
|
#include "util/QtVersion.h"
|
||||||
#include "util/rstime.h"
|
#include "util/rstime.h"
|
||||||
|
#include "util/rsdebug.h"
|
||||||
|
|
||||||
#include "retroshare/rsgxsflags.h"
|
#include "retroshare/rsgxsflags.h"
|
||||||
#include "retroshare/rsmsgs.h"
|
#include "retroshare/rsmsgs.h"
|
||||||
@ -55,6 +56,7 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
/******
|
/******
|
||||||
* #define ID_DEBUG 1
|
* #define ID_DEBUG 1
|
||||||
@ -506,21 +508,24 @@ void IdDialog::updateCircles()
|
|||||||
std::cerr << "Retrieving post data for post " << mThreadId << std::endl;
|
std::cerr << "Retrieving post data for post " << mThreadId << std::endl;
|
||||||
#endif
|
#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;
|
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
|
/* 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
|
* thread, for example to update the data model with new information
|
||||||
* after a blocking call to RetroShare API complete */
|
* after a blocking call to RetroShare API complete */
|
||||||
|
|
||||||
loadCircles(circle_metas);
|
loadCircles(*circle_metas);
|
||||||
|
|
||||||
}, this );
|
}, this );
|
||||||
|
|
||||||
@ -1305,19 +1310,17 @@ void IdDialog::updateIdList()
|
|||||||
return;
|
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)
|
RsQThreadUtils::postToObject(
|
||||||
ids_set[(*it).mMeta.mGroupId] = *it;
|
[ids_set = std::move(ids_set), this] ()
|
||||||
|
|
||||||
RsQThreadUtils::postToObject( [ids_set,this]()
|
|
||||||
{
|
{
|
||||||
/* Here it goes any code you want to be executed on the Qt Gui
|
/* 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
|
* thread, for example to update the data model with new information
|
||||||
* after a blocking call to RetroShare API complete */
|
* after a blocking call to RetroShare API complete */
|
||||||
|
loadIdentities(*ids_set);
|
||||||
loadIdentities(ids_set);
|
|
||||||
|
|
||||||
}, this );
|
}, this );
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -210,8 +210,8 @@ void IdEditDialog::setupExistingId(const RsGxsGroupId& keyId)
|
|||||||
RsThread::async([this,keyId]()
|
RsThread::async([this,keyId]()
|
||||||
{
|
{
|
||||||
std::vector<RsGxsIdGroup> datavector;
|
std::vector<RsGxsIdGroup> datavector;
|
||||||
|
bool res = rsIdentity->getIdentitiesInfo(
|
||||||
bool res = rsIdentity->getIdentitiesInfo(std::set<RsGxsId>({(RsGxsId)keyId}),datavector);
|
std::set<RsGxsId>({(RsGxsId)keyId}), datavector );
|
||||||
|
|
||||||
RsQThreadUtils::postToObject( [this,keyId,res,datavector]()
|
RsQThreadUtils::postToObject( [this,keyId,res,datavector]()
|
||||||
{
|
{
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include <retroshare/rsstatus.h>
|
#include <retroshare/rsstatus.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#define COLUMN_NAME 0
|
#define COLUMN_NAME 0
|
||||||
#define COLUMN_CHECK 0
|
#define COLUMN_CHECK 0
|
||||||
@ -250,24 +251,21 @@ void FriendSelectionWidget::loadIdentities()
|
|||||||
|
|
||||||
if(!rsIdentity->getIdentitiesSummaries(ids_meta))
|
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;
|
return;
|
||||||
}
|
}
|
||||||
std::vector<RsGxsGroupId> ids;
|
|
||||||
|
|
||||||
for(auto& meta:ids_meta)
|
auto ids = std::make_unique<std::vector<RsGxsGroupId>>();
|
||||||
ids.push_back(meta.mGroupId) ;
|
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
|
// We do that is the GUI thread. Dont try it on another thread!
|
||||||
* thread, for example to update the data model with new information
|
gxsIds = *ids;
|
||||||
* after a blocking call to RetroShare API complete */
|
/* TODO: To furter optimize away a copy gxsIds could be a unique_ptr
|
||||||
|
* too */
|
||||||
gxsIds = ids; // we do that is the GUI thread. Dont try it on another thread!
|
fillList();
|
||||||
|
|
||||||
fillList() ;
|
|
||||||
|
|
||||||
}, this );
|
}, this );
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -19,10 +19,43 @@
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
#include <QResizeEvent>
|
||||||
#include "RSTreeView.h"
|
#include "RSTreeView.h"
|
||||||
|
|
||||||
RSTreeView::RSTreeView(QWidget *parent) : QTreeView(parent)
|
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)
|
void RSTreeView::setPlaceholderText(const QString &text)
|
||||||
|
@ -33,8 +33,20 @@ public:
|
|||||||
|
|
||||||
void setPlaceholderText(const QString &text);
|
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:
|
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;
|
QString placeholderText;
|
||||||
};
|
};
|
||||||
|
@ -36,10 +36,12 @@ GxsCommentDialog::GxsCommentDialog(QWidget *parent, RsTokenService *token_servic
|
|||||||
/* Invoke the Qt Designer generated QObject setup routine */
|
/* Invoke the Qt Designer generated QObject setup routine */
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
//ui->postFrame->setVisible(false);
|
setTokenService(token_service,comment_service);
|
||||||
|
init();
|
||||||
ui->treeWidget->setup(token_service, comment_service);
|
}
|
||||||
|
|
||||||
|
void GxsCommentDialog::init()
|
||||||
|
{
|
||||||
/* Set header resize modes and initial section sizes */
|
/* Set header resize modes and initial section sizes */
|
||||||
QHeaderView * ttheader = ui->treeWidget->header () ;
|
QHeaderView * ttheader = ui->treeWidget->header () ;
|
||||||
ttheader->resizeSection (0, 440);
|
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));
|
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()
|
GxsCommentDialog::~GxsCommentDialog()
|
||||||
{
|
{
|
||||||
delete(ui);
|
delete(ui);
|
||||||
|
@ -32,9 +32,11 @@ class GxsCommentDialog: public QWidget
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
GxsCommentDialog(QWidget *parent);
|
||||||
GxsCommentDialog(QWidget *parent, RsTokenService *token_service, RsGxsCommentService *comment_service);
|
GxsCommentDialog(QWidget *parent, RsTokenService *token_service, RsGxsCommentService *comment_service);
|
||||||
virtual ~GxsCommentDialog();
|
virtual ~GxsCommentDialog();
|
||||||
|
|
||||||
|
void setTokenService(RsTokenService *token_service, RsGxsCommentService *comment_service);
|
||||||
void setCommentHeader(QWidget *header);
|
void setCommentHeader(QWidget *header);
|
||||||
void commentLoad(const RsGxsGroupId &grpId, const std::set<RsGxsMessageId> &msg_versions, const RsGxsMessageId &most_recent_msgId);
|
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);
|
void sortComments(int);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void init();
|
||||||
|
|
||||||
RsGxsGroupId mGrpId;
|
RsGxsGroupId mGrpId;
|
||||||
RsGxsMessageId mMostRecentMsgId;
|
RsGxsMessageId mMostRecentMsgId;
|
||||||
std::set<RsGxsMessageId> mMsgVersions;
|
std::set<RsGxsMessageId> mMsgVersions;
|
||||||
|
@ -89,7 +89,6 @@ GxsGroupFrameDialog::GxsGroupFrameDialog(RsGxsIfaceHelper *ifaceImpl, QWidget *p
|
|||||||
mSubscribedGroups = NULL;
|
mSubscribedGroups = NULL;
|
||||||
mPopularGroups = NULL;
|
mPopularGroups = NULL;
|
||||||
mOtherGroups = NULL;
|
mOtherGroups = NULL;
|
||||||
mMessageWidget = NULL;
|
|
||||||
|
|
||||||
/* Setup Queue */
|
/* Setup Queue */
|
||||||
mInterface = ifaceImpl;
|
mInterface = ifaceImpl;
|
||||||
@ -251,6 +250,13 @@ void GxsGroupFrameDialog::processSettings(bool load)
|
|||||||
Settings->endGroup();
|
Settings->endGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool GxsGroupFrameDialog::useTabs()
|
||||||
|
{
|
||||||
|
GroupFrameSettings groupFrameSettings;
|
||||||
|
|
||||||
|
return Settings->getGroupFrameSettings(groupFrameSettingsType(), groupFrameSettings) && groupFrameSettings.mOpenAllInNewTab;
|
||||||
|
}
|
||||||
|
|
||||||
void GxsGroupFrameDialog::settingsChanged()
|
void GxsGroupFrameDialog::settingsChanged()
|
||||||
{
|
{
|
||||||
GroupFrameSettings groupFrameSettings;
|
GroupFrameSettings groupFrameSettings;
|
||||||
@ -262,17 +268,15 @@ void GxsGroupFrameDialog::settingsChanged()
|
|||||||
|
|
||||||
void GxsGroupFrameDialog::setSingleTab(bool singleTab)
|
void GxsGroupFrameDialog::setSingleTab(bool singleTab)
|
||||||
{
|
{
|
||||||
if (singleTab) {
|
if (singleTab)
|
||||||
if (!mMessageWidget) {
|
{
|
||||||
mMessageWidget = createMessageWidget(RsGxsGroupId());
|
while(ui->messageTabWidget->count() > 1)
|
||||||
// remove close button of the the first tab
|
{
|
||||||
ui->messageTabWidget->hideCloseButton(ui->messageTabWidget->indexOf(mMessageWidget));
|
auto w = ui->messageTabWidget->widget(0) ;
|
||||||
}
|
ui->messageTabWidget->removeTab(0);
|
||||||
} else {
|
delete w;
|
||||||
if (mMessageWidget) {
|
|
||||||
delete(mMessageWidget);
|
|
||||||
mMessageWidget = NULL;
|
|
||||||
}
|
}
|
||||||
|
ui->messageTabWidget->hideCloseButton(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,6 +387,7 @@ static uint32_t checkDelay(uint32_t time_in_secs)
|
|||||||
|
|
||||||
return 365 * 86400;
|
return 365 * 86400;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GxsGroupFrameDialog::groupTreeCustomPopupMenu(QPoint point)
|
void GxsGroupFrameDialog::groupTreeCustomPopupMenu(QPoint point)
|
||||||
{
|
{
|
||||||
// First separately handle the case of search top level items
|
// First separately handle the case of search top level items
|
||||||
@ -425,12 +430,10 @@ void GxsGroupFrameDialog::groupTreeCustomPopupMenu(QPoint point)
|
|||||||
QMenu contextMnu(this);
|
QMenu contextMnu(this);
|
||||||
QAction *action;
|
QAction *action;
|
||||||
|
|
||||||
if (mMessageWidget) {
|
|
||||||
action = contextMnu.addAction(QIcon(IMAGE_TABNEW), tr("Open in new tab"), this, SLOT(openInNewTab()));
|
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);
|
action->setEnabled(false);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSubscribed) {
|
if (isSubscribed) {
|
||||||
action = contextMnu.addAction(QIcon(IMAGE_UNSUBSCRIBE), tr("Unsubscribe"), this, SLOT(unsubscribeGroup()));
|
action = contextMnu.addAction(QIcon(IMAGE_UNSUBSCRIBE), tr("Unsubscribe"), this, SLOT(unsubscribeGroup()));
|
||||||
@ -666,7 +669,7 @@ bool GxsGroupFrameDialog::getCurrentGroupName(QString& name)
|
|||||||
|
|
||||||
void GxsGroupFrameDialog::markMsgAsRead()
|
void GxsGroupFrameDialog::markMsgAsRead()
|
||||||
{
|
{
|
||||||
GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId, false);
|
GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId);
|
||||||
if (msgWidget) {
|
if (msgWidget) {
|
||||||
msgWidget->setAllMessagesRead(true);
|
msgWidget->setAllMessagesRead(true);
|
||||||
}
|
}
|
||||||
@ -674,7 +677,7 @@ void GxsGroupFrameDialog::markMsgAsRead()
|
|||||||
|
|
||||||
void GxsGroupFrameDialog::markMsgAsUnread()
|
void GxsGroupFrameDialog::markMsgAsUnread()
|
||||||
{
|
{
|
||||||
GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId, false);
|
GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId);
|
||||||
if (msgWidget) {
|
if (msgWidget) {
|
||||||
msgWidget->setAllMessagesRead(false);
|
msgWidget->setAllMessagesRead(false);
|
||||||
}
|
}
|
||||||
@ -752,7 +755,7 @@ bool GxsGroupFrameDialog::navigate(const RsGxsGroupId &groupId, const RsGxsMessa
|
|||||||
changedCurrentGroup(groupIdString);
|
changedCurrentGroup(groupIdString);
|
||||||
|
|
||||||
/* search exisiting tab */
|
/* search exisiting tab */
|
||||||
GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId, false);
|
GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId);
|
||||||
if (!msgWidget) {
|
if (!msgWidget) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -764,18 +767,17 @@ bool GxsGroupFrameDialog::navigate(const RsGxsGroupId &groupId, const RsGxsMessa
|
|||||||
return msgWidget->navigate(msgId);
|
return msgWidget->navigate(msgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
GxsMessageFrameWidget *GxsGroupFrameDialog::messageWidget(const RsGxsGroupId &groupId, bool ownTab)
|
GxsMessageFrameWidget *GxsGroupFrameDialog::messageWidget(const RsGxsGroupId &groupId)
|
||||||
{
|
{
|
||||||
int tabCount = ui->messageTabWidget->count();
|
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));
|
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 childWidget;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -783,9 +785,9 @@ GxsMessageFrameWidget *GxsGroupFrameDialog::messageWidget(const RsGxsGroupId &gr
|
|||||||
GxsMessageFrameWidget *GxsGroupFrameDialog::createMessageWidget(const RsGxsGroupId &groupId)
|
GxsMessageFrameWidget *GxsGroupFrameDialog::createMessageWidget(const RsGxsGroupId &groupId)
|
||||||
{
|
{
|
||||||
GxsMessageFrameWidget *msgWidget = createMessageFrameWidget(groupId);
|
GxsMessageFrameWidget *msgWidget = createMessageFrameWidget(groupId);
|
||||||
if (!msgWidget) {
|
|
||||||
|
if (!msgWidget)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
|
||||||
|
|
||||||
int index = ui->messageTabWidget->addTab(msgWidget, msgWidget->groupName(true));
|
int index = ui->messageTabWidget->addTab(msgWidget, msgWidget->groupName(true));
|
||||||
ui->messageTabWidget->setTabIcon(index, msgWidget->groupIcon());
|
ui->messageTabWidget->setTabIcon(index, msgWidget->groupIcon());
|
||||||
@ -810,40 +812,44 @@ GxsCommentDialog *GxsGroupFrameDialog::commentWidget(const RsGxsMessageId& msgId
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GxsGroupFrameDialog::changedCurrentGroup(const QString &groupId)
|
void GxsGroupFrameDialog::changedCurrentGroup(const QString& groupId)
|
||||||
{
|
{
|
||||||
if (mInFill) {
|
if (mInFill) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (groupId.isEmpty()) {
|
if (groupId.isEmpty())
|
||||||
if (mMessageWidget) {
|
{
|
||||||
mMessageWidget->setGroupId(RsGxsGroupId());
|
auto w = currentWidget();
|
||||||
ui->messageTabWidget->setCurrentWidget(mMessageWidget);
|
|
||||||
}
|
if(w)
|
||||||
|
w->setGroupId(RsGxsGroupId());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mGroupId = RsGxsGroupId(groupId.toStdString());
|
mGroupId = RsGxsGroupId(groupId.toStdString());
|
||||||
if (mGroupId.isNull()) {
|
|
||||||
|
if (mGroupId.isNull())
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
/* search exisiting tab */
|
/* search exisiting tab */
|
||||||
GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId, true);
|
GxsMessageFrameWidget *msgWidget = messageWidget(mGroupId);
|
||||||
|
|
||||||
if (!msgWidget) {
|
// check that we have at least one tab
|
||||||
if (mMessageWidget) {
|
|
||||||
/* not found, use standard tab */
|
|
||||||
msgWidget = mMessageWidget;
|
|
||||||
msgWidget->setGroupId(mGroupId);
|
|
||||||
} else {
|
|
||||||
/* create new tab */
|
|
||||||
msgWidget = createMessageWidget(mGroupId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if(msgWidget)
|
||||||
ui->messageTabWidget->setCurrentWidget(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)
|
void GxsGroupFrameDialog::groupTreeMiddleButtonClicked(QTreeWidgetItem *item)
|
||||||
@ -863,37 +869,31 @@ void GxsGroupFrameDialog::openGroupInNewTab(const RsGxsGroupId &groupId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* search exisiting tab */
|
/* search exisiting tab */
|
||||||
GxsMessageFrameWidget *msgWidget = messageWidget(groupId, true);
|
GxsMessageFrameWidget *msgWidget = createMessageWidget(groupId);
|
||||||
if (!msgWidget) {
|
|
||||||
/* not found, create new tab */
|
|
||||||
msgWidget = createMessageWidget(groupId);
|
|
||||||
}
|
|
||||||
|
|
||||||
ui->messageTabWidget->setCurrentWidget(msgWidget);
|
ui->messageTabWidget->setCurrentWidget(msgWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GxsGroupFrameDialog::messageTabCloseRequested(int index)
|
void GxsGroupFrameDialog::messageTabCloseRequested(int index)
|
||||||
{
|
{
|
||||||
QWidget *widget = ui->messageTabWidget->widget(index);
|
if(ui->messageTabWidget->count() == 1) /* Don't close single tab */
|
||||||
if (!widget) {
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
GxsMessageFrameWidget *msgWidget = dynamic_cast<GxsMessageFrameWidget*>(widget);
|
GxsMessageFrameWidget *msgWidget = dynamic_cast<GxsMessageFrameWidget*>(ui->messageTabWidget->widget(index));
|
||||||
if (msgWidget && msgWidget == mMessageWidget) {
|
delete msgWidget ;
|
||||||
/* Don't close single tab */
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(widget);
|
GxsMessageFrameWidget *GxsGroupFrameDialog::currentWidget() const
|
||||||
|
{
|
||||||
|
return dynamic_cast<GxsMessageFrameWidget*>(ui->messageTabWidget->widget(ui->messageTabWidget->currentIndex()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GxsGroupFrameDialog::messageTabChanged(int index)
|
void GxsGroupFrameDialog::messageTabChanged(int index)
|
||||||
{
|
{
|
||||||
GxsMessageFrameWidget *msgWidget = dynamic_cast<GxsMessageFrameWidget*>(ui->messageTabWidget->widget(index));
|
GxsMessageFrameWidget *msgWidget = dynamic_cast<GxsMessageFrameWidget*>(ui->messageTabWidget->widget(index));
|
||||||
if (!msgWidget) {
|
|
||||||
|
if (!msgWidget)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
ui->groupTreeWidget->activateId(QString::fromStdString(msgWidget->groupId().toStdString()), false);
|
ui->groupTreeWidget->activateId(QString::fromStdString(msgWidget->groupId().toStdString()), false);
|
||||||
}
|
}
|
||||||
|
@ -181,7 +181,7 @@ private:
|
|||||||
|
|
||||||
// subscribe/unsubscribe ack.
|
// subscribe/unsubscribe ack.
|
||||||
|
|
||||||
GxsMessageFrameWidget *messageWidget(const RsGxsGroupId &groupId, bool ownTab);
|
GxsMessageFrameWidget *messageWidget(const RsGxsGroupId &groupId);
|
||||||
GxsMessageFrameWidget *createMessageWidget(const RsGxsGroupId &groupId);
|
GxsMessageFrameWidget *createMessageWidget(const RsGxsGroupId &groupId);
|
||||||
|
|
||||||
GxsCommentDialog *commentWidget(const RsGxsMessageId &msgId);
|
GxsCommentDialog *commentWidget(const RsGxsMessageId &msgId);
|
||||||
@ -193,13 +193,15 @@ protected:
|
|||||||
bool mCountChildMsgs; // Count unread child messages?
|
bool mCountChildMsgs; // Count unread child messages?
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
GxsMessageFrameWidget *currentWidget() const;
|
||||||
|
bool useTabs();
|
||||||
|
|
||||||
bool mInitialized;
|
bool mInitialized;
|
||||||
bool mInFill;
|
bool mInFill;
|
||||||
bool mDistSyncAllowed;
|
bool mDistSyncAllowed;
|
||||||
QString mSettingsName;
|
QString mSettingsName;
|
||||||
RsGxsGroupId mGroupId;
|
RsGxsGroupId mGroupId;
|
||||||
RsGxsIfaceHelper *mInterface;
|
RsGxsIfaceHelper *mInterface;
|
||||||
GxsMessageFrameWidget *mMessageWidget;
|
|
||||||
|
|
||||||
QTreeWidgetItem *mYourGroups;
|
QTreeWidgetItem *mYourGroups;
|
||||||
QTreeWidgetItem *mSubscribedGroups;
|
QTreeWidgetItem *mSubscribedGroups;
|
||||||
|
@ -55,6 +55,7 @@ signals:
|
|||||||
void groupChanged(QWidget *widget);
|
void groupChanged(QWidget *widget);
|
||||||
void waitingChanged(QWidget *widget);
|
void waitingChanged(QWidget *widget);
|
||||||
void loadComment(const RsGxsGroupId &groupId, const QVector<RsGxsMessageId>& msg_versions,const RsGxsMessageId &msgId, const QString &title);
|
void loadComment(const RsGxsGroupId &groupId, const QVector<RsGxsMessageId>& msg_versions,const RsGxsMessageId &msgId, const QString &title);
|
||||||
|
void groupDataLoaded();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void setAllMessagesReadDo(bool read, uint32_t &token) = 0;
|
virtual void setAllMessagesReadDo(bool read, uint32_t &token) = 0;
|
||||||
|
@ -72,6 +72,7 @@ CreateGxsChannelMsg::CreateGxsChannelMsg(const RsGxsGroupId &cId, RsGxsMessageId
|
|||||||
|
|
||||||
connect(addFileButton, SIGNAL(clicked() ), this , SLOT(addExtraFile()));
|
connect(addFileButton, SIGNAL(clicked() ), this , SLOT(addExtraFile()));
|
||||||
connect(addfilepushButton, 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(addThumbnailButton, SIGNAL(clicked() ), this , SLOT(addThumbnail()));
|
||||||
connect(thumbNailCb, SIGNAL(toggled(bool)), this, SLOT(allowAutoMediaThumbNail(bool)));
|
connect(thumbNailCb, SIGNAL(toggled(bool)), this, SLOT(allowAutoMediaThumbNail(bool)));
|
||||||
@ -605,6 +606,11 @@ void CreateGxsChannelMsg::saveChannelInfo(const RsGroupMetaData &meta)
|
|||||||
subjectEdit->setFocus();
|
subjectEdit->setFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CreateGxsChannelMsg::updatePreviewText(const QString& s)
|
||||||
|
{
|
||||||
|
preview_W->setText(s);
|
||||||
|
}
|
||||||
|
|
||||||
void CreateGxsChannelMsg::sendMsg()
|
void CreateGxsChannelMsg::sendMsg()
|
||||||
{
|
{
|
||||||
#ifdef DEBUG_CREATE_GXS_MSG
|
#ifdef DEBUG_CREATE_GXS_MSG
|
||||||
@ -717,7 +723,7 @@ void CreateGxsChannelMsg::sendMessage(const std::string &subject, const std::str
|
|||||||
|
|
||||||
void CreateGxsChannelMsg::addThumbnail()
|
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())
|
if (img.isNull())
|
||||||
return;
|
return;
|
||||||
@ -725,7 +731,7 @@ void CreateGxsChannelMsg::addThumbnail()
|
|||||||
picture = img;
|
picture = img;
|
||||||
|
|
||||||
// to show the selected
|
// to show the selected
|
||||||
thumbnail_label->setPixmap(picture);
|
preview_W->setPixmap(picture);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateGxsChannelMsg::loadOriginalChannelPostInfo()
|
void CreateGxsChannelMsg::loadOriginalChannelPostInfo()
|
||||||
@ -769,7 +775,7 @@ void CreateGxsChannelMsg::loadOriginalChannelPostInfo()
|
|||||||
if(post.mThumbnail.mData != NULL)
|
if(post.mThumbnail.mData != NULL)
|
||||||
{
|
{
|
||||||
GxsIdDetails::loadPixmapFromData(post.mThumbnail.mData,post.mThumbnail.mSize,picture,GxsIdDetails::ORIGINAL);
|
GxsIdDetails::loadPixmapFromData(post.mThumbnail.mData,post.mThumbnail.mSize,picture,GxsIdDetails::ORIGINAL);
|
||||||
thumbnail_label->setPixmap(picture);
|
preview_W->setPixmap(picture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ private slots:
|
|||||||
void addExtraFile();
|
void addExtraFile();
|
||||||
void checkAttachmentReady();
|
void checkAttachmentReady();
|
||||||
void deleteAttachment();
|
void deleteAttachment();
|
||||||
|
void updatePreviewText(const QString &);
|
||||||
|
|
||||||
void cancelMsg();
|
void cancelMsg();
|
||||||
void sendMsg();
|
void sendMsg();
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>671</width>
|
<width>736</width>
|
||||||
<height>513</height>
|
<height>271</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="acceptDrops">
|
<property name="acceptDrops">
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<iconset resource="../images.qrc">
|
<iconset resource="../images.qrc">
|
||||||
<normaloff>:/images/logo/logo_16.png</normaloff>:/images/logo/logo_16.png</iconset>
|
<normaloff>:/images/logo/logo_16.png</normaloff>:/images/logo/logo_16.png</iconset>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="CreateGxsChannelMsgGLayout">
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
<property name="leftMargin">
|
<property name="leftMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
@ -33,7 +33,10 @@
|
|||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="spacing">
|
<property name="horizontalSpacing">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
|
<property name="verticalSpacing">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item row="0" column="0">
|
<item row="0" column="0">
|
||||||
@ -52,53 +55,15 @@
|
|||||||
<enum>QFrame::Raised</enum>
|
<enum>QFrame::Raised</enum>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QGridLayout" name="gridLayout_4">
|
<layout class="QGridLayout" name="gridLayout_4">
|
||||||
<item row="0" column="0">
|
<property name="topMargin">
|
||||||
<widget class="QPushButton" name="channelpostButton">
|
<number>3</number>
|
||||||
<property name="text">
|
|
||||||
<string>Channel Post</string>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="icon">
|
<property name="horizontalSpacing">
|
||||||
<iconset resource="../icons.qrc">
|
<number>6</number>
|
||||||
<normaloff>:/icons/png/comment.png</normaloff>:/icons/png/comment.png</iconset>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="iconSize">
|
<property name="verticalSpacing">
|
||||||
<size>
|
<number>0</number>
|
||||||
<width>24</width>
|
|
||||||
<height>24</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
</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">
|
<item row="1" column="0" colspan="3">
|
||||||
<widget class="QStackedWidget" name="stackedWidget">
|
<widget class="QStackedWidget" name="stackedWidget">
|
||||||
<property name="mouseTracking">
|
<property name="mouseTracking">
|
||||||
@ -114,7 +79,10 @@
|
|||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="stackedWidgetPage1">
|
<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">
|
<property name="leftMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
@ -127,116 +95,27 @@
|
|||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<item row="1" column="0">
|
<item>
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
<item row="0" column="0" rowspan="2">
|
<property name="spacing">
|
||||||
<widget class="QLabel" name="thumbnail_label">
|
<number>6</number>
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>156</width>
|
|
||||||
<height>107</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="sizeIncrement">
|
<property name="sizeConstraint">
|
||||||
<size>
|
<enum>QLayout::SetFixedSize</enum>
|
||||||
<width>0</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
</property>
|
||||||
<property name="pixmap">
|
<item>
|
||||||
<pixmap resource="../images.qrc">:/images/thumb-default-video.png</pixmap>
|
<widget class="ChannelPostThumbnailView" name="preview_W" native="true"/>
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
</item>
|
||||||
<item row="0" column="1" colspan="4">
|
<item>
|
||||||
<widget class="QLabel" name="channelAttachLabel">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<property name="text">
|
<property name="spacing">
|
||||||
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
<number>6</number>
|
||||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
|
||||||
p, li { white-space: pre-wrap; }
|
|
||||||
</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;">
|
|
||||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; font-weight:600;">Attachments:</span></p>
|
|
||||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/images/feedback_arrow.png" /><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt;"> Use Drag and Drop / Add Files button, to Hash new files.</span></p>
|
|
||||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/images/feedback_arrow.png" /><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt;"> Copy/Paste RetroShare links from your shares</span></p></body></html></string>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
<item>
|
||||||
</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">
|
|
||||||
<layout class="QHBoxLayout" name="channelNameHLayout">
|
<layout class="QHBoxLayout" name="channelNameHLayout">
|
||||||
|
<property name="topMargin">
|
||||||
|
<number>6</number>
|
||||||
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="channelNameLabel">
|
<widget class="QLabel" name="channelNameLabel">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@ -268,6 +147,107 @@ p, li { white-space: pre-wrap; }
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="channelAttachLabel">
|
||||||
|
<property name="text">
|
||||||
|
<string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||||
|
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||||
|
p, li { white-space: pre-wrap; }
|
||||||
|
</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||||
|
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:10pt; font-weight:600;">Attachments:</span></p>
|
||||||
|
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/images/feedback_arrow.png" /><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt;"> Use Drag and Drop / Add Files button, to Hash new files.</span></p>
|
||||||
|
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/images/feedback_arrow.png" /><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt;"> Copy/Paste RetroShare links from your shares</span></p></body></html></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>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="stackedWidgetPage2">
|
<widget class="QWidget" name="stackedWidgetPage2">
|
||||||
@ -276,7 +256,7 @@ p, li { white-space: pre-wrap; }
|
|||||||
<number>0</number>
|
<number>0</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="topMargin">
|
<property name="topMargin">
|
||||||
<number>0</number>
|
<number>6</number>
|
||||||
</property>
|
</property>
|
||||||
<property name="rightMargin">
|
<property name="rightMargin">
|
||||||
<number>0</number>
|
<number>0</number>
|
||||||
@ -329,7 +309,7 @@ p, li { white-space: pre-wrap; }
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>632</width>
|
<width>81</width>
|
||||||
<height>24</height>
|
<height>24</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
@ -403,6 +383,53 @@ p, li { white-space: pre-wrap; }
|
|||||||
</widget>
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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">
|
<item row="2" column="0" colspan="3">
|
||||||
<layout class="QHBoxLayout" name="buttonHLayout">
|
<layout class="QHBoxLayout" name="buttonHLayout">
|
||||||
<item>
|
<item>
|
||||||
@ -455,10 +482,16 @@ p, li { white-space: pre-wrap; }
|
|||||||
<header>util/RichTextEdit.h</header>
|
<header>util/RichTextEdit.h</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>ChannelPostThumbnailView</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>gui/gxschannels/GxsChannelPostThumbnail.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../icons.qrc"/>
|
|
||||||
<include location="../images.qrc"/>
|
<include location="../images.qrc"/>
|
||||||
|
<include location="../icons.qrc"/>
|
||||||
</resources>
|
</resources>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
#include "GxsChannelDialog.h"
|
#include "GxsChannelDialog.h"
|
||||||
#include "GxsChannelGroupDialog.h"
|
#include "GxsChannelGroupDialog.h"
|
||||||
#include "GxsChannelPostsWidget.h"
|
#include "GxsChannelPostsWidgetWithModel.h"
|
||||||
#include "CreateGxsChannelMsg.h"
|
#include "CreateGxsChannelMsg.h"
|
||||||
#include "GxsChannelUserNotify.h"
|
#include "GxsChannelUserNotify.h"
|
||||||
#include "gui/gxs/GxsGroupShareKey.h"
|
#include "gui/gxs/GxsGroupShareKey.h"
|
||||||
@ -204,7 +204,7 @@ int GxsChannelDialog::shareKeyType()
|
|||||||
|
|
||||||
GxsMessageFrameWidget *GxsChannelDialog::createMessageFrameWidget(const RsGxsGroupId &groupId)
|
GxsMessageFrameWidget *GxsChannelDialog::createMessageFrameWidget(const RsGxsGroupId &groupId)
|
||||||
{
|
{
|
||||||
return new GxsChannelPostsWidget(groupId);
|
return new GxsChannelPostsWidgetWithModel(groupId,this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GxsChannelDialog::setDefaultDirectory()
|
void GxsChannelDialog::setDefaultDirectory()
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
* *
|
* *
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#include <QMenu>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
@ -27,11 +28,13 @@
|
|||||||
#include "GxsChannelFilesStatusWidget.h"
|
#include "GxsChannelFilesStatusWidget.h"
|
||||||
#include "ui_GxsChannelFilesStatusWidget.h"
|
#include "ui_GxsChannelFilesStatusWidget.h"
|
||||||
#include "gui/common/RsUrlHandler.h"
|
#include "gui/common/RsUrlHandler.h"
|
||||||
|
#include "gui/common/FilesDefs.h"
|
||||||
|
#include "util/misc.h"
|
||||||
|
|
||||||
#include "retroshare/rsfiles.h"
|
#include "retroshare/rsfiles.h"
|
||||||
|
|
||||||
GxsChannelFilesStatusWidget::GxsChannelFilesStatusWidget(const RsGxsGroupId &groupId, const RsGxsMessageId &messageId, const RsGxsFile &file, QWidget *parent) :
|
GxsChannelFilesStatusWidget::GxsChannelFilesStatusWidget(const RsGxsFile &file, QWidget *parent) :
|
||||||
QWidget(parent), mGroupId(groupId), mMessageId(messageId), mFile(file), ui(new Ui::GxsChannelFilesStatusWidget)
|
QWidget(parent), mFile(file), ui(new Ui::GxsChannelFilesStatusWidget)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
@ -40,11 +43,21 @@ GxsChannelFilesStatusWidget::GxsChannelFilesStatusWidget(const RsGxsGroupId &gro
|
|||||||
setSize(mFile.mSize);
|
setSize(mFile.mSize);
|
||||||
|
|
||||||
/* Connect signals */
|
/* 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->resumeToolButton, SIGNAL(clicked()), this, SLOT(resume()));
|
||||||
connect(ui->pauseToolButton, SIGNAL(clicked()), this, SLOT(pause()));
|
connect(ui->pauseToolButton, SIGNAL(clicked()), this, SLOT(pause()));
|
||||||
connect(ui->cancelToolButton, SIGNAL(clicked()), this, SLOT(cancel()));
|
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();
|
check();
|
||||||
}
|
}
|
||||||
@ -80,6 +93,17 @@ void GxsChannelFilesStatusWidget::check()
|
|||||||
if (rsFiles->alreadyHaveFile(mFile.mHash, fileInfo)) {
|
if (rsFiles->alreadyHaveFile(mFile.mHash, fileInfo)) {
|
||||||
mState = STATE_LOCAL;
|
mState = STATE_LOCAL;
|
||||||
setSize(fileInfo.size);
|
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 {
|
} else {
|
||||||
FileInfo fileInfo;
|
FileInfo fileInfo;
|
||||||
bool detailsOk = rsFiles->FileDetails(mFile.mHash, RS_FILE_HINTS_DOWNLOAD | RS_FILE_HINTS_SPEC_ONLY, 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:
|
case STATE_ERROR:
|
||||||
repeat = 0;
|
repeat = 0;
|
||||||
|
|
||||||
ui->downloadToolButton->hide();
|
ui->downloadPushButton->hide();
|
||||||
ui->resumeToolButton->hide();
|
ui->resumeToolButton->hide();
|
||||||
ui->pauseToolButton->hide();
|
ui->pauseToolButton->hide();
|
||||||
ui->cancelToolButton->hide();
|
ui->cancelToolButton->hide();
|
||||||
ui->progressBar->hide();
|
ui->progressBar->hide();
|
||||||
|
ui->openFilePushButton->hide();
|
||||||
ui->openFolderToolButton->hide();
|
ui->openFolderToolButton->hide();
|
||||||
|
|
||||||
statusText = tr("Error");
|
statusText = tr("Error");
|
||||||
@ -140,11 +165,12 @@ void GxsChannelFilesStatusWidget::check()
|
|||||||
case STATE_REMOTE:
|
case STATE_REMOTE:
|
||||||
repeat = 30000;
|
repeat = 30000;
|
||||||
|
|
||||||
ui->downloadToolButton->show();
|
ui->downloadPushButton->show();
|
||||||
ui->resumeToolButton->hide();
|
ui->resumeToolButton->hide();
|
||||||
ui->pauseToolButton->hide();
|
ui->pauseToolButton->hide();
|
||||||
ui->cancelToolButton->hide();
|
ui->cancelToolButton->hide();
|
||||||
ui->progressBar->hide();
|
ui->progressBar->hide();
|
||||||
|
ui->openFilePushButton->hide();
|
||||||
ui->openFolderToolButton->hide();
|
ui->openFolderToolButton->hide();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -152,11 +178,12 @@ void GxsChannelFilesStatusWidget::check()
|
|||||||
case STATE_DOWNLOAD:
|
case STATE_DOWNLOAD:
|
||||||
repeat = 1000;
|
repeat = 1000;
|
||||||
|
|
||||||
ui->downloadToolButton->hide();
|
ui->downloadPushButton->hide();
|
||||||
ui->resumeToolButton->hide();
|
ui->resumeToolButton->hide();
|
||||||
ui->pauseToolButton->show();
|
ui->pauseToolButton->show();
|
||||||
ui->cancelToolButton->show();
|
ui->cancelToolButton->show();
|
||||||
ui->progressBar->show();
|
ui->progressBar->show();
|
||||||
|
ui->openFilePushButton->hide();
|
||||||
ui->openFolderToolButton->hide();
|
ui->openFolderToolButton->hide();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -164,11 +191,12 @@ void GxsChannelFilesStatusWidget::check()
|
|||||||
case STATE_PAUSED:
|
case STATE_PAUSED:
|
||||||
repeat = 1000;
|
repeat = 1000;
|
||||||
|
|
||||||
ui->downloadToolButton->hide();
|
ui->downloadPushButton->hide();
|
||||||
ui->resumeToolButton->show();
|
ui->resumeToolButton->show();
|
||||||
ui->pauseToolButton->hide();
|
ui->pauseToolButton->hide();
|
||||||
ui->cancelToolButton->show();
|
ui->cancelToolButton->show();
|
||||||
ui->progressBar->hide();
|
ui->progressBar->hide();
|
||||||
|
ui->openFilePushButton->hide();
|
||||||
ui->openFolderToolButton->hide();
|
ui->openFolderToolButton->hide();
|
||||||
|
|
||||||
statusText = tr("Paused");
|
statusText = tr("Paused");
|
||||||
@ -178,11 +206,12 @@ void GxsChannelFilesStatusWidget::check()
|
|||||||
case STATE_WAITING:
|
case STATE_WAITING:
|
||||||
repeat = 1000;
|
repeat = 1000;
|
||||||
|
|
||||||
ui->downloadToolButton->hide();
|
ui->downloadPushButton->hide();
|
||||||
ui->resumeToolButton->hide();
|
ui->resumeToolButton->hide();
|
||||||
ui->pauseToolButton->show();
|
ui->pauseToolButton->show();
|
||||||
ui->cancelToolButton->show();
|
ui->cancelToolButton->show();
|
||||||
ui->progressBar->hide();
|
ui->progressBar->hide();
|
||||||
|
ui->openFilePushButton->hide();
|
||||||
ui->openFolderToolButton->hide();
|
ui->openFolderToolButton->hide();
|
||||||
|
|
||||||
statusText = tr("Waiting");
|
statusText = tr("Waiting");
|
||||||
@ -192,11 +221,12 @@ void GxsChannelFilesStatusWidget::check()
|
|||||||
case STATE_CHECKING:
|
case STATE_CHECKING:
|
||||||
repeat = 1000;
|
repeat = 1000;
|
||||||
|
|
||||||
ui->downloadToolButton->hide();
|
ui->downloadPushButton->hide();
|
||||||
ui->resumeToolButton->hide();
|
ui->resumeToolButton->hide();
|
||||||
ui->pauseToolButton->hide();
|
ui->pauseToolButton->hide();
|
||||||
ui->cancelToolButton->show();
|
ui->cancelToolButton->show();
|
||||||
ui->progressBar->hide();
|
ui->progressBar->hide();
|
||||||
|
ui->openFilePushButton->hide();
|
||||||
ui->openFolderToolButton->hide();
|
ui->openFolderToolButton->hide();
|
||||||
|
|
||||||
statusText = tr("Checking");
|
statusText = tr("Checking");
|
||||||
@ -206,11 +236,12 @@ void GxsChannelFilesStatusWidget::check()
|
|||||||
case STATE_LOCAL:
|
case STATE_LOCAL:
|
||||||
repeat = 60000;
|
repeat = 60000;
|
||||||
|
|
||||||
ui->downloadToolButton->hide();
|
ui->downloadPushButton->hide();
|
||||||
ui->resumeToolButton->hide();
|
ui->resumeToolButton->hide();
|
||||||
ui->pauseToolButton->hide();
|
ui->pauseToolButton->hide();
|
||||||
ui->cancelToolButton->hide();
|
ui->cancelToolButton->hide();
|
||||||
ui->progressBar->hide();
|
ui->progressBar->hide();
|
||||||
|
ui->openFilePushButton->show();
|
||||||
ui->openFolderToolButton->show();
|
ui->openFolderToolButton->show();
|
||||||
|
|
||||||
break;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -34,7 +34,7 @@ class GxsChannelFilesStatusWidget : public QWidget
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GxsChannelFilesStatusWidget(const RsGxsGroupId &groupId, const RsGxsMessageId &messageId, const RsGxsFile &file, QWidget *parent = 0);
|
explicit GxsChannelFilesStatusWidget(const RsGxsFile &file, QWidget *parent = 0);
|
||||||
~GxsChannelFilesStatusWidget();
|
~GxsChannelFilesStatusWidget();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
@ -44,6 +44,7 @@ private slots:
|
|||||||
void pause();
|
void pause();
|
||||||
void resume();
|
void resume();
|
||||||
void openFolder();
|
void openFolder();
|
||||||
|
void openFile();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setSize(uint64_t size);
|
void setSize(uint64_t size);
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>367</width>
|
<width>421</width>
|
||||||
<height>27</height>
|
<height>29</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -48,7 +48,7 @@
|
|||||||
<number>2</number>
|
<number>2</number>
|
||||||
</property>
|
</property>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QToolButton" name="downloadToolButton">
|
<widget class="QPushButton" name="downloadPushButton">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
@ -126,18 +126,31 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QToolButton" name="openFolderToolButton">
|
<widget class="QPushButton" name="openFilePushButton">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</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">
|
<property name="focusPolicy">
|
||||||
<enum>Qt::NoFocus</enum>
|
<enum>Qt::NoFocus</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="text">
|
<property name="popupMode">
|
||||||
<string>Open folder</string>
|
<enum>QToolButton::InstantPopup</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@ -148,6 +161,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="../images.qrc"/>
|
<include location="../images.qrc"/>
|
||||||
|
<include location="../icons.qrc"/>
|
||||||
</resources>
|
</resources>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
@ -86,7 +86,7 @@ GxsChannelFilesWidget::~GxsChannelFilesWidget()
|
|||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GxsChannelFilesWidget::addFiles(const RsGxsChannelPost &post, bool related)
|
void GxsChannelFilesWidget::addFiles(const RsGxsChannelPost& post, bool related)
|
||||||
{
|
{
|
||||||
if (related) {
|
if (related) {
|
||||||
removeItems(post.mMeta.mGroupId, post.mMeta.mMsgId);
|
removeItems(post.mMeta.mGroupId, post.mMeta.mMsgId);
|
||||||
@ -113,7 +113,7 @@ void GxsChannelFilesWidget::addFiles(const RsGxsChannelPost &post, bool related)
|
|||||||
|
|
||||||
ui->treeWidget->addTopLevelItem(treeItem);
|
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);
|
ui->treeWidget->setItemWidget(treeItem, COLUMN_STATUS, statusWidget);
|
||||||
|
|
||||||
filterItem(treeItem);
|
filterItem(treeItem);
|
||||||
|
476
retroshare-gui/src/gui/gxschannels/GxsChannelPostFilesModel.cpp
Normal file
476
retroshare-gui/src/gui/gxschannels/GxsChannelPostFilesModel.cpp
Normal 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();
|
||||||
|
}
|
173
retroshare-gui/src/gui/gxschannels/GxsChannelPostFilesModel.h
Normal file
173
retroshare-gui/src/gui/gxschannels/GxsChannelPostFilesModel.h
Normal 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;
|
||||||
|
};
|
125
retroshare-gui/src/gui/gxschannels/GxsChannelPostThumbnail.h
Normal file
125
retroshare-gui/src/gui/gxschannels/GxsChannelPostThumbnail.h
Normal 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;
|
||||||
|
};
|
||||||
|
|
736
retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.cpp
Normal file
736
retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.cpp
Normal 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();
|
||||||
|
}
|
||||||
|
|
239
retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.h
Normal file
239
retroshare-gui/src/gui/gxschannels/GxsChannelPostsModel.h
Normal 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;
|
||||||
|
};
|
@ -551,13 +551,9 @@ void GxsChannelPostsWidget::createPostItem(const RsGxsChannelPost& post, bool re
|
|||||||
ui->feedWidget->addFeedItem(item, ROLE_PUBLISH, QDateTime::fromTime_t(meta.mPublishTs));
|
ui->feedWidget->addFeedItem(item, ROLE_PUBLISH, QDateTime::fromTime_t(meta.mPublishTs));
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef TODO
|
|
||||||
ui->fileWidget->addFiles(post, related);
|
ui->fileWidget->addFiles(post, related);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void GxsChannelPostsWidget::fillThreadCreatePost(const QVariant &post, bool related, int current, int count)
|
void GxsChannelPostsWidget::fillThreadCreatePost(const QVariant &post, bool related, int current, int count)
|
||||||
{
|
{
|
||||||
/* show fill progress */
|
/* show fill progress */
|
||||||
|
@ -98,7 +98,7 @@ private:
|
|||||||
int viewMode();
|
int viewMode();
|
||||||
|
|
||||||
void insertChannelDetails(const RsGxsChannelGroup &group);
|
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 createPostItem(const RsGxsChannelPost &post, bool related);
|
||||||
void handleEvent_main_thread(std::shared_ptr<const RsEvent> event);
|
void handleEvent_main_thread(std::shared_ptr<const RsEvent> event);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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
|
@ -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"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||||
|
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||||
|
p, li { white-space: pre-wrap; }
|
||||||
|
</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;">
|
||||||
|
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt;">Description</span></p></body></html></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>
|
@ -123,9 +123,9 @@ public:
|
|||||||
{
|
{
|
||||||
default:
|
default:
|
||||||
case 3:
|
case 3:
|
||||||
case 0: icon = QIcon(IMAGE_VOID); break;
|
case 0: icon = FilesDefs::getIconFromQtResourcePath(IMAGE_VOID); break;
|
||||||
case 1: icon = QIcon(IMAGE_WARNING_YELLOW); break;
|
case 1: icon = FilesDefs::getIconFromQtResourcePath(IMAGE_WARNING_YELLOW); break;
|
||||||
case 2: icon = QIcon(IMAGE_WARNING_RED); break;
|
case 2: icon = FilesDefs::getIconFromQtResourcePath(IMAGE_WARNING_RED); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap pix = icon.pixmap(r.size());
|
QPixmap pix = icon.pixmap(r.size());
|
||||||
@ -172,9 +172,9 @@ public:
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (unread)
|
if (unread)
|
||||||
icon = QIcon(":/images/message-state-unread.png");
|
icon = FilesDefs::getIconFromQtResourcePath(":/images/message-state-unread.png");
|
||||||
else
|
else
|
||||||
icon = QIcon(":/images/message-state-read.png");
|
icon = FilesDefs::getIconFromQtResourcePath(":/images/message-state-read.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap pix = icon.pixmap(r.size());
|
QPixmap pix = icon.pixmap(r.size());
|
||||||
@ -462,7 +462,7 @@ QString GxsForumThreadWidget::groupName(bool withUnreadCount)
|
|||||||
QIcon GxsForumThreadWidget::groupIcon()
|
QIcon GxsForumThreadWidget::groupIcon()
|
||||||
{
|
{
|
||||||
if (mNewCount) {
|
if (mNewCount) {
|
||||||
return QIcon(":/images/message-state-new.png");
|
return FilesDefs::getIconFromQtResourcePath(":/images/message-state-new.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
return QIcon();
|
return QIcon();
|
||||||
@ -498,6 +498,7 @@ void GxsForumThreadWidget::recursRestoreExpandedItems(const QModelIndex& /*index
|
|||||||
ui->threadTreeWidget->setExpanded( mThreadProxyModel->mapFromSource(mThreadModel->getIndexOfMessage(*it)) ,true) ;
|
ui->threadTreeWidget->setExpanded( mThreadProxyModel->mapFromSource(mThreadModel->getIndexOfMessage(*it)) ,true) ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GxsForumThreadWidget::updateDisplay(bool complete)
|
void GxsForumThreadWidget::updateDisplay(bool complete)
|
||||||
{
|
{
|
||||||
#ifdef DEBUG_FORUMS
|
#ifdef DEBUG_FORUMS
|
||||||
@ -560,35 +561,35 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/)
|
|||||||
#ifdef DEBUG_FORUMS
|
#ifdef DEBUG_FORUMS
|
||||||
std::cerr << "Clicked on msg " << current_post.mMsgId << std::endl;
|
std::cerr << "Clicked on msg " << current_post.mMsgId << std::endl;
|
||||||
#endif
|
#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()));
|
connect(editAct, SIGNAL(triggered()), this, SLOT(editforummessage()));
|
||||||
|
|
||||||
bool is_pinned = mForumGroup.mPinnedPosts.ids.find(mThreadId) != mForumGroup.mPinnedPosts.ids.end();
|
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()));
|
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()));
|
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()));
|
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->setToolTip(tr("This will block/hide messages from this person, and notify friend nodes.")) ;
|
||||||
flagaspositiveAct->setData(static_cast<uint32_t>(RsOpinion::POSITIVE));
|
flagaspositiveAct->setData(static_cast<uint32_t>(RsOpinion::POSITIVE));
|
||||||
connect(flagaspositiveAct, SIGNAL(triggered()), this, SLOT(flagperson()));
|
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->setToolTip(tr("Doing this, you trust your friends to decide to forward this message or not.")) ;
|
||||||
flagasneutralAct->setData(static_cast<uint32_t>(RsOpinion::NEUTRAL));
|
flagasneutralAct->setData(static_cast<uint32_t>(RsOpinion::NEUTRAL));
|
||||||
connect(flagasneutralAct, SIGNAL(triggered()), this, SLOT(flagperson()));
|
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->setToolTip(tr("This will block/hide messages from this person, and notify friend nodes.")) ;
|
||||||
flagasnegativeAct->setData(static_cast<uint32_t>(RsOpinion::NEGATIVE));
|
flagasnegativeAct->setData(static_cast<uint32_t>(RsOpinion::NEGATIVE));
|
||||||
connect(flagasnegativeAct, SIGNAL(triggered()), this, SLOT(flagperson()));
|
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));
|
newthreadAct->setEnabled (IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags));
|
||||||
connect(newthreadAct , SIGNAL(triggered()), this, SLOT(createthread()));
|
connect(newthreadAct , SIGNAL(triggered()), this, SLOT(createthread()));
|
||||||
|
|
||||||
@ -598,19 +599,19 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/)
|
|||||||
QAction* collapseAll = new QAction(tr( "Collapse all"), &contextMnu);
|
QAction* collapseAll = new QAction(tr( "Collapse all"), &contextMnu);
|
||||||
connect(collapseAll, SIGNAL(triggered()), ui->threadTreeWidget, SLOT(collapseAll()));
|
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()));
|
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()));
|
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()));
|
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()));
|
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()));
|
connect(showinpeopleAct, SIGNAL(triggered()), this, SLOT(showInPeopleTab()));
|
||||||
|
|
||||||
if (IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags))
|
if (IS_GROUP_SUBSCRIBED(mForumGroup.mMeta.mSubscribeFlags))
|
||||||
@ -663,7 +664,7 @@ void GxsForumThreadWidget::threadListCustomPopupMenu(QPoint /*point*/)
|
|||||||
|
|
||||||
contextMnu.addAction(replyAct);
|
contextMnu.addAction(replyAct);
|
||||||
contextMnu.addAction(newthreadAct);
|
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());
|
action->setEnabled(!groupId().isNull() && !mThreadId.isNull());
|
||||||
contextMnu.addSeparator();
|
contextMnu.addSeparator();
|
||||||
contextMnu.addAction(markMsgAsRead);
|
contextMnu.addAction(markMsgAsRead);
|
||||||
@ -755,7 +756,7 @@ void GxsForumThreadWidget::togglethreadview_internal()
|
|||||||
{
|
{
|
||||||
// if (ui->expandButton->isChecked()) {
|
// if (ui->expandButton->isChecked()) {
|
||||||
ui->postText->setVisible(true);
|
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"));
|
ui->expandButton->setToolTip(tr("Hide"));
|
||||||
// } else {
|
// } else {
|
||||||
// ui->postText->setVisible(false);
|
// ui->postText->setVisible(false);
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
<file>icons/png/anonymous.png</file>
|
<file>icons/png/anonymous.png</file>
|
||||||
<file>icons/png/attach-image.png</file>
|
<file>icons/png/attach-image.png</file>
|
||||||
<file>icons/png/attach.png</file>
|
<file>icons/png/attach.png</file>
|
||||||
|
<file>icons/png/arrow.png</file>
|
||||||
<file>icons/png/cert.png</file>
|
<file>icons/png/cert.png</file>
|
||||||
<file>icons/png/channels-notify.png</file>
|
<file>icons/png/channels-notify.png</file>
|
||||||
<file>icons/png/channels.png</file>
|
<file>icons/png/channels.png</file>
|
||||||
|
BIN
retroshare-gui/src/gui/icons/png/arrow.png
Normal file
BIN
retroshare-gui/src/gui/icons/png/arrow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 935 B |
@ -745,12 +745,7 @@ GxsForumMsgItem QFrame#frame{
|
|||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
GxsChannelPostsWidget QFrame#infoFrame
|
GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton {
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
GxsChannelPostsWidget QToolButton#subscribeToolButton {
|
|
||||||
font: bold;
|
font: bold;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: white;
|
color: white;
|
||||||
@ -759,18 +754,18 @@ GxsChannelPostsWidget QToolButton#subscribeToolButton {
|
|||||||
max-height: 27px;
|
max-height: 27px;
|
||||||
}
|
}
|
||||||
|
|
||||||
GxsChannelPostsWidget QToolButton#subscribeToolButton:hover {
|
GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton:hover {
|
||||||
background: #03b1f3;
|
background: #03b1f3;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
GxsChannelPostsWidget QToolButton#subscribeToolButton:pressed {
|
GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton:pressed {
|
||||||
background: #03b1f3;
|
background: #03b1f3;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
GxsChannelPostsWidget QToolButton#subscribeToolButton:disabled {
|
GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton:disabled {
|
||||||
background: gray;
|
background: gray;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid gray;
|
border: 1px solid gray;
|
||||||
@ -778,19 +773,35 @@ GxsChannelPostsWidget QToolButton#subscribeToolButton:disabled {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* only for MenuButtonPopup */
|
/* only for MenuButtonPopup */
|
||||||
GxsChannelPostsWidget QToolButton#subscribeToolButton[popupMode="1"] {
|
GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton[popupMode="1"] {
|
||||||
padding-right: 0px;
|
padding-right: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
GxsChannelPostsWidget QToolButton#subscribeToolButton::menu-arrow {
|
GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton::menu-arrow {
|
||||||
image: none;
|
image: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
GxsChannelPostsWidget QToolButton#subscribeToolButton::menu-button {
|
GxsChannelPostsWidgetWithModel QToolButton#subscribeToolButton::menu-button {
|
||||||
image: none;
|
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{
|
GxsGroupDialog QLabel#groupLogo{
|
||||||
border: 2px solid #CCCCCC;
|
border: 2px solid #CCCCCC;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
@ -84,7 +84,7 @@ ConfCertDialog QLabel#servicePermissionsLabel
|
|||||||
qproperty-fontSizeFactor: 125;
|
qproperty-fontSizeFactor: 125;
|
||||||
}
|
}
|
||||||
|
|
||||||
GxsChannelPostsWidget QLabel#nameLabel
|
GxsChannelPostsWidgetWithModel QLabel#channelName_LB
|
||||||
{
|
{
|
||||||
qproperty-fontSizeFactor: 250;
|
qproperty-fontSizeFactor: 250;
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include <gui/notifyqt.h>
|
#include <gui/notifyqt.h>
|
||||||
#include "rshare.h"
|
#include "rshare.h"
|
||||||
#include "rsharesettings.h"
|
#include "rsharesettings.h"
|
||||||
|
#include "util/i2pcommon.h"
|
||||||
#include "util/RsNetUtil.h"
|
#include "util/RsNetUtil.h"
|
||||||
#include "util/misc.h"
|
#include "util/misc.h"
|
||||||
|
|
||||||
@ -82,6 +83,10 @@ ServerPage::ServerPage(QWidget * parent, Qt::WindowFlags flags)
|
|||||||
manager = NULL ;
|
manager = NULL ;
|
||||||
mOngoingConnectivityCheck = -1;
|
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::isHiddenNode())
|
||||||
{
|
{
|
||||||
if(RsAccounts::isTorAuto())
|
if(RsAccounts::isTorAuto())
|
||||||
@ -1352,7 +1357,7 @@ void ServerPage::updateInProxyIndicator()
|
|||||||
ui.iconlabel_service_incoming->setMovie(movie);
|
ui.iconlabel_service_incoming->setMovie(movie);
|
||||||
movie->start();
|
movie->start();
|
||||||
|
|
||||||
if (mHiddenType == RS_HIDDEN_TYPE_I2P && mBobSettings.enableBob) {
|
if (mHiddenType == RS_HIDDEN_TYPE_I2P && mBobSettings.enable) {
|
||||||
|
|
||||||
QTcpSocket tcpSocket;
|
QTcpSocket tcpSocket;
|
||||||
|
|
||||||
@ -1439,15 +1444,16 @@ void ServerPage::getNewKey()
|
|||||||
|
|
||||||
void ServerPage::loadKey()
|
void ServerPage::loadKey()
|
||||||
{
|
{
|
||||||
mBobSettings.keys = ui.pteBobServerKey->toPlainText().toStdString();
|
mBobSettings.address.privateKey = ui.pteBobServerKey->toPlainText().toStdString();
|
||||||
mBobSettings.addr = p3I2pBob::keyToBase32Addr(mBobSettings.keys);
|
mBobSettings.address.publicKey = i2p::publicKeyFromPrivate(mBobSettings.address.privateKey);
|
||||||
|
mBobSettings.address.base32 = i2p::keyToBase32Addr(mBobSettings.address.publicKey);
|
||||||
|
|
||||||
rsAutoProxyMonitor::taskSync(autoProxyType::I2PBOB, autoProxyTask::setSettings, &mBobSettings);
|
rsAutoProxyMonitor::taskSync(autoProxyType::I2PBOB, autoProxyTask::setSettings, &mBobSettings);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ServerPage::enableBob(bool checked)
|
void ServerPage::enableBob(bool checked)
|
||||||
{
|
{
|
||||||
mBobSettings.enableBob = checked;
|
mBobSettings.enable = checked;
|
||||||
|
|
||||||
rsAutoProxyMonitor::taskSync(autoProxyType::I2PBOB, autoProxyTask::setSettings, &mBobSettings);
|
rsAutoProxyMonitor::taskSync(autoProxyType::I2PBOB, autoProxyTask::setSettings, &mBobSettings);
|
||||||
|
|
||||||
@ -1487,7 +1493,7 @@ void ServerPage::toggleBobAdvancedSettings(bool checked)
|
|||||||
{
|
{
|
||||||
ui.swBobAdvanced->setCurrentIndex(checked ? 1 : 0);
|
ui.swBobAdvanced->setCurrentIndex(checked ? 1 : 0);
|
||||||
|
|
||||||
if (!mBobSettings.keys.empty()) {
|
if (!mBobSettings.address.privateKey.empty()) {
|
||||||
if (checked) {
|
if (checked) {
|
||||||
ui.pbBobGenAddr->show();
|
ui.pbBobGenAddr->show();
|
||||||
} else {
|
} else {
|
||||||
@ -1578,9 +1584,9 @@ void ServerPage::loadCommon()
|
|||||||
whileBlocking(ui.hiddenpage_proxyPort_i2p_2)->setValue(proxyport); // this one is for bob tab
|
whileBlocking(ui.hiddenpage_proxyPort_i2p_2)->setValue(proxyport); // this one is for bob tab
|
||||||
|
|
||||||
// don't use whileBlocking here
|
// 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.lBobB32Addr->show();
|
||||||
ui.leBobB32Addr->show();
|
ui.leBobB32Addr->show();
|
||||||
}
|
}
|
||||||
@ -1623,13 +1629,13 @@ void ServerPage::saveBob()
|
|||||||
|
|
||||||
void ServerPage::updateStatusBob()
|
void ServerPage::updateStatusBob()
|
||||||
{
|
{
|
||||||
QString addr = QString::fromStdString(mBobSettings.addr);
|
QString addr = QString::fromStdString(mBobSettings.address.base32);
|
||||||
if (ui.leBobB32Addr->text() != addr) {
|
if (ui.leBobB32Addr->text() != addr) {
|
||||||
ui.leBobB32Addr->setText(addr);
|
ui.leBobB32Addr->setText(addr);
|
||||||
ui.hiddenpage_serviceAddress->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
|
// we have an addr -> show fields
|
||||||
ui.lBobB32Addr->show();
|
ui.lBobB32Addr->show();
|
||||||
ui.leBobB32Addr->show();
|
ui.leBobB32Addr->show();
|
||||||
@ -1655,7 +1661,7 @@ void ServerPage::updateStatusBob()
|
|||||||
QString bobSimpleText = QString();
|
QString bobSimpleText = QString();
|
||||||
bobSimpleText.append(tr("RetroShare uses BOB to set up a %1 tunnel at %2:%3 (named %4)\n\n"
|
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").
|
"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_proxyAddress_i2p_2->text(),
|
||||||
ui.hiddenpage_proxyPort_i2p_2->text(),
|
ui.hiddenpage_proxyPort_i2p_2->text(),
|
||||||
bs.tunnelName.empty() ? tr("unknown") :
|
bs.tunnelName.empty() ? tr("unknown") :
|
||||||
@ -1777,15 +1783,15 @@ void ServerPage::updateStatusBob()
|
|||||||
|
|
||||||
void ServerPage::setUpBobElements()
|
void ServerPage::setUpBobElements()
|
||||||
{
|
{
|
||||||
ui.gbBob->setEnabled(mBobSettings.enableBob);
|
ui.gbBob->setEnabled(mBobSettings.enable);
|
||||||
if (mBobSettings.enableBob) {
|
if (mBobSettings.enable) {
|
||||||
ui.hiddenpage_proxyAddress_i2p->setEnabled(false);
|
ui.hiddenpage_proxyAddress_i2p->setEnabled(false);
|
||||||
ui.hiddenpage_proxyAddress_i2p->setToolTip("Use I2P/BOB settings to change this value");
|
ui.hiddenpage_proxyAddress_i2p->setToolTip("Use I2P/BOB settings to change this value");
|
||||||
ui.hiddenpage_proxyPort_i2p->setEnabled(false);
|
ui.hiddenpage_proxyPort_i2p->setEnabled(false);
|
||||||
ui.hiddenpage_proxyPort_i2p->setToolTip("Use I2P/BOB settings to change this value");
|
ui.hiddenpage_proxyPort_i2p->setToolTip("Use I2P/BOB settings to change this value");
|
||||||
|
|
||||||
ui.leBobB32Addr->setText(QString::fromStdString(mBobSettings.addr));
|
ui.leBobB32Addr->setText(QString::fromStdString(mBobSettings.address.base32));
|
||||||
ui.pteBobServerKey->setPlainText(QString::fromStdString(mBobSettings.keys));
|
ui.pteBobServerKey->setPlainText(QString::fromStdString(mBobSettings.address.privateKey));
|
||||||
|
|
||||||
// cast to int to avoid problems
|
// cast to int to avoid problems
|
||||||
int li, lo, qi, qo, vi, vo;
|
int li, lo, qi, qo, vi, vo;
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QFontMetrics>
|
#include <QFontMetrics>
|
||||||
@ -463,24 +464,25 @@ void GxsTransportStatistics::loadGroups()
|
|||||||
#ifdef DEBUG_FORUMS
|
#ifdef DEBUG_FORUMS
|
||||||
std::cerr << "Retrieving post data for post " << mThreadId << std::endl;
|
std::cerr << "Retrieving post data for post " << mThreadId << std::endl;
|
||||||
#endif
|
#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;
|
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
|
/* 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
|
* thread, for example to update the data model with new information
|
||||||
* after a blocking call to RetroShare API complete */
|
* after a blocking call to RetroShare API complete */
|
||||||
|
|
||||||
mGroupStats = stats;
|
// TODO: consider making mGroupStats an unique_ptr to avoid copying
|
||||||
|
mGroupStats = *stats;
|
||||||
updateContent();
|
updateContent();
|
||||||
|
|
||||||
mStateHelper->setLoading(GXSTRANS_GROUP_META, false);
|
mStateHelper->setLoading(GXSTRANS_GROUP_META, false);
|
||||||
|
|
||||||
}, this );
|
}, this );
|
||||||
|
@ -1337,13 +1337,19 @@ gxschannels {
|
|||||||
gui/gxschannels/GxsChannelGroupDialog.h \
|
gui/gxschannels/GxsChannelGroupDialog.h \
|
||||||
gui/gxschannels/CreateGxsChannelMsg.h \
|
gui/gxschannels/CreateGxsChannelMsg.h \
|
||||||
gui/gxschannels/GxsChannelPostsWidget.h \
|
gui/gxschannels/GxsChannelPostsWidget.h \
|
||||||
|
gui/gxschannels/GxsChannelPostsWidgetWithModel.h \
|
||||||
|
gui/gxschannels/GxsChannelPostsModel.h \
|
||||||
|
gui/gxschannels/GxsChannelPostFilesModel.h \
|
||||||
gui/gxschannels/GxsChannelFilesWidget.h \
|
gui/gxschannels/GxsChannelFilesWidget.h \
|
||||||
|
gui/gxschannels/GxsChannelPostThumbnail.h \
|
||||||
gui/gxschannels/GxsChannelFilesStatusWidget.h \
|
gui/gxschannels/GxsChannelFilesStatusWidget.h \
|
||||||
gui/feeds/GxsChannelGroupItem.h \
|
gui/feeds/GxsChannelGroupItem.h \
|
||||||
gui/feeds/GxsChannelPostItem.h \
|
gui/feeds/GxsChannelPostItem.h \
|
||||||
gui/gxschannels/GxsChannelUserNotify.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/GxsChannelFilesWidget.ui \
|
||||||
gui/gxschannels/GxsChannelFilesStatusWidget.ui \
|
gui/gxschannels/GxsChannelFilesStatusWidget.ui \
|
||||||
gui/gxschannels/CreateGxsChannelMsg.ui \
|
gui/gxschannels/CreateGxsChannelMsg.ui \
|
||||||
@ -1352,6 +1358,9 @@ gxschannels {
|
|||||||
|
|
||||||
SOURCES += gui/gxschannels/GxsChannelDialog.cpp \
|
SOURCES += gui/gxschannels/GxsChannelDialog.cpp \
|
||||||
gui/gxschannels/GxsChannelPostsWidget.cpp \
|
gui/gxschannels/GxsChannelPostsWidget.cpp \
|
||||||
|
gui/gxschannels/GxsChannelPostsWidgetWithModel.cpp \
|
||||||
|
gui/gxschannels/GxsChannelPostsModel.cpp \
|
||||||
|
gui/gxschannels/GxsChannelPostFilesModel.cpp \
|
||||||
gui/gxschannels/GxsChannelFilesWidget.cpp \
|
gui/gxschannels/GxsChannelFilesWidget.cpp \
|
||||||
gui/gxschannels/GxsChannelFilesStatusWidget.cpp \
|
gui/gxschannels/GxsChannelFilesStatusWidget.cpp \
|
||||||
gui/gxschannels/GxsChannelGroupDialog.cpp \
|
gui/gxschannels/GxsChannelGroupDialog.cpp \
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* util/qthreadutils.h *
|
* 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 *
|
* This program is free software: you can redistribute it and/or modify *
|
||||||
* it under the terms of the GNU Affero General Public License as *
|
* it under the terms of the GNU Affero General Public License as *
|
||||||
@ -27,7 +27,9 @@
|
|||||||
|
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include <QtCore>
|
#include <QtCore>
|
||||||
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace RsQThreadUtils {
|
namespace RsQThreadUtils {
|
||||||
|
|
||||||
@ -44,7 +46,7 @@ void postToObject(F &&fun, QObject *obj = qApp)
|
|||||||
QObject src;
|
QObject src;
|
||||||
auto type = obj->metaObject();
|
auto type = obj->metaObject();
|
||||||
QObject::connect( &src, &QObject::destroyed, obj,
|
QObject::connect( &src, &QObject::destroyed, obj,
|
||||||
[fun, type, obj]
|
[fun = std::move(fun), type, obj]
|
||||||
{
|
{
|
||||||
// ensure that the object is not being destructed
|
// ensure that the object is not being destructed
|
||||||
if (obj->metaObject()->inherits(type)) fun();
|
if (obj->metaObject()->inherits(type)) fun();
|
||||||
|
@ -140,6 +140,11 @@ rs_macos10.15:CONFIG -= rs_macos10.11
|
|||||||
CONFIG *= no_rs_jsonapi
|
CONFIG *= no_rs_jsonapi
|
||||||
rs_jsonapi: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
|
# To enable channel indexing append the following assignation to qmake command
|
||||||
# line "CONFIG+=rs_deep_channel_index"
|
# line "CONFIG+=rs_deep_channel_index"
|
||||||
CONFIG *= no_rs_deep_channel_index
|
CONFIG *= no_rs_deep_channel_index
|
||||||
@ -550,6 +555,10 @@ rs_webui {
|
|||||||
DEFINES *= RS_WEBUI
|
DEFINES *= RS_WEBUI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rs_bob {
|
||||||
|
DEFINES *= RS_USE_I2P_BOB
|
||||||
|
}
|
||||||
|
|
||||||
rs_deep_channels_index:DEFINES *= RS_DEEP_CHANNEL_INDEX
|
rs_deep_channels_index:DEFINES *= RS_DEEP_CHANNEL_INDEX
|
||||||
|
|
||||||
rs_deep_files_index:DEFINES *= RS_DEEP_FILES_INDEX
|
rs_deep_files_index:DEFINES *= RS_DEEP_FILES_INDEX
|
||||||
|
Loading…
Reference in New Issue
Block a user