Initial commit
[tietoopcom] / src / TocEngine / TocEngine.cpp
1 /** \file TocEngine.cpp
2  * \brief Implementation of TocEngine class
3  * 
4  * Tieto Open Communicator - Client for the Telepathy communications framework.
5  * Copyright (c) 2010, Tieto Corporation
6  *
7  * All rights reserved.
8  * Redistribution and use in source and binary forms, with or without modification,
9  * are permitted provided that the following conditions are met:
10  *
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.
19  *
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.
29  * 
30  */
31
32 #include "TocEngine"
33 #include "TocSettings"
34 #include "macros.h"
35
36 #include <QString>
37 #include <QDebug>
38
39 #include <QtTapioca/ConnectionManager>
40 #include <QtTapioca/ConnectionManagerFactory>
41 #include <QtTapioca/Contact>
42 #include <QtTapioca/ContactList>
43 #include <QtTapioca/TextChannel>
44
45 using namespace QtTapioca;
46
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);
52
53 TocEngine::TocEngine( QObject * parent ): 
54         TocBaseEngine( parent ), 
55         _bNewUserContact(true), 
56         _bContactListSynchronized(false), 
57         _pConnection(0) 
58 {
59
60         qRegisterMetaType<QtTapioca::Connection::Status>();
61         qRegisterMetaType<QtTapioca::Connection::Reason>();
62         qRegisterMetaType<QtTapioca::ContactBase::Presence>();
63         qRegisterMetaType<TocContactList>();
64
65         TocSettings* pSettings = TocSettings::getInstance();
66         _pTocContactList = pSettings->contactList();
67         _pAddedContacts = pSettings->contactsToAdd();
68         _pRemovedContacts = pSettings->contactsToRemove();
69         
70 }
71
72 TocEngine::~TocEngine() {
73         if(_pConnection)
74                 _pConnection->disconnect();
75
76         delete _pConnection;
77         
78         TocSettings *pSettings = TocSettings::getInstance();
79         delete pSettings;
80 }
81
82 void TocEngine::initializeFromSettings() {
83
84         _bNewUserContact = true;
85
86         TocSettings* pSettings = TocSettings::getInstance();
87
88         // Disconnect first
89         if(_pConnection) {
90                 _pConnection->disconnect();
91                 delete _pConnection;
92                 _pConnection = 0;
93         }
94         // Reset the UserContact flag
95         _bNewUserContact = true;
96         
97         ConnectionManagerFactory *pFactory = ConnectionManagerFactory::self();
98
99         // Find new available protocols
100         QList<ConnectionManager*> connectionManagers = pFactory->getAllConnectionManagers();
101         ConnectionManager *pConnectionManager;
102         foreach ( pConnectionManager, connectionManagers ) {
103                 QString protocol;
104                 QStringList protocols = pConnectionManager->supportedProtocols();
105                 foreach (protocol, protocols) {
106                         if( !pSettings->isProtocolInstalled( protocol ) )
107                                 pSettings->installProtocol( pConnectionManager->name() + "/" + protocol, protocol );
108                 }
109         }
110
111         // Get Connection Manager
112         pConnectionManager = pFactory->getConnectionManager( pSettings->protocol() );
113         if (!pConnectionManager) {
114                 _pConnection = 0;
115                 return;
116         }
117         
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) ) {
126
127                                 // This doesn't always work... why?
128                                 pConnection->disconnect();
129
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)));
134                         
135                                 return;
136                         }
137                 }
138         }
139
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)));
154
155         _pConnection = pConnectionManager->requestConnection( pSettings->protocol(), parameters );
156         
157         if( !_pConnection ) {
158                 return;
159         }
160
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)));
165 }
166
167 void TocEngine::onPresenceChange(Presence presence, QString description) {
168         
169         if( !_pConnection )
170                 initializeFromSettings();
171
172         if( !_pConnection )
173                 return;
174
175         if( _pConnection->status() == QtTapioca::Connection::Connected && _pConnection->userContact())
176                 _pConnection->userContact()->setPresenceWithMessage(static_cast <QtTapioca::ContactBase::Presence> (presence), static_cast <QString> (description));
177         
178         else if( _pConnection->status() == QtTapioca::Connection::Disconnected )
179                 if(presence == Offline)
180                                 _bContactListSynchronized = false; // Just to be sure
181                 else
182                         _pConnection->connect(static_cast <QtTapioca::ContactBase::Presence> (presence), static_cast <QString> (description));
183 }
184
185 void TocEngine::onStatusChange(QtTapioca::Connection* pConnection, QtTapioca::Connection::Status status, QtTapioca::Connection::Reason reason) {
186         
187         Q_UNUSED(pConnection);
188         
189         emit statusChanged(static_cast <Status> (status), static_cast <Reason> (reason));
190                 
191         if( _bNewUserContact && _pConnection->userContact() && _pConnection->contactList() && _pConnection->status() == QtTapioca::Connection::Connected) {
192
193                 CONNECT(_pConnection->userContact(), SIGNAL(presenceUpdated(QtTapioca::ContactBase*,QtTapioca::ContactBase::Presence,QString)), this, SLOT(onSelfPresenceUpdated(QtTapioca::ContactBase*,QtTapioca::ContactBase::Presence,QString)));
194
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*)));
197                 
198                 CONNECT(_pConnection->contactList(), SIGNAL(authorizationRequested(QtTapioca::Contact*)), this, SLOT(onAuthorizationRequested(QtTapioca::Contact*)));
199                 _bNewUserContact = false;
200                 
201         } else if(_pConnection->status() == QtTapioca::Connection::Disconnected) {
202                 
203                 _bContactListSynchronized = false;
204 //              delete _pConnection; // TODO: Why does it crash??
205                 _pConnection = 0;
206         }
207 }
208
209 void TocEngine::onPresenceUpdated(QtTapioca::ContactBase* pContact, QtTapioca::ContactBase::Presence presence, const QString &presenceMessage) {
210
211         emit contactPresenceUpdated(pContact->uri(), static_cast <Presence> (presence), presenceMessage);
212 }
213
214 void TocEngine::onSelfPresenceUpdated(QtTapioca::ContactBase* pContact, QtTapioca::ContactBase::Presence presence, const QString &presenceMessage) {
215
216         Q_UNUSED(pContact);
217
218         emit presenceUpdated(static_cast <Presence> (presence), presenceMessage);
219
220         if(!_bContactListSynchronized) {
221                 synchronizeContactLists();
222                 _bContactListSynchronized = true;
223                 
224         } else if(presence == QtTapioca::ContactBase::Offline) {
225                         _bContactListSynchronized = false;
226                         _pConnection->disconnect();
227         }
228 }
229
230 void TocEngine::onAddContactRequest(TocContact* pContact){
231
232         _pTocContactList->append(pContact);
233         _pAddedContacts->append( pContact->uid );
234         _pRemovedContacts->removeOne( pContact->uid );
235
236         addPendingContacts();
237 }
238
239 void TocEngine::addPendingContacts() {
240
241         if( _pConnection && _pConnection->status() == Connection::Connected ) {
242
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
249                                 
250                                 CONNECT(pContact, SIGNAL(presenceUpdated(QtTapioca::ContactBase*,QtTapioca::ContactBase::Presence,const QString&)),
251                                                 this, SLOT(onPresenceUpdated(QtTapioca::ContactBase*, QtTapioca::ContactBase::Presence, const QString&)));
252                         } else {}
253                                 // TODO: Notify UI about the error (usually UID not valid for the protocol)
254                 }
255         }
256 }
257
258
259 void TocEngine::onEditContactRequest(TocContact* pContact, QString oldUid){
260
261         if( pContact->uid != oldUid ) {
262                 
263                 if(!_pAddedContacts->removeOne( oldUid ))
264                         _pRemovedContacts->append( oldUid );
265                 
266                 _pAddedContacts->append( pContact->uid );
267                 _pRemovedContacts->removeOne( pContact->uid );
268                 removePendingContacts();
269                 addPendingContacts();
270         }
271 }
272
273 void TocEngine::onRemoveContactRequest(QString uid) {
274
275         if(!_pTocContactList) return;
276         
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
281                 }
282
283         if(!_pAddedContacts->removeOne( uid ))
284                 _pRemovedContacts->append( uid );
285
286         removePendingContacts();
287 }
288
289 void TocEngine::removePendingContacts() {
290
291         if( _pConnection && _pConnection->status() == Connection::Connected ) {
292
293                 while( !_pRemovedContacts->isEmpty() ) {
294                         QString uid = _pRemovedContacts->takeFirst();
295                         Contact* pContact = _pConnection->contactList()->contact( uid );
296                         if( pContact )
297                                 _pConnection->contactList()->removeContact( pContact );
298                 }
299         }
300 }
301
302 int TocEngine::indexForUid( QString uid ) {
303
304         for(int i = 0; i < _pTocContactList->count(); ++i)
305                 if( _pTocContactList->at(i)->uid == uid ) {
306                         return i;
307                 }
308         
309         return -1;
310 }
311
312 void TocEngine::removeContact( QString uid ) {
313         
314         int i = indexForUid(uid);
315         if( i != -1 ) {
316                 _pTocContactList->removeAt(i);
317                 //TODO: remove cached picture if needed
318         }
319 }
320
321 void TocEngine::addContact( const Contact* pContact ) {
322         
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;
330
331         _pTocContactList->append(pTocContact);
332 }
333
334 void TocEngine::syncContactsFromServer() {
335
336         QStringList localContactsList;
337
338         QList<Contact*> serverContactsList = _pConnection->contactList()->knownContacts();
339
340         for(int i = 0; i < _pTocContactList->count(); ++i)
341                 localContactsList.append( _pTocContactList->at(i)->uid );
342
343         // Find differences
344         int idx = 0;
345         while( idx < serverContactsList.count() ) {
346
347                 int idy = localContactsList.indexOf( serverContactsList.at(idx)->uri() );
348
349                 if( idy != -1 ) {
350                         serverContactsList.removeAt(idx);
351                         localContactsList.removeAt(idy);
352                         --idx;
353                 }
354
355                 ++idx;
356         }
357         
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.
361
362         // To add
363         Contact* pContact;      
364         foreach(pContact, serverContactsList) {
365
366                 addContact(pContact);
367                 
368                 CONNECT(pContact, SIGNAL(presenceUpdated(QtTapioca::ContactBase*,QtTapioca::ContactBase::Presence,const QString&)),
369                                 this, SLOT(onPresenceUpdated(QtTapioca::ContactBase*, QtTapioca::ContactBase::Presence, const QString&)));
370                                                 
371         }
372
373         // To remove //TODO: Maybe add an option to turn off removal of contacts that are removed on server by other clients
374         QString uid;    
375         
376         foreach(uid, localContactsList) {
377
378                 removeContact(uid);
379         }
380 }
381
382 void TocEngine::synchronizeContactLists() {
383         
384         Contact* pContact;
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&)));
388
389         addPendingContacts();
390         removePendingContacts();
391         syncContactsFromServer();
392
393         emit contactListReceived(*_pTocContactList);
394         _bContactListSynchronized = true;
395
396 }
397
398 void TocEngine::onNewMessageReady(QString uid, Message message) {
399         
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
404         }
405
406         Channel* pDestinationChannel = NULL;
407         bool bNewChannel = false;
408         
409         QList<Channel*> pChannels = _pConnection->openChannels();
410         int i;
411         for(i = 0; i < pChannels.count(); ++i)
412                 if(pChannels[i]->target()->uri() == uid)
413                         break;
414
415         if(i == pChannels.count()) { //not found, create new channel
416                 Contact* pContact = _pConnection->contactList()->contact(uid);
417                 if(!pContact) {
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
421                 }
422                 pDestinationChannel = _pConnection->createChannel(Channel::Text, pContact);
423                 if(pDestinationChannel)
424                         bNewChannel = true;
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
429                 }
430         } else {
431                 pDestinationChannel = pChannels[i];
432         }
433         
434         if(pDestinationChannel->type() == Channel::Text) {
435                 
436                 TextChannel* pTextChannel = dynamic_cast<TextChannel*>(pDestinationChannel);
437                 
438                 if(bNewChannel) {
439                         
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)));
444                 }
445                 
446                 QtTapioca::TextChannel::Message mess(message.contents);
447                 pTextChannel->sendMessage(mess);
448         }
449 }
450
451 void TocEngine::onMessageReceived(const QtTapioca::TextChannel* pTextChannel, const QtTapioca::TextChannel::Message& message) {
452
453         struct Message tecMessage;
454         tecMessage.contents = message.contents();
455         tecMessage.timestamp = message.timestamp();
456         tecMessage.error = NoError;
457         
458         if(pTextChannel) {
459                 emit incomingMessage(pTextChannel->target()->uri(), tecMessage);
460                 const_cast<QtTapioca::TextChannel*>(pTextChannel)->acknowledge(message);
461         }
462 }
463
464 void TocEngine::onMessageDeliveryError(const QtTapioca::TextChannel::Message &message, QtTapioca::TextChannel::Message::DeliveryError error) {
465         
466         struct Message tecMessage;
467         tecMessage.contents = message.contents();
468         tecMessage.timestamp = message.timestamp();
469         tecMessage.error = static_cast<MessageDeliveryError>(error);
470         
471         QObject* pSender = sender();
472         if(pSender) {
473                 TextChannel* pTextChannel = dynamic_cast<TextChannel*>(pSender);
474                 emit incomingMessage(pTextChannel->target()->uri(), tecMessage);
475         }
476 }
477
478 void TocEngine::onChannelCreated(QtTapioca::Connection* pConnection, QtTapioca::Channel* pChannel, bool bSuppressHandler) {
479         
480         Q_UNUSED(pConnection);
481         Q_UNUSED(bSuppressHandler);
482
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)));
489
490                 QList<TextChannel::Message> messages = pTextChannel->pendingMessages();
491                 if(messages.count()) {
492                         int i;
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);
499                         }
500                 }
501         }
502 }
503
504 void TocEngine::onSessionClosed(QString uid) {
505         
506         if( !_pConnection )
507                 return;
508
509         QList<Channel*> pChannels = _pConnection->openChannels();
510         int i;
511         for(i = 0; i < pChannels.count(); ++i)
512                 if(pChannels[i]->target()->uri() == uid)
513                         break;
514
515         if(i < pChannels.count()) {
516                 pChannels.at(i)->close();
517         }
518 }
519
520 void TocEngine::onAuthorizationRequested(QtTapioca::Contact *pContact) {
521         
522         // TODO: should be dependent on user pSettings
523         pContact->authorize( true );
524         pContact->subscribe( true );
525
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
529
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&)));
533
534         emit contactListReceived(*_pTocContactList);
535 }