channelDestroyed hadling, TPSESSION_DEBUG env
[tpsession] / tpsession-0.1 / tpsession / tpsessionaccount.cpp
1 /*
2  * This file is part of TpSession
3  *
4  * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
5  * Contact Kate Alhola  kate.alholanokia.com
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 #include "tpsessionaccount.h"
22 #include <TelepathyQt4/Message>
23
24 /**
25  * \class TpSessionAccount
26  * \headerfile <tpsessionaccount.h>
27  *
28  * TpSessionAccount class represents every account you have. As example account for “Ring” connection manager represents your cellular
29  * account and you may send and receive SMS with it. Gabble represents your GoogleTalk account if you have defined them.
30  * TpSessionAccounts are created by TpSession class,they are not intended to be created stand-alone
31
32  */
33 /**
34  * \fn void TpSessionAccount::accountReady(TpSessionAccount *);
35  *
36  * Emitted when the account becomes ready
37  *
38  * \param  TpSessionAccount  pointer to account become ready
39  */
40 /**
41  * \fn void TpSessionAccount::channelReady(TpSessionAccount *);
42  *
43  * Emitted when the account Manager becomes ready
44  *
45  * \param  TpSession  pointer to TpSession class
46  */
47 /**
48  * \fn void TpSessionAccount::messageReceived(const Tp::ReceivedMessage &,TpSessionAccount *);
49  *
50  * Emitted when any of Account Managers recived message
51  *
52  * \param  Tp::ReceivedMessage  Message received
53  * \param  TpSessionAccount  pointer to account received message
54  */
55
56 /**
57  * \fn void TpSessionAccount::newChannel(TpSessionAccount *,QString CjhannelType,QString peerId,const Tp::ChannelDetails &);
58  * \param  TpSession  pointer to TpSession class
59  * \param  ChannelType type of Channel, TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT for text channel, TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA for steram media, as exmple for incoming call
60  * \param  peedId PeerId, as example caller telephone number
61  * \param channeDetails needed if you would like to create a channel. For text chanels TpSession creates channel automatically. For calls, Maemo Call UI handles callcreation
62  */
63
64 /**
65  * Construct a new TpSessionAccount object. This constructor is called by TpSession class when new account is created or fetched from account manager. It is not inended to be used stand alone
66  *
67  * \param am          Telepathy-Qt4 account manager for this account
68  * \param objectPath  Dbus object path tonew account
69  */
70 TpSessionAccount::TpSessionAccount(Tp::AccountManagerPtr am,const QString &objectPath):
71         mAcc(Tp::Account::create(am->dbusConnection(),am->busName(), objectPath))
72
73 {
74   connect(mAcc->becomeReady(),SIGNAL(finished(Tp::PendingOperation *)),SLOT(onReady(Tp::PendingOperation *)));
75   ready=false;
76   //  qDebug() << "TpSessionAccount::TpSessionAccount objectPath=" << objectPath;
77 };
78
79
80 void TpSessionAccount::onReady(Tp::PendingOperation *op)
81 {
82     Q_UNUSED(op);
83     acc = mAcc.data();
84     //    qDebug() << "TpSessionAccount::onReady cmName=" << acc->cmName() << "haveConnection=" <<
85     //    (acc->haveConnection()? ( acc->connection()->isReady() ? "Ready":"notReady"):"no");
86
87     if(acc->haveConnection()) {
88
89             connect(acc->connection()->becomeReady(Tp::Connection::FeatureRoster | Tp::Connection::FeatureSelfContact ),
90                 SIGNAL(finished(Tp::PendingOperation *)),
91                 SLOT(onContactsConnectionReady(Tp::PendingOperation *)));
92          if (acc->connection()->isReady() && acc->connection()->interfaces().contains(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS)) {
93            //            qDebug() << "TpSessionAccount::onReady: connecting to Connection.Interface.NewChannels";
94             connect(acc->connection()->requestsInterface(),
95                 SIGNAL(NewChannels(const Tp::ChannelDetailsList&)),
96                 SLOT(onNewChannels(const Tp::ChannelDetailsList&)));
97         }
98      }
99          else { // If there is no connection, we are ready now, else we are ready when contacts connection is ready
100             ready=true;
101             emit accountReady(this);
102         }
103      }
104
105 void TpSessionAccount::onContactsConnectionReady(Tp::PendingOperation *op)
106 {
107     if (op->isError()) {
108         qWarning() << "Connection cannot become ready" << acc->cmName();
109         return;
110     }
111
112     if (acc->connection()->interfaces().contains(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS)) {
113       //      //            qDebug() << "TpSessionAccount::onContactsConectionReady: connecting to Connection.Interface.NewChannels";
114             connect(acc->connection()->requestsInterface(),
115                 SIGNAL(NewChannels(const Tp::ChannelDetailsList&)),
116                 SLOT(onNewChannels(const Tp::ChannelDetailsList&)));
117         } else qDebug() << "TpSessionAccount::onContactsConnectionReady: does NO have CONNECTION_INTERFACE_REQUESTS";
118     Tp::PendingReady *pr = qobject_cast<Tp::PendingReady *>(op);
119      contactsConn = Tp::ConnectionPtr(qobject_cast<Tp::Connection *>(pr->object()));
120 #if 0
121     connect(contactsConn->contactManager(),
122             SIGNAL(presencePublicationRequested(const Tp::Contacts &)),
123             SLOT(onPresencePublicationRequested(const Tp::Contacts &)));
124 #endif
125     //    qDebug() << "TpSessionAccount::onContactsConnectionReady "<< acc->cmName() ;
126     //    RosterItem *item;
127     myContacts=contactsConn->contactManager()->allKnownContacts();
128     foreach (const Tp::ContactPtr &contact, myContacts) {
129       //        qDebug() << "id=" <<contact->id() << " alias=" << contact->alias() << " presence=" << contact->presenceStatus()  ;
130         if(contact->id()==reqContact) {
131             addOutgoingChannel(contact);
132             reqContact="";
133         }
134     };
135     if(!reqContact.isEmpty() ) makeContactFromAddress(reqContact);
136     ready=true;
137     emit accountReady(this);
138 }
139
140
141 /**
142  * Fetch Tp::ContactPtr for contact with given address. Contact is searched among contacts returned by contact manager for ths account.
143  * All connecions managers does not return contacts, as example Ring telephony contact manager does not. Gabble for Googletalk or Spirit for Skype does
144  * return contacts-
145  *
146  * \param id            Contact address/id, as example email address, telephone number etc. Only exact matches
147  * \return      TpContactPtr, if nontact is not returned TpContactPtr.isNull() is true
148  */
149
150 Tp::ContactPtr TpSessionAccount::getContactFromAddress(QString id)
151 {
152     Q_UNUSED(id);
153     Tp::ContactPtr p;
154     foreach (const Tp::ContactPtr &contact, myContacts) {
155     if(contact->id()==id) return p=contact;
156     }
157     return p;
158 }
159 /**
160  * Fetch TpSessionChannel for with given address. Contact is searched among active channels for this account.
161  *
162  *
163  * \param id            Contact address/id, as example email address, telephone number etc. Only exact matches
164  * \return          Pointer to TpSessionChannel or NULL if nit found
165  */
166
167 TpSessionChannel* TpSessionAccount::getChannelFromPeerAddress(QString id)
168 {
169   TpSessionChannel* p=NULL;
170     foreach (TpSessionChannel* channel, myChannels) {
171         if(channel->peerId()==id) p=channel;
172     }
173     return p;
174 }
175 /**
176  * Creates new contact with given address. This function is Acynchronous, it sends request to contact manager for contact creation,
177  *
178  * \param address           Contact address/id, as example email address, telephone number etc.
179  * \return                  true in success
180  *
181  * \return    emits contactRetrieved(Tp:.contactPtr,bool success,bool requested)
182 */
183
184 bool TpSessionAccount::makeContactFromAddress(QString address)
185 {
186      if(!contactsConn) return false; // No contacts connection, return fail
187      reqContact=address;  // When we get retrieved signal, we check if it is this one
188      Tp::PendingContacts *pc=contactsConn->contactManager()->contactsForIdentifiers(QStringList(address));
189      connect(pc,SIGNAL(finished(Tp::PendingOperation *)),SLOT(onNewContactRetrieved(Tp::PendingOperation *)));
190      return true;
191 }
192
193 void TpSessionAccount::onNewContactRetrieved(Tp::PendingOperation *op)
194 {
195     Tp::ContactPtr contact;
196     Tp::PendingContacts *pcontacts = qobject_cast<Tp::PendingContacts *>(op);
197     QList<Tp::ContactPtr> contacts = pcontacts->contacts();
198     QString username = pcontacts->identifiers().first();
199     if (contacts.size() != 1 || !contacts.first()) {
200         qDebug() << "Unable to add contact " <<reqContact;
201         emit contactRetrieved(contact,false,false);
202         if(!reqMessage.isEmpty()) { // If there is requesting messsage, can't queue if contact failed
203           emit messageQueued(this,false);
204           reqMessage.clear();
205         };
206
207         return;
208     }
209
210     contact = contacts.first();
211     //    qDebug() << "TpSessionAccount::onContactRetrieved" << reqContact;
212     if(!reqContact.isEmpty()) {
213         addOutgoingChannel(contacts.first());
214         emit contactRetrieved(contact,true,true);
215     }
216     else
217         emit contactRetrieved(contact,true,false);
218 }
219 /**
220  * Send message to given address. This function is compled Acynchronous function that may produce multiple state transitions beforecomletion.
221  * If there is already existing TpSessionChannel for this contact, it simply queues message for sending and no forther transitions are needed
222  * If there are no hannel, it first check is there contact for this address, if is, it requests new channel to be created for ths channel and message
223  * is left waiting untill channel is created. If there is no contact, it sends request fr contact creation and when contact is created state machine
224  * proceeds to channel creation.
225  *
226  * MessageSent() signal is emitted when completed
227  *
228  * \param address           Contact address/id, as example email address, telephone number etc.
229  * \param message           Message string
230  */
231
232 void TpSessionAccount::sendMessageToAddress(QString address,QString message)
233 {
234     Tp::ContactPtr p;
235     TpSessionChannel* channel=getChannelFromPeerAddress(address);
236     if(channel) {
237         if(TpSession::tpsDebug()) qDebug() << "TpSessionAccount::sendMessageToAddress peer:" << channel->peerId() << "queuing:" << message;
238         channel->sendMessage(message); // We have already channel
239         emit messageQueued(this,true);
240     }
241     else {
242         reqMessage=message;
243         p=getContactFromAddress(address); // Do we have contact ready ?
244         if(p.isNull())  // If not, create it
245             makeContactFromAddress(address); // Create and after created, send
246         else
247             addOutgoingChannel(p); // Create channel and when ready, send
248     };
249 }
250
251 void TpSessionAccount::addOutgoingChannel(const Tp::ContactPtr &contact)
252 {
253
254
255   if(TpSession::tpsDebug()) qDebug() << "TpSessionAccount::addOutgoingChannel";
256
257      TpSessionChannel* newChannel=new TpSessionChannel(contact->manager()->connection(),contact);
258      connect(newChannel,SIGNAL(messageReceived(const Tp::ReceivedMessage &,TpSessionChannel *)),
259              SLOT(onMessageReceived(const Tp::ReceivedMessage &,TpSessionChannel *)));
260      connect(newChannel,SIGNAL(messageSent(const Tp::Message &,Tp::MessageSendingFlags, const QString &,TpSessionChannel *)),
261              SLOT(onMessageSent(const Tp::Message &,Tp::MessageSendingFlags, const QString &,TpSessionChannel *)));
262      connect(newChannel,SIGNAL(channelReady(TpSessionChannel *)),
263             SLOT(onOutgoingChannelReady(TpSessionChannel*)));
264      connect(newChannel,SIGNAL(channelDestroyed(TpSessionChannel *)),
265             SLOT(onChannelDestroyed(TpSessionChannel*)));
266      myChannels+=newChannel;
267
268 }
269
270 void TpSessionAccount::onOutgoingChannelReady(TpSessionChannel *ch)
271 {
272  if(TpSession::tpsDebug()) qDebug() << "TpSessionAccount::onOutgoingChannelReady";
273  emit channelReady(this);
274  if(!reqMessage.isEmpty()) {
275    if(TpSession::tpsDebug())  qDebug() << "TpSessionAccount::onOutgoingChannelReady peer:" << ch->peerId() << "queuing:" << reqMessage;
276    ch->sendMessage(reqMessage);
277    emit messageQueued(this,true);
278  };
279  reqMessage.clear();
280 }
281
282
283 void TpSessionAccount::onMessageSent(const Tp::Message &msg,Tp::MessageSendingFlags, const QString &flags,TpSessionChannel *ch)
284 {
285     Q_UNUSED(flags);
286     if(TpSession::tpsDebug()) qDebug() << "TpSessionAccount::onMessageSent peer:" << ch->peerId() <<"txt:" << msg.text();
287     emit messageSent(msg,this);
288 };
289
290 void TpSessionAccount::onMessageReceived(const Tp::ReceivedMessage &msg,TpSessionChannel *ch)
291 {
292   Q_UNUSED(ch);
293   //    qDebug() << "TpSessionAccount::onMessageReceived " << msg.text();
294   emit messageReceived(msg,this);
295 };
296
297 void TpSessionAccount::onNewChannels(const Tp::ChannelDetailsList &channels)
298 {
299
300     Tp::TextChannelPtr myIngoingTextChannel;
301     //    qDebug() << "TpSessionAccount::onNewChannels";
302     foreach (const Tp::ChannelDetails &details, channels) {
303         QString channelType = details.properties.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString();
304         QString targetId = details.properties.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID")).toString();
305         bool requested = details.properties.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested")).toBool();
306         //        qDebug() << " channelType:" << channelType <<" requested  :" << requested << " targetId" << targetId;
307
308         emit newChannel(this,channelType,targetId,details);
309         if (channelType == TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT && !requested) {
310
311             myIngoingTextChannel = Tp::TextChannel::create(acc->connection(),details.channel.path(),details.properties);
312             //            qDebug() << "TpSessionAccount::onNewChannels path=" <<"path " << myIngoingTextChannel->objectPath();
313
314             TpSessionChannel* newChannel=new TpSessionChannel( myIngoingTextChannel);
315             connect(newChannel,SIGNAL(messageReceived(const Tp::ReceivedMessage &,TpSessionChannel *)),
316              SLOT(onMessageReceived(const Tp::ReceivedMessage &,TpSessionChannel *)));
317             connect(newChannel,SIGNAL(messageSent(const Tp::Message &,Tp::MessageSendingFlags, const QString &,TpSessionChannel *)),
318                     SLOT(onMessageSent(const Tp::Message &,Tp::MessageSendingFlags, const QString &,TpSessionChannel *)));
319             connect(newChannel,SIGNAL(channelDestroyed(TpSessionChannel *)),
320                    SLOT(onChannelDestroyed(TpSessionChannel*)));
321             myChannels+=newChannel;
322         }
323         if (channelType == TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA && !requested) {
324           //            qDebug() << "Incoming call" ;
325         }
326     }
327 }
328
329 void TpSessionAccount::onChannelDestroyed(TpSessionChannel *ch)
330 {
331    if(TpSession::tpsDebug()) qDebug() << "TpSessionAccount::onMessageSent peer:" << ch->peerId();
332    myChannels.remove(ch);
333 };