/** * This file is part of TelepathyQt4 * * @copyright Copyright (C) 2008 Collabora Ltd. * @copyright Copyright (C) 2008 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 #include "TelepathyQt4/channel-internal.h" #include "TelepathyQt4/_gen/cli-channel-body.hpp" #include "TelepathyQt4/_gen/cli-channel.moc.hpp" #include "TelepathyQt4/_gen/channel.moc.hpp" #include "TelepathyQt4/_gen/channel-internal.moc.hpp" #include "TelepathyQt4/debug-internal.h" #include "TelepathyQt4/future-internal.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Tp { using TpFuture::Client::ChannelInterfaceMergeableConferenceInterface; using TpFuture::Client::ChannelInterfaceSplittableInterface; struct TELEPATHY_QT4_NO_EXPORT Channel::Private { Private(Channel *parent, const ConnectionPtr &connection, const QVariantMap &immutableProperties); ~Private(); static void introspectMain(Private *self); void introspectMainProperties(); void introspectMainFallbackChannelType(); void introspectMainFallbackHandle(); void introspectMainFallbackInterfaces(); void introspectGroup(); void introspectGroupFallbackFlags(); void introspectGroupFallbackMembers(); void introspectGroupFallbackLocalPendingWithInfo(); void introspectGroupFallbackSelfHandle(); void introspectConference(); static void introspectConferenceInitialInviteeContacts(Private *self); void continueIntrospection(); void extractMainProps(const QVariantMap &props); void extract0176GroupProps(const QVariantMap &props); void nowHaveInterfaces(); void nowHaveInitialMembers(); bool setGroupFlags(uint groupFlags); void buildContacts(); void doMembersChangedDetailed(const UIntList &, const UIntList &, const UIntList &, const UIntList &, const QVariantMap &); void processMembersChanged(); void updateContacts(const QList &contacts = QList()); bool fakeGroupInterfaceIfNeeded(); void setReady(); QString groupMemberChangeDetailsTelepathyError( const GroupMemberChangeDetails &details); inline ChannelInterfaceMergeableConferenceInterface *mergeableConferenceInterface( InterfaceSupportedChecking check = CheckInterfaceSupported) const { return parent->optionalInterface(check); } inline ChannelInterfaceSplittableInterface *splittableInterface( InterfaceSupportedChecking check = CheckInterfaceSupported) const { return parent->optionalInterface(check); } void processConferenceChannelRemoved(); struct GroupMembersChangedInfo; struct ConferenceChannelRemovedInfo; // Public object Channel *parent; // Instance of generated interface class Client::ChannelInterface *baseInterface; // Mandatory properties interface proxy Client::DBus::PropertiesInterface *properties; // Owning connection - it can be a SharedPtr as Connection does not cache // channels ConnectionPtr connection; QVariantMap immutableProperties; // Optional interface proxies Client::ChannelInterfaceGroupInterface *group; Client::ChannelInterfaceConferenceInterface *conference; ReadinessHelper *readinessHelper; // Introspection QQueue introspectQueue; // Introspected properties // Main interface QString channelType; uint targetHandleType; uint targetHandle; QString targetId; ContactPtr targetContact; bool requested; uint initiatorHandle; ContactPtr initiatorContact; // Group flags uint groupFlags; bool usingMembersChangedDetailed; // Group member introspection bool groupHaveMembers; bool buildingContacts; // Queue of received MCD signals to process QQueue groupMembersChangedQueue; GroupMembersChangedInfo *currentGroupMembersChangedInfo; // Pending from the MCD signal currently processed, but contacts not yet built QSet pendingGroupMembers; QSet pendingGroupLocalPendingMembers; QSet pendingGroupRemotePendingMembers; UIntList groupMembersToRemove; UIntList groupLocalPendingMembersToRemove; UIntList groupRemotePendingMembersToRemove; // Initial members UIntList groupInitialMembers; LocalPendingInfoList groupInitialLP; UIntList groupInitialRP; // Current members QHash groupContacts; QHash groupLocalPendingContacts; QHash groupRemotePendingContacts; // Stored change info QHash groupLocalPendingContactsChangeInfo; GroupMemberChangeDetails groupSelfContactRemoveInfo; // Group handle owners bool groupAreHandleOwnersAvailable; HandleOwnerMap groupHandleOwners; // Group self identity bool pendingRetrieveGroupSelfContact; bool groupIsSelfHandleTracked; uint groupSelfHandle; ContactPtr groupSelfContact; // Conference bool introspectingConference; QHash conferenceChannels; QHash conferenceInitialChannels; QString conferenceInvitationMessage; QHash conferenceOriginalChannels; UIntList conferenceInitialInviteeHandles; Contacts conferenceInitialInviteeContacts; QQueue conferenceChannelRemovedQueue; bool buildingConferenceChannelRemovedActorContact; static const QString keyActor; }; struct TELEPATHY_QT4_NO_EXPORT Channel::Private::GroupMembersChangedInfo { GroupMembersChangedInfo(const UIntList &added, const UIntList &removed, const UIntList &localPending, const UIntList &remotePending, const QVariantMap &details) : added(added), removed(removed), localPending(localPending), remotePending(remotePending), details(details), // TODO most of these probably should be removed once the rest of the code using them is sanitized actor(qdbus_cast(details.value(keyActor))), reason(qdbus_cast(details.value(keyChangeReason))), message(qdbus_cast(details.value(keyMessage))) { } UIntList added; UIntList removed; UIntList localPending; UIntList remotePending; QVariantMap details; uint actor; uint reason; QString message; static const QString keyChangeReason; static const QString keyMessage; static const QString keyContactIds; }; struct TELEPATHY_QT4_NO_EXPORT Channel::Private::ConferenceChannelRemovedInfo { ConferenceChannelRemovedInfo(const QDBusObjectPath &channelPath, const QVariantMap &details) : channelPath(channelPath), details(details) { } QDBusObjectPath channelPath; QVariantMap details; }; const QString Channel::Private::keyActor(QLatin1String("actor")); const QString Channel::Private::GroupMembersChangedInfo::keyChangeReason( QLatin1String("change-reason")); const QString Channel::Private::GroupMembersChangedInfo::keyMessage(QLatin1String("message")); const QString Channel::Private::GroupMembersChangedInfo::keyContactIds(QLatin1String("contact-ids")); Channel::Private::Private(Channel *parent, const ConnectionPtr &connection, const QVariantMap &immutableProperties) : parent(parent), baseInterface(new Client::ChannelInterface(parent)), properties(parent->interface()), connection(connection), immutableProperties(immutableProperties), group(0), conference(0), readinessHelper(parent->readinessHelper()), targetHandleType(0), targetHandle(0), requested(false), initiatorHandle(0), groupFlags(0), usingMembersChangedDetailed(false), groupHaveMembers(false), buildingContacts(false), currentGroupMembersChangedInfo(0), groupAreHandleOwnersAvailable(false), pendingRetrieveGroupSelfContact(false), groupIsSelfHandleTracked(false), groupSelfHandle(0), introspectingConference(false), buildingConferenceChannelRemovedActorContact(false) { debug() << "Creating new Channel:" << parent->objectPath(); if (connection->isValid()) { debug() << " Connecting to Channel::Closed() signal"; parent->connect(baseInterface, SIGNAL(Closed()), SLOT(onClosed())); debug() << " Connection to owning connection's lifetime signals"; parent->connect(connection.data(), SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), SLOT(onConnectionInvalidated())); } else { warning() << "Connection given as the owner for a Channel was " "invalid! Channel will be stillborn."; parent->invalidate(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), QLatin1String("Connection given as the owner of this channel was invalid")); } ReadinessHelper::Introspectables introspectables; // As Channel does not have predefined statuses let's simulate one (0) ReadinessHelper::Introspectable introspectableCore( QSet() << 0, // makesSenseForStatuses Features(), // dependsOnFeatures QStringList(), // dependsOnInterfaces (ReadinessHelper::IntrospectFunc) &Private::introspectMain, this); introspectables[FeatureCore] = introspectableCore; // As Channel does not have predefined statuses let's simulate one (0) ReadinessHelper::Introspectable introspectableConferenceInitialInviteeContacts( QSet() << 0, // makesSenseForStatuses Features() << FeatureCore, // dependsOnFeatures QStringList() << TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE, // dependsOnInterfaces (ReadinessHelper::IntrospectFunc) &Private::introspectConferenceInitialInviteeContacts, this); introspectables[FeatureConferenceInitialInviteeContacts] = introspectableConferenceInitialInviteeContacts; readinessHelper->addIntrospectables(introspectables); } Channel::Private::~Private() { delete currentGroupMembersChangedInfo; foreach (GroupMembersChangedInfo *info, groupMembersChangedQueue) { delete info; } foreach (ConferenceChannelRemovedInfo *info, conferenceChannelRemovedQueue) { delete info; } } void Channel::Private::introspectMain(Channel::Private *self) { // Make sure connection object is ready, as we need to use some methods that // are only available after connection object gets ready. debug() << "Calling Connection::becomeReady()"; self->parent->connect(self->connection->becomeReady(), SIGNAL(finished(Tp::PendingOperation*)), SLOT(onConnectionReady(Tp::PendingOperation*))); } void Channel::Private::introspectMainProperties() { QVariantMap props; QString key; bool needIntrospectMainProps = false; const unsigned numNames = 8; const static QString names[numNames] = { QLatin1String("ChannelType"), QLatin1String("Interfaces"), QLatin1String("TargetHandleType"), QLatin1String("TargetHandle"), QLatin1String("TargetID"), QLatin1String("Requested"), QLatin1String("InitiatorHandle"), QLatin1String("InitiatorID") }; const static QString qualifiedNames[numNames] = { QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Interfaces"), QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"), QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"), QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested"), QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorHandle"), QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorID") }; for (unsigned i = 0; i < numNames; ++i) { const QString &qualified = qualifiedNames[i]; if (!immutableProperties.contains(qualified)) { needIntrospectMainProps = true; break; } props.insert(names[i], immutableProperties.value(qualified)); } // Save Requested and InitiatorHandle here, so even if the GetAll return doesn't have them but // the given immutable props do (eg. due to the PendingChannel fallback guesses) we use them requested = qdbus_cast(props[QLatin1String("Requested")]); initiatorHandle = qdbus_cast(props[QLatin1String("InitiatorHandle")]); if (props.contains(QLatin1String("InitiatorID"))) { QString initiatorId = qdbus_cast(props[QLatin1String("InitiatorID")]); connection->lowlevel()->injectContactId(initiatorHandle, initiatorId); } if (needIntrospectMainProps) { debug() << "Calling Properties::GetAll(Channel)"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CHANNEL)), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotMainProperties(QDBusPendingCallWatcher*))); } else { extractMainProps(props); continueIntrospection(); } } void Channel::Private::introspectMainFallbackChannelType() { debug() << "Calling Channel::GetChannelType()"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(baseInterface->GetChannelType(), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotChannelType(QDBusPendingCallWatcher*))); } void Channel::Private::introspectMainFallbackHandle() { debug() << "Calling Channel::GetHandle()"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(baseInterface->GetHandle(), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotHandle(QDBusPendingCallWatcher*))); } void Channel::Private::introspectMainFallbackInterfaces() { debug() << "Calling Channel::GetInterfaces()"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(baseInterface->GetInterfaces(), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotInterfaces(QDBusPendingCallWatcher*))); } void Channel::Private::introspectGroup() { Q_ASSERT(properties != 0); if (!group) { group = parent->interface(); Q_ASSERT(group != 0); } debug() << "Introspecting Channel.Interface.Group for" << parent->objectPath(); parent->connect(group, SIGNAL(GroupFlagsChanged(uint,uint)), SLOT(onGroupFlagsChanged(uint,uint))); parent->connect(group, SIGNAL(MembersChanged(QString,Tp::UIntList, Tp::UIntList,Tp::UIntList, Tp::UIntList,uint,uint)), SLOT(onMembersChanged(QString,Tp::UIntList, Tp::UIntList,Tp::UIntList, Tp::UIntList,uint,uint))); parent->connect(group, SIGNAL(MembersChangedDetailed(Tp::UIntList, Tp::UIntList,Tp::UIntList, Tp::UIntList,QVariantMap)), SLOT(onMembersChangedDetailed(Tp::UIntList, Tp::UIntList,Tp::UIntList, Tp::UIntList,QVariantMap))); parent->connect(group, SIGNAL(HandleOwnersChanged(Tp::HandleOwnerMap, Tp::UIntList)), SLOT(onHandleOwnersChanged(Tp::HandleOwnerMap, Tp::UIntList))); parent->connect(group, SIGNAL(SelfHandleChanged(uint)), SLOT(onSelfHandleChanged(uint))); debug() << "Calling Properties::GetAll(Channel.Interface.Group)"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP)), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotGroupProperties(QDBusPendingCallWatcher*))); } void Channel::Private::introspectGroupFallbackFlags() { Q_ASSERT(group != 0); debug() << "Calling Channel.Interface.Group::GetGroupFlags()"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(group->GetGroupFlags(), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotGroupFlags(QDBusPendingCallWatcher*))); } void Channel::Private::introspectGroupFallbackMembers() { Q_ASSERT(group != 0); debug() << "Calling Channel.Interface.Group::GetAllMembers()"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(group->GetAllMembers(), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotAllMembers(QDBusPendingCallWatcher*))); } void Channel::Private::introspectGroupFallbackLocalPendingWithInfo() { Q_ASSERT(group != 0); debug() << "Calling Channel.Interface.Group::GetLocalPendingMembersWithInfo()"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(group->GetLocalPendingMembersWithInfo(), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotLocalPendingMembersWithInfo(QDBusPendingCallWatcher*))); } void Channel::Private::introspectGroupFallbackSelfHandle() { Q_ASSERT(group != 0); debug() << "Calling Channel.Interface.Group::GetSelfHandle()"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(group->GetSelfHandle(), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotSelfHandle(QDBusPendingCallWatcher*))); } void Channel::Private::introspectConference() { Q_ASSERT(properties != 0); Q_ASSERT(conference == 0); debug() << "Introspecting Conference interface"; conference = parent->interface(); Q_ASSERT(conference != 0); introspectingConference = true; debug() << "Connecting to Channel.Interface.Conference.ChannelMerged/Removed"; parent->connect(conference, SIGNAL(ChannelMerged(QDBusObjectPath,uint,QVariantMap)), SLOT(onConferenceChannelMerged(QDBusObjectPath,uint,QVariantMap))); parent->connect(conference, SIGNAL(ChannelRemoved(QDBusObjectPath,QVariantMap)), SLOT(onConferenceChannelRemoved(QDBusObjectPath,QVariantMap))); debug() << "Calling Properties::GetAll(Channel.Interface.Conference)"; QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_CONFERENCE)), parent); parent->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), SLOT(gotConferenceProperties(QDBusPendingCallWatcher*))); } void Channel::Private::introspectConferenceInitialInviteeContacts(Private *self) { if (!self->conferenceInitialInviteeHandles.isEmpty()) { ContactManagerPtr manager = self->connection->contactManager(); PendingContacts *pendingContacts = manager->contactsForHandles( self->conferenceInitialInviteeHandles); self->parent->connect(pendingContacts, SIGNAL(finished(Tp::PendingOperation *)), SLOT(gotConferenceInitialInviteeContacts(Tp::PendingOperation *))); } else { self->readinessHelper->setIntrospectCompleted( FeatureConferenceInitialInviteeContacts, true); } } void Channel::Private::continueIntrospection() { if (introspectQueue.isEmpty()) { // this should always be true, but let's make sure if (!parent->isReady(Channel::FeatureCore)) { if (groupMembersChangedQueue.isEmpty() && !buildingContacts && !introspectingConference) { debug() << "Both the IS and the MCD queue empty for the first time. Ready."; setReady(); } else { debug() << "Introspection done before contacts done - contacts sets ready"; } } } else { (this->*(introspectQueue.dequeue()))(); } } void Channel::Private::extractMainProps(const QVariantMap &props) { const static QString keyChannelType(QLatin1String("ChannelType")); const static QString keyInterfaces(QLatin1String("Interfaces")); const static QString keyTargetHandle(QLatin1String("TargetHandle")); const static QString keyTargetHandleType(QLatin1String("TargetHandleType")); bool haveProps = props.size() >= 4 && props.contains(keyChannelType) && !qdbus_cast(props[keyChannelType]).isEmpty() && props.contains(keyInterfaces) && props.contains(keyTargetHandle) && props.contains(keyTargetHandleType); if (!haveProps) { warning() << "Channel properties specified in 0.17.7 not found"; introspectQueue.enqueue(&Private::introspectMainFallbackChannelType); introspectQueue.enqueue(&Private::introspectMainFallbackHandle); introspectQueue.enqueue(&Private::introspectMainFallbackInterfaces); } else { parent->setInterfaces(qdbus_cast(props[keyInterfaces])); readinessHelper->setInterfaces(parent->interfaces()); channelType = qdbus_cast(props[keyChannelType]); targetHandle = qdbus_cast(props[keyTargetHandle]); targetHandleType = qdbus_cast(props[keyTargetHandleType]); const static QString keyTargetId(QLatin1String("TargetID")); const static QString keyRequested(QLatin1String("Requested")); const static QString keyInitiatorHandle(QLatin1String("InitiatorHandle")); const static QString keyInitiatorId(QLatin1String("InitiatorID")); if (props.contains(keyTargetId)) { targetId = qdbus_cast(props[keyTargetId]); if (targetHandleType == HandleTypeContact) { connection->lowlevel()->injectContactId(targetHandle, targetId); } } if (props.contains(keyRequested)) { requested = qdbus_cast(props[keyRequested]); } if (props.contains(keyInitiatorHandle)) { initiatorHandle = qdbus_cast(props[keyInitiatorHandle]); } if (props.contains(keyInitiatorId)) { QString initiatorId = qdbus_cast(props[keyInitiatorId]); connection->lowlevel()->injectContactId(initiatorHandle, initiatorId); } if (!fakeGroupInterfaceIfNeeded() && !parent->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP)) && initiatorHandle) { // No group interface, so nobody will build the poor fellow for us. Will do it ourselves // out of pity for him. // TODO: needs testing. I would imagine some of the elaborate updateContacts logic // tripping over with just this. buildContacts(); } nowHaveInterfaces(); } debug() << "Have initiator handle:" << (initiatorHandle ? "yes" : "no"); } void Channel::Private::extract0176GroupProps(const QVariantMap &props) { const static QString keyGroupFlags(QLatin1String("GroupFlags")); const static QString keyHandleOwners(QLatin1String("HandleOwners")); const static QString keyLPMembers(QLatin1String("LocalPendingMembers")); const static QString keyMembers(QLatin1String("Members")); const static QString keyRPMembers(QLatin1String("RemotePendingMembers")); const static QString keySelfHandle(QLatin1String("SelfHandle")); bool haveProps = props.size() >= 6 && (props.contains(keyGroupFlags) && (qdbus_cast(props[keyGroupFlags]) & ChannelGroupFlagProperties)) && props.contains(keyHandleOwners) && props.contains(keyLPMembers) && props.contains(keyMembers) && props.contains(keyRPMembers) && props.contains(keySelfHandle); if (!haveProps) { warning() << " Properties specified in 0.17.6 not found"; warning() << " Handle owners and self handle tracking disabled"; introspectQueue.enqueue(&Private::introspectGroupFallbackFlags); introspectQueue.enqueue(&Private::introspectGroupFallbackMembers); introspectQueue.enqueue(&Private::introspectGroupFallbackLocalPendingWithInfo); introspectQueue.enqueue(&Private::introspectGroupFallbackSelfHandle); } else { debug() << " Found properties specified in 0.17.6"; groupAreHandleOwnersAvailable = true; groupIsSelfHandleTracked = true; setGroupFlags(qdbus_cast(props[keyGroupFlags])); groupHandleOwners = qdbus_cast(props[keyHandleOwners]); groupInitialMembers = qdbus_cast(props[keyMembers]); groupInitialLP = qdbus_cast(props[keyLPMembers]); groupInitialRP = qdbus_cast(props[keyRPMembers]); uint propSelfHandle = qdbus_cast(props[keySelfHandle]); // Don't overwrite the self handle we got from the Connection with 0 if (propSelfHandle) { groupSelfHandle = propSelfHandle; } nowHaveInitialMembers(); } } void Channel::Private::nowHaveInterfaces() { debug() << "Channel has" << parent->interfaces().size() << "optional interfaces:" << parent->interfaces(); QStringList interfaces = parent->interfaces(); if (interfaces.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) { introspectQueue.enqueue(&Private::introspectGroup); } if (interfaces.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_CONFERENCE))) { introspectQueue.enqueue(&Private::introspectConference); } } void Channel::Private::nowHaveInitialMembers() { // Must be called with no contacts anywhere in the first place Q_ASSERT(!parent->isReady(Channel::FeatureCore)); Q_ASSERT(!buildingContacts); Q_ASSERT(pendingGroupMembers.isEmpty()); Q_ASSERT(pendingGroupLocalPendingMembers.isEmpty()); Q_ASSERT(pendingGroupRemotePendingMembers.isEmpty()); Q_ASSERT(groupContacts.isEmpty()); Q_ASSERT(groupLocalPendingContacts.isEmpty()); Q_ASSERT(groupRemotePendingContacts.isEmpty()); // Set groupHaveMembers so we start queueing fresh MCD signals Q_ASSERT(!groupHaveMembers); groupHaveMembers = true; // Synthesize MCD for current + RP groupMembersChangedQueue.enqueue(new GroupMembersChangedInfo( groupInitialMembers, // Members UIntList(), // Removed - obviously, none UIntList(), // LP - will be handled separately, see below groupInitialRP, // Remote pending QVariantMap())); // No details for members + RP // Synthesize one MCD for each initial LP member - they might have different details foreach (const LocalPendingInfo &info, groupInitialLP) { QVariantMap details; if (info.actor != 0) { details.insert(QLatin1String("actor"), info.actor); } if (info.reason != ChannelGroupChangeReasonNone) { details.insert(QLatin1String("change-reason"), info.reason); } if (!info.message.isEmpty()) { details.insert(QLatin1String("message"), info.message); } groupMembersChangedQueue.enqueue(new GroupMembersChangedInfo(UIntList(), UIntList(), UIntList() << info.toBeAdded, UIntList(), details)); } // At least our added MCD event to process processMembersChanged(); } bool Channel::Private::setGroupFlags(uint newGroupFlags) { if (groupFlags == newGroupFlags) { return false; } groupFlags = newGroupFlags; // this shouldn't happen but let's make sure if (!parent->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) { return false; } if ((groupFlags & ChannelGroupFlagMembersChangedDetailed) && !usingMembersChangedDetailed) { usingMembersChangedDetailed = true; debug() << "Starting to exclusively listen to MembersChangedDetailed for" << parent->objectPath(); parent->disconnect(group, SIGNAL(MembersChanged(QString,Tp::UIntList, Tp::UIntList,Tp::UIntList, Tp::UIntList,uint,uint)), parent, SLOT(onMembersChanged(QString,Tp::UIntList, Tp::UIntList,Tp::UIntList, Tp::UIntList,uint,uint))); } else if (!(groupFlags & ChannelGroupFlagMembersChangedDetailed) && usingMembersChangedDetailed) { warning() << " Channel service did spec-incompliant removal of MCD from GroupFlags"; usingMembersChangedDetailed = false; parent->connect(group, SIGNAL(MembersChanged(QString,Tp::UIntList, Tp::UIntList,Tp::UIntList, Tp::UIntList,uint,uint)), parent, SLOT(onMembersChanged(QString,Tp::UIntList, Tp::UIntList,Tp::UIntList, Tp::UIntList,uint,uint))); } return true; } void Channel::Private::buildContacts() { buildingContacts = true; ContactManagerPtr manager = connection->contactManager(); UIntList toBuild = QSet(pendingGroupMembers + pendingGroupLocalPendingMembers + pendingGroupRemotePendingMembers).toList(); if (currentGroupMembersChangedInfo && currentGroupMembersChangedInfo->actor != 0) { toBuild.append(currentGroupMembersChangedInfo->actor); } if (!initiatorContact && initiatorHandle) { // No initiator contact, but Yes initiator handle - might do something about it with just // that information toBuild.append(initiatorHandle); } if (!targetContact && targetHandleType == HandleTypeContact && targetHandle != 0) { toBuild.append(targetHandle); } // always try to retrieve selfContact and check if it changed on // updateContacts or on gotContacts, in case we were not able to retrieve it if (groupSelfHandle) { toBuild.append(groupSelfHandle); } // group self handle changed to 0 <- strange but it may happen, and contacts // were being built at the time, so check now if (toBuild.isEmpty()) { if (!groupSelfHandle && groupSelfContact) { groupSelfContact.reset(); if (parent->isReady(Channel::FeatureCore)) { emit parent->groupSelfContactChanged(); } } buildingContacts = false; return; } PendingContacts *pendingContacts = manager->contactsForHandles( toBuild); parent->connect(pendingContacts, SIGNAL(finished(Tp::PendingOperation*)), SLOT(gotContacts(Tp::PendingOperation*))); } void Channel::Private::processMembersChanged() { Q_ASSERT(!buildingContacts); if (groupMembersChangedQueue.isEmpty()) { if (pendingRetrieveGroupSelfContact) { pendingRetrieveGroupSelfContact = false; // nothing queued but selfContact changed buildContacts(); return; } if (!parent->isReady(Channel::FeatureCore)) { if (introspectQueue.isEmpty()) { debug() << "Both the MCD and the introspect queue empty for the first time. Ready!"; if (initiatorHandle && !initiatorContact) { warning() << " Unable to create contact object for initiator with handle" << initiatorHandle; } if (targetHandleType == HandleTypeContact && targetHandle != 0 && !targetContact) { warning() << " Unable to create contact object for target with handle" << targetHandle; } if (groupSelfHandle && !groupSelfContact) { warning() << " Unable to create contact object for self handle" << groupSelfHandle; } continueIntrospection(); } else { debug() << "Contact queue empty but introspect queue isn't. IS will set ready."; } } return; } Q_ASSERT(pendingGroupMembers.isEmpty()); Q_ASSERT(pendingGroupLocalPendingMembers.isEmpty()); Q_ASSERT(pendingGroupRemotePendingMembers.isEmpty()); // always set this to false here, as buildContacts will always try to // retrieve the selfContact and updateContacts will check if the built // contact is the same as the current contact. pendingRetrieveGroupSelfContact = false; currentGroupMembersChangedInfo = groupMembersChangedQueue.dequeue(); foreach (uint handle, currentGroupMembersChangedInfo->added) { if (!groupContacts.contains(handle)) { pendingGroupMembers.insert(handle); } // the member was added to current members, check if it was in the // local/pending lists and if true, schedule for removal from that list if (groupLocalPendingContacts.contains(handle)) { groupLocalPendingMembersToRemove.append(handle); } else if(groupRemotePendingContacts.contains(handle)) { groupRemotePendingMembersToRemove.append(handle); } } foreach (uint handle, currentGroupMembersChangedInfo->localPending) { if (!groupLocalPendingContacts.contains(handle)) { pendingGroupLocalPendingMembers.insert(handle); } } foreach (uint handle, currentGroupMembersChangedInfo->remotePending) { if (!groupRemotePendingContacts.contains(handle)) { pendingGroupRemotePendingMembers.insert(handle); } } foreach (uint handle, currentGroupMembersChangedInfo->removed) { groupMembersToRemove.append(handle); } // Always go through buildContacts - we might have a self/initiator/whatever handle to build buildContacts(); } void Channel::Private::updateContacts(const QList &contacts) { Contacts groupContactsAdded; Contacts groupLocalPendingContactsAdded; Contacts groupRemotePendingContactsAdded; ContactPtr actorContact; bool selfContactUpdated = false; debug() << "Entering Chan::Priv::updateContacts() with" << contacts.size() << "contacts"; // FIXME: simplify. Some duplication of logic present. foreach (ContactPtr contact, contacts) { uint handle = contact->handle()[0]; if (pendingGroupMembers.contains(handle)) { groupContactsAdded.insert(contact); groupContacts[handle] = contact; } else if (pendingGroupLocalPendingMembers.contains(handle)) { groupLocalPendingContactsAdded.insert(contact); groupLocalPendingContacts[handle] = contact; // FIXME: should set the details and actor here too groupLocalPendingContactsChangeInfo[handle] = GroupMemberChangeDetails(); } else if (pendingGroupRemotePendingMembers.contains(handle)) { groupRemotePendingContactsAdded.insert(contact); groupRemotePendingContacts[handle] = contact; } if (groupSelfHandle == handle && groupSelfContact != contact) { groupSelfContact = contact; selfContactUpdated = true; } if (!initiatorContact && initiatorHandle == handle) { // No initiator contact stored, but there's a contact for the initiator handle // We can use that! initiatorContact = contact; } if (!targetContact && targetHandleType == HandleTypeContact && targetHandle == handle) { targetContact = contact; if (targetId.isEmpty()) { // For some reason, TargetID was missing from the property map. We can initialize it // here in that case. targetId = targetContact->id(); } } if (currentGroupMembersChangedInfo && currentGroupMembersChangedInfo->actor == contact->handle()[0]) { actorContact = contact; } } if (!groupSelfHandle && groupSelfContact) { groupSelfContact.reset(); selfContactUpdated = true; } pendingGroupMembers.clear(); pendingGroupLocalPendingMembers.clear(); pendingGroupRemotePendingMembers.clear(); // FIXME: This shouldn't be needed. Clearer would be to first scan for the actor being present // in the contacts supplied. foreach (ContactPtr contact, contacts) { uint handle = contact->handle()[0]; if (groupLocalPendingContactsChangeInfo.contains(handle)) { groupLocalPendingContactsChangeInfo[handle] = GroupMemberChangeDetails(actorContact, currentGroupMembersChangedInfo ? currentGroupMembersChangedInfo->details : QVariantMap()); } } Contacts groupContactsRemoved; ContactPtr contactToRemove; foreach (uint handle, groupMembersToRemove) { if (groupContacts.contains(handle)) { contactToRemove = groupContacts[handle]; groupContacts.remove(handle); } else if (groupLocalPendingContacts.contains(handle)) { contactToRemove = groupLocalPendingContacts[handle]; groupLocalPendingContacts.remove(handle); } else if (groupRemotePendingContacts.contains(handle)) { contactToRemove = groupRemotePendingContacts[handle]; groupRemotePendingContacts.remove(handle); } if (groupLocalPendingContactsChangeInfo.contains(handle)) { groupLocalPendingContactsChangeInfo.remove(handle); } if (contactToRemove) { groupContactsRemoved.insert(contactToRemove); } } groupMembersToRemove.clear(); // FIXME: drop the LPToRemove and RPToRemove sets - they're redundant foreach (uint handle, groupLocalPendingMembersToRemove) { groupLocalPendingContacts.remove(handle); } groupLocalPendingMembersToRemove.clear(); foreach (uint handle, groupRemotePendingMembersToRemove) { groupRemotePendingContacts.remove(handle); } groupRemotePendingMembersToRemove.clear(); if (!groupContactsAdded.isEmpty() || !groupLocalPendingContactsAdded.isEmpty() || !groupRemotePendingContactsAdded.isEmpty() || !groupContactsRemoved.isEmpty()) { GroupMemberChangeDetails details( actorContact, currentGroupMembersChangedInfo ? currentGroupMembersChangedInfo->details : QVariantMap()); if (currentGroupMembersChangedInfo && currentGroupMembersChangedInfo->removed.contains(groupSelfHandle)) { // Update groupSelfContactRemoveInfo with the proper actor in case // the actor was not available by the time onMembersChangedDetailed // was called. groupSelfContactRemoveInfo = details; } if (parent->isReady(Channel::FeatureCore)) { // Channel is ready, we can signal membership changes to the outside world without // confusing anyone's fragile logic. emit parent->groupMembersChanged( groupContactsAdded, groupLocalPendingContactsAdded, groupRemotePendingContactsAdded, groupContactsRemoved, details); } } delete currentGroupMembersChangedInfo; currentGroupMembersChangedInfo = 0; if (selfContactUpdated && parent->isReady(Channel::FeatureCore)) { emit parent->groupSelfContactChanged(); } processMembersChanged(); } bool Channel::Private::fakeGroupInterfaceIfNeeded() { if (parent->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) { return false; } else if (targetHandleType != HandleTypeContact) { return false; } // fake group interface if (connection->selfHandle() && targetHandle) { // Fake groupSelfHandle and initial members, let the MCD handling take care of the rest // TODO connect to Connection::selfHandleChanged groupSelfHandle = connection->selfHandle(); groupInitialMembers = UIntList() << groupSelfHandle << targetHandle; debug().nospace() << "Faking a group on channel with self handle=" << groupSelfHandle << " and other handle=" << targetHandle; nowHaveInitialMembers(); } else { warning() << "Connection::selfHandle is 0 or targetHandle is 0, " "not faking a group on channel"; } return true; } void Channel::Private::setReady() { Q_ASSERT(!parent->isReady(Channel::FeatureCore)); debug() << "Channel fully ready"; debug() << " Channel type" << channelType; debug() << " Target handle" << targetHandle; debug() << " Target handle type" << targetHandleType; if (parent->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) { debug() << " Group: flags" << groupFlags; if (groupAreHandleOwnersAvailable) { debug() << " Group: Number of handle owner mappings" << groupHandleOwners.size(); } else { debug() << " Group: No handle owners property present"; } debug() << " Group: Number of current members" << groupContacts.size(); debug() << " Group: Number of local pending members" << groupLocalPendingContacts.size(); debug() << " Group: Number of remote pending members" << groupRemotePendingContacts.size(); debug() << " Group: Self handle" << groupSelfHandle << "tracked:" << (groupIsSelfHandleTracked ? "yes" : "no"); } readinessHelper->setIntrospectCompleted(FeatureCore, true); } QString Channel::Private::groupMemberChangeDetailsTelepathyError( const GroupMemberChangeDetails &details) { QString error; uint reason = details.reason(); switch (reason) { case ChannelGroupChangeReasonOffline: error = QLatin1String(TELEPATHY_ERROR_OFFLINE); break; case ChannelGroupChangeReasonKicked: error = QLatin1String(TELEPATHY_ERROR_CHANNEL_KICKED); break; case ChannelGroupChangeReasonBanned: error = QLatin1String(TELEPATHY_ERROR_CHANNEL_BANNED); break; case ChannelGroupChangeReasonBusy: error = QLatin1String(TELEPATHY_ERROR_BUSY); break; case ChannelGroupChangeReasonNoAnswer: error = QLatin1String(TELEPATHY_ERROR_NO_ANSWER); break; case ChannelGroupChangeReasonPermissionDenied: error = QLatin1String(TELEPATHY_ERROR_PERMISSION_DENIED); break; case ChannelGroupChangeReasonInvalidContact: error = QLatin1String(TELEPATHY_ERROR_DOES_NOT_EXIST); break; // The following change reason are being mapped to default // case ChannelGroupChangeReasonNone: // case ChannelGroupChangeReasonInvited: // shouldn't happen // case ChannelGroupChangeReasonError: // case ChannelGroupChangeReasonRenamed: // case ChannelGroupChangeReasonSeparated: // shouldn't happen default: // let's use the actor handle and selfHandle here instead of the // contacts, as the contacts may not be ready. error = ((qdbus_cast(details.allDetails().value(QLatin1String("actor"))) == groupSelfHandle) ? QLatin1String(TELEPATHY_ERROR_CANCELLED) : QLatin1String(TELEPATHY_ERROR_TERMINATED)); break; } return error; } void Channel::Private::processConferenceChannelRemoved() { if (buildingConferenceChannelRemovedActorContact || conferenceChannelRemovedQueue.isEmpty()) { return; } ConferenceChannelRemovedInfo *info = conferenceChannelRemovedQueue.first(); if (!conferenceChannels.contains(info->channelPath.path())) { info = conferenceChannelRemovedQueue.dequeue(); delete info; processConferenceChannelRemoved(); return; } buildingConferenceChannelRemovedActorContact = true; if (info->details.contains(keyActor)) { ContactManagerPtr manager = connection->contactManager(); PendingContacts *pendingContacts = manager->contactsForHandles( UIntList() << qdbus_cast(info->details.value(keyActor))); parent->connect(pendingContacts, SIGNAL(finished(Tp::PendingOperation*)), SLOT(gotConferenceChannelRemovedActorContact(Tp::PendingOperation*))); } else { parent->gotConferenceChannelRemovedActorContact(0); } } struct TELEPATHY_QT4_NO_EXPORT Channel::GroupMemberChangeDetails::Private : public QSharedData { Private(const ContactPtr &actor, const QVariantMap &details) : actor(actor), details(details) {} ContactPtr actor; QVariantMap details; }; /** * \class Channel::GroupMemberChangeDetails * \ingroup clientchannel * \headerfile TelepathyQt4/channel.h * * \brief The Channel::GroupMemberChangeDetails class represents the details of a group membership * change. * * Extended information is not always available; this will be reflected by * the return value of isValid(). */ /** * Constructs a new invalid GroupMemberChangeDetails instance. */ Channel::GroupMemberChangeDetails::GroupMemberChangeDetails() { } /** * Copy constructor. */ Channel::GroupMemberChangeDetails::GroupMemberChangeDetails(const GroupMemberChangeDetails &other) : mPriv(other.mPriv) { } /** * Class destructor. */ Channel::GroupMemberChangeDetails::~GroupMemberChangeDetails() { } /** * Assigns all information (validity, details) from other to this. */ Channel::GroupMemberChangeDetails &Channel::GroupMemberChangeDetails::operator=( const GroupMemberChangeDetails &other) { this->mPriv = other.mPriv; return *this; } /** * \fn bool Channel::GroupMemberChangeDetails::isValid() const * * Return whether the details are valid (have actually been received from the service). * * \return \c true if valid, \c false otherwise. */ /** * Return whether the details specify an actor. * * If present, actor() will return the contact object representing the person who made the change. * * \return \c true if the actor is known, \c false otherwise. * \sa actor() */ bool Channel::GroupMemberChangeDetails::hasActor() const { return isValid() && !mPriv->actor.isNull(); } /** * Return the contact object representing the person who made the change (actor), if known. * * \return A pointer to the Contact object, or a null ContactPtr if the actor is unknown. * \sa hasActor() */ ContactPtr Channel::GroupMemberChangeDetails::actor() const { return isValid() ? mPriv->actor : ContactPtr(); } /** * \fn bool Channel::GroupMemberChangeDetails::hasReason() const * * Return whether the details specify the reason for the change. * * \return \c true if the reason is known, \c false otherwise. * \sa reason() */ /** * \fn ChannelGroupChangeReason Channel::GroupMemberChangeDetails::reason() const * * Return the reason for the change, if known. * * \return The change reason as #ChannelGroupChangeReason, or #ChannelGroupChangeReasonNone * if the reason is unknown. * \sa hasReason() */ /** * \fn bool Channel::GroupMemberChangeDetails::hasMessage() const * * Return whether the details specify a human-readable message from the contact represented by * actor() pertaining to the change. * * \return \c true if the message is known, \c false otherwise. * \sa message() */ /** * \fn QString Channel::GroupMemberChangeDetails::message() const * * Return a human-readable message from the contact represented by actor() pertaining to the change, * if known. * * \return The message, or an empty string if the message is unknown. * \sa hasMessage() */ /** * \fn bool Channel::GroupMemberChangeDetails::hasError() const * * Return whether the details specify a D-Bus error describing the change. * * \return \c true if the error is known, \c false otherwise. * \sa error() */ /** * \fn QString Channel::GroupMemberChangeDetails::error() const * * Return the D-Bus error describing the change, if known. * * The D-Bus error provides more specific information than the reason() and should be used if * applicable. * * \return A D-Bus error describing the change, or an empty string if the error is unknown. * \sa hasError() */ /** * \fn bool Channel::GroupMemberChangeDetails::hasDebugMessage() const * * Return whether the details specify a debug message. * * \return \c true if debug message is present, \c false otherwise. * \sa debugMessage() */ /** * \fn QString Channel::GroupMemberChangeDetails::debugMessage() const * * Return the debug message specified by the details, if any. * * The debug message is purely informational, offered for display for bug reporting purposes, and * should not be attempted to be parsed. * * \return The debug message, or an empty string if there is none. * \sa hasDebugMessage() */ /** * Return a map containing all details of the group members change. * * This is useful for accessing domain-specific additional details. * * \return The details of the group members change as QVariantMap. */ QVariantMap Channel::GroupMemberChangeDetails::allDetails() const { return isValid() ? mPriv->details : QVariantMap(); } Channel::GroupMemberChangeDetails::GroupMemberChangeDetails(const ContactPtr &actor, const QVariantMap &details) : mPriv(new Private(actor, details)) { } /** * \class Channel * \ingroup clientchannel * \headerfile TelepathyQt4/channel.h * * \brief The Channel class represents a Telepathy channel. * * All communication in the Telepathy framework is carried out via channel * objects. Specialized classes for some specific channel types such as * StreamedMediaChannel, TextChannel, FileTransferChannel are provided. * * The remote object accessor functions on this object (channelType(), targetHandleType(), * and so on) don't make any D-Bus calls; instead, they return/use * values cached from a previous introspection run. The introspection process * populates their values in the most efficient way possible based on what the * service implements. * * To avoid unnecessary D-Bus traffic, some accessors only return valid * information after specific features have been enabled. * For instance, to retrieve the initial invitee contacts in a conference channel, * it is necessary to enable the feature Channel::FeatureConferenceInitialInviteeContacts. * See the individual methods descriptions for more details. * * Channel features can be enabled by constructing a ChannelFactory and enabling * the desired features, and passing it to AccountManager, Account or ClientRegistrar * when creating them as appropriate. However, if a particular * feature is only ever used in a specific circumstance, such as an user opening * some settings dialog separate from the general view of the application, * features can be later enabled as needed by calling becomeReady() with the additional * features, and waiting for the resulting PendingOperation to finish. * * Each channel is owned by a connection. If the Connection object becomes invalidated * the Channel object will also get invalidated. * * \section chan_usage_sec Usage * * \subsection chan_create_sec Creating a channel object * * Channel objects can be created in various ways, but the preferred way is * trough Account channel creation methods such as Account::ensureTextChat(), * Account::createFileTransfer(), which uses the channel dispatcher. * * If you already know the object path, you can just call create(). * For example: * * \code * * ChannelPtr chan = Channel::create(connection, objectPath, * immutableProperties); * * \endcode * * \subsection chan_ready_sec Making channel ready to use * * A Channel object needs to become ready before usage, meaning that the * introspection process finished and the object accessors can be used. * * To make the object ready, use becomeReady() and wait for the * PendingOperation::finished() signal to be emitted. * * \code * * class MyClass : public QObject * { * QOBJECT * * public: * MyClass(QObject *parent = 0); * ~MyClass() { } * * private Q_SLOTS: * void onChannelReady(Tp::PendingOperation*); * * private: * ChannelPtr chan; * }; * * MyClass::MyClass(const ConnectionPtr &connection, * const QString &objectPath, const QVariantMap &immutableProperties) * : QObject(parent) * chan(Channel::create(connection, objectPath, immutableProperties)) * { * connect(chan->becomeReady(), * SIGNAL(finished(Tp::PendingOperation*)), * SLOT(onChannelReady(Tp::PendingOperation*))); * } * * void MyClass::onChannelReady(Tp::PendingOperation *op) * { * if (op->isError()) { * qWarning() << "Channel cannot become ready:" << * op->errorName() << "-" << op->errorMessage(); * return; * } * * // Channel is now ready * } * * \endcode * * See \ref async_model, \ref shared_ptr */ /** * Feature representing the core that needs to become ready to make the Channel * object usable. * * Note that this feature must be enabled in order to use most Channel methods. * See specific methods documentation for more details. * * When calling isReady(), becomeReady(), this feature is implicitly added * to the requested features. */ const Feature Channel::FeatureCore = Feature(QLatin1String(Channel::staticMetaObject.className()), 0, true); /** * Feature used in order to access the conference initial invitee contacts info. * * \sa conferenceInitialInviteeContacts() */ const Feature Channel::FeatureConferenceInitialInviteeContacts = Feature(QLatin1String(Channel::staticMetaObject.className()), 1, true); /** * Create a new Channel object. * * \param connection Connection owning this channel, and specifying the * service. * \param objectPath The channel object path. * \param immutableProperties The channel immutable properties. * \return A ChannelPtr object pointing to the newly created Channel object. * * \todo \a immutableProperties should be used to populate the corresponding accessors (such as * channelType()) already on construction, not only when making FeatureCore ready (fd.o #41654) */ ChannelPtr Channel::create(const ConnectionPtr &connection, const QString &objectPath, const QVariantMap &immutableProperties) { return ChannelPtr(new Channel(connection, objectPath, immutableProperties, Channel::FeatureCore)); } /** * Construct a new Channel object. * * \param connection Connection owning this channel, and specifying the * service. * \param objectPath The channel object path. * \param immutableProperties The channel immutable properties. * \param coreFeature The core feature of the channel type. The corresponding introspectable should * depend on Channel::FeatureCore. */ Channel::Channel(const ConnectionPtr &connection, const QString &objectPath, const QVariantMap &immutableProperties, const Feature &coreFeature) : StatefulDBusProxy(connection->dbusConnection(), connection->busName(), objectPath, coreFeature), OptionalInterfaceFactory(this), mPriv(new Private(this, connection, immutableProperties)) { } /** * Class destructor. */ Channel::~Channel() { delete mPriv; } /** * Return the connection owning this channel. * * \return A pointer to the Connection object. */ ConnectionPtr Channel::connection() const { return mPriv->connection; } /** * Return the immutable properties of the channel. * * If the channel is ready (isReady(Channel::FeatureCore) returns true), the following keys are * guaranteed to be present: * org.freedesktop.Telepathy.Channel.ChannelType, * org.freedesktop.Telepathy.Channel.TargetHandleType, * org.freedesktop.Telepathy.Channel.TargetHandle and * org.freedesktop.Telepathy.Channel.Requested. * * 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. * * \return The immutable properties as QVariantMap. */ QVariantMap Channel::immutableProperties() const { if (isReady(Channel::FeatureCore)) { QString key; key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"); if (!mPriv->immutableProperties.contains(key)) { mPriv->immutableProperties.insert(key, mPriv->channelType); } key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Interfaces"); if (!mPriv->immutableProperties.contains(key)) { mPriv->immutableProperties.insert(key, interfaces()); } key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"); if (!mPriv->immutableProperties.contains(key)) { mPriv->immutableProperties.insert(key, mPriv->targetHandleType); } key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"); if (!mPriv->immutableProperties.contains(key)) { mPriv->immutableProperties.insert(key, mPriv->targetHandle); } key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"); if (!mPriv->immutableProperties.contains(key)) { mPriv->immutableProperties.insert(key, mPriv->targetId); } key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested"); if (!mPriv->immutableProperties.contains(key)) { mPriv->immutableProperties.insert(key, mPriv->requested); } key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorHandle"); if (!mPriv->immutableProperties.contains(key)) { mPriv->immutableProperties.insert(key, mPriv->initiatorHandle); } key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorID"); if (!mPriv->immutableProperties.contains(key) && !mPriv->initiatorContact.isNull()) { mPriv->immutableProperties.insert(key, mPriv->initiatorContact->id()); } } return mPriv->immutableProperties; } /** * Return the D-Bus interface name for the type of this channel. * * This method requires Channel::FeatureCore to be ready. * * \return The D-Bus interface name for the type of the channel. */ QString Channel::channelType() const { // Similarly, we don't want warnings triggered when using the type interface // proxies internally. if (!isReady(Channel::FeatureCore) && mPriv->channelType.isEmpty()) { warning() << "Channel::channelType() before the channel type has " "been received"; } else if (!isValid()) { warning() << "Channel::channelType() used with channel closed"; } return mPriv->channelType; } /** * Return the type of the handle returned by targetHandle() as specified in * #HandleType. * * This method requires Channel::FeatureCore to be ready. * * \return The target handle type as #HandleType. * \sa targetHandle(), targetId() */ HandleType Channel::targetHandleType() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::targetHandleType() used channel not ready"; } return (HandleType) mPriv->targetHandleType; } /** * Return the handle of the remote party with which this channel * communicates. * * This method requires Channel::FeatureCore to be ready. * * \return An integer representing the target handle, which is of the type * targetHandleType() indicates. * \sa targetHandleType(), targetId() */ uint Channel::targetHandle() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::targetHandle() used channel not ready"; } return mPriv->targetHandle; } /** * Return the persistent unique ID of the remote party with which this channel communicates. * * If targetHandleType() is #HandleTypeContact, this will be the ID of the remote contact, and * similarly the unique ID of the room when targetHandleType() is #HandleTypeRoom. * * This is not necessarily the best identifier to display to the user, though. In particular, for * contacts, their alias should be displayed instead. It can be used for matching channels and UI * elements for them across reconnects, though, at which point the old channels and contacts are * invalidated. * * This method requires Channel::FeatureCore to be ready. * * \return The target identifier. * \sa targetHandle(), targetContact() */ QString Channel::targetId() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::targetId() used, but the channel is not ready"; } return mPriv->targetId; } /** * Return the contact with which this channel communicates for its lifetime, if applicable. * * This method requires Channel::FeatureCore to be ready. * * \return A pointer to the Contact object, or a null ContactPtr if targetHandleType() is not * #HandleTypeContact. * \sa targetHandle(), targetId() */ ContactPtr Channel::targetContact() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::targetContact() used, but the channel is not ready"; } else if (targetHandleType() != HandleTypeContact) { warning() << "Channel::targetContact() used with targetHandleType() != Contact"; } return mPriv->targetContact; } /** * Return whether this channel was created in response to a * local request. * * This method requires Channel::FeatureCore to be ready. * * \return \c true if the channel was created in response to a local request, * \c false otherwise. */ bool Channel::isRequested() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::isRequested() used channel not ready"; } return mPriv->requested; } /** * Return the contact who initiated this channel. * * This method requires Channel::FeatureCore to be ready. * * \return A pointer to the Contact object representing the contact who initiated the channel, * or a null ContactPtr if it can't be retrieved. */ ContactPtr Channel::initiatorContact() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::initiatorContact() used channel not ready"; } return mPriv->initiatorContact; } /** * Start an asynchronous request that this channel be closed. * * The returned PendingOperation object will signal the success or failure * of this request; under normal circumstances, it can be expected to * succeed. * * \return A PendingOperation which will emit PendingOperation::finished * when the call has finished. * \sa requestLeave() */ PendingOperation *Channel::requestClose() { // Closing a channel does not make sense if it is already closed, // just silently Return. if (!isValid()) { return new PendingSuccess(ChannelPtr(this)); } return new PendingVoid(mPriv->baseInterface->Close(), ChannelPtr(this)); } Channel::PendingLeave::PendingLeave(const ChannelPtr &chan, const QString &message, ChannelGroupChangeReason reason) : PendingOperation(chan) { Q_ASSERT(chan->mPriv->group != NULL); QDBusPendingCall call = chan->mPriv->group->RemoveMembersWithReason( UIntList() << chan->mPriv->groupSelfHandle, message, reason); connect(chan.data(), SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), this, SLOT(onChanInvalidated(Tp::DBusProxy*))); connect(new PendingVoid(call, chan), SIGNAL(finished(Tp::PendingOperation*)), this, SLOT(onRemoveFinished(Tp::PendingOperation*))); } void Channel::PendingLeave::onChanInvalidated(Tp::DBusProxy *proxy) { if (isFinished()) { return; } debug() << "Finishing PendingLeave successfully as the channel was invalidated"; setFinished(); } void Channel::PendingLeave::onRemoveFinished(Tp::PendingOperation *op) { if (isFinished()) { return; } ChannelPtr chan = ChannelPtr::staticCast(_object()); if (op->isValid()) { debug() << "We left the channel" << chan->objectPath(); ContactPtr c = chan->groupSelfContact(); if (chan->groupContacts().contains(c) || chan->groupLocalPendingContacts().contains(c) || chan->groupRemotePendingContacts().contains(c)) { debug() << "Waiting for self remove to be picked up"; connect(chan.data(), SIGNAL(groupMembersChanged(Tp::Contacts,Tp::Contacts,Tp::Contacts,Tp::Contacts, Tp::Channel::GroupMemberChangeDetails)), this, SLOT(onMembersChanged(Tp::Contacts,Tp::Contacts,Tp::Contacts,Tp::Contacts))); } else { setFinished(); } return; } debug() << "Leave RemoveMembersWithReason failed with " << op->errorName() << op->errorMessage() << "- falling back to Close"; // If the channel has been closed or otherwise invalidated already in this mainloop iteration, // the requestClose() operation will early-succeed connect(chan->requestClose(), SIGNAL(finished(Tp::PendingOperation*)), this, SLOT(onCloseFinished(Tp::PendingOperation*))); } void Channel::PendingLeave::onMembersChanged(const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &removed) { if (isFinished()) { return; } ChannelPtr chan = ChannelPtr::staticCast(_object()); ContactPtr c = chan->groupSelfContact(); if (removed.contains(c)) { debug() << "Leave event picked up for" << chan->objectPath(); setFinished(); } } void Channel::PendingLeave::onCloseFinished(Tp::PendingOperation *op) { if (isFinished()) { return; } ChannelPtr chan = ChannelPtr::staticCast(_object()); if (op->isError()) { warning() << "Closing the channel" << chan->objectPath() << "as a fallback for leaving it failed with" << op->errorName() << op->errorMessage() << "- so didn't leave"; setFinishedWithError(op->errorName(), op->errorMessage()); } else { debug() << "We left (by closing) the channel" << chan->objectPath(); setFinished(); } } /** * Start an asynchronous request to leave this channel as gracefully as possible. * * If leaving any more gracefully is not possible, this will revert to the same as requestClose(). * In particular, this will be the case for channels with no group interface * (#TP_QT4_IFACE_CHANNEL_INTERFACE_GROUP not in the list returned by interfaces()). * * The returned PendingOperation object will signal the success or failure * of this request; under normal circumstances, it can be expected to * succeed. * * A message and a reason may be provided along with the request, which will be sent to the server * if supported, which is indicated by #ChannelGroupFlagMessageDepart and/or * #ChannelGroupFlagMessageReject. * * Attempting to leave again when we have already left, either by our request or forcibly, will be a * no-op, with the returned PendingOperation immediately finishing successfully. * * \param message The message, which can be blank if desired. * \param reason A reason for leaving. * \return A PendingOperation which will emit PendingOperation::finished * when the call has finished. */ PendingOperation *Channel::requestLeave(const QString &message, ChannelGroupChangeReason reason) { // Leaving a channel does not make sense if it is already closed, // just silently Return. if (!isValid()) { return new PendingSuccess(ChannelPtr(this)); } if (!isReady(Channel::FeatureCore)) { return new PendingFailure(TP_QT4_ERROR_NOT_AVAILABLE, QLatin1String("Channel::FeatureCore must be ready to leave a channel"), ChannelPtr(this)); } if (!interfaces().contains(TP_QT4_IFACE_CHANNEL_INTERFACE_GROUP)) { return requestClose(); } ContactPtr self = groupSelfContact(); if (!groupContacts().contains(self) && !groupLocalPendingContacts().contains(self) && !groupRemotePendingContacts().contains(self)) { debug() << "Channel::requestLeave() called for " << objectPath() << "which we aren't a member of"; return new PendingSuccess(ChannelPtr(this)); } return new PendingLeave(ChannelPtr(this), message, reason); } /** * \name Group interface * * Cached access to state of the group interface on the associated remote * object, if the interface is present. * * Some methods can be used when targetHandleType() == #HandleTypeContact, such * as groupFlags(), groupCanAddContacts(), groupCanRemoveContacts(), * groupSelfContact() and groupContacts(). * * As the group interface state can change freely during the lifetime of the * channel due to events like new contacts joining the group, the cached state * is automatically kept in sync with the remote object's state by hooking * to the change notification signals present in the D-Bus interface. * * As the cached value changes, change notification signals are emitted. * * Signals such as groupMembersChanged(), groupSelfContactChanged(), etc., are emitted to * indicate that properties have changed. * * Check the individual signals' descriptions for details. */ //@{ /** * Return a set of flags indicating the capabilities and behaviour of the * group on this channel. * * Change notification is via the groupFlagsChanged() signal. * * This method requires Channel::FeatureCore to be ready. * * \return The bitfield combination of flags as #ChannelGroupFlags. * \sa groupFlagsChanged() */ ChannelGroupFlags Channel::groupFlags() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupFlags() used channel not ready"; } return (ChannelGroupFlags) mPriv->groupFlags; } /** * Return whether contacts can be added or invited to this channel. * * Change notification is via the groupCanAddContactsChanged() signal. * * This method requires Channel::FeatureCore to be ready. * * \return \c true if contacts can be added or invited to the channel, * \c false otherwise. * \sa groupFlags(), groupAddContacts() */ bool Channel::groupCanAddContacts() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupCanAddContacts() used channel not ready"; } return mPriv->groupFlags & ChannelGroupFlagCanAdd; } /** * Return whether a message is expected when adding/inviting contacts, who * are not already members, to this channel. * * This method requires Channel::FeatureCore to be ready. * * \return \c true if a message is expected, \c false otherwise. * \sa groupFlags(), groupAddContacts() */ bool Channel::groupCanAddContactsWithMessage() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupCanAddContactsWithMessage() used when channel not ready"; } return mPriv->groupFlags & ChannelGroupFlagMessageAdd; } /** * Return whether a message is expected when accepting contacts' requests to * join this channel. * * This method requires Channel::FeatureCore to be ready. * * \return \c true if a message is expected, \c false otherwise. * \sa groupFlags(), groupAddContacts() */ bool Channel::groupCanAcceptContactsWithMessage() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupCanAcceptContactsWithMessage() used when channel not ready"; } return mPriv->groupFlags & ChannelGroupFlagMessageAccept; } /** * Add contacts to this channel. * * Contacts on the local pending list (those waiting for permission to join * the channel) can always be added. If groupCanAcceptContactsWithMessage() * returns \c true, an optional message is expected when doing this; if not, * the message parameter is likely to be ignored (so the user should not be * asked for a message, and the message parameter should be left empty). * * Other contacts can only be added if groupCanAddContacts() returns \c true. * If groupCanAddContactsWithMessage() returns \c true, an optional message is * expected when doing this, and if not, the message parameter is likely to be * ignored. * * This method requires Channel::FeatureCore to be ready. * * \param contacts Contacts to be added. * \param message A string message, which can be blank if desired. * \return A PendingOperation which will emit PendingOperation::finished * when the call has finished. * \sa groupCanAddContacts(), groupCanAddContactsWithMessage(), groupCanAcceptContactsWithMessage() */ PendingOperation *Channel::groupAddContacts(const QList &contacts, const QString &message) { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupAddContacts() used channel not ready"; return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), QLatin1String("Channel not ready"), ChannelPtr(this)); } else if (contacts.isEmpty()) { warning() << "Channel::groupAddContacts() used with empty contacts param"; return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), QLatin1String("contacts cannot be an empty list"), ChannelPtr(this)); } foreach (const ContactPtr &contact, contacts) { if (!contact) { warning() << "Channel::groupAddContacts() used but contacts param contains " "invalid contact"; return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), QLatin1String("Unable to add invalid contacts"), ChannelPtr(this)); } } if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) { warning() << "Channel::groupAddContacts() used with no group interface"; return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), QLatin1String("Channel does not support group interface"), ChannelPtr(this)); } UIntList handles; foreach (const ContactPtr &contact, contacts) { handles << contact->handle()[0]; } return new PendingVoid(mPriv->group->AddMembers(handles, message), ChannelPtr(this)); } /** * Return whether contacts in groupRemotePendingContacts() can be removed from * this channel (i.e. whether an invitation can be rescinded). * * Change notification is via the groupCanRescindContactsChanged() signal. * * This method requires Channel::FeatureCore to be ready. * * \return \c true if contacts can be removed, \c false otherwise. * \sa groupFlags(), groupRemoveContacts() */ bool Channel::groupCanRescindContacts() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupCanRescindContacts() used channel not ready"; } return mPriv->groupFlags & ChannelGroupFlagCanRescind; } /** * Return whether a message is expected when removing contacts who are in * groupRemotePendingContacts() from this channel (i.e. rescinding an * invitation). * * This method requires Channel::FeatureCore to be ready. * * \return \c true if a message is expected, \c false otherwise. * \sa groupFlags(), groupRemoveContacts() */ bool Channel::groupCanRescindContactsWithMessage() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupCanRescindContactsWithMessage() used when channel not ready"; } return mPriv->groupFlags & ChannelGroupFlagMessageRescind; } /** * Return if contacts in groupContacts() can be removed from this channel. * * Note that contacts in local pending lists, and the groupSelfContact(), can * always be removed from the channel. * * Change notification is via the groupCanRemoveContactsChanged() signal. * * This method requires Channel::FeatureCore to be ready. * * \return \c true if contacts can be removed, \c false otherwise. * \sa groupFlags(), groupRemoveContacts() */ bool Channel::groupCanRemoveContacts() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupCanRemoveContacts() used channel not ready"; } return mPriv->groupFlags & ChannelGroupFlagCanRemove; } /** * Return whether a message is expected when removing contacts who are in * groupContacts() from this channel. * * This method requires Channel::FeatureCore to be ready. * * \return \c true if a message is expected, \c false otherwise. * \sa groupFlags(), groupRemoveContacts() */ bool Channel::groupCanRemoveContactsWithMessage() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupCanRemoveContactsWithMessage() used when channel not ready"; } return mPriv->groupFlags & ChannelGroupFlagMessageRemove; } /** * Return whether a message is expected when removing contacts who are in * groupLocalPendingContacts() from this channel (i.e. rejecting a request to * join). * * This method requires Channel::FeatureCore to be ready. * * \return \c true if a message is expected, \c false otherwise. * \sa groupFlags(), groupRemoveContacts() */ bool Channel::groupCanRejectContactsWithMessage() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupCanRejectContactsWithMessage() used when channel not ready"; } return mPriv->groupFlags & ChannelGroupFlagMessageReject; } /** * Return whether a message is expected when removing the groupSelfContact() * from this channel (i.e. departing from the channel). * * \return \c true if a message is expected, \c false otherwise. * \sa groupFlags(), groupRemoveContacts() */ bool Channel::groupCanDepartWithMessage() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupCanDepartWithMessage() used when channel not ready"; } return mPriv->groupFlags & ChannelGroupFlagMessageDepart; } /** * Remove contacts from this channel. * * Contacts on the local pending list (those waiting for permission to join * the channel) can always be removed. If groupCanRejectContactsWithMessage() * returns \c true, an optional message is expected when doing this; if not, * the message parameter is likely to be ignored (so the user should not be * asked for a message, and the message parameter should be left empty). * * The groupSelfContact() can also always be removed, as a way to leave the * group with an optional departure message and/or departure reason indication. * If groupCanDepartWithMessage() returns \c true, an optional message is * expected when doing this, and if not, the message parameter is likely to * be ignored. * * Contacts in the group can only be removed (e.g. kicked) if * groupCanRemoveContacts() returns \c true. If * groupCanRemoveContactsWithMessage() returns \c true, an optional message is * expected when doing this, and if not, the message parameter is likely to be * ignored. * * Contacts in the remote pending list (those who have been invited to the * channel) can only be removed (have their invitations rescinded) if * groupCanRescindContacts() returns \c true. If * groupCanRescindContactsWithMessage() returns \c true, an optional message is * expected when doing this, and if not, the message parameter is likely to be * ignored. * * This method requires Channel::FeatureCore to be ready. * * \param contacts Contacts to be removed. * \param message A string message, which can be blank if desired. * \param reason Reason of the change, as specified in * #ChannelGroupChangeReason * \return A PendingOperation which will emit PendingOperation::finished * when the call has finished. * \sa groupCanRemoveContacts(), groupCanRemoveContactsWithMessage(), * groupCanRejectContactsWithMessage(), groupCanRescindContacts(), * groupCanRescindContacts(), groupCanRescindContactsWithMessage(), * groupCanDepartWithMessage() */ PendingOperation *Channel::groupRemoveContacts(const QList &contacts, const QString &message, ChannelGroupChangeReason reason) { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupRemoveContacts() used channel not ready"; return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), QLatin1String("Channel not ready"), ChannelPtr(this)); } if (contacts.isEmpty()) { warning() << "Channel::groupRemoveContacts() used with empty contacts param"; return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), QLatin1String("contacts param cannot be an empty list"), ChannelPtr(this)); } foreach (const ContactPtr &contact, contacts) { if (!contact) { warning() << "Channel::groupRemoveContacts() used but contacts param contains " "invalid contact:"; return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), QLatin1String("Unable to remove invalid contacts"), ChannelPtr(this)); } } if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) { warning() << "Channel::groupRemoveContacts() used with no group interface"; return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), QLatin1String("Channel does not support group interface"), ChannelPtr(this)); } UIntList handles; foreach (const ContactPtr &contact, contacts) { handles << contact->handle()[0]; } return new PendingVoid( mPriv->group->RemoveMembersWithReason(handles, message, reason), ChannelPtr(this)); } /** * Return the current contacts of the group. * * Change notification is via the groupMembersChanged() signal. * * This method requires Channel::FeatureCore to be ready. * * \return A set of pointers to the Contact objects. * \sa groupLocalPendingContacts(), groupRemotePendingContacts() */ Contacts Channel::groupContacts() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupMembers() used channel not ready"; } return mPriv->groupContacts.values().toSet(); } /** * Return the contacts currently waiting for local approval to join the * group. * * Change notification is via the groupMembersChanged() signal. * * This method requires Channel::FeatureCore to be ready. * * \return A set of pointers to the Contact objects. * \sa groupContacts(), groupRemotePendingContacts() */ Contacts Channel::groupLocalPendingContacts() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupLocalPendingContacts() used channel not ready"; } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) { warning() << "Channel::groupLocalPendingContacts() used with no group interface"; } return mPriv->groupLocalPendingContacts.values().toSet(); } /** * Return the contacts currently waiting for remote approval to join the * group. * * Change notification is via the groupMembersChanged() signal. * * This method requires Channel::FeatureCore to be ready. * * \return A set of pointers to the Contact objects. * \sa groupContacts(), groupLocalPendingContacts() */ Contacts Channel::groupRemotePendingContacts() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupRemotePendingContacts() used channel not ready"; } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) { warning() << "Channel::groupRemotePendingContacts() used with no " "group interface"; } return mPriv->groupRemotePendingContacts.values().toSet(); } /** * Return information of a local pending contact change. If * no information is available, an object for which * GroupMemberChangeDetails::isValid() returns false is returned. * * This method requires Channel::FeatureCore to be ready. * * \param contact A Contact object that is on the local pending contacts list. * \return The change info as a GroupMemberChangeDetails object. */ Channel::GroupMemberChangeDetails Channel::groupLocalPendingContactChangeInfo( const ContactPtr &contact) const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupLocalPendingContactChangeInfo() used channel not ready"; } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) { warning() << "Channel::groupLocalPendingContactChangeInfo() used with no group interface"; } else if (!contact) { warning() << "Channel::groupLocalPendingContactChangeInfo() used with null contact param"; return GroupMemberChangeDetails(); } uint handle = contact->handle()[0]; return mPriv->groupLocalPendingContactsChangeInfo.value(handle); } /** * Return information on the removal of the local user from the group. If * the user hasn't been removed from the group, an object for which * GroupMemberChangeDetails::isValid() returns false is returned. * * This method should be called only after you've left the channel. * This is useful for getting the remove information after missing the * corresponding groupMembersChanged() signal, as the local user being * removed usually causes the channel to be closed. * * The returned information is not guaranteed to be correct if * groupIsSelfHandleTracked() returns false and a self handle change has * occurred on the remote object. * * This method requires Channel::FeatureCore to be ready. * * \return The remove info as a GroupMemberChangeDetails object. */ Channel::GroupMemberChangeDetails Channel::groupSelfContactRemoveInfo() const { // Oftentimes, the channel will be closed as a result from being left - so checking a channel's // self remove info when it has been closed and hence invalidated is valid if (isValid() && !isReady(Channel::FeatureCore)) { warning() << "Channel::groupSelfContactRemoveInfo() used before Channel::FeatureCore is ready"; } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) { warning() << "Channel::groupSelfContactRemoveInfo() used with " "no group interface"; } return mPriv->groupSelfContactRemoveInfo; } /** * Return whether globally valid handles can be looked up using the * channel-specific handle on this channel using this object. * * Handle owner lookup is only available if: *
    *
  • The object is ready *
  • The list returned by interfaces() contains #TP_QT4_IFACE_CHANNEL_INTERFACE_GROUP
  • *
  • The set of flags returned by groupFlags() contains * #GroupFlagProperties and #GroupFlagChannelSpecificHandles
  • *
* * If this function returns \c false, the return value of * groupHandleOwners() is undefined and groupHandleOwnersChanged() will * never be emitted. * * The value returned by this function will stay fixed for the entire time * the object is ready, so no change notification is provided. * * This method requires Channel::FeatureCore to be ready. * * \return \c true if handle owner lookup functionality is available, \c false otherwise. */ bool Channel::groupAreHandleOwnersAvailable() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupAreHandleOwnersAvailable() used channel not ready"; } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) { warning() << "Channel::groupAreHandleOwnersAvailable() used with " "no group interface"; } return mPriv->groupAreHandleOwnersAvailable; } /** * Return a mapping of handles specific to this channel to globally valid * handles. * * The mapping includes at least all of the channel-specific handles in this * channel's members, local-pending and remote-pending sets as keys. Any * handle not in the keys of this mapping is not channel-specific in this * channel. Handles which are channel-specific, but for which the owner is * unknown, appear in this mapping with 0 as owner. * * Change notification is via the groupHandleOwnersChanged() signal. * * This method requires Channel::FeatureCore to be ready. * * \return A mapping from group-specific handles to globally valid handles. */ HandleOwnerMap Channel::groupHandleOwners() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupHandleOwners() used channel not ready"; } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) { warning() << "Channel::groupAreHandleOwnersAvailable() used with no " "group interface"; } else if (!groupAreHandleOwnersAvailable()) { warning() << "Channel::groupAreHandleOwnersAvailable() used, but handle " "owners not available"; } return mPriv->groupHandleOwners; } /** * Return whether the value returned by groupSelfContact() is guaranteed to * accurately represent the local user even after nickname changes, etc. * * This should always be \c true for new services implementing the group interface. * * Older services not providing group properties don't necessarily * emit the SelfHandleChanged signal either, so self contact changes can't be * reliably tracked. * * This method requires Channel::FeatureCore to be ready. * * \return \c true if changes to the self contact are tracked, \c false otherwise. */ bool Channel::groupIsSelfContactTracked() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupIsSelfHandleTracked() used channel not ready"; } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_GROUP))) { warning() << "Channel::groupIsSelfHandleTracked() used with " "no group interface"; } return mPriv->groupIsSelfHandleTracked; } /** * Return a Contact object representing the user in the group if at all possible, otherwise a * Contact object representing the user globally. * * Change notification is via the groupSelfContactChanged() signal. * * This method requires Channel::FeatureCore to be ready. * * \return A pointer to the Contact object. */ ContactPtr Channel::groupSelfContact() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupSelfContact() used channel not ready"; } return mPriv->groupSelfContact; } /** * Return whether the local user is in the "local pending" state. This * indicates that the local user needs to take action to accept an invitation, * an incoming call, etc. * * This method requires Channel::FeatureCore to be ready. * * \return \c true if local user is in the channel's local-pending set, \c false otherwise. */ bool Channel::groupSelfHandleIsLocalPending() const { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupSelfHandleIsLocalPending() used when " "channel not ready"; return false; } return mPriv->groupLocalPendingContacts.contains(mPriv->groupSelfHandle); } /** * Attempt to add the local user to this channel. In some channel types, * such as Text and StreamedMedia, this is used to accept an invitation or an * incoming call. * * This method requires Channel::FeatureCore to be ready. * * \return A PendingOperation which will emit PendingOperation::finished * when the call has finished. */ PendingOperation *Channel::groupAddSelfHandle() { if (!isReady(Channel::FeatureCore)) { warning() << "Channel::groupAddSelfHandle() used when channel not " "ready"; return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), QLatin1String("Channel object not ready"), ChannelPtr(this)); } UIntList handles; if (mPriv->groupSelfHandle == 0) { handles << mPriv->connection->selfHandle(); } else { handles << mPriv->groupSelfHandle; } return new PendingVoid( mPriv->group->AddMembers(handles, QLatin1String("")), ChannelPtr(this)); } //@} /** * Return whether this channel implements the conference interface * (#TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE is in the list returned by interfaces()). * * This method requires Channel::FeatureCore to be ready. * * \return \c true if the conference interface is supported, \c false otherwise. */ bool Channel::isConference() const { return hasInterface(TP_QT4_IFACE_CHANNEL_INTERFACE_CONFERENCE); } /** * Return a list of contacts invited to this conference when it was created. * * This method requires Channel::FeatureConferenceInitialInviteeContacts to be ready. * * \return A set of pointers to the Contact objects. */ Contacts Channel::conferenceInitialInviteeContacts() const { return mPriv->conferenceInitialInviteeContacts; } /** * Return the individual channels that are part of this conference. * * Change notification is via the conferenceChannelMerged() and * conferenceChannelRemoved() signals. * * Note that the returned channels are not guaranteed to be ready. Calling * Channel::becomeReady() may be needed. * * This method requires Channel::FeatureCore to be ready. * * \return A list of pointers to Channel objects containing all channels in the conference. * \sa conferenceInitialChannels(), conferenceOriginalChannels() */ QList Channel::conferenceChannels() const { return mPriv->conferenceChannels.values(); } /** * Return the initial value of conferenceChannels(). * * Note that the returned channels are not guaranteed to be ready. Calling * Channel::becomeReady() may be needed. * * This method requires Channel::FeatureCore to be ready. * * \return A list of pointers to Channel objects containing all channels that were initially * part of the conference. * \sa conferenceChannels(), conferenceOriginalChannels() */ QList Channel::conferenceInitialChannels() const { return mPriv->conferenceInitialChannels.values(); } /** * Return a map between channel specific handles and the corresponding channels of this conference. * * This method is only relevant on GSM conference calls where it is possible to have the same phone * number in a conference twice; for instance, it could be the number of a corporate switchboard. * This is represented using channel-specific handles; whether or not a channel uses * channel-specific handles is reported in groupFlags(). The groupHandleOwners() specifies the * mapping from opaque channel-specific handles to actual numbers; this property specifies the * original 1-1 channel corresponding to each channel-specific handle in the conference. * * In protocols where this situation cannot arise, such as XMPP, this method will return an empty * hash. * * Example, consider this situation: * 1. Place a call (with path /call/to/simon) to the contact +441234567890 (which is assigned the * handle h, say), and ask to be put through to Simon McVittie; * 2. Put that call on hold; * 3. Place another call (with path /call/to/jonny) to +441234567890, and ask to be put through to * Jonny Lamb; * 4. Request a new conference channel with initial channels: ['/call/to/simon', '/call/to/jonny']. * * The new channel will have the following properties, for some handles s and j: * * { * groupFlags(): ChannelGroupFlagChannelSpecificHandles | (other flags), * groupMembers(): [self handle, s, j], * groupHandleOwners(): { s: h, j: h }, * conferenceInitialChannels(): ['/call/to/simon', '/call/to/jonny'], * conferenceChannels(): ['/call/to/simon', '/call/to/jonny'], * conferenceOriginalChannels(): { s: '/call/to/simon', j: '/call/to/jonny' }, * # ... * } * * Note that the returned channels are not guaranteed to be ready. Calling * Channel::becomeReady() may be needed. * * This method requires Channel::FeatureCore to be ready. * * \return A map of channel specific handles to pointers to Channel objects. * \sa conferenceChannels(), conferenceInitialChannels() */ QHash Channel::conferenceOriginalChannels() const { return mPriv->conferenceOriginalChannels; } /** * Return whether this channel supports conference merging using conferenceMergeChannel(). * * This method requires Channel::FeatureCore to be ready. * * \return \c true if the interface is supported, \c false otherwise. * \sa conferenceMergeChannel() */ bool Channel::supportsConferenceMerging() const { return interfaces().contains(QLatin1String( TP_FUTURE_INTERFACE_CHANNEL_INTERFACE_MERGEABLE_CONFERENCE)); } /** * Request that the given channel be incorporated into this channel. * * This method requires Channel::FeatureCore to be ready. * * \return A PendingOperation which will emit PendingOperation::finished * when the call has finished. * \sa supportsConferenceMerging() */ PendingOperation *Channel::conferenceMergeChannel(const ChannelPtr &channel) { if (!supportsConferenceMerging()) { return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), QLatin1String("Channel does not support MergeableConference interface"), ChannelPtr(this)); } return new PendingVoid(mPriv->mergeableConferenceInterface()->Merge( QDBusObjectPath(channel->objectPath())), ChannelPtr(this)); } /** * Return whether this channel supports splitting using conferenceSplitChannel(). * * This method requires Channel::FeatureCore to be ready. * * \return \c true if the interface is supported, \c false otherwise. * \sa conferenceSplitChannel() */ bool Channel::supportsConferenceSplitting() const { return interfaces().contains(QLatin1String( TP_FUTURE_INTERFACE_CHANNEL_INTERFACE_SPLITTABLE)); } /** * Request that this channel is removed from any conference of which it is * a part. * * This method requires Channel::FeatureCore to be ready. * * \return A PendingOperation which will emit PendingOperation::finished * when the call has finished. * \sa supportsConferenceSplitting() */ PendingOperation *Channel::conferenceSplitChannel() { if (!supportsConferenceSplitting()) { return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), QLatin1String("Channel does not support Splittable interface"), ChannelPtr(this)); } return new PendingVoid(mPriv->splittableInterface()->Split(), ChannelPtr(this)); } /** * Return the Client::ChannelInterface interface proxy object for this channel. * This method is protected since the convenience methods provided by this * class should generally be used instead of calling D-Bus methods * directly. * * \return A pointer to the existing Client::ChannelInterface object for this * Channel object. */ Client::ChannelInterface *Channel::baseInterface() const { return mPriv->baseInterface; } void Channel::gotMainProperties(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; QVariantMap props; if (!reply.isError()) { debug() << "Got reply to Properties::GetAll(Channel)"; props = reply.value(); } else { warning().nospace() << "Properties::GetAll(Channel) failed with " << reply.error().name() << ": " << reply.error().message(); } mPriv->extractMainProps(props); mPriv->continueIntrospection(); } void Channel::gotChannelType(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (reply.isError()) { warning().nospace() << "Channel::GetChannelType() failed with " << reply.error().name() << ": " << reply.error().message() << ", Channel officially dead"; invalidate(reply.error()); return; } debug() << "Got reply to fallback Channel::GetChannelType()"; mPriv->channelType = reply.value(); mPriv->continueIntrospection(); } void Channel::gotHandle(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (reply.isError()) { warning().nospace() << "Channel::GetHandle() failed with " << reply.error().name() << ": " << reply.error().message() << ", Channel officially dead"; invalidate(reply.error()); return; } debug() << "Got reply to fallback Channel::GetHandle()"; mPriv->targetHandleType = reply.argumentAt<0>(); mPriv->targetHandle = reply.argumentAt<1>(); mPriv->continueIntrospection(); } void Channel::gotInterfaces(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (reply.isError()) { warning().nospace() << "Channel::GetInterfaces() failed with " << reply.error().name() << ": " << reply.error().message() << ", Channel officially dead"; invalidate(reply.error()); return; } debug() << "Got reply to fallback Channel::GetInterfaces()"; setInterfaces(reply.value()); mPriv->readinessHelper->setInterfaces(interfaces()); mPriv->nowHaveInterfaces(); mPriv->fakeGroupInterfaceIfNeeded(); mPriv->continueIntrospection(); } void Channel::onClosed() { debug() << "Got Channel::Closed"; QString error; QString message; if (mPriv->groupSelfContactRemoveInfo.isValid() && mPriv->groupSelfContactRemoveInfo.hasReason()) { error = mPriv->groupMemberChangeDetailsTelepathyError( mPriv->groupSelfContactRemoveInfo); message = mPriv->groupSelfContactRemoveInfo.message(); } else { error = TP_QT4_ERROR_CANCELLED; message = QLatin1String("channel closed"); } invalidate(error, message); } void Channel::onConnectionReady(PendingOperation *op) { if (op->isError()) { invalidate(op->errorName(), op->errorMessage()); return; } // FIXME: should connect to selfHandleChanged and act accordingly, but that is a PITA for // keeping the Contacts built and even if we don't do it, the new code is better than the // old one anyway because earlier on we just wouldn't have had a self contact. // // besides, the only thing which breaks without connecting in the world likely is if you're // using idle and decide to change your nick, which I don't think we necessarily even have API // to do from tp-qt4 anyway (or did I make idle change the nick when setting your alias? can't // remember) // // Simply put, I just don't care ATM. // Will be overwritten by the group self handle, if we can discover any. Q_ASSERT(!mPriv->groupSelfHandle); mPriv->groupSelfHandle = mPriv->connection->selfHandle(); mPriv->introspectMainProperties(); } void Channel::onConnectionInvalidated() { debug() << "Owning connection died leaving an orphan Channel, " "changing to closed"; invalidate(QLatin1String(TP_QT4_ERROR_ORPHANED), QLatin1String("Connection given as the owner of this channel was invalidated")); } void Channel::gotGroupProperties(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; QVariantMap props; if (!reply.isError()) { debug() << "Got reply to Properties::GetAll(Channel.Interface.Group)"; props = reply.value(); } else { warning().nospace() << "Properties::GetAll(Channel.Interface.Group) " "failed with " << reply.error().name() << ": " << reply.error().message(); } mPriv->extract0176GroupProps(props); // Add extraction (and possible fallbacks) in similar functions, called from here mPriv->continueIntrospection(); } void Channel::gotGroupFlags(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (reply.isError()) { warning().nospace() << "Channel.Interface.Group::GetGroupFlags() failed with " << reply.error().name() << ": " << reply.error().message(); } else { debug() << "Got reply to fallback Channel.Interface.Group::GetGroupFlags()"; mPriv->setGroupFlags(reply.value()); if (mPriv->groupFlags & ChannelGroupFlagProperties) { warning() << " Reply included ChannelGroupFlagProperties, even " "though properties specified in 0.17.7 didn't work! - unsetting"; mPriv->groupFlags &= ~ChannelGroupFlagProperties; } } mPriv->continueIntrospection(); } void Channel::gotAllMembers(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (reply.isError()) { warning().nospace() << "Channel.Interface.Group::GetAllMembers() failed with " << reply.error().name() << ": " << reply.error().message(); } else { debug() << "Got reply to fallback Channel.Interface.Group::GetAllMembers()"; mPriv->groupInitialMembers = reply.argumentAt<0>(); mPriv->groupInitialRP = reply.argumentAt<2>(); foreach (uint handle, reply.argumentAt<1>()) { LocalPendingInfo info = {handle, 0, ChannelGroupChangeReasonNone, QLatin1String("")}; mPriv->groupInitialLP.push_back(info); } } mPriv->continueIntrospection(); } void Channel::gotLocalPendingMembersWithInfo(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (reply.isError()) { warning().nospace() << "Channel.Interface.Group::GetLocalPendingMembersWithInfo() " "failed with " << reply.error().name() << ": " << reply.error().message(); warning() << " Falling back to what GetAllMembers returned with no extended info"; } else { debug() << "Got reply to fallback " "Channel.Interface.Group::GetLocalPendingMembersWithInfo()"; // Overrides the previous vague list provided by gotAllMembers mPriv->groupInitialLP = reply.value(); } mPriv->continueIntrospection(); } void Channel::gotSelfHandle(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; if (reply.isError()) { warning().nospace() << "Channel.Interface.Group::GetSelfHandle() failed with " << reply.error().name() << ": " << reply.error().message(); } else { debug() << "Got reply to fallback Channel.Interface.Group::GetSelfHandle()"; // Don't overwrite the self handle we got from the connection with 0 if (reply.value()) { mPriv->groupSelfHandle = reply.value(); } } mPriv->nowHaveInitialMembers(); mPriv->continueIntrospection(); } void Channel::gotContacts(PendingOperation *op) { PendingContacts *pending = qobject_cast(op); mPriv->buildingContacts = false; QList contacts; if (pending->isValid()) { contacts = pending->contacts(); if (!pending->invalidHandles().isEmpty()) { warning() << "Unable to construct Contact objects for handles:" << pending->invalidHandles(); if (mPriv->groupSelfHandle && pending->invalidHandles().contains(mPriv->groupSelfHandle)) { warning() << "Unable to retrieve self contact"; mPriv->groupSelfContact.reset(); emit groupSelfContactChanged(); } } } else { warning().nospace() << "Getting contacts failed with " << pending->errorName() << ":" << pending->errorMessage(); } mPriv->updateContacts(contacts); } void Channel::onGroupFlagsChanged(uint added, uint removed) { debug().nospace() << "Got Channel.Interface.Group::GroupFlagsChanged(" << hex << added << ", " << removed << ")"; added &= ~(mPriv->groupFlags); removed &= mPriv->groupFlags; debug().nospace() << "Arguments after filtering (" << hex << added << ", " << removed << ")"; uint groupFlags = mPriv->groupFlags; groupFlags |= added; groupFlags &= ~removed; // just emit groupFlagsChanged and related signals if the flags really // changed and we are ready if (mPriv->setGroupFlags(groupFlags) && isReady(Channel::FeatureCore)) { debug() << "Emitting groupFlagsChanged with" << mPriv->groupFlags << "value" << added << "added" << removed << "removed"; emit groupFlagsChanged((ChannelGroupFlags) mPriv->groupFlags, (ChannelGroupFlags) added, (ChannelGroupFlags) removed); if (added & ChannelGroupFlagCanAdd || removed & ChannelGroupFlagCanAdd) { debug() << "Emitting groupCanAddContactsChanged"; emit groupCanAddContactsChanged(groupCanAddContacts()); } if (added & ChannelGroupFlagCanRemove || removed & ChannelGroupFlagCanRemove) { debug() << "Emitting groupCanRemoveContactsChanged"; emit groupCanRemoveContactsChanged(groupCanRemoveContacts()); } if (added & ChannelGroupFlagCanRescind || removed & ChannelGroupFlagCanRescind) { debug() << "Emitting groupCanRescindContactsChanged"; emit groupCanRescindContactsChanged(groupCanRescindContacts()); } } } void Channel::onMembersChanged(const QString &message, const UIntList &added, const UIntList &removed, const UIntList &localPending, const UIntList &remotePending, uint actor, uint reason) { // Ignore the signal if we're using the MCD signal to not duplicate events if (mPriv->usingMembersChangedDetailed) { return; } debug() << "Got Channel.Interface.Group::MembersChanged with" << added.size() << "added," << removed.size() << "removed," << localPending.size() << "moved to LP," << remotePending.size() << "moved to RP," << actor << "being the actor," << reason << "the reason and" << message << "the message"; debug() << " synthesizing a corresponding MembersChangedDetailed signal"; QVariantMap details; if (!message.isEmpty()) { details.insert(QLatin1String("message"), message); } if (actor != 0) { details.insert(QLatin1String("actor"), actor); } details.insert(QLatin1String("change-reason"), reason); mPriv->doMembersChangedDetailed(added, removed, localPending, remotePending, details); } void Channel::onMembersChangedDetailed( const UIntList &added, const UIntList &removed, const UIntList &localPending, const UIntList &remotePending, const QVariantMap &details) { // Ignore the signal if we aren't (yet) using MCD to not duplicate events if (!mPriv->usingMembersChangedDetailed) { return; } debug() << "Got Channel.Interface.Group::MembersChangedDetailed with" << added.size() << "added," << removed.size() << "removed," << localPending.size() << "moved to LP," << remotePending.size() << "moved to RP and with" << details.size() << "details"; mPriv->doMembersChangedDetailed(added, removed, localPending, remotePending, details); } void Channel::Private::doMembersChangedDetailed( const UIntList &added, const UIntList &removed, const UIntList &localPending, const UIntList &remotePending, const QVariantMap &details) { if (!groupHaveMembers) { debug() << "Still waiting for initial group members, " "so ignoring delta signal..."; return; } if (added.isEmpty() && removed.isEmpty() && localPending.isEmpty() && remotePending.isEmpty()) { debug() << "Nothing really changed, so skipping membersChanged"; return; } // let's store groupSelfContactRemoveInfo here as we may not have time // to build the contacts in case self contact is removed, // as Closed will be emitted right after if (removed.contains(groupSelfHandle)) { if (qdbus_cast(details.value(QLatin1String("change-reason"))) == ChannelGroupChangeReasonRenamed) { if (removed.size() != 1 || (added.size() + localPending.size() + remotePending.size()) != 1) { // spec-incompliant CM, ignoring members changed warning() << "Received MembersChangedDetailed with reason " "Renamed and removed.size != 1 or added.size + " "localPending.size + remotePending.size != 1. Ignoring"; return; } uint newHandle = 0; if (!added.isEmpty()) { newHandle = added.first(); } else if (!localPending.isEmpty()) { newHandle = localPending.first(); } else if (!remotePending.isEmpty()) { newHandle = remotePending.first(); } parent->onSelfHandleChanged(newHandle); return; } // let's try to get the actor contact from contact manager if available groupSelfContactRemoveInfo = GroupMemberChangeDetails( connection->contactManager()->lookupContactByHandle( qdbus_cast(details.value(QLatin1String("actor")))), details); } HandleIdentifierMap contactIds = qdbus_cast( details.value(GroupMembersChangedInfo::keyContactIds)); connection->lowlevel()->injectContactIds(contactIds); groupMembersChangedQueue.enqueue( new Private::GroupMembersChangedInfo( added, removed, localPending, remotePending, details)); if (!buildingContacts) { // if we are building contacts, we should wait it to finish so we don't // present the user with wrong information processMembersChanged(); } } void Channel::onHandleOwnersChanged(const HandleOwnerMap &added, const UIntList &removed) { debug() << "Got Channel.Interface.Group::HandleOwnersChanged with" << added.size() << "added," << removed.size() << "removed"; if (!mPriv->groupAreHandleOwnersAvailable) { debug() << "Still waiting for initial handle owners, so ignoring " "delta signal..."; return; } UIntList emitAdded; UIntList emitRemoved; for (HandleOwnerMap::const_iterator i = added.begin(); i != added.end(); ++i) { uint handle = i.key(); uint global = i.value(); if (!mPriv->groupHandleOwners.contains(handle) || mPriv->groupHandleOwners[handle] != global) { debug() << " +++/changed" << handle << "->" << global; mPriv->groupHandleOwners[handle] = global; emitAdded.append(handle); } } foreach (uint handle, removed) { if (mPriv->groupHandleOwners.contains(handle)) { debug() << " ---" << handle; mPriv->groupHandleOwners.remove(handle); emitRemoved.append(handle); } } // just emit groupHandleOwnersChanged if it really changed and // we are ready if ((emitAdded.size() || emitRemoved.size()) && isReady(Channel::FeatureCore)) { debug() << "Emitting groupHandleOwnersChanged with" << emitAdded.size() << "added" << emitRemoved.size() << "removed"; emit groupHandleOwnersChanged(mPriv->groupHandleOwners, emitAdded, emitRemoved); } } void Channel::onSelfHandleChanged(uint selfHandle) { debug().nospace() << "Got Channel.Interface.Group::SelfHandleChanged"; if (selfHandle != mPriv->groupSelfHandle) { mPriv->groupSelfHandle = selfHandle; debug() << " Emitting groupSelfHandleChanged with new self handle" << selfHandle; // FIXME: fix self contact building with no group mPriv->pendingRetrieveGroupSelfContact = true; } } void Channel::gotConferenceProperties(QDBusPendingCallWatcher *watcher) { QDBusPendingReply reply = *watcher; QVariantMap props; mPriv->introspectingConference = false; if (!reply.isError()) { debug() << "Got reply to Properties::GetAll(Channel.Interface.Conference)"; props = reply.value(); ConnectionPtr conn = connection(); ChannelFactoryConstPtr chanFactory = conn->channelFactory(); ObjectPathList channels = qdbus_cast(props[QLatin1String("Channels")]); foreach (const QDBusObjectPath &channelPath, channels) { if (mPriv->conferenceChannels.contains(channelPath.path())) { continue; } PendingReady *readyOp = chanFactory->proxy(conn, channelPath.path(), QVariantMap()); ChannelPtr channel(ChannelPtr::qObjectCast(readyOp->proxy())); Q_ASSERT(!channel.isNull()); mPriv->conferenceChannels.insert(channelPath.path(), channel); } ObjectPathList initialChannels = qdbus_cast(props[QLatin1String("InitialChannels")]); foreach (const QDBusObjectPath &channelPath, initialChannels) { if (mPriv->conferenceInitialChannels.contains(channelPath.path())) { continue; } PendingReady *readyOp = chanFactory->proxy(conn, channelPath.path(), QVariantMap()); ChannelPtr channel(ChannelPtr::qObjectCast(readyOp->proxy())); Q_ASSERT(!channel.isNull()); mPriv->conferenceInitialChannels.insert(channelPath.path(), channel); } mPriv->conferenceInitialInviteeHandles = qdbus_cast(props[QLatin1String("InitialInviteeHandles")]); QStringList conferenceInitialInviteeIds = qdbus_cast(props[QLatin1String("InitialInviteeIDs")]); if (mPriv->conferenceInitialInviteeHandles.size() == conferenceInitialInviteeIds.size()) { HandleIdentifierMap contactIds; int i = 0; foreach (uint handle, mPriv->conferenceInitialInviteeHandles) { contactIds.insert(handle, conferenceInitialInviteeIds.at(i++)); } mPriv->connection->lowlevel()->injectContactIds(contactIds); } mPriv->conferenceInvitationMessage = qdbus_cast(props[QLatin1String("InvitationMessage")]); ChannelOriginatorMap originalChannels = qdbus_cast( props[QLatin1String("OriginalChannels")]); for (ChannelOriginatorMap::const_iterator i = originalChannels.constBegin(); i != originalChannels.constEnd(); ++i) { PendingReady *readyOp = chanFactory->proxy(conn, i.value().path(), QVariantMap()); ChannelPtr channel(ChannelPtr::qObjectCast(readyOp->proxy())); Q_ASSERT(!channel.isNull()); mPriv->conferenceOriginalChannels.insert(i.key(), channel); } } else { warning().nospace() << "Properties::GetAll(Channel.Interface.Conference) " "failed with " << reply.error().name() << ": " << reply.error().message(); } mPriv->continueIntrospection(); } void Channel::gotConferenceInitialInviteeContacts(PendingOperation *op) { PendingContacts *pending = qobject_cast(op); if (pending->isValid()) { mPriv->conferenceInitialInviteeContacts = pending->contacts().toSet(); } else { warning().nospace() << "Getting conference initial invitee contacts " "failed with " << pending->errorName() << ":" << pending->errorMessage(); } mPriv->readinessHelper->setIntrospectCompleted( FeatureConferenceInitialInviteeContacts, true); } void Channel::onConferenceChannelMerged(const QDBusObjectPath &channelPath, uint channelSpecificHandle, const QVariantMap &properties) { if (mPriv->conferenceChannels.contains(channelPath.path())) { return; } ConnectionPtr conn = connection(); ChannelFactoryConstPtr chanFactory = conn->channelFactory(); PendingReady *readyOp = chanFactory->proxy(conn, channelPath.path(), properties); ChannelPtr channel(ChannelPtr::qObjectCast(readyOp->proxy())); Q_ASSERT(!channel.isNull()); mPriv->conferenceChannels.insert(channelPath.path(), channel); emit conferenceChannelMerged(channel); if (channelSpecificHandle != 0) { mPriv->conferenceOriginalChannels.insert(channelSpecificHandle, channel); } } void Channel::onConferenceChannelMerged(const QDBusObjectPath &channelPath) { onConferenceChannelMerged(channelPath, 0, QVariantMap()); } void Channel::onConferenceChannelRemoved(const QDBusObjectPath &channelPath, const QVariantMap &details) { if (!mPriv->conferenceChannels.contains(channelPath.path())) { return; } HandleIdentifierMap contactIds = qdbus_cast( details.value(Private::GroupMembersChangedInfo::keyContactIds)); mPriv->connection->lowlevel()->injectContactIds(contactIds); mPriv->conferenceChannelRemovedQueue.enqueue( new Private::ConferenceChannelRemovedInfo(channelPath, details)); mPriv->processConferenceChannelRemoved(); } void Channel::onConferenceChannelRemoved(const QDBusObjectPath &channelPath) { onConferenceChannelRemoved(channelPath, QVariantMap()); } void Channel::gotConferenceChannelRemovedActorContact(PendingOperation *op) { ContactPtr actorContact; if (op) { PendingContacts *pc = qobject_cast(op); if (pc->isValid()) { Q_ASSERT(pc->contacts().size() == 1); actorContact = pc->contacts().first(); } else { warning().nospace() << "Getting conference channel removed actor " "failed with " << pc->errorName() << ":" << pc->errorMessage(); } } Private::ConferenceChannelRemovedInfo *info = mPriv->conferenceChannelRemovedQueue.dequeue(); ChannelPtr channel = mPriv->conferenceChannels[info->channelPath.path()]; mPriv->conferenceChannels.remove(info->channelPath.path()); emit conferenceChannelRemoved(channel, GroupMemberChangeDetails(actorContact, info->details)); for (QHash::iterator i = mPriv->conferenceOriginalChannels.begin(); i != mPriv->conferenceOriginalChannels.end();) { if (i.value() == channel) { i = mPriv->conferenceOriginalChannels.erase(i); } else { ++i; } } delete info; mPriv->buildingConferenceChannelRemovedActorContact = false; mPriv->processConferenceChannelRemoved(); } /** * \fn void Channel::groupFlagsChanged(uint flags, uint added, uint removed) * * Emitted when the value of groupFlags() changes. * * \param flags The value which would now be returned by groupFlags(). * \param added Flags added compared to the previous value. * \param removed Flags removed compared to the previous value. */ /** * \fn void Channel::groupCanAddContactsChanged(bool canAddContacts) * * Emitted when the value of groupCanAddContacts() changes. * * \param canAddContacts Whether a contact can be added to this channel. * \sa groupCanAddContacts() */ /** * \fn void Channel::groupCanRemoveContactsChanged(bool canRemoveContacts) * * Emitted when the value of groupCanRemoveContacts() changes. * * \param canRemoveContacts Whether a contact can be removed from this channel. * \sa groupCanRemoveContacts() */ /** * \fn void Channel::groupCanRescindContactsChanged(bool canRescindContacts) * * Emitted when the value of groupCanRescindContacts() changes. * * \param canRescindContacts Whether contact invitations can be rescinded. * \sa groupCanRescindContacts() */ /** * \fn void Channel::groupMembersChanged( * const Tp::Contacts &groupMembersAdded, * const Tp::Contacts &groupLocalPendingMembersAdded, * const Tp::Contacts &groupRemotePendingMembersAdded, * const Tp::Contacts &groupMembersRemoved, * const Channel::GroupMemberChangeDetails &details) * * Emitted when the value returned by groupContacts(), groupLocalPendingContacts() or * groupRemotePendingContacts() changes. * * \param groupMembersAdded The contacts that were added to this channel. * \param groupLocalPendingMembersAdded The local pending contacts that were * added to this channel. * \param groupRemotePendingMembersAdded The remote pending contacts that were * added to this channel. * \param groupMembersRemoved The contacts removed from this channel. * \param details Additional details such as the contact requesting or causing * the change. */ /** * \fn void Channel::groupHandleOwnersChanged(const HandleOwnerMap &owners, * const Tp::UIntList &added, const Tp::UIntList &removed) * * Emitted when the value returned by groupHandleOwners() changes. * * \param owners The value which would now be returned by * groupHandleOwners(). * \param added Handles which have been added to the mapping as keys, or * existing handle keys for which the mapped-to value has changed. * \param removed Handles which have been removed from the mapping. */ /** * \fn void Channel::groupSelfContactChanged() * * Emitted when the value returned by groupSelfContact() changes. */ /** * \fn void Channel::conferenceChannelMerged(const Tp::ChannelPtr &channel) * * Emitted when a new channel is added to the value of conferenceChannels(). * * \param channel The channel that was added to conferenceChannels(). */ /** * \fn void Channel::conferenceChannelRemoved(const Tp::ChannelPtr &channel, * const Tp::Channel::GroupMemberChangeDetails &details) * * Emitted when a new channel is removed from the value of conferenceChannels(). * * \param channel The channel that was removed from conferenceChannels(). * \param details The change details. */ } // Tp