diff options
| -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"), | 
 Swift
 Swift