diff options
author | Kevin Smith <git@kismith.co.uk> | 2010-11-18 12:09:08 (GMT) |
---|---|---|
committer | Kevin Smith <git@kismith.co.uk> | 2010-12-22 20:19:07 (GMT) |
commit | 9f04a7ec3429303118f12607703b877d8ba43888 (patch) | |
tree | 3e868395fa3c4f9cbbf84614094bb0251b425c15 /Swift/Controllers/Chat | |
parent | 04b99ce8430109d0467c29c85cb6bacb85c54c44 (diff) | |
download | swift-9f04a7ec3429303118f12607703b877d8ba43888.zip swift-9f04a7ec3429303118f12607703b877d8ba43888.tar.bz2 |
Basic User Search support, and Find Rooms cleanup.
Adds a throbber to the MUC search, turns the Add Contact dialog into something searchy, adds the option to open chats to arbitrary JIDs.
Resolves: #614
Resolves: #695
Resolves: #436
Release-Notes: On servers that support it, users can now perform searches for contacts to add or chat to.
Diffstat (limited to 'Swift/Controllers/Chat')
-rw-r--r-- | Swift/Controllers/Chat/MUCSearchController.cpp | 144 | ||||
-rw-r--r-- | Swift/Controllers/Chat/MUCSearchController.h | 14 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UserSearchController.cpp | 122 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UserSearchController.h | 59 |
4 files changed, 254 insertions, 85 deletions
diff --git a/Swift/Controllers/Chat/MUCSearchController.cpp b/Swift/Controllers/Chat/MUCSearchController.cpp index 7368dbb..c254e51 100644 --- a/Swift/Controllers/Chat/MUCSearchController.cpp +++ b/Swift/Controllers/Chat/MUCSearchController.cpp @@ -11,22 +11,26 @@ #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> -#include "Swiften/Disco/GetDiscoInfoRequest.h" -#include "Swiften/Disco/GetDiscoItemsRequest.h" +#include <Swiften/Disco/GetDiscoInfoRequest.h> +#include <Swiften/Disco/GetDiscoItemsRequest.h> -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swift/Controllers/UIEvents/RequestMUCSearchUIEvent.h" -#include "Swift/Controllers/UIInterfaces/MUCSearchWindow.h" -#include "Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h" +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/RequestMUCSearchUIEvent.h> +#include <Swift/Controllers/UIInterfaces/MUCSearchWindow.h> +#include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h> +#include <Swift/Controllers/DiscoServiceWalker.h> +#include <Swiften/Client/NickResolver.h> namespace Swift { static const String SEARCHED_SERVICES = "searchedServices"; -MUCSearchController::MUCSearchController(const JID& jid, UIEventStream* uiEventStream, MUCSearchWindowFactory* factory, IQRouter* iqRouter, SettingsProvider* settings) : jid_(jid) { +MUCSearchController::MUCSearchController(const JID& jid, UIEventStream* uiEventStream, MUCSearchWindowFactory* factory, IQRouter* iqRouter, SettingsProvider* settings, NickResolver *nickResolver) : jid_(jid) { iqRouter_ = iqRouter; settings_ = settings; uiEventStream_ = uiEventStream; + nickResolver_ = nickResolver; + itemsInProgress_ = 0; uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&MUCSearchController::handleUIEvent, this, _1)); window_ = NULL; factory_ = factory; @@ -34,6 +38,9 @@ MUCSearchController::MUCSearchController(const JID& jid, UIEventStream* uiEventS } MUCSearchController::~MUCSearchController() { + foreach (DiscoServiceWalker* walker, walksInProgress_) { + delete walker; + } delete window_; } @@ -42,12 +49,12 @@ void MUCSearchController::handleUIEvent(boost::shared_ptr<UIEvent> event) { if (searchEvent) { if (!window_) { window_ = factory_->createMUCSearchWindow(uiEventStream_); - window_->onAddService.connect(boost::bind(&MUCSearchController::handleAddService, this, _1, true)); + window_->onAddService.connect(boost::bind(&MUCSearchController::handleAddService, this, _1)); window_->addSavedServices(savedServices_); - handleAddService(JID(jid_.getDomain()), true); + handleAddService(JID(jid_.getDomain())); } window_->setMUC(""); - window_->setNick(jid_.getNode()); + window_->setNick(nickResolver_->jidToNick(jid_)); window_->show(); return; } @@ -79,76 +86,66 @@ void MUCSearchController::addAndSaveServices(const JID& jid) { window_->addSavedServices(savedServices_); } -void MUCSearchController::handleAddService(const JID& jid, bool userTriggered) { - if (userTriggered) { - addAndSaveServices(jid); - } - if (std::find(services_.begin(), services_.end(), jid) != services_.end()) { - if (!userTriggered) { - /* No infinite recursion. (Some buggy servers do infinitely deep disco of themselves)*/ - return; - } - } else if (userTriggered) { - services_.push_back(jid); - serviceDetails_[jid].setComplete(false); - refreshView(); - } +void MUCSearchController::handleAddService(const JID& jid) { if (!jid.isValid()) { //Set Window to say error this isn't valid return; } - GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(jid, iqRouter_); - discoInfoRequest->onResponse.connect(boost::bind(&MUCSearchController::handleDiscoInfoResponse, this, _1, _2, jid)); - discoInfoRequest->send(); -} - -void MUCSearchController::removeService(const JID& jid) { - serviceDetails_.erase(jid); - services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end()); + addAndSaveServices(jid); + services_.push_back(jid); + serviceDetails_[jid].setComplete(false); + window_->setSearchInProgress(true); refreshView(); + DiscoServiceWalker* walker = new DiscoServiceWalker(jid, iqRouter_); + walker->onServiceFound.connect(boost::bind(&MUCSearchController::handleDiscoServiceFound, this, _1, _2)); + walker->onWalkComplete.connect(boost::bind(&MUCSearchController::handleDiscoWalkFinished, this, walker)); + walksInProgress_.push_back(walker); + walker->beginWalk(); } -void MUCSearchController::handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error, const JID& jid) { - if (error) { - handleDiscoError(jid, error); - return; - } - GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(jid, iqRouter_); - bool mucService = false; - bool couldContainServices = false; +void MUCSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info) { + bool isMUC; String name; foreach (DiscoInfo::Identity identity, info->getIdentities()) { - if ((identity.getCategory() == "directory" - && identity.getType() == "chatroom") - || (identity.getCategory() == "conference" - && identity.getType() == "text")) { - mucService = true; - name = identity.getName(); - } - if (identity.getCategory() == "server") { - couldContainServices = true; - name = identity.getName(); - } + if ((identity.getCategory() == "directory" + && identity.getType() == "chatroom") + || (identity.getCategory() == "conference" + && identity.getType() == "text")) { + isMUC = true; + name = identity.getName(); + } } - services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end()); /* Bring it back to the end on a refresh */ - services_.push_back(jid); - serviceDetails_[jid].setName(name); - serviceDetails_[jid].setJID(jid); - serviceDetails_[jid].setComplete(false); - - if (mucService) { + if (isMUC) { + services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end()); /* Bring it back to the end on a refresh */ + services_.push_back(jid); + serviceDetails_[jid].setName(name); + serviceDetails_[jid].setJID(jid); + serviceDetails_[jid].setComplete(false); + itemsInProgress_++; + GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(jid, iqRouter_); discoItemsRequest->onResponse.connect(boost::bind(&MUCSearchController::handleRoomsItemsResponse, this, _1, _2, jid)); - } else if (couldContainServices) { - discoItemsRequest->onResponse.connect(boost::bind(&MUCSearchController::handleServerItemsResponse, this, _1, _2, jid)); + discoItemsRequest->send(); } else { removeService(jid); - return; } - discoItemsRequest->send(); + refreshView(); +} + +void MUCSearchController::handleDiscoWalkFinished(DiscoServiceWalker* walker) { + walksInProgress_.erase(std::remove(walksInProgress_.begin(), walksInProgress_.end(), walker), walksInProgress_.end()); + updateInProgressness(); + delete walker; +} + +void MUCSearchController::removeService(const JID& jid) { + serviceDetails_.erase(jid); + services_.erase(std::remove(services_.begin(), services_.end(), jid), services_.end()); refreshView(); } void MUCSearchController::handleRoomsItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid) { + itemsInProgress_--; + updateInProgressness(); if (error) { handleDiscoError(jid, error); return; @@ -161,25 +158,6 @@ void MUCSearchController::handleRoomsItemsResponse(boost::shared_ptr<DiscoItems> refreshView(); } -void MUCSearchController::handleServerItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid) { - if (error) { - handleDiscoError(jid, error); - return; - } - if (jid.isValid()) { - removeService(jid); - } - foreach (DiscoItems::Item item, items->getItems()) { - if (item.getNode().isEmpty()) { - /* Don't look at noded items. It's possible that this will exclude some services, - * but I've never seen one in the wild, and it's an easy fix for not looping. - */ - handleAddService(item.getJID()); - } - } - refreshView(); -} - void MUCSearchController::handleDiscoError(const JID& jid, ErrorPayload::ref error) { serviceDetails_[jid].setComplete(true); serviceDetails_[jid].setError(error->getText()); @@ -192,4 +170,8 @@ void MUCSearchController::refreshView() { } } +void MUCSearchController::updateInProgressness() { + window_->setSearchInProgress(walksInProgress_.size() + itemsInProgress_ > 0); +} + } diff --git a/Swift/Controllers/Chat/MUCSearchController.h b/Swift/Controllers/Chat/MUCSearchController.h index f09a801..6caee54 100644 --- a/Swift/Controllers/Chat/MUCSearchController.h +++ b/Swift/Controllers/Chat/MUCSearchController.h @@ -27,6 +27,8 @@ namespace Swift { class MUCSearchWindow; class MUCSearchWindowFactory; class IQRouter; + class DiscoServiceWalker; + class NickResolver; class MUCService { public: @@ -86,28 +88,32 @@ namespace Swift { class MUCSearchController { public: - MUCSearchController(const JID& jid, UIEventStream* uiEventStream, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, SettingsProvider* settings); + MUCSearchController(const JID& jid, UIEventStream* uiEventStream, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, SettingsProvider* settings, NickResolver* nickResolver); ~MUCSearchController(); private: void handleUIEvent(boost::shared_ptr<UIEvent> event); - void handleAddService(const JID& jid, bool userTriggered=false); - void handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error, const JID& jid); + void handleAddService(const JID& jid); void handleRoomsItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid); - void handleServerItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, const JID& jid); void handleDiscoError(const JID& jid, ErrorPayload::ref error); + void handleDiscoServiceFound(const JID&, boost::shared_ptr<DiscoInfo>); + void handleDiscoWalkFinished(DiscoServiceWalker* walker); void removeService(const JID& jid); void refreshView(); void loadServices(); void addAndSaveServices(const JID& jid); + void updateInProgressness(); UIEventStream* uiEventStream_; MUCSearchWindow* window_; MUCSearchWindowFactory* factory_; SettingsProvider* settings_; + NickResolver* nickResolver_; boost::bsignals::scoped_connection uiEventConnection_; std::vector<JID> services_; std::vector<JID> savedServices_; std::map<JID, MUCService> serviceDetails_; + std::vector<DiscoServiceWalker*> walksInProgress_; IQRouter* iqRouter_; JID jid_; + int itemsInProgress_; }; } diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp new file mode 100644 index 0000000..c3e40c8 --- /dev/null +++ b/Swift/Controllers/Chat/UserSearchController.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swift/Controllers/Chat/UserSearchController.h" + +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> + +#include <Swiften/Disco/GetDiscoInfoRequest.h> +#include <Swiften/Disco/GetDiscoItemsRequest.h> + +#include <Swift/Controllers/DiscoServiceWalker.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/RequestUserSearchUIEvent.h> +#include <Swift/Controllers/UIInterfaces/UserSearchWindow.h> +#include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h> + +namespace Swift { +UserSearchController::UserSearchController(const JID& jid, UIEventStream* uiEventStream, UserSearchWindowFactory* factory, IQRouter* iqRouter) : jid_(jid) { + iqRouter_ = iqRouter; + uiEventStream_ = uiEventStream; + uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&UserSearchController::handleUIEvent, this, _1)); + window_ = NULL; + factory_ = factory; + discoWalker_ = NULL; +} + +UserSearchController::~UserSearchController() { + delete window_; + delete discoWalker_; +} + +void UserSearchController::handleUIEvent(boost::shared_ptr<UIEvent> event) { + boost::shared_ptr<RequestUserSearchUIEvent> searchEvent = boost::dynamic_pointer_cast<RequestUserSearchUIEvent>(event); + if (searchEvent) { + if (!window_) { + window_ = factory_->createUserSearchWindow(uiEventStream_); + window_->onFormRequested.connect(boost::bind(&UserSearchController::handleFormRequested, this, _1)); + window_->onSearchRequested.connect(boost::bind(&UserSearchController::handleSearch, this, _1, _2)); + window_->setSelectedService(JID(jid_.getDomain())); + window_->clear(); + } + window_->show(); + return; + } +} + +void UserSearchController::handleFormRequested(const JID& service) { + window_->setSearchError(false); + window_->setServerSupportsSearch(true); + //Abort a previous search if is active + delete discoWalker_; + discoWalker_ = new DiscoServiceWalker(service, iqRouter_); + discoWalker_->onServiceFound.connect(boost::bind(&UserSearchController::handleDiscoServiceFound, this, _1, _2, discoWalker_)); + discoWalker_->onWalkComplete.connect(boost::bind(&UserSearchController::handleDiscoWalkFinished, this, discoWalker_)); + discoWalker_->beginWalk(); +} + +void UserSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info, DiscoServiceWalker* walker) { + bool isUserDirectory = false; + bool supports55 = false; + foreach (DiscoInfo::Identity identity, info->getIdentities()) { + if ((identity.getCategory() == "directory" + && identity.getType() == "user")) { + isUserDirectory = true; + } + } + std::vector<String> features = info->getFeatures(); + supports55 = std::find(features.begin(), features.end(), DiscoInfo::JabberSearchFeature) != features.end(); + if (/*isUserDirectory && */supports55) { //FIXME: once M-Link correctly advertises directoryness. + /* Abort further searches.*/ + delete discoWalker_; + discoWalker_ = NULL; + boost::shared_ptr<GenericRequest<SearchPayload> > searchRequest(new GenericRequest<SearchPayload>(IQ::Get, jid, boost::shared_ptr<SearchPayload>(new SearchPayload()), iqRouter_)); + searchRequest->onResponse.connect(boost::bind(&UserSearchController::handleFormResponse, this, _1, _2, jid)); + searchRequest->send(); + } +} + +void UserSearchController::handleFormResponse(boost::shared_ptr<SearchPayload> fields, ErrorPayload::ref error, const JID& jid) { + if (error || !fields) { + window_->setServerSupportsSearch(false); + return; + } + window_->setSearchFields(fields); +} + +void UserSearchController::handleSearch(boost::shared_ptr<SearchPayload> fields, const JID& jid) { + boost::shared_ptr<GenericRequest<SearchPayload> > searchRequest(new GenericRequest<SearchPayload>(IQ::Set, jid, fields, iqRouter_)); + searchRequest->onResponse.connect(boost::bind(&UserSearchController::handleSearchResponse, this, _1, _2, jid)); + searchRequest->send(); +} + +void UserSearchController::handleSearchResponse(boost::shared_ptr<SearchPayload> resultsPayload, ErrorPayload::ref error, const JID& jid) { + if (error || !resultsPayload) { + window_->setSearchError(true); + return; + } + std::vector<UserSearchResult> results; + foreach (SearchPayload::Item item, resultsPayload->getItems()) { + JID jid(item.jid); + std::map<String, String> fields; + fields["first"] = item.first; + fields["last"] = item.last; + fields["nick"] = item.nick; + fields["email"] = item.email; + UserSearchResult result(jid, fields); + results.push_back(result); + } + window_->setResults(results); +} + +void UserSearchController::handleDiscoWalkFinished(DiscoServiceWalker* walker) { + window_->setServerSupportsSearch(false); + delete discoWalker_; + discoWalker_ = NULL; +} + +} diff --git a/Swift/Controllers/Chat/UserSearchController.h b/Swift/Controllers/Chat/UserSearchController.h new file mode 100644 index 0000000..3ba3352 --- /dev/null +++ b/Swift/Controllers/Chat/UserSearchController.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <map> +#include <vector> +#include <Swiften/Base/boost_bsignals.h> + +#include <Swiften/Elements/SearchPayload.h> +#include <Swiften/Base/String.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Elements/DiscoItems.h> +#include <Swiften/Elements/ErrorPayload.h> + +namespace Swift { + class UIEventStream; + class UIEvent; + class UserSearchWindow; + class UserSearchWindowFactory; + class IQRouter; + class DiscoServiceWalker; + + class UserSearchResult { + public: + UserSearchResult(const JID& jid, const std::map<String, String>& fields) : jid_(jid), fields_(fields) {} + const JID& getJID() const {return jid_;} + const std::map<String, String>& getFields() const {return fields_;} + private: + JID jid_; + std::map<String, String> fields_; + }; + + class UserSearchController { + public: + UserSearchController(const JID& jid, UIEventStream* uiEventStream, UserSearchWindowFactory* userSearchWindowFactory, IQRouter* iqRouter); + ~UserSearchController(); + private: + void handleUIEvent(boost::shared_ptr<UIEvent> event); + void handleFormRequested(const JID& service); + void handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info, DiscoServiceWalker* walker); + void handleDiscoWalkFinished(DiscoServiceWalker* walker); + void handleFormResponse(boost::shared_ptr<SearchPayload> items, ErrorPayload::ref error, const JID& jid); + void handleSearch(boost::shared_ptr<SearchPayload> fields, const JID& jid); + void handleSearchResponse(boost::shared_ptr<SearchPayload> results, ErrorPayload::ref error, const JID& jid); + UIEventStream* uiEventStream_; + UserSearchWindow* window_; + UserSearchWindowFactory* factory_; + boost::bsignals::scoped_connection uiEventConnection_; + IQRouter* iqRouter_; + JID jid_; + DiscoServiceWalker* discoWalker_; + }; +} |