From 7b84b125df24985cb66a9ffd83254b12be59ff9d Mon Sep 17 00:00:00 2001 From: csoler Date: Sun, 15 Mar 2009 22:45:40 +0000 Subject: [PATCH] turtle search is now working. Next task: update search gui git-svn-id: http://svn.code.sf.net/p/retroshare/code/trunk@1078 b45a01b8-16f6-495d-af2f-9b41ad6348cc --- libretroshare/src/dbase/fistore.cc | 75 +- libretroshare/src/dbase/fistore.h | 2 +- libretroshare/src/ft/ftserver.cc | 4 +- libretroshare/src/ft/ftserver.h | 2 +- libretroshare/src/rsiface/rsfiles.h | 2 +- libretroshare/src/rsiface/rsiface.h | 6 + libretroshare/src/rsiface/rsturtle.h | 10 + libretroshare/src/rsserver/p3files.cc | 4 +- libretroshare/src/server/filedexserver.cc | 5 +- libretroshare/src/services/p3turtle.cc | 200 ++- libretroshare/src/services/p3turtle.h | 4 +- retroshare-gui/src/RetroShare.pro | 13 +- retroshare-gui/src/gui/MainWindow.cpp | 10 +- retroshare-gui/src/gui/MainWindow.h | 6 + retroshare-gui/src/gui/SearchDialog.cpp | 12 +- retroshare-gui/src/gui/TurtleSearchDialog.cpp | 660 +++++++++ retroshare-gui/src/gui/TurtleSearchDialog.h | 133 ++ retroshare-gui/src/gui/TurtleSearchDialog.ui | 1230 +++++++++++++++++ retroshare-gui/src/gui/images.qrc | 1 + retroshare-gui/src/gui/images/turtle.png | Bin 0 -> 35232 bytes retroshare-gui/src/main.cpp | 5 + retroshare-gui/src/rsiface/notifyqt.cpp | 35 + retroshare-gui/src/rsiface/notifyqt.h | 10 +- retroshare-gui/src/rsiface/rsfiles.h | 2 +- retroshare-gui/src/rsiface/rsiface.h | 6 + retroshare-gui/src/rsiface/rsturtle.h | 10 + 26 files changed, 2329 insertions(+), 118 deletions(-) create mode 100644 retroshare-gui/src/gui/TurtleSearchDialog.cpp create mode 100644 retroshare-gui/src/gui/TurtleSearchDialog.h create mode 100644 retroshare-gui/src/gui/TurtleSearchDialog.ui create mode 100644 retroshare-gui/src/gui/images/turtle.png diff --git a/libretroshare/src/dbase/fistore.cc b/libretroshare/src/dbase/fistore.cc index bd2a345b4..02896c5ab 100644 --- a/libretroshare/src/dbase/fistore.cc +++ b/libretroshare/src/dbase/fistore.cc @@ -405,7 +405,7 @@ int FileIndexStore::SearchHash(std::string hash, std::list &results) } -int FileIndexStore::SearchKeywords(std::list keywords, std::list &results) const +int FileIndexStore::SearchKeywords(std::list keywords, std::list &results,uint32_t flags) const { lockData(); std::map::const_iterator pit; @@ -417,49 +417,52 @@ int FileIndexStore::SearchKeywords(std::list keywords, std::listsecond)->searchTerms(keywords, firesults); - /* translate results */ - for(rit = firesults.begin(); rit != firesults.end(); rit++) + if(flags & DIR_FLAGS_REMOTE) + for(pit = indices.begin(); pit != indices.end(); pit++) { - FileDetail fd; - fd.id = pit->first; - fd.name = (*rit)->name; - fd.hash = (*rit)->hash; - fd.path = ""; /* TODO */ - fd.size = (*rit)->size; - fd.age = now - (*rit)->modtime; - fd.rank = (*rit)->pop; + firesults.clear(); + + (pit->second)->searchTerms(keywords, firesults); + /* translate results */ + for(rit = firesults.begin(); rit != firesults.end(); rit++) + { + FileDetail fd; + fd.id = pit->first; + fd.name = (*rit)->name; + fd.hash = (*rit)->hash; + fd.path = ""; /* TODO */ + fd.size = (*rit)->size; + fd.age = now - (*rit)->modtime; + fd.rank = (*rit)->pop; + + results.push_back(fd); + } - results.push_back(fd); } - } - if (localindex) - { - firesults.clear(); - - localindex->searchTerms(keywords, firesults); - /* translate results */ - for(rit = firesults.begin(); rit != firesults.end(); rit++) + if(flags & DIR_FLAGS_LOCAL) + if (localindex) { - FileDetail fd; - fd.id = "Local"; //localId; - fd.name = (*rit)->name; - fd.hash = (*rit)->hash; - fd.path = ""; /* TODO */ - fd.size = (*rit)->size; - fd.age = now - (*rit)->modtime; - fd.rank = (*rit)->pop; + firesults.clear(); + + localindex->searchTerms(keywords, firesults); + /* translate results */ + for(rit = firesults.begin(); rit != firesults.end(); rit++) + { + FileDetail fd; + fd.id = "Local"; //localId; + fd.name = (*rit)->name; + fd.hash = (*rit)->hash; + fd.path = ""; /* TODO */ + fd.size = (*rit)->size; + fd.age = now - (*rit)->modtime; + fd.rank = (*rit)->pop; + + results.push_back(fd); + } - results.push_back(fd); } - } - unlockData(); return results.size(); } diff --git a/libretroshare/src/dbase/fistore.h b/libretroshare/src/dbase/fistore.h index ad3f6e319..76d0821f9 100644 --- a/libretroshare/src/dbase/fistore.h +++ b/libretroshare/src/dbase/fistore.h @@ -74,7 +74,7 @@ virtual int loadCache(const CacheData &data); /* actual load, once data availa int SearchHash(std::string hash, std::list &results) const; /* Search Interface - For Search Interface */ - int SearchKeywords(std::list terms, std::list &results) const; + int SearchKeywords(std::list terms, std::list &results,uint32_t flags) const; /* Search Interface - for Adv Search Interface */ int searchBoolExp(Expression * exp, std::list &results) const; diff --git a/libretroshare/src/ft/ftserver.cc b/libretroshare/src/ft/ftserver.cc index 5c13b27ae..5110f7a20 100644 --- a/libretroshare/src/ft/ftserver.cc +++ b/libretroshare/src/ft/ftserver.cc @@ -383,7 +383,7 @@ int ftServer::RequestDirDetails(void *ref, DirDetails &details, uint32_t flags) /***************************************************************/ -int ftServer::SearchKeywords(std::list keywords, std::list &results) +int ftServer::SearchKeywords(std::list keywords, std::list &results,uint32_t flags) { #ifdef SERVER_DEBUG std::cerr << "ftServer::SearchKeywords()"; @@ -396,7 +396,7 @@ int ftServer::SearchKeywords(std::list keywords, std::listSearchKeywords(keywords, results); + return mFiStore->SearchKeywords(keywords, results,flags); } int ftServer::SearchBoolExp(Expression * exp, std::list &results) diff --git a/libretroshare/src/ft/ftserver.h b/libretroshare/src/ft/ftserver.h index 3957e5754..8926ece92 100644 --- a/libretroshare/src/ft/ftserver.h +++ b/libretroshare/src/ft/ftserver.h @@ -140,7 +140,7 @@ virtual bool ExtraFileMove(std::string fname, std::string hash, uint64_t size, virtual int RequestDirDetails(std::string uid, std::string path, DirDetails &details); virtual int RequestDirDetails(void *ref, DirDetails &details, uint32_t flags); -virtual int SearchKeywords(std::list keywords, std::list &results); +virtual int SearchKeywords(std::list keywords, std::list &results,uint32_t flags); virtual int SearchBoolExp(Expression * exp, std::list &results); /*** diff --git a/libretroshare/src/rsiface/rsfiles.h b/libretroshare/src/rsiface/rsfiles.h index 3f8575035..58335ee48 100644 --- a/libretroshare/src/rsiface/rsfiles.h +++ b/libretroshare/src/rsiface/rsfiles.h @@ -130,7 +130,7 @@ virtual bool ExtraFileMove(std::string fname, std::string hash, uint64_t size, virtual int RequestDirDetails(std::string uid, std::string path, DirDetails &details) = 0; virtual int RequestDirDetails(void *ref, DirDetails &details, uint32_t flags) = 0; -virtual int SearchKeywords(std::list keywords, std::list &results) = 0; +virtual int SearchKeywords(std::list keywords, std::list &results,uint32_t flags) = 0; virtual int SearchBoolExp(Expression * exp, std::list &results) = 0; /*** diff --git a/libretroshare/src/rsiface/rsiface.h b/libretroshare/src/rsiface/rsiface.h index 215e54f54..9a58454e5 100644 --- a/libretroshare/src/rsiface/rsiface.h +++ b/libretroshare/src/rsiface/rsiface.h @@ -35,6 +35,9 @@ class NotifyBase; class RsIface; class RsControl; class RsInit; +#ifdef TURTLE_HOPPING +struct TurtleFileInfo ; +#endif /* declare single RsIface for everyone to use! */ @@ -200,6 +203,9 @@ class NotifyBase virtual void notifyErrorMsg(int list, int sev, std::string msg) { (void) list; (void) sev; (void) msg; return; } virtual void notifyChat() { return; } virtual void notifyHashingInfo(std::string fileinfo) { (void)fileinfo; return ; } +#ifdef TURTLE_HOPPING + virtual void notifyTurtleSearchResult(uint32_t search_id,const std::list& files) { (void)files; } +#endif }; const int NOTIFY_LIST_NEIGHBOURS = 1; diff --git a/libretroshare/src/rsiface/rsturtle.h b/libretroshare/src/rsiface/rsturtle.h index 0bfb4937a..abaf4e24e 100644 --- a/libretroshare/src/rsiface/rsturtle.h +++ b/libretroshare/src/rsiface/rsturtle.h @@ -35,6 +35,16 @@ extern RsTurtle *rsTurtle ; typedef uint32_t TurtleRequestId ; +// This is the structure used to send back results of the turtle search +// to the notifyBase class. + +struct TurtleFileInfo +{ + std::string hash ; + std::string name ; + uint64_t size ; +}; + // Interface class for turtle hopping. // // This class mainly interacts with the turtle router, that is responsible diff --git a/libretroshare/src/rsserver/p3files.cc b/libretroshare/src/rsserver/p3files.cc index bdf3a3ae2..6217a44af 100644 --- a/libretroshare/src/rsserver/p3files.cc +++ b/libretroshare/src/rsserver/p3files.cc @@ -252,12 +252,12 @@ int p3Files::RequestDirDetails(void *ref, DirDetails &details, uint32_t flags) } -int p3Files::SearchKeywords(std::list keywords, std::list &results) +int p3Files::SearchKeywords(std::list keywords, std::list &results,uint32_t flags) { lockRsCore(); /* LOCK */ /* call to filedexmServer */ - int val = mServer->SearchKeywords(keywords, results); + int val = mServer->SearchKeywords(keywords, results,flags); unlockRsCore(); /* UNLOCK */ diff --git a/libretroshare/src/server/filedexserver.cc b/libretroshare/src/server/filedexserver.cc index bff863b72..127a90651 100644 --- a/libretroshare/src/server/filedexserver.cc +++ b/libretroshare/src/server/filedexserver.cc @@ -393,10 +393,9 @@ int filedexserver::RequestDirDetails(void *ref, DirDetails &details, uint32_t fl return fiStore->RequestDirDetails(ref, details, flags); } -int filedexserver::SearchKeywords(std::list keywords, - std::list &results) +int filedexserver::SearchKeywords(std::list keywords,std::list &results,uint32_t flags) { - return fiStore->SearchKeywords(keywords, results); + return fiStore->SearchKeywords(keywords, results,flags); } int filedexserver::SearchBoolExp(Expression * exp, std::list &results) diff --git a/libretroshare/src/services/p3turtle.cc b/libretroshare/src/services/p3turtle.cc index 6518945b7..63cf9cc81 100644 --- a/libretroshare/src/services/p3turtle.cc +++ b/libretroshare/src/services/p3turtle.cc @@ -27,6 +27,7 @@ #include "rsiface/rsiface.h" #include "rsiface/rspeers.h" +#include "rsiface/rsfiles.h" #include "pqi/p3authmgr.h" #include "pqi/p3connmgr.h" @@ -54,6 +55,7 @@ p3turtle::p3turtle(p3ConnectMgr *cm) :p3Service(RS_SERVICE_TYPE_TURTLE), mConnMg { RsStackMutex stack(mTurtleMtx); /********** STACK LOCKED MTX ******/ + srand48(time(NULL)) ; addSerialType(new RsTurtleSerialiser()); } @@ -94,10 +96,20 @@ TurtleRequestId p3turtle::turtleSearch(const std::string& string_to_match) TurtleRequestId id = generateRandomRequestId() ; - // form a request packet + // Form a request packet that simulates a request from us. // RsTurtleSearchRequestItem *item = new RsTurtleSearchRequestItem ; +#ifdef P3TURTLE_DEBUG + std::cerr << "performing search. OwnId = " << mConnMgr->getOwnId() << std::endl ; +#endif + while(mConnMgr->getOwnId() == "") + { + std::cerr << "... waitting for connect manager to form own id." << std::endl ; + sleep(1) ; + } + + item->PeerId(mConnMgr->getOwnId()) ; item->match_string = string_to_match ; item->request_id = id ; item->depth = 0 ; @@ -122,8 +134,8 @@ void p3turtle::turtleDownload(const std::string& file_hash) int p3turtle::handleIncoming() { #ifdef P3TURTLE_DEBUG - std::cerr << "p3turtle::handleIncoming()"; - std::cerr << std::endl; +// std::cerr << "p3turtle::handleIncoming()"; +// std::cerr << std::endl; #endif int nhandled = 0; @@ -160,7 +172,7 @@ void p3turtle::handleSearchRequest(RsTurtleSearchRequestItem *item) // - If the item destimation is #ifdef P3TURTLE_DEBUG - std::cerr << "Received search request: " << std::endl ; + std::cerr << "Received search request from peer " << item->PeerId() << ": " << std::endl ; item->print(std::cerr,0) ; #endif // If the item contains an already handled search request, give up. This @@ -186,7 +198,7 @@ void p3turtle::handleSearchRequest(RsTurtleSearchRequestItem *item) #ifdef P3TURTLE_DEBUG std::cerr << " Request not from us. Performing local search" << std::endl ; #endif - std::map result ; + std::list result ; performLocalSearch(item->match_string,result) ; if(!result.empty()) @@ -203,7 +215,7 @@ void p3turtle::handleSearchRequest(RsTurtleSearchRequestItem *item) res_item->PeerId(item->PeerId()) ; // send back to the same guy #ifdef P3TURTLE_DEBUG - std::cerr << " " << result.size() << " matches found. Sending back to origin." << std::endl ; + std::cerr << " " << result.size() << " matches found. Sending back to origin (" << res_item->PeerId() << ")." << std::endl ; #endif sendItem(res_item) ; } @@ -215,6 +227,9 @@ void p3turtle::handleSearchRequest(RsTurtleSearchRequestItem *item) { std::list onlineIds ; mConnMgr->getOnlineList(onlineIds); +#ifdef P3TURTLE_DEBUG + std::cerr << " Looking for online peers" << std::endl ; +#endif for(std::list::const_iterator it(onlineIds.begin());it!=onlineIds.end();++it) if(*it != item->PeerId()) @@ -259,6 +274,8 @@ void p3turtle::handleSearchResult(RsTurtleSearchResultItem *item) // Is this result's target actually ours ? + ++(item->depth) ; // increase depth + if(it->second == mConnMgr->getOwnId()) returnSearchResult(item) ; // Yes, so send upward. else @@ -268,8 +285,6 @@ void p3turtle::handleSearchResult(RsTurtleSearchResultItem *item) #endif RsTurtleSearchResultItem *fwd_item = new RsTurtleSearchResultItem(*item) ; // copy the item - ++(fwd_item->depth) ; // increase depth - // normally here, we should setup the forward adress, so that the owner's of the files found can be further reached by a tunnel. fwd_item->PeerId(it->second) ; @@ -281,6 +296,7 @@ void p3turtle::handleSearchResult(RsTurtleSearchResultItem *item) std::ostream& RsTurtleSearchRequestItem::print(std::ostream& o, uint16_t) { o << "Search request:" << std::endl ; + o << " direct origin: \"" << PeerId() << "\"" << std::endl ; o << " match string: \"" << match_string << "\"" << std::endl ; o << " Req. Id: " << request_id << std::endl ; o << " Depth : " << depth << std::endl ; @@ -292,13 +308,13 @@ std::ostream& RsTurtleSearchResultItem::print(std::ostream& o, uint16_t) { o << "Search result:" << std::endl ; - o << " Peer id: " << peer_id << std::endl ; + o << " Peer id: " << PeerId() << std::endl ; o << " Depth : " << depth << std::endl ; o << " Req. Id: " << request_id << std::endl ; o << " Files:" << std::endl ; - for(std::map::const_iterator it(result.begin());it!=result.end();++it) - o << " " << it->first << " " << it->second << std::endl ; + for(std::list::const_iterator it(result.begin());it!=result.end();++it) + o << " " << it->hash << " " << it->size << " " << it->name << std::endl ; return o ; } @@ -307,8 +323,11 @@ void p3turtle::returnSearchResult(RsTurtleSearchResultItem *item) { // just cout for now, but it should be notified to the gui - std::cerr << "Received result for search request: " << std::endl ; - item->print(std::cerr,0) ; +#ifdef P3TURTLE_DEBUG + std::cerr << " Returning result for search request " << item->request_id << " upwards." << std::endl ; +#endif + + rsicontrol->getNotify().notifyTurtleSearchResult(item->request_id,item->result) ; } /************* from pqiMonitor *******************/ @@ -350,6 +369,61 @@ uint32_t RsTurtleSearchRequestItem::serial_size() return s ; } +uint32_t RsTurtleSearchResultItem::serial_size() +{ + uint32_t s = 0 ; + + s += 8 ; // header + s += 4 ; // search request id + s += 2 ; // depth + s += 4 ; // number of results + + for(std::list::const_iterator it(result.begin());it!=result.end();++it) + { + s += 8 ; // file size + s += GetTlvStringSize(it->hash) ; // file hash + s += GetTlvStringSize(it->name) ; // file name + } + + return s ; +} + +RsItem *RsTurtleSerialiser::deserialise(void *data, uint32_t *size) +{ + // look what we have... + + /* get the type */ + uint32_t rstype = getRsItemId(data); +#ifdef P3TURTLE_DEBUG + std::cerr << "p3turtle: deserialising packet: " << std::endl ; +#endif + if ((RS_PKT_VERSION_SERVICE != getRsItemVersion(rstype)) || (RS_SERVICE_TYPE_TURTLE != getRsItemService(rstype))) + { +#ifdef P3TURTLE_DEBUG + std::cerr << " Wrong type !!" << std::endl ; +#endif + return NULL; /* wrong type */ + } + + try + { + switch(getRsItemSubType(rstype)) + { + case RS_TURTLE_SUBTYPE_SEARCH_REQUEST: return new RsTurtleSearchRequestItem(data,*size) ; + case RS_TURTLE_SUBTYPE_SEARCH_RESULT: return new RsTurtleSearchResultItem(data,*size) ; + + default: + std::cerr << "Unknown packet type in RsTurtle!" << std::endl ; + return NULL ; + } + } + catch(std::exception& e) + { + std::cerr << "Exception raised: " << e.what() << std::endl ; + return NULL ; + } +} + bool RsTurtleSearchRequestItem::serialize(void *data,uint32_t& pktsize) { uint32_t tlvsize = serial_size(); @@ -384,21 +458,24 @@ bool RsTurtleSearchRequestItem::serialize(void *data,uint32_t& pktsize) return ok; } -uint32_t RsTurtleSearchResultItem::serial_size() +RsTurtleSearchRequestItem::RsTurtleSearchRequestItem(void *data,uint32_t pktsize) + : RsTurtleItem(RS_TURTLE_SUBTYPE_SEARCH_REQUEST) { - uint32_t s = 0 ; +#ifdef P3TURTLE_DEBUG + std::cerr << " type = search request" << std::endl ; +#endif + uint32_t offset = 8; // skip the header + uint32_t rssize = getRsItemSize(data); + bool ok = true ; - s += 8 ; // header - s += 4 ; // search request id - s += 4 ; // number of results + ok &= GetTlvString(data, pktsize, &offset, TLV_TYPE_STR_VALUE, match_string); // file hash + ok &= getRawUInt32(data, pktsize, &offset, &request_id); + ok &= getRawUInt16(data, pktsize, &offset, &depth); - for(std::map::const_iterator it(result.begin());it!=result.end();++it) - { - s += GetTlvStringSize(it->first) ; // file hash - s += GetTlvStringSize(it->second) ; // file name - } - - return s ; + if (offset != rssize) + throw std::runtime_error("Size error while deserializing.") ; + if (!ok) + throw std::runtime_error("Unknown error while deserializing.") ; } bool RsTurtleSearchResultItem::serialize(void *data,uint32_t& pktsize) @@ -421,12 +498,14 @@ bool RsTurtleSearchResultItem::serialize(void *data,uint32_t& pktsize) /* add mandatory parts first */ ok &= setRawUInt32(data, tlvsize, &offset, request_id); + ok &= setRawUInt16(data, tlvsize, &offset, depth); ok &= setRawUInt32(data, tlvsize, &offset, result.size()); - for(std::map::const_iterator it(result.begin());it!=result.end();++it) + for(std::list::const_iterator it(result.begin());it!=result.end();++it) { - ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_HASH_SHA1, it->first); // file hash - ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_NAME, it->second); // file name + ok &= setRawUInt64(data, tlvsize, &offset, it->size); // file size + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_HASH_SHA1, it->hash); // file hash + ok &= SetTlvString(data, tlvsize, &offset, TLV_TYPE_STR_NAME, it->name); // file name } if (offset != tlvsize) @@ -438,35 +517,15 @@ bool RsTurtleSearchResultItem::serialize(void *data,uint32_t& pktsize) } return ok; - } -RsItem *RsTurtleSerialiser::deserialise(void *data, uint32_t *size) -{ - // look what we have... - - /* get the type */ - uint32_t rstype = getRsItemId(data); - - if ((RS_PKT_VERSION_SERVICE != getRsItemVersion(rstype)) || (RS_SERVICE_TYPE_TURTLE != getRsItemService(rstype))) - { - return NULL; /* wrong type */ - } - - switch(getRsItemSubType(rstype)) - { - case RS_TURTLE_SUBTYPE_SEARCH_REQUEST: return new RsTurtleSearchResultItem(data,*size) ; - case RS_TURTLE_SUBTYPE_SEARCH_RESULT: return new RsTurtleSearchResultItem(data,*size) ; - - default: - std::cerr << "Unknown packet type in RsTurtle!" << std::endl ; - return NULL ; - } -} RsTurtleSearchResultItem::RsTurtleSearchResultItem(void *data,uint32_t pktsize) : RsTurtleItem(RS_TURTLE_SUBTYPE_SEARCH_RESULT) { +#ifdef P3TURTLE_DEBUG + std::cerr << " type = search result" << std::endl ; +#endif uint32_t offset = 8; // skip the header uint32_t rssize = getRsItemSize(data); @@ -475,18 +534,23 @@ RsTurtleSearchResultItem::RsTurtleSearchResultItem(void *data,uint32_t pktsize) bool ok = true ; uint32_t s ; ok &= getRawUInt32(data, pktsize, &offset, &request_id); + ok &= getRawUInt16(data, pktsize, &offset, &depth); ok &= getRawUInt32(data, pktsize, &offset, &s) ; +#ifdef P3TURTLE_DEBUG + std::cerr << " reuqest_id=" << request_id << ", depth=" << depth << ", s=" << s << std::endl ; +#endif result.clear() ; for(uint i=0;i& result) +{ + /* call to core */ + std::list initialResults; + std::list words ; + + // to do: split search string into words. + words.push_back(s) ; + + // now, search! + rsFiles->SearchKeywords(words, initialResults,DIR_FLAGS_LOCAL); + + result.clear() ; + + for(std::list::const_iterator it(initialResults.begin());it!=initialResults.end();++it) + { + TurtleFileInfo i ; + i.hash = it->hash ; + i.size = it->size ; + i.name = it->name ; + + result.push_back(i) ; + } +} + + diff --git a/libretroshare/src/services/p3turtle.h b/libretroshare/src/services/p3turtle.h index c49e8d259..feb5d1078 100644 --- a/libretroshare/src/services/p3turtle.h +++ b/libretroshare/src/services/p3turtle.h @@ -86,7 +86,7 @@ class RsTurtleSearchResultItem: public RsTurtleItem TurtleRequestId request_id ; // randomly generated request id. - std::map result ; + std::list result ; virtual std::ostream& print(std::ostream& o, uint16_t) ; protected: @@ -175,7 +175,7 @@ class p3turtle: public p3Service, public pqiMonitor, public RsTurtle // int handleOutgoing(); // Performs a search calling local cache and search structure. - void performLocalSearch(const std::string& s,std::map& result) {} + void performLocalSearch(const std::string& s,std::list& result) ; void handleSearchRequest(RsTurtleSearchRequestItem *item); void handleSearchResult(RsTurtleSearchResultItem *item); diff --git a/retroshare-gui/src/RetroShare.pro b/retroshare-gui/src/RetroShare.pro index 86f8cca82..50da73758 100644 --- a/retroshare-gui/src/RetroShare.pro +++ b/retroshare-gui/src/RetroShare.pro @@ -17,7 +17,7 @@ linux-g++ { LIBS += ../../../../lib/linux-g++/libssl.a LIBS += ../../../../lib/linux-g++/libcrypto.a LIBS += -lQtUiTools - LIBS += -lz + LIBS += -lz } linux-g++-64 { OBJECTS_DIR = temp/linux-g++-64/obj @@ -489,6 +489,17 @@ TRANSLATIONS += \ lang/retroshare_sr.ts \ lang/retroshare_se.ts +# To compile for turtle hopping. I'm using this flag to avoid conflict while developping. +# Just do a +# qmake CONFIG=turtle + +turtle { + SOURCES += gui/TurtleSearchDialog.cpp + HEADERS += rsiface/rsturtle.h gui/TurtleSearchDialog.h + FORMS += gui/TurtleSearchDialog.ui + DEFINES *= TURTLE_HOPPING +} + diff --git a/retroshare-gui/src/gui/MainWindow.cpp b/retroshare-gui/src/gui/MainWindow.cpp index 601a796a0..636c2f034 100644 --- a/retroshare-gui/src/gui/MainWindow.cpp +++ b/retroshare-gui/src/gui/MainWindow.cpp @@ -48,6 +48,10 @@ #include "games/qbackgammon/bgwindow.h" //#include "smplayer.h" +#ifdef TURTLE_HOPPING +#include "gui/TurtleSearchDialog.h" +#endif + #include "statusbar/peerstatus.h" #include "Preferences/PreferencesWindow.h" #include "Settings/gsettingswin.h" @@ -75,6 +79,7 @@ #define IMAGE_FILES ":/images/fileshare24.png" #define IMAGE_CHANNELS ":/images/channels.png" #define IMAGE_FORUMS ":/images/konversation.png" +#define IMAGE_TURTLE ":/images/turtle.png" #define IMAGE_PREFERENCES ":/images/kcmsystem24.png" #define IMAGE_CHAT ":/images/groupchat.png" #define IMAGE_RETROSHARE ":/images/rstray3.png" @@ -179,7 +184,10 @@ MainWindow::MainWindow(QWidget* parent, Qt::WFlags flags) //PeersFeed *peersFeed = NULL; //ui.stackPages->add(peersFeed = new PeersFeed(ui.stackPages), // createPageAction(QIcon(IMAGE_PEERS), tr("Peers"), grp)); - +#ifdef TURTLE_HOPPING + ui.stackPages->add(turtleDialog = new TurtleSearchDialog(ui.stackPages), + createPageAction(QIcon(IMAGE_TURTLE), tr("Turtle"), grp)); +#endif ui.stackPages->add(searchDialog = new SearchDialog(ui.stackPages), createPageAction(QIcon(IMAGE_SEARCH), tr("Search"), grp)); diff --git a/retroshare-gui/src/gui/MainWindow.h b/retroshare-gui/src/gui/MainWindow.h index 4fdc2901e..f883fc272 100644 --- a/retroshare-gui/src/gui/MainWindow.h +++ b/retroshare-gui/src/gui/MainWindow.h @@ -39,6 +39,9 @@ #include "MessengerWindow.h" #include "ApplicationWindow.h" #include "PluginsPage.h" +#ifdef TURTLE_HOPPING +#include "TurtleSearchDialog.h" +#endif #include "Preferences/PreferencesWindow.h" #include "Settings/gsettingswin.h" @@ -95,6 +98,9 @@ public: NetworkDialog *networkDialog; PeersDialog *peersDialog; SearchDialog *searchDialog; +#ifdef TURTLE_HOPPING + TurtleSearchDialog *turtleDialog; +#endif TransfersDialog *transfersDialog; ChatDialog *chatDialog; MessagesDialog *messagesDialog; diff --git a/retroshare-gui/src/gui/SearchDialog.cpp b/retroshare-gui/src/gui/SearchDialog.cpp index a01e8960b..c686cca9b 100644 --- a/retroshare-gui/src/gui/SearchDialog.cpp +++ b/retroshare-gui/src/gui/SearchDialog.cpp @@ -26,9 +26,6 @@ #include "rsiface/rsexpr.h" #include "rsiface/rsfiles.h" #include "rsiface/rspeers.h" -#ifdef TURTLE_HOPPING -#include "rsiface/rsturtle.h" -#endif #include "util/misc.h" #include @@ -419,12 +416,6 @@ void SearchDialog::advancedSearch(Expression* expression) void SearchDialog::searchKeywords() { -#ifdef TURTLE_HOPPING - QString qTxt = ui.lineEdit->text(); - std::string txt = qTxt.toStdString(); - - TurtleRequestId id = rsTurtle->turtleSearch(txt) ; -#else QString qTxt = ui.lineEdit->text(); std::string txt = qTxt.toStdString(); @@ -449,7 +440,7 @@ void SearchDialog::searchKeywords() std::list initialResults; std::list * finalResults = 0; - rsFiles -> SearchKeywords(words, initialResults); + rsFiles -> SearchKeywords(words, initialResults,DIR_FLAGS_LOCAL | DIR_FLAGS_REMOTE); /* which extensions do we use? */ QString qExt, qName; int extIndex; @@ -497,7 +488,6 @@ void SearchDialog::searchKeywords() /* abstraction to allow reusee of tree rendering code */ resultsToTree(txt, *finalResults); -#endif } void SearchDialog::resultsToTree(std::string txt, std::list results) diff --git a/retroshare-gui/src/gui/TurtleSearchDialog.cpp b/retroshare-gui/src/gui/TurtleSearchDialog.cpp new file mode 100644 index 000000000..423451f1b --- /dev/null +++ b/retroshare-gui/src/gui/TurtleSearchDialog.cpp @@ -0,0 +1,660 @@ +/**************************************************************** + * RShare is distributed under the following license: + * + * Copyright (C) 2006, crypton + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + ****************************************************************/ + +#include + +#include "rshare.h" +#include "TurtleSearchDialog.h" +#include "rsiface/rsiface.h" +#include "rsiface/rsexpr.h" +#include "rsiface/rsfiles.h" +#include "rsiface/rspeers.h" +#ifdef TURTLE_HOPPING +#include "rsiface/rsturtle.h" +#endif +#include "util/misc.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* Images for context menu icons */ +#define IMAGE_START ":/images/download.png" +#define IMAGE_REMOVE ":/images/delete.png" +#define IMAGE_REMOVEALL ":/images/deleteall.png" + +/* Key for UI Preferences */ +#define UI_PREF_ADVANCED_SEARCH "UIOptions/AdvancedSearch" + +/* indicies for search results item columns SR_ = Search Result */ +/* indicies for search results item columns SR_ = Search Result */ +#define SR_ICON_COL 0 +#define SR_NAME_COL 1 +#define SR_SIZE_COL 2 +#define SR_ID_COL 3 +#define SR_TYPE_COL 4 +#define SR_HASH_COL 5 +#define SR_SEARCH_ID_COL 6 + +#define SR_UID_COL 7 +#define SR_REALSIZE_COL 8 + +/* indicies for search summary item columns SS_ = Search Summary */ +#define SS_TEXT_COL 0 +#define SS_COUNT_COL 1 +#define SS_SEARCH_ID_COL 2 + +/* static members */ +/* These indices MUST be identical to their equivalent indices in the combobox */ +const int TurtleSearchDialog::FILETYPE_IDX_ANY = 0; +const int TurtleSearchDialog::FILETYPE_IDX_ARCHIVE = 1; +const int TurtleSearchDialog::FILETYPE_IDX_AUDIO = 2; +const int TurtleSearchDialog::FILETYPE_IDX_CDIMAGE = 3; +const int TurtleSearchDialog::FILETYPE_IDX_DOCUMENT = 4; +const int TurtleSearchDialog::FILETYPE_IDX_PICTURE = 5; +const int TurtleSearchDialog::FILETYPE_IDX_PROGRAM = 6; +const int TurtleSearchDialog::FILETYPE_IDX_VIDEO = 7; +QMap * TurtleSearchDialog::FileTypeExtensionMap = new QMap(); +bool TurtleSearchDialog::initialised = false; + +/** Constructor */ +TurtleSearchDialog::TurtleSearchDialog(QWidget *parent) +: MainPage(parent), + advSearchDialog(NULL), + contextMnu(NULL), contextMnu2(NULL), + nextSearchId(1) +{ + /* Invoke the Qt Designer generated object setup routine */ + ui.setupUi(this); + + /* initialise the filetypes mapping */ + if (!TurtleSearchDialog::initialised) + { + initialiseFileTypeMappings(); + } + + /* Advanced search panel specifica */ + RshareSettings rsharesettings; + QString key (UI_PREF_ADVANCED_SEARCH); + bool useAdvanced = rsharesettings.value(key, QVariant(false)).toBool(); + if (useAdvanced) + { + ui.toggleAdvancedSearchBtn->setChecked(true); + ui.SimpleSearchPanel->hide(); + } else { + ui.AdvancedSearchPanel->hide(); + } + + connect(ui.toggleAdvancedSearchBtn, SIGNAL(toggled(bool)), this, SLOT(toggleAdvancedSearchDialog(bool))); + connect(ui.focusAdvSearchDialogBtn, SIGNAL(clicked()), this, SLOT(showAdvSearchDialog())); + + /* End Advanced Search Panel specifics */ + + + connect( ui.searchResultWidget, SIGNAL( customContextMenuRequested( QPoint ) ), this, SLOT( searchtableWidgetCostumPopupMenu( QPoint ) ) ); + + connect( ui.searchSummaryWidget, SIGNAL( customContextMenuRequested( QPoint ) ), this, SLOT( searchtableWidget2CostumPopupMenu( QPoint ) ) ); + + connect( ui.lineEdit, SIGNAL( returnPressed ( void ) ), this, SLOT( searchKeywords( void ) ) ); + connect( ui.pushButtonsearch, SIGNAL( released ( void ) ), this, SLOT( searchKeywords( void ) ) ); + connect( ui.pushButtonDownload, SIGNAL( released ( void ) ), this, SLOT( download( void ) ) ); + //connect( ui.searchSummaryWidget, SIGNAL( itemSelectionChanged ( void ) ), this, SLOT( selectSearchResults( void ) ) ); + + connect ( ui.searchSummaryWidget, SIGNAL( currentItemChanged ( QTreeWidgetItem *, QTreeWidgetItem * ) ), + this, SLOT( selectSearchResults( void ) ) ); + + + /* hide the Tree +/- */ + ui.searchResultWidget -> setRootIsDecorated( false ); + ui.searchResultWidget -> setColumnHidden( SR_UID_COL,true ); + ui.searchResultWidget -> setColumnHidden( SR_REALSIZE_COL,true ); + ui.searchSummaryWidget -> setRootIsDecorated( false ); + + /* make it extended selection */ + ui.searchResultWidget -> setSelectionMode(QAbstractItemView::ExtendedSelection); + + + /* Set header resize modes and initial section sizes */ + ui.searchSummaryWidget->setColumnCount(3); + + QHeaderView * _smheader = ui.searchSummaryWidget->header () ; + _smheader->setResizeMode (0, QHeaderView::Interactive); + _smheader->setResizeMode (1, QHeaderView::Interactive); + _smheader->setResizeMode (2, QHeaderView::Interactive); + + _smheader->resizeSection ( 0, 80 ); + _smheader->resizeSection ( 1, 75 ); + _smheader->resizeSection ( 2, 75 ); + + ui.searchResultWidget->setColumnCount(6); + _smheader = ui.searchResultWidget->header () ; + _smheader->setResizeMode (0, QHeaderView::Custom); + _smheader->setResizeMode (1, QHeaderView::Interactive); + _smheader->setResizeMode (2, QHeaderView::Interactive); + _smheader->setResizeMode (3, QHeaderView::Interactive); + + _smheader->resizeSection ( 0, 20 ); + _smheader->resizeSection ( 1, 220 ); + _smheader->resizeSection ( 2, 75 ); + _smheader->resizeSection ( 3, 75 ); + _smheader->resizeSection ( 4, 75 ); + _smheader->resizeSection ( 5, 240 ); + + + // set header text aligment + QTreeWidgetItem * headerItem = ui.searchResultWidget->headerItem(); + headerItem->setTextAlignment(2, Qt::AlignRight | Qt::AlignRight); + headerItem->setTextAlignment(3, Qt::AlignRight | Qt::AlignRight); + + ui.searchResultWidget->sortItems(SR_NAME_COL, Qt::AscendingOrder); + + + +/* Hide platform specific features */ +#ifdef Q_WS_WIN + +#endif +} + +void TurtleSearchDialog::initialiseFileTypeMappings() +{ + /* edit these strings to change the range of extensions recognised by the search */ + TurtleSearchDialog::FileTypeExtensionMap->insert(FILETYPE_IDX_ANY, ""); + TurtleSearchDialog::FileTypeExtensionMap->insert(FILETYPE_IDX_AUDIO, "aac aif iff m3u mid midi mp3 mpa ogg ra ram wav wma"); + TurtleSearchDialog::FileTypeExtensionMap->insert(FILETYPE_IDX_ARCHIVE, "7z bz2 gz pkg rar sea sit sitx tar zip"); + TurtleSearchDialog::FileTypeExtensionMap->insert(FILETYPE_IDX_CDIMAGE, "iso nrg mdf"); + TurtleSearchDialog::FileTypeExtensionMap->insert(FILETYPE_IDX_DOCUMENT, "doc odt ott rtf pdf ps txt log msg wpd wps" ); + TurtleSearchDialog::FileTypeExtensionMap->insert(FILETYPE_IDX_PICTURE, "3dm 3dmf ai bmp drw dxf eps gif ico indd jpe jpeg jpg mng pcx pcc pct pgm " + "pix png psd psp qxd qxprgb sgi svg tga tif tiff xbm xcf"); + TurtleSearchDialog::FileTypeExtensionMap->insert(FILETYPE_IDX_PROGRAM, "app bat cgi com bin exe js pif py pl sh vb ws "); + TurtleSearchDialog::FileTypeExtensionMap->insert(FILETYPE_IDX_VIDEO, "3gp asf asx avi mov mp4 mkv flv mpeg mpg qt rm swf vob wmv"); + TurtleSearchDialog::initialised = true; +} + +void TurtleSearchDialog::searchtableWidgetCostumPopupMenu( QPoint point ) +{ + // block the popup if no results available + if ((ui.searchResultWidget->selectedItems()).size() == 0) return; + + // create the menu as required + if (contextMnu == 0) + { + contextMnu = new QMenu(this); + + downloadAct = new QAction(QIcon(IMAGE_START), tr( "Download" ), this ); + connect( downloadAct , SIGNAL( triggered() ), this, SLOT( download() ) ); + + broadcastonchannelAct = new QAction( tr( "Broadcast on Channel" ), this ); + connect( broadcastonchannelAct , SIGNAL( triggered() ), this, SLOT( broadcastonchannel() ) ); + + recommendtofriendsAct = new QAction( tr( "Recommend to Friends" ), this ); + connect( recommendtofriendsAct , SIGNAL( triggered() ), this, SLOT( recommendtofriends() ) ); + + + contextMnu->clear(); + contextMnu->addAction( downloadAct); + contextMnu->addSeparator(); + contextMnu->addAction( broadcastonchannelAct); + contextMnu->addAction( recommendtofriendsAct); + } + + QMouseEvent *mevent = new QMouseEvent( QEvent::MouseButtonPress, point, + Qt::RightButton, Qt::RightButton, Qt::NoModifier ); + contextMnu->exec( mevent->globalPos() ); +} + + +void TurtleSearchDialog::download() +{ + /* should also be able to handle multi-selection */ + QList itemsForDownload = ui.searchResultWidget->selectedItems(); + int numdls = itemsForDownload.size(); + QTreeWidgetItem * item; + bool attemptDownloadLocal = false; + + for (int i = 0; i < numdls; ++i) { + item = itemsForDownload.at(i); + // call the download + if (item->text(SR_ID_COL) != "Local") + { + std::cerr << "TurtleSearchDialog::download() Calling File Request"; + std::cerr << std::endl; + std::list srcIds; + srcIds.push_back(item->text(SR_UID_COL).toStdString()) ; + + rsFiles -> FileRequest((item->text(SR_NAME_COL)).toStdString(), + (item->text(SR_HASH_COL)).toStdString(), + (item->text(SR_REALSIZE_COL)).toInt(), + "", 0, srcIds); + + std::cout << "isuing file request from search dialog: -" << (item->text(SR_NAME_COL)).toStdString() << "-" << (item->text(SR_HASH_COL)).toStdString() << "-" << (item->text(SR_REALSIZE_COL)).toInt() << "-ids=" ; + for(std::list::const_iterator it(srcIds.begin());it!=srcIds.end();++it) + std::cout << *it << "-" << std::endl ; + } + else + { + attemptDownloadLocal = true; + } + } + if (attemptDownloadLocal) + { + QMessageBox::information(0, tr("Download Notice"), tr("Skipping Local Files")); + } +} + + +void TurtleSearchDialog::broadcastonchannel() +{ + + QMessageBox::warning(0, tr("Sorry"), tr("This function is not yet implemented.")); +} + + +void TurtleSearchDialog::recommendtofriends() +{ + QMessageBox::warning(0, tr("Sorry"), tr("This function is not yet implemented.")); + +} + + +/** context menu searchTablewidget2 **/ +void TurtleSearchDialog::searchtableWidget2CostumPopupMenu( QPoint point ) +{ + + // block the popup if no results available + if ((ui.searchSummaryWidget->selectedItems()).size() == 0) return; + + // create the menu as required + if (contextMnu2 == 0) + { + contextMnu2 = new QMenu( this ); + + searchRemoveAct = new QAction(QIcon(IMAGE_REMOVE), tr( "Remove" ), this ); + connect( searchRemoveAct , SIGNAL( triggered() ), this, SLOT( searchRemove() ) ); + + searchRemoveAllAct = new QAction(QIcon(IMAGE_REMOVEALL), tr( "Remove All" ), this ); + connect( searchRemoveAllAct , SIGNAL( triggered() ), this, SLOT( searchRemoveAll() ) ); + + contextMnu2->clear(); + contextMnu2->addAction( searchRemoveAct); + contextMnu2->addAction( searchRemoveAllAct); + } + + QMouseEvent *mevent2 = new QMouseEvent( QEvent::MouseButtonPress, point, Qt::RightButton, Qt::RightButton, Qt::NoModifier ); + contextMnu2->exec( mevent2->globalPos() ); +} + +/** remove selected search result **/ +void TurtleSearchDialog::searchRemove() +{ + /* get the current search id from the summary window */ + QTreeWidgetItem *ci = ui.searchSummaryWidget->currentItem(); + if (!ci) + return; + + /* get the searchId text */ + QString searchId = ci->text(SS_SEARCH_ID_COL); + + std::cerr << "TurtleSearchDialog::searchRemove(): searchId: " << searchId.toStdString(); + std::cerr << std::endl; + + /* show only matching searchIds in main window */ + int items = ui.searchResultWidget->topLevelItemCount(); + for(int i = 0; i < items;) + { + /* get item */ + QTreeWidgetItem *ti = ui.searchResultWidget->topLevelItem(i); + if (ti->text(SR_SEARCH_ID_COL) == searchId) + { + /* remove */ + delete (ui.searchResultWidget->takeTopLevelItem(i)); + items--; + } + else + { + /* step to the next */ + i++; + } + } + int sii = ui.searchSummaryWidget->indexOfTopLevelItem(ci); + if (sii != -1) + { + delete (ui.searchSummaryWidget->takeTopLevelItem(sii)); + } + + ui.searchResultWidget->update(); + ui.searchSummaryWidget->update(); +} + +/** remove all search results **/ +void TurtleSearchDialog::searchRemoveAll() +{ + ui.searchResultWidget->clear(); + ui.searchSummaryWidget->clear(); + nextSearchId = 1; +} + +/* ***************************************************************** + Advanced search implementation +*******************************************************************/ +// Event handlers for hide and show events +void TurtleSearchDialog::hideEvent(QHideEvent * event) +{ + showAdvSearchDialog(false); + MainPage::hideEvent(event); +} + +void TurtleSearchDialog::toggleAdvancedSearchDialog(bool toggled) +{ + // record the users preference for future reference + RshareSettings rsharesettings; + QString key (UI_PREF_ADVANCED_SEARCH); + rsharesettings.setValue(key, QVariant(toggled)); + + showAdvSearchDialog(toggled); +} + +void TurtleSearchDialog::showAdvSearchDialog(bool show) +{ + // instantiate if about to show for the first time + if (advSearchDialog == 0 && show) + { + advSearchDialog = new AdvancedSearchDialog(); + connect(advSearchDialog, SIGNAL(search(Expression*)), + this, SLOT(advancedSearch(Expression*))); + } + if (show) { + advSearchDialog->show(); + advSearchDialog->raise(); + advSearchDialog->setFocus(); + } else if (advSearchDialog != 0){ + advSearchDialog->hide(); + } +} + +void TurtleSearchDialog::advancedSearch(Expression* expression) +{ + advSearchDialog->hide(); + + /* call to core */ + std::list results; + rsFiles -> SearchBoolExp(expression, results); + + /* abstraction to allow reusee of tree rendering code */ + resultsToTree((advSearchDialog->getSearchAsString()).toStdString(), results); +} + + + +void TurtleSearchDialog::searchKeywords() +{ + QString qTxt = ui.lineEdit->text(); + std::string txt = qTxt.toStdString(); + + TurtleRequestId id = rsTurtle->turtleSearch(txt) ; +} + +void TurtleSearchDialog::updateFiles(qulonglong search_id,TurtleFileInfo files) +{ + std::cerr << "updating file list for id " << search_id << std::endl ; +#ifdef A_VIRER + QString qTxt = ui.lineEdit->text(); + std::string txt = qTxt.toStdString(); + + std::cerr << "TurtleSearchDialog::searchKeywords() : " << txt << std::endl; + + /* extract keywords from lineEdit */ + QStringList qWords = qTxt.split(" ", QString::SkipEmptyParts); + std::list words; + QStringListIterator qWordsIter(qWords); + while (qWordsIter.hasNext()) + { + words.push_back(qWordsIter.next().toStdString()); + } + + if (words.size() < 1) + { + /* ignore */ + return; + } + + /* call to core */ + std::list initialResults; + std::list * finalResults = 0; + + rsFiles -> SearchKeywords(words, initialResults,DIR_FLAGS_LOCAL | DIR_FLAGS_REMOTE); + /* which extensions do we use? */ + QString qExt, qName; + int extIndex; + bool matched =false; + FileDetail fd; + + if (ui.FileTypeComboBox->currentIndex() == FILETYPE_IDX_ANY) + { + finalResults = &initialResults; + } else { + finalResults = new std::list; + // amend the text description of the search + txt += " (" + ui.FileTypeComboBox->currentText().toStdString() + ")"; + // collect the extensions to use + QString extStr = TurtleSearchDialog::FileTypeExtensionMap->value(ui.FileTypeComboBox->currentIndex()); + QStringList extList = extStr.split(" "); + + // now iterate through the results ignoring those with wrong extensions + std::list::iterator resultsIter; + for (resultsIter = initialResults.begin(); resultsIter != initialResults.end(); resultsIter++) + { + fd = *resultsIter; + // get this file's extension + qName = QString::fromStdString(fd.name); + extIndex = qName.lastIndexOf("."); + if (extIndex >= 0) { + qExt = qName.mid(extIndex+1); + if (qExt != "" ) + { + // does it match? + matched = false; + /* iterate through the requested extensions */ + for (int i = 0; i < extList.size(); ++i) + { + if (qExt.toUpper() == extList.at(i).toUpper()) + { + finalResults->push_back(fd); + matched = true; + } + } + } + } + } + } + + /* abstraction to allow reusee of tree rendering code */ + resultsToTree(txt, *finalResults); +#endif +} + +void TurtleSearchDialog::resultsToTree(std::string txt, std::list results) +{ + /* translate search results */ + int searchId = nextSearchId++; + std::ostringstream out; + out << searchId; + + std::list::iterator it; + for(it = results.begin(); it != results.end(); it++) + { + QTreeWidgetItem *item = new QTreeWidgetItem(); + item->setText(SR_NAME_COL, QString::fromStdString(it->name)); + item->setText(SR_HASH_COL, QString::fromStdString(it->hash)); + item->setText(SR_SEARCH_ID_COL, QString::fromStdString(out.str())); + + QString ext = QFileInfo(QString::fromStdString(it->name)).suffix(); + if (ext == "jpg" || ext == "jpeg" || ext == "png" || ext == "gif" + || ext == "bmp" || ext == "ico" || ext == "svg") + { + item->setIcon(SR_ICON_COL, QIcon(":/images/FileTypePicture.png")); + item->setText(SR_TYPE_COL, QString::fromUtf8("Picture")); + } + else if (ext == "avi" || ext == "mpg" || ext == "mpeg" || ext == "wmv" + || ext == "mkv" || ext == "mp4" || ext == "flv" || ext == "mov" + || ext == "vob" || ext == "qt" || ext == "rm" || ext == "3gp") + { + item->setIcon(SR_ICON_COL, QIcon(":/images/FileTypeVideo.png")); + item->setText(SR_TYPE_COL, QString::fromUtf8("Video")); + } + else if (ext == "ogg" || ext == "mp3" || ext == "wav" || ext == "wma") + { + item->setIcon(SR_ICON_COL, QIcon(":/images/FileTypeAudio.png")); + item->setText(SR_TYPE_COL, QString::fromUtf8("Audio")); + } + else if (ext == "tar" || ext == "bz2" || ext == "zip" || ext == "gz" + || ext == "rar" || ext == "rpm" || ext == "deb") + { + item->setIcon(SR_ICON_COL, QIcon(":/images/FileTypeArchive.png")); + item->setText(SR_TYPE_COL, QString::fromUtf8("Archive")); + } + else if (ext == "app" || ext == "bat" || ext == "cgi" || ext == "com" + || ext == "bin" || ext == "exe" || ext == "js" || ext == "pif" + || ext == "py" || ext == "pl" || ext == "sh" || ext == "vb" || ext == "ws") + { + item->setIcon(SR_ICON_COL, QIcon(":/images/FileTypeProgram.png")); + item->setText(SR_TYPE_COL, QString::fromUtf8("Program")); + } + else if (ext == "iso" || ext == "nrg" || ext == "mdf" ) + { + item->setIcon(SR_ICON_COL, QIcon(":/images/FileTypeCDImage.png")); + item->setText(SR_TYPE_COL, QString::fromUtf8("CD-Image")); + } + else if (ext == "txt" || ext == "cpp" || ext == "c" || ext == "h") + { + item->setIcon(SR_ICON_COL, QIcon(":/images/FileTypeDocument.png")); + item->setText(SR_TYPE_COL, QString::fromUtf8("Document")); + } + else if (ext == "doc" || ext == "rtf" || ext == "sxw" || ext == "xls" + || ext == "sxc" || ext == "odt" || ext == "ods") + { + item->setIcon(SR_ICON_COL, QIcon(":/images/FileTypeDocument.png")); + item->setText(SR_TYPE_COL, QString::fromUtf8("Document")); + } + else if (ext == "html" || ext == "htm" || ext == "php") + { + item->setIcon(SR_ICON_COL, QIcon(":/images/FileTypeDocument.png")); + item->setText(SR_TYPE_COL, QString::fromUtf8("Document")); + } + else + { + item->setIcon(SR_ICON_COL, QIcon(":/images/FileTypeAny.png")); + } + + + + /* + * to facilitate downlaods we need to save the file size too + */ + //QVariant * variant = new QVariant((qulonglong)it->size); + //item->setText(SR_SIZE_COL, QString(variant->toString())); + item->setText(SR_SIZE_COL, misc::friendlyUnit(it->size)); + item->setText(SR_REALSIZE_COL, QString::number(it->size)); + item->setTextAlignment( SR_SIZE_COL, Qt::AlignRight ); + + + // I kept the color code green=online, grey=offline + // Qt::blue is very dark and hardly compatible with the black text on it. + // + if (it->id == "Local") + { + item->setText(SR_ID_COL, QString::fromStdString(it->id)); + item->setText(SR_UID_COL, QString::fromStdString(rsPeers->getOwnId())); + item->setBackground(3, QBrush(Qt::red)); /* colour green? */ + } + else + { + item->setText(SR_ID_COL, QString::fromStdString( rsPeers->getPeerName(it->id))); + item->setText(SR_UID_COL, QString::fromStdString( it->id)); + if(rsPeers->isOnline(it->id)) + item->setBackground(3, QBrush(Qt::green)); + else + item->setBackground(3, QBrush(Qt::lightGray)); + } + + ui.searchResultWidget->addTopLevelItem(item); + } + + /* add to the summary as well */ + + QTreeWidgetItem *item = new QTreeWidgetItem(); + item->setText(SS_TEXT_COL, QString::fromStdString(txt)); + std::ostringstream out2; + out2 << results.size(); + + item->setText(SS_COUNT_COL, QString::fromStdString(out2.str())); + item->setText(SS_SEARCH_ID_COL, QString::fromStdString(out.str())); + + ui.searchSummaryWidget->addTopLevelItem(item); + ui.searchSummaryWidget->setCurrentItem(item); + + /* select this search result */ + selectSearchResults(); +} + + +//void QTreeWidget::currentItemChanged ( QTreeWidgetItem * current, QTreeWidgetItem * previous ) [signal] + + +void TurtleSearchDialog::selectSearchResults() +{ + /* highlight this search in summary window */ + QTreeWidgetItem *ci = ui.searchSummaryWidget->currentItem(); + if (!ci) + return; + + /* get the searchId text */ + QString searchId = ci->text(SS_SEARCH_ID_COL); + + std::cerr << "TurtleSearchDialog::selectSearchResults(): searchId: " << searchId.toStdString(); + std::cerr << std::endl; + + /* show only matching searchIds in main window */ + int items = ui.searchResultWidget->topLevelItemCount(); + for(int i = 0; i < items; i++) + { + /* get item */ + QTreeWidgetItem *ti = ui.searchResultWidget->topLevelItem(i); + if (ti->text(SR_SEARCH_ID_COL) == searchId) + { + ti->setHidden(false); + } + else + { + ti->setHidden(true); + } + } + ui.searchResultWidget->update(); +} + + diff --git a/retroshare-gui/src/gui/TurtleSearchDialog.h b/retroshare-gui/src/gui/TurtleSearchDialog.h new file mode 100644 index 000000000..62f53beb8 --- /dev/null +++ b/retroshare-gui/src/gui/TurtleSearchDialog.h @@ -0,0 +1,133 @@ +/**************************************************************** +* RShare is distributed under the following license: +* +* Copyright (C) 2006, crypton +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301, USA. +****************************************************************/ + +#ifndef _TURTLESEARCHDIALOG_H +#define _TURTLESEARCHDIALOG_H + +#include +#include + +#include +#include + +//#include + +#include +#include "mainpage.h" +#include "ui_TurtleSearchDialog.h" +#include "advsearch/advancedsearchdialog.h" +#include "Preferences/rsharesettings.h" + +class FileDetail; + +class TurtleSearchDialog : public MainPage +{ + Q_OBJECT + + public: + /** Default Constructor */ + TurtleSearchDialog(QWidget *parent = 0); + /** Default Destructor */ + + + public slots: + void updateFiles(qulonglong request_id,TurtleFileInfo file) ; + + private slots: + + /** Create the context popup menu and it's submenus */ + void searchtableWidgetCostumPopupMenu( QPoint point ); + void searchtableWidget2CostumPopupMenu( QPoint point ); + + void download(); + + void broadcastonchannel(); + + void recommendtofriends(); + + + + void searchRemove(); + + void searchRemoveAll(); + + void searchKeywords(); + + /** management of the adv search dialog object when switching search modes */ + void toggleAdvancedSearchDialog(bool); + void hideEvent(QHideEvent * event); + + /** raises (and if necessary instantiates) the advanced search dialog */ + void showAdvSearchDialog(bool=true); + + /** perform the advanced search */ + void advancedSearch(Expression*); + + void selectSearchResults(); + + private: + /** render the results to the tree widget display */ + void resultsToTree(std::string, std::list); + + /** the advanced search dialog instance */ + AdvancedSearchDialog * advSearchDialog; + + /** Define the popup menus for the Context menu */ + QMenu* contextMnu; + + QMenu* contextMnu2; + + /** Defines the actions for the context menu */ + QAction* downloadAct; + QAction* broadcastonchannelAct; + QAction* recommendtofriendsAct; + + QAction* searchRemoveAct; + QAction* searchRemoveAllAct; + + /** Contains the mapping of filetype combobox to filetype extensions */ + static const int FILETYPE_IDX_ANY; + static const int FILETYPE_IDX_ARCHIVE; + static const int FILETYPE_IDX_AUDIO; + static const int FILETYPE_IDX_CDIMAGE; + static const int FILETYPE_IDX_DOCUMENT; + static const int FILETYPE_IDX_PICTURE; + static const int FILETYPE_IDX_PROGRAM; + static const int FILETYPE_IDX_VIDEO; + + + static QMap * FileTypeExtensionMap; + static bool initialised; + void initialiseFileTypeMappings(); + + /**** + QTreeWidget *searchtableWidget; + QTreeWidget *searchtablewidget2; + ****/ + + int nextSearchId; + + /** Qt Designer generated object */ + Ui::TurtleSearchDialog ui; +}; + +#endif + diff --git a/retroshare-gui/src/gui/TurtleSearchDialog.ui b/retroshare-gui/src/gui/TurtleSearchDialog.ui new file mode 100644 index 000000000..eb7a11796 --- /dev/null +++ b/retroshare-gui/src/gui/TurtleSearchDialog.ui @@ -0,0 +1,1230 @@ + + TurtleSearchDialog + + + + 0 + 0 + 661 + 289 + + + + + 1 + 1 + + + + + + + + + 0 + 0 + 0 + + + + + + + 208 + 208 + 208 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 104 + 104 + 104 + + + + + + + 139 + 139 + 139 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 128 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 255 + + + + + + + 255 + 0 + 255 + + + + + + + 231 + 231 + 231 + + + + + + + + + 0 + 0 + 0 + + + + + + + 208 + 208 + 208 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 104 + 104 + 104 + + + + + + + 139 + 139 + 139 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 192 + 192 + 192 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 255 + + + + + + + 255 + 0 + 255 + + + + + + + 231 + 231 + 231 + + + + + + + + + 104 + 104 + 104 + + + + + + + 208 + 208 + 208 + + + + + + + 255 + 255 + 255 + + + + + + + 247 + 247 + 247 + + + + + + + 104 + 104 + 104 + + + + + + + 139 + 139 + 139 + + + + + + + 104 + 104 + 104 + + + + + + + 255 + 255 + 255 + + + + + + + 104 + 104 + 104 + + + + + + + 240 + 240 + 240 + + + + + + + 240 + 240 + 240 + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 128 + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 0 + 255 + + + + + + + 255 + 0 + 255 + + + + + + + 231 + 231 + 231 + + + + + + + + + Arial + 8 + 50 + false + false + false + false + + + + Qt::NoContextMenu + + + + 6 + + + 0 + + + 6 + + + 0 + + + 1 + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 6 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + + 34 + 34 + + + + + 34 + 34 + + + + Toggle advanced searching on and off. + + + + + + :/images/advsearch_24x24.png + + + + 24 + 24 + + + + true + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + 255 + 0 + + + + + 390 + 24 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::NoFrame + + + QFrame::Plain + + + <h3>Simple Search:</h3> + + + + + + + Qt::Horizontal + + + + 10 + 20 + + + + + + + + + 0 + 0 + + + + Keywords + + + + + + + + 3 + 0 + + + + + 558 + 16777215 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 16 + 32 + + + + + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Arial'; font-size:8pt; font-weight:400; font-style:normal; text-decoration:none;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Format</p></body></html> + + + + + + + + Any + + + :/images/FileTypeAny.png + + + + + Archive + + + :/images/FileTypeArchive.png + + + + + Audio + + + :/images/FileTypeAudio.png + + + + + CD-Image + + + :/images/FileTypeCDImage.png + + + + + Document + + + :/images/FileTypeDocument.png + + + + + Picture + + + :/images/FileTypePicture.png + + + + + Program + + + :/images/FileTypeProgram.png + + + + + Video + + + :/images/FileTypeVideo.png + + + + + + + + Qt::Horizontal + + + + 16 + 20 + + + + + + + + + 34 + 34 + + + + + 34 + 34 + + + + Perform simple search + + + + + + :/images/find.png + + + + 24 + 24 + + + + + + + + + + + + + true + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <h3>Advanced Search:</h3> + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 50 + 20 + + + + + + + + true + + + + 0 + 0 + + + + Show Advanced Search Tool + + + + + + + + + + + + + + + 6 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + + 34 + 34 + + + + + 34 + 34 + + + + Download + + + + + + :/images/down_24x24.png + + + + 24 + 24 + + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + + + 0 + 1 + + + + Qt::Horizontal + + + + + 1 + 0 + + + + Qt::CustomContextMenu + + + true + + + QAbstractItemView::DragOnly + + + true + + + + + + + + + Filename + + + + + Size + + + + + Sources + + + + + Type + + + + + Hash + + + + + + + 0 + 1 + + + + + 0 + 0 + + + + + 0 + 0 + + + + true + + + Qt::CustomContextMenu + + + true + + + + KeyWords + + + + + Results + + + + + Search Id + + + + + + + + + + + + SearchTreeWidget + QTreeWidget +
gui/SearchTreeWidget.h
+
+
+ + + + + + toggleAdvancedSearchBtn + toggled(bool) + SimpleSearchPanel + setHidden(bool) + + + 29 + 35 + + + 72 + 43 + + + + + toggleAdvancedSearchBtn + toggled(bool) + AdvancedSearchPanel + setVisible(bool) + + + 28 + 29 + + + 80 + 64 + + + + +
diff --git a/retroshare-gui/src/gui/images.qrc b/retroshare-gui/src/gui/images.qrc index cfbe30004..51ca75060 100644 --- a/retroshare-gui/src/gui/images.qrc +++ b/retroshare-gui/src/gui/images.qrc @@ -17,6 +17,7 @@ images/channels.png images/channeldelete.png images/channelsubscribe.png + images/turtle.png images/channels16.png images/channels24.png images/channels32.png diff --git a/retroshare-gui/src/gui/images/turtle.png b/retroshare-gui/src/gui/images/turtle.png new file mode 100644 index 0000000000000000000000000000000000000000..84ece220592505ffb17c0fa9c1b12dbec887edb0 GIT binary patch literal 35232 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 literal 0 HcmV?d00001 diff --git a/retroshare-gui/src/main.cpp b/retroshare-gui/src/main.cpp index a899cc57c..bc8c71a22 100644 --- a/retroshare-gui/src/main.cpp +++ b/retroshare-gui/src/main.cpp @@ -153,6 +153,11 @@ int main(int argc, char *argv[]) // avoid clashes between infos from threads. // QObject::connect(notify,SIGNAL(hashingInfoChanged(const QString&)),w ,SLOT(updateHashingInfo(const QString&))) ; +#ifdef TURTLE_HOPPING + qRegisterMetaType("TurtleFileInfo") ; + std::cerr << "connecting signals and slots" << std::endl ; + QObject::connect(notify,SIGNAL(gotTurtleSearchResult(qulonglong,TurtleFileInfo)),w->turtleDialog,SLOT(updateFiles(qulonglong,TurtleFileInfo))) ; +#endif QObject::connect(notify,SIGNAL(filesPreModChanged(bool)) ,w->sharedfilesDialog,SLOT(preModDirectories(bool) )) ; QObject::connect(notify,SIGNAL(filesPostModChanged(bool)) ,w->sharedfilesDialog,SLOT(postModDirectories(bool) )) ; QObject::connect(notify,SIGNAL(transfersChanged()) ,w->transfersDialog ,SLOT(insertTransfers() )) ; diff --git a/retroshare-gui/src/rsiface/notifyqt.cpp b/retroshare-gui/src/rsiface/notifyqt.cpp index 01cb01e62..f7f6909ac 100644 --- a/retroshare-gui/src/rsiface/notifyqt.cpp +++ b/retroshare-gui/src/rsiface/notifyqt.cpp @@ -3,6 +3,9 @@ #include "rsiface/rsnotify.h" #include "rsiface/rspeers.h" #include "rsiface/rsphoto.h" +#ifdef TURTLE_HOPPING +#include +#endif #include "gui/NetworkDialog.h" #include "gui/PeersDialog.h" @@ -36,6 +39,18 @@ void NotifyQt::notifyErrorMsg(int list, int type, std::string msg) return; } +#ifdef TURTLE_HOPPING +void NotifyQt::notifyTurtleSearchResult(uint32_t search_id,const std::list& files) +{ + std::cerr << "in notify search result..." << std::endl ; + +// QList qfiles ; + + for(std::list::const_iterator it(files.begin());it!=files.end();++it) + emit gotTurtleSearchResult(search_id,*it) ; +// qfiles.push_back(*it) ; +} +#endif void NotifyQt::notifyHashingInfo(std::string fileinfo) { emit hashingInfoChanged(QString::fromStdString(fileinfo)) ; @@ -56,12 +71,21 @@ void NotifyQt::notifyListChange(int list, int type) switch(list) { case NOTIFY_LIST_NEIGHBOURS: +#ifdef DEBUG + std::cerr << "received neighbrs changed" << std::endl ; +#endif emit neighborsChanged(); break; case NOTIFY_LIST_FRIENDS: +#ifdef DEBUG + std::cerr << "received friends changed" << std::endl ; +#endif emit friendsChanged() ; break; case NOTIFY_LIST_DIRLIST: +#ifdef DEBUG + std::cerr << "received files changed" << std::endl ; +#endif emit filesPostModChanged(false) ; /* Remote */ emit filesPostModChanged(true) ; /* Local */ break; @@ -69,15 +93,24 @@ void NotifyQt::notifyListChange(int list, int type) //displaySearch(); break; case NOTIFY_LIST_MESSAGELIST: +#ifdef DEBUG + std::cerr << "received msg changed" << std::endl ; +#endif emit messagesChanged() ; break; case NOTIFY_LIST_CHANNELLIST: //displayChannels(); break; case NOTIFY_LIST_TRANSFERLIST: +#ifdef DEBUG + std::cerr << "received transfer changed" << std::endl ; +#endif emit transfersChanged() ; break; case NOTIFY_LIST_CONFIG: +#ifdef DEBUG + std::cerr << "received config changed" << std::endl ; +#endif emit configChanged() ; break ; default: @@ -133,6 +166,8 @@ void NotifyQt::UpdateGUI() /* hack to force updates until we've fixed that part */ static time_t lastTs = 0; +// std::cerr << "Got update signal t=" << lastTs << std::endl ; + if (time(NULL) > lastTs) // always update, every 1 sec. { emit transfersChanged(); diff --git a/retroshare-gui/src/rsiface/notifyqt.h b/retroshare-gui/src/rsiface/notifyqt.h index 39d3c414d..bee6382f1 100644 --- a/retroshare-gui/src/rsiface/notifyqt.h +++ b/retroshare-gui/src/rsiface/notifyqt.h @@ -14,8 +14,12 @@ class ChatDialog; class MessagesDialog; class ChannelsDialog; class MessengerWindow; +#ifdef TURTLE_HOPPING +#include "rsiface/rsturtle.h" +class TurtleSearchDialog ; - +struct TurtleFileInfo ; +#endif //class NotifyQt: public NotifyBase, public QObject class NotifyQt: public QObject, public NotifyBase @@ -46,6 +50,9 @@ class NotifyQt: public QObject, public NotifyBase virtual void notifyErrorMsg(int list, int sev, std::string msg); virtual void notifyChat(); virtual void notifyHashingInfo(std::string fileinfo); +#ifdef TURTLE_HOPPING + virtual void notifyTurtleSearchResult(uint32_t search_id,const std::list& found_files); +#endif signals: // It's beneficial to send info to the GUI using signals, because signals are thread-safe @@ -59,6 +66,7 @@ class NotifyQt: public QObject, public NotifyBase void neighborsChanged() const ; void messagesChanged() const ; void configChanged() const ; + void gotTurtleSearchResult(qulonglong search_id,TurtleFileInfo file) const ; public slots: diff --git a/retroshare-gui/src/rsiface/rsfiles.h b/retroshare-gui/src/rsiface/rsfiles.h index 3f8575035..58335ee48 100644 --- a/retroshare-gui/src/rsiface/rsfiles.h +++ b/retroshare-gui/src/rsiface/rsfiles.h @@ -130,7 +130,7 @@ virtual bool ExtraFileMove(std::string fname, std::string hash, uint64_t size, virtual int RequestDirDetails(std::string uid, std::string path, DirDetails &details) = 0; virtual int RequestDirDetails(void *ref, DirDetails &details, uint32_t flags) = 0; -virtual int SearchKeywords(std::list keywords, std::list &results) = 0; +virtual int SearchKeywords(std::list keywords, std::list &results,uint32_t flags) = 0; virtual int SearchBoolExp(Expression * exp, std::list &results) = 0; /*** diff --git a/retroshare-gui/src/rsiface/rsiface.h b/retroshare-gui/src/rsiface/rsiface.h index 215e54f54..9a58454e5 100644 --- a/retroshare-gui/src/rsiface/rsiface.h +++ b/retroshare-gui/src/rsiface/rsiface.h @@ -35,6 +35,9 @@ class NotifyBase; class RsIface; class RsControl; class RsInit; +#ifdef TURTLE_HOPPING +struct TurtleFileInfo ; +#endif /* declare single RsIface for everyone to use! */ @@ -200,6 +203,9 @@ class NotifyBase virtual void notifyErrorMsg(int list, int sev, std::string msg) { (void) list; (void) sev; (void) msg; return; } virtual void notifyChat() { return; } virtual void notifyHashingInfo(std::string fileinfo) { (void)fileinfo; return ; } +#ifdef TURTLE_HOPPING + virtual void notifyTurtleSearchResult(uint32_t search_id,const std::list& files) { (void)files; } +#endif }; const int NOTIFY_LIST_NEIGHBOURS = 1; diff --git a/retroshare-gui/src/rsiface/rsturtle.h b/retroshare-gui/src/rsiface/rsturtle.h index 0bfb4937a..abaf4e24e 100644 --- a/retroshare-gui/src/rsiface/rsturtle.h +++ b/retroshare-gui/src/rsiface/rsturtle.h @@ -35,6 +35,16 @@ extern RsTurtle *rsTurtle ; typedef uint32_t TurtleRequestId ; +// This is the structure used to send back results of the turtle search +// to the notifyBase class. + +struct TurtleFileInfo +{ + std::string hash ; + std::string name ; + uint64_t size ; +}; + // Interface class for turtle hopping. // // This class mainly interacts with the turtle router, that is responsible