diff options
author | Tobias Markmann <tm@ayena.de> | 2016-04-21 09:02:07 (GMT) |
---|---|---|
committer | Kevin Smith <kevin.smith@isode.com> | 2016-11-10 11:09:16 (GMT) |
commit | d2ba1ab8a36333523bf794c23226bd044e1717c2 (patch) | |
tree | 737ac14bc1dfcec4b80192ea23e7b0e860a2f8d3 | |
parent | 41c771ec02682c2b344263d29f68eb6452c42dbe (diff) | |
download | swift-d2ba1ab8a36333523bf794c23226bd044e1717c2.zip swift-d2ba1ab8a36333523bf794c23226bd044e1717c2.tar.bz2 |
Use FeatureOracle to detect file-transfer support in roster
The FeatureOracle provides tri-state feature lookup
functionality for bare JIDs. It returns Yes if a feature is
supported by all resources of the bare JID, Maybe if some
support it, and No if none of the resources support it.
If passed a full JID, it returns the specific features supported
by that end-point.
Sending a file to a bare JID, will send a file to the resource
of the bare JID with the highest availability by presence, show
status and priority and which supports the features required
for a Jingle file-transfer.
Test-Information:
Added unit test verifying new behavior.
All tests pass on OS X 10.11.6. Added new unit tests for
FeatureOracle.
Manually verified that the roster and chat window both use
the same mechanism to detect support for file-transfers.
Manually verified that file-transfers via the contact list
goes to already bound full JIDs if there is an existing
ChatController.
Change-Id: I0175ac42ecb73f1d54f9c96ffbba773eb5e24296
-rw-r--r-- | Swift/Controllers/Chat/ChatsManager.cpp | 21 | ||||
-rw-r--r-- | Swift/Controllers/MainController.cpp | 5 | ||||
-rw-r--r-- | Swift/Controllers/Roster/RosterController.cpp | 30 | ||||
-rw-r--r-- | Swift/Controllers/Roster/RosterController.h | 6 | ||||
-rw-r--r-- | Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp | 135 | ||||
-rw-r--r-- | Swiften/Base/SConscript | 18 | ||||
-rw-r--r-- | Swiften/Disco/FeatureOracle.cpp | 194 | ||||
-rw-r--r-- | Swiften/Disco/FeatureOracle.h | 16 | ||||
-rw-r--r-- | Swiften/Disco/UnitTest/FeatureOracleTest.cpp | 151 | ||||
-rw-r--r-- | Swiften/Elements/Stanza.h | 5 | ||||
-rw-r--r-- | Swiften/FileTransfer/FileTransferManager.cpp | 15 | ||||
-rw-r--r-- | Swiften/FileTransfer/FileTransferManager.h | 2 | ||||
-rw-r--r-- | Swiften/FileTransfer/FileTransferManagerImpl.cpp | 31 | ||||
-rw-r--r-- | Swiften/Presence/PresenceOracle.cpp | 21 | ||||
-rw-r--r-- | Swiften/SConscript | 3 |
15 files changed, 484 insertions, 169 deletions
diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index 232d903..b9e2cf4 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -24,6 +24,7 @@ #include <Swiften/Client/NickResolver.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Disco/DiscoServiceWalker.h> +#include <Swiften/Disco/FeatureOracle.h> #include <Swiften/Elements/CarbonsReceived.h> #include <Swiften/Elements/CarbonsSent.h> #include <Swiften/Elements/ChatState.h> @@ -59,6 +60,7 @@ #include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h> #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> #include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> #include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h> #include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h> #include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h> @@ -199,6 +201,7 @@ ChatsManager::~ChatsManager() { roster_->onJIDRemoved.disconnect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1)); roster_->onJIDUpdated.disconnect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1)); roster_->onRosterCleared.disconnect(boost::bind(&ChatsManager::handleRosterCleared, this)); + ftOverview_->onNewFileTransferController.disconnect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1)); delete joinMUCWindow_; for (JIDChatControllerPair controllerPair : chatControllers_) { delete controllerPair.second; @@ -562,6 +565,24 @@ void ChatsManager::handleUIEvent(std::shared_ptr<UIEvent> event) { mucBookmarkManager_->addBookmark(addMUCBookmarkEvent->getBookmark()); return; } + std::shared_ptr<SendFileUIEvent> sendFileEvent = std::dynamic_pointer_cast<SendFileUIEvent>(event); + if (sendFileEvent) { + JID fileReceiver = sendFileEvent->getJID(); + if (fileReceiver.isBare()) { + // See if there is a chat controller for a conversation with a bound + // full JID. Check if this JID supports file transfer and use it instead + // of the bare JID. + ChatController* controller = getChatControllerIfExists(fileReceiver, false); + if (controller) { + JID controllerJID = controller->getToJID(); + if (!controllerJID.isBare() && (FeatureOracle(entityCapsProvider_, presenceOracle_).isFileTransferSupported(controllerJID) == Yes)) { + fileReceiver = controllerJID; + } + } + } + ftOverview_->sendFile(fileReceiver, sendFileEvent->getFilename()); + return; + } std::shared_ptr<CreateImpromptuMUCUIEvent> createImpromptuMUCEvent = std::dynamic_pointer_cast<CreateImpromptuMUCUIEvent>(event); if (createImpromptuMUCEvent) { diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index a9d3f5c..0d9f1b8 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -15,7 +15,6 @@ #include <Swiften/Base/Algorithm.h> #include <Swiften/Base/Log.h> #include <Swiften/Base/String.h> -#include <Swiften/Base/foreach.h> #include <Swiften/Base/format.h> #include <Swiften/Client/Client.h> #include <Swiften/Client/ClientBlockListManager.h> @@ -180,7 +179,7 @@ MainController::MainController( ClientOptions cachedOptions; bool eagle = settings_->getSetting(SettingConstants::FORGET_PASSWORDS); if (!eagle) { - foreach (std::string profile, settings->getAvailableProfiles()) { + for (auto&& profile : settings->getAvailableProfiles()) { ProfileSettingsProvider profileSettings(profile, settings); std::string password = profileSettings.getStringSetting("pass"); std::string certificate = profileSettings.getStringSetting("certificate"); @@ -349,7 +348,7 @@ void MainController::handleConnected() { showProfileController_ = new ShowProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_); ftOverview_ = new FileTransferOverview(client_->getFileTransferManager()); fileTransferListController_->setFileTransferOverview(ftOverview_); - rosterController_ = new RosterController(boundJID_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_, client_->getClientBlockListManager(), client_->getVCardManager()); + rosterController_ = new RosterController(boundJID_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), client_->getClientBlockListManager(), client_->getVCardManager()); rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2)); rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this)); rosterController_->getWindow()->onShowCertificateRequest.connect(boost::bind(&MainController::handleShowCertificateRequest, this)); diff --git a/Swift/Controllers/Roster/RosterController.cpp b/Swift/Controllers/Roster/RosterController.cpp index 116ef2e..1d20c4a 100644 --- a/Swift/Controllers/Roster/RosterController.cpp +++ b/Swift/Controllers/Roster/RosterController.cpp @@ -17,6 +17,7 @@ #include <Swiften/Client/NickManager.h> #include <Swiften/Client/NickResolver.h> #include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Disco/FeatureOracle.h> #include <Swiften/Elements/DiscoInfo.h> #include <Swiften/FileTransfer/FileTransferManager.h> #include <Swiften/JID/JID.h> @@ -30,7 +31,6 @@ #include <Swiften/Roster/XMPPRosterItem.h> #include <Swiften/VCards/VCardManager.h> -#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/Roster/GroupRosterItem.h> #include <Swift/Controllers/Roster/ItemOperations/AppearOffline.h> @@ -49,7 +49,6 @@ #include <Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h> #include <Swift/Controllers/UIEvents/RenameGroupUIEvent.h> #include <Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h> -#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> #include <Swift/Controllers/UIInterfaces/MainWindow.h> #include <Swift/Controllers/UIInterfaces/MainWindowFactory.h> #include <Swift/Controllers/XMPPEvents/ErrorEvent.h> @@ -61,9 +60,8 @@ namespace Swift { /** * The controller does not gain ownership of these parameters. */ -RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsManager, FileTransferOverview* fileTransferOverview, ClientBlockListManager* clientBlockListManager, VCardManager* vcardManager) - : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), vcardManager_(vcardManager), avatarManager_(avatarManager), nickManager_(nickManager), nickResolver_(nickResolver), presenceOracle_(presenceOracle), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), ftOverview_(fileTransferOverview), clientBlockListManager_(clientBlockListManager) { - assert(fileTransferOverview); +RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsManager, ClientBlockListManager* clientBlockListManager, VCardManager* vcardManager) + : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), vcardManager_(vcardManager), avatarManager_(avatarManager), nickManager_(nickManager), nickResolver_(nickResolver), presenceOracle_(presenceOracle), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), clientBlockListManager_(clientBlockListManager) { iqRouter_ = iqRouter; subscriptionManager_ = subscriptionManager; eventController_ = eventController; @@ -81,6 +79,8 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata subscriptionManager_->onPresenceSubscriptionRequest.connect(boost::bind(&RosterController::handleSubscriptionRequest, this, _1, _2)); uiEventConnection_ = uiEventStream->onUIEvent.connect(boost::bind(&RosterController::handleUIEvent, this, _1)); + featureOracle_ = std::unique_ptr<FeatureOracle>(new FeatureOracle(entityCapsManager_, presenceOracle_)); + vcardManager_->onOwnVCardChanged.connect(boost::bind(&RosterController::handleOwnVCardChanged, this, _1)); avatarManager_->onAvatarChanged.connect(boost::bind(&RosterController::handleAvatarChanged, this, _1)); presenceOracle_->onPresenceChange.connect(boost::bind(&RosterController::handlePresenceChanged, this, _1)); @@ -267,9 +267,6 @@ void RosterController::handleUIEvent(std::shared_ptr<UIEvent> event) { } } } - else if (std::shared_ptr<SendFileUIEvent> sendFileEvent = std::dynamic_pointer_cast<SendFileUIEvent>(event)) { - ftOverview_->sendFile(sendFileEvent->getJID(), sendFileEvent->getFilename()); - } } void RosterController::setContactGroups(const JID& jid, const std::vector<std::string>& groups) { @@ -396,17 +393,14 @@ std::set<std::string> RosterController::getGroups() const { } void RosterController::handleOnCapsChanged(const JID& jid) { - DiscoInfo::ref info = entityCapsManager_->getCaps(jid); - if (info) { - std::set<ContactRosterItem::Feature> features; - if (FileTransferManager::isSupportedBy(info)) { - features.insert(ContactRosterItem::FileTransferFeature); - } - if (info->hasFeature(DiscoInfo::WhiteboardFeature)) { - features.insert(ContactRosterItem::WhiteboardFeature); - } - roster_->applyOnItems(SetAvailableFeatures(jid, features)); + std::set<ContactRosterItem::Feature> features; + if (featureOracle_->isFileTransferSupported(jid.toBare()) == Tristate::Yes || featureOracle_->isFileTransferSupported(jid.toBare()) == Tristate::Maybe) { + features.insert(ContactRosterItem::FileTransferFeature); + } + if (featureOracle_->isWhiteboardSupported(jid.toBare()) == Tristate::Yes) { + features.insert(ContactRosterItem::WhiteboardFeature); } + roster_->applyOnItems(SetAvailableFeatures(jid, features)); } } diff --git a/Swift/Controllers/Roster/RosterController.h b/Swift/Controllers/Roster/RosterController.h index 980c545..ca2ecdc 100644 --- a/Swift/Controllers/Roster/RosterController.h +++ b/Swift/Controllers/Roster/RosterController.h @@ -27,8 +27,8 @@ namespace Swift { class ClientBlockListManager; class EntityCapsProvider; class EventController; + class FeatureOracle; class FileTransferManager; - class FileTransferOverview; class IQRouter; class MainWindow; class MainWindowFactory; @@ -49,7 +49,7 @@ namespace Swift { class RosterController { public: - RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsProvider, FileTransferOverview* fileTransferOverview, ClientBlockListManager* clientBlockListManager, VCardManager* vcardManager); + RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsProvider, ClientBlockListManager* clientBlockListManager, VCardManager* vcardManager); ~RosterController(); void showRosterWindow(); void setJID(const JID& jid) { myJID_ = jid; } @@ -111,10 +111,10 @@ namespace Swift { SettingsProvider* settings_; UIEventStream* uiEventStream_; EntityCapsProvider* entityCapsManager_; - FileTransferOverview* ftOverview_; ClientBlockListManager* clientBlockListManager_; RosterVCardProvider* rosterVCardProvider_; std::shared_ptr<ContactRosterItem> ownContact_; + std::unique_ptr<FeatureOracle> featureOracle_; boost::signals2::scoped_connection blockingOnStateChangedConnection_; boost::signals2::scoped_connection blockingOnItemAddedConnection_; diff --git a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp index 0cd4080..ddbd7d3 100644 --- a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp @@ -12,13 +12,16 @@ #include <Swiften/Client/ClientBlockListManager.h> #include <Swiften/Client/DummyNickManager.h> #include <Swiften/Client/DummyStanzaChannel.h> +#include <Swiften/Client/MemoryStorages.h> #include <Swiften/Client/NickResolver.h> #include <Swiften/Crypto/CryptoProvider.h> #include <Swiften/Crypto/PlatformCryptoProvider.h> +#include <Swiften/Disco/CapsInfoGenerator.h> +#include <Swiften/Disco/CapsManager.h> #include <Swiften/Disco/CapsProvider.h> +#include <Swiften/Disco/ClientDiscoManager.h> #include <Swiften/Disco/EntityCapsManager.h> #include <Swiften/EventLoop/DummyEventLoop.h> -#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> #include <Swiften/Jingle/JingleSessionManager.h> #include <Swiften/MUC/MUCRegistry.h> #include <Swiften/Presence/PresenceOracle.h> @@ -29,7 +32,6 @@ #include <Swiften/VCards/VCardManager.h> #include <Swiften/VCards/VCardMemoryStorage.h> -#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> #include <Swift/Controllers/Roster/ContactRosterItem.h> #include <Swift/Controllers/Roster/GroupRosterItem.h> #include <Swift/Controllers/Roster/Roster.h> @@ -42,8 +44,6 @@ using namespace Swift; -#define CHILDREN mainWindow_->roster->getRoot()->getChildren() - class DummyCapsProvider : public CapsProvider { DiscoInfo::ref getCaps(const std::string&) const {return DiscoInfo::ref(new DiscoInfo());} }; @@ -61,6 +61,7 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_TEST(testUnavailablePresence); CPPUNIT_TEST(testRemoveResultsInUnavailablePresence); CPPUNIT_TEST(testOwnContactInRosterPresence); + CPPUNIT_TEST(testMultiResourceFileTransferFeature); CPPUNIT_TEST_SUITE_END(); public: @@ -70,6 +71,8 @@ class RosterControllerTest : public CppUnit::TestFixture { avatarManager_ = new NullAvatarManager(); mainWindowFactory_ = new MockMainWindowFactory(); mucRegistry_ = new MUCRegistry(); + crypto_ = PlatformCryptoProvider::create(); + storages_ = std::unique_ptr<MemoryStorages>(new MemoryStorages(crypto_)); nickResolver_ = new NickResolver(jid_.toBare(), xmppRoster_, nullptr, mucRegistry_); channel_ = new DummyIQChannel(); router_ = new IQRouter(channel_); @@ -80,18 +83,16 @@ class RosterControllerTest : public CppUnit::TestFixture { uiEventStream_ = new UIEventStream(); settings_ = new DummySettingsProvider(); nickManager_ = new DummyNickManager(); - capsProvider_ = new DummyCapsProvider(); - entityCapsManager_ = new EntityCapsManager(capsProvider_, stanzaChannel_); + capsManager_ = std::unique_ptr<CapsManager>(new CapsManager(storages_->getCapsStorage(), stanzaChannel_, router_, crypto_)); + entityCapsManager_ = new EntityCapsManager(capsManager_.get(), stanzaChannel_); jingleSessionManager_ = new JingleSessionManager(router_); - ftManager_ = new DummyFileTransferManager(); - ftOverview_ = new FileTransferOverview(ftManager_); clientBlockListManager_ = new ClientBlockListManager(router_); - crypto_ = PlatformCryptoProvider::create(); vcardStorage_ = new VCardMemoryStorage(crypto_); vcardManager_ = new VCardManager(jid_, router_, vcardStorage_); - rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_, clientBlockListManager_, vcardManager_); + rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, clientBlockListManager_, vcardManager_); mainWindow_ = mainWindowFactory_->last; + capsInfoGenerator_ = std::unique_ptr<CapsInfoGenerator>(new CapsInfoGenerator("", crypto_)); } void tearDown() { @@ -100,11 +101,8 @@ class RosterControllerTest : public CppUnit::TestFixture { delete vcardStorage_; delete crypto_; delete clientBlockListManager_; - delete ftOverview_; - delete ftManager_; delete jingleSessionManager_; delete entityCapsManager_; - delete capsProvider_; delete nickManager_; delete nickResolver_; delete mucRegistry_; @@ -122,7 +120,7 @@ class RosterControllerTest : public CppUnit::TestFixture { } GroupRosterItem* groupChild(size_t i) { - return dynamic_cast<GroupRosterItem*>(CHILDREN[i]); + return dynamic_cast<GroupRosterItem*>(getUIRosterChildren()[i]); } JID withResource(const JID& jid, const std::string& resource) { @@ -140,10 +138,10 @@ class RosterControllerTest : public CppUnit::TestFixture { presence->setPriority(2); presence->setStatus("So totally here"); stanzaChannel_->onPresenceReceived(presence); - ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(CHILDREN[0])->getChildren()[0]); + ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(getUIRosterChildren()[0])->getChildren()[0]); CPPUNIT_ASSERT(item); CPPUNIT_ASSERT_EQUAL(presence->getStatus(), item->getStatusText()); - ContactRosterItem* item2 = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(CHILDREN[1])->getChildren()[0]); + ContactRosterItem* item2 = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(getUIRosterChildren()[1])->getChildren()[0]); CPPUNIT_ASSERT(item2); CPPUNIT_ASSERT_EQUAL(presence->getStatus(), item2->getStatusText()); } @@ -163,7 +161,7 @@ class RosterControllerTest : public CppUnit::TestFixture { highPresence->setStatus("So totally here"); stanzaChannel_->onPresenceReceived(lowPresence); stanzaChannel_->onPresenceReceived(highPresence); - ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(CHILDREN[0])->getChildren()[0]); + ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(getUIRosterChildren()[0])->getChildren()[0]); CPPUNIT_ASSERT(item); CPPUNIT_ASSERT_EQUAL(highPresence->getStatus(), item->getStatusText()); } @@ -183,7 +181,7 @@ class RosterControllerTest : public CppUnit::TestFixture { highPresence->setStatus("So totally here"); stanzaChannel_->onPresenceReceived(highPresence); stanzaChannel_->onPresenceReceived(lowPresence); - ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(CHILDREN[0])->getChildren()[0]); + ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(getUIRosterChildren()[0])->getChildren()[0]); CPPUNIT_ASSERT(item); CPPUNIT_ASSERT_EQUAL(highPresence->getStatus(), item->getStatusText()); } @@ -223,7 +221,7 @@ class RosterControllerTest : public CppUnit::TestFixture { stanzaChannel_->onPresenceReceived(highPresenceOffline); // After this, the roster should show the low presence. - ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(CHILDREN[0])->getChildren()[0]); + ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(getUIRosterChildren()[0])->getChildren()[0]); CPPUNIT_ASSERT(item); Presence::ref low = presenceOracle_->getAccountPresence(from); @@ -233,7 +231,7 @@ class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_ASSERT_EQUAL(lowPresence->getShow(), item->getStatusShow()); CPPUNIT_ASSERT_EQUAL(lowPresence->getStatus(), item->getStatusText()); stanzaChannel_->onPresenceReceived(lowPresenceOffline); - item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(CHILDREN[0])->getChildren()[0]); + item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(getUIRosterChildren()[0])->getChildren()[0]); CPPUNIT_ASSERT(item); /* A verification that if the test fails, it's the RosterController, not the PresenceOracle. */ low = presenceOracle_->getHighestPriorityPresence(from); @@ -249,7 +247,7 @@ class RosterControllerTest : public CppUnit::TestFixture { groups.push_back("testGroup2"); xmppRoster_->addContact(JID("test@testdomain.com/bob"), "name", groups, RosterItemPayload::Both); - CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(CHILDREN.size())); + CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(getUIRosterChildren().size())); //CPPUNIT_ASSERT_EQUAL(std::string("Bob"), xmppRoster_->getNameForJID(JID("foo@bar.com"))); } @@ -258,14 +256,14 @@ class RosterControllerTest : public CppUnit::TestFixture { JID jid("test@testdomain.com"); xmppRoster_->addContact(jid, "name", groups, RosterItemPayload::None); - CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(CHILDREN.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(getUIRosterChildren().size())); CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); xmppRoster_->addContact(jid, "name", groups, RosterItemPayload::To); - CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(CHILDREN.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(getUIRosterChildren().size())); CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); xmppRoster_->addContact(jid, "name", groups, RosterItemPayload::Both); - CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(CHILDREN.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(getUIRosterChildren().size())); CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); } @@ -275,11 +273,11 @@ class RosterControllerTest : public CppUnit::TestFixture { JID jid("test@testdomain.com"); xmppRoster_->addContact(jid, "name", groups, RosterItemPayload::Both); - CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(CHILDREN.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(getUIRosterChildren().size())); CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); CPPUNIT_ASSERT_EQUAL(std::string("name"), groupChild(0)->getChildren()[0]->getDisplayName()); xmppRoster_->addContact(jid, "NewName", groups, RosterItemPayload::Both); - CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(CHILDREN.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(getUIRosterChildren().size())); CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); CPPUNIT_ASSERT_EQUAL(std::string("NewName"), groupChild(0)->getChildren()[0]->getDisplayName()); } @@ -293,18 +291,18 @@ class RosterControllerTest : public CppUnit::TestFixture { JID jid("test@testdomain.com"); xmppRoster_->addContact(jid, "", oldGroups, RosterItemPayload::Both); - CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(CHILDREN.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(getUIRosterChildren().size())); CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); CPPUNIT_ASSERT_EQUAL(jid.toString(), groupChild(0)->getChildren()[0]->getDisplayName()); xmppRoster_->addContact(jid, "new name", newGroups, RosterItemPayload::Both); - CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(CHILDREN.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(getUIRosterChildren().size())); CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); CPPUNIT_ASSERT_EQUAL(std::string("new name"), groupChild(0)->getChildren()[0]->getDisplayName()); CPPUNIT_ASSERT_EQUAL(std::string("A Group"), groupChild(0)->getDisplayName()); xmppRoster_->addContact(jid, "new name", newestGroups, RosterItemPayload::Both); - CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(CHILDREN.size())); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(getUIRosterChildren().size())); CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(groupChild(0)->getChildren().size())); CPPUNIT_ASSERT_EQUAL(std::string("new name"), groupChild(0)->getChildren()[0]->getDisplayName()); CPPUNIT_ASSERT_EQUAL(std::string("Best Group"), groupChild(0)->getDisplayName()); @@ -364,14 +362,79 @@ class RosterControllerTest : public CppUnit::TestFixture { presence->setPriority(2); presence->setStatus("So totally here"); stanzaChannel_->onPresenceReceived(presence); - ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(CHILDREN[0])->getChildren()[0]); + ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(getUIRosterChildren()[0])->getChildren()[0]); CPPUNIT_ASSERT(item); CPPUNIT_ASSERT_EQUAL(presence->getStatus(), item->getStatusText()); - ContactRosterItem* item2 = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(CHILDREN[1])->getChildren()[0]); + ContactRosterItem* item2 = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(getUIRosterChildren()[1])->getChildren()[0]); CPPUNIT_ASSERT(item2); CPPUNIT_ASSERT_EQUAL(presence->getStatus(), item2->getStatusText()); } + // This tests a scenario of a contact having a resource supporting Jingle File Transfer and + // one resource not supporting it, and the contact features being set correctly. + void testMultiResourceFileTransferFeature() { + JID contact("test@testdomain.com"); + xmppRoster_->addContact(contact, "Name", {}, RosterItemPayload::Both); + + auto sendPresenceAndAnswerCaps = [=](const JID& from, const DiscoInfo& discoInfo) { + auto capsInfo = capsInfoGenerator_->generateCapsInfo(discoInfo); + + auto ftClientPresence = std::make_shared<Presence>(); + ftClientPresence->setFrom(from); + ftClientPresence->setPriority(0); + ftClientPresence->setShow(StatusShow::Online); + ftClientPresence->addPayload(std::make_shared<CapsInfo>(capsInfo)); + stanzaChannel_->onPresenceReceived(ftClientPresence); + + // disco reply + auto discoRequest = channel_->iqs_.back(); + CPPUNIT_ASSERT(discoRequest); + auto discoReply = IQ::createResult(discoRequest->getFrom(), ftClientPresence->getFrom(), discoRequest->getID(), std::make_shared<DiscoInfo>(discoInfo)); + channel_->onIQReceived(discoReply); + }; + + auto ftDiscoInfo = DiscoInfo(); + ftDiscoInfo.addFeature(DiscoInfo::JingleFeature); + ftDiscoInfo.addFeature(DiscoInfo::JingleFTFeature); + ftDiscoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature); + + sendPresenceAndAnswerCaps(contact.withResource("ft-supported"), ftDiscoInfo); + + auto* item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(getUIRosterChildren()[0])->getChildren()[0]); + CPPUNIT_ASSERT(item); + CPPUNIT_ASSERT_EQUAL(contact, item->getJID()); + CPPUNIT_ASSERT_EQUAL(true, item->supportsFeature(ContactRosterItem::FileTransferFeature)); + + sendPresenceAndAnswerCaps(contact.withResource("ft-unsupported"), DiscoInfo()); + + item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(getUIRosterChildren()[0])->getChildren()[0]); + CPPUNIT_ASSERT(item); + CPPUNIT_ASSERT_EQUAL(contact, item->getJID()); + CPPUNIT_ASSERT_EQUAL(true, item->supportsFeature(ContactRosterItem::FileTransferFeature)); + + auto unavailablePresence = std::make_shared<Presence>(); + unavailablePresence->setFrom(contact.withResource("ft-unsupported")); + unavailablePresence->setPriority(0); + unavailablePresence->setType(Presence::Unavailable); + stanzaChannel_->onPresenceReceived(unavailablePresence); + + item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(getUIRosterChildren()[0])->getChildren()[0]); + CPPUNIT_ASSERT(item); + CPPUNIT_ASSERT_EQUAL(contact, item->getJID()); + CPPUNIT_ASSERT_EQUAL(true, item->supportsFeature(ContactRosterItem::FileTransferFeature)); + + unavailablePresence = std::make_shared<Presence>(); + unavailablePresence->setFrom(contact.withResource("ft-supported")); + unavailablePresence->setPriority(0); + unavailablePresence->setType(Presence::Unavailable); + stanzaChannel_->onPresenceReceived(unavailablePresence); + + item = dynamic_cast<ContactRosterItem*>(dynamic_cast<GroupRosterItem*>(getUIRosterChildren()[0])->getChildren()[0]); + CPPUNIT_ASSERT(item); + CPPUNIT_ASSERT_EQUAL(contact, item->getJID()); + CPPUNIT_ASSERT_EQUAL(false, item->supportsFeature(ContactRosterItem::FileTransferFeature)); + } + void assertVectorsEqual(const std::vector<std::string>& v1, const std::vector<std::string>& v2, int line) { for (const auto& entry : v1) { if (std::find(v2.begin(), v2.end(), entry) == v2.end()) { @@ -382,8 +445,13 @@ class RosterControllerTest : public CppUnit::TestFixture { } } + const std::vector<RosterItem*>& getUIRosterChildren() const { + return mainWindow_->roster->getRoot()->getChildren(); + } + private: JID jid_; + std::unique_ptr<MemoryStorages> storages_; XMPPRosterImpl* xmppRoster_; MUCRegistry* mucRegistry_; AvatarManager* avatarManager_; @@ -400,15 +468,14 @@ class RosterControllerTest : public CppUnit::TestFixture { UIEventStream* uiEventStream_; MockMainWindow* mainWindow_; DummySettingsProvider* settings_; - DummyCapsProvider* capsProvider_; + std::unique_ptr<CapsManager> capsManager_; EntityCapsManager* entityCapsManager_; JingleSessionManager* jingleSessionManager_; - FileTransferManager* ftManager_; - FileTransferOverview* ftOverview_; ClientBlockListManager* clientBlockListManager_; CryptoProvider* crypto_; VCardStorage* vcardStorage_; VCardManager* vcardManager_; + std::unique_ptr<CapsInfoGenerator> capsInfoGenerator_; }; CPPUNIT_TEST_SUITE_REGISTRATION(RosterControllerTest); diff --git a/Swiften/Base/SConscript b/Swiften/Base/SConscript index 92e3da8..de502c2 100644 --- a/Swiften/Base/SConscript +++ b/Swiften/Base/SConscript @@ -1,23 +1,23 @@ Import("swiften_env") objects = swiften_env.SwiftenObject([ + "BoostRandomGenerator.cpp", "ByteArray.cpp", "DateTime.cpp", - "SafeByteArray.cpp", - "SafeAllocator.cpp", "Error.cpp", + "FileSize.cpp", + "IDGenerator.cpp", "Log.cpp", "LogSerializers.cpp", "Path.cpp", "Paths.cpp", - "String.cpp", - "IDGenerator.cpp", - "SimpleIDGenerator.cpp", "RandomGenerator.cpp", - "BoostRandomGenerator.cpp", - "sleep.cpp", - "URL.cpp", "Regex.cpp", - "FileSize.cpp" + "SafeAllocator.cpp", + "SafeByteArray.cpp", + "SimpleIDGenerator.cpp", + "String.cpp", + "URL.cpp", + "sleep.cpp", ]) swiften_env.Append(SWIFTEN_OBJECTS = [objects]) diff --git a/Swiften/Disco/FeatureOracle.cpp b/Swiften/Disco/FeatureOracle.cpp index 8328984..2baf87c 100644 --- a/Swiften/Disco/FeatureOracle.cpp +++ b/Swiften/Disco/FeatureOracle.cpp @@ -8,10 +8,12 @@ #include <algorithm> #include <iterator> +#include <unordered_set> #include <vector> -#include <Swiften/Base/foreach.h> +#include <Swiften/Base/Log.h> #include <Swiften/Disco/EntityCapsProvider.h> +#include <Swiften/Elements/Idle.h> #include <Swiften/Elements/Presence.h> #include <Swiften/FileTransfer/FileTransferManager.h> #include <Swiften/JID/JID.h> @@ -20,78 +22,178 @@ namespace Swift { FeatureOracle::FeatureOracle(EntityCapsProvider* capsProvider, PresenceOracle* presenceOracle) : capsProvider_(capsProvider), presenceOracle_(presenceOracle) { - } Tristate FeatureOracle::isFileTransferSupported(const JID& jid) { - DiscoInfo::ref discoInfo = getDiscoResultForJID(jid); - if (discoInfo) { - return FileTransferManager::isSupportedBy(discoInfo) ? Yes : No; + Tristate fileTransferSupported = No; + + auto isYesOrMaybe = [](Tristate tristate) { return tristate == Yes || tristate == Maybe; }; + auto isYes = [](Tristate tristate) { return tristate == Yes; }; + + auto supportedFeatures = getFeaturesForJID(jid); + + auto jingleSupported = isFeatureSupported(supportedFeatures, DiscoInfo::JingleFeature); + auto jingleFTSupported = isFeatureSupported(supportedFeatures, DiscoInfo::JingleFTFeature); + auto jingleTransportIBBSupported = isFeatureSupported(supportedFeatures, DiscoInfo::JingleTransportsIBBFeature); + auto jingleTransportS5BSupported = isFeatureSupported(supportedFeatures, DiscoInfo::JingleTransportsS5BFeature); + + if (isYes(jingleSupported) && isYes(jingleFTSupported) && (isYes(jingleTransportIBBSupported) || isYes(jingleTransportS5BSupported))) { + fileTransferSupported = Yes; } - else { - return Maybe; + else if (isYesOrMaybe(jingleSupported) && isYesOrMaybe(jingleFTSupported) && (isYesOrMaybe(jingleTransportIBBSupported) || isYesOrMaybe(jingleTransportS5BSupported))) { + fileTransferSupported = Maybe; } + + return fileTransferSupported; } Tristate FeatureOracle::isMessageReceiptsSupported(const JID& jid) { - return isFeatureSupported(jid, DiscoInfo::MessageDeliveryReceiptsFeature); + return isFeatureSupported(getFeaturesForJID(jid), DiscoInfo::MessageDeliveryReceiptsFeature); } Tristate FeatureOracle::isMessageCorrectionSupported(const JID& jid) { - return isFeatureSupported(jid, DiscoInfo::MessageCorrectionFeature); + return isFeatureSupported(getFeaturesForJID(jid), DiscoInfo::MessageCorrectionFeature); +} + +Tristate FeatureOracle::isWhiteboardSupported(const JID& jid) { + return isFeatureSupported(getFeaturesForJID(jid), DiscoInfo::WhiteboardFeature); } -DiscoInfo::ref FeatureOracle::getDiscoResultForJID(const JID& jid) { - DiscoInfo::ref discoInfo; +class PresenceFeatureAvailablityComparator { + public: + static int preferenceFromStatusShow(StatusShow::Type showType) { + switch (showType) { + case StatusShow::FFC: + return 5; + case StatusShow::Online: + return 4; + case StatusShow::DND: + return 3; + case StatusShow::Away: + return 2; + case StatusShow::XA: + return 1; + case StatusShow::None: + return 0; + } + assert(false); + return -1; + } + + static int compareWithoutResource(const Presence::ref& a, const Presence::ref& b) { + int aPreference = preferenceFromStatusShow(a->getShow()); + int bPreference = preferenceFromStatusShow(b->getShow()); + + if (aPreference != bPreference) { + return aPreference < bPreference ? 1 : -1; + } + + Idle::ref aIdle = a->getPayload<Idle>(); + Idle::ref bIdle = b->getPayload<Idle>(); + + if (aIdle && !bIdle) { + return -1; + } + else if (!aIdle && bIdle) { + return 1; + } + + if (a->getPriority() != b->getPriority()) { + return a->getPriority() < b->getPriority() ? 1 : -1; + } + + return 0; + } + + /* + * This method returns true, if \ref Presence \p b is more available than + * \ref Presence \p a. + * Going by \ref StatusShow::Type first, then by idle state, then by + * presence priority and finally by resource name. + */ + bool operator()(const Presence::ref& a, const Presence::ref& b) { + int aMoreAvailableThanB = compareWithoutResource(a, b); + if (aMoreAvailableThanB < 0) { + return true; + } + else if (aMoreAvailableThanB > 0) { + return false; + } + + return a->getFrom().getResource() < b->getFrom().getResource(); + } +}; + +JID FeatureOracle::getMostAvailableClientForFileTrasfer(const JID& bareJID) { + JID fullJID; + assert(bareJID.isBare()); + + std::vector<Presence::ref> allPresences = presenceOracle_->getAllPresence(bareJID); + std::sort(allPresences.begin(), allPresences.end(), PresenceFeatureAvailablityComparator()); + + for (const auto& presence : allPresences) { + if (presence->isAvailable()) { + if (isFileTransferSupported(presence->getFrom()) == Yes) { + fullJID = presence->getFrom(); + break; + } + } + } + + SWIFT_LOG_ASSERT(!fullJID.isBare(), error); + return fullJID; +} + +std::unordered_map<std::string, Tristate> FeatureOracle::getFeaturesForJID(const JID& jid) { + std::unordered_map<std::string, Tristate> supportedFeatures; if (jid.isBare()) { - // Calculate the common subset of disco features of all available results and return that. - std::vector<Presence::ref> availablePresences = presenceOracle_->getAllPresence(jid); - - bool commonFeaturesInitialized = false; - std::vector<std::string> commonFeatures; - foreach(Presence::ref presence, availablePresences) { - DiscoInfo::ref presenceDiscoInfo = capsProvider_->getCaps(presence->getFrom()); - if (presenceDiscoInfo) { - std::vector<std::string> features = presenceDiscoInfo->getFeatures(); - if (!commonFeaturesInitialized) { - commonFeatures = features; - commonFeaturesInitialized = true; - } - else { - std::vector<std::string> featuresToRemove; - foreach(const std::string& feature, commonFeatures) { - if (std::find(features.begin(), features.end(), feature) == features.end()) { - featuresToRemove.push_back(feature); - } - } - foreach(const std::string& featureToRemove, featuresToRemove) { - commonFeatures.erase(std::remove(commonFeatures.begin(), commonFeatures.end(), featureToRemove), commonFeatures.end()); - } + // Calculate the union of disco features of all most available results and return that. + std::vector<DiscoInfo::ref> onlineDiscoInfos; + std::unordered_set<std::string> features; + + // Collect relevant disco info results and the set of features. + for (auto&& presence : presenceOracle_->getAllPresence(jid)) { + if (presence->getType() == Presence::Available) { + DiscoInfo::ref presenceDiscoInfo = capsProvider_->getCaps(presence->getFrom()); + if (presenceDiscoInfo) { + onlineDiscoInfos.push_back(presenceDiscoInfo); + features.insert(presenceDiscoInfo->getFeatures().begin(), presenceDiscoInfo->getFeatures().end()); } } } - discoInfo = std::make_shared<DiscoInfo>(); - foreach(const std::string& commonFeature, commonFeatures) { - discoInfo->addFeature(commonFeature); + // Calculate supportedFeaturesMap. + for (auto&& feature : features) { + Tristate supported = Yes; + for (auto&& discoInfo : onlineDiscoInfos) { + if (!discoInfo->hasFeature(feature)) { + supported = Maybe; + break; + } + } + supportedFeatures[feature] = supported; } } else { // Return the disco result of the full JID. - discoInfo = capsProvider_->getCaps(jid); + auto discoInfo = capsProvider_->getCaps(jid); + if (discoInfo) { + for (auto&& feature : discoInfo->getFeatures()) { + supportedFeatures[feature] = Yes; + } + } } - return discoInfo; + return supportedFeatures; } -Tristate FeatureOracle::isFeatureSupported(const JID& jid, const std::string& feature) { - DiscoInfo::ref discoInfo = getDiscoResultForJID(jid); - if (discoInfo) { - return discoInfo->hasFeature(feature) ? Yes : No; - } - else { - return Maybe; +Tristate FeatureOracle::isFeatureSupported(const std::unordered_map<std::string, Tristate>& supportedFeatures, const std::string& feature) { + Tristate supported = No; + auto lookupResult = supportedFeatures.find(feature); + if (lookupResult != supportedFeatures.end()) { + supported = lookupResult->second; } + return supported; } } diff --git a/Swiften/Disco/FeatureOracle.h b/Swiften/Disco/FeatureOracle.h index d434e86..be0cd6f 100644 --- a/Swiften/Disco/FeatureOracle.h +++ b/Swiften/Disco/FeatureOracle.h @@ -1,11 +1,14 @@ /* - * Copyright (c) 2015 Isode Limited. + * Copyright (c) 2015-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #pragma once +#include <unordered_map> +#include <unordered_set> + #include <Swiften/Base/API.h> #include <Swiften/Base/Tristate.h> #include <Swiften/Elements/DiscoInfo.h> @@ -27,16 +30,19 @@ class SWIFTEN_API FeatureOracle { Tristate isFileTransferSupported(const JID& jid); Tristate isMessageReceiptsSupported(const JID& jid); Tristate isMessageCorrectionSupported(const JID& jid); + Tristate isWhiteboardSupported(const JID& jid); + + JID getMostAvailableClientForFileTrasfer(const JID& bareJID); private: /** * @brief getDiscoResultForJID returns a shared reference to a DiscoInfo representing features supported by the jid. - * @param jid The JID to return the DiscoInfo::ref for. - * @return DiscoResult::ref + * @param jid The JID to return an std::unordered_map<std::string, Tristate> for. + * @return std::unordered_map<std::string, Tristate> */ - DiscoInfo::ref getDiscoResultForJID(const JID& jid); + std::unordered_map<std::string, Tristate> getFeaturesForJID(const JID& jid); - Tristate isFeatureSupported(const JID& jid, const std::string& feature); + Tristate isFeatureSupported(const std::unordered_map<std::string, Tristate>& supportedFeatures, const std::string& feature); private: EntityCapsProvider* capsProvider_; diff --git a/Swiften/Disco/UnitTest/FeatureOracleTest.cpp b/Swiften/Disco/UnitTest/FeatureOracleTest.cpp new file mode 100644 index 0000000..e5ff09b --- /dev/null +++ b/Swiften/Disco/UnitTest/FeatureOracleTest.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <vector> + +#include <boost/bind.hpp> + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include <Swiften/Base/Tristate.h> +#include <Swiften/Client/DummyStanzaChannel.h> +#include <Swiften/Crypto/CryptoProvider.h> +#include <Swiften/Crypto/PlatformCryptoProvider.h> +#include <Swiften/Disco/CapsInfoGenerator.h> +#include <Swiften/Disco/CapsProvider.h> +#include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Disco/FeatureOracle.h> +#include <Swiften/Elements/CapsInfo.h> +#include <Swiften/Presence/PresenceOracle.h> +#include <Swiften/Roster/XMPPRosterImpl.h> + +using namespace Swift; + +class FeatureOracleTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(FeatureOracleTest); + CPPUNIT_TEST(testMergeAvailableResourcesForFeatures); + CPPUNIT_TEST(testMostAvailableFileTransferClient); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + crypto_ = std::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create()); + dummyStanzaChannel_ = new DummyStanzaChannel(); + xmppRosterImpl_ = new XMPPRosterImpl(); + dummyCapsProvider_ = new DummyCapsProvider(); + entityCapsManager_ = new EntityCapsManager(dummyCapsProvider_, dummyStanzaChannel_); + presenceOracle_ = new PresenceOracle(dummyStanzaChannel_, xmppRosterImpl_); + featureOracle_ = new FeatureOracle(entityCapsManager_, presenceOracle_); + } + + void tearDown() { + delete featureOracle_; + delete presenceOracle_; + delete entityCapsManager_; + delete dummyCapsProvider_; + delete xmppRosterImpl_; + delete dummyStanzaChannel_; + } + + void simulateIncomingPresence(const JID& from, Presence::Type type, StatusShow::Type status, const DiscoInfo::ref& disco, const std::vector<Payload::ref>& additionalPayloads = {}) { + auto capsInfo = std::make_shared<CapsInfo>(CapsInfoGenerator("http://example.com", crypto_.get()).generateCapsInfo(*disco.get())); + dummyCapsProvider_->caps[capsInfo->getVersion()] = disco; + + Presence::ref capsNotifyPresence = std::make_shared<Presence>(); + capsNotifyPresence->setType(type); + capsNotifyPresence->setFrom(from); + capsNotifyPresence->setShow(status); + capsNotifyPresence->addPayload(capsInfo); + + capsNotifyPresence->addPayloads(additionalPayloads); + + xmppRosterImpl_->addContact(from, "Foo", {}, RosterItemPayload::Both); + dummyStanzaChannel_->onPresenceReceived(capsNotifyPresence); + } + + DiscoInfo::ref fileTransferSupportingDisco() { + DiscoInfo::ref discoInfo = std::make_shared<DiscoInfo>(); + discoInfo->addFeature(DiscoInfo::JingleFeature); + discoInfo->addFeature(DiscoInfo::JingleFTFeature); + discoInfo->addFeature(DiscoInfo::JingleTransportsS5BFeature); + discoInfo->addFeature(DiscoInfo::JingleTransportsIBBFeature); + return discoInfo; + } + + DiscoInfo::ref noFileTransferSupportingDisco() { + DiscoInfo::ref discoInfo = std::make_shared<DiscoInfo>(); + discoInfo->addFeature(DiscoInfo::JingleFeature); + return discoInfo; + } + + void testMergeAvailableResourcesForFeatures() { + CPPUNIT_ASSERT_EQUAL(No, featureOracle_->isFileTransferSupported(baseJID)); + + simulateIncomingPresence(noFileTransferJID, Presence::Available, StatusShow::Online, noFileTransferSupportingDisco()); + + CPPUNIT_ASSERT_EQUAL(size_t(1), presenceOracle_->getAllPresence(baseJID).size()); + CPPUNIT_ASSERT_EQUAL(No, featureOracle_->isFileTransferSupported(baseJID)); + + simulateIncomingPresence(fileTransferJID, Presence::Available, StatusShow::Online, fileTransferSupportingDisco()); + + CPPUNIT_ASSERT_EQUAL(size_t(2), presenceOracle_->getAllPresence(baseJID).size()); + CPPUNIT_ASSERT_EQUAL(Maybe, featureOracle_->isFileTransferSupported(baseJID)); + + simulateIncomingPresence(noFileTransferJID, Presence::Unavailable, StatusShow::None, noFileTransferSupportingDisco()); + + CPPUNIT_ASSERT_EQUAL(size_t(1), presenceOracle_->getAllPresence(baseJID).size()); + CPPUNIT_ASSERT_EQUAL(Yes, featureOracle_->isFileTransferSupported(baseJID)); + + simulateIncomingPresence(fileTransferJID, Presence::Unavailable, StatusShow::None, fileTransferSupportingDisco()); + + CPPUNIT_ASSERT_EQUAL(size_t(1), presenceOracle_->getAllPresence(baseJID).size()); + CPPUNIT_ASSERT_EQUAL(No, featureOracle_->isFileTransferSupported(baseJID)); + } + + void testMostAvailableFileTransferClient() { + simulateIncomingPresence(fileTransferJID, Presence::Available, StatusShow::DND, fileTransferSupportingDisco()); + + CPPUNIT_ASSERT_EQUAL(fileTransferJID, featureOracle_->getMostAvailableClientForFileTrasfer(baseJID)); + + simulateIncomingPresence(noFileTransferJID, Presence::Available, StatusShow::Online, noFileTransferSupportingDisco()); + + CPPUNIT_ASSERT_EQUAL(fileTransferJID, featureOracle_->getMostAvailableClientForFileTrasfer(baseJID)); + + auto moreAvailableJID = baseJID.withResource("moreAvailableFt"); + simulateIncomingPresence(moreAvailableJID, Presence::Available, StatusShow::Online, fileTransferSupportingDisco()); + + CPPUNIT_ASSERT_EQUAL(moreAvailableJID, featureOracle_->getMostAvailableClientForFileTrasfer(baseJID)); + } + + private: + struct DummyCapsProvider : public CapsProvider { + virtual DiscoInfo::ref getCaps(const std::string& hash) const { + std::map<std::string, DiscoInfo::ref>::const_iterator i = caps.find(hash); + if (i != caps.end()) { + return i->second; + } + return DiscoInfo::ref(); + } + + std::map<std::string, DiscoInfo::ref> caps; + }; + + private: + JID baseJID = "test@example.com"; + JID fileTransferJID = baseJID.withResource("fileTransfer"); + JID noFileTransferJID = baseJID.withResource("noFileTransfer"); + + std::shared_ptr<CryptoProvider> crypto_; + DummyCapsProvider* dummyCapsProvider_; + DummyStanzaChannel* dummyStanzaChannel_; + EntityCapsManager* entityCapsManager_; + FeatureOracle* featureOracle_; + PresenceOracle* presenceOracle_; + XMPPRosterImpl* xmppRosterImpl_; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(FeatureOracleTest); diff --git a/Swiften/Elements/Stanza.h b/Swiften/Elements/Stanza.h index 9a69696..9284bc3 100644 --- a/Swiften/Elements/Stanza.h +++ b/Swiften/Elements/Stanza.h @@ -68,6 +68,11 @@ namespace Swift { payloads_.insert(payloads_.end(), begin, end); } + template<typename Container> + void addPayloads(const Container& container) { + payloads_.insert(payloads_.end(), std::begin(container), std::end(container)); + } + void updatePayload(std::shared_ptr<Payload> payload); void removePayloadOfSameType(std::shared_ptr<Payload>); diff --git a/Swiften/FileTransfer/FileTransferManager.cpp b/Swiften/FileTransfer/FileTransferManager.cpp index 94f4ab7..a5d7313 100644 --- a/Swiften/FileTransfer/FileTransferManager.cpp +++ b/Swiften/FileTransfer/FileTransferManager.cpp @@ -4,6 +4,12 @@ * See Documentation/Licenses/BSD-simplified.txt for more information. */ +/* + * Copyright (c) 2016 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + #include <Swiften/FileTransfer/FileTransferManager.h> namespace Swift { @@ -11,13 +17,4 @@ namespace Swift { FileTransferManager::~FileTransferManager() { } -bool FileTransferManager::isSupportedBy(const DiscoInfo::ref info) { - if (info) { - return info->hasFeature(DiscoInfo::JingleFeature) - && info->hasFeature(DiscoInfo::JingleFTFeature) - && (info->hasFeature(DiscoInfo::JingleTransportsIBBFeature) || info->hasFeature(DiscoInfo::JingleTransportsS5BFeature)); - } - return false; -} - } diff --git a/Swiften/FileTransfer/FileTransferManager.h b/Swiften/FileTransfer/FileTransferManager.h index e315c67..07cfc90 100644 --- a/Swiften/FileTransfer/FileTransferManager.h +++ b/Swiften/FileTransfer/FileTransferManager.h @@ -47,8 +47,6 @@ namespace Swift { std::shared_ptr<ReadBytestream> bytestream, const FileTransferOptions& = FileTransferOptions()) = 0; - static bool isSupportedBy(const DiscoInfo::ref info); - boost::signals2::signal<void (IncomingFileTransfer::ref)> onIncomingFileTransfer; }; } diff --git a/Swiften/FileTransfer/FileTransferManagerImpl.cpp b/Swiften/FileTransfer/FileTransferManagerImpl.cpp index d449179..05dd3bb 100644 --- a/Swiften/FileTransfer/FileTransferManagerImpl.cpp +++ b/Swiften/FileTransfer/FileTransferManagerImpl.cpp @@ -19,8 +19,8 @@ #include <Swiften/Base/BoostFilesystemVersion.h> #include <Swiften/Base/Log.h> #include <Swiften/Base/Path.h> -#include <Swiften/Base/foreach.h> #include <Swiften/Disco/EntityCapsProvider.h> +#include <Swiften/Disco/FeatureOracle.h> #include <Swiften/Elements/JingleFileTransferFileInfo.h> #include <Swiften/Elements/Presence.h> #include <Swiften/FileTransfer/DefaultFileTransferTransporterFactory.h> @@ -99,28 +99,6 @@ void FileTransferManagerImpl::stop() { s5bServerManager->stop(); } -boost::optional<JID> FileTransferManagerImpl::highestPriorityJIDSupportingFileTransfer(const JID& bareJID) { - JID fullReceipientJID; - int priority = INT_MIN; - - //getAllPresence(bareJID) gives you all presences for the bare JID (i.e. all resources) Isode Limited. @ 11:11 - std::vector<Presence::ref> presences = presenceOracle->getAllPresence(bareJID); - - //iterate over them - foreach(Presence::ref pres, presences) { - if (pres->getPriority() > priority) { - // look up caps from the jid - DiscoInfo::ref info = capsProvider->getCaps(pres->getFrom()); - if (isSupportedBy(info)) { - priority = pres->getPriority(); - fullReceipientJID = pres->getFrom(); - } - } - } - - return fullReceipientJID.isValid() ? boost::optional<JID>(fullReceipientJID) : boost::optional<JID>(); -} - OutgoingFileTransfer::ref FileTransferManagerImpl::createOutgoingFileTransfer( const JID& to, const boost::filesystem::path& filepath, @@ -155,9 +133,10 @@ OutgoingFileTransfer::ref FileTransferManagerImpl::createOutgoingFileTransfer( JID receipient = to; if(receipient.isBare()) { - boost::optional<JID> fullJID = highestPriorityJIDSupportingFileTransfer(receipient); - if (fullJID.is_initialized()) { - receipient = fullJID.get(); + auto featureOracle = FeatureOracle(capsProvider, presenceOracle); + JID fullJID = featureOracle.getMostAvailableClientForFileTrasfer(receipient); + if (!fullJID.toString().empty()) { + receipient = fullJID; } else { return OutgoingFileTransfer::ref(); } diff --git a/Swiften/Presence/PresenceOracle.cpp b/Swiften/Presence/PresenceOracle.cpp index c2c1152..1c9d0ea 100644 --- a/Swiften/Presence/PresenceOracle.cpp +++ b/Swiften/Presence/PresenceOracle.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2015 Isode Limited. + * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ @@ -10,7 +10,6 @@ #include <boost/bind.hpp> -#include <Swiften/Base/foreach.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Elements/StatusShow.h> #include <Swiften/Roster/XMPPRoster.h> @@ -101,11 +100,10 @@ std::vector<Presence::ref> PresenceOracle::getAllPresence(const JID& bareJID) co if (i == entries_.end()) { return results; } - PresenceMap presenceMap = i->second; - PresenceMap::const_iterator j = presenceMap.begin(); - for (; j != presenceMap.end(); ++j) { - Presence::ref current = j->second; - results.push_back(current); + for (const auto& jidPresence : i->second) { + if (jidPresence.second) { + results.push_back(jidPresence.second); + } } return results; } @@ -153,7 +151,7 @@ Presence::ref PresenceOracle::getActivePresence(const std::vector<Presence::ref> PresenceAccountPriorityQueue away; PresenceAccountPriorityQueue offline; - foreach(Presence::ref presence, presences) { + for (auto&& presence : presences) { switch (presence->getShow()) { case StatusShow::Online: online.push(presence); @@ -200,18 +198,15 @@ Presence::ref PresenceOracle::getHighestPriorityPresence(const JID& bareJID) con if (i == entries_.end()) { return Presence::ref(); } - PresenceMap presenceMap = i->second; - PresenceMap::const_iterator j = presenceMap.begin(); Presence::ref highest; - for (; j != presenceMap.end(); ++j) { - Presence::ref current = j->second; + for (const auto& jidPresence : i->second) { + Presence::ref current = jidPresence.second; if (!highest || current->getPriority() > highest->getPriority() || (current->getPriority() == highest->getPriority() && StatusShow::typeToAvailabilityOrdering(current->getShow()) > StatusShow::typeToAvailabilityOrdering(highest->getShow()))) { highest = current; } - } return highest; } diff --git a/Swiften/SConscript b/Swiften/SConscript index eb7ae19..a8fb88a 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -387,9 +387,10 @@ if env["SCONS_STAGE"] == "build" : File("Component/UnitTest/ComponentSessionTest.cpp"), File("Disco/UnitTest/CapsInfoGeneratorTest.cpp"), File("Disco/UnitTest/CapsManagerTest.cpp"), + File("Disco/UnitTest/DiscoInfoResponderTest.cpp"), File("Disco/UnitTest/EntityCapsManagerTest.cpp"), + File("Disco/UnitTest/FeatureOracleTest.cpp"), File("Disco/UnitTest/JIDDiscoInfoResponderTest.cpp"), - File("Disco/UnitTest/DiscoInfoResponderTest.cpp"), File("Elements/UnitTest/IQTest.cpp"), File("Elements/UnitTest/StanzaTest.cpp"), File("Elements/UnitTest/FormTest.cpp"), |