1
0
forked from blue/squawk

adding and removing contacts, rester testing

This commit is contained in:
Blue 2019-06-15 18:29:15 +03:00
parent f0f26ae1a1
commit bb509be29a
16 changed files with 389 additions and 14 deletions

View File

@ -195,11 +195,14 @@ void Core::Account::onRosterItemRemoved(const QString& bareJid)
return; return;
} }
Contact* contact = itr->second; Contact* contact = itr->second;
contacts.erase(itr);
QSet<QString> cGroups = contact->getGroups(); QSet<QString> cGroups = contact->getGroups();
for (QSet<QString>::const_iterator itr = cGroups.begin(), end = cGroups.end(); itr != end; ++itr) { for (QSet<QString>::const_iterator itr = cGroups.begin(), end = cGroups.end(); itr != end; ++itr) {
removeFromGroup(bareJid, *itr); removeFromGroup(bareJid, *itr);
} }
emit removeContact(bareJid); emit removeContact(bareJid);
contact->deleteLater();
} }
void Core::Account::addedAccount(const QString& jid) void Core::Account::addedAccount(const QString& jid)
@ -240,7 +243,12 @@ void Core::Account::addedAccount(const QString& jid)
if (grCount == 0) { if (grCount == 0) {
emit addContact(jid, "", cData); emit addContact(jid, "", cData);
} }
handleNewContact(contact);
}
}
void Core::Account::handleNewContact(Core::Contact* contact)
{
QObject::connect(contact, SIGNAL(groupAdded(const QString&)), this, SLOT(onContactGroupAdded(const QString&))); QObject::connect(contact, SIGNAL(groupAdded(const QString&)), this, SLOT(onContactGroupAdded(const QString&)));
QObject::connect(contact, SIGNAL(groupRemoved(const QString&)), this, SLOT(onContactGroupRemoved(const QString&))); QObject::connect(contact, SIGNAL(groupRemoved(const QString&)), this, SLOT(onContactGroupRemoved(const QString&)));
QObject::connect(contact, SIGNAL(nameChanged(const QString&)), this, SLOT(onContactNameChanged(const QString&))); QObject::connect(contact, SIGNAL(nameChanged(const QString&)), this, SLOT(onContactNameChanged(const QString&)));
@ -249,7 +257,7 @@ void Core::Account::addedAccount(const QString& jid)
QObject::connect(contact, SIGNAL(needHistory(const QString&, const QString&)), this, SLOT(onContactNeedHistory(const QString&, const QString&))); QObject::connect(contact, SIGNAL(needHistory(const QString&, const QString&)), this, SLOT(onContactNeedHistory(const QString&, const QString&)));
QObject::connect(contact, SIGNAL(historyResponse(const std::list<Shared::Message>&)), this, SLOT(onContactHistoryResponse(const std::list<Shared::Message>&))); QObject::connect(contact, SIGNAL(historyResponse(const std::list<Shared::Message>&)), this, SLOT(onContactHistoryResponse(const std::list<Shared::Message>&)));
} }
}
void Core::Account::onPresenceReceived(const QXmppPresence& presence) void Core::Account::onPresenceReceived(const QXmppPresence& presence)
{ {
@ -431,8 +439,21 @@ bool Core::Account::handleChatMessage(const QXmppMessage& msg, bool outgoing, bo
const QString& id(msg.id()); const QString& id(msg.id());
Shared::Message sMsg(Shared::Message::chat); Shared::Message sMsg(Shared::Message::chat);
initializeMessage(sMsg, msg, outgoing, forwarded, guessing); initializeMessage(sMsg, msg, outgoing, forwarded, guessing);
std::map<QString, Contact*>::const_iterator itr = contacts.find(sMsg.getPenPalJid()); QString jid = sMsg.getPenPalJid();
itr->second->appendMessageToArchive(sMsg); std::map<QString, Contact*>::const_iterator itr = contacts.find(jid);
Contact* cnt;
if (itr != contacts.end()) {
cnt = itr->second;
} else {
cnt = new Contact(jid, name);
contacts.insert(std::make_pair(jid, cnt));
cnt->setSubscriptionState(Shared::unknown);
emit addContact(jid, "", QMap<QString, QVariant>({
{"state", Shared::unknown}
}));
handleNewContact(cnt);
}
cnt->appendMessageToArchive(sMsg);
emit message(sMsg); emit message(sMsg);

View File

@ -107,6 +107,7 @@ private slots:
private: private:
void addedAccount(const QString &bareJid); void addedAccount(const QString &bareJid);
void handleNewContact(Contact* contact);
bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false); bool handleChatMessage(const QXmppMessage& msg, bool outgoing = false, bool forwarded = false, bool guessing = false);
void addToGroup(const QString& jid, const QString& group); void addToGroup(const QString& jid, const QString& group);
void removeFromGroup(const QString& jid, const QString& group); void removeFromGroup(const QString& jid, const QString& group);

View File

@ -365,3 +365,14 @@ void Core::Squawk::removeContactRequest(const QString& account, const QString& j
itr->second->removeContactRequest(jid); itr->second->removeContactRequest(jid);
} }
void Core::Squawk::addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups)
{
AccountsMap::const_iterator itr = amap.find(account);
if (itr == amap.end()) {
qDebug("An attempt to add contact to a non existing account, skipping");
return;
}
itr->second->addContactRequest(jid, name, groups);
}

View File

@ -52,6 +52,7 @@ public slots:
void subscribeContact(const QString& account, const QString& jid, const QString& reason); void subscribeContact(const QString& account, const QString& jid, const QString& reason);
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason); void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
void removeContactRequest(const QString& account, const QString& jid); void removeContactRequest(const QString& account, const QString& jid);
void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
private: private:
typedef std::deque<Account*> Accounts; typedef std::deque<Account*> Accounts;

View File

@ -10,6 +10,7 @@ int main(int argc, char *argv[])
{ {
qRegisterMetaType<Shared::Message>("Shared::Message"); qRegisterMetaType<Shared::Message>("Shared::Message");
qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>"); qRegisterMetaType<std::list<Shared::Message>>("std::list<Shared::Message>");
qRegisterMetaType<QSet<QString>>("QSet<QString>");
QApplication app(argc, argv); QApplication app(argc, argv);
SignalCatcher sc(&app); SignalCatcher sc(&app);
@ -45,6 +46,10 @@ int main(int argc, char *argv[])
squawk, SLOT(subscribeContact(const QString&, const QString&, const QString&))); squawk, SLOT(subscribeContact(const QString&, const QString&, const QString&)));
QObject::connect(&w, SIGNAL(unsubscribeContact(const QString&, const QString&, const QString&)), QObject::connect(&w, SIGNAL(unsubscribeContact(const QString&, const QString&, const QString&)),
squawk, SLOT(unsubscribeContact(const QString&, const QString&, const QString&))); squawk, SLOT(unsubscribeContact(const QString&, const QString&, const QString&)));
QObject::connect(&w, SIGNAL(addContactRequest(const QString&, const QString&, const QString&, const QSet<QString>&)),
squawk, SLOT(addContactRequest(const QString&, const QString&, const QString&, const QSet<QString>&)));
QObject::connect(&w, SIGNAL(removeContactRequest(const QString&, const QString&)),
squawk, SLOT(removeContactRequest(const QString&, const QString&)));
QObject::connect(squawk, SIGNAL(newAccount(const QMap<QString, QVariant>&)), &w, SLOT(newAccount(const QMap<QString, QVariant>&))); QObject::connect(squawk, SIGNAL(newAccount(const QMap<QString, QVariant>&)), &w, SLOT(newAccount(const QMap<QString, QVariant>&)));
QObject::connect(squawk, SIGNAL(addContact(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)), QObject::connect(squawk, SIGNAL(addContact(const QString&, const QString&, const QString&, const QMap<QString, QVariant>&)),

View File

@ -21,6 +21,7 @@ set(squawkUI_SRC
models/presence.cpp models/presence.cpp
conversation.cpp conversation.cpp
messageline.cpp messageline.cpp
newcontact.cpp
) )
# Tell CMake to create the helloworld executable # Tell CMake to create the helloworld executable

View File

@ -42,6 +42,7 @@ void Accounts::onAccountAccepted()
emit newAccount(map); emit newAccount(map);
} }
acc->deleteLater();
editing = false; editing = false;
} }

View File

@ -66,6 +66,8 @@ void Models::Accounts::addAccount(Account* account)
accs.push_back(account); accs.push_back(account);
connect(account, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onAccountChanged(Models::Item*, int, int))); connect(account, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onAccountChanged(Models::Item*, int, int)));
endInsertRows(); endInsertRows();
emit sizeChanged(accs.size());
} }
void Models::Accounts::onAccountChanged(Item* item, int row, int col) void Models::Accounts::onAccountChanged(Item* item, int row, int col)
@ -93,4 +95,17 @@ void Models::Accounts::removeAccount(int index)
disconnect(account, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onAccountChanged(Models::Item*, int, int))); disconnect(account, SIGNAL(childChanged(Models::Item*, int, int)), this, SLOT(onAccountChanged(Models::Item*, int, int)));
accs.erase(accs.begin() + index); accs.erase(accs.begin() + index);
endRemoveRows(); endRemoveRows();
emit sizeChanged(accs.size());
}
std::deque<QString> Models::Accounts::getNames() const
{
std::deque<QString> res;
for (std::deque<Models::Account*>::const_iterator i = accs.begin(), end = accs.end(); i != end; ++i) {
res.push_back((*i)->getName());
}
return res;
} }

View File

@ -25,8 +25,11 @@ public:
Account* getAccount(int index); Account* getAccount(int index);
std::deque<QString> getNames() const;
signals: signals:
void changed(); void changed();
void sizeChanged(unsigned int size);
private: private:
std::deque<Account*> accs; std::deque<Account*> accs;

View File

@ -260,6 +260,7 @@ void Models::Roster::addContact(const QString& account, const QString& jid, cons
qDebug() << "An attempt to add a contact " << jid << " ungrouped to non the account " << account << " for the second time, skipping"; qDebug() << "An attempt to add a contact " << jid << " ungrouped to non the account " << account << " for the second time, skipping";
return; return;
} }
itr++;
} }
parent = acc; parent = acc;
} else { } else {
@ -345,7 +346,8 @@ void Models::Roster::removeGroup(const QString& account, const QString& name)
} }
} }
delete item; item->deleteLater();
groups.erase(gItr);
} }
void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data) void Models::Roster::changeContact(const QString& account, const QString& jid, const QMap<QString, QVariant>& data)
@ -366,6 +368,7 @@ void Models::Roster::removeContact(const QString& account, const QString& jid)
ElId id(account, jid); ElId id(account, jid);
std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound(id); std::multimap<ElId, Contact*>::iterator cBeg = contacts.lower_bound(id);
std::multimap<ElId, Contact*>::iterator cEnd = contacts.upper_bound(id); std::multimap<ElId, Contact*>::iterator cEnd = contacts.upper_bound(id);
std::multimap<ElId, Contact*>::iterator cpBeg = cBeg;
QSet<QString> toRemove; QSet<QString> toRemove;
for (; cBeg != cEnd; ++cBeg) { for (; cBeg != cEnd; ++cBeg) {
@ -376,8 +379,9 @@ void Models::Roster::removeContact(const QString& account, const QString& jid)
} }
parent->removeChild(contact->row()); parent->removeChild(contact->row());
delete contact; contact->deleteLater();
} }
contacts.erase(cpBeg, cEnd);
for (QSet<QString>::const_iterator itr = toRemove.begin(), end = toRemove.end(); itr != end; ++itr) { for (QSet<QString>::const_iterator itr = toRemove.begin(), end = toRemove.end(); itr != end; ++itr) {
removeGroup(account, *itr); removeGroup(account, *itr);
@ -402,6 +406,7 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c
for (;cBeg != cEnd; ++cBeg) { for (;cBeg != cEnd; ++cBeg) {
if (cBeg->second->parentItem() == gr) { if (cBeg->second->parentItem() == gr) {
cont = cBeg->second; cont = cBeg->second;
contacts.erase(cBeg);
break; break;
} }
} }
@ -412,7 +417,7 @@ void Models::Roster::removeContact(const QString& account, const QString& jid, c
} }
gr->removeChild(cont->row()); gr->removeChild(cont->row());
delete cont; cont->deleteLater();
if (gr->childCount() == 0) { if (gr->childCount() == 0) {
removeGroup(account, group); removeGroup(account, group);

78
ui/newcontact.cpp Normal file
View File

@ -0,0 +1,78 @@
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2019 Юрий Губич <y.gubich@initi.ru>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "newcontact.h"
#include "ui_newcontact.h"
#include <QDebug>
NewContact::NewContact(const Models::Accounts* accounts, QWidget* parent):
QDialog(parent),
m_ui(new Ui::NewContact())
{
m_ui->setupUi ( this );
std::deque<QString> names = accounts->getNames();
for (std::deque<QString>::const_iterator i = names.begin(), end = names.end(); i != end; i++) {
m_ui->account->addItem(*i);
}
m_ui->account->setCurrentIndex(0);
}
NewContact::NewContact(const QString& acc, const Models::Accounts* accounts, QWidget* parent):
QDialog(parent),
m_ui(new Ui::NewContact())
{
m_ui->setupUi ( this );
std::deque<QString> names = accounts->getNames();
int index = 0;
bool found = false;
for (std::deque<QString>::const_iterator i = names.begin(), end = names.end(); i != end; i++) {
const QString& name = *i;
m_ui->account->addItem(name);
if (!found) {
if (name == acc) {
found = true;
} else {
index++;
}
}
}
if (!found) {
qDebug() << "Couldn't find a correct account among available accounts creating NewContact dialog, setting to 0";
}
m_ui->account->setCurrentIndex(index);
}
NewContact::~NewContact()
{
}
NewContact::Data NewContact::value() const
{
return {
m_ui->jid->text(),
m_ui->name->text(),
m_ui->account->currentText(),
{}
};
}

54
ui/newcontact.h Normal file
View File

@ -0,0 +1,54 @@
/*
* <one line to give the program's name and a brief idea of what it does.>
* Copyright (C) 2019 Юрий Губич <y.gubich@initi.ru>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef NEWCONTACT_H
#define NEWCONTACT_H
#include <QDialog>
#include <QScopedPointer>
#include <QSet>
#include "models/accounts.h"
namespace Ui
{
class NewContact;
}
class NewContact : public QDialog
{
Q_OBJECT
public:
struct Data {
QString jid;
QString name;
QString account;
QSet<QString> groups;
};
NewContact(const Models::Accounts* accounts, QWidget* parent = 0);
NewContact(const QString& acc, const Models::Accounts* accounts, QWidget* parent = 0);
~NewContact();
Data value() const;
private:
QScopedPointer<Ui::NewContact> m_ui;
};
#endif // NEWCONTACT_H

129
ui/newcontact.ui Normal file
View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NewContact</class>
<widget class="QDialog" name="NewContact">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>376</width>
<height>222</height>
</rect>
</property>
<property name="windowTitle">
<string>NewContact</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Account</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="account">
<property name="toolTip">
<string>An account that is going to have new contact</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>JID</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="jid">
<property name="toolTip">
<string>Jabber id of your new contact</string>
</property>
<property name="placeholderText">
<string>name@server.dmn</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="name">
<property name="toolTip">
<string>The way this new contact will be labeled in your roster (optional)</string>
</property>
<property name="placeholderText">
<string>John Smith</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>NewContact</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>NewContact</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -21,9 +21,12 @@ Squawk::Squawk(QWidget *parent) :
m_ui->comboBox->setCurrentIndex(Shared::offline); m_ui->comboBox->setCurrentIndex(Shared::offline);
connect(m_ui->actionAccounts, SIGNAL(triggered()), this, SLOT(onAccounts())); connect(m_ui->actionAccounts, SIGNAL(triggered()), this, SLOT(onAccounts()));
connect(m_ui->actionAddContact, SIGNAL(triggered()), this, SLOT(onNewContact()));
connect(m_ui->comboBox, SIGNAL(activated(int)), this, SLOT(onComboboxActivated(int))); connect(m_ui->comboBox, SIGNAL(activated(int)), this, SLOT(onComboboxActivated(int)));
connect(m_ui->roster, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onRosterItemDoubleClicked(const QModelIndex&))); connect(m_ui->roster, SIGNAL(doubleClicked(const QModelIndex&)), this, SLOT(onRosterItemDoubleClicked(const QModelIndex&)));
connect(m_ui->roster, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onRosterContextMenu(const QPoint&))); connect(m_ui->roster, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(onRosterContextMenu(const QPoint&)));
connect(rosterModel.accountsModel, SIGNAL(sizeChanged(unsigned int)), this, SLOT(onAccountsSizeChanged(unsigned int)));
//m_ui->mainToolBar->addWidget(m_ui->comboBox); //m_ui->mainToolBar->addWidget(m_ui->comboBox);
} }
@ -51,6 +54,35 @@ void Squawk::onAccounts()
} }
} }
void Squawk::onAccountsSizeChanged(unsigned int size)
{
if (size > 0) {
m_ui->actionAddContact->setEnabled(true);
} else {
m_ui->actionAddContact->setEnabled(false);
}
}
void Squawk::onNewContact()
{
NewContact* nc = new NewContact(rosterModel.accountsModel, this);
connect(nc, SIGNAL(accepted()), this, SLOT(onNewContactAccepted()));
connect(nc, SIGNAL(rejected()), nc, SLOT(deleteLater()));
nc->exec();
}
void Squawk::onNewContactAccepted()
{
NewContact* nc = static_cast<NewContact*>(sender());
NewContact::Data value = nc->value();
emit addContactRequest(value.account, value.jid, value.name, value.groups);
nc->deleteLater();
}
void Squawk::closeEvent(QCloseEvent* event) void Squawk::closeEvent(QCloseEvent* event)
{ {
if (accounts != 0) { if (accounts != 0) {

View File

@ -11,6 +11,7 @@
#include "accounts.h" #include "accounts.h"
#include "conversation.h" #include "conversation.h"
#include "models/roster.h" #include "models/roster.h"
#include "newcontact.h"
#include "../global.h" #include "../global.h"
@ -38,6 +39,7 @@ signals:
void subscribeContact(const QString& account, const QString& jid, const QString& reason); void subscribeContact(const QString& account, const QString& jid, const QString& reason);
void unsubscribeContact(const QString& account, const QString& jid, const QString& reason); void unsubscribeContact(const QString& account, const QString& jid, const QString& reason);
void removeContactRequest(const QString& account, const QString& jid); void removeContactRequest(const QString& account, const QString& jid);
void addContactRequest(const QString& account, const QString& jid, const QString& name, const QSet<QString>& groups);
public slots: public slots:
void newAccount(const QMap<QString, QVariant>& account); void newAccount(const QMap<QString, QVariant>& account);
@ -69,6 +71,9 @@ protected:
private slots: private slots:
void onAccounts(); void onAccounts();
void onNewContact();
void onNewContactAccepted();
void onAccountsSizeChanged(unsigned int size);
void onAccountsClosed(QObject* parent = 0); void onAccountsClosed(QObject* parent = 0);
void onConversationClosed(QObject* parent = 0); void onConversationClosed(QObject* parent = 0);
void onComboboxActivated(int index); void onComboboxActivated(int index);

View File

@ -80,6 +80,7 @@
<property name="title"> <property name="title">
<string>File</string> <string>File</string>
</property> </property>
<addaction name="actionAddContact"/>
<addaction name="actionQuit"/> <addaction name="actionQuit"/>
</widget> </widget>
<addaction name="menuFile"/> <addaction name="menuFile"/>
@ -96,12 +97,24 @@
</action> </action>
<action name="actionQuit"> <action name="actionQuit">
<property name="icon"> <property name="icon">
<iconset theme="application-exit"/> <iconset theme="application-exit">
<normaloff>.</normaloff>.</iconset>
</property> </property>
<property name="text"> <property name="text">
<string>Quit</string> <string>Quit</string>
</property> </property>
</action> </action>
<action name="actionAddContact">
<property name="enabled">
<bool>false</bool>
</property>
<property name="icon">
<iconset theme="list-add-user"/>
</property>
<property name="text">
<string>Add contact</string>
</property>
</action>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections/>