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 @@ -1,91 +1,93 @@ /* * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <Swift/Controllers/Chat/ChatsManager.h> #include <memory> #include <boost/algorithm/string.hpp> #include <boost/archive/text_iarchive.hpp> #include <boost/archive/text_oarchive.hpp> #include <boost/bind.hpp> #include <boost/serialization/map.hpp> #include <boost/serialization/optional.hpp> #include <boost/serialization/split_free.hpp> #include <boost/serialization/string.hpp> #include <boost/serialization/vector.hpp> #include <Swiften/Avatars/AvatarManager.h> #include <Swiften/Base/Log.h> #include <Swiften/Client/ClientBlockListManager.h> #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> #include <Swiften/Elements/DeliveryReceipt.h> #include <Swiften/Elements/DeliveryReceiptRequest.h> #include <Swiften/Elements/Forwarded.h> #include <Swiften/Elements/MUCInvitationPayload.h> #include <Swiften/Elements/MUCUserPayload.h> #include <Swiften/MUC/MUCBookmarkManager.h> #include <Swiften/MUC/MUCManager.h> #include <Swiften/Presence/PresenceSender.h> #include <Swiften/Roster/XMPPRoster.h> #include <Swiften/StringCodecs/Base64.h> #include <Swiften/VCards/VCardManager.h> #include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h> #include <Swift/Controllers/Chat/ChatController.h> #include <Swift/Controllers/Chat/ChatControllerBase.h> #include <Swift/Controllers/Chat/ChatMessageParser.h> #include <Swift/Controllers/Chat/MUCController.h> #include <Swift/Controllers/Chat/MUCSearchController.h> #include <Swift/Controllers/Chat/UserSearchController.h> #include <Swift/Controllers/FileTransfer/FileTransferController.h> #include <Swift/Controllers/FileTransfer/FileTransferOverview.h> #include <Swift/Controllers/ProfileSettingsProvider.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/Settings/SettingsProvider.h> #include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h> #include <Swift/Controllers/UIEvents/CreateImpromptuMUCUIEvent.h> #include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h> #include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h> #include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> #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> #include <Swift/Controllers/WhiteboardManager.h> #include <Swift/Controllers/XMPPEvents/EventController.h> BOOST_CLASS_VERSION(Swift::ChatListWindow::Chat, 1) namespace boost { namespace serialization { template<class Archive> void save(Archive& ar, const Swift::JID& jid, const unsigned int /*version*/) { std::string jidStr = jid.toString(); ar << jidStr; } template<class Archive> void load(Archive& ar, Swift::JID& jid, const unsigned int /*version*/) { std::string stringJID; ar >> stringJID; jid = Swift::JID(stringJID); } template<class Archive> inline void serialize(Archive& ar, Swift::JID& t, const unsigned int file_version){ split_free(ar, t, file_version); } template<class Archive> void serialize(Archive& ar, Swift::ChatListWindow::Chat& chat, const unsigned int version) { ar & chat.jid; ar & chat.chatName; ar & chat.activity; ar & chat.isMUC; @@ -172,60 +174,61 @@ ChatsManager::ChatsManager( joinMUCWindow_ = nullptr; mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, profileSettings_); mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1)); ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1)); whiteboardManager_->onSessionRequest.connect(boost::bind(&ChatsManager::handleWhiteboardSessionRequest, this, _1, _2)); whiteboardManager_->onRequestAccepted.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardAccepted)); whiteboardManager_->onSessionTerminate.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardTerminated)); whiteboardManager_->onRequestRejected.connect(boost::bind(&ChatsManager::handleWhiteboardStateChange, this, _1, ChatWindow::WhiteboardRejected)); roster_->onJIDAdded.connect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1)); roster_->onJIDRemoved.connect(boost::bind(&ChatsManager::handleJIDRemovedFromRoster, this, _1)); roster_->onJIDUpdated.connect(boost::bind(&ChatsManager::handleJIDUpdatedInRoster, this, _1)); roster_->onRosterCleared.connect(boost::bind(&ChatsManager::handleRosterCleared, this)); settings_->onSettingChanged.connect(boost::bind(&ChatsManager::handleSettingChanged, this, _1)); userWantsReceipts_ = settings_->getSetting(SettingConstants::REQUEST_DELIVERYRECEIPTS); setupBookmarks(); loadRecents(); autoAcceptMUCInviteDecider_ = new AutoAcceptMUCInviteDecider(jid.getDomain(), roster_, settings_); } ChatsManager::~ChatsManager() { settings_->onSettingChanged.disconnect(boost::bind(&ChatsManager::handleSettingChanged, this, _1)); roster_->onJIDAdded.disconnect(boost::bind(&ChatsManager::handleJIDAddedToRoster, this, _1)); 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; } for (JIDMUCControllerPair controllerPair : mucControllers_) { delete controllerPair.second; } delete mucBookmarkManager_; delete mucSearchController_; delete autoAcceptMUCInviteDecider_; } void ChatsManager::saveRecents() { std::stringstream serializeStream; boost::archive::text_oarchive oa(serializeStream); std::vector<ChatListWindow::Chat> recentsLimited = std::vector<ChatListWindow::Chat>(recentChats_.begin(), recentChats_.end()); if (recentsLimited.size() > 25) { recentsLimited.erase(recentsLimited.begin() + 25, recentsLimited.end()); } if (eagleMode_) { for (ChatListWindow::Chat& chat : recentsLimited) { chat.activity = ""; } } class RemoveRecent { public: static bool ifPrivateMessage(const ChatListWindow::Chat& chat) { return chat.isPrivateMessage; } @@ -535,60 +538,78 @@ void ChatsManager::finalizeImpromptuJoin(MUC::ref muc, const std::vector<JID>& j boost::optional<JID> realJID = occupant.second.getRealJID(); if (realJID) { missingJIDsToInvite.erase(std::remove(missingJIDsToInvite.begin(), missingJIDsToInvite.end(), realJID->toBare()), missingJIDsToInvite.end()); } } if (reuseChatJID) { muc->invitePerson(reuseChatJID.get(), reason, true, true); } for (const JID& jid : missingJIDsToInvite) { muc->invitePerson(jid, reason, true); } } void ChatsManager::handleUIEvent(std::shared_ptr<UIEvent> event) { std::shared_ptr<RequestChatUIEvent> chatEvent = std::dynamic_pointer_cast<RequestChatUIEvent>(event); if (chatEvent) { handleChatRequest(chatEvent->getContact()); return; } std::shared_ptr<RemoveMUCBookmarkUIEvent> removeMUCBookmarkEvent = std::dynamic_pointer_cast<RemoveMUCBookmarkUIEvent>(event); if (removeMUCBookmarkEvent) { mucBookmarkManager_->removeBookmark(removeMUCBookmarkEvent->getBookmark()); return; } std::shared_ptr<AddMUCBookmarkUIEvent> addMUCBookmarkEvent = std::dynamic_pointer_cast<AddMUCBookmarkUIEvent>(event); if (addMUCBookmarkEvent) { 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) { assert(!localMUCServiceJID_.toString().empty()); // create new muc JID roomJID = createImpromptuMUCEvent->getRoomJID().toString().empty() ? JID(idGenerator_.generateID(), localMUCServiceJID_) : createImpromptuMUCEvent->getRoomJID(); // join muc MUC::ref muc = handleJoinMUCRequest(roomJID, boost::optional<std::string>(), nickResolver_->jidToNick(jid_), false, true, true); mucControllers_[roomJID]->onImpromptuConfigCompleted.connect(boost::bind(&ChatsManager::finalizeImpromptuJoin, this, muc, createImpromptuMUCEvent->getJIDs(), createImpromptuMUCEvent->getReason(), boost::optional<JID>())); mucControllers_[roomJID]->activateChatWindow(); } std::shared_ptr<EditMUCBookmarkUIEvent> editMUCBookmarkEvent = std::dynamic_pointer_cast<EditMUCBookmarkUIEvent>(event); if (editMUCBookmarkEvent) { mucBookmarkManager_->replaceBookmark(editMUCBookmarkEvent->getOldBookmark(), editMUCBookmarkEvent->getNewBookmark()); } else if (JoinMUCUIEvent::ref joinEvent = std::dynamic_pointer_cast<JoinMUCUIEvent>(event)) { handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getPassword(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically(), joinEvent->getCreateAsReservedRoomIfNew(), joinEvent->isImpromptu()); mucControllers_[joinEvent->getJID()]->activateChatWindow(); } else if (std::shared_ptr<RequestJoinMUCUIEvent> joinEvent = std::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) { if (!joinMUCWindow_) { joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow(uiEventStream_); joinMUCWindow_->onSearchMUC.connect(boost::bind(&ChatsManager::handleSearchMUCRequest, this)); } joinMUCWindow_->setMUC(joinEvent->getRoom()); joinMUCWindow_->setNick(nickResolver_->jidToNick(jid_)); joinMUCWindow_->show(); } 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 @@ -1,48 +1,47 @@ /* * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <Swift/Controllers/MainController.h> #include <cstdlib> #include <memory> #include <boost/bind.hpp> #include <boost/lexical_cast.hpp> #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> #include <Swiften/Client/ClientXMLTracer.h> #include <Swiften/Client/NickResolver.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Client/Storages.h> #include <Swiften/Crypto/CryptoProvider.h> #include <Swiften/Disco/CapsInfoGenerator.h> #include <Swiften/Disco/ClientDiscoManager.h> #include <Swiften/Disco/GetDiscoInfoRequest.h> #include <Swiften/Elements/ChatState.h> #include <Swiften/Elements/DiscoInfo.h> #include <Swiften/Elements/Presence.h> #include <Swiften/Elements/VCardUpdate.h> #include <Swiften/FileTransfer/FileTransferManager.h> #include <Swiften/Network/NetworkFactories.h> #include <Swiften/Network/TimerFactory.h> #include <Swiften/Presence/PresenceSender.h> #include <Swiften/Queries/Requests/EnableCarbonsRequest.h> #include <Swiften/StringCodecs/Base64.h> #include <Swiften/StringCodecs/Hexify.h> #include <Swiften/VCards/GetVCardRequest.h> #include <Swiften/VCards/VCardManager.h> #ifdef SWIFTEN_PLATFORM_WIN32 #include <Swiften/SASL/WindowsAuthentication.h> #endif #include <Swift/Controllers/AdHocManager.h> @@ -153,61 +152,61 @@ MainController::MainController( clientInitialized_ = false; offlineRequested_ = false; timeBeforeNextReconnect_ = -1; dock_ = dock; uiEventStream_ = new UIEventStream(); notifier_ = new TogglableNotifier(notifier); notifier_->setPersistentEnabled(settings_->getSetting(SettingConstants::SHOW_NOTIFICATIONS)); eventController_ = new EventController(); eventController_->onEventQueueLengthChange.connect(boost::bind(&MainController::handleEventQueueLengthChange, this, _1)); systemTrayController_ = new SystemTrayController(eventController_, systemTray); loginWindow_ = uiFactory_->createLoginWindow(uiEventStream_); loginWindow_->setShowNotificationToggle(!notifier->isExternallyConfigured()); highlightManager_ = new HighlightManager(settings_); highlightEditorController_ = new HighlightEditorController(uiEventStream_, uiFactory_, highlightManager_); soundEventController_ = new SoundEventController(eventController_, soundPlayer, settings, highlightManager_); xmppURIController_ = new XMPPURIController(uriHandler_, uiEventStream_); std::string selectedLoginJID = settings_->getSetting(SettingConstants::LAST_LOGIN_JID); bool loginAutomatically = settings_->getSetting(SettingConstants::LOGIN_AUTOMATICALLY); std::string cachedPassword; std::string cachedCertificate; 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"); std::string jid = profileSettings.getStringSetting("jid"); ClientOptions clientOptions = parseClientOptions(profileSettings.getStringSetting("options")); #ifdef SWIFTEN_PLATFORM_WIN32 clientOptions.singleSignOn = settings_->getSetting(SettingConstants::SINGLE_SIGN_ON); #endif loginWindow_->addAvailableAccount(jid, password, certificate, clientOptions); if (jid == selectedLoginJID) { cachedPassword = password; cachedCertificate = certificate; cachedOptions = clientOptions; } } loginWindow_->selectUser(selectedLoginJID); loginWindow_->setLoginAutomatically(loginAutomatically); } loginWindow_->onLoginRequest.connect(boost::bind(&MainController::handleLoginRequest, this, _1, _2, _3, _4, _5, _6, _7)); loginWindow_->onPurgeSavedLoginRequest.connect(boost::bind(&MainController::handlePurgeSavedLoginRequest, this, _1)); loginWindow_->onCancelLoginRequest.connect(boost::bind(&MainController::handleCancelLoginRequest, this)); loginWindow_->onQuitRequest.connect(boost::bind(&MainController::handleQuitRequest, this)); idleDetector_->setIdleTimeSeconds(settings->getSetting(SettingConstants::IDLE_TIMEOUT)); idleDetector_->onIdleChanged.connect(boost::bind(&MainController::handleInputIdleChanged, this, _1)); @@ -322,61 +321,61 @@ void MainController::resetPendingReconnects() { if (reconnectTimer_) { reconnectTimer_->stop(); reconnectTimer_.reset(); } resetCurrentError(); } void MainController::resetCurrentError() { if (lastDisconnectError_) { lastDisconnectError_->conclude(); lastDisconnectError_ = std::shared_ptr<ErrorEvent>(); } } void MainController::handleConnected() { boundJID_ = client_->getJID(); resetCurrentError(); resetPendingReconnects(); if (settings_->getSetting(SettingConstants::FORGET_PASSWORDS)) { purgeCachedCredentials(); } bool freshLogin = rosterController_ == nullptr; myStatusLooksOnline_ = true; if (freshLogin) { profileController_ = new ProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_); 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)); blockListController_ = new BlockListController(client_->getClientBlockListManager(), uiEventStream_, uiFactory_, eventController_); contactEditController_ = new ContactEditController(rosterController_, client_->getVCardManager(), uiFactory_, uiEventStream_); whiteboardManager_ = new WhiteboardManager(uiFactory_, uiEventStream_, client_->getNickResolver(), client_->getWhiteboardSessionManager()); /* Doing this early as an ordering fix. Various things later will * want to have the user's nick available and this means it will * be before they receive stanzas that need it (e.g. bookmarks).*/ client_->getVCardManager()->requestOwnVCard(); contactSuggesterWithoutRoster_ = new ContactSuggester(); contactSuggesterWithRoster_ = new ContactSuggester(); userSearchControllerInvite_ = new UserSearchController(UserSearchController::InviteToChat, jid_, uiEventStream_, client_->getVCardManager(), uiFactory_, client_->getIQRouter(), rosterController_, contactSuggesterWithRoster_, client_->getAvatarManager(), client_->getPresenceOracle(), profileSettings_); #ifdef SWIFT_EXPERIMENTAL_HISTORY historyController_ = new HistoryController(storages_->getHistoryStorage()); historyViewController_ = new HistoryViewController(jid_, uiEventStream_, historyController_, client_->getNickResolver(), client_->getAvatarManager(), client_->getPresenceOracle(), uiFactory_); chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, historyController_, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, client_->getVCardManager()); #else chatsManager_ = new ChatsManager(jid_, client_->getStanzaChannel(), client_->getIQRouter(), eventController_, uiFactory_, uiFactory_, client_->getNickResolver(), client_->getPresenceOracle(), client_->getPresenceSender(), uiEventStream_, uiFactory_, useDelayForLatency_, networkFactories_->getTimerFactory(), client_->getMUCRegistry(), client_->getEntityCapsProvider(), client_->getMUCManager(), uiFactory_, profileSettings_, ftOverview_, client_->getRoster(), !settings_->getSetting(SettingConstants::REMEMBER_RECENT_CHATS), settings_, nullptr, whiteboardManager_, highlightManager_, client_->getClientBlockListManager(), emoticons_, client_->getVCardManager()); #endif contactsFromRosterProvider_ = new ContactsFromXMPPRoster(client_->getRoster(), client_->getAvatarManager(), client_->getPresenceOracle()); contactSuggesterWithoutRoster_->addContactProvider(chatsManager_); contactSuggesterWithRoster_->addContactProvider(chatsManager_); contactSuggesterWithRoster_->addContactProvider(contactsFromRosterProvider_); highlightEditorController_->setContactSuggester(contactSuggesterWithoutRoster_); 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 @@ -1,113 +1,113 @@ /* * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <Swift/Controllers/Roster/RosterController.h> #include <memory> #include <boost/bind.hpp> #include <Swiften/Avatars/AvatarManager.h> #include <Swiften/Base/Path.h> #include <Swiften/Base/format.h> #include <Swiften/Client/ClientBlockListManager.h> #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> #include <Swiften/Jingle/JingleSessionManager.h> #include <Swiften/Presence/PresenceOracle.h> #include <Swiften/Presence/SubscriptionManager.h> #include <Swiften/Queries/IQRouter.h> #include <Swiften/Roster/GetRosterRequest.h> #include <Swiften/Roster/SetRosterRequest.h> #include <Swiften/Roster/XMPPRoster.h> #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> #include <Swift/Controllers/Roster/ItemOperations/SetAvailableFeatures.h> #include <Swift/Controllers/Roster/ItemOperations/SetAvatar.h> #include <Swift/Controllers/Roster/ItemOperations/SetBlockingState.h> #include <Swift/Controllers/Roster/ItemOperations/SetName.h> #include <Swift/Controllers/Roster/ItemOperations/SetPresence.h> #include <Swift/Controllers/Roster/ItemOperations/SetVCard.h> #include <Swift/Controllers/Roster/OfflineRosterFilter.h> #include <Swift/Controllers/Roster/Roster.h> #include <Swift/Controllers/Roster/RosterGroupExpandinessPersister.h> #include <Swift/Controllers/Roster/RosterVCardProvider.h> #include <Swift/Controllers/SettingConstants.h> #include <Swift/Controllers/UIEvents/AddContactUIEvent.h> #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> #include <Swift/Controllers/XMPPEvents/EventController.h> #include <Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h> 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; settings_ = settings; expandiness_ = new RosterGroupExpandinessPersister(roster_, settings); mainWindow_->setRosterModel(roster_); rosterVCardProvider_ = new RosterVCardProvider(roster_, vcardManager, JID::WithoutResource); changeStatusConnection_ = mainWindow_->onChangeStatusRequest.connect(boost::bind(&RosterController::handleChangeStatusRequest, this, _1, _2)); signOutConnection_ = mainWindow_->onSignOutRequest.connect(boost::bind(boost::ref(onSignOutRequest))); xmppRoster_->onJIDAdded.connect(boost::bind(&RosterController::handleOnJIDAdded, this, _1)); xmppRoster_->onJIDUpdated.connect(boost::bind(&RosterController::handleOnJIDUpdated, this, _1, _2, _3)); xmppRoster_->onJIDRemoved.connect(boost::bind(&RosterController::handleOnJIDRemoved, this, _1)); xmppRoster_->onRosterCleared.connect(boost::bind(&RosterController::handleRosterCleared, this)); 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)); mainWindow_->setMyAvatarPath(pathToString(avatarManager_->getAvatarPath(myJID_.toBare()))); nickManager_->onOwnNickChanged.connect(boost::bind(&MainWindow::setMyNick, mainWindow_, _1)); mainWindow_->setMyJID(jid); mainWindow_->setMyNick(nickManager_->getOwnNick()); entityCapsManager_->onCapsChanged.connect(boost::bind(&RosterController::handleOnCapsChanged, this, _1)); settings_->onSettingChanged.connect(boost::bind(&RosterController::handleSettingChanged, this, _1)); handleShowOfflineToggled(settings_->getSetting(SettingConstants::SHOW_OFFLINE)); ownContact_ = std::make_shared<ContactRosterItem>(myJID_.toBare(), myJID_.toBare(), nickManager_->getOwnNick(), static_cast<GroupRosterItem*>(nullptr)); ownContact_->setVCard(vcardManager_->getVCard(myJID_.toBare())); ownContact_->setAvatarPath(pathToString(avatarManager_->getAvatarPath(myJID_.toBare()))); mainWindow_->setMyContactRosterItem(ownContact_); } RosterController::~RosterController() { settings_->onSettingChanged.disconnect(boost::bind(&RosterController::handleSettingChanged, this, _1)); nickManager_->onOwnNickChanged.disconnect(boost::bind(&MainWindow::setMyNick, mainWindow_, _1)); delete offlineFilter_; delete expandiness_; mainWindow_->setRosterModel(nullptr); @@ -240,63 +240,60 @@ void RosterController::handleUIEvent(std::shared_ptr<UIEvent> event) { } else if (std::shared_ptr<RenameRosterItemUIEvent> renameEvent = std::dynamic_pointer_cast<RenameRosterItemUIEvent>(event)) { JID contact(renameEvent->getJID()); RosterItemPayload item(contact, renameEvent->getNewName(), xmppRoster_->getSubscriptionStateForJID(contact)); item.setGroups(xmppRoster_->getGroupsForJID(contact)); std::shared_ptr<RosterPayload> roster(new RosterPayload()); roster->addItem(item); SetRosterRequest::ref request = SetRosterRequest::create(roster, iqRouter_); request->onResponse.connect(boost::bind(&RosterController::handleRosterSetError, this, _1, roster)); request->send(); } else if (std::shared_ptr<RenameGroupUIEvent> renameGroupEvent = std::dynamic_pointer_cast<RenameGroupUIEvent>(event)) { std::vector<XMPPRosterItem> items = xmppRoster_->getItems(); std::string group = renameGroupEvent->getGroup(); // FIXME: We should handle contacts groups specially to avoid clashes if (group == QT_TRANSLATE_NOOP("", "Contacts")) { group = ""; } for (auto& item : items) { std::vector<std::string> groups = item.getGroups(); if ( (group.empty() && groups.empty()) || std::find(groups.begin(), groups.end(), group) != groups.end()) { groups.erase(std::remove(groups.begin(), groups.end(), group), groups.end()); if (std::find(groups.begin(), groups.end(), renameGroupEvent->getNewName()) == groups.end()) { groups.push_back(renameGroupEvent->getNewName()); } item.setGroups(groups); updateItem(item); } } } - 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) { updateItem(XMPPRosterItem(jid, xmppRoster_->getNameForJID(jid), groups, xmppRoster_->getSubscriptionStateForJID(jid))); } void RosterController::updateItem(const XMPPRosterItem& item) { RosterItemPayload itemPayload(item.getJID(), item.getName(), item.getSubscription()); itemPayload.setGroups(item.getGroups()); RosterPayload::ref roster = std::make_shared<RosterPayload>(); roster->addItem(itemPayload); SetRosterRequest::ref request = SetRosterRequest::create(roster, iqRouter_); request->onResponse.connect(boost::bind(&RosterController::handleRosterItemUpdated, this, _1, roster)); request->send(); } void RosterController::initBlockingCommand() { std::shared_ptr<BlockList> blockList = clientBlockListManager_->requestBlockList(); blockingOnStateChangedConnection_ = blockList->onStateChanged.connect(boost::bind(&RosterController::handleBlockingStateChanged, this)); blockingOnItemAddedConnection_ = blockList->onItemAdded.connect(boost::bind(&RosterController::handleBlockingItemAdded, this, _1)); blockingOnItemRemovedConnection_ = blockList->onItemRemoved.connect(boost::bind(&RosterController::handleBlockingItemRemoved, this, _1)); roster_->setBlockingSupported(true); if (blockList->getState() == BlockList::Available) { for (const auto& jid : blockList->getItems()) { roster_->applyOnItems(SetBlockingState(jid, ContactRosterItem::IsBlocked)); } } @@ -369,44 +366,41 @@ void RosterController::handleOwnVCardChanged(VCard::ref vcard) { mainWindow_->setMyContactRosterItem(ownContact_); } void RosterController::handleAvatarChanged(const JID& jid) { boost::filesystem::path path = avatarManager_->getAvatarPath(jid); roster_->applyOnItems(SetAvatar(jid, path)); if (jid.equals(myJID_, JID::WithoutResource)) { mainWindow_->setMyAvatarPath(pathToString(path)); ownContact_->setAvatarPath(pathToString(path)); mainWindow_->setMyContactRosterItem(ownContact_); } } void RosterController::handlePresenceChanged(Presence::ref presence) { if (presence->getFrom().equals(myJID_, JID::WithResource)) { ownContact_->applyPresence(presence); mainWindow_->setMyContactRosterItem(ownContact_); } handleIncomingPresence(presence); } boost::optional<XMPPRosterItem> RosterController::getItem(const JID& jid) const { return xmppRoster_->getItem(jid); } std::set<std::string> RosterController::getGroups() const { return xmppRoster_->getGroups(); } 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 @@ -1,82 +1,82 @@ /* * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #pragma once #include <memory> #include <set> #include <string> #include <boost/signals2.hpp> #include <Swiften/Avatars/AvatarManager.h> #include <Swiften/Elements/ErrorPayload.h> #include <Swiften/Elements/Presence.h> #include <Swiften/Elements/RosterPayload.h> #include <Swiften/Elements/VCard.h> #include <Swiften/JID/JID.h> #include <Swift/Controllers/Roster/ContactRosterItem.h> #include <Swift/Controllers/UIEvents/UIEvent.h> namespace Swift { class AvatarManager; class ClientBlockListManager; class EntityCapsProvider; class EventController; + class FeatureOracle; class FileTransferManager; - class FileTransferOverview; class IQRouter; class MainWindow; class MainWindowFactory; class NickManager; class NickResolver; class OfflineRosterFilter; class PresenceOracle; class Roster; class RosterGroupExpandinessPersister; class RosterVCardProvider; class SettingsProvider; class SubscriptionManager; class SubscriptionRequestEvent; class UIEventStream; class VCardManager; class XMPPRoster; class XMPPRosterItem; 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; } MainWindow* getWindow() {return mainWindow_;} boost::signals2::signal<void (StatusShow::Type, const std::string&)> onChangeStatusRequest; boost::signals2::signal<void ()> onSignOutRequest; void handleOwnVCardChanged(VCard::ref vcard); void handleAvatarChanged(const JID& jid); void handlePresenceChanged(Presence::ref presence); void setEnabled(bool enabled); boost::optional<XMPPRosterItem> getItem(const JID&) const; std::set<std::string> getGroups() const; void setContactGroups(const JID& jid, const std::vector<std::string>& groups); void updateItem(const XMPPRosterItem&); void initBlockingCommand(); private: void handleOnJIDAdded(const JID &jid); void handleRosterCleared(); void handleOnJIDRemoved(const JID &jid); void handleOnJIDUpdated(const JID &jid, const std::string& oldName, const std::vector<std::string>& oldGroups); void handleStartChatRequest(const JID& contact); void handleChangeStatusRequest(StatusShow::Type show, const std::string &statusText); void handleShowOfflineToggled(bool state); void handleIncomingPresence(std::shared_ptr<Presence> newPresence); void handleSubscriptionRequest(const JID& jid, const std::string& message); void handleSubscriptionRequestAccepted(SubscriptionRequestEvent* event); @@ -84,43 +84,43 @@ namespace Swift { void handleUIEvent(std::shared_ptr<UIEvent> event); void handleRosterItemUpdated(ErrorPayload::ref error, std::shared_ptr<RosterPayload> rosterPayload); void handleRosterSetError(ErrorPayload::ref error, std::shared_ptr<RosterPayload> rosterPayload); void applyAllPresenceTo(const JID& jid); void handleEditProfileRequest(); void handleOnCapsChanged(const JID& jid); void handleSettingChanged(const std::string& settingPath); void handleBlockingStateChanged(); void handleBlockingItemAdded(const JID& jid); void handleBlockingItemRemoved(const JID& jid); JID myJID_; XMPPRoster* xmppRoster_; MainWindowFactory* mainWindowFactory_; MainWindow* mainWindow_; Roster* roster_; OfflineRosterFilter* offlineFilter_; VCardManager* vcardManager_; AvatarManager* avatarManager_; NickManager* nickManager_; NickResolver* nickResolver_; PresenceOracle* presenceOracle_; SubscriptionManager* subscriptionManager_; EventController* eventController_; RosterGroupExpandinessPersister* expandiness_; IQRouter* iqRouter_; 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_; boost::signals2::scoped_connection blockingOnItemRemovedConnection_; boost::signals2::scoped_connection changeStatusConnection_; boost::signals2::scoped_connection signOutConnection_; boost::signals2::scoped_connection uiEventConnection_; }; } 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 @@ -1,414 +1,481 @@ /* * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> #include <Swiften/Avatars/NullAvatarManager.h> #include <Swiften/Base/Algorithm.h> #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> #include <Swiften/Presence/SubscriptionManager.h> #include <Swiften/Queries/DummyIQChannel.h> #include <Swiften/Queries/IQRouter.h> #include <Swiften/Roster/XMPPRosterImpl.h> #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> #include <Swift/Controllers/Roster/RosterController.h> #include <Swift/Controllers/Settings/DummySettingsProvider.h> #include <Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UnitTest/MockMainWindowFactory.h> #include <Swift/Controllers/XMPPEvents/EventController.h> 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());} }; class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(RosterControllerTest); CPPUNIT_TEST(testAdd); CPPUNIT_TEST(testAddSubscription); CPPUNIT_TEST(testReceiveRename); CPPUNIT_TEST(testReceiveRegroup); CPPUNIT_TEST(testSendRename); CPPUNIT_TEST(testPresence); CPPUNIT_TEST(testHighestPresence); CPPUNIT_TEST(testNotHighestPresence); CPPUNIT_TEST(testUnavailablePresence); CPPUNIT_TEST(testRemoveResultsInUnavailablePresence); CPPUNIT_TEST(testOwnContactInRosterPresence); + CPPUNIT_TEST(testMultiResourceFileTransferFeature); CPPUNIT_TEST_SUITE_END(); public: void setUp() { jid_ = JID("testjid@swift.im/swift"); xmppRoster_ = new XMPPRosterImpl(); 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_); stanzaChannel_ = new DummyStanzaChannel(); presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_); subscriptionManager_ = new SubscriptionManager(stanzaChannel_); eventController_ = new EventController(); 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() { delete rosterController_; delete vcardManager_; delete vcardStorage_; delete crypto_; delete clientBlockListManager_; - delete ftOverview_; - delete ftManager_; delete jingleSessionManager_; delete entityCapsManager_; - delete capsProvider_; delete nickManager_; delete nickResolver_; delete mucRegistry_; delete mainWindowFactory_; delete avatarManager_; delete router_; delete channel_; delete eventController_; delete subscriptionManager_; delete presenceOracle_; delete stanzaChannel_; delete uiEventStream_; delete settings_; delete xmppRoster_; } 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) { return JID(jid.toBare().toString() + "/" + resource); } void testPresence() { std::vector<std::string> groups; groups.push_back("testGroup1"); groups.push_back("testGroup2"); JID from("test@testdomain.com"); xmppRoster_->addContact(from, "name", groups, RosterItemPayload::Both); Presence::ref presence(new Presence()); presence->setFrom(withResource(from, "bob")); 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()); } void testHighestPresence() { std::vector<std::string> groups; groups.push_back("testGroup1"); JID from("test@testdomain.com"); xmppRoster_->addContact(from, "name", groups, RosterItemPayload::Both); Presence::ref lowPresence(new Presence()); lowPresence->setFrom(withResource(from, "bob")); lowPresence->setPriority(2); lowPresence->setStatus("Not here"); Presence::ref highPresence(new Presence()); highPresence->setFrom(withResource(from, "bert")); highPresence->setPriority(10); 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()); } void testNotHighestPresence() { std::vector<std::string> groups; groups.push_back("testGroup1"); JID from("test@testdomain.com"); xmppRoster_->addContact(from, "name", groups, RosterItemPayload::Both); Presence::ref lowPresence(new Presence()); lowPresence->setFrom(withResource(from, "bob")); lowPresence->setPriority(2); lowPresence->setStatus("Not here"); Presence::ref highPresence(new Presence()); highPresence->setFrom(withResource(from, "bert")); highPresence->setPriority(10); 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()); } void testUnavailablePresence() { std::vector<std::string> groups; groups.push_back("testGroup1"); JID from("test@testdomain.com"); xmppRoster_->addContact(from, "name", groups, RosterItemPayload::Both); Presence::ref lowPresence(new Presence()); lowPresence->setFrom(withResource(from, "bob")); lowPresence->setPriority(2); lowPresence->setShow(StatusShow::Away); lowPresence->setStatus("Not here"); Presence::ref lowPresenceOffline(new Presence()); lowPresenceOffline->setFrom(withResource(from, "bob")); lowPresenceOffline->setStatus("Signing out"); lowPresenceOffline->setType(Presence::Unavailable); Presence::ref highPresence(new Presence()); highPresence->setFrom(withResource(from, "bert")); highPresence->setPriority(10); highPresence->setStatus("So totally here"); Presence::ref highPresenceOffline(new Presence()); highPresenceOffline->setFrom(withResource(from, "bert")); highPresenceOffline->setType(Presence::Unavailable); stanzaChannel_->onPresenceReceived(lowPresence); Presence::ref accountPresence = presenceOracle_->getAccountPresence(from); CPPUNIT_ASSERT_EQUAL(StatusShow::Away, accountPresence->getShow()); stanzaChannel_->onPresenceReceived(highPresence); accountPresence = presenceOracle_->getAccountPresence(from); CPPUNIT_ASSERT_EQUAL(StatusShow::Online, accountPresence->getShow()); 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); CPPUNIT_ASSERT_EQUAL(Presence::Available, low->getType()); CPPUNIT_ASSERT_EQUAL(lowPresence->getStatus(), low->getStatus()); 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); CPPUNIT_ASSERT_EQUAL(Presence::Unavailable, low->getType()); CPPUNIT_ASSERT_EQUAL(lowPresenceOffline->getStatus(), low->getStatus()); CPPUNIT_ASSERT_EQUAL(StatusShow::None, item->getStatusShow()); CPPUNIT_ASSERT_EQUAL(lowPresenceOffline->getStatus(), item->getStatusText()); } void testAdd() { std::vector<std::string> groups; groups.push_back("testGroup1"); 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"))); } void testAddSubscription() { std::vector<std::string> groups; 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())); } void testReceiveRename() { std::vector<std::string> groups; 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()); } void testReceiveRegroup() { std::vector<std::string> oldGroups; std::vector<std::string> newGroups; newGroups.push_back("A Group"); std::vector<std::string> newestGroups; newestGroups.push_back("Best Group"); 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()); } void testSendRename() { JID jid("testling@wonderland.lit"); std::vector<std::string> groups; groups.push_back("Friends"); groups.push_back("Enemies"); xmppRoster_->addContact(jid, "Bob", groups, RosterItemPayload::From); CPPUNIT_ASSERT_EQUAL(groups.size(), xmppRoster_->getGroupsForJID(jid).size()); uiEventStream_->send(std::make_shared<RenameRosterItemUIEvent>(jid, "Robert")); CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), channel_->iqs_.size()); CPPUNIT_ASSERT_EQUAL(IQ::Set, channel_->iqs_[0]->getType()); std::shared_ptr<RosterPayload> payload = channel_->iqs_[0]->getPayload<RosterPayload>(); CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), payload->getItems().size()); RosterItemPayload item = payload->getItems()[0]; CPPUNIT_ASSERT_EQUAL(jid, item.getJID()); CPPUNIT_ASSERT_EQUAL(std::string("Robert"), item.getName()); CPPUNIT_ASSERT_EQUAL(groups.size(), item.getGroups().size()); assertVectorsEqual(groups, item.getGroups(), __LINE__); } void testRemoveResultsInUnavailablePresence() { std::vector<std::string> groups; groups.push_back("testGroup1"); JID from("test@testdomain.com"); xmppRoster_->addContact(from, "name", groups, RosterItemPayload::Both); Presence::ref lowPresence(new Presence()); lowPresence->setFrom(withResource(from, "bob")); lowPresence->setPriority(2); lowPresence->setStatus("Not here"); Presence::ref highPresence(new Presence()); highPresence->setFrom(withResource(from, "bert")); highPresence->setPriority(10); highPresence->setStatus("So totally here"); stanzaChannel_->onPresenceReceived(highPresence); stanzaChannel_->onPresenceReceived(lowPresence); CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), presenceOracle_->getAllPresence("test@testdomain.com").size()); xmppRoster_->onJIDRemoved(JID("test@testdomain.com")); CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), presenceOracle_->getAllPresence("test@testdomain.com").size()); CPPUNIT_ASSERT_EQUAL(Presence::Unavailable, presenceOracle_->getAllPresence("test@testdomain.com")[0]->getType()); } void testOwnContactInRosterPresence() { std::vector<std::string> groups; groups.push_back("testGroup1"); groups.push_back("testGroup2"); JID from = jid_; xmppRoster_->addContact(from.toBare(), "name", groups, RosterItemPayload::Both); Presence::ref presence(new Presence()); presence->setFrom(from); 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()) { std::stringstream stream; stream << "Couldn't find " << entry << " in v2 (line " << line << ")"; CPPUNIT_FAIL(stream.str()); } } } + 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_; MockMainWindowFactory* mainWindowFactory_; NickManager* nickManager_; NickResolver* nickResolver_; RosterController* rosterController_; DummyIQChannel* channel_; DummyStanzaChannel* stanzaChannel_; IQRouter* router_; PresenceOracle* presenceOracle_; SubscriptionManager* subscriptionManager_; EventController* eventController_; 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 @@ -1,99 +1,201 @@ /* * Copyright (c) 2015-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <Swiften/Disco/FeatureOracle.h> #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> #include <Swiften/Presence/PresenceOracle.h> 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,47 +1,53 @@ /* - * 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> namespace Swift { class EntityCapsProvider; class JID; class PresenceOracle; /** * @brief The FeatureOracle class enables direct feature support lookup for client features supported by Swiften. */ class SWIFTEN_API FeatureOracle { public: FeatureOracle(EntityCapsProvider* capsProvider, PresenceOracle* presenceOracle); public: 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_; PresenceOracle* presenceOracle_; }; } 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 @@ -41,56 +41,61 @@ namespace Swift { } return std::shared_ptr<T>(); } template<typename T> std::vector< std::shared_ptr<T> > getPayloads() const { std::vector< std::shared_ptr<T> > results; for (const auto& payload : payloads_) { std::shared_ptr<T> result(std::dynamic_pointer_cast<T>(payload)); if (result) { results.push_back(result); } } return results; } const std::vector< std::shared_ptr<Payload> >& getPayloads() const { return payloads_; } void addPayload(std::shared_ptr<Payload> payload) { payloads_.push_back(payload); } template<typename InputIterator> void addPayloads(InputIterator begin, InputIterator end) { 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>); std::shared_ptr<Payload> getPayloadOfSameType(std::shared_ptr<Payload>) const; const JID& getFrom() const { return from_; } void setFrom(const JID& from) { from_ = from; } const JID& getTo() const { return to_; } void setTo(const JID& to) { to_ = to; } const std::string& getID() const { return id_; } void setID(const std::string& id) { id_ = id; } boost::optional<boost::posix_time::ptime> getTimestamp() const; // Falls back to any timestamp if no specific timestamp for the given JID is found. boost::optional<boost::posix_time::ptime> getTimestampFrom(const JID& jid) const; private: std::string id_; JID from_; JID to_; std::vector< std::shared_ptr<Payload> > payloads_; }; } 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 @@ -1,23 +1,20 @@ /* * Copyright (c) 2011 Tobias Markmann * Licensed under the simplified BSD license. * 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 { 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 @@ -20,35 +20,33 @@ #include <Swiften/Base/API.h> #include <Swiften/Elements/DiscoInfo.h> #include <Swiften/FileTransfer/FileTransferOptions.h> #include <Swiften/FileTransfer/IncomingFileTransfer.h> #include <Swiften/FileTransfer/OutgoingFileTransfer.h> #include <Swiften/JID/JID.h> namespace Swift { class ReadBytestream; class SWIFTEN_API FileTransferManager { public: virtual ~FileTransferManager(); virtual OutgoingFileTransfer::ref createOutgoingFileTransfer( const JID& to, const boost::filesystem::path& filepath, const std::string& description, std::shared_ptr<ReadBytestream> bytestream, const FileTransferOptions& = FileTransferOptions()) = 0; virtual OutgoingFileTransfer::ref createOutgoingFileTransfer( const JID& to, const std::string& filename, const std::string& description, const boost::uintmax_t sizeInBytes, const boost::posix_time::ptime& lastModified, 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 @@ -1,53 +1,53 @@ /* * Copyright (c) 2011 Tobias Markmann * Licensed under the simplified BSD license. * See Documentation/Licenses/BSD-simplified.txt for more information. */ /* * Copyright (c) 2013-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <Swiften/FileTransfer/FileTransferManagerImpl.h> #include <boost/bind.hpp> #include <boost/cstdint.hpp> #include <boost/filesystem.hpp> #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> #include <Swiften/FileTransfer/IncomingFileTransferManager.h> #include <Swiften/FileTransfer/OutgoingFileTransferManager.h> #include <Swiften/FileTransfer/SOCKS5BytestreamProxiesManager.h> #include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h> #include <Swiften/FileTransfer/SOCKS5BytestreamServerManager.h> #include <Swiften/JID/JID.h> #include <Swiften/Network/ConnectionFactory.h> #include <Swiften/Network/ConnectionServerFactory.h> #include <Swiften/Network/HostAddress.h> #include <Swiften/Network/NATTraverser.h> #include <Swiften/Presence/PresenceOracle.h> #include <Swiften/Queries/IQRouter.h> namespace Swift { FileTransferManagerImpl::FileTransferManagerImpl( const JID& ownJID, JingleSessionManager* jingleSessionManager, IQRouter* router, EntityCapsProvider* capsProvider, PresenceOracle* presOracle, ConnectionFactory* connectionFactory, ConnectionServerFactory* connectionServerFactory, TimerFactory* timerFactory, DomainNameResolver* domainNameResolver, NetworkEnvironment* networkEnvironment, NATTraverser* natTraverser, @@ -72,115 +72,94 @@ FileTransferManagerImpl::FileTransferManagerImpl( outgoingFTManager = new OutgoingFileTransferManager( jingleSessionManager, iqRouter, transporterFactory, timerFactory, crypto); incomingFTManager = new IncomingFileTransferManager( jingleSessionManager, transporterFactory, timerFactory, crypto); incomingFTManager->onIncomingFileTransfer.connect(onIncomingFileTransfer); } FileTransferManagerImpl::~FileTransferManagerImpl() { delete incomingFTManager; delete outgoingFTManager; delete transporterFactory; delete bytestreamProxy; delete s5bServerManager; delete bytestreamRegistry; } void FileTransferManagerImpl::start() { } 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, const std::string& description, std::shared_ptr<ReadBytestream> bytestream, const FileTransferOptions& config) { #if BOOST_FILESYSTEM_VERSION == 2 // TODO: Delete this when boost 1.44 becomes a minimum requirement, and we no longer need v2 std::string filename = filepath.filename(); #else std::string filename = pathToString(filepath.filename()); #endif boost::uintmax_t sizeInBytes = boost::filesystem::file_size(filepath); boost::posix_time::ptime lastModified = boost::posix_time::from_time_t(boost::filesystem::last_write_time(filepath)); return createOutgoingFileTransfer(to, filename, description, sizeInBytes, lastModified, bytestream, config); } OutgoingFileTransfer::ref FileTransferManagerImpl::createOutgoingFileTransfer( const JID& to, const std::string& filename, const std::string& description, const boost::uintmax_t sizeInBytes, const boost::posix_time::ptime& lastModified, std::shared_ptr<ReadBytestream> bytestream, const FileTransferOptions& config) { JingleFileTransferFileInfo fileInfo; fileInfo.setDate(lastModified); fileInfo.setSize(sizeInBytes); fileInfo.setName(filename); fileInfo.setDescription(description); 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(); } } assert(!iqRouter->getJID().isBare()); DiscoInfo::ref capabilities = capsProvider->getCaps(receipient); FileTransferOptions options = config; if (capabilities) { if (!capabilities->hasFeature(DiscoInfo::JingleTransportsS5BFeature)) { options = options.withAssistedAllowed(false).withDirectAllowed(false).withProxiedAllowed(false); } if (!capabilities->hasFeature(DiscoInfo::JingleTransportsIBBFeature)) { options = options.withInBandAllowed(false); } } else { SWIFT_LOG(warning) << "No entity capabilities information for " << receipient.toString() << std::endl; } return outgoingFTManager->createOutgoingFileTransfer(iqRouter->getJID(), receipient, bytestream, fileInfo, options); } } 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,43 +1,42 @@ /* - * Copyright (c) 2010-2015 Isode Limited. + * Copyright (c) 2010-2016 Isode Limited. * All rights reserved. * See the COPYING file for more information. */ #include <Swiften/Presence/PresenceOracle.h> #include <queue> #include <boost/bind.hpp> -#include <Swiften/Base/foreach.h> #include <Swiften/Client/StanzaChannel.h> #include <Swiften/Elements/StatusShow.h> #include <Swiften/Roster/XMPPRoster.h> namespace Swift { PresenceOracle::PresenceOracle(StanzaChannel* stanzaChannel, XMPPRoster* roster) : stanzaChannel_(stanzaChannel), xmppRoster_(roster) { stanzaChannel_->onPresenceReceived.connect(boost::bind(&PresenceOracle::handleIncomingPresence, this, _1)); stanzaChannel_->onAvailableChanged.connect(boost::bind(&PresenceOracle::handleStanzaChannelAvailableChanged, this, _1)); xmppRoster_->onJIDRemoved.connect(boost::bind(&PresenceOracle::handleJIDRemoved, this, _1)); } PresenceOracle::~PresenceOracle() { stanzaChannel_->onPresenceReceived.disconnect(boost::bind(&PresenceOracle::handleIncomingPresence, this, _1)); stanzaChannel_->onAvailableChanged.disconnect(boost::bind(&PresenceOracle::handleStanzaChannelAvailableChanged, this, _1)); xmppRoster_->onJIDRemoved.disconnect(boost::bind(&PresenceOracle::handleJIDRemoved, this, _1)); } void PresenceOracle::handleStanzaChannelAvailableChanged(bool available) { if (available) { entries_.clear(); } } void PresenceOracle::handleIncomingPresence(Presence::ref presence) { JID bareJID(presence->getFrom().toBare()); if (presence->getType() == Presence::Subscribe) { } else { Presence::ref passedPresence = presence; @@ -74,146 +73,142 @@ void PresenceOracle::handleJIDRemoved(const JID& removedJID) { if (entries_.find(removedJID) != entries_.end()) { entries_[removedJID].clear(); entries_[removedJID][removedJID] = unavailablePresence; } onPresenceChange(unavailablePresence); } Presence::ref PresenceOracle::getLastPresence(const JID& jid) const { PresencesMap::const_iterator i = entries_.find(jid.toBare()); if (i == entries_.end()) { return Presence::ref(); } PresenceMap presenceMap = i->second; PresenceMap::const_iterator j = presenceMap.find(jid); if (j != presenceMap.end()) { return j->second; } else { return Presence::ref(); } } std::vector<Presence::ref> PresenceOracle::getAllPresence(const JID& bareJID) const { std::vector<Presence::ref> results; PresencesMap::const_iterator i = entries_.find(bareJID); 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; } struct PresenceAccountCmp { 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; } bool operator()(const Presence::ref& a, const Presence::ref& b) { int aPreference = preferenceFromStatusShow(a->getShow()); int bPreference = preferenceFromStatusShow(b->getShow()); if (aPreference != bPreference) { return aPreference < bPreference; } if (a->getPriority() != b->getPriority()) { return a->getPriority() < b->getPriority(); } return a->getFrom().getResource() < b->getFrom().getResource(); } }; typedef std::priority_queue<Presence::ref, std::vector<Presence::ref>, PresenceAccountCmp> PresenceAccountPriorityQueue; Presence::ref PresenceOracle::getActivePresence(const std::vector<Presence::ref> presences) { Presence::ref accountPresence; PresenceAccountPriorityQueue online; PresenceAccountPriorityQueue away; PresenceAccountPriorityQueue offline; - foreach(Presence::ref presence, presences) { + for (auto&& presence : presences) { switch (presence->getShow()) { case StatusShow::Online: online.push(presence); break; case StatusShow::Away: away.push(presence); break; case StatusShow::FFC: online.push(presence); break; case StatusShow::XA: away.push(presence); break; case StatusShow::DND: away.push(presence); break; case StatusShow::None: offline.push(presence); break; } } if (!online.empty()) { accountPresence = online.top(); } else if (!away.empty()) { accountPresence = away.top(); } else if (!offline.empty()) { accountPresence = offline.top(); } return accountPresence; } Presence::ref PresenceOracle::getAccountPresence(const JID& jid) const { Presence::ref accountPresence; std::vector<Presence::ref> allPresences = getAllPresence(jid.toBare()); accountPresence = getActivePresence(allPresences); return accountPresence; } Presence::ref PresenceOracle::getHighestPriorityPresence(const JID& bareJID) const { PresencesMap::const_iterator i = entries_.find(bareJID); 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 @@ -360,63 +360,64 @@ if env["SCONS_STAGE"] == "build" : os.unlink(str(target[0])) os.symlink(source[0].get_contents(), str(target[0])) for alias in myenv["SWIFTEN_LIBRARY_ALIASES"] : myenv.Command(myenv.File(alias), [myenv.Value(swiften_lib[0].name), swiften_lib[0]], symlink) env.Append(UNITTEST_SOURCES = [ File("Avatars/UnitTest/VCardUpdateAvatarManagerTest.cpp"), File("Avatars/UnitTest/VCardAvatarManagerTest.cpp"), File("Avatars/UnitTest/CombinedAvatarProviderTest.cpp"), File("Avatars/UnitTest/AvatarManagerImplTest.cpp"), File("Base/UnitTest/IDGeneratorTest.cpp"), File("Base/UnitTest/SimpleIDGeneratorTest.cpp"), File("Base/UnitTest/StringTest.cpp"), File("Base/UnitTest/DateTimeTest.cpp"), File("Base/UnitTest/ByteArrayTest.cpp"), File("Base/UnitTest/URLTest.cpp"), File("Base/UnitTest/PathTest.cpp"), File("Chat/UnitTest/ChatStateNotifierTest.cpp"), # File("Chat/UnitTest/ChatStateTrackerTest.cpp"), File("Client/UnitTest/ClientSessionTest.cpp"), File("Client/UnitTest/NickResolverTest.cpp"), File("Client/UnitTest/ClientBlockListManagerTest.cpp"), File("Client/UnitTest/BlockListImplTest.cpp"), File("Compress/UnitTest/ZLibCompressorTest.cpp"), File("Compress/UnitTest/ZLibDecompressorTest.cpp"), File("Component/UnitTest/ComponentHandshakeGeneratorTest.cpp"), File("Component/UnitTest/ComponentConnectorTest.cpp"), 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"), File("EventLoop/UnitTest/EventLoopTest.cpp"), File("EventLoop/UnitTest/SimpleEventLoopTest.cpp"), # File("History/UnitTest/SQLiteHistoryManagerTest.cpp"), File("JID/UnitTest/JIDTest.cpp"), File("LinkLocal/UnitTest/LinkLocalConnectorTest.cpp"), File("LinkLocal/UnitTest/LinkLocalServiceBrowserTest.cpp"), File("LinkLocal/UnitTest/LinkLocalServiceInfoTest.cpp"), File("LinkLocal/UnitTest/LinkLocalServiceTest.cpp"), File("MUC/UnitTest/MUCTest.cpp"), File("MUC/UnitTest/MockMUC.cpp"), File("Network/UnitTest/HostAddressTest.cpp"), File("Network/UnitTest/ConnectorTest.cpp"), File("Network/UnitTest/ChainedConnectorTest.cpp"), File("Network/UnitTest/DomainNameServiceQueryTest.cpp"), File("Network/UnitTest/HTTPConnectProxiedConnectionTest.cpp"), File("Network/UnitTest/BOSHConnectionTest.cpp"), File("Network/UnitTest/BOSHConnectionPoolTest.cpp"), File("Parser/PayloadParsers/UnitTest/BlockParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/BodyParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/DiscoInfoParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/DiscoItemsParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/ErrorParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/FormParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/CommandParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/PriorityParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/RawXMLPayloadParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/ResourceBindParserTest.cpp"), |