diff options
Diffstat (limited to 'Swift/Controllers')
104 files changed, 3507 insertions, 514 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..a3d9fb5 100644 --- a/Swift/Controllers/Chat/ChatController.cpp +++ b/Swift/Controllers/Chat/ChatController.cpp @@ -11,16 +11,22 @@ #include <Swift/Controllers/Intl.h> #include <Swiften/Base/format.h> -#include "Swiften/Avatars/AvatarManager.h" -#include "Swiften/Chat/ChatStateNotifier.h" -#include "Swiften/Chat/ChatStateTracker.h" -#include "Swiften/Client/StanzaChannel.h" -#include "Swiften/Disco/EntityCapsProvider.h" -#include "Swift/Controllers/UIInterfaces/ChatWindow.h" -#include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h" -#include "Swiften/Client/NickResolver.h" -#include "Swift/Controllers/XMPPEvents/EventController.h" +#include <Swiften/Base/Algorithm.h> +#include <Swiften/Avatars/AvatarManager.h> +#include <Swiften/Chat/ChatStateNotifier.h> +#include <Swiften/Chat/ChatStateTracker.h> +#include <Swiften/Client/StanzaChannel.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> +#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h> +#include <Swiften/Client/NickResolver.h> +#include <Swift/Controllers/XMPPEvents/EventController.h> +#include <Swift/Controllers/FileTransfer/FileTransferController.h> #include <Swift/Controllers/StatusUtil.h> +#include <Swiften/Disco/EntityCapsProvider.h> +#include <Swiften/Base/foreach.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> + namespace Swift { @@ -28,7 +34,7 @@ namespace Swift { * The controller does not gain ownership of the stanzaChannel, nor the factory. */ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQRouter* iqRouter, ChatWindowFactory* chatWindowFactory, const JID &contact, NickResolver* nickResolver, PresenceOracle* presenceOracle, AvatarManager* avatarManager, bool isInMUC, bool useDelayForLatency, UIEventStream* eventStream, EventController* eventController, TimerFactory* timerFactory, EntityCapsProvider* entityCapsProvider) - : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory) { + : ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, contact, presenceOracle, avatarManager, useDelayForLatency, eventStream, eventController, timerFactory, entityCapsProvider), eventStream_(eventStream) { isInMUC_ = isInMUC; lastWasPresence_ = false; chatStateNotifier_ = new ChatStateNotifier(stanzaChannel, contact, entityCapsProvider); @@ -59,6 +65,11 @@ ChatController::ChatController(const JID& self, StanzaChannel* stanzaChannel, IQ chatWindow_->addSystemMessage(startMessage); chatWindow_->onUserTyping.connect(boost::bind(&ChatStateNotifier::setUserIsTyping, chatStateNotifier_)); chatWindow_->onUserCancelsTyping.connect(boost::bind(&ChatStateNotifier::userCancelledNewMessage, chatStateNotifier_)); + chatWindow_->onFileTransferStart.connect(boost::bind(&ChatController::handleFileTransferStart, this, _1, _2)); + chatWindow_->onFileTransferAccept.connect(boost::bind(&ChatController::handleFileTransferAccept, this, _1, _2)); + chatWindow_->onFileTransferCancel.connect(boost::bind(&ChatController::handleFileTransferCancel, this, _1)); + chatWindow_->onSendFileRequest.connect(boost::bind(&ChatController::handleSendFileRequest, this, _1)); + handleBareJIDCapsChanged(toJID_); } @@ -74,6 +85,19 @@ ChatController::~ChatController() { delete chatStateTracker_; } +void ChatController::handleBareJIDCapsChanged(const JID& /*jid*/) { + DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_); + if (disco) { + if (disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) { + chatWindow_->setCorrectionEnabled(ChatWindow::Yes); + } else { + chatWindow_->setCorrectionEnabled(ChatWindow::No); + } + } else { + chatWindow_->setCorrectionEnabled(ChatWindow::Maybe); + } +} + void ChatController::setToJID(const JID& jid) { chatStateNotifier_->setContact(jid); ChatControllerBase::setToJID(jid); @@ -84,6 +108,7 @@ void ChatController::setToJID(const JID& jid) { presence = jid.isBare() ? presenceOracle_->getHighestPriorityPresence(jid.toBare()) : presenceOracle_->getLastPresence(jid); } chatStateNotifier_->setContactIsOnline(presence && presence->getType() == Presence::Available); + handleBareJIDCapsChanged(toJID_); } bool ChatController::isIncomingMessageFromMe(boost::shared_ptr<Message>) { @@ -102,8 +127,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 +141,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(); @@ -149,6 +180,45 @@ void ChatController::setOnline(bool online) { ChatControllerBase::setOnline(online); } +void ChatController::handleNewFileTransferController(FileTransferController* ftc) { + std::string nick = senderDisplayNameFromMessage(ftc->getOtherParty()); + std::string ftID = ftc->setChatWindow(chatWindow_, nick); + + ftControllers[ftID] = ftc; +} + +void ChatController::handleFileTransferCancel(std::string id) { + std::cout << "handleFileTransferCancel(" << id << ")" << std::endl; + if (ftControllers.find(id) != ftControllers.end()) { + ftControllers[id]->cancel(); + } else { + std::cerr << "unknown file transfer UI id" << std::endl; + } +} + +void ChatController::handleFileTransferStart(std::string id, std::string description) { + std::cout << "handleFileTransferStart(" << id << ", " << description << ")" << std::endl; + if (ftControllers.find(id) != ftControllers.end()) { + ftControllers[id]->start(description); + } else { + std::cerr << "unknown file transfer UI id" << std::endl; + } +} + +void ChatController::handleFileTransferAccept(std::string id, std::string filename) { + std::cout << "handleFileTransferAccept(" << id << ", " << filename << ")" << std::endl; + if (ftControllers.find(id) != ftControllers.end()) { + ftControllers[id]->accept(filename); + } else { + std::cerr << "unknown file transfer UI id" << std::endl; + } +} + +void ChatController::handleSendFileRequest(std::string filename) { + std::cout << "ChatController::handleSendFileRequest(" << filename << ")" << std::endl; + eventStream_->send(boost::make_shared<SendFileUIEvent>(getToJID(), filename)); +} + std::string ChatController::senderDisplayNameFromMessage(const JID& from) { return nickResolver_->jidToNick(from); } diff --git a/Swift/Controllers/Chat/ChatController.h b/Swift/Controllers/Chat/ChatController.h index dd4bf90..2531adb 100644 --- a/Swift/Controllers/Chat/ChatController.h +++ b/Swift/Controllers/Chat/ChatController.h @@ -8,12 +8,16 @@ #include "Swift/Controllers/Chat/ChatControllerBase.h" +#include <map> +#include <string> + namespace Swift { class AvatarManager; class ChatStateNotifier; class ChatStateTracker; class NickResolver; class EntityCapsProvider; + class FileTransferController; class ChatController : public ChatControllerBase { public: @@ -21,6 +25,7 @@ namespace Swift { virtual ~ChatController(); virtual void setToJID(const JID& jid); virtual void setOnline(bool online); + virtual void handleNewFileTransferController(FileTransferController* ftc); private: void handlePresenceChange(boost::shared_ptr<Presence> newPresence); @@ -35,16 +40,26 @@ namespace Swift { void handleStanzaAcked(boost::shared_ptr<Stanza> stanza); void dayTicked() {lastWasPresence_ = false;} void handleContactNickChanged(const JID& jid, const std::string& /*oldNick*/); + void handleBareJIDCapsChanged(const JID& jid); + + void handleFileTransferCancel(std::string /* id */); + void handleFileTransferStart(std::string /* id */, std::string /* description */); + void handleFileTransferAccept(std::string /* id */, std::string /* filename */); + void handleSendFileRequest(std::string filename); private: NickResolver* nickResolver_; ChatStateNotifier* chatStateNotifier_; ChatStateTracker* chatStateTracker_; + std::string myLastMessageUIID_; bool isInMUC_; bool lastWasPresence_; std::string lastStatusChangeString_; std::map<boost::shared_ptr<Stanza>, std::string> unackedStanzas_; StatusShow::Type lastShownStatus_; + UIEventStream* eventStream_; + + std::map<std::string, FileTransferController*> ftControllers; }; } diff --git a/Swift/Controllers/Chat/ChatControllerBase.cpp b/Swift/Controllers/Chat/ChatControllerBase.cpp index 281d968..df59c2f 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> @@ -20,6 +21,7 @@ #include "Swiften/Elements/Delay.h" #include "Swiften/Base/foreach.h" #include "Swift/Controllers/XMPPEvents/EventController.h" +#include "Swiften/Disco/EntityCapsProvider.h" #include "Swift/Controllers/UIInterfaces/ChatWindow.h" #include "Swift/Controllers/UIInterfaces/ChatWindowFactory.h" #include "Swiften/Queries/Requests/GetSecurityLabelsCatalogRequest.h" @@ -27,10 +29,11 @@ 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) { +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, EntityCapsProvider* entityCapsProvider) : selfJID_(self), stanzaChannel_(stanzaChannel), iqRouter_(iqRouter), chatWindowFactory_(chatWindowFactory), toJID_(toJID), labelsEnabled_(false), presenceOracle_(presenceOracle), avatarManager_(avatarManager), useDelayForLatency_(useDelayForLatency), eventController_(eventController), timerFactory_(timerFactory), entityCapsProvider_(entityCapsProvider) { 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)); + entityCapsProvider_->onCapsChanged.connect(boost::bind(&ChatControllerBase::handleCapsChanged, this, _1)); setOnline(stanzaChannel->isAvailable() && iqRouter->isAvailable()); createDayChangeTimer(); } @@ -39,6 +42,12 @@ ChatControllerBase::~ChatControllerBase() { delete chatWindow_; } +void ChatControllerBase::handleCapsChanged(const JID& jid) { + if (jid.compare(toJID_, JID::WithoutResource) == 0) { + handleBareJIDCapsChanged(jid); + } +} + void ChatControllerBase::createDayChangeTimer() { if (timerFactory_) { boost::posix_time::ptime now = boost::posix_time::second_clock::local_time(); @@ -81,14 +90,21 @@ void ChatControllerBase::setAvailableServerFeatures(boost::shared_ptr<DiscoInfo> } void ChatControllerBase::handleAllMessagesRead() { - foreach (boost::shared_ptr<MessageEvent> messageEvent, unreadMessages_) { - messageEvent->read(); + if (!unreadMessages_.empty()) { + foreach (boost::shared_ptr<MessageEvent> messageEvent, unreadMessages_) { + messageEvent->read(); + } + unreadMessages_.clear(); + chatWindow_->setUnreadMessageCount(0); + onUnreadCountChanged(); } - unreadMessages_.clear(); - chatWindow_->setUnreadMessageCount(0); } -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 +120,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 +173,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 +184,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 +200,25 @@ void ChatControllerBase::handleIncomingMessage(boost::shared_ptr<MessageEvent> m if (messageTimeStamp) { timeStamp = *messageTimeStamp; } + onActivity(body); - addMessage(body, senderDisplayNameFromMessage(from), isIncomingMessageFromMe(message), label, std::string(avatarManager_->getAvatarPath(from).string()), timeStamp); + 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..67bd74f 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; @@ -35,6 +35,7 @@ namespace Swift { class AvatarManager; class UIEventStream; class EventController; + class EntityCapsProvider; class ChatControllerBase : public boost::bsignals::trackable { public: @@ -47,8 +48,14 @@ 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_;} + void handleCapsChanged(const JID& jid); 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); + 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, EntityCapsProvider* entityCapsProvider); /** * Pass the Message appended, and the stanza used to send it. @@ -62,13 +69,16 @@ namespace Swift { virtual bool isFromContact(const JID& from); virtual boost::optional<boost::posix_time::ptime> getMessageTimestamp(boost::shared_ptr<Message>) const = 0; virtual void dayTicked() {}; + virtual void handleBareJIDCapsChanged(const JID& jid) = 0; + std::string getErrorMessage(boost::shared_ptr<ErrorPayload>); 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>); void handleDayChangeTick(); protected: @@ -80,13 +90,13 @@ namespace Swift { ChatWindow* chatWindow_; JID toJID_; bool labelsEnabled_; + std::map<JID, std::string> lastMessagesUIID_; PresenceOracle* presenceOracle_; AvatarManager* avatarManager_; bool useDelayForLatency_; EventController* eventController_; boost::shared_ptr<Timer> dateChangeTimer_; TimerFactory* timerFactory_; + EntityCapsProvider* entityCapsProvider_; }; } - -#endif diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index 94d4b9a..edc1a79 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,40 @@ #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/FileTransfer/FileTransferController.h> +#include <Swift/Controllers/FileTransfer/FileTransferOverview.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,13 +58,15 @@ ChatsManager::ChatsManager( EntityCapsProvider* entityCapsProvider, MUCManager* mucManager, MUCSearchWindowFactory* mucSearchWindowFactory, - SettingsProvider* settings) : + ProfileSettingsProvider* settings, + FileTransferOverview* ftOverview) : jid_(jid), joinMUCWindowFactory_(joinMUCWindowFactory), useDelayForLatency_(useDelayForLatency), mucRegistry_(mucRegistry), entityCapsProvider_(entityCapsProvider), - mucManager(mucManager) { + mucManager(mucManager), + ftOverview_(ftOverview) { timerFactory_ = timerFactory; eventController_ = eventController; stanzaChannel_ = stanzaChannel; @@ -68,13 +79,21 @@ 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)); + chatListWindow_->onClearRecentsRequested.connect(boost::bind(&ChatsManager::handleClearRecentsRequested, this)); + joinMUCWindow_ = NULL; mucSearchController_ = new MUCSearchController(jid_, mucSearchWindowFactory, iqRouter, settings); mucSearchController_->onMUCSelected.connect(boost::bind(&ChatsManager::handleMUCSelectedAfterSearch, this, _1)); + ftOverview_->onNewFileTransferController.connect(boost::bind(&ChatsManager::handleNewFileTransferController, this, _1)); setupBookmarks(); + loadRecents(); } ChatsManager::~ChatsManager() { @@ -89,6 +108,72 @@ 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")); + if (activity.size() == 0) { + /* Work around Boost bug https://svn.boost.org/trac/boost/ticket/4751 */ + activity.push_back(""); + } + 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::handleClearRecentsRequested() { + recentChats_.clear(); + saveRecents(); + handleUnreadCountChanged(NULL); +} + +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 +183,7 @@ void ChatsManager::setupBookmarks() { if (chatListWindow_) { chatListWindow_->setBookmarksEnabled(false); - chatListWindow_->clear(); + chatListWindow_->clearBookmarks(); } } } @@ -122,10 +207,90 @@ 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; + chatListWindow_->setRecents(recentChats_); + break; + } + } mucControllers_.erase(it); delete mucController; return; @@ -155,15 +320,15 @@ void ChatsManager::handleUIEvent(boost::shared_ptr<UIEvent> event) { mucBookmarkManager_->replaceBookmark(editMUCBookmarkEvent->getOldBookmark(), editMUCBookmarkEvent->getNewBookmark()); } else if (JoinMUCUIEvent::ref joinEvent = boost::dynamic_pointer_cast<JoinMUCUIEvent>(event)) { - handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getNick(), false); + handleJoinMUCRequest(joinEvent->getJID(), joinEvent->getNick(), joinEvent->getShouldJoinAutomatically()); + 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_ = joinMUCWindowFactory_->createJoinMUCWindow(uiEventStream_); joinMUCWindow_->onSearchMUC.connect(boost::bind(&ChatsManager::handleSearchMUCRequest, this)); } - joinMUCWindow_->setMUC(""); + joinMUCWindow_->setMUC(joinEvent->getRoom()); joinMUCWindow_->setNick(nickResolver_->jidToNick(jid_)); joinMUCWindow_->show(); } @@ -174,6 +339,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() && !chat.isMUC) { + 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 +360,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 +437,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 +447,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 +459,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 +480,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) { @@ -296,12 +496,17 @@ void ChatsManager::handleJoinMUCRequest(const JID &mucJID, const boost::optional } else { std::string nick = nickMaybe ? nickMaybe.get() : jid_.getNode(); MUC::ref muc = mucManager->createMUC(mucJID); - MUCController* controller = new MUCController(jid_, muc, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_); + MUCController* controller = new MUCController(jid_, muc, nick, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory_, eventController_, entityCapsProvider_); 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() { @@ -311,7 +516,7 @@ void ChatsManager::handleSearchMUCRequest() { void ChatsManager::handleIncomingMessage(boost::shared_ptr<Message> message) { JID jid = message->getFrom(); boost::shared_ptr<MessageEvent> event(new MessageEvent(message)); - if (!event->isReadable() && !message->getPayload<ChatState>() && message->getSubject().empty()) { + if (!event->isReadable() && !message->getPayload<ChatState>() && !message->hasSubject()) { return; } @@ -338,5 +543,24 @@ void ChatsManager::handleMUCSelectedAfterSearch(const JID& muc) { } } +void ChatsManager::handleMUCBookmarkActivated(const MUCBookmark& mucBookmark) { + uiEventStream_->send(boost::make_shared<JoinMUCUIEvent>(mucBookmark.getRoom(), mucBookmark.getNick())); +} + +void ChatsManager::handleNewFileTransferController(FileTransferController* ftc) { + ChatController* chatController = getChatControllerOrCreate(ftc->getOtherParty()); + chatController->handleNewFileTransferController(ftc); + chatController->activateChatWindow(); +} + +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..57643eb 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,29 @@ 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 FileTransferOverview; + class FileTransferController; + 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, FileTransferOverview* ftOverview); 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 +68,26 @@ 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 handleNewFileTransferController(FileTransferController*); + 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); + void handleClearRecentsRequested(); + 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 +112,8 @@ namespace Swift { EntityCapsProvider* entityCapsProvider_; MUCManager* mucManager; MUCSearchController* mucSearchController_; + std::list<ChatListWindow::Chat> recentChats_; + ProfileSettingsProvider* profileSettings_; + FileTransferOverview* ftOverview_; }; } diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index 2914116..e1d02ae 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,26 @@ #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 <Swift/Controllers/Roster/ContactRosterItem.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/Disco/EntityCapsProvider.h> #define MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS 60000 @@ -50,8 +53,9 @@ MUCController::MUCController ( UIEventStream* uiEventStream, bool useDelayForLatency, TimerFactory* timerFactory, - EventController* eventController) : - ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory), muc_(muc), nick_(nick), desiredNick_(nick) { + EventController* eventController, + EntityCapsProvider* entityCapsProvider) : + ChatControllerBase(self, stanzaChannel, iqRouter, chatWindowFactory, muc->getJID(), presenceOracle, avatarManager, useDelayForLatency, uiEventStream, eventController, timerFactory, entityCapsProvider), muc_(muc), nick_(nick), desiredNick_(nick) { parting_ = true; joined_ = false; lastWasPresence_ = false; @@ -65,12 +69,19 @@ MUCController::MUCController ( chatWindow_->setTabComplete(completer_); chatWindow_->setName(muc->getJID().getNode()); chatWindow_->onClosed.connect(boost::bind(&MUCController::handleWindowClosed, this)); + chatWindow_->onOccupantSelectionChanged.connect(boost::bind(&MUCController::handleWindowOccupantSelectionChanged, this, _1)); + chatWindow_->onOccupantActionSelected.connect(boost::bind(&MUCController::handleActionRequestedOnOccupant, this, _1, _2)); + chatWindow_->onChangeSubjectRequest.connect(boost::bind(&MUCController::handleChangeSubjectRequest, this, _1)); + chatWindow_->onConfigureRequest.connect(boost::bind(&MUCController::handleConfigureRequest, this, _1)); + chatWindow_->onDestroyRequest.connect(boost::bind(&MUCController::handleDestroyRoomRequest, this)); muc_->onJoinComplete.connect(boost::bind(&MUCController::handleJoinComplete, this, _1)); muc_->onJoinFailed.connect(boost::bind(&MUCController::handleJoinFailed, this, _1)); muc_->onOccupantJoined.connect(boost::bind(&MUCController::handleOccupantJoined, this, _1)); muc_->onOccupantPresenceChange.connect(boost::bind(&MUCController::handleOccupantPresenceChange, this, _1)); muc_->onOccupantLeft.connect(boost::bind(&MUCController::handleOccupantLeft, this, _1, _2, _3)); muc_->onOccupantRoleChanged.connect(boost::bind(&MUCController::handleOccupantRoleChanged, this, _1, _2, _3)); + muc_->onConfigurationFailed.connect(boost::bind(&MUCController::handleConfigurationFailed, this, _1)); + muc_->onConfigurationFormReceived.connect(boost::bind(&MUCController::handleConfigurationFormReceived, this, _1)); if (timerFactory) { loginCheckTimer_ = boost::shared_ptr<Timer>(timerFactory->createTimer(MUC_JOIN_WARNING_TIMEOUT_MILLISECONDS)); loginCheckTimer_->onTick.connect(boost::bind(&MUCController::handleJoinTimeoutTick, this)); @@ -81,6 +92,7 @@ MUCController::MUCController ( if (avatarManager_ != NULL) { avatarChangedConnection_ = (avatarManager_->onAvatarChanged.connect(boost::bind(&MUCController::handleAvatarChanged, this, _1))); } + handleBareJIDCapsChanged(muc->getJID()); } MUCController::~MUCController() { @@ -93,6 +105,38 @@ MUCController::~MUCController() { delete completer_; } +void MUCController::handleWindowOccupantSelectionChanged(ContactRosterItem* item) { + std::vector<ChatWindow::OccupantAction> actions; + //FIXME + if (item) { + actions.push_back(ChatWindow::Kick); + } + chatWindow_->setAvailableOccupantActions(actions); +} + +void MUCController::handleActionRequestedOnOccupant(ChatWindow::OccupantAction action, ContactRosterItem* item) { + switch (action) { + case ChatWindow::Kick: muc_->kickUser(item->getJID());break; + } +} + +void MUCController::handleBareJIDCapsChanged(const JID& /*jid*/) { + ChatWindow::Tristate support = ChatWindow::Yes; + bool any = false; + foreach (const std::string& nick, currentOccupants_) { + DiscoInfo::ref disco = entityCapsProvider_->getCaps(toJID_.toBare().toString() + "/" + nick); + if (disco && disco->hasFeature(DiscoInfo::MessageCorrectionFeature)) { + any = true; + } else { + support = ChatWindow::Maybe; + } + } + if (!any) { + support = ChatWindow::No; + } + chatWindow_->setCorrectionEnabled(support); +} + /** * Join the MUC if not already in it. */ @@ -109,6 +153,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 +210,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 +228,7 @@ void MUCController::handleJoinComplete(const std::string& nick) { clearPresenceQueue(); shouldJoinOnReconnect_ = true; setEnabled(true); + onUserJoined(); } void MUCController::handleAvatarChanged(const JID& jid) { @@ -206,7 +259,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 +303,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); } @@ -279,8 +344,9 @@ void MUCController::preHandleIncomingMessage(boost::shared_ptr<MessageEvent> mes receivedActivity(); joined_ = true; - if (!message->getSubject().empty() && message->getBody().empty()) { + if (message->hasSubject() && message->getBody().empty()) { chatWindow_->addSystemMessage(str(format(QT_TRANSLATE_NOOP("", "The room subject is now: %1%")) % message->getSubject()));; + chatWindow_->setSubject(message->getSubject()); doneGettingHistory_ = true; } @@ -309,7 +375,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 +479,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; @@ -508,4 +576,31 @@ std::string MUCController::generateJoinPartString(const std::vector<NickJoinPart return result; } +void MUCController::handleChangeSubjectRequest(const std::string& subject) { + muc_->changeSubject(subject); +} + +void MUCController::handleConfigureRequest(Form::ref form) { + if (form) { + muc_->configureRoom(form); + } + else { + muc_->requestConfigurationForm(); + } +} + +void MUCController::handleConfigurationFailed(ErrorPayload::ref error) { + std::string errorMessage = getErrorMessage(error); + errorMessage = str(format(QT_TRANSLATE_NOOP("", "Room configuration failed: %1%.")) % errorMessage); + chatWindow_->addErrorMessage(errorMessage); +} + +void MUCController::handleConfigurationFormReceived(Form::ref form) { + chatWindow_->showRoomConfigurationForm(form); +} + +void MUCController::handleDestroyRoomRequest() { + muc_->destroyRoom(); +} + } diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index ebdd6cd..7a7461b 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -7,23 +7,24 @@ #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> +#include <Swift/Controllers/Roster/RosterItem.h> +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { class StanzaChannel; class IQRouter; - class ChatWindow; class ChatWindowFactory; class Roster; class AvatarManager; @@ -41,14 +42,17 @@ namespace Swift { class MUCController : public ChatControllerBase { public: - 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(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, EntityCapsProvider* entityCapsProvider); ~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); @@ -61,6 +65,8 @@ namespace Swift { private: void clearPresenceQueue(); void addPresenceMessage(const std::string& message); + void handleWindowOccupantSelectionChanged(ContactRosterItem* item); + void handleActionRequestedOnOccupant(ChatWindow::OccupantAction, ContactRosterItem* item); void handleWindowClosed(); void handleAvatarChanged(const JID& jid); void handleOccupantJoined(const MUCOccupant& occupant); @@ -70,7 +76,9 @@ namespace Swift { void handleJoinComplete(const std::string& nick); void handleJoinFailed(boost::shared_ptr<ErrorPayload> error); void handleJoinTimeoutTick(); + void handleChangeSubjectRequest(const std::string&); 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(); @@ -79,6 +87,11 @@ namespace Swift { bool shouldUpdateJoinParts(); void dayTicked() {lastWasPresence_ = false;} void processUserPart(); + void handleBareJIDCapsChanged(const JID& jid); + void handleConfigureRequest(Form::ref); + void handleConfigurationFailed(ErrorPayload::ref); + void handleConfigurationFormReceived(Form::ref); + void handleDestroyRoomRequest(); private: MUC::ref muc_; diff --git a/Swift/Controllers/Chat/MUCSearchController.cpp b/Swift/Controllers/Chat/MUCSearchController.cpp index 743aabb..5312fa7 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,19 +11,20 @@ #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> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIInterfaces/MUCSearchWindowFactory.h> -#include <Swift/Controllers/DiscoServiceWalker.h> +#include <Swiften/Disco/DiscoServiceWalker.h> #include <Swiften/Client/NickResolver.h> 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..5339703 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. */ @@ -8,8 +8,11 @@ #include <cppunit/extensions/TestFactoryRegistry.h> #include "3rdParty/hippomocks.h" +#include <boost/bind.hpp> + #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" @@ -35,10 +38,14 @@ #include "Swiften/Client/DummyStanzaChannel.h" #include "Swiften/Queries/DummyIQChannel.h" #include "Swiften/Presence/PresenceOracle.h" +#include "Swiften/Jingle/JingleSessionManager.h" +#include "Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h" #include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" #include "Swift/Controllers/UIEvents/UIEventStream.h" - +#include <Swift/Controllers/ProfileSettingsProvider.h> +#include "Swift/Controllers/FileTransfer/FileTransferOverview.h" +#include <Swiften/Base/Algorithm.h> using namespace Swift; @@ -59,7 +66,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,19 +89,25 @@ 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(); + ftManager_ = new DummyFileTransferManager(); + ftOverview_ = new FileTransferOverview(ftManager_); + 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_, ftOverview_); avatarManager_ = new NullAvatarManager(); manager_->setAvatarManager(avatarManager_); }; void tearDown() { - //delete chatListWindowFactory_; + //delete chatListWindowFactory delete settings_; - delete mocks_; + delete profileSettings_; delete avatarManager_; delete manager_; + delete ftOverview_; + delete ftManager_; delete directedPresenceSender_; delete presenceSender_; delete presenceOracle_; @@ -109,6 +122,8 @@ public: delete xmppRoster_; delete entityCapsManager_; delete capsProvider_; + delete chatListWindow_; + delete mocks_; } void testFirstOpenWindowIncoming() { @@ -346,6 +361,10 @@ private: CapsProvider* capsProvider_; MUCManager* mucManager_; DummySettingsProvider* settings_; + ProfileSettingsProvider* profileSettings_; + ChatListWindow* chatListWindow_; + FileTransferOverview* ftOverview_; + FileTransferManager* ftManager_; }; CPPUNIT_TEST_SUITE_REGISTRATION(ChatsManagerTest); diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp index 5f5e44d..ad5ceac 100644 --- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp +++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp @@ -25,6 +25,7 @@ #include "Swiften/Presence/PresenceOracle.h" #include "Swiften/Network/TimerFactory.h" #include "Swiften/Elements/MUCUserPayload.h" +#include "Swiften/Disco/DummyEntityCapsProvider.h" using namespace Swift; @@ -56,12 +57,14 @@ public: TimerFactory* timerFactory = NULL; window_ = new MockChatWindow();//mocks_->InterfaceMock<ChatWindow>(); mucRegistry_ = new MUCRegistry(); + entityCapsProvider_ = new DummyEntityCapsProvider(); muc_ = MUC::ref(new MUC(stanzaChannel_, iqRouter_, directedPresenceSender_, JID("teaparty@rooms.wonderland.lit"), mucRegistry_)); mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_); - controller_ = new MUCController (self_, muc_, nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_); + controller_ = new MUCController (self_, muc_, nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_); }; void tearDown() { + delete entityCapsProvider_; delete controller_; delete eventController_; delete presenceOracle_; @@ -240,6 +243,7 @@ private: UIEventStream* uiEventStream_; MockChatWindow* window_; MUCRegistry* mucRegistry_; + DummyEntityCapsProvider* entityCapsProvider_; }; CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest); 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..3e734df 100644 --- a/Swift/Controllers/Chat/UserSearchController.cpp +++ b/Swift/Controllers/Chat/UserSearchController.cpp @@ -9,10 +9,10 @@ #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 <Swiften/Disco/DiscoServiceWalker.h> #include <Swift/Controllers/UIEvents/UIEventStream.h> #include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h> #include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.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 deleted file mode 100644 index ce29927..0000000 --- a/Swift/Controllers/DiscoServiceWalker.cpp +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ - -#include <Swift/Controllers/DiscoServiceWalker.h> -#include <Swiften/Base/Log.h> - -#include <boost/bind.hpp> - -namespace Swift { - -DiscoServiceWalker::DiscoServiceWalker(const JID& service, IQRouter* iqRouter, size_t maxSteps) : service_(service), iqRouter_(iqRouter), maxSteps_(maxSteps), active_(false) { - -} - -void DiscoServiceWalker::beginWalk() { - SWIFT_LOG(debug) << "Starting walk to " << service_ << std::endl; - assert(!active_); - assert(servicesBeingSearched_.empty()); - active_ = true; - walkNode(service_); -} - -void DiscoServiceWalker::endWalk() { - if (active_) { - SWIFT_LOG(debug) << "Ending walk to " << service_ << std::endl; - foreach (GetDiscoInfoRequest::ref request, pendingDiscoInfoRequests_) { - request->onResponse.disconnect(boost::bind(&DiscoServiceWalker::handleDiscoInfoResponse, this, _1, _2, request)); - } - foreach (GetDiscoItemsRequest::ref request, pendingDiscoItemsRequests_) { - request->onResponse.disconnect(boost::bind(&DiscoServiceWalker::handleDiscoItemsResponse, this, _1, _2, request)); - } - active_ = false; - } -} - -void DiscoServiceWalker::walkNode(const JID& jid) { - SWIFT_LOG(debug) << "Walking node " << jid << std::endl; - servicesBeingSearched_.insert(jid); - searchedServices_.insert(jid); - GetDiscoInfoRequest::ref discoInfoRequest = GetDiscoInfoRequest::create(jid, iqRouter_); - discoInfoRequest->onResponse.connect(boost::bind(&DiscoServiceWalker::handleDiscoInfoResponse, this, _1, _2, discoInfoRequest)); - pendingDiscoInfoRequests_.insert(discoInfoRequest); - discoInfoRequest->send(); -} - -void DiscoServiceWalker::handleReceivedDiscoItem(const JID& item) { - SWIFT_LOG(debug) << "Received disco item " << item << std::endl; - - /* If we got canceled, don't do anything */ - if (!active_) { - return; - } - - if (std::find(searchedServices_.begin(), searchedServices_.end(), item) != searchedServices_.end()) { - /* Don't recurse infinitely */ - return; - } - walkNode(item); -} - -void DiscoServiceWalker::handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error, GetDiscoInfoRequest::ref request) { - /* If we got canceled, don't do anything */ - if (!active_) { - return; - } - - SWIFT_LOG(debug) << "Disco info response from " << request->getReceiver() << std::endl; - - pendingDiscoInfoRequests_.erase(request); - if (error) { - handleDiscoError(request->getReceiver(), error); - return; - } - - bool couldContainServices = false; - foreach (DiscoInfo::Identity identity, info->getIdentities()) { - if (identity.getCategory() == "server") { - couldContainServices = true; - } - } - bool completed = false; - if (couldContainServices) { - GetDiscoItemsRequest::ref discoItemsRequest = GetDiscoItemsRequest::create(request->getReceiver(), iqRouter_); - discoItemsRequest->onResponse.connect(boost::bind(&DiscoServiceWalker::handleDiscoItemsResponse, this, _1, _2, discoItemsRequest)); - pendingDiscoItemsRequests_.insert(discoItemsRequest); - discoItemsRequest->send(); - } else { - completed = true; - } - onServiceFound(request->getReceiver(), info); - if (completed) { - markNodeCompleted(request->getReceiver()); - } -} - -void DiscoServiceWalker::handleDiscoItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, GetDiscoItemsRequest::ref request) { - /* If we got canceled, don't do anything */ - if (!active_) { - return; - } - - SWIFT_LOG(debug) << "Received disco item from " << request->getReceiver() << std::endl; - - pendingDiscoItemsRequests_.erase(request); - if (error) { - handleDiscoError(request->getReceiver(), error); - return; - } - foreach (DiscoItems::Item item, items->getItems()) { - if (item.getNode().empty()) { - /* Don't look at noded items. It's possible that this will exclude some services, - * but I've never seen one in the wild, and it's an easy fix for not looping. - */ - handleReceivedDiscoItem(item.getJID()); - } - } - markNodeCompleted(request->getReceiver()); -} - -void DiscoServiceWalker::handleDiscoError(const JID& jid, ErrorPayload::ref /*error*/) { - /* If we got canceled, don't do anything */ - if (!active_) { - return; - } - - SWIFT_LOG(debug) << "Disco error from " << jid << std::endl; - - markNodeCompleted(jid); -} - -void DiscoServiceWalker::markNodeCompleted(const JID& jid) { - // Check whether we weren't canceled in between a 'emit result' and this call - if (!active_) { - return; - } - SWIFT_LOG(debug) << "Node completed " << jid << std::endl; - - servicesBeingSearched_.erase(jid); - /* All results are in */ - if (servicesBeingSearched_.size() == 0) { - active_ = false; - onWalkComplete(); - } - /* Check if we're on a rampage */ - else if (searchedServices_.size() >= maxSteps_) { - active_ = false; - onWalkComplete(); - } -} - -} diff --git a/Swift/Controllers/DiscoServiceWalker.h b/Swift/Controllers/DiscoServiceWalker.h deleted file mode 100644 index 7982bbc..0000000 --- a/Swift/Controllers/DiscoServiceWalker.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ - -#pragma once - -#include <vector> -#include <set> - -#include <boost/shared_ptr.hpp> -#include <Swiften/Base/boost_bsignals.h> -#include <string> -#include <Swiften/JID/JID.h> -#include <Swiften/Elements/DiscoInfo.h> -#include <Swiften/Elements/DiscoItems.h> -#include <Swiften/Elements/ErrorPayload.h> -#include <Swiften/Disco/GetDiscoInfoRequest.h> -#include <Swiften/Disco/GetDiscoItemsRequest.h> - -namespace Swift { - class IQRouter; - /** - * Recursively walk service discovery trees to find all services offered. - * This stops on any disco item that's not reporting itself as a server. - */ - class DiscoServiceWalker { - public: - DiscoServiceWalker(const JID& service, IQRouter* iqRouter, size_t maxSteps = 200); - - /** - * Start the walk. - * - * Call this exactly once. - */ - void beginWalk(); - - /** - * End the walk. - */ - void endWalk(); - - bool isActive() const { - return active_; - } - - /** Emitted for each service found. */ - boost::signal<void(const JID&, boost::shared_ptr<DiscoInfo>)> onServiceFound; - - /** Emitted when walking is complete.*/ - boost::signal<void()> onWalkComplete; - - private: - void handleReceivedDiscoItem(const JID& item); - void walkNode(const JID& jid); - void markNodeCompleted(const JID& jid); - void handleDiscoInfoResponse(boost::shared_ptr<DiscoInfo> info, ErrorPayload::ref error, GetDiscoInfoRequest::ref request); - void handleDiscoItemsResponse(boost::shared_ptr<DiscoItems> items, ErrorPayload::ref error, GetDiscoItemsRequest::ref request); - void handleDiscoError(const JID& jid, ErrorPayload::ref error); - - private: - JID service_; - IQRouter* iqRouter_; - size_t maxSteps_; - bool active_; - std::set<JID> servicesBeingSearched_; - std::set<JID> searchedServices_; - std::set<GetDiscoInfoRequest::ref> pendingDiscoInfoRequests_; - std::set<GetDiscoItemsRequest::ref> pendingDiscoItemsRequests_; - }; -} diff --git a/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/FileTransfer/FileTransferController.cpp b/Swift/Controllers/FileTransfer/FileTransferController.cpp new file mode 100644 index 0000000..afa907d --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferController.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "FileTransferController.h" +#include "Swiften/FileTransfer/OutgoingJingleFileTransfer.h" +#include "Swiften/FileTransfer/FileTransferManager.h" +#include <Swiften/FileTransfer/FileReadBytestream.h> +#include <Swiften/Base/boost_bsignals.h> +#include <boost/bind.hpp> +#include "Swift/Controllers/UIInterfaces/ChatWindow.h" + +#include <boost/smart_ptr/make_shared.hpp> + +namespace Swift { + +FileTransferController::FileTransferController(const JID& receipient, const std::string& filename, FileTransferManager* fileTransferManager) : + sending(true), otherParty(receipient), filename(filename), ftManager(fileTransferManager), ftProgressInfo(0), chatWindow(0), currentState(FileTransfer::State::WaitingForStart) { + +} + +FileTransferController::FileTransferController(IncomingFileTransfer::ref transfer) : + sending(false), otherParty(transfer->getSender()), filename(transfer->filename), transfer(transfer), ftManager(0), ftProgressInfo(0), chatWindow(0), currentState(FileTransfer::State::WaitingForStart) { + +} + +FileTransferController::~FileTransferController() { + delete ftProgressInfo; +} + +const JID &FileTransferController::getOtherParty() const { + return otherParty; +} + +std::string FileTransferController::setChatWindow(ChatWindow* wnd, std::string nickname) { + chatWindow = wnd; + if (sending) { + uiID = wnd->addFileTransfer("me", true, filename, boost::filesystem::file_size(boost::filesystem::path(filename))); + } else { + uiID = wnd->addFileTransfer(nickname, false, filename, transfer->fileSizeInBytes); + } + return uiID; +} + +void FileTransferController::setReceipient(const JID& receipient) { + this->otherParty = receipient; +} + +bool FileTransferController::isIncoming() const { + return !sending; +} + +FileTransfer::State FileTransferController::getState() const { + return currentState; +} + +int FileTransferController::getProgress() const { + return ftProgressInfo ? ftProgressInfo->getPercentage() : 0; +} + +boost::uintmax_t FileTransferController::getSize() const { + if (transfer) { + return transfer->fileSizeInBytes; + } else { + return 0; + } +} + +void FileTransferController::start(std::string& description) { + std::cout << "FileTransferController::start" << std::endl; + fileReadStream = boost::make_shared<FileReadBytestream>(boost::filesystem::path(filename)); + OutgoingFileTransfer::ref outgoingTransfer = ftManager->createOutgoingFileTransfer(otherParty, boost::filesystem::path(filename), description, fileReadStream); + if (outgoingTransfer) { + ftProgressInfo = new FileTransferProgressInfo(outgoingTransfer->fileSizeInBytes); + ftProgressInfo->onProgressPercentage.connect(boost::bind(&FileTransferController::handleProgressPercentageChange, this, _1)); + outgoingTransfer->onStateChange.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); + outgoingTransfer->onProcessedBytes.connect(boost::bind(&FileTransferProgressInfo::setBytesProcessed, ftProgressInfo, _1)); + outgoingTransfer->start(); + transfer = outgoingTransfer; + } else { + std::cerr << "File transfer not supported!" << std::endl; + } +} + +void FileTransferController::accept(std::string& file) { + std::cout << "FileTransferController::accept" << std::endl; + IncomingFileTransfer::ref incomingTransfer = boost::dynamic_pointer_cast<IncomingFileTransfer>(transfer); + if (incomingTransfer) { + fileWriteStream = boost::make_shared<FileWriteBytestream>(boost::filesystem::path(file)); + + ftProgressInfo = new FileTransferProgressInfo(transfer->fileSizeInBytes); + ftProgressInfo->onProgressPercentage.connect(boost::bind(&FileTransferController::handleProgressPercentageChange, this, _1)); + transfer->onStateChange.connect(boost::bind(&FileTransferController::handleFileTransferStateChange, this, _1)); + transfer->onProcessedBytes.connect(boost::bind(&FileTransferProgressInfo::setBytesProcessed, ftProgressInfo, _1)); + incomingTransfer->accept(fileWriteStream); + } else { + std::cerr << "Expected an incoming transfer in this situation!" << std::endl; + } +} + +void FileTransferController::cancel() { + if (transfer) { + transfer->cancel(); + } else { + chatWindow->setFileTransferStatus(uiID, ChatWindow::Canceled); + } +} + +void FileTransferController::handleFileTransferStateChange(FileTransfer::State state) { + currentState = state; + onStateChage(); + switch(state.state) { + case FileTransfer::State::Negotiating: + chatWindow->setFileTransferStatus(uiID, ChatWindow::Negotiating); + return; + case FileTransfer::State::Transferring: + chatWindow->setFileTransferStatus(uiID, ChatWindow::Transferring); + return; + case FileTransfer::State::Canceled: + chatWindow->setFileTransferStatus(uiID, ChatWindow::Canceled); + return; + case FileTransfer::State::Finished: + chatWindow->setFileTransferStatus(uiID, ChatWindow::Finished); + if (fileWriteStream) { + fileWriteStream->close(); + } + return; + case FileTransfer::State::Failed: + chatWindow->setFileTransferStatus(uiID, ChatWindow::FTFailed); + return; + case FileTransfer::State::WaitingForAccept: + chatWindow->setFileTransferStatus(uiID, ChatWindow::WaitingForAccept); + return; + case FileTransfer::State::WaitingForStart: + return; + } + std::cerr << "Unhandled FileTransfer::State!" << std::endl; +} + +void FileTransferController::handleProgressPercentageChange(int percentage) { + onProgressChange(); + chatWindow->setFileTransferProgress(uiID, percentage); +} + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferController.h b/Swift/Controllers/FileTransfer/FileTransferController.h new file mode 100644 index 0000000..3d6f7d5 --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferController.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> + +#include <boost/shared_ptr.hpp> +#include <boost/cstdint.hpp> + +#include <Swiften/JID/JID.h> +#include <Swiften/FileTransfer/FileTransfer.h> +#include <Swiften/FileTransfer/IncomingFileTransfer.h> +#include <Swiften/FileTransfer/FileReadBytestream.h> +#include <Swiften/FileTransfer/FileWriteBytestream.h> +#include <Swift/Controllers/FileTransfer/FileTransferProgressInfo.h> + +namespace Swift { + +class FileTransferManager; +class ChatWindow; + +class FileTransferController { +public: + /** + * For outgoing file transfers. It'll create a file transfer via FileTransferManager as soon as the descriptive information is available. + */ + FileTransferController(const JID&, const std::string&, FileTransferManager*); + + /** + * For incoming file transfers. + */ + FileTransferController(IncomingFileTransfer::ref transfer); + ~FileTransferController(); + + std::string setChatWindow(ChatWindow*, std::string nickname); + void setReceipient(const JID& otherParty); + + void start(std::string& description); + void accept(std::string& file); + void cancel(); + + const JID &getOtherParty() const; + bool isIncoming() const; + FileTransfer::State getState() const; + int getProgress() const; + boost::uintmax_t getSize() const; + + boost::signal<void ()> onStateChage; + boost::signal<void ()> onProgressChange; + +private: + void handleFileTransferStateChange(FileTransfer::State); + void handleProgressPercentageChange(int percentage); + +private: + bool sending; + JID otherParty; + std::string filename; + FileTransfer::ref transfer; + boost::shared_ptr<FileReadBytestream> fileReadStream; + boost::shared_ptr<FileWriteBytestream> fileWriteStream; + FileTransferManager* ftManager; + FileTransferProgressInfo* ftProgressInfo; + ChatWindow* chatWindow; + std::string uiID; + FileTransfer::State currentState; +}; + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferOverview.cpp b/Swift/Controllers/FileTransfer/FileTransferOverview.cpp new file mode 100644 index 0000000..c3ffc5c --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferOverview.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "FileTransferOverview.h" + +#include <boost/bind.hpp> +#include <Swiften/Base/boost_bsignals.h> + +#include <Swiften/FileTransfer/FileTransferManager.h> + +namespace Swift { + +FileTransferOverview::FileTransferOverview(FileTransferManager* ftm) : fileTransferManager(ftm) { + fileTransferManager->onIncomingFileTransfer.connect(boost::bind(&FileTransferOverview::handleIncomingFileTransfer, this, _1)); +} + +FileTransferOverview::~FileTransferOverview() { + +} + +void FileTransferOverview::sendFile(const JID& jid, const std::string& filename) { + FileTransferController* controller = new FileTransferController(jid, filename, fileTransferManager); + fileTransfers.push_back(controller); + + onNewFileTransferController(controller); +} + +void FileTransferOverview::handleIncomingFileTransfer(IncomingFileTransfer::ref transfer) { + FileTransferController* controller = new FileTransferController(transfer); + fileTransfers.push_back(controller); + onNewFileTransferController(controller); +} + +const std::vector<FileTransferController*>& FileTransferOverview::getFileTransfers() const { + return fileTransfers; +} + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferOverview.h b/Swift/Controllers/FileTransfer/FileTransferOverview.h new file mode 100644 index 0000000..716666a --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferOverview.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> + +#include "Swift/Controllers/FileTransfer/FileTransferController.h" + +#include <Swiften/Base/boost_bsignals.h> + +namespace Swift { + +class ChatsManager; +class FileTransferManager; + +class FileTransferOverview { +public: + FileTransferOverview(FileTransferManager*); + ~FileTransferOverview(); + + void sendFile(const JID&, const std::string&); + const std::vector<FileTransferController*>& getFileTransfers() const; + + boost::signal<void (FileTransferController*)> onNewFileTransferController; + +private: + void handleIncomingFileTransfer(IncomingFileTransfer::ref transfer); + +private: + std::vector<FileTransferController*> fileTransfers; + FileTransferManager *fileTransferManager; +}; + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp new file mode 100644 index 0000000..6d19fa1 --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "FileTransferProgressInfo.h" + +#include <Swiften/Base/Log.h> + +namespace Swift { + +FileTransferProgressInfo::FileTransferProgressInfo(boost::uintmax_t completeBytes) : completeBytes(completeBytes), completedBytes(0), percentage(0) { + onProgressPercentage(0); +} + +void FileTransferProgressInfo::setBytesProcessed(int processedBytes) { + int oldPercentage = int(double(completedBytes) / double(completeBytes) * 100.0); + completedBytes += processedBytes; + int newPercentage = int(double(completedBytes) / double(completeBytes) * 100.0); + if (oldPercentage != newPercentage) { + onProgressPercentage(newPercentage); + } + percentage = newPercentage; +} + +int FileTransferProgressInfo::getPercentage() const { + return percentage; +} + +} diff --git a/Swift/Controllers/FileTransfer/FileTransferProgressInfo.h b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.h new file mode 100644 index 0000000..bb3c0fc --- /dev/null +++ b/Swift/Controllers/FileTransfer/FileTransferProgressInfo.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swiften/Base/boost_bsignals.h> + +namespace Swift { + +class FileTransferProgressInfo { +public: + FileTransferProgressInfo(boost::uintmax_t completeBytes); + +public: + void setBytesProcessed(int processedBytes); + + int getPercentage() const; + boost::signal<void (int)> onProgressPercentage; + +private: + boost::uintmax_t completeBytes; + boost::uintmax_t completedBytes; + int percentage; +}; + +} diff --git a/Swift/Controllers/FileTransferListController.cpp b/Swift/Controllers/FileTransferListController.cpp new file mode 100644 index 0000000..093a3c4 --- /dev/null +++ b/Swift/Controllers/FileTransferListController.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include "Swift/Controllers/FileTransferListController.h" + +#include <boost/bind.hpp> + +#include "Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h" +#include "Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h" + +namespace Swift { + +FileTransferListController::FileTransferListController(UIEventStream* uiEventStream, FileTransferListWidgetFactory* fileTransferListWidgetFactory) : fileTransferListWidgetFactory(fileTransferListWidgetFactory), fileTransferListWidget(NULL), fileTransferOverview(0) { + uiEventStream->onUIEvent.connect(boost::bind(&FileTransferListController::handleUIEvent, this, _1)); +} + +FileTransferListController::~FileTransferListController() { + delete fileTransferListWidget; +} + +void FileTransferListController::setFileTransferOverview(FileTransferOverview *overview) { + fileTransferOverview = overview; + if (fileTransferListWidget) { + fileTransferListWidget->setFileTransferOverview(fileTransferOverview); + } +} + +void FileTransferListController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) { + boost::shared_ptr<RequestFileTransferListUIEvent> event = boost::dynamic_pointer_cast<RequestFileTransferListUIEvent>(rawEvent); + if (event != NULL) { + if (fileTransferListWidget == NULL) { + fileTransferListWidget = fileTransferListWidgetFactory->createFileTransferListWidget(); + if (fileTransferOverview) { + fileTransferListWidget->setFileTransferOverview(fileTransferOverview); + } + } + fileTransferListWidget->show(); + fileTransferListWidget->activate(); + } +} + +} diff --git a/Swift/Controllers/FileTransferListController.h b/Swift/Controllers/FileTransferListController.h new file mode 100644 index 0000000..c5c8893 --- /dev/null +++ b/Swift/Controllers/FileTransferListController.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <boost/shared_ptr.hpp> + +#include "Swift/Controllers/UIEvents/UIEventStream.h" + +namespace Swift { + +class FileTransferListWidgetFactory; +class FileTransferListWidget; +class FileTransferOverview; + +class FileTransferListController { +public: + FileTransferListController(UIEventStream* uiEventStream, FileTransferListWidgetFactory* fileTransferListWidgetFactory); + ~FileTransferListController(); + + void setFileTransferOverview(FileTransferOverview* overview); + +private: + void handleUIEvent(boost::shared_ptr<UIEvent> event); + +private: + FileTransferListWidgetFactory* fileTransferListWidgetFactory; + FileTransferListWidget* fileTransferListWidget; + FileTransferOverview* fileTransferOverview; +}; + +} diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp index 9a35cc1..364dd57 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" @@ -38,9 +37,11 @@ #include "Swift/Controllers/SystemTray.h" #include "Swift/Controllers/SystemTrayController.h" #include "Swift/Controllers/XMLConsoleController.h" +#include "Swift/Controllers/FileTransferListController.h" #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 +61,17 @@ #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" +#include <SwifTools/Idle/IdleDetector.h> +#include <Swift/Controllers/FileTransfer/FileTransferOverview.h> +#include <Swiften/FileTransfer/FileTransferManager.h> +#include <Swiften/Client/ClientXMLTracer.h> namespace Swift { @@ -84,16 +91,22 @@ MainController::MainController( CertificateStorageFactory* certificateStorageFactory, Dock* dock, Notifier* notifier, - bool useDelayForLatency) : + URIHandler* uriHandler, + IdleDetector* idleDetector, + bool useDelayForLatency, + bool eagleMode) : eventLoop_(eventLoop), networkFactories_(networkFactories), uiFactory_(uiFactories), - idleDetector_(&idleQuerier_, networkFactories_->getTimerFactory(), 100), storagesFactory_(storagesFactory), certificateStorageFactory_(certificateStorageFactory), settings_(settings), + uriHandler_(uriHandler), + idleDetector_(idleDetector), loginWindow_(NULL) , - useDelayForLatency_(useDelayForLatency) { + useDelayForLatency_(useDelayForLatency), + eagleMode_(eagleMode), + ftOverview_(NULL) { storages_ = NULL; certificateStorage_ = NULL; statusTracker_ = NULL; @@ -121,33 +134,43 @@ 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)); loginWindow_->onQuitRequest.connect(boost::bind(&MainController::handleQuitRequest, this)); - idleDetector_.setIdleTimeSeconds(600); - idleDetector_.onIdleChanged.connect(boost::bind(&MainController::handleInputIdleChanged, this, _1)); + idleDetector_->setIdleTimeSeconds(600); + idleDetector_->onIdleChanged.connect(boost::bind(&MainController::handleInputIdleChanged, this, _1)); xmlConsoleController_ = new XMLConsoleController(uiEventStream_, uiFactory_); + fileTransferListController_ = new FileTransferListController(uiEventStream_, uiFactory_); + uiEventStream_->onUIEvent.connect(boost::bind(&MainController::handleUIEvent, this, _1)); bool enabled = settings_->getBoolSetting(SHOW_NOTIFICATIONS, true); uiEventStream_->send(boost::shared_ptr<ToggleNotificationsUIEvent>(new ToggleNotificationsUIEvent(enabled))); @@ -161,12 +184,16 @@ MainController::MainController( } MainController::~MainController() { + idleDetector_->onIdleChanged.disconnect(boost::bind(&MainController::handleInputIdleChanged, this, _1)); + + purgeCachedCredentials(); //setManagersOffline(); eventController_->disconnectAll(); resetClient(); - + delete fileTransferListController_; delete xmlConsoleController_; + delete xmppURIController_; delete soundEventController_; delete systemTrayController_; delete eventController_; @@ -174,7 +201,12 @@ MainController::~MainController() { delete uiEventStream_; } +void MainController::purgeCachedCredentials() { + safeClear(password_); +} + void MainController::resetClient() { + purgeCachedCredentials(); resetCurrentError(); resetPendingReconnects(); vCardPhotoHash_.clear(); @@ -186,6 +218,8 @@ void MainController::resetClient() { eventWindowController_ = NULL; delete chatsManager_; chatsManager_ = NULL; + delete ftOverview_; + ftOverview_ = NULL; delete rosterController_; rosterController_ = NULL; delete eventNotifier_; @@ -234,20 +268,30 @@ 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) { profileController_ = new ProfileController(client_->getVCardManager(), uiFactory_, uiEventStream_); - rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_); + srand(time(NULL)); + int randomPort = 10000 + rand() % 10000; + client_->getFileTransferManager()->startListeningOnPort(randomPort); + ftOverview_ = new FileTransferOverview(client_->getFileTransferManager()); + fileTransferListController_->setFileTransferOverview(ftOverview_); + rosterController_ = new RosterController(jid_, client_->getRoster(), client_->getAvatarManager(), uiFactory_, client_->getNickManager(), client_->getNickResolver(), client_->getPresenceOracle(), client_->getSubscriptionManager(), eventController_, uiEventStream_, client_->getIQRouter(), settings_, client_->getEntityCapsProvider(), ftOverview_); rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2)); rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this)); 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_, ftOverview_); + client_->onMessageReceived.connect(boost::bind(&ChatsManager::handleIncomingMessage, chatsManager_, _1)); chatsManager_->setAvatarManager(client_->getAvatarManager()); @@ -259,17 +303,25 @@ void MainController::handleConnected() { discoInfo.addIdentity(DiscoInfo::Identity(CLIENT_NAME, "client", "pc")); discoInfo.addFeature(DiscoInfo::ChatStatesFeature); discoInfo.addFeature(DiscoInfo::SecurityLabelsFeature); + discoInfo.addFeature(DiscoInfo::MessageCorrectionFeature); +#ifdef SWIFT_EXPERIMENTAL_FT + discoInfo.addFeature(DiscoInfo::JingleFeature); + discoInfo.addFeature(DiscoInfo::JingleFTFeature); + discoInfo.addFeature(DiscoInfo::JingleTransportsIBBFeature); + discoInfo.addFeature(DiscoInfo::JingleTransportsS5BFeature); +#endif 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 +408,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 +437,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 +456,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 +486,16 @@ void MainController::performLoginFromCachedCredentials() { if (rosterController_) { rosterController_->getWindow()->setConnecting(); } - - client_->connect(); + ClientOptions clientOptions; + clientOptions.forgetPassword = eagleMode_; + clientOptions.useTLS = eagleMode_ ? ClientOptions::RequireTLS : ClientOptions::UseTLSWhenAvailable; + client_->connect(clientOptions); } void MainController::handleDisconnected(const boost::optional<ClientError>& error) { + if (eagleMode_) { + purgeCachedCredentials(); + } if (quitRequested_) { resetClient(); loginWindow_->quit(); @@ -483,19 +552,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 +594,9 @@ void MainController::handleCancelLoginRequest() { } void MainController::signOut() { + if (eagleMode_) { + purgeCachedCredentials(); + } eventController_->clear(); logout(); loginWindow_->loggedOut(); @@ -524,6 +604,9 @@ void MainController::signOut() { } void MainController::logout() { + if (eagleMode_) { + purgeCachedCredentials(); + } systemTrayController_->setMyStatusType(StatusShow::None); if (clientInitialized_ /*&& client_->isAvailable()*/) { client_->disconnect(); @@ -554,11 +637,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..12028d7 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. */ @@ -11,8 +11,6 @@ #include <vector> #include "Swiften/Network/Timer.h" -#include "SwifTools/Idle/PlatformIdleQuerier.h" -#include "SwifTools/Idle/ActualIdleDetector.h" #include <string> #include "Swiften/Client/ClientError.h" #include "Swiften/JID/JID.h" @@ -25,8 +23,10 @@ #include "Swiften/Elements/CapsInfo.h" #include "Swift/Controllers/XMPPEvents/ErrorEvent.h" #include "Swift/Controllers/UIEvents/UIEvent.h" +#include "Swiften/Client/ClientXMLTracer.h" namespace Swift { + class IdleDetector; class UIFactory; class EventLoop; class Client; @@ -52,6 +52,7 @@ namespace Swift { class SoundEventController; class SoundPlayer; class XMLConsoleController; + class FileTransferListController; class UIEventStream; class EventWindowFactory; class EventWindowController; @@ -62,6 +63,11 @@ namespace Swift { class Storages; class StoragesFactory; class NetworkFactories; + class URIHandler; + class XMPPURIController; + class AdHocManager; + class AdHocCommandWindowFactory; + class FileTransferOverview; class MainController { public: @@ -76,7 +82,10 @@ namespace Swift { CertificateStorageFactory* certificateStorageFactory, Dock* dock, Notifier* notifier, - bool useDelayForLatency); + URIHandler* uriHandler, + IdleDetector* idleDetector, + bool useDelayForLatency, + bool eagleMode); ~MainController(); @@ -106,13 +115,12 @@ namespace Swift { void setManagersOffline(); void handleNotificationClicked(const JID& jid); void handleForceQuit(); + void purgeCachedCredentials(); private: EventLoop* eventLoop_; NetworkFactories* networkFactories_; UIFactory* uiFactory_; - PlatformIdleQuerier idleQuerier_; - ActualIdleDetector idleDetector_; StoragesFactory* storagesFactory_; Storages* storages_; CertificateStorageFactory* certificateStorageFactory_; @@ -123,15 +131,19 @@ namespace Swift { SettingsProvider *settings_; ProfileSettingsProvider* profileSettings_; Dock* dock_; + URIHandler* uriHandler_; + IdleDetector* idleDetector_; TogglableNotifier* notifier_; PresenceNotifier* presenceNotifier_; EventNotifier* eventNotifier_; RosterController* rosterController_; EventController* eventController_; EventWindowController* eventWindowController_; + AdHocManager* adHocManager_; LoginWindow* loginWindow_; UIEventStream* uiEventStream_; XMLConsoleController* xmlConsoleController_; + FileTransferListController* fileTransferListController_; ChatsManager* chatsManager_; ProfileController* profileController_; ContactEditController* contactEditController_; @@ -139,6 +151,7 @@ namespace Swift { JID boundJID_; SystemTrayController* systemTrayController_; SoundEventController* soundEventController_; + XMPPURIController* xmppURIController_; std::string vCardPhotoHash_; std::string password_; std::string certificateFile_; @@ -152,5 +165,7 @@ namespace Swift { bool myStatusLooksOnline_; bool quitRequested_; static const int SecondsToWaitBeforeForceQuitting; + bool eagleMode_; + FileTransferOverview* ftOverview_; }; } 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..8c388bf 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 { @@ -111,6 +113,14 @@ void ContactRosterItem::removeGroup(const std::string& group) { groups_.erase(std::remove(groups_.begin(), groups_.end(), group), groups_.end()); } +void ContactRosterItem::setSupportedFeatures(const std::set<Feature>& features) { + features_ = features; +} + +bool ContactRosterItem::supportsFeature(const Feature feature) const { + return features_.find(feature) != features_.end(); +} + } diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h index 7aa948c..9932dc4 100644 --- a/Swift/Controllers/Roster/ContactRosterItem.h +++ b/Swift/Controllers/Roster/ContactRosterItem.h @@ -13,6 +13,7 @@ #include "Swiften/Elements/Presence.h" #include <map> +#include <set> #include <boost/bind.hpp> #include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> @@ -22,6 +23,11 @@ namespace Swift { class GroupRosterItem; class ContactRosterItem : public RosterItem { public: + enum Feature { + FileTransferFeature, + }; + + public: ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent); virtual ~ContactRosterItem(); @@ -40,6 +46,9 @@ class ContactRosterItem : public RosterItem { /** Only used so a contact can know about the groups it's in*/ void addGroup(const std::string& group); void removeGroup(const std::string& group); + + void setSupportedFeatures(const std::set<Feature>& features); + bool supportsFeature(Feature feature) const; private: JID jid_; JID displayJID_; @@ -48,6 +57,7 @@ class ContactRosterItem : public RosterItem { boost::shared_ptr<Presence> offlinePresence_; boost::shared_ptr<Presence> shownPresence_; std::vector<std::string> groups_; + std::set<Feature> features_; }; } 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..83837b1 100644 --- a/Swift/Controllers/Roster/Roster.cpp +++ b/Swift/Controllers/Roster/Roster.cpp @@ -17,6 +17,7 @@ #include <boost/bind.hpp> #include <iostream> +#include <set> #include <deque> namespace Swift { @@ -60,6 +61,16 @@ GroupRosterItem* Roster::getGroup(const std::string& groupName) { return group; } +void Roster::setAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& features) { + JID actualJID = fullJIDMapping_ ? jid : jid.toBare(); + if (itemMap_[actualJID].empty()) { + return; + } + foreach(ContactRosterItem* item, itemMap_[actualJID]) { + item->setSupportedFeatures(features); + } +} + void Roster::removeGroup(const std::string& group) { root_->removeGroupChild(group); } @@ -74,7 +85,7 @@ void Roster::handleChildrenChanged(GroupRosterItem* item) { void Roster::addContact(const JID& jid, const JID& displayJID, const std::string& name, const std::string& groupName, const std::string& avatarPath) { GroupRosterItem* group(getGroup(groupName)); - ContactRosterItem *item = new ContactRosterItem(jid, displayJID, name, group); + ContactRosterItem *item = new ContactRosterItem(jid, displayJID, name, group); item->setAvatarPath(avatarPath); group->addChild(item); if (itemMap_[fullJIDMapping_ ? jid : jid.toBare()].size() > 0) { @@ -107,7 +118,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 +135,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 +190,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/Roster.h b/Swift/Controllers/Roster/Roster.h index 53161a8..2b4dd27 100644 --- a/Swift/Controllers/Roster/Roster.h +++ b/Swift/Controllers/Roster/Roster.h @@ -10,6 +10,7 @@ #include "Swiften/JID/JID.h" #include "Swift/Controllers/Roster/RosterItemOperation.h" #include "Swift/Controllers/Roster/RosterFilter.h" +#include <Swift/Controllers/Roster/ContactRosterItem.h> #include <vector> #include <map> @@ -43,6 +44,8 @@ class Roster { boost::signal<void (GroupRosterItem*)> onGroupAdded; boost::signal<void (RosterItem*)> onDataChanged; GroupRosterItem* getGroup(const std::string& groupName); + void setAvailableFeatures(const JID& jid, const std::set<ContactRosterItem::Feature>& features); + private: void handleDataChanged(RosterItem* item); void handleChildrenChanged(GroupRosterItem* item); diff --git a/Swift/Controllers/Roster/RosterController.cpp b/Swift/Controllers/Roster/RosterController.cpp index 5b61abf..66948c1 100644 --- a/Swift/Controllers/Roster/RosterController.cpp +++ b/Swift/Controllers/Roster/RosterController.cpp @@ -36,9 +36,14 @@ #include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h" #include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h" #include "Swift/Controllers/UIEvents/ToggleShowOfflineUIEvent.h" +#include "Swift/Controllers/UIEvents/SendFileUIEvent.h" +#include <Swiften/FileTransfer/FileTransferManager.h> #include <Swiften/Client/NickManager.h> #include <Swift/Controllers/Intl.h> #include <Swiften/Base/format.h> +#include <Swiften/Elements/DiscoInfo.h> +#include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Jingle/JingleSessionManager.h> namespace Swift { @@ -47,8 +52,9 @@ static const std::string SHOW_OFFLINE = "showOffline"; /** * The controller does not gain ownership of these parameters. */ -RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings) - : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver), uiEventStream_(uiEventStream) { +RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings, EntityCapsProvider* entityCapsManager, FileTransferOverview* fileTransferOverview) + : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()), nickManager_(nickManager), nickResolver_(nickResolver), uiEventStream_(uiEventStream), entityCapsManager_(entityCapsManager), ftOverview_(fileTransferOverview) { + assert(fileTransferOverview); iqRouter_ = iqRouter; presenceOracle_ = presenceOracle; subscriptionManager_ = subscriptionManager; @@ -74,15 +80,17 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata nickManager_->onOwnNickChanged.connect(boost::bind(&MainWindow::setMyNick, mainWindow_, _1)); mainWindow_->setMyJID(jid); mainWindow_->setMyNick(nickManager_->getOwnNick()); + + entityCapsManager_->onCapsChanged.connect(boost::bind(&RosterController::handleOnCapsChanged, this, _1)); if (settings->getBoolSetting(SHOW_OFFLINE, false)) { uiEventStream->onUIEvent(boost::shared_ptr<UIEvent>(new ToggleShowOfflineUIEvent(true))); } } -RosterController::~RosterController() { +RosterController::~RosterController() { nickManager_->onOwnNickChanged.disconnect(boost::bind(&MainWindow::setMyNick, mainWindow_, _1)); - + delete offlineFilter_; delete expandiness_; @@ -91,6 +99,7 @@ RosterController::~RosterController() { delete mainWindow_; } delete roster_; + } void RosterController::setEnabled(bool enabled) { @@ -226,6 +235,10 @@ void RosterController::handleUIEvent(boost::shared_ptr<UIEvent> event) { } } } + else if (boost::shared_ptr<SendFileUIEvent> sendFileEvent = boost::dynamic_pointer_cast<SendFileUIEvent>(event)) { + //TODO add send file dialog to ChatView of receipient jid + ftOverview_->sendFile(sendFileEvent->getJID(), sendFileEvent->getFilename()); + } } void RosterController::setContactGroups(const JID& jid, const std::vector<std::string>& groups) { @@ -302,4 +315,15 @@ std::set<std::string> RosterController::getGroups() const { return xmppRoster_->getGroups(); } +void RosterController::handleOnCapsChanged(const JID& jid) { + DiscoInfo::ref info = entityCapsManager_->getCaps(jid); + if (info) { + std::set<ContactRosterItem::Feature> features; + if (info->hasFeature(DiscoInfo::JingleFeature) && info->hasFeature(DiscoInfo::JingleFTFeature) && info->hasFeature(DiscoInfo::JingleTransportsIBBFeature)) { + features.insert(ContactRosterItem::FileTransferFeature); + } + roster_->setAvailableFeatures(jid, features); + } +} + } diff --git a/Swift/Controllers/Roster/RosterController.h b/Swift/Controllers/Roster/RosterController.h index 0a2b818..66748ca 100644 --- a/Swift/Controllers/Roster/RosterController.h +++ b/Swift/Controllers/Roster/RosterController.h @@ -8,12 +8,14 @@ #include "Swiften/JID/JID.h" #include <string> +#include <set> #include "Swiften/Elements/Presence.h" #include "Swiften/Elements/ErrorPayload.h" #include "Swiften/Elements/RosterPayload.h" #include "Swiften/Avatars/AvatarManager.h" #include "Swift/Controllers/UIEvents/UIEvent.h" #include "RosterGroupExpandinessPersister.h" +#include "Swift/Controllers/FileTransfer/FileTransferOverview.h" #include "Swiften/Base/boost_bsignals.h" #include <boost/shared_ptr.hpp> @@ -35,10 +37,12 @@ namespace Swift { class IQRouter; class SettingsProvider; class NickManager; - + class EntityCapsProvider; + class FileTransferManager; + class RosterController { public: - RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings); + RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickManager* nickManager, NickResolver* nickResolver, PresenceOracle* presenceOracle, SubscriptionManager* subscriptionManager, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings, EntityCapsProvider* entityCapsProvider, FileTransferOverview* fileTransferOverview); ~RosterController(); void showRosterWindow(); MainWindow* getWindow() {return mainWindow_;}; @@ -69,6 +73,7 @@ namespace Swift { void handleRosterSetError(ErrorPayload::ref error, boost::shared_ptr<RosterPayload> rosterPayload); void applyAllPresenceTo(const JID& jid); void handleEditProfileRequest(); + void handleOnCapsChanged(const JID& jid); JID myJID_; XMPPRoster* xmppRoster_; @@ -86,6 +91,9 @@ namespace Swift { IQRouter* iqRouter_; SettingsProvider* settings_; UIEventStream* uiEventStream_; + EntityCapsProvider* entityCapsManager_; + FileTransferOverview* ftOverview_; + boost::bsignals::scoped_connection changeStatusConnection_; boost::bsignals::scoped_connection signOutConnection_; boost::bsignals::scoped_connection uiEventConnection_; 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..c00bf4f --- /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 || i1.avatarPath.empty() != i2.avatarPath.empty(); + } + }; + + 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..963c5cd --- /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 <boost/assign/list_of.hpp> +#include <functional> + +#include <QA/Checker/IO.h> +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/extensions/TestFactoryRegistry.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..fbee894 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" @@ -30,11 +31,21 @@ #include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h" #include "Swiften/MUC/MUCRegistry.h" #include <Swiften/Client/DummyNickManager.h> +#include <Swiften/Disco/EntityCapsManager.h> +#include <Swiften/Disco/CapsProvider.h> +#include <Swiften/Jingle/JingleSessionManager.h> +#include <Swiften/FileTransfer/UnitTest/DummyFileTransferManager.h> +#include <Swiften/Base/Algorithm.h> +#include <Swiften/EventLoop/DummyEventLoop.h> using namespace Swift; #define CHILDREN mainWindow_->roster->getRoot()->getChildren() +class DummyCapsProvider : public CapsProvider { + DiscoInfo::ref getCaps(const std::string&) const {return DiscoInfo::ref(new DiscoInfo());} +}; + class RosterControllerTest : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(RosterControllerTest); CPPUNIT_TEST(testAdd); @@ -65,12 +76,21 @@ class RosterControllerTest : public CppUnit::TestFixture { uiEventStream_ = new UIEventStream(); settings_ = new DummySettingsProvider(); nickManager_ = new DummyNickManager(); - rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_); + capsProvider_ = new DummyCapsProvider(); + entityCapsManager_ = new EntityCapsManager(capsProvider_, stanzaChannel_); + jingleSessionManager_ = new JingleSessionManager(router_); + + ftManager_ = new DummyFileTransferManager(); + ftOverview_ = new FileTransferOverview(ftManager_); + rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickManager_, nickResolver_, presenceOracle_, subscriptionManager_, eventController_, uiEventStream_, router_, settings_, entityCapsManager_, ftOverview_); mainWindow_ = mainWindowFactory_->last; }; void tearDown() { delete rosterController_; + delete ftManager_; + delete jingleSessionManager_; + delete nickManager_; delete nickResolver_; delete mucRegistry_; @@ -312,6 +332,11 @@ class RosterControllerTest : public CppUnit::TestFixture { UIEventStream* uiEventStream_; MockMainWindow* mainWindow_; DummySettingsProvider* settings_; + DummyCapsProvider* capsProvider_; + EntityCapsManager* entityCapsManager_; + JingleSessionManager* jingleSessionManager_; + FileTransferManager* ftManager_; + FileTransferOverview* ftOverview_; }; CPPUNIT_TEST_SUITE_REGISTRATION(RosterControllerTest); 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..e48f382 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -27,41 +27,57 @@ if env["SCONS_STAGE"] == "build" : "Chat/MUCController.cpp", "Chat/MUCSearchController.cpp", "Chat/UserSearchController.cpp", - "DiscoServiceWalker.cpp", "MainController.cpp", "ProfileController.cpp", "ContactEditController.cpp", + "FileTransfer/FileTransferController.cpp", + "FileTransfer/FileTransferOverview.cpp", + "FileTransfer/FileTransferProgressInfo.cpp", "Roster/RosterController.cpp", "Roster/RosterGroupExpandinessPersister.cpp", "Roster/ContactRosterItem.cpp", "Roster/GroupRosterItem.cpp", "Roster/RosterItem.cpp", "Roster/Roster.cpp", + "Roster/TableRoster.cpp", "EventWindowController.cpp", "SoundEventController.cpp", "SystemTrayController.cpp", "XMLConsoleController.cpp", + "FileTransferListController.cpp", "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..d799a90 --- /dev/null +++ b/Swift/Controllers/Storages/VCardFileStorage.cpp @@ -0,0 +1,115 @@ +/* + * 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 { + boost::shared_ptr<VCard> result = VCardPersister().loadPayloadGeneric(getVCardPath(jid)); + getAndUpdatePhotoHash(jid, result); + return result; +} + +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 { + try { + std::string file(jid.toString()); + String::replaceAll(file, '/', "%2f"); + return boost::filesystem::path(vcardsPath / (file + ".xml")); + } + catch (const boost::filesystem::filesystem_error& e) { + std::cerr << "ERROR: " << e.what() << std::endl; + return boost::filesystem::path(); + } +} + +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/JoinMUCUIEvent.h b/Swift/Controllers/UIEvents/JoinMUCUIEvent.h index c7f8be6..dea3ead 100644 --- a/Swift/Controllers/UIEvents/JoinMUCUIEvent.h +++ b/Swift/Controllers/UIEvents/JoinMUCUIEvent.h @@ -10,17 +10,21 @@ #include <boost/shared_ptr.hpp> #include <string> +#include <Swiften/JID/JID.h> + #include "Swift/Controllers/UIEvents/UIEvent.h" namespace Swift { class JoinMUCUIEvent : public UIEvent { public: typedef boost::shared_ptr<JoinMUCUIEvent> ref; - JoinMUCUIEvent(const JID& jid, const boost::optional<std::string>& nick = boost::optional<std::string>()) : jid_(jid), nick_(nick) {}; + JoinMUCUIEvent(const JID& jid, const boost::optional<std::string>& nick = boost::optional<std::string>(), bool joinAutomaticallyInFuture = false) : jid_(jid), nick_(nick), joinAutomatically_(joinAutomaticallyInFuture){}; boost::optional<std::string> getNick() {return nick_;}; JID getJID() {return jid_;}; + bool getShouldJoinAutomatically() {return joinAutomatically_;} private: JID jid_; boost::optional<std::string> nick_; + bool joinAutomatically_; }; } 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/RequestFileTransferListUIEvent.h b/Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h new file mode 100644 index 0000000..aff6909 --- /dev/null +++ b/Swift/Controllers/UIEvents/RequestFileTransferListUIEvent.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + +class RequestFileTransferListUIEvent : public UIEvent { +}; + +} 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/UIEvents/SendFileUIEvent.h b/Swift/Controllers/UIEvents/SendFileUIEvent.h new file mode 100644 index 0000000..3bfa69d --- /dev/null +++ b/Swift/Controllers/UIEvents/SendFileUIEvent.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <string> + +#include <Swiften/JID/JID.h> +#include <Swift/Controllers/UIEvents/UIEvent.h> + +namespace Swift { + class SendFileUIEvent : public UIEvent { + public: + typedef boost::shared_ptr<SendFileUIEvent> ref; + + SendFileUIEvent(const JID& jid, const std::string& filename) : jid(jid), filename(filename) { + } + + const JID& getJID() const { + return jid; + } + + const std::string& getFilename() const { + return filename; + } + + private: + JID jid; + std::string filename; + }; +} 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..d047f8c 100644 --- a/Swift/Controllers/UIInterfaces/ChatListWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatListWindow.h @@ -1,23 +1,60 @@ /* - * 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; + boost::signal<void ()> onClearRecentsRequested; }; } diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h index c7bcf1e..df57d80 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatWindow.h @@ -14,18 +14,26 @@ #include <vector> #include <string> -#include "Swiften/Elements/SecurityLabelsCatalog.h" -#include "Swiften/Elements/ChatState.h" +#include <Swiften/Elements/SecurityLabelsCatalog.h> +#include <Swiften/Elements/ChatState.h> +#include <Swiften/Elements/Form.h> + namespace Swift { class AvatarManager; class TreeWidget; class Roster; class TabComplete; + class RosterItem; + class ContactRosterItem; + class FileTransferController; class ChatWindow { public: enum AckState {Pending, Received, Failed}; + enum Tristate {Yes, No, Maybe}; + enum OccupantAction {Kick}; + enum FileTransferState {WaitingForAccept, Negotiating, Transferring, Canceled, Finished, FTFailed}; ChatWindow() {} virtual ~ChatWindow() {}; @@ -40,6 +48,12 @@ 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; + + // File transfer related stuff + virtual std::string addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes) = 0; + virtual void setFileTransferProgress(std::string, const int percentageDone) = 0; + virtual void setFileTransferStatus(std::string, const FileTransferState state, const std::string& msg = "") = 0; virtual void setContactChatState(ChatState::ChatStateType state) = 0; virtual void setName(const std::string& name) = 0; @@ -47,6 +61,7 @@ namespace Swift { virtual void activate() = 0; virtual void setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) = 0; virtual void setSecurityLabelsEnabled(bool enabled) = 0; + virtual void setCorrectionEnabled(Tristate enabled) = 0; virtual void setUnreadMessageCount(int count) = 0; virtual void convertToMUC() = 0; // virtual TreeWidget *getTreeWidget() = 0; @@ -58,12 +73,42 @@ namespace Swift { virtual void replaceLastMessage(const std::string& message) = 0; virtual void setAckState(const std::string& id, AckState state) = 0; virtual void flash() = 0; + virtual void setSubject(const std::string& subject) = 0; + /** + * Set an alert on the window. + * @param alertText Description of alert (required). + * @param buttonText Button text to use (optional, no button is shown if empty). + */ + virtual void setAlert(const std::string& alertText, const std::string& buttonText = "") = 0; + /** + * Removes an alert. + */ + virtual void cancelAlert() = 0; + + /** + * Actions that can be performed on the selected occupant. + */ + virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions) = 0; + virtual void showRoomConfigurationForm(Form::ref) = 0; 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; + boost::signal<void ()> onAlertButtonClicked; + boost::signal<void (ContactRosterItem*)> onOccupantSelectionChanged; + boost::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected; + boost::signal<void (const std::string&)> onChangeSubjectRequest; + boost::signal<void (Form::ref)> onConfigureRequest; + boost::signal<void ()> onDestroyRequest; + + // File transfer related + boost::signal<void (std::string /* id */)> onFileTransferCancel; + boost::signal<void (std::string /* id */, std::string /* description */)> onFileTransferStart; + boost::signal<void (std::string /* id */, std::string /* path */)> onFileTransferAccept; + boost::signal<void (std::string /* path */)> onSendFileRequest; }; } #endif 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/FileTransferListWidget.h b/Swift/Controllers/UIInterfaces/FileTransferListWidget.h new file mode 100644 index 0000000..01dcfd3 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/FileTransferListWidget.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +namespace Swift { + +class FileTransferOverview; + +class FileTransferListWidget { +public: + virtual ~FileTransferListWidget() {} + + virtual void show() = 0; + virtual void activate() = 0; + + virtual void setFileTransferOverview(FileTransferOverview*) = 0; +}; + +} diff --git a/Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h b/Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h new file mode 100644 index 0000000..0b08fb3 --- /dev/null +++ b/Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2011 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include "Swift/Controllers/UIInterfaces/FileTransferListWidget.h" + +namespace Swift { + +class FileTransferListWidgetFactory { +public: + virtual ~FileTransferListWidgetFactory() {} + + virtual FileTransferListWidget* createFileTransferListWidget() = 0; +}; + +} diff --git a/Swift/Controllers/UIInterfaces/JoinMUCWindow.h b/Swift/Controllers/UIInterfaces/JoinMUCWindow.h index 2e3d43c..4873c9b 100644 --- a/Swift/Controllers/UIInterfaces/JoinMUCWindow.h +++ b/Swift/Controllers/UIInterfaces/JoinMUCWindow.h @@ -21,7 +21,6 @@ namespace Swift { virtual void setMUC(const std::string& nick) = 0; virtual void show() = 0; - boost::signal<void (const JID& /* muc */, const std::string& /* nick */, bool /* autoJoin */)> onJoinMUC; boost::signal<void ()> onSearchMUC; }; } diff --git a/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h b/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h index 9c8bd77..cd8021b 100644 --- a/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h +++ b/Swift/Controllers/UIInterfaces/JoinMUCWindowFactory.h @@ -9,10 +9,11 @@ #include <Swift/Controllers/UIInterfaces/JoinMUCWindow.h> namespace Swift { + class UIEventStream; class JoinMUCWindowFactory { public: virtual ~JoinMUCWindowFactory() {}; - virtual JoinMUCWindow* createJoinMUCWindow() = 0; + virtual JoinMUCWindow* createJoinMUCWindow(UIEventStream* uiEventStream) = 0; }; } 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..cf89dab 100644 --- a/Swift/Controllers/UIInterfaces/UIFactory.h +++ b/Swift/Controllers/UIInterfaces/UIFactory.h @@ -17,6 +17,8 @@ #include <Swift/Controllers/UIInterfaces/XMLConsoleWidgetFactory.h> #include <Swift/Controllers/UIInterfaces/ProfileWindowFactory.h> #include <Swift/Controllers/UIInterfaces/ContactEditWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/AdHocCommandWindowFactory.h> +#include <Swift/Controllers/UIInterfaces/FileTransferListWidgetFactory.h> namespace Swift { class UIFactory : @@ -26,11 +28,13 @@ namespace Swift { public LoginWindowFactory, public MainWindowFactory, public MUCSearchWindowFactory, - public XMLConsoleWidgetFactory, + public XMLConsoleWidgetFactory, public UserSearchWindowFactory, public JoinMUCWindowFactory, public ProfileWindowFactory, - public ContactEditWindowFactory { + public ContactEditWindowFactory, + public AdHocCommandWindowFactory, + public FileTransferListWidgetFactory { 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..ad8856b 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 "";}; @@ -20,6 +20,11 @@ namespace Swift { virtual void addErrorMessage(const std::string& /*message*/) {}; virtual void addPresenceMessage(const std::string& /*message*/) {}; + // File transfer related stuff + virtual std::string addFileTransfer(const std::string& /*senderName*/, bool /*senderIsSelf*/,const std::string& /*filename*/, const boost::uintmax_t /*sizeInBytes*/) { return 0; }; + virtual void setFileTransferProgress(std::string /*id*/, const int /*alreadyTransferedBytes*/) { }; + virtual void setFileTransferStatus(std::string /*id*/, const FileTransferState /*state*/, const std::string& /*msg*/) { }; + virtual void setContactChatState(ChatState::ChatStateType /*state*/) {}; virtual void setName(const std::string& name) {name_ = name;}; virtual void show() {}; @@ -34,12 +39,19 @@ 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() {}; + virtual void setAlert(const std::string& /*alertText*/, const std::string& /*buttonText*/) {}; + virtual void cancelAlert() {}; + virtual void setCorrectionEnabled(Tristate /*enabled*/) {} + void setAvailableOccupantActions(const std::vector<OccupantAction>&/* actions*/) {} + void setSubject(const std::string& /*subject*/) {} + virtual void showRoomConfigurationForm(Form::ref) {} 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; + }; +} |