diff options
Diffstat (limited to 'Swift/Controllers')
83 files changed, 2516 insertions, 233 deletions
diff --git a/Swift/Controllers/AdHocManager.cpp b/Swift/Controllers/AdHocManager.cpp new file mode 100644 index 0000000..e926138 --- /dev/null +++ b/Swift/Controllers/AdHocManager.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2010-2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/AdHocManager.h> + +#include <boost/bind.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Queries/IQRouter.h> +#include <Swiften/AdHoc/OutgoingAdHocCommandSession.h> +#include <Swift/Controllers/UIInterfaces/MainWindow.h> +#include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h> + +namespace Swift { + +AdHocManager::AdHocManager(const JID& jid, AdHocCommandWindowFactory* factory, IQRouter* iqRouter, UIEventStream* uiEventStream, MainWindow* mainWindow) : jid_(jid) { + iqRouter_ = iqRouter; + uiEventStream_ = uiEventStream; + mainWindow_ = mainWindow; + factory_ = factory; + + uiEventStream_->onUIEvent.connect(boost::bind(&AdHocManager::handleUIEvent, this, _1)); +} + +AdHocManager::~AdHocManager() { + uiEventStream_->onUIEvent.disconnect(boost::bind(&AdHocManager::handleUIEvent, this, _1)); +} + +void AdHocManager::setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info) { + if (iqRouter_->isAvailable() && info->hasFeature(DiscoInfo::CommandsFeature)) { + if (discoItemsRequest_) { + discoItemsRequest_->onResponse.disconnect(boost::bind(&AdHocManager::handleServerDiscoItemsResponse, this, _1, _2)); + discoItemsRequest_.reset(); + } + discoItemsRequest_ = GetDiscoItemsRequest::create(JID(jid_.getDomain()), DiscoInfo::CommandsFeature, iqRouter_); + discoItemsRequest_->onResponse.connect(boost::bind(&AdHocManager::handleServerDiscoItemsResponse, this, _1, _2)); + discoItemsRequest_->send(); + } else { + mainWindow_->setAvailableAdHocCommands(std::vector<DiscoItems::Item>()); + } + +} + +void AdHocManager::handleServerDiscoItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error) { + std::vector<DiscoItems::Item> commands; + if (!error) { + foreach (DiscoItems::Item item, items->getItems()) { + if (item.getNode() != "http://isode.com/xmpp/commands#test") { + commands.push_back(item); + } + } + } + mainWindow_->setAvailableAdHocCommands(commands); +} + +void AdHocManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { + boost::shared_ptr<RequestAdHocUIEvent> adHocEvent = boost::dynamic_pointer_cast<RequestAdHocUIEvent>(event); + if (adHocEvent) { + factory_->createAdHocCommandWindow(boost::make_shared<OutgoingAdHocCommandSession>(adHocEvent->getCommand().getJID(), adHocEvent->getCommand().getNode(), iqRouter_)); + } +} + +} diff --git a/Swift/Controllers/AdHocManager.h b/Swift/Controllers/AdHocManager.h new file mode 100644 index 0000000..47b03cd --- /dev/null +++ b/Swift/Controllers/AdHocManager.h @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010-2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <vector> + +#include <Swiften/Base/boost_bsignals.h> +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Elements/DiscoItems.h> +#include <Swiften/Elements/ErrorPayload.h> +#include <Swiften/Disco/GetDiscoItemsRequest.h> +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + class IQRouter; + class MainWindow; + class UIEventStream; + class AdHocCommandWindowFactory; + class AdHocManager { + public: + AdHocManager(const JID& jid, AdHocCommandWindowFactory* factory, IQRouter* iqRouter, UIEventStream* uiEventStream, MainWindow* mainWindow); + ~AdHocManager(); + void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info); + private: + void handleUIEvent(boost::shared_ptr<UIEvent> event); + void handleServerDiscoItemsResponse(boost::shared_ptr<DiscoItems>, ErrorPayload::ref error); + JID jid_; + IQRouter* iqRouter_; + UIEventStream* uiEventStream_; + MainWindow* mainWindow_; + AdHocCommandWindowFactory* factory_; + GetDiscoItemsRequest::ref discoItemsRequest_; + }; +} diff --git a/Swift/Controllers/CertificateMemoryStorageFactory.h b/Swift/Controllers/CertificateMemoryStorageFactory.h new file mode 100644 index 0000000..adbce80 --- /dev/null +++ b/Swift/Controllers/CertificateMemoryStorageFactory.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/Storages/CertificateStorageFactory.h> +#include <Swift/Controllers/Storages/CertificateMemoryStorage.h> + +namespace Swift { + class CertificateFactory; + + class CertificateMemoryStorageFactory : public CertificateStorageFactory { + public: + CertificateMemoryStorageFactory() { + } + + virtual CertificateStorage* createCertificateStorage(const JID&) const { + return new CertificateMemoryStorage(); + } + + private: + boost::filesystem::path basePath; + CertificateFactory* certificateFactory; + }; +} diff --git a/Swift/Controllers/Chat/ChatController.cpp b/Swift/Controllers/Chat/ChatController.cpp index 22ef68d..513b446 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -11,6 +11,7 @@ #include <Swift/Controllers/Intl.h> #include <Swiften/Base/format.h> +#include <Swiften/Base/Algorithm.h> #include "Swiften/Avatars/AvatarManager.h" #include "Swiften/Chat/ChatStateNotifier.h" #include "Swiften/Chat/ChatStateTracker.h" @@ -102,8 +103,8 @@ void ChatController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> me setToJID(from); } } - chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>()); chatStateTracker_->handleMessageReceived(message); + chatStateNotifier_->receivedMessageFromContact(message->getPayload<ChatState>()); } void ChatController::postHandleIncomingMessage(boost::shared_ptr<MessageEvent> messageEvent) { @@ -116,27 +117,33 @@ void ChatController::preSendMessageRequest(boost::shared_ptr<Message> message) { } void ChatController::postSendMessage(const std::string& body, boost::shared_ptr<Stanza> sentStanza) { - std::string id = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), std::string(avatarManager_->getAvatarPath(selfJID_).string()), boost::posix_time::microsec_clock::universal_time()); - if (stanzaChannel_->getStreamManagementEnabled()) { - chatWindow_->setAckState(id, ChatWindow::Pending); - unackedStanzas_[sentStanza] = id; + boost::shared_ptr<Replace> replace = sentStanza->getPayload<Replace>(); + if (replace) { + eraseIf(unackedStanzas_, PairSecondEquals<boost::shared_ptr<Stanza>, std::string>(myLastMessageUIID_)); + chatWindow_->replaceMessage(body, myLastMessageUIID_, boost::posix_time::microsec_clock::universal_time()); + } else { + myLastMessageUIID_ = addMessage(body, QT_TRANSLATE_NOOP("", "me"), true, labelsEnabled_ ? chatWindow_->getSelectedSecurityLabel().getLabel() : boost::shared_ptr<SecurityLabel>(), std::string(avatarManager_->getAvatarPath(selfJID_).string()), boost::posix_time::microsec_clock::universal_time()); + } + if (stanzaChannel_->getStreamManagementEnabled() && !myLastMessageUIID_.empty() ) { + chatWindow_->setAckState(myLastMessageUIID_, ChatWindow::Pending); + unackedStanzas_[sentStanza] = myLastMessageUIID_; } lastWasPresence_ = false; chatStateNotifier_->userSentMessage(); } void ChatController::handleStanzaAcked(boost::shared_ptr<Stanza> stanza) { - std::string id = unackedStanzas_[stanza]; - if (id != "") { - chatWindow_->setAckState(id, ChatWindow::Received); + std::map<boost::shared_ptr<Stanza>, std::string>::iterator unackedStanza = unackedStanzas_.find(stanza); + if (unackedStanza != unackedStanzas_.end()) { + chatWindow_->setAckState(unackedStanza->second, ChatWindow::Received); + unackedStanzas_.erase(unackedStanza); } - unackedStanzas_.erase(unackedStanzas_.find(stanza)); } void ChatController::setOnline(bool online) { if (!online) { std::map<boost::shared_ptr<Stanza>, std::string>::iterator it = unackedStanzas_.begin(); - for ( ; it != unackedStanzas_.end(); it++) { + for ( ; it != unackedStanzas_.end(); ++it) { chatWindow_->setAckState(it->second, ChatWindow::Failed); } unackedStanzas_.clear(); diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index dd4bf90..4fafb44 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -40,6 +40,7 @@ namespace Swift { NickResolver* nickResolver_; ChatStateNotifier* chatStateNotifier_; ChatStateTracker* chatStateTracker_; + std::string myLastMessageUIID_; bool isInMUC_; bool lastWasPresence_; std::string lastStatusChangeString_; diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index 281d968..5e3c45f 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.cpp +++ b/Swift/Controllers/Chat/ChatControllerBase.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -7,6 +7,7 @@ #include "Swift/Controllers/Chat/ChatControllerBase.h" #include <sstream> +#include <map> #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> @@ -30,7 +31,7 @@ namespace Swift { ChatControllerBase::ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory) { chatWindow_ = chatWindowFactory_->createChatWindow(toJID, eventStream); chatWindow_->onAllMessagesRead.connect(boost::bind(&ChatControllerBase::handleAllMessagesRead, this)); - chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1)); + chatWindow_->onSendMessageRequest.connect(boost::bind(&ChatControllerBase::handleSendMessageRequest, this, _1, _2)); setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable()); createDayChangeTimer(); } @@ -86,9 +87,14 @@ void ChatControllerBase::handleAllMessagesRead() { } unreadMessages_.clear(); chatWindow_->setUnreadMessageCount(0); + onUnreadCountChanged(); } -void ChatControllerBase::handleSendMessageRequest(const std::string &body) { +int ChatControllerBase::getUnreadCount() { + return unreadMessages_.size(); +} + +void ChatControllerBase::handleSendMessageRequest(const std::string &body, bool isCorrectionMessage) { if (!stanzaChannel_->isAvailable() || body.empty()) { return; } @@ -104,8 +110,13 @@ void ChatControllerBase::handleSendMessageRequest(const std::string &body) { boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); message->addPayload(boost::shared_ptr<Delay>(new Delay(now, selfJID_))); } + if (isCorrectionMessage) { + message->addPayload(boost::shared_ptr<Replace> (new Replace(lastSentMessageStanzaID_))); + } + message->setID(lastSentMessageStanzaID_ = idGenerator_.generateID()); stanzaChannel_->sendMessage(message); postSendMessage(message->getBody(), boost::dynamic_pointer_cast<Stanza>(message)); + onActivity(message->getBody()); } void ChatControllerBase::handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog> catalog, ErrorPayload::ref error) { @@ -152,7 +163,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m boost::shared_ptr<Message> message = messageEvent->getStanza(); std::string body = message->getBody(); if (message->isError()) { - std::string errorMessage = getErrorMessage(message->getPayload<ErrorPayload>()); + std::string errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't send message: %1%")) % getErrorMessage(message->getPayload<ErrorPayload>())); chatWindow_->addErrorMessage(errorMessage); } else { @@ -163,7 +174,7 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m JID from = message->getFrom(); std::vector<boost::shared_ptr<Delay> > delayPayloads = message->getPayloads<Delay>(); for (size_t i = 0; useDelayForLatency_ && i < delayPayloads.size(); i++) { - if (!delayPayloads[i]->getFrom()) { + if (!delayPayloads[i]->getFrom()) { continue; } boost::posix_time::ptime now = boost::posix_time::microsec_clock::universal_time(); @@ -179,11 +190,25 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m if (messageTimeStamp) { timeStamp = *messageTimeStamp; } - - addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp); + onActivity(body); + + boost::shared_ptr<Replace> replace = message->getPayload<Replace>(); + if (replace) { + std::string body = message->getBody(); + // Should check if the user has a previous message + std::map<JID, std::string>::iterator lastMessage; + lastMessage = lastMessagesUIID_.find(from); + if (lastMessage != lastMessagesUIID_.end()) { + chatWindow_->replaceMessage(body, lastMessagesUIID_[from], timeStamp); + } + } + else { + lastMessagesUIID_[from] = addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp); + } } chatWindow_->show(); chatWindow_->setUnreadMessageCount(unreadMessages_.size()); + onUnreadCountChanged(); postHandleIncomingMessage(messageEvent); } diff --git a/Swift/Controllers/Chat/ChatControllerBase.h b/Swift/Controllers/Chat/ChatControllerBase.h index 9573b1b..86c1ef2 100644 --- a/Swift/Controllers/Chat/ChatControllerBase.h +++ b/Swift/Controllers/Chat/ChatControllerBase.h @@ -4,8 +4,7 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#ifndef SWIFTEN_ChatControllerBase_H -#define SWIFTEN_ChatControllerBase_H +#pragma once #include <map> #include <vector> @@ -26,6 +25,7 @@ #include "Swiften/Elements/ErrorPayload.h" #include "Swiften/Presence/PresenceOracle.h" #include "Swiften/Queries/IQRouter.h" +#include "Swiften/Base/IDGenerator.h" namespace Swift { class IQRouter; @@ -47,6 +47,11 @@ namespace Swift { virtual void setOnline(bool online); virtual void setEnabled(bool enabled); virtual void setToJID(const JID& jid) {toJID_ = jid;}; + /** Used for determining when something is recent.*/ + boost::signal<void (const std::string& /*activity*/)> onActivity; + boost::signal<void ()> onUnreadCountChanged; + int getUnreadCount(); + const JID& getToJID() {return toJID_;} protected: ChatControllerBase(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &toJID, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory); @@ -64,8 +69,10 @@ namespace Swift { virtual void dayTicked() {}; private: + IDGenerator idGenerator_; + std::string lastSentMessageStanzaID_; void createDayChangeTimer(); - void handleSendMessageRequest(const std::string &body); + void handleSendMessageRequest(const std::string &body, bool isCorrectionMessage); void handleAllMessagesRead(); void handleSecurityLabelsCatalogResponse(boost::shared_ptr<SecurityLabelsCatalog>, ErrorPayload::ref error); std::string getErrorMessage(boost::shared_ptr<ErrorPayload>); @@ -80,6 +87,7 @@ namespace Swift { ChatWindow* chatWindow_; JID toJID_; bool labelsEnabled_; + std::map<JID, std::string> lastMessagesUIID_; PresenceOracle* presenceOracle_; AvatarManager* avatarManager_; bool useDelayForLatency_; @@ -88,5 +96,3 @@ namespace Swift { TimerFactory* timerFactory_; }; } - -#endif diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index 94d4b9a..5c0475d 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -7,31 +7,38 @@ #include "Swift/Controllers/Chat/ChatsManager.h" #include <boost/bind.hpp> - -#include "Swift/Controllers/Chat/ChatController.h" -#include "Swift/Controllers/Chat/MUCSearchController.h" -#include "Swift/Controllers/XMPPEvents/EventController.h" -#include "Swift/Controllers/Chat/MUCController.h" -#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" -#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" -#include "Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h" -#include "Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h" -#include "Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h" -#include "Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h" -#include "Swift/Controllers/UIInterfaces/ChatListWindowFactory.h" -#include "Swift/Controllers/UIInterfaces/JoinMUCWindow.h" -#include "Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h" -#include "Swiften/Presence/PresenceSender.h" -#include "Swiften/Client/NickResolver.h" -#include "Swiften/MUC/MUCManager.h" -#include "Swiften/Elements/ChatState.h" -#include "Swiften/MUC/MUCBookmarkManager.h" +#include <boost/algorithm/string.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swift/Controllers/Chat/ChatController.h> +#include <Swift/Controllers/Chat/ChatControllerBase.h> +#include <Swift/Controllers/Chat/MUCSearchController.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/Chat/MUCController.h> +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h> +#include <Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h> +#include <Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h> +#include <Swift/Controllers/UIInterfaces/ChatListWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h> +#include <Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h> +#include <Swiften/Presence/PresenceSender.h> +#include <Swiften/Client/NickResolver.h> +#include <Swiften/MUC/MUCManager.h> +#include <Swiften/Elements/ChatState.h> +#include <Swiften/MUC/MUCBookmarkManager.h> +#include <Swift/Controllers/ProfileSettingsProvider.h> +#include <Swiften/Avatars/AvatarManager.h> namespace Swift { typedef std::pair<JID, ChatController*> JIDChatControllerPair; typedef std::pair<JID, MUCController*> JIDMUCControllerPair; +#define RECENT_CHATS "recent_chats" + ChatsManager::ChatsManager( JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, @@ -49,7 +56,7 @@ ChatsManager::ChatsManager( EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, - SettingsProvider* settings) : + ProfileSettingsProvider* settings) : jid_(jid), joinMUCWindowFactory_(joinMUCWindowFactory), useDelayForLatency_(useDelayForLatency), @@ -68,13 +75,19 @@ ChatsManager::ChatsManager( presenceSender_ = presenceSender; uiEventStream_ = uiEventStream; mucBookmarkManager_ = NULL; + profileSettings_ = settings; presenceOracle_->onPresenceChange.connect(boost::bind(&ChatsManager::handlePresenceChange, this, _1)); uiEventConnection_ = uiEventStream_->onUIEvent.connect(boost::bind(&ChatsManager::handleUIEvent, this, _1)); + chatListWindow_ = chatListWindowFactory->createChatListWindow(uiEventStream_); + chatListWindow_->onMUCBookmarkActivated.connect(boost::bind(&ChatsManager::handleMUCBookmarkActivated, this, _1)); + chatListWindow_->onRecentActivated.connect(boost::bind(&ChatsManager::handleRecentActivated, this, _1)); + joinMUCWindow_ = NULL; mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, settings); mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1)); setupBookmarks(); + loadRecents(); } ChatsManager::~ChatsManager() { @@ -89,6 +102,62 @@ ChatsManager::~ChatsManager() { delete mucSearchController_; } +void ChatsManager::saveRecents() { + std::string recents; + int i = 1; + foreach (ChatListWindow::Chat chat, recentChats_) { + std::vector<std::string> activity; + boost::split(activity, chat.activity, boost::is_any_of("\t\n")); + std::string recent = chat.jid.toString() + "\t" + activity[0] + "\t" + (chat.isMUC ? "true" : "false") + "\t" + chat.nick; + recents += recent + "\n"; + if (i++ > 25) { + break; + } + } + profileSettings_->storeString(RECENT_CHATS, recents); +} + +void ChatsManager::loadRecents() { + std::string recentsString(profileSettings_->getStringSetting(RECENT_CHATS)); + std::vector<std::string> recents; + boost::split(recents, recentsString, boost::is_any_of("\n")); + int i = 0; + foreach (std::string recentString, recents) { + if (i++ > 30) { + break; + } + std::vector<std::string> recent; + boost::split(recent, recentString, boost::is_any_of("\t")); + if (recent.size() < 4) { + continue; + } + JID jid(recent[0]); + if (!jid.isValid()) { + continue; + } + std::string activity(recent[1]); + bool isMUC = recent[2] == "true"; + std::string nick(recent[3]); + StatusShow::Type type = StatusShow::None; + boost::filesystem::path path; + if (isMUC) { + if (mucControllers_.find(jid.toBare()) != mucControllers_.end()) { + type = StatusShow::Online; + } + } else { + if (avatarManager_) { + path = avatarManager_->getAvatarPath(jid); + } + Presence::ref presence = presenceOracle_->getHighestPriorityPresence(jid.toBare()); + type = presence ? presence->getShow() : StatusShow::None; + } + + ChatListWindow::Chat chat(jid, nickResolver_->jidToNick(jid), activity, 0, type, path, isMUC, nick); + prependRecent(chat); + } + handleUnreadCountChanged(NULL); +} + void ChatsManager::setupBookmarks() { if (!mucBookmarkManager_) { mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_); @@ -98,7 +167,7 @@ void ChatsManager::setupBookmarks() { if (chatListWindow_) { chatListWindow_->setBookmarksEnabled(false); - chatListWindow_->clear(); + chatListWindow_->clearBookmarks(); } } } @@ -122,10 +191,88 @@ void ChatsManager::handleMUCBookmarkRemoved(const MUCBookmark& bookmark) { chatListWindow_->removeMUCBookmark(bookmark); } +ChatListWindow::Chat ChatsManager::createChatListChatItem(const JID& jid, const std::string& activity) { + int unreadCount = 0; + if (mucRegistry_->isMUC(jid)) { + MUCController* controller = mucControllers_[jid.toBare()]; + StatusShow::Type type = StatusShow::None; + std::string nick = ""; + if (controller) { + unreadCount = controller->getUnreadCount(); + if (controller->isJoined()) { + type = StatusShow::Online; + } + nick = controller->getNick(); + } + return ChatListWindow::Chat(jid, jid.toString(), activity, unreadCount, type, boost::filesystem::path(), true, nick); + + } else { + ChatController* controller = getChatControllerIfExists(jid, false); + if (controller) { + unreadCount = controller->getUnreadCount(); + } + JID bareishJID = mucRegistry_->isMUC(jid.toBare()) ? jid : jid.toBare(); + Presence::ref presence = presenceOracle_->getHighestPriorityPresence(bareishJID); + StatusShow::Type type = presence ? presence->getShow() : StatusShow::None; + boost::filesystem::path avatarPath = avatarManager_ ? avatarManager_->getAvatarPath(bareishJID) : boost::filesystem::path(); + return ChatListWindow::Chat(bareishJID, nickResolver_->jidToNick(bareishJID), activity, unreadCount, type, avatarPath, false); + } +} + +void ChatsManager::handleChatActivity(const JID& jid, const std::string& activity, bool isMUC) { + if (mucRegistry_->isMUC(jid.toBare()) && !isMUC) { + /* Don't include PMs in MUC rooms.*/ + return; + } + ChatListWindow::Chat chat = createChatListChatItem(jid, activity); + /* FIXME: handle nick changes */ + appendRecent(chat); + handleUnreadCountChanged(NULL); + saveRecents(); +} + +void ChatsManager::handleUnreadCountChanged(ChatControllerBase* controller) { + int unreadTotal = 0; + bool controllerIsMUC = dynamic_cast<MUCController*>(controller); + bool isPM = controller && !controllerIsMUC && mucRegistry_->isMUC(controller->getToJID().toBare()); + foreach (ChatListWindow::Chat& chatItem, recentChats_) { + bool match = false; + if (controller) { + /* Matching MUC item */ + match |= chatItem.isMUC == controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare(); + /* Matching PM */ + match |= isPM && chatItem.jid == controller->getToJID(); + /* Matching non-PM */ + match |= !isPM && !controllerIsMUC && chatItem.jid.toBare() == controller->getToJID().toBare(); + } + if (match) { + chatItem.setUnreadCount(controller->getUnreadCount()); + } + unreadTotal += chatItem.unreadCount; + } + chatListWindow_->setRecents(recentChats_); + chatListWindow_->setUnreadCount(unreadTotal); +} + +void ChatsManager::appendRecent(const ChatListWindow::Chat& chat) { + recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end()); + recentChats_.push_front(chat); +} + +void ChatsManager::prependRecent(const ChatListWindow::Chat& chat) { + recentChats_.erase(std::remove(recentChats_.begin(), recentChats_.end(), chat), recentChats_.end()); + recentChats_.push_back(chat); +} + void ChatsManager::handleUserLeftMUC(MUCController* mucController) { std::map<JID, MUCController*>::iterator it; - for (it = mucControllers_.begin(); it != mucControllers_.end(); it++) { + for (it = mucControllers_.begin(); it != mucControllers_.end(); ++it) { if ((*it).second == mucController) { + foreach (ChatListWindow::Chat& chat, recentChats_) { + if (chat.isMUC && chat.jid == (*it).first) { + chat.statusType = StatusShow::None; + } + } mucControllers_.erase(it); delete mucController; return; @@ -156,14 +303,15 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { } else if (JoinMUCUIEvent::ref joinEvent = boost::dynamic_pointer_cast<JoinMUCUIEvent>(event)) { handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getNick(), false); + mucControllers_[joinEvent->getJID()]->activateChatWindow(); } - else if (boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) { + else if (boost::shared_ptr<RequestJoinMUCUIEvent> joinEvent = boost::dynamic_pointer_cast<RequestJoinMUCUIEvent>(event)) { if (!joinMUCWindow_) { joinMUCWindow_ = joinMUCWindowFactory_->createJoinMUCWindow(); joinMUCWindow_->onJoinMUC.connect(boost::bind(&ChatsManager::handleJoinMUCRequest, this, _1, _2, _3)); joinMUCWindow_->onSearchMUC.connect(boost::bind(&ChatsManager::handleSearchMUCRequest, this)); } - joinMUCWindow_->setMUC(""); + joinMUCWindow_->setMUC(joinEvent->getRoom()); joinMUCWindow_->setNick(nickResolver_->jidToNick(jid_)); joinMUCWindow_->show(); } @@ -174,6 +322,16 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { */ void ChatsManager::handlePresenceChange(boost::shared_ptr<Presence> newPresence) { if (mucRegistry_->isMUC(newPresence->getFrom().toBare())) return; + + foreach (ChatListWindow::Chat& chat, recentChats_) { + if (newPresence->getFrom().toBare() == chat.jid.toBare()) { + Presence::ref presence = presenceOracle_->getHighestPriorityPresence(chat.jid.toBare()); + chat.setStatusType(presence ? presence->getShow() : StatusShow::None); + chatListWindow_->setRecents(recentChats_); + break; + } + } + //if (newPresence->getType() != Presence::Unavailable) return; JID fullJID(newPresence->getFrom()); std::map<JID, ChatController*>::iterator it = chatControllers_.find(fullJID); @@ -185,7 +343,25 @@ void ChatsManager::handlePresenceChange(boost::shared_ptr<Presence> newPresence) } void ChatsManager::setAvatarManager(AvatarManager* avatarManager) { + if (avatarManager_) { + avatarManager_->onAvatarChanged.disconnect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1)); + } avatarManager_ = avatarManager; + foreach (ChatListWindow::Chat& chat, recentChats_) { + if (!chat.isMUC) { + chat.setAvatarPath(avatarManager_->getAvatarPath(chat.jid)); + } + } + avatarManager_->onAvatarChanged.connect(boost::bind(&ChatsManager::handleAvatarChanged, this, _1)); +} + +void ChatsManager::handleAvatarChanged(const JID& jid) { + foreach (ChatListWindow::Chat& chat, recentChats_) { + if (!chat.isMUC && jid.toBare() == chat.jid.toBare()) { + chat.setAvatarPath(avatarManager_->getAvatarPath(jid)); + break; + } + } } void ChatsManager::setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info) { @@ -244,6 +420,8 @@ ChatController* ChatsManager::createNewChatController(const JID& contact) { ChatController* controller = new ChatController(jid_, stanzaChannel_, iqRouter_, chatWindowFactory_, contact, nickResolver_, presenceOracle_, avatarManager_, mucRegistry_->isMUC(contact.toBare()), useDelayForLatency_, uiEventStream_, eventController_, timerFactory_, entityCapsProvider_); chatControllers_[contact] = controller; controller->setAvailableServerFeatures(serverDiscoInfo_); + controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, contact, _1, false)); + controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller)); return controller; } @@ -252,7 +430,7 @@ ChatController* ChatsManager::getChatControllerOrCreate(const JID &contact) { return controller ? controller : createNewChatController(contact); } -ChatController* ChatsManager::getChatControllerIfExists(const JID &contact) { +ChatController* ChatsManager::getChatControllerIfExists(const JID &contact, bool rebindIfNeeded) { if (chatControllers_.find(contact) == chatControllers_.end()) { if (mucRegistry_->isMUC(contact.toBare())) { return NULL; @@ -264,8 +442,13 @@ ChatController* ChatsManager::getChatControllerIfExists(const JID &contact) { } else { foreach (JIDChatControllerPair pair, chatControllers_) { if (pair.first.toBare() == contact.toBare()) { - rebindControllerJID(pair.first, contact); - return chatControllers_[contact]; + if (rebindIfNeeded) { + rebindControllerJID(pair.first, contact); + return chatControllers_[contact]; + } else { + return pair.second; + } + } } return NULL; @@ -280,8 +463,8 @@ void ChatsManager::rebindControllerJID(const JID& from, const JID& to) { chatControllers_[to]->setToJID(to); } -void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& nickMaybe, bool autoJoin) { - if (autoJoin) { +void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional<std::string>& nickMaybe, bool addAutoJoin) { + if (addAutoJoin) { MUCBookmark bookmark(mucJID, mucJID.getNode()); bookmark.setAutojoin(true); if (nickMaybe) { @@ -300,8 +483,13 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional mucControllers_[mucJID] = controller; controller->setAvailableServerFeatures(serverDiscoInfo_); controller->onUserLeft.connect(boost::bind(&ChatsManager::handleUserLeftMUC, this, controller)); + controller->onUserJoined.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), "", true)); + controller->onActivity.connect(boost::bind(&ChatsManager::handleChatActivity, this, mucJID.toBare(), _1, true)); + controller->onUnreadCountChanged.connect(boost::bind(&ChatsManager::handleUnreadCountChanged, this, controller)); + handleChatActivity(mucJID.toBare(), "", true); } - mucControllers_[mucJID]->activateChatWindow(); + + mucControllers_[mucJID]->showChatWindow(); } void ChatsManager::handleSearchMUCRequest() { @@ -338,5 +526,18 @@ void ChatsManager::handleMUCSelectedAfterSearch(const JID& muc) { } } +void ChatsManager::handleMUCBookmarkActivated(const MUCBookmark& mucBookmark) { + uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(mucBookmark.getRoom(), mucBookmark.getNick())); +} + +void ChatsManager::handleRecentActivated(const ChatListWindow::Chat& chat) { + if (chat.isMUC) { + uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(chat.jid, chat.nick)); + } + else { + uiEventStream_->send(boost::make_shared<RequestChatUIEvent>(chat.jid)); + } +} + } diff --git a/Swift/Controllers/Chat/ChatsManager.h b/Swift/Controllers/Chat/ChatsManager.h index 3740186..b4db523 100644 --- a/Swift/Controllers/Chat/ChatsManager.h +++ b/Swift/Controllers/Chat/ChatsManager.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -11,17 +11,19 @@ #include <boost/shared_ptr.hpp> #include <string> -#include "Swiften/Elements/DiscoInfo.h" -#include "Swiften/Elements/Message.h" -#include "Swiften/Elements/Presence.h" -#include "Swiften/JID/JID.h" -#include "Swiften/MUC/MUCRegistry.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swiften/MUC/MUCBookmark.h" +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Elements/Message.h> +#include <Swiften/Elements/Presence.h> +#include <Swiften/JID/JID.h> +#include <Swiften/MUC/MUCRegistry.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIInterfaces/ChatListWindow.h> +#include <Swiften/MUC/MUCBookmark.h> namespace Swift { class EventController; class ChatController; + class ChatControllerBase; class MUCController; class MUCManager; class ChatWindowFactory; @@ -34,26 +36,27 @@ namespace Swift { class IQRouter; class PresenceSender; class MUCBookmarkManager; - class ChatListWindow; class ChatListWindowFactory; class TimerFactory; class EntityCapsProvider; class DirectedPresenceSender; class MUCSearchWindowFactory; - class SettingsProvider; + class ProfileSettingsProvider; class MUCSearchController; class ChatsManager { public: - ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, SettingsProvider* settings); + ChatsManager(JID jid, StanzaChannel* stanzaChannel, IQRouter* iqRouter, EventController* eventController, ChatWindowFactory* chatWindowFactory, JoinMUCWindowFactory* joinMUCWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, UIEventStream* uiEventStream, ChatListWindowFactory* chatListWindowFactory, bool useDelayForLatency, TimerFactory* timerFactory, MUCRegistry* mucRegistry, EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, ProfileSettingsProvider* settings); virtual ~ChatsManager(); void setAvatarManager(AvatarManager* avatarManager); void setOnline(bool enabled); void setServerDiscoInfo(boost::shared_ptr<DiscoInfo> info); void handleIncomingMessage(boost::shared_ptr<Message> message); + private: + ChatListWindow::Chat createChatListChatItem(const JID& jid, const std::string& activity); void handleChatRequest(const std::string& contact); - void handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& nick, bool autoJoin); + void handleJoinMUCRequest(const JID& muc, const boost::optional<std::string>& nick, bool addAutoJoin); void handleSearchMUCRequest(); void handleMUCSelectedAfterSearch(const JID&); void rebindControllerJID(const JID& from, const JID& to); @@ -63,11 +66,24 @@ namespace Swift { void handleMUCBookmarkRemoved(const MUCBookmark& bookmark); void handleUserLeftMUC(MUCController* mucController); void handleBookmarksReady(); + void handleChatActivity(const JID& jid, const std::string& activity, bool isMUC); + void appendRecent(const ChatListWindow::Chat& chat); + void prependRecent(const ChatListWindow::Chat& chat); void setupBookmarks(); + void loadRecents(); + void saveRecents(); + void handleChatMadeRecent(); + void handleMUCBookmarkActivated(const MUCBookmark&); + void handleRecentActivated(const ChatListWindow::Chat&); + void handleUnreadCountChanged(ChatControllerBase* controller); + void handleAvatarChanged(const JID& jid); + ChatController* getChatControllerOrFindAnother(const JID &contact); ChatController* createNewChatController(const JID &contact); ChatController* getChatControllerOrCreate(const JID &contact); - ChatController* getChatControllerIfExists(const JID &contact); + ChatController* getChatControllerIfExists(const JID &contact, bool rebindIfNeeded = true); + + private: std::map<JID, MUCController*> mucControllers_; std::map<JID, ChatController*> chatControllers_; EventController* eventController_; @@ -92,5 +108,7 @@ namespace Swift { EntityCapsProvider* entityCapsProvider_; MUCManager* mucManager; MUCSearchController* mucSearchController_; + std::list<ChatListWindow::Chat> recentChats_; + ProfileSettingsProvider* profileSettings_; }; } diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index 2914116..93ceb13 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -4,7 +4,7 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swift/Controllers/Chat/MUCController.h" +#include <Swift/Controllers/Chat/MUCController.h> #include <boost/bind.hpp> #include <boost/regex.hpp> @@ -12,23 +12,24 @@ #include <Swift/Controllers/Intl.h> #include <Swiften/Base/format.h> -#include "Swiften/Network/Timer.h" -#include "Swiften/Network/TimerFactory.h" -#include "Swiften/Base/foreach.h" -#include "SwifTools/TabComplete.h" -#include "Swiften/Base/foreach.h" -#include "Swift/Controllers/XMPPEvents/EventController.h" -#include "Swift/Controllers/UIInterfaces/ChatWindow.h" -#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" -#include "Swiften/Avatars/AvatarManager.h" -#include "Swiften/Elements/Delay.h" -#include "Swiften/MUC/MUC.h" -#include "Swiften/Client/StanzaChannel.h" -#include "Swift/Controllers/Roster/Roster.h" -#include "Swift/Controllers/Roster/SetAvatar.h" -#include "Swift/Controllers/Roster/SetPresence.h" +#include <Swiften/Network/Timer.h> +#include <Swiften/Network/TimerFactory.h> +#include <Swiften/Base/foreach.h> +#include <SwifTools/TabComplete.h> +#include <Swiften/Base/foreach.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swiften/Avatars/AvatarManager.h> +#include <Swiften/Elements/Delay.h> +#include <Swiften/MUC/MUC.h> +#include <Swiften/Client/StanzaChannel.h> +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/SetAvatar.h> +#include <Swift/Controllers/Roster/SetPresence.h> #define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000 @@ -109,6 +110,14 @@ void MUCController::rejoin() { } } +bool MUCController::isJoined() { + return joined_; +} + +const std::string& MUCController::getNick() { + return nick_; +} + void MUCController::handleJoinTimeoutTick() { receivedActivity(); chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "Room %1% is not responding. This operation may never complete.")) % toJID_.toString())); @@ -158,7 +167,7 @@ void MUCController::handleJoinFailed(boost::shared_ptr<ErrorPayload> error) { default: break; } } - errorMessage += "."; + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Couldn't join room: %1%.")) % errorMessage); chatWindow_->addErrorMessage(errorMessage); if (!rejoinNick.empty()) { nick_ = rejoinNick; @@ -176,6 +185,7 @@ void MUCController::handleJoinComplete(const std::string& nick) { clearPresenceQueue(); shouldJoinOnReconnect_ = true; setEnabled(true); + onUserJoined(); } void MUCController::handleAvatarChanged(const JID& jid) { @@ -206,7 +216,9 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) { currentOccupants_.insert(occupant.getNick()); NickJoinPart event(occupant.getNick(), Join); appendToJoinParts(joinParts_, event); - roster_->addContact(jid, realJID, occupant.getNick(), roleToGroupName(occupant.getRole()), avatarManager_->getAvatarPath(jid).string()); + std::string groupName(roleToGroupName(occupant.getRole())); + roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid).string()); + roster_->getGroup(groupName)->setManualSort(roleToSortName(occupant.getRole())); if (joined_) { std::string joinString; MUCOccupant::Role role = occupant.getRole(); @@ -248,6 +260,16 @@ std::string MUCController::roleToFriendlyName(MUCOccupant::Role role) { return ""; } +std::string MUCController::roleToSortName(MUCOccupant::Role role) { + switch (role) { + case MUCOccupant::Moderator: return "1"; + case MUCOccupant::Participant: return "2"; + case MUCOccupant::Visitor: return "3"; + case MUCOccupant::NoRole: return "4"; + } + return "5"; +} + JID MUCController::nickToJID(const std::string& nick) { return JID(toJID_.getNode(), toJID_.getDomain(), nick); } @@ -309,7 +331,9 @@ void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUC if (occupant.getRealJID()) { realJID = occupant.getRealJID().get(); } - roster_->addContact(jid, realJID, nick, roleToGroupName(occupant.getRole()), avatarManager_->getAvatarPath(jid).string()); + std::string group(roleToGroupName(occupant.getRole())); + roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid).string()); + roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole())); chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))); } @@ -411,7 +435,7 @@ void MUCController::updateJoinParts() { void MUCController::appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent) { std::vector<NickJoinPart>::iterator it = joinParts.begin(); bool matched = false; - for (; it != joinParts.end(); it++) { + for (; it != joinParts.end(); ++it) { if ((*it).nick == newEvent.nick) { matched = true; JoinPart type = (*it).type; diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index ebdd6cd..c6901df 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -7,18 +7,18 @@ #pragma once #include <boost/shared_ptr.hpp> -#include "Swiften/Base/boost_bsignals.h" +#include <Swiften/Base/boost_bsignals.h> #include <boost/signals/connection.hpp> -#include <set> +#include <set> #include <string> -#include "Swiften/Network/Timer.h" -#include "Swift/Controllers/Chat/ChatControllerBase.h" -#include "Swiften/Elements/Message.h" -#include "Swiften/Elements/DiscoInfo.h" -#include "Swiften/JID/JID.h" -#include "Swiften/MUC/MUC.h" -#include "Swiften/Elements/MUCOccupant.h" +#include <Swiften/Network/Timer.h> +#include <Swift/Controllers/Chat/ChatControllerBase.h> +#include <Swiften/Elements/Message.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/JID/JID.h> +#include <Swiften/MUC/MUC.h> +#include <Swiften/Elements/MUCOccupant.h> namespace Swift { class StanzaChannel; @@ -44,11 +44,14 @@ namespace Swift { MUCController(const JID& self, MUC::ref muc, const std::string &nick, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, PresenceOracle* presenceOracle, AvatarManager* avatarManager, UIEventStream* events, bool useDelayForLatency, TimerFactory* timerFactory, EventController* eventController); ~MUCController(); boost::signal<void ()> onUserLeft; + boost::signal<void ()> onUserJoined; virtual void setOnline(bool online); void rejoin(); static void appendToJoinParts(std::vector<NickJoinPart>& joinParts, const NickJoinPart& newEvent); static std::string generateJoinPartString(const std::vector<NickJoinPart>& joinParts); static std::string concatenateListOfNames(const std::vector<NickJoinPart>& joinParts); + bool isJoined(); + const std::string& getNick(); protected: void preSendMessageRequest(boost::shared_ptr<Message> message); @@ -71,6 +74,7 @@ namespace Swift { void handleJoinFailed(boost::shared_ptr<ErrorPayload> error); void handleJoinTimeoutTick(); std::string roleToGroupName(MUCOccupant::Role role); + std::string roleToSortName(MUCOccupant::Role role); JID nickToJID(const std::string& nick); std::string roleToFriendlyName(MUCOccupant::Role role); void receivedActivity(); diff --git a/Swift/Controllers/Chat/MUCSearchController.cpp b/Swift/Controllers/Chat/MUCSearchController.cpp index 743aabb..2cb89b4 100644 --- a/Swift/Controllers/Chat/MUCSearchController.cpp +++ b/Swift/Controllers/Chat/MUCSearchController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -11,6 +11,7 @@ #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> +#include <Swiften/Base/foreach.h> #include <Swiften/Disco/GetDiscoItemsRequest.h> #include <Swiften/Base/Log.h> #include <Swiften/Base/String.h> @@ -23,7 +24,7 @@ namespace Swift { static const std::string SEARCHED_SERVICES = "searchedServices"; -MUCSearchController::MUCSearchController(const JID& jid, MUCSearchWindowFactory* factory, IQRouter* iqRouter, SettingsProvider* settings) : jid_(jid), factory_(factory), iqRouter_(iqRouter), settings_(settings), window_(NULL), walker_(NULL) { +MUCSearchController::MUCSearchController(const JID& jid, MUCSearchWindowFactory* factory, IQRouter* iqRouter, ProfileSettingsProvider* settings) : jid_(jid), factory_(factory), iqRouter_(iqRouter), settings_(settings), window_(NULL), walker_(NULL) { itemsInProgress_ = 0; loadSavedServices(); } diff --git a/Swift/Controllers/Chat/MUCSearchController.h b/Swift/Controllers/Chat/MUCSearchController.h index c8040ed..f90e4a7 100644 --- a/Swift/Controllers/Chat/MUCSearchController.h +++ b/Swift/Controllers/Chat/MUCSearchController.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -16,7 +16,7 @@ #include "Swiften/JID/JID.h" #include "Swift/Controllers/UIEvents/UIEvent.h" -#include "Swift/Controllers/Settings/SettingsProvider.h" +#include "Swift/Controllers/ProfileSettingsProvider.h" #include "Swiften/Elements/DiscoInfo.h" #include "Swiften/Elements/DiscoItems.h" #include "Swiften/Elements/ErrorPayload.h" @@ -87,7 +87,7 @@ namespace Swift { class MUCSearchController { public: - MUCSearchController(const JID& jid, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, SettingsProvider* settings); + MUCSearchController(const JID& jid, MUCSearchWindowFactory* mucSearchWindowFactory, IQRouter* iqRouter, ProfileSettingsProvider* settings); ~MUCSearchController(); void openSearchWindow(); @@ -112,7 +112,7 @@ namespace Swift { JID jid_; MUCSearchWindowFactory* factory_; IQRouter* iqRouter_; - SettingsProvider* settings_; + ProfileSettingsProvider* settings_; MUCSearchWindow* window_; DiscoServiceWalker* walker_; std::list<JID> services_; diff --git a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp index 40f7445..b8cb368 100644 --- a/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/ChatsManagerTest.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -10,6 +10,7 @@ #include "Swift/Controllers/Chat/ChatsManager.h" +#include "Swift/Controllers/Chat/UnitTest/MockChatListWindow.h" #include "Swift/Controllers/UIInterfaces/ChatWindow.h" #include "Swift/Controllers/Settings/DummySettingsProvider.h" #include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h" @@ -38,6 +39,7 @@ #include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" #include "Swift/Controllers/UIEvents/UIEventStream.h" +#include <Swift/Controllers/ProfileSettingsProvider.h> using namespace Swift; @@ -59,7 +61,7 @@ class ChatsManagerTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE_END(); public: - void setUp() { + void setUp() { mocks_ = new MockRepository(); jid_ = JID("test@test.com/resource"); stanzaChannel_ = new DummyStanzaChannel(); @@ -82,8 +84,10 @@ public: chatListWindowFactory_ = mocks_->InterfaceMock<ChatListWindowFactory>(); mucSearchWindowFactory_ = mocks_->InterfaceMock<MUCSearchWindowFactory>(); settings_ = new DummySettingsProvider(); - mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(NULL); - manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, settings_); + profileSettings_ = new ProfileSettingsProvider("a", settings_); + chatListWindow_ = new MockChatListWindow(); + mocks_->ExpectCall(chatListWindowFactory_, ChatListWindowFactory::createChatListWindow).With(uiEventStream_).Return(chatListWindow_); + manager_ = new ChatsManager(jid_, stanzaChannel_, iqRouter_, eventController_, chatWindowFactory_, joinMUCWindowFactory_, nickResolver_, presenceOracle_, directedPresenceSender_, uiEventStream_, chatListWindowFactory_, true, NULL, mucRegistry_, entityCapsManager_, mucManager_, mucSearchWindowFactory_, profileSettings_); avatarManager_ = new NullAvatarManager(); manager_->setAvatarManager(avatarManager_); @@ -92,7 +96,7 @@ public: void tearDown() { //delete chatListWindowFactory_; delete settings_; - delete mocks_; + delete profileSettings_; delete avatarManager_; delete manager_; delete directedPresenceSender_; @@ -109,6 +113,8 @@ public: delete xmppRoster_; delete entityCapsManager_; delete capsProvider_; + delete chatListWindow_; + delete mocks_; } void testFirstOpenWindowIncoming() { @@ -346,6 +352,8 @@ private: CapsProvider* capsProvider_; MUCManager* mucManager_; DummySettingsProvider* settings_; + ProfileSettingsProvider* profileSettings_; + ChatListWindow* chatListWindow_; }; CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest); diff --git a/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h new file mode 100644 index 0000000..6ac8d4a --- /dev/null +++ b/Swift/Controllers/Chat/UnitTest/MockChatListWindow.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include "Swift/Controllers/UIInterfaces/ChatListWindow.h" + +namespace Swift { + + class MockChatListWindow : public ChatListWindow { + public: + MockChatListWindow() {}; + virtual ~MockChatListWindow() {}; + void addMUCBookmark(const MUCBookmark& /*bookmark*/) {} + void removeMUCBookmark(const MUCBookmark& /*bookmark*/) {} + void setBookmarksEnabled(bool /*enabled*/) {} + void setRecents(const std::list<ChatListWindow::Chat>& /*recents*/) {} + void setUnreadCount(int /*unread*/) {} + void clearBookmarks() {} + }; + +} diff --git a/Swift/Controllers/Chat/UserSearchController.cpp b/Swift/Controllers/Chat/UserSearchController.cpp index b3403d7..5a76c5d 100644 --- a/Swift/Controllers/Chat/UserSearchController.cpp +++ b/Swift/Controllers/Chat/UserSearchController.cpp @@ -9,9 +9,9 @@ #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> +#include <Swiften/Base/foreach.h> #include <Swiften/Disco/GetDiscoInfoRequest.h> #include <Swiften/Disco/GetDiscoItemsRequest.h> - #include <Swift/Controllers/DiscoServiceWalker.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h> @@ -85,12 +85,12 @@ void UserSearchController::endDiscoWalker() { void UserSearchController::handleDiscoServiceFound(const JID& jid, boost::shared_ptr<DiscoInfo> info) { - bool isUserDirectory = false; + //bool isUserDirectory = false; bool supports55 = false; foreach (DiscoInfo::Identity identity, info->getIdentities()) { if ((identity.getCategory() == "directory" && identity.getType() == "user")) { - isUserDirectory = true; + //isUserDirectory = true; } } std::vector<std::string> features = info->getFeatures(); diff --git a/Swift/Controllers/ChatMessageSummarizer.cpp b/Swift/Controllers/ChatMessageSummarizer.cpp new file mode 100644 index 0000000..d95a177 --- /dev/null +++ b/Swift/Controllers/ChatMessageSummarizer.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/ChatMessageSummarizer.h> + +#include <Swiften/Base/format.h> +#include <Swift/Controllers/Intl.h> +#include <Swiften/Base/foreach.h> + +using namespace Swift; +using namespace std; + +string ChatMessageSummarizer::getSummary(const string& current, const vector<UnreadPair>& unreads) { + vector<UnreadPair> others; + int currentUnread = 0; + int otherCount = 0; + foreach (UnreadPair unread, unreads) { + if (unread.first == current) { + currentUnread += unread.second; + } else { + if (unread.second > 0) { + otherCount += unread.second; + others.push_back(unread); + } + } + } + string myString(current); + + if (currentUnread > 0) { + string result(QT_TRANSLATE_NOOP("", "%1% (%2%)")); + myString = str(format(result) % current % currentUnread); + } + + if (others.size() > 1) { + string result(QT_TRANSLATE_NOOP("", "%1% and %2% others (%3%)")); + myString = str(format(result) % myString % others.size() % otherCount); + } else if (!others.empty()) { + string result(QT_TRANSLATE_NOOP("", "%1%, %2% (%3%)")); + myString = str(format(result) % myString % others[0].first % otherCount); + } + return myString; +} diff --git a/Swift/Controllers/ChatMessageSummarizer.h b/Swift/Controllers/ChatMessageSummarizer.h new file mode 100644 index 0000000..d4ff188 --- /dev/null +++ b/Swift/Controllers/ChatMessageSummarizer.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> +#include <utility> +#include <string> + +namespace Swift { +typedef std::pair<std::string, int> UnreadPair; + + class ChatMessageSummarizer { + public: + std::string getSummary(const std::string& current, const std::vector<UnreadPair>& unreads); + }; +} diff --git a/Swift/Controllers/DiscoServiceWalker.cpp b/Swift/Controllers/DiscoServiceWalker.cpp index ce29927..1ca4930 100644 --- a/Swift/Controllers/DiscoServiceWalker.cpp +++ b/Swift/Controllers/DiscoServiceWalker.cpp @@ -6,6 +6,7 @@ #include <Swift/Controllers/DiscoServiceWalker.h> #include <Swiften/Base/Log.h> +#include <Swiften/Base/foreach.h> #include <boost/bind.hpp> @@ -140,7 +141,7 @@ void DiscoServiceWalker::markNodeCompleted(const JID& jid) { servicesBeingSearched_.erase(jid); /* All results are in */ - if (servicesBeingSearched_.size() == 0) { + if (servicesBeingSearched_.empty()) { active_ = false; onWalkComplete(); } diff --git a/Swift/Controllers/EventWindowController.cpp b/Swift/Controllers/EventWindowController.cpp index fbe9a8a..47554ce 100644 --- a/Swift/Controllers/EventWindowController.cpp +++ b/Swift/Controllers/EventWindowController.cpp @@ -27,8 +27,11 @@ void EventWindowController::handleEventQueueEventAdded(boost::shared_ptr<StanzaE if (event->getConcluded()) { handleEventConcluded(event); } else { - event->onConclusion.connect(boost::bind(&EventWindowController::handleEventConcluded, this, event)); - window_->addEvent(event, true); + boost::shared_ptr<MessageEvent> message = boost::dynamic_pointer_cast<MessageEvent>(event); + if (!(message && message->isReadable())) { + event->onConclusion.connect(boost::bind(&EventWindowController::handleEventConcluded, this, event)); + window_->addEvent(event, true); + } } } diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 9a35cc1..58b2dbe 100644 --- a/Swift/Controllers/MainController.cpp +++ b/Swift/Controllers/MainController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -14,14 +14,13 @@ #include <stdlib.h> #include <Swiften/Base/format.h> +#include <Swiften/Base/Algorithm.h> #include <Swift/Controllers/Intl.h> #include <Swift/Controllers/UIInterfaces/UIFactory.h> #include "Swiften/Network/TimerFactory.h" #include "Swift/Controllers/BuildVersion.h" -#include "Swift/Controllers/StoragesFactory.h" #include "Swiften/Client/Storages.h" #include "Swiften/VCards/VCardManager.h" -#include "Swift/Controllers/Chat/MUCSearchController.h" #include "Swift/Controllers/Chat/UserSearchController.h" #include "Swift/Controllers/Chat/ChatsManager.h" #include "Swift/Controllers/XMPPEvents/EventController.h" @@ -41,6 +40,7 @@ #include "Swift/Controllers/UIEvents/UIEventStream.h" #include "Swift/Controllers/PresenceNotifier.h" #include "Swift/Controllers/EventNotifier.h" +#include "Swift/Controllers/Storages/StoragesFactory.h" #include "SwifTools/Dock/Dock.h" #include "SwifTools/Notifier/TogglableNotifier.h" #include "Swiften/Base/foreach.h" @@ -60,11 +60,13 @@ #include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" #include "Swift/Controllers/UIEvents/ToggleNotificationsUIEvent.h" #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" -#include "Swift/Controllers/CertificateStorageFactory.h" -#include "Swift/Controllers/CertificateStorageTrustChecker.h" +#include "Swift/Controllers/Storages/CertificateStorageFactory.h" +#include "Swift/Controllers/Storages/CertificateStorageTrustChecker.h" #include "Swiften/Network/NetworkFactories.h" #include <Swift/Controllers/ProfileController.h> #include <Swift/Controllers/ContactEditController.h> +#include <Swift/Controllers/XMPPURIController.h> +#include "Swift/Controllers/AdHocManager.h" namespace Swift { @@ -84,16 +86,20 @@ MainController::MainController( CertificateStorageFactory* certificateStorageFactory, Dock* dock, Notifier* notifier, - bool useDelayForLatency) : + URIHandler* uriHandler, + bool useDelayForLatency, + bool eagleMode) : eventLoop_(eventLoop), networkFactories_(networkFactories), uiFactory_(uiFactories), - idleDetector_(&idleQuerier_, networkFactories_->getTimerFactory(), 100), + idleDetector_(&idleQuerier_, networkFactories_->getTimerFactory(), 1000), storagesFactory_(storagesFactory), certificateStorageFactory_(certificateStorageFactory), settings_(settings), + uriHandler_(uriHandler), loginWindow_(NULL) , - useDelayForLatency_(useDelayForLatency) { + useDelayForLatency_(useDelayForLatency), + eagleMode_(eagleMode) { storages_ = NULL; certificateStorage_ = NULL; statusTracker_ = NULL; @@ -121,23 +127,31 @@ MainController::MainController( loginWindow_ = uiFactory_->createLoginWindow(uiEventStream_); soundEventController_ = new SoundEventController(eventController_, soundPlayer, settings, uiEventStream_); + xmppURIController_ = new XMPPURIController(uriHandler_, uiEventStream_); + std::string selectedLoginJID = settings_->getStringSetting("lastLoginJID"); bool loginAutomatically = settings_->getBoolSetting("loginAutomatically", false); std::string cachedPassword; std::string cachedCertificate; - foreach (std::string 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"); - loginWindow_->addAvailableAccount(jid, password, certificate); - if (jid == selectedLoginJID) { - cachedPassword = password; - cachedCertificate = certificate; + if (!eagleMode_) { + foreach (std::string profile, settings->getAvailableProfiles()) { + ProfileSettingsProvider profileSettings(profile, settings); + std::string password = eagleMode ? "" : profileSettings.getStringSetting("pass"); + std::string certificate = profileSettings.getStringSetting("certificate"); + std::string jid = profileSettings.getStringSetting("jid"); + loginWindow_->addAvailableAccount(jid, password, certificate); + if (jid == selectedLoginJID) { + cachedPassword = password; + cachedCertificate = certificate; + } } + loginWindow_->selectUser(selectedLoginJID); + loginWindow_->setLoginAutomatically(loginAutomatically); + } else { + loginWindow_->setRememberingAllowed(false); } - loginWindow_->selectUser(selectedLoginJID); - loginWindow_->setLoginAutomatically(loginAutomatically); + + loginWindow_->onLoginRequest.connect(boost::bind(&MainController::handleLoginRequest, this, _1, _2, _3, _4, _5)); loginWindow_->onPurgeSavedLoginRequest.connect(boost::bind(&MainController::handlePurgeSavedLoginRequest, this, _1)); loginWindow_->onCancelLoginRequest.connect(boost::bind(&MainController::handleCancelLoginRequest, this)); @@ -161,12 +175,14 @@ MainController::MainController( } MainController::~MainController() { + purgeCachedCredentials(); //setManagersOffline(); eventController_->disconnectAll(); resetClient(); delete xmlConsoleController_; + delete xmppURIController_; delete soundEventController_; delete systemTrayController_; delete eventController_; @@ -174,7 +190,12 @@ MainController::~MainController() { delete uiEventStream_; } +void MainController::purgeCachedCredentials() { + safeClear(password_); +} + void MainController::resetClient() { + purgeCachedCredentials(); resetCurrentError(); resetPendingReconnects(); vCardPhotoHash_.clear(); @@ -234,9 +255,13 @@ void MainController::resetCurrentError() { void MainController::handleConnected() { boundJID_ = client_->getJID(); - loginWindow_->setIsLoggingIn(false); resetCurrentError(); resetPendingReconnects(); + + if (eagleMode_) { + purgeCachedCredentials(); + } + bool freshLogin = rosterController_ == NULL; myStatusLooksOnline_ = true; if (freshLogin) { @@ -247,7 +272,7 @@ void MainController::handleConnected() { contactEditController_ = new ContactEditController(rosterController_, uiFactory_, uiEventStream_); - 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_, settings_); + 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_); client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1)); chatsManager_->setAvatarManager(client_->getAvatarManager()); @@ -262,14 +287,15 @@ void MainController::handleConnected() { client_->getDiscoManager()->setCapsNode(CLIENT_NODE); client_->getDiscoManager()->setDiscoInfo(discoInfo); - userSearchControllerChat_ = new UserSearchController(UserSearchController::StartChat, jid_, uiEventStream_, uiFactory_, client_->getIQRouter(), rosterController_); userSearchControllerAdd_ = new UserSearchController(UserSearchController::AddContact, jid_, uiEventStream_, uiFactory_, client_->getIQRouter(), rosterController_); + adHocManager_ = new AdHocManager(boundJID_, uiFactory_, client_->getIQRouter(), uiEventStream_, rosterController_->getWindow()); } - + loginWindow_->setIsLoggingIn(false); + client_->requestRoster(); - GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(JID(), client_->getIQRouter()); + GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(boundJID_.toBare(), client_->getIQRouter()); discoInfoRequest->onResponse.connect(boost::bind(&MainController::handleServerDiscoInfoResponse, this, _1, _2)); discoInfoRequest->send(); @@ -356,19 +382,27 @@ void MainController::handleInputIdleChanged(bool idle) { } void MainController::handleLoginRequest(const std::string &username, const std::string &password, const std::string& certificateFile, bool remember, bool loginAutomatically) { - loginWindow_->setMessage(""); - loginWindow_->setIsLoggingIn(true); - profileSettings_ = new ProfileSettingsProvider(username, settings_); - profileSettings_->storeString("jid", username); - profileSettings_->storeString("certificate", certificateFile); - profileSettings_->storeString("pass", (remember || loginAutomatically) ? password : ""); - settings_->storeString("lastLoginJID", username); - settings_->storeBool("loginAutomatically", loginAutomatically); - loginWindow_->addAvailableAccount(profileSettings_->getStringSetting("jid"), profileSettings_->getStringSetting("pass"), profileSettings_->getStringSetting("certificate")); jid_ = JID(username); - password_ = password; - certificateFile_ = certificateFile; - performLoginFromCachedCredentials(); + if (!jid_.isValid() || jid_.getNode().empty()) { + loginWindow_->setMessage(QT_TRANSLATE_NOOP("", "User address invalid. User address should be of the form 'alice@wonderland.lit'")); + loginWindow_->setIsLoggingIn(false); + } else { + loginWindow_->setMessage(""); + loginWindow_->setIsLoggingIn(true); + profileSettings_ = new ProfileSettingsProvider(username, settings_); + if (!eagleMode_) { + profileSettings_->storeString("jid", username); + profileSettings_->storeString("certificate", certificateFile); + profileSettings_->storeString("pass", (remember || loginAutomatically) ? password : ""); + settings_->storeString("lastLoginJID", username); + settings_->storeBool("loginAutomatically", loginAutomatically); + loginWindow_->addAvailableAccount(profileSettings_->getStringSetting("jid"), profileSettings_->getStringSetting("pass"), profileSettings_->getStringSetting("certificate")); + } + + password_ = password; + certificateFile_ = certificateFile; + performLoginFromCachedCredentials(); + } } void MainController::handlePurgeSavedLoginRequest(const std::string& username) { @@ -377,6 +411,10 @@ void MainController::handlePurgeSavedLoginRequest(const std::string& username) { } void MainController::performLoginFromCachedCredentials() { + if (eagleMode_ && password_.empty()) { + /* Then we can't try to login again. */ + return; + } /* If we logged in with a bare JID, and we have a full bound JID, re-login with the * bound JID to try and keep dynamically assigned resources */ JID clientJID = jid_; @@ -392,7 +430,7 @@ void MainController::performLoginFromCachedCredentials() { certificateStorage_ = certificateStorageFactory_->createCertificateStorage(jid_.toBare()); certificateTrustChecker_ = new CertificateStorageTrustChecker(certificateStorage_); - client_ = boost::make_shared<Swift::Client>(clientJID, password_, networkFactories_, storages_); + client_ = boost::make_shared<Swift::Client>(clientJID, createSafeByteArray(password_.c_str()), networkFactories_, storages_); clientInitialized_ = true; client_->setCertificateTrustChecker(certificateTrustChecker_); client_->onDataRead.connect(boost::bind(&XMLConsoleController::handleDataRead, xmlConsoleController_, _1)); @@ -422,11 +460,15 @@ void MainController::performLoginFromCachedCredentials() { if (rosterController_) { rosterController_->getWindow()->setConnecting(); } - - client_->connect(); + ClientOptions clientOptions; + clientOptions.forgetPassword = eagleMode_; + client_->connect(clientOptions); } void MainController::handleDisconnected(const boost::optional<ClientError>& error) { + if (eagleMode_) { + purgeCachedCredentials(); + } if (quitRequested_) { resetClient(); loginWindow_->quit(); @@ -483,19 +525,27 @@ void MainController::handleDisconnected(const boost::optional<ClientError>& erro else if (!rosterController_) { //hasn't been logged in yet signOut(); loginWindow_->setMessage(message); + loginWindow_->setIsLoggingIn(false); } else { logout(); - setReconnectTimer(); - if (lastDisconnectError_) { - message = str(format(QT_TRANSLATE_NOOP("", "Reconnect to %1% failed: %2%. Will retry in %3% seconds.")) % jid_.getDomain() % message % boost::lexical_cast<std::string>(timeBeforeNextReconnect_)); - lastDisconnectError_->conclude(); + if (eagleMode_) { + message = str(format(QT_TRANSLATE_NOOP("", "Disconnected from %1%: %2%. To reconnect, Sign Out and provide your password again.")) % jid_.getDomain() % message); } else { - message = str(format(QT_TRANSLATE_NOOP("", "Disconnected from %1%: %2%.")) % jid_.getDomain() % message); + setReconnectTimer(); + if (lastDisconnectError_) { + message = str(format(QT_TRANSLATE_NOOP("", "Reconnect to %1% failed: %2%. Will retry in %3% seconds.")) % jid_.getDomain() % message % boost::lexical_cast<std::string>(timeBeforeNextReconnect_)); + lastDisconnectError_->conclude(); + } else { + message = str(format(QT_TRANSLATE_NOOP("", "Disconnected from %1%: %2%.")) % jid_.getDomain() % message); + } + lastDisconnectError_ = boost::shared_ptr<ErrorEvent>(new ErrorEvent(JID(jid_.getDomain()), message)); + eventController_->handleIncomingEvent(lastDisconnectError_); } - lastDisconnectError_ = boost::shared_ptr<ErrorEvent>(new ErrorEvent(JID(jid_.getDomain()), message)); - eventController_->handleIncomingEvent(lastDisconnectError_); } } + else if (!rosterController_) { //hasn't been logged in yet + loginWindow_->setIsLoggingIn(false); + } } void MainController::setReconnectTimer() { @@ -517,6 +567,9 @@ void MainController::handleCancelLoginRequest() { } void MainController::signOut() { + if (eagleMode_) { + purgeCachedCredentials(); + } eventController_->clear(); logout(); loginWindow_->loggedOut(); @@ -524,6 +577,9 @@ void MainController::signOut() { } void MainController::logout() { + if (eagleMode_) { + purgeCachedCredentials(); + } systemTrayController_->setMyStatusType(StatusShow::None); if (clientInitialized_ /*&& client_->isAvailable()*/) { client_->disconnect(); @@ -554,11 +610,12 @@ void MainController::setManagersOffline() { void MainController::handleServerDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error) { if (!error) { chatsManager_->setServerDiscoInfo(info); + adHocManager_->setServerDiscoInfo(info); } } void MainController::handleVCardReceived(const JID& jid, VCard::ref vCard) { - if (!jid.equals(jid_, JID::WithoutResource) || !vCard || vCard->getPhoto().isEmpty()) { + if (!jid.equals(jid_, JID::WithoutResource) || !vCard || vCard->getPhoto().empty()) { return; } std::string hash = Hexify::hexify(SHA1::getHash(vCard->getPhoto())); diff --git a/Swift/Controllers/MainController.h b/Swift/Controllers/MainController.h index f402f8f..21460ec 100644 --- a/Swift/Controllers/MainController.h +++ b/Swift/Controllers/MainController.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -62,6 +62,10 @@ namespace Swift { class Storages; class StoragesFactory; class NetworkFactories; + class URIHandler; + class XMPPURIController; + class AdHocManager; + class AdHocCommandWindowFactory; class MainController { public: @@ -76,7 +80,9 @@ namespace Swift { CertificateStorageFactory* certificateStorageFactory, Dock* dock, Notifier* notifier, - bool useDelayForLatency); + URIHandler* uriHandler, + bool useDelayForLatency, + bool eagleMode); ~MainController(); @@ -106,6 +112,7 @@ namespace Swift { void setManagersOffline(); void handleNotificationClicked(const JID& jid); void handleForceQuit(); + void purgeCachedCredentials(); private: EventLoop* eventLoop_; @@ -123,12 +130,14 @@ namespace Swift { SettingsProvider *settings_; ProfileSettingsProvider* profileSettings_; Dock* dock_; + URIHandler* uriHandler_; TogglableNotifier* notifier_; PresenceNotifier* presenceNotifier_; EventNotifier* eventNotifier_; RosterController* rosterController_; EventController* eventController_; EventWindowController* eventWindowController_; + AdHocManager* adHocManager_; LoginWindow* loginWindow_; UIEventStream* uiEventStream_; XMLConsoleController* xmlConsoleController_; @@ -139,6 +148,7 @@ namespace Swift { JID boundJID_; SystemTrayController* systemTrayController_; SoundEventController* soundEventController_; + XMPPURIController* xmppURIController_; std::string vCardPhotoHash_; std::string password_; std::string certificateFile_; @@ -152,5 +162,6 @@ namespace Swift { bool myStatusLooksOnline_; bool quitRequested_; static const int SecondsToWaitBeforeForceQuitting; + bool eagleMode_; }; } diff --git a/Swift/Controllers/PreviousStatusStore.cpp b/Swift/Controllers/PreviousStatusStore.cpp index 947cdc7..ca0a12e 100644 --- a/Swift/Controllers/PreviousStatusStore.cpp +++ b/Swift/Controllers/PreviousStatusStore.cpp @@ -40,7 +40,7 @@ std::vector<TypeStringPair> PreviousStatusStore::getSuggestions(const std::strin suggestions.push_back(status); } } - if (suggestions.size() == 0) { + if (suggestions.empty()) { TypeStringPair suggestion(StatusShow::Online, message); suggestions.push_back(suggestion); } diff --git a/Swift/Controllers/ProfileSettingsProvider.h b/Swift/Controllers/ProfileSettingsProvider.h index 74bcd12..8ba250c 100644 --- a/Swift/Controllers/ProfileSettingsProvider.h +++ b/Swift/Controllers/ProfileSettingsProvider.h @@ -7,6 +7,7 @@ #pragma once #include "Swift/Controllers/Settings/SettingsProvider.h" +#include <Swiften/Base/foreach.h> namespace Swift { diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp index 0fe88aa..bbe81e6 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.cpp +++ b/Swift/Controllers/Roster/ContactRosterItem.cpp @@ -7,6 +7,8 @@ #include "Swift/Controllers/Roster/ContactRosterItem.h" #include "Swift/Controllers/Roster/GroupRosterItem.h" +#include <Swiften/Base/foreach.h> + namespace Swift { @@ -89,7 +91,7 @@ void ContactRosterItem::applyPresence(const std::string& resource, boost::shared presences_.erase(resource); } } - if (presences_.size() == 0) { + if (presences_.empty()) { offlinePresence_ = presence; } } else { diff --git a/Swift/Controllers/Roster/GroupRosterItem.cpp b/Swift/Controllers/Roster/GroupRosterItem.cpp index f0a377a..2a7bfa5 100644 --- a/Swift/Controllers/Roster/GroupRosterItem.cpp +++ b/Swift/Controllers/Roster/GroupRosterItem.cpp @@ -12,7 +12,7 @@ namespace Swift { -GroupRosterItem::GroupRosterItem(const std::string& name, GroupRosterItem* parent, bool sortByStatus) : RosterItem(name, parent), sortByStatus_(sortByStatus) { +GroupRosterItem::GroupRosterItem(const std::string& name, GroupRosterItem* parent, bool sortByStatus) : RosterItem(name, parent), sortByStatus_(sortByStatus), manualSort_(false) { expanded_ = true; } @@ -20,6 +20,20 @@ GroupRosterItem::~GroupRosterItem() { } +void GroupRosterItem::setManualSort(const std::string& manualSortValue) { + manualSort_ = true; + bool changed = manualSortValue_ != manualSortValue; + manualSortValue_ = manualSortValue; + if (changed) { + onChildrenChanged(); + onDataChanged(); + } +} + +const std::string& GroupRosterItem::getSortableDisplayName() const { + return manualSort_ ? manualSortValue_ : RosterItem::getSortableDisplayName(); +} + bool GroupRosterItem::isExpanded() const { return expanded_; } @@ -75,7 +89,7 @@ void GroupRosterItem::removeAll() { delete group; } } - it++; + ++it; } children_.clear(); } @@ -103,7 +117,7 @@ ContactRosterItem* GroupRosterItem::removeChild(const JID& jid) { removed = groupRemoved; } } - it++; + ++it; } onChildrenChanged(); onDataChanged(); @@ -122,7 +136,7 @@ GroupRosterItem* GroupRosterItem::removeGroupChild(const std::string& groupName) it = children_.erase(it); continue; } - it++; + ++it; } onChildrenChanged(); onDataChanged(); @@ -210,7 +224,8 @@ void GroupRosterItem::handleChildrenChanged(GroupRosterItem* group) { } else { displayedChildren_.erase(std::remove(displayedChildren_.begin(), displayedChildren_.end(), group), displayedChildren_.end()); } - if (oldSize != getDisplayedChildren().size()) { + + if (oldSize != getDisplayedChildren().size() || sortDisplayed()) { onChildrenChanged(); onDataChanged(); } diff --git a/Swift/Controllers/Roster/GroupRosterItem.h b/Swift/Controllers/Roster/GroupRosterItem.h index 57fa9fa..beb7705 100644 --- a/Swift/Controllers/Roster/GroupRosterItem.h +++ b/Swift/Controllers/Roster/GroupRosterItem.h @@ -31,6 +31,8 @@ class GroupRosterItem : public RosterItem { void setExpanded(bool expanded); bool isExpanded() const; boost::signal<void (bool)> onExpandedChanged; + void setManualSort(const std::string& manualSortValue); + virtual const std::string& getSortableDisplayName() const; private: void handleChildrenChanged(GroupRosterItem* group); void handleDataChanged(RosterItem* item); @@ -40,6 +42,8 @@ class GroupRosterItem : public RosterItem { std::vector<RosterItem*> children_; std::vector<RosterItem*> displayedChildren_; bool sortByStatus_; + bool manualSort_; + std::string manualSortValue_; }; } diff --git a/Swift/Controllers/Roster/LeastCommonSubsequence.h b/Swift/Controllers/Roster/LeastCommonSubsequence.h new file mode 100644 index 0000000..dd3c95a --- /dev/null +++ b/Swift/Controllers/Roster/LeastCommonSubsequence.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> + +namespace Swift { + using std::equal_to; + + namespace Detail { + template<typename XIt, typename YIt, typename Length, typename Predicate> + void computeLeastCommonSubsequenceMatrix(XIt xBegin, XIt xEnd, YIt yBegin, YIt yEnd, std::vector<Length>& result) { + size_t width = std::distance(xBegin, xEnd) + 1; + size_t height = std::distance(yBegin, yEnd) + 1; + result.resize(width * height); + + // Initialize first row & column + for (size_t i = 0; i < width; ++i) { + result[i] = 0; + } + for (size_t j = 0; j < height; ++j) { + result[j*width] = 0; + } + + // Compute the LCS lengths for subsets + Predicate predicate; + for (size_t i = 1; i < width; ++i) { + for (size_t j = 1; j < height; ++j) { + result[i + j*width] = (predicate(*(xBegin + i-1), *(yBegin + j-1)) ? result[(i-1) + (j-1)*width] + 1 : std::max(result[i + (j-1)*width], result[i-1 + (j*width)])); + } + } + } + } + + template<typename X, typename InsertRemovePredicate, typename UpdatePredicate> + void computeIndexDiff(const std::vector<X>& x, const std::vector<X>& y, std::vector<size_t>& updates, std::vector<size_t>& postUpdates, std::vector<size_t>& removes, std::vector<size_t>& inserts) { + InsertRemovePredicate insertRemovePredicate; + UpdatePredicate updatePredicate; + + // Find & handle common prefix (Optimization to reduce LCS matrix size) + typename std::vector<X>::const_iterator xBegin = x.begin(); + typename std::vector<X>::const_iterator yBegin = y.begin(); + while (xBegin < x.end() && yBegin < y.end() && insertRemovePredicate(*xBegin, *yBegin)) { + if (updatePredicate(*xBegin, *yBegin)) { + updates.push_back(std::distance(x.begin(), xBegin)); + postUpdates.push_back(std::distance(y.begin(), yBegin)); + } + ++xBegin; + ++yBegin; + } + size_t prefixLength = std::distance(x.begin(), xBegin); + + // Find & handle common suffix (Optimization to reduce LCS matrix size) + typename std::vector<X>::const_reverse_iterator xEnd = x.rbegin(); + typename std::vector<X>::const_reverse_iterator yEnd = y.rbegin(); + while (xEnd.base() > xBegin && yEnd.base() > yBegin && insertRemovePredicate(*xEnd, *yEnd)) { + if (updatePredicate(*xEnd, *yEnd)) { + updates.push_back(std::distance(x.begin(), xEnd.base()) - 1); + postUpdates.push_back(std::distance(y.begin(), yEnd.base()) - 1); + } + ++xEnd; + ++yEnd; + } + + // Compute lengths + size_t xLength = std::distance(xBegin, xEnd.base()); + size_t yLength = std::distance(yBegin, yEnd.base()); + + // Compute LCS matrix + std::vector<unsigned int> lcs; + Detail::computeLeastCommonSubsequenceMatrix<typename std::vector<X>::const_iterator, typename std::vector<X>::const_iterator, unsigned int, InsertRemovePredicate>(xBegin, xEnd.base(), yBegin, yEnd.base(), lcs); + + // Process LCS matrix + size_t i = xLength; + size_t j = yLength; + const size_t width = xLength + 1; + while (true) { + if (i > 0 && j > 0 && insertRemovePredicate(x[prefixLength + i-1], y[prefixLength + j-1])) { + // x[i-1] same + if (updatePredicate(x[prefixLength + i - 1], y[prefixLength + j - 1])) { + updates.push_back(prefixLength + i-1); + postUpdates.push_back(prefixLength + j-1); + } + i -= 1; + j -= 1; + } + else if (j > 0 && (i == 0 || lcs[i + (j-1)*width] >= lcs[i-1 + j*width])) { + // y[j-1] added + inserts.push_back(prefixLength + j-1); + j -= 1; + } + else if (i > 0 && (j == 0 || lcs[i + (j-1)*width] < lcs[i-1 + j*width])) { + // x[i-1] removed + removes.push_back(prefixLength + i-1); + i -= 1; + } + else { + break; + } + } + } +} diff --git a/Swift/Controllers/Roster/Roster.cpp b/Swift/Controllers/Roster/Roster.cpp index 4e34105..a62a18a 100644 --- a/Swift/Controllers/Roster/Roster.cpp +++ b/Swift/Controllers/Roster/Roster.cpp @@ -107,7 +107,7 @@ void Roster::removeAll() { void Roster::removeContact(const JID& jid) { std::vector<ContactRosterItem*>* items = &itemMap_[fullJIDMapping_ ? jid : jid.toBare()]; items->erase(std::remove_if(items->begin(), items->end(), JIDEqualsTo(jid)), items->end()); - if (items->size() == 0) { + if (items->empty()) { itemMap_.erase(fullJIDMapping_ ? jid : jid.toBare()); } //Causes the delete @@ -124,7 +124,7 @@ void Roster::removeContactFromGroup(const JID& jid, const std::string& groupName std::vector<ContactRosterItem*>* items = &itemMap_[fullJIDMapping_ ? jid : jid.toBare()]; items->erase(std::remove(items->begin(), items->end(), deleted), items->end()); } - it++; + ++it; } foreach (ContactRosterItem* item, itemMap_[fullJIDMapping_ ? jid : jid.toBare()]) { item->removeGroup(groupName); @@ -179,7 +179,7 @@ void Roster::filterContact(ContactRosterItem* contact, GroupRosterItem* group) { foreach (RosterFilter *filter, filters_) { hide &= (*filter)(contact); } - group->setDisplayed(contact, filters_.size() == 0 || !hide); + group->setDisplayed(contact, filters_.empty() || !hide); int newDisplayedSize = group->getDisplayedChildren().size(); if (oldDisplayedSize == 0 && newDisplayedSize > 0) { onGroupAdded(group); diff --git a/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp b/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp index c1045ee..0a242ae 100644 --- a/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp +++ b/Swift/Controllers/Roster/RosterGroupExpandinessPersister.cpp @@ -9,6 +9,7 @@ #include <boost/bind.hpp> #include <vector> +#include <Swiften/Base/foreach.h> #include "Swiften/Base/String.h" #include "Swift/Controllers/Roster/GroupRosterItem.h" diff --git a/Swift/Controllers/Roster/RosterItem.cpp b/Swift/Controllers/Roster/RosterItem.cpp index 3f130bb..77db8a3 100644 --- a/Swift/Controllers/Roster/RosterItem.cpp +++ b/Swift/Controllers/Roster/RosterItem.cpp @@ -33,11 +33,11 @@ void RosterItem::setDisplayName(const std::string& name) { onDataChanged(); } -std::string RosterItem::getDisplayName() const { +const std::string& RosterItem::getDisplayName() const { return name_; } -std::string RosterItem::getSortableDisplayName() const { +const std::string& RosterItem::getSortableDisplayName() const { return sortableDisplayName_; } diff --git a/Swift/Controllers/Roster/RosterItem.h b/Swift/Controllers/Roster/RosterItem.h index ed8cb16..769f72d 100644 --- a/Swift/Controllers/Roster/RosterItem.h +++ b/Swift/Controllers/Roster/RosterItem.h @@ -20,8 +20,8 @@ class RosterItem { boost::signal<void ()> onDataChanged; GroupRosterItem* getParent() const; void setDisplayName(const std::string& name); - std::string getDisplayName() const; - std::string getSortableDisplayName() const; + const std::string& getDisplayName() const; + virtual const std::string& getSortableDisplayName() const; private: std::string name_; std::string sortableDisplayName_; diff --git a/Swift/Controllers/Roster/TableRoster.cpp b/Swift/Controllers/Roster/TableRoster.cpp new file mode 100644 index 0000000..171e8ed --- /dev/null +++ b/Swift/Controllers/Roster/TableRoster.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/Roster/TableRoster.h> + +#include <boost/cast.hpp> +#include <cassert> +#include <algorithm> +#include <Swiften/Base/foreach.h> + +#include <Swiften/Network/TimerFactory.h> +#include <Swiften/Network/Timer.h> +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/LeastCommonSubsequence.h> + +namespace Swift { + struct SectionNameEquals { + bool operator()(const TableRoster::Section& s1, const TableRoster::Section& s2) const { + return s1.name == s2.name; + } + }; + + template<typename T> + struct True { + bool operator()(const T&, const T&) const { + return true; + } + }; + + struct ItemEquals { + bool operator()(const TableRoster::Item& i1, const TableRoster::Item& i2) const { + return i1.jid == i2.jid; + } + }; + + + struct ItemNeedsUpdate { + bool operator()(const TableRoster::Item& i1, const TableRoster::Item& i2) const { + return i1.status != i2.status || i1.description != i2.description || i1.name != i2.name; + } + }; + + struct CreateIndexForSection { + CreateIndexForSection(size_t section) : section(section) { + } + + TableRoster::Index operator()(size_t row) const { + return TableRoster::Index(section, row); + } + + size_t section; + }; +} + +using namespace Swift; + +TableRoster::TableRoster(Roster* model, TimerFactory* timerFactory, int updateDelay) : model(model), updatePending(false) { + updateTimer = timerFactory->createTimer(updateDelay); + updateTimer->onTick.connect(boost::bind(&TableRoster::handleUpdateTimerTick, this)); + if (model) { + model->onChildrenChanged.connect(boost::bind(&TableRoster::scheduleUpdate, this)); + model->onGroupAdded.connect(boost::bind(&TableRoster::scheduleUpdate, this)); + model->onDataChanged.connect(boost::bind(&TableRoster::scheduleUpdate, this)); + } +} + +TableRoster::~TableRoster() { + updateTimer->stop(); + updateTimer->onTick.disconnect(boost::bind(&TableRoster::handleUpdateTimerTick, this)); + if (model) { + model->onDataChanged.disconnect(boost::bind(&TableRoster::scheduleUpdate, this)); + model->onGroupAdded.disconnect(boost::bind(&TableRoster::scheduleUpdate, this)); + model->onChildrenChanged.disconnect(boost::bind(&TableRoster::scheduleUpdate, this)); + } +} + +size_t TableRoster::getNumberOfSections() const { + return sections.size(); +} + +const std::string& TableRoster::getSectionTitle(size_t section) { + return sections[section].name; +} + +size_t TableRoster::getNumberOfRowsInSection(size_t section) const { + return sections[section].items.size(); +} + +const TableRoster::Item& TableRoster::getItem(const Index& index) const { + return sections[index.section].items[index.row]; +} + +void TableRoster::handleUpdateTimerTick() { + updateTimer->stop(); + updatePending = false; + + // Get a model for the new roster + std::vector<Section> newSections; + if (model) { + foreach(RosterItem* item, model->getRoot()->getDisplayedChildren()) { + if (GroupRosterItem* groupItem = boost::polymorphic_downcast<GroupRosterItem*>(item)) { + //std::cerr << "* " << groupItem->getDisplayName() << std::endl; + Section section(groupItem->getDisplayName()); + foreach(RosterItem* groupChildItem, groupItem->getDisplayedChildren()) { + if (ContactRosterItem* contact = boost::polymorphic_downcast<ContactRosterItem*>(groupChildItem)) { + //std::cerr << " - " << contact->getDisplayJID() << std::endl; + section.items.push_back(Item(contact->getDisplayName(), contact->getStatusText(), contact->getDisplayJID(), contact->getStatusShow(), contact->getAvatarPath())); + } + } + newSections.push_back(section); + } + } + } + + // Do a diff with the previous roster + Update update; + std::vector<size_t> sectionUpdates; + std::vector<size_t> sectionPostUpdates; + computeIndexDiff<Section,SectionNameEquals,True<Section> >(sections, newSections, sectionUpdates, sectionPostUpdates, update.deletedSections, update.insertedSections); + assert(sectionUpdates.size() == sectionPostUpdates.size()); + for (size_t i = 0; i < sectionUpdates.size(); ++i) { + assert(sectionUpdates[i] < sections.size()); + assert(sectionPostUpdates[i] < newSections.size()); + std::vector<size_t> itemUpdates; + std::vector<size_t> itemPostUpdates; + std::vector<size_t> itemRemoves; + std::vector<size_t> itemInserts; + computeIndexDiff<Item, ItemEquals, ItemNeedsUpdate >(sections[sectionUpdates[i]].items, newSections[sectionPostUpdates[i]].items, itemUpdates, itemPostUpdates, itemRemoves, itemInserts); + size_t end = update.insertedRows.size(); + update.insertedRows.resize(update.insertedRows.size() + itemInserts.size()); + std::transform(itemInserts.begin(), itemInserts.end(), update.insertedRows.begin() + end, CreateIndexForSection(sectionPostUpdates[i])); + end = update.deletedRows.size(); + update.deletedRows.resize(update.deletedRows.size() + itemRemoves.size()); + std::transform(itemRemoves.begin(), itemRemoves.end(), update.deletedRows.begin() + end, CreateIndexForSection(sectionUpdates[i])); + end = update.updatedRows.size(); + update.updatedRows.resize(update.updatedRows.size() + itemUpdates.size()); + std::transform(itemUpdates.begin(), itemUpdates.end(), update.updatedRows.begin() + end, CreateIndexForSection(sectionPostUpdates[i])); + } + + // Switch the old model with the new + sections.swap(newSections); + + /* + std::cerr << "-S: "; + for (size_t i = 0; i < update.deletedSections.size(); ++i) { + std::cerr << update.deletedSections[i] << " "; + } + std::cerr << std::endl; + std::cerr << "+S: "; + for (size_t i = 0; i < update.insertedSections.size(); ++i) { + std::cerr << update.insertedSections[i] << " "; + } + std::cerr << std::endl; + std::cerr << "-R: "; + for (size_t i = 0; i < update.deletedRows.size(); ++i) { + std::cerr << update.deletedRows[i].section << "," << update.deletedRows[i].row << " "; + } + std::cerr << std::endl; + std::cerr << "*R: "; + for (size_t i = 0; i < update.updatedRows.size(); ++i) { + std::cerr << update.updatedRows[i].section << "," << update.updatedRows[i].row << " "; + } + std::cerr << std::endl; + std::cerr << "+R: "; + for (size_t i = 0; i < update.insertedRows.size(); ++i) { + std::cerr << update.insertedRows[i].section << "," << update.insertedRows[i].row << " "; + } + std::cerr << std::endl; + */ + + // Emit the update + onUpdate(update); +} + +void TableRoster::scheduleUpdate() { + if (!updatePending) { + updatePending = true; + updateTimer->start(); + } +} diff --git a/Swift/Controllers/Roster/TableRoster.h b/Swift/Controllers/Roster/TableRoster.h new file mode 100644 index 0000000..8ff16d0 --- /dev/null +++ b/Swift/Controllers/Roster/TableRoster.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> +#include <vector> +#include <Swiften/Base/boost_bsignals.h> + +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/StatusShow.h> + +namespace Swift { + class Roster; + class TimerFactory; + class Timer; + + class TableRoster { + public: + struct Item { + Item(const std::string& name, const std::string& description, const JID& jid, StatusShow::Type status, const std::string& avatarPath) : name(name), description(description), jid(jid), status(status), avatarPath(avatarPath) { + } + std::string name; + std::string description; + JID jid; + StatusShow::Type status; + std::string avatarPath; + }; + + struct Index { + Index(size_t section = 0, size_t row = 0) : section(section), row(row) { + } + size_t section; + size_t row; + + bool operator==(const Index& o) const { + return o.section == section && o.row == row; + } + }; + + struct Update { + std::vector<Index> updatedRows; + std::vector<Index> insertedRows; + std::vector<Index> deletedRows; + std::vector<size_t> insertedSections; + std::vector<size_t> deletedSections; + }; + + TableRoster(Roster* model, TimerFactory* timerFactory, int updateDelay); + ~TableRoster(); + + size_t getNumberOfSections() const; + size_t getNumberOfRowsInSection(size_t section) const; + + const std::string& getSectionTitle(size_t); + + const Item& getItem(const Index&) const; + + boost::signal<void (const Update&)> onUpdate; + + private: + void handleUpdateTimerTick(); + void scheduleUpdate(); + + private: + friend class SectionNameEquals; + struct Section { + Section(const std::string& name) : name(name) { + } + + std::string name; + std::vector<Item> items; + }; + + Roster* model; + std::vector<Section> sections; + bool updatePending; + boost::shared_ptr<Timer> updateTimer; + }; +} diff --git a/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp b/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp new file mode 100644 index 0000000..3acab12 --- /dev/null +++ b/Swift/Controllers/Roster/UnitTest/LeastCommonSubsequenceTest.cpp @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/assign/list_of.hpp> +#include <functional> + +#include <QA/Checker/IO.h> +#include <Swift/Controllers/Roster/LeastCommonSubsequence.h> + +using namespace Swift; + +struct IsBOrC { + bool operator()(char c, char c2) const { + CPPUNIT_ASSERT_EQUAL(c, c2); + return c == 'b' || c == 'c'; + } +}; + +struct IsXOrY { + bool operator()(char c, char c2) const { + CPPUNIT_ASSERT_EQUAL(c, c2); + return c == 'x' || c == 'y'; + } +}; + +struct IsArizonaOrNewJersey { + bool operator()(const std::string& s, const std::string& s2) const { + CPPUNIT_ASSERT_EQUAL(s, s2); + return s == "Arizona" || s == "New Jersey"; + } +}; + +class LeastCommonSubsequenceTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(LeastCommonSubsequenceTest); + CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_1); + CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_2); + CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_Sequence1Empty); + CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_Sequence2Empty); + CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_BothSequencesEmpty); + CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_NoCommonSequence); + CPPUNIT_TEST(testComputeLeastCommonSubsequenceMatrix_SameSequences); + CPPUNIT_TEST(testComputeIndexDiff_1); + CPPUNIT_TEST(testComputeIndexDiff_2); + CPPUNIT_TEST(testComputeIndexDiff_Sequence1Empty); + CPPUNIT_TEST(testComputeIndexDiff_Sequence2Empty); + CPPUNIT_TEST(testComputeIndexDiff_BothSequencesEmpty); + CPPUNIT_TEST(testComputeIndexDiff_NoCommonSequence); + CPPUNIT_TEST(testComputeIndexDiff_SameSequences); + CPPUNIT_TEST(testComputeIndexDiff_CommonPrefixAndSuffix); + CPPUNIT_TEST_SUITE_END(); + + public: + void testComputeLeastCommonSubsequenceMatrix_1() { + std::vector<char> x = boost::assign::list_of('x')('m')('j')('y')('a')('u')('z'); + std::vector<char> y = boost::assign::list_of('m')('z')('j')('a')('w')('x')('u'); + + std::vector<int> result; + Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + + std::vector<int> expected = boost::assign::list_of + (0)(0)(0)(0)(0)(0)(0)(0) + (0)(0)(1)(1)(1)(1)(1)(1) + (0)(0)(1)(1)(1)(1)(1)(2) + (0)(0)(1)(2)(2)(2)(2)(2) + (0)(0)(1)(2)(2)(3)(3)(3) + (0)(0)(1)(2)(2)(3)(3)(3) + (0)(1)(1)(2)(2)(3)(3)(3) + (0)(1)(1)(2)(2)(3)(4)(4); + CPPUNIT_ASSERT_EQUAL(expected, result); + } + + void testComputeLeastCommonSubsequenceMatrix_2() { + std::vector<char> x = boost::assign::list_of('x')('x')('x')('m')('j')('y')('a')('u')('z'); + std::vector<char> y = boost::assign::list_of('m')('z')('j')('a')('w')('x')('u'); + + std::vector<int> result; + Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + + std::vector<int> expected = boost::assign::list_of + (0)(0)(0)(0)(0)(0)(0)(0)(0)(0) + (0)(0)(0)(0)(1)(1)(1)(1)(1)(1) + (0)(0)(0)(0)(1)(1)(1)(1)(1)(2) + (0)(0)(0)(0)(1)(2)(2)(2)(2)(2) + (0)(0)(0)(0)(1)(2)(2)(3)(3)(3) + (0)(0)(0)(0)(1)(2)(2)(3)(3)(3) + (0)(1)(1)(1)(1)(2)(2)(3)(3)(3) + (0)(1)(1)(1)(1)(2)(2)(3)(4)(4); + CPPUNIT_ASSERT_EQUAL(expected, result); + } + + void testComputeLeastCommonSubsequenceMatrix_Sequence1Empty() { + std::vector<char> x; + std::vector<char> y = boost::assign::list_of('a')('b')('c'); + + std::vector<int> result; + Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + + std::vector<int> expected = boost::assign::list_of + (0) + (0) + (0) + (0); + CPPUNIT_ASSERT_EQUAL(expected, result); + } + + void testComputeLeastCommonSubsequenceMatrix_Sequence2Empty() { + std::vector<char> x = boost::assign::list_of('a')('b')('c'); + std::vector<char> y; + + std::vector<int> result; + Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + + std::vector<int> expected = boost::assign::list_of + (0)(0)(0)(0); + CPPUNIT_ASSERT_EQUAL(expected, result); + } + + void testComputeLeastCommonSubsequenceMatrix_BothSequencesEmpty() { + std::vector<char> x; + std::vector<char> y; + + std::vector<int> result; + Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + + std::vector<int> expected = boost::assign::list_of(0); + CPPUNIT_ASSERT_EQUAL(expected, result); + } + + void testComputeLeastCommonSubsequenceMatrix_NoCommonSequence() { + std::vector<char> x = boost::assign::list_of('a')('b')('c'); + std::vector<char> y = boost::assign::list_of('d')('e')('f')('g'); + + std::vector<int> result; + Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + + std::vector<int> expected = boost::assign::list_of + (0)(0)(0)(0) + (0)(0)(0)(0) + (0)(0)(0)(0) + (0)(0)(0)(0) + (0)(0)(0)(0); + CPPUNIT_ASSERT_EQUAL(expected, result); + } + + void testComputeLeastCommonSubsequenceMatrix_SameSequences() { + std::vector<char> x = boost::assign::list_of('a')('b')('c'); + std::vector<char> y = boost::assign::list_of('a')('b')('c'); + + std::vector<int> result; + Detail::computeLeastCommonSubsequenceMatrix<std::vector<char>::const_iterator, std::vector<char>::const_iterator, int, std::equal_to<char> >(x.begin(), x.end(), y.begin(), y.end(), result); + + std::vector<int> expected = boost::assign::list_of + (0)(0)(0)(0) + (0)(1)(1)(1) + (0)(1)(2)(2) + (0)(1)(2)(3); + CPPUNIT_ASSERT_EQUAL(expected, result); + } + + void testComputeIndexDiff_1() { + std::vector<std::string> x = boost::assign::list_of("Arizona")("California")("Delaware")("New Jersey")("Washington"); + std::vector<std::string> y = boost::assign::list_of("Alaska")("Arizona")("California")("Georgia")("New Jersey")("Virginia"); + + std::vector<size_t> updates; + std::vector<size_t> postUpdates; + std::vector<size_t> removes; + std::vector<size_t> inserts; + computeIndexDiff<std::string, std::equal_to<std::string>, IsArizonaOrNewJersey >(x, y, updates, postUpdates, removes, inserts); + + std::vector<size_t> expectedUpdates = boost::assign::list_of(3)(0); + std::vector<size_t> expectedPostUpdates = boost::assign::list_of(4)(1); + std::vector<size_t> expectedRemoves = boost::assign::list_of(4)(2); + std::vector<size_t> expectedInserts = boost::assign::list_of(5)(3)(0); + CPPUNIT_ASSERT_EQUAL(expectedUpdates, updates); + CPPUNIT_ASSERT_EQUAL(expectedPostUpdates, postUpdates); + CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes); + CPPUNIT_ASSERT_EQUAL(expectedInserts, inserts); + } + + void testComputeIndexDiff_2() { + std::vector<char> x = boost::assign::list_of('x')('y'); + std::vector<char> y = boost::assign::list_of('x'); + + std::vector<size_t> updates; + std::vector<size_t> postUpdates; + std::vector<size_t> removes; + std::vector<size_t> inserts; + computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts); + + std::vector<size_t> expectedRemoves = boost::assign::list_of(1); + CPPUNIT_ASSERT(updates.empty()); + CPPUNIT_ASSERT(postUpdates.empty()); + CPPUNIT_ASSERT(inserts.empty()); + CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes); + } + + void testComputeIndexDiff_Sequence1Empty() { + std::vector<char> x; + std::vector<char> y = boost::assign::list_of('a')('b')('c'); + + std::vector<size_t> updates; + std::vector<size_t> postUpdates; + std::vector<size_t> removes; + std::vector<size_t> inserts; + computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts); + + std::vector<size_t> expectedInserts = boost::assign::list_of(2)(1)(0); + CPPUNIT_ASSERT(updates.empty()); + CPPUNIT_ASSERT(postUpdates.empty()); + CPPUNIT_ASSERT(removes.empty()); + CPPUNIT_ASSERT_EQUAL(expectedInserts, inserts); + } + + void testComputeIndexDiff_Sequence2Empty() { + std::vector<char> x = boost::assign::list_of('a')('b')('c'); + std::vector<char> y; + + std::vector<size_t> updates; + std::vector<size_t> postUpdates; + std::vector<size_t> removes; + std::vector<size_t> inserts; + computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts); + + std::vector<size_t> expectedRemoves = boost::assign::list_of(2)(1)(0); + CPPUNIT_ASSERT(updates.empty()); + CPPUNIT_ASSERT(postUpdates.empty()); + CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes); + CPPUNIT_ASSERT(inserts.empty()); + } + + void testComputeIndexDiff_BothSequencesEmpty() { + std::vector<char> x; + std::vector<char> y; + + std::vector<size_t> updates; + std::vector<size_t> postUpdates; + std::vector<size_t> removes; + std::vector<size_t> inserts; + computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts); + + CPPUNIT_ASSERT(updates.empty()); + CPPUNIT_ASSERT(postUpdates.empty()); + CPPUNIT_ASSERT(removes.empty()); + CPPUNIT_ASSERT(inserts.empty()); + } + + void testComputeIndexDiff_NoCommonSequence() { + std::vector<char> x = boost::assign::list_of('a')('b')('c'); + std::vector<char> y = boost::assign::list_of('d')('e')('f')('g'); + + std::vector<size_t> updates; + std::vector<size_t> postUpdates; + std::vector<size_t> removes; + std::vector<size_t> inserts; + computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts); + + std::vector<size_t> expectedRemoves = boost::assign::list_of(2)(1)(0); + std::vector<size_t> expectedInserts = boost::assign::list_of(3)(2)(1)(0); + CPPUNIT_ASSERT(updates.empty()); + CPPUNIT_ASSERT(postUpdates.empty()); + CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes); + CPPUNIT_ASSERT_EQUAL(expectedInserts, inserts); + } + + void testComputeIndexDiff_SameSequences() { + std::vector<char> x = boost::assign::list_of('a')('b')('c'); + std::vector<char> y = boost::assign::list_of('a')('b')('c'); + + std::vector<size_t> updates; + std::vector<size_t> postUpdates; + std::vector<size_t> removes; + std::vector<size_t> inserts; + computeIndexDiff<char, std::equal_to<char>, IsBOrC >(x, y, updates, postUpdates, removes, inserts); + + std::vector<size_t> expectedUpdates = boost::assign::list_of(1)(2); + CPPUNIT_ASSERT_EQUAL(expectedUpdates, updates); + CPPUNIT_ASSERT_EQUAL(expectedUpdates, postUpdates); + CPPUNIT_ASSERT(removes.empty()); + CPPUNIT_ASSERT(inserts.empty()); + } + + void testComputeIndexDiff_CommonPrefixAndSuffix() { + std::vector<char> x = boost::assign::list_of('x')('x')('x')('x')('a')('b')('c')('d')('e')('y')('y')('y'); + std::vector<char> y = boost::assign::list_of('x')('x')('x')('x')('e')('a')('b')('f')('d')('g')('y')('y')('y'); + + std::vector<size_t> updates; + std::vector<size_t> postUpdates; + std::vector<size_t> removes; + std::vector<size_t> inserts; + computeIndexDiff<char, std::equal_to<char>, IsXOrY >(x, y, updates, postUpdates, removes, inserts); + + std::vector<size_t> expectedUpdates = boost::assign::list_of(0)(1)(2)(3)(11)(10)(9); + std::vector<size_t> expectedPostUpdates = boost::assign::list_of(0)(1)(2)(3)(12)(11)(10); + std::vector<size_t> expectedRemoves = boost::assign::list_of(8)(6); + std::vector<size_t> expectedInserts = boost::assign::list_of(9)(7)(4); + CPPUNIT_ASSERT_EQUAL(expectedUpdates, updates); + CPPUNIT_ASSERT_EQUAL(expectedPostUpdates, postUpdates); + CPPUNIT_ASSERT_EQUAL(expectedRemoves, removes); + CPPUNIT_ASSERT_EQUAL(expectedInserts, inserts); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(LeastCommonSubsequenceTest); diff --git a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp index 16f2745..ca74dbb 100644 --- a/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/RosterControllerTest.cpp @@ -8,6 +8,7 @@ #include <cppunit/extensions/HelperMacros.h> #include <cppunit/extensions/TestFactoryRegistry.h> +#include <Swiften/Base/foreach.h> #include "Swift/Controllers/Roster/RosterController.h" #include "Swift/Controllers/UnitTest/MockMainWindowFactory.h" // #include "Swiften/Elements/Payload.h" diff --git a/Swift/Controllers/Roster/UnitTest/RosterTest.cpp b/Swift/Controllers/Roster/UnitTest/RosterTest.cpp index cbef787..4444e8a 100644 --- a/Swift/Controllers/Roster/UnitTest/RosterTest.cpp +++ b/Swift/Controllers/Roster/UnitTest/RosterTest.cpp @@ -21,6 +21,7 @@ class RosterTest : public CppUnit::TestFixture { CPPUNIT_TEST(testRemoveSecondContact); CPPUNIT_TEST(testRemoveSecondContactSameBare); CPPUNIT_TEST(testApplyPresenceLikeMUC); + CPPUNIT_TEST(testReSortLikeMUC); CPPUNIT_TEST_SUITE_END(); public: @@ -117,6 +118,22 @@ class RosterTest : public CppUnit::TestFixture { } + void testReSortLikeMUC() { + JID jid4a("a@b/c"); + JID jid4b("a@b/d"); + JID jid4c("a@b/e"); + roster_->addContact(jid4a, JID(), "Bird", "group1", ""); + roster_->addContact(jid4b, JID(), "Cookie", "group2", ""); + roster_->addContact(jid4b, JID(), "Ernie", "group1", ""); + roster_->getGroup("group1")->setManualSort("2"); + roster_->getGroup("group2")->setManualSort("1"); + GroupRosterItem* root = roster_->getRoot(); + const std::vector<RosterItem*> kids = root->getDisplayedChildren(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), kids.size()); + CPPUNIT_ASSERT_EQUAL(std::string("group2"), kids[0]->getDisplayName()); + CPPUNIT_ASSERT_EQUAL(std::string("group1"), kids[1]->getDisplayName()); + } + private: Roster *roster_; JID jid1_; diff --git a/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp b/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp new file mode 100644 index 0000000..e433b50 --- /dev/null +++ b/Swift/Controllers/Roster/UnitTest/TableRosterTest.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/Roster/TableRoster.h> + +std::ostream& operator<<(std::ostream& os, const Swift::TableRoster::Index& i) { + os << "(" << i.section << ", " << i.row << ")"; + return os; +} + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <boost/shared_ptr.hpp> +#include <boost/variant.hpp> + +#include <Swiften/Network/DummyTimerFactory.h> +#include <Swift/Controllers/Roster/Roster.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/Roster/SetPresence.h> + +using namespace Swift; + +class TableRosterTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(TableRosterTest); + CPPUNIT_TEST(testAddContact_EmptyRoster); + CPPUNIT_TEST_SUITE_END(); + + public: + void setUp() { + timerFactory = new DummyTimerFactory(); + roster = new Roster(); + jid1 = JID("jid1@example.com"); + jid2 = JID("jid2@example.com"); + } + + void tearDown() { + delete roster; + delete timerFactory; + } + + void testAddContact_EmptyRoster() { + /* + boost::shared_ptr<TableRoster> tableRoster(createTestling()); + + addContact(jid1, "1", "group1"); + + CPPUNIT_ASSERT_EQUAL(4, static_cast<int>(events.size())); + CPPUNIT_ASSERT(boost::get<BeginUpdatesEvent>(&events[0])); + CPPUNIT_ASSERT(boost::get<SectionsInsertedEvent>(&events[1])); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(boost::get<SectionsInsertedEvent>(events[1]).sections.size())); + CPPUNIT_ASSERT_EQUAL(0, static_cast<int>(boost::get<SectionsInsertedEvent>(events[1]).sections[0])); + CPPUNIT_ASSERT(boost::get<RowsInsertedEvent>(&events[2])); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(boost::get<RowsInsertedEvent>(events[2]).rows.size())); + CPPUNIT_ASSERT_EQUAL(TableRoster::Index(0, 0), boost::get<RowsInsertedEvent>(events[2]).rows[0]); + CPPUNIT_ASSERT(boost::get<EndUpdatesEvent>(&events[3])); + + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(tableRoster->getNumberOfSections())); + CPPUNIT_ASSERT_EQUAL(std::string("group1"), tableRoster->getSectionTitle(0)); + CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(tableRoster->getNumberOfRowsInSection(0))); + CPPUNIT_ASSERT_EQUAL(jid1, tableRoster->getItem(TableRoster::Index(0, 0)).jid); + */ + } + + private: + void addContact(const JID& jid, const std::string& name, const std::string& group) { + roster->addContact(jid, JID(), name, group, ""); + } + + TableRoster* createTestling() { + TableRoster* result = new TableRoster(roster, timerFactory, 10); + result->onUpdate.connect(boost::bind(&TableRosterTest::handleUpdate, this, _1)); + return result; + } + + void handleUpdate(const TableRoster::Update& update) { + updates.push_back(update); + } + + private: + DummyTimerFactory* timerFactory; + Roster* roster; + JID jid1; + JID jid2; + std::vector<TableRoster::Update> updates; +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TableRosterTest); + diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 61da9fb..031e93a 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -37,6 +37,7 @@ if env["SCONS_STAGE"] == "build" : "Roster/GroupRosterItem.cpp", "Roster/RosterItem.cpp", "Roster/Roster.cpp", + "Roster/TableRoster.cpp", "EventWindowController.cpp", "SoundEventController.cpp", "SystemTrayController.cpp", @@ -44,24 +45,36 @@ if env["SCONS_STAGE"] == "build" : "StatusTracker.cpp", "PresenceNotifier.cpp", "EventNotifier.cpp", + "AdHocManager.cpp", "XMPPEvents/EventController.cpp", "UIEvents/UIEvent.cpp", "UIInterfaces/XMLConsoleWidget.cpp", "UIInterfaces/ChatListWindow.cpp", "PreviousStatusStore.cpp", - "CertificateStorageFactory.cpp", - "CertificateStorage.cpp", - "CertificateFileStorage.cpp", + "Storages/CertificateStorageFactory.cpp", + "Storages/CertificateStorage.cpp", + "Storages/CertificateFileStorage.cpp", + "Storages/CertificateMemoryStorage.cpp", + "Storages/AvatarFileStorage.cpp", + "Storages/FileStorages.cpp", + "Storages/RosterFileStorage.cpp", + "Storages/CapsFileStorage.cpp", + "Storages/VCardFileStorage.cpp", "StatusUtil.cpp", "Translator.cpp", + "XMPPURIController.cpp", + "ChatMessageSummarizer.cpp", ]) env.Append(UNITTEST_SOURCES = [ File("Roster/UnitTest/RosterControllerTest.cpp"), File("Roster/UnitTest/RosterTest.cpp"), + File("Roster/UnitTest/LeastCommonSubsequenceTest.cpp"), + File("Roster/UnitTest/TableRosterTest.cpp"), File("UnitTest/PreviousStatusStoreTest.cpp"), File("UnitTest/PresenceNotifierTest.cpp"), File("Chat/UnitTest/ChatsManagerTest.cpp"), File("Chat/UnitTest/MUCControllerTest.cpp"), File("UnitTest/MockChatWindow.cpp"), + File("UnitTest/ChatMessageSummarizerTest.cpp"), ]) diff --git a/Swift/Controllers/Storages/AvatarFileStorage.cpp b/Swift/Controllers/Storages/AvatarFileStorage.cpp new file mode 100644 index 0000000..b39e586 --- /dev/null +++ b/Swift/Controllers/Storages/AvatarFileStorage.cpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/Storages/AvatarFileStorage.h> + +#include <iostream> +#include <boost/filesystem/fstream.hpp> +#include <boost/filesystem.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Base/String.h> +#include <Swiften/StringCodecs/SHA1.h> +#include <Swiften/StringCodecs/Hexify.h> + +namespace Swift { + +AvatarFileStorage::AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile) : avatarsDir(avatarsDir), avatarsFile(avatarsFile) { + if (boost::filesystem::exists(avatarsFile)) { + try { + boost::filesystem::ifstream file(avatarsFile); + std::string line; + if (file.is_open()) { + while (!file.eof()) { + getline(file, line); + std::pair<std::string, std::string> r = String::getSplittedAtFirst(line, ' '); + JID jid(r.second); + if (jid.isValid()) { + jidAvatars.insert(std::make_pair(jid, r.first)); + } + else if (!r.first.empty() || !r.second.empty()) { + std::cerr << "Invalid entry in avatars file: " << r.second << std::endl; + } + } + } + } + catch (...) { + std::cerr << "Error reading avatars file" << std::endl; + } + } +} + +bool AvatarFileStorage::hasAvatar(const std::string& hash) const { + return boost::filesystem::exists(getAvatarPath(hash)); +} + +void AvatarFileStorage::addAvatar(const std::string& hash, const ByteArray& avatar) { + assert(Hexify::hexify(SHA1::getHash(avatar)) == hash); + + boost::filesystem::path avatarPath = getAvatarPath(hash); + if (!boost::filesystem::exists(avatarPath.parent_path())) { + try { + boost::filesystem::create_directories(avatarPath.parent_path()); + } + catch (const boost::filesystem::filesystem_error& e) { + std::cerr << "ERROR: " << e.what() << std::endl; + } + } + boost::filesystem::ofstream file(avatarPath, boost::filesystem::ofstream::binary|boost::filesystem::ofstream::out); + file.write(reinterpret_cast<const char*>(vecptr(avatar)), static_cast<std::streamsize>(avatar.size())); + file.close(); +} + +boost::filesystem::path AvatarFileStorage::getAvatarPath(const std::string& hash) const { + return avatarsDir / hash; +} + +ByteArray AvatarFileStorage::getAvatar(const std::string& hash) const { + ByteArray data; + readByteArrayFromFile(data, getAvatarPath(hash).string()); + return data; +} + +void AvatarFileStorage::setAvatarForJID(const JID& jid, const std::string& hash) { + std::pair<JIDAvatarMap::iterator, bool> r = jidAvatars.insert(std::make_pair(jid, hash)); + if (r.second) { + saveJIDAvatars(); + } + else if (r.first->second != hash) { + r.first->second = hash; + saveJIDAvatars(); + } +} + +std::string AvatarFileStorage::getAvatarForJID(const JID& jid) const { + JIDAvatarMap::const_iterator i = jidAvatars.find(jid); + return i == jidAvatars.end() ? "" : i->second; +} + +void AvatarFileStorage::saveJIDAvatars() { + try { + boost::filesystem::ofstream file(avatarsFile); + for (JIDAvatarMap::const_iterator i = jidAvatars.begin(); i != jidAvatars.end(); ++i) { + file << i->second << " " << i->first.toString() << std::endl; + } + file.close(); + } + catch (...) { + std::cerr << "Error writing avatars file" << std::endl; + } +} + +} diff --git a/Swift/Controllers/Storages/AvatarFileStorage.h b/Swift/Controllers/Storages/AvatarFileStorage.h new file mode 100644 index 0000000..b7e73f5 --- /dev/null +++ b/Swift/Controllers/Storages/AvatarFileStorage.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <map> +#include <string> +#include <boost/filesystem/path.hpp> + +#include <Swiften/JID/JID.h> +#include "Swiften/Base/ByteArray.h" +#include "Swiften/Avatars/AvatarStorage.h" + +namespace Swift { + class AvatarFileStorage : public AvatarStorage { + public: + AvatarFileStorage(const boost::filesystem::path& avatarsDir, const boost::filesystem::path& avatarsFile); + + virtual bool hasAvatar(const std::string& hash) const; + virtual void addAvatar(const std::string& hash, const ByteArray& avatar); + virtual ByteArray getAvatar(const std::string& hash) const; + + virtual boost::filesystem::path getAvatarPath(const std::string& hash) const; + + virtual void setAvatarForJID(const JID& jid, const std::string& hash); + virtual std::string getAvatarForJID(const JID& jid) const; + + private: + void saveJIDAvatars(); + + private: + boost::filesystem::path avatarsDir; + boost::filesystem::path avatarsFile; + typedef std::map<JID, std::string> JIDAvatarMap; + JIDAvatarMap jidAvatars; + }; + +} diff --git a/Swift/Controllers/Storages/CapsFileStorage.cpp b/Swift/Controllers/Storages/CapsFileStorage.cpp new file mode 100644 index 0000000..b7593fd --- /dev/null +++ b/Swift/Controllers/Storages/CapsFileStorage.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swift/Controllers/Storages/CapsFileStorage.h" + +#include <Swiften/Entity/GenericPayloadPersister.h> +#include "Swiften/Serializer/PayloadSerializers/DiscoInfoSerializer.h" +#include "Swiften/Parser/PayloadParsers/DiscoInfoParser.h" +#include "Swiften/StringCodecs/Hexify.h" +#include "Swiften/StringCodecs/Base64.h" + +using namespace Swift; + +typedef GenericPayloadPersister<DiscoInfo, DiscoInfoParser, DiscoInfoSerializer> DiscoInfoPersister; + +CapsFileStorage::CapsFileStorage(const boost::filesystem::path& path) : path(path) { +} + +DiscoInfo::ref CapsFileStorage::getDiscoInfo(const std::string& hash) const { + return DiscoInfoPersister().loadPayloadGeneric(getCapsPath(hash)); +} + +void CapsFileStorage::setDiscoInfo(const std::string& hash, DiscoInfo::ref discoInfo) { + DiscoInfo::ref bareDiscoInfo(new DiscoInfo(*discoInfo.get())); + bareDiscoInfo->setNode(""); + DiscoInfoPersister().savePayload(bareDiscoInfo, getCapsPath(hash)); +} + +boost::filesystem::path CapsFileStorage::getCapsPath(const std::string& hash) const { + return path / (Hexify::hexify(Base64::decode(hash)) + ".xml"); +} diff --git a/Swift/Controllers/Storages/CapsFileStorage.h b/Swift/Controllers/Storages/CapsFileStorage.h new file mode 100644 index 0000000..b3757e0 --- /dev/null +++ b/Swift/Controllers/Storages/CapsFileStorage.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/filesystem/path.hpp> + +#include "Swiften/Disco/CapsStorage.h" +#include <string> + +namespace Swift { + class CapsFileStorage : public CapsStorage { + public: + CapsFileStorage(const boost::filesystem::path& path); + + virtual DiscoInfo::ref getDiscoInfo(const std::string& hash) const; + virtual void setDiscoInfo(const std::string& hash, DiscoInfo::ref discoInfo); + + private: + boost::filesystem::path getCapsPath(const std::string& hash) const; + + private: + boost::filesystem::path path; + }; +} diff --git a/Swift/Controllers/CertificateFileStorage.cpp b/Swift/Controllers/Storages/CertificateFileStorage.cpp index cf924ee..a4a95c7 100644 --- a/Swift/Controllers/CertificateFileStorage.cpp +++ b/Swift/Controllers/Storages/CertificateFileStorage.cpp @@ -4,7 +4,7 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include <Swift/Controllers/CertificateFileStorage.h> +#include <Swift/Controllers/Storages/CertificateFileStorage.h> #include <iostream> #include <boost/filesystem/fstream.hpp> @@ -23,7 +23,7 @@ bool CertificateFileStorage::hasCertificate(Certificate::ref certificate) const boost::filesystem::path certificatePath = getCertificatePath(certificate); if (boost::filesystem::exists(certificatePath)) { ByteArray data; - data.readFromFile(certificatePath.string()); + readByteArrayFromFile(data, certificatePath.string()); Certificate::ref storedCertificate = certificateFactory->createCertificateFromDER(data); if (storedCertificate && storedCertificate->toDER() == certificate->toDER()) { return true; @@ -50,7 +50,7 @@ void CertificateFileStorage::addCertificate(Certificate::ref certificate) { } boost::filesystem::ofstream file(certificatePath, boost::filesystem::ofstream::binary|boost::filesystem::ofstream::out); ByteArray data = certificate->toDER(); - file.write(reinterpret_cast<const char*>(data.getData()), data.getSize()); + file.write(reinterpret_cast<const char*>(vecptr(data)), data.size()); file.close(); } diff --git a/Swift/Controllers/CertificateFileStorage.h b/Swift/Controllers/Storages/CertificateFileStorage.h index 2b853ed..f7a60b9 100644 --- a/Swift/Controllers/CertificateFileStorage.h +++ b/Swift/Controllers/Storages/CertificateFileStorage.h @@ -8,7 +8,7 @@ #include <boost/filesystem.hpp> -#include "Swift/Controllers/CertificateStorage.h" +#include "Swift/Controllers/Storages/CertificateStorage.h" namespace Swift { class CertificateFactory; diff --git a/Swift/Controllers/CertificateFileStorageFactory.h b/Swift/Controllers/Storages/CertificateFileStorageFactory.h index 7ed8287..b215165 100644 --- a/Swift/Controllers/CertificateFileStorageFactory.h +++ b/Swift/Controllers/Storages/CertificateFileStorageFactory.h @@ -6,8 +6,8 @@ #pragma once -#include <Swift/Controllers/CertificateStorageFactory.h> -#include <Swift/Controllers/CertificateFileStorage.h> +#include <Swift/Controllers/Storages/CertificateStorageFactory.h> +#include <Swift/Controllers/Storages/CertificateFileStorage.h> namespace Swift { class CertificateFactory; diff --git a/Swift/Controllers/Storages/CertificateMemoryStorage.cpp b/Swift/Controllers/Storages/CertificateMemoryStorage.cpp new file mode 100644 index 0000000..71d7c4a --- /dev/null +++ b/Swift/Controllers/Storages/CertificateMemoryStorage.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/Storages/CertificateMemoryStorage.h> + +#include <Swiften/Base/foreach.h> + +using namespace Swift; + +CertificateMemoryStorage::CertificateMemoryStorage() { +} + +bool CertificateMemoryStorage::hasCertificate(Certificate::ref certificate) const { + foreach(Certificate::ref storedCert, certificates) { + if (storedCert->toDER() == certificate->toDER()) { + return true; + } + } + return false; +} + +void CertificateMemoryStorage::addCertificate(Certificate::ref certificate) { + certificates.push_back(certificate); +} diff --git a/Swift/Controllers/Storages/CertificateMemoryStorage.h b/Swift/Controllers/Storages/CertificateMemoryStorage.h new file mode 100644 index 0000000..5c0333d --- /dev/null +++ b/Swift/Controllers/Storages/CertificateMemoryStorage.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <vector> + +#include <Swift/Controllers/Storages/CertificateStorage.h> + +namespace Swift { + class CertificateMemoryStorage : public CertificateStorage { + public: + CertificateMemoryStorage(); + + virtual bool hasCertificate(Certificate::ref certificate) const; + virtual void addCertificate(Certificate::ref certificate); + + private: + std::vector<Certificate::ref> certificates; + }; + +} diff --git a/Swift/Controllers/CertificateStorage.cpp b/Swift/Controllers/Storages/CertificateStorage.cpp index 343fccd..ee942c0 100644 --- a/Swift/Controllers/CertificateStorage.cpp +++ b/Swift/Controllers/Storages/CertificateStorage.cpp @@ -4,7 +4,7 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swift/Controllers/CertificateStorage.h" +#include "Swift/Controllers/Storages/CertificateStorage.h" namespace Swift { diff --git a/Swift/Controllers/CertificateStorage.h b/Swift/Controllers/Storages/CertificateStorage.h index f8c6fb5..f8c6fb5 100644 --- a/Swift/Controllers/CertificateStorage.h +++ b/Swift/Controllers/Storages/CertificateStorage.h diff --git a/Swift/Controllers/CertificateStorageFactory.cpp b/Swift/Controllers/Storages/CertificateStorageFactory.cpp index 613a8c3..ba0179a 100644 --- a/Swift/Controllers/CertificateStorageFactory.cpp +++ b/Swift/Controllers/Storages/CertificateStorageFactory.cpp @@ -4,7 +4,7 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include <Swift/Controllers/CertificateStorageFactory.h> +#include <Swift/Controllers/Storages/CertificateStorageFactory.h> namespace Swift { diff --git a/Swift/Controllers/CertificateStorageFactory.h b/Swift/Controllers/Storages/CertificateStorageFactory.h index 5b85757..5b85757 100644 --- a/Swift/Controllers/CertificateStorageFactory.h +++ b/Swift/Controllers/Storages/CertificateStorageFactory.h diff --git a/Swift/Controllers/CertificateStorageTrustChecker.h b/Swift/Controllers/Storages/CertificateStorageTrustChecker.h index f33287c..40838dd 100644 --- a/Swift/Controllers/CertificateStorageTrustChecker.h +++ b/Swift/Controllers/Storages/CertificateStorageTrustChecker.h @@ -7,7 +7,7 @@ #pragma once #include <Swiften/TLS/CertificateTrustChecker.h> -#include <Swift/Controllers/CertificateStorage.h> +#include <Swift/Controllers/Storages/CertificateStorage.h> namespace Swift { /** diff --git a/Swift/Controllers/Storages/FileStorages.cpp b/Swift/Controllers/Storages/FileStorages.cpp new file mode 100644 index 0000000..6447099 --- /dev/null +++ b/Swift/Controllers/Storages/FileStorages.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swift/Controllers/Storages/FileStorages.h" +#include "Swift/Controllers/Storages/VCardFileStorage.h" +#include "Swift/Controllers/Storages/AvatarFileStorage.h" +#include "Swift/Controllers/Storages/CapsFileStorage.h" +#include "Swift/Controllers/Storages/RosterFileStorage.h" + +namespace Swift { + +FileStorages::FileStorages(const boost::filesystem::path& baseDir, const JID& jid) { + std::string profile = jid.toBare(); + vcardStorage = new VCardFileStorage(baseDir / profile / "vcards"); + capsStorage = new CapsFileStorage(baseDir / "caps"); + avatarStorage = new AvatarFileStorage(baseDir / "avatars", baseDir / profile / "avatars"); + rosterStorage = new RosterFileStorage(baseDir / profile / "roster.xml"); +} + +FileStorages::~FileStorages() { + delete rosterStorage; + delete avatarStorage; + delete capsStorage; + delete vcardStorage; +} + +VCardStorage* FileStorages::getVCardStorage() const { + return vcardStorage; +} + +CapsStorage* FileStorages::getCapsStorage() const { + return capsStorage; +} + +AvatarStorage* FileStorages::getAvatarStorage() const { + return avatarStorage; +} + +RosterStorage* FileStorages::getRosterStorage() const { + return rosterStorage; +} + +} diff --git a/Swift/Controllers/Storages/FileStorages.h b/Swift/Controllers/Storages/FileStorages.h new file mode 100644 index 0000000..28df314 --- /dev/null +++ b/Swift/Controllers/Storages/FileStorages.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/filesystem/path.hpp> + +#include "Swiften/Client/Storages.h" + +namespace Swift { + class VCardFileStorage; + class AvatarFileStorage; + class CapsFileStorage; + class RosterFileStorage; + class JID; + + /** + * A storages implementation that stores all controller data on disk. + */ + class FileStorages : public Storages { + public: + /** + * Creates the storages interface. + * + * All data will be stored relative to a base directory, and + * for some controllers, in a subdirectory for the given profile. + * The data is stored in the following places: + * - Avatars: <basedir>/avatars + * - VCards: <basedir>/<profile>/vcards + * - Entity capabilities: <basedir>/caps + * + * \param baseDir the base dir to store data relative to + * \param jid the subdir in which profile-specific data will be stored. + * The bare JID will be used as the subdir name. + */ + FileStorages(const boost::filesystem::path& baseDir, const JID& jid); + ~FileStorages(); + + virtual VCardStorage* getVCardStorage() const; + virtual AvatarStorage* getAvatarStorage() const; + virtual CapsStorage* getCapsStorage() const; + virtual RosterStorage* getRosterStorage() const; + + private: + VCardFileStorage* vcardStorage; + AvatarFileStorage* avatarStorage; + CapsFileStorage* capsStorage; + RosterFileStorage* rosterStorage; + }; +} diff --git a/Swift/Controllers/FileStoragesFactory.h b/Swift/Controllers/Storages/FileStoragesFactory.h index bd7cdfb..0676bc3 100644 --- a/Swift/Controllers/FileStoragesFactory.h +++ b/Swift/Controllers/Storages/FileStoragesFactory.h @@ -6,8 +6,8 @@ #pragma once -#include "Swift/Controllers/StoragesFactory.h" -#include "Swiften/Client/FileStorages.h" +#include "Swift/Controllers/Storages/StoragesFactory.h" +#include "Swift/Controllers/Storages/FileStorages.h" namespace Swift { class FileStoragesFactory : public StoragesFactory { diff --git a/Swift/Controllers/MemoryStoragesFactory.h b/Swift/Controllers/Storages/MemoryStoragesFactory.h index 8408e10..0dea349 100644 --- a/Swift/Controllers/MemoryStoragesFactory.h +++ b/Swift/Controllers/Storages/MemoryStoragesFactory.h @@ -6,10 +6,12 @@ #pragma once -#include "Swift/Controllers/StoragesFactory.h" +#include "Swift/Controllers/Storages/StoragesFactory.h" #include "Swiften/Client/MemoryStorages.h" namespace Swift { + class JID; + class MemoryStoragesFactory : public StoragesFactory { public: MemoryStoragesFactory() {} diff --git a/Swift/Controllers/Storages/RosterFileStorage.cpp b/Swift/Controllers/Storages/RosterFileStorage.cpp new file mode 100644 index 0000000..73e582f --- /dev/null +++ b/Swift/Controllers/Storages/RosterFileStorage.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/Storages/RosterFileStorage.h> + +#include <Swiften/Entity/GenericPayloadPersister.h> +#include <Swiften/Serializer/PayloadSerializers/RosterSerializer.h> +#include <Swiften/Parser/PayloadParsers/RosterParser.h> + +using namespace Swift; + +typedef GenericPayloadPersister<RosterPayload, RosterParser, RosterSerializer> RosterPersister; + +RosterFileStorage::RosterFileStorage(const boost::filesystem::path& path) : path(path) { +} + +boost::shared_ptr<RosterPayload> RosterFileStorage::getRoster() const { + return RosterPersister().loadPayloadGeneric(path); +} + +void RosterFileStorage::setRoster(boost::shared_ptr<RosterPayload> roster) { + RosterPersister().savePayload(roster, path); +} diff --git a/Swift/Controllers/Storages/RosterFileStorage.h b/Swift/Controllers/Storages/RosterFileStorage.h new file mode 100644 index 0000000..cb00969 --- /dev/null +++ b/Swift/Controllers/Storages/RosterFileStorage.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/filesystem/path.hpp> + +#include <Swiften/Roster/RosterStorage.h> + +namespace Swift { + class RosterFileStorage : public RosterStorage { + public: + RosterFileStorage(const boost::filesystem::path& path); + + virtual boost::shared_ptr<RosterPayload> getRoster() const; + virtual void setRoster(boost::shared_ptr<RosterPayload>); + + private: + boost::filesystem::path path; + }; +} diff --git a/Swift/Controllers/StoragesFactory.h b/Swift/Controllers/Storages/StoragesFactory.h index 441a4e9..203f9c9 100644 --- a/Swift/Controllers/StoragesFactory.h +++ b/Swift/Controllers/Storages/StoragesFactory.h @@ -8,6 +8,7 @@ namespace Swift { class Storages; + class JID; class StoragesFactory { public: diff --git a/Swift/Controllers/Storages/VCardFileStorage.cpp b/Swift/Controllers/Storages/VCardFileStorage.cpp new file mode 100644 index 0000000..5f657a4 --- /dev/null +++ b/Swift/Controllers/Storages/VCardFileStorage.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "Swift/Controllers/Storages/VCardFileStorage.h" + +#include <boost/filesystem/fstream.hpp> +#include <boost/filesystem.hpp> +#include <iostream> + +#include <Swiften/Entity/GenericPayloadPersister.h> +#include <Swiften/Base/String.h> +#include <Swiften/StringCodecs/Hexify.h> +#include <Swiften/StringCodecs/SHA1.h> +#include <Swiften/Base/foreach.h> +#include "Swiften/JID/JID.h" +#include "Swiften/Elements/VCard.h" +#include "Swiften/Serializer/PayloadSerializers/VCardSerializer.h" +#include "Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h" +#include "Swiften/Parser/PayloadParsers/VCardParser.h" + +using namespace Swift; + +typedef GenericPayloadPersister<VCard, VCardParser, VCardSerializer> VCardPersister; + +VCardFileStorage::VCardFileStorage(boost::filesystem::path dir) : vcardsPath(dir) { + cacheFile = vcardsPath / "phashes"; + if (boost::filesystem::exists(cacheFile)) { + try { + boost::filesystem::ifstream file(cacheFile); + std::string line; + if (file.is_open()) { + while (!file.eof()) { + getline(file, line); + std::pair<std::string, std::string> r = String::getSplittedAtFirst(line, ' '); + JID jid(r.second); + if (jid.isValid()) { + photoHashes.insert(std::make_pair(jid, r.first)); + } + else if (!r.first.empty() || !r.second.empty()) { + std::cerr << "Invalid entry in phashes file" << std::endl; + } + } + } + } + catch (...) { + std::cerr << "Error reading phashes file" << std::endl; + } + } +} + +boost::shared_ptr<VCard> VCardFileStorage::getVCard(const JID& jid) const { + return VCardPersister().loadPayloadGeneric(getVCardPath(jid)); +} + +void VCardFileStorage::setVCard(const JID& jid, VCard::ref v) { + VCardPersister().savePayload(v, getVCardPath(jid)); + getAndUpdatePhotoHash(jid, v); +} + +boost::filesystem::path VCardFileStorage::getVCardPath(const JID& jid) const { + std::string file(jid.toString()); + String::replaceAll(file, '/', "%2f"); + return boost::filesystem::path(vcardsPath / (file + ".xml")); +} + +std::string VCardFileStorage::getPhotoHash(const JID& jid) const { + PhotoHashMap::const_iterator i = photoHashes.find(jid); + if (i != photoHashes.end()) { + return i->second; + } + else { + VCard::ref vCard = getVCard(jid); + return getAndUpdatePhotoHash(jid, vCard); + } +} + +std::string VCardFileStorage::getAndUpdatePhotoHash(const JID& jid, VCard::ref vCard) const { + std::string hash; + if (vCard && !vCard->getPhoto().empty()) { + hash = Hexify::hexify(SHA1::getHash(vCard->getPhoto())); + } + std::pair<PhotoHashMap::iterator, bool> r = photoHashes.insert(std::make_pair(jid, hash)); + if (r.second) { + savePhotoHashes(); + } + else if (r.first->second != hash) { + r.first->second = hash; + savePhotoHashes(); + } + return hash; +} + +void VCardFileStorage::savePhotoHashes() const { + try { + boost::filesystem::ofstream file(cacheFile); + for (PhotoHashMap::const_iterator i = photoHashes.begin(); i != photoHashes.end(); ++i) { + file << i->second << " " << i->first.toString() << std::endl; + } + file.close(); + } + catch (...) { + std::cerr << "Error writing vcards file" << std::endl; + } +} diff --git a/Swift/Controllers/Storages/VCardFileStorage.h b/Swift/Controllers/Storages/VCardFileStorage.h new file mode 100644 index 0000000..ba422f4 --- /dev/null +++ b/Swift/Controllers/Storages/VCardFileStorage.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2010 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> +#include <boost/filesystem/path.hpp> +#include <string> +#include <map> + +#include "Swiften/VCards/VCardStorage.h" + +namespace Swift { + class VCardFileStorage : public VCardStorage { + public: + VCardFileStorage(boost::filesystem::path dir); + + virtual VCard::ref getVCard(const JID& jid) const; + virtual void setVCard(const JID& jid, VCard::ref v); + + virtual std::string getPhotoHash(const JID&) const; + + private: + boost::filesystem::path getVCardPath(const JID&) const; + + std::string getAndUpdatePhotoHash(const JID& jid, VCard::ref vcard) const; + void savePhotoHashes() const; + + private: + boost::filesystem::path vcardsPath; + boost::filesystem::path cacheFile; + typedef std::map<JID, std::string> PhotoHashMap; + mutable PhotoHashMap photoHashes; + }; +} diff --git a/Swift/Controllers/SystemTrayController.cpp b/Swift/Controllers/SystemTrayController.cpp index 598771c..31fd4ff 100644 --- a/Swift/Controllers/SystemTrayController.cpp +++ b/Swift/Controllers/SystemTrayController.cpp @@ -22,7 +22,7 @@ SystemTrayController::SystemTrayController(EventController* eventController, Sys void SystemTrayController::handleEventQueueLengthChange(int /*length*/) { EventList events = eventController_->getEvents(); bool found = false; - for (EventList::iterator it = events.begin(); it != events.end(); it++) { + for (EventList::iterator it = events.begin(); it != events.end(); ++it) { if (boost::dynamic_pointer_cast<MessageEvent>(*it)) { found = true; break; diff --git a/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h b/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h new file mode 100644 index 0000000..c3b4b49 --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestAdHocUIEvent.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIInterfaces/MainWindow.h> + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + class RequestAdHocUIEvent : public UIEvent { + public: + RequestAdHocUIEvent(const DiscoItems::Item& command) : command_(command) {}; + const DiscoItems::Item& getCommand() const {return command_;} + private: + DiscoItems::Item command_; + }; +} diff --git a/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h b/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h index dd2ff6c..2c7b105 100644 --- a/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h +++ b/Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h @@ -6,18 +6,25 @@ #pragma once -#include <boost/optional.hpp> #include <boost/shared_ptr.hpp> - #include <string> + #include <Swift/Controllers/UIEvents/UIEvent.h> +#include <Swiften/JID/JID.h> namespace Swift { class RequestJoinMUCUIEvent : public UIEvent { public: typedef boost::shared_ptr<RequestJoinMUCUIEvent> ref; - RequestJoinMUCUIEvent() { + RequestJoinMUCUIEvent(const JID& room = JID()) : room(room) { } + + const JID& getRoom() const { + return room; + } + + private: + JID room; }; } diff --git a/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h b/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h new file mode 100644 index 0000000..f7a5d39 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/AdHocCommandWindow.h @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +namespace Swift { + class AdHocCommandWindow { + public: + virtual ~AdHocCommandWindow() {}; + }; +} diff --git a/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h b/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h new file mode 100644 index 0000000..ae77180 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2010-2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIInterfaces/AdHocCommandWindow.h> +#include <Swiften/AdHoc/OutgoingAdHocCommandSession.h> + +namespace Swift { + class AdHocCommandWindowFactory { + public: + virtual ~AdHocCommandWindowFactory() {} + /** + * The UI should deal with the lifetime of this window (i.e. DeleteOnClose), + * so the result isn't returned. + */ + virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) = 0; + }; +} diff --git a/Swift/Controllers/UIInterfaces/ChatListWindow.h b/Swift/Controllers/UIInterfaces/ChatListWindow.h index a2a0874..85700cb 100644 --- a/Swift/Controllers/UIInterfaces/ChatListWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatListWindow.h @@ -1,23 +1,59 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ #pragma once +#include <list> #include <boost/shared_ptr.hpp> +#include <Swiften/MUC/MUCBookmark.h> +#include <Swiften/Elements/StatusShow.h> +#include <boost/filesystem/path.hpp> -#include "Swiften/MUC/MUCBookmark.h" +#include <Swiften/Base/boost_bsignals.h> namespace Swift { class ChatListWindow { public: + class Chat { + public: + Chat(const JID& jid, const std::string& chatName, const std::string& activity, int unreadCount, StatusShow::Type statusType, const boost::filesystem::path& avatarPath, bool isMUC, const std::string& nick = "") + : jid(jid), chatName(chatName), activity(activity), statusType(statusType), isMUC(isMUC), nick(nick), unreadCount(unreadCount), avatarPath(avatarPath) {} + /** Assume that nicks and other transient features aren't important for equality */ + bool operator==(const Chat& other) const { + return jid.toBare() == other.jid.toBare() + && isMUC == other.isMUC; + }; + void setUnreadCount(int unread) { + unreadCount = unread; + } + void setStatusType(StatusShow::Type type) { + statusType = type; + } + void setAvatarPath(const boost::filesystem::path& path) { + avatarPath = path; + } + JID jid; + std::string chatName; + std::string activity; + StatusShow::Type statusType; + bool isMUC; + std::string nick; + int unreadCount; + boost::filesystem::path avatarPath; + }; virtual ~ChatListWindow(); virtual void setBookmarksEnabled(bool enabled) = 0; virtual void addMUCBookmark(const MUCBookmark& bookmark) = 0; virtual void removeMUCBookmark(const MUCBookmark& bookmark) = 0; - virtual void clear() = 0; + virtual void setRecents(const std::list<Chat>& recents) = 0; + virtual void setUnreadCount(int unread) = 0; + virtual void clearBookmarks() = 0; + + boost::signal<void (const MUCBookmark&)> onMUCBookmarkActivated; + boost::signal<void (const Chat&)> onRecentActivated; }; } diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h index c7bcf1e..f65328d 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatWindow.h @@ -40,6 +40,7 @@ namespace Swift { virtual void addSystemMessage(const std::string& message) = 0; virtual void addPresenceMessage(const std::string& message) = 0; virtual void addErrorMessage(const std::string& message) = 0; + virtual void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) = 0; virtual void setContactChatState(ChatState::ChatStateType state) = 0; virtual void setName(const std::string& name) = 0; @@ -61,7 +62,8 @@ namespace Swift { boost::signal<void ()> onClosed; boost::signal<void ()> onAllMessagesRead; - boost::signal<void (const std::string&)> onSendMessageRequest; + boost::signal<void (const std::string&, bool isCorrection)> onSendMessageRequest; + boost::signal<void ()> onSendCorrectionMessageRequest; boost::signal<void ()> onUserTyping; boost::signal<void ()> onUserCancelsTyping; }; diff --git a/Swift/Controllers/UIInterfaces/EventWindow.h b/Swift/Controllers/UIInterfaces/EventWindow.h index e756655..3ca2c82 100644 --- a/Swift/Controllers/UIInterfaces/EventWindow.h +++ b/Swift/Controllers/UIInterfaces/EventWindow.h @@ -6,7 +6,8 @@ #pragma once -#include "boost/shared_ptr.hpp" +#include <boost/shared_ptr.hpp> + #include "Swift/Controllers/XMPPEvents/StanzaEvent.h" namespace Swift { diff --git a/Swift/Controllers/UIInterfaces/LoginWindow.h b/Swift/Controllers/UIInterfaces/LoginWindow.h index 61fcaa1..fcaeb42 100644 --- a/Swift/Controllers/UIInterfaces/LoginWindow.h +++ b/Swift/Controllers/UIInterfaces/LoginWindow.h @@ -27,6 +27,8 @@ namespace Swift { boost::signal<void (const std::string&, const std::string&, const std::string& /* certificateFile */, bool /* remember password*/, bool /* login automatically */)> onLoginRequest; virtual void setLoginAutomatically(bool loginAutomatically) = 0; virtual void quit() = 0; + /** Disable any GUI elements that suggest saving of passwords. */ + virtual void setRememberingAllowed(bool allowed) = 0; /** Blocking request whether a cert should be permanently trusted.*/ virtual bool askUserToTrustCertificatePermanently(const std::string& message, Certificate::ref) = 0; diff --git a/Swift/Controllers/UIInterfaces/MainWindow.h b/Swift/Controllers/UIInterfaces/MainWindow.h index 2fd463b..93584e7 100644 --- a/Swift/Controllers/UIInterfaces/MainWindow.h +++ b/Swift/Controllers/UIInterfaces/MainWindow.h @@ -9,6 +9,7 @@ #include <string> #include "Swiften/JID/JID.h" #include "Swiften/Elements/StatusShow.h" +#include "Swiften/Elements/DiscoItems.h" #include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> @@ -33,6 +34,7 @@ namespace Swift { /** Must be able to cope with NULL to clear the roster */ virtual void setRosterModel(Roster* roster) = 0; virtual void setConnecting() = 0; + virtual void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands) = 0; boost::signal<void (StatusShow::Type, const std::string&)> onChangeStatusRequest; boost::signal<void ()> onSignOutRequest; diff --git a/Swift/Controllers/UIInterfaces/UIFactory.h b/Swift/Controllers/UIInterfaces/UIFactory.h index 9b36ac5..57f55d0 100644 --- a/Swift/Controllers/UIInterfaces/UIFactory.h +++ b/Swift/Controllers/UIInterfaces/UIFactory.h @@ -17,6 +17,7 @@ #include <Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h> #include <Swift/Controllers/UIInterfaces/ProfileWindowFactory.h> #include <Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h> namespace Swift { class UIFactory : @@ -30,7 +31,8 @@ namespace Swift { public UserSearchWindowFactory, public JoinMUCWindowFactory, public ProfileWindowFactory, - public ContactEditWindowFactory { + public ContactEditWindowFactory, + public AdHocCommandWindowFactory { public: virtual ~UIFactory() {} }; diff --git a/Swift/Controllers/UIInterfaces/XMLConsoleWidget.h b/Swift/Controllers/UIInterfaces/XMLConsoleWidget.h index 3cd0947..caec38c 100644 --- a/Swift/Controllers/UIInterfaces/XMLConsoleWidget.h +++ b/Swift/Controllers/UIInterfaces/XMLConsoleWidget.h @@ -6,15 +6,15 @@ #pragma once -#include <string> +#include <Swiften/Base/SafeByteArray.h> namespace Swift { class XMLConsoleWidget { public: virtual ~XMLConsoleWidget(); - virtual void handleDataRead(const std::string& data) = 0; - virtual void handleDataWritten(const std::string& data) = 0; + virtual void handleDataRead(const SafeByteArray& data) = 0; + virtual void handleDataWritten(const SafeByteArray& data) = 0; virtual void show() = 0; virtual void activate() = 0; diff --git a/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp b/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp new file mode 100644 index 0000000..ee0ee9f --- /dev/null +++ b/Swift/Controllers/UnitTest/ChatMessageSummarizerTest.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.h> + +#include "Swift/Controllers/ChatMessageSummarizer.h" + +using namespace Swift; +using namespace std; + +class ChatMessageSummarizerTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(ChatMessageSummarizerTest); + CPPUNIT_TEST(testEmpty); + CPPUNIT_TEST(testCurrentNone); + CPPUNIT_TEST(testCurrentCount); + CPPUNIT_TEST(testCurrentCountOthersNone); + CPPUNIT_TEST(testCurrentCountOtherCount); + CPPUNIT_TEST(testCurrentNoneOtherCount); + CPPUNIT_TEST(testCurrentCountOthersCount); + CPPUNIT_TEST(testCurrentNoneOthersCount); + CPPUNIT_TEST(testCurrentCountSomeOthersCount); + CPPUNIT_TEST_SUITE_END(); + +public: + ChatMessageSummarizerTest() {}; + + void setUp() { + + } + + void testEmpty() { + string current(""); + vector<UnreadPair> unreads; + ChatMessageSummarizer summary; + CPPUNIT_ASSERT_EQUAL(current, summary.getSummary(current, unreads)); + } + + void testCurrentNone() { + string current("Bob"); + vector<UnreadPair> unreads; + unreads.push_back(UnreadPair("Bob", 0)); + ChatMessageSummarizer summary; + CPPUNIT_ASSERT_EQUAL(current, summary.getSummary(current, unreads)); + } + + void testCurrentCount() { + string current("Bob"); + vector<UnreadPair> unreads; + unreads.push_back(UnreadPair("Bob", 3)); + ChatMessageSummarizer summary; + CPPUNIT_ASSERT_EQUAL(string("Bob (3)"), summary.getSummary(current, unreads)); + } + + void testCurrentCountOthersNone() { + string current("Bob"); + vector<UnreadPair> unreads; + unreads.push_back(UnreadPair("Bert", 0)); + unreads.push_back(UnreadPair("Bob", 3)); + unreads.push_back(UnreadPair("Betty", 0)); + ChatMessageSummarizer summary; + CPPUNIT_ASSERT_EQUAL(string("Bob (3)"), summary.getSummary(current, unreads)); + } + + void testCurrentCountOtherCount() { + string current("Bob"); + vector<UnreadPair> unreads; + unreads.push_back(UnreadPair("Bert", 0)); + unreads.push_back(UnreadPair("Bob", 3)); + unreads.push_back(UnreadPair("Betty", 7)); + ChatMessageSummarizer summary; + CPPUNIT_ASSERT_EQUAL(string("Bob (3), Betty (7)"), summary.getSummary(current, unreads)); + } + + void testCurrentNoneOtherCount() { + string current("Bob"); + vector<UnreadPair> unreads; + unreads.push_back(UnreadPair("Bert", 0)); + unreads.push_back(UnreadPair("Bob", 0)); + unreads.push_back(UnreadPair("Betty", 7)); + ChatMessageSummarizer summary; + CPPUNIT_ASSERT_EQUAL(string("Bob, Betty (7)"), summary.getSummary(current, unreads)); + } + + void testCurrentNoneOthersCount() { + string current("Bob"); + vector<UnreadPair> unreads; + unreads.push_back(UnreadPair("Bert", 2)); + unreads.push_back(UnreadPair("Bob", 0)); + unreads.push_back(UnreadPair("Betty", 7)); + ChatMessageSummarizer summary; + CPPUNIT_ASSERT_EQUAL(string("Bob and 2 others (9)"), summary.getSummary(current, unreads)); + } + + void testCurrentCountOthersCount() { + string current("Bob"); + vector<UnreadPair> unreads; + unreads.push_back(UnreadPair("Bert", 2)); + unreads.push_back(UnreadPair("Bob", 11)); + unreads.push_back(UnreadPair("Betty", 7)); + ChatMessageSummarizer summary; + CPPUNIT_ASSERT_EQUAL(string("Bob (11) and 2 others (9)"), summary.getSummary(current, unreads)); + } + + void testCurrentCountSomeOthersCount() { + string current("Bob"); + vector<UnreadPair> unreads; + unreads.push_back(UnreadPair("Bert", 2)); + unreads.push_back(UnreadPair("Beverly", 0)); + unreads.push_back(UnreadPair("Bob", 11)); + unreads.push_back(UnreadPair("Beatrice", 0)); + unreads.push_back(UnreadPair("Betty", 7)); + ChatMessageSummarizer summary; + CPPUNIT_ASSERT_EQUAL(string("Bob (11) and 2 others (9)"), summary.getSummary(current, unreads)); + } + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(ChatMessageSummarizerTest); diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h index 53a90a7..a5765fd 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -11,7 +11,7 @@ namespace Swift { class MockChatWindow : public ChatWindow { public: - MockChatWindow() {}; + MockChatWindow() : labelsEnabled_(false) {}; virtual ~MockChatWindow(); virtual std::string addMessage(const std::string& message, const std::string& /*senderName*/, bool /*senderIsSelf*/, boost::shared_ptr<SecurityLabel> /*label*/, const std::string& /*avatarPath*/, const boost::posix_time::ptime&) {lastMessageBody_ = message; return "";}; @@ -34,12 +34,13 @@ namespace Swift { virtual void setRosterModel(Roster* /*roster*/) {}; virtual void setTabComplete(TabComplete*) {}; virtual void replaceLastMessage(const std::string&) {}; + virtual void replaceMessage(const std::string&, const std::string&, const boost::posix_time::ptime&) {}; void setAckState(const std::string& /*id*/, AckState /*state*/) {}; virtual void flash() {}; boost::signal<void ()> onClosed; boost::signal<void ()> onAllMessagesRead; - boost::signal<void (const std::string&)> onSendMessageRequest; + boost::signal<void (const std::string&, bool isCorrection)> onSendMessageRequest; std::string name_; std::string lastMessageBody_; diff --git a/Swift/Controllers/UnitTest/MockMainWindow.h b/Swift/Controllers/UnitTest/MockMainWindow.h index afa5c2a..f773062 100644 --- a/Swift/Controllers/UnitTest/MockMainWindow.h +++ b/Swift/Controllers/UnitTest/MockMainWindow.h @@ -20,6 +20,7 @@ namespace Swift { virtual void setMyAvatarPath(const std::string& /*path*/) {}; virtual void setMyStatusText(const std::string& /*status*/) {}; virtual void setMyStatusType(StatusShow::Type /*type*/) {}; + virtual void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& /*commands*/) {}; virtual void setConnecting() {}; Roster* roster; diff --git a/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp b/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp index 42cfc5f..3e9be13 100644 --- a/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp +++ b/Swift/Controllers/UnitTest/PresenceNotifierTest.cpp @@ -70,7 +70,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testReceiveFirstPresenceCreatesAvailableNotification() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); sendPresence(user1, StatusShow::Online); @@ -79,7 +79,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testReceiveSecondPresenceCreatesStatusChangeNotification() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); sendPresence(user1, StatusShow::Away); notifier->notifications.clear(); @@ -90,7 +90,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testReceiveUnavailablePresenceAfterAvailablePresenceCreatesUnavailableNotification() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); sendPresence(user1, StatusShow::Away); notifier->notifications.clear(); @@ -101,7 +101,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testReceiveUnavailablePresenceWithoutAvailableDoesNotCreateNotification() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); sendUnavailablePresence(user1); @@ -109,7 +109,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testReceiveAvailablePresenceAfterUnavailableCreatesAvailableNotification() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); sendPresence(user1, StatusShow::Away); sendUnavailablePresence(user1); notifier->notifications.clear(); @@ -121,7 +121,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testReceiveAvailablePresenceAfterReconnectCreatesAvailableNotification() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); sendPresence(user1, StatusShow::Away); stanzaChannel->setAvailable(false); stanzaChannel->setAvailable(true); @@ -134,7 +134,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testReceiveAvailablePresenceFromMUCDoesNotCreateNotification() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); mucRegistry->addMUC(JID("teaparty@wonderland.lit")); sendPresence(JID("teaparty@wonderland.lit/Alice"), StatusShow::Away); @@ -143,8 +143,8 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testNotificationPicture() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); - avatarManager->avatars[user1] = ByteArray("abcdef"); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); + avatarManager->avatars[user1] = createByteArray("abcdef"); sendPresence(user1, StatusShow::Online); @@ -153,7 +153,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testNotificationActivationEmitsSignal() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); sendPresence(user1, StatusShow::Online); CPPUNIT_ASSERT(notifier->notifications[0].callback); @@ -164,7 +164,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testNotificationSubjectContainsNameForJIDInRoster() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); roster->addContact(user1.toBare(), "User 1", std::vector<std::string>(), RosterItemPayload::Both); sendPresence(user1, StatusShow::Online); @@ -175,7 +175,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testNotificationSubjectContainsJIDForJIDNotInRoster() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); sendPresence(user1, StatusShow::Online); @@ -185,7 +185,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testNotificationSubjectContainsStatus() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); sendPresence(user1, StatusShow::Away); @@ -195,7 +195,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testNotificationMessageContainsStatusMessage() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); sendPresence(user1, StatusShow::Away); @@ -204,7 +204,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testReceiveFirstPresenceWithQuietPeriodDoesNotNotify() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); testling->setInitialQuietPeriodMS(10); sendPresence(user1, StatusShow::Online); @@ -213,7 +213,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testReceivePresenceDuringQuietPeriodDoesNotNotify() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); testling->setInitialQuietPeriodMS(10); sendPresence(user1, StatusShow::Online); @@ -224,7 +224,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testReceivePresenceDuringQuietPeriodResetsTimer() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); testling->setInitialQuietPeriodMS(10); sendPresence(user1, StatusShow::Online); @@ -237,7 +237,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testReceivePresenceAfterQuietPeriodNotifies() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); testling->setInitialQuietPeriodMS(10); sendPresence(user1, StatusShow::Online); @@ -248,7 +248,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testReceiveFirstPresenceWithQuietPeriodDoesNotCountAsQuietPeriod() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); testling->setInitialQuietPeriodMS(10); timerFactory->setTime(11); @@ -258,7 +258,7 @@ class PresenceNotifierTest : public CppUnit::TestFixture { } void testReceiveFirstPresenceAfterReconnectWithQuietPeriodDoesNotNotify() { - std::auto_ptr<PresenceNotifier> testling = createNotifier(); + boost::shared_ptr<PresenceNotifier> testling = createNotifier(); testling->setInitialQuietPeriodMS(10); sendPresence(user1, StatusShow::Online); timerFactory->setTime(15); @@ -275,8 +275,8 @@ class PresenceNotifierTest : public CppUnit::TestFixture { private: - std::auto_ptr<PresenceNotifier> createNotifier() { - std::auto_ptr<PresenceNotifier> result(new PresenceNotifier(stanzaChannel, notifier, mucRegistry, avatarManager, nickResolver, presenceOracle, timerFactory)); + boost::shared_ptr<PresenceNotifier> createNotifier() { + boost::shared_ptr<PresenceNotifier> result(new PresenceNotifier(stanzaChannel, notifier, mucRegistry, avatarManager, nickResolver, presenceOracle, timerFactory)); result->onNotificationActivated.connect(boost::bind(&PresenceNotifierTest::handleNotificationActivated, this, _1)); result->setInitialQuietPeriodMS(0); return result; diff --git a/Swift/Controllers/XMLConsoleController.cpp b/Swift/Controllers/XMLConsoleController.cpp index a3510d1..d21f312 100644 --- a/Swift/Controllers/XMLConsoleController.cpp +++ b/Swift/Controllers/XMLConsoleController.cpp @@ -30,13 +30,13 @@ void XMLConsoleController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) { } } -void XMLConsoleController::handleDataRead(const std::string& data) { +void XMLConsoleController::handleDataRead(const SafeByteArray& data) { if (xmlConsoleWidget) { xmlConsoleWidget->handleDataRead(data); } } -void XMLConsoleController::handleDataWritten(const std::string& data) { +void XMLConsoleController::handleDataWritten(const SafeByteArray& data) { if (xmlConsoleWidget) { xmlConsoleWidget->handleDataWritten(data); } diff --git a/Swift/Controllers/XMLConsoleController.h b/Swift/Controllers/XMLConsoleController.h index d12982f..6426a85 100644 --- a/Swift/Controllers/XMLConsoleController.h +++ b/Swift/Controllers/XMLConsoleController.h @@ -11,6 +11,7 @@ #include <boost/shared_ptr.hpp> #include "Swift/Controllers/UIEvents/UIEventStream.h" +#include <Swiften/Base/SafeByteArray.h> namespace Swift { @@ -23,8 +24,8 @@ namespace Swift { ~XMLConsoleController(); public: - void handleDataRead(const std::string& data); - void handleDataWritten(const std::string& data); + void handleDataRead(const SafeByteArray& data); + void handleDataWritten(const SafeByteArray& data); private: void handleUIEvent(boost::shared_ptr<UIEvent> event); diff --git a/Swift/Controllers/XMPPEvents/EventController.cpp b/Swift/Controllers/XMPPEvents/EventController.cpp index 7f8f216..98fd634 100644 --- a/Swift/Controllers/XMPPEvents/EventController.cpp +++ b/Swift/Controllers/XMPPEvents/EventController.cpp @@ -9,6 +9,7 @@ #include <boost/bind.hpp> #include <algorithm> +#include <Swiften/Base/foreach.h> #include "Swift/Controllers/XMPPEvents/MessageEvent.h" #include "Swift/Controllers/XMPPEvents/ErrorEvent.h" #include "Swift/Controllers/XMPPEvents/SubscriptionRequestEvent.h" diff --git a/Swift/Controllers/XMPPURIController.cpp b/Swift/Controllers/XMPPURIController.cpp new file mode 100644 index 0000000..00759b8 --- /dev/null +++ b/Swift/Controllers/XMPPURIController.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/Controllers/XMPPURIController.h> + +#include <boost/bind.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <SwifTools/URIHandler/URIHandler.h> +#include <SwifTools/URIHandler/XMPPURI.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> + +using namespace Swift; + +XMPPURIController::XMPPURIController(URIHandler* uriHandler, UIEventStream* uiEventStream) : uriHandler(uriHandler), uiEventStream(uiEventStream) { + uriHandler->onURI.connect(boost::bind(&XMPPURIController::handleURI, this, _1)); +} + +XMPPURIController::~XMPPURIController() { + uriHandler->onURI.disconnect(boost::bind(&XMPPURIController::handleURI, this, _1)); +} + +void XMPPURIController::handleURI(const std::string& s) { + XMPPURI uri = XMPPURI::fromString(s); + if (!uri.isNull()) { + if (uri.getQueryType() == "join") { + uiEventStream->send(boost::make_shared<RequestJoinMUCUIEvent>(uri.getPath())); + } + else { + uiEventStream->send(boost::make_shared<RequestChatUIEvent>(uri.getPath())); + } + } +} diff --git a/Swift/Controllers/XMPPURIController.h b/Swift/Controllers/XMPPURIController.h new file mode 100644 index 0000000..54534d4 --- /dev/null +++ b/Swift/Controllers/XMPPURIController.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> +#include <Swiften/Base/boost_bsignals.h> + +namespace Swift { + class URIHandler; + class JID; + class UIEventStream; + + class XMPPURIController { + public: + XMPPURIController(URIHandler* uriHandler, UIEventStream* uiEventStream); + ~XMPPURIController(); + + boost::signal<void (const JID&)> onStartChat; + boost::signal<void (const JID&)> onJoinMUC; + + private: + void handleURI(const std::string&); + + private: + URIHandler* uriHandler; + UIEventStream* uiEventStream; + }; +} |