From f9c432ca127d6e7d87b49d2fbf6aace34bea0e06 Mon Sep 17 00:00:00 2001 From: Kevin Smith <git@kismith.co.uk> Date: Wed, 21 Sep 2011 13:25:27 +0100 Subject: Add support for kicking people from MUCs. This also introduces a new DOM-like parser structure, used for the MUC parsers. Partially Resolves: #689 diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp index aa0a1e7..19d7b4d 100644 --- a/Swift/Controllers/Chat/MUCController.cpp +++ b/Swift/Controllers/Chat/MUCController.cpp @@ -23,6 +23,7 @@ #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> @@ -68,6 +69,8 @@ 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)); 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)); @@ -97,6 +100,21 @@ 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; diff --git a/Swift/Controllers/Chat/MUCController.h b/Swift/Controllers/Chat/MUCController.h index 3a79920..39e5fa4 100644 --- a/Swift/Controllers/Chat/MUCController.h +++ b/Swift/Controllers/Chat/MUCController.h @@ -19,11 +19,12 @@ #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; @@ -64,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); diff --git a/Swift/Controllers/UIInterfaces/ChatWindow.h b/Swift/Controllers/UIInterfaces/ChatWindow.h index e84116d..faef5c8 100644 --- a/Swift/Controllers/UIInterfaces/ChatWindow.h +++ b/Swift/Controllers/UIInterfaces/ChatWindow.h @@ -22,11 +22,14 @@ namespace Swift { class TreeWidget; class Roster; class TabComplete; + class RosterItem; + class ContactRosterItem; class ChatWindow { public: enum AckState {Pending, Received, Failed}; enum Tristate {Yes, No, Maybe}; + enum OccupantAction {Kick}; ChatWindow() {} virtual ~ChatWindow() {}; @@ -72,6 +75,10 @@ namespace Swift { */ virtual void cancelAlert() = 0; + /** + * Actions that can be performed on the selected occupant. + */ + virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions) = 0; boost::signal<void ()> onClosed; boost::signal<void ()> onAllMessagesRead; boost::signal<void (const std::string&, bool isCorrection)> onSendMessageRequest; @@ -79,6 +86,8 @@ namespace Swift { boost::signal<void ()> onUserTyping; boost::signal<void ()> onUserCancelsTyping; boost::signal<void ()> onAlertButtonClicked; + boost::signal<void (ContactRosterItem*)> onOccupantSelectionChanged; + boost::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected; }; } #endif diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h index 7d6828f..574248f 100644 --- a/Swift/Controllers/UnitTest/MockChatWindow.h +++ b/Swift/Controllers/UnitTest/MockChatWindow.h @@ -40,6 +40,7 @@ namespace Swift { 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) {} boost::signal<void ()> onClosed; boost::signal<void ()> onAllMessagesRead; diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index a52d2de..a36bc32 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -7,7 +7,9 @@ #include "QtChatWindow.h" #include "QtSwiftUtil.h" #include "Swift/Controllers/Roster/Roster.h" -#include "Roster/QtTreeWidget.h" +#include "Swift/Controllers/Roster/RosterItem.h" +#include "Swift/Controllers/Roster/ContactRosterItem.h" +#include "Roster/QtOccupantListWidget.h" #include "SwifTools/Linkify.h" #include "QtChatView.h" #include "MessageSnippet.h" @@ -70,7 +72,7 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt messageLog_ = new QtChatView(theme, this); logRosterSplitter_->addWidget(messageLog_); - treeWidget_ = new QtTreeWidget(eventStream_); + treeWidget_ = new QtOccupantListWidget(eventStream_, this); treeWidget_->hide(); logRosterSplitter_->addWidget(treeWidget_); logRosterSplitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -115,7 +117,8 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt connect(messageLog_, SIGNAL(gotFocus()), input_, SLOT(setFocus())); resize(400,300); connect(messageLog_, SIGNAL(fontResized(int)), this, SIGNAL(fontResized(int))); - + treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QtChatWindow::handleOccupantSelectionChanged, this, _1)); + treeWidget_->onOccupantActionSelected.connect(boost::bind(boost::ref(onOccupantActionSelected), _1, _2)); } @@ -123,6 +126,9 @@ QtChatWindow::~QtChatWindow() { } +void QtChatWindow::handleOccupantSelectionChanged(RosterItem* item) { + onOccupantSelectionChanged(dynamic_cast<ContactRosterItem*>(item)); +} void QtChatWindow::handleFontResized(int fontSizeSteps) { messageLog_->resizeFont(fontSizeSteps); @@ -530,4 +536,8 @@ void QtChatWindow::replaceLastMessage(const std::string& message) { messageLog_->replaceLastMessage(P2QSTRING(Linkify::linkify(message))); } +void QtChatWindow::setAvailableOccupantActions(const std::vector<OccupantAction>& actions) { + treeWidget_->setAvailableOccupantActions(actions); +} + } diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h index bc1045d..d38e9c6 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -23,8 +23,7 @@ class QPushButton; namespace Swift { class QtChatView; - class QtTreeWidget; - class QtTreeWidgetFactory; + class QtOccupantListWidget; class QtChatTheme; class TreeWidget; class QtTextEdit; @@ -60,6 +59,7 @@ namespace Swift { void setAckState(const std::string& id, AckState state); void flash(); QByteArray getSplitterState(); + virtual void setAvailableOccupantActions(const std::vector<OccupantAction>& actions); public slots: void handleChangeSplitterState(QByteArray state); @@ -88,13 +88,13 @@ namespace Swift { void handleKeyPressEvent(QKeyEvent* event); void handleSplitterMoved(int pos, int index); void handleAlertButtonClicked(); - private: void updateTitleWithUnreadCount(); void tabComplete(); void beginCorrection(); void cancelCorrection(); std::string addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const QString& style, const boost::posix_time::ptime& time); + void handleOccupantSelectionChanged(RosterItem* item); int unreadCount_; bool contactIsTyping_; @@ -105,7 +105,7 @@ namespace Swift { QtChatTheme* theme_; QtTextEdit* input_; QComboBox* labelsWidget_; - QtTreeWidget* treeWidget_; + QtOccupantListWidget* treeWidget_; QLabel* correctingLabel_; QLabel* alertLabel_; QWidget* alertWidget_; diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp index 7c84773..9d35435 100644 --- a/Swift/QtUI/QtMainWindow.cpp +++ b/Swift/QtUI/QtMainWindow.cpp @@ -62,7 +62,7 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS contactTabLayout->setSpacing(0); contactTabLayout->setContentsMargins(0, 0, 0, 0); - treeWidget_ = new QtRosterWidget(uiEventStream_); + treeWidget_ = new QtRosterWidget(uiEventStream_, this); contactTabLayout->addWidget(treeWidget_); tabs_->addTab(contactsTabWidget_, tr("&Contacts")); @@ -115,7 +115,7 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS connect(signOutAction, SIGNAL(triggered()), SLOT(handleSignOutAction())); actionsMenu->addAction(signOutAction); - connect(treeWidget_, SIGNAL(onSomethingSelectedChanged(bool)), editUserAction_, SLOT(setEnabled(bool))); + treeWidget_->onSomethingSelectedChanged.connect(boost::bind(&QAction::setEnabled, editUserAction_, _1)); setAvailableAdHocCommands(std::vector<DiscoItems::Item>()); QAction* adHocAction = new QAction(tr("Collecting commands..."), this); diff --git a/Swift/QtUI/Roster/QtOccupantListWidget.cpp b/Swift/QtUI/Roster/QtOccupantListWidget.cpp index 2f992bf..cbda0f1 100644 --- a/Swift/QtUI/Roster/QtOccupantListWidget.cpp +++ b/Swift/QtUI/Roster/QtOccupantListWidget.cpp @@ -9,6 +9,7 @@ #include <QContextMenuEvent> #include <QMenu> +#include <QAction> #include <QInputDialog> #include "Swift/Controllers/Roster/ContactRosterItem.h" @@ -26,33 +27,33 @@ QtOccupantListWidget::~QtOccupantListWidget() { } +void QtOccupantListWidget::setAvailableOccupantActions(const std::vector<ChatWindow::OccupantAction>& actions) { + availableOccupantActions_ = actions; +} + void QtOccupantListWidget::contextMenuEvent(QContextMenuEvent* event) { -// QModelIndex index = indexAt(event->pos()); -// if (!index.isValid()) { -// return; -// } -// RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); -// QMenu contextMenu; -// if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { -// QAction* editContact = contextMenu.addAction(tr("Edit")); -// QAction* removeContact = contextMenu.addAction(tr("Remove")); -// QAction* result = contextMenu.exec(event->globalPos()); -// if (result == editContact) { -// eventStream_->send(boost::make_shared<RequestContactEditorUIEvent>(contact->getJID())); -// } -// else if (result == removeContact) { -// if (QtContactEditWindow::confirmContactDeletion(contact->getJID())) { -// eventStream_->send(boost::make_shared<RemoveRosterItemUIEvent>(contact->getJID())); -// } -// } -// } -// else if (GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item)) { -// QAction* renameGroupAction = contextMenu.addAction(tr("Rename")); -// QAction* result = contextMenu.exec(event->globalPos()); -// if (result == renameGroupAction) { -// renameGroup(group); -// } -// } + QModelIndex index = indexAt(event->pos()); + if (!index.isValid()) { + return; + } + RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact) { + QMenu contextMenu; + std::map<QAction*, ChatWindow::OccupantAction> actions; + foreach (ChatWindow::OccupantAction availableAction, availableOccupantActions_) { + QString text = "Error: missing string"; + switch (availableAction) { + case ChatWindow::Kick: text = tr("Kick user"); break; + } + QAction* action = contextMenu.addAction(text); + actions[action] = availableAction; + } + QAction* result = contextMenu.exec(event->globalPos()); + if (result) { + onOccupantActionSelected(actions[result], contact); + } + } } } diff --git a/Swift/QtUI/Roster/QtOccupantListWidget.h b/Swift/QtUI/Roster/QtOccupantListWidget.h index 5444210..ef29c00 100644 --- a/Swift/QtUI/Roster/QtOccupantListWidget.h +++ b/Swift/QtUI/Roster/QtOccupantListWidget.h @@ -8,6 +8,9 @@ #include "Swift/QtUI/Roster/QtTreeWidget.h" +#include "Swiften/Base/boost_bsignals.h" +#include "Swift/Controllers/UIInterfaces/ChatWindow.h" + namespace Swift { class QtOccupantListWidget : public QtTreeWidget { @@ -15,8 +18,12 @@ class QtOccupantListWidget : public QtTreeWidget { public: QtOccupantListWidget(UIEventStream* eventStream, QWidget* parent = 0); virtual ~QtOccupantListWidget(); + void setAvailableOccupantActions(const std::vector<ChatWindow::OccupantAction>& actions); + boost::signal<void (ChatWindow::OccupantAction, ContactRosterItem*)> onOccupantActionSelected; protected: void contextMenuEvent(QContextMenuEvent* event); + private: + std::vector<ChatWindow::OccupantAction> availableOccupantActions_; }; } diff --git a/Swift/QtUI/Roster/QtRosterWidget.cpp b/Swift/QtUI/Roster/QtRosterWidget.cpp index 79d7f67..923f977 100644 --- a/Swift/QtUI/Roster/QtRosterWidget.cpp +++ b/Swift/QtUI/Roster/QtRosterWidget.cpp @@ -81,20 +81,4 @@ void QtRosterWidget::renameGroup(GroupRosterItem* group) { } } -void QtRosterWidget::currentChanged(const QModelIndex& current, const QModelIndex& previous) { - bool valid = false; - QModelIndexList selectedIndexList = getSelectedIndexes(); - if (selectedIndexList.empty() || !selectedIndexList[0].isValid()) { - /* I didn't quite understand why using current didn't seem to work here.*/ - } - else if (current.isValid()) { - RosterItem* item = static_cast<RosterItem*>(current.internalPointer()); - if (dynamic_cast<ContactRosterItem*>(item)) { - valid = true; - } - } - onSomethingSelectedChanged(valid); - QTreeView::currentChanged(current, previous); -} - } diff --git a/Swift/QtUI/Roster/QtRosterWidget.h b/Swift/QtUI/Roster/QtRosterWidget.h index 7781e07..f870237 100644 --- a/Swift/QtUI/Roster/QtRosterWidget.h +++ b/Swift/QtUI/Roster/QtRosterWidget.h @@ -9,7 +9,6 @@ #include "Swift/QtUI/Roster/QtTreeWidget.h" namespace Swift { - class QtRosterWidget : public QtTreeWidget { Q_OBJECT public: @@ -17,12 +16,8 @@ class QtRosterWidget : public QtTreeWidget { virtual ~QtRosterWidget(); public slots: void handleEditUserActionTriggered(bool checked); - signals: - void onSomethingSelectedChanged(bool); protected: void contextMenuEvent(QContextMenuEvent* event); - protected slots: - virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous); private: void renameGroup(GroupRosterItem* group); }; diff --git a/Swift/QtUI/Roster/QtTreeWidget.cpp b/Swift/QtUI/Roster/QtTreeWidget.cpp index 7de4028..96a078b 100644 --- a/Swift/QtUI/Roster/QtTreeWidget.cpp +++ b/Swift/QtUI/Roster/QtTreeWidget.cpp @@ -81,6 +81,21 @@ QModelIndexList QtTreeWidget::getSelectedIndexes() const { return selectedIndexList; } +void QtTreeWidget::currentChanged(const QModelIndex& current, const QModelIndex& previous) { + RosterItem* item = NULL; + QModelIndexList selectedIndexList = getSelectedIndexes(); + if (selectedIndexList.empty() || !selectedIndexList[0].isValid()) { + /* I didn't quite understand why using current didn't seem to work here.*/ + } + else if (current.isValid()) { + item = static_cast<RosterItem*>(current.internalPointer()); + item = dynamic_cast<ContactRosterItem*>(item); + } + onSomethingSelectedChanged(item); + QTreeView::currentChanged(current, previous); +} + + void QtTreeWidget::handleItemActivated(const QModelIndex& index) { RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); diff --git a/Swift/QtUI/Roster/QtTreeWidget.h b/Swift/QtUI/Roster/QtTreeWidget.h index c45a1cd..1ad56d6 100644 --- a/Swift/QtUI/Roster/QtTreeWidget.h +++ b/Swift/QtUI/Roster/QtTreeWidget.h @@ -23,6 +23,7 @@ class QtTreeWidget : public QTreeView{ QtTreeWidgetItem* getRoot(); void setRosterModel(Roster* roster); Roster* getRoster() {return roster_;} + boost::signal<void (RosterItem*)> onSomethingSelectedChanged; private slots: void handleItemActivated(const QModelIndex&); @@ -35,7 +36,8 @@ class QtTreeWidget : public QTreeView{ QModelIndexList getSelectedIndexes() const; private: void drawBranches(QPainter*, const QRect&, const QModelIndex&) const; - + protected slots: + virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous); protected: UIEventStream* eventStream_; diff --git a/Swiften/Elements/MUCAdminPayload.h b/Swiften/Elements/MUCAdminPayload.h new file mode 100644 index 0000000..7b3da20 --- /dev/null +++ b/Swiften/Elements/MUCAdminPayload.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <boost/optional.hpp> +#include <boost/shared_ptr.hpp> +#include <string> +#include <vector> + +#include <Swiften/JID/JID.h> +#include <Swiften/Elements/Payload.h> +#include <Swiften/Elements/MUCOccupant.h> +#include <Swiften/Elements/MUCItem.h> + +namespace Swift { + class MUCAdminPayload : public Payload { + public: + typedef boost::shared_ptr<MUCAdminPayload> ref; + + + MUCAdminPayload() { + } + + void addItem(MUCItem item) {items_.push_back(item);} + + const std::vector<MUCItem>& getItems() const {return items_;} + + private: + std::vector<MUCItem> items_; + }; +} diff --git a/Swiften/Elements/MUCItem.h b/Swiften/Elements/MUCItem.h new file mode 100644 index 0000000..86217ec --- /dev/null +++ b/Swiften/Elements/MUCItem.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/Elements/MUCOccupant.h> +#include <Swiften/JID/JID.h> +namespace Swift { +struct MUCItem { + MUCItem() {} + boost::optional<JID> realJID; + boost::optional<std::string> nick; + boost::optional<MUCOccupant::Affiliation> affiliation; + boost::optional<MUCOccupant::Role> role; + boost::optional<JID> actor; + boost::optional<std::string> reason; +}; +} diff --git a/Swiften/Elements/MUCUserPayload.h b/Swiften/Elements/MUCUserPayload.h index 0276964..de9f1e5 100644 --- a/Swiften/Elements/MUCUserPayload.h +++ b/Swiften/Elements/MUCUserPayload.h @@ -14,20 +14,13 @@ #include <Swiften/JID/JID.h> #include <Swiften/Elements/Payload.h> #include <Swiften/Elements/MUCOccupant.h> +#include <Swiften/Elements/MUCItem.h> namespace Swift { class MUCUserPayload : public Payload { public: typedef boost::shared_ptr<MUCUserPayload> ref; - struct Item { - Item(MUCOccupant::Affiliation affiliation = MUCOccupant::NoAffiliation, MUCOccupant::Role role = MUCOccupant::NoRole) : affiliation(affiliation), role(role) {} - boost::optional<JID> realJID; - boost::optional<std::string> nick; - MUCOccupant::Affiliation affiliation; - MUCOccupant::Role role; - }; - struct StatusCode { StatusCode() : code(0) {} int code; @@ -48,16 +41,16 @@ namespace Swift { MUCUserPayload() { } - void addItem(Item item) {items_.push_back(item);} + void addItem(MUCItem item) {items_.push_back(item);} void addStatusCode(StatusCode code) {statusCodes_.push_back(code);} - const std::vector<Item>& getItems() const {return items_;} + const std::vector<MUCItem>& getItems() const {return items_;} const std::vector<StatusCode>& getStatusCodes() const {return statusCodes_;} private: - std::vector<Item> items_; + std::vector<MUCItem> items_; std::vector<StatusCode> statusCodes_; }; } diff --git a/Swiften/MUC/MUC.cpp b/Swiften/MUC/MUC.cpp index 1ade7e3..4b3960f 100644 --- a/Swiften/MUC/MUC.cpp +++ b/Swiften/MUC/MUC.cpp @@ -17,6 +17,7 @@ #include <Swiften/Elements/Form.h> #include <Swiften/Elements/IQ.h> #include <Swiften/Elements/MUCUserPayload.h> +#include <Swiften/Elements/MUCAdminPayload.h> #include <Swiften/Elements/MUCPayload.h> #include <Swiften/MUC/MUCRegistry.h> #include <Swiften/Queries/GenericRequest.h> @@ -121,8 +122,8 @@ void MUC::handleIncomingPresence(Presence::ref presence) { MUCOccupant::Affiliation affiliation(MUCOccupant::NoAffiliation); boost::optional<JID> realJID; if (mucPayload && mucPayload->getItems().size() > 0) { - role = mucPayload->getItems()[0].role; - affiliation = mucPayload->getItems()[0].affiliation; + role = mucPayload->getItems()[0].role ? mucPayload->getItems()[0].role.get() : MUCOccupant::NoRole; + affiliation = mucPayload->getItems()[0].affiliation ? mucPayload->getItems()[0].affiliation.get() : MUCOccupant::NoAffiliation; realJID = mucPayload->getItems()[0].realJID; } @@ -217,6 +218,23 @@ MUCOccupant MUC::getOccupant(const std::string& nick) { return occupants.find(nick)->second; } +void MUC::kickUser(const JID& jid) { + MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>(); + MUCItem item; + item.role = MUCOccupant::NoRole; + item.nick = jid.getResource(); + mucPayload->addItem(item); + GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Set, getJID(), mucPayload, iqRouter_); + request->onResponse.connect(boost::bind(&MUC::handleKickResponse, this, _1, _2, jid)); + request->send(); +} + +void MUC::handleKickResponse(MUCAdminPayload::ref /*unused*/, ErrorPayload::ref error, const JID& jid) { + if (error) { + onKickFailed(error, jid); + } +} + //FIXME: Recognise Topic changes //TODO: Invites(direct/mediated) diff --git a/Swiften/MUC/MUC.h b/Swiften/MUC/MUC.h index 828a36f..41dbc4a 100644 --- a/Swiften/MUC/MUC.h +++ b/Swiften/MUC/MUC.h @@ -13,6 +13,7 @@ #include <Swiften/Elements/MUCOccupant.h> #include <Swiften/MUC/MUCRegistry.h> #include <Swiften/Elements/MUCOwnerPayload.h> +#include <Swiften/Elements/MUCAdminPayload.h> #include <boost/shared_ptr.hpp> #include <Swiften/Base/boost_bsignals.h> @@ -54,9 +55,11 @@ namespace Swift { /** Get occupant information*/ MUCOccupant getOccupant(const std::string& nick); bool hasOccupant(const std::string& nick); + void kickUser(const JID& jid); public: boost::signal<void (const std::string& /*nick*/)> onJoinComplete; boost::signal<void (ErrorPayload::ref)> onJoinFailed; + boost::signal<void (ErrorPayload::ref, const JID&)> onKickFailed; boost::signal<void (Presence::ref)> onOccupantPresenceChange; boost::signal<void (const std::string&, const MUCOccupant& /*now*/, const MUCOccupant::Role& /*old*/)> onOccupantRoleChanged; boost::signal<void (const std::string&, const MUCOccupant::Affiliation& /*new*/, const MUCOccupant::Affiliation& /*old*/)> onOccupantAffiliationChanged; @@ -79,6 +82,7 @@ namespace Swift { void handleIncomingPresence(Presence::ref presence); void internalJoin(const std::string& nick); void handleCreationConfigResponse(MUCOwnerPayload::ref, ErrorPayload::ref); + void handleKickResponse(MUCAdminPayload::ref, ErrorPayload::ref, const JID&); private: JID ownMUCJID; diff --git a/Swiften/Parser/GenericPayloadTreeParser.cpp b/Swiften/Parser/GenericPayloadTreeParser.cpp new file mode 100644 index 0000000..0e25cbc --- /dev/null +++ b/Swiften/Parser/GenericPayloadTreeParser.cpp @@ -0,0 +1,8 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/Parser/GenericPayloadTreeParser.h> + diff --git a/Swiften/Parser/GenericPayloadTreeParser.h b/Swiften/Parser/GenericPayloadTreeParser.h new file mode 100644 index 0000000..0030af7 --- /dev/null +++ b/Swiften/Parser/GenericPayloadTreeParser.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <deque> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Parser/GenericPayloadParser.h> +#include <Swiften/Parser/Tree/ParserElement.h> +#include <Swiften/Parser/Tree/NullParserElement.h> + +#include <iostream> + +namespace Swift { + + + /** + * Generic parser offering something a bit like a DOM to work with. + */ + template<typename PAYLOAD_TYPE> + class GenericPayloadTreeParser : public GenericPayloadParser<PAYLOAD_TYPE> { + public: + virtual void handleStartElement(const std::string& element, const std::string& xmlns, const AttributeMap& attributes) { + //std::cerr << element << ", " << xmlns << ", " << attributes.getEntries().size(); + if (!root_) { + root_ = boost::make_shared<ParserElement>(element, xmlns, attributes); + elementStack_.push_back(root_); + } else { + ParserElement::ref current = *elementStack_.rbegin(); + elementStack_.push_back(current->addChild(element, xmlns, attributes)); + } + } + + virtual void handleEndElement(const std::string& /*element*/, const std::string&) { + elementStack_.pop_back(); + if (elementStack_.empty()) { + handleTree(root_); + } + } + + virtual void handleCharacterData(const std::string& data) { + ParserElement::ref current = *elementStack_.rbegin(); + current->appendCharacterData(data); + } + + virtual void handleTree(ParserElement::ref root) = 0; + + private: + std::deque<ParserElement::ref> elementStack_; + ParserElement::ref root_; + }; +} diff --git a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp index e9c49ee..1b59f6f 100644 --- a/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp +++ b/Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.cpp @@ -44,6 +44,7 @@ #include <Swiften/Parser/PayloadParsers/PrivateStorageParserFactory.h> #include <Swiften/Parser/PayloadParsers/DelayParser.h> #include <Swiften/Parser/PayloadParsers/MUCUserPayloadParserFactory.h> +#include <Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.h> #include <Swiften/Parser/PayloadParsers/NicknameParserFactory.h> #include <Swiften/Parser/PayloadParsers/ReplaceParser.h> #include <Swiften/Parser/PayloadParsers/LastParser.h> @@ -88,6 +89,7 @@ FullPayloadParserFactoryCollection::FullPayloadParserFactoryCollection() { factories_.push_back(shared_ptr<PayloadParserFactory>(new PrivateStorageParserFactory(this))); factories_.push_back(shared_ptr<PayloadParserFactory>(new ChatStateParserFactory())); factories_.push_back(shared_ptr<PayloadParserFactory>(new MUCUserPayloadParserFactory())); + factories_.push_back(shared_ptr<PayloadParserFactory>(new GenericPayloadParserFactory<MUCAdminPayloadParser>("query", "http://jabber.org/protocol/muc#admin"))); factories_.push_back(shared_ptr<PayloadParserFactory>(new NicknameParserFactory())); foreach(shared_ptr<PayloadParserFactory> factory, factories_) { addFactory(factory.get()); diff --git a/Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.cpp b/Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.cpp new file mode 100644 index 0000000..137ead4 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.h> + +#include <boost/lexical_cast.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Elements/MUCOccupant.h> + +namespace Swift { + +void MUCAdminPayloadParser::handleTree(ParserElement::ref root) { + foreach (ParserElement::ref itemElement, root->getChildren("item", "http://jabber.org/protocol/muc#admin")) { + MUCItem item = MUCItemParser::itemFromTree(itemElement); + getPayloadInternal()->addItem(item); + } +} + +} diff --git a/Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.h b/Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.h new file mode 100644 index 0000000..dfa26da --- /dev/null +++ b/Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.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 <boost/optional.hpp> + +#include <Swiften/Elements/MUCAdminPayload.h> +#include <Swiften/Parser/GenericPayloadTreeParser.h> +#include <Swiften/Parser/PayloadParsers/MUCItemParser.h> + +namespace Swift { + class MUCAdminPayloadParser : public GenericPayloadTreeParser<MUCAdminPayload> { + public: + virtual void handleTree(ParserElement::ref root); + }; +} diff --git a/Swiften/Parser/PayloadParsers/MUCItemParser.cpp b/Swiften/Parser/PayloadParsers/MUCItemParser.cpp new file mode 100644 index 0000000..2eb9997 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/MUCItemParser.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/Parser/PayloadParsers/MUCItemParser.h> + +#include <boost/lexical_cast.hpp> + +#include <Swiften/Elements/MUCOccupant.h> + +#include <cassert> +#include <iostream> + +namespace Swift { + +MUCItem MUCItemParser::itemFromTree(ParserElement::ref root) { + MUCItem item; + std::string affiliation = root->getAttributes().getAttribute("affiliation"); + std::string role = root->getAttributes().getAttribute("role"); + std::string nick = root->getAttributes().getAttribute("nick"); + std::string jid = root->getAttributes().getAttribute("jid"); + item.affiliation = parseAffiliation(affiliation); + item.role = parseRole(role); + if (!jid.empty()) { + item.realJID = JID(jid); + } + if (!nick.empty()) { + item.nick = nick; + } + std::string xmlns = root->getNamespace(); + std::string reason = root->getChild("reason", xmlns)->getText(); + std::string actor = root->getChild("actor", xmlns)->getAttributes().getAttribute("jid"); + if (!reason.empty()) { + item.reason = reason; + } + if (!actor.empty()) { + item.actor = JID(actor); + } + + return item; +} + +boost::optional<MUCOccupant::Role> MUCItemParser::parseRole(const std::string& roleString) { + if (roleString == "moderator") { + return MUCOccupant::Moderator; + } + if (roleString == "participant") { + return MUCOccupant::Participant; + } + if (roleString == "visitor") { + return MUCOccupant::Visitor; + } + if (roleString == "none") { + return MUCOccupant::NoRole; + } + return boost::optional<MUCOccupant::Role>(); +} + +boost::optional<MUCOccupant::Affiliation> MUCItemParser::parseAffiliation(const std::string& affiliationString) { + if (affiliationString == "owner") { + return MUCOccupant::Owner; + } + if (affiliationString == "admin") { + return MUCOccupant::Admin; + } + if (affiliationString == "member") { + return MUCOccupant::Member; + } + if (affiliationString == "outcast") { + return MUCOccupant::Outcast; + } + if (affiliationString == "none") { + return MUCOccupant::NoAffiliation; + } + return boost::optional<MUCOccupant::Affiliation>(); +} + +} + + diff --git a/Swiften/Parser/PayloadParsers/MUCItemParser.h b/Swiften/Parser/PayloadParsers/MUCItemParser.h new file mode 100644 index 0000000..a0ea060 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/MUCItemParser.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 <Swiften/Elements/MUCItem.h> +#include <Swiften/Parser/GenericPayloadTreeParser.h> + +namespace Swift { + class MUCItemParser { + public: + static MUCItem itemFromTree(ParserElement::ref root); + private: + static boost::optional<MUCOccupant::Role> parseRole(const std::string& itemString); + static boost::optional<MUCOccupant::Affiliation> parseAffiliation(const std::string& statusString); + }; +} diff --git a/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp index bd81b88..71ae571 100644 --- a/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp +++ b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.cpp @@ -8,87 +8,24 @@ #include <boost/lexical_cast.hpp> +#include <Swiften/Base/foreach.h> #include <Swiften/Elements/MUCOccupant.h> -#include <cassert> -#include <iostream> - namespace Swift { -MUCUserPayloadParser::MUCUserPayloadParser() : level(TopLevel) { -} - -void MUCUserPayloadParser::handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes) { - if (level == ItemLevel) { - if (element == "item") { - MUCUserPayload::Item item; - std::string affiliation = attributes.getAttribute("affiliation"); - std::string role = attributes.getAttribute("role"); - std::string nick = attributes.getAttribute("nick"); - std::string jid = attributes.getAttribute("jid"); - item.affiliation = parseAffiliation(affiliation); - item.role = parseRole(role); - if (!jid.empty()) { - item.realJID = JID(jid); - } - if (!nick.empty()) { - item.nick = nick; - } - getPayloadInternal()->addItem(item); - } else if (element == "status") { - MUCUserPayload::StatusCode status; - try { - status.code = boost::lexical_cast<int>(attributes.getAttribute("code").c_str()); - getPayloadInternal()->addStatusCode(status); - } catch (boost::bad_lexical_cast&) { - } +void MUCUserPayloadParser::handleTree(ParserElement::ref root) { + foreach (ParserElement::ref itemElement, root->getChildren("item", "http://jabber.org/protocol/muc#user")) { + MUCItem item = MUCItemParser::itemFromTree(itemElement); + getPayloadInternal()->addItem(item); + } + foreach (ParserElement::ref statusElement, root->getChildren("item", "http://jabber.org/protocol/muc#user")) { + MUCUserPayload::StatusCode status; + try { + status.code = boost::lexical_cast<int>(statusElement->getAttributes().getAttribute("code").c_str()); + getPayloadInternal()->addStatusCode(status); + } catch (boost::bad_lexical_cast&) { } } - ++level; -} - -MUCOccupant::Role MUCUserPayloadParser::parseRole(const std::string& roleString) const { - if (roleString == "moderator") { - return MUCOccupant::Moderator; - } - if (roleString == "participant") { - return MUCOccupant::Participant; - } - if (roleString == "visitor") { - return MUCOccupant::Visitor; - } - if (roleString == "none") { - return MUCOccupant::NoRole; - } - return MUCOccupant::NoRole; -} - -MUCOccupant::Affiliation MUCUserPayloadParser::parseAffiliation(const std::string& affiliationString) const { - if (affiliationString == "owner") { - return MUCOccupant::Owner; - } - if (affiliationString == "admin") { - return MUCOccupant::Admin; - } - if (affiliationString == "member") { - return MUCOccupant::Member; - } - if (affiliationString == "outcast") { - return MUCOccupant::Outcast; - } - if (affiliationString == "none") { - return MUCOccupant::NoAffiliation; - } - return MUCOccupant::NoAffiliation; -} - - -void MUCUserPayloadParser::handleEndElement(const std::string& /*element*/, const std::string&) { - --level; -} - -void MUCUserPayloadParser::handleCharacterData(const std::string& /*data*/) { - } } diff --git a/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h index b819905..e020127 100644 --- a/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h +++ b/Swiften/Parser/PayloadParsers/MUCUserPayloadParser.h @@ -9,23 +9,12 @@ #include <boost/optional.hpp> #include <Swiften/Elements/MUCUserPayload.h> -#include <Swiften/Parser/GenericPayloadParser.h> +#include <Swiften/Parser/GenericPayloadTreeParser.h> +#include <Swiften/Parser/PayloadParsers/MUCItemParser.h> namespace Swift { - class MUCUserPayloadParser : public GenericPayloadParser<MUCUserPayload> { + class MUCUserPayloadParser : public GenericPayloadTreeParser<MUCUserPayload> { public: - MUCUserPayloadParser(); - - virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap& attributes); - virtual void handleEndElement(const std::string& element, const std::string&); - virtual void handleCharacterData(const std::string& data); - MUCOccupant::Role parseRole(const std::string& itemString) const; - MUCOccupant::Affiliation parseAffiliation(const std::string& statusString) const; - private: - enum Level { - TopLevel = 0, - ItemLevel = 1 - }; - int level; + virtual void handleTree(ParserElement::ref root); }; } diff --git a/Swiften/Parser/PayloadParsers/UnitTest/MUCAdminPayloadParserTest.cpp b/Swiften/Parser/PayloadParsers/UnitTest/MUCAdminPayloadParserTest.cpp new file mode 100644 index 0000000..0648f58 --- /dev/null +++ b/Swiften/Parser/PayloadParsers/UnitTest/MUCAdminPayloadParserTest.cpp @@ -0,0 +1,41 @@ +/* + * 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 <Swiften/Parser/PayloadParsers/MUCAdminPayloadParser.h> +#include <Swiften/Parser/PayloadParsers/UnitTest/PayloadsParserTester.h> + +using namespace Swift; + +class MUCAdminPayloadParserTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(MUCAdminPayloadParserTest); + CPPUNIT_TEST(testParse); + CPPUNIT_TEST_SUITE_END(); + + public: + MUCAdminPayloadParserTest() {} + + void testParse() { + PayloadsParserTester parser; + + CPPUNIT_ASSERT(parser.parse("<query xmlns=\"http://jabber.org/protocol/muc#admin\"><item affiliation=\"owner\" role=\"visitor\"><actor jid=\"kev@tester.lit\"/><reason>malice</reason></item></query>")); + + MUCAdminPayload::ref payload = boost::dynamic_pointer_cast<MUCAdminPayload>(parser.getPayload()); + MUCItem item = payload->getItems()[0]; + CPPUNIT_ASSERT_EQUAL(MUCOccupant::Owner, item.affiliation.get()); + CPPUNIT_ASSERT_EQUAL(MUCOccupant::Visitor, item.role.get()); + CPPUNIT_ASSERT_EQUAL(JID("kev@tester.lit"), item.actor.get()); + CPPUNIT_ASSERT_EQUAL(std::string("malice"), item.reason.get()); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(MUCAdminPayloadParserTest); + + + diff --git a/Swiften/Parser/SConscript b/Swiften/Parser/SConscript index 17505f1..b9fcebb 100644 --- a/Swiften/Parser/SConscript +++ b/Swiften/Parser/SConscript @@ -14,6 +14,7 @@ sources = [ "CompressParser.cpp", "ElementParser.cpp", "IQParser.cpp", + "GenericPayloadTreeParser.cpp", "MessageParser.cpp", "PayloadParser.cpp", "StanzaAckParser.cpp", @@ -51,6 +52,8 @@ sources = [ "PayloadParsers/VCardUpdateParser.cpp", "PayloadParsers/DelayParser.cpp", "PayloadParsers/MUCUserPayloadParser.cpp", + "PayloadParsers/MUCAdminPayloadParser.cpp", + "PayloadParsers/MUCItemParser.cpp", "PayloadParsers/NicknameParser.cpp", "PayloadParsers/ReplaceParser.cpp", "PayloadParsers/LastParser.cpp", @@ -63,6 +66,7 @@ sources = [ "StreamManagementEnabledParser.cpp", "StreamResumeParser.cpp", "StreamResumedParser.cpp", + "Tree/ParserElement.cpp", "XMLParser.cpp", "XMLParserClient.cpp", "XMLParserFactory.cpp", diff --git a/Swiften/Parser/Tree/NullParserElement.h b/Swiften/Parser/Tree/NullParserElement.h new file mode 100644 index 0000000..93c0662 --- /dev/null +++ b/Swiften/Parser/Tree/NullParserElement.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <string> +#include <Swiften/Parser/Tree/ParserElement.h> + +namespace Swift { + + class NullParserElement : public ParserElement { + public: + NullParserElement() : ParserElement("", "", AttributeMap()) {} + virtual operator bool() {return false;}; + }; +} diff --git a/Swiften/Parser/Tree/ParserElement.cpp b/Swiften/Parser/Tree/ParserElement.cpp new file mode 100644 index 0000000..c851b41 --- /dev/null +++ b/Swiften/Parser/Tree/ParserElement.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + + +#include <Swiften/Parser/Tree/ParserElement.h> +#include <Swiften/Parser/Tree/NullParserElement.h> + +#include <iostream> + +namespace Swift{ + +ParserElement::ParserElement(const std::string& name, const std::string& xmlns, const AttributeMap& attributes) : name_(name), xmlns_(xmlns), attributes_(attributes) { + +} + +ParserElement::~ParserElement() { + +} + +ParserElement::operator bool() { + return true; +} + +ParserElement::ref ParserElement::addChild(const std::string& name, const std::string& xmlns, const AttributeMap& attributes) { + ParserElement::ref child = boost::make_shared<ParserElement>(name, xmlns, attributes); + children_.push_back(child); + return child; +} + +void ParserElement::appendCharacterData(const std::string& data) { + text_ += data; +} + +std::string ParserElement::getText() { + return text_; +} + +std::string ParserElement::getName() { + return name_; +} + +std::string ParserElement::getNamespace() { + return xmlns_; +} + +struct DoesntMatch { + public: + DoesntMatch(const std::string& tagName, const std::string& ns) : tagName(tagName), ns(ns) {} + bool operator()(ParserElement::ref element) { return element->getName() != tagName || element->getNamespace() != ns; } + private: + std::string tagName; + std::string ns; +}; + + +std::vector<ParserElement::ref> ParserElement::getChildren(const std::string& name, const std::string& xmlns) { + std::vector<ParserElement::ref> result; + std::remove_copy_if(children_.begin(), children_.end(), std::back_inserter(result), DoesntMatch(name, xmlns)); + return result; +} + +ParserElement::ref ParserElement::getChild(const std::string& name, const std::string& xmlns) { + std::vector<ParserElement::ref> results = getChildren(name, xmlns); + boost::shared_ptr<NullParserElement> nullParser = boost::make_shared<NullParserElement>(); + ParserElement::ref result = results.empty() ? boost::dynamic_pointer_cast<ParserElement>(nullParser) : results[0]; + return result; +} + +} diff --git a/Swiften/Parser/Tree/ParserElement.h b/Swiften/Parser/Tree/ParserElement.h new file mode 100644 index 0000000..ddf67fa --- /dev/null +++ b/Swiften/Parser/Tree/ParserElement.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2011 Kevin Smith + * 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/Parser/AttributeMap.h> +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +namespace Swift { +class ParserElement { + public: + typedef boost::shared_ptr<ParserElement> ref; + ParserElement(const std::string& name, const std::string& xmlns, const AttributeMap& attributes); + virtual ~ParserElement(); + virtual operator bool(); + ParserElement::ref addChild(const std::string& name, const std::string& xmlns, const AttributeMap& attributes); + void appendCharacterData(const std::string& data); + std::string getText(); + std::string getName(); + std::string getNamespace(); + std::vector<ParserElement::ref> getChildren(const std::string& name, const std::string& xmlns); + ParserElement::ref getChild(const std::string& name, const std::string& xmlns); + const AttributeMap& getAttributes() const {return attributes_;} + private: + std::vector<ParserElement::ref> children_; + std::string name_; + std::string xmlns_; + AttributeMap attributes_; + std::string text_; + +}; + +} diff --git a/Swiften/Parser/UnitTest/GenericPayloadTreeParserTest.cpp b/Swiften/Parser/UnitTest/GenericPayloadTreeParserTest.cpp new file mode 100644 index 0000000..d095afc --- /dev/null +++ b/Swiften/Parser/UnitTest/GenericPayloadTreeParserTest.cpp @@ -0,0 +1,58 @@ +/* + * 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 <Swiften/Parser/GenericPayloadTreeParser.h> +#include <Swiften/Parser/PayloadParsers/UnitTest/PayloadParserTester.h> +#include <Swiften/Elements/RawXMLPayload.h> + +using namespace Swift; + +class GenericPayloadTreeParserTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(GenericPayloadTreeParserTest); + CPPUNIT_TEST(testTree); + CPPUNIT_TEST_SUITE_END(); + + public: + void testTree() { + MyParser testling; + + std::string data = "<topLevel xmlns='urn:test:top'><firstLevelInheritedEmpty/><firstLevelInherited><secondLevelMultiChildren num='1'/><secondLevelMultiChildren num='2'/></firstLevelInherited><firstLevelNS xmlns='urn:test:first'/></topLevel>"; + + PayloadParserTester tester(&testling); + tester.parse(data); + + ParserElement::ref tree = testling.tree; + + CPPUNIT_ASSERT_EQUAL(std::string("topLevel"), tree->getName()); + CPPUNIT_ASSERT_EQUAL(std::string("urn:test:top"), tree->getNamespace()); + CPPUNIT_ASSERT(tree->getChild("firstLevelInheritedEmpty", "urn:test:top")); + CPPUNIT_ASSERT(!*tree->getChild("firstLevelInheritedEmpty", "")); + CPPUNIT_ASSERT(tree->getChild("firstLevelInherited", "urn:test:top")); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), tree->getChild("firstLevelInherited", "urn:test:top")->getChildren("secondLevelMultiChildren", "urn:test:top").size()); + CPPUNIT_ASSERT_EQUAL(std::string("1"), tree->getChild("firstLevelInherited", "urn:test:top")->getChildren("secondLevelMultiChildren", "urn:test:top")[0]->getAttributes().getAttribute("num")); + CPPUNIT_ASSERT_EQUAL(std::string("2"), tree->getChild("firstLevelInherited", "urn:test:top")->getChildren("secondLevelMultiChildren", "urn:test:top")[1]->getAttributes().getAttribute("num")); + CPPUNIT_ASSERT(tree->getChild("firstLevelNS", "urn:test:first")); + } + + private: + + + class MyParser : public GenericPayloadTreeParser<RawXMLPayload> + { + public: + virtual ~MyParser() {} + virtual void handleTree(ParserElement::ref root) { + tree = root; + } + ParserElement::ref tree; + }; + +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(GenericPayloadTreeParserTest); diff --git a/Swiften/SConscript b/Swiften/SConscript index 0144ddb..e20e5e6 100644 --- a/Swiften/SConscript +++ b/Swiften/SConscript @@ -146,6 +146,7 @@ if env["SCONS_STAGE"] == "build" : "Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp", "Serializer/PayloadSerializers/MUCPayloadSerializer.cpp", "Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp", + "Serializer/PayloadSerializers/MUCAdminPayloadSerializer.cpp", "Serializer/PayloadSerializers/MUCOwnerPayloadSerializer.cpp", "Serializer/PayloadSerializers/ResourceBindSerializer.cpp", "Serializer/PayloadSerializers/RosterItemExchangeSerializer.cpp", @@ -292,8 +293,10 @@ if env["SCONS_STAGE"] == "build" : File("Parser/PayloadParsers/UnitTest/PrivateStorageParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/VCardUpdateParserTest.cpp"), File("Parser/PayloadParsers/UnitTest/ReplaceTest.cpp"), + File("Parser/PayloadParsers/UnitTest/MUCAdminPayloadParserTest.cpp"), File("Parser/UnitTest/AttributeMapTest.cpp"), File("Parser/UnitTest/IQParserTest.cpp"), + File("Parser/UnitTest/GenericPayloadTreeParserTest.cpp"), File("Parser/UnitTest/MessageParserTest.cpp"), File("Parser/UnitTest/PayloadParserFactoryCollectionTest.cpp"), File("Parser/UnitTest/PresenceParserTest.cpp"), @@ -336,6 +339,7 @@ if env["SCONS_STAGE"] == "build" : File("Serializer/PayloadSerializers/UnitTest/StorageSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/PrivateStorageSerializerTest.cpp"), File("Serializer/PayloadSerializers/UnitTest/ReplaceSerializerTest.cpp"), + File("Serializer/PayloadSerializers/UnitTest/MUCAdminPayloadSerializerTest.cpp"), File("Serializer/UnitTest/StreamFeaturesSerializerTest.cpp"), File("Serializer/UnitTest/AuthSuccessSerializerTest.cpp"), File("Serializer/UnitTest/AuthChallengeSerializerTest.cpp"), diff --git a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp index 0746c37..0ddd445 100644 --- a/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp +++ b/Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.cpp @@ -21,6 +21,7 @@ #include <Swiften/Serializer/PayloadSerializers/RosterItemExchangeSerializer.h> #include <Swiften/Serializer/PayloadSerializers/MUCPayloadSerializer.h> #include <Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h> +#include <Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.h> #include <Swiften/Serializer/PayloadSerializers/MUCOwnerPayloadSerializer.h> #include <Swiften/Serializer/PayloadSerializers/SoftwareVersionSerializer.h> #include <Swiften/Serializer/PayloadSerializers/StatusSerializer.h> @@ -61,6 +62,7 @@ FullPayloadSerializerCollection::FullPayloadSerializerCollection() { serializers_.push_back(new RosterItemExchangeSerializer()); serializers_.push_back(new MUCPayloadSerializer()); serializers_.push_back(new MUCUserPayloadSerializer()); + serializers_.push_back(new MUCAdminPayloadSerializer()); serializers_.push_back(new MUCOwnerPayloadSerializer(this)); serializers_.push_back(new SoftwareVersionSerializer()); serializers_.push_back(new StatusSerializer()); diff --git a/Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.cpp new file mode 100644 index 0000000..552f7f1 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.h> + +#include <sstream> + +#include <boost/shared_ptr.hpp> +#include <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Base/foreach.h> +#include <Swiften/Serializer/XML/XMLElement.h> +#include <Swiften/Serializer/XML/XMLTextNode.h> +#include <Swiften/Serializer/PayloadSerializers/MUCItemSerializer.h> + + +namespace Swift { + +MUCAdminPayloadSerializer::MUCAdminPayloadSerializer() : GenericPayloadSerializer<MUCAdminPayload>() { +} + +std::string MUCAdminPayloadSerializer::serializePayload(boost::shared_ptr<MUCAdminPayload> payload) const { + XMLElement mucElement("query", "http://jabber.org/protocol/muc#admin"); + foreach (const MUCItem item, payload->getItems()) { + mucElement.addNode(MUCItemSerializer::itemToElement(item)); + } + return mucElement.serialize(); +} + + +} diff --git a/Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.h new file mode 100644 index 0000000..e288cd7 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Elements/MUCAdminPayload.h> + +namespace Swift { + class MUCAdminPayloadSerializer : public GenericPayloadSerializer<MUCAdminPayload> { + public: + MUCAdminPayloadSerializer(); + std::string affiliationToString(MUCOccupant::Affiliation affiliation) const; + std::string roleToString(MUCOccupant::Role role) const; + + virtual std::string serializePayload(boost::shared_ptr<MUCAdminPayload> version) const; + }; +} + diff --git a/Swiften/Serializer/PayloadSerializers/MUCItemSerializer.h b/Swiften/Serializer/PayloadSerializers/MUCItemSerializer.h new file mode 100644 index 0000000..7cb662c --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/MUCItemSerializer.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <Swiften/Serializer/GenericPayloadSerializer.h> +#include <Swiften/Elements/MUCItem.h> +#include <boost/smart_ptr/make_shared.hpp> + +namespace Swift { + class MUCItemSerializer { + public: + static std::string affiliationToString(MUCOccupant::Affiliation affiliation) { + std::string result; + switch (affiliation) { + case MUCOccupant::Owner: result = "owner"; break; + case MUCOccupant::Admin: result = "admin"; break; + case MUCOccupant::Member: result = "member"; break; + case MUCOccupant::Outcast: result = "outcast"; break; + case MUCOccupant::NoAffiliation: result = "none"; break; + default: assert(false); + } + return result; + } + + static std::string roleToString(MUCOccupant::Role role) { + std::string result; + switch (role) { + case MUCOccupant::Moderator: result = "moderator"; break; + case MUCOccupant::NoRole: result = "none"; break; + case MUCOccupant::Participant: result = "participant"; break; + case MUCOccupant::Visitor: result = "visitor"; break; + default: assert(false); + } + return result; + + } + + static boost::shared_ptr<XMLElement> itemToElement(const MUCItem& item) { + boost::shared_ptr<XMLElement> itemElement(new XMLElement("item")); + if (item.affiliation) { + itemElement->setAttribute("affiliation", affiliationToString(item.affiliation.get())); + } + if (item.role) { + itemElement->setAttribute("role", roleToString(item.role.get())); + } + if (item.realJID) { + itemElement->setAttribute("jid", item.realJID.get()); + } + if (item.nick) { + itemElement->setAttribute("nick", item.nick.get()); + } + if (item.actor) { + boost::shared_ptr<XMLElement> actorElement(new XMLElement("actor")); + actorElement->setAttribute("jid", item.actor->toString()); + itemElement->addNode(actorElement); + } + if (item.reason) { + boost::shared_ptr<XMLElement> reasonElement(new XMLElement("reason")); + reasonElement->addNode(boost::make_shared<XMLTextNode>(*item.reason)); + itemElement->addNode(reasonElement); + } + return itemElement; + } + }; +} diff --git a/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp index 44aa506..0a8153d 100644 --- a/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp +++ b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.cpp @@ -13,7 +13,7 @@ #include <Swiften/Base/foreach.h> #include <Swiften/Serializer/XML/XMLElement.h> #include <Swiften/Serializer/XML/XMLTextNode.h> - +#include <Swiften/Serializer/PayloadSerializers/MUCItemSerializer.h> namespace Swift { @@ -29,46 +29,13 @@ std::string MUCUserPayloadSerializer::serializePayload(boost::shared_ptr<MUCUser statusElement->setAttribute("code", code.str()); mucElement.addNode(statusElement); } - foreach (const MUCUserPayload::Item item, payload->getItems()) { - boost::shared_ptr<XMLElement> itemElement(new XMLElement("item")); - itemElement->setAttribute("affiliation", affiliationToString(item.affiliation)); - itemElement->setAttribute("role", roleToString(item.role)); - if (item.realJID) { - itemElement->setAttribute("jid", item.realJID.get()); - } - if (item.nick) { - itemElement->setAttribute("nick", item.nick.get()); - } - mucElement.addNode(itemElement); + foreach (const MUCItem item, payload->getItems()) { + mucElement.addNode(MUCItemSerializer::itemToElement(item)); } return mucElement.serialize(); } -std::string MUCUserPayloadSerializer::affiliationToString(MUCOccupant::Affiliation affiliation) const { - std::string result; - switch (affiliation) { - case MUCOccupant::Owner: result = "owner"; break; - case MUCOccupant::Admin: result = "admin"; break; - case MUCOccupant::Member: result = "member"; break; - case MUCOccupant::Outcast: result = "outcast"; break; - case MUCOccupant::NoAffiliation: result = "none"; break; - default: assert(false); - } - return result; -} - -std::string MUCUserPayloadSerializer::roleToString(MUCOccupant::Role role) const { - std::string result; - switch (role) { - case MUCOccupant::Moderator: result = "moderator"; break; - case MUCOccupant::NoRole: result = "none"; break; - case MUCOccupant::Participant: result = "participant"; break; - case MUCOccupant::Visitor: result = "visitor"; break; - default: assert(false); - } - return result; -} } diff --git a/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h index 634ce22..84c4a96 100644 --- a/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h +++ b/Swiften/Serializer/PayloadSerializers/MUCUserPayloadSerializer.h @@ -13,8 +13,6 @@ namespace Swift { class MUCUserPayloadSerializer : public GenericPayloadSerializer<MUCUserPayload> { public: MUCUserPayloadSerializer(); - std::string affiliationToString(MUCOccupant::Affiliation affiliation) const; - std::string roleToString(MUCOccupant::Role role) const; virtual std::string serializePayload(boost::shared_ptr<MUCUserPayload> version) const; }; diff --git a/Swiften/Serializer/PayloadSerializers/UnitTest/MUCAdminPayloadSerializerTest.cpp b/Swiften/Serializer/PayloadSerializers/UnitTest/MUCAdminPayloadSerializerTest.cpp new file mode 100644 index 0000000..a8acf80 --- /dev/null +++ b/Swiften/Serializer/PayloadSerializers/UnitTest/MUCAdminPayloadSerializerTest.cpp @@ -0,0 +1,39 @@ +/* + * 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 <boost/smart_ptr/make_shared.hpp> + +#include <Swiften/Serializer/PayloadSerializers/MUCAdminPayloadSerializer.h> + +using namespace Swift; + +class MUCAdminPayloadSerializerTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(MUCAdminPayloadSerializerTest); + CPPUNIT_TEST(testSerialize); + CPPUNIT_TEST_SUITE_END(); + + public: + MUCAdminPayloadSerializerTest() {} + + void testSerialize() { + MUCAdminPayloadSerializer testling; + boost::shared_ptr<MUCAdminPayload> admin = boost::make_shared<MUCAdminPayload>(); + MUCItem item; + item.affiliation = MUCOccupant::Owner; + item.role = MUCOccupant::Visitor; + item.reason = "malice"; + item.actor = JID("kev@tester.lit"); + admin->addItem(item); + + CPPUNIT_ASSERT_EQUAL(std::string("<query xmlns=\"http://jabber.org/protocol/muc#admin\"><item affiliation=\"owner\" role=\"visitor\"><actor jid=\"kev@tester.lit\"/><reason>malice</reason></item></query>"), testling.serialize(admin)); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(MUCAdminPayloadSerializerTest); -- cgit v0.10.2-6-g49f6