~ubuntu-branches/ubuntu/vivid/psi/vivid

« back to all changes in this revision

Viewing changes to src/sxe/sxemanager.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Jan Niehusmann
  • Date: 2008-08-28 18:46:52 UTC
  • mfrom: (1.2.4 upstream)
  • Revision ID: james.westby@ubuntu.com-20080828184652-iiik12dl91nq7cdi
Tags: 0.12-2
Uploading to unstable (Closes: Bug#494352)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * sxemanager.cpp - Whiteboard manager
 
3
 * Copyright (C) 2006  Joonas Govenius
 
4
 *
 
5
 * This program is free software; you can redistribute it and/or
 
6
 * modify it under the terms of the GNU General Public License
 
7
 * as published by the Free Software Foundation; either version 2
 
8
 * of the License, or (at your option) any later version.
 
9
 *
 
10
 * This program 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
 
13
 * GNU General Public License for more details.
 
14
 *
 
15
 * You should have received a copy of the GNU General Public License
 
16
 * along with this library; if not, write to the Free Software
 
17
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
18
 *
 
19
 */
 
20
 
 
21
#include "sxemanager.h"
 
22
#include "psipopup.h"
 
23
#include "psioptions.h"
 
24
#include "common.h"
 
25
#include <QUrl>
 
26
 
 
27
using namespace XMPP;
 
28
 
 
29
//----------------------------------------------------------------------------
 
30
// SxeManager
 
31
//----------------------------------------------------------------------------
 
32
 
 
33
SxeManager::SxeManager(Client* client, PsiAccount* pa) : client_(client) {
 
34
    sxeId_ = QTime::currentTime().toString("z").toInt();
 
35
 
 
36
    pa_ = pa;
 
37
    connect(client_, SIGNAL(messageReceived(const Message &)), SLOT(messageReceived(const Message &)));
 
38
    connect(client_, SIGNAL(groupChatLeft(const Jid &)), SLOT(groupChatLeft(const Jid &)));
 
39
    // connect(client_, SIGNAL(groupChatJoined(const Jid &, const Jid &)), SLOT(groupChatJoined(const Jid &, const Jid &)));
 
40
 
 
41
    negotiationTimer_.setSingleShot(true);
 
42
    negotiationTimer_.setInterval(120000);
 
43
    connect(&negotiationTimer_, SIGNAL(timeout()), SLOT(negotiationTimeout()));
 
44
}
 
45
 
 
46
void SxeManager::addInvitationCallback(bool (*callback)(const Jid &peer, const QList<QString> &features)) {
 
47
    invitationCallbacks_ += callback;
 
48
}
 
49
 
 
50
void SxeManager::messageReceived(const Message &message) {
 
51
    // only process messages that contain a <sxe/> with a nonempty
 
52
    // 'session' attribute and that are addressed to this particular account
 
53
    if(!message.sxe().attribute("session").isEmpty()) {
 
54
 
 
55
        // skip messages from self
 
56
        if(ownJids_.contains(message.from().full())) {
 
57
            qDebug("from self");
 
58
            return;
 
59
        }
 
60
 
 
61
        // // Don't process delayed messages (chat history) but remember the session id
 
62
        // if(!message.spooled()) {
 
63
 
 
64
            // Check if the <sxe/> contains a <negotiation/>
 
65
            if(message.sxe().elementsByTagName("negotiation").length() > 0) {
 
66
                processNegotiationMessage(message);
 
67
                // processNegotiationMessage() will also pass regular SXE edits in the message
 
68
                // to the session so we're done
 
69
                return;
 
70
            }
 
71
 
 
72
            // otherwise, try finding a matching session for the session if new one not negotiated
 
73
            SxeSession* w = findSession(message.sxe().attribute("session"));
 
74
 
 
75
            if(w) {
 
76
                // pass the message to the session if already established
 
77
                w->processIncomingSxeElement(message.sxe(), message.from());
 
78
            } else {
 
79
                // otherwise record the session id as a "detected session"
 
80
                recordDetectedSession(message);
 
81
            }
 
82
 
 
83
        // } else {
 
84
        //      recordDetectedSession(message);
 
85
        // }
 
86
 
 
87
    }
 
88
}
 
89
 
 
90
void SxeManager::recordDetectedSession(const Message &message) {
 
91
    // check if a record of the session exists
 
92
    foreach(DetectedSession d, DetectedSessions_) {
 
93
        if(d.session == message.sxe().attribute("session")
 
94
            && d.jid.compare(message.from(), message.type() != "groupchat"))
 
95
            return;
 
96
    }
 
97
 
 
98
    // store a record of a detected session
 
99
    DetectedSession detected;
 
100
    detected.session = message.sxe().attribute("session");
 
101
    if(message.type() == "groupchat")
 
102
        detected.jid = message.from().bare();
 
103
    else
 
104
        detected.jid = message.from();
 
105
    detected.time = QTime::currentTime();
 
106
    DetectedSessions_.append(detected);
 
107
}
 
108
 
 
109
void SxeManager::removeSession(SxeSession* session) {
 
110
    sessions_.removeAll(session);
 
111
 
 
112
    // cancel possible negotiations
 
113
    foreach(SxeNegotiation* negotiation, negotiations_.values(session->session())) {
 
114
        if(negotiation->target.compare(session->target(), true))
 
115
            abortNegotiation(negotiation);
 
116
    }
 
117
 
 
118
    // notify the target
 
119
    QDomDocument doc;
 
120
    QDomElement sxe = doc.createElementNS(SXDENS, "sxe");
 
121
    sxe.setAttribute("session", session->session());
 
122
    QDomElement negotiation = doc.createElementNS(SXDENS, "negotiation");
 
123
    negotiation.appendChild(doc.createElementNS(SXDENS, "left-session"));
 
124
    sxe.appendChild(negotiation);
 
125
    sendSxe(sxe, session->target(), session->groupChat());
 
126
 
 
127
    // delete the session
 
128
    session->deleteLater();
 
129
}
 
130
 
 
131
bool SxeManager::processNegotiationAsParticipant(const QDomNode &negotiationElement, SxeNegotiation* negotiation, QDomNode response) {
 
132
    QDomDocument doc = QDomDocument();
 
133
 
 
134
    if(negotiationElement.nodeName() == "left-session") {
 
135
 
 
136
        if(negotiation->state == SxeNegotiation::Finished)
 
137
            emit negotiation->session->peerLeftSession(negotiation->peer);
 
138
 
 
139
    } else if(negotiationElement.nodeName() == "abort-negotiation") {
 
140
 
 
141
        if(negotiation->state < SxeNegotiation::HistoryOffered
 
142
            && negotiation->state != SxeNegotiation::DocumentBegan) {
 
143
            // Abort, as in delete session, if still establishing it and not trying to create a new groupchat session
 
144
            if(!(negotiation->groupChat && negotiation->peer.resource().isEmpty())) {
 
145
                removeNegotiation(negotiation);
 
146
                return false;
 
147
            }
 
148
        } else {
 
149
            // Just remove the "negotation" but keep the session
 
150
            negotiation->state = SxeNegotiation::Finished;
 
151
        }
 
152
 
 
153
    } else if(negotiationElement.nodeName() == "connect-request"
 
154
            && negotiation->state == SxeNegotiation::Finished) {
 
155
 
 
156
        // accept all <connect-request/>'s automatically
 
157
        // if currently not negotiating with someone else
 
158
        negotiation->state = SxeNegotiation::HistoryOffered;
 
159
        response.appendChild(doc.createElementNS(SXDENS, "history-offer"));
 
160
 
 
161
    } else if((negotiationElement.nodeName() == "accept-history"
 
162
                    && negotiation->state == SxeNegotiation::HistoryOffered)
 
163
            || (negotiationElement.nodeName() == "accept-invitation"
 
164
                    && negotiation->state == SxeNegotiation::InvitationSent)) {
 
165
 
 
166
        // If this is a new session (negotiation->state == SxeNegotiation::HistoryOffered),
 
167
        // create a new SxeSession
 
168
        if(!negotiation->session) {
 
169
            negotiation->session = createSxeSession(negotiation->target, negotiation->sessionId, negotiation->ownJid, negotiation->groupChat, negotiation->features);
 
170
            negotiation->session->initializeDocument(negotiation->initialDoc);
 
171
        }
 
172
 
 
173
        if(!negotiation->session) {
 
174
            // Creating a new session failed for some reason.
 
175
            abortNegotiation(negotiation);
 
176
            return false;
 
177
        }
 
178
 
 
179
        // Retrieve all the edits to the session so far and start queueing new edits
 
180
        QList<const SxeEdit*> snapshot = negotiation->session->startQueueing();
 
181
 
 
182
        // append <document-begin/>
 
183
        QDomElement documentBegin = doc.createElementNS(SXDENS, "document-begin");
 
184
        response.appendChild(documentBegin);
 
185
        QString prolog = SxeSession::parseProlog(negotiation->session->document());
 
186
        if(!prolog.isEmpty()) {
 
187
            QUrl::encode(prolog);
 
188
            documentBegin.setAttribute("prolog", QString("data:text/xml,%1").arg(prolog));
 
189
        }
 
190
        response.appendChild(documentBegin);
 
191
 
 
192
        // append all the SxeEdit's returned by startQueueing()
 
193
        foreach(const SxeEdit* e, snapshot) {
 
194
            response.appendChild(e->xml(doc));
 
195
        }
 
196
 
 
197
        // append <documend-end/>
 
198
        QDomElement documentEnd = doc.createElementNS(SXDENS, "document-end");
 
199
        documentEnd.setAttribute("last-sender", negotiation->session->lastSxe()["sender"]);
 
200
        documentEnd.setAttribute("last-id", negotiation->session->lastSxe()["id"]);
 
201
        response.appendChild(documentEnd);
 
202
 
 
203
        // Need to "flush" the sxe here before stopping queueing
 
204
        if(response.hasChildNodes()) {
 
205
            QDomElement sxe = doc.createElementNS(SXDENS, "sxe");
 
206
            sxe.setAttribute("session", negotiation->sessionId);
 
207
            sxe.appendChild(response);
 
208
            sendSxe(sxe.toElement(), negotiation->peer, negotiation->groupChat);
 
209
            sxe.removeChild(response);
 
210
        }
 
211
        while(response.hasChildNodes())
 
212
            response.removeChild(response.firstChild());
 
213
 
 
214
        // we're all set and can stop queueing new edits to the session
 
215
        negotiation->state = SxeNegotiation::Finished;
 
216
        negotiation->session->stopQueueing();
 
217
 
 
218
        // signal that a peer joined
 
219
        emit negotiation->session->peerJoinedSession(negotiation->peer);
 
220
 
 
221
    } else if(negotiationElement.nodeName() == "decline-invitation" && negotiation->state == SxeNegotiation::InvitationSent) {
 
222
 
 
223
        QDomNodeList alternatives = negotiationElement.toElement().elementsByTagName("alternative-session");
 
224
        for(int i = 0; i < alternatives.size(); i++) {
 
225
            emit alternativeSession(negotiation->target, alternatives.at(i).toElement().text());
 
226
        }
 
227
 
 
228
        if(!negotiation->groupChat || alternatives.size() > 0) {
 
229
            abortNegotiation(negotiation);
 
230
            return false;
 
231
        }
 
232
 
 
233
    }
 
234
    
 
235
    return true;
 
236
}
 
237
 
 
238
bool SxeManager::processNegotiationAsJoiner(const QDomNode &negotiationElement, SxeNegotiation* negotiation, QDomNode response, const Message &message) {
 
239
    QDomDocument doc = QDomDocument();
 
240
    
 
241
    if(negotiationElement.nodeName() == "abort-negotiation") {
 
242
 
 
243
        // Abort, as in delete session, if not trying to join a groupchat session
 
244
        if(!(negotiation->groupChat && negotiation->peer.resource().isEmpty())) {
 
245
            removeNegotiation(negotiation);
 
246
            return false;
 
247
        }
 
248
 
 
249
    } else if(negotiationElement.nodeName() == "invitation"
 
250
                && negotiation->state == SxeNegotiation::NotStarted) {
 
251
 
 
252
        // copy the feature strings to negotiation-features
 
253
        for(uint k = 0; k < negotiationElement.childNodes().length(); k++) {
 
254
            if(negotiationElement.childNodes().at(k).nodeName() == "feature") {
 
255
                negotiation->features += negotiationElement.childNodes().at(k).toElement().text();
 
256
            }
 
257
        }
 
258
 
 
259
        // check if one of the invitation callbacks accepts the invitation.
 
260
        foreach(bool (*callback)(const Jid &peer, const QList<QString> &features), invitationCallbacks_) {
 
261
            if(callback(negotiation->peer, negotiation->features)) {
 
262
                response.appendChild(doc.createElementNS(SXDENS, "accept-invitation"));
 
263
                negotiation->state = SxeNegotiation::InvitationAccepted;
 
264
                return true;
 
265
            }
 
266
        }
 
267
        // othewise abort negotiation
 
268
        abortNegotiation(negotiation);
 
269
        return false;
 
270
 
 
271
    } else if(negotiationElement.nodeName() == "history-offer"
 
272
            && negotiation->state == SxeNegotiation::ConnectionRequested) {
 
273
 
 
274
        // accept the first <history-offer/> that arrives in response to a <connect-request/>
 
275
        negotiation->state = SxeNegotiation::HistoryAccepted;
 
276
        negotiation->peer = message.from();
 
277
        response.appendChild(doc.createElementNS(SXDENS, "accept-history"));
 
278
 
 
279
    } else if(negotiationElement.nodeName() == "document-begin"
 
280
            && (negotiation->state == SxeNegotiation::HistoryAccepted
 
281
                || negotiation->state == SxeNegotiation::InvitationAccepted)) {
 
282
 
 
283
        // Create the new SxeSession
 
284
        if(!negotiation->session) {
 
285
            negotiation->session = createSxeSession(negotiation->target, negotiation->sessionId, negotiation->ownJid, negotiation->groupChat, negotiation->features);
 
286
        }
 
287
 
 
288
        if(negotiation->session) {
 
289
            // set the session to "importing" state which bypasses some version control
 
290
            QDomDocument doc;
 
291
            if(negotiationElement.toElement().hasAttribute("prolog")) {
 
292
                QString prolog = negotiationElement.toElement().attribute("prolog");
 
293
                if(prolog.startsWith("data:")) {
 
294
                    // Assuming non-base64
 
295
                    prolog = prolog.mid(prolog.indexOf(",") + 1);
 
296
                    QUrl::decode(prolog);
 
297
                    doc.setContent(prolog);
 
298
                }
 
299
            }
 
300
 
 
301
            negotiation->session->setImporting(true, doc);
 
302
            negotiation->state = SxeNegotiation::DocumentBegan;
 
303
        } else {
 
304
            // creating the session failed for some reason
 
305
            abortNegotiation(negotiation);
 
306
            return false;
 
307
        }
 
308
    } else if(negotiationElement.nodeName() != "document-end"
 
309
            && negotiation->state == SxeNegotiation::DocumentBegan) {
 
310
 
 
311
        // pass the edit to the session
 
312
        QDomElement sxe = doc.createElementNS(SXDENS, "sxe");
 
313
        sxe.setAttribute("session", negotiation->sessionId);
 
314
        sxe.appendChild(negotiationElement.cloneNode());
 
315
        negotiation->session->processIncomingSxeElement(sxe, negotiation->peer);
 
316
 
 
317
    } else if(negotiationElement.nodeName() == "document-end"
 
318
            && negotiation->state == SxeNegotiation::DocumentBegan) {
 
319
 
 
320
        // The initial document has been received and we're done
 
321
        negotiation->state = SxeNegotiation::Finished;
 
322
 
 
323
        negotiation->session->eraseQueueUntil(negotiationElement.toElement().attribute("last-sender"),
 
324
                                                negotiationElement.toElement().attribute("last-id"));
 
325
 
 
326
        // Exit the "importing" state so that normal version control resumes
 
327
        negotiation->session->setImporting(false);
 
328
 
 
329
    }
 
330
 
 
331
    return true;
 
332
}
 
333
 
 
334
QPointer<SxeSession> SxeManager::processNegotiationMessage(const Message &message) {
 
335
 
 
336
    if(PsiOptions::instance()->getOption("options.messages.ignore-non-roster-contacts").toBool() && message.type() != "groupchat") {
 
337
        // Ignore the message if contact not in roster
 
338
        if(!pa_->find(message.from())) {
 
339
            qDebug("SXE invitation received from contact that is not in roster.");
 
340
            return 0;
 
341
        }
 
342
    }
 
343
 
 
344
    // Find or create a negotiation object
 
345
    SxeNegotiation* negotiation = findNegotiation(message.from(), message.sxe().attribute("session"));
 
346
    if(negotiation) {
 
347
 
 
348
        // Only accept further negotiation messages from the source we are already negotiationing with or if we've requested connection to a groupchat session
 
349
        if(!negotiation->peer.compare(message.from()) && !(negotiation->groupChat && negotiation->peer.resource().isEmpty())) {
 
350
            abortNegotiation(negotiation->sessionId, message.from(), true);
 
351
            return 0;
 
352
        }
 
353
 
 
354
    } else
 
355
        negotiation = createNegotiation(message);
 
356
 
 
357
    // Prepare the response <sxe/>
 
358
    QDomDocument doc;
 
359
    QDomElement sxe = doc.createElementNS(SXDENS, "sxe");
 
360
    sxe.setAttribute("session", negotiation->sessionId);
 
361
    QDomElement response = doc.createElementNS(SXDENS, "negotiation");
 
362
 
 
363
    // Process each child of the <sxe/>
 
364
    QDomNode n;
 
365
    for(int i = 0; i < message.sxe().childNodes().count(); i++) {
 
366
        n = message.sxe().childNodes().at(i);
 
367
 
 
368
        // skip non-elements
 
369
        if(!n.isElement())
 
370
            continue;
 
371
 
 
372
        if(n.nodeName() == "negotiation") {
 
373
 
 
374
            // Process each child element of <negotiation/>
 
375
            for(int j = 0; j < n.childNodes().count(); j++) {
 
376
                
 
377
                if(negotiation->role == SxeNegotiation::Participant) {
 
378
                    if(!processNegotiationAsParticipant(n.childNodes().at(j), negotiation, response))
 
379
                        return 0;
 
380
                } else if(negotiation->role == SxeNegotiation::Joiner) {
 
381
                    if(!processNegotiationAsJoiner(n.childNodes().at(j), negotiation, response, message))
 
382
                        return 0;
 
383
                } else
 
384
                    assert(false);
 
385
 
 
386
                // Send any responses that were generated
 
387
                if(response.hasChildNodes()) {
 
388
                    sxe.appendChild(response);
 
389
                    sendSxe(sxe.cloneNode().toElement(), negotiation->peer, negotiation->groupChat);
 
390
                    sxe.removeChild(response);
 
391
                }    
 
392
                
 
393
                while(response.hasChildNodes())
 
394
                    response.removeChild(response.firstChild());
 
395
            }
 
396
 
 
397
        } else if(negotiation->state == SxeNegotiation::Finished
 
398
                && negotiation->session) {
 
399
 
 
400
            // If in finished state,
 
401
            // pass the edits to the session normally
 
402
            sxe.appendChild(n);
 
403
            negotiation->session->processIncomingSxeElement(sxe, negotiation->peer);
 
404
            sxe.removeChild(n);
 
405
 
 
406
        }
 
407
    }
 
408
 
 
409
 
 
410
    // Cleanup:
 
411
    // Delete negotation objects that are no longer needed
 
412
    if(negotiation) {
 
413
 
 
414
        if(negotiation->state == SxeNegotiation::NotStarted) {
 
415
 
 
416
            removeNegotiation(negotiation);
 
417
 
 
418
        } else if(negotiation->state == SxeNegotiation::Finished) {
 
419
 
 
420
            // Save session for successful negotiations but delete the negotiation object
 
421
            SxeSession* sxesession = negotiation->session;
 
422
            if(sxesession) {
 
423
                if(!sessions_.contains(sxesession)) {
 
424
                    
 
425
                    // store and emit a signal about the session only if it's new
 
426
                    sessions_.append(sxesession);
 
427
                    emit sessionNegotiated(sxesession);
 
428
 
 
429
                }
 
430
            }
 
431
 
 
432
            removeNegotiation(negotiation);
 
433
 
 
434
            // return a handle to the session
 
435
            return sxesession;
 
436
 
 
437
        }
 
438
    }
 
439
    return 0;
 
440
}
 
441
 
 
442
SxeManager::SxeNegotiation* SxeManager::findNegotiation(const Jid &jid, const QString &session) {
 
443
    QList<SxeNegotiation*> negotiations = negotiations_.values(session);
 
444
    foreach(SxeNegotiation* negotiation, negotiations) {
 
445
        if(negotiation->state != SxeNegotiation::Aborted
 
446
            && negotiation->peer.compare(jid, negotiation->state != SxeNegotiation::ConnectionRequested))
 
447
            return negotiation;
 
448
    }
 
449
 
 
450
    return 0;
 
451
}
 
452
 
 
453
void SxeManager::removeNegotiation(SxeNegotiation* negotiation) {
 
454
    negotiations_.remove(negotiation->sessionId, negotiation);
 
455
    delete negotiation;
 
456
}
 
457
 
 
458
SxeManager::SxeNegotiation* SxeManager::createNegotiation(SxeNegotiation::Role role, SxeNegotiation::State state, const QString &sessionId, const Jid &target, const Jid &ownJid, bool groupChat) {
 
459
    SxeNegotiation* negotiation = new SxeNegotiation;
 
460
    negotiation->role = role;
 
461
    negotiation->state = state;
 
462
    negotiation->sessionId = sessionId;
 
463
    negotiation->target = target;
 
464
    negotiation->peer = target;
 
465
    negotiation->ownJid = ownJid;
 
466
    negotiation->groupChat = groupChat;
 
467
    negotiation->session = 0;
 
468
 
 
469
    negotiations_.insert(sessionId, negotiation);
 
470
 
 
471
    return negotiation;
 
472
}
 
473
 
 
474
SxeManager::SxeNegotiation* SxeManager::createNegotiation(const Message &message) {
 
475
 
 
476
    // Create a new negotiation object
 
477
 
 
478
    SxeNegotiation* negotiation = new SxeNegotiation;
 
479
 
 
480
    negotiation->sessionId = message.sxe().attribute("session");
 
481
    negotiation->session = findSession(negotiation->sessionId);
 
482
 
 
483
    negotiation->peer = message.from();
 
484
 
 
485
    if(negotiation->session) {
 
486
        
 
487
        // If negotiation exists, we're going to be the "server" for the negotiation
 
488
        negotiation->role = SxeNegotiation::Participant;
 
489
        negotiation->state = SxeNegotiation::Finished;
 
490
        negotiation->target = negotiation->session->target();
 
491
        negotiation->groupChat = negotiation->session->groupChat();
 
492
        negotiation->ownJid = negotiation->session->ownJid();
 
493
        negotiation->features = negotiation->session->features();
 
494
 
 
495
    } else {
 
496
 
 
497
        // Otherwise we're joining a session
 
498
        negotiation->role = SxeNegotiation::Joiner;
 
499
        negotiation->state = SxeNegotiation::NotStarted;
 
500
 
 
501
        if(message.type() == "groupchat") {
 
502
 
 
503
            // If we're being invited from a groupchat,
 
504
            // ownJid is determined based on the bare part of ownJids_
 
505
 
 
506
            negotiation->groupChat = true;
 
507
            foreach(QString j, ownJids_) {
 
508
                if(message.from().bare() == j.left(j.indexOf("/"))) {
 
509
                    negotiation->ownJid = j;
 
510
                    break;
 
511
                }
 
512
            }
 
513
            // Also, the target is just the bare JID in a groupchat
 
514
            negotiation->target = message.from().bare();
 
515
 
 
516
        } else {
 
517
 
 
518
            negotiation->groupChat = false;
 
519
            negotiation->target = negotiation->peer;
 
520
 
 
521
        }
 
522
 
 
523
        if(negotiation->ownJid.isEmpty())
 
524
            negotiation->ownJid = message.to();
 
525
 
 
526
    }
 
527
 
 
528
    // Reset the timeout
 
529
    negotiationTimer_.start();
 
530
 
 
531
    // Store the session
 
532
    negotiations_.insert(negotiation->sessionId, negotiation);
 
533
    
 
534
    return negotiation;
 
535
}
 
536
 
 
537
void SxeManager::joinSession(const Jid &target, const Jid &ownJid, bool groupChat, const QString &session) {
 
538
    // Prepare the <connect-request/>
 
539
    QDomDocument doc;
 
540
    QDomElement sxe = doc.createElementNS(SXDENS, "sxe");
 
541
    sxe.setAttribute("session", session);
 
542
    QDomElement negotiationElement = doc.createElementNS(SXDENS, "negotiation");
 
543
    QDomElement request = doc.createElementNS(SXDENS, "connect-request");
 
544
    negotiationElement.appendChild(request);
 
545
    sxe.appendChild(negotiationElement);
 
546
 
 
547
    // Create the negotiation object
 
548
    createNegotiation(SxeNegotiation::Joiner, SxeNegotiation::ConnectionRequested, session, target, ownJid, groupChat);
 
549
 
 
550
    sendSxe(sxe, target, groupChat);
 
551
 
 
552
    // Reset the timeout for negotiations
 
553
    negotiationTimer_.start();
 
554
 
 
555
    return;
 
556
}
 
557
 
 
558
void SxeManager::startNewSession(const Jid &target, const Jid &ownJid, bool groupChat, const QDomDocument &initialDoc, QList<QString> features) {
 
559
 
 
560
    // generate a session identifier
 
561
    QString session;
 
562
    do {
 
563
        session = SxeSession::generateUUID();
 
564
    } while (findSession(session));
 
565
 
 
566
    if(features.size() == 0) {
 
567
        // some features must be specified
 
568
        return;
 
569
    }
 
570
 
 
571
    // Prepare the <invitation/>
 
572
    QDomDocument doc;
 
573
    QDomElement sxe = doc.createElementNS(SXDENS, "sxe");
 
574
    sxe.setAttribute("session", session);
 
575
    QDomElement negotiationElement = doc.createElementNS(SXDENS, "negotiation");
 
576
    QDomElement request = doc.createElementNS(SXDENS, "invitation");
 
577
    QDomElement feature = doc.createElementNS(SXDENS, "feature");
 
578
    foreach(QString f, features) {
 
579
        feature = feature.cloneNode(false).toElement();
 
580
        feature.appendChild(doc.createTextNode(f));
 
581
        request.appendChild(feature);
 
582
    }
 
583
    negotiationElement.appendChild(request);
 
584
    sxe.appendChild(negotiationElement);
 
585
 
 
586
    // Create the negotiation object
 
587
    SxeNegotiation* negotiation = createNegotiation(SxeNegotiation::Participant, SxeNegotiation::InvitationSent, session, target, ownJid, groupChat);
 
588
    negotiation->initialDoc = initialDoc;
 
589
    negotiation->features = features;
 
590
 
 
591
    sendSxe(sxe, target, groupChat);
 
592
 
 
593
    // Reset the timeout for negotiations
 
594
    negotiationTimer_.start();
 
595
 
 
596
    return;
 
597
}
 
598
 
 
599
void SxeManager::negotiationTimeout() {
 
600
    foreach(SxeNegotiation* negotiation, negotiations_.values()){
 
601
        if(negotiation->role == SxeNegotiation::Participant && negotiation->state < SxeNegotiation::HistoryOffered && negotiation->state != SxeNegotiation::DocumentBegan) {     
 
602
            if(negotiation->session)
 
603
                negotiation->session->endSession();
 
604
            abortNegotiation(negotiation->sessionId, negotiation->peer, negotiation->groupChat);
 
605
        }
 
606
        delete negotiation;
 
607
    }
 
608
    
 
609
    negotiations_.clear();
 
610
}
 
611
 
 
612
// #include <QTextStream>
 
613
void SxeManager::sendSxe(QDomElement sxe, const Jid & receiver, bool groupChat) {
 
614
 
 
615
    // Add a unique id to each sent sxe element
 
616
    sxeId_--;
 
617
    sxe.setAttribute("id", sxeId_);
 
618
 
 
619
    SxeSession* session = qobject_cast<SxeSession*>(sender());
 
620
    if(session)
 
621
        session->setLastSxe(session->ownJid().full(), QString("%1").arg(sxeId_));
 
622
 
 
623
    Message m(receiver);
 
624
    m.setSxe(sxe);
 
625
    if(groupChat && receiver.resource().isEmpty())
 
626
        m.setType("groupchat");
 
627
 
 
628
    if(client_->isActive()) {
 
629
        // send queued messages first
 
630
        while(!queuedMessages_.isEmpty())
 
631
            client_->sendMessage(queuedMessages_.takeFirst());
 
632
        
 
633
        client_->sendMessage(m);
 
634
    } else {
 
635
        queuedMessages_.append(m);
 
636
    }
 
637
}
 
638
 
 
639
QList< QPointer<SxeSession> > SxeManager::findSession(const Jid &jid) {
 
640
    // find if a session for the jid already exists
 
641
    QList< QPointer<SxeSession> > matching;
 
642
    foreach(QPointer<SxeSession> w, sessions_) {
 
643
        // does the jid match?
 
644
        if(w->target().compare(jid)) {
 
645
            matching.append(w);
 
646
        }
 
647
    }
 
648
    return matching;
 
649
}
 
650
 
 
651
QPointer<SxeSession> SxeManager::findSession(const QString &session) {
 
652
    // find if a session for the session already exists
 
653
    foreach(SxeSession* w, sessions_) {
 
654
        // does the session match?
 
655
        if(w->session() == session)
 
656
            return w;
 
657
    }
 
658
    return 0;
 
659
}
 
660
 
 
661
QPointer<SxeSession> SxeManager::createSxeSession(const Jid &target, QString session, const Jid &ownJid, bool groupChat, const QList<QString> &features) {
 
662
    if(session.isEmpty() || !target.isValid())
 
663
        return 0;
 
664
    if(!ownJids_.contains(ownJid.full()))
 
665
        ownJids_.append(ownJid.full());
 
666
    // FIXME: detect serverside support
 
667
    bool serverSupport = false;
 
668
    // create the SxeSession
 
669
    QPointer<SxeSession> w = new SxeSession(target, session, ownJid, groupChat, serverSupport, features);
 
670
    // connect the signals
 
671
    connect(w, SIGNAL(newSxeElement(QDomElement, Jid, bool)), SLOT(sendSxe(const QDomElement &, const Jid &, bool)));
 
672
    connect(w, SIGNAL(sessionEnded(SxeSession*)), SLOT(removeSession(SxeSession*)));
 
673
    removeDetectedSession(w);
 
674
    return w;
 
675
    // Note: the session should be added to sessions_ once negotiation is finished
 
676
}
 
677
 
 
678
void SxeManager::abortNegotiation(QString session, const Jid &peer, bool groupChat) {
 
679
    QDomDocument doc = QDomDocument();
 
680
    QDomElement sxe = doc.createElementNS(SXDENS, "sxe");
 
681
    sxe.setAttribute("session", session);
 
682
    QDomElement negotiationElement = doc.createElementNS(SXDENS, "negotiation");
 
683
    negotiationElement.appendChild(doc.createElementNS(SXDENS, "abort-negotiation"));
 
684
    sxe.appendChild(negotiationElement);
 
685
    sendSxe(sxe, peer, groupChat);
 
686
}
 
687
 
 
688
void SxeManager::abortNegotiation(SxeNegotiation* negotiation) {
 
689
    abortNegotiation(negotiation->sessionId, negotiation->peer, negotiation->groupChat);
 
690
 
 
691
    removeNegotiation(negotiation);
 
692
}
 
693
 
 
694
void SxeManager::removeDetectedSession(SxeSession* session) {
 
695
    for(int i = 0; i < DetectedSessions_.size(); i++) {
 
696
        DetectedSession detected = DetectedSessions_.at(i);
 
697
        // Remove the specified session from the list
 
698
        if(detected.session == session->session() && detected.jid.compare(session->target(), true))
 
699
            DetectedSessions_.removeAt(i);
 
700
        else if(detected.time.secsTo(QTime::currentTime()) > 1800)
 
701
            // Remove detected session that are old
 
702
            DetectedSessions_.removeAt(i);
 
703
    }
 
704
}
 
705
 
 
706
void SxeManager::groupChatLeft(const Jid &jid) {
 
707
    for(int i = 0; i < ownJids_.size(); i++) {
 
708
        if(jid.bare() == ownJids_.at(i).left(ownJids_.at(i).indexOf("/")))
 
709
            ownJids_.removeAt(i);
 
710
    }
 
711
    QList< QPointer<SxeSession> > matching = findSession(jid);
 
712
    foreach(QPointer<SxeSession> w, matching)
 
713
        w->endSession();
 
714
}
 
715
 
 
716
void SxeManager::groupChatJoined(const Jid &, const Jid &ownJid) {
 
717
    if(!ownJids_.contains(ownJid.full()))
 
718
        ownJids_.append(ownJid.full());
 
719
}