diff options
Diffstat (limited to 'Swiften/Disco')
-rw-r--r-- | Swiften/Disco/DiscoServiceWalker.cpp | 132 | ||||
-rw-r--r-- | Swiften/Disco/DiscoServiceWalker.h | 71 | ||||
-rw-r--r-- | Swiften/Disco/SConscript | 1 |
3 files changed, 204 insertions, 0 deletions
diff --git a/Swiften/Disco/DiscoServiceWalker.cpp b/Swiften/Disco/DiscoServiceWalker.cpp new file mode 100644 index 0000000..c8c3e1b --- /dev/null +++ b/Swiften/Disco/DiscoServiceWalker.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/Disco/DiscoServiceWalker.h> + +#include <Swiften/Base/Log.h> +#include <Swiften/Base/foreach.h> + +#include <boost/bind.hpp> + +namespace Swift { + +DiscoServiceWalker::DiscoServiceWalker(const JID& service, IQRouter* iqRouter, size_t maxSteps) : service_(service), iqRouter_(iqRouter), maxSteps_(maxSteps), active_(false) { + +} + +void DiscoServiceWalker::beginWalk() { + SWIFT_LOG(debug) << "Starting walk to " << service_ << std::endl; + assert(!active_); + assert(servicesBeingSearched_.empty()); + active_ = true; + walkNode(service_); +} + +void DiscoServiceWalker::endWalk() { + if (active_) { + SWIFT_LOG(debug) << "Ending walk to " << service_ << std::endl; + foreach (GetDiscoInfoRequest::ref request, pendingDiscoInfoRequests_) { + request->onResponse.disconnect(boost::bind(&DiscoServiceWalker::handleDiscoInfoResponse, this, _1, _2, request)); + } + foreach (GetDiscoItemsRequest::ref request, pendingDiscoItemsRequests_) { + request->onResponse.disconnect(boost::bind(&DiscoServiceWalker::handleDiscoItemsResponse, this, _1, _2, request)); + } + active_ = false; + } +} + +void DiscoServiceWalker::walkNode(const JID& jid) { + SWIFT_LOG(debug) << "Walking node " << jid << std::endl; + servicesBeingSearched_.insert(jid); + searchedServices_.insert(jid); + GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(jid, iqRouter_); + discoInfoRequest->onResponse.connect(boost::bind(&DiscoServiceWalker::handleDiscoInfoResponse, this, _1, _2, discoInfoRequest)); + pendingDiscoInfoRequests_.insert(discoInfoRequest); + discoInfoRequest->send(); +} + +void DiscoServiceWalker::handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error, GetDiscoInfoRequest::ref request) { + /* If we got canceled, don't do anything */ + if (!active_) { + return; + } + + SWIFT_LOG(debug) << "Disco info response from " << request->getReceiver() << std::endl; + + pendingDiscoInfoRequests_.erase(request); + if (error) { + handleDiscoError(request->getReceiver(), error); + return; + } + + bool couldContainServices = false; + foreach (DiscoInfo::Identity identity, info->getIdentities()) { + if (identity.getCategory() == "server") { + couldContainServices = true; + } + } + bool completed = false; + if (couldContainServices) { + GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(request->getReceiver(), iqRouter_); + discoItemsRequest->onResponse.connect(boost::bind(&DiscoServiceWalker::handleDiscoItemsResponse, this, _1, _2, discoItemsRequest)); + pendingDiscoItemsRequests_.insert(discoItemsRequest); + discoItemsRequest->send(); + } else { + completed = true; + } + onServiceFound(request->getReceiver(), info); + if (completed) { + markNodeCompleted(request->getReceiver()); + } +} + +void DiscoServiceWalker::handleDiscoItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, GetDiscoItemsRequest::ref request) { + /* If we got canceled, don't do anything */ + if (!active_) { + return; + } + + SWIFT_LOG(debug) << "Received disco items from " << request->getReceiver() << std::endl; + pendingDiscoItemsRequests_.erase(request); + if (error) { + handleDiscoError(request->getReceiver(), error); + return; + } + foreach (DiscoItems::Item item, items->getItems()) { + if (item.getNode().empty()) { + /* 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. + */ + if (std::find(searchedServices_.begin(), searchedServices_.end(), item.getJID()) == searchedServices_.end()) { /* Don't recurse infinitely */ + SWIFT_LOG(debug) << "Received disco item " << item.getJID() << std::endl; + walkNode(item.getJID()); + } + } + } + markNodeCompleted(request->getReceiver()); +} + +void DiscoServiceWalker::handleDiscoError(const JID& jid, ErrorPayload::ref /*error*/) { + SWIFT_LOG(debug) << "Disco error from " << jid << std::endl; + markNodeCompleted(jid); +} + +void DiscoServiceWalker::markNodeCompleted(const JID& jid) { + SWIFT_LOG(debug) << "Node completed " << jid << std::endl; + servicesBeingSearched_.erase(jid); + /* All results are in */ + if (servicesBeingSearched_.empty()) { + active_ = false; + onWalkComplete(); + } + /* Check if we're on a rampage */ + else if (searchedServices_.size() >= maxSteps_) { + active_ = false; + onWalkComplete(); + } +} + +} diff --git a/Swiften/Disco/DiscoServiceWalker.h b/Swiften/Disco/DiscoServiceWalker.h new file mode 100644 index 0000000..fd749fc --- /dev/null +++ b/Swiften/Disco/DiscoServiceWalker.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> +#include <set> + +#include <boost/shared_ptr.hpp> +#include <Swiften/Base/boost_bsignals.h> +#include <string> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Elements/DiscoItems.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/Disco/GetDiscoInfoRequest.h> +#include <Swiften/Disco/GetDiscoItemsRequest.h> + +namespace Swift { + class IQRouter; + /** + * Recursively walk service discovery trees to find all services offered. + * This stops on any disco item that's not reporting itself as a server. + */ + class DiscoServiceWalker { + public: + DiscoServiceWalker(const JID& service, IQRouter* iqRouter, size_t maxSteps = 200); + + /** + * Start the walk. + * + * Call this exactly once. + */ + void beginWalk(); + + /** + * End the walk. + */ + void endWalk(); + + bool isActive() const { + return active_; + } + + /** Emitted for each service found. */ + boost::signal<void(const JID&, boost::shared_ptr<DiscoInfo>)> onServiceFound; + + /** Emitted when walking is complete.*/ + boost::signal<void()> onWalkComplete; + + private: + void walkNode(const JID& jid); + void markNodeCompleted(const JID& jid); + void handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error, GetDiscoInfoRequest::ref request); + void handleDiscoItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, GetDiscoItemsRequest::ref request); + void handleDiscoError(const JID& jid, ErrorPayload::ref error); + + private: + JID service_; + IQRouter* iqRouter_; + size_t maxSteps_; + bool active_; + std::set<JID> servicesBeingSearched_; + std::set<JID> searchedServices_; + std::set<GetDiscoInfoRequest::ref> pendingDiscoInfoRequests_; + std::set<GetDiscoItemsRequest::ref> pendingDiscoItemsRequests_; + }; +} diff --git a/Swiften/Disco/SConscript b/Swiften/Disco/SConscript index 434018a..c821b42 100644 --- a/Swiften/Disco/SConscript +++ b/Swiften/Disco/SConscript @@ -10,5 +10,6 @@ objects = swiften_env.SwiftenObject([ "ClientDiscoManager.cpp", "DiscoInfoResponder.cpp", "JIDDiscoInfoResponder.cpp", + "DiscoServiceWalker.cpp", ]) swiften_env.Append(SWIFTEN_OBJECTS = [objects]) |