summaryrefslogtreecommitdiff
path: root/qt4/TelepathyQt4/pending-channel.cpp
blob: c968ab7ebce84abc56c7b7bdd07e26240436276c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
/**
 * This file is part of TelepathyQt4
 *
 * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/>
 * @copyright Copyright (C) 2008-2010 Nokia Corporation
 * @license LGPL 2.1
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <TelepathyQt4/PendingChannel>

#include "TelepathyQt4/_gen/pending-channel.moc.hpp"

#include "TelepathyQt4/debug-internal.h"
#include "TelepathyQt4/fake-handler-manager-internal.h"
#include "TelepathyQt4/request-temporary-handler-internal.h"

#include <TelepathyQt4/Channel>
#include <TelepathyQt4/ChannelFactory>
#include <TelepathyQt4/ClientRegistrar>
#include <TelepathyQt4/Connection>
#include <TelepathyQt4/ConnectionLowlevel>
#include <TelepathyQt4/Constants>
#include <TelepathyQt4/HandledChannelNotifier>
#include <TelepathyQt4/PendingChannelRequest>
#include <TelepathyQt4/PendingReady>

namespace Tp
{

struct TELEPATHY_QT4_NO_EXPORT PendingChannel::Private
{
    class FakeAccountFactory;

    ConnectionPtr connection;
    bool create;
    bool yours;
    QString channelType;
    uint handleType;
    uint handle;
    QVariantMap immutableProperties;
    ChannelPtr channel;

    ClientRegistrarPtr cr;
    SharedPtr<RequestTemporaryHandler> handler;
    HandledChannelNotifier *notifier;
    static uint numHandlers;
};

uint PendingChannel::Private::numHandlers = 0;

class TELEPATHY_QT4_NO_EXPORT PendingChannel::Private::FakeAccountFactory : public AccountFactory
{
public:
    static AccountFactoryPtr create(const AccountPtr &account)
    {
        return AccountFactoryPtr(new FakeAccountFactory(account));
    }

    ~FakeAccountFactory() { }

    AccountPtr account() const { return mAccount; }

protected:
    AccountPtr construct(const QString &busName, const QString &objectPath,
            const ConnectionFactoryConstPtr &connFactory,
            const ChannelFactoryConstPtr &chanFactory,
            const ContactFactoryConstPtr &contactFactory) const
    {
        if (mAccount->objectPath() != objectPath) {
            warning() << "Account received by the fake factory is different from original account";
        }
        return mAccount;
    }

private:
    FakeAccountFactory(const AccountPtr &account)
        : AccountFactory(account->dbusConnection(), Features()),
          mAccount(account)
    {
    }

    AccountPtr mAccount;
};

/**
 * \class PendingChannel
 * \ingroup clientchannel
 * \headerfile TelepathyQt4/pending-channel.h <TelepathyQt4/PendingChannel>
 *
 * \brief The PendingChannel class represents the parameters of and the reply to
 * an asynchronous channel request.
 *
 * Instances of this class cannot be constructed directly; the only way to get
 * one is trough Connection or Account.
 *
 * See \ref async_model
 */

/**
 * Construct a new PendingChannel object that will fail immediately.
 *
 * \param connection Connection to use.
 * \param errorName The error name.
 * \param errorMessage The error message.
 */
PendingChannel::PendingChannel(const ConnectionPtr &connection, const QString &errorName,
        const QString &errorMessage)
    : PendingOperation(connection),
      mPriv(new Private)
{
    mPriv->connection = connection;
    mPriv->yours = false;
    mPriv->handleType = 0;
    mPriv->handle = 0;
    mPriv->notifier = 0;
    mPriv->create = false;

    setFinishedWithError(errorName, errorMessage);
}

/**
 * Construct a new PendingChannel object.
 *
 * \param connection Connection to use.
 * \param request A dictionary containing the desirable properties.
 * \param create Whether createChannel or ensureChannel should be called.
 */
PendingChannel::PendingChannel(const ConnectionPtr &connection,
        const QVariantMap &request, bool create, int timeout)
    : PendingOperation(connection),
      mPriv(new Private)
{
    mPriv->connection = connection;
    mPriv->yours = create;
    mPriv->channelType = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString();
    mPriv->handleType = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt();
    mPriv->handle = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle")).toUInt();
    mPriv->notifier = 0;
    mPriv->create = create;

    Client::ConnectionInterfaceRequestsInterface *requestsInterface =
        connection->interface<Client::ConnectionInterfaceRequestsInterface>();
    if (create) {
        QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
                requestsInterface->CreateChannel(request, timeout), this);
        connect(watcher,
                SIGNAL(finished(QDBusPendingCallWatcher*)),
                SLOT(onConnectionCreateChannelFinished(QDBusPendingCallWatcher*)));
    }
    else {
        QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(
                requestsInterface->EnsureChannel(request, timeout), this);
        connect(watcher,
                SIGNAL(finished(QDBusPendingCallWatcher*)),
                SLOT(onConnectionEnsureChannelFinished(QDBusPendingCallWatcher*)));
    }
}

PendingChannel::PendingChannel(const AccountPtr &account,
        const QVariantMap &request, const QDateTime &userActionTime,
        bool create)
    : PendingOperation(account),
      mPriv(new Private)
{
    mPriv->yours = true;
    mPriv->channelType = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString();
    mPriv->handleType = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt();
    mPriv->handle = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle")).toUInt();

    mPriv->cr = ClientRegistrar::create(
            Private::FakeAccountFactory::create(account),
            account->connectionFactory(),
            account->channelFactory(),
            account->contactFactory());
    mPriv->handler = RequestTemporaryHandler::create(account);
    mPriv->notifier = 0;
    mPriv->create = create;

    QString handlerName = QString(QLatin1String("TpQt4RaH_%1_%2"))
        .arg(account->dbusConnection().baseService()
            .replace(QLatin1String(":"), QLatin1String("_"))
            .replace(QLatin1String("."), QLatin1String("_")))
        .arg(Private::numHandlers++);
    if (!mPriv->cr->registerClient(mPriv->handler, handlerName, false)) {
        warning() << "Unable to register handler" << handlerName;
        setFinishedWithError(TP_QT4_ERROR_NOT_AVAILABLE,
                QLatin1String("Unable to register handler"));
        return;
    }

    connect(mPriv->handler.data(),
            SIGNAL(error(QString,QString)),
            SLOT(onHandlerError(QString,QString)));
    connect(mPriv->handler.data(),
            SIGNAL(channelReceived(Tp::ChannelPtr,QDateTime,Tp::ChannelRequestHints)),
            SLOT(onHandlerChannelReceived(Tp::ChannelPtr)));

    handlerName = QString(QLatin1String("org.freedesktop.Telepathy.Client.%1")).arg(handlerName);

    debug() << "Requesting channel through account using handler" << handlerName;
    PendingChannelRequest *pcr;
    if (create) {
        pcr = account->createChannel(request, userActionTime, handlerName, ChannelRequestHints());
    } else {
        pcr = account->ensureChannel(request, userActionTime, handlerName, ChannelRequestHints());
    }
    connect(pcr,
            SIGNAL(finished(Tp::PendingOperation*)),
            SLOT(onAccountCreateChannelFinished(Tp::PendingOperation*)));
}

/**
 * Construct a new PendingChannel object that will fail immediately.
 *
 * \param errorName The name of a D-Bus error.
 * \param errorMessage The error message.
 */
PendingChannel::PendingChannel(const QString &errorName, const QString &errorMessage)
    : PendingOperation(ConnectionPtr()), mPriv(new Private)
{
    setFinishedWithError(errorName, errorMessage);
}

/**
 * Class destructor.
 */
PendingChannel::~PendingChannel()
{
    delete mPriv;
}

/**
 * Return the connection through which the channel request was made.
 *
 * Note that if this channel request was created through Account, a null ConnectionPtr will be
 * returned.
 *
 * \return A pointer to the Connection object.
 */
ConnectionPtr PendingChannel::connection() const
{
    return mPriv->connection;
}

/**
 * Return whether this channel belongs to this process.
 *
 * If \c false, the caller must assume that some other process is
 * handling this channel; if \c true, the caller should handle it
 * themselves or delegate it to another client.
 *
 * \return \c true if it belongs, \c false otherwise.
 */
bool PendingChannel::yours() const
{
    if (!isFinished()) {
        warning() << "PendingChannel::yours called before finished, returning undefined value";
    }
    else if (!isValid()) {
        warning() << "PendingChannel::yours called when not valid, returning undefined value";
    }

    return mPriv->yours;
}

/**
 * Return the channel type specified in the channel request.
 *
 * \return The D-Bus interface name for the type of the channel.
 */
const QString &PendingChannel::channelType() const
{
    return mPriv->channelType;
}

/**
 * If the channel request has finished, return the handle type of the resulting
 * channel. Otherwise, return the handle type that was requested.
 *
 * (One example of a request producing a different target handle type is that
 * on protocols like MSN, one-to-one conversations don't really exist, and if
 * you request a text channel with handle type HandleTypeContact, what you
 * will actually get is a text channel with handle type HandleTypeNone, with
 * the requested contact as a member.)
 *
 * \return The target handle type as #HandleType.
 * \sa targetHandle()
 */
uint PendingChannel::targetHandleType() const
{
    return mPriv->handleType;
}

/**
 * If the channel request has finished, return the target handle of the
 * resulting channel. Otherwise, return the target handle that was requested
 * (which might be different in some situations - see targetHandleType()).
 *
 * \return An integer representing the target handle, which is of the type
 *         targetHandleType() indicates.
 * \sa targetHandleType()
 */
uint PendingChannel::targetHandle() const
{
    return mPriv->handle;
}

/**
 * If this channel request has finished, return the immutable properties of
 * the resulting channel. Otherwise, return an empty map.
 *
 * The keys and values in this map are defined by the \telepathy_spec,
 * or by third-party extensions to that specification.
 * These are the properties that cannot change over the lifetime of the
 * channel; they're announced in the result of the request, for efficiency.
 * This map should be passed to the constructor of Channel or its subclasses
 * (such as TextChannel).
 *
 * These properties can also be used to process channels in a way that does
 * not require the creation of a Channel object - for instance, a
 * ChannelDispatcher implementation should be able to classify and process
 * channels based on their immutable properties, without needing to create
 * Channel objects.
 *
 * \return The immutable properties as QVariantMap.
 */
QVariantMap PendingChannel::immutableProperties() const
{
    QVariantMap props = mPriv->immutableProperties;

    // This is a reasonable guess - if it's Yours it's guaranteedly Requested by us, and if it's not
    // it could be either Requested by somebody else but also an incoming channel just as well.
    if (!props.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested"))) {
        debug() << "CM didn't provide Requested in channel immutable props, guessing"
            << mPriv->yours;
        props[QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested")] =
            mPriv->yours;
    }

    // Also, the spec says that if the channel was Requested by the local user, InitiatorHandle must
    // be the Connection's self handle
    if (!props.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorHandle"))) {
        if (qdbus_cast<bool>(props.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested")))) {
            if (connection() && connection()->isReady(Connection::FeatureCore)) {
                debug() << "CM didn't provide InitiatorHandle in channel immutable props, but we "
                    "know it's the conn's self handle (and have it)";
                props[QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorHandle")] =
                    connection()->selfHandle();
            }
        }
    }

    return props;
}

/**
 * Return the channel resulting from the channel request.
 *
 * \return A pointer to the Channel object.
 */
ChannelPtr PendingChannel::channel() const
{
    if (!isFinished()) {
        warning() << "PendingChannel::channel called before finished, returning 0";
        return ChannelPtr();
    } else if (!isValid()) {
        warning() << "PendingChannel::channel called when not valid, returning 0";
        return ChannelPtr();
    }

    return mPriv->channel;
}

/**
 * If this channel request has finished and was created through Account,
 * return a HandledChannelNotifier object that will keep track of channel() being re-requested.
 *
 * \return A HandledChannelNotifier instance, or 0 if an error occurred.
 */
HandledChannelNotifier *PendingChannel::handledChannelNotifier() const
{
    if (!isFinished()) {
        warning() << "PendingChannel::handledChannelNotifier called before finished, returning 0";
        return 0;
    } else if (!isValid()) {
        warning() << "PendingChannel::handledChannelNotifier called when not valid, returning 0";
        return 0;
    }

    if (mPriv->cr && !mPriv->notifier) {
        mPriv->notifier = new HandledChannelNotifier(mPriv->cr, mPriv->handler);
    }
    return mPriv->notifier;
}

void PendingChannel::onConnectionCreateChannelFinished(QDBusPendingCallWatcher *watcher)
{
    QDBusPendingReply<QDBusObjectPath, QVariantMap> reply = *watcher;

    if (!reply.isError()) {
        QString objectPath = reply.argumentAt<0>().path();
        QVariantMap map = reply.argumentAt<1>();

        debug() << "Got reply to Connection.CreateChannel - object path:" << objectPath;

        PendingReady *channelReady =
            connection()->channelFactory()->proxy(connection(), objectPath, map);
        mPriv->channel = ChannelPtr::qObjectCast(channelReady->proxy());

        mPriv->immutableProperties = map;
        mPriv->channelType = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString();
        mPriv->handleType = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt();
        mPriv->handle = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle")).toUInt();

        connect(channelReady,
                SIGNAL(finished(Tp::PendingOperation*)),
                SLOT(onChannelReady(Tp::PendingOperation*)));
    } else {
        debug().nospace() << "CreateChannel failed:" <<
            reply.error().name() << ": " << reply.error().message();
        setFinishedWithError(reply.error());
    }

    watcher->deleteLater();
}

void PendingChannel::onConnectionEnsureChannelFinished(QDBusPendingCallWatcher *watcher)
{
    QDBusPendingReply<bool, QDBusObjectPath, QVariantMap> reply = *watcher;

    if (!reply.isError()) {
        mPriv->yours = reply.argumentAt<0>();
        QString objectPath = reply.argumentAt<1>().path();
        QVariantMap map = reply.argumentAt<2>();

        debug() << "Got reply to Connection.EnsureChannel - object path:" << objectPath;

        PendingReady *channelReady =
            connection()->channelFactory()->proxy(connection(), objectPath, map);
        mPriv->channel = ChannelPtr::qObjectCast(channelReady->proxy());

        mPriv->immutableProperties = map;
        mPriv->channelType = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString();
        mPriv->handleType = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt();
        mPriv->handle = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle")).toUInt();

        connect(channelReady,
                SIGNAL(finished(Tp::PendingOperation*)),
                SLOT(onChannelReady(Tp::PendingOperation*)));
    } else {
        debug().nospace() << "EnsureChannel failed:" <<
            reply.error().name() << ": " << reply.error().message();
        setFinishedWithError(reply.error());
    }

    watcher->deleteLater();
}

void PendingChannel::onChannelReady(PendingOperation *op)
{
    if (!op->isError()) {
        setFinished();
    } else {
        debug() << "Making the channel ready for" << this << "failed with" << op->errorName()
            << ":" << op->errorMessage();
        setFinishedWithError(op->errorName(), op->errorMessage());
    }
}

void PendingChannel::onHandlerError(const QString &errorName, const QString &errorMessage)
{
    if (isFinished()) {
        return;
    }

    warning() << "Creating/ensuring channel failed with" << errorName
        << ":" << errorMessage;
    setFinishedWithError(errorName, errorMessage);
}

void PendingChannel::onHandlerChannelReceived(const ChannelPtr &channel)
{
    if (isFinished()) {
        warning() << "Handler received the channel but this operation already finished due "
            "to failure in the channel request";
        return;
    }

    mPriv->handleType = channel->targetHandleType();
    mPriv->handle = channel->targetHandle();
    mPriv->immutableProperties = channel->immutableProperties();
    mPriv->channel = channel;

    // register the CR in FakeHandlerManager so that at least one handler per bus stays alive
    // until all channels requested using R&H gets invalidated/destroyed.
    // This is important in case Mission Control happens to restart while
    // the channel is still in use, since it will close each channel it
    // doesn't find a handler for it.
    FakeHandlerManager::instance()->registerClientRegistrar(mPriv->cr);

    setFinished();
}

void PendingChannel::onAccountCreateChannelFinished(PendingOperation *op)
{
    if (isFinished()) {
        if (isError()) {
            warning() << "Creating/ensuring channel finished with a failure after the internal "
                "handler already got a channel, ignoring";
        }
        return;
    }

    if (op->isError()) {
        warning() << "Creating/ensuring channel failed with" << op->errorName()
            << ":" << op->errorMessage();
        setFinishedWithError(op->errorName(), op->errorMessage());
        return;
    }

    if (!mPriv->handler->isDBusHandlerInvoked()) {
        // Our handler hasn't be called but the channel request is complete.
        // That means another handler handled the channels so we don't own it.
        if (mPriv->create) {
            warning() << "Creating/ensuring channel failed with" << TP_QT4_ERROR_SERVICE_CONFUSED
                << ":" << QLatin1String("CD.CreateChannel/WithHints returned successfully and "
                        "the handler didn't receive the channel yet");
            setFinishedWithError(TP_QT4_ERROR_SERVICE_CONFUSED,
                    QLatin1String("CD.CreateChannel/WithHints returned successfully and "
                        "the handler didn't receive the channel yet"));
        } else {
            warning() << "Creating/ensuring channel failed with" << TP_QT4_ERROR_NOT_YOURS
                << ":" << QLatin1String("Another handler is handling this channel");
            setFinishedWithError(TP_QT4_ERROR_NOT_YOURS,
                    QLatin1String("Another handler is handling this channel"));
        }
        return;
    }
}

} // Tp