2 <one line to give the library's name and an idea of what it does.>
3 Copyright (C) 2011 Vojta Kulicka <vojtechkulicka@gmail.com>
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 #include <TelepathyQt4/ChannelRequest>
22 #include <TelepathyQt4/TextChannel>
23 #include <TelepathyQt4/StreamedMediaChannel>
24 #include <TelepathyQt4/Message>
25 #include <TelepathyQt4/PendingSendMessage>
26 #include <TelepathyQt4/Contact>
27 #include <TelepathyQt4/Account>
28 #include <TelepathyQt4/ReferencedHandles>
33 #include "voip/callchannelhandler.h"
34 #include "telepathy-client.h"
35 #include <TelepathyQt4/FileTransferChannel>
36 #include <TelepathyQt4/IncomingFileTransferChannel>
37 #include <TelepathyQt4/OutgoingFileTransferChannel>
39 using namespace MaknetoBackend
;
41 //Constructor for sessions created as result of an incoming channel
43 Session::Session(const Tp::ChannelPtr
&channel
, SesstionTypes chatType
, QObject
*parent
)
45 m_contacts
= new QSet
<Tp::ContactPtr
> ();
46 m_account
= TelepathyClient::Instance()->getAccountforChannel(channel
);
48 m_chatType
= (SessionTypeNone
| chatType
);
50 m_mediaChannel
.reset();
51 m_textChannel
.reset();
52 m_fileTransferChannel
.reset();
53 m_callChannelHandler
= NULL
;
55 m_myContact
= channel
->groupSelfContact();
57 //set the peer's contactPtr
58 if (m_chatType
!= SessionTypeMuc
) {
59 //for one-to-one chat contains two contacts, me and peer
60 //for calls there is just the peer contact
61 Tp::Contacts contacts
= channel
->groupContacts();
63 foreach(Tp::ContactPtr contact
, contacts
) {
64 if (contact
== channel
->groupSelfContact())
68 qDebug() << "Session: " << contact
->alias() << " contactId: " << contact
->id();
72 qWarning() << "Session: start session with NULL contact?!";
75 m_sessionName
= m_contact
? m_contact
->alias() : "undefined";
76 m_uniqueSessionName
= Session::sessionName(m_account
, m_contact
);
79 Tp::Contacts contacts
= channel
->groupContacts();
81 foreach(Tp::ContactPtr contact
, contacts
) {
82 if (contact
== channel
->groupSelfContact())
85 m_contacts
->insert(contact
);
86 qDebug() << "Session: " << contact
->alias() << " id: " << contact
->id();
89 m_sessionName
= channel
->targetId();
90 m_uniqueSessionName
= Session::sessionNameForMuc(m_account
, channel
->targetId());
93 //now is only informative
97 qDebug() << "Session: Text";
99 case SessionTypeAudio
:
100 qDebug() << "Session: Audio";
102 case SessionTypeVideo
:
103 qDebug() << "Session: Video";
106 qDebug() << "Session: MUC";
109 qDebug() << "Default session type. Something is wrong";
113 if (m_textChannel
|| m_mediaChannel
)
114 qDebug() << "Session: initialized";
118 //Constructor for sessions created as result of an outgoing channel
120 Session::Session(const Tp::AccountPtr
&account
, const Tp::ContactPtr
&contact
, SesstionTypes sessionType
, QObject
*parent
)
124 //most likely is not neccessary here
125 m_contacts
= new QSet
<Tp::ContactPtr
> ();
126 m_myContact
= account
->connection()->selfContact();
128 m_chatType
= (SessionTypeNone
| sessionType
);
130 m_mediaChannel
.reset();
131 m_textChannel
.reset();
132 m_fileTransferChannel
.reset();
133 m_callChannelHandler
= NULL
;
135 //set the peer's contactPtr
136 m_sessionName
= contact
->alias();
137 m_uniqueSessionName
= Session::sessionName(m_account
, m_contact
); // FIXME: how to get name for outgoing MUC here?
138 qDebug() << "Session: name " << m_sessionName
<< " id: " << m_uniqueSessionName
<< "";
140 switch (m_chatType
) {
142 case SessionTypeText
:
145 case SessionTypeAudio
:
146 startMediaCall(false);
148 case SessionTypeVideo
:
149 startMediaCall(true);
153 //startTextChatroom();
156 qDebug() << "Session: Default session type. Something is wrong.";
160 qDebug() << "Session: initialized";
163 Session::~Session() {
164 if (m_callChannelHandler
&& (!m_mediaChannel
.isNull())) // FIXME: it is correct?
165 delete m_callChannelHandler
;
168 void Session::onAddCapability(Session::SesstionTypes chatType
) {
172 case SessionTypeText
:
175 case SessionTypeAudio
:
176 startMediaCall(false);
178 case SessionTypeVideo
:
179 startMediaCall(true);
183 //startTextChatroom();
186 qDebug() << "Session: Default session type. Something is wrong.";
189 m_chatType
= m_chatType
| chatType
;
193 ///-----------------------------------------------------------------------------------------------------------------------
194 ///************************************************** Text Chat ******************************************************
195 ///-----------------------------------------------------------------------------------------------------------------------
197 void Session::startTextChat() {
198 qDebug() << "Session: Start text chat (one-to-one)";
199 //returns PendingChannel object, which emits succeeded signal, see if it would be better
200 //to wait for that instead of distributing the channel through handleChannels.
201 m_account
->ensureTextChat(m_contact
, QDateTime::currentDateTime(), "org.freedesktop.Telepathy.Client.Makneto");
204 void Session::onTextChannelReady(Tp::TextChannelPtr
& textChannel
) {
205 qDebug() << "Session: Text Channel ready";
206 if (m_textChannel
.isNull()) {
207 m_textChannel
= textChannel
;
208 m_chatType
.operator|=(SessionTypeText
);
210 connect(m_textChannel
.data(),
211 SIGNAL(messageReceived(const Tp::ReceivedMessage
&)),
212 SLOT(onMessageReceived(const Tp::ReceivedMessage
&)));
214 //chat status changed
215 connect(m_textChannel
.data(),
216 SIGNAL(chatStateChanged(Tp::ContactPtr
, Tp::ChannelChatState
)),
217 SLOT(onChatStateChanged(Tp::ContactPtr
, Tp::ChannelChatState
)));
220 //QStringList *messages = new QStringList();
222 foreach(Tp::ReceivedMessage message
, m_textChannel
->messageQueue()) {
224 //messages->append(message.text());
225 emit
messageReceived(message
.text(), message
.sender()->alias());
226 m_textChannel
->acknowledge(QList
<Tp::ReceivedMessage
> () << message
);
228 //emit textChatReady(messages);
232 void Session::onMessageReceived(const Tp::ReceivedMessage
&message
) {
233 //qDebug() << "Message received: " << message.text();
235 if (message
.sender() && message
.text() != QString()) {
236 sender
= message
.sender()->alias();
237 emit
messageReceived(message
.text(), sender
);
239 //message acknowledging causes the messages to be deleted from pendingMessageQueue
240 m_textChannel
->acknowledge(QList
<Tp::ReceivedMessage
> () << message
);
244 void Session::sendMessage(const QString
& message
) {
246 if (message
== QString())
249 if (m_textChannel
.isNull()){
250 qWarning() << "Sesssion: m_textChannel shared pointer is NULL !!!";
254 //qDebug() << "Session: Sending message: " << message;
255 connect(m_textChannel
->send(message
), SIGNAL(finished(Tp::PendingOperation
*)),
256 this, SLOT(onMessageSent(Tp::PendingOperation
*)));
259 void Session::onMessageSent(Tp::PendingOperation
*op
) {
261 Tp::PendingSendMessage
*pendingMessage
= qobject_cast
<Tp::PendingSendMessage
*>(op
);
262 qDebug() << "Session: Error sending message: " << pendingMessage
->message().text()
263 << "\nError: " << pendingMessage
->errorMessage();
268 void Session::onChatStateChanged(Tp::ContactPtr contact
, Tp::ChannelChatState chatState
) {
269 qDebug() << "Session: Channel chat state changed for: " << contact
->alias() << " to: " << chatState
;
270 //the contact alias is for MUC - I think
273 case Tp::ChannelChatStateComposing
:
274 emit
chatStateChanged(ChatStateComposing
, contact
->alias());
276 case Tp::ChannelChatStateGone
:
277 emit
chatStateChanged(ChatStateGone
, contact
->alias());
279 case Tp::ChannelChatStateActive
:
280 emit
chatStateChanged(ChatStateActive
, contact
->alias());
282 case Tp::ChannelChatStateInactive
:
283 emit
chatStateChanged(ChatStateInactive
, contact
->alias());
285 case Tp::ChannelChatStatePaused
:
286 emit
chatStateChanged(ChatStatePaused
, contact
->alias());
289 qDebug() << "Session: Warning: Unknown chat state";
296 ///-----------------------------------------------------------------------------------------------------------------------
297 ///************************************************** MUC ************************************************************
298 ///-----------------------------------------------------------------------------------------------------------------------
303 void Session::startTextChatroom(const QString
&chatRoomName
) {
304 qDebug() << "Session: Start text chat chatroom (MUC)";
305 m_account
->ensureTextChatroom(chatRoomName
, QDateTime::currentDateTime(), "org.freedesktop.Telepathy.Client.Makneto");
308 void Session::onTextChannelReady(Tp::TextChannelPtr
& textChannel
, bool isMUC
) {
309 qDebug() << "Session: Text Channel ready";
310 if (m_textChannel
.isNull()) {
311 m_textChannel
= textChannel
;
313 isMUC
? m_chatType
= SessionTypeMuc
314 : m_chatType
.operator|=(SessionTypeText
);
316 connect(m_textChannel
.data(),
317 SIGNAL(messageReceived(const Tp::ReceivedMessage
&)),
318 SLOT(onMessageReceived(const Tp::ReceivedMessage
&)));
320 //chat status changed TODO
321 connect(m_textChannel
.data(),
322 SIGNAL(chatStateChanged(Tp::ContactPtr
, Tp::ChannelChatState
)),
323 SLOT(onChatStateChanged(Tp::ContactPtr
, Tp::ChannelChatState
)));
325 foreach(Tp::ReceivedMessage message
, m_textChannel
->messageQueue()) {
326 emit
messageReceived(message
.text(), message
.sender()->alias());
327 m_textChannel
->acknowledge(QList
<Tp::ReceivedMessage
> () << message
);
332 bool Session::isMUC() {
333 return m_chatType
.testFlag(SessionTypeMuc
);
337 ///-----------------------------------------------------------------------------------------------------------------------
338 ///************************************************** VoIP **********************************************************
339 ///-----------------------------------------------------------------------------------------------------------------------
341 void Session::startMediaCall(bool video
) {
342 qDebug() << "Session: start media call (" << m_uniqueSessionName
<< ")";
344 if (m_contact
.isNull()){
345 qWarning() << "Session: m_contact shared pointer is NULL !!!";
350 request
.insert(TELEPATHY_INTERFACE_CHANNEL
".ChannelType",
351 TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA
);
352 request
.insert(TELEPATHY_INTERFACE_CHANNEL
".TargetHandleType", Tp::HandleTypeContact
);
353 request
.insert(TELEPATHY_INTERFACE_CHANNEL
".TargetHandle", m_contact
->handle()[0]);
354 request
.insert(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA
".InitialAudio", true);
356 request
.insert(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA
".InitialVideo", true);
358 m_account
->ensureChannel(request
, QDateTime::currentDateTime(), "org.freedesktop.Telepathy.Client.Makneto");
364 void Session::onIncomingCallChannel(Tp::StreamedMediaChannelPtr
& mediaChannel
, SessionType mediaType
) {
366 qDebug() << "Session: Incoming call channel";
368 if (!m_mediaChannel
.isNull()) {
369 qWarning() << "previous channel is not cleaned?!";
373 m_chatType
.operator|=(mediaType
);
374 m_mediaChannel
= mediaChannel
;
376 //TODO connect channel's signals
378 if (m_mediaChannel
->isRequested())
384 void Session::acceptCall() {
385 qDebug() << "Session: Accept Call";
386 if (m_mediaChannel
.isNull() && m_callChannelHandler
== NULL
)
389 m_mediaChannel
->acceptCall();
391 m_callChannelHandler
= new CallChannelHandler(m_mediaChannel
, this);
393 connect(m_callChannelHandler
, SIGNAL(callEnded(QString
)),
394 this, SLOT(onCallEnded(QString
)));
395 connect(m_callChannelHandler
, SIGNAL(error(QString
)),
396 this, SLOT(onCallError(QString
)));
397 connect(m_callChannelHandler
, SIGNAL(participantLeft(CallParticipant
*)),
398 this, SLOT(onCallParticipantLeft(CallParticipant
*)));
399 connect(m_callChannelHandler
, SIGNAL(participantJoined(CallParticipant
*)),
400 this, SLOT(onCallParticipantJoined(CallParticipant
*)));
403 void Session::rejectCall() {
404 qDebug() << "Session: Rejecting call from: " << m_contact
->alias();
405 if (!m_mediaChannel
.isNull()) {
406 m_mediaChannel
->hangupCall();
407 m_mediaChannel
.reset();
409 if (m_chatType
.testFlag(SessionTypeVideo
))
410 m_chatType
.operator^=(SessionTypeVideo
);
411 m_chatType
.operator^=(SessionTypeAudio
);
414 void MaknetoBackend::Session::onCallEnded(const QString
&message
) {
415 qDebug() << "Session: Call ended";
416 //TODO figure out what to do with the channel
417 if (m_callChannelHandler
)
418 m_callChannelHandler
->deleteLater(); //TODO check out
420 m_mediaChannel
.reset();
421 if (m_chatType
.testFlag(SessionTypeVideo
))
422 m_chatType
.operator^=(SessionTypeVideo
);
423 m_chatType
.operator^=(SessionTypeAudio
);
425 emit
callEnded(message
);
428 void Session::onHangup() {
429 qDebug() << "Session: Hang up";
430 //TODO check if the call still lasts
431 if (!m_mediaChannel
.isNull() && m_mediaChannel
->isValid()) {
432 m_callChannelHandler
->hangup();
434 m_mediaChannel
.reset();
435 if (m_chatType
.testFlag(SessionTypeVideo
))
436 m_chatType
.operator^=(SessionTypeVideo
);
437 m_chatType
.operator^=(SessionTypeAudio
);
440 void Session::onCallError(const QString
&msg
) {
441 // just forward to higher layer
442 Q_EMIT
callError(msg
);
445 void Session::onCallParticipantLeft(CallParticipant
*participant
) {
446 // FIXME: should we do something? all streams are removed already
449 void Session::onCallParticipantJoined(CallParticipant
*participant
) {
450 connect(participant
, SIGNAL(videoStreamAdded(CallParticipant
*)),
451 this, SLOT(onVideoStreamAdded(CallParticipant
*)));
452 connect(participant
, SIGNAL(videoStreamRemoved(CallParticipant
*)),
453 this, SLOT(onVideoStreamRemoved(CallParticipant
*)));
456 void Session::onVideoStreamAdded(CallParticipant
*participant
) {
457 if (!participant
->videoWidget()) {
458 qWarning() << "video stream added, but we dont have videoWidget!";
462 if (participant
->isMyself()) {
463 Q_EMIT
videoPreviewAvailable(participant
->videoWidget());
465 Q_EMIT
videoAvailable(participant
->videoWidget());
469 void Session::onVideoStreamRemoved(CallParticipant
*participant
) {
470 if (participant
->isMyself()) {
471 Q_EMIT
videoPreviewRemoved();
473 Q_EMIT
videoRemoved();
478 ///-----------------------------------------------------------------------------------------------------------------------
479 ///********************************************** File Transfer ******************************************************
480 ///-----------------------------------------------------------------------------------------------------------------------
482 void Session::startFileTransfer(const QString
&filename
) {
483 if (m_chatType
.testFlag(SessionTypeMuc
)) {
485 qDebug() << "Session: File transfer does not work with MUC";
488 if (!m_fileTransferChannel
.isNull() && m_fileTransferChannel
->state() != Tp::FileTransferStateNone
) {
489 qDebug() << "Session: There already is an unfinished file transfer.";
490 emit
fileTransferAlreadyInProgress();
494 m_fileTransferFile
.setFileName(filename
);
495 QFileInfo
fileInfo(m_fileTransferFile
);
496 Tp::FileTransferChannelCreationProperties fileTransferProperties
= Tp::FileTransferChannelCreationProperties(fileInfo
.fileName(),
497 QString("application/octet-stream"),
498 (qulonglong
) fileInfo
.size());
499 m_account
->createFileTransfer(m_contact
,
500 fileTransferProperties
,
501 QDateTime::currentDateTime(),
502 "org.freedesktop.Telepathy.Client.Makneto");
505 void Session::onFileTransfer(Tp::FileTransferChannelPtr
& fileTransferChannel
) {
506 //check if there is not an active file transfer
507 if (!m_fileTransferChannel
.isNull() && m_fileTransferChannel
->state() != Tp::FileTransferStateNone
) {
508 qDebug() << "Session: Incoming file transfer ignored, there is already one in progress";
512 m_fileTransferChannel
= fileTransferChannel
;
513 if (m_fileTransferChannel
->isRequested()) {
515 if (!m_fileTransferFile
.open(QIODevice::ReadOnly
)) { //TODO let GUI know
516 qDebug() << "Session: error opening file: " << m_fileTransferFile
.fileName();
517 m_fileTransferChannel
->cancel();
518 m_fileTransferChannel
.reset();
521 Tp::OutgoingFileTransferChannelPtr outgoingFileTransfer
= Tp::OutgoingFileTransferChannelPtr::dynamicCast(m_fileTransferChannel
);
522 if (outgoingFileTransfer
)
523 outgoingFileTransfer
->provideFile(&m_fileTransferFile
);
525 connect(m_fileTransferChannel
.data(), SIGNAL(stateChanged(Tp::FileTransferState
, Tp::FileTransferStateChangeReason
)),
526 this, SLOT(onFileTransferStateChanged(Tp::FileTransferState
, Tp::FileTransferStateChangeReason
)));
527 connect(m_fileTransferChannel
.data(), SIGNAL(transferredBytesChanged(qulonglong
)),
528 this, SLOT(onFileTransferTransferredBytesChanged(qulonglong
)));
529 emit
fileTransferCreated();
531 emit
incomingFileTransfer(fileTransferChannel
->fileName());
535 void Session::onAcceptFileTransfer(bool accept
, QString filename
) {
537 Tp::IncomingFileTransferChannelPtr incomingFT
= Tp::IncomingFileTransferChannelPtr::dynamicCast(m_fileTransferChannel
);
541 if (filename
== QString()) {
542 filename
= incomingFT
->fileName();
544 m_fileTransferFile
.setFileName(filename
);
545 if (m_fileTransferFile
.open(QIODevice::WriteOnly
)) {
546 qDebug() << "Session: Could not open/create file: " << m_fileTransferFile
.fileName();
548 incomingFT
->acceptFile(0, &m_fileTransferFile
); //the first number is the offset of the file
550 connect(m_fileTransferChannel
.data(), SIGNAL(stateChanged(Tp::FileTransferState
, Tp::FileTransferStateChangeReason
)),
551 this, SLOT(onFileTransferStateChanged(Tp::FileTransferState
, Tp::FileTransferStateChangeReason
)));
552 connect(m_fileTransferChannel
.data(), SIGNAL(transferredBytesChanged(qulonglong
)),
553 SLOT(onFileTransferTransferredBytesChanged(qulonglong
)));
557 //return PendingOperation object - possibly check if it finished ok
558 m_fileTransferChannel
->cancel(); //TODO check if this is the method to call, or just wait it out - not do anything
559 m_fileTransferChannel
.reset();
563 void Session::onFileTransferStateChanged(Tp::FileTransferState state
, Tp::FileTransferStateChangeReason reason
) {
566 case Tp::FileTransferStateAccepted
:
568 emit
fileTransferStateChanged(fileTransferAccepted
);
570 case Tp::FileTransferStateCancelled
:
571 emit
fileTransferStateChanged(fileTransferCancelled
);
572 m_fileTransferChannel
.reset();
573 m_fileTransferFile
.close();
575 case Tp::FileTransferStateCompleted
:
576 emit
fileTransferStateChanged(fileTransferCompleted
);
577 m_fileTransferChannel
.reset();
578 m_fileTransferFile
.close();
580 case Tp::FileTransferStateOpen
:
581 emit
fileTransferStateChanged(fileTransferOpen
);
583 default: qDebug() << "Session: File transfer channel in an for now unsupported state";
588 QString
Session::sessionNameForChannel(const Tp::AccountPtr
&account
, const Tp::ChannelPtr
&channel
) {
589 Tp::Contacts allContacts
= channel
->groupContacts();
590 if (allContacts
.size() == 2) { //one-to-one chat
592 foreach(Tp::ContactPtr contact
, allContacts
) {
593 if (contact
!= channel
->groupSelfContact())
594 return sessionName(account
, contact
);
596 } else if (allContacts
.size() == 1) {
597 Tp::Contacts pendingContacts
= channel
->groupRemotePendingContacts();
598 if (pendingContacts
.size() == 1) {
599 return (account
? account
->uniqueIdentifier() : "undefinedAccount") + "/" +(pendingContacts
.begin()->constData()->id());
603 return sessionNameForMuc(account
, channel
->targetId());
606 QString
Session::sessionName(const Tp::AccountPtr
&account
, const Tp::ContactPtr
&contact
) {
607 return (account
? account
->uniqueIdentifier() : "undefinedAccount") + "/" +(contact
? contact
->id() : "undefinedContact");
610 QString
Session::sessionNameForMuc(const Tp::AccountPtr
&account
, const QString
&mucName
){
611 return (account
? account
->uniqueIdentifier() : "undefinedAccount") + "/MUC/" +(mucName
);
615 void Session::onFileTransferTransferredBytesChanged(qulonglong bytes
) {
616 qDebug() << "Session: Transferred bytes: " << bytes
;
617 emit
fileTransferTransferredBytesChanged(bytes
);
620 QString
Session::getName() {
621 return m_sessionName
;
624 QString
Session::getUniqueName() {
625 return m_uniqueSessionName
;
628 QString
Session::getMyId() {
629 return m_myContact
->id();
632 QString
Session::getMyNick() {
633 return m_myContact
->alias();
636 Session::SesstionTypes
Session::getChatType() {
640 QString
Session::getIcon(){
641 if (m_contact
.isNull())
643 return m_contact
->avatarData().fileName
;
647 #include "session.moc"