1 /** \file TocEngine.cpp
2 * \brief Implementation of TocEngine class
4 * Tieto Open Communicator - Client for the Telepathy communications framework.
5 * Copyright (c) 2010, Tieto Corporation
8 * Redistribution and use in source and binary forms, with or without modification,
9 * are permitted provided that the following conditions are met:
11 * Redistributions of source code must retain the above copyright notice,
12 * this list of conditions and the following disclaimer.
13 * Redistributions in binary form must reproduce the above copyright notice,
14 * this list of conditions and the following disclaimer in the documentation
15 * and/or other materials provided with the distribution.
16 * Neither the name of the Tieto Corporation nor the names of its contributors
17 * may be used to endorse or promote products derived from this software without
18 * specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 #include "TocSettings"
39 #include <QtTapioca/ConnectionManager>
40 #include <QtTapioca/ConnectionManagerFactory>
41 #include <QtTapioca/Contact>
42 #include <QtTapioca/ContactList>
43 #include <QtTapioca/TextChannel>
45 using namespace QtTapioca;
47 Q_DECLARE_METATYPE(QtTapioca::Connection*);
48 Q_DECLARE_METATYPE(QtTapioca::ContactBase*);
49 Q_DECLARE_METATYPE(QtTapioca::Connection::Status);
50 Q_DECLARE_METATYPE(QtTapioca::Connection::Reason);
51 Q_DECLARE_METATYPE(QtTapioca::ContactBase::Presence);
53 TocEngine::TocEngine( QObject * parent ):
54 TocBaseEngine( parent ),
55 _bNewUserContact(true),
56 _bContactListSynchronized(false),
60 qRegisterMetaType<QtTapioca::Connection::Status>();
61 qRegisterMetaType<QtTapioca::Connection::Reason>();
62 qRegisterMetaType<QtTapioca::ContactBase::Presence>();
63 qRegisterMetaType<TocContactList>();
65 TocSettings* pSettings = TocSettings::getInstance();
66 _pTocContactList = pSettings->contactList();
67 _pAddedContacts = pSettings->contactsToAdd();
68 _pRemovedContacts = pSettings->contactsToRemove();
72 TocEngine::~TocEngine() {
74 _pConnection->disconnect();
78 TocSettings *pSettings = TocSettings::getInstance();
82 void TocEngine::initializeFromSettings() {
84 _bNewUserContact = true;
86 TocSettings* pSettings = TocSettings::getInstance();
90 _pConnection->disconnect();
94 // Reset the UserContact flag
95 _bNewUserContact = true;
97 ConnectionManagerFactory *pFactory = ConnectionManagerFactory::self();
99 // Find new available protocols
100 QList<ConnectionManager*> connectionManagers = pFactory->getAllConnectionManagers();
101 ConnectionManager *pConnectionManager;
102 foreach ( pConnectionManager, connectionManagers ) {
104 QStringList protocols = pConnectionManager->supportedProtocols();
105 foreach (protocol, protocols) {
106 if( !pSettings->isProtocolInstalled( protocol ) )
107 pSettings->installProtocol( pConnectionManager->name() + "/" + protocol, protocol );
111 // Get Connection Manager
112 pConnectionManager = pFactory->getConnectionManager( pSettings->protocol() );
113 if (!pConnectionManager) {
118 // Search for "lost" connection
119 QList<Connection *> connectionsList = pConnectionManager->connections();
120 if( !connectionsList.empty() ) {
121 Connection *pConnection;
122 QString account = static_cast<QString>(pSettings->protocol() ) + "." +
123 static_cast<QString>(pSettings->accountUid()).replace("@", "_40").replace(".", "_2e");
124 foreach(pConnection, connectionsList) {
125 if( pConnection->serviceName().contains(account) ) {
127 // This doesn't always work... why?
128 pConnection->disconnect();
130 _pConnection = pConnection;
131 CONNECT(_pConnection, SIGNAL(statusChanged(QtTapioca::Connection*,QtTapioca::Connection::Status,QtTapioca::Connection::Reason)), this, SLOT(onStatusChange(QtTapioca::Connection*,QtTapioca::Connection::Status,QtTapioca::Connection::Reason)));
132 CONNECT(_pConnection, SIGNAL(channelCreated(QtTapioca::Connection*,QtTapioca::Channel*,bool)),
133 this, SLOT(onChannelCreated(QtTapioca::Connection*,QtTapioca::Channel*,bool)));
140 // Setup connection parameters
141 QList<ConnectionManager::Parameter> parameters;
142 parameters.append(ConnectionManager::Parameter("account", pSettings->accountUid()));
143 parameters.append(ConnectionManager::Parameter("password", pSettings->accountPasswd()));
144 if( !pSettings->server().isEmpty() )
145 parameters.append(ConnectionManager::Parameter("server", pSettings->server()));
146 if( pSettings->port() > 0 )
147 parameters.append(ConnectionManager::Parameter("port", QVariant( (uint)pSettings->port() )));
148 if( pSettings->isOldSslEnabled() )
149 parameters.append(ConnectionManager::Parameter("old-ssl", QVariant(true)));
150 if( pSettings->isIgnoreSslErrorsEnabled() )
151 parameters.append(ConnectionManager::Parameter("ignore-ssl-errors", QVariant(true)));
152 if( pSettings->isRegister() )
153 parameters.append(ConnectionManager::Parameter("register", QVariant(true)));
155 _pConnection = pConnectionManager->requestConnection( pSettings->protocol(), parameters );
157 if( !_pConnection ) {
161 CONNECT(_pConnection, SIGNAL(statusChanged(QtTapioca::Connection*,QtTapioca::Connection::Status,QtTapioca::Connection::Reason)),
162 this, SLOT(onStatusChange(QtTapioca::Connection*,QtTapioca::Connection::Status,QtTapioca::Connection::Reason)));
163 CONNECT(_pConnection, SIGNAL(channelCreated(QtTapioca::Connection*,QtTapioca::Channel*,bool)),
164 this, SLOT(onChannelCreated(QtTapioca::Connection*,QtTapioca::Channel*,bool)));
167 void TocEngine::onPresenceChange(Presence presence, QString description) {
170 initializeFromSettings();
175 if( _pConnection->status() == QtTapioca::Connection::Connected && _pConnection->userContact())
176 _pConnection->userContact()->setPresenceWithMessage(static_cast <QtTapioca::ContactBase::Presence> (presence), static_cast <QString> (description));
178 else if( _pConnection->status() == QtTapioca::Connection::Disconnected )
179 if(presence == Offline)
180 _bContactListSynchronized = false; // Just to be sure
182 _pConnection->connect(static_cast <QtTapioca::ContactBase::Presence> (presence), static_cast <QString> (description));
185 void TocEngine::onStatusChange(QtTapioca::Connection* pConnection, QtTapioca::Connection::Status status, QtTapioca::Connection::Reason reason) {
187 Q_UNUSED(pConnection);
189 emit statusChanged(static_cast <Status> (status), static_cast <Reason> (reason));
191 if( _bNewUserContact && _pConnection->userContact() && _pConnection->contactList() && _pConnection->status() == QtTapioca::Connection::Connected) {
193 CONNECT(_pConnection->userContact(), SIGNAL(presenceUpdated(QtTapioca::ContactBase*,QtTapioca::ContactBase::Presence,QString)), this, SLOT(onSelfPresenceUpdated(QtTapioca::ContactBase*,QtTapioca::ContactBase::Presence,QString)));
195 // disconnect for safety of double connections because we don't really know if it's the same object or not..
196 disconnect(_pConnection->contactList(), SIGNAL(authorizationRequested(QtTapioca::Contact*)), this, SLOT(onAuthorizationRequested(QtTapioca::Contact*)));
198 CONNECT(_pConnection->contactList(), SIGNAL(authorizationRequested(QtTapioca::Contact*)), this, SLOT(onAuthorizationRequested(QtTapioca::Contact*)));
199 _bNewUserContact = false;
201 } else if(_pConnection->status() == QtTapioca::Connection::Disconnected) {
203 _bContactListSynchronized = false;
204 // delete _pConnection; // TODO: Why does it crash??
209 void TocEngine::onPresenceUpdated(QtTapioca::ContactBase* pContact, QtTapioca::ContactBase::Presence presence, const QString &presenceMessage) {
211 emit contactPresenceUpdated(pContact->uri(), static_cast <Presence> (presence), presenceMessage);
214 void TocEngine::onSelfPresenceUpdated(QtTapioca::ContactBase* pContact, QtTapioca::ContactBase::Presence presence, const QString &presenceMessage) {
218 emit presenceUpdated(static_cast <Presence> (presence), presenceMessage);
220 if(!_bContactListSynchronized) {
221 synchronizeContactLists();
222 _bContactListSynchronized = true;
224 } else if(presence == QtTapioca::ContactBase::Offline) {
225 _bContactListSynchronized = false;
226 _pConnection->disconnect();
230 void TocEngine::onAddContactRequest(TocContact* pContact){
232 _pTocContactList->append(pContact);
233 _pAddedContacts->append( pContact->uid );
234 _pRemovedContacts->removeOne( pContact->uid );
236 addPendingContacts();
239 void TocEngine::addPendingContacts() {
241 if( _pConnection && _pConnection->status() == Connection::Connected ) {
243 while( !_pAddedContacts->isEmpty() ) {
244 QString uid = _pAddedContacts->takeFirst();
245 Contact* pContact = _pConnection->contactList()->addContact( uid );
246 if( pContact ) { // If it suceeded
247 pContact->authorize(true); //TODO: This should be removed when we have the facility to grant authorization at a later time
248 pContact->subscribe(true); // Ask for subscription
250 CONNECT(pContact, SIGNAL(presenceUpdated(QtTapioca::ContactBase*,QtTapioca::ContactBase::Presence,const QString&)),
251 this, SLOT(onPresenceUpdated(QtTapioca::ContactBase*, QtTapioca::ContactBase::Presence, const QString&)));
253 // TODO: Notify UI about the error (usually UID not valid for the protocol)
259 void TocEngine::onEditContactRequest(TocContact* pContact, QString oldUid){
261 if( pContact->uid != oldUid ) {
263 if(!_pAddedContacts->removeOne( oldUid ))
264 _pRemovedContacts->append( oldUid );
266 _pAddedContacts->append( pContact->uid );
267 _pRemovedContacts->removeOne( pContact->uid );
268 removePendingContacts();
269 addPendingContacts();
273 void TocEngine::onRemoveContactRequest(QString uid) {
275 if(!_pTocContactList) return;
277 for(int i = 0; i < _pTocContactList->count(); ++i)
278 if( _pTocContactList->at(i)->uid == uid ) {
279 _pTocContactList->removeAt(i);
280 //TODO: remove cached picture if needed
283 if(!_pAddedContacts->removeOne( uid ))
284 _pRemovedContacts->append( uid );
286 removePendingContacts();
289 void TocEngine::removePendingContacts() {
291 if( _pConnection && _pConnection->status() == Connection::Connected ) {
293 while( !_pRemovedContacts->isEmpty() ) {
294 QString uid = _pRemovedContacts->takeFirst();
295 Contact* pContact = _pConnection->contactList()->contact( uid );
297 _pConnection->contactList()->removeContact( pContact );
302 int TocEngine::indexForUid( QString uid ) {
304 for(int i = 0; i < _pTocContactList->count(); ++i)
305 if( _pTocContactList->at(i)->uid == uid ) {
312 void TocEngine::removeContact( QString uid ) {
314 int i = indexForUid(uid);
316 _pTocContactList->removeAt(i);
317 //TODO: remove cached picture if needed
321 void TocEngine::addContact( const Contact* pContact ) {
323 TocContact* pTocContact = new TocContact;
324 pTocContact->uid = pContact->uri();
325 pTocContact->name = pContact->alias(); // That's TOO BAD! I cann't set it to MY OWN selected name.. because of lack of support by telepathy
326 pTocContact->customIconId = 0;
327 pTocContact->customIconPath = "";
328 pTocContact->gender = NotSpecified;
329 pTocContact->presence = Offline;
331 _pTocContactList->append(pTocContact);
334 void TocEngine::syncContactsFromServer() {
336 QStringList localContactsList;
338 QList<Contact*> serverContactsList = _pConnection->contactList()->knownContacts();
340 for(int i = 0; i < _pTocContactList->count(); ++i)
341 localContactsList.append( _pTocContactList->at(i)->uid );
345 while( idx < serverContactsList.count() ) {
347 int idy = localContactsList.indexOf( serverContactsList.at(idx)->uri() );
350 serverContactsList.removeAt(idx);
351 localContactsList.removeAt(idy);
358 // Now we're left with two lists,
359 // one with contacts to be removed
360 // and one to be added to our local list.
364 foreach(pContact, serverContactsList) {
366 addContact(pContact);
368 CONNECT(pContact, SIGNAL(presenceUpdated(QtTapioca::ContactBase*,QtTapioca::ContactBase::Presence,const QString&)),
369 this, SLOT(onPresenceUpdated(QtTapioca::ContactBase*, QtTapioca::ContactBase::Presence, const QString&)));
373 // To remove //TODO: Maybe add an option to turn off removal of contacts that are removed on server by other clients
376 foreach(uid, localContactsList) {
382 void TocEngine::synchronizeContactLists() {
385 foreach(pContact, _pConnection->contactList()->knownContacts())
386 CONNECT(pContact, SIGNAL(presenceUpdated(QtTapioca::ContactBase*,QtTapioca::ContactBase::Presence,const QString&)),
387 this, SLOT(onPresenceUpdated(QtTapioca::ContactBase*, QtTapioca::ContactBase::Presence, const QString&)));
389 addPendingContacts();
390 removePendingContacts();
391 syncContactsFromServer();
393 emit contactListReceived(*_pTocContactList);
394 _bContactListSynchronized = true;
398 void TocEngine::onNewMessageReady(QString uid, Message message) {
400 if( !_pConnection || _pConnection->status() != QtTapioca::Connection::Connected) {
401 message.error = UserOffline;
402 emit incomingMessage(uid, message);
403 return; // Message that You cann't send messages when offline
406 Channel* pDestinationChannel = NULL;
407 bool bNewChannel = false;
409 QList<Channel*> pChannels = _pConnection->openChannels();
411 for(i = 0; i < pChannels.count(); ++i)
412 if(pChannels[i]->target()->uri() == uid)
415 if(i == pChannels.count()) { //not found, create new channel
416 Contact* pContact = _pConnection->contactList()->contact(uid);
418 message.error = NotOnContactList;
419 emit incomingMessage(uid, message);
420 return; // Message that You cann't send messages to the contact not on users contact list
422 pDestinationChannel = _pConnection->createChannel(Channel::Text, pContact);
423 if(pDestinationChannel)
425 else { // Channel could not be created
426 message.error = CannotCreateChannel;
427 emit incomingMessage(uid, message);
428 return; // Message that Channel could not be created
431 pDestinationChannel = pChannels[i];
434 if(pDestinationChannel->type() == Channel::Text) {
436 TextChannel* pTextChannel = dynamic_cast<TextChannel*>(pDestinationChannel);
440 CONNECT(pTextChannel, SIGNAL(messageReceived(const QtTapioca::TextChannel*,const QtTapioca::TextChannel::Message&)),
441 this, SLOT(onMessageReceived(const QtTapioca::TextChannel*,const QtTapioca::TextChannel::Message&)));
442 CONNECT(pTextChannel, SIGNAL(messageDeliveryError(const QtTapioca::TextChannel::Message&, QtTapioca::TextChannel::Message::DeliveryError)),
443 this, SLOT(onMessageDeliveryError(const QtTapioca::TextChannel::Message&, QtTapioca::TextChannel::Message::DeliveryError)));
446 QtTapioca::TextChannel::Message mess(message.contents);
447 pTextChannel->sendMessage(mess);
451 void TocEngine::onMessageReceived(const QtTapioca::TextChannel* pTextChannel, const QtTapioca::TextChannel::Message& message) {
453 struct Message tecMessage;
454 tecMessage.contents = message.contents();
455 tecMessage.timestamp = message.timestamp();
456 tecMessage.error = NoError;
459 emit incomingMessage(pTextChannel->target()->uri(), tecMessage);
460 const_cast<QtTapioca::TextChannel*>(pTextChannel)->acknowledge(message);
464 void TocEngine::onMessageDeliveryError(const QtTapioca::TextChannel::Message &message, QtTapioca::TextChannel::Message::DeliveryError error) {
466 struct Message tecMessage;
467 tecMessage.contents = message.contents();
468 tecMessage.timestamp = message.timestamp();
469 tecMessage.error = static_cast<MessageDeliveryError>(error);
471 QObject* pSender = sender();
473 TextChannel* pTextChannel = dynamic_cast<TextChannel*>(pSender);
474 emit incomingMessage(pTextChannel->target()->uri(), tecMessage);
478 void TocEngine::onChannelCreated(QtTapioca::Connection* pConnection, QtTapioca::Channel* pChannel, bool bSuppressHandler) {
480 Q_UNUSED(pConnection);
481 Q_UNUSED(bSuppressHandler);
483 if(pChannel->type() == Channel::Text) {
484 TextChannel* pTextChannel = dynamic_cast<TextChannel*>(pChannel);
485 CONNECT(pTextChannel, SIGNAL(messageReceived (const QtTapioca::TextChannel*,const QtTapioca::TextChannel::Message&)),
486 this, SLOT(onMessageReceived(const QtTapioca::TextChannel*,const QtTapioca::TextChannel::Message&)));
487 CONNECT(pTextChannel, SIGNAL(messageDeliveryError(const QtTapioca::TextChannel::Message&, QtTapioca::TextChannel::Message::DeliveryError)),
488 this, SLOT(onMessageDeliveryError(const QtTapioca::TextChannel::Message&, QtTapioca::TextChannel::Message::DeliveryError)));
490 QList<TextChannel::Message> messages = pTextChannel->pendingMessages();
491 if(messages.count()) {
493 struct Message message;
494 message.error = NoError;
495 for(i = 0; i < messages.count(); ++i) {
496 message.contents = messages.at(i).contents();
497 message.timestamp = messages.at(i).timestamp();
498 emit incomingMessage(pChannel->target()->uri(), message);
504 void TocEngine::onSessionClosed(QString uid) {
509 QList<Channel*> pChannels = _pConnection->openChannels();
511 for(i = 0; i < pChannels.count(); ++i)
512 if(pChannels[i]->target()->uri() == uid)
515 if(i < pChannels.count()) {
516 pChannels.at(i)->close();
520 void TocEngine::onAuthorizationRequested(QtTapioca::Contact *pContact) {
522 // TODO: should be dependent on user pSettings
523 pContact->authorize( true );
524 pContact->subscribe( true );
526 for(int i = 0; i < _pTocContactList->count(); ++i)
527 if(_pTocContactList->at(i)->uid == pContact->uri())
528 return; // Return if the contact is already on our contact list
530 addContact(pContact); // Add Contact to notify user
531 CONNECT(pContact, SIGNAL(presenceUpdated(QtTapioca::ContactBase*,QtTapioca::ContactBase::Presence,const QString&)),
532 this, SLOT(onPresenceUpdated(QtTapioca::ContactBase*, QtTapioca::ContactBase::Presence, const QString&)));
534 emit contactListReceived(*_pTocContactList);