diff options
author | Jeremy Whiting <jeremy.whiting@collabora.com> | 2011-11-10 15:21:06 -0700 |
---|---|---|
committer | Andre Moreira Magalhaes (andrunko) <andre.magalhaes@collabora.co.uk> | 2011-11-24 14:45:44 -0200 |
commit | be40b6f313c8d7b1f0fb59fd06ea87b0934e4bad (patch) | |
tree | ed66d713ce0d85acf0bcb65d3145693e4d150dae /TelepathyQt | |
parent | aafde57c570a56bb98df4103a4ee38ed25b91897 (diff) |
Renamed TelepathyQt4 directory to TelepathyQt.
Diffstat (limited to 'TelepathyQt')
459 files changed, 67731 insertions, 0 deletions
diff --git a/TelepathyQt/AbstractClient b/TelepathyQt/AbstractClient new file mode 100644 index 00000000..7f0c94af --- /dev/null +++ b/TelepathyQt/AbstractClient @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AbstractClient_HEADER_GUARD_ +#define _TelepathyQt_AbstractClient_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/abstract-client.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AbstractClientApprover b/TelepathyQt/AbstractClientApprover new file mode 100644 index 00000000..d264aae9 --- /dev/null +++ b/TelepathyQt/AbstractClientApprover @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AbstractClientApprover_HEADER_GUARD_ +#define _TelepathyQt_AbstractClientApprover_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/abstract-client.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AbstractClientHandler b/TelepathyQt/AbstractClientHandler new file mode 100644 index 00000000..0bec382e --- /dev/null +++ b/TelepathyQt/AbstractClientHandler @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AbstractClientHandler_HEADER_GUARD_ +#define _TelepathyQt_AbstractClientHandler_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/abstract-client.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AbstractClientObserver b/TelepathyQt/AbstractClientObserver new file mode 100644 index 00000000..ae779798 --- /dev/null +++ b/TelepathyQt/AbstractClientObserver @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AbstractClientObserver_HEADER_GUARD_ +#define _TelepathyQt_AbstractClientObserver_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/abstract-client.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AbstractInterface b/TelepathyQt/AbstractInterface new file mode 100644 index 00000000..fd780b61 --- /dev/null +++ b/TelepathyQt/AbstractInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AbstractInterface_HEADER_GUARD_ +#define _TelepathyQt_AbstractInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/abstract-interface.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Account b/TelepathyQt/Account new file mode 100644 index 00000000..9c2387e9 --- /dev/null +++ b/TelepathyQt/Account @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Account_HEADER_GUARD_ +#define _TelepathyQt_Account_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/account.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AccountCapabilityFilter b/TelepathyQt/AccountCapabilityFilter new file mode 100644 index 00000000..29a7253f --- /dev/null +++ b/TelepathyQt/AccountCapabilityFilter @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AccountCapabilityFilter_HEADER_GUARD_ +#define _TelepathyQt_AccountCapabilityFilter_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/account-capability-filter.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AccountFactory b/TelepathyQt/AccountFactory new file mode 100644 index 00000000..e8a23acf --- /dev/null +++ b/TelepathyQt/AccountFactory @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AccountFactory_HEADER_GUARD_ +#define _TelepathyQt_AccountFactory_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/account-factory.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AccountFilter b/TelepathyQt/AccountFilter new file mode 100644 index 00000000..643d92e4 --- /dev/null +++ b/TelepathyQt/AccountFilter @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AccountFilter_HEADER_GUARD_ +#define _TelepathyQt_AccountFilter_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/account-filter.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AccountInterface b/TelepathyQt/AccountInterface new file mode 100644 index 00000000..f9f45617 --- /dev/null +++ b/TelepathyQt/AccountInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AccountInterface_HEADER_GUARD_ +#define _TelepathyQt_AccountInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/account.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AccountInterfaceAddressingInterface b/TelepathyQt/AccountInterfaceAddressingInterface new file mode 100644 index 00000000..c5fc153a --- /dev/null +++ b/TelepathyQt/AccountInterfaceAddressingInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AccountInterfaceAddressingInterface_HEADER_GUARD_ +#define _TelepathyQt_AccountInterfaceAddressingInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/account.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AccountInterfaceAvatarInterface b/TelepathyQt/AccountInterfaceAvatarInterface new file mode 100644 index 00000000..b1d9e2e7 --- /dev/null +++ b/TelepathyQt/AccountInterfaceAvatarInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AccountInterfaceAvatarInterface_HEADER_GUARD_ +#define _TelepathyQt_AccountInterfaceAvatarInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/account.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AccountManager b/TelepathyQt/AccountManager new file mode 100644 index 00000000..2ca7a6e5 --- /dev/null +++ b/TelepathyQt/AccountManager @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AccountManager_HEADER_GUARD_ +#define _TelepathyQt_AccountManager_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/account-manager.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AccountManagerInterface b/TelepathyQt/AccountManagerInterface new file mode 100644 index 00000000..0266ec43 --- /dev/null +++ b/TelepathyQt/AccountManagerInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AccountManagerInterface_HEADER_GUARD_ +#define _TelepathyQt_AccountManagerInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/account-manager.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AccountPropertyFilter b/TelepathyQt/AccountPropertyFilter new file mode 100644 index 00000000..7f493242 --- /dev/null +++ b/TelepathyQt/AccountPropertyFilter @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AccountPropertyFilter_HEADER_GUARD_ +#define _TelepathyQt_AccountPropertyFilter_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/account-property-filter.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AccountSet b/TelepathyQt/AccountSet new file mode 100644 index 00000000..a60e4a40 --- /dev/null +++ b/TelepathyQt/AccountSet @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AccountSet_HEADER_GUARD_ +#define _TelepathyQt_AccountSet_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/account-set.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AndFilter b/TelepathyQt/AndFilter new file mode 100644 index 00000000..f9f8eef8 --- /dev/null +++ b/TelepathyQt/AndFilter @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AndFilter_HEADER_GUARD_ +#define _TelepathyQt_AndFilter_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/and-filter.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AuthenticationTLSCertificateInterface b/TelepathyQt/AuthenticationTLSCertificateInterface new file mode 100644 index 00000000..613f93cb --- /dev/null +++ b/TelepathyQt/AuthenticationTLSCertificateInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AuthenticationTLSCertificateInterface_HEADER_GUARD_ +#define _TelepathyQt_AuthenticationTLSCertificateInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/tls-certificate.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AvatarData b/TelepathyQt/AvatarData new file mode 100644 index 00000000..1615d6f2 --- /dev/null +++ b/TelepathyQt/AvatarData @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AvatarData_HEADER_GUARD_ +#define _TelepathyQt_AvatarData_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/avatar.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/AvatarSpec b/TelepathyQt/AvatarSpec new file mode 100644 index 00000000..838f9192 --- /dev/null +++ b/TelepathyQt/AvatarSpec @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_AvatarSpec_HEADER_GUARD_ +#define _TelepathyQt_AvatarSpec_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/avatar.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/CMakeLists.txt b/TelepathyQt/CMakeLists.txt new file mode 100644 index 00000000..69f127ca --- /dev/null +++ b/TelepathyQt/CMakeLists.txt @@ -0,0 +1,694 @@ +file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_gen") + +# Set the required flags found in TelepathyDefaults +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${VISIBILITY_HIDDEN_FLAGS} ${COMPILER_COVERAGE_FLAGS} ${DEPRECATED_DECLARATIONS_FLAGS}") +set(LD_FLAGS "${LD_FLAGS} ${VISIBILITY_HIDDEN_FLAGS} ${COMPILER_COVERAGE_FLAGS} ${DEPRECATED_DECLARATIONS_FLAGS}") + +# We are building Telepathy-Qt4 +add_definitions(-DBUILDING_TP_QT) + +# Sources for Tp-Qt4 +set(telepathy_qt4_SRCS + abstract-client.cpp + abstract-interface.cpp + account.cpp + account-factory.cpp + account-manager.cpp + account-property-filter.cpp + account-set.cpp + account-set-internal.h + avatar.cpp + capabilities-base.cpp + channel.cpp + channel-class-spec.cpp + channel-dispatcher.cpp + channel-dispatch-operation.cpp + channel-factory.cpp + channel-internal.h + channel-request.cpp + client.cpp + client-registrar.cpp + client-registrar-internal.h + connection.cpp + connection-capabilities.cpp + connection-factory.cpp + connection-internal.h + connection-manager.cpp + connection-manager-internal.h + contact.cpp + contact-capabilities.cpp + contact-factory.cpp + contact-manager.cpp + contact-manager-roster.cpp + contact-messenger.cpp + contact-search-channel.cpp + dbus.cpp + dbus-proxy.cpp + dbus-proxy-factory.cpp + dbus-proxy-factory-internal.h + debug.cpp + debug-internal.h + fake-handler-manager-internal.cpp + fake-handler-manager-internal.h + feature.cpp + file-transfer-channel.cpp + file-transfer-channel-creation-properties.cpp + fixed-feature-factory.cpp + future.cpp + future-internal.h + handled-channel-notifier.cpp + incoming-file-transfer-channel.cpp + incoming-stream-tube-channel.cpp + key-file.cpp + location-info.cpp + manager-file.cpp + media-session-handler.cpp + media-stream-handler.cpp + message.cpp + message-content-part.cpp + object.cpp + optional-interface-factory.cpp + outgoing-file-transfer-channel.cpp + outgoing-stream-tube-channel.cpp + pending-account.cpp + pending-channel.cpp + pending-channel-request.cpp + pending-channel-request-internal.h + pending-connection.cpp + pending-contact-attributes.cpp + pending-contact-info.cpp + pending-contacts.cpp + pending-handles.cpp + pending-operation.cpp + pending-ready.cpp + pending-send-message.cpp + pending-string-list.cpp + pending-stream-tube-connection.cpp + pending-variant.cpp + presence.cpp + pending-variant-map.cpp + profile.cpp + profile-manager.cpp + properties.cpp + protocol-info.cpp + protocol-parameter.cpp + readiness-helper.cpp + requestable-channel-class-spec.cpp + ready-object.cpp + referenced-handles.cpp + request-temporary-handler-internal.cpp + request-temporary-handler-internal.h + room-list-channel.cpp + simple-call-observer.cpp + simple-observer.cpp + simple-observer-internal.h + simple-stream-tube-handler.cpp + simple-text-observer.cpp + simple-text-observer-internal.h + stream-tube-channel.cpp + stream-tube-client.cpp + stream-tube-client-internal.h + stream-tube-server.cpp + stream-tube-server-internal.h + streamed-media-channel.cpp + test-backdoors.cpp + test-backdoors.h + text-channel.cpp + tls-certificate.cpp + tube-channel.cpp + types.cpp + types-internal.h + utils.cpp) + +# Exported headers for Tp-Qt4 +set(telepathy_qt4_HEADERS + AbstractClient + AbstractClientApprover + abstract-client.h + AbstractClientHandler + AbstractClientObserver + AbstractInterface + abstract-interface.h + Account + account.h + AccountCapabilityFilter + account-capability-filter.h + AccountFactory + account-factory.h + AccountFilter + account-filter.h + AccountInterface + AccountInterfaceAddressingInterface + AccountInterfaceAvatarInterface + AccountManager + account-manager.h + AccountManagerInterface + account-property-filter.h + AccountPropertyFilter + AccountSet + account-set.h + AndFilter + and-filter.h + AuthenticationTLSCertificateInterface + AvatarData + AvatarSpec + avatar.h + CapabilitiesBase + capabilities-base.h + Channel + channel.h + ChannelClassFeatures + channel-class-features.h + ChannelClassSpec + ChannelClassSpecList + channel-class-spec.h + ChannelDispatcher + ChannelDispatcherInterface + channel-dispatcher.h + ChannelDispatchOperation + channel-dispatch-operation.h + ChannelDispatchOperationInterface + ChannelFactory + channel-factory.h + ChannelInterface + ChannelInterfaceAnonymityInterface + ChannelInterfaceCallStateInterface + ChannelInterfaceChatStateInterface + ChannelInterfaceConferenceInterface + ChannelInterfaceDTMFInterface + ChannelInterfaceGroupInterface + ChannelInterfaceHoldInterface + ChannelInterfaceMediaSignallingInterface + ChannelInterfaceMessagesInterface + ChannelInterfacePasswordInterface + ChannelInterfaceSASLAuthenticationInterface + ChannelInterfaceSecurableInterface + ChannelInterfaceServicePointInterface + ChannelInterfaceTubeInterface + ChannelRequest + ChannelRequestHints + channel-request.h + ChannelRequestInterface + ChannelTypeContactListInterface + ChannelTypeContactSearchInterface + ChannelTypeFileTransferInterface + ChannelTypeRoomListInterface + ChannelTypeServerAuthenticationInterface + ChannelTypeServerTLSConnectionInterface + ChannelTypeStreamedMediaInterface + ChannelTypeStreamTubeInterface + ChannelTypeTextInterface + ChannelTypeTubeInterface + ChannelTypeTubesInterface + Client + ClientApproverInterface + client.h + ClientHandlerInterface + ClientInterface + ClientInterfaceRequestsInterface + ClientObserverInterface + ClientRegistrar + client-registrar.h + Connection + ConnectionCapabilities + connection-capabilities.h + connection.h + ConnectionFactory + connection-factory.h + connection-lowlevel.h + ConnectionInterface + ConnectionInterfaceAliasingInterface + ConnectionInterfaceAnonymityInterface + ConnectionInterfaceAvatarsInterface + ConnectionInterfaceBalanceInterface + ConnectionInterfaceCapabilitiesInterface + ConnectionInterfaceCellularInterface + ConnectionInterfaceClientTypes + ConnectionInterfaceContactBlockingInterface + ConnectionInterfaceClientTypesInterface + ConnectionInterfaceContactCapabilitiesInterface + ConnectionInterfaceContactGroups + ConnectionInterfaceContactGroupsInterface + ConnectionInterfaceContactInfoInterface + ConnectionInterfaceContactList + ConnectionInterfaceContactListInterface + ConnectionInterfaceContactsInterface + ConnectionInterfaceLocationInterface + ConnectionInterfaceMailNotificationInterface + ConnectionInterfacePowerSaving + ConnectionInterfacePowerSavingInterface + ConnectionInterfacePresenceInterface + ConnectionInterfaceRequestsInterface + ConnectionInterfaceServicePointInterface + ConnectionInterfaceSimplePresenceInterface + ConnectionLowlevel + ConnectionManager + connection-manager.h + connection-manager-lowlevel.h + ConnectionManagerInterface + ConnectionManagerLowlevel + Constants + constants.h + Contact + contact.h + ContactCapabilities + contact-capabilities.h + ContactFactory + contact-factory.h + ContactManager + contact-manager.h + ContactMessenger + contact-messenger.h + ContactSearchChannel + contact-search-channel.h + DBus + DBusDaemonInterface + dbus.h + DBusProxy + dbus-proxy.h + DBusProxyFactory + dbus-proxy-factory.h + Debug + debug.h + Feature + Features + feature.h + FileTransferChannel + FileTransferChannelCreationProperties + file-transfer-channel-creation-properties.h + file-transfer-channel.h + Filter + filter.h + FixedFeatureFactory + fixed-feature-factory.h + GenericCapabilityFilter + generic-capability-filter.h + GenericPropertyFilter + generic-property-filter.h + Global + global.h + HandledChannelNotifier + handled-channel-notifier.h + IncomingFileTransferChannel + incoming-file-transfer-channel.h + IncomingStreamTubeChannel + incoming-stream-tube-channel.h + IntrospectableInterface + KeyFile + key-file.h + LocationInfo + location-info.h + ManagerFile + manager-file.h + MediaSessionHandler + media-session-handler.h + MediaSessionHandlerInterface + MediaStreamHandler + media-stream-handler.h + MediaStreamHandlerInterface + Message + message.h + MessageContentPart + MessageContentPartList + message-content-part.h + MethodInvocationContext + method-invocation-context.h + NotFilter + not-filter.h + Object + object.h + OptionalInterfaceFactory + optional-interface-factory.h + OrFilter + or-filter.h + OutgoingFileTransferChannel + outgoing-file-transfer-channel.h + OutgoingStreamTubeChannel + outgoing-stream-tube-channel.h + PeerInterface + PendingAccount + pending-account.h + PendingChannel + pending-channel.h + PendingChannelRequest + pending-channel-request.h + PendingComposite + PendingConnection + pending-connection.h + PendingContactAttributes + pending-contact-attributes.h + PendingContactInfo + pending-contact-info.h + PendingContacts + pending-contacts.h + PendingFailure + PendingHandles + pending-handles.h + PendingOperation + pending-operation.h + PendingReady + pending-ready.h + PendingSendMessage + pending-send-message.h + PendingStreamedMediaStreams + PendingStreamTubeConnection + pending-stream-tube-connection.h + PendingStringList + pending-string-list.h + PendingSuccess + PendingVariant + pending-variant.h + PendingVariantMap + pending-variant-map.h + PendingVoid + Presence + presence.h + PresenceSpec + PresenceSpecList + Profile + profile.h + ProfileManager + profile-manager.h + Properties + properties.h + PropertiesInterface + PropertiesInterfaceInterface + ProtocolInfo + protocol-info.h + ProtocolParameter + protocol-parameter.h + ReadinessHelper + readiness-helper.h + ReadyObject + ready-object.h + ReceivedMessage + RefCounted + ReferencedHandles + referenced-handles.h + ReferencedHandlesIterator + requestable-channel-class-spec.h + RequestableChannelClassSpec + RequestableChannelClassSpecList + RoomListChannel + room-list-channel.h + SharedPtr + shared-ptr.h + SimpleCallObserver + simple-call-observer.h + SimpleObserver + simple-observer.h + simple-pending-operations.h + SimpleTextObserver + simple-text-observer.h + StatefulDBusProxy + StatelessDBusProxy + StreamTubeChannel + StreamTubeClient + StreamTubeServer + stream-tube-channel.h + stream-tube-client.h + stream-tube-server.h + StreamedMediaChannel + streamed-media-channel.h + StreamedMediaStream + TextChannel + text-channel.h + tls-certificate.h + TubeChannel + tube-channel.h + Types + types.h + Utils + utils.h) + +# Generated headers which will be installed and exported +set(telepathy_qt4_gen_HEADERS + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-account.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-account-manager.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-channel.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-channel-dispatcher.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-channel-dispatch-operation.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-channel-request.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-client.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-connection.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-connection-manager.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-dbus.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-media-session-handler.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-media-stream-handler.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-properties.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-tls-certificate.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/constants.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/types.h) + +# Headers file moc will be run on +set(telepathy_qt4_MOC_SRCS + abstract-interface.h + account.h + account-factory.h + account-manager.h + account-set.h + account-set-internal.h + channel.h + channel-dispatch-operation.h + channel-dispatch-operation-internal.h + channel-factory.h + channel-internal.h + channel-request.h + client-registrar.h + client-registrar-internal.h + connection.h + connection-internal.h + connection-lowlevel.h + connection-manager.h + connection-manager-internal.h + connection-manager-lowlevel.h + contact.h + contact-manager.h + contact-manager-internal.h + contact-messenger.h + contact-search-channel.h + contact-search-channel-internal.h + dbus-proxy.h + dbus-proxy-factory.h + dbus-proxy-factory-internal.h + fake-handler-manager-internal.h + file-transfer-channel.h + fixed-feature-factory.h + handled-channel-notifier.h + incoming-file-transfer-channel.h + incoming-stream-tube-channel.h + object.h + outgoing-file-transfer-channel.h + outgoing-stream-tube-channel.h + outgoing-stream-tube-channel-internal.h + pending-account.h + pending-channel.h + pending-channel-request.h + pending-channel-request-internal.h + pending-connection.h + pending-contact-attributes.h + pending-contact-info.h + pending-contacts.h + pending-handles.h + pending-operation.h + pending-ready.h + pending-send-message.h + pending-stream-tube-connection.h + pending-string-list.h + pending-variant.h + pending-variant-map.h + profile-manager.h + readiness-helper.h + request-temporary-handler-internal.h + room-list-channel.h + simple-call-observer.h + simple-pending-operations.h + simple-observer.h + simple-observer-internal.h + simple-stream-tube-handler.h + simple-text-observer.h + simple-text-observer-internal.h + stream-tube-channel.h + stream-tube-client.h + stream-tube-client-internal.h + stream-tube-server.h + stream-tube-server-internal.h + streamed-media-channel.h + text-channel.h + tube-channel.h) + +# Generate the spec files for both stable and future spec +set(gen_stable_spec_xml ${CMAKE_CURRENT_BINARY_DIR}/_gen/stable-spec.xml) +set(gen_future_spec_xml ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-spec.xml) + +tpqt_xincludator(stable-ifaces-includator ${CMAKE_CURRENT_SOURCE_DIR}/stable-interfaces.xml ${gen_stable_spec_xml}) +tpqt_xincludator(future-ifaces-includator ${CMAKE_CURRENT_SOURCE_DIR}/future-interfaces.xml ${gen_future_spec_xml}) + +add_custom_target(all-generated-sources) + +tpqt_constants_gen(stable-constants ${gen_stable_spec_xml} ${CMAKE_CURRENT_BINARY_DIR}/_gen/constants.h + --namespace=Tp + --str-constant-prefix=TELEPATHY_ + --define-prefix=TP_QT_ + --must-define=IN_TP_QT_HEADER + DEPENDS stable-ifaces-includator) +tpqt_constants_gen(future-constants ${gen_future_spec_xml} ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-constants.h + --namespace=TpFuture + --str-constant-prefix=TP_FUTURE_ + --define-prefix=TP_QT_FUTURE_ + DEPENDS future-ifaces-includator) + +tpqt_types_gen(stable-typesgen ${gen_stable_spec_xml} + ${CMAKE_CURRENT_BINARY_DIR}/_gen/types.h ${CMAKE_CURRENT_BINARY_DIR}/_gen/types-body.hpp + Tp TelepathyQt/types.h TelepathyQt/Types + --must-define=IN_TP_QT_HEADER + --visibility=TP_QT_EXPORT + DEPENDS stable-constants) +tpqt_types_gen(future-typesgen ${gen_future_spec_xml} + ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-types.h ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-types-body.hpp + TpFuture TelepathyQt/future-internal.h TelepathyQt/future-internal.h + DEPENDS future-constants) + +# Add the generated types to the library's sources +list(APPEND telepathy_qt4_SRCS ${CMAKE_CURRENT_BINARY_DIR}/_gen/types.h) +list(APPEND telepathy_qt4_SRCS ${CMAKE_CURRENT_BINARY_DIR}/_gen/types-body.hpp) +list(APPEND telepathy_qt4_SRCS ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-constants.h) +list(APPEND telepathy_qt4_SRCS ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-types.h) +list(APPEND telepathy_qt4_SRCS ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-types-body.hpp) + +# For each spec, both stable and future, generate a cli file and add it to the sources (including mocs). +set(SPECS + account + account-manager + channel + channel-dispatcher + channel-dispatch-operation + channel-request + client + connection + connection-manager + dbus + media-session-handler + media-stream-handler + properties + tls-certificate) +foreach(spec ${SPECS}) + tpqt_xincludator(${spec}-spec-xincludator ${CMAKE_CURRENT_SOURCE_DIR}/${spec}.xml ${CMAKE_CURRENT_BINARY_DIR}/_gen/spec-${spec}.xml + DEPENDS stable-typesgen) + set(NEW_FILES + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-${spec}-body.hpp + ${CMAKE_CURRENT_BINARY_DIR}/_gen/cli-${spec}.moc.hpp) + list(APPEND telepathy_qt4_SRCS ${NEW_FILES}) + list(APPEND telepathy_qt4_generated_specs_mocs "moc-cli-${spec}.moc.hpp") + set_source_files_properties(${NEW_FILES} PROPERTIES GENERATED true) +endforeach(spec ${SPECS}) + +set(FUTURE_SPECS + channel + channel-dispatcher + misc) +foreach(spec ${FUTURE_SPECS}) + tpqt_xincludator(${spec}-future-xincludator ${CMAKE_CURRENT_SOURCE_DIR}/future-${spec}.xml ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}.xml + DEPENDS stable-typesgen future-typesgen) + set(NEW_FILES + ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}.h + ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}-body.hpp + ${CMAKE_CURRENT_BINARY_DIR}/_gen/future-${spec}.moc.hpp) + list(APPEND telepathy_qt4_SRCS ${NEW_FILES}) + list(APPEND telepathy_qt4_generated_specs_mocs "moc-future-${spec}.moc.hpp") + set_source_files_properties(${NEW_FILES} PROPERTIES GENERATED true) +endforeach(spec ${FUTURE_SPECS}) + +# The escape character in MSVC is ^ +if(MSVC) + set(TYPES_INCLUDE ^<TelepathyQt/Types^> ) +else(MSVC) + set(TYPES_INCLUDE '<TelepathyQt/Types>' ) +endif(MSVC) + +# Use the client generator for generating headers out of specs +tpqt_client_generator(account clientaccount AccountManager Tp::Client --mainiface=Tp::Client::AccountInterface DEPENDS account-spec-xincludator) +tpqt_client_generator(account-manager clientam AccountManager Tp::Client --mainiface=Tp::Client::AccountManagerInterface DEPENDS account-manager-spec-xincludator) +tpqt_client_generator(channel clientchannel Channel Tp::Client --mainiface=Tp::Client::ChannelInterface DEPENDS channel-spec-xincludator) +tpqt_client_generator(channel-dispatcher clientchanneldispatcher ChannelDispatcher Tp::Client --mainiface=Tp::Client::ChannelDispatcherInterface DEPENDS channel-dispatcher-spec-xincludator) +tpqt_client_generator(channel-dispatch-operation clientchanneldispatchoperation ChannelDispatchOperation Tp::Client --mainiface=Tp::Client::ChannelDispatchOperationInterface DEPENDS channel-dispatch-operation-spec-xincludator) +tpqt_client_generator(channel-request clientchannelrequest ChannelRequest Tp::Client --mainiface=Tp::Client::ChannelRequestInterface DEPENDS channel-request-spec-xincludator) +tpqt_client_generator(client clientclient Client Tp::Client --mainiface=Tp::Client::ClientInterface DEPENDS client-spec-xincludator) +tpqt_client_generator(connection clientconn Connection Tp::Client --mainiface=Tp::Client::ConnectionInterface DEPENDS connection-spec-xincludator) +tpqt_client_generator(connection-manager clientcm ConnectionManager Tp::Client --mainiface=Tp::Client::ConnectionManagerInterface DEPENDS connection-manager-spec-xincludator) +tpqt_client_generator(dbus clientdbus DBus Tp::Client::DBus DEPENDS dbus-spec-xincludator) +tpqt_client_generator(media-session-handler clientmsesh MediaSessionHandler Tp::Client --mainiface=Tp::Client::MediaSessionHandlerInterface DEPENDS media-session-handler-spec-xincludator) +tpqt_client_generator(media-stream-handler clientmstrh MediaStreamHandler Tp::Client --mainiface=Tp::Client::MediaStreamHandlerInterface DEPENDS media-stream-handler-spec-xincludator) +tpqt_client_generator(properties clientprops Properties Tp::Client DEPENDS properties-spec-xincludator) +tpqt_client_generator(tls-certificate clienttls TLSCertificate Tp::Client DEPENDS tls-certificate-spec-xincludator) + +tpqt_future_client_generator(channel TpFuture::Client --mainiface=Tp::Client::ChannelInterface DEPENDS channel-future-xincludator) +tpqt_future_client_generator(channel-dispatcher TpFuture::Client --mainiface=Tp::Client::ChannelDispatcherInterface DEPENDS channel-dispatcher-future-xincludator) +tpqt_future_client_generator(misc TpFuture::Client DEPENDS misc-future-xincludator) + +if (TARGET doxygen-doc) + add_dependencies(doxygen-doc all-generated-sources) +endif (TARGET doxygen-doc) + +# Create the library +if (ENABLE_COMPILER_COVERAGE) + add_library(telepathy-qt4 STATIC ${telepathy_qt4_SRCS}) +else (ENABLE_COMPILER_COVERAGE) + add_library(telepathy-qt4 SHARED ${telepathy_qt4_SRCS}) +endif (ENABLE_COMPILER_COVERAGE) + +# generate client moc files +foreach(moc_src ${telepathy_qt4_MOC_SRCS}) + set(generated_file _gen/${moc_src}) + string(REPLACE ".h" ".moc.hpp" generated_file ${generated_file}) + tpqt_generate_moc_i_target_deps(${CMAKE_CURRENT_SOURCE_DIR}/${moc_src} ${CMAKE_CURRENT_BINARY_DIR}/${generated_file} + ${telepathy_qt4_generated_specs_mocs}) + list(APPEND telepathy_qt4_SRCS ${CMAKE_CURRENT_BINARY_DIR}/${generated_file}) + string(REPLACE ".h" ".moc.hpp" moc_src ${moc_src}) + add_dependencies(telepathy-qt4 "moc-${moc_src}") +endforeach(moc_src ${telepathy_qt4_MOC_SRCS}) + +# Link +target_link_libraries(telepathy-qt4 + ${QT_QTCORE_LIBRARY} + ${QT_QTDBUS_LIBRARY} + ${QT_QTNETWORK_LIBRARIES} + ${QT_QTXML_LIBRARIES}) + +if (ENABLE_COMPILER_COVERAGE) + target_link_libraries(telepathy-qt4 gcov) +endif (ENABLE_COMPILER_COVERAGE) + +# Set the correct version number +set_target_properties(telepathy-qt4 PROPERTIES + SOVERSION ${TP_QT_ABI_VERSION} + VERSION ${TP_QT_LIBRARY_VERSION}) + + +# Install header files +install(FILES ${telepathy_qt4_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/telepathy-1.0/TelepathyQt COMPONENT headers) +install(FILES ${telepathy_qt4_gen_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/telepathy-1.0/TelepathyQt/_gen COMPONENT headers) + +# Install the library - watch out for the correct components +if (WIN32) + install(TARGETS telepathy-qt4 RUNTIME DESTINATION ${LIB_INSTALL_DIR} COMPONENT mainlibrary + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} COMPONENT headers) +else (WIN32) + install(TARGETS telepathy-qt4 LIBRARY DESTINATION ${LIB_INSTALL_DIR} COMPONENT mainlibrary + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} COMPONENT headers) +endif (WIN32) + +# pkg-config file +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/TelepathyQt.pc.in ${CMAKE_CURRENT_BINARY_DIR}/TelepathyQt.pc) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/TelepathyQt-uninstalled.pc.in ${CMAKE_CURRENT_BINARY_DIR}/TelepathyQt-uninstalled.pc) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/TelepathyQt.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig COMPONENT headers) + +# If Farsight was found, install its pkg-config files as well, and go into the subdirectory +if(FARSIGHT_COMPONENTS_FOUND) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/TelepathyQtFarsight.pc.in ${CMAKE_CURRENT_BINARY_DIR}/TelepathyQtFarsight.pc) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/TelepathyQtFarsight-uninstalled.pc.in ${CMAKE_CURRENT_BINARY_DIR}/TelepathyQtFarsight-uninstalled.pc) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/TelepathyQtFarsight.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig COMPONENT farsight_headers) +endif(FARSIGHT_COMPONENTS_FOUND) + +add_subdirectory(Farsight) + diff --git a/TelepathyQt/CapabilitiesBase b/TelepathyQt/CapabilitiesBase new file mode 100644 index 00000000..c31c2e06 --- /dev/null +++ b/TelepathyQt/CapabilitiesBase @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_CapabilitiesBase_HEADER_GUARD_ +#define _TelepathyQt_CapabilitiesBase_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/capabilities-base.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Channel b/TelepathyQt/Channel new file mode 100644 index 00000000..d928dc41 --- /dev/null +++ b/TelepathyQt/Channel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Channel_HEADER_GUARD_ +#define _TelepathyQt_Channel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelClassFeatures b/TelepathyQt/ChannelClassFeatures new file mode 100644 index 00000000..76d561ee --- /dev/null +++ b/TelepathyQt/ChannelClassFeatures @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelClassFeatures_HEADER_GUARD_ +#define _TelepathyQt_ChannelClassFeatures_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel-class-features.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelClassSpec b/TelepathyQt/ChannelClassSpec new file mode 100644 index 00000000..7ea7c105 --- /dev/null +++ b/TelepathyQt/ChannelClassSpec @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelClassSpec_HEADER_GUARD_ +#define _TelepathyQt_ChannelClassSpec_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel-class-spec.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelClassSpecList b/TelepathyQt/ChannelClassSpecList new file mode 100644 index 00000000..7e9bf67a --- /dev/null +++ b/TelepathyQt/ChannelClassSpecList @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelClassSpecList_HEADER_GUARD_ +#define _TelepathyQt_ChannelClassSpecList_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel-class-spec.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelDispatchOperation b/TelepathyQt/ChannelDispatchOperation new file mode 100644 index 00000000..2753db02 --- /dev/null +++ b/TelepathyQt/ChannelDispatchOperation @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelDispatchOperation_HEADER_GUARD_ +#define _TelepathyQt_ChannelDispatchOperation_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel-dispatch-operation.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelDispatchOperationInterface b/TelepathyQt/ChannelDispatchOperationInterface new file mode 100644 index 00000000..8d056b9e --- /dev/null +++ b/TelepathyQt/ChannelDispatchOperationInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelDispatchOperationInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelDispatchOperationInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel-dispatch-operation.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelDispatcher b/TelepathyQt/ChannelDispatcher new file mode 100644 index 00000000..3d63b2a0 --- /dev/null +++ b/TelepathyQt/ChannelDispatcher @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelDispatcher_HEADER_GUARD_ +#define _TelepathyQt_ChannelDispatcher_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel-dispatcher.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelDispatcherInterface b/TelepathyQt/ChannelDispatcherInterface new file mode 100644 index 00000000..6b03037f --- /dev/null +++ b/TelepathyQt/ChannelDispatcherInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelDispatcherInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelDispatcherInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel-dispatcher.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelFactory b/TelepathyQt/ChannelFactory new file mode 100644 index 00000000..7e56a397 --- /dev/null +++ b/TelepathyQt/ChannelFactory @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelFactory_HEADER_GUARD_ +#define _TelepathyQt_ChannelFactory_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel-factory.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterface b/TelepathyQt/ChannelInterface new file mode 100644 index 00000000..20db0895 --- /dev/null +++ b/TelepathyQt/ChannelInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfaceAnonymityInterface b/TelepathyQt/ChannelInterfaceAnonymityInterface new file mode 100644 index 00000000..dfafd9e9 --- /dev/null +++ b/TelepathyQt/ChannelInterfaceAnonymityInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfaceAnonymityInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfaceAnonymityInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfaceCallStateInterface b/TelepathyQt/ChannelInterfaceCallStateInterface new file mode 100644 index 00000000..794aa5b1 --- /dev/null +++ b/TelepathyQt/ChannelInterfaceCallStateInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfaceCallStateInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfaceCallStateInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfaceChatStateInterface b/TelepathyQt/ChannelInterfaceChatStateInterface new file mode 100644 index 00000000..84469132 --- /dev/null +++ b/TelepathyQt/ChannelInterfaceChatStateInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfaceChatStateInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfaceChatStateInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfaceConferenceInterface b/TelepathyQt/ChannelInterfaceConferenceInterface new file mode 100644 index 00000000..dcb0eb98 --- /dev/null +++ b/TelepathyQt/ChannelInterfaceConferenceInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfaceConferenceInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfaceConferenceInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfaceDTMFInterface b/TelepathyQt/ChannelInterfaceDTMFInterface new file mode 100644 index 00000000..f72e4908 --- /dev/null +++ b/TelepathyQt/ChannelInterfaceDTMFInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfaceDTMFInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfaceDTMFInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfaceGroupInterface b/TelepathyQt/ChannelInterfaceGroupInterface new file mode 100644 index 00000000..87a26a5a --- /dev/null +++ b/TelepathyQt/ChannelInterfaceGroupInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfaceGroupInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfaceGroupInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfaceHoldInterface b/TelepathyQt/ChannelInterfaceHoldInterface new file mode 100644 index 00000000..a99ee042 --- /dev/null +++ b/TelepathyQt/ChannelInterfaceHoldInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfaceHoldInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfaceHoldInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfaceMediaSignallingInterface b/TelepathyQt/ChannelInterfaceMediaSignallingInterface new file mode 100644 index 00000000..4747f68e --- /dev/null +++ b/TelepathyQt/ChannelInterfaceMediaSignallingInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfaceMediaSignallingInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfaceMediaSignallingInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfaceMessagesInterface b/TelepathyQt/ChannelInterfaceMessagesInterface new file mode 100644 index 00000000..342ab11d --- /dev/null +++ b/TelepathyQt/ChannelInterfaceMessagesInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfaceMessagesInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfaceMessagesInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfacePasswordInterface b/TelepathyQt/ChannelInterfacePasswordInterface new file mode 100644 index 00000000..804d836c --- /dev/null +++ b/TelepathyQt/ChannelInterfacePasswordInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfacePasswordInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfacePasswordInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfaceSASLAuthenticationInterface b/TelepathyQt/ChannelInterfaceSASLAuthenticationInterface new file mode 100644 index 00000000..67e07552 --- /dev/null +++ b/TelepathyQt/ChannelInterfaceSASLAuthenticationInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfaceSASLConnectionInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfaceSASLConnectionInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfaceSecurableInterface b/TelepathyQt/ChannelInterfaceSecurableInterface new file mode 100644 index 00000000..05a80b19 --- /dev/null +++ b/TelepathyQt/ChannelInterfaceSecurableInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfaceSecurableInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfaceSecurableInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfaceServicePointInterface b/TelepathyQt/ChannelInterfaceServicePointInterface new file mode 100644 index 00000000..6d5ffd36 --- /dev/null +++ b/TelepathyQt/ChannelInterfaceServicePointInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfaceServicePointInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfaceServicePointInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelInterfaceTubeInterface b/TelepathyQt/ChannelInterfaceTubeInterface new file mode 100644 index 00000000..65ae6845 --- /dev/null +++ b/TelepathyQt/ChannelInterfaceTubeInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelInterfaceTubeInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelInterfaceTubeInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelRequest b/TelepathyQt/ChannelRequest new file mode 100644 index 00000000..fec50cbe --- /dev/null +++ b/TelepathyQt/ChannelRequest @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelRequest_HEADER_GUARD_ +#define _TelepathyQt_ChannelRequest_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel-request.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelRequestHints b/TelepathyQt/ChannelRequestHints new file mode 100644 index 00000000..2264d95b --- /dev/null +++ b/TelepathyQt/ChannelRequestHints @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelRequestHints_HEADER_GUARD_ +#define _TelepathyQt_ChannelRequestHints_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel-request.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelRequestInterface b/TelepathyQt/ChannelRequestInterface new file mode 100644 index 00000000..1de5b515 --- /dev/null +++ b/TelepathyQt/ChannelRequestInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelRequestInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelRequestInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel-request.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelTypeContactListInterface b/TelepathyQt/ChannelTypeContactListInterface new file mode 100644 index 00000000..2bedccf9 --- /dev/null +++ b/TelepathyQt/ChannelTypeContactListInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelTypeContactListInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelTypeContactListInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelTypeContactSearchInterface b/TelepathyQt/ChannelTypeContactSearchInterface new file mode 100644 index 00000000..1f7b7afd --- /dev/null +++ b/TelepathyQt/ChannelTypeContactSearchInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelTypeContactSearchInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelTypeContactSearchInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelTypeFileTransferInterface b/TelepathyQt/ChannelTypeFileTransferInterface new file mode 100644 index 00000000..8d6692c6 --- /dev/null +++ b/TelepathyQt/ChannelTypeFileTransferInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelTypeFileTransferInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelTypeFileTransferInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelTypeRoomListInterface b/TelepathyQt/ChannelTypeRoomListInterface new file mode 100644 index 00000000..ba763a51 --- /dev/null +++ b/TelepathyQt/ChannelTypeRoomListInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelTypeRoomListInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelTypeRoomListInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelTypeServerAuthenticationInterface b/TelepathyQt/ChannelTypeServerAuthenticationInterface new file mode 100644 index 00000000..f03cd149 --- /dev/null +++ b/TelepathyQt/ChannelTypeServerAuthenticationInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelTypeServerAuthenticationInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelTypeServerAuthenticationInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelTypeServerTLSConnectionInterface b/TelepathyQt/ChannelTypeServerTLSConnectionInterface new file mode 100644 index 00000000..e69a6542 --- /dev/null +++ b/TelepathyQt/ChannelTypeServerTLSConnectionInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelTypeServerTLSConnectionInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelTypeServerTLSConnectionInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelTypeStreamTubeInterface b/TelepathyQt/ChannelTypeStreamTubeInterface new file mode 100644 index 00000000..390b9774 --- /dev/null +++ b/TelepathyQt/ChannelTypeStreamTubeInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Client_ChannelTypeStreamTubeInterface_HEADER_GUARD_ +#define _TelepathyQt_Client_ChannelTypeStreamTubeInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelTypeStreamedMediaInterface b/TelepathyQt/ChannelTypeStreamedMediaInterface new file mode 100644 index 00000000..965d664e --- /dev/null +++ b/TelepathyQt/ChannelTypeStreamedMediaInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelTypeStreamedMediaInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelTypeStreamedMediaInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelTypeTextInterface b/TelepathyQt/ChannelTypeTextInterface new file mode 100644 index 00000000..291690fd --- /dev/null +++ b/TelepathyQt/ChannelTypeTextInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelTypeTextInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelTypeTextInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelTypeTubeInterface b/TelepathyQt/ChannelTypeTubeInterface new file mode 100644 index 00000000..54b4ddb9 --- /dev/null +++ b/TelepathyQt/ChannelTypeTubeInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Client_ChannelTypeTubeInterface_HEADER_GUARD_ +#define _TelepathyQt_Client_ChannelTypeTubeInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ChannelTypeTubesInterface b/TelepathyQt/ChannelTypeTubesInterface new file mode 100644 index 00000000..2fc367a5 --- /dev/null +++ b/TelepathyQt/ChannelTypeTubesInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ChannelTypeTubesInterface_HEADER_GUARD_ +#define _TelepathyQt_ChannelTypeTubesInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Client b/TelepathyQt/Client new file mode 100644 index 00000000..4df3c82e --- /dev/null +++ b/TelepathyQt/Client @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Client_HEADER_GUARD_ +#define _TelepathyQt_Client_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/client.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ClientApproverInterface b/TelepathyQt/ClientApproverInterface new file mode 100644 index 00000000..f69f9d82 --- /dev/null +++ b/TelepathyQt/ClientApproverInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ClientApproverInterface_HEADER_GUARD_ +#define _TelepathyQt_ClientApproverInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/client.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ClientHandlerInterface b/TelepathyQt/ClientHandlerInterface new file mode 100644 index 00000000..1ddc9062 --- /dev/null +++ b/TelepathyQt/ClientHandlerInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ClientHandlerInterface_HEADER_GUARD_ +#define _TelepathyQt_ClientHandlerInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/client.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ClientInterface b/TelepathyQt/ClientInterface new file mode 100644 index 00000000..4a6b2cc5 --- /dev/null +++ b/TelepathyQt/ClientInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ClientInterface_HEADER_GUARD_ +#define _TelepathyQt_ClientInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/client.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ClientInterfaceRequestsInterface b/TelepathyQt/ClientInterfaceRequestsInterface new file mode 100644 index 00000000..cef1cabc --- /dev/null +++ b/TelepathyQt/ClientInterfaceRequestsInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ClientInterfaceRequestsInterface_HEADER_GUARD_ +#define _TelepathyQt_ClientInterfaceRequestsInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/client.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ClientObserverInterface b/TelepathyQt/ClientObserverInterface new file mode 100644 index 00000000..ace48ff4 --- /dev/null +++ b/TelepathyQt/ClientObserverInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ClientObserverInterface_HEADER_GUARD_ +#define _TelepathyQt_ClientObserverInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/client.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ClientRegistrar b/TelepathyQt/ClientRegistrar new file mode 100644 index 00000000..eac9e94e --- /dev/null +++ b/TelepathyQt/ClientRegistrar @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ClientRegistrar_HEADER_GUARD_ +#define _TelepathyQt_ClientRegistrar_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/client-registrar.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Connection b/TelepathyQt/Connection new file mode 100644 index 00000000..1f108be6 --- /dev/null +++ b/TelepathyQt/Connection @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Connection_HEADER_GUARD_ +#define _TelepathyQt_Connection_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionCapabilities b/TelepathyQt/ConnectionCapabilities new file mode 100644 index 00000000..3d5a9b1d --- /dev/null +++ b/TelepathyQt/ConnectionCapabilities @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionCapabilities_HEADER_GUARD_ +#define _TelepathyQt_ConnectionCapabilities_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection-capabilities.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionFactory b/TelepathyQt/ConnectionFactory new file mode 100644 index 00000000..8af15eed --- /dev/null +++ b/TelepathyQt/ConnectionFactory @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionFactory_HEADER_GUARD_ +#define _TelepathyQt_ConnectionFactory_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection-factory.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterface b/TelepathyQt/ConnectionInterface new file mode 100644 index 00000000..4f4fd3f8 --- /dev/null +++ b/TelepathyQt/ConnectionInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceAliasingInterface b/TelepathyQt/ConnectionInterfaceAliasingInterface new file mode 100644 index 00000000..08d44a08 --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceAliasingInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceAliasingInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceAliasingInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceAnonymityInterface b/TelepathyQt/ConnectionInterfaceAnonymityInterface new file mode 100644 index 00000000..5bd148ed --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceAnonymityInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceAnonymityInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceAnonymityInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceAvatarsInterface b/TelepathyQt/ConnectionInterfaceAvatarsInterface new file mode 100644 index 00000000..deebb8cc --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceAvatarsInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceAvatarsInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceAvatarsInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceBalanceInterface b/TelepathyQt/ConnectionInterfaceBalanceInterface new file mode 100644 index 00000000..8a6fbfd3 --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceBalanceInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceBalanceInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceBalanceInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceCapabilitiesInterface b/TelepathyQt/ConnectionInterfaceCapabilitiesInterface new file mode 100644 index 00000000..7129834b --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceCapabilitiesInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceCapabilitiesInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceCapabilitiesInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceCellularInterface b/TelepathyQt/ConnectionInterfaceCellularInterface new file mode 100644 index 00000000..8fb1b72a --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceCellularInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceCellularInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceCellularInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceClientTypes b/TelepathyQt/ConnectionInterfaceClientTypes new file mode 100644 index 00000000..4d829532 --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceClientTypes @@ -0,0 +1,17 @@ +#ifndef _TelepathyQt_ConnectionInterfaceClientTyes_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceClientTyes_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#ifdef TP_QT_DEPRECATED_WARNINGS +#warning "This file will be removed in a future tp-qt4 release, use #include <TelepathyQt/ConnectionInterfaceClientTypesInterface> instead" +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceClientTypesInterface b/TelepathyQt/ConnectionInterfaceClientTypesInterface new file mode 100644 index 00000000..9fba41f3 --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceClientTypesInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceClientTypesInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceClientTypesInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceContactBlockingInterface b/TelepathyQt/ConnectionInterfaceContactBlockingInterface new file mode 100644 index 00000000..57acbebe --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceContactBlockingInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceContactBlockingInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceContactBlockingInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceContactCapabilitiesInterface b/TelepathyQt/ConnectionInterfaceContactCapabilitiesInterface new file mode 100644 index 00000000..a30c4b22 --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceContactCapabilitiesInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceContactCapabilitiesInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceContactCapabilitiesInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceContactGroups b/TelepathyQt/ConnectionInterfaceContactGroups new file mode 100644 index 00000000..48800391 --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceContactGroups @@ -0,0 +1,17 @@ +#ifndef _TelepathyQt_ConnectionInterfaceContactGroups_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceContactGroups_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#ifdef TP_QT_DEPRECATED_WARNINGS +#warning "This file will be removed in a future tp-qt4 release, use #include <TelepathyQt/ConnectionInterfaceContactGroupsInterface> instead" +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceContactGroupsInterface b/TelepathyQt/ConnectionInterfaceContactGroupsInterface new file mode 100644 index 00000000..8891d430 --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceContactGroupsInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceContactGroupsInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceContactGroupsInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceContactInfoInterface b/TelepathyQt/ConnectionInterfaceContactInfoInterface new file mode 100644 index 00000000..1b465d2b --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceContactInfoInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceContactInfoInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceContactInfoInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceContactList b/TelepathyQt/ConnectionInterfaceContactList new file mode 100644 index 00000000..674ab4f1 --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceContactList @@ -0,0 +1,17 @@ +#ifndef _TelepathyQt_ConnectionInterfaceContactList_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceContactList_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#ifdef TP_QT_DEPRECATED_WARNINGS +#warning "This file will be removed in a future tp-qt4 release, use #include <TelepathyQt/ConnectionInterfaceContactListInterface> instead" +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceContactListInterface b/TelepathyQt/ConnectionInterfaceContactListInterface new file mode 100644 index 00000000..3f80a62a --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceContactListInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceContactListInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceContactListInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceContactsInterface b/TelepathyQt/ConnectionInterfaceContactsInterface new file mode 100644 index 00000000..ed8e3e2e --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceContactsInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceContactsInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceContactsInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceLocationInterface b/TelepathyQt/ConnectionInterfaceLocationInterface new file mode 100644 index 00000000..158efea3 --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceLocationInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceLocationInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceLocationInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceMailNotificationInterface b/TelepathyQt/ConnectionInterfaceMailNotificationInterface new file mode 100644 index 00000000..43cd102a --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceMailNotificationInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceMailNotificationInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceMailNotificationInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfacePowerSaving b/TelepathyQt/ConnectionInterfacePowerSaving new file mode 100644 index 00000000..5a9d6d09 --- /dev/null +++ b/TelepathyQt/ConnectionInterfacePowerSaving @@ -0,0 +1,17 @@ +#ifndef _TelepathyQt_ConnectionInterfacePowerSaving_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfacePowerSaving_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#ifdef TP_QT_DEPRECATED_WARNINGS +#warning "This file will be removed in a future tp-qt4 release, use #include <TelepathyQt/ConnectionInterfacePowerSavingInterface> instead" +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfacePowerSavingInterface b/TelepathyQt/ConnectionInterfacePowerSavingInterface new file mode 100644 index 00000000..ed0a01c7 --- /dev/null +++ b/TelepathyQt/ConnectionInterfacePowerSavingInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfacePowerSavingInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfacePowerSavingInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfacePresenceInterface b/TelepathyQt/ConnectionInterfacePresenceInterface new file mode 100644 index 00000000..7626a27c --- /dev/null +++ b/TelepathyQt/ConnectionInterfacePresenceInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfacePresenceInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfacePresenceInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceRequestsInterface b/TelepathyQt/ConnectionInterfaceRequestsInterface new file mode 100644 index 00000000..ece2c7c2 --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceRequestsInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceRequestsInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceRequestsInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceServicePointInterface b/TelepathyQt/ConnectionInterfaceServicePointInterface new file mode 100644 index 00000000..19f7bfbd --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceServicePointInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceServicePointInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceServicePointInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionInterfaceSimplePresenceInterface b/TelepathyQt/ConnectionInterfaceSimplePresenceInterface new file mode 100644 index 00000000..47dddb9e --- /dev/null +++ b/TelepathyQt/ConnectionInterfaceSimplePresenceInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionInterfaceSimplePresenceInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionInterfaceSimplePresenceInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionLowlevel b/TelepathyQt/ConnectionLowlevel new file mode 100644 index 00000000..25a53129 --- /dev/null +++ b/TelepathyQt/ConnectionLowlevel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionLowlevel_HEADER_GUARD +#define _TelepathyQt_ConnectionLowlevel_HEADER_GUARD + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection-lowlevel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionManager b/TelepathyQt/ConnectionManager new file mode 100644 index 00000000..3ae63f6f --- /dev/null +++ b/TelepathyQt/ConnectionManager @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionManager_HEADER_GUARD_ +#define _TelepathyQt_ConnectionManager_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection-manager.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionManagerInterface b/TelepathyQt/ConnectionManagerInterface new file mode 100644 index 00000000..2f829012 --- /dev/null +++ b/TelepathyQt/ConnectionManagerInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionManagerInterface_HEADER_GUARD_ +#define _TelepathyQt_ConnectionManagerInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection-manager.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ConnectionManagerLowlevel b/TelepathyQt/ConnectionManagerLowlevel new file mode 100644 index 00000000..d75e427f --- /dev/null +++ b/TelepathyQt/ConnectionManagerLowlevel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ConnectionManagerLowlevel_HEADER_GUARD +#define _TelepathyQt_ConnectionManagerLowlevel_HEADER_GUARD + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/connection-manager-lowlevel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Constants b/TelepathyQt/Constants new file mode 100644 index 00000000..c7ddc806 --- /dev/null +++ b/TelepathyQt/Constants @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Constants_HEADER_GUARD_ +#define _TelepathyQt_Constants_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/constants.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Contact b/TelepathyQt/Contact new file mode 100644 index 00000000..ae1a9c8d --- /dev/null +++ b/TelepathyQt/Contact @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Contact_HEADER_GUARD_ +#define _TelepathyQt_Contact_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/contact.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ContactCapabilities b/TelepathyQt/ContactCapabilities new file mode 100644 index 00000000..c012e0dd --- /dev/null +++ b/TelepathyQt/ContactCapabilities @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ContactCapabilities_HEADER_GUARD_ +#define _TelepathyQt_ContactCapabilities_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/contact-capabilities.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ContactFactory b/TelepathyQt/ContactFactory new file mode 100644 index 00000000..957f2e41 --- /dev/null +++ b/TelepathyQt/ContactFactory @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ContactFactory_HEADER_GUARD_ +#define _TelepathyQt_ContactFactory_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/contact-factory.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ContactManager b/TelepathyQt/ContactManager new file mode 100644 index 00000000..6f5fae54 --- /dev/null +++ b/TelepathyQt/ContactManager @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ContactManager_HEADER_GUARD_ +#define _TelepathyQt_ContactManager_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/contact-manager.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ContactMessenger b/TelepathyQt/ContactMessenger new file mode 100644 index 00000000..58cb7e10 --- /dev/null +++ b/TelepathyQt/ContactMessenger @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ContactMessenger_HEADER_GUARD_ +#define _TelepathyQt_ContactMessenger_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/contact-messenger.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ContactSearchChannel b/TelepathyQt/ContactSearchChannel new file mode 100644 index 00000000..e4b8bf0d --- /dev/null +++ b/TelepathyQt/ContactSearchChannel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ContactSearchChannel_HEADER_GUARD_ +#define _TelepathyQt_ContactSearchChannel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/contact-search-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/DBus b/TelepathyQt/DBus new file mode 100644 index 00000000..9a72aa46 --- /dev/null +++ b/TelepathyQt/DBus @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_DBus_HEADER_GUARD_ +#define _TelepathyQt_DBus_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/dbus.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/DBusDaemonInterface b/TelepathyQt/DBusDaemonInterface new file mode 100644 index 00000000..e003fd6a --- /dev/null +++ b/TelepathyQt/DBusDaemonInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_DBusDaemonInterface_HEADER_GUARD_ +#define _TelepathyQt_DBusDaemonInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/dbus.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/DBusProxy b/TelepathyQt/DBusProxy new file mode 100644 index 00000000..a26c1c2e --- /dev/null +++ b/TelepathyQt/DBusProxy @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_DBusProxy_HEADER_GUARD_ +#define _TelepathyQt_DBusProxy_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/dbus-proxy.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/DBusProxyFactory b/TelepathyQt/DBusProxyFactory new file mode 100644 index 00000000..c40af4ef --- /dev/null +++ b/TelepathyQt/DBusProxyFactory @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_DBusProxyFactory_HEADER_GUARD_ +#define _TelepathyQt_DBusProxyFactory_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/dbus-proxy-factory.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Debug b/TelepathyQt/Debug new file mode 100644 index 00000000..f67ec73b --- /dev/null +++ b/TelepathyQt/Debug @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Debug_HEADER_GUARD_ +#define _TelepathyQt_Debug_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/debug.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Farsight/CMakeLists.txt b/TelepathyQt/Farsight/CMakeLists.txt new file mode 100644 index 00000000..0deba3c2 --- /dev/null +++ b/TelepathyQt/Farsight/CMakeLists.txt @@ -0,0 +1,52 @@ +if(FARSIGHT_COMPONENTS_FOUND) + include_directories(${TELEPATHY_FARSIGHT_INCLUDE_DIR} + ${GSTREAMER_INCLUDE_DIR} + ${GLIB2_INCLUDE_DIR} + ${LIBXML2_INCLUDE_DIR} + ${DBUS_INCLUDE_DIR}) + + # It gets inherited from the previous directory, hence it has to be removed explicitely + remove_definitions(-DBUILDING_TP_QT) + # We are building Telepathy-Qt4-Farsight + add_definitions(-DBUILDING_TP_QT_FARSIGHT -DQT_NO_KEYWORDS) + + set(telepathy_qt4_farsight_SRCS + channel.cpp) + + set(telepathy_qt4_farsight_HEADERS + Channel + channel.h + global.h) + + # Create the library + if (ENABLE_COMPILER_COVERAGE) + add_library(telepathy-qt4-farsight STATIC ${telepathy_qt4_farsight_SRCS}) + else (ENABLE_COMPILER_COVERAGE) + add_library(telepathy-qt4-farsight SHARED ${telepathy_qt4_farsight_SRCS}) + endif (ENABLE_COMPILER_COVERAGE) + # Link + target_link_libraries(telepathy-qt4-farsight + ${QT_QTDBUS_LIBRARY} + ${QT_QTCORE_LIBRARY} + ${TELEPATHY_FARSIGHT_LIBRARIES} + ${GSTREAMER_INTERFACE_LIBRARY} + telepathy-qt4) + + # Set the correct version number + set_target_properties(telepathy-qt4-farsight PROPERTIES + SOVERSION ${TP_QT_ABI_VERSION} + VERSION ${TP_QT_LIBRARY_VERSION}) + + # Install the library - watch out for the correct components + if (WIN32) + install(TARGETS telepathy-qt4-farsight RUNTIME DESTINATION ${LIB_INSTALL_DIR} COMPONENT farsight + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} COMPONENT farsight_headers) + else (WIN32) + install(TARGETS telepathy-qt4-farsight LIBRARY DESTINATION ${LIB_INSTALL_DIR} COMPONENT farsight + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} COMPONENT farsight_headers) + endif (WIN32) + + # Install headers + install(FILES ${telepathy_qt4_farsight_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR}/telepathy-1.0/TelepathyQt/Farsight + COMPONENT farsight_headers) +endif(FARSIGHT_COMPONENTS_FOUND) diff --git a/TelepathyQt/Farsight/Channel b/TelepathyQt/Farsight/Channel new file mode 100644 index 00000000..ea05f89d --- /dev/null +++ b/TelepathyQt/Farsight/Channel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Farsight_Channel_HEADER_GUARD_ +#define _TelepathyQt_Farsight_Channel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Farsight/channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Farsight/channel.cpp b/TelepathyQt/Farsight/channel.cpp new file mode 100644 index 00000000..d920176e --- /dev/null +++ b/TelepathyQt/Farsight/channel.cpp @@ -0,0 +1,87 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/Farsight/Channel> + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Channel> +#include <TelepathyQt/Connection> +#include <TelepathyQt/StreamedMediaChannel> + +#include <telepathy-glib/channel.h> +#include <telepathy-glib/connection.h> +#include <telepathy-glib/dbus.h> + +namespace Tp +{ + +TfChannel *createFarsightChannel(const StreamedMediaChannelPtr &channel) +{ + if (!channel->handlerStreamingRequired()) { + warning() << "Handler streaming not required"; + return 0; + } + + TpDBusDaemon *dbus = tp_dbus_daemon_dup(0); + + if (!dbus) { + warning() << "Unable to connect to D-Bus"; + return 0; + } + + ConnectionPtr connection = channel->connection(); + + TpConnection *gconnection = tp_connection_new(dbus, + connection->busName().toAscii(), + connection->objectPath().toAscii(), + 0); + g_object_unref(dbus); + dbus = 0; + + if (!gconnection) { + warning() << "Unable to construct TpConnection"; + return 0; + } + + TpChannel *gchannel = tp_channel_new(gconnection, + channel->objectPath().toAscii(), + TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA, + (TpHandleType) channel->targetHandleType(), + channel->targetHandle(), + 0); + g_object_unref(gconnection); + gconnection = 0; + + if (!gchannel) { + warning() << "Unable to construct TpChannel"; + return 0; + } + + TfChannel *ret = tf_channel_new(gchannel); + g_object_unref(gchannel); + gchannel = 0; + + return ret; +} + +} // Tp diff --git a/TelepathyQt/Farsight/channel.h b/TelepathyQt/Farsight/channel.h new file mode 100644 index 00000000..1dffb940 --- /dev/null +++ b/TelepathyQt/Farsight/channel.h @@ -0,0 +1,43 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_Farsight_channel_h_HEADER_GUARD_ +#define _TelepathyQt_Farsight_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Farsight/global.h> + +#include <TelepathyQt/Types> + +#include <telepathy-farsight/channel.h> + +namespace Tp +{ + +TP_QT_FS_EXPORT TfChannel *createFarsightChannel(const StreamedMediaChannelPtr &channel); + +} // Tp + +#endif diff --git a/TelepathyQt/Farsight/global.h b/TelepathyQt/Farsight/global.h new file mode 100644 index 00000000..2d5c1100 --- /dev/null +++ b/TelepathyQt/Farsight/global.h @@ -0,0 +1,46 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_Farsight_global_h_HEADER_GUARD_ +#define _TelepathyQt_Farsight_global_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <QtGlobal> + +#ifdef BUILDING_TP_QT_FARSIGHT +# define TP_QT_FS_EXPORT Q_DECL_EXPORT +#else +# define TP_QT_FS_EXPORT Q_DECL_IMPORT +#endif + +#if !defined(Q_OS_WIN) && defined(QT_VISIBILITY_AVAILABLE) +# define TP_QT_FS_NO_EXPORT __attribute__((visibility("hidden"))) +#endif + +#ifndef TP_QT_FS_NO_EXPORT +# define TP_QT_FS_NO_EXPORT +#endif + +#endif diff --git a/TelepathyQt/Feature b/TelepathyQt/Feature new file mode 100644 index 00000000..20d307a4 --- /dev/null +++ b/TelepathyQt/Feature @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Feature_HEADER_GUARD_ +#define _TelepathyQt_Feature_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/feature.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Features b/TelepathyQt/Features new file mode 100644 index 00000000..d2e6f515 --- /dev/null +++ b/TelepathyQt/Features @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Features_HEADER_GUARD_ +#define _TelepathyQt_Features_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/feature.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/FileTransferChannel b/TelepathyQt/FileTransferChannel new file mode 100644 index 00000000..fc506226 --- /dev/null +++ b/TelepathyQt/FileTransferChannel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_FileTransferChannel_HEADER_GUARD_ +#define _TelepathyQt_FileTransferChannel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/file-transfer-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/FileTransferChannelCreationProperties b/TelepathyQt/FileTransferChannelCreationProperties new file mode 100644 index 00000000..5908db7b --- /dev/null +++ b/TelepathyQt/FileTransferChannelCreationProperties @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_FileTransferChannelCreationProperties_HEADER_GUARD_ +#define _TelepathyQt_FileTransferChannelCreationProperties_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/file-transfer-channel-creation-properties.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Filter b/TelepathyQt/Filter new file mode 100644 index 00000000..c14e4eaf --- /dev/null +++ b/TelepathyQt/Filter @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Filter_HEADER_GUARD_ +#define _TelepathyQt_Filter_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/filter.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/FixedFeatureFactory b/TelepathyQt/FixedFeatureFactory new file mode 100644 index 00000000..4fc90138 --- /dev/null +++ b/TelepathyQt/FixedFeatureFactory @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_FixedFeatureFactory_HEADER_GUARD_ +#define _TelepathyQt_FixedFeatureFactory_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/fixed-feature-factory.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/GenericCapabilityFilter b/TelepathyQt/GenericCapabilityFilter new file mode 100644 index 00000000..f6c41e90 --- /dev/null +++ b/TelepathyQt/GenericCapabilityFilter @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_GenericCapabilityFilter_HEADER_GUARD_ +#define _TelepathyQt_GenericCapabilityFilter_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/generic-capability-filter.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/GenericPropertyFilter b/TelepathyQt/GenericPropertyFilter new file mode 100644 index 00000000..1dc535cd --- /dev/null +++ b/TelepathyQt/GenericPropertyFilter @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_GenericPropertyFilter_HEADER_GUARD_ +#define _TelepathyQt_GenericPropertyFilter_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/generic-property-filter.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Global b/TelepathyQt/Global new file mode 100644 index 00000000..6d227ae5 --- /dev/null +++ b/TelepathyQt/Global @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Global_HEADER_GUARD_ +#define _TelepathyQt_Global_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/global.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/HandledChannelNotifier b/TelepathyQt/HandledChannelNotifier new file mode 100644 index 00000000..987cb8e8 --- /dev/null +++ b/TelepathyQt/HandledChannelNotifier @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_HandledChannelNotifier_HEADER_GUARD_ +#define _TelepathyQt_HandledChannelNotifier_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/handled-channel-notifier.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/IncomingFileTransferChannel b/TelepathyQt/IncomingFileTransferChannel new file mode 100644 index 00000000..092bd683 --- /dev/null +++ b/TelepathyQt/IncomingFileTransferChannel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_IncomingFileTransferChannel_HEADER_GUARD_ +#define _TelepathyQt_IncomingFileTransferChannel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/incoming-file-transfer-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/IncomingStreamTubeChannel b/TelepathyQt/IncomingStreamTubeChannel new file mode 100644 index 00000000..36e32486 --- /dev/null +++ b/TelepathyQt/IncomingStreamTubeChannel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_IncomingStreamTubeChannel_HEADER_GUARD_ +#define _TelepathyQt_IncomingStreamTubeChannel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/incoming-stream-tube-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/IntrospectableInterface b/TelepathyQt/IntrospectableInterface new file mode 100644 index 00000000..1b11a4d4 --- /dev/null +++ b/TelepathyQt/IntrospectableInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_IntrospectableInterface_HEADER_GUARD_ +#define _TelepathyQt_IntrospectableInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/dbus.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/KeyFile b/TelepathyQt/KeyFile new file mode 100644 index 00000000..1bbb3cd0 --- /dev/null +++ b/TelepathyQt/KeyFile @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_KeyFile_HEADER_GUARD_ +#define _TelepathyQt_KeyFile_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/key-file.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/LocationInfo b/TelepathyQt/LocationInfo new file mode 100644 index 00000000..c924e9aa --- /dev/null +++ b/TelepathyQt/LocationInfo @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_LocationInfo_HEADER_GUARD_ +#define _TelepathyQt_LocationInfo_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/location-info.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ManagerFile b/TelepathyQt/ManagerFile new file mode 100644 index 00000000..0765484b --- /dev/null +++ b/TelepathyQt/ManagerFile @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ManagerFile_HEADER_GUARD_ +#define _TelepathyQt_ManagerFile_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/manager-file.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/MediaSessionHandler b/TelepathyQt/MediaSessionHandler new file mode 100644 index 00000000..bdabd07c --- /dev/null +++ b/TelepathyQt/MediaSessionHandler @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_MediaSessionHandler_HEADER_GUARD_ +#define _TelepathyQt_MediaSessionHandler_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/media-session-handler.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/MediaSessionHandlerInterface b/TelepathyQt/MediaSessionHandlerInterface new file mode 100644 index 00000000..25760fa5 --- /dev/null +++ b/TelepathyQt/MediaSessionHandlerInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_MediaSessionHandlerInterface_HEADER_GUARD_ +#define _TelepathyQt_MediaSessionHandlerInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/media-session-handler.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/MediaStreamHandler b/TelepathyQt/MediaStreamHandler new file mode 100644 index 00000000..3ead065c --- /dev/null +++ b/TelepathyQt/MediaStreamHandler @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_MediaStreamHandler_HEADER_GUARD_ +#define _TelepathyQt_MediaStreamHandler_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/media-stream-handler.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/MediaStreamHandlerInterface b/TelepathyQt/MediaStreamHandlerInterface new file mode 100644 index 00000000..760eb087 --- /dev/null +++ b/TelepathyQt/MediaStreamHandlerInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_MediaStreamHandlerInterface_HEADER_GUARD_ +#define _TelepathyQt_MediaStreamHandlerInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/media-stream-handler.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Message b/TelepathyQt/Message new file mode 100644 index 00000000..2ebdbf6a --- /dev/null +++ b/TelepathyQt/Message @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Message_HEADER_GUARD_ +#define _TelepathyQt_Message_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/message.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/MessageContentPart b/TelepathyQt/MessageContentPart new file mode 100644 index 00000000..94a45e74 --- /dev/null +++ b/TelepathyQt/MessageContentPart @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_MessageContentPart_HEADER_GUARD_ +#define _TelepathyQt_MessageContentPart_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/message-content-part.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/MessageContentPartList b/TelepathyQt/MessageContentPartList new file mode 100644 index 00000000..b15e5aca --- /dev/null +++ b/TelepathyQt/MessageContentPartList @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_MessageContentPartList_HEADER_GUARD_ +#define _TelepathyQt_MessageContentPartList_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/message-content-part.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/MethodInvocationContext b/TelepathyQt/MethodInvocationContext new file mode 100644 index 00000000..5c9bfd66 --- /dev/null +++ b/TelepathyQt/MethodInvocationContext @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_MethodInvocationContext_HEADER_GUARD_ +#define _TelepathyQt_MethodInvocationContext_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/method-invocation-context.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/NotFilter b/TelepathyQt/NotFilter new file mode 100644 index 00000000..9947d99b --- /dev/null +++ b/TelepathyQt/NotFilter @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_NotFilter_HEADER_GUARD_ +#define _TelepathyQt_NotFilter_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/not-filter.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Object b/TelepathyQt/Object new file mode 100644 index 00000000..077c9317 --- /dev/null +++ b/TelepathyQt/Object @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Object_HEADER_GUARD_ +#define _TelepathyQt_Object_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/object.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/OptionalInterfaceFactory b/TelepathyQt/OptionalInterfaceFactory new file mode 100644 index 00000000..910a65a8 --- /dev/null +++ b/TelepathyQt/OptionalInterfaceFactory @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_OptionalInterfaceFactory_HEADER_GUARD_ +#define _TelepathyQt_OptionalInterfaceFactory_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/optional-interface-factory.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/OrFilter b/TelepathyQt/OrFilter new file mode 100644 index 00000000..a54ceb89 --- /dev/null +++ b/TelepathyQt/OrFilter @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_OrFilter_HEADER_GUARD_ +#define _TelepathyQt_OrFilter_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/or-filter.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/OutgoingFileTransferChannel b/TelepathyQt/OutgoingFileTransferChannel new file mode 100644 index 00000000..f04a4336 --- /dev/null +++ b/TelepathyQt/OutgoingFileTransferChannel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_OutgoingFileTransferChannel_HEADER_GUARD_ +#define _TelepathyQt_OutgoingFileTransferChannel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/outgoing-file-transfer-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/OutgoingStreamTubeChannel b/TelepathyQt/OutgoingStreamTubeChannel new file mode 100644 index 00000000..18a0b147 --- /dev/null +++ b/TelepathyQt/OutgoingStreamTubeChannel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_OutgoingStreamTubeChannel_HEADER_GUARD_ +#define _TelepathyQt_OutgoingStreamTubeChannel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/outgoing-stream-tube-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PeerInterface b/TelepathyQt/PeerInterface new file mode 100644 index 00000000..3acee4af --- /dev/null +++ b/TelepathyQt/PeerInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PeerInterface_HEADER_GUARD_ +#define _TelepathyQt_PeerInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/dbus.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingAccount b/TelepathyQt/PendingAccount new file mode 100644 index 00000000..66cba3cd --- /dev/null +++ b/TelepathyQt/PendingAccount @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingAccount_HEADER_GUARD_ +#define _TelepathyQt_PendingAccount_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-account.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingChannel b/TelepathyQt/PendingChannel new file mode 100644 index 00000000..1b023c8d --- /dev/null +++ b/TelepathyQt/PendingChannel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingChannel_HEADER_GUARD_ +#define _TelepathyQt_PendingChannel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingChannelRequest b/TelepathyQt/PendingChannelRequest new file mode 100644 index 00000000..22e8bbb3 --- /dev/null +++ b/TelepathyQt/PendingChannelRequest @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingChannelRequest_HEADER_GUARD_ +#define _TelepathyQt_PendingChannelRequest_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-channel-request.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingComposite b/TelepathyQt/PendingComposite new file mode 100644 index 00000000..d6c281e1 --- /dev/null +++ b/TelepathyQt/PendingComposite @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingComposite_HEADER_GUARD_ +#define _TelepathyQt_PendingComposite_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/simple-pending-operations.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingConnection b/TelepathyQt/PendingConnection new file mode 100644 index 00000000..6f4ab9ac --- /dev/null +++ b/TelepathyQt/PendingConnection @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingConnection_HEADER_GUARD_ +#define _TelepathyQt_PendingConnection_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingContactAttributes b/TelepathyQt/PendingContactAttributes new file mode 100644 index 00000000..e67e2055 --- /dev/null +++ b/TelepathyQt/PendingContactAttributes @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingContactAttributes_HEADER_GUARD_ +#define _TelepathyQt_PendingContactAttributes_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-contact-attributes.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingContactInfo b/TelepathyQt/PendingContactInfo new file mode 100644 index 00000000..95cc7933 --- /dev/null +++ b/TelepathyQt/PendingContactInfo @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingContactInfo_HEADER_GUARD_ +#define _TelepathyQt_PendingContactInfo_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-contact-info.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingContacts b/TelepathyQt/PendingContacts new file mode 100644 index 00000000..d74e6cee --- /dev/null +++ b/TelepathyQt/PendingContacts @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingContacts_HEADER_GUARD_ +#define _TelepathyQt_PendingContacts_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-contacts.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingFailure b/TelepathyQt/PendingFailure new file mode 100644 index 00000000..ccc62b3e --- /dev/null +++ b/TelepathyQt/PendingFailure @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingFailure_HEADER_GUARD_ +#define _TelepathyQt_PendingFailure_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/simple-pending-operations.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingHandles b/TelepathyQt/PendingHandles new file mode 100644 index 00000000..27d0e197 --- /dev/null +++ b/TelepathyQt/PendingHandles @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingHandles_HEADER_GUARD_ +#define _TelepathyQt_PendingHandles_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-handles.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingOperation b/TelepathyQt/PendingOperation new file mode 100644 index 00000000..b56e8d42 --- /dev/null +++ b/TelepathyQt/PendingOperation @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingOperation_HEADER_GUARD_ +#define _TelepathyQt_PendingOperation_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-operation.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingReady b/TelepathyQt/PendingReady new file mode 100644 index 00000000..f2f67e94 --- /dev/null +++ b/TelepathyQt/PendingReady @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingReady_HEADER_GUARD_ +#define _TelepathyQt_PendingReady_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-ready.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingSendMessage b/TelepathyQt/PendingSendMessage new file mode 100644 index 00000000..9b6f3496 --- /dev/null +++ b/TelepathyQt/PendingSendMessage @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingSendMessage_HEADER_GUARD_ +#define _TelepathyQt_PendingSendMessage_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-send-message.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingStreamTubeConnection b/TelepathyQt/PendingStreamTubeConnection new file mode 100644 index 00000000..36abeaa1 --- /dev/null +++ b/TelepathyQt/PendingStreamTubeConnection @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingStreamTubeConnection_HEADER_GUARD_ +#define _TelepathyQt_PendingStreamTubeConnection_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-stream-tube-connection.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingStreamedMediaStreams b/TelepathyQt/PendingStreamedMediaStreams new file mode 100644 index 00000000..6373fe6c --- /dev/null +++ b/TelepathyQt/PendingStreamedMediaStreams @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingStreamedMediaStreams_HEADER_GUARD_ +#define _TelepathyQt_PendingStreamedMediaStreams_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/streamed-media-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingStringList b/TelepathyQt/PendingStringList new file mode 100644 index 00000000..cab8b464 --- /dev/null +++ b/TelepathyQt/PendingStringList @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingStringList_HEADER_GUARD_ +#define _TelepathyQt_PendingStringList_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-string-list.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingSuccess b/TelepathyQt/PendingSuccess new file mode 100644 index 00000000..80876b72 --- /dev/null +++ b/TelepathyQt/PendingSuccess @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingSuccess_HEADER_GUARD_ +#define _TelepathyQt_PendingSuccess_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/simple-pending-operations.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingVariant b/TelepathyQt/PendingVariant new file mode 100644 index 00000000..42228800 --- /dev/null +++ b/TelepathyQt/PendingVariant @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingVariant_HEADER_GUARD_ +#define _TelepathyQt_PendingVariant_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-variant.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingVariantMap b/TelepathyQt/PendingVariantMap new file mode 100644 index 00000000..32dae3ce --- /dev/null +++ b/TelepathyQt/PendingVariantMap @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingVariantMap_HEADER_GUARD_ +#define _TelepathyQt_PendingVariantMap_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/pending-variant-map.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PendingVoid b/TelepathyQt/PendingVoid new file mode 100644 index 00000000..56d82372 --- /dev/null +++ b/TelepathyQt/PendingVoid @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PendingVoid_HEADER_GUARD_ +#define _TelepathyQt_PendingVoid_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/simple-pending-operations.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Presence b/TelepathyQt/Presence new file mode 100644 index 00000000..74638b30 --- /dev/null +++ b/TelepathyQt/Presence @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Presence_HEADER_GUARD_ +#define _TelepathyQt_Presence_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/presence.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PresenceSpec b/TelepathyQt/PresenceSpec new file mode 100644 index 00000000..b8ec4448 --- /dev/null +++ b/TelepathyQt/PresenceSpec @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PresenceSpec_HEADER_GUARD_ +#define _TelepathyQt_PresenceSpec_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/presence.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PresenceSpecList b/TelepathyQt/PresenceSpecList new file mode 100644 index 00000000..71600307 --- /dev/null +++ b/TelepathyQt/PresenceSpecList @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PresenceSpecList_HEADER_GUARD_ +#define _TelepathyQt_PresenceSpecList_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/presence.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Profile b/TelepathyQt/Profile new file mode 100644 index 00000000..536856bf --- /dev/null +++ b/TelepathyQt/Profile @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Profile_HEADER_GUARD_ +#define _TelepathyQt_Profile_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/profile.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ProfileManager b/TelepathyQt/ProfileManager new file mode 100644 index 00000000..711d5dc0 --- /dev/null +++ b/TelepathyQt/ProfileManager @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ProfileManager_HEADER_GUARD_ +#define _TelepathyQt_ProfileManager_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/profile-manager.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Properties b/TelepathyQt/Properties new file mode 100644 index 00000000..111f1ad1 --- /dev/null +++ b/TelepathyQt/Properties @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Properties_HEADER_GUARD_ +#define _TelepathyQt_Properties_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/properties.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PropertiesInterface b/TelepathyQt/PropertiesInterface new file mode 100644 index 00000000..a34d9109 --- /dev/null +++ b/TelepathyQt/PropertiesInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PropertiesInterface_HEADER_GUARD_ +#define _TelepathyQt_PropertiesInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/dbus.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/PropertiesInterfaceInterface b/TelepathyQt/PropertiesInterfaceInterface new file mode 100644 index 00000000..4b11dc0c --- /dev/null +++ b/TelepathyQt/PropertiesInterfaceInterface @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_PropertiesInterfaceInterface_HEADER_GUARD_ +#define _TelepathyQt_PropertiesInterfaceInterface_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/properties.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ProtocolInfo b/TelepathyQt/ProtocolInfo new file mode 100644 index 00000000..02398b47 --- /dev/null +++ b/TelepathyQt/ProtocolInfo @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ProtocolInfo_HEADER_GUARD_ +#define _TelepathyQt_ProtocolInfo_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/protocol-info.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ProtocolParameter b/TelepathyQt/ProtocolParameter new file mode 100644 index 00000000..93db0dc4 --- /dev/null +++ b/TelepathyQt/ProtocolParameter @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ProtocolParameter_HEADER_GUARD_ +#define _TelepathyQt_ProtocolParameter_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/protocol-parameter.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ReadinessHelper b/TelepathyQt/ReadinessHelper new file mode 100644 index 00000000..7e3339bd --- /dev/null +++ b/TelepathyQt/ReadinessHelper @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ReadinessHelper_HEADER_GUARD_ +#define _TelepathyQt_ReadinessHelper_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/readiness-helper.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ReadyObject b/TelepathyQt/ReadyObject new file mode 100644 index 00000000..699a7c54 --- /dev/null +++ b/TelepathyQt/ReadyObject @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ReadyObject_HEADER_GUARD_ +#define _TelepathyQt_ReadyObject_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/ready-object.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ReceivedMessage b/TelepathyQt/ReceivedMessage new file mode 100644 index 00000000..ad43afed --- /dev/null +++ b/TelepathyQt/ReceivedMessage @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_ReceivedMessage_HEADER_GUARD_ +#define _TelepathyQt_ReceivedMessage_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/message.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/RefCounted b/TelepathyQt/RefCounted new file mode 100644 index 00000000..879d287d --- /dev/null +++ b/TelepathyQt/RefCounted @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_RefCounted_HEADER_GUARD_ +#define _TelepathyQt_RefCounted_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/shared-ptr.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/ReferencedHandles b/TelepathyQt/ReferencedHandles new file mode 100644 index 00000000..14c8a125 --- /dev/null +++ b/TelepathyQt/ReferencedHandles @@ -0,0 +1,12 @@ +#ifndef _TelepathyQt_ReferencedHandles_HEADER_GUARD_ +#define _TelepathyQt_ReferencedHandles_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/referenced-handles.h> + +#undef IN_TP_QT_HEADER + +#endif diff --git a/TelepathyQt/ReferencedHandlesIterator b/TelepathyQt/ReferencedHandlesIterator new file mode 100644 index 00000000..7c4eb994 --- /dev/null +++ b/TelepathyQt/ReferencedHandlesIterator @@ -0,0 +1,6 @@ +#ifndef _TelepathyQt_ReferencedHandlesIterator_HEADER_GUARD_ +#define _TelepathyQt_ReferencedHandlesIterator_HEADER_GUARD_ + +#include <TelepathyQt/referenced-handles.h> + +#endif diff --git a/TelepathyQt/RequestableChannelClassSpec b/TelepathyQt/RequestableChannelClassSpec new file mode 100644 index 00000000..404372cd --- /dev/null +++ b/TelepathyQt/RequestableChannelClassSpec @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_RequestableChannelClassSpec_HEADER_GUARD_ +#define _TelepathyQt_RequestableChannelClassSpec_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/requestable-channel-class-spec.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/RequestableChannelClassSpecList b/TelepathyQt/RequestableChannelClassSpecList new file mode 100644 index 00000000..1ab34cb8 --- /dev/null +++ b/TelepathyQt/RequestableChannelClassSpecList @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_RequestableChannelClassSpecList_HEADER_GUARD_ +#define _TelepathyQt_RequestableChannelClassSpecList_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/requestable-channel-class-spec.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/RoomListChannel b/TelepathyQt/RoomListChannel new file mode 100644 index 00000000..b8a66b97 --- /dev/null +++ b/TelepathyQt/RoomListChannel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_RoomListChannel_HEADER_GUARD_ +#define _TelepathyQt_RoomListChannel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/room-list-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/SharedPtr b/TelepathyQt/SharedPtr new file mode 100644 index 00000000..9ecff90c --- /dev/null +++ b/TelepathyQt/SharedPtr @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_SharedPtr_HEADER_GUARD_ +#define _TelepathyQt_SharedPtr_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/shared-ptr.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/SimpleCallObserver b/TelepathyQt/SimpleCallObserver new file mode 100644 index 00000000..e46c423c --- /dev/null +++ b/TelepathyQt/SimpleCallObserver @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_SimpleCallObserver_HEADER_GUARD_ +#define _TelepathyQt_SimpleCallObserver_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/simple-call-observer.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/SimpleObserver b/TelepathyQt/SimpleObserver new file mode 100644 index 00000000..7b984380 --- /dev/null +++ b/TelepathyQt/SimpleObserver @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_SimpleObserver_HEADER_GUARD_ +#define _TelepathyQt_SimpleObserver_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/simple-observer.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/SimpleTextObserver b/TelepathyQt/SimpleTextObserver new file mode 100644 index 00000000..85ffc6ce --- /dev/null +++ b/TelepathyQt/SimpleTextObserver @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_SimpleTextObserver_HEADER_GUARD_ +#define _TelepathyQt_SimpleTextObserver_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/simple-text-observer.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/StatefulDBusProxy b/TelepathyQt/StatefulDBusProxy new file mode 100644 index 00000000..beb6dec7 --- /dev/null +++ b/TelepathyQt/StatefulDBusProxy @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_StatefulDBusProxy_HEADER_GUARD_ +#define _TelepathyQt_StatefulDBusProxy_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/dbus-proxy.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/StatelessDBusProxy b/TelepathyQt/StatelessDBusProxy new file mode 100644 index 00000000..ca5bd535 --- /dev/null +++ b/TelepathyQt/StatelessDBusProxy @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_StatelessDBusProxy_HEADER_GUARD_ +#define _TelepathyQt_StatelessDBusProxy_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/dbus-proxy.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/StreamTubeChannel b/TelepathyQt/StreamTubeChannel new file mode 100644 index 00000000..c541efff --- /dev/null +++ b/TelepathyQt/StreamTubeChannel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_StreamTubeChannel_HEADER_GUARD_ +#define _TelepathyQt_StreamTubeChannel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/stream-tube-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/StreamTubeClient b/TelepathyQt/StreamTubeClient new file mode 100644 index 00000000..8e768358 --- /dev/null +++ b/TelepathyQt/StreamTubeClient @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_StreamTubeClient_HEADER_GUARD_ +#define _TelepathyQt_StreamTubeClient_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/stream-tube-client.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/StreamTubeServer b/TelepathyQt/StreamTubeServer new file mode 100644 index 00000000..f83908f8 --- /dev/null +++ b/TelepathyQt/StreamTubeServer @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_StreamTubeServer_HEADER_GUARD_ +#define _TelepathyQt_StreamTubeServer_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/stream-tube-server.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/StreamedMediaChannel b/TelepathyQt/StreamedMediaChannel new file mode 100644 index 00000000..ea74bd75 --- /dev/null +++ b/TelepathyQt/StreamedMediaChannel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_StreamedMediaChannel_HEADER_GUARD_ +#define _TelepathyQt_StreamedMediaChannel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/streamed-media-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/StreamedMediaStream b/TelepathyQt/StreamedMediaStream new file mode 100644 index 00000000..81f1e6fc --- /dev/null +++ b/TelepathyQt/StreamedMediaStream @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_StreamedMediaStream_HEADER_GUARD_ +#define _TelepathyQt_StreamedMediaStream_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/streamed-media-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/TelepathyQt-uninstalled.pc.in b/TelepathyQt/TelepathyQt-uninstalled.pc.in new file mode 100644 index 00000000..a1d00985 --- /dev/null +++ b/TelepathyQt/TelepathyQt-uninstalled.pc.in @@ -0,0 +1,11 @@ +prefix=/nonexistent +exec_prefix=/nonexistent +abs_top_builddir=${CMAKE_BINARY_DIR} +abs_top_srcdir=${CMAKE_SOURCE_DIR} + +Name: Telepathy-Qt (uninstalled copy) +Description: Qt utility library for the Telepathy framework +Version: ${PACKAGE_VERSION} +Requires.private: QtCore >= 4.5, QtDBus >= 4.5, QtNetwork >= 4.5 +Libs: ${CMAKE_BINARY_DIR}/TelepathyQt/libtelepathy-qt.so +Cflags: -I${CMAKE_SOURCE_DIR} -I${CMAKE_BINARY_DIR} diff --git a/TelepathyQt/TelepathyQt.pc.in b/TelepathyQt/TelepathyQt.pc.in new file mode 100644 index 00000000..a61d6a37 --- /dev/null +++ b/TelepathyQt/TelepathyQt.pc.in @@ -0,0 +1,11 @@ +prefix=${CMAKE_INSTALL_PREFIX} +exec_prefix=${CMAKE_INSTALL_PREFIX} +libdir=${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR} +includedir=${CMAKE_INSTALL_PREFIX}/${INCLUDE_INSTALL_DIR} + +Name: Telepathy-Qt +Description: Qt utility library for the Telepathy framework +Version: ${PACKAGE_VERSION} +Requires.private: QtCore >= 4.5, QtDBus >= 4.5, QtNetwork >= 4.5 +Libs: -L${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR} -ltelepathy-qt +Cflags: -I${CMAKE_INSTALL_PREFIX}/${INCLUDE_INSTALL_DIR}/telepathy-1.0 diff --git a/TelepathyQt/TelepathyQtFarsight-uninstalled.pc.in b/TelepathyQt/TelepathyQtFarsight-uninstalled.pc.in new file mode 100644 index 00000000..679ded95 --- /dev/null +++ b/TelepathyQt/TelepathyQtFarsight-uninstalled.pc.in @@ -0,0 +1,11 @@ +prefix=/nonexistent +exec_prefix=/nonexistent +abs_top_builddir=${CMAKE_BINARY_DIR} +abs_top_srcdir=${CMAKE_SOURCE_DIR} + +Name: Telepathy-Qt-Farsight (uninstalled copy) +Description: Qt Telepathy Farsight utility library for the Telepathy framework +Version: ${PACKAGE_VERSION} +Requires.private: QtCore >= 4.5, QtDBus >= 4.5, telepathy-glib >= 0.7.28, telepathy-farsight >= 0.0.4, TelepathyQt = ${PACKAGE_VERSION} +Libs: ${CMAKE_BINARY_DIR}/TelepathyQt/Farsight/libtelepathy-qt-farsight.so +Cflags: -I${CMAKE_SOURCE_DIR} -I${CMAKE_BINARY_DIR} diff --git a/TelepathyQt/TelepathyQtFarsight.pc.in b/TelepathyQt/TelepathyQtFarsight.pc.in new file mode 100644 index 00000000..476504cd --- /dev/null +++ b/TelepathyQt/TelepathyQtFarsight.pc.in @@ -0,0 +1,11 @@ +prefix=${CMAKE_INSTALL_PREFIX} +exec_prefix=${CMAKE_INSTALL_PREFIX} +libdir=${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR} +includedir=${CMAKE_INSTALL_PREFIX}/${INCLUDE_INSTALL_DIR} + +Name: Telepathy-Qt-Farsight +Description: Qt Telepathy Farsight utility library for the Telepathy framework +Version: ${PACKAGE_VERSION} +Requires.private: QtCore >= 4.5, QtDBus >= 4.5, telepathy-glib >= 0.7.28, telepathy-farsight >= 0.0.4, TelepathyQt = ${PACKAGE_VERSION} +Libs: -L${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR} -ltelepathy-qt-farsight +Cflags: -I${CMAKE_INSTALL_PREFIX}/${INCLUDE_INSTALL_DIR}/telepathy-1.0 diff --git a/TelepathyQt/TextChannel b/TelepathyQt/TextChannel new file mode 100644 index 00000000..de71a775 --- /dev/null +++ b/TelepathyQt/TextChannel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_TextChannel_HEADER_GUARD_ +#define _TelepathyQt_TextChannel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/text-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/TubeChannel b/TelepathyQt/TubeChannel new file mode 100644 index 00000000..e98f80bb --- /dev/null +++ b/TelepathyQt/TubeChannel @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_TubeChannel_HEADER_GUARD_ +#define _TelepathyQt_TubeChannel_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/tube-channel.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Types b/TelepathyQt/Types new file mode 100644 index 00000000..ec49c2d6 --- /dev/null +++ b/TelepathyQt/Types @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Types_HEADER_GUARD_ +#define _TelepathyQt_Types_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/types.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/Utils b/TelepathyQt/Utils new file mode 100644 index 00000000..6c3283d6 --- /dev/null +++ b/TelepathyQt/Utils @@ -0,0 +1,13 @@ +#ifndef _TelepathyQt_Utils_HEADER_GUARD_ +#define _TelepathyQt_Utils_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#define IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/utils.h> + +#undef IN_TP_QT_HEADER + +#endif +// vim:set ft=cpp: diff --git a/TelepathyQt/abstract-client.cpp b/TelepathyQt/abstract-client.cpp new file mode 100644 index 00000000..11a0d74e --- /dev/null +++ b/TelepathyQt/abstract-client.cpp @@ -0,0 +1,988 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/AbstractClient> + +#include <QSharedData> +#include <QString> + +#include <TelepathyQt/ChannelClassSpecList> + +namespace Tp +{ + +/** + * \class AbstractClient + * \ingroup clientclient + * \headerfile TelepathyQt/abstract-client.h <TelepathyQt/AbstractClient> + * + * \brief The AbstractClient class represents a Telepathy client. + * + * Clients are programs used to process channels, approving, handling or + * observing them. User interface processes are the obvious example of clients, + * but they can provide other functionality, such as address-book + * synchronization, message logging, etc. + * + * Each client is either an observer, an approver, a handler, or some + * combination of these. + * + * Clients can be activatable services (those with a D-Bus .service file) + * so that they can run in response to channel creation, or non-activatable + * services (those that do not register a D-Bus .service file + * for their well-known name, but do request it at runtime) so + * that they can process channels, but only if they are already + * running - for instance, a full-screen media center application might do this. + * + * As an optimization, service-activatable clients should install a file + * $XDG_DATA_DIRS/telepathy/clients/clientname.client containing a cached version + * of their immutable properties. The syntax of these files is documented in the <a + * href="http://telepathy.freedesktop.org/spec/org.freedesktop.Telepathy.Client.html"> + * Telepathy specification</a>. + * + * Non-activatable clients may install a .client file, but there's not much + * point in them doing so. + * + * This is a base class and should not be used directly, use the + * specialized classes AbstractClientObserver, AbstractClientApprover and + * AbstractClientHandler instead. + * + * If the same process wants to be either a mix of observer, approver and + * handler, or a combination of those it can multiple inherit the specialized + * abstract classes. + * + * \sa AbstractClientObserver, AbstractClientApprover, AbstractClientHandler + */ + +/** + * Construct a new AbstractClient object. + * + * Note that this is a base class and should not be used directly, use the + * specialized classes AbstractClientObserver, AbstractClientApprover and + * AbstractClientHandler instead. + */ +AbstractClient::AbstractClient() +{ +} + +/** + * Class destructor. + */ +AbstractClient::~AbstractClient() +{ +} + +struct TP_QT_NO_EXPORT AbstractClientObserver::Private +{ + Private(const ChannelClassList &channelFilter, bool shouldRecover) + : channelFilter(channelFilter), + shouldRecover(shouldRecover) + { + } + + ChannelClassList channelFilter; + bool shouldRecover; +}; + +/** + * \class AbstractClientObserver + * \ingroup clientclient + * \headerfile TelepathyQt/abstract-client.h <TelepathyQt/AbstractClientObserver> + * + * \brief The AbstractClientObserver class represents a Telepathy observer. + * + * Observers are clients that monitor the creation of new channels. + * This functionality can be used for things like message logging. + * + * Observers should not modify the state of a channel except via user + * interaction. + * + * Observers must not carry out actions that exactly one process must take + * responsibility for (e.g. acknowledging text messages, or carrying out + * the actual file transfer), since arbitrarily many observers can be + * activated for each channel. The handler is responsible for such tasks. + * + * Handlers may, of course, delegate responsibility for these tasks to other + * clients (including those run as observers), but this must be done + * explicitly via a request from the handler to the observer. + * + * Whenever a collection of new channels is signalled, the channel dispatcher + * will notify all running or activatable observers whose filter indicates that + * they are interested in some of the channels. + * + * Observers are activated for all channels in which they have registered an + * interest - incoming, outgoing or automatically created - although of course + * the filter property can be set to filter specific channels. + * + * To become an observer one should inherit AbstractClientObserver and + * implement the pure virtual observeChannels() method. After that the object + * representing the observer must be registered using + * ClientRegistrar::registerClient(). + * + * When new channels in which the observer has registered an interest are + * announced, the method observeChannels() is invoked. All observers are + * notified simultaneously. + * + * \section observer_usage_sec Usage + * + * \subsection observer_create_sec Implementing an observer + * + * \code + * + * class MyObserver : public AbstractClientObserver + * { + * public: + * MyObserver(const ChannelClassSpecList &channelFilter); + * ~MyObserver() { } + * + * void observeChannels(const MethodInvocationContextPtr<> &context, + * const AccountPtr &account, + * const ConnectionPtr &connection, + * const QList<ChannelPtr> &channels, + * const ChannelDispatchOperationPtr &dispatchOperation, + * const QList<ChannelRequestPtr> &requestsSatisfied, + * const AbstractClientObserver::ObserverInfo &observerInfo); + * }; + * + * MyObserver::MyObserver(const ChannelClassSpecList &channelFilter) + * : AbstractClientObserver(channelFilter) + * { + * } + * + * void MyObserver::observeChannels(const MethodInvocationContextPtr<> &context, + * const AccountPtr &account, + * const ConnectionPtr &connection, + * const QList<ChannelPtr> &channels, + * const ChannelDispatchOperationPtr &dispatchOperation, + * const QList<ChannelRequestPtr> &requestsSatisfied, + * const AbstractClientObserver::ObserverInfo &observerInfo) + * { + * // do something, log messages, ... + * + * context->setFinished(); + * } + * + * \endcode + * + * \subsection observer_register_sec Registering an observer + * + * \code + * + * ClientRegistrar registrar = ClientRegistrar::create(); + * AbstractClientPtr observer = AbstractClientPtr::dynamicCast( + * SharedPtr<MyObserver>(new MyObserver( + * ChannelClassSpecList() << ChannelClassSpec::textChat()))); + * registrar->registerClient(observer, "myobserver"); + * + * \endcode + * + * \sa AbstractClient + */ + +/** + * \class AbstractClientObserver::ObserverInfo + * \ingroup clientclient + * \headerfile TelepathyQt/abstract-client.h <TelepathyQt/AbstractClientObserver> + * + * \brief The AbstractClientObserver::ObserverInfo class provides a wrapper + * around the additional info about the channels passed to observeChannels(). + * + * \sa AbstractClientObserver + */ + +struct TP_QT_NO_EXPORT AbstractClientObserver::ObserverInfo::Private : public QSharedData +{ + Private(const QVariantMap &info) + : info(info) {} + + QVariantMap info; +}; + +AbstractClientObserver::ObserverInfo::ObserverInfo(const QVariantMap &info) + : mPriv(new Private(info)) +{ +} + +AbstractClientObserver::ObserverInfo::ObserverInfo(const ObserverInfo &other) + : mPriv(other.mPriv) +{ +} + +AbstractClientObserver::ObserverInfo::~ObserverInfo() +{ +} + +AbstractClientObserver::ObserverInfo &AbstractClientObserver::ObserverInfo::operator=( + const ObserverInfo &other) +{ + if (this == &other) { + return *this; + } + + mPriv = other.mPriv; + return *this; +} + +QVariantMap AbstractClientObserver::ObserverInfo::allInfo() const +{ + return mPriv->info; +} + +/** + * Construct a new AbstractClientObserver object. + * + * \param channelFilter A specification of the channels in which this observer + * is interested. + * \param shouldRecover Whether upon the startup of this observer, + * observeChannels() will be called for every already + * existing channel matching its observerChannelFilter(). + */ +AbstractClientObserver::AbstractClientObserver( + const ChannelClassSpecList &channelFilter, + bool shouldRecover) + : mPriv(new Private(channelFilter.bareClasses(), shouldRecover)) + // The channel filter is converted here to the low-level class so that any warnings are + // emitted immediately rather than only when the CD introspects this Client +{ +} + +/** + * Class destructor. + */ +AbstractClientObserver::~AbstractClientObserver() +{ + delete mPriv; +} + +/** + * Return the property containing a specification of the channels that this + * channel observer is interested. The observeChannels() method should be called + * by the channel dispatcher whenever any of the newly created channels match + * this description. + * + * See <a + * href="http://telepathy.freedesktop.org/spec/org.freedesktop.Telepathy.Client.Observer.html#org.freedesktop.Telepathy.Client.Observer.ObserverChannelFilter"> + * the Telepathy specification</a> for documentation about the allowed + * types and how to define filters. + * + * This property never changes while the observer process owns its client bus + * name. If an observer wants to add extra channels to its list of interests at + * runtime, it can register an additional client bus name using + * ClientRegistrar::registerClient(). + * To remove those filters, it can release the bus name using + * ClientRegistrar::unregisterClient(). + * + * The same principle is applied to approvers and handlers. + * + * \return A specification of the channels that this channel observer is + * interested as a list of ChannelClassSpec objects. + * \sa observeChannels() + */ +ChannelClassSpecList AbstractClientObserver::observerFilter() const +{ + return ChannelClassSpecList(mPriv->channelFilter); +} + +/** + * Return whether upon the startup of this observer, observeChannels() + * will be called for every already existing channel matching its + * observerChannelFilter(). + * + * \param \c true if this observer observerChannels() will be called for every + * already existing channel matching its observerChannelFilter(), + * \c false otherwise. + */ +bool AbstractClientObserver::shouldRecover() const +{ + return mPriv->shouldRecover; +} + +/** + * \fn void AbstractClientObserver::observeChannels( + * const MethodInvocationContextPtr<> &context, + * const AccountPtr &account, + * const ConnectionPtr &connection, + * const QList<ChannelPtr> &channels, + * const ChannelDispatchOperationPtr &dispatchOperation, + * const QList<ChannelRequestPtr> &requestsSatisfied, + * const ObserverInfo &observerInfo); + * + * Called by the channel dispatcher when channels in which the observer has + * registered an interest are announced. + * + * If the announced channels contains channels that match the + * observerChannelFilter(), and some that do not, then only a subset of the + * channels (those that do match the filter) are passed to this method. + * + * If the channel dispatcher will split up the channels from a single + * announcement and dispatch them separately (for instance because no + * installed handler can handle all of them), it will call this method + * several times. + * + * The observer must not call MethodInvocationContext::setFinished() until it + * is ready for a handler for the channel to run (which may change the + * channel's state). For instance the received \a context object should be + * stored until this method is finished processing and then + * MethodInvocationContext::setFinished() or + * MethodInvocationContext::setFinishedWithError() should be called on the + * received \a context object. + * + * Specialized observers must reimplement this method. + * + * \param context A MethodInvocationContextPtr object that must be used to + * indicate whether this method finished processing. + * \param account The account with which the channels are associated. + * \param connection The connection with which the channels are associated. + * \param channels The channels to be observed. + * \param dispatchOperation The dispatch operation for these channels. + * The object will be invalid (DBusProxy::isValid() + * will be false) if there is no dispatch + * operation in place (because the channels were + * requested, not incoming). + * If the Observer calls + * ChannelDispatchOperation::claim() or + * ChannelDispatchOperation::handleWith() on this + * object, it must be careful to avoid deadlock, since + * these methods cannot return until the observer has + * returned from observeChannels(). + * \param requestsSatisfied The requests satisfied by these channels. + * \param observerInfo Additional information about these channels. + */ + +struct TP_QT_NO_EXPORT AbstractClientApprover::Private +{ + Private(const ChannelClassList &channelFilter) + : channelFilter(channelFilter) + { + } + + ChannelClassList channelFilter; +}; + +/** + * \class AbstractClientApprover + * \ingroup clientclient + * \headerfile TelepathyQt/abstract-client.h <TelepathyQt/AbstractClientApprover> + * + * \brief The AbstractClientApprover class represents a Telepathy approver. + * + * Approvers are clients that notify the user that new channels have been + * created, and allow the user to accept or reject those channels. + * + * Approvers can also select which channel handler will be used for the channel, + * for instance by offering the user a list of possible handlers rather than + * just an accept/reject choice. However, the channel dispatcher must be able to + * prioritize possible handlers on its own using some reasonable heuristic, + * probably based on user configuration. + * + * It is possible (and useful) to have an approver and a channel handler in the + * same process; this is particularly useful if a channel handler wants to claim + * responsibility for particular channels itself. + * + * All approvers are notified simultaneously. For instance, in a desktop system, + * there might be one approver that displays a notification-area icon, one that + * is part of a contact list window and highlights contacts there, and one that + * is part of a full-screen media player. + * + * Any approver can approve the handling of a channel dispatch operation with a + * particular channel handler by calling the + * ChannelDispatchOperation::handleWith() method. Approvers can also attempt to + * claim channels by calling ChannelDispatchOperation::claim(). If this + * succeeds, the approver may handle the channels itself (if it is also a + * handler), or close the channels in order to reject them. + * + * Approvers wishing to reject channels should call the + * ChannelDispatchOperation::claim() method, then (if it succeeds) close the + * channels in any way they see fit. + * + * The first approver to reply gets its decision acted on; any other approvers + * that reply at approximately the same time will get an error, indicating that + * the channel has already been dealt with. + * + * Approvers should usually prompt the user and ask for confirmation, rather + * than dispatching the channel to a handler straight away. + * + * To become an approver one should inherit AbstractClientApprover and + * implement the pure virtual addDispatchOperation() method. After that the + * object representing the approver must be registered using + * ClientRegistrar::registerClient(). + * + * When new channels in which the approver has registered an interest are + * ready to be dispatched, the method addDispatchOperation() is invoked. + * The new channels are represented by a ChannelDispatchOperation object, which + * is passed to the addDispatchOperation() method. + * All approvers are notified simultaneously. + * + * \section approver_usage_sec Usage + * + * \subsection approver_create_sec Implementing an approver + * + * \code + * + * class MyApprover : public AbstractClientApprover + * { + * public: + * MyApprover(const ChannelClassSpecSpecList &channelFilter); + * ~MyApprover() { } + * + * void addDispatchOperation(const MethodInvocationContextPtr<> &context, + * const ChannelDispatchOperationPtr &dispatchOperation); + * }; + * + * MyApprover::MyApprover(const ChannelClassSpecList &channelFilter) + * : AbstractClientApprover(channelFilter) + * { + * } + * + * void MyApprover::addDispatchOperation( + * const MethodInvocationContextPtr<> &context, + * const ChannelDispatchOperationPtr &dispatchOperation) + * { + * // do something with dispatchOperation + * + * context->setFinished(); + * } + * + * \endcode + * + * \subsection approver_register_sec Registering an approver + * + * \code + * + * ClientRegistrar registrar = ClientRegistrar::create(); + * AbstractClientPtr approver = AbstractClientPtr::dynamicCast( + * SharedPtr<MyApprover>(new MyApprover( + * ChannelClassSpecList() << ChannelClassSpec::textChat()))); + * registrar->registerClient(approver, "myapprover"); + * + * \endcode + * + * \sa AbstractClient + */ + +/** + * Construct a new AbstractClientApprover object. + * + * \param channelFilter A specification of the channels in which this approver + * is interested. + */ +AbstractClientApprover::AbstractClientApprover( + const ChannelClassSpecList &channelFilter) + : mPriv(new Private(channelFilter.bareClasses())) +{ +} + +/** + * Class destructor. + */ +AbstractClientApprover::~AbstractClientApprover() +{ + delete mPriv; +} + +/** + * Return the property containing a specification of the channels that this + * channel approver is interested. The addDispatchOperation() method should be + * called by the channel dispatcher whenever at least one of the channels in + * a channel dispatch operation matches this description. + * + * This method works in exactly the same way as the + * AbstractClientObserver::observerChannelFilter() method. In particular, the + * returned value cannot change while the handler process continues to own the + * corresponding client bus name. + * + * In the .client file, represented in the same way as observer channel + * filter, the group is #TP_QT_IFACE_CLIENT_APPROVER followed by + * ApproverChannelFilter instead. + * + * \return A specification of the channels that this channel approver is + * interested as a list of ChannelClassSpec objects. + * \sa addDispatchOperation() + */ +ChannelClassSpecList AbstractClientApprover::approverFilter() const +{ + return ChannelClassSpecList(mPriv->channelFilter); +} + +/** + * \fn void AbstractClientApprover::addDispatchOperation( + * const MethodInvocationContextPtr<> &context, + * const ChannelDispatchOperationPtr &dispatchOperation); + * + * Called by the channel dispatcher when a dispatch operation in which the + * approver has registered an interest is created, or when the approver starts + * up while such channel dispatch operations already exist. + * + * The received \a context object should be stored until this + * method is finished processing and then MethodInvocationContext::setFinished() + * or MethodInvocationContext::setFinishedWithError() should be called on the + * received \a context object. + * + * Specialized approvers must reimplement this method. + * + * \param context A MethodInvocationContextPtr object that must be used to + * indicate whether this method finished processing. + * \param dispatchOperation The dispatch operation to be processed. + */ + +struct TP_QT_NO_EXPORT AbstractClientHandler::Private +{ + Private(const ChannelClassList &channelFilter, + const Capabilities &capabilities, + bool wantsRequestNotification) + : channelFilter(channelFilter), + capabilities(capabilities), + wantsRequestNotification(wantsRequestNotification), + registered(false) + { + } + + ChannelClassList channelFilter; + Capabilities capabilities; + bool wantsRequestNotification; + bool registered; +}; + +/** + * \class AbstractClientHandler + * \ingroup clientclient + * \headerfile TelepathyQt/abstract-client.h <TelepathyQt/AbstractClientHandler> + * + * \brief The AbstractClientHandler class represents a Telepathy handler. + * + * Handlers are the user interface for a channel. They turn an abstract + * channel into something the user wants to see, like a text message + * stream or an audio and/or video call. + * + * For its entire lifetime, each channel on a connection known to the channel + * dispatcher is either being processed by the channel dispatcher, or being + * handled by precisely one handler. + * + * Because each channel is only handled by one handler, handlers may perform + * actions that only make sense to do once, such as acknowledging text messages, + * transferring the file, etc. + * + * When a new incoming channel is offered to approvers by the channel + * dispatcher, it also offers the approvers a list of all the running or + * activatable handlers whose filter indicates that they are able to handle + * the channel. The approvers can choose one of those channel handlers to + * handle the channel. + * + * When a new outgoing channel appears, the channel dispatcher passes it to + * an appropriate channel handler automatically. + * + * To become an handler one should inherit AbstractClientHandler and + * implement the pure virtual bypassApproval() and handleChannels() methods. + * After that the object representing the handler must be registered using + * ClientRegistrar::registerClient(). + * + * When new channels in which the approver has registered an interest are + * ready to be handled, the method handleChannels() is invoked. + * + * \section handler_usage_sec Usage + * + * \subsection handler_create_sec Implementing a handler + * + * \code + * + * class MyHandler : public AbstractClientHandler + * { + * public: + * MyHandler(const ChannelClassSpecList &channelFilter); + * ~MyHandler() { } + * + * void bypassApproval() const; + * + * void handleChannels(const MethodInvocationContextPtr<> &context, + * const AccountPtr &account, + * const ConnectionPtr &connection, + * const QList<ChannelPtr> &channels, + * const QList<ChannelRequestPtr> &requestsSatisfied, + * const QDateTime &userActionTime, + * const AbstractClientHandler::HandlerInfo &handlerInfo); + * }; + * + * MyHandler::MyHandler(const ChannelClassSpecList &channelFilter) + * : AbstractClientHandler(channelFilter) + * { + * } + * + * void MyHandler::bypassApproval() const + * { + * return false; + * } + * + * void MyHandler::handleChannels(const MethodInvocationContextPtr<> &context, + * const AccountPtr &account, + * const ConnectionPtr &connection, + * const QList<ChannelPtr> &channels, + * const QList<ChannelRequestPtr> &requestsSatisfied, + * const QDateTime &userActionTime, + * const AbstractClientHandler::HandlerInfo &handlerInfo) + * { + * // do something + * + * context->setFinished(); + * } + * + * \endcode + * + * \subsection handler_register_sec Registering a handler + * + * \code + * + * ClientRegistrar registrar = ClientRegistrar::create(); + * AbstractClientPtr handler = AbstractClientPtr::dynamicCast( + * SharedPtr<MyHandler>(new MyHandler( + * ChannelClassSpecList() << ChannelClassSpec::textChat()))); + * registrar->registerClient(handler, "myhandler"); + * + * \endcode + * + * \sa AbstractClient + */ + +/** + * \class AbstractClientHandler::Capabilities + * \ingroup clientclient + * \headerfile TelepathyQt/abstract-client.h <TelepathyQt/AbstractClientHandler> + * + * \brief The AbstractClientHandler::Capabilities class provides a wrapper + * around the capabilities of a handler. + * + * \sa AbstractClientHandler + */ + +/** + * \class AbstractClientHandler::HandlerInfo + * \ingroup clientclient + * \headerfile TelepathyQt/abstract-client.h <TelepathyQt/AbstractClientHandler> + * + * \brief The AbstractClientHandler::HandlerInfo class provides a wrapper + * around the additional info about the channels passed to handleChannels(). + * + * \sa AbstractClientHandler + */ + +struct TP_QT_NO_EXPORT AbstractClientHandler::Capabilities::Private : public QSharedData +{ + Private(const QStringList &tokens) + : tokens(QSet<QString>::fromList(tokens)) {} + + QSet<QString> tokens; +}; + +AbstractClientHandler::Capabilities::Capabilities(const QStringList &tokens) + : mPriv(new Private(tokens)) +{ +} + +AbstractClientHandler::Capabilities::Capabilities(const Capabilities &other) + : mPriv(other.mPriv) +{ +} + +AbstractClientHandler::Capabilities::~Capabilities() +{ +} + +AbstractClientHandler::Capabilities &AbstractClientHandler::Capabilities::operator=( + const Capabilities &other) +{ + if (this == &other) { + return *this; + } + + mPriv = other.mPriv; + return *this; +} + +bool AbstractClientHandler::Capabilities::hasToken(const QString &token) const +{ + return mPriv->tokens.contains(token); +} + +void AbstractClientHandler::Capabilities::setToken(const QString &token) +{ + mPriv->tokens.insert(token); +} + +void AbstractClientHandler::Capabilities::unsetToken(const QString &token) +{ + mPriv->tokens.remove(token); +} + +QStringList AbstractClientHandler::Capabilities::allTokens() const +{ + return mPriv->tokens.toList(); +} + +struct TP_QT_NO_EXPORT AbstractClientHandler::HandlerInfo::Private : public QSharedData +{ + Private(const QVariantMap &info) + : info(info) {} + + QVariantMap info; +}; + +AbstractClientHandler::HandlerInfo::HandlerInfo(const QVariantMap &info) + : mPriv(new Private(info)) +{ +} + +AbstractClientHandler::HandlerInfo::HandlerInfo(const HandlerInfo &other) + : mPriv(other.mPriv) +{ +} + +AbstractClientHandler::HandlerInfo::~HandlerInfo() +{ +} + +AbstractClientHandler::HandlerInfo &AbstractClientHandler::HandlerInfo::operator=( + const HandlerInfo &other) +{ + if (this == &other) { + return *this; + } + + mPriv = other.mPriv; + return *this; +} + +QVariantMap AbstractClientHandler::HandlerInfo::allInfo() const +{ + return mPriv->info; +} + +/** + * Construct a new AbstractClientHandler object. + * + * \param channelFilter A specification of the channels in which this observer + * is interested. + * \param wantsRequestNotification Whether this handler wants to receive channel + * requests notification via addRequest() and + * removeRequest(). + * \param capabilities The set of additional capabilities supported by this + * handler. + */ +AbstractClientHandler::AbstractClientHandler(const ChannelClassSpecList &channelFilter, + const Capabilities &capabilities, + bool wantsRequestNotification) + : mPriv(new Private(channelFilter.bareClasses(), capabilities, wantsRequestNotification)) +{ +} + +/** + * Class destructor. + */ +AbstractClientHandler::~AbstractClientHandler() +{ + delete mPriv; +} + +/** + * Return whether this handler is registered. + * + * \return \c true if registered, \c false otherwise. + */ +bool AbstractClientHandler::isRegistered() const +{ + return mPriv->registered; +} + +/** + * Return the property containing a specification of the channels that this + * channel handler can deal with. It will be offered to approvers as a potential + * channel handler for bundles that contain only suitable channels, or for + * suitable channels that must be handled separately. + * + * This method works in exactly the same way as the + * AbstractClientObserver::observerChannelFilter() method. In particular, the + * returned value cannot change while the handler process continues to own the + * corresponding client bus name. + * + * In the .client file, represented in the same way as observer channel + * filter, the group is #TP_QT_IFACE_CLIENT_HANDLER suffixed + * by HandlerChannelFilter instead. + * + * \return A specification of the channels that this channel handler can deal + * with as a list of ChannelClassSpec objects. + */ +ChannelClassSpecList AbstractClientHandler::handlerFilter() const +{ + return ChannelClassSpecList(mPriv->channelFilter); +} + +/** + * Return the set of additional capabilities supported by this handler. + * + * \return The capabilities as an AbstractClientHandler::Capabilities object. + */ +AbstractClientHandler::Capabilities AbstractClientHandler::handlerCapabilities() const +{ + return mPriv->capabilities; +} + +/** + * \fn bool AbstractClientHandler::bypassApproval() const; + * + * Return whether channels destined for this handler are automatically + * handled, without invoking approvers. + * + * \return \c true if automatically handled, \c false otherwise. + */ + +/** + * \fn void AbstractClientHandler::handleChannels( + * const MethodInvocationContextPtr<> &context, + * const AccountPtr &account, + * const ConnectionPtr &connection, + * const QList<ChannelPtr> &channels, + * const QList<ChannelRequestPtr> &requestsSatisfied, + * const QDateTime &userActionTime, + * const HandlerInfo &handlerInfo); + * + * Called by the channel dispatcher when this handler should handle these + * channels, or when this handler should present channels that it is already + * handling to the user (e.g. bring them into the foreground). + * + * Clients are expected to know what channels they're already handling, and + * which channel object corresponds to which window or tab. + * + * After handleChannels() replies successfully by calling + * MethodInvocationContext::setFinished(), the client process is considered + * to be responsible for the channel until it its unique name disappears from + * the bus. + * + * If a process has multiple client bus names - some temporary and some + * long-lived - and drops one of the temporary bus names in order to reduce the + * set of channels that it will handle, any channels that it is already handling + * will remain unaffected. + * + * The received \a context object should be stored until this + * method is finished processing and then MethodInvocationContext::setFinished() + * or MethodInvocationContext::setFinishedWithError() should be called on the + * received \a context object. + * + * Specialized handlers must reimplement this method. + * + * \param context A MethodInvocationContextPtr object that must be used to + * indicate whether this method finished processing. + * \param account The account with which the channels are associated. + * \param connection The connection with which the channels are associated. + * \param channels The channels to be handled. + * \param dispatchOperation The dispatch operation for these channels. + * The object will be invalid (DBusProxy::isValid() + * will be false) if there is no dispatch + * operation in place (because the channels were + * requested, not incoming). + * \param requestsSatisfied The requests satisfied by these channels. + * \param userActionTime The time at which user action occurred, or 0 if this + * channel is to be handled for some reason not involving + * user action. Handlers should use this for + * focus-stealing prevention, if applicable. + * \param handlerInfo Additional information about these channels. + */ + +/** + * Return whether this handler wants to receive notification of channel requests + * via addRequest() and removeRequest(). + * + * This property is set by the constructor and cannot be changed after that. + * + * \return \c true if receiving channel requests notification is desired, + * \c false otherwise. + */ +bool AbstractClientHandler::wantsRequestNotification() const +{ + return mPriv->wantsRequestNotification; +} + +/** + * Called by the channel dispatcher to indicate that channels have been + * requested, and that if the request is successful, they will probably be + * handled by this handler. + * + * This allows the UI to start preparing to handle the channels in advance + * (e.g. render a window with an "in progress" message), improving perceived + * responsiveness. + * + * If the request succeeds and is given to the expected handler, the + * requestsSatisfied parameter to handleChannels() can be used to match the + * channel to a previous addRequest() call. + * + * This lets the UI direct the channels to the window that it already opened. + * + * If the request fails, the expected handler is notified by the channel + * dispatcher calling its removeRequest() method. + * + * This lets the UI close the window or display the error. + * + * The channel dispatcher will attempt to ensure that handleChannels() is called + * on the same handler that received addRequest(). If that isn't possible, + * removeRequest() will be called on the handler that previously received + * addRequest(), with the special error #TP_QT_ERROR_NOT_YOURS, which + * indicates that some other handler received the channel instead. + * + * Expected handling is for the UI to close the window it previously opened. + * + * Specialized handlers that want to be notified of newly requested channel + * should reimplement this method. + * + * \param channelRequest The newly created channel request. + * \sa removeRequest() + */ +void AbstractClientHandler::addRequest( + const ChannelRequestPtr &channelRequest) +{ + // do nothing, subclasses that want to listen requests should reimplement + // this method +} + +/** + * Called by the ChannelDispatcher to indicate that a request previously passed + * to addRequest() has failed and should be disregarded. + * + * Specialized handlers that want to be notified of removed channel requests + * should reimplement this method. + * + * \param channelRequest The channel request that failed. + * \param errorName The name of the D-Bus error with which the request failed. + * If this is #TP_QT_ERROR_NOT_YOURS, this indicates that + * the request succeeded, but all the resulting channels were + * given to some other handler. + * \param errorMessage Any message supplied with the D-Bus error. + */ +void AbstractClientHandler::removeRequest( + const ChannelRequestPtr &channelRequest, + const QString &errorName, const QString &errorMessage) +{ + // do nothing, subclasses that want to listen requests should reimplement + // this method +} + +void AbstractClientHandler::setRegistered(bool registered) +{ + mPriv->registered = registered; +} + +} // Tp diff --git a/TelepathyQt/abstract-client.h b/TelepathyQt/abstract-client.h new file mode 100644 index 00000000..aef615f6 --- /dev/null +++ b/TelepathyQt/abstract-client.h @@ -0,0 +1,323 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_abstract_client_h_HEADER_GUARD_ +#define _TelepathyQt_abstract_client_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Constants> +#include <TelepathyQt/SharedPtr> +#include <TelepathyQt/Types> + +#include <QList> +#include <QObject> +#include <QSharedDataPointer> +#include <QString> +#include <QVariantMap> + +namespace Tp +{ + +class ClientRegistrar; +class ChannelClassSpecList; + +class TP_QT_EXPORT AbstractClient : public RefCounted +{ + Q_DISABLE_COPY(AbstractClient) + +public: + AbstractClient(); + virtual ~AbstractClient(); +}; + +class TP_QT_EXPORT AbstractClientObserver : public virtual AbstractClient +{ + Q_DISABLE_COPY(AbstractClientObserver) + +public: + class ObserverInfo + { + public: + ObserverInfo(const QVariantMap &info = QVariantMap()); + ObserverInfo(const ObserverInfo &other); + ~ObserverInfo(); + + ObserverInfo &operator=(const ObserverInfo &other); + + bool isRecovering() const { return qdbus_cast<bool>(allInfo().value(QLatin1String("recovering"))); } + + QVariantMap allInfo() const; + + private: + struct Private; + QSharedDataPointer<Private> mPriv; + }; + + virtual ~AbstractClientObserver(); + + ChannelClassSpecList observerFilter() const; + + bool shouldRecover() const; + + virtual void observeChannels(const MethodInvocationContextPtr<> &context, + const AccountPtr &account, + const ConnectionPtr &connection, + const QList<ChannelPtr> &channels, + const ChannelDispatchOperationPtr &dispatchOperation, + const QList<ChannelRequestPtr> &requestsSatisfied, + const ObserverInfo &observerInfo) = 0; + +protected: + AbstractClientObserver(const ChannelClassSpecList &channelFilter, bool shouldRecover = false); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +class TP_QT_EXPORT AbstractClientApprover : public virtual AbstractClient +{ + Q_DISABLE_COPY(AbstractClientApprover) + +public: + virtual ~AbstractClientApprover(); + + ChannelClassSpecList approverFilter() const; + + virtual void addDispatchOperation(const MethodInvocationContextPtr<> &context, + const ChannelDispatchOperationPtr &dispatchOperation) = 0; + +protected: + AbstractClientApprover(const ChannelClassSpecList &channelFilter); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +/* + * TODO: use case specific subclasses: + * - StreamTubeHandler(QString(List) protocol(s)) + * - handleTube(DBusTubeChannelPtr, userActionTime) + * - DBusTubeHandler(QString(List) serviceName(s)) + * - handleTube(DBusTubeChannelPtr, userActionTime) + */ +class TP_QT_EXPORT AbstractClientHandler : public virtual AbstractClient +{ + Q_DISABLE_COPY(AbstractClientHandler) + +public: + class Capabilities + { + public: + Capabilities(const QStringList &tokens = QStringList()); + Capabilities(const Capabilities &other); + ~Capabilities(); + + Capabilities &operator=(const Capabilities &other); + + bool hasGTalkP2PNATTraversalToken() const + { + return hasToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/gtalk-p2p")); + } + + void setGTalkP2PNATTraversalToken() + { + setToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/gtalk-p2p")); + } + + void unsetGTalkP2PNATTraversalToken() + { + unsetToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/gtalk-p2p")); + } + + bool hasICEUDPNATTraversalToken() const + { + return hasToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/ice-udp")); + } + + void setICEUDPNATTraversalToken() + { + setToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/ice-udp")); + } + + void unsetICEUDPNATTraversalToken() + { + unsetToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/ice-udp")); + } + + bool hasWLM85NATTraversalToken() const + { + return hasToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/wlm-8.5")); + } + + void setWLM85NATTraversalToken() + { + setToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/wlm-8.5")); + } + + void unsetWLM85NATTraversalToken() + { + unsetToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/wlm-8.5")); + } + + bool hasWLM2009NATTraversalToken() const + { + return hasToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/wlm-2009")); + } + + void setWLM2009NATTraversalToken() + { + setToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/wlm-2009")); + } + + void unsetWLM2009NATTraversalToken() + { + unsetToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/wlm-2009")); + } + + bool hasAudioCodecToken(const QString &mimeSubType) const + { + return hasToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/audio/") + mimeSubType.toLower()); + } + + void setAudioCodecToken(const QString &mimeSubType) + { + setToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/audio/") + mimeSubType.toLower()); + } + + void unsetAudioCodecToken(const QString &mimeSubType) + { + unsetToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/audio/") + mimeSubType.toLower()); + } + + bool hasVideoCodecToken(const QString &mimeSubType) const + { + return hasToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/video/") + mimeSubType.toLower()); + } + + void setVideoCodecToken(const QString &mimeSubType) + { + setToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/video/") + mimeSubType.toLower()); + } + + void unsetVideoCodecToken(const QString &mimeSubType) + { + unsetToken(TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING + + QLatin1String("/video/") + mimeSubType.toLower()); + } + + bool hasToken(const QString &token) const; + void setToken(const QString &token); + void unsetToken(const QString &token); + + QStringList allTokens() const; + + private: + struct Private; + QSharedDataPointer<Private> mPriv; + }; + + class HandlerInfo + { + public: + HandlerInfo(const QVariantMap &info = QVariantMap()); + HandlerInfo(const HandlerInfo &other); + ~HandlerInfo(); + + HandlerInfo &operator=(const HandlerInfo &other); + + QVariantMap allInfo() const; + + private: + struct Private; + QSharedDataPointer<Private> mPriv; + }; + + virtual ~AbstractClientHandler(); + + // FIXME (API/ABI break) Move isRegistered/setRegistered to AbstractClient + bool isRegistered() const; + + ChannelClassSpecList handlerFilter() const; + + Capabilities handlerCapabilities() const; + + virtual bool bypassApproval() const = 0; + + virtual void handleChannels(const MethodInvocationContextPtr<> &context, + const AccountPtr &account, + const ConnectionPtr &connection, + const QList<ChannelPtr> &channels, + const QList<ChannelRequestPtr> &requestsSatisfied, + const QDateTime &userActionTime, + const HandlerInfo &handlerInfo) = 0; + + bool wantsRequestNotification() const; + virtual void addRequest(const ChannelRequestPtr &request); + virtual void removeRequest(const ChannelRequestPtr &request, + const QString &errorName, const QString &errorMessage); + +protected: + AbstractClientHandler(const ChannelClassSpecList &channelFilter, + const Capabilities &capabilities = Capabilities(), + bool wantsRequestNotification = false); + +private: + friend class ClientRegistrar; + + void setRegistered(bool registered); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::AbstractClientObserver::ObserverInfo); +Q_DECLARE_METATYPE(Tp::AbstractClientHandler::Capabilities); +Q_DECLARE_METATYPE(Tp::AbstractClientHandler::HandlerInfo); + +#endif diff --git a/TelepathyQt/abstract-interface.cpp b/TelepathyQt/abstract-interface.cpp new file mode 100644 index 00000000..bdaf677b --- /dev/null +++ b/TelepathyQt/abstract-interface.cpp @@ -0,0 +1,136 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/AbstractInterface> + +#include "TelepathyQt/_gen/abstract-interface.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Constants> +#include <TelepathyQt/DBusProxy> +#include <TelepathyQt/PendingVariant> +#include <TelepathyQt/PendingVariantMap> +#include <TelepathyQt/PendingVoid> +#include <TelepathyQt/Types> + +#include <QDBusPendingCall> +#include <QDBusVariant> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT AbstractInterface::Private +{ + QString mError; + QString mMessage; +}; + +/** + * \class AbstractInterface + * \ingroup clientsideproxies + * \headerfile TelepathyQt/abstract-interface.h <TelepathyQt/AbstractInterface> + * + * \brief The AbstractInterface class is the base class for all client side + * D-Bus interfaces, allowing access to remote methods/properties/signals. + */ + +AbstractInterface::AbstractInterface(const QString &busName, + const QString &path, const QLatin1String &interface, + const QDBusConnection &dbusConnection, QObject *parent) + : QDBusAbstractInterface(busName, path, interface.latin1(), dbusConnection, parent), + mPriv(new Private) +{ +} + +AbstractInterface::AbstractInterface(DBusProxy *parent, const QLatin1String &interface) + : QDBusAbstractInterface(parent->busName(), parent->objectPath(), + interface.latin1(), parent->dbusConnection(), parent), + mPriv(new Private) +{ + connect(parent, SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + this, SLOT(invalidate(Tp::DBusProxy*,QString,QString))); +} + +AbstractInterface::~AbstractInterface() +{ + delete mPriv; +} + +bool AbstractInterface::isValid() const +{ + return QDBusAbstractInterface::isValid() && mPriv->mError.isEmpty(); +} + +QString AbstractInterface::invalidationReason() const +{ + return mPriv->mError; +} + +QString AbstractInterface::invalidationMessage() const +{ + return mPriv->mMessage; +} + +void AbstractInterface::invalidate(DBusProxy *proxy, + const QString &error, const QString &message) +{ + Q_ASSERT(!error.isEmpty()); + + if (mPriv->mError.isEmpty()) { + mPriv->mError = error; + mPriv->mMessage = message; + } +} + +PendingVariant *AbstractInterface::internalRequestProperty(const QString &name) const +{ + QDBusMessage msg = QDBusMessage::createMethodCall(service(), path(), + TP_QT_IFACE_PROPERTIES, QLatin1String("Get")); + msg << interface() << name; + QDBusPendingCall pendingCall = connection().asyncCall(msg); + DBusProxy *proxy = qobject_cast<DBusProxy*>(parent()); + return new PendingVariant(pendingCall, DBusProxyPtr(proxy)); +} + +PendingOperation *AbstractInterface::internalSetProperty(const QString &name, + const QVariant &newValue) +{ + QDBusMessage msg = QDBusMessage::createMethodCall(service(), path(), + TP_QT_IFACE_PROPERTIES, QLatin1String("Set")); + msg << interface() << name << QVariant::fromValue(QDBusVariant(newValue)); + QDBusPendingCall pendingCall = connection().asyncCall(msg); + DBusProxy *proxy = qobject_cast<DBusProxy*>(parent()); + return new PendingVoid(pendingCall, DBusProxyPtr(proxy)); +} + +PendingVariantMap *AbstractInterface::internalRequestAllProperties() const +{ + QDBusMessage msg = QDBusMessage::createMethodCall(service(), path(), + TP_QT_IFACE_PROPERTIES, QLatin1String("GetAll")); + msg << interface(); + QDBusPendingCall pendingCall = connection().asyncCall(msg); + DBusProxy *proxy = qobject_cast<DBusProxy*>(parent()); + return new PendingVariantMap(pendingCall, DBusProxyPtr(proxy)); +} + +} // Tp diff --git a/TelepathyQt/abstract-interface.h b/TelepathyQt/abstract-interface.h new file mode 100644 index 00000000..53205e07 --- /dev/null +++ b/TelepathyQt/abstract-interface.h @@ -0,0 +1,76 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_abstract_interface_h_HEADER_GUARD_ +#define _TelepathyQt_abstract_interface_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> + +#include <QDBusAbstractInterface> + +namespace Tp +{ + +class DBusProxy; +class PendingVariant; +class PendingOperation; +class PendingVariantMap; + +class TP_QT_EXPORT AbstractInterface : public QDBusAbstractInterface +{ + Q_OBJECT + Q_DISABLE_COPY(AbstractInterface) + +public: + virtual ~AbstractInterface(); + + bool isValid() const; + QString invalidationReason() const; + QString invalidationMessage() const; + +protected Q_SLOTS: + virtual void invalidate(Tp::DBusProxy *proxy, + const QString &error, const QString &message); + +protected: + AbstractInterface(DBusProxy *proxy, const QLatin1String &interface); + AbstractInterface(const QString &busName, const QString &path, + const QLatin1String &interface, const QDBusConnection &connection, + QObject *parent); + + PendingVariant *internalRequestProperty(const QString &name) const; + PendingOperation *internalSetProperty(const QString &name, const QVariant &newValue); + PendingVariantMap *internalRequestAllProperties() const; + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/account-capability-filter.dox b/TelepathyQt/account-capability-filter.dox new file mode 100644 index 00000000..7148993e --- /dev/null +++ b/TelepathyQt/account-capability-filter.dox @@ -0,0 +1,30 @@ +/* + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +/** + * \class Tp::AccountCapabilityFilter + * \ingroup utils + * \headerfile TelepathyQt/account-capability-filter.h <TelepathyQt/AccountCapabilityFilter> + * + * \brief The AccountCapabilityFilter class provides a filter object to be used + * to filter accounts by capabilities. + */ diff --git a/TelepathyQt/account-capability-filter.h b/TelepathyQt/account-capability-filter.h new file mode 100644 index 00000000..b5d3cdaa --- /dev/null +++ b/TelepathyQt/account-capability-filter.h @@ -0,0 +1,39 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_account_capability_filter_h_HEADER_GUARD_ +#define _TelepathyQt_account_capability_filter_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/GenericCapabilityFilter> + +namespace Tp +{ + +typedef GenericCapabilityFilter<Account> AccountCapabilityFilter; + +} // Tp + +#endif diff --git a/TelepathyQt/account-factory.cpp b/TelepathyQt/account-factory.cpp new file mode 100644 index 00000000..4f76278e --- /dev/null +++ b/TelepathyQt/account-factory.cpp @@ -0,0 +1,157 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/AccountFactory> + +#include "TelepathyQt/_gen/account-factory.moc.hpp" + +#include <TelepathyQt/Account> + +namespace Tp +{ + +/** + * \class AccountFactory + * \ingroup utils + * \headerfile TelepathyQt/account-factory.h <TelepathyQt/AccountFactory> + * + * \brief The AccountFactory class is responsible for constructing Account + * objects according to application-defined settings. + * + * The class is used by AccountManager and other classes which construct Account + * proxy instances to enable sharing instances of application-defined Account + * subclasses with certain features always ready. + */ + +/** + * Create a new AccountFactory object. + * + * Optionally, the \a features to make ready on all constructed proxies can be specified. The + * default is to make no features ready. It should be noted that unlike Account::becomeReady(), + * FeatureCore isn't assumed. If no features are specified, which is the default behavior, no + * Account::becomeReady() call is made at all and the proxy won't be Account::isReady(). + * + * \param bus The QDBusConnection for proxies constructed using this factory to use. + * \param features The features to make ready on constructed Accounts. + * \return An AccountFactoryPtr object pointing to the newly created + * AccountFactory object. + */ +AccountFactoryPtr AccountFactory::create(const QDBusConnection &bus, const Features &features) +{ + return AccountFactoryPtr(new AccountFactory(bus, features)); +} + +/** + * Construct a new AccountFactory object. + * + * As in create(), it should be noted that unlike Account::becomeReady(), FeatureCore isn't assumed. + * If no \a features are specified, no Account::becomeReady() call is made at all and the proxy + * won't be Account::isReady(). + * + * \param bus The QDBusConnection for proxies constructed using this factory to use. + * \param features The features to make ready on constructed Accounts. + */ +AccountFactory::AccountFactory(const QDBusConnection &bus, const Features &features) + : FixedFeatureFactory(bus) +{ + addFeatures(features); +} + +/** + * Class destructor. + */ +AccountFactory::~AccountFactory() +{ +} + +/** + * Constructs an Account proxy and begins making it ready. + * + * If a valid proxy already exists in the factory cache for the given combination of \a busName and + * \a objectPath, it is returned instead. All newly created proxies are automatically cached until + * they're either DBusProxy::invalidated() or the last reference to them outside the factory has + * been dropped. + * + * The proxy can be accessed immediately after this function returns using PendingReady::proxy(). + * The ready operation only finishes, however, when the features specified by features(), if any, + * are made ready as much as possible. If the service doesn't support a given feature, they won't + * obviously be ready even if the operation finished successfully, as is the case for + * Account::becomeReady(). + * + * \param busName The bus/service name of the D-Bus account object the proxy is constructed for. + * (Usually #TP_QT_ACCOUNT_MANAGER_BUS_NAME). + * \param objectPath The object path of the account. + * \param connFactory The connection factory to use for the Account. + * \param chanFactory The channel factory to use for the Account. + * \param contactFactory The channel factory to use for the Account. + * \return A PendingReady operation with the proxy in PendingReady::proxy(). + */ +PendingReady *AccountFactory::proxy(const QString &busName, const QString &objectPath, + const ConnectionFactoryConstPtr &connFactory, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) const +{ + DBusProxyPtr proxy = cachedProxy(busName, objectPath); + if (proxy.isNull()) { + proxy = construct(busName, objectPath, connFactory, chanFactory, contactFactory); + } + + return nowHaveProxy(proxy); +} + +/** + * Can be used by subclasses to override the Account subclass constructed by the factory. + * + * This is automatically called by proxy() to construct proxy instances if no valid cached proxy is + * found. + * + * The default implementation constructs Tp::Account objects. + * + * \param busName The bus/service name of the D-Bus account object the proxy is constructed for. + * (Usually #TP_QT_ACCOUNT_MANAGER_BUS_NAME). + * \param objectPath The object path of the account. + * \param connFactory The connection factory to use for the Account. + * \param chanFactory The channel factory to use for the Account. + * \param contactFactory The channel factory to use for the Account. + * \return A pointer to the constructed Account object. + */ +AccountPtr AccountFactory::construct(const QString &busName, const QString &objectPath, + const ConnectionFactoryConstPtr &connFactory, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) const +{ + return Account::create(dbusConnection(), busName, objectPath, connFactory, chanFactory, + contactFactory); +} + +/** + * Identity transform, as is appropriate for Account objects. + * + * \param uniqueOrWellKnown The name to transform. + * \return \a uniqueOrWellKnown + */ +QString AccountFactory::finalBusNameFrom(const QString &uniqueOrWellKnown) const +{ + return uniqueOrWellKnown; +} + +} diff --git a/TelepathyQt/account-factory.h b/TelepathyQt/account-factory.h new file mode 100644 index 00000000..cb59f129 --- /dev/null +++ b/TelepathyQt/account-factory.h @@ -0,0 +1,79 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_account_factory_h_HEADER_GUARD_ +#define _TelepathyQt_account_factory_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> +#include <TelepathyQt/SharedPtr> +#include <TelepathyQt/Types> + +#include <TelepathyQt/Account> +#include <TelepathyQt/Feature> +#include <TelepathyQt/FixedFeatureFactory> + +class QDBusConnection; + +namespace Tp +{ + +class PendingReady; + +class TP_QT_EXPORT AccountFactory : public FixedFeatureFactory +{ + Q_OBJECT + Q_DISABLE_COPY(AccountFactory) + +public: + static AccountFactoryPtr create(const QDBusConnection &bus, + const Features &features = Features()); + + virtual ~AccountFactory(); + + PendingReady *proxy(const QString &busName, const QString &objectPath, + const ConnectionFactoryConstPtr &connFactory, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) const; + +protected: + AccountFactory(const QDBusConnection &bus, const Features &features); + + virtual AccountPtr construct(const QString &busName, const QString &objectPath, + const ConnectionFactoryConstPtr &connFactory, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) const; + virtual QString finalBusNameFrom(const QString &uniqueOrWellKnown) const; + // Nothing we'd like to prepare() + // Fixed features + +private: + struct Private; + Private *mPriv; // Currently unused, just for future-proofing +}; + +} // Tp + +#endif diff --git a/TelepathyQt/account-filter.h b/TelepathyQt/account-filter.h new file mode 100644 index 00000000..cc13c52e --- /dev/null +++ b/TelepathyQt/account-filter.h @@ -0,0 +1,39 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_account_filter_h_HEADER_GUARD_ +#define _TelepathyQt_account_filter_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Filter> + +namespace Tp +{ + +typedef Filter<Account> AccountFilter; + +} // Tp + +#endif diff --git a/TelepathyQt/account-manager.cpp b/TelepathyQt/account-manager.cpp new file mode 100644 index 00000000..025301ab --- /dev/null +++ b/TelepathyQt/account-manager.cpp @@ -0,0 +1,1115 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/AccountManager> + +#include "TelepathyQt/_gen/account-manager.moc.hpp" +#include "TelepathyQt/_gen/cli-account-manager.moc.hpp" +#include "TelepathyQt/_gen/cli-account-manager-body.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/AccountCapabilityFilter> +#include <TelepathyQt/AccountFilter> +#include <TelepathyQt/AccountSet> +#include <TelepathyQt/Constants> +#include <TelepathyQt/PendingAccount> +#include <TelepathyQt/PendingComposite> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/ReadinessHelper> + +#include <QQueue> +#include <QSet> +#include <QTimer> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT AccountManager::Private +{ + Private(AccountManager *parent, const AccountFactoryConstPtr &accFactory, + const ConnectionFactoryConstPtr &connFactory, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory); + ~Private(); + + void init(); + + static void introspectMain(Private *self); + + void checkIntrospectionCompleted(); + + QSet<QString> getAccountPathsFromProp(const QVariant &prop); + QSet<QString> getAccountPathsFromProps(const QVariantMap &props); + void addAccountForPath(const QString &accountObjectPath); + + // Public object + AccountManager *parent; + + // Instance of generated interface class + Client::AccountManagerInterface *baseInterface; + + // Mandatory properties interface proxy + Client::DBus::PropertiesInterface *properties; + + ReadinessHelper *readinessHelper; + + AccountFactoryConstPtr accFactory; + ConnectionFactoryConstPtr connFactory; + ChannelFactoryConstPtr chanFactory; + ContactFactoryConstPtr contactFactory; + + // Introspection + int reintrospectionRetries; + bool gotInitialAccounts; + QHash<QString, AccountPtr> incompleteAccounts; + QHash<QString, AccountPtr> accounts; + QStringList supportedAccountProperties; +}; + +static const int maxReintrospectionRetries = 5; +static const int reintrospectionRetryInterval = 3; + +AccountManager::Private::Private(AccountManager *parent, + const AccountFactoryConstPtr &accFactory, const ConnectionFactoryConstPtr &connFactory, + const ChannelFactoryConstPtr &chanFactory, const ContactFactoryConstPtr &contactFactory) + : parent(parent), + baseInterface(new Client::AccountManagerInterface(parent)), + properties(parent->interface<Client::DBus::PropertiesInterface>()), + readinessHelper(parent->readinessHelper()), + accFactory(accFactory), + connFactory(connFactory), + chanFactory(chanFactory), + contactFactory(contactFactory), + reintrospectionRetries(0), + gotInitialAccounts(false) +{ + debug() << "Creating new AccountManager:" << parent->busName(); + + if (accFactory->dbusConnection().name() != parent->dbusConnection().name()) { + warning() << " The D-Bus connection in the account factory is not the proxy connection"; + } + + if (connFactory->dbusConnection().name() != parent->dbusConnection().name()) { + warning() << " The D-Bus connection in the connection factory is not the proxy connection"; + } + + if (chanFactory->dbusConnection().name() != parent->dbusConnection().name()) { + warning() << " The D-Bus connection in the channel factory is not the proxy connection"; + } + + ReadinessHelper::Introspectables introspectables; + + // As AccountManager does not have predefined statuses let's simulate one (0) + ReadinessHelper::Introspectable introspectableCore( + QSet<uint>() << 0, // makesSenseForStatuses + Features(), // dependsOnFeatures + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectMain, + this); + introspectables[FeatureCore] = introspectableCore; + + readinessHelper->addIntrospectables(introspectables); + readinessHelper->becomeReady(Features() << FeatureCore); + + init(); +} + +AccountManager::Private::~Private() +{ + delete baseInterface; +} + +void AccountManager::Private::init() +{ + if (!parent->isValid()) { + return; + } + + parent->connect(baseInterface, + SIGNAL(AccountValidityChanged(QDBusObjectPath,bool)), + SLOT(onAccountValidityChanged(QDBusObjectPath,bool))); + parent->connect(baseInterface, + SIGNAL(AccountRemoved(QDBusObjectPath)), + SLOT(onAccountRemoved(QDBusObjectPath))); +} + +void AccountManager::Private::introspectMain(AccountManager::Private *self) +{ + debug() << "Calling Properties::GetAll(AccountManager)"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + self->properties->GetAll( + QLatin1String(TELEPATHY_INTERFACE_ACCOUNT_MANAGER)), + self->parent); + self->parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotMainProperties(QDBusPendingCallWatcher*))); +} + +void AccountManager::Private::checkIntrospectionCompleted() +{ + if (!parent->isReady(FeatureCore) && + incompleteAccounts.size() == 0) { + readinessHelper->setIntrospectCompleted(FeatureCore, true); + } +} + +QSet<QString> AccountManager::Private::getAccountPathsFromProp( + const QVariant &prop) +{ + QSet<QString> set; + + ObjectPathList paths = qdbus_cast<ObjectPathList>(prop); + if (paths.size() == 0) { + /* maybe the AccountManager is buggy, like Mission Control + * 5.0.beta45, and returns an array of strings rather than + * an array of object paths? */ + QStringList wronglyTypedPaths = qdbus_cast<QStringList>(prop); + if (wronglyTypedPaths.size() > 0) { + warning() << "AccountManager returned wrong type for" + "Valid/InvalidAccounts (expected 'ao', got 'as'); " + "working around it"; + foreach (QString path, wronglyTypedPaths) { + set << path; + } + } + } else { + foreach (const QDBusObjectPath &path, paths) { + set << path.path(); + } + } + + return set; +} + +QSet<QString> AccountManager::Private::getAccountPathsFromProps( + const QVariantMap &props) +{ + return getAccountPathsFromProp(props[QLatin1String("ValidAccounts")]).unite( + getAccountPathsFromProp(props[QLatin1String("InvalidAccounts")])); +} + +void AccountManager::Private::addAccountForPath(const QString &path) +{ + // Also check incompleteAccounts, because otherwise we end up introspecting an account twice + // when getting an AccountValidityChanged signal for a new account before we get the initial + // introspection accounts list from the GetAll return (the GetAll return function + // unconditionally calls addAccountForPath + if (accounts.contains(path) || incompleteAccounts.contains(path)) { + return; + } + + PendingReady *readyOp = accFactory->proxy(parent->busName(), path, connFactory, + chanFactory, contactFactory); + AccountPtr account(AccountPtr::qObjectCast(readyOp->proxy())); + Q_ASSERT(!account.isNull()); + + parent->connect(readyOp, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onAccountReady(Tp::PendingOperation*))); + incompleteAccounts.insert(path, account); +} + +/** + * \class AccountManager + * \ingroup clientam + * \headerfile TelepathyQt/account-manager.h <TelepathyQt/AccountManager> + * + * \brief The AccountManager class represents a Telepathy account manager. + * + * The remote object accessor functions on this object (allAccounts(), + * validAccounts(), 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 AccountManager::FeatureCore has been enabled. + * See the individual methods descriptions for more details. + * + * AccountManager features can be enabled by calling becomeReady() + * with the desired set of features as an argument (currently only AccountManager::FeatureCore is + * supported), and waiting for the resulting PendingOperation to finish. + * + * All accounts returned by AccountManager are guaranteed to have the features set in the + * AccountFactory used by it ready. + * + * A signal is emitted to indicate that accounts are added. See newCreated() for more details. + * + * \section am_usage_sec Usage + * + * \subsection am_create_sec Creating an AccountManager object + * + * One way to create an AccountManager object is to just call the create method. + * For example: + * + * \code AccountManagerPtr am = AccountManager::create(); \endcode + * + * An AccountManagerPtr object is returned, which will automatically keep + * track of object lifetime. + * + * You can also provide a D-Bus connection as a QDBusConnection: + * + * \code AccountManagerPtr am = AccountManager::create(QDBusConnection::sessionBus()); \endcode + * + * \subsection am_ready_sec Making AccountManager ready to use + * + * An AccountManager 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 onAccountManagerReady(Tp::PendingOperation*); + * + * private: + * AccountManagerPtr mAM; + * }; + * + * MyClass::MyClass(QObject *parent) + * : QObject(parent) + * mAM(AccountManager::create()) + * { + * connect(mAM->becomeReady(), + * SIGNAL(finished(Tp::PendingOperation*)), + * SLOT(onAccountManagerReady(Tp::PendingOperation*))); + * } + * + * void MyClass::onAccountManagerReady(Tp::PendingOperation *op) + * { + * if (op->isError()) { + * qWarning() << "Account manager cannot become ready:" << + * op->errorName() << "-" << op->errorMessage(); + * return; + * } + * + * // AccountManager is now ready + * qDebug() << "All accounts:"; + * foreach (const Tp::AccountPtr &acc, mAM->allAccounts()) { + * qDebug() << " path:" << acc->objectPath(); + * } + * } + * + * \endcode + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Feature representing the core that needs to become ready to make the + * AccountManager object usable. + * + * Note that this feature must be enabled in order to use most AccountManager + * methods. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature AccountManager::FeatureCore = Feature(QLatin1String(AccountManager::staticMetaObject.className()), 0, true); + +/** + * Create a new AccountManager object using the given \a bus. + * + * The instance will use an account factory creating Tp::Account objects with Account::FeatureCore + * ready, a connection factory creating Tp::Connection objects with no features ready, a channel + * factory creating stock Tp::Channel subclasses, as appropriate, with no features ready, and a + * contact factory creating Tp::Contact objects with no features ready. + * + * \param bus QDBusConnection to use. + * \return An AccountManagerPtr object pointing to the newly created + * AccountManager object. + */ +AccountManagerPtr AccountManager::create(const QDBusConnection &bus) +{ + return AccountManagerPtr(new AccountManager(bus, + AccountFactory::create(bus, Account::FeatureCore), ConnectionFactory::create(bus), + ChannelFactory::create(bus), ContactFactory::create(), + AccountManager::FeatureCore)); +} + +/** + * Create a new AccountManager using QDBusConnection::sessionBus() and the given factories. + * + * The connection, channel and contact factories are passed to any Account objects created by this + * account manager object. In fact, they're not used directly by AccountManager at all. + * + * A warning is printed if the factories are for a bus different from QDBusConnection::sessionBus(). + * + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return An AccountManagerPtr object pointing to the newly created + * AccountManager object. + */ +AccountManagerPtr AccountManager::create(const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return AccountManagerPtr(new AccountManager(QDBusConnection::sessionBus(), + accountFactory, connectionFactory, channelFactory, contactFactory, + AccountManager::FeatureCore)); +} + +/** + * Create a new AccountManager using the given \a bus and the given factories. + * + * The connection, channel and contact factories are passed to any Account objects created by this + * account manager object. In fact, they're not used directly by AccountManager at all. + * + * A warning is printed if the factories are not for \a bus. + * + * \param bus QDBusConnection to use. + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return An AccountManagerPtr object pointing to the newly created + * AccountManager object. + */ +AccountManagerPtr AccountManager::create(const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return AccountManagerPtr(new AccountManager(bus, + accountFactory, connectionFactory, channelFactory, contactFactory, + AccountManager::FeatureCore)); +} + +/** + * Construct a new AccountManager object using the given \a bus and the given factories. + * + * The connection, channel and contact factories are passed to any Account objects created by this + * account manager object. In fact, they're not used directly by AccountManager at all. + * + * A warning is printed if the factories are not for \a bus. + * + * \param bus QDBusConnection to use. + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \param coreFeature The core feature of the Account subclass. The corresponding introspectable + * should depend on AccountManager::FeatureCore. + */ +AccountManager::AccountManager(const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory, + const Feature &coreFeature) + : StatelessDBusProxy(bus, + TP_QT_ACCOUNT_MANAGER_BUS_NAME, + TP_QT_ACCOUNT_MANAGER_OBJECT_PATH, coreFeature), + OptionalInterfaceFactory<AccountManager>(this), + mPriv(new Private(this, accountFactory, connectionFactory, channelFactory, contactFactory)) +{ +} + +/** + * Class destructor. + */ +AccountManager::~AccountManager() +{ + delete mPriv; +} + +/** + * Return the account factory used by this account manager. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the manager would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the AccountFactory object. + */ +AccountFactoryConstPtr AccountManager::accountFactory() const +{ + return mPriv->accFactory; +} + +/** + * Return the connection factory used by this account manager. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the manager would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ConnectionFactory object. + */ +ConnectionFactoryConstPtr AccountManager::connectionFactory() const +{ + return mPriv->connFactory; +} + +/** + * Return the channel factory used by this account manager. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the manager would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ChannelFactory object. + */ +ChannelFactoryConstPtr AccountManager::channelFactory() const +{ + return mPriv->chanFactory; +} + +/** + * Return the contact factory used by this account manager. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the manager would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ContactFactory object. + */ +ContactFactoryConstPtr AccountManager::contactFactory() const +{ + return mPriv->contactFactory; +} + +/** + * Return a list containing all accounts. + * + * Newly accounts added and/or discovered are signaled via newAccount(). + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A list of pointers to Account objects. + */ +QList<AccountPtr> AccountManager::allAccounts() const +{ + QList<AccountPtr> ret; + foreach (const AccountPtr &account, mPriv->accounts) { + ret << account; + } + return ret; +} + +/** + * Return a set of accounts containing all valid accounts. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::validAccounts() const +{ + QVariantMap filter; + filter.insert(QLatin1String("valid"), true); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all invalid accounts. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::invalidAccounts() const +{ + QVariantMap filter; + filter.insert(QLatin1String("valid"), false); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all enabled accounts. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::enabledAccounts() const +{ + QVariantMap filter; + filter.insert(QLatin1String("enabled"), true); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all disabled accounts. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::disabledAccounts() const +{ + QVariantMap filter; + filter.insert(QLatin1String("enabled"), false); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all online accounts. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::onlineAccounts() const +{ + QVariantMap filter; + filter.insert(QLatin1String("online"), true); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all offline accounts. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::offlineAccounts() const +{ + QVariantMap filter; + filter.insert(QLatin1String("online"), false); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all accounts that support text chats by + * providing a contact identifier. + * + * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities + * ready. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::textChatAccounts() const +{ + if (!accountFactory()->features().contains(Account::FeatureCapabilities)) { + warning() << "Account filtering by capabilities can only be used with an AccountFactory" + << "which makes Account::FeatureCapabilities ready"; + return filterAccounts(AccountFilterConstPtr()); + } + + AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create(); + filter->addRequestableChannelClassSubset(RequestableChannelClassSpec::textChat()); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all accounts that support text chat + * rooms. + * + * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities + * ready. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::textChatroomAccounts() const +{ + if (!accountFactory()->features().contains(Account::FeatureCapabilities)) { + warning() << "Account filtering by capabilities can only be used with an AccountFactory" + << "which makes Account::FeatureCapabilities ready"; + return filterAccounts(AccountFilterConstPtr()); + } + + AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create(); + filter->addRequestableChannelClassSubset(RequestableChannelClassSpec::textChatroom()); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all accounts that support media calls (using the + * StreamedMedia interface) by providing a contact identifier. + * + * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities + * ready. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::streamedMediaCallAccounts() const +{ + if (!accountFactory()->features().contains(Account::FeatureCapabilities)) { + warning() << "Account filtering by capabilities can only be used with an AccountFactory" + << "which makes Account::FeatureCapabilities ready"; + return filterAccounts(AccountFilterConstPtr()); + } + + AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create(); + filter->addRequestableChannelClassSubset(RequestableChannelClassSpec::streamedMediaCall()); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all accounts that support audio calls (using the + * StreamedMedia interface) by providing a contact identifier. + * + * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities + * ready. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::streamedMediaAudioCallAccounts() const +{ + if (!accountFactory()->features().contains(Account::FeatureCapabilities)) { + warning() << "Account filtering by capabilities can only be used with an AccountFactory" + << "which makes Account::FeatureCapabilities ready"; + return filterAccounts(AccountFilterConstPtr()); + } + + AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create(); + filter->addRequestableChannelClassSubset(RequestableChannelClassSpec::streamedMediaAudioCall()); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all accounts that support video calls (using the + * StreamedMedia interface) by providing a contact identifier. + * + * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities + * ready. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::streamedMediaVideoCallAccounts() const +{ + if (!accountFactory()->features().contains(Account::FeatureCapabilities)) { + warning() << "Account filtering by capabilities can only be used with an AccountFactory" + << "which makes Account::FeatureCapabilities ready"; + return filterAccounts(AccountFilterConstPtr()); + } + + AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create(); + filter->addRequestableChannelClassSubset(RequestableChannelClassSpec::streamedMediaVideoCall()); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all accounts that support video calls with audio (using the + * StreamedMedia interface) by providing a contact identifier. + * + * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities + * ready. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::streamedMediaVideoCallWithAudioAccounts() const +{ + if (!accountFactory()->features().contains(Account::FeatureCapabilities)) { + warning() << "Account filtering by capabilities can only be used with an AccountFactory" + << "which makes Account::FeatureCapabilities ready"; + return filterAccounts(AccountFilterConstPtr()); + } + + AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create(); + filter->addRequestableChannelClassSubset( + RequestableChannelClassSpec::streamedMediaVideoCallWithAudio()); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all accounts that support file transfers by + * providing a contact identifier. + * + * For this method to work, you must use an AccountFactory which makes Account::FeatureCapabilities + * ready. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::fileTransferAccounts() const +{ + if (!accountFactory()->features().contains(Account::FeatureCapabilities)) { + warning() << "Account filtering by capabilities can only be used with an AccountFactory" + << "which makes Account::FeatureCapabilities ready"; + return filterAccounts(AccountFilterConstPtr()); + } + + AccountCapabilityFilterPtr filter = AccountCapabilityFilter::create(); + filter->addRequestableChannelClassSubset(RequestableChannelClassSpec::fileTransfer()); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all accounts for the given \a + * protocolName. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \param protocolName The name of the protocol used to filter accounts. + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::accountsByProtocol( + const QString &protocolName) const +{ + if (!isReady(FeatureCore)) { + warning() << "Account filtering requires AccountManager to be ready"; + return filterAccounts(AccountFilterConstPtr()); + } + + QVariantMap filter; + filter.insert(QLatin1String("protocolName"), protocolName); + return filterAccounts(filter); +} + +/** + * Return a set of accounts containing all accounts that match the given \a + * filter criteria. + * + * For AccountCapabilityFilter filtering, an AccountFactory which makes + * Account::FeatureCapabilities ready must be used. + * + * See AccountSet documentation for more details. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \param filter The desired filter. + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::filterAccounts(const AccountFilterConstPtr &filter) const +{ + if (!isReady(FeatureCore)) { + warning() << "Account filtering requires AccountManager to be ready"; + return AccountSetPtr(new AccountSet(AccountManagerPtr( + (AccountManager *) this), AccountFilterConstPtr())); + } + + return AccountSetPtr(new AccountSet(AccountManagerPtr( + (AccountManager *) this), filter)); +} + +/** + * Return a set of accounts containing all accounts that match the given \a + * filter criteria. + * + * The \a filter is composed by Account property names and values as map items. + * + * The following example will return all jabber accounts that are enabled: + * + * \code + * + * void MyClass::init() + * { + * mAM = AccountManager::create(); + * connect(mAM->becomeReady(), + * SIGNAL(finished(Tp::PendingOperation*)), + * SLOT(onAccountManagerReady(Tp::PendingOperation*))); + * } + * + * void MyClass::onAccountManagerReady(Tp::PendingOperation *op) + * { + * if (op->isError()) { + * qWarning() << "Account manager cannot become ready:" << + * op->errorName() << "-" << op->errorMessage(); + * return; + * } + * + * QVariantMap filter; + * filter.insert(QLatin1String("protocolName"), QLatin1String("jabber")); + * filter.insert(QLatin1String("enabled"), true); + * filteredAccountSet = mAM->filterAccounts(filter); + * // connect to AccountSet::accountAdded/accountRemoved signals + * QList<AccountPtr> accounts = filteredAccountSet->accounts(); + * // do something with accounts + * } + * + * \endcode + * + * See AccountSet documentation for more details. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \param filter The desired filter. + * \return A pointer to an AccountSet object containing the matching accounts. + */ +AccountSetPtr AccountManager::filterAccounts(const QVariantMap &filter) const +{ + if (!isReady(FeatureCore)) { + warning() << "Account filtering requires AccountManager to be ready"; + return AccountSetPtr(new AccountSet(AccountManagerPtr( + (AccountManager *) this), QVariantMap())); + } + + return AccountSetPtr(new AccountSet(AccountManagerPtr( + (AccountManager *) this), filter)); +} + +/** + * Return the account for the given \a path. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \param path The account object path. + * \return A pointer to an AccountSet object containing the matching accounts. + * \sa allAccounts(), accountsForPaths() + */ +AccountPtr AccountManager::accountForPath(const QString &path) const +{ + if (!isReady(FeatureCore)) { + return AccountPtr(); + } + + return mPriv->accounts.value(path); +} + +/** + * Return a list of accounts for the given \a paths. + * + * The returned list will have one AccountPtr object for each given path. If + * a given path is invalid the returned AccountPtr object will point to 0. + * AccountPtr::isNull() will return true. + * + * This method requires AccountManager::FeatureCore to be ready. + * + * \param paths List of accounts object paths. + * \return A list of pointers to Account objects for the given + * \a paths. Null AccountPtr objects will be used as list elements for each invalid path. + * \sa allAccounts(), accountForPath() + */ +QList<AccountPtr> AccountManager::accountsForPaths(const QStringList &paths) const +{ + if (!isReady(FeatureCore)) { + return QList<AccountPtr>(); + } + + QList<AccountPtr> result; + foreach (const QString &path, paths) { + result << accountForPath(path); + } + return result; +} + +/** + * Return a list of the fully qualified names of properties that can be set + * when calling createAccount(). + * + * \return A list of fully qualified D-Bus property names, + * such as "org.freedesktop.Telepathy.Account.Enabled". + * \sa createAccount() + */ +QStringList AccountManager::supportedAccountProperties() const +{ + return mPriv->supportedAccountProperties; +} + +/** + * Create an account with the given parameters. + * + * The optional \a properties argument can be used to set any property listed in + * supportedAccountProperties() at the time the account is created. + * + * \param connectionManager The name of the connection manager to create the account + * for. + * \param protocol The name of the protocol to create the account for. + * \param displayName The account display name. + * \param parameters The account parameters. + * \param properties An optional map from fully qualified D-Bus property + * names such as "org.freedesktop.Telepathy.Account.Enabled" + * to their values. + * \return A PendingAccount object which will emit PendingAccount::finished + * when the account has been created of failed its creation process. + * \sa supportedAccountProperties() + */ +PendingAccount *AccountManager::createAccount(const QString &connectionManager, + const QString &protocol, const QString &displayName, + const QVariantMap ¶meters, const QVariantMap &properties) +{ + return new PendingAccount(AccountManagerPtr(this), connectionManager, + protocol, displayName, parameters, properties); +} + +/** + * Return the Client::AccountManagerInterface interface proxy object for this + * account manager. 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::AccountManagerInterface object for + * this AccountManager object. + */ +Client::AccountManagerInterface *AccountManager::baseInterface() const +{ + return mPriv->baseInterface; +} + +void AccountManager::introspectMain() +{ + mPriv->introspectMain(mPriv); +} + +void AccountManager::gotMainProperties(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + QVariantMap props; + + if (!reply.isError()) { + mPriv->gotInitialAccounts = true; + + debug() << "Got reply to Properties.GetAll(AccountManager)"; + props = reply.value(); + + if (props.contains(QLatin1String("Interfaces"))) { + setInterfaces(qdbus_cast<QStringList>(props[QLatin1String("Interfaces")])); + mPriv->readinessHelper->setInterfaces(interfaces()); + } + + if (props.contains(QLatin1String("SupportedAccountProperties"))) { + mPriv->supportedAccountProperties = + qdbus_cast<QStringList>(props[QLatin1String("SupportedAccountProperties")]); + } + + QSet<QString> paths = mPriv->getAccountPathsFromProps(props); + foreach (const QString &path, paths) { + mPriv->addAccountForPath(path); + } + + mPriv->checkIntrospectionCompleted(); + } else { + if (mPriv->reintrospectionRetries++ < maxReintrospectionRetries) { + int retryInterval = reintrospectionRetryInterval; + if (reply.error().type() == QDBusError::TimedOut) { + retryInterval = 0; + } + QTimer::singleShot(retryInterval, this, SLOT(introspectMain())); + } else { + warning() << "GetAll(AccountManager) failed with" << + reply.error().name() << ":" << reply.error().message(); + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, + false, reply.error()); + } + } + + watcher->deleteLater(); +} + +void AccountManager::onAccountReady(Tp::PendingOperation *op) +{ + PendingReady *pr = qobject_cast<PendingReady*>(op); + AccountPtr account(AccountPtr::qObjectCast(pr->proxy())); + QString path = account->objectPath(); + + /* Some error occurred or the account was removed before become ready */ + if (op->isError() || !mPriv->incompleteAccounts.contains(path)) { + mPriv->incompleteAccounts.remove(path); + mPriv->checkIntrospectionCompleted(); + return; + } + + mPriv->incompleteAccounts.remove(path); + + // We shouldn't end up here twice for the same account - that would also mean newAccount being + // emitted twice for an account, and AccountSets getting confused as a result + Q_ASSERT(!mPriv->accounts.contains(path)); + mPriv->accounts.insert(path, account); + + if (isReady(FeatureCore)) { + emit newAccount(account); + } + + mPriv->checkIntrospectionCompleted(); +} + +void AccountManager::onAccountValidityChanged(const QDBusObjectPath &objectPath, + bool valid) +{ + if (!mPriv->gotInitialAccounts) { + return; + } + + QString path = objectPath.path(); + + if (!mPriv->incompleteAccounts.contains(path) && + !mPriv->accounts.contains(path)) { + debug() << "New account" << path; + mPriv->addAccountForPath(path); + } +} + +void AccountManager::onAccountRemoved(const QDBusObjectPath &objectPath) +{ + if (!mPriv->gotInitialAccounts) { + return; + } + + QString path = objectPath.path(); + + /* the account is either in mPriv->incompleteAccounts or mPriv->accounts */ + if (mPriv->accounts.contains(path)) { + mPriv->accounts.remove(path); + + if (isReady(FeatureCore)) { + debug() << "Account" << path << "removed"; + } else { + debug() << "Account" << path << "removed while the AM " + "was not completely introspected"; + } + } else if (mPriv->incompleteAccounts.contains(path)) { + mPriv->incompleteAccounts.remove(path); + debug() << "Account" << path << "was removed, but it was " + "not completely introspected, ignoring"; + } else { + debug() << "Got AccountRemoved for unknown account" << path << ", ignoring"; + } +} + +/** + * \fn void AccountManager::newAccount(const Tp::AccountPtr &account) + * + * Emitted when a new account is created. + * + * The new \a account will have the features set in the AccountFactory used by this + * account manager ready and the same connection, channel and contact factories as used by this + * account manager. + * + * \param account The newly created account. + */ + +} // Tp diff --git a/TelepathyQt/account-manager.h b/TelepathyQt/account-manager.h new file mode 100644 index 00000000..53ab2cd7 --- /dev/null +++ b/TelepathyQt/account-manager.h @@ -0,0 +1,152 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_account_manager_h_HEADER_GUARD_ +#define _TelepathyQt_account_manager_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/_gen/cli-account-manager.h> + +#include <TelepathyQt/Account> +#include <TelepathyQt/AccountFactory> +#include <TelepathyQt/ChannelFactory> +#include <TelepathyQt/ConnectionFactory> +#include <TelepathyQt/ContactFactory> +#include <TelepathyQt/StatelessDBusProxy> +#include <TelepathyQt/Filter> +#include <TelepathyQt/OptionalInterfaceFactory> +#include <TelepathyQt/SharedPtr> +#include <TelepathyQt/Types> + +#include <QDBusObjectPath> +#include <QSet> +#include <QString> +#include <QVariantMap> + +namespace Tp +{ + +class PendingAccount; + +class TP_QT_EXPORT AccountManager : public StatelessDBusProxy, + public OptionalInterfaceFactory<AccountManager> +{ + Q_OBJECT + Q_DISABLE_COPY(AccountManager) + +public: + static const Feature FeatureCore; + + static AccountManagerPtr create(const QDBusConnection &bus); + static AccountManagerPtr create( + const AccountFactoryConstPtr &accountFactory = + AccountFactory::create(QDBusConnection::sessionBus(), Account::FeatureCore), + const ConnectionFactoryConstPtr &connectionFactory = + ConnectionFactory::create(QDBusConnection::sessionBus()), + const ChannelFactoryConstPtr &channelFactory = + ChannelFactory::create(QDBusConnection::sessionBus()), + const ContactFactoryConstPtr &contactFactory = + ContactFactory::create()); + static AccountManagerPtr create(const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory = + ContactFactory::create()); + + virtual ~AccountManager(); + + AccountFactoryConstPtr accountFactory() const; + ConnectionFactoryConstPtr connectionFactory() const; + ChannelFactoryConstPtr channelFactory() const; + ContactFactoryConstPtr contactFactory() const; + + QList<AccountPtr> allAccounts() const; + + AccountSetPtr validAccounts() const; + AccountSetPtr invalidAccounts() const; + + AccountSetPtr enabledAccounts() const; + AccountSetPtr disabledAccounts() const; + + AccountSetPtr onlineAccounts() const; + AccountSetPtr offlineAccounts() const; + + AccountSetPtr textChatAccounts() const; + AccountSetPtr textChatroomAccounts() const; + + AccountSetPtr streamedMediaCallAccounts() const; + AccountSetPtr streamedMediaAudioCallAccounts() const; + AccountSetPtr streamedMediaVideoCallAccounts() const; + AccountSetPtr streamedMediaVideoCallWithAudioAccounts() const; + + AccountSetPtr fileTransferAccounts() const; + + AccountSetPtr accountsByProtocol(const QString &protocolName) const; + + AccountSetPtr filterAccounts(const AccountFilterConstPtr &filter) const; + AccountSetPtr filterAccounts(const QVariantMap &filter) const; + + AccountPtr accountForPath(const QString &path) const; + QList<AccountPtr> accountsForPaths(const QStringList &paths) const; + + QStringList supportedAccountProperties() const; + PendingAccount *createAccount(const QString &connectionManager, + const QString &protocol, const QString &displayName, + const QVariantMap ¶meters, + const QVariantMap &properties = QVariantMap()); + +Q_SIGNALS: + void newAccount(const Tp::AccountPtr &account); + +protected: + AccountManager(const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory, + const Feature &coreFeature); + + Client::AccountManagerInterface *baseInterface() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void introspectMain(); + TP_QT_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onAccountReady(Tp::PendingOperation *op); + TP_QT_NO_EXPORT void onAccountValidityChanged(const QDBusObjectPath &objectPath, + bool valid); + TP_QT_NO_EXPORT void onAccountRemoved(const QDBusObjectPath &objectPath); + +private: + friend class PendingAccount; + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/account-manager.xml b/TelepathyQt/account-manager.xml new file mode 100644 index 00000000..278f4276 --- /dev/null +++ b/TelepathyQt/account-manager.xml @@ -0,0 +1,9 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Account Manager interfaces</tp:title> + +<xi:include href="../spec/Account_Manager.xml"/> + +</tp:spec> diff --git a/TelepathyQt/account-property-filter.cpp b/TelepathyQt/account-property-filter.cpp new file mode 100644 index 00000000..10c5ba77 --- /dev/null +++ b/TelepathyQt/account-property-filter.cpp @@ -0,0 +1,94 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/AccountPropertyFilter> + +#include "TelepathyQt/debug-internal.h" + +#include <QLatin1String> +#include <QStringList> +#include <QMetaObject> +#include <QVariantMap> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT AccountPropertyFilter::Private +{ + Private() + { + if (supportedAccountProperties.isEmpty()) { + const QMetaObject metaObject = Account::staticMetaObject; + for (int i = metaObject.propertyOffset(); i < metaObject.propertyCount(); ++i) { + supportedAccountProperties << QLatin1String(metaObject.property(i).name()); + } + } + } + + static QStringList supportedAccountProperties; +}; + +QStringList AccountPropertyFilter::Private::supportedAccountProperties; + +/** + * \class Tp::AccountPropertyFilter + * \ingroup utils + * \headerfile TelepathyQt/account-property-filter.h <TelepathyQt/AccountPropertyFilter> + * + * \brief The AccountPropertyFilter class provides a filter object to be used + * to filter accounts by properties. + */ + +AccountPropertyFilter::AccountPropertyFilter() + : GenericPropertyFilter<Account>(), + mPriv(new Private()) +{ +} + +AccountPropertyFilter::~AccountPropertyFilter() +{ + delete mPriv; +} + +bool AccountPropertyFilter::isValid() const +{ + QVariantMap mFilter = filter(); + if (mFilter.isEmpty()) { + return false; + } + + QVariantMap::const_iterator i = mFilter.constBegin(); + QVariantMap::const_iterator end = mFilter.constEnd(); + while (i != end) { + QString propertyName = i.key(); + if (!mPriv->supportedAccountProperties.contains(propertyName)) { + warning() << "Invalid filter key" << propertyName << + "while filtering account by properties"; + return false; + } + ++i; + } + + return true; +} + +} // Tp diff --git a/TelepathyQt/account-property-filter.h b/TelepathyQt/account-property-filter.h new file mode 100644 index 00000000..5694831e --- /dev/null +++ b/TelepathyQt/account-property-filter.h @@ -0,0 +1,59 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_account_property_filter_h_HEADER_GUARD_ +#define _TelepathyQt_account_property_filter_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Account> +#include <TelepathyQt/GenericPropertyFilter> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class TP_QT_EXPORT AccountPropertyFilter : public GenericPropertyFilter<Account> +{ +public: + static AccountPropertyFilterPtr create() + { + return AccountPropertyFilterPtr(new AccountPropertyFilter); + } + + ~AccountPropertyFilter(); + + bool isValid() const; + +private: + AccountPropertyFilter(); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/account-set-internal.h b/TelepathyQt/account-set-internal.h new file mode 100644 index 00000000..f7dbf9e0 --- /dev/null +++ b/TelepathyQt/account-set-internal.h @@ -0,0 +1,82 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/AccountPropertyFilter> + +namespace Tp +{ + +class ConnectionCapabilities; + +struct TP_QT_NO_EXPORT AccountSet::Private +{ + class AccountWrapper; + + Private(AccountSet *parent, const AccountManagerPtr &accountManager, + const AccountFilterConstPtr &filter); + Private(AccountSet *parent, const AccountManagerPtr &accountManager, + const QVariantMap &filter); + + void init(); + void connectSignals(); + void insertAccounts(); + void insertAccount(const AccountPtr &account); + void removeAccount(const AccountPtr &account); + void wrapAccount(const AccountPtr &account); + void filterAccount(const AccountPtr &account); + bool accountMatchFilter(AccountWrapper *account); + + AccountSet *parent; + AccountManagerPtr accountManager; + AccountFilterConstPtr filter; + QHash<QString, AccountWrapper *> wrappers; + QHash<QString, AccountPtr> accounts; + bool ready; +}; + +class TP_QT_NO_EXPORT AccountSet::Private::AccountWrapper : public QObject +{ + Q_OBJECT + +public: + AccountWrapper(const AccountPtr &account, QObject *parent = 0); + ~AccountWrapper(); + + AccountPtr account() const { return mAccount; } + +Q_SIGNALS: + void accountRemoved(const Tp::AccountPtr &account); + void accountPropertyChanged(const Tp::AccountPtr &account, + const QString &propertyName); + void accountCapabilitiesChanged(const Tp::AccountPtr &account, + const Tp::ConnectionCapabilities &capabilities); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onAccountRemoved(); + TP_QT_NO_EXPORT void onAccountPropertyChanged(const QString &propertyName); + TP_QT_NO_EXPORT void onAccountCapalitiesChanged(const Tp::ConnectionCapabilities &capabilities); + +private: + AccountPtr mAccount; +}; + +} // Tp diff --git a/TelepathyQt/account-set.cpp b/TelepathyQt/account-set.cpp new file mode 100644 index 00000000..fce92a39 --- /dev/null +++ b/TelepathyQt/account-set.cpp @@ -0,0 +1,418 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/AccountSet> +#include "TelepathyQt/account-set-internal.h" + +#include "TelepathyQt/_gen/account-set.moc.hpp" +#include "TelepathyQt/_gen/account-set-internal.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Account> +#include <TelepathyQt/AccountFilter> +#include <TelepathyQt/AccountManager> +#include <TelepathyQt/ConnectionCapabilities> +#include <TelepathyQt/ConnectionManager> + +namespace Tp +{ + +AccountSet::Private::Private(AccountSet *parent, + const AccountManagerPtr &accountManager, + const AccountFilterConstPtr &filter) + : parent(parent), + accountManager(accountManager), + filter(filter), + ready(false) +{ + init(); +} + +AccountSet::Private::Private(AccountSet *parent, + const AccountManagerPtr &accountManager, + const QVariantMap &filterMap) + : parent(parent), + accountManager(accountManager), + ready(false) +{ + AccountPropertyFilterPtr propertyFilter = AccountPropertyFilter::create(); + for (QVariantMap::const_iterator i = filterMap.constBegin(); + i != filterMap.constEnd(); ++i) { + propertyFilter->addProperty(i.key(), i.value()); + } + filter = AccountFilterPtr::dynamicCast(propertyFilter); + init(); +} + +void AccountSet::Private::init() +{ + if (filter->isValid()) { + connectSignals(); + insertAccounts(); + ready = true; + } +} + +void AccountSet::Private::connectSignals() +{ + parent->connect(accountManager.data(), + SIGNAL(newAccount(Tp::AccountPtr)), + SLOT(onNewAccount(Tp::AccountPtr))); +} + +void AccountSet::Private::insertAccounts() +{ + foreach (const Tp::AccountPtr &account, accountManager->allAccounts()) { + insertAccount(account); + } +} + +void AccountSet::Private::insertAccount(const Tp::AccountPtr &account) +{ + QString accountPath = account->objectPath(); + Q_ASSERT(!wrappers.contains(accountPath)); + wrapAccount(account); + filterAccount(account); +} + +void AccountSet::Private::removeAccount(const Tp::AccountPtr &account) +{ + QString accountPath = account->objectPath(); + Q_ASSERT(wrappers.contains(accountPath)); + accounts.remove(accountPath); + + AccountWrapper *wrapper = wrappers.take(accountPath); + Q_ASSERT(wrapper->disconnect(parent)); + wrapper->deleteLater(); + + emit parent->accountRemoved(account); +} + +void AccountSet::Private::wrapAccount(const AccountPtr &account) +{ + AccountWrapper *wrapper = new AccountWrapper(account, parent); + parent->connect(wrapper, + SIGNAL(accountRemoved(Tp::AccountPtr)), + SLOT(onAccountRemoved(Tp::AccountPtr))); + parent->connect(wrapper, + SIGNAL(accountPropertyChanged(Tp::AccountPtr,QString)), + SLOT(onAccountChanged(Tp::AccountPtr))); + parent->connect(wrapper, + SIGNAL(accountCapabilitiesChanged(Tp::AccountPtr,Tp::ConnectionCapabilities)), + SLOT(onAccountChanged(Tp::AccountPtr))); + wrappers.insert(account->objectPath(), wrapper); +} + +void AccountSet::Private::filterAccount(const AccountPtr &account) +{ + QString accountPath = account->objectPath(); + Q_ASSERT(wrappers.contains(accountPath)); + AccountWrapper *wrapper = wrappers[accountPath]; + + /* account changed, let's check if it matches filter */ + if (accountMatchFilter(wrapper)) { + if (!accounts.contains(account->objectPath())) { + accounts.insert(account->objectPath(), account); + if (ready) { + emit parent->accountAdded(account); + } + } + } else { + if (accounts.contains(account->objectPath())) { + accounts.remove(account->objectPath()); + if (ready) { + emit parent->accountRemoved(account); + } + } + } +} + +bool AccountSet::Private::accountMatchFilter(AccountWrapper *wrapper) +{ + if (!filter) { + return true; + } + + return filter->matches(wrapper->account()); +} + +AccountSet::Private::AccountWrapper::AccountWrapper( + const AccountPtr &account, QObject *parent) + : QObject(parent), + mAccount(account) +{ + connect(account.data(), + SIGNAL(removed()), + SLOT(onAccountRemoved())); + connect(account.data(), + SIGNAL(propertyChanged(QString)), + SLOT(onAccountPropertyChanged(QString))); + connect(account.data(), + SIGNAL(capabilitiesChanged(Tp::ConnectionCapabilities)), + SLOT(onAccountCapalitiesChanged(Tp::ConnectionCapabilities))); +} + +AccountSet::Private::AccountWrapper::~AccountWrapper() +{ +} + +void AccountSet::Private::AccountWrapper::onAccountRemoved() +{ + emit accountRemoved(mAccount); +} + +void AccountSet::Private::AccountWrapper::onAccountPropertyChanged( + const QString &propertyName) +{ + emit accountPropertyChanged(mAccount, propertyName); +} + +void AccountSet::Private::AccountWrapper::onAccountCapalitiesChanged( + const ConnectionCapabilities &caps) +{ + emit accountCapabilitiesChanged(mAccount, caps); +} + +/** + * \class AccountSet + * \ingroup clientaccount + * \headerfile TelepathyQt/account-set.h <TelepathyQt/AccountSet> + * + * \brief The AccountSet class represents a set of Telepathy accounts + * filtered by a given criteria. + * + * AccountSet is automatically updated whenever accounts that match the given + * criteria are added, removed or updated. + * + * \section account_set_usage_sec Usage + * + * \subsection account_set_create_sec Creating an AccountSet object + * + * The easiest way to create AccountSet objects is through AccountManager. One + * can just use the AccountManager convenience methods such as + * AccountManager::validAccounts() to get a set of account objects + * representing valid accounts. + * + * For example: + * + * \code + * + * class MyClass : public QObject + * { + * QOBJECT + * + * public: + * MyClass(QObject *parent = 0); + * ~MyClass() { } + * + * private Q_SLOTS: + * void onAccountManagerReady(Tp::PendingOperation *); + * void onValidAccountAdded(const Tp::AccountPtr &); + * void onValidAccountRemoved(const Tp::AccountPtr &); + * + * private: + * AccountManagerPtr am; + * AccountSetPtr validAccountsSet; + * }; + * + * MyClass::MyClass(QObject *parent) + * : QObject(parent) + * am(AccountManager::create()) + * { + * connect(am->becomeReady(), + * SIGNAL(finished(Tp::PendingOperation*)), + * SLOT(onAccountManagerReady(Tp::PendingOperation*))); + * } + * + * void MyClass::onAccountManagerReady(Tp::PendingOperation *op) + * { + * if (op->isError()) { + * qWarning() << "Account manager cannot become ready:" << + * op->errorName() << "-" << op->errorMessage(); + * return; + * } + * + * validAccountsSet = am->validAccounts(); + * connect(validAccountsSet.data(), + * SIGNAL(accountAdded(const Tp::AccountPtr &)), + * SLOT(onValidAccountAdded(const Tp::AccountPtr &))); + * connect(validAccountsSet.data(), + * SIGNAL(accountRemoved(const Tp::AccountPtr &)), + * SLOT(onValidAccountRemoved(const Tp::AccountPtr &))); + * + * QList<AccountPtr> accounts = validAccountsSet->accounts(); + * // do something with accounts + * } + * + * void MyClass::onValidAccountAdded(const Tp::AccountPtr &account) + * { + * // do something with account + * } + * + * void MyClass::onValidAccountRemoved(const Tp::AccountPtr &account) + * { + * // do something with account + * } + * + * \endcode + * + * You can also define your own filter using AccountManager::filterAccounts: + * + * \code + * + * void MyClass::onAccountManagerReady(Tp::PendingOperation *op) + * { + * ... + * + * AccountPropertyFilterPtr filter = AccountPropertyFilter::create(); + * filter->addProperty(QLatin1String("protocolName"), QLatin1String("jabber")); + * filter->addProperty(QLatin1String("enabled"), true); + * + * AccountSetPtr filteredAccountSet = am->filterAccounts(filter); + * // connect to AccountSet::accountAdded/accountRemoved signals + * QList<AccountPtr> accounts = filteredAccountSet->accounts(); + * // do something with accounts + * + * .... + * } + * + * \endcode + * + * Note that for AccountSet to property work with AccountCapabilityFilter + * objects, the feature Account::FeatureCapabilities need to be enabled in all + * accounts return by the AccountManager passed as param in the constructor. + * The easiest way to do this is to enable AccountManager feature + * AccountManager::FeatureFilterByCapabilities. + * + * AccountSet can also be instantiated directly, but when doing it, + * the AccountManager object passed as param in the constructor must be ready + * for AccountSet properly work. + */ + +/** + * Construct a new AccountSet object. + * + * \param accountManager An account manager object used to filter accounts. + * The account manager object must be ready. + * \param filter The desired filter. + */ +AccountSet::AccountSet(const AccountManagerPtr &accountManager, + const AccountFilterConstPtr &filter) + : Object(), + mPriv(new Private(this, accountManager, filter)) +{ +} + +/** + * Construct a new AccountSet object. + * + * The \a filter must contain Account property names and values as map items. + * + * \param accountManager An account manager object used to filter accounts. + * The account manager object must be ready. + * \param filter The desired filter. + */ +AccountSet::AccountSet(const AccountManagerPtr &accountManager, + const QVariantMap &filter) + : Object(), + mPriv(new Private(this, accountManager, filter)) +{ +} + +/** + * Class destructor. + */ +AccountSet::~AccountSet() +{ + delete mPriv; +} + +/** + * Return the account manager object used to filter accounts. + * + * \return A pointer to the AccountManager object. + */ +AccountManagerPtr AccountSet::accountManager() const +{ + return mPriv->accountManager; +} + +/** + * Return the filter used to filter accounts. + * + * \return A read-only pointer the AccountFilter object. + */ +AccountFilterConstPtr AccountSet::filter() const +{ + return mPriv->filter; +} + +/** + * Return a list of account objects that match filter. + * + * Change notification is via the accountAdded() and accountRemoved() signals. + * + * \return A list of pointers to Account objects. + * \sa accountAdded(), accountRemoved() + */ +QList<AccountPtr> AccountSet::accounts() const +{ + return mPriv->accounts.values(); +} + +/** + * \fn void AccountSet::accountAdded(const Tp::AccountPtr &account) + * + * Emitted whenever an account that matches filter is added to + * this set. + * + * \param account The account that was added to this set. + * \sa accounts() + */ + +/** + * \fn void AccountSet::accountRemoved(const Tp::AccountPtr &account) + * + * Emitted whenever an account that matches filter is removed + * from this set. + * + * \param account The account that was removed from this set. + * \sa accounts() + */ + +void AccountSet::onNewAccount(const AccountPtr &account) +{ + mPriv->insertAccount(account); +} + +void AccountSet::onAccountRemoved(const AccountPtr &account) +{ + mPriv->removeAccount(account); +} + +void AccountSet::onAccountChanged(const AccountPtr &account) +{ + mPriv->filterAccount(account); +} + +} // Tp diff --git a/TelepathyQt/account-set.h b/TelepathyQt/account-set.h new file mode 100644 index 00000000..40dd79af --- /dev/null +++ b/TelepathyQt/account-set.h @@ -0,0 +1,79 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_account_set_h_HEADER_GUARD_ +#define _TelepathyQt_account_set_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Filter> +#include <TelepathyQt/Object> +#include <TelepathyQt/Types> + +#include <QList> +#include <QString> +#include <QVariantMap> + +namespace Tp +{ + +class TP_QT_EXPORT AccountSet : public Object +{ + Q_OBJECT + Q_DISABLE_COPY(AccountSet) + Q_PROPERTY(AccountManagerPtr accountManager READ accountManager) + Q_PROPERTY(AccountFilterConstPtr filter READ filter) + Q_PROPERTY(QList<AccountPtr> accounts READ accounts) + +public: + AccountSet(const AccountManagerPtr &accountManager, + const AccountFilterConstPtr &filter); + AccountSet(const AccountManagerPtr &accountManager, + const QVariantMap &filter); + virtual ~AccountSet(); + + AccountManagerPtr accountManager() const; + + AccountFilterConstPtr filter() const; + + QList<AccountPtr> accounts() const; + +Q_SIGNALS: + void accountAdded(const Tp::AccountPtr &account); + void accountRemoved(const Tp::AccountPtr &account); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onNewAccount(const Tp::AccountPtr &account); + TP_QT_NO_EXPORT void onAccountRemoved(const Tp::AccountPtr &account); + TP_QT_NO_EXPORT void onAccountChanged(const Tp::AccountPtr &account); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/account.cpp b/TelepathyQt/account.cpp new file mode 100644 index 00000000..e9c864a2 --- /dev/null +++ b/TelepathyQt/account.cpp @@ -0,0 +1,4472 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/Account> + +#include "TelepathyQt/_gen/account.moc.hpp" +#include "TelepathyQt/_gen/cli-account.moc.hpp" +#include "TelepathyQt/_gen/cli-account-body.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include "TelepathyQt/connection-internal.h" + +#include <TelepathyQt/AccountManager> +#include <TelepathyQt/Channel> +#include <TelepathyQt/ChannelDispatcherInterface> +#include <TelepathyQt/ConnectionCapabilities> +#include <TelepathyQt/ConnectionLowlevel> +#include <TelepathyQt/ConnectionManager> +#include <TelepathyQt/PendingChannel> +#include <TelepathyQt/PendingChannelRequest> +#include <TelepathyQt/PendingFailure> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/PendingStringList> +#include <TelepathyQt/PendingVariant> +#include <TelepathyQt/PendingVoid> +#include <TelepathyQt/Profile> +#include <TelepathyQt/ReferencedHandles> +#include <TelepathyQt/Constants> +#include <TelepathyQt/Debug> + +#include <QQueue> +#include <QRegExp> +#include <QSharedPointer> +#include <QTimer> +#include <QWeakPointer> + +#include <string.h> + +namespace Tp +{ + +namespace +{ + +struct PresenceStatusInfo +{ + QString name; + Tp::SimpleStatusSpec spec; +}; + +Tp::ConnectionPresenceType presenceTypeForStatus(const QString &status, bool &maySetOnSelf) +{ + static PresenceStatusInfo statuses[] = { + { QLatin1String("available"), { Tp::ConnectionPresenceTypeAvailable, true, true } }, + { QLatin1String("chat"), { Tp::ConnectionPresenceTypeAvailable, true, true } }, + { QLatin1String("chatty"), { Tp::ConnectionPresenceTypeAvailable, true, true } }, + { QLatin1String("away"), { Tp::ConnectionPresenceTypeAway, true, true } }, + { QLatin1String("brb"), { Tp::ConnectionPresenceTypeAway, true, true } }, + { QLatin1String("out-to-lunch"), { Tp::ConnectionPresenceTypeAway, true, true } }, + { QLatin1String("xa"), { Tp::ConnectionPresenceTypeExtendedAway, true, true } }, + { QLatin1String("hidden"), { Tp::ConnectionPresenceTypeHidden, true, true } }, + { QLatin1String("invisible"), { Tp::ConnectionPresenceTypeHidden, true, true } }, + { QLatin1String("offline"), { Tp::ConnectionPresenceTypeOffline, true, false } }, + { QLatin1String("unknown"), { Tp::ConnectionPresenceTypeUnknown, false, false } }, + { QLatin1String("error"), { Tp::ConnectionPresenceTypeError, false, false } } + }; + + for (uint i = 0; i < sizeof(statuses) / sizeof(PresenceStatusInfo); ++i) { + if (status == statuses[i].name) { + maySetOnSelf = statuses[i].spec.maySetOnSelf; + return (Tp::ConnectionPresenceType) statuses[i].spec.type; + } + } + + // fallback to type away if we don't know status + maySetOnSelf = true; + return Tp::ConnectionPresenceTypeAway; +} + +Tp::PresenceSpec presenceSpecForStatus(const QString &status, bool canHaveStatusMessage) +{ + Tp::SimpleStatusSpec spec; + spec.type = presenceTypeForStatus(status, spec.maySetOnSelf); + spec.canHaveMessage = canHaveStatusMessage; + return Tp::PresenceSpec(status, spec); +} + +QVariantMap textChatCommonRequest() +{ + QVariantMap request; + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT)); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), + (uint) Tp::HandleTypeContact); + return request; +} + +QVariantMap textChatRequest(const QString &contactIdentifier) +{ + QVariantMap request = textChatCommonRequest(); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"), + contactIdentifier); + return request; +} + +QVariantMap textChatRequest(const Tp::ContactPtr &contact) +{ + QVariantMap request = textChatCommonRequest(); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"), + contact ? contact->handle().at(0) : (uint) 0); + return request; +} + +QVariantMap textChatroomRequest(const QString &roomName) +{ + QVariantMap request; + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT)); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), + (uint) Tp::HandleTypeRoom); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"), + roomName); + return request; +} + +QVariantMap streamedMediaCallCommonRequest() +{ + QVariantMap request; + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA)); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), + (uint) Tp::HandleTypeContact); + return request; +} + +QVariantMap streamedMediaCallRequest(const QString &contactIdentifier) +{ + QVariantMap request = streamedMediaCallCommonRequest(); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"), + contactIdentifier); + return request; +} + +QVariantMap streamedMediaCallRequest(const Tp::ContactPtr &contact) +{ + QVariantMap request = streamedMediaCallCommonRequest(); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"), + contact ? contact->handle().at(0) : (uint) 0); + return request; +} + +QVariantMap streamedMediaAudioCallRequest(const QString &contactIdentifier) +{ + QVariantMap request = streamedMediaCallRequest(contactIdentifier); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio"), + true); + return request; +} + +QVariantMap streamedMediaAudioCallRequest(const Tp::ContactPtr &contact) +{ + QVariantMap request = streamedMediaCallRequest(contact); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio"), + true); + return request; +} + +QVariantMap streamedMediaVideoCallRequest(const QString &contactIdentifier, bool withAudio) +{ + QVariantMap request = streamedMediaCallRequest(contactIdentifier); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo"), + true); + if (withAudio) { + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio"), + true); + } + return request; +} + +QVariantMap streamedMediaVideoCallRequest(const Tp::ContactPtr &contact, bool withAudio) +{ + QVariantMap request = streamedMediaCallRequest(contact); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo"), + true); + if (withAudio) { + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio"), + true); + } + return request; +} + +QVariantMap fileTransferCommonRequest(const Tp::FileTransferChannelCreationProperties &properties) +{ + if (!properties.isValid()) { + warning() << "Invalid file transfer creation properties"; + return QVariantMap(); + } + + QVariantMap request; + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER)); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), + (uint) Tp::HandleTypeContact); + + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".Filename"), + properties.suggestedFileName()); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentType"), + properties.contentType()); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".Size"), + properties.size()); + + if (properties.hasContentHash()) { + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHashType"), + (uint) properties.contentHashType()); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".ContentHash"), + properties.contentHash()); + } + + if (properties.hasDescription()) { + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".Description"), + properties.description()); + } + + if (properties.hasLastModificationTime()) { + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".Date"), + (qulonglong) properties.lastModificationTime().toTime_t()); + } + + if (properties.hasUri()) { + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER ".URI"), + properties.uri()); + } + + return request; +} + +QVariantMap fileTransferRequest(const QString &contactIdentifier, + const Tp::FileTransferChannelCreationProperties &properties) +{ + QVariantMap request = fileTransferCommonRequest(properties); + + if (!request.isEmpty()) { + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"), + contactIdentifier); + } + + return request; +} + +QVariantMap fileTransferRequest(const Tp::ContactPtr &contact, + const Tp::FileTransferChannelCreationProperties &properties) +{ + QVariantMap request = fileTransferCommonRequest(properties); + + if (!request.isEmpty()) { + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"), + contact ? contact->handle().at(0) : (uint) 0); + } + + return request; +} + +QVariantMap streamTubeCommonRequest(const QString &service) +{ + QVariantMap request; + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE)); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), + (uint) Tp::HandleTypeContact); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"), + service); + return request; +} + +QVariantMap streamTubeRequest(const QString &contactIdentifier, const QString &service) +{ + QVariantMap request = streamTubeCommonRequest(service); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"), + contactIdentifier); + return request; +} + +QVariantMap streamTubeRequest(const Tp::ContactPtr &contact, const QString &service) +{ + QVariantMap request = streamTubeCommonRequest(service); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"), + contact ? contact->handle().at(0) : (uint) 0); + return request; +} + +QVariantMap conferenceCommonRequest(const char *channelType, Tp::HandleType targetHandleType, + const QList<Tp::ChannelPtr> &channels) +{ + QVariantMap request; + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), + QLatin1String(channelType)); + if (targetHandleType != Tp::HandleTypeNone) { + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), + (uint) targetHandleType); + } + + Tp::ObjectPathList objectPaths; + foreach (const Tp::ChannelPtr &channel, channels) { + objectPaths << QDBusObjectPath(channel->objectPath()); + } + + request.insert(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels"), + qVariantFromValue(objectPaths)); + return request; +} + +QVariantMap conferenceRequest(const char *channelType, Tp::HandleType targetHandleType, + const QList<Tp::ChannelPtr> &channels, const QStringList &initialInviteeContactsIdentifiers) +{ + QVariantMap request = conferenceCommonRequest(channelType, targetHandleType, channels); + if (!initialInviteeContactsIdentifiers.isEmpty()) { + request.insert(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeIDs"), + initialInviteeContactsIdentifiers); + } + return request; +} + +QVariantMap conferenceRequest(const char *channelType, Tp::HandleType targetHandleType, + const QList<Tp::ChannelPtr> &channels, const QList<Tp::ContactPtr> &initialInviteeContacts) +{ + QVariantMap request = conferenceCommonRequest(channelType, targetHandleType, channels); + if (!initialInviteeContacts.isEmpty()) { + Tp::UIntList handles; + foreach (const Tp::ContactPtr &contact, initialInviteeContacts) { + if (!contact) { + continue; + } + handles << contact->handle()[0]; + } + if (!handles.isEmpty()) { + request.insert(TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + + QLatin1String(".InitialInviteeHandles"), qVariantFromValue(handles)); + } + } + return request; +} + +QVariantMap conferenceTextChatRequest(const QList<Tp::ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers) +{ + QVariantMap request = conferenceRequest(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT, + Tp::HandleTypeNone, channels, initialInviteeContactsIdentifiers); + return request; +} + +QVariantMap conferenceTextChatRequest(const QList<Tp::ChannelPtr> &channels, + const QList<Tp::ContactPtr> &initialInviteeContacts) +{ + QVariantMap request = conferenceRequest(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT, + Tp::HandleTypeNone, channels, initialInviteeContacts); + return request; +} + +QVariantMap conferenceTextChatroomRequest(const QString &roomName, + const QList<Tp::ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers) +{ + QVariantMap request = conferenceRequest(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT, + Tp::HandleTypeRoom, channels, initialInviteeContactsIdentifiers); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"), roomName); + return request; +} + +QVariantMap conferenceTextChatroomRequest(const QString &roomName, + const QList<Tp::ChannelPtr> &channels, + const QList<Tp::ContactPtr> &initialInviteeContacts) +{ + QVariantMap request = conferenceRequest(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT, + Tp::HandleTypeRoom, channels, initialInviteeContacts); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"), roomName); + return request; +} + +QVariantMap conferenceStreamedMediaCallRequest(const QList<Tp::ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers) +{ + QVariantMap request = conferenceRequest(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA, + Tp::HandleTypeNone, channels, initialInviteeContactsIdentifiers); + return request; +} + +QVariantMap conferenceStreamedMediaCallRequest(const QList<Tp::ChannelPtr> &channels, + const QList<Tp::ContactPtr> &initialInviteeContacts) +{ + QVariantMap request = conferenceRequest(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA, + Tp::HandleTypeNone, channels, initialInviteeContacts); + return request; +} + +QVariantMap contactSearchRequest(const ConnectionCapabilities &capabilities, + const QString &server, uint limit) +{ + QVariantMap request; + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_SEARCH)); + if (capabilities.contactSearchesWithSpecificServer()) { + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_SEARCH ".Server"), + server); + } else if (!server.isEmpty()) { + warning() << "Ignoring Server parameter for contact search, since the protocol does not support it."; + } + if (capabilities.contactSearchesWithLimit()) { + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_SEARCH ".Limit"), limit); + } else if (limit > 0) { + warning() << "Ignoring Limit parameter for contact search, since the protocol does not support it."; + } + return request; +} + +} // anonymous namespace + +struct TP_QT_NO_EXPORT Account::Private +{ + Private(Account *parent, const ConnectionFactoryConstPtr &connFactory, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory); + ~Private(); + + void init(); + + static void introspectMain(Private *self); + static void introspectAvatar(Private *self); + static void introspectProtocolInfo(Private *self); + static void introspectCapabilities(Private *self); + + void updateProperties(const QVariantMap &props); + void retrieveAvatar(); + bool processConnQueue(); + + bool checkCapabilitiesChanged(bool profileChanged); + + QString connectionObjectPath() const; + + // Public object + Account *parent; + + // Factories + ConnectionFactoryConstPtr connFactory; + ChannelFactoryConstPtr chanFactory; + ContactFactoryConstPtr contactFactory; + + // Instance of generated interface class + Client::AccountInterface *baseInterface; + + // Mandatory properties interface proxy + Client::DBus::PropertiesInterface *properties; + + ReadinessHelper *readinessHelper; + + // Introspection + QVariantMap parameters; + bool valid; + bool enabled; + bool connectsAutomatically; + bool hasBeenOnline; + bool changingPresence; + QString cmName; + QString protocolName; + QString serviceName; + ProfilePtr profile; + QString displayName; + QString nickname; + QString iconName; + QQueue<QString> connObjPathQueue; + ConnectionPtr connection; + bool mayFinishCore, coreFinished; + QString normalizedName; + Avatar avatar; + ConnectionManagerPtr cm; + ConnectionStatus connectionStatus; + ConnectionStatusReason connectionStatusReason; + QString connectionError; + Connection::ErrorDetails connectionErrorDetails; + Presence automaticPresence; + Presence currentPresence; + Presence requestedPresence; + bool usingConnectionCaps; + ConnectionCapabilities customCaps; + + // The contexts should never be removed from the map, to guarantee O(1) CD introspections per bus + struct DispatcherContext; + static QMap<QString, QSharedPointer<DispatcherContext> > dispatcherContexts; + QSharedPointer<DispatcherContext> dispatcherContext; +}; + +struct Account::Private::DispatcherContext +{ + DispatcherContext(const QDBusConnection &bus) + : iface(new Client::ChannelDispatcherInterface(bus, TP_QT_CHANNEL_DISPATCHER_BUS_NAME, TP_QT_CHANNEL_DISPATCHER_OBJECT_PATH)), + introspected(false), supportsHints(false) + { + } + + ~DispatcherContext() + { + delete iface; + } + + Client::ChannelDispatcherInterface *iface; + + bool introspected, supportsHints; + QWeakPointer<PendingVariant> introspectOp; + +private: + DispatcherContext(const DispatcherContext &); + void operator=(const DispatcherContext &); +}; + +Account::Private::Private(Account *parent, const ConnectionFactoryConstPtr &connFactory, + const ChannelFactoryConstPtr &chanFactory, const ContactFactoryConstPtr &contactFactory) + : parent(parent), + connFactory(connFactory), + chanFactory(chanFactory), + contactFactory(contactFactory), + baseInterface(new Client::AccountInterface(parent)), + properties(parent->interface<Client::DBus::PropertiesInterface>()), + readinessHelper(parent->readinessHelper()), + valid(false), + enabled(false), + connectsAutomatically(false), + hasBeenOnline(false), + changingPresence(false), + mayFinishCore(false), + coreFinished(false), + connectionStatus(ConnectionStatusDisconnected), + connectionStatusReason(ConnectionStatusReasonNoneSpecified), + usingConnectionCaps(false), + dispatcherContext(dispatcherContexts.value(parent->dbusConnection().name())) +{ + // FIXME: QRegExp probably isn't the most efficient possible way to parse + // this :-) + QRegExp rx(QLatin1String("^") + TP_QT_ACCOUNT_OBJECT_PATH_BASE + + QLatin1String("/([_A-Za-z][_A-Za-z0-9]*)" // cap(1) is the CM + "/([_A-Za-z][_A-Za-z0-9]*)" // cap(2) is the protocol + "/([_A-Za-z][_A-Za-z0-9]*)" // account-specific part + )); + + if (rx.exactMatch(parent->objectPath())) { + cmName = rx.cap(1); + protocolName = rx.cap(2).replace(QLatin1Char('_'), QLatin1Char('-')); + } else { + warning() << "Account object path is not spec-compliant, " + "trying again with a different account-specific part check"; + + rx = QRegExp(QLatin1String("^") + TP_QT_ACCOUNT_OBJECT_PATH_BASE + + QLatin1String("/([_A-Za-z][_A-Za-z0-9]*)" // cap(1) is the CM + "/([_A-Za-z][_A-Za-z0-9]*)" // cap(2) is the protocol + "/([_A-Za-z0-9]*)" // account-specific part + )); + if (rx.exactMatch(parent->objectPath())) { + cmName = rx.cap(1); + protocolName = rx.cap(2).replace(QLatin1Char('_'), QLatin1Char('-')); + } else { + warning() << "Not a valid Account object path:" << + parent->objectPath(); + } + } + + ReadinessHelper::Introspectables introspectables; + + // As Account does not have predefined statuses let's simulate one (0) + ReadinessHelper::Introspectable introspectableCore( + QSet<uint>() << 0, // makesSenseForStatuses + Features(), // dependsOnFeatures + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectMain, + this); + introspectables[FeatureCore] = introspectableCore; + + ReadinessHelper::Introspectable introspectableAvatar( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << FeatureCore, // dependsOnFeatures (core) + QStringList() << QLatin1String(TELEPATHY_INTERFACE_ACCOUNT_INTERFACE_AVATAR), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectAvatar, + this); + introspectables[FeatureAvatar] = introspectableAvatar; + + ReadinessHelper::Introspectable introspectableProtocolInfo( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << FeatureCore, // dependsOnFeatures (core) + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectProtocolInfo, + this); + introspectables[FeatureProtocolInfo] = introspectableProtocolInfo; + + ReadinessHelper::Introspectable introspectableCapabilities( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << FeatureCore << FeatureProtocolInfo << FeatureProfile, // dependsOnFeatures + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectCapabilities, + this); + introspectables[FeatureCapabilities] = introspectableCapabilities; + + readinessHelper->addIntrospectables(introspectables); + + if (connFactory->dbusConnection().name() != parent->dbusConnection().name()) { + warning() << " The D-Bus connection in the conn factory is not the proxy connection for" + << parent->objectPath(); + } + + if (chanFactory->dbusConnection().name() != parent->dbusConnection().name()) { + warning() << " The D-Bus connection in the channel factory is not the proxy connection for" + << parent->objectPath(); + } + + if (!dispatcherContext) { + dispatcherContext = QSharedPointer<DispatcherContext>(new DispatcherContext(parent->dbusConnection())); + dispatcherContexts.insert(parent->dbusConnection().name(), dispatcherContext); + } + + init(); +} + +Account::Private::~Private() +{ +} + +bool Account::Private::checkCapabilitiesChanged(bool profileChanged) +{ + /* when the capabilities changed: + * + * - We were using the connection caps and now we don't have connection or + * the connection we have is not connected (changed to CM caps) + * - We were using the CM caps and now we have a connected connection + * (changed to new connection caps) + */ + bool changed = false; + + if (usingConnectionCaps && + (parent->connection().isNull() || + connection->status() != ConnectionStatusConnected)) { + usingConnectionCaps = false; + changed = true; + } else if (!usingConnectionCaps && + !parent->connection().isNull() && + connection->status() == ConnectionStatusConnected) { + usingConnectionCaps = true; + changed = true; + } else if (!usingConnectionCaps && profileChanged) { + changed = true; + } + + if (changed && parent->isReady(FeatureCapabilities)) { + emit parent->capabilitiesChanged(parent->capabilities()); + } + + return changed; +} + +QString Account::Private::connectionObjectPath() const +{ + return !connection.isNull() ? connection->objectPath() : QString(); +} + +QMap<QString, QSharedPointer<Account::Private::DispatcherContext> > Account::Private::dispatcherContexts; + +/** + * \class Account + * \ingroup clientaccount + * \headerfile TelepathyQt/account.h <TelepathyQt/Account> + * + * \brief The Account class represents a Telepathy account. + * + * The remote object accessor functions on this object (isValidAccount(), + * isEnabled(), 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 account protocol information, it is necessary to + * enable the feature Account::FeatureProtocolInfo. + * See the individual methods descriptions for more details. + * + * Account features can be enabled by constructing an AccountFactory and enabling + * the desired features, and passing it to AccountManager 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. + * + * As an addition to accessors, signals are emitted to indicate that properties have + * changed, for example displayNameChanged(), iconNameChanged(), etc. + * + * Convenience methods to create channels using the channel dispatcher such as + * ensureTextChat(), createFileTransfer() are also provided. + * + * If the account is deleted from the AccountManager, this object + * will not be deleted automatically; however, it will emit invalidated() + * with error code #TP_QT_ERROR_OBJECT_REMOVED and will cease to + * be useful. + * + * \section account_usage_sec Usage + * + * \subsection account_create_sec Creating an account object + * + * The easiest way to create account objects is through AccountManager. One can + * just use the AccountManager convenience methods such as + * AccountManager::validAccounts() to get a list of account objects representing + * valid accounts. + * + * If you already know the object path, you can just call create(). + * For example: + * + * \code AccountPtr acc = Account::create(busName, objectPath); \endcode + * + * An AccountPtr object is returned, which will automatically keep + * track of object lifetime. + * + * You can also provide a D-Bus connection as a QDBusConnection: + * + * \code + * + * AccountPtr acc = Account::create(QDBusConnection::sessionBus(), + * busName, objectPath); + * + * \endcode + * + * \subsection account_ready_sec Making account ready to use + * + * An Account 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 onAccountReady(Tp::PendingOperation*); + * + * private: + * AccountPtr acc; + * }; + * + * MyClass::MyClass(const QString &busName, const QString &objectPath, + * QObject *parent) + * : QObject(parent) + * acc(Account::create(busName, objectPath)) + * { + * connect(acc->becomeReady(), + * SIGNAL(finished(Tp::PendingOperation*)), + * SLOT(onAccountReady(Tp::PendingOperation*))); + * } + * + * void MyClass::onAccountReady(Tp::PendingOperation *op) + * { + * if (op->isError()) { + * qWarning() << "Account cannot become ready:" << + * op->errorName() << "-" << op->errorMessage(); + * return; + * } + * + * // Account is now ready + * qDebug() << "Display name:" << acc->displayName(); + * } + * + * \endcode + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Feature representing the core that needs to become ready to make the Account + * object usable. + * + * Note that this feature must be enabled in order to use most Account methods. + * See specific methods documentation for more details. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature Account::FeatureCore = Feature(QLatin1String(Account::staticMetaObject.className()), 0, true); + +/** + * Feature used in order to access account avatar info. + * + * See avatar specific methods' documentation for more details. + * + * \sa avatar(), avatarChanged() + */ +const Feature Account::FeatureAvatar = Feature(QLatin1String(Account::staticMetaObject.className()), 1); + +/** + * Feature used in order to access account protocol info. + * + * See protocol info specific methods' documentation for more details. + * + * \sa protocolInfo() + */ +const Feature Account::FeatureProtocolInfo = Feature(QLatin1String(Account::staticMetaObject.className()), 2); + +/** + * Feature used in order to access account capabilities. + * + * Enabling this feature will also enable FeatureProtocolInfo and FeatureProfile. + * + * See capabilities specific methods' documentation for more details. + * + * \sa capabilities(), capabilitiesChanged() + */ +const Feature Account::FeatureCapabilities = Feature(QLatin1String(Account::staticMetaObject.className()), 3); + +/** + * Feature used in order to access account profile info. + * + * See profile specific methods' documentation for more details. + * + * \sa profile(), profileChanged() + */ +const Feature Account::FeatureProfile = FeatureProtocolInfo; +// FeatureProfile is the same as FeatureProtocolInfo for now, as it only needs +// the protocol info, cm name and protocol name to build a fake profile. Make it +// a full-featured feature if needed later. + +/** + * Create a new Account object using QDBusConnection::sessionBus() and the given factories. + * + * A warning is printed if the factories are not for QDBusConnection::sessionBus(). + * + * \param busName The account well-known bus name (sometimes called a "service + * name"). This is usually the same as the account manager + * bus name #TP_QT_ACCOUNT_MANAGER_BUS_NAME. + * \param objectPath The account object path. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return An AccountPtr object pointing to the newly created Account object. + */ +AccountPtr Account::create(const QString &busName, const QString &objectPath, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return AccountPtr(new Account(QDBusConnection::sessionBus(), busName, objectPath, + connectionFactory, channelFactory, contactFactory, Account::FeatureCore)); +} + +/** + * Create a new Account object using the given \a bus and the given factories. + * + * A warning is printed if the factories are not for \a bus. + * + * \param bus QDBusConnection to use. + * \param busName The account well-known bus name (sometimes called a "service + * name"). This is usually the same as the account manager + * bus name #TP_QT_ACCOUNT_MANAGER_BUS_NAME. + * \param objectPath The account object path. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return An AccountPtr object pointing to the newly created Account object. + */ +AccountPtr Account::create(const QDBusConnection &bus, + const QString &busName, const QString &objectPath, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return AccountPtr(new Account(bus, busName, objectPath, connectionFactory, channelFactory, + contactFactory, Account::FeatureCore)); +} + +/** + * Construct a new Account object using the given \a bus and the given factories. + * + * A warning is printed if the factories are not for \a bus. + * + * \param bus QDBusConnection to use. + * \param busName The account well-known bus name (sometimes called a "service + * name"). This is usually the same as the account manager + * bus name #TP_QT_ACCOUNT_MANAGER_BUS_NAME. + * \param objectPath The account object path. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \param coreFeature The core feature of the Account subclass. The corresponding introspectable + * should depend on Account::FeatureCore. + */ +Account::Account(const QDBusConnection &bus, + const QString &busName, const QString &objectPath, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory, + const Feature &coreFeature) + : StatelessDBusProxy(bus, busName, objectPath, coreFeature), + OptionalInterfaceFactory<Account>(this), + mPriv(new Private(this, connectionFactory, channelFactory, contactFactory)) +{ +} + +/** + * Class destructor. + */ +Account::~Account() +{ + delete mPriv; +} + +/** + * Return the connection factory used by this account. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the account would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ConnectionFactory object. + */ +ConnectionFactoryConstPtr Account::connectionFactory() const +{ + return mPriv->connFactory; +} + +/** + * Return the channel factory used by this account. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the account would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ChannelFactory object. + */ +ChannelFactoryConstPtr Account::channelFactory() const +{ + return mPriv->chanFactory; +} + +/** + * Return the contact factory used by this account. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the account would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ContactFactory object. + */ +ContactFactoryConstPtr Account::contactFactory() const +{ + return mPriv->contactFactory; +} + +/** + * Return whether this account is valid. + * + * If \c true, this account is considered by the account manager to be complete + * and usable. If \c false, user action is required to make it usable, and it will + * never attempt to connect. For instance, this might be caused by the absence + * or misconfiguration of a required parameter, in which case updateParameters() + * may be used to properly set the parameters values. + * + * Change notification is via the validityChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return \c true if valid, \c false otherwise. + * \sa validityChanged(), updateParameters() + */ +bool Account::isValidAccount() const +{ + return mPriv->valid; +} + +/** + * Return whether this account is enabled. + * + * Change notification is via the stateChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return \c true if enabled, \c false otherwise. + * \sa stateChanged(), setEnabled() + */ +bool Account::isEnabled() const +{ + return mPriv->enabled; +} + +/** + * Set whether this account should be enabled or disabled. + * + * This gives users the possibility to prevent an account from + * being used. + * + * Note that changing this property won't change the validity of the account. + * + * \param value Whether this account should be enabled or disabled. + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa stateChanged(), isEnabled() + */ +PendingOperation *Account::setEnabled(bool value) +{ + return new PendingVoid( + mPriv->properties->Set( + QLatin1String(TELEPATHY_INTERFACE_ACCOUNT), + QLatin1String("Enabled"), + QDBusVariant(value)), + AccountPtr(this)); +} + +/** + * Return the connection manager name of this account. + * + * \return The connection manager name. + */ +QString Account::cmName() const +{ + return mPriv->cmName; +} + +/** + * Return the protocol name of this account. + * + * \return The protocol name. + */ +QString Account::protocolName() const +{ + return mPriv->protocolName; +} + +/** + * Return the service name of this account. + * + * Note that this method will fallback to protocolName() if service name + * is not known. + * + * Change notification is via the serviceNameChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The service name. + * \sa serviceNameChanged(), setServiceName(), protocolName() + */ +QString Account::serviceName() const +{ + if (mPriv->serviceName.isEmpty()) { + return mPriv->protocolName; + } + return mPriv->serviceName; +} + +/** + * Set the service name of this account. + * + * Some protocols, like XMPP and SIP, are used by various different user-recognised brands, + * such as Google Talk. On accounts for such services, this method can be used + * to set the name describing the service, which must consist only of ASCII letters, numbers and + * hyphen/minus signs, and start with a letter. + * For the jabber protocol, one of the following service names should be used if possible: + * + * google-talk (for Google's IM service) + * facebook (for Facebook's IM service) + * lj-talk (for LiveJournal's IM service) + * + * The service name may also be set, if appropriate, when creating the account. See + * AccountManager::createAccount() for more details. + * + * Note that changing this property may also change the profile() used by this account, + * in which case profileChanged() will be emitted in addition to serviceNameChanged(), if + * Account::FeatureProfile is enabled. + * + * \param value The service name of this account. + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa serviceNameChanged(), serviceName() + */ +PendingOperation *Account::setServiceName(const QString &value) +{ + return new PendingVoid( + mPriv->properties->Set( + QLatin1String(TELEPATHY_INTERFACE_ACCOUNT), + QLatin1String("Service"), + QDBusVariant(value)), + AccountPtr(this)); +} + +/** + * Return the profile used by this account. + * + * Profiles are intended to describe variants of the basic protocols supported by Telepathy + * connection managers. + * An example of this would be Google Talk vs Facebook chat vs Jabber/XMPP. Google Talk is a + * specific case of XMPP with well-known capabilities, quirks and settings, and Facebook chat is + * a subset of the standard XMPP offering. + * + * This method will return the profile for this account based on the service used by it. + * + * Note that if a profile for serviceName() is not available, a fake profile + * (Profile::isFake() is \c true) will be returned in case protocolInfo() is valid. + * + * The fake profile will contain the following info: + * - Profile::type() will return "IM" + * - Profile::provider() will return an empty string + * - Profile::serviceName() will return "cmName()-serviceName()" + * - Profile::name() and Profile::protocolName() will return protocolName() + * - Profile::iconName() will return "im-protocolName()" + * - Profile::cmName() will return cmName() + * - Profile::parameters() will return a list matching CM default parameters for protocol with name + * protocolName() + * - Profile::presences() will return an empty list and + * Profile::allowOtherPresences() will return \c true, meaning that CM + * presences should be used + * - Profile::unsupportedChannelClassSpecs() will return an empty list + * + * Change notification is via the profileChanged() signal. + * + * This method requires Account::FeatureProfile to be ready. + * + * \return A pointer to the Profile object. + * \sa profileChanged(), serviceName() + */ +ProfilePtr Account::profile() const +{ + if (!isReady(FeatureProfile)) { + warning() << "Account::profile() requires Account::FeatureProfile to be ready"; + return ProfilePtr(); + } + + if (!mPriv->profile) { + mPriv->profile = Profile::createForServiceName(serviceName()); + if (!mPriv->profile->isValid()) { + if (protocolInfo().isValid()) { + mPriv->profile = ProfilePtr(new Profile( + QString(QLatin1String("%1-%2")).arg(mPriv->cmName).arg(serviceName()), + mPriv->cmName, + mPriv->protocolName, + protocolInfo())); + } else { + warning() << "Cannot create profile as neither a .profile is installed for service" << + serviceName() << "nor protocol info can be retrieved"; + } + } + } + return mPriv->profile; +} + +/** + * Return the display name of this account. + * + * Change notification is via the displayNameChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The display name. + * \sa displayNameChanged(), setDisplayName() + */ +QString Account::displayName() const +{ + return mPriv->displayName; +} + +/** + * Set the display name of this account. + * + * The display name is the user-visible name of this account. + * This is usually chosen by the user at account creation time. + * See AccountManager::createAccount() for more details. + * + * \param value The display name of this account. + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa displayNameChanged(), displayName() + */ +PendingOperation *Account::setDisplayName(const QString &value) +{ + return new PendingVoid( + mPriv->properties->Set( + QLatin1String(TELEPATHY_INTERFACE_ACCOUNT), + QLatin1String("DisplayName"), + QDBusVariant(value)), + AccountPtr(this)); +} + +/** + * Return the icon name of this account. + * + * If the account has no icon, and Account::FeatureProfile is enabled, the icon from the result of + * profile() will be used. + * + * If neither the account nor the profile has an icon, and Account::FeatureProtocolInfo is + * enabled, the icon from protocolInfo() will be used if set. + * + * As a last resort, "im-" + protocolName() will be returned. + * + * This matches the fallbacks recommended by the \telepathy_spec. + * + * Change notification is via the iconNameChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The icon name. + * \sa iconNameChanged(), setIconName() + */ +QString Account::iconName() const +{ + if (mPriv->iconName.isEmpty()) { + if (isReady(FeatureProfile)) { + ProfilePtr pr = profile(); + if (pr && pr->isValid()) { + QString iconName = pr->iconName(); + if (!iconName.isEmpty()) { + return iconName; + } + } + } + + if (isReady(FeatureProtocolInfo) && protocolInfo().isValid()) { + return protocolInfo().iconName(); + } + + return QString(QLatin1String("im-%1")).arg(protocolName()); + } + + return mPriv->iconName; +} + +/** + * Set the icon name of this account. + * + * \param value The icon name of this account. + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa iconNameChanged(), iconName() + */ +PendingOperation *Account::setIconName(const QString &value) +{ + return new PendingVoid( + mPriv->properties->Set( + QLatin1String(TELEPATHY_INTERFACE_ACCOUNT), + QLatin1String("Icon"), + QDBusVariant(value)), + AccountPtr(this)); +} + +/** + * Return the nickname of this account. + * + * Change notification is via the nicknameChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The nickname. + * \sa nicknameChanged(), setNickname() + */ +QString Account::nickname() const +{ + return mPriv->nickname; +} + +/** + * Set the nickname of this account as seen to other contacts. + * + * \param value The nickname of this account. + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa nicknameChanged(), nickname() + */ +PendingOperation *Account::setNickname(const QString &value) +{ + return new PendingVoid( + mPriv->properties->Set( + QLatin1String(TELEPATHY_INTERFACE_ACCOUNT), + QLatin1String("Nickname"), + QDBusVariant(value)), + AccountPtr(this)); +} + +/** + * Return the avatar requirements (size limits, supported MIME types, etc) + * for avatars passed to setAvatar() on this account. + * + * For now this method will only return the avatar requirements found in protocolInfo() if + * Account::FeatureProtocolInfo is ready, otherwise an invalid AvatarSpec instance is returned. + * + * \return The requirements as an AvatarSpec object. + * \sa avatar(), setAvatar() + */ +AvatarSpec Account::avatarRequirements() const +{ + // TODO Once connection has support for avatar requirements use it if the connection is usable + ProtocolInfo pi = protocolInfo(); + if (pi.isValid()) { + return pi.avatarRequirements(); + } + return AvatarSpec(); +} + +/** + * Return the avatar of this account. + * + * Change notification is via the avatarChanged() signal. + * + * This method requires Account::FeatureAvatar to be ready. + * + * \return The avatar as an Avatar object. + * \sa avatarChanged(), setAvatar() + */ +const Avatar &Account::avatar() const +{ + if (!isReady(Features() << FeatureAvatar)) { + warning() << "Trying to retrieve avatar from account, but " + "avatar is not supported or was not requested. " + "Use becomeReady(FeatureAvatar)"; + } + + return mPriv->avatar; +} + +/** + * Set avatar of this account as seen to other contacts. + * + * Note that \a avatar must match the requirements as returned by avatarRequirements(). + * + * \param avatar The avatar of this account. + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa avatarChanged(), avatar(), avatarRequirements() + */ +PendingOperation *Account::setAvatar(const Avatar &avatar) +{ + if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_ACCOUNT_INTERFACE_AVATAR))) { + return new PendingFailure( + QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Account does not support Avatar"), + AccountPtr(this)); + } + + return new PendingVoid( + mPriv->properties->Set( + QLatin1String(TELEPATHY_INTERFACE_ACCOUNT_INTERFACE_AVATAR), + QLatin1String("Avatar"), + QDBusVariant(QVariant::fromValue(avatar))), + AccountPtr(this)); +} + +/** + * Return the parameters of this account. + * + * The account parameters are represented as a map from connection manager parameter names + * to their values. + * + * Change notification is via the parametersChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The parameters as QVariantMap. + * \sa parametersChanged(), updateParameters() + */ +QVariantMap Account::parameters() const +{ + return mPriv->parameters; +} + +/** + * Update this account parameters. + * + * On success, the PendingOperation returned by this method will produce a + * list of strings, which are the names of parameters whose changes will not + * take effect until the account is disconnected and reconnected (for instance + * by calling reconnect()). + * + * \param set Parameters to set. + * \param unset Parameters to unset. + * \return A PendingStringList which will emit PendingStringList::finished + * when the request has been made + * \sa parametersChanged(), parameters(), reconnect() + */ +PendingStringList *Account::updateParameters(const QVariantMap &set, + const QStringList &unset) +{ + return new PendingStringList( + baseInterface()->UpdateParameters(set, unset), + AccountPtr(this)); +} + +/** + * Return the protocol info of this account protocol. + * + * This method requires Account::FeatureProtocolInfo to be ready. + * + * \return The protocol info as a ProtocolInfo object. + */ +ProtocolInfo Account::protocolInfo() const +{ + if (!isReady(Features() << FeatureProtocolInfo)) { + warning() << "Trying to retrieve protocol info from account, but " + "protocol info is not supported or was not requested. " + "Use becomeReady(FeatureProtocolInfo)"; + return ProtocolInfo(); + } + + return mPriv->cm->protocol(mPriv->protocolName); +} + +/** + * Return the capabilities for this account. + * + * Note that this method will return the connection() capabilities if the + * account is online and ready. If the account is disconnected, it will fallback + * to return the subtraction of the protocolInfo() capabilities and the profile() unsupported + * capabilities. + * + * Change notification is via the capabilitiesChanged() signal. + * + * This method requires Account::FeatureCapabilities to be ready. + * + * \return The capabilities as a ConnectionCapabilities object. + * \sa capabilitiesChanged(), protocolInfo(), profile() + */ +ConnectionCapabilities Account::capabilities() const +{ + if (!isReady(FeatureCapabilities)) { + warning() << "Trying to retrieve capabilities from account, but " + "FeatureCapabilities was not requested. " + "Use becomeReady(FeatureCapabilities)"; + return ConnectionCapabilities(); + } + + // if the connection is online and ready use its caps + if (mPriv->connection && + mPriv->connection->status() == ConnectionStatusConnected) { + return mPriv->connection->capabilities(); + } + + // if we are here it means FeatureProtocolInfo and FeatureProfile are ready, as + // FeatureCapabilities depend on them, so let's use the subtraction of protocol info caps rccs + // and profile unsupported rccs. + // + // However, if we failed to introspect the CM (eg. this is a test), then let's not try to use + // the protocolInfo because it'll be NULL! Profile may also be NULL in case a .profile for + // serviceName() is not present and protocolInfo is NULL. + ProtocolInfo pi = protocolInfo(); + if (!pi.isValid()) { + return ConnectionCapabilities(); + } + ProfilePtr pr; + if (isReady(FeatureProfile)) { + pr = profile(); + } + if (!pr || !pr->isValid()) { + return pi.capabilities(); + } + + RequestableChannelClassSpecList piClassSpecs = pi.capabilities().allClassSpecs(); + RequestableChannelClassSpecList prUnsupportedClassSpecs = pr->unsupportedChannelClassSpecs(); + RequestableChannelClassSpecList classSpecs; + bool unsupported; + foreach (const RequestableChannelClassSpec &piClassSpec, piClassSpecs) { + unsupported = false; + foreach (const RequestableChannelClassSpec &prUnsuportedClassSpec, prUnsupportedClassSpecs) { + // Here we check the following: + // - If the unsupported spec has no allowed property it means it does not support any + // class whose fixed properties match. + // E.g: Doesn't support any media calls, be it audio or video. + // - If the unsupported spec has allowed properties it means it does not support a + // specific class whose fixed properties and allowed properties should match. + // E.g: Doesn't support video calls but does support audio calls. + if (prUnsuportedClassSpec.allowedProperties().isEmpty()) { + if (piClassSpec.fixedProperties() == prUnsuportedClassSpec.fixedProperties()) { + unsupported = true; + break; + } + } else { + if (piClassSpec == prUnsuportedClassSpec) { + unsupported = true; + break; + } + } + } + if (!unsupported) { + classSpecs.append(piClassSpec); + } + } + mPriv->customCaps = ConnectionCapabilities(classSpecs); + return mPriv->customCaps; +} + +/** + * Return whether this account should be put online automatically whenever + * possible. + * + * Change notification is via the connectsAutomaticallyPropertyChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return \c true if it should try to connect automatically, \c false + * otherwise. + * \sa connectsAutomaticallyPropertyChanged(), setConnectsAutomatically() + */ +bool Account::connectsAutomatically() const +{ + return mPriv->connectsAutomatically; +} + +/** + * Set whether this account should be put online automatically whenever + * possible. + * + * \param value Value indicating if this account should be put online whenever + * possible. + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa connectsAutomaticallyPropertyChanged(), connectsAutomatically() + */ +PendingOperation *Account::setConnectsAutomatically(bool value) +{ + return new PendingVoid( + mPriv->properties->Set( + QLatin1String(TELEPATHY_INTERFACE_ACCOUNT), + QLatin1String("ConnectAutomatically"), + QDBusVariant(value)), + AccountPtr(this)); +} + +/** + * Return whether this account has ever been put online successfully. + * + * This property cannot change from \c true to \c false, only from \c false to \c true. + * When the account successfully goes online for the first time, or when it + * is detected that this has already happened, the firstOnline() signal is + * emitted. + * + * This method requires Account::FeatureCore to be ready. + * + * \return \c true if ever been online, \c false otherwise. + */ +bool Account::hasBeenOnline() const +{ + return mPriv->hasBeenOnline; +} + +/** + * Return the status of this account connection. + * + * Note that this method may return a different value from the one returned by Connection::status() + * on this account connection. This happens because this value will change whenever the connection + * status of this account connection changes and won't consider the Connection introspection + * process. The same rationale also applies to connectionStatusReason() and + * connectionErrorDetails(). + * + * A valid use case for this is for account creation UIs that wish to display the accounts + * connection status and nothing else on the connections (e.g. retrieve the contact list). + * + * Change notification is via the connectionStatusChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The connection status as #ConnectionStatus. + * \sa connectionStatusChanged(), connectionStatusReason(), connectionError(), + * connectionErrorDetails() + */ +ConnectionStatus Account::connectionStatus() const +{ + return mPriv->connectionStatus; +} + +/** + * Return the reason for this account connection status. + * + * This represents the reason for the last change to connectionStatus(). + * + * Note that this method may return a different value from the one returned by + * Connection::statusReason() on this account connection. + * See connectionStatus() for more details. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The connection status reason as #ConnectionStatusReason. + * \sa connectionStatusChanged(), connectionStatus(), connectionError(), connectionErrorDetails() + */ +ConnectionStatusReason Account::connectionStatusReason() const +{ + return mPriv->connectionStatusReason; +} + +/** + * Return the D-Bus error name for the last disconnection or connection failure, + * (in particular, #TP_QT_ERROR_CANCELLED if it was disconnected by user + * request), or an empty string if the account is connected. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The D-Bus error name for the last disconnection or connection failure. + * \sa connectionErrorDetails(), connectionStatus(), connectionStatusReason(), + * connectionStatusChanged() + */ +QString Account::connectionError() const +{ + return mPriv->connectionError; +} + +/** + * Return detailed information related to connectionError(). + * + * Note that this method may return a different value from the one returned by + * Connection::errorDetails() on this account connection. + * See connectionStatus() for more details. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The connection error details as a Connection::ErrorDetails object. + * \sa connectionError(), connectionStatus(), connectionStatusReason(), connectionStatusChanged(), + * Connection::ErrorDetails. + */ +Connection::ErrorDetails Account::connectionErrorDetails() const +{ + return mPriv->connectionErrorDetails; +} + +/** + * Return the object representing the connection of this account. + * + * Note that the Connection object returned by this method will have the + * features set in the connectionFactory() used by this account ready. + * + * Change notification is via the connectionChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return A pointer to the Connection object, or a null ConnectionPtr if + * there is no connection currently or if an error occurred. + * \sa connectionChanged() + */ +ConnectionPtr Account::connection() const +{ + return mPriv->connection; +} + +/** + * Return whether this account connection is changing presence. + * + * Change notification is via the changingPresence() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return \c true if changing presence, \c false otherwise. + * \sa changingPresence(), currentPresenceChanged(), setRequestedPresence() + */ +bool Account::isChangingPresence() const +{ + return mPriv->changingPresence; +} + +/** + * Return a list of presences allowed by a connection to this account. + * + * In particular, for the statuses reported here it can be assumed that setting them as the + * requested presence via setRequestedPresence() will eventually result in currentPresence() + * changing to exactly said presence. Other statuses are only guaranteed to be matched as closely as + * possible. + * + * The statuses can be also used for the automatic presence, as set by setAutomaticPresence(), with + * the exception of any status specifications for which Presence::type() is + * Tp::ConnectionPresenceTypeOffline for the Presence returned by PresenceSpec::presence(). + * + * However, the optional parameter can be used to allow reporting also other possible presence + * statuses on this protocol besides the others that can be set on yourself. These are purely + * informatory, for e.g. adjusting an UI to allow all possible remote contact statuses to be + * displayed. + * + * An offline presence status is always included, because it's always valid to make an account + * offline by setting the requested presence to an offline status. + * + * Full functionality requires Account::FeatureProtocolInfo and Account::FeatureProfile to be ready + * as well as connection with Connection::FeatureSimplePresence enabled. If the connection is online + * and Connection::FeatureSimplePresence is enabled, it will return the connection allowed statuses, + * otherwise it will return a list os statuses based on profile() and protocolInfo() information + * if the corresponding features are enabled. + * + * If there's a mismatch between the presence status info provided in the .profile file and/or the + * .manager file and what an online Connection actually reports (for example, the said data files + * are missing or too old to include presence information), the returned value can change, in + * particular when connectionChanged() is emitted with a connection for which Connection::status() + * is Tp::ConnectionStatusConnected. + * + * This method requires Account::FeatureCore to be ready. + * + * \param includeAllStatuses Whether the returned list will include all statuses or just the ones + * that can be settable using setRequestedPresence(). + * \return The allowed statuses as a list of PresenceSpec objects. + */ +PresenceSpecList Account::allowedPresenceStatuses(bool includeAllStatuses) const +{ + QMap<QString, PresenceSpec> specMap; + + // if the connection is online and ready use it + if (mPriv->connection && + mPriv->connection->status() == ConnectionStatusConnected && + mPriv->connection->actualFeatures().contains(Connection::FeatureSimplePresence)) { + SimpleStatusSpecMap connectionAllowedPresences = + mPriv->connection->lowlevel()->allowedPresenceStatuses(); + SimpleStatusSpecMap::const_iterator i = connectionAllowedPresences.constBegin(); + SimpleStatusSpecMap::const_iterator end = connectionAllowedPresences.constEnd(); + for (; i != end; ++i) { + PresenceSpec presence = PresenceSpec(i.key(), i.value()); + specMap.insert(i.key(), presence); + } + } else { + ProtocolInfo pi = protocolInfo(); + if (pi.isValid()) { + // add all ProtocolInfo presences to the returned map + foreach (const PresenceSpec &piPresence, pi.allowedPresenceStatuses()) { + QString piStatus = piPresence.presence().status(); + specMap.insert(piStatus, piPresence); + } + } + + ProfilePtr pr; + if (isReady(FeatureProfile)) { + pr = profile(); + } + if (pr && pr->isValid()) { + // add all Profile presences to the returned map + foreach (const Profile::Presence &prPresence, pr->presences()) { + QString prStatus = prPresence.id(); + if (specMap.contains(prStatus)) { + // we already got the presence from ProtocolInfo, just update + // canHaveStatusMessage if needed + PresenceSpec presence = specMap.value(prStatus); + if (presence.canHaveStatusMessage() != prPresence.canHaveStatusMessage()) { + SimpleStatusSpec spec; + spec.type = presence.presence().type(); + spec.maySetOnSelf = presence.maySetOnSelf(); + spec.canHaveMessage = prPresence.canHaveStatusMessage(); + specMap.insert(prStatus, PresenceSpec(prStatus, spec)); + } + } else { + // presence not found in ProtocolInfo, adding it + specMap.insert(prStatus, presenceSpecForStatus(prStatus, + prPresence.canHaveStatusMessage())); + } + } + + // now remove all presences that are not in the Profile, if it does + // not allow other presences, and the ones that are disabled + QMap<QString, PresenceSpec>::iterator i = specMap.begin(); + QMap<QString, PresenceSpec>::iterator end = specMap.end(); + while (i != end) { + PresenceSpec presence = i.value(); + QString status = presence.presence().status(); + bool hasPresence = pr->hasPresence(status); + Profile::Presence prPresence = pr->presence(status); + if ((!hasPresence && !pr->allowOtherPresences()) || (hasPresence && prPresence.isDisabled())) { + i = specMap.erase(i); + } else { + ++i; + } + } + } + } + + // filter out presences that may not be set on self if includeAllStatuses is false + if (!includeAllStatuses) { + QMap<QString, PresenceSpec>::iterator i = specMap.begin(); + QMap<QString, PresenceSpec>::iterator end = specMap.end(); + while (i != end) { + PresenceSpec presence = i.value(); + if (!presence.maySetOnSelf()) { + i = specMap.erase(i); + } else { + ++i; + } + } + } + + if (!specMap.size()) { + // If we didn't discover any statuses, either the protocol doesn't really support presence, + // or we lack information (e.g. features not enabled or info not provided in the .manager or + // .profile files). "available" - just the fact that you're online in the first place, is at + // least a valid option for any protocol, so we'll include it as a fallback. + + specMap.insert(QLatin1String("available"), + presenceSpecForStatus(QLatin1String("available"), false)); + } + + // We'll always include "offline". It is always valid to make an account offline via + // setRequestedPresence(). + if (!specMap.contains(QLatin1String("offline"))) { + specMap.insert(QLatin1String("offline"), + presenceSpecForStatus(QLatin1String("offline"), false)); + } + + return specMap.values(); +} + +/** + * Return the maximum length for a presence status message. + * + * If a status message set using setRequestedPresence() (or setAutomaticPresence()) is longer than + * the maximum length allowed, the message will be truncated and + * currentPresenceChanged() will be emitted (if setting the presence worked) + * with the truncated message. + * + * Full functionality requires Connection with Connection::FeatureSimplePresence + * enabled. If the connection is online and Connection::FeatureSimplePresence is + * enabled, it will return the connection maximum status message length, + * otherwise it will return 0. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The maximum length, or 0 if there is no limit. + */ +uint Account::maxPresenceStatusMessageLength() const +{ + // if the connection is online and ready use it + if (mPriv->connection && + mPriv->connection->status() == ConnectionStatusConnected && + mPriv->connection->actualFeatures().contains(Connection::FeatureSimplePresence)) { + return mPriv->connection->lowlevel()->maxPresenceStatusMessageLength(); + } + + return 0; +} + +/** + * Return the presence status that this account will have set on it by the + * account manager if it brings it online automatically. + * + * Change notification is via the automaticPresenceChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The automatic presence as a Presence object. + * \sa automaticPresenceChanged(), setAutomaticPresence() + */ +Presence Account::automaticPresence() const +{ + return mPriv->automaticPresence; +} + +/** + * Set the presence status that this account should have if it is brought + * online automatically by the account manager. + * + * Note that changing this property won't actually change the account's status + * until the next time it is (re)connected for some reason. + * + * The value of this property must be one that would be acceptable for setRequestedPresence(), + * as returned by allowedPresenceStatuses(), with the additional restriction that the offline + * presence cannot be used. + * + * \param presence The presence to set when this account is brought + * online automatically by the account manager. + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa automaticPresenceChanged(), automaticPresence(), setRequestedPresence() + */ +PendingOperation *Account::setAutomaticPresence(const Presence &presence) +{ + return new PendingVoid( + mPriv->properties->Set( + QLatin1String(TELEPATHY_INTERFACE_ACCOUNT), + QLatin1String("AutomaticPresence"), + QDBusVariant(QVariant::fromValue(presence.barePresence()))), + AccountPtr(this)); +} + +/** + * Return the actual presence of this account. + * + * Change notification is via the currentPresenceChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The current presence as a Presence object. + * \sa currentPresenceChanged(), setRequestedPresence(), requestedPresence(), automaticPresence() + */ +Presence Account::currentPresence() const +{ + return mPriv->currentPresence; +} + +/** + * Return the requested presence of this account. + * + * Change notification is via the requestedPresenceChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The requested presence as a Presence object. + * \sa requestedPresenceChanged(), setRequestedPresence(), currentPresence(), automaticPresence() + */ +Presence Account::requestedPresence() const +{ + return mPriv->requestedPresence; +} + +/** + * Set the requested presence of this account. + * + * When the requested presence is changed, the account manager will attempt to + * manipulate the connection to make currentPresence() match requestedPresence() + * as closely as possible. + * + * \param presence The requested presence. + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa requestedPresenceChanged(), currentPresence(), automaticPresence(), setAutomaticPresence() + */ +PendingOperation *Account::setRequestedPresence(const Presence &presence) +{ + return new PendingVoid( + mPriv->properties->Set( + QLatin1String(TELEPATHY_INTERFACE_ACCOUNT), + QLatin1String("RequestedPresence"), + QDBusVariant(QVariant::fromValue(presence.barePresence()))), + AccountPtr(this)); +} + +/** + * Return whether this account is online. + * + * Change notification is via the onlinenessChanged() signal. + * + * This method requires Account::FeatureCore to be ready. + * + * \return \c true if online, otherwise \c false. + * \sa onlinenessChanged() + */ +bool Account::isOnline() const +{ + return mPriv->currentPresence.type() != ConnectionPresenceTypeOffline; +} + +/** + * Return the unique identifier of this account. + * + * \return The unique identifier. + */ +QString Account::uniqueIdentifier() const +{ + QString path = objectPath(); + return path.right(path.length() - + strlen("/org/freedesktop/Telepathy/Account/")); +} + +/** + * Return the normalized user ID of the local user of this account. + * + * It is unspecified whether this user ID is globally unique. + * + * As currently implemented, IRC user IDs are only unique within the same + * IRCnet. On some saner protocols, the user ID includes a DNS name which + * provides global uniqueness. + * + * If this value is not known yet (which will always be the case for accounts + * that have never been online), it will be an empty string. + * + * It is possible that this value will change if the connection manager's + * normalization algorithm changes. + * + * This method requires Account::FeatureCore to be ready. + * + * \return The normalized user ID of the local user. + * \sa normalizedNameChanged() + */ +QString Account::normalizedName() const +{ + return mPriv->normalizedName; +} + +/** + * If this account is currently connected, disconnect and reconnect it. If it + * is currently trying to connect, cancel the attempt to connect and start + * another. If it is currently disconnected, do nothing. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + */ +PendingOperation *Account::reconnect() +{ + return new PendingVoid(baseInterface()->Reconnect(), AccountPtr(this)); +} + +/** + * Delete this account. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa removed() + */ +PendingOperation *Account::remove() +{ + return new PendingVoid(baseInterface()->Remove(), AccountPtr(this)); +} + +/** + * Return whether passing hints on channel requests on this account is known to be supported. + * + * This method requires Account::FeatureCore to be ready. + * + * \return \c true if supported, \c false otherwise. + */ +bool Account::supportsRequestHints() const +{ + return mPriv->dispatcherContext->supportsHints; +} + +/** + * Return whether the ChannelRequest::succeeded(const Tp::ChannelPtr &channel) signal is expected to + * be emitted with a non-NULL channel parameter for requests made using this account. + * + * This can be used as a run-time check for the Channel Dispatcher implementation being new enough. + * In particular, similarly old Channel Dispatchers don't support request hints either, so the + * return value for this function and Account::supportsRequestHints() will bet he same. + * + * This method requires Account::FeatureCore to be ready. + * + * \return \c true if supported, \c false otherwise. + */ +bool Account::requestsSucceedWithChannel() const +{ + return supportsRequestHints(); +} + +/** + * Same as \c ensureTextChat(contactIdentifier, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::ensureTextChat( + const QString &contactIdentifier, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return ensureTextChat(contactIdentifier, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to ensure that a text channel with the given + * contact \a contactIdentifier exists, creating it if necessary. + * + * See ensureChannel() for more details. + * + * \param contactIdentifier The identifier of the contact to chat with. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::ensureTextChat( + const QString &contactIdentifier, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = textChatRequest(contactIdentifier); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, false, hints); +} + +/** + * Same as \c ensureTextChat(contact, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::ensureTextChat( + const ContactPtr &contact, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return ensureTextChat(contact, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to ensure that a text channel with the given + * contact \a contact exists, creating it if necessary. + * + * See ensureChannel() for more details. + * + * \param contact The contact to chat with. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::ensureTextChat( + const ContactPtr &contact, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = textChatRequest(contact); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, false, hints); +} + +/** + * Same as \c ensureTextChatroom(roomName, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::ensureTextChatroom( + const QString &roomName, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return ensureTextChatroom(roomName, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to ensure that a text chat room with the given + * room name \a roomName exists, creating it if necessary. + * + * See ensureChannel() for more details. + * + * \param roomName The name of the chat room. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::ensureTextChatroom( + const QString &roomName, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = textChatroomRequest(roomName); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, false, hints); +} + +/** + * Same as \c ensureStreamedMediaCall(contactIdentifier, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::ensureStreamedMediaCall( + const QString &contactIdentifier, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return ensureStreamedMediaCall(contactIdentifier, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to ensure that a media channel with the given + * contact \a contactIdentifier exists, creating it if necessary. + * + * See ensureChannel() for more details. + * + * \param contactIdentifier The identifier of the contact to call. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::ensureStreamedMediaCall( + const QString &contactIdentifier, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = streamedMediaCallRequest(contactIdentifier); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, false, hints); +} + +/** + * Same as \c ensureStreamedMediaCall(contact, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::ensureStreamedMediaCall( + const ContactPtr &contact, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return ensureStreamedMediaCall(contact, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to ensure that a media channel with the given + * contact \a contact exists, creating it if necessary. + * + * See ensureChannel() for more details. + * + * \param contact The contact to call. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::ensureStreamedMediaCall( + const ContactPtr &contact, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = streamedMediaCallRequest(contact); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, false, hints); +} + +/** + * Same as \c ensureStreamedMediaAudioCall(contactIdentifier, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::ensureStreamedMediaAudioCall( + const QString &contactIdentifier, + QDateTime userActionTime, + const QString &preferredHandler) +{ + return ensureStreamedMediaAudioCall(contactIdentifier, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to ensure that an audio call with the given + * contact \a contactIdentifier exists, creating it if necessary. + * + * See ensureChannel() for more details. + * + * This will only work on relatively modern connection managers, + * like telepathy-gabble 0.9.0 or later. + * + * \param contactIdentifier The identifier of the contact to call. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::ensureStreamedMediaAudioCall( + const QString &contactIdentifier, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = streamedMediaAudioCallRequest(contactIdentifier); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, false, hints); +} + +/** + * Same as \c ensureStreamedMediaAudioCall(contact, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::ensureStreamedMediaAudioCall( + const ContactPtr &contact, + QDateTime userActionTime, + const QString &preferredHandler) +{ + return ensureStreamedMediaAudioCall(contact, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to ensure that an audio call with the given + * contact \a contact exists, creating it if necessary. + * + * See ensureChannel() for more details. + * + * This will only work on relatively modern connection managers, + * like telepathy-gabble 0.9.0 or later. + * + * \param contact The contact to call. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::ensureStreamedMediaAudioCall( + const ContactPtr &contact, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = streamedMediaAudioCallRequest(contact); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, false, hints); +} + +/** + * Same as \c ensureStreamedMediaVideoCall(contactIdentifier, withAudio, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::ensureStreamedMediaVideoCall( + const QString &contactIdentifier, + bool withAudio, + QDateTime userActionTime, + const QString &preferredHandler) +{ + return ensureStreamedMediaVideoCall(contactIdentifier, withAudio, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to ensure that a video call with the given + * contact \a contactIdentifier exists, creating it if necessary. + * + * See ensureChannel() for more details. + * + * This will only work on relatively modern connection managers, + * like telepathy-gabble 0.9.0 or later. + * + * \param contactIdentifier The identifier of the contact to call. + * \param withAudio true if both audio and video are required, false for a + * video-only call. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::ensureStreamedMediaVideoCall( + const QString &contactIdentifier, + bool withAudio, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = streamedMediaVideoCallRequest(contactIdentifier, withAudio); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, false, hints); +} + +/** + * Same as \c ensureStreamedMediaVideoCall(contact, withAudio, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::ensureStreamedMediaVideoCall( + const ContactPtr &contact, + bool withAudio, + QDateTime userActionTime, + const QString &preferredHandler) +{ + return ensureStreamedMediaVideoCall(contact, withAudio, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to ensure that a video call with the given + * contact \a contact exists, creating it if necessary. + * + * See ensureChannel() for more details. + * + * This will only work on relatively modern connection managers, + * like telepathy-gabble 0.9.0 or later. + * + * \param contact The contact to call. + * \param withAudio true if both audio and video are required, false for a + * video-only call. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::ensureStreamedMediaVideoCall( + const ContactPtr &contact, + bool withAudio, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = streamedMediaVideoCallRequest(contact, withAudio); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, false, hints); +} + +/** + * Same as \c createFileTransfer(contactIdentifier, properties, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::createFileTransfer( + const QString &contactIdentifier, + const FileTransferChannelCreationProperties &properties, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return createFileTransfer(contactIdentifier, properties, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to create a file transfer channel with the given + * contact \a contact. + * + * \param contactIdentifier The identifier of the contact to send a file. + * \param properties The desired properties. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::createFileTransfer( + const QString &contactIdentifier, + const FileTransferChannelCreationProperties &properties, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = fileTransferRequest(contactIdentifier, properties); + + if (request.isEmpty()) { + return new PendingChannelRequest(AccountPtr(this), TP_QT_ERROR_INVALID_ARGUMENT, + QLatin1String("Cannot create a file transfer with invalid parameters")); + } + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, true, hints); +} + +/** + * Same as \c createFileTransfer(contact, properties, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::createFileTransfer( + const ContactPtr &contact, + const FileTransferChannelCreationProperties &properties, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return createFileTransfer(contact, properties, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to create a file transfer channel with the given + * contact \a contact. + * + * \param contact The contact to send a file. + * \param properties The desired properties. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::createFileTransfer( + const ContactPtr &contact, + const FileTransferChannelCreationProperties &properties, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = fileTransferRequest(contact, properties); + + if (request.isEmpty()) { + return new PendingChannelRequest(AccountPtr(this), TP_QT_ERROR_INVALID_ARGUMENT, + QLatin1String("Cannot create a file transfer with invalid parameters")); + } + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, true, hints); +} + +/** + * Start a request to create a stream tube channel with the given + * contact identifier \a contactIdentifier. + * + * \param contactIdentifier The identifier of the contact to open a stream tube with. + * \param service The stream tube service. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::createStreamTube( + const QString &contactIdentifier, + const QString &service, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = streamTubeRequest(contactIdentifier, service); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, true, hints); +} + +/** + * Start a request to create a stream tube channel with the given + * contact \a contact. + * + * \param contact The contact to open a stream tube with. + * \param service The stream tube service. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::createStreamTube( + const ContactPtr &contact, + const QString &service, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = streamTubeRequest(contact, service); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, true, hints); +} + +/** + * Same as \c createConferenceStreamedMediaCall(channels, initialInviteeContactsIdentifiers, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::createConferenceStreamedMediaCall( + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return createConferenceStreamedMediaCall(channels, initialInviteeContactsIdentifiers, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to create a conference media call with the given + * channels \a channels. + * + * \param channels The conference channels. + * \param initialInviteeContactsIdentifiers A list of additional contacts + * identifiers to be invited to this + * conference when it is created. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::createConferenceStreamedMediaCall( + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = conferenceStreamedMediaCallRequest(channels, + initialInviteeContactsIdentifiers); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, true, hints); +} + +/** + * Same as \c createConferenceStreamedMediaCall(channels, initialInviteeContacts, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::createConferenceStreamedMediaCall( + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return createConferenceStreamedMediaCall(channels, initialInviteeContacts, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to create a conference media call with the given + * channels \a channels. + * + * \param channels The conference channels. + * \param initialInviteeContacts A list of additional contacts + * to be invited to this + * conference when it is created. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::createConferenceStreamedMediaCall( + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = conferenceStreamedMediaCallRequest(channels, initialInviteeContacts); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, true, hints); +} + +/** + * Same as \c createConferenceTextChat(channels, initialInviteeContactsIdentifiers, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::createConferenceTextChat( + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return createConferenceTextChat(channels, initialInviteeContactsIdentifiers, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to create a conference text chat with the given + * channels \a channels. + * + * \param channels The conference channels. + * \param initialInviteeContactsIdentifiers A list of additional contacts + * identifiers to be invited to this + * conference when it is created. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::createConferenceTextChat( + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = conferenceTextChatRequest(channels, initialInviteeContactsIdentifiers); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, true, hints); +} + +/** + * Same as \c createConferenceTextChat(channels, initialInviteeContacts, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::createConferenceTextChat( + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return createConferenceTextChat(channels, initialInviteeContacts, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to create a conference text chat with the given + * channels \a channels. + * + * \param channels The conference channels. + * \param initialInviteeContacts A list of additional contacts + * to be invited to this + * conference when it is created. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::createConferenceTextChat( + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = conferenceTextChatRequest(channels, initialInviteeContacts); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, true, hints); +} + +/** + * Same as \c createConferenceTextChatroom(roomName, channels, initialInviteeContactsIdentifiers, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::createConferenceTextChatRoom( + const QString &roomName, + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return createConferenceTextChatroom(roomName, channels, initialInviteeContactsIdentifiers, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to create a conference text chat room with the given + * channels \a channels and room name \a roomName. + * + * \param roomName The room name. + * \param channels The conference channels. + * \param initialInviteeContactsIdentifiers A list of additional contacts + * identifiers to be invited to this + * conference when it is created. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::createConferenceTextChatroom( + const QString &roomName, + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = conferenceTextChatroomRequest(roomName, channels, + initialInviteeContactsIdentifiers); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, true, hints); +} + +/** + * Same as \c createConferenceTextChatroom(roomName, channels, initialInviteeContacts, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::createConferenceTextChatRoom( + const QString &roomName, + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return createConferenceTextChatroom(roomName, channels, initialInviteeContacts, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to create a conference text chat room with the given + * channels \a channels and room name \a roomName. + * + * \param roomName The room name. + * \param channels The conference channels. + * \param initialInviteeContacts A list of additional contacts + * to be invited to this + * conference when it is created. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa ensureChannel(), createChannel() + */ +PendingChannelRequest *Account::createConferenceTextChatroom( + const QString &roomName, + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = conferenceTextChatroomRequest(roomName, channels, + initialInviteeContacts); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, true, hints); +} + +/** + * Same as \c createContactSearch(server, limit, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::createContactSearch( + const QString &server, + uint limit, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return createContactSearch(server, limit, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to create a contact search channel with the given + * server \a server and limit \a limit. + * + * \param server For protocols which support searching for contacts on multiple servers with + * different DNS names (like XMPP), the DNS name of the server to be searched, + * e.g. "characters.shakespeare.lit". Otherwise, an empty string. + * \param limit The desired maximum number of results that should be returned by a doing a search. + * If the protocol does not support specifying a limit for the number of results + * returned at a time, this will be ignored. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa createChannel() + */ +PendingChannelRequest *Account::createContactSearch( + const QString &server, + uint limit, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + QVariantMap request = contactSearchRequest(capabilities(), server, limit); + + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, true, hints); +} + +/** + * Start a request to ensure that a text channel with the given + * contact \a contactIdentifier exists, creating it if necessary. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param contactIdentifier The identifier of the contact to chat with. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::ensureAndHandleTextChat( + const QString &contactIdentifier, + const QDateTime &userActionTime) +{ + QVariantMap request = textChatRequest(contactIdentifier); + + return ensureAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to ensure that a text channel with the given + * contact \a contact exists, creating it if necessary. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param contact The contact to chat with. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::ensureAndHandleTextChat( + const ContactPtr &contact, + const QDateTime &userActionTime) +{ + QVariantMap request = textChatRequest(contact); + + return ensureAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to ensure that a text chat room with the given + * room name \a roomName exists, creating it if necessary. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param roomName The name of the chat room. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::ensureAndHandleTextChatroom( + const QString &roomName, + const QDateTime &userActionTime) +{ + QVariantMap request = textChatroomRequest(roomName); + + return ensureAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to ensure that a media channel with the given + * contact \a contactIdentifier exists, creating it if necessary. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param contactIdentifier The identifier of the contact to call. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::ensureAndHandleStreamedMediaCall( + const QString &contactIdentifier, + const QDateTime &userActionTime) +{ + QVariantMap request = streamedMediaCallRequest(contactIdentifier); + + return ensureAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to ensure that a media channel with the given + * contact \a contact exists, creating it if necessary. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param contact The contact to call. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::ensureAndHandleStreamedMediaCall( + const ContactPtr &contact, + const QDateTime &userActionTime) +{ + QVariantMap request = streamedMediaCallRequest(contact); + + return ensureAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to ensure that an audio call with the given + * contact \a contactIdentifier exists, creating it if necessary. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * This will only work on relatively modern connection managers, + * like telepathy-gabble 0.9.0 or later. + * + * \param contactIdentifier The identifier of the contact to call. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::ensureAndHandleStreamedMediaAudioCall( + const QString &contactIdentifier, + const QDateTime &userActionTime) +{ + QVariantMap request = streamedMediaAudioCallRequest(contactIdentifier); + + return ensureAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to ensure that an audio call with the given + * contact \a contact exists, creating it if necessary. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * This will only work on relatively modern connection managers, + * like telepathy-gabble 0.9.0 or later. + * + * \param contact The contact to call. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::ensureAndHandleStreamedMediaAudioCall( + const ContactPtr &contact, + const QDateTime &userActionTime) +{ + QVariantMap request = streamedMediaAudioCallRequest(contact); + + return ensureAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to ensure that a video call with the given + * contact \a contactIdentifier exists, creating it if necessary. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * This will only work on relatively modern connection managers, + * like telepathy-gabble 0.9.0 or later. + * + * \param contactIdentifier The identifier of the contact to call. + * \param withAudio true if both audio and video are required, false for a + * video-only call. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::ensureAndHandleStreamedMediaVideoCall( + const QString &contactIdentifier, + bool withAudio, + const QDateTime &userActionTime) +{ + QVariantMap request = streamedMediaVideoCallRequest(contactIdentifier, withAudio); + + return ensureAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to ensure that a video call with the given + * contact \a contact exists, creating it if necessary. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * This will only work on relatively modern connection managers, + * like telepathy-gabble 0.9.0 or later. + * + * \param contact The contact to call. + * \param withAudio true if both audio and video are required, false for a + * video-only call. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::ensureAndHandleStreamedMediaVideoCall( + const ContactPtr &contact, + bool withAudio, + const QDateTime &userActionTime) +{ + QVariantMap request = streamedMediaVideoCallRequest(contact, withAudio); + + return ensureAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to create a file transfer channel with the given + * contact \a contactIdentifier. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param contactIdentifier The identifier of the contact to send a file. + * \param properties The desired properties. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::createAndHandleFileTransfer( + const QString &contactIdentifier, + const FileTransferChannelCreationProperties &properties, + const QDateTime &userActionTime) +{ + QVariantMap request = fileTransferRequest(contactIdentifier, properties); + + if (request.isEmpty()) { + return new PendingChannel(TP_QT_ERROR_INVALID_ARGUMENT, + QLatin1String("Cannot create a file transfer with invalid parameters")); + } + + return createAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to create a file transfer channel with the given + * contact \a contact. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param contact The contact to send a file. + * \param properties The desired properties. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::createAndHandleFileTransfer( + const ContactPtr &contact, + const FileTransferChannelCreationProperties &properties, + const QDateTime &userActionTime) +{ + QVariantMap request = fileTransferRequest(contact, properties); + + if (request.isEmpty()) { + return new PendingChannel(TP_QT_ERROR_INVALID_ARGUMENT, + QLatin1String("Cannot create a file transfer with invalid parameters")); + } + + return createAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to create a stream tube channel with the given + * contact identifier \a contactIdentifier. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param contactIdentifier The identifier of the contact to open a stream tube with. + * \param service The stream tube service. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::createAndHandleStreamTube( + const QString &contactIdentifier, + const QString &service, + const QDateTime &userActionTime) +{ + QVariantMap request = streamTubeRequest(contactIdentifier, service); + + return createAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to create a stream tube channel with the given + * contact \a contact. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param contact The contact to open a stream tube with. + * \param service The stream tube service. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::createAndHandleStreamTube( + const ContactPtr &contact, + const QString &service, + const QDateTime &userActionTime) +{ + QVariantMap request = streamTubeRequest(contact, service); + + return createAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to create a conference text chat with the given + * channels \a channels. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param channels The conference channels. + * \param initialInviteeContactsIdentifiers A list of additional contacts + * identifiers to be invited to this + * conference when it is created. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::createAndHandleConferenceTextChat( + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers, + const QDateTime &userActionTime) +{ + QVariantMap request = conferenceTextChatRequest(channels, initialInviteeContactsIdentifiers); + + return createAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to create a conference text chat with the given + * channels \a channels. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param channels The conference channels. + * \param initialInviteeContacts A list of additional contacts + * to be invited to this + * conference when it is created. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::createAndHandleConferenceTextChat( + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts, + const QDateTime &userActionTime) +{ + QVariantMap request = conferenceTextChatRequest(channels, initialInviteeContacts); + + return createAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to create a conference text chat room with the given + * channels \a channels and room name \a roomName. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param roomName The room name. + * \param channels The conference channels. + * \param initialInviteeContactsIdentifiers A list of additional contacts + * identifiers to be invited to this + * conference when it is created. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::createAndHandleConferenceTextChatroom( + const QString &roomName, + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers, + const QDateTime &userActionTime) +{ + QVariantMap request = conferenceTextChatroomRequest(roomName, channels, + initialInviteeContactsIdentifiers); + + return createAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to create a conference text chat room with the given + * channels \a channels and room name \a roomName. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param roomName The room name. + * \param channels The conference channels. + * \param initialInviteeContacts A list of additional contacts + * to be invited to this + * conference when it is created. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::createAndHandleConferenceTextChatroom( + const QString &roomName, + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts, + const QDateTime &userActionTime) +{ + QVariantMap request = conferenceTextChatroomRequest(roomName, channels, + initialInviteeContacts); + + return createAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to create a conference media call with the given + * channels \a channels. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param channels The conference channels. + * \param initialInviteeContactsIdentifiers A list of additional contacts + * identifiers to be invited to this + * conference when it is created. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::createAndHandleConferenceStreamedMediaCall( + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers, + const QDateTime &userActionTime) +{ + QVariantMap request = conferenceStreamedMediaCallRequest(channels, + initialInviteeContactsIdentifiers); + + return createAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to create a conference media call with the given + * channels \a channels. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param channels The conference channels. + * \param initialInviteeContacts A list of additional contacts + * to be invited to this + * conference when it is created. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::createAndHandleConferenceStreamedMediaCall( + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts, + const QDateTime &userActionTime) +{ + QVariantMap request = conferenceStreamedMediaCallRequest(channels, initialInviteeContacts); + + return createAndHandleChannel(request, userActionTime); +} + +/** + * Start a request to create a contact search channel with the given + * server \a server and limit \a limit. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * \param server For protocols which support searching for contacts on multiple servers with + * different DNS names (like XMPP), the DNS name of the server to be searched, + * e.g. "characters.shakespeare.lit". Otherwise, an empty string. + * If the protocol does not support specifying a search server, this will be ignored. + * \param limit The desired maximum number of results that should be returned by a doing a search. + * If the protocol does not support specifying a limit for the number of results + * returned at a time, this will be ignored. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel(), createAndHandleChannel() + */ +PendingChannel *Account::createAndHandleContactSearch( + const QString &server, + uint limit, + const QDateTime &userActionTime) +{ + QVariantMap request = contactSearchRequest(capabilities(), server, limit); + + return createAndHandleChannel(request, userActionTime); +} + +/** + * Same as \c createChannel(request, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::createChannel( + const QVariantMap &request, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return createChannel(request, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to create a channel. + * This initially just creates a PendingChannelRequest object, + * which can be used to track the success or failure of the request, + * or to cancel it. + * + * Helper methods for text chat, text chat room, media call and conference are + * provided and should be used if appropriate. + * + * \param request A dictionary containing desirable properties. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa createChannel() + */ +PendingChannelRequest *Account::createChannel( + const QVariantMap &request, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, true, hints); +} + +/** + * Same as \c ensureChannel(request, userActionTime, preferredHandler, ChannelRequestHints()) + */ +PendingChannelRequest *Account::ensureChannel( + const QVariantMap &request, + const QDateTime &userActionTime, + const QString &preferredHandler) +{ + return ensureChannel(request, userActionTime, preferredHandler, ChannelRequestHints()); +} + +/** + * Start a request to ensure that a channel exists, creating it if necessary. + * This initially just creates a PendingChannelRequest object, + * which can be used to track the success or failure of the request, + * or to cancel it. + * + * Helper methods for text chat, text chat room, media call and conference are + * provided and should be used if appropriate. + * + * \param request A dictionary containing desirable properties. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param hints Arbitrary metadata which will be relayed to the handler if supported, + * as indicated by supportsRequestHints(). + * \return A PendingChannelRequest which will emit PendingChannelRequest::finished + * when the request has been made. + * \sa createChannel() + */ +PendingChannelRequest *Account::ensureChannel( + const QVariantMap &request, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints) +{ + return new PendingChannelRequest(AccountPtr(this), request, userActionTime, + preferredHandler, false, hints); +} + +/** + * Start a request to create channel. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * Helper methods for text chat, text chat room, media call and conference are + * provided and should be used if appropriate. + * + * The caller is responsible for closing the channel with + * Channel::requestClose() or Channel::requestLeave() when it has finished handling it. + * + * A possible error returned by this method is #TP_QT_ERROR_NOT_AVAILABLE, in case a conflicting + * channel that matches \a request already exists. + * + * \param request A dictionary containing desirable properties. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa ensureAndHandleChannel() + */ +PendingChannel *Account::createAndHandleChannel( + const QVariantMap &request, + const QDateTime &userActionTime) +{ + return new PendingChannel(AccountPtr(this), request, userActionTime, true); +} + +/** + * Start a request to ensure that a channel exists, creating it if necessary. + * This initially just creates a PendingChannel object, + * which can be used to track the success or failure of the request. + * + * Helper methods for text chat, text chat room, media call and conference are + * provided and should be used if appropriate. + * + * The caller is responsible for closing the channel with + * Channel::requestClose() or Channel::requestLeave() when it has finished handling it. + * + * A possible error returned by this method is #TP_QT_ERROR_NOT_YOURS, in case somebody else is + * already handling a channel that matches \a request. + * + * \param request A dictionary containing desirable properties. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \return A PendingChannel which will emit PendingChannel::finished + * successfully, when the Channel is available for handling using + * PendingChannel::channel(), or with an error if one has been encountered. + * \sa createAndHandleChannel() + */ +PendingChannel *Account::ensureAndHandleChannel( + const QVariantMap &request, + const QDateTime &userActionTime) +{ + return new PendingChannel(AccountPtr(this), request, userActionTime, false); +} + +/** + * Return the Client::AccountInterface interface proxy object for this account. + * 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::AccountInterface object for this + * Account object. + */ +Client::AccountInterface *Account::baseInterface() const +{ + return mPriv->baseInterface; +} + +/** + * Return the Client::ChannelDispatcherInterface interface proxy object to use for requesting + * channels on this account. + * + * 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::ChannelDispatcherInterface object for this + * Account object. + */ +Client::ChannelDispatcherInterface *Account::dispatcherInterface() const +{ + return mPriv->dispatcherContext->iface; +} + +/**** Private ****/ +void Account::Private::init() +{ + if (!parent->isValid()) { + return; + } + + parent->connect(baseInterface, + SIGNAL(Removed()), + SLOT(onRemoved())); + parent->connect(baseInterface, + SIGNAL(AccountPropertyChanged(QVariantMap)), + SLOT(onPropertyChanged(QVariantMap))); +} + +void Account::Private::introspectMain(Account::Private *self) +{ + if (self->dispatcherContext->introspected) { + self->parent->onDispatcherIntrospected(0); + return; + } + + if (!self->dispatcherContext->introspectOp) { + debug() << "Discovering if the Channel Dispatcher supports request hints"; + self->dispatcherContext->introspectOp = + self->dispatcherContext->iface->requestPropertySupportsRequestHints(); + } + + connect(self->dispatcherContext->introspectOp.data(), + SIGNAL(finished(Tp::PendingOperation*)), + self->parent, + SLOT(onDispatcherIntrospected(Tp::PendingOperation*))); +} + +void Account::Private::introspectAvatar(Account::Private *self) +{ + debug() << "Calling GetAvatar(Account)"; + // we already checked if avatar interface exists, so bypass avatar interface + // checking + Client::AccountInterfaceAvatarInterface *iface = + self->parent->interface<Client::AccountInterfaceAvatarInterface>(); + + // If we are here it means the user cares about avatar, so + // connect to avatar changed signal, so we update the avatar + // when it changes. + self->parent->connect(iface, + SIGNAL(AvatarChanged()), + SLOT(onAvatarChanged())); + + self->retrieveAvatar(); +} + +void Account::Private::introspectProtocolInfo(Account::Private *self) +{ + Q_ASSERT(!self->cm); + + self->cm = ConnectionManager::create( + self->parent->dbusConnection(), self->cmName, + self->connFactory, self->chanFactory, self->contactFactory); + self->parent->connect(self->cm->becomeReady(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onConnectionManagerReady(Tp::PendingOperation*))); +} + +void Account::Private::introspectCapabilities(Account::Private *self) +{ + if (!self->connection) { + // there is no connection, just make capabilities ready + self->readinessHelper->setIntrospectCompleted(FeatureCapabilities, true); + return; + } + + self->parent->connect(self->connection->becomeReady(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onConnectionReady(Tp::PendingOperation*))); +} + +void Account::Private::updateProperties(const QVariantMap &props) +{ + debug() << "Account::updateProperties: changed:"; + + if (props.contains(QLatin1String("Interfaces"))) { + parent->setInterfaces(qdbus_cast<QStringList>(props[QLatin1String("Interfaces")])); + debug() << " Interfaces:" << parent->interfaces(); + } + + QString oldIconName = parent->iconName(); + bool serviceNameChanged = false; + bool profileChanged = false; + if (props.contains(QLatin1String("Service")) && + serviceName != qdbus_cast<QString>(props[QLatin1String("Service")])) { + serviceNameChanged = true; + serviceName = qdbus_cast<QString>(props[QLatin1String("Service")]); + debug() << " Service Name:" << parent->serviceName(); + /* use parent->serviceName() here as if the service name is empty we are going to use the + * protocol name */ + emit parent->serviceNameChanged(parent->serviceName()); + parent->notify("serviceName"); + + /* if we had a profile and the service changed, it means the profile also changed */ + if (parent->isReady(Account::FeatureProfile)) { + /* service name changed, let's recreate profile */ + profileChanged = true; + profile.reset(); + emit parent->profileChanged(parent->profile()); + parent->notify("profile"); + } + } + + if (props.contains(QLatin1String("DisplayName")) && + displayName != qdbus_cast<QString>(props[QLatin1String("DisplayName")])) { + displayName = qdbus_cast<QString>(props[QLatin1String("DisplayName")]); + debug() << " Display Name:" << displayName; + emit parent->displayNameChanged(displayName); + parent->notify("displayName"); + } + + if ((props.contains(QLatin1String("Icon")) && + oldIconName != qdbus_cast<QString>(props[QLatin1String("Icon")])) || + serviceNameChanged) { + + if (props.contains(QLatin1String("Icon"))) { + iconName = qdbus_cast<QString>(props[QLatin1String("Icon")]); + } + + QString newIconName = parent->iconName(); + if (oldIconName != newIconName) { + debug() << " Icon:" << newIconName; + emit parent->iconNameChanged(newIconName); + parent->notify("iconName"); + } + } + + if (props.contains(QLatin1String("Nickname")) && + nickname != qdbus_cast<QString>(props[QLatin1String("Nickname")])) { + nickname = qdbus_cast<QString>(props[QLatin1String("Nickname")]); + debug() << " Nickname:" << nickname; + emit parent->nicknameChanged(nickname); + parent->notify("nickname"); + } + + if (props.contains(QLatin1String("NormalizedName")) && + normalizedName != qdbus_cast<QString>(props[QLatin1String("NormalizedName")])) { + normalizedName = qdbus_cast<QString>(props[QLatin1String("NormalizedName")]); + debug() << " Normalized Name:" << normalizedName; + emit parent->normalizedNameChanged(normalizedName); + parent->notify("normalizedName"); + } + + if (props.contains(QLatin1String("Valid")) && + valid != qdbus_cast<bool>(props[QLatin1String("Valid")])) { + valid = qdbus_cast<bool>(props[QLatin1String("Valid")]); + debug() << " Valid:" << (valid ? "true" : "false"); + emit parent->validityChanged(valid); + parent->notify("valid"); + } + + if (props.contains(QLatin1String("Enabled")) && + enabled != qdbus_cast<bool>(props[QLatin1String("Enabled")])) { + enabled = qdbus_cast<bool>(props[QLatin1String("Enabled")]); + debug() << " Enabled:" << (enabled ? "true" : "false"); + emit parent->stateChanged(enabled); + parent->notify("enabled"); + } + + if (props.contains(QLatin1String("ConnectAutomatically")) && + connectsAutomatically != + qdbus_cast<bool>(props[QLatin1String("ConnectAutomatically")])) { + connectsAutomatically = + qdbus_cast<bool>(props[QLatin1String("ConnectAutomatically")]); + debug() << " Connects Automatically:" << (connectsAutomatically ? "true" : "false"); + emit parent->connectsAutomaticallyPropertyChanged(connectsAutomatically); + parent->notify("connectsAutomatically"); + } + + if (props.contains(QLatin1String("HasBeenOnline")) && + !hasBeenOnline && + qdbus_cast<bool>(props[QLatin1String("HasBeenOnline")])) { + hasBeenOnline = true; + debug() << " HasBeenOnline changed to true"; + // don't emit firstOnline unless we're already ready, that would be + // misleading - we'd emit it just before any already-used account + // became ready + if (parent->isReady(Account::FeatureCore)) { + emit parent->firstOnline(); + } + parent->notify("hasBeenOnline"); + } + + if (props.contains(QLatin1String("Parameters")) && + parameters != qdbus_cast<QVariantMap>(props[QLatin1String("Parameters")])) { + parameters = qdbus_cast<QVariantMap>(props[QLatin1String("Parameters")]); + emit parent->parametersChanged(parameters); + parent->notify("parameters"); + } + + if (props.contains(QLatin1String("AutomaticPresence")) && + automaticPresence.barePresence() != qdbus_cast<SimplePresence>( + props[QLatin1String("AutomaticPresence")])) { + automaticPresence = Presence(qdbus_cast<SimplePresence>( + props[QLatin1String("AutomaticPresence")])); + debug() << " Automatic Presence:" << automaticPresence.type() << + "-" << automaticPresence.status(); + emit parent->automaticPresenceChanged(automaticPresence); + parent->notify("automaticPresence"); + } + + if (props.contains(QLatin1String("CurrentPresence")) && + currentPresence.barePresence() != qdbus_cast<SimplePresence>( + props[QLatin1String("CurrentPresence")])) { + currentPresence = Presence(qdbus_cast<SimplePresence>( + props[QLatin1String("CurrentPresence")])); + debug() << " Current Presence:" << currentPresence.type() << + "-" << currentPresence.status(); + emit parent->currentPresenceChanged(currentPresence); + parent->notify("currentPresence"); + emit parent->onlinenessChanged(parent->isOnline()); + parent->notify("online"); + } + + if (props.contains(QLatin1String("RequestedPresence")) && + requestedPresence.barePresence() != qdbus_cast<SimplePresence>( + props[QLatin1String("RequestedPresence")])) { + requestedPresence = Presence(qdbus_cast<SimplePresence>( + props[QLatin1String("RequestedPresence")])); + debug() << " Requested Presence:" << requestedPresence.type() << + "-" << requestedPresence.status(); + emit parent->requestedPresenceChanged(requestedPresence); + parent->notify("requestedPresence"); + } + + if (props.contains(QLatin1String("ChangingPresence")) && + changingPresence != qdbus_cast<bool>( + props[QLatin1String("ChangingPresence")])) { + changingPresence = qdbus_cast<bool>( + props[QLatin1String("ChangingPresence")]); + debug() << " Changing Presence:" << changingPresence; + emit parent->changingPresence(changingPresence); + parent->notify("changingPresence"); + } + + if (props.contains(QLatin1String("Connection"))) { + QString path = qdbus_cast<QDBusObjectPath>(props[QLatin1String("Connection")]).path(); + if (path.isEmpty()) { + debug() << " The map contains \"Connection\" but it's empty as a QDBusObjectPath!"; + debug() << " Trying QString (known bug in some MC/dbus-glib versions)"; + path = qdbus_cast<QString>(props[QLatin1String("Connection")]); + } + + debug() << " Connection Object Path:" << path; + if (path == QLatin1String("/")) { + path = QString(); + } + + connObjPathQueue.enqueue(path); + + if (connObjPathQueue.size() == 1) { + processConnQueue(); + } + + // onConnectionBuilt for a previous path will make sure the path we enqueued is processed if + // the queue wasn't empty (so is now size() > 1) + } + + bool connectionStatusChanged = false; + if (props.contains(QLatin1String("ConnectionStatus")) || + props.contains(QLatin1String("ConnectionStatusReason")) || + props.contains(QLatin1String("ConnectionError")) || + props.contains(QLatin1String("ConnectionErrorDetails"))) { + ConnectionStatus oldConnectionStatus = connectionStatus; + + if (props.contains(QLatin1String("ConnectionStatus")) && + connectionStatus != ConnectionStatus( + qdbus_cast<uint>(props[QLatin1String("ConnectionStatus")]))) { + connectionStatus = ConnectionStatus( + qdbus_cast<uint>(props[QLatin1String("ConnectionStatus")])); + debug() << " Connection Status:" << connectionStatus; + connectionStatusChanged = true; + } + + if (props.contains(QLatin1String("ConnectionStatusReason")) && + connectionStatusReason != ConnectionStatusReason( + qdbus_cast<uint>(props[QLatin1String("ConnectionStatusReason")]))) { + connectionStatusReason = ConnectionStatusReason( + qdbus_cast<uint>(props[QLatin1String("ConnectionStatusReason")])); + debug() << " Connection StatusReason:" << connectionStatusReason; + connectionStatusChanged = true; + } + + if (connectionStatusChanged) { + parent->notify("connectionStatus"); + parent->notify("connectionStatusReason"); + } + + if (props.contains(QLatin1String("ConnectionError")) && + connectionError != qdbus_cast<QString>( + props[QLatin1String("ConnectionError")])) { + connectionError = qdbus_cast<QString>( + props[QLatin1String("ConnectionError")]); + debug() << " Connection Error:" << connectionError; + connectionStatusChanged = true; + } + + if (props.contains(QLatin1String("ConnectionErrorDetails")) && + connectionErrorDetails.allDetails() != qdbus_cast<QVariantMap>( + props[QLatin1String("ConnectionErrorDetails")])) { + connectionErrorDetails = Connection::ErrorDetails(qdbus_cast<QVariantMap>( + props[QLatin1String("ConnectionErrorDetails")])); + debug() << " Connection Error Details:" << connectionErrorDetails.allDetails(); + connectionStatusChanged = true; + } + + if (connectionStatusChanged) { + /* Something other than status changed, let's not emit connectionStatusChanged + * and keep the error/errorDetails, for the next interaction. + * It may happen if ConnectionError changes and in another property + * change the status changes to Disconnected, so we use the error + * previously signalled. If the status changes to something other + * than Disconnected later, the error is cleared. */ + if (oldConnectionStatus != connectionStatus) { + /* We don't signal error for status other than Disconnected */ + if (connectionStatus != ConnectionStatusDisconnected) { + connectionError = QString(); + connectionErrorDetails = Connection::ErrorDetails(); + } else if (connectionError.isEmpty()) { + connectionError = ConnectionHelper::statusReasonToErrorName( + connectionStatusReason, oldConnectionStatus); + } + + checkCapabilitiesChanged(profileChanged); + + emit parent->connectionStatusChanged(connectionStatus); + parent->notify("connectionError"); + parent->notify("connectionErrorDetails"); + } else { + connectionStatusChanged = false; + } + } + } + + if (!connectionStatusChanged && profileChanged) { + checkCapabilitiesChanged(profileChanged); + } +} + +void Account::Private::retrieveAvatar() +{ + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + parent->mPriv->properties->Get( + QLatin1String(TELEPATHY_INTERFACE_ACCOUNT_INTERFACE_AVATAR), + QLatin1String("Avatar")), parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotAvatar(QDBusPendingCallWatcher*))); +} + +bool Account::Private::processConnQueue() +{ + while (!connObjPathQueue.isEmpty()) { + QString path = connObjPathQueue.head(); + if (path.isEmpty()) { + if (!connection.isNull()) { + debug() << "Dropping connection for account" << parent->objectPath(); + + connection.reset(); + emit parent->connectionChanged(connection); + parent->notify("connection"); + parent->notify("connectionObjectPath"); + } + + connObjPathQueue.dequeue(); + } else { + debug() << "Building connection" << path << "for account" << parent->objectPath(); + + if (connection && connection->objectPath() == path) { + debug() << " Connection already built"; + connObjPathQueue.dequeue(); + continue; + } + + QString busName = path.mid(1).replace(QLatin1String("/"), QLatin1String(".")); + parent->connect(connFactory->proxy(busName, path, chanFactory, contactFactory), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onConnectionBuilt(Tp::PendingOperation*))); + + // No dequeue here, but only in onConnectionBuilt, so we will queue future changes + return false; // Only move on to the next paths when that build finishes + } + } + + return true; +} + +void Account::onDispatcherIntrospected(Tp::PendingOperation *op) +{ + if (!mPriv->dispatcherContext->introspected) { + Tp::PendingVariant *pv = static_cast<Tp::PendingVariant *>(op); + Q_ASSERT(pv != NULL); + + // Only the first Account for a given dispatcher will enter this branch, and will + // immediately make further created accounts skip the whole waiting for CD to get + // introspected part entirely + mPriv->dispatcherContext->introspected = true; + + if (pv->isValid()) { + mPriv->dispatcherContext->supportsHints = qdbus_cast<bool>(pv->result()); + debug() << "Discovered channel dispatcher support for request hints: " + << mPriv->dispatcherContext->supportsHints; + } else { + if (pv->errorName() == TP_QT_ERROR_NOT_IMPLEMENTED) { + debug() << "Channel Dispatcher does not implement support for request hints"; + } else { + warning() << "(Too old?) Channel Dispatcher failed to tell us whether" + << "it supports request hints, assuming it doesn't:" + << pv->errorName() << ':' << pv->errorMessage(); + } + mPriv->dispatcherContext->supportsHints = false; + } + } + + debug() << "Calling Properties::GetAll(Account) on " << objectPath(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + mPriv->properties->GetAll( + QLatin1String(TELEPATHY_INTERFACE_ACCOUNT)), this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotMainProperties(QDBusPendingCallWatcher*))); +} + +void Account::gotMainProperties(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + + if (!reply.isError()) { + debug() << "Got reply to Properties.GetAll(Account) for" << objectPath(); + mPriv->updateProperties(reply.value()); + + mPriv->readinessHelper->setInterfaces(interfaces()); + mPriv->mayFinishCore = true; + + if (mPriv->connObjPathQueue.isEmpty()) { + debug() << "Account basic functionality is ready"; + mPriv->coreFinished = true; + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true); + } else { + debug() << "Deferring finishing Account::FeatureCore until the connection is built"; + } + } else { + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, reply.error()); + + warning().nospace() << + "GetAll(Account) failed: " << + reply.error().name() << ": " << reply.error().message(); + } + + watcher->deleteLater(); +} + +void Account::gotAvatar(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariant> reply = *watcher; + + if (!reply.isError()) { + debug() << "Got reply to GetAvatar(Account)"; + mPriv->avatar = qdbus_cast<Avatar>(reply); + + // It could be in either of actual or missing from the first time in corner cases like the + // object going away, so let's be prepared for both (only checking for actualFeatures here + // actually used to trigger a rare bug) + // + // Anyway, the idea is to not do setIntrospectCompleted twice + if (!mPriv->readinessHelper->actualFeatures().contains(FeatureAvatar) && + !mPriv->readinessHelper->missingFeatures().contains(FeatureAvatar)) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureAvatar, true); + } + + emit avatarChanged(mPriv->avatar); + notify("avatar"); + } else { + // check if the feature is already there, and for some reason retrieveAvatar + // failed when called the second time + if (!mPriv->readinessHelper->actualFeatures().contains(FeatureAvatar) && + !mPriv->readinessHelper->missingFeatures().contains(FeatureAvatar)) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureAvatar, false, reply.error()); + } + + warning().nospace() << + "GetAvatar(Account) failed: " << + reply.error().name() << ": " << reply.error().message(); + } + + watcher->deleteLater(); +} + +void Account::onAvatarChanged() +{ + debug() << "Avatar changed, retrieving it"; + mPriv->retrieveAvatar(); +} + +void Account::onConnectionManagerReady(PendingOperation *operation) +{ + bool error = operation->isError(); + if (!error) { + error = !mPriv->cm->hasProtocol(mPriv->protocolName); + } + + if (!error) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureProtocolInfo, true); + } + else { + warning() << "Failed to find the protocol in the CM protocols for account" << objectPath(); + mPriv->readinessHelper->setIntrospectCompleted(FeatureProtocolInfo, false, + operation->errorName(), operation->errorMessage()); + } +} + +void Account::onConnectionReady(PendingOperation *op) +{ + mPriv->checkCapabilitiesChanged(false); + + /* let's not fail if connection can't become ready, the caps will still + * work, but return the CM caps instead. Also no need to call + * setIntrospectCompleted if the feature was already set to complete once, + * since this method will be called whenever the account connection + * changes */ + if (!isReady(FeatureCapabilities)) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureCapabilities, true); + } +} + +void Account::onPropertyChanged(const QVariantMap &delta) +{ + mPriv->updateProperties(delta); +} + +void Account::onRemoved() +{ + mPriv->valid = false; + mPriv->enabled = false; + invalidate(TP_QT_ERROR_OBJECT_REMOVED, + QLatin1String("Account removed from AccountManager")); + emit removed(); +} + +void Account::onConnectionBuilt(PendingOperation *op) +{ + PendingReady *readyOp = qobject_cast<PendingReady *>(op); + Q_ASSERT(readyOp != NULL); + + if (op->isError()) { + warning() << "Building connection" << mPriv->connObjPathQueue.head() << "failed with" << + op->errorName() << "-" << op->errorMessage(); + + if (!mPriv->connection.isNull()) { + mPriv->connection.reset(); + emit connectionChanged(mPriv->connection); + notify("connection"); + notify("connectionObjectPath"); + } + } else { + ConnectionPtr prevConn = mPriv->connection; + QString prevConnPath = mPriv->connectionObjectPath(); + + mPriv->connection = ConnectionPtr::qObjectCast(readyOp->proxy()); + Q_ASSERT(mPriv->connection); + + debug() << "Connection" << mPriv->connectionObjectPath() << "built for" << objectPath(); + + if (prevConn != mPriv->connection) { + notify("connection"); + emit connectionChanged(mPriv->connection); + } + + if (prevConnPath != mPriv->connectionObjectPath()) { + notify("connectionObjectPath"); + } + } + + mPriv->connObjPathQueue.dequeue(); + + if (mPriv->processConnQueue() && !mPriv->coreFinished && mPriv->mayFinishCore) { + debug() << "Account" << objectPath() << "basic functionality is ready (connections built)"; + mPriv->coreFinished = true; + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true); + } +} + +/** + * \fn void Account::removed() + * + * Emitted when this account is removed from the account manager it belonged. + * + * \sa remove(). + */ + +/** + * \fn void Account::validityChanged(bool validity) + * + * Emitted when the value of isValidAccount() changes. + * + * \param validity The new validity of this account. + * \sa isValidAccount() + */ + +/** + * \fn void Account::stateChanged(bool state) + * + * Emitted when the value of isEnabled() changes. + * + * \param state The new state of this account. + * \sa isEnabled() + */ + +/** + * \fn void Account::serviceNameChanged(const QString &serviceName) + * + * Emitted when the value of serviceName() changes. + * + * \param serviceName The new service name of this account. + * \sa serviceName(), setServiceName() + */ + +/** + * \fn void Account::profileChanged(const Tp::ProfilePtr &profile) + * + * Emitted when the value of profile() changes. + * + * \param profile The new profile of this account. + * \sa profile() + */ + +/** + * \fn void Account::displayNameChanged(const QString &displayName) + * + * Emitted when the value of displayName() changes. + * + * \param displayName The new display name of this account. + * \sa displayName(), setDisplayName() + */ + +/** + * \fn void Account::iconNameChanged(const QString &iconName) + * + * Emitted when the value of iconName() changes. + * + * \param iconName The new icon name of this account. + * \sa iconName(), setIconName() + */ + +/** + * \fn void Account::nicknameChanged(const QString &nickname) + * + * Emitted when the value of nickname() changes. + * + * \param nickname The new nickname of this account. + * \sa nickname(), setNickname() + */ + +/** + * \fn void Account::normalizedNameChanged(const QString &normalizedName) + * + * Emitted when the value of normalizedName() changes. + * + * \param normalizedName The new normalized name of this account. + * \sa normalizedName() + */ + +/** + * \fn void Account::capabilitiesChanged(const Tp::ConnectionCapabilities &capabilities) + * + * Emitted when the value of capabilities() changes. + * + * \param capabilities The new capabilities of this account. + * \sa capabilities() + */ + +/** + * \fn void Account::connectsAutomaticallyPropertyChanged(bool connectsAutomatically) + * + * Emitted when the value of connectsAutomatically() changes. + * + * \param connectsAutomatically The new value of connects automatically property + * of this account. + * \sa isEnabled() + */ + +/** + * \fn void Account::firstOnline() + * + * Emitted when this account is first put online. + * + * \sa hasBeenOnline() + */ + +/** + * \fn void Account::parametersChanged(const QVariantMap ¶meters) + * + * Emitted when the value of parameters() changes. + * + * \param parameters The new parameters of this account. + * \sa parameters() + */ + +/** + * \fn void Account::changingPresence(bool value) + * + * Emitted when the value of isChangingPresence() changes. + * + * \param value Whether this account's connection is changing presence. + * \sa isChangingPresence() + */ + +/** + * \fn void Account::automaticPresenceChanged(const Tp::Presence &automaticPresence) + * + * Emitted when the value of automaticPresence() changes. + * + * \param automaticPresence The new value of automatic presence property of this + * account. + * \sa automaticPresence(), currentPresenceChanged() + */ + +/** + * \fn void Account::currentPresenceChanged(const Tp::Presence ¤tPresence) + * + * Emitted when the value of currentPresence() changes. + * + * \param currentPresence The new value of the current presence property of this + * account. + * \sa currentPresence() + */ + +/** + * \fn void Account::requestedPresenceChanged(const Tp::Presence &requestedPresence) + * + * Emitted when the value of requestedPresence() changes. + * + * \param requestedPresence The new value of the requested presence property of this + * account. + * \sa requestedPresence(), currentPresenceChanged() + */ + +/** + * \fn void Account::onlinenessChanged(bool online) + * + * Emitted when the value of isOnline() changes. + * + * \param online Whether this account is online. + * \sa isOnline(), currentPresence() + */ + +/** + * \fn void Account::avatarChanged(const Tp::Avatar &avatar) + * + * Emitted when the value of avatar() changes. + * + * \param avatar The new avatar of this account. + * \sa avatar() + */ + +/** + * \fn void Account::connectionStatusChanged(Tp::ConnectionStatus status) + * + * Emitted when the connection status changes. + * + * \param status The new status of this account connection. + * \sa connectionStatus(), connectionStatusReason(), connectionError(), connectionErrorDetails(), + * Connection::ErrorDetails + */ + +/** + * \fn void Account::connectionChanged(const Tp::ConnectionPtr &connection) + * + * Emitted when the value of connection() changes. + * + * The \a connection will have the features set in the ConnectionFactory used by this account ready + * and the same channel and contact factories used by this account. + * + * \param connection A ConnectionPtr pointing to the new Connection object or a null ConnectionPtr + * if there is no connection. + * \sa connection() + */ + +} // Tp diff --git a/TelepathyQt/account.h b/TelepathyQt/account.h new file mode 100644 index 00000000..4ca2d8fe --- /dev/null +++ b/TelepathyQt/account.h @@ -0,0 +1,598 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_account_h_HEADER_GUARD_ +#define _TelepathyQt_account_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/_gen/cli-account.h> + +#include <TelepathyQt/ChannelRequestHints> +#include <TelepathyQt/Connection> +#include <TelepathyQt/ConnectionCapabilities> +#include <TelepathyQt/ConnectionFactory> +#include <TelepathyQt/ContactFactory> +#include <TelepathyQt/ChannelFactory> +#include <TelepathyQt/ChannelDispatcherInterface> +#include <TelepathyQt/DBus> +#include <TelepathyQt/DBusProxy> +#include <TelepathyQt/FileTransferChannelCreationProperties> +#include <TelepathyQt/OptionalInterfaceFactory> +#include <TelepathyQt/Presence> +#include <TelepathyQt/PresenceSpec> +#include <TelepathyQt/ProtocolInfo> +#include <TelepathyQt/ReadinessHelper> +#include <TelepathyQt/Types> +#include <TelepathyQt/Constants> +#include <TelepathyQt/SharedPtr> + +#include <QSet> +#include <QString> +#include <QStringList> +#include <QVariantMap> + +namespace Tp +{ + +class Account; +class Connection; +class PendingChannel; +class PendingChannelRequest; +class PendingConnection; +class PendingOperation; +class PendingReady; +class PendingStringList; + +class TP_QT_EXPORT Account : public StatelessDBusProxy, + public OptionalInterfaceFactory<Account> +{ + Q_OBJECT + Q_DISABLE_COPY(Account) + Q_PROPERTY(bool valid READ isValidAccount NOTIFY validityChanged) + Q_PROPERTY(bool enabled READ isEnabled NOTIFY stateChanged) + Q_PROPERTY(QString cmName READ cmName) + Q_PROPERTY(QString protocolName READ protocolName) + Q_PROPERTY(QString serviceName READ serviceName NOTIFY serviceNameChanged) + Q_PROPERTY(ProfilePtr profile READ profile NOTIFY profileChanged) + Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged) + Q_PROPERTY(QString iconName READ iconName NOTIFY iconNameChanged) + Q_PROPERTY(QString nickname READ nickname NOTIFY nicknameChanged) + Q_PROPERTY(AvatarSpec avatarRequirements READ avatarRequirements) + Q_PROPERTY(Avatar avatar READ avatar NOTIFY avatarChanged) + Q_PROPERTY(QVariantMap parameters READ parameters NOTIFY parametersChanged) + Q_PROPERTY(ProtocolInfo protocolInfo READ protocolInfo) + Q_PROPERTY(ConnectionCapabilities capabilities READ capabilities NOTIFY capabilitiesChanged) + Q_PROPERTY(bool hasBeenOnline READ hasBeenOnline) + Q_PROPERTY(bool connectsAutomatically READ connectsAutomatically NOTIFY connectsAutomaticallyPropertyChanged) + Q_PROPERTY(ConnectionStatus connectionStatus READ connectionStatus NOTIFY connectionStatusChanged) + Q_PROPERTY(ConnectionStatusReason connectionStatusReason READ connectionStatusReason) + Q_PROPERTY(QString connectionError READ connectionError) + Q_PROPERTY(Tp::Connection::ErrorDetails connectionErrorDetails READ connectionErrorDetails) + Q_PROPERTY(ConnectionPtr connection READ connection NOTIFY connectionChanged) + Q_PROPERTY(bool changingPresence READ isChangingPresence NOTIFY changingPresence) + Q_PROPERTY(Presence automaticPresence READ automaticPresence NOTIFY automaticPresenceChanged) + Q_PROPERTY(Presence currentPresence READ currentPresence NOTIFY currentPresenceChanged) + Q_PROPERTY(Presence requestedPresence READ requestedPresence NOTIFY requestedPresenceChanged) + Q_PROPERTY(bool online READ isOnline NOTIFY onlinenessChanged) + Q_PROPERTY(QString uniqueIdentifier READ uniqueIdentifier) + Q_PROPERTY(QString normalizedName READ normalizedName NOTIFY normalizedNameChanged) + +public: + static const Feature FeatureCore; + static const Feature FeatureAvatar; + static const Feature FeatureProtocolInfo; + static const Feature FeatureCapabilities; + static const Feature FeatureProfile; + + static AccountPtr create(const QString &busName, const QString &objectPath, + const ConnectionFactoryConstPtr &connectionFactory = + ConnectionFactory::create(QDBusConnection::sessionBus()), + const ChannelFactoryConstPtr &channelFactory = + ChannelFactory::create(QDBusConnection::sessionBus()), + const ContactFactoryConstPtr &contactFactory = + ContactFactory::create()); + static AccountPtr create(const QDBusConnection &bus, + const QString &busName, const QString &objectPath, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory = + ContactFactory::create()); + virtual ~Account(); + + ConnectionFactoryConstPtr connectionFactory() const; + ChannelFactoryConstPtr channelFactory() const; + ContactFactoryConstPtr contactFactory() const; + + bool isValidAccount() const; + + bool isEnabled() const; + PendingOperation *setEnabled(bool value); + + QString cmName() const; + + QString protocolName() const; + + QString serviceName() const; + PendingOperation *setServiceName(const QString &value); + + ProfilePtr profile() const; + + QString displayName() const; + PendingOperation *setDisplayName(const QString &value); + + QString iconName() const; + PendingOperation *setIconName(const QString &value); + + QString nickname() const; + PendingOperation *setNickname(const QString &value); + + AvatarSpec avatarRequirements() const; + // TODO: We probably want to expose the avatar file name once we have the avatar token and MC + // starts sharing the cache used by tp-qt4 and tp-glib and use Tp::AvatarData to represent + // it as used in Tp::Contact + const Avatar &avatar() const; + PendingOperation *setAvatar(const Avatar &avatar); + + QVariantMap parameters() const; + PendingStringList *updateParameters(const QVariantMap &set, + const QStringList &unset); + + ProtocolInfo protocolInfo() const; + + ConnectionCapabilities capabilities() const; + + bool connectsAutomatically() const; + PendingOperation *setConnectsAutomatically(bool value); + + bool hasBeenOnline() const; + + ConnectionStatus connectionStatus() const; + ConnectionStatusReason connectionStatusReason() const; + QString connectionError() const; + Connection::ErrorDetails connectionErrorDetails() const; + ConnectionPtr connection() const; + + bool isChangingPresence() const; + + PresenceSpecList allowedPresenceStatuses(bool includeAllStatuses = false) const; + uint maxPresenceStatusMessageLength() const; + + // TODO: Add overload methods to set presence from a Profile::Presence + // TODO: Add usablePresences() that would return a list of presences that could be set on the + // account + Presence automaticPresence() const; + PendingOperation *setAutomaticPresence(const Presence &presence); + + Presence currentPresence() const; + + Presence requestedPresence() const; + PendingOperation *setRequestedPresence(const Presence &presence); + + bool isOnline() const; + + QString uniqueIdentifier() const; + + QString normalizedName() const; + + PendingOperation *reconnect(); + + PendingOperation *remove(); + + bool supportsRequestHints() const; + bool requestsSucceedWithChannel() const; + + // TODO ABI break: collapse all of the overloads without a hints arg with the corresponding + // hints versions and add a default param for the hints args + + PendingChannelRequest *ensureTextChat( + const QString &contactIdentifier, + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *ensureTextChat( + const QString &contactIdentifier, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *ensureTextChat( + const ContactPtr &contact, + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *ensureTextChat( + const ContactPtr &contact, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *ensureTextChatroom( + const QString &roomName, + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *ensureTextChatroom( + const QString &roomName, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *ensureStreamedMediaCall( + const QString &contactIdentifier, + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *ensureStreamedMediaCall( + const QString &contactIdentifier, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *ensureStreamedMediaCall( + const ContactPtr &contact, + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *ensureStreamedMediaCall( + const ContactPtr &contact, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *ensureStreamedMediaAudioCall( + const QString &contactIdentifier, + QDateTime userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *ensureStreamedMediaAudioCall( + const QString &contactIdentifier, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *ensureStreamedMediaAudioCall( + const ContactPtr &contact, + QDateTime userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *ensureStreamedMediaAudioCall( + const ContactPtr &contact, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *ensureStreamedMediaVideoCall( + const QString &contactIdentifier, + bool withAudio = true, + QDateTime userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *ensureStreamedMediaVideoCall( + const QString &contactIdentifier, + bool withAudio, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *ensureStreamedMediaVideoCall( + const ContactPtr &contact, + bool withAudio = true, + QDateTime userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *ensureStreamedMediaVideoCall( + const ContactPtr &contact, + bool withAudio, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *createFileTransfer( + const QString &contactIdentifier, + const FileTransferChannelCreationProperties &properties, + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *createFileTransfer( + const QString &contactIdentifier, + const FileTransferChannelCreationProperties &properties, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *createFileTransfer( + const ContactPtr &contact, + const FileTransferChannelCreationProperties &properties, + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *createFileTransfer( + const ContactPtr &contact, + const FileTransferChannelCreationProperties &properties, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *createStreamTube( + const QString &contactIdentifier, + const QString &service, + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString(), + const ChannelRequestHints &hints = ChannelRequestHints()); + PendingChannelRequest *createStreamTube( + const ContactPtr &contact, + const QString &service, + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString(), + const ChannelRequestHints &hints = ChannelRequestHints()); + + PendingChannelRequest *createConferenceStreamedMediaCall( + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers = QStringList(), + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *createConferenceStreamedMediaCall( + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *createConferenceStreamedMediaCall( + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(), + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *createConferenceStreamedMediaCall( + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *createConferenceTextChat( + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(), + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *createConferenceTextChat( + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *createConferenceTextChat( + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers = QStringList(), + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *createConferenceTextChat( + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + TP_QT_DEPRECATED PendingChannelRequest *createConferenceTextChatRoom( + const QString &roomName, + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers = QStringList(), + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *createConferenceTextChatroom( + const QString &roomName, + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers = QStringList(), + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString(), + const ChannelRequestHints &hints = ChannelRequestHints()); + + TP_QT_DEPRECATED PendingChannelRequest *createConferenceTextChatRoom( + const QString &roomName, + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(), + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *createConferenceTextChatroom( + const QString &roomName, + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(), + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString(), + const ChannelRequestHints &hints = ChannelRequestHints()); + + PendingChannelRequest *createContactSearch( + const QString &server = QString(), + uint limit = 0, + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *createContactSearch( + const QString &server, + uint limit, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannel *ensureAndHandleTextChat( + const QString &contactIdentifier, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + PendingChannel *ensureAndHandleTextChat( + const ContactPtr &contact, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + + PendingChannel *ensureAndHandleTextChatroom( + const QString &roomName, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + + PendingChannel *ensureAndHandleStreamedMediaCall( + const QString &contactIdentifier, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + PendingChannel *ensureAndHandleStreamedMediaCall( + const ContactPtr &contact, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + + PendingChannel *ensureAndHandleStreamedMediaAudioCall( + const QString &contactIdentifier, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + PendingChannel *ensureAndHandleStreamedMediaAudioCall( + const ContactPtr &contact, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + + PendingChannel *ensureAndHandleStreamedMediaVideoCall( + const QString &contactIdentifier, + bool withAudio = true, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + PendingChannel *ensureAndHandleStreamedMediaVideoCall( + const ContactPtr &contact, + bool withAudio = true, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + + PendingChannel *createAndHandleFileTransfer( + const QString &contactIdentifier, + const FileTransferChannelCreationProperties &properties, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + PendingChannel *createAndHandleFileTransfer( + const ContactPtr &contact, + const FileTransferChannelCreationProperties &properties, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + + PendingChannel *createAndHandleStreamTube( + const QString &contactIdentifier, + const QString &service, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + PendingChannel *createAndHandleStreamTube( + const ContactPtr &contact, + const QString &service, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + + PendingChannel *createAndHandleConferenceTextChat( + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(), + const QDateTime &userActionTime = QDateTime::currentDateTime()); + PendingChannel *createAndHandleConferenceTextChat( + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers = QStringList(), + const QDateTime &userActionTime = QDateTime::currentDateTime()); + + PendingChannel *createAndHandleConferenceTextChatroom( + const QString &roomName, + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers = QStringList(), + const QDateTime &userActionTime = QDateTime::currentDateTime()); + PendingChannel *createAndHandleConferenceTextChatroom( + const QString &roomName, + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(), + const QDateTime &userActionTime = QDateTime::currentDateTime()); + + PendingChannel *createAndHandleConferenceStreamedMediaCall( + const QList<ChannelPtr> &channels, + const QStringList &initialInviteeContactsIdentifiers = QStringList(), + const QDateTime &userActionTime = QDateTime::currentDateTime()); + PendingChannel *createAndHandleConferenceStreamedMediaCall( + const QList<ChannelPtr> &channels, + const QList<ContactPtr> &initialInviteeContacts = QList<ContactPtr>(), + const QDateTime &userActionTime = QDateTime::currentDateTime()); + + PendingChannel *createAndHandleContactSearch( + const QString &server = QString(), + uint limit = 0, + const QDateTime &userActionTime = QDateTime::currentDateTime()); + + // advanced + PendingChannelRequest *createChannel( + const QVariantMap &requestedProperties, + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *createChannel( + const QVariantMap &requestedProperties, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannelRequest *ensureChannel( + const QVariantMap &requestedProperties, + const QDateTime &userActionTime = QDateTime::currentDateTime(), + const QString &preferredHandler = QString()); + PendingChannelRequest *ensureChannel( + const QVariantMap &requestedProperties, + const QDateTime &userActionTime, + const QString &preferredHandler, + const ChannelRequestHints &hints); + + PendingChannel *createAndHandleChannel( + const QVariantMap &requestedProperties, + const QDateTime &userActionTime); + PendingChannel *ensureAndHandleChannel( + const QVariantMap &requestedProperties, + const QDateTime &userActionTime); + +Q_SIGNALS: + void removed(); + void serviceNameChanged(const QString &serviceName); + void profileChanged(const Tp::ProfilePtr &profile); + void displayNameChanged(const QString &displayName); + void iconNameChanged(const QString &iconName); + void nicknameChanged(const QString &nickname); + void normalizedNameChanged(const QString &normalizedName); + void validityChanged(bool validity); + void stateChanged(bool state); + void capabilitiesChanged(const Tp::ConnectionCapabilities &capabilities); + void connectsAutomaticallyPropertyChanged(bool connectsAutomatically); + void firstOnline(); + void parametersChanged(const QVariantMap ¶meters); + void changingPresence(bool value); + void automaticPresenceChanged(const Tp::Presence &automaticPresence); + void currentPresenceChanged(const Tp::Presence ¤tPresence); + void requestedPresenceChanged(const Tp::Presence &requestedPresence); + void onlinenessChanged(bool online); + void avatarChanged(const Tp::Avatar &avatar); + void connectionStatusChanged(Tp::ConnectionStatus status); + void connectionChanged(const Tp::ConnectionPtr &connection); + +protected: + friend class PendingChannelRequest; // to access dispatcherInterface() + + Account(const QDBusConnection &bus, + const QString &busName, const QString &objectPath, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory, + const Feature &coreFeature); + + Client::AccountInterface *baseInterface() const; + Client::ChannelDispatcherInterface *dispatcherInterface() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onDispatcherIntrospected(Tp::PendingOperation *op); + TP_QT_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *); + TP_QT_NO_EXPORT void gotAvatar(QDBusPendingCallWatcher *); + TP_QT_NO_EXPORT void onAvatarChanged(); + TP_QT_NO_EXPORT void onConnectionManagerReady(Tp::PendingOperation *); + TP_QT_NO_EXPORT void onConnectionReady(Tp::PendingOperation *); + TP_QT_NO_EXPORT void onPropertyChanged(const QVariantMap &delta); + TP_QT_NO_EXPORT void onRemoved(); + TP_QT_NO_EXPORT void onConnectionBuilt(Tp::PendingOperation *); + +private: + struct Private; + friend struct Private; + + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/account.xml b/TelepathyQt/account.xml new file mode 100644 index 00000000..1cc5b2a1 --- /dev/null +++ b/TelepathyQt/account.xml @@ -0,0 +1,13 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Account interfaces</tp:title> + +<xi:include href="../spec/Account.xml"/> + +<xi:include href="../spec/Account_Interface_Addressing.xml"/> +<xi:include href="../spec/Account_Interface_Avatar.xml"/> +<xi:include href="../spec/Account_Interface_Storage.xml"/> + +</tp:spec> diff --git a/TelepathyQt/and-filter.dox b/TelepathyQt/and-filter.dox new file mode 100644 index 00000000..85e8c304 --- /dev/null +++ b/TelepathyQt/and-filter.dox @@ -0,0 +1,33 @@ +/* + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +/** + * \class Tp::AndFilter + * \ingroup utils + * \headerfile TelepathyQt/and-filter.h <TelepathyQt/AndFilter> + * + * \brief The AndFilter class provides a generic filter object to be used + * in conjunction of other filters. + * + * The AndFilter will match if all of its given list of filters matches + * their criteria. + */ diff --git a/TelepathyQt/and-filter.h b/TelepathyQt/and-filter.h new file mode 100644 index 00000000..4e27ea18 --- /dev/null +++ b/TelepathyQt/and-filter.h @@ -0,0 +1,83 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_and_filter_h_HEADER_GUARD_ +#define _TelepathyQt_and_filter_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Filter> +#include <TelepathyQt/Types> + +namespace Tp +{ + +template <class T> +class AndFilter : public Filter<T> +{ +public: + static SharedPtr<AndFilter<T> > create( + const QList<SharedPtr<const Filter<T> > > &filters = QList<SharedPtr<const Filter<T> > >()) + { + return SharedPtr<AndFilter<T> >(new AndFilter<T>(filters)); + } + + inline virtual ~AndFilter() { } + + inline virtual bool isValid() const + { + Q_FOREACH (const SharedPtr<const Filter<T> > &filter, mFilters) { + if (!filter || !filter->isValid()) { + return false; + } + } + return true; + } + + inline virtual bool matches(const SharedPtr<T> &t) const + { + if (!isValid()) { + return false; + } + + Q_FOREACH (const SharedPtr<const Filter<T> > &filter, mFilters) { + if (!filter->matches(t)) { + return false; + } + } + return true; + } + + inline QList<SharedPtr<const Filter<T> > > filters() const { return mFilters; } + +private: + AndFilter(const QList<SharedPtr<const Filter<T> > > &filters) + : Filter<T>(), mFilters(filters) { } + + QList<SharedPtr<const Filter<T> > > mFilters; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/async-model.dox b/TelepathyQt/async-model.dox new file mode 100644 index 00000000..e2dac6da --- /dev/null +++ b/TelepathyQt/async-model.dox @@ -0,0 +1,56 @@ +/* + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008 Nokia Corporation + * + * 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 + */ + +/** + * \page async_model Asynchronous Object Model + * + * \section async_model_overview Overview + * + * Telepathy-Qt4 uses \dbus to communicate with applications implementing the \telepathy_spec. + * + * When dealing with D-Bus, method calls can take some time to return, + * and in this case is not desirable to make synchronous calls, + * which could turn into applications hanging waiting for method returns. + * In order to avoid this issue, all Telepathy-Qt4 high-level methods requiring + * D-Bus method calls will return a \link Tp::PendingOperation \endlink which will + * emit the signal \link Tp::PendingOperation::finished() \endlink when the operation + * has ended. See individual methods' documentation for more details. + * + * Additionally Telepathy-Qt4 introduces a concept in which object features need + * to be enabled before usage. Information corresponding to enabled features can be inspected + * synchronously with no need for asynchronous D-Bus method calls and the associated + * programming complexity. + * + * To avoid the complexity of doing asynchronous calls when making object features ready + * Telepathy-Qt4 also provides so called factories for the main objects. + * These object features can be enabled by constructing a corresponding factory and enabling the + * desired features, and passing these factories to the objects responsible for creating + * the objects whose features are required. + * Doing that, applications are guaranteed that the specified features are ready in objects + * signaled to them by the library. + * + * 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(), or in the + * \link Tp::Contact \endlink case by calling \link Tp::ContactManager::upgradeContacts() \endlink, + * with the additional features on the object, and waiting for the resulting PendingOperation to + * finish. + */ diff --git a/TelepathyQt/avatar.cpp b/TelepathyQt/avatar.cpp new file mode 100644 index 00000000..f7fd17ec --- /dev/null +++ b/TelepathyQt/avatar.cpp @@ -0,0 +1,172 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 <TelepathyQt/AvatarSpec> + +namespace Tp +{ + +/** + * \class AvatarData + * \ingroup wrappers + * \headerfile TelepathyQt/avatar.h <TelepathyQt/AvatarData> + * + * \brief The AvatarData class represents a Telepathy avatar. + */ + +struct TP_QT_NO_EXPORT AvatarSpec::Private : public QSharedData +{ + Private(const QStringList &supportedMimeTypes, + uint minHeight, uint maxHeight, uint recommendedHeight, + uint minWidth, uint maxWidth, uint recommendedWidth, + uint maxBytes) + : supportedMimeTypes(supportedMimeTypes), + minHeight(minHeight), + maxHeight(maxHeight), + recommendedHeight(recommendedHeight), + minWidth(minWidth), + maxWidth(maxWidth), + recommendedWidth(recommendedWidth), + maxBytes(maxBytes) + { + } + + QStringList supportedMimeTypes; + uint minHeight; + uint maxHeight; + uint recommendedHeight; + uint minWidth; + uint maxWidth; + uint recommendedWidth; + uint maxBytes; +}; + +/** + * \class AvatarSpec + * \ingroup wrappers + * \headerfile TelepathyQt/avatar.h <TelepathyQt/AvatarSpec> + * + * \brief The AvatarSpec class represents a Telepathy avatar information + * supported by a protocol. + */ + +AvatarSpec::AvatarSpec() +{ +} + +AvatarSpec::AvatarSpec(const QStringList &supportedMimeTypes, + uint minHeight, uint maxHeight, uint recommendedHeight, + uint minWidth, uint maxWidth, uint recommendedWidth, + uint maxBytes) + : mPriv(new Private(supportedMimeTypes, minHeight, maxHeight, recommendedHeight, + minWidth, maxWidth, recommendedWidth, maxBytes)) +{ +} + +AvatarSpec::AvatarSpec(const AvatarSpec &other) + : mPriv(other.mPriv) +{ +} + +AvatarSpec::~AvatarSpec() +{ +} + +AvatarSpec &AvatarSpec::operator=(const AvatarSpec &other) +{ + this->mPriv = other.mPriv; + return *this; +} + +QStringList AvatarSpec::supportedMimeTypes() const +{ + if (!isValid()) { + return QStringList(); + } + + return mPriv->supportedMimeTypes; +} + +uint AvatarSpec::minimumHeight() const +{ + if (!isValid()) { + return 0; + } + + return mPriv->minHeight; +} + +uint AvatarSpec::maximumHeight() const +{ + if (!isValid()) { + return 0; + } + + return mPriv->maxHeight; +} + +uint AvatarSpec::recommendedHeight() const +{ + if (!isValid()) { + return 0; + } + + return mPriv->recommendedHeight; +} + +uint AvatarSpec::minimumWidth() const +{ + if (!isValid()) { + return 0; + } + + return mPriv->minWidth; +} + +uint AvatarSpec::maximumWidth() const +{ + if (!isValid()) { + return 0; + } + + return mPriv->maxWidth; +} + +uint AvatarSpec::recommendedWidth() const +{ + if (!isValid()) { + return 0; + } + + return mPriv->recommendedWidth; +} + +uint AvatarSpec::maximumBytes() const +{ + if (!isValid()) { + return 0; + } + + return mPriv->maxBytes; +} + +} // Tp diff --git a/TelepathyQt/avatar.h b/TelepathyQt/avatar.h new file mode 100644 index 00000000..c48d3fd1 --- /dev/null +++ b/TelepathyQt/avatar.h @@ -0,0 +1,86 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010-2011 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 + */ + +#ifndef _TelepathyQt_avatar_h_HEADER_GUARD_ +#define _TelepathyQt_avatar_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> + +#include <QSharedDataPointer> +#include <QString> +#include <QStringList> +#include <QMetaType> + +namespace Tp +{ + +struct TP_QT_EXPORT AvatarData +{ +public: + inline AvatarData(const QString &fileName, const QString &mimeType) + : fileName(fileName), mimeType(mimeType) {} + inline AvatarData() {} + + QString fileName; + QString mimeType; +}; + +class TP_QT_EXPORT AvatarSpec +{ +public: + AvatarSpec(); + AvatarSpec(const QStringList &supportedMimeTypes, + uint minHeight, uint maxHeight, uint recommendedHeight, + uint minWidth, uint maxWidth, uint recommendedWidth, + uint maxBytes); + AvatarSpec(const AvatarSpec &other); + ~AvatarSpec(); + + bool isValid() const { return mPriv.constData() != 0; } + + AvatarSpec &operator=(const AvatarSpec &other); + + QStringList supportedMimeTypes() const; + uint minimumHeight() const; + uint maximumHeight() const; + uint recommendedHeight() const; + uint minimumWidth() const; + uint maximumWidth() const; + uint recommendedWidth() const; + uint maximumBytes() const; + +private: + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::AvatarData); +Q_DECLARE_METATYPE(Tp::AvatarSpec); + +#endif diff --git a/TelepathyQt/capabilities-base.cpp b/TelepathyQt/capabilities-base.cpp new file mode 100644 index 00000000..c55f8850 --- /dev/null +++ b/TelepathyQt/capabilities-base.cpp @@ -0,0 +1,348 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/CapabilitiesBase> + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Types> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT CapabilitiesBase::Private : public QSharedData +{ + Private(bool specificToContact); + Private(const RequestableChannelClassSpecList &rccSpecs, bool specificToContact); + + RequestableChannelClassSpecList rccSpecs; + bool specificToContact; +}; + +CapabilitiesBase::Private::Private(bool specificToContact) + : specificToContact(specificToContact) +{ +} + +CapabilitiesBase::Private::Private(const RequestableChannelClassSpecList &rccSpecs, + bool specificToContact) + : rccSpecs(rccSpecs), + specificToContact(specificToContact) +{ +} + +/** + * \class CapabilitiesBase + * \ingroup clientconn + * \headerfile TelepathyQt/capabilities-base.h <TelepathyQt/CapabilitiesBase> + * + * \brief The CapabilitiesBase class represents the capabilities a Connection + * or a Contact supports. + */ + +/** + * Construct a new CapabilitiesBase object. + */ +CapabilitiesBase::CapabilitiesBase() + : mPriv(new Private(false)) +{ +} + +/** + * Construct a new CapabilitiesBase object. + * + * \param specificToContact Whether this object describes the capabilities of a + * particular contact. + */ +CapabilitiesBase::CapabilitiesBase(bool specificToContact) + : mPriv(new Private(specificToContact)) +{ +} + +/** + * Construct a new CapabilitiesBase object using the given \a rccs. + * + * \param rccs RequestableChannelClassList representing the capabilities of a + * connection or contact. + * \param specificToContact Whether this object describes the capabilities of a + * particular contact. + */ +CapabilitiesBase::CapabilitiesBase(const RequestableChannelClassList &rccs, + bool specificToContact) + : mPriv(new Private(RequestableChannelClassSpecList(rccs), specificToContact)) +{ +} + +/** + * Construct a new CapabilitiesBase object using the given \a rccSpecs. + * + * \param rccSpecs RequestableChannelClassSpecList representing the capabilities of a + * connection or contact. + * \param specificToContact Whether this object describes the capabilities of a + * particular contact. + */ +CapabilitiesBase::CapabilitiesBase(const RequestableChannelClassSpecList &rccSpecs, + bool specificToContact) + : mPriv(new Private(rccSpecs, specificToContact)) +{ +} + +CapabilitiesBase::CapabilitiesBase(const CapabilitiesBase &other) + : mPriv(other.mPriv) +{ +} + +/** + * Class destructor. + */ +CapabilitiesBase::~CapabilitiesBase() +{ +} + +CapabilitiesBase &CapabilitiesBase::operator=(const CapabilitiesBase &other) +{ + this->mPriv = other.mPriv; + return *this; +} + +/** + * Return the list of requestable channel class spec representing the requests that can succeed. + * + * This can be used by advanced clients to determine whether an unusually + * complex request would succeed. See the \telepathy_spec + * for details of how to interpret the returned list. + * + * The higher-level methods like textChats() are likely to be more + * useful to the majority of clients. + * + * \return A RequestableChannelClassSpecList indicating the parameters to + * Account::createChannel, Account::ensureChannel, + * Connection::createChannel and Connection::ensureChannel + * that can be expected to work. + */ +RequestableChannelClassSpecList CapabilitiesBase::allClassSpecs() const +{ + return mPriv->rccSpecs; +} + +void CapabilitiesBase::updateRequestableChannelClasses( + const RequestableChannelClassList &rccs) +{ + mPriv->rccSpecs = RequestableChannelClassSpecList(rccs); +} + +/** + * Return whether this object accurately describes the capabilities of a + * particular contact, or if it's only a guess based on the + * capabilities of the underlying connection. + * + * In protocols like XMPP where each contact advertises their capabilities + * to others, Contact::capabilities() will generally return an object where + * this method returns true. + * + * In protocols like SIP where contacts' capabilities are not known, + * Contact::capabilities() will return an object where this method returns + * false, whose methods textChats() etc. are based on what the + * underlying connection supports. + * + * This reflects the fact that the best assumption an application can make is + * that every contact supports every channel type supported by the connection, + * while indicating that requests to communicate might fail if the contact + * does not actually have the necessary functionality. + * + * \return \c true if this object describes the capabilities of a particular + * contact, \c false otherwise. + */ +bool CapabilitiesBase::isSpecificToContact() const +{ + return mPriv->specificToContact; +} + +/** + * Return whether private text channels can be established by providing + * a contact identifier. + * + * If the protocol is such that text chats can be established, but only via + * a more elaborate D-Bus API than normal (because more information is needed), + * then this method will return false. + * + * \return \c true if Account::ensureTextChat() can be expected to work, + * \c false otherwise. + */ +bool CapabilitiesBase::textChats() const +{ + foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::textChat())) { + return true; + } + } + return false; +} + +/** + * Return whether private audio and/or video calls can be established by + * providing a contact identifier. + * + * If the protocol is such that these calls can be established, but only via + * a more elaborate D-Bus API than normal (because more information is needed), + * then this method will return false. + * + * \return \c true if Account::ensureStreamedMediaCall() can be expected to work, + * \c false otherwise. + * \sa streamedMediaAudioCalls(), streamedMediaVideoCalls(), + * streamedMediaVideoCallsWithAudio() + */ +bool CapabilitiesBase::streamedMediaCalls() const +{ + foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::streamedMediaCall())) { + return true; + } + } + return false; +} + +/** + * Return whether private audio calls can be established by providing a + * contact identifier. + * + * Call upgradingCalls() to determine whether such calls are + * likely to be upgradable to have a video stream later. + * + * If the protocol is such that these calls can be established, but only via + * a more elaborate D-Bus API than normal (because more information is needed), + * then this method will return false. + * + * In some older connection managers, streamedMediaAudioCalls() and + * streamedMediaVideoCalls() might both return false, even though streamedMediaCalls() returns + * true. This indicates that only an older API is supported - clients of these connection managers + * must call Account::ensureStreamedMediaCall() to get an empty call, then add audio and/or + * video streams to it. + * + * \return \c true if Account::ensureStreamedMediaAudioCall() can be expected to work, + * \c false otherwise. + * \sa streamedMediaCalls(), streamedMediaVideoCalls(), streamedMediaVideoCallsWithAudio() + */ +bool CapabilitiesBase::streamedMediaAudioCalls() const +{ + foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::streamedMediaAudioCall())) { + return true; + } + } + return false; +} + +/** + * Return whether private video calls can be established by providing a + * contact identifier. + * + * The same comments as for streamedMediaAudioCalls() apply to this method. + * + * \return \c true if Account::ensureStreamedMediaVideoCall() can be expected to work, + * if given \c false as \a withAudio parameter, \c false otherwise. + * \sa streamedMediaCalls(), streamedMediaAudioCalls(), streamedMediaVideoCallsWithAudio() + */ +bool CapabilitiesBase::streamedMediaVideoCalls() const +{ + foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::streamedMediaVideoCall())) { + return true; + } + } + return false; +} + +/** + * Return whether private video calls with audio can be established by providing a + * contact identifier. + * + * The same comments as for streamedMediaAudioCalls() apply to this method. + * + * \return \c true if Account::ensureStreamedMediaVideoCall() can be expected to work, + * if given \c true as \a withAudio parameter, \c false otherwise. + * \sa streamedMediaCalls(), streamedMediaAudioCalls(), streamedMediaVideoCalls() + */ +bool CapabilitiesBase::streamedMediaVideoCallsWithAudio() const +{ + foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::streamedMediaVideoCallWithAudio())) { + return true; + } + } + return false; +} + +/** + * Return whether the protocol supports adding streams of a different type + * to ongoing media calls. + * + * In some protocols and clients (such as XMPP Jingle), all calls potentially + * support both audio and video. This is indicated by returning true. + * + * In other protocols and clients (such as MSN, and the variant of XMPP Jingle + * used by Google clients), the streams are fixed at the time the call is + * started, so if you will ever want video, you have to ask for it at the + * beginning, for instance with ensureStreamedMediaVideoCall(). This is indicated by + * returning false. + * + * User interfaces can use this method as a UI hint. If it returns false, + * then a UI wishing to support both audio and video calls will have to + * provide separate "audio call" and "video call" buttons or menu items; + * if it returns true, a single button that makes an audio call is sufficient, + * because video can be added later. + * + * (The underlying Telepathy feature is the ImmutableStreams property; if this + * method returns true, then ImmutableStreams is false, and vice versa). + * + * \return \c true if audio calls can be upgraded to audio + video, + * \c false otherwise. + */ +bool CapabilitiesBase::upgradingStreamedMediaCalls() const +{ + foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) { + if (rccSpec.channelType() == TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA && + !rccSpec.allowsProperty(TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".ImmutableStreams"))) { + // TODO should we test all classes that have channelType + // StreamedMedia or just one is fine? + return true; + } + } + return false; +} + +/** + * Return whether file transfer can be established by providing a contact identifier + * + * \return \c true if file transfers can be expected to work, + * \c false otherwise. + */ +bool CapabilitiesBase::fileTransfers() const +{ + foreach (const RequestableChannelClassSpec &rccSpec, mPriv->rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::fileTransfer())) { + return true; + } + } + return false; +} + +} // Tp diff --git a/TelepathyQt/capabilities-base.h b/TelepathyQt/capabilities-base.h new file mode 100644 index 00000000..a2af63fb --- /dev/null +++ b/TelepathyQt/capabilities-base.h @@ -0,0 +1,85 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_capabilities_base_h_HEADER_GUARD_ +#define _TelepathyQt_capabilities_base_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/RequestableChannelClassSpec> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class TP_QT_EXPORT CapabilitiesBase +{ +public: + CapabilitiesBase(); + CapabilitiesBase(const CapabilitiesBase &other); + virtual ~CapabilitiesBase(); + + CapabilitiesBase &operator=(const CapabilitiesBase &other); + + RequestableChannelClassSpecList allClassSpecs() const; + + bool isSpecificToContact() const; + + bool textChats() const; + + bool streamedMediaCalls() const; + bool streamedMediaAudioCalls() const; + bool streamedMediaVideoCalls() const; + bool streamedMediaVideoCallsWithAudio() const; + bool upgradingStreamedMediaCalls() const; + + bool fileTransfers() const; + + // later: FIXME TODO why not now? + // QList<FileHashType> fileTransfersRequireHash() const; + +protected: + CapabilitiesBase(bool specificToContact); + CapabilitiesBase(const RequestableChannelClassList &rccs, + bool specificToContact); + CapabilitiesBase(const RequestableChannelClassSpecList &rccSpecs, + bool specificToContact); + + virtual void updateRequestableChannelClasses( + const RequestableChannelClassList &rccs); + +private: + friend class Connection; + friend class Contact; + + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::CapabilitiesBase); + +#endif diff --git a/TelepathyQt/channel-class-features.h b/TelepathyQt/channel-class-features.h new file mode 100644 index 00000000..bd03117e --- /dev/null +++ b/TelepathyQt/channel-class-features.h @@ -0,0 +1,45 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +#ifndef _TelepathyQt_channel_class_features_h_HEADER_GUARD_ +#define _TelepathyQt_channel_class_features_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/ChannelClassSpec> +#include <TelepathyQt/Features> + +#include <QMetaType> +#include <QPair> + +namespace Tp +{ + +typedef QPair<ChannelClassSpec, Features> ChannelClassFeatures; + +} // Tp + +Q_DECLARE_METATYPE(Tp::ChannelClassFeatures); + +#endif diff --git a/TelepathyQt/channel-class-spec.cpp b/TelepathyQt/channel-class-spec.cpp new file mode 100644 index 00000000..86083727 --- /dev/null +++ b/TelepathyQt/channel-class-spec.cpp @@ -0,0 +1,555 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ChannelClassSpec> + +#include "TelepathyQt/_gen/future-constants.h" + +#include "TelepathyQt/debug-internal.h" + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ChannelClassSpec::Private : public QSharedData +{ + QVariantMap props; +}; + +/** + * \class ChannelClassSpec + * \ingroup wrappers + * \headerfile TelepathyQt/channel-class-spec.h <TelepathyQt/ChannelClassSpec> + * + * \brief The ChannelClassSpec class represents a Telepathy channel class. + */ + +ChannelClassSpec::ChannelClassSpec() +{ +} + +ChannelClassSpec::ChannelClassSpec(const ChannelClass &cc) + : mPriv(new Private) +{ + foreach (QString key, cc.keys()) { + setProperty(key, cc.value(key).variant()); + } +} + +ChannelClassSpec::ChannelClassSpec(const QVariantMap &props) + : mPriv(new Private) +{ + setChannelType(qdbus_cast<QString>( + props.value(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType")))); + setTargetHandleType((HandleType) qdbus_cast<uint>( + props.value(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType")))); + + foreach (QString propName, props.keys()) { + setProperty(propName, props.value(propName)); + } +} + +ChannelClassSpec::ChannelClassSpec(const QString &channelType, HandleType targetHandleType, + const QVariantMap &otherProperties) + : mPriv(new Private) +{ + setChannelType(channelType); + setTargetHandleType(targetHandleType); + foreach (QString key, otherProperties.keys()) { + setProperty(key, otherProperties.value(key)); + } +} + +ChannelClassSpec::ChannelClassSpec(const QString &channelType, HandleType targetHandleType, + bool requested, const QVariantMap &otherProperties) + : mPriv(new Private) +{ + setChannelType(channelType); + setTargetHandleType(targetHandleType); + setRequested(requested); + foreach (QString key, otherProperties.keys()) { + setProperty(key, otherProperties.value(key)); + } +} + +ChannelClassSpec::ChannelClassSpec(const ChannelClassSpec &other, + const QVariantMap &additionalProperties) + : mPriv(other.mPriv) +{ + if (!additionalProperties.isEmpty()) { + foreach (QString key, additionalProperties.keys()) { + setProperty(key, additionalProperties.value(key)); + } + } +} + +ChannelClassSpec::~ChannelClassSpec() +{ +} + +bool ChannelClassSpec::isValid() const +{ + return mPriv.constData() != 0 && + !(qdbus_cast<QString>( + mPriv->props.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"))) + .isEmpty()) && + mPriv->props.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")); +} + +ChannelClassSpec &ChannelClassSpec::operator=(const ChannelClassSpec &other) +{ + if (this == &other) { + return *this; + } + + this->mPriv = other.mPriv; + return *this; +} + +bool ChannelClassSpec::isSubsetOf(const ChannelClassSpec &other) const +{ + if (!mPriv) { + // Invalid instances have no properties - hence they're subset of anything + return true; + } + + foreach (QString propName, mPriv->props.keys()) { + if (!other.hasProperty(propName)) { + return false; + } else if (property(propName) != other.property(propName)) { + return false; + } + } + + // other had all of the properties we have and they all had the same values + + return true; +} + +bool ChannelClassSpec::matches(const QVariantMap &immutableProperties) const +{ + // We construct a ChannelClassSpec for comparison so the StreamedMedia props are normalized + // consistently etc + return this->isSubsetOf(ChannelClassSpec(immutableProperties)); +} + +bool ChannelClassSpec::hasProperty(const QString &qualifiedName) const +{ + return mPriv.constData() != 0 ? mPriv->props.contains(qualifiedName) : false; +} + +QVariant ChannelClassSpec::property(const QString &qualifiedName) const +{ + return mPriv.constData() != 0 ? mPriv->props.value(qualifiedName) : QVariant(); +} + +void ChannelClassSpec::setProperty(const QString &qualifiedName, const QVariant &value) +{ + if (mPriv.constData() == 0) { + mPriv = new Private; + } + + mPriv->props.insert(qualifiedName, value); +} + +void ChannelClassSpec::unsetProperty(const QString &qualifiedName) +{ + if (mPriv.constData() == 0) { + // No properties set for sure, so don't have to unset any + return; + } + + mPriv->props.remove(qualifiedName); +} + +QVariantMap ChannelClassSpec::allProperties() const +{ + return mPriv.constData() != 0 ? mPriv->props : QVariantMap(); +} + +ChannelClass ChannelClassSpec::bareClass() const +{ + ChannelClass cc; + + if (!isValid()) { + warning() << "Tried to convert an invalid ChannelClassSpec to a ChannelClass"; + return ChannelClass(); + } + + QVariantMap props = mPriv->props; + foreach (QString propName, props.keys()) { + QVariant value = props.value(propName); + + cc.insert(propName, QDBusVariant(value)); + } + + return cc; +} + +ChannelClassSpec ChannelClassSpec::textChat(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT), + HandleTypeContact); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::textChatroom(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT), + HandleTypeRoom); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::unnamedTextChat(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT), + HandleTypeNone); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::streamedMediaCall(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA), + HandleTypeContact); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::streamedMediaAudioCall(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA), + HandleTypeContact); + spec.setStreamedMediaInitialAudioFlag(); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::streamedMediaVideoCall(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA), + HandleTypeContact); + spec.setStreamedMediaInitialVideoFlag(); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::streamedMediaVideoCallWithAudio(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA), + HandleTypeContact); + spec.setStreamedMediaInitialAudioFlag(); + spec.setStreamedMediaInitialVideoFlag(); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::unnamedStreamedMediaCall(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA), + HandleTypeNone); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::unnamedStreamedMediaAudioCall(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA), + HandleTypeNone); + spec.setStreamedMediaInitialAudioFlag(); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::unnamedStreamedMediaVideoCall(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA), + HandleTypeNone); + spec.setStreamedMediaInitialVideoFlag(); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::unnamedStreamedMediaVideoCallWithAudio(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA), + HandleTypeNone); + spec.setStreamedMediaInitialAudioFlag(); + spec.setStreamedMediaInitialVideoFlag(); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::roomList(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_ROOM_LIST), + HandleTypeNone); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::outgoingFileTransfer(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER), + HandleTypeContact, true); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::incomingFileTransfer(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER), + HandleTypeContact, false); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +ChannelClassSpec ChannelClassSpec::outgoingStreamTube(const QString &service, + const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE), + HandleTypeContact, true); + } + + QVariantMap props = additionalProperties; + if (!service.isEmpty()) { + props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"), + service); + } + + if (props.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, props); + } +} + +ChannelClassSpec ChannelClassSpec::incomingStreamTube(const QString &service, + const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE), + HandleTypeContact, false); + } + + QVariantMap props = additionalProperties; + if (!service.isEmpty()) { + props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"), + service); + } + + if (props.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, props); + } +} + +ChannelClassSpec ChannelClassSpec::outgoingRoomStreamTube(const QString &service, + const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE), + HandleTypeRoom, true); + } + + QVariantMap props = additionalProperties; + if (!service.isEmpty()) { + props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"), + service); + } + + if (props.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, props); + } +} + +ChannelClassSpec ChannelClassSpec::incomingRoomStreamTube(const QString &service, + const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE), + HandleTypeRoom, false); + } + + QVariantMap props = additionalProperties; + if (!service.isEmpty()) { + props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAM_TUBE ".Service"), + service); + } + + if (props.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, props); + } +} + +ChannelClassSpec ChannelClassSpec::contactSearch(const QVariantMap &additionalProperties) +{ + static ChannelClassSpec spec; + + if (!spec.mPriv.constData()) { + spec = ChannelClassSpec(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_SEARCH), + HandleTypeNone); + } + + if (additionalProperties.isEmpty()) { + return spec; + } else { + return ChannelClassSpec(spec, additionalProperties); + } +} + +/** + * \class ChannelClassSpecList + * \ingroup wrappers + * \headerfile TelepathyQt/channel-class-spec.h <TelepathyQt/ChannelClassSpecList> + * + * \brief The ChannelClassSpecList class represents a list of ChannelClassSpec. + */ + +} // Tp diff --git a/TelepathyQt/channel-class-spec.h b/TelepathyQt/channel-class-spec.h new file mode 100644 index 00000000..5b8bf357 --- /dev/null +++ b/TelepathyQt/channel-class-spec.h @@ -0,0 +1,277 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_channel_class_spec_h_HEADER_GUARD_ +#define _TelepathyQt_channel_class_spec_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Global> +#include <TelepathyQt/Types> + +#include <QSharedDataPointer> +#include <QVariant> +#include <QVariantMap> +#include <QPair> + +namespace Tp +{ + +class TP_QT_EXPORT ChannelClassSpec +{ +public: + ChannelClassSpec(); + ChannelClassSpec(const ChannelClass &cc); + ChannelClassSpec(const QVariantMap &props); + ChannelClassSpec(const QString &channelType, HandleType targetHandleType, + const QVariantMap &otherProperties = QVariantMap()); + ChannelClassSpec(const QString &channelType, HandleType targetHandleType, bool requested, + const QVariantMap &otherProperties = QVariantMap()); + ChannelClassSpec(const ChannelClassSpec &other, + const QVariantMap &additionalProperties = QVariantMap()); + ~ChannelClassSpec(); + + bool isValid() const; + + ChannelClassSpec &operator=(const ChannelClassSpec &other); + + bool operator==(const ChannelClassSpec &other) const + { + return this->allProperties() == other.allProperties(); + } + + bool isSubsetOf(const ChannelClassSpec &other) const; + bool matches(const QVariantMap &immutableProperties) const; + + // TODO: Use new TP_QT_... constants + QString channelType() const + { + return qdbus_cast<QString>( + property(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"))); + } + + void setChannelType(const QString &type) + { + setProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), + QVariant::fromValue(type)); + } + + HandleType targetHandleType() const + { + return (HandleType) qdbus_cast<uint>( + property( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"))); + } + + void setTargetHandleType(HandleType type) + { + setProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), + QVariant::fromValue((uint) type)); + } + + bool hasRequested() const + { + return hasProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested")); + } + + bool isRequested() const + { + return qdbus_cast<bool>( + property(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested"))); + } + + void setRequested(bool requested) + { + setProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested"), + QVariant::fromValue(requested)); + } + + void unsetRequested() + { + unsetProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested")); + } + + bool hasStreamedMediaInitialAudioFlag() const + { + return qdbus_cast<bool>( + property(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA + ".InitialAudio"))); + } + + void setStreamedMediaInitialAudioFlag() + { + setProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio"), + QVariant::fromValue(true)); + } + + void unsetStreamedMediaInitialAudioFlag() + { + unsetProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA + ".InitialAudio")); + } + + bool hasStreamedMediaInitialVideoFlag() const + { + return qdbus_cast<bool>( + property(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA + ".InitialVideo"))); + } + + void setStreamedMediaInitialVideoFlag() + { + setProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo"), + QVariant::fromValue(true)); + } + + void unsetStreamedMediaInitialVideoFlag() + { + unsetProperty(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA + ".InitialVideo")); + } + + bool hasProperty(const QString &qualifiedName) const; + QVariant property(const QString &qualifiedName) const; + + void setProperty(const QString &qualifiedName, const QVariant &value); + void unsetProperty(const QString &qualifiedName); + + QVariantMap allProperties() const; + ChannelClass bareClass() const; + + static ChannelClassSpec textChat(const QVariantMap &additionalProperties = QVariantMap()); + static ChannelClassSpec textChatroom(const QVariantMap &additionalProperties = QVariantMap()); + static ChannelClassSpec unnamedTextChat(const QVariantMap &additionalProperties = QVariantMap()); + + static ChannelClassSpec streamedMediaCall(const QVariantMap &additionalProperties = QVariantMap()); + static ChannelClassSpec streamedMediaAudioCall(const QVariantMap &additionalProperties = + QVariantMap()); + static ChannelClassSpec streamedMediaVideoCall(const QVariantMap &additionalProperties = + QVariantMap()); + static ChannelClassSpec streamedMediaVideoCallWithAudio(const QVariantMap &additionalProperties = + QVariantMap()); + + static ChannelClassSpec unnamedStreamedMediaCall(const QVariantMap &additionalProperties = + QVariantMap()); + static ChannelClassSpec unnamedStreamedMediaAudioCall(const QVariantMap &additionalProperties = + QVariantMap()); + static ChannelClassSpec unnamedStreamedMediaVideoCall(const QVariantMap &additionalProperties = + QVariantMap()); + static ChannelClassSpec unnamedStreamedMediaVideoCallWithAudio(const QVariantMap &additionalProperties = + QVariantMap()); + + // TODO: add Call when it's undrafted + static ChannelClassSpec roomList(const QVariantMap &additionalProperties = QVariantMap()); + static ChannelClassSpec outgoingFileTransfer(const QVariantMap &additionalProperties = QVariantMap()); + static ChannelClassSpec incomingFileTransfer(const QVariantMap &additionalProperties = QVariantMap()); + static ChannelClassSpec outgoingStreamTube(const QString &service = QString(), + const QVariantMap &additionalProperties = QVariantMap()); + static ChannelClassSpec incomingStreamTube(const QString &service = QString(), + const QVariantMap &additionalProperties = QVariantMap()); + static ChannelClassSpec outgoingRoomStreamTube(const QString &service = QString(), + const QVariantMap &additionalProperties = QVariantMap()); + static ChannelClassSpec incomingRoomStreamTube(const QString &service = QString(), + const QVariantMap &additionalProperties = QVariantMap()); + // TODO: add dbus tubes when they're implemented + static ChannelClassSpec contactSearch(const QVariantMap &additionalProperties = QVariantMap()); + +private: + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +class TP_QT_EXPORT ChannelClassSpecList : + public QList<ChannelClassSpec> +{ +public: + ChannelClassSpecList() { } + + ChannelClassSpecList(const ChannelClassSpec &spec) + { + append(spec); + } + + ChannelClassSpecList(const QList<ChannelClassSpec> &other) + : QList<ChannelClassSpec>(other) + { + } + + ChannelClassSpecList(const ChannelClassList &classes) + { + // Why doesn't Qt have range constructors like STL... stupid, so stupid. + Q_FOREACH (const ChannelClass &cc, classes) { + append(cc); + } + } + + ChannelClassList bareClasses() const + { + ChannelClassList list; + Q_FOREACH (const ChannelClassSpec &spec, *this) { + list.append(spec.bareClass()); + } + return list; + } +}; + +inline uint qHash(const ChannelClassSpec &spec) +{ + uint ret = 0; + QVariantMap::const_iterator it = spec.allProperties().constBegin(); + QVariantMap::const_iterator end = spec.allProperties().constEnd(); + int i = spec.allProperties().size() + 1; + for (; it != end; ++it) { + // all D-Bus types should be convertible to QString + QPair<QString, QString> p(it.key(), it.value().toString()); + int h = qHash(p); + ret ^= ((h << (2 << i)) | (h >> (2 >> i))); + i--; + } + return ret; +} + +inline uint qHash(const QSet<ChannelClassSpec> &specSet) +{ + int ret = 0; + Q_FOREACH (const ChannelClassSpec &spec, specSet) { + int h = qHash(spec); + ret ^= h; + } + return ret; +} + +inline uint qHash(const ChannelClassSpecList &specList) +{ + // Make it unique by converting to QSet + QSet<ChannelClassSpec> uniqueSet = specList.toSet(); + return qHash(uniqueSet); +} + +} // Tp + +Q_DECLARE_METATYPE(Tp::ChannelClassSpec); +Q_DECLARE_METATYPE(Tp::ChannelClassSpecList); + +#endif diff --git a/TelepathyQt/channel-dispatch-operation-internal.h b/TelepathyQt/channel-dispatch-operation-internal.h new file mode 100644 index 00000000..7eef7253 --- /dev/null +++ b/TelepathyQt/channel-dispatch-operation-internal.h @@ -0,0 +1,51 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +#ifndef _TelepathyQt_channel_dispatch_operation_internal_h_HEADER_GUARD_ +#define _TelepathyQt_channel_dispatch_operation_internal_h_HEADER_GUARD_ + +#include <TelepathyQt/AbstractClientHandler> +#include <TelepathyQt/PendingOperation> + +namespace Tp +{ + +class TP_QT_NO_EXPORT ChannelDispatchOperation::PendingClaim : public PendingOperation +{ + Q_OBJECT + +public: + PendingClaim(const ChannelDispatchOperationPtr &op, + const AbstractClientHandlerPtr &handler = AbstractClientHandlerPtr()); + ~PendingClaim(); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onClaimFinished(Tp::PendingOperation *op); + +private: + ChannelDispatchOperationPtr mDispatchOp; + AbstractClientHandlerPtr mHandler; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/channel-dispatch-operation.cpp b/TelepathyQt/channel-dispatch-operation.cpp new file mode 100644 index 00000000..a24dbcb3 --- /dev/null +++ b/TelepathyQt/channel-dispatch-operation.cpp @@ -0,0 +1,623 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/ChannelDispatchOperation> +#include "TelepathyQt/channel-dispatch-operation-internal.h" + +#include "TelepathyQt/_gen/cli-channel-dispatch-operation-body.hpp" +#include "TelepathyQt/_gen/cli-channel-dispatch-operation.moc.hpp" +#include "TelepathyQt/_gen/channel-dispatch-operation.moc.hpp" +#include "TelepathyQt/_gen/channel-dispatch-operation-internal.moc.hpp" + +#include "TelepathyQt/debug-internal.h" +#include "TelepathyQt/fake-handler-manager-internal.h" + +#include <TelepathyQt/Account> +#include <TelepathyQt/AccountFactory> +#include <TelepathyQt/Channel> +#include <TelepathyQt/ChannelFactory> +#include <TelepathyQt/Connection> +#include <TelepathyQt/ConnectionFactory> +#include <TelepathyQt/ContactFactory> +#include <TelepathyQt/PendingComposite> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/PendingVoid> + +#include <QPointer> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ChannelDispatchOperation::Private +{ + Private(ChannelDispatchOperation *parent); + ~Private(); + + static void introspectMain(Private *self); + + void extractMainProps(const QVariantMap &props, + bool immutableProperties); + + // Public object + ChannelDispatchOperation *parent; + + // Context + AccountFactoryConstPtr accFactory; + ConnectionFactoryConstPtr connFactory; + ChannelFactoryConstPtr chanFactory; + ContactFactoryConstPtr contactFactory; + + // Instance of generated interface class + Client::ChannelDispatchOperationInterface *baseInterface; + + // Mandatory properties interface proxy + Client::DBus::PropertiesInterface *properties; + + ReadinessHelper *readinessHelper; + + // Introspection + QVariantMap immutableProperties; + ConnectionPtr connection; + AccountPtr account; + QList<ChannelPtr> channels; + QStringList possibleHandlers; + bool gotPossibleHandlers; +}; + +ChannelDispatchOperation::Private::Private(ChannelDispatchOperation *parent) + : parent(parent), + baseInterface(new Client::ChannelDispatchOperationInterface(parent)), + properties(parent->interface<Client::DBus::PropertiesInterface>()), + readinessHelper(parent->readinessHelper()), + gotPossibleHandlers(false) +{ + debug() << "Creating new ChannelDispatchOperation:" << parent->objectPath(); + + parent->connect(baseInterface, + SIGNAL(Finished()), + SLOT(onFinished())); + + parent->connect(baseInterface, + SIGNAL(ChannelLost(QDBusObjectPath,QString,QString)), + SLOT(onChannelLost(QDBusObjectPath,QString,QString))); + + ReadinessHelper::Introspectables introspectables; + + // As ChannelDispatchOperation does not have predefined statuses let's simulate one (0) + ReadinessHelper::Introspectable introspectableCore( + QSet<uint>() << 0, // makesSenseForStatuses + Features(), // dependsOnFeatures + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectMain, + this); + introspectables[FeatureCore] = introspectableCore; + + readinessHelper->addIntrospectables(introspectables); +} + +ChannelDispatchOperation::Private::~Private() +{ +} + +void ChannelDispatchOperation::Private::introspectMain(ChannelDispatchOperation::Private *self) +{ + QVariantMap mainProps; + foreach (QString key, self->immutableProperties.keys()) { + if (key.startsWith(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION "."))) { + QVariant value = self->immutableProperties.value(key); + mainProps.insert( + key.remove(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".")), + value); + } + } + + if (!self->channels.isEmpty() && mainProps.contains(QLatin1String("Account")) + && mainProps.contains(QLatin1String("Connection")) + && mainProps.contains(QLatin1String("Interfaces")) + && mainProps.contains(QLatin1String("PossibleHandlers"))) { + debug() << "Supplied properties were sufficient, not introspecting" + << self->parent->objectPath(); + self->extractMainProps(mainProps, true); + return; + } + + debug() << "Calling Properties::GetAll(ChannelDispatchOperation)"; + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher( + self->properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION)), + self->parent); + self->parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotMainProperties(QDBusPendingCallWatcher*))); +} + +void ChannelDispatchOperation::Private::extractMainProps(const QVariantMap &props, + bool immutableProperties) +{ + parent->setInterfaces(qdbus_cast<QStringList>(props.value(QLatin1String("Interfaces")))); + + QList<PendingOperation *> readyOps; + + if (!connection && props.contains(QLatin1String("Connection"))) { + QDBusObjectPath connectionObjectPath = + qdbus_cast<QDBusObjectPath>(props.value(QLatin1String("Connection"))); + QString connectionBusName = + connectionObjectPath.path().mid(1).replace(QLatin1String("/"), + QLatin1String(".")); + + PendingReady *readyOp = + connFactory->proxy(connectionBusName, connectionObjectPath.path(), + chanFactory, contactFactory); + connection = ConnectionPtr::qObjectCast(readyOp->proxy()); + readyOps.append(readyOp); + } + + if (!account && props.contains(QLatin1String("Account"))) { + QDBusObjectPath accountObjectPath = + qdbus_cast<QDBusObjectPath>(props.value(QLatin1String("Account"))); + + PendingReady *readyOp = + accFactory->proxy(TP_QT_ACCOUNT_MANAGER_BUS_NAME, + accountObjectPath.path(), connFactory, chanFactory, contactFactory); + account = AccountPtr::qObjectCast(readyOp->proxy()); + readyOps.append(readyOp); + } + + if (!immutableProperties) { + // If we're here, it means we had to introspect the object, and now for sure have the + // correct channels list, so let's overwrite the initial channels - but keep the refs around + // for a while as an optimization enabling the factory to still return the same ones instead + // of constructing everything anew. Note that this is not done at all in the case the + // immutable props and initial channels etc were sufficient. + QList<ChannelPtr> saveChannels = channels; + channels.clear(); + + ChannelDetailsList channelDetailsList = + qdbus_cast<ChannelDetailsList>(props.value(QLatin1String("Channels"))); + ChannelPtr channel; + foreach (const ChannelDetails &channelDetails, channelDetailsList) { + PendingReady *readyOp = + chanFactory->proxy(connection, + channelDetails.channel.path(), channelDetails.properties); + channels.append(ChannelPtr::qObjectCast(readyOp->proxy())); + readyOps.append(readyOp); + } + + // saveChannels goes out of scope now, so any initial channels which don't exist anymore are + // freed + } + + if (props.contains(QLatin1String("PossibleHandlers"))) { + possibleHandlers = qdbus_cast<QStringList>(props.value(QLatin1String("PossibleHandlers"))); + gotPossibleHandlers = true; + } + + if (readyOps.isEmpty()) { + debug() << "No proxies to prepare for CDO" << parent->objectPath(); + readinessHelper->setIntrospectCompleted(FeatureCore, true); + } else { + parent->connect(new PendingComposite(readyOps, ChannelDispatchOperationPtr(parent)), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onProxiesPrepared(Tp::PendingOperation*))); + } +} + +ChannelDispatchOperation::PendingClaim::PendingClaim(const ChannelDispatchOperationPtr &op, + const AbstractClientHandlerPtr &handler) + : PendingOperation(op), + mDispatchOp(op), + mHandler(handler) +{ + debug() << "Invoking CDO.Claim"; + connect(new PendingVoid(op->baseInterface()->Claim(), op), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onClaimFinished(Tp::PendingOperation*))); +} + +ChannelDispatchOperation::PendingClaim::~PendingClaim() +{ +} + +void ChannelDispatchOperation::PendingClaim::onClaimFinished( + PendingOperation *op) +{ + if (!op->isError()) { + debug() << "CDO.Claim returned successfully, updating HandledChannels"; + if (mHandler) { + // register the channels in HandledChannels + FakeHandlerManager::instance()->registerChannels( + mDispatchOp->channels()); + } + setFinished(); + } else { + warning() << "CDO.Claim failed with" << op->errorName() << "-" << op->errorMessage(); + setFinishedWithError(op->errorName(), op->errorMessage()); + } +} + +/** + * \class ChannelDispatchOperation + * \ingroup clientchanneldispatchoperation + * \headerfile TelepathyQt/channel-dispatch-operation.h <TelepathyQt/ChannelDispatchOperation> + * + * \brief The ChannelDispatchOperation class represents a Telepathy channel + * dispatch operation. + * + * One of the channel dispatcher's functions is to offer incoming channels to + * Approver clients for approval. An approver should generally ask the user + * whether they want to participate in the requested communication channels + * (join the chat or chatroom, answer the call, accept the file transfer, or + * whatever is appropriate). A collection of channels offered in this way + * is represented by a ChannelDispatchOperation object. + * + * If the user wishes to accept the communication channels, the approver + * should call handleWith() to indicate the user's or approver's preferred + * handler for the channels (the empty string indicates no particular + * preference, and will cause any suitable handler to be used). + * + * If the user wishes to reject the communication channels, or if the user + * accepts the channels and the approver will handle them itself, the approver + * should call claim(). If the resulting PendingOperation succeeds, the approver + * immediately has control over the channels as their primary handler, + * and may do anything with them (in particular, it may close them in whatever + * way seems most appropriate). + * + * There are various situations in which the channel dispatch operation will + * be closed, causing the DBusProxy::invalidated() signal to be emitted. If this + * happens, the approver should stop prompting the user. + * + * Because all approvers are launched simultaneously, the user might respond + * to another approver; if this happens, the invalidated signal will be + * emitted with the error code #TP_QT_ERROR_OBJECT_REMOVED. + * + * If a channel closes, the signal channelLost() is emitted. If all channels + * close, there is nothing more to dispatch, so the invalidated signal will be + * emitted with the error code #TP_QT_ERROR_OBJECT_REMOVED. + * + * If the channel dispatcher crashes or exits, the invalidated + * signal will be emitted with the error code + * #TP_QT_DBUS_ERROR_NAME_HAS_NO_OWNER. In a high-quality implementation, + * the dispatcher should be restarted, at which point it will create new + * channel dispatch operations for any undispatched channels, and the approver + * will be notified again. + */ + +/** + * Feature representing the core that needs to become ready to make the + * ChannelDispatchOperation object usable. + * + * Note that this feature must be enabled in order to use most + * ChannelDispatchOperation methods. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature ChannelDispatchOperation::FeatureCore = Feature(QLatin1String(ChannelDispatchOperation::staticMetaObject.className()), 0, true); + +/** + * Create a new channel dispatch operation object using the given \a bus, the given factories and + * the given initial channels. + * + * \param bus QDBusConnection to use. + * \param objectPath The channel dispatch operation object path. + * \param immutableProperties The channel dispatch operation immutable properties. + * \param initialChannels The channels this CDO has initially (further tracking is done internally). + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return A ChannelDispatchOperationPtr object pointing to the newly created + * ChannelDispatchOperation object. + */ +ChannelDispatchOperationPtr ChannelDispatchOperation::create(const QDBusConnection &bus, + const QString &objectPath, const QVariantMap &immutableProperties, + const QList<ChannelPtr> &initialChannels, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return ChannelDispatchOperationPtr(new ChannelDispatchOperation( + bus, objectPath, immutableProperties, initialChannels, accountFactory, + connectionFactory, channelFactory, contactFactory)); +} + +/** + * Construct a new channel dispatch operation object using the given \a bus, the given factories and + * the given initial channels. + * + * \param bus QDBusConnection to use + * \param objectPath The channel dispatch operation object path. + * \param immutableProperties The channel dispatch operation immutable properties. + * \param initialChannels The channels this CDO has initially (further tracking is done internally). + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + */ +ChannelDispatchOperation::ChannelDispatchOperation(const QDBusConnection &bus, + const QString &objectPath, const QVariantMap &immutableProperties, + const QList<ChannelPtr> &initialChannels, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) + : StatefulDBusProxy(bus, + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER), + objectPath, FeatureCore), + OptionalInterfaceFactory<ChannelDispatchOperation>(this), + mPriv(new Private(this)) +{ + if (accountFactory->dbusConnection().name() != bus.name()) { + warning() << " The D-Bus connection in the account factory is not the proxy connection"; + } + + if (connectionFactory->dbusConnection().name() != bus.name()) { + warning() << " The D-Bus connection in the connection factory is not the proxy connection"; + } + + if (channelFactory->dbusConnection().name() != bus.name()) { + warning() << " The D-Bus connection in the channel factory is not the proxy connection"; + } + + mPriv->channels = initialChannels; + + mPriv->accFactory = accountFactory; + mPriv->connFactory = connectionFactory; + mPriv->chanFactory = channelFactory; + mPriv->contactFactory = contactFactory; + + mPriv->immutableProperties = immutableProperties; +} + +/** + * Class destructor. + */ +ChannelDispatchOperation::~ChannelDispatchOperation() +{ + delete mPriv; +} + +/** + * Return the connection with which the channels for this dispatch + * operation are associated. + * + * This method requires ChannelDispatchOperation::FeatureCore to be ready. + * + * \return A pointer to the Connection object. + */ +ConnectionPtr ChannelDispatchOperation::connection() const +{ + return mPriv->connection; +} + +/** + * Return the account with which the connection and channels for this dispatch + * operation are associated. + * + * This method requires ChannelDispatchOperation::FeatureCore to be ready. + * + * \return A pointer to the Account object. + */ +AccountPtr ChannelDispatchOperation::account() const +{ + return mPriv->account; +} + +/** + * Return the channels to be dispatched. + * + * This method requires ChannelDispatchOperation::FeatureCore to be ready. + * + * \return A list of pointers to Channel objects. + */ +QList<ChannelPtr> ChannelDispatchOperation::channels() const +{ + if (!isReady()) { + warning() << "ChannelDispatchOperation::channels called with channel " + "not ready"; + } + return mPriv->channels; +} + +/** + * Return the well known bus names (starting with + * org.freedesktop.Telepathy.Client.) of the possible Handlers for this + * dispatch operation channels with the preferred handlers first. + * + * As a result, approvers should use the first handler by default, unless they + * have a reason to do otherwise. + * + * This method requires ChannelDispatchOperation::FeatureCore to be ready. + * + * \return List of possible handlers names. + */ +QStringList ChannelDispatchOperation::possibleHandlers() const +{ + return mPriv->possibleHandlers; +} + +/** + * Called by an approver to accept a channel bundle and request that the given + * handler be used to handle it. + * + * If successful, this method will cause the ChannelDispatchOperation object to + * disappear, emitting invalidated with error + * #TP_QT_ERROR_OBJECT_REMOVED. + * + * However, this method may fail because the dispatch has already been completed + * and the object has already gone. If this occurs, it indicates that another + * approver has asked for the bundle to be handled by a particular handler. The + * approver must not attempt to interact with the channels further in this case, + * unless it is separately invoked as the handler. + * + * Approvers which are also channel handlers should use claim() instead of + * this method to request that they can handle a channel bundle themselves. + * + * \param handler The well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the channel handler that + * should handle the channel, or an empty string if + * the client has no preferred channel handler. + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + */ +PendingOperation *ChannelDispatchOperation::handleWith(const QString &handler) +{ + return new PendingVoid( + mPriv->baseInterface->HandleWith(handler), + ChannelDispatchOperationPtr(this)); +} + +/** + * Called by an approver to claim channels for closing them. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + */ +PendingOperation *ChannelDispatchOperation::claim() +{ + return new PendingClaim(ChannelDispatchOperationPtr(this)); +} + +/** + * Called by an approver to claim channels for handling internally. If this + * method is called successfully, the \a handler becomes the + * handler for the channel, but does not have the + * AbstractClientHandler::handleChannels() method called on it. + * + * Approvers wishing to reject channels must call this method to claim ownership + * of them, and must not call requestClose() on the channels unless/until this + * method returns successfully. + * + * The channel dispatcher can't know how best to close arbitrary channel types, + * so it leaves it up to the approver to do so. For instance, for text channels + * it is necessary to acknowledge any messages that have already been displayed + * to the user first - ideally, the approver would display and then acknowledge + * the messages - or to call Channel::requestClose() if the destructive + * behaviour of that method is desired. + * + * Similarly, an approver for streamed media channels can close the channel with + * a reason (e.g. "busy") if desired. The channel dispatcher, which is designed + * to have no specific knowledge of particular channel types, can't do that. + * + * If successful, this method will cause the ChannelDispatchOperation object to + * disappear, emitting Finished, in the same way as for handleWith(). + * + * This method may fail because the dispatch operation has already been + * completed. Again, see handleWith() for more details. The approver must not + * attempt to interact with the channels further in this case. + * + * \param handler The channel handler, that should remain registered during the + * lifetime of channels(), otherwise dispatching will fail if the + * channel dispatcher restarts. + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + * \sa claim(), handleWith() + */ +PendingOperation *ChannelDispatchOperation::claim(const AbstractClientHandlerPtr &handler) +{ + if (!handler->isRegistered()) { + return new PendingFailure(TP_QT_ERROR_INVALID_ARGUMENT, + QLatin1String("Handler must be registered for using claim(handler)"), + ChannelDispatchOperationPtr(this)); + } + + return new PendingClaim(ChannelDispatchOperationPtr(this), + handler); +} + +/** + * \fn void ChannelDispatchOperation::channelLost(const ChannelPtr &channel, + * const QString &errorName, const QString &errorMessage); + * + * Emitted when a channel has closed before it could be claimed or handled. If this is + * emitted for the last remaining channel in a channel dispatch operation, it + * will immediately be followed by invalidated() with error + * #TP_QT_ERROR_OBJECT_REMOVED. + * + * \param channel The channel that was closed. + * \param error The name of a D-Bus error indicating why the channel closed. + * \param errorMessage The error message. + */ + +/** + * Return the ChannelDispatchOperationInterface for this ChannelDispatchOperation + * class. This method is protected since the convenience methods provided by + * this class should always be used instead of the interface by users of the + * class. + * + * \return A pointer to the existing Client::ChannelDispatchOperationInterface object for this + * ChannelDispatchOperation object. + */ +Client::ChannelDispatchOperationInterface *ChannelDispatchOperation::baseInterface() const +{ + return mPriv->baseInterface; +} + +void ChannelDispatchOperation::onFinished() +{ + debug() << "ChannelDispatchOperation finished and was removed"; + invalidate(TP_QT_ERROR_OBJECT_REMOVED, + QLatin1String("ChannelDispatchOperation finished and was removed")); +} + +void ChannelDispatchOperation::gotMainProperties(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + + // Watcher is NULL if we didn't have to introspect at all + if (!reply.isError()) { + debug() << "Got reply to Properties::GetAll(ChannelDispatchOperation)"; + mPriv->extractMainProps(reply.value(), false); + } else { + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, + false, reply.error()); + warning().nospace() << "Properties::GetAll(ChannelDispatchOperation) failed with " + << reply.error().name() << ": " << reply.error().message(); + } +} + +void ChannelDispatchOperation::onChannelLost( + const QDBusObjectPath &channelObjectPath, + const QString &errorName, const QString &errorMessage) +{ + foreach (const ChannelPtr &channel, mPriv->channels) { + if (channel->objectPath() == channelObjectPath.path()) { + emit channelLost(channel, errorName, errorMessage); + mPriv->channels.removeOne(channel); + return; + } + } +} + +void ChannelDispatchOperation::onProxiesPrepared(Tp::PendingOperation *op) +{ + if (op->isError()) { + warning() << "Preparing proxies for CDO" << objectPath() << "failed with" + << op->errorName() << ":" << op->errorMessage(); + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false); + } else { + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true); + } +} + +} // Tp diff --git a/TelepathyQt/channel-dispatch-operation.h b/TelepathyQt/channel-dispatch-operation.h new file mode 100644 index 00000000..9d319d23 --- /dev/null +++ b/TelepathyQt/channel-dispatch-operation.h @@ -0,0 +1,114 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_channel_dispatch_operation_h_HEADER_GUARD_ +#define _TelepathyQt_channel_dispatch_operation_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/_gen/cli-channel-dispatch-operation.h> + +#include <TelepathyQt/Constants> +#include <TelepathyQt/DBus> +#include <TelepathyQt/DBusProxy> +#include <TelepathyQt/Feature> +#include <TelepathyQt/OptionalInterfaceFactory> +#include <TelepathyQt/ReadinessHelper> +#include <TelepathyQt/Types> +#include <TelepathyQt/SharedPtr> + +#include <QString> +#include <QStringList> +#include <QVariantMap> + +namespace Tp +{ + +class PendingOperation; + +class TP_QT_EXPORT ChannelDispatchOperation : public StatefulDBusProxy, + public OptionalInterfaceFactory<ChannelDispatchOperation> +{ + Q_OBJECT + Q_DISABLE_COPY(ChannelDispatchOperation) + +public: + static const Feature FeatureCore; + + static ChannelDispatchOperationPtr create(const QDBusConnection &bus, + const QString &objectPath, const QVariantMap &immutableProperties, + const QList<ChannelPtr> &initialChannels, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory); + virtual ~ChannelDispatchOperation(); + + ConnectionPtr connection() const; + + AccountPtr account() const; + + QList<ChannelPtr> channels() const; + + QStringList possibleHandlers() const; + + PendingOperation *handleWith(const QString &handler); + + PendingOperation *claim(); + PendingOperation *claim(const AbstractClientHandlerPtr &handler); + +Q_SIGNALS: + void channelLost(const Tp::ChannelPtr &channel, const QString &errorName, + const QString &errorMessage); + +protected: + ChannelDispatchOperation(const QDBusConnection &bus, + const QString &objectPath, const QVariantMap &immutableProperties, + const QList<ChannelPtr> &initialChannels, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory); + + Client::ChannelDispatchOperationInterface *baseInterface() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onFinished(); + TP_QT_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onChannelLost(const QDBusObjectPath &channelObjectPath, + const QString &errorName, const QString &errorMessage); + TP_QT_NO_EXPORT void onProxiesPrepared(Tp::PendingOperation *op); + +private: + class PendingClaim; + friend class PendingClaim; + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/channel-dispatch-operation.xml b/TelepathyQt/channel-dispatch-operation.xml new file mode 100644 index 00000000..d7a6cdda --- /dev/null +++ b/TelepathyQt/channel-dispatch-operation.xml @@ -0,0 +1,9 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Channel Dispatch Operation interface</tp:title> + +<xi:include href="../spec/Channel_Dispatch_Operation.xml"/> + +</tp:spec> diff --git a/TelepathyQt/channel-dispatcher.cpp b/TelepathyQt/channel-dispatcher.cpp new file mode 100644 index 00000000..b86b617a --- /dev/null +++ b/TelepathyQt/channel-dispatcher.cpp @@ -0,0 +1,26 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/ChannelDispatcher> + +#include "TelepathyQt/_gen/cli-channel-dispatcher-body.hpp" +#include "TelepathyQt/_gen/cli-channel-dispatcher.moc.hpp" diff --git a/TelepathyQt/channel-dispatcher.h b/TelepathyQt/channel-dispatcher.h new file mode 100644 index 00000000..d94284e9 --- /dev/null +++ b/TelepathyQt/channel-dispatcher.h @@ -0,0 +1,32 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_channel_dispatcher_h_HEADER_GUARD_ +#define _TelepathyQt_channel_dispatcher_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/_gen/cli-channel-dispatcher.h> + +#endif diff --git a/TelepathyQt/channel-dispatcher.xml b/TelepathyQt/channel-dispatcher.xml new file mode 100644 index 00000000..a1588a42 --- /dev/null +++ b/TelepathyQt/channel-dispatcher.xml @@ -0,0 +1,9 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Channel Dispatcher interface</tp:title> + +<xi:include href="../spec/Channel_Dispatcher.xml"/> + +</tp:spec> diff --git a/TelepathyQt/channel-factory.cpp b/TelepathyQt/channel-factory.cpp new file mode 100644 index 00000000..10bf4cbd --- /dev/null +++ b/TelepathyQt/channel-factory.cpp @@ -0,0 +1,529 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ChannelFactory> + +#include "TelepathyQt/_gen/channel-factory.moc.hpp" + +#include "TelepathyQt/_gen/future-constants.h" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Channel> +#include <TelepathyQt/ChannelClassSpec> +#include <TelepathyQt/ChannelClassFeatures> +#include <TelepathyQt/Connection> +#include <TelepathyQt/Constants> +#include <TelepathyQt/ContactSearchChannel> +#include <TelepathyQt/FileTransferChannel> +#include <TelepathyQt/IncomingFileTransferChannel> +#include <TelepathyQt/IncomingStreamTubeChannel> +#include <TelepathyQt/OutgoingFileTransferChannel> +#include <TelepathyQt/OutgoingStreamTubeChannel> +#include <TelepathyQt/RoomListChannel> +#include <TelepathyQt/StreamTubeChannel> +#include <TelepathyQt/StreamedMediaChannel> +#include <TelepathyQt/TextChannel> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ChannelFactory::Private +{ + Private(); + + QList<ChannelClassFeatures> features; + + typedef QPair<ChannelClassSpec, ConstructorConstPtr> CtorPair; + QList<CtorPair> ctors; +}; + +ChannelFactory::Private::Private() +{ +} + +/** + * \class ChannelFactory + * \ingroup utils + * \headerfile TelepathyQt/channel-factory.h <TelepathyQt/ChannelFactory> + * + * \brief The ChannelFactory class is responsible for constructing Channel + * objects according to application-defined settings. + */ + +/** + * Create a new ChannelFactory object. + * + * The returned factory will construct channel subclasses provided by TelepathyQt as appropriate + * for the channel immutable properties, but not make any features ready. + * + * \param bus The QDBusConnection the proxies constructed using this factory should use. + * \return An ChannelFactoryPtr pointing to the newly created factory. + */ +ChannelFactoryPtr ChannelFactory::create(const QDBusConnection &bus) +{ + return ChannelFactoryPtr(new ChannelFactory(bus)); +} + +/** + * Construct a new ChannelFactory object. + * + * The constructed factory will construct channel subclasses provided by TelepathyQt as appropriate + * for the channel immutable properties, but not make any features ready. + * + * \param bus The QDBusConnection the proxies constructed using this factory should use. + */ +ChannelFactory::ChannelFactory(const QDBusConnection &bus) + : DBusProxyFactory(bus), mPriv(new Private) +{ + setSubclassForTextChats<TextChannel>(); + setSubclassForTextChatrooms<TextChannel>(); + setSubclassForStreamedMediaCalls<StreamedMediaChannel>(); + setSubclassForRoomLists<RoomListChannel>(); + setSubclassForIncomingFileTransfers<IncomingFileTransferChannel>(); + setSubclassForOutgoingFileTransfers<OutgoingFileTransferChannel>(); + setSubclassForIncomingStreamTubes<IncomingStreamTubeChannel>(); + setSubclassForOutgoingStreamTubes<OutgoingStreamTubeChannel>(); + setSubclassForIncomingRoomStreamTubes<IncomingStreamTubeChannel>(); + setSubclassForOutgoingRoomStreamTubes<OutgoingStreamTubeChannel>(); + setSubclassForContactSearches<ContactSearchChannel>(); + setFallbackSubclass<Channel>(); +} + +/** + * Class destructor. + */ +ChannelFactory::~ChannelFactory() +{ + delete mPriv; +} + +Features ChannelFactory::featuresForTextChats(const QVariantMap &additionalProps) const +{ + return featuresFor(ChannelClassSpec::textChat(additionalProps)); +} + +void ChannelFactory::addFeaturesForTextChats(const Features &features, + const QVariantMap &additionalProps) +{ + addFeaturesFor(ChannelClassSpec::textChat(additionalProps), features); + addFeaturesFor(ChannelClassSpec::unnamedTextChat(additionalProps), features); +} + +ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForTextChats( + const QVariantMap &additionalProps) const +{ + return constructorFor(ChannelClassSpec::textChat(additionalProps)); +} + +void ChannelFactory::setConstructorForTextChats(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps) +{ + setConstructorFor(ChannelClassSpec::textChat(additionalProps), ctor); + setConstructorFor(ChannelClassSpec::unnamedTextChat(additionalProps), ctor); +} + +Features ChannelFactory::featuresForTextChatrooms(const QVariantMap &additionalProps) const +{ + return featuresFor(ChannelClassSpec::textChatroom(additionalProps)); +} + +void ChannelFactory::addFeaturesForTextChatrooms(const Features &features, + const QVariantMap &additionalProps) +{ + addFeaturesFor(ChannelClassSpec::textChatroom(additionalProps), features); +} + +ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForTextChatrooms( + const QVariantMap &additionalProps) const +{ + return constructorFor(ChannelClassSpec::textChatroom(additionalProps)); +} + +void ChannelFactory::setConstructorForTextChatrooms(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps) +{ + setConstructorFor(ChannelClassSpec::textChatroom(additionalProps), ctor); +} + +Features ChannelFactory::featuresForStreamedMediaCalls(const QVariantMap &additionalProps) const +{ + return featuresFor(ChannelClassSpec::streamedMediaCall(additionalProps)); +} + +void ChannelFactory::addFeaturesForStreamedMediaCalls(const Features &features, + const QVariantMap &additionalProps) +{ + ChannelClassSpec smSpec = ChannelClassSpec::streamedMediaCall(additionalProps); + ChannelClassSpec unnamedSMSpec = ChannelClassSpec::unnamedStreamedMediaCall(additionalProps); + + addFeaturesFor(smSpec, features); + addFeaturesFor(unnamedSMSpec, features); +} + +ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForStreamedMediaCalls( + const QVariantMap &additionalProps) const +{ + return constructorFor(ChannelClassSpec::streamedMediaCall(additionalProps)); +} + +void ChannelFactory::setConstructorForStreamedMediaCalls(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps) +{ + ChannelClassSpec smSpec = ChannelClassSpec::streamedMediaCall(additionalProps); + ChannelClassSpec unnamedSMSpec = ChannelClassSpec::unnamedStreamedMediaCall(additionalProps); + + setConstructorFor(smSpec, ctor); + setConstructorFor(unnamedSMSpec, ctor); +} + +Features ChannelFactory::featuresForRoomLists(const QVariantMap &additionalProps) const +{ + return featuresFor(ChannelClassSpec::roomList(additionalProps)); +} + +void ChannelFactory::addFeaturesForRoomLists(const Features &features, + const QVariantMap &additionalProps) +{ + addFeaturesFor(ChannelClassSpec::roomList(additionalProps), features); +} + +ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForRoomLists( + const QVariantMap &additionalProps) const +{ + return constructorFor(ChannelClassSpec::roomList(additionalProps)); +} + +void ChannelFactory::setConstructorForRoomLists(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps) +{ + setConstructorFor(ChannelClassSpec::roomList(additionalProps), ctor); +} + +Features ChannelFactory::featuresForOutgoingFileTransfers(const QVariantMap &additionalProps) const +{ + return featuresFor(ChannelClassSpec::outgoingFileTransfer(additionalProps)); +} + +void ChannelFactory::addFeaturesForOutgoingFileTransfers(const Features &features, + const QVariantMap &additionalProps) +{ + addFeaturesFor(ChannelClassSpec::outgoingFileTransfer(additionalProps), features); +} + +ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForOutgoingFileTransfers( + const QVariantMap &additionalProps) const +{ + return constructorFor(ChannelClassSpec::outgoingFileTransfer(additionalProps)); +} + +void ChannelFactory::setConstructorForOutgoingFileTransfers(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps) +{ + setConstructorFor(ChannelClassSpec::outgoingFileTransfer(additionalProps), ctor); +} + +Features ChannelFactory::featuresForIncomingFileTransfers(const QVariantMap &additionalProps) const +{ + return featuresFor(ChannelClassSpec::incomingFileTransfer(additionalProps)); +} + +void ChannelFactory::addFeaturesForIncomingFileTransfers(const Features &features, + const QVariantMap &additionalProps) +{ + addFeaturesFor(ChannelClassSpec::incomingFileTransfer(additionalProps), features); +} + +ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForIncomingFileTransfers( + const QVariantMap &additionalProps) const +{ + return constructorFor(ChannelClassSpec::incomingFileTransfer(additionalProps)); +} + +void ChannelFactory::setConstructorForIncomingFileTransfers(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps) +{ + setConstructorFor(ChannelClassSpec::incomingFileTransfer(additionalProps), ctor); +} + +Features ChannelFactory::featuresForOutgoingStreamTubes(const QVariantMap &additionalProps) const +{ + return featuresFor(ChannelClassSpec::outgoingStreamTube(QString(), additionalProps)); +} + +void ChannelFactory::addFeaturesForOutgoingStreamTubes(const Features &features, + const QVariantMap &additionalProps) +{ + addFeaturesFor(ChannelClassSpec::outgoingStreamTube(QString(), additionalProps), features); +} + +ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForOutgoingStreamTubes( + const QVariantMap &additionalProps) const +{ + return constructorFor(ChannelClassSpec::outgoingStreamTube(QString(), additionalProps)); +} + +void ChannelFactory::setConstructorForOutgoingStreamTubes(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps) +{ + setConstructorFor(ChannelClassSpec::outgoingStreamTube(QString(), additionalProps), ctor); +} + +Features ChannelFactory::featuresForIncomingStreamTubes(const QVariantMap &additionalProps) const +{ + return featuresFor(ChannelClassSpec::incomingStreamTube(QString(), additionalProps)); +} + +void ChannelFactory::addFeaturesForIncomingStreamTubes(const Features &features, + const QVariantMap &additionalProps) +{ + addFeaturesFor(ChannelClassSpec::incomingStreamTube(QString(), additionalProps), features); +} + +ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForIncomingStreamTubes( + const QVariantMap &additionalProps) const +{ + return constructorFor(ChannelClassSpec::incomingStreamTube(QString(), additionalProps)); +} + +void ChannelFactory::setConstructorForIncomingStreamTubes(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps) +{ + setConstructorFor(ChannelClassSpec::incomingStreamTube(QString(), additionalProps), ctor); +} + +Features ChannelFactory::featuresForOutgoingRoomStreamTubes(const QVariantMap &additionalProps) const +{ + return featuresFor(ChannelClassSpec::outgoingRoomStreamTube(QString(), additionalProps)); +} + +void ChannelFactory::addFeaturesForOutgoingRoomStreamTubes(const Features &features, + const QVariantMap &additionalProps) +{ + addFeaturesFor(ChannelClassSpec::outgoingRoomStreamTube(QString(), additionalProps), features); +} + +ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForOutgoingRoomStreamTubes( + const QVariantMap &additionalProps) const +{ + return constructorFor(ChannelClassSpec::outgoingRoomStreamTube(QString(), additionalProps)); +} + +void ChannelFactory::setConstructorForOutgoingRoomStreamTubes(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps) +{ + setConstructorFor(ChannelClassSpec::outgoingRoomStreamTube(QString(), additionalProps), ctor); +} + +Features ChannelFactory::featuresForIncomingRoomStreamTubes(const QVariantMap &additionalProps) const +{ + return featuresFor(ChannelClassSpec::incomingRoomStreamTube(QString(), additionalProps)); +} + +void ChannelFactory::addFeaturesForIncomingRoomStreamTubes(const Features &features, + const QVariantMap &additionalProps) +{ + addFeaturesFor(ChannelClassSpec::incomingRoomStreamTube(QString(), additionalProps), features); +} + +ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForIncomingRoomStreamTubes( + const QVariantMap &additionalProps) const +{ + return constructorFor(ChannelClassSpec::incomingRoomStreamTube(QString(), additionalProps)); +} + +void ChannelFactory::setConstructorForIncomingRoomStreamTubes(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps) +{ + setConstructorFor(ChannelClassSpec::incomingRoomStreamTube(QString(), additionalProps), ctor); +} + +Features ChannelFactory::featuresForContactSearches(const QVariantMap &additionalProps) const +{ + return featuresFor(ChannelClassSpec::contactSearch(additionalProps)); +} + +void ChannelFactory::addFeaturesForContactSearches(const Features &features, + const QVariantMap &additionalProps) +{ + addFeaturesFor(ChannelClassSpec::contactSearch(additionalProps), features); +} + +ChannelFactory::ConstructorConstPtr ChannelFactory::constructorForContactSearches( + const QVariantMap &additionalProps) const +{ + return constructorFor(ChannelClassSpec::contactSearch(additionalProps)); +} + +void ChannelFactory::setConstructorForContactSearches(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps) +{ + setConstructorFor(ChannelClassSpec::contactSearch(additionalProps), ctor); +} + +Features ChannelFactory::commonFeatures() const +{ + return featuresFor(ChannelClassSpec()); +} + +void ChannelFactory::addCommonFeatures(const Features &features) +{ + addFeaturesFor(ChannelClassSpec(), features); +} + +ChannelFactory::ConstructorConstPtr ChannelFactory::fallbackConstructor() const +{ + return constructorFor(ChannelClassSpec()); +} + +void ChannelFactory::setFallbackConstructor(const ConstructorConstPtr &ctor) +{ + setConstructorFor(ChannelClassSpec(), ctor); +} + +Features ChannelFactory::featuresFor(const ChannelClassSpec &channelClass) const +{ + Features features; + + foreach (const ChannelClassFeatures &pair, mPriv->features) { + if (pair.first.isSubsetOf(channelClass)) { + features.unite(pair.second); + } + } + + return features; +} + +void ChannelFactory::addFeaturesFor(const ChannelClassSpec &channelClass, const Features &features) +{ + QList<ChannelClassFeatures>::iterator i; + for (i = mPriv->features.begin(); i != mPriv->features.end(); ++i) { + if (channelClass.allProperties().size() > i->first.allProperties().size()) { + break; + } + + if (i->first == channelClass) { + i->second.unite(features); + return; + } + } + + // We ran out of feature specifications (for the given size/specificity of a channel class) + // before finding a matching one, so let's create a new entry + mPriv->features.insert(i, qMakePair(channelClass, features)); +} + +ChannelFactory::ConstructorConstPtr ChannelFactory::constructorFor(const ChannelClassSpec &cc) const +{ + QList<Private::CtorPair>::iterator i; + for (i = mPriv->ctors.begin(); i != mPriv->ctors.end(); ++i) { + if (i->first.isSubsetOf(cc)) { + return i->second; + } + } + + // If this is reached, we didn't have a proper fallback constructor + Q_ASSERT(false); + return ConstructorConstPtr(); +} + +void ChannelFactory::setConstructorFor(const ChannelClassSpec &channelClass, + const ConstructorConstPtr &ctor) +{ + if (ctor.isNull()) { + warning().nospace() << "Tried to set a NULL ctor for ChannelClass(" + << channelClass.channelType() << ", " << channelClass.targetHandleType() << ", " + << channelClass.allProperties().size() << "props in total)"; + return; + } + + QList<Private::CtorPair>::iterator i; + for (i = mPriv->ctors.begin(); i != mPriv->ctors.end(); ++i) { + if (channelClass.allProperties().size() > i->first.allProperties().size()) { + break; + } + + if (i->first == channelClass) { + i->second = ctor; + return; + } + } + + // We ran out of constructors (for the given size/specificity of a channel class) + // before finding a matching one, so let's create a new entry + mPriv->ctors.insert(i, qMakePair(channelClass, ctor)); +} + +/** + * Constructs a Channel proxy and begins making it ready. + * + * If a valid proxy already exists in the factory cache for the given combination of \a busName and + * \a objectPath, it is returned instead. All newly created proxies are automatically cached until + * they're either DBusProxy::invalidated() or the last reference to them outside the factory has + * been dropped. + * + * The proxy can be accessed immediately after this function returns using PendingReady::proxy(). + * + * \param connection Proxy for the owning connection of the channel. + * \param channelPath The object path of the channel. + * \param immutableProperties The immutable properties of the channel. + * \return A PendingReady operation with the proxy in PendingReady::proxy(). + */ +PendingReady *ChannelFactory::proxy(const ConnectionPtr &connection, const QString &channelPath, + const QVariantMap &immutableProperties) const +{ + DBusProxyPtr proxy = cachedProxy(connection->busName(), channelPath); + if (proxy.isNull()) { + proxy = constructorFor(ChannelClassSpec(immutableProperties))->construct(connection, + channelPath, immutableProperties); + } + + return nowHaveProxy(proxy); +} + +/** + * Transforms well-known names to the corresponding unique names, as is appropriate for Channel + * + * \param uniqueOrWellKnown The name to transform. + * \return The unique name corresponding to \a uniqueOrWellKnown (which may be it itself). + */ +QString ChannelFactory::finalBusNameFrom(const QString &uniqueOrWellKnown) const +{ + return StatefulDBusProxy::uniqueNameFrom(dbusConnection(), uniqueOrWellKnown); +} + +/** + * Return features as configured for the channel class given by the Channel::immutableProperties() + * of \a proxy. + * + * \param proxy The Channel proxy to determine the features for. + * \return A list of Feature objects. + */ +Features ChannelFactory::featuresFor(const DBusProxyPtr &proxy) const +{ + ChannelPtr chan = ChannelPtr::qObjectCast(proxy); + Q_ASSERT(!chan.isNull()); + + return featuresFor(ChannelClassSpec(chan->immutableProperties())); +} + +} // Tp diff --git a/TelepathyQt/channel-factory.h b/TelepathyQt/channel-factory.h new file mode 100644 index 00000000..52503369 --- /dev/null +++ b/TelepathyQt/channel-factory.h @@ -0,0 +1,305 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_channel_factory_h_HEADER_GUARD_ +#define _TelepathyQt_channel_factory_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/DBusProxyFactory> +#include <TelepathyQt/SharedPtr> +#include <TelepathyQt/Types> + +// For Q_DISABLE_COPY +#include <QtGlobal> +#include <QString> +#include <QVariantMap> + +class QDBusConnection; + +namespace Tp +{ + +class ChannelClassSpec; + +class TP_QT_EXPORT ChannelFactory : public DBusProxyFactory +{ + Q_OBJECT + Q_DISABLE_COPY(ChannelFactory) + +public: +#ifndef DOXYGEN_SHOULD_SKIP_THIS + struct TP_QT_EXPORT Constructor : public RefCounted + { + virtual ~Constructor() {} + + virtual ChannelPtr construct(const ConnectionPtr &conn, const QString &objectPath, + const QVariantMap &immutableProperties) const = 0; + }; + typedef SharedPtr<Constructor> ConstructorPtr; + typedef SharedPtr<const Constructor> ConstructorConstPtr; +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + + static ChannelFactoryPtr create(const QDBusConnection &bus); + + virtual ~ChannelFactory(); + + Features featuresForTextChats(const QVariantMap &additionalProps = QVariantMap()) const; + void addFeaturesForTextChats(const Features &features, + const QVariantMap &additionalProps = QVariantMap()); + + ConstructorConstPtr constructorForTextChats( + const QVariantMap &additionalProps = QVariantMap()) const; + + template<typename Subclass> + void setSubclassForTextChats(const QVariantMap &additionalProps = QVariantMap()) + { + setConstructorForTextChats(SubclassCtor<Subclass>::create(), additionalProps); + } + + void setConstructorForTextChats(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps = QVariantMap()); + + Features featuresForTextChatrooms(const QVariantMap &additionalProps = QVariantMap()) const; + void addFeaturesForTextChatrooms(const Features &features, + const QVariantMap &additionalProps = QVariantMap()); + + ConstructorConstPtr constructorForTextChatrooms( + const QVariantMap &additionalProps = QVariantMap()) const; + + template<typename Subclass> + void setSubclassForTextChatrooms(const QVariantMap &additionalProps = QVariantMap()) + { + setConstructorForTextChatrooms(SubclassCtor<Subclass>::create(), additionalProps); + } + + void setConstructorForTextChatrooms(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps = QVariantMap()); + + Features featuresForStreamedMediaCalls(const QVariantMap &additionalProps = QVariantMap()) const; + void addFeaturesForStreamedMediaCalls(const Features &features, + const QVariantMap &additionalProps = QVariantMap()); + + ConstructorConstPtr constructorForStreamedMediaCalls( + const QVariantMap &additionalProps = QVariantMap()) const; + + template<typename Subclass> + void setSubclassForStreamedMediaCalls(const QVariantMap &additionalProps = QVariantMap()) + { + setConstructorForStreamedMediaCalls(SubclassCtor<Subclass>::create(), additionalProps); + } + + void setConstructorForStreamedMediaCalls(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps = QVariantMap()); + + Features featuresForRoomLists(const QVariantMap &additionalProps = QVariantMap()) const; + void addFeaturesForRoomLists(const Features &features, + const QVariantMap &additionalProps = QVariantMap()); + + ConstructorConstPtr constructorForRoomLists( + const QVariantMap &additionalProps = QVariantMap()) const; + + template<typename Subclass> + void setSubclassForRoomLists(const QVariantMap &additionalProps = QVariantMap()) + { + setConstructorForRoomLists(SubclassCtor<Subclass>::create(), additionalProps); + } + + void setConstructorForRoomLists(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps = QVariantMap()); + + Features featuresForOutgoingFileTransfers(const QVariantMap &additionalProps = QVariantMap()) const; + void addFeaturesForOutgoingFileTransfers(const Features &features, + const QVariantMap &additionalProps = QVariantMap()); + + ConstructorConstPtr constructorForOutgoingFileTransfers( + const QVariantMap &additionalProps = QVariantMap()) const; + + template<typename Subclass> + void setSubclassForOutgoingFileTransfers(const QVariantMap &additionalProps = QVariantMap()) + { + setConstructorForOutgoingFileTransfers(SubclassCtor<Subclass>::create(), additionalProps); + } + + void setConstructorForOutgoingFileTransfers(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps = QVariantMap()); + + Features featuresForIncomingFileTransfers(const QVariantMap &additionalProps = QVariantMap()) const; + void addFeaturesForIncomingFileTransfers(const Features &features, + const QVariantMap &additionalProps = QVariantMap()); + + ConstructorConstPtr constructorForIncomingFileTransfers( + const QVariantMap &additionalProps = QVariantMap()) const; + + template<typename Subclass> + void setSubclassForIncomingFileTransfers(const QVariantMap &additionalProps = QVariantMap()) + { + setConstructorForIncomingFileTransfers(SubclassCtor<Subclass>::create(), additionalProps); + } + + void setConstructorForIncomingFileTransfers(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps = QVariantMap()); + + Features featuresForOutgoingStreamTubes(const QVariantMap &additionalProps = QVariantMap()) const; + void addFeaturesForOutgoingStreamTubes(const Features &features, + const QVariantMap &additionalProps = QVariantMap()); + + ConstructorConstPtr constructorForOutgoingStreamTubes( + const QVariantMap &additionalProps = QVariantMap()) const; + + template<typename Subclass> + void setSubclassForOutgoingStreamTubes(const QVariantMap &additionalProps = QVariantMap()) + { + setConstructorForOutgoingStreamTubes(SubclassCtor<Subclass>::create(), additionalProps); + } + + void setConstructorForOutgoingStreamTubes(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps = QVariantMap()); + + Features featuresForIncomingStreamTubes(const QVariantMap &additionalProps = QVariantMap()) const; + void addFeaturesForIncomingStreamTubes(const Features &features, + const QVariantMap &additionalProps = QVariantMap()); + + ConstructorConstPtr constructorForIncomingStreamTubes( + const QVariantMap &additionalProps = QVariantMap()) const; + + template<typename Subclass> + void setSubclassForIncomingStreamTubes(const QVariantMap &additionalProps = QVariantMap()) + { + setConstructorForIncomingStreamTubes(SubclassCtor<Subclass>::create(), additionalProps); + } + + void setConstructorForIncomingStreamTubes(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps = QVariantMap()); + + Features featuresForOutgoingRoomStreamTubes(const QVariantMap &additionalProps = QVariantMap()) const; + void addFeaturesForOutgoingRoomStreamTubes(const Features &features, + const QVariantMap &additionalProps = QVariantMap()); + + ConstructorConstPtr constructorForOutgoingRoomStreamTubes( + const QVariantMap &additionalProps = QVariantMap()) const; + + template<typename Subclass> + void setSubclassForOutgoingRoomStreamTubes(const QVariantMap &additionalProps = QVariantMap()) + { + setConstructorForOutgoingRoomStreamTubes(SubclassCtor<Subclass>::create(), additionalProps); + } + + void setConstructorForOutgoingRoomStreamTubes(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps = QVariantMap()); + + Features featuresForIncomingRoomStreamTubes(const QVariantMap &additionalProps = QVariantMap()) const; + void addFeaturesForIncomingRoomStreamTubes(const Features &features, + const QVariantMap &additionalProps = QVariantMap()); + + ConstructorConstPtr constructorForIncomingRoomStreamTubes( + const QVariantMap &additionalProps = QVariantMap()) const; + + template<typename Subclass> + void setSubclassForIncomingRoomStreamTubes(const QVariantMap &additionalProps = QVariantMap()) + { + setConstructorForIncomingRoomStreamTubes(SubclassCtor<Subclass>::create(), additionalProps); + } + + void setConstructorForIncomingRoomStreamTubes(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps = QVariantMap()); + + Features featuresForContactSearches(const QVariantMap &additionalProps = QVariantMap()) const; + void addFeaturesForContactSearches(const Features &features, + const QVariantMap &additionalProps = QVariantMap()); + + ConstructorConstPtr constructorForContactSearches( + const QVariantMap &additionalProps = QVariantMap()) const; + + template<typename Subclass> + void setSubclassForContactSearches(const QVariantMap &additionalProps = QVariantMap()) + { + setConstructorForContactSearches(SubclassCtor<Subclass>::create(), additionalProps); + } + + void setConstructorForContactSearches(const ConstructorConstPtr &ctor, + const QVariantMap &additionalProps = QVariantMap()); + + Features commonFeatures() const; + void addCommonFeatures(const Features &features); + + ConstructorConstPtr fallbackConstructor() const; + + template <typename Subclass> + void setFallbackSubclass() + { + setFallbackConstructor(SubclassCtor<Subclass>::create()); + } + + void setFallbackConstructor(const ConstructorConstPtr &ctor); + + Features featuresFor(const ChannelClassSpec &channelClass) const; + void addFeaturesFor(const ChannelClassSpec &channelClass, const Features &features); + + template <typename Subclass> + void setSubclassFor(const ChannelClassSpec &channelClass) + { + setConstructorFor(channelClass, SubclassCtor<Subclass>::create()); + } + + ConstructorConstPtr constructorFor(const ChannelClassSpec &channelClass) const; + void setConstructorFor(const ChannelClassSpec &channelClass, const ConstructorConstPtr &ctor); + + PendingReady *proxy(const ConnectionPtr &connection, const QString &channelPath, + const QVariantMap &immutableProperties) const; + +protected: + ChannelFactory(const QDBusConnection &bus); + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + template <typename Subclass> + struct SubclassCtor : public Constructor + { + static ConstructorPtr create() + { + return ConstructorPtr(new SubclassCtor<Subclass>()); + } + + virtual ~SubclassCtor() {} + + ChannelPtr construct(const ConnectionPtr &conn, const QString &objectPath, + const QVariantMap &immutableProperties) const + { + return Subclass::create(conn, objectPath, immutableProperties); + } + }; +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + + virtual QString finalBusNameFrom(const QString &uniqueOrWellKnown) const; + // Nothing we'd like to prepare() + virtual Features featuresFor(const DBusProxyPtr &proxy) const; + +private: + struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/channel-internal.h b/TelepathyQt/channel-internal.h new file mode 100644 index 00000000..ff5641f0 --- /dev/null +++ b/TelepathyQt/channel-internal.h @@ -0,0 +1,50 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_channel_internal_h_HEADER_GUARD_ +#define _TelepathyQt_channel_internal_h_HEADER_GUARD_ + +#include <TelepathyQt/Channel> +#include <TelepathyQt/PendingOperation> + +namespace Tp +{ + +class TP_QT_NO_EXPORT Channel::PendingLeave : public PendingOperation +{ + Q_OBJECT + +public: + PendingLeave(const ChannelPtr &channel, const QString &message, + ChannelGroupChangeReason reason); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onChanInvalidated(Tp::DBusProxy *proxy); + TP_QT_NO_EXPORT void onRemoveFinished(Tp::PendingOperation *); + TP_QT_NO_EXPORT void onMembersChanged(const Tp::Contacts &, const Tp::Contacts &, const Tp::Contacts &, + const Tp::Contacts &); + TP_QT_NO_EXPORT void onCloseFinished(Tp::PendingOperation *); +}; + +} // Tp + +#endif diff --git a/TelepathyQt/channel-request.cpp b/TelepathyQt/channel-request.cpp new file mode 100644 index 00000000..5e84f57c --- /dev/null +++ b/TelepathyQt/channel-request.cpp @@ -0,0 +1,803 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/ChannelRequest> + +#include "TelepathyQt/_gen/cli-channel-request-body.hpp" +#include "TelepathyQt/_gen/cli-channel-request.moc.hpp" +#include "TelepathyQt/_gen/channel-request.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Account> +#include <TelepathyQt/AccountFactory> +#include <TelepathyQt/PendingFailure> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/PendingVoid> + +#include <QDateTime> +#include <QSharedData> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ChannelRequest::Private +{ + Private(ChannelRequest *parent, const QVariantMap &immutableProperties, + const AccountFactoryConstPtr &, const ConnectionFactoryConstPtr &, + const ChannelFactoryConstPtr &, const ContactFactoryConstPtr &); + ~Private(); + + static void introspectMain(Private *self); + + // \param lastCall Is this the last call to extractMainProps ie. should actions that only must + // be done once be done in this call + void extractMainProps(const QVariantMap &props, bool lastCall); + + // Public object + ChannelRequest *parent; + + // Context + AccountFactoryConstPtr accFact; + ConnectionFactoryConstPtr connFact; + ChannelFactoryConstPtr chanFact; + ContactFactoryConstPtr contactFact; + + // Instance of generated interface class + Client::ChannelRequestInterface *baseInterface; + + // Mandatory properties interface proxy + Client::DBus::PropertiesInterface *properties; + + QVariantMap immutableProperties; + + ReadinessHelper *readinessHelper; + + // Introspection + AccountPtr account; + QDateTime userActionTime; + QString preferredHandler; + QualifiedPropertyValueMapList requests; + ChannelRequestHints hints; + bool propertiesDone; + + bool gotSWC; + ChannelPtr chan; +}; + +ChannelRequest::Private::Private(ChannelRequest *parent, + const QVariantMap &immutableProperties, + const AccountFactoryConstPtr &accFact, + const ConnectionFactoryConstPtr &connFact, + const ChannelFactoryConstPtr &chanFact, + const ContactFactoryConstPtr &contactFact) + : parent(parent), + accFact(accFact), + connFact(connFact), + chanFact(chanFact), + contactFact(contactFact), + baseInterface(new Client::ChannelRequestInterface(parent)), + properties(parent->interface<Client::DBus::PropertiesInterface>()), + immutableProperties(immutableProperties), + readinessHelper(parent->readinessHelper()), + propertiesDone(false), + gotSWC(false) +{ + debug() << "Creating new ChannelRequest:" << parent->objectPath(); + + parent->connect(baseInterface, + SIGNAL(Failed(QString,QString)), + SIGNAL(failed(QString,QString))); + parent->connect(baseInterface, + SIGNAL(Succeeded()), + SLOT(onLegacySucceeded())); + parent->connect(baseInterface, + SIGNAL(SucceededWithChannel(QDBusObjectPath,QVariantMap,QDBusObjectPath,QVariantMap)), + SLOT(onSucceededWithChannel(QDBusObjectPath,QVariantMap,QDBusObjectPath,QVariantMap))); + + ReadinessHelper::Introspectables introspectables; + + // As ChannelRequest does not have predefined statuses let's simulate one (0) + ReadinessHelper::Introspectable introspectableCore( + QSet<uint>() << 0, // makesSenseForStatuses + Features(), // dependsOnFeatures + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectMain, + this); + introspectables[FeatureCore] = introspectableCore; + + readinessHelper->addIntrospectables(introspectables); + + // For early access to the immutable properties through the friendly getters - will be called + // again with lastCall = true eventually, if/when becomeReady is called, though + QVariantMap mainProps; + foreach (QString key, immutableProperties.keys()) { + // The key.count thing is so that we don't match "org.fdo.Tp.CR.OptionalInterface.Prop" too + if (key.startsWith(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".")) + && key.count(QLatin1Char('.')) == + QString::fromAscii(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".").count(QLatin1Char('.'))) { + QVariant value = immutableProperties.value(key); + mainProps.insert(key.remove(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".")), value); + } + } + extractMainProps(mainProps, false); +} + +ChannelRequest::Private::~Private() +{ +} + +void ChannelRequest::Private::introspectMain(ChannelRequest::Private *self) +{ + QVariantMap props; + QString key; + bool needIntrospectMainProps = false; + const char *propertiesNames[] = { "Account", "UserActionTime", + "PreferredHandler", "Requests", "Interfaces", + NULL }; + for (unsigned i = 0; propertiesNames[i] != NULL; ++i) { + key = QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST "."); + key += QLatin1String(propertiesNames[i]); + if (!self->immutableProperties.contains(key)) { + needIntrospectMainProps = true; + break; + } + props.insert(QLatin1String(propertiesNames[i]), + self->immutableProperties[key]); + } + + if (needIntrospectMainProps) { + debug() << "Calling Properties::GetAll(ChannelRequest)"; + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher( + self->properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST)), + self->parent); + // FIXME: This is a Qt bug fixed upstream, should be in the next Qt release. + // We should not need to check watcher->isFinished() here, remove the + // check when a fixed Qt version is released. + if (watcher->isFinished()) { + self->parent->gotMainProperties(watcher); + } else { + self->parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotMainProperties(QDBusPendingCallWatcher*))); + } + } else { + self->extractMainProps(props, true); + } +} + +void ChannelRequest::Private::extractMainProps(const QVariantMap &props, bool lastCall) +{ + PendingReady *readyOp = 0; + + if (props.contains(QLatin1String("Account"))) { + QDBusObjectPath accountObjectPath = + qdbus_cast<QDBusObjectPath>(props.value(QLatin1String("Account"))); + + if (!account.isNull()) { + if (accountObjectPath.path() == account->objectPath()) { + // Most often a no-op, but we want this to guarantee the old behavior in all cases + readyOp = account->becomeReady(); + } else { + warning() << "The account" << accountObjectPath.path() << "was not the expected" + << account->objectPath() << "for CR" << parent->objectPath(); + // Construct a new one instead + account.reset(); + } + } + + // We need to check again because we might have dropped the expected account just a sec ago + if (account.isNull() && !accountObjectPath.path().isEmpty()) { + if (!accFact.isNull()) { + readyOp = accFact->proxy( + TP_QT_ACCOUNT_MANAGER_BUS_NAME, accountObjectPath.path(), + connFact, chanFact, contactFact); + account = AccountPtr::qObjectCast(readyOp->proxy()); + } else { + account = Account::create( + TP_QT_ACCOUNT_MANAGER_BUS_NAME, + accountObjectPath.path(), connFact, chanFact, contactFact); + readyOp = account->becomeReady(); + } + } + } + + // FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690 + uint stamp = (uint) qdbus_cast<qlonglong>(props.value(QLatin1String("UserActionTime"))); + if (stamp != 0) { + userActionTime = QDateTime::fromTime_t(stamp); + } + + preferredHandler = qdbus_cast<QString>(props.value(QLatin1String("PreferredHandler"))); + requests = qdbus_cast<QualifiedPropertyValueMapList>(props.value(QLatin1String("Requests"))); + + parent->setInterfaces(qdbus_cast<QStringList>(props[QLatin1String("Interfaces")])); + readinessHelper->setInterfaces(parent->interfaces()); + + if (props.contains(QLatin1String("Hints"))) { + hints = qdbus_cast<QVariantMap>(props.value(QLatin1String("Hints"))); + } + + if (lastCall) { + propertiesDone = true; + } + + if (account) { + parent->connect(readyOp, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onAccountReady(Tp::PendingOperation*))); + } else if (lastCall) { + warning() << "No account for ChannelRequest" << parent->objectPath(); + readinessHelper->setIntrospectCompleted(FeatureCore, true); + } +} + +/** + * \class ChannelRequest + * \ingroup clientchannelrequest + * \headerfile TelepathyQt/channel-request.h <TelepathyQt/ChannelRequest> + * + * \brief The ChannelRequest class represents a Telepathy channel request. + * + * A channel request is an object in the channel dispatcher representing an + * ongoing request for some channels to be created or found. There can be any + * number of channel request objects at the same time. + * + * A channel request can be cancelled by any client (not just the one that + * requested it). This means that the channel dispatcher will close the + * resulting channel, or refrain from requesting it at all, rather than + * dispatching it to a handler. + * + * See \ref async_model + */ + +/** + * Feature representing the core that needs to become ready to make the + * ChannelRequest object usable. + * + * Note that this feature must be enabled in order to use most + * ChannelRequest methods. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature ChannelRequest::FeatureCore = Feature(QLatin1String(ChannelRequest::staticMetaObject.className()), 0, true); + +/** + * Create a new channel request object using the given \a bus and the given factories. + * + * \param objectPath The channel request object path. + * \param immutableProperties The channel request immutable properties. + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return A ChannelRequestPtr object pointing to the newly created ChannelRequest object. + */ +ChannelRequestPtr ChannelRequest::create(const QDBusConnection &bus, const QString &objectPath, + const QVariantMap &immutableProperties, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return ChannelRequestPtr(new ChannelRequest(bus, objectPath, immutableProperties, + accountFactory, connectionFactory, channelFactory, contactFactory)); +} + +/** + * Create a new channel request object for the given \a account. + * + * The returned instance will use factories from the account. + * + * \param account The account that the request was made through. + * \param objectPath The channel request object path. + * \param immutableProperties The channel request immutable properties. + * \return A ChannelRequestPtr object pointing to the newly created ChannelRequest object. + */ +ChannelRequestPtr ChannelRequest::create(const AccountPtr &account, const QString &objectPath, + const QVariantMap &immutableProperties) +{ + return ChannelRequestPtr(new ChannelRequest(account, objectPath, immutableProperties)); +} + +/** + * Construct a new channel request object using the given \a bus and the given factories. + * + * \param bus QDBusConnection to use. + * \param objectPath The channel request object path. + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \param immutableProperties The immutable properties of the channel request. + * \return A ChannelRequestPtr object pointing to the newly created ChannelRequest. + */ +ChannelRequest::ChannelRequest(const QDBusConnection &bus, + const QString &objectPath, const QVariantMap &immutableProperties, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) + : StatefulDBusProxy(bus, + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER), + objectPath, FeatureCore), + OptionalInterfaceFactory<ChannelRequest>(this), + mPriv(new Private(this, immutableProperties, accountFactory, connectionFactory, + channelFactory, contactFactory)) +{ + if (accountFactory->dbusConnection().name() != bus.name()) { + warning() << " The D-Bus connection in the account factory is not the proxy connection"; + } + + if (connectionFactory->dbusConnection().name() != bus.name()) { + warning() << " The D-Bus connection in the connection factory is not the proxy connection"; + } + + if (channelFactory->dbusConnection().name() != bus.name()) { + warning() << " The D-Bus connection in the channel factory is not the proxy connection"; + } +} + +/** + * Construct a new channel request object using the given \a account. + * + * The constructed instance will use the factories from the account. + * + * \param account Account to use. + * \param objectPath The channel request object path. + * \param immutableProperties The immutable properties of the channel request. + * \return A ChannelRequestPtr object pointing to the newly created ChannelRequest. + */ +ChannelRequest::ChannelRequest(const AccountPtr &account, + const QString &objectPath, const QVariantMap &immutableProperties) + : StatefulDBusProxy(account->dbusConnection(), + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER), + objectPath, FeatureCore), + OptionalInterfaceFactory<ChannelRequest>(this), + mPriv(new Private(this, immutableProperties, AccountFactoryPtr(), + account->connectionFactory(), + account->channelFactory(), + account->contactFactory())) +{ + mPriv->account = account; +} + +/** + * Class destructor. + */ +ChannelRequest::~ChannelRequest() +{ + delete mPriv; +} + +/** + * Return the account on which this request was made. + * + * This method can be used even before the ChannelRequest is ready, in which case the account object + * corresponding to the immutable properties is returned. In this case, the Account object is not + * necessarily ready either. This is useful for eg. matching ChannelRequests from + * ClientHandlerInterface::addRequest() with existing accounts in the application: either by object + * path, or if account factories are in use, even by object identity. + * + * If the account is not provided in the immutable properties, this will only return a non-\c NULL + * AccountPtr once ChannelRequest::FeatureCore is ready on this object. + * + * \return A pointer to the Account object. + */ +AccountPtr ChannelRequest::account() const +{ + return mPriv->account; +} + +/** + * Return the time at which the user action occurred, or 0 if this channel + * request is for some reason not involving user action. + * + * Unix developers: this corresponds to the _NET_WM_USER_TIME property in EWMH. + * + * This property is set when the channel request is created, and can never + * change. + * + * This method can be used even before the ChannelRequest is ready: in this case, the user action + * time from the immutable properties, if any, is returned. + * + * \return The time at which the user action occurred as QDateTime. + */ +QDateTime ChannelRequest::userActionTime() const +{ + return mPriv->userActionTime; +} + +/** + * Return either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred handler for this channel, + * or an empty string to indicate that any handler would be acceptable. + * + * This property is set when the channel request is created, and can never + * change. + * + * This method can be used even before the ChannelRequest is ready: in this case, the preferred + * handler from the immutable properties, if any, is returned. + * + * \return The preferred handler, or an empty string if any handler would be + * acceptable. + */ +QString ChannelRequest::preferredHandler() const +{ + return mPriv->preferredHandler; +} + +/** + * Return the desirable properties for the channel or channels to be created, as specified when + * placing the request in the first place. + * + * This property is set when the channel request is created, and can never + * change. + * + * This method can be used even before the ChannelRequest is ready: in this case, the requested + * channel properties from the immutable properties, if any, are returned. This is useful for e.g. + * matching ChannelRequests from ClientHandlerInterface::addRequest() with existing requests in the + * application (by the target ID or handle, most likely). + * + * \return The requested desirable channel properties as a list of + * QualifiedPropertyValueMap objects. + */ +QualifiedPropertyValueMapList ChannelRequest::requests() const +{ + return mPriv->requests; +} + +/** + * Return the dictionary of metadata provided by the channel requester when requesting the channel. + * + * This property is set when the channel request is created, and can never change. + * + * This method can be used even before the ChannelRequest is ready: in this case, the requested + * channel properties from the immutable properties, if any, are returned. This is useful for e.g. + * matching ChannelRequests from ClientHandlerInterface::addRequest() with existing requests in the + * application (by the target ID or handle, most likely). + * + * \sa Account::supportsRequestHints() + * \return The hints in the request as a ChannelRequestHints object, if any. + */ +ChannelRequestHints ChannelRequest::hints() const +{ + return mPriv->hints; +} + +/** + * Return all of the immutable properties passed to this object when created. + * + * This is useful for e.g. getting to domain-specific properties of channel requests. + * + * \return The immutable properties as QVariantMap. + */ +QVariantMap ChannelRequest::immutableProperties() const +{ + QVariantMap props = mPriv->immutableProperties; + + if (!account().isNull()) { + props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Account"), + QVariant::fromValue(QDBusObjectPath(account()->objectPath()))); + } + + if (userActionTime().isValid()) { + props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".UserActionTime"), + QVariant::fromValue(userActionTime().toTime_t())); + } + + if (!preferredHandler().isNull()) { + props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".PreferredHandler"), + preferredHandler()); + } + + if (!requests().isEmpty()) { + props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Requests"), + QVariant::fromValue(requests())); + } + + props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_REQUEST ".Interfaces"), + QVariant::fromValue(interfaces())); + + return props; +} + +/** + * Cancel the channel request. + * + * If failed() is emitted in response to this method, the error will be + * #TP_QT_ERROR_CANCELLED. + * + * If the channel has already been dispatched to a handler, then it's too late + * to call this method, and the channel request will no longer exist. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + */ +PendingOperation *ChannelRequest::cancel() +{ + return new PendingVoid(mPriv->baseInterface->Cancel(), + ChannelRequestPtr(this)); +} + +/** + * Return the Channel which this request succeeded with, if any. + * + * This will only ever be populated if Account::requestsSucceedWithChannel() is \c true, and + * succeeded() has already been emitted on this ChannelRequest. Note that a PendingChannelRequest + * being successfully finished already implies succeeded() has been emitted. + * + * \return A pointer to the Channel object, or a null ChannelPtr if there isn't any. + */ +ChannelPtr ChannelRequest::channel() const +{ + return mPriv->chan; +} + +/** + * Proceed with the channel request. + * + * The client that created this object calls this method when it has connected + * signal handlers for succeeded() and failed(). Note that this is done + * automatically when using PendingChannelRequest. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + */ +PendingOperation *ChannelRequest::proceed() +{ + return new PendingVoid(mPriv->baseInterface->Proceed(), + ChannelRequestPtr(this)); +} + +/** + * Return the ChannelRequestInterface for this ChannelRequest class. This method is + * protected since the convenience methods provided by this class should + * always be used instead of the interface by users of the class. + * + * \return A pointer to the existing Client::ChannelRequestInterface object for this + * ChannelRequest object. + */ +Client::ChannelRequestInterface *ChannelRequest::baseInterface() const +{ + return mPriv->baseInterface; +} + +void ChannelRequest::gotMainProperties(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + QVariantMap props; + + if (!reply.isError()) { + debug() << "Got reply to Properties::GetAll(ChannelRequest)"; + props = reply.value(); + + mPriv->extractMainProps(props, true); + } else { + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, + false, reply.error()); + warning().nospace() << "Properties::GetAll(ChannelRequest) failed with " + << reply.error().name() << ": " << reply.error().message(); + } + + watcher->deleteLater(); +} + +void ChannelRequest::onAccountReady(PendingOperation *op) +{ + if (op->isError()) { + warning() << "Unable to make ChannelRequest.Account ready"; + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, + op->errorName(), op->errorMessage()); + return; + } + + if (mPriv->propertiesDone && !isReady()) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true); + } +} + +void ChannelRequest::onLegacySucceeded() +{ + if (mPriv->gotSWC) { + return; + } + + emit succeeded(); // API/ABI break TODO: don't + emit succeeded(ChannelPtr()); +} + +void ChannelRequest::onSucceededWithChannel( + const QDBusObjectPath &connPath, + const QVariantMap &connProps, + const QDBusObjectPath &chanPath, + const QVariantMap &chanProps) +{ + if (mPriv->gotSWC) { + warning().nospace() << "Got SucceededWithChannel again for CR(" << objectPath() << ")!"; + return; + } + + mPriv->gotSWC = true; + + QList<PendingOperation *> readyOps; + + QString connBusName = connPath.path().mid(1).replace( + QLatin1String("/"), QLatin1String(".")); + PendingReady *connReady = mPriv->connFact->proxy(connBusName, connPath.path(), + mPriv->chanFact, mPriv->contactFact); + ConnectionPtr conn = ConnectionPtr::qObjectCast(connReady->proxy()); + readyOps.append(connReady); + + PendingReady *chanReady = mPriv->chanFact->proxy(conn, chanPath.path(), chanProps); + mPriv->chan = ChannelPtr::qObjectCast(chanReady->proxy()); + readyOps.append(chanReady); + + connect(new PendingComposite(readyOps, ChannelRequestPtr(this)), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onChanBuilt(Tp::PendingOperation*))); +} + +void ChannelRequest::onChanBuilt(Tp::PendingOperation *op) +{ + if (op->isError()) { + warning() << "Failed to build Channel which the ChannelRequest succeeded with," + << "succeeding with NULL channel:" << op->errorName() << ',' << op->errorMessage(); + mPriv->chan.reset(); + } + + emit succeeded(); // API/ABI break TODO: don't + emit succeeded(mPriv->chan); +} + +/** + * \fn void ChannelRequest::failed(const QString &errorName, + * const QString &errorMessage) + * + * Emitted when the channel request has failed. No further + * methods must not be called on it. + * + * \param errorName The name of a D-Bus error. + * \param errorMessage The error message. + * \sa succeeded() + */ + +/** + * \fn void ChannelRequest::succeeded() + * + * \deprecated Use ChannelRequest::succeeded(const ChannelPtr &) instead. + */ + +/** + * \fn void ChannelRequest::succeeded(const Tp::ChannelPtr &channel) + * + * Emitted when the channel request has succeeded. No further + * methods must not be called on it. + * + * The \a channel parameter can be used to observe the channel resulting from the request (e.g. for + * it getting closed). The pointer may be NULL if the Channel Dispatcher implementation is too old. + * Whether a non-NULL channel can be expected can be checked with + * Account::requestsSucceedWithChannel(). + * + * If there is a channel, it will be of the subclass determined by and made ready (or not) according + * to the settings of the ChannelFactory on the Account the request was made through. + * + * \param channel Pointer to a proxy for the resulting channel, if the Channel Dispatcher reported it. + * \sa failed() + */ + +void ChannelRequest::connectNotify(const char *signalName) +{ + if (qstrcmp(signalName, SIGNAL(succeeded())) == 0) { + warning() << "Connecting to deprecated signal ChannelRequest::succeeded()"; + } +} + +/** + * \class ChannelRequestHints + * \ingroup clientchannelrequest + * \headerfile TelepathyQt/channel-request.h <TelepathyQt/ChannelRequestHints> + * + * \brief The ChannelRequestHints class represents a dictionary of metadata + * provided by the channel requester when requesting a channel. + */ + +struct TP_QT_NO_EXPORT ChannelRequestHints::Private : public QSharedData +{ + Private() {} + Private(const QVariantMap &hints) : hints(hints) {} + + QVariantMap hints; +}; + +ChannelRequestHints::ChannelRequestHints() +{ +} + +ChannelRequestHints::ChannelRequestHints(const QVariantMap &hints) + : mPriv(new Private(hints)) +{ +} + +ChannelRequestHints::ChannelRequestHints(const ChannelRequestHints &crh) + : mPriv(crh.mPriv) +{ +} + +ChannelRequestHints::~ChannelRequestHints() +{ +} + +ChannelRequestHints &ChannelRequestHints::operator=(const ChannelRequestHints &other) +{ + if (this == &other) { + return *this; + } + + this->mPriv = other.mPriv; + return *this; +} + +bool ChannelRequestHints::isValid() const +{ + return mPriv.constData() != 0; +} + +bool ChannelRequestHints::hasHint(const QString &reversedDomain, const QString &localName) const +{ + if (!isValid()) { + return false; + } + + const QString qualifiedName = reversedDomain + QLatin1Char('.') + localName; + return mPriv->hints.contains(qualifiedName); +} + +QVariant ChannelRequestHints::hint(const QString &reversedDomain, const QString &localName) const +{ + if (!isValid()) { + return QVariant(); + } + + const QString qualifiedName = reversedDomain + QLatin1Char('.') + localName; + return mPriv->hints.value(qualifiedName); +} + +void ChannelRequestHints::setHint(const QString &reversedDomain, const QString &localName, const QVariant &value) +{ + const QString qualifiedName = reversedDomain + QLatin1Char('.') + localName; + + if (!isValid()) { + mPriv = new Private(); + } + + mPriv->hints.insert(qualifiedName, value); +} + +QVariantMap ChannelRequestHints::allHints() const +{ + return isValid() ? mPriv->hints : QVariantMap(); +} + +} // Tp diff --git a/TelepathyQt/channel-request.h b/TelepathyQt/channel-request.h new file mode 100644 index 00000000..5e42154d --- /dev/null +++ b/TelepathyQt/channel-request.h @@ -0,0 +1,154 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_channel_request_h_HEADER_GUARD_ +#define _TelepathyQt_channel_request_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/_gen/cli-channel-request.h> + +#include <TelepathyQt/Constants> +#include <TelepathyQt/DBus> +#include <TelepathyQt/DBusProxy> +#include <TelepathyQt/Feature> +#include <TelepathyQt/OptionalInterfaceFactory> +#include <TelepathyQt/ReadinessHelper> +#include <TelepathyQt/Types> +#include <TelepathyQt/SharedPtr> + +#include <QSharedDataPointer> +#include <QString> +#include <QStringList> +#include <QVariantMap> + +namespace Tp +{ + +class ChannelRequestHints; +class PendingOperation; + +class TP_QT_EXPORT ChannelRequest : public StatefulDBusProxy, + public OptionalInterfaceFactory<ChannelRequest> +{ + Q_OBJECT + Q_DISABLE_COPY(ChannelRequest) + +public: + static const Feature FeatureCore; + + static ChannelRequestPtr create(const QDBusConnection &bus, + const QString &objectPath, const QVariantMap &immutableProperties, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory); + + static ChannelRequestPtr create(const AccountPtr &account, + const QString &objectPath, const QVariantMap &immutableProperties); + + virtual ~ChannelRequest(); + + AccountPtr account() const; + QDateTime userActionTime() const; + QString preferredHandler() const; + QualifiedPropertyValueMapList requests() const; + ChannelRequestHints hints() const; + + QVariantMap immutableProperties() const; + + PendingOperation *cancel(); + + ChannelPtr channel() const; + +Q_SIGNALS: + void failed(const QString &errorName, const QString &errorMessage); + void succeeded(); // TODO API/ABI break: remove + void succeeded(const Tp::ChannelPtr &channel); + +protected: + ChannelRequest(const QDBusConnection &bus, + const QString &objectPath, const QVariantMap &immutableProperties, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory); + + ChannelRequest(const AccountPtr &account, + const QString &objectPath, const QVariantMap &immutableProperties); + + Client::ChannelRequestInterface *baseInterface() const; + +protected: + // TODO: (API/ABI break) Remove connectNotify + void connectNotify(const char *); + +private Q_SLOTS: + TP_QT_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onAccountReady(Tp::PendingOperation *op); + + TP_QT_NO_EXPORT void onLegacySucceeded(); + TP_QT_NO_EXPORT void onSucceededWithChannel(const QDBusObjectPath &connPath, const QVariantMap &connProps, + const QDBusObjectPath &chanPath, const QVariantMap &chanProps); + TP_QT_NO_EXPORT void onChanBuilt(Tp::PendingOperation *op); + +private: + friend class PendingChannelRequest; + + PendingOperation *proceed(); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +class TP_QT_EXPORT ChannelRequestHints +{ +public: + ChannelRequestHints(); + ChannelRequestHints(const QVariantMap &hints); + ChannelRequestHints(const ChannelRequestHints &other); + ~ChannelRequestHints(); + + ChannelRequestHints &operator=(const ChannelRequestHints &other); + + bool isValid() const; + + bool hasHint(const QString &reversedDomain, const QString &localName) const; + QVariant hint(const QString &reversedDomain, const QString &localName) const; + void setHint(const QString &reversedDomain, const QString &localName, const QVariant &value); + + QVariantMap allHints() const; + +private: + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::ChannelRequestHints); + +#endif diff --git a/TelepathyQt/channel-request.xml b/TelepathyQt/channel-request.xml new file mode 100644 index 00000000..92fc8817 --- /dev/null +++ b/TelepathyQt/channel-request.xml @@ -0,0 +1,9 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Channel Request interface</tp:title> + +<xi:include href="../spec/Channel_Request.xml"/> + +</tp:spec> diff --git a/TelepathyQt/channel.cpp b/TelepathyQt/channel.cpp new file mode 100644 index 00000000..7a6872ae --- /dev/null +++ b/TelepathyQt/channel.cpp @@ -0,0 +1,3596 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/Channel> +#include "TelepathyQt/channel-internal.h" + +#include "TelepathyQt/_gen/cli-channel-body.hpp" +#include "TelepathyQt/_gen/cli-channel.moc.hpp" +#include "TelepathyQt/_gen/channel.moc.hpp" +#include "TelepathyQt/_gen/channel-internal.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include "TelepathyQt/future-internal.h" + +#include <TelepathyQt/ChannelFactory> +#include <TelepathyQt/Connection> +#include <TelepathyQt/ConnectionCapabilities> +#include <TelepathyQt/ConnectionLowlevel> +#include <TelepathyQt/ContactManager> +#include <TelepathyQt/PendingContacts> +#include <TelepathyQt/PendingFailure> +#include <TelepathyQt/PendingOperation> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/PendingSuccess> +#include <TelepathyQt/StreamTubeChannel> +#include <TelepathyQt/ReferencedHandles> +#include <TelepathyQt/Constants> + +#include <QHash> +#include <QQueue> +#include <QSharedData> +#include <QTimer> + +namespace Tp +{ + +using TpFuture::Client::ChannelInterfaceMergeableConferenceInterface; +using TpFuture::Client::ChannelInterfaceSplittableInterface; + +struct TP_QT_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<ContactPtr> &contacts = + QList<ContactPtr>()); + bool fakeGroupInterfaceIfNeeded(); + void setReady(); + + QString groupMemberChangeDetailsTelepathyError( + const GroupMemberChangeDetails &details); + + inline ChannelInterfaceMergeableConferenceInterface *mergeableConferenceInterface( + InterfaceSupportedChecking check = CheckInterfaceSupported) const + { + return parent->optionalInterface<ChannelInterfaceMergeableConferenceInterface>(check); + } + + inline ChannelInterfaceSplittableInterface *splittableInterface( + InterfaceSupportedChecking check = CheckInterfaceSupported) const + { + return parent->optionalInterface<ChannelInterfaceSplittableInterface>(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<void (Private::*)()> 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<GroupMembersChangedInfo *> groupMembersChangedQueue; + GroupMembersChangedInfo *currentGroupMembersChangedInfo; + + // Pending from the MCD signal currently processed, but contacts not yet built + QSet<uint> pendingGroupMembers; + QSet<uint> pendingGroupLocalPendingMembers; + QSet<uint> pendingGroupRemotePendingMembers; + UIntList groupMembersToRemove; + UIntList groupLocalPendingMembersToRemove; + UIntList groupRemotePendingMembersToRemove; + + // Initial members + UIntList groupInitialMembers; + LocalPendingInfoList groupInitialLP; + UIntList groupInitialRP; + + // Current members + QHash<uint, ContactPtr> groupContacts; + QHash<uint, ContactPtr> groupLocalPendingContacts; + QHash<uint, ContactPtr> groupRemotePendingContacts; + + // Stored change info + QHash<uint, GroupMemberChangeDetails> 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<QString, ChannelPtr> conferenceChannels; + QHash<QString, ChannelPtr> conferenceInitialChannels; + QString conferenceInvitationMessage; + QHash<uint, ChannelPtr> conferenceOriginalChannels; + UIntList conferenceInitialInviteeHandles; + Contacts conferenceInitialInviteeContacts; + QQueue<ConferenceChannelRemovedInfo *> conferenceChannelRemovedQueue; + bool buildingConferenceChannelRemovedActorContact; + + static const QString keyActor; +}; + +struct TP_QT_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<uint>(details.value(keyActor))), + reason(qdbus_cast<uint>(details.value(keyChangeReason))), + message(qdbus_cast<QString>(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 TP_QT_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<Client::DBus::PropertiesInterface>()), + 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<uint>() << 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<uint>() << 0, // makesSenseForStatuses + Features() << FeatureCore, // dependsOnFeatures + QStringList() << TP_QT_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<bool>(props[QLatin1String("Requested")]); + initiatorHandle = qdbus_cast<uint>(props[QLatin1String("InitiatorHandle")]); + + if (props.contains(QLatin1String("InitiatorID"))) { + QString initiatorId = qdbus_cast<QString>(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<Client::ChannelInterfaceGroupInterface>(); + 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<Client::ChannelInterfaceConferenceInterface>(); + 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<QString>(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<QStringList>(props[keyInterfaces])); + readinessHelper->setInterfaces(parent->interfaces()); + channelType = qdbus_cast<QString>(props[keyChannelType]); + targetHandle = qdbus_cast<uint>(props[keyTargetHandle]); + targetHandleType = qdbus_cast<uint>(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<QString>(props[keyTargetId]); + + if (targetHandleType == HandleTypeContact) { + connection->lowlevel()->injectContactId(targetHandle, targetId); + } + } + + if (props.contains(keyRequested)) { + requested = qdbus_cast<uint>(props[keyRequested]); + } + + if (props.contains(keyInitiatorHandle)) { + initiatorHandle = qdbus_cast<uint>(props[keyInitiatorHandle]); + } + + if (props.contains(keyInitiatorId)) { + QString initiatorId = qdbus_cast<QString>(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<uint>(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<uint>(props[keyGroupFlags])); + groupHandleOwners = qdbus_cast<HandleOwnerMap>(props[keyHandleOwners]); + + groupInitialMembers = qdbus_cast<UIntList>(props[keyMembers]); + groupInitialLP = qdbus_cast<LocalPendingInfoList>(props[keyLPMembers]); + groupInitialRP = qdbus_cast<UIntList>(props[keyRPMembers]); + + uint propSelfHandle = qdbus_cast<uint>(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<uint>(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<ContactPtr> &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<uint>(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<uint>(info->details.value(keyActor))); + parent->connect(pendingContacts, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(gotConferenceChannelRemovedActorContact(Tp::PendingOperation*))); + } else { + parent->gotConferenceChannelRemovedActorContact(0); + } +} + +struct TP_QT_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 TelepathyQt/channel.h <TelepathyQt/Channel> + * + * \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 TelepathyQt/channel.h <TelepathyQt/Channel> + * + * \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<Channel>(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_QT_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_QT_ERROR_NOT_AVAILABLE, + QLatin1String("Channel::FeatureCore must be ready to leave a channel"), + ChannelPtr(this)); + } + + if (!interfaces().contains(TP_QT_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<ContactPtr> &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<ContactPtr> &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 <code>false</code> 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 <code>false</code> 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: + * <ul> + * <li>The object is ready + * <li>The list returned by interfaces() contains #TP_QT_IFACE_CHANNEL_INTERFACE_GROUP</li> + * <li>The set of flags returned by groupFlags() contains + * #GroupFlagProperties and #GroupFlagChannelSpecificHandles</li> + * </ul> + * + * 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_QT_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_QT_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<ChannelPtr> 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<ChannelPtr> 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<uint, ChannelPtr> 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<QVariantMap> 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<QString> 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<uint, uint> 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<QStringList> 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_QT_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(TP_QT_ERROR_ORPHANED, + QLatin1String("Connection given as the owner of this channel was invalidated")); +} + +void Channel::gotGroupProperties(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> 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<uint> 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<UIntList, UIntList, UIntList> 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<LocalPendingInfoList> 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<uint> 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<PendingContacts *>(op); + + mPriv->buildingContacts = false; + + QList<ContactPtr> 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<uint>(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<uint>(details.value(QLatin1String("actor")))), + details); + } + + HandleIdentifierMap contactIds = qdbus_cast<HandleIdentifierMap>( + 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<QVariantMap> 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<ObjectPathList>(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<ObjectPathList>(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<UIntList>(props[QLatin1String("InitialInviteeHandles")]); + QStringList conferenceInitialInviteeIds = + qdbus_cast<QStringList>(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<QString>(props[QLatin1String("InvitationMessage")]); + + ChannelOriginatorMap originalChannels = qdbus_cast<ChannelOriginatorMap>( + 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<PendingContacts *>(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<HandleIdentifierMap>( + 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<PendingContacts *>(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<uint, ChannelPtr>::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 diff --git a/TelepathyQt/channel.h b/TelepathyQt/channel.h new file mode 100644 index 00000000..eb7512aa --- /dev/null +++ b/TelepathyQt/channel.h @@ -0,0 +1,256 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_channel_h_HEADER_GUARD_ +#define _TelepathyQt_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/_gen/cli-channel.h> + +#include <TelepathyQt/Constants> +#include <TelepathyQt/DBus> +#include <TelepathyQt/DBusProxy> +#include <TelepathyQt/OptionalInterfaceFactory> +#include <TelepathyQt/ReadinessHelper> +#include <TelepathyQt/SharedPtr> +#include <TelepathyQt/Types> + +#include <QSet> +#include <QSharedDataPointer> +#include <QVariantMap> + +namespace Tp +{ + +class Connection; +class PendingOperation; +class PendingReady; + +class TP_QT_EXPORT Channel : public StatefulDBusProxy, + public OptionalInterfaceFactory<Channel> +{ + Q_OBJECT + Q_DISABLE_COPY(Channel) + +public: + static const Feature FeatureCore; + static const Feature FeatureConferenceInitialInviteeContacts; + + static ChannelPtr create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties); + + virtual ~Channel(); + + ConnectionPtr connection() const; + + QVariantMap immutableProperties() const; + + QString channelType() const; + + HandleType targetHandleType() const; + uint targetHandle() const; + QString targetId() const; + ContactPtr targetContact() const; + + bool isRequested() const; + ContactPtr initiatorContact() const; + + PendingOperation *requestClose(); + PendingOperation *requestLeave(const QString &message = QString(), + ChannelGroupChangeReason reason = ChannelGroupChangeReasonNone); + + ChannelGroupFlags groupFlags() const; + + bool groupCanAddContacts() const; + bool groupCanAddContactsWithMessage() const; + bool groupCanAcceptContactsWithMessage() const; + PendingOperation *groupAddContacts(const QList<ContactPtr> &contacts, + const QString &message = QString()); + bool groupCanRescindContacts() const; + bool groupCanRescindContactsWithMessage() const; + bool groupCanRemoveContacts() const; + bool groupCanRemoveContactsWithMessage() const; + bool groupCanRejectContactsWithMessage() const; + bool groupCanDepartWithMessage() const; + PendingOperation *groupRemoveContacts(const QList<ContactPtr> &contacts, + const QString &message = QString(), + ChannelGroupChangeReason reason = ChannelGroupChangeReasonNone); + + /** + * TODO: have parameters on these like + * Contacts groupContacts(bool includeSelfContact = true); + */ + Contacts groupContacts() const; + Contacts groupLocalPendingContacts() const; + Contacts groupRemotePendingContacts() const; + + class GroupMemberChangeDetails + { + public: + GroupMemberChangeDetails(); + GroupMemberChangeDetails(const GroupMemberChangeDetails &other); + ~GroupMemberChangeDetails(); + + GroupMemberChangeDetails &operator=(const GroupMemberChangeDetails &other); + + bool isValid() const { return mPriv.constData() != 0; } + + bool hasActor() const; + ContactPtr actor() const; + + bool hasReason() const { return allDetails().contains(QLatin1String("change-reason")); } + ChannelGroupChangeReason reason() const { return (ChannelGroupChangeReason) qdbus_cast<uint>(allDetails().value(QLatin1String("change-reason"))); } + + bool hasMessage() const { return allDetails().contains(QLatin1String("message")); } + QString message () const { return qdbus_cast<QString>(allDetails().value(QLatin1String("message"))); } + + bool hasError() const { return allDetails().contains(QLatin1String("error")); } + QString error() const { return qdbus_cast<QString>(allDetails().value(QLatin1String("error"))); } + + bool hasDebugMessage() const { return allDetails().contains(QLatin1String("debug-message")); } + QString debugMessage() const { return qdbus_cast<QString>(allDetails().value(QLatin1String("debug-message"))); } + + QVariantMap allDetails() const; + + private: + friend class Channel; + friend class Contact; + friend class ContactManager; + + TP_QT_NO_EXPORT GroupMemberChangeDetails(const ContactPtr &actor, const QVariantMap &details); + + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; + }; + + GroupMemberChangeDetails groupLocalPendingContactChangeInfo(const ContactPtr &contact) const; + GroupMemberChangeDetails groupSelfContactRemoveInfo() const; + + bool groupAreHandleOwnersAvailable() const; + HandleOwnerMap groupHandleOwners() const; + + bool groupIsSelfContactTracked() const; + ContactPtr groupSelfContact() const; + + bool isConference() const; + Contacts conferenceInitialInviteeContacts() const; + QList<ChannelPtr> conferenceChannels() const; + QList<ChannelPtr> conferenceInitialChannels() const; + QHash<uint, ChannelPtr> conferenceOriginalChannels() const; + + bool supportsConferenceMerging() const; + PendingOperation *conferenceMergeChannel(const ChannelPtr &channel); + + bool supportsConferenceSplitting() const; + PendingOperation *conferenceSplitChannel(); + +Q_SIGNALS: + void groupFlagsChanged(Tp::ChannelGroupFlags flags, + Tp::ChannelGroupFlags added, Tp::ChannelGroupFlags removed); + + void groupCanAddContactsChanged(bool canAddContacts); + void groupCanRemoveContactsChanged(bool canRemoveContacts); + void groupCanRescindContactsChanged(bool canRescindContacts); + + void groupMembersChanged( + const Tp::Contacts &groupMembersAdded, + const Tp::Contacts &groupLocalPendingMembersAdded, + const Tp::Contacts &groupRemotePendingMembersAdded, + const Tp::Contacts &groupMembersRemoved, + const Tp::Channel::GroupMemberChangeDetails &details); + + void groupHandleOwnersChanged(const Tp::HandleOwnerMap &owners, + const Tp::UIntList &added, const Tp::UIntList &removed); + + void groupSelfContactChanged(); + + void conferenceChannelMerged(const Tp::ChannelPtr &channel); + void conferenceChannelRemoved(const Tp::ChannelPtr &channel, + const Tp::Channel::GroupMemberChangeDetails &details); + +protected: + Channel(const ConnectionPtr &connection,const QString &objectPath, + const QVariantMap &immutableProperties, const Feature &coreFeature); + + Client::ChannelInterface *baseInterface() const; + + bool groupSelfHandleIsLocalPending() const; + +protected Q_SLOTS: + PendingOperation *groupAddSelfHandle(); + +private Q_SLOTS: + TP_QT_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotChannelType(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotHandle(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotInterfaces(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onClosed(); + + TP_QT_NO_EXPORT void onConnectionReady(Tp::PendingOperation *op); + TP_QT_NO_EXPORT void onConnectionInvalidated(); + + TP_QT_NO_EXPORT void gotGroupProperties(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotGroupFlags(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotAllMembers(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotLocalPendingMembersWithInfo(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotSelfHandle(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotContacts(Tp::PendingOperation *op); + + TP_QT_NO_EXPORT void onGroupFlagsChanged(uint added, uint removed); + TP_QT_NO_EXPORT void onMembersChanged(const QString &message, + const Tp::UIntList &added, const Tp::UIntList &removed, + const Tp::UIntList &localPending, const Tp::UIntList &remotePending, + uint actor, uint reason); + TP_QT_NO_EXPORT void onMembersChangedDetailed( + const Tp::UIntList &added, const Tp::UIntList &removed, + const Tp::UIntList &localPending, const Tp::UIntList &remotePending, + const QVariantMap &details); + TP_QT_NO_EXPORT void onHandleOwnersChanged(const Tp::HandleOwnerMap &added, const Tp::UIntList &removed); + TP_QT_NO_EXPORT void onSelfHandleChanged(uint selfHandle); + + TP_QT_NO_EXPORT void gotConferenceProperties(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotConferenceInitialInviteeContacts(Tp::PendingOperation *op); + TP_QT_NO_EXPORT void onConferenceChannelMerged(const QDBusObjectPath &channel, uint channelSpecificHandle, + const QVariantMap &properties); + TP_QT_NO_EXPORT void onConferenceChannelMerged(const QDBusObjectPath &channel); + TP_QT_NO_EXPORT void onConferenceChannelRemoved(const QDBusObjectPath &channel, const QVariantMap &details); + TP_QT_NO_EXPORT void onConferenceChannelRemoved(const QDBusObjectPath &channel); + TP_QT_NO_EXPORT void gotConferenceChannelRemovedActorContact(Tp::PendingOperation *op); + +private: + class PendingLeave; + friend class PendingLeave; + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::Channel::GroupMemberChangeDetails); + +#endif diff --git a/TelepathyQt/channel.xml b/TelepathyQt/channel.xml new file mode 100644 index 00000000..c850462e --- /dev/null +++ b/TelepathyQt/channel.xml @@ -0,0 +1,37 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Channel interfaces</tp:title> + +<xi:include href="../spec/Channel.xml"/> + +<xi:include href="../spec/Channel_Type_Contact_List.xml"/> +<xi:include href="../spec/Channel_Type_Contact_Search.xml"/> +<xi:include href="../spec/Channel_Type_DBus_Tube.xml"/> +<xi:include href="../spec/Channel_Type_File_Transfer.xml"/> +<xi:include href="../spec/Channel_Type_Room_List.xml"/> +<xi:include href="../spec/Channel_Type_Server_Authentication.xml"/> +<xi:include href="../spec/Channel_Type_Server_TLS_Connection.xml"/> +<xi:include href="../spec/Channel_Type_Streamed_Media.xml"/> +<xi:include href="../spec/Channel_Type_Stream_Tube.xml"/> +<xi:include href="../spec/Channel_Type_Text.xml"/> +<xi:include href="../spec/Channel_Type_Tubes.xml"/> + +<xi:include href="../spec/Channel_Interface_Anonymity.xml"/> +<xi:include href="../spec/Channel_Interface_Call_State.xml"/> +<xi:include href="../spec/Channel_Interface_Chat_State.xml"/> +<xi:include href="../spec/Channel_Interface_Conference.xml"/> +<xi:include href="../spec/Channel_Interface_Destroyable.xml"/> +<xi:include href="../spec/Channel_Interface_DTMF.xml"/> +<xi:include href="../spec/Channel_Interface_Group.xml"/> +<xi:include href="../spec/Channel_Interface_Hold.xml"/> +<xi:include href="../spec/Channel_Interface_Media_Signalling.xml"/> +<xi:include href="../spec/Channel_Interface_Messages.xml"/> +<xi:include href="../spec/Channel_Interface_Password.xml"/> +<xi:include href="../spec/Channel_Interface_SASL_Authentication.xml"/> +<xi:include href="../spec/Channel_Interface_Securable.xml"/> +<xi:include href="../spec/Channel_Interface_Service_Point.xml"/> +<xi:include href="../spec/Channel_Interface_Tube.xml"/> + +</tp:spec> diff --git a/TelepathyQt/client-registrar-internal.h b/TelepathyQt/client-registrar-internal.h new file mode 100644 index 00000000..3f0b4ec3 --- /dev/null +++ b/TelepathyQt/client-registrar-internal.h @@ -0,0 +1,365 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_client_registrar_internal_h_HEADER_GUARD_ +#define _TelepathyQt_client_registrar_internal_h_HEADER_GUARD_ + +#include <QtCore/QObject> +#include <QtDBus/QtDBus> + +#include <TelepathyQt/AbstractClientHandler> +#include <TelepathyQt/Channel> +#include <TelepathyQt/ChannelClassSpecList> +#include <TelepathyQt/Types> + +#include "TelepathyQt/fake-handler-manager-internal.h" + +namespace Tp +{ + +class PendingOperation; + +class TP_QT_NO_EXPORT ClientAdaptor : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Client") + Q_CLASSINFO("D-Bus Introspection", "" +" <interface name=\"org.freedesktop.Telepathy.Client\" >\n" +" <property name=\"Interfaces\" type=\"as\" access=\"read\" />\n" +" </interface>\n" + "") + + Q_PROPERTY(QStringList Interfaces READ Interfaces) + +public: + ClientAdaptor(ClientRegistrar *registrar, const QStringList &interfaces, QObject *parent); + virtual ~ClientAdaptor(); + + inline const ClientRegistrar *registrar() const + { + return mRegistrar; + } + +public: // Properties + inline QStringList Interfaces() const + { + return mInterfaces; + } + +private: + ClientRegistrar *mRegistrar; + QStringList mInterfaces; +}; + +class TP_QT_NO_EXPORT ClientObserverAdaptor : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Client.Observer") + Q_CLASSINFO("D-Bus Introspection", "" +" <interface name=\"org.freedesktop.Telepathy.Client.Observer\" >\n" +" <property name=\"ObserverChannelFilter\" type=\"aa{sv}\" access=\"read\" />\n" +" <property name=\"Recover\" type=\"b\" access=\"read\" />\n" +" <method name=\"ObserveChannels\" >\n" +" <arg name=\"Account\" type=\"o\" direction=\"in\" />\n" +" <arg name=\"Connection\" type=\"o\" direction=\"in\" />\n" +" <arg name=\"Channels\" type=\"a(oa{sv})\" direction=\"in\" />\n" +" <arg name=\"Dispatch_Operation\" type=\"o\" direction=\"in\" />\n" +" <arg name=\"Requests_Satisfied\" type=\"ao\" direction=\"in\" />\n" +" <arg name=\"Observer_Info\" type=\"a{sv}\" direction=\"in\" />\n" +" </method>\n" +" </interface>\n" + "") + + Q_PROPERTY(Tp::ChannelClassList ObserverChannelFilter READ ObserverChannelFilter) + Q_PROPERTY(bool Recover READ Recover) + +public: + ClientObserverAdaptor(ClientRegistrar *registrar, + AbstractClientObserver *client, + QObject *parent); + virtual ~ClientObserverAdaptor(); + + inline const ClientRegistrar *registrar() const + { + return mRegistrar; + } + +public: // Properties + inline Tp::ChannelClassList ObserverChannelFilter() const + { + return mClient->observerFilter().bareClasses(); + } + + inline bool Recover() const + { + return mClient->shouldRecover(); + } + +public Q_SLOTS: // Methods + void ObserveChannels(const QDBusObjectPath &account, + const QDBusObjectPath &connection, + const Tp::ChannelDetailsList &channels, + const QDBusObjectPath &dispatchOperation, + const Tp::ObjectPathList &requestsSatisfied, + const QVariantMap &observerInfo, + const QDBusMessage &message); + +private Q_SLOTS: + void onReadyOpFinished(Tp::PendingOperation *); + +private: + struct InvocationData : RefCounted + { + InvocationData() : readyOp(0) {} + + PendingOperation *readyOp; + QString error, message; + + MethodInvocationContextPtr<> ctx; + AccountPtr acc; + ConnectionPtr conn; + QList<ChannelPtr> chans; + ChannelDispatchOperationPtr dispatchOp; + QList<ChannelRequestPtr> chanReqs; + AbstractClientObserver::ObserverInfo observerInfo; + }; + QLinkedList<SharedPtr<InvocationData> > mInvocations; + + ClientRegistrar *mRegistrar; + QDBusConnection mBus; + AbstractClientObserver *mClient; +}; + +class TP_QT_NO_EXPORT ClientApproverAdaptor : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Client.Approver") + Q_CLASSINFO("D-Bus Introspection", "" +" <interface name=\"org.freedesktop.Telepathy.Client.Approver\" >\n" +" <property name=\"ApproverChannelFilter\" type=\"aa{sv}\" access=\"read\" />\n" +" <method name=\"AddDispatchOperation\" >\n" +" <arg name=\"Channels\" type=\"a(oa{sv})\" direction=\"in\" />\n" +" <arg name=\"Dispatch_Operation\" type=\"o\" direction=\"in\" />\n" +" <arg name=\"Properties\" type=\"a{sv}\" direction=\"in\" />\n" +" </method>\n" +" </interface>\n" + "") + + Q_PROPERTY(Tp::ChannelClassList ApproverChannelFilter READ ApproverChannelFilter) + +public: + ClientApproverAdaptor(ClientRegistrar *registrar, + AbstractClientApprover *client, + QObject *parent); + virtual ~ClientApproverAdaptor(); + + inline const ClientRegistrar *registrar() const + { + return mRegistrar; + } + +public: // Properties + inline Tp::ChannelClassList ApproverChannelFilter() const + { + return mClient->approverFilter().bareClasses(); + } + +public Q_SLOTS: // Methods + void AddDispatchOperation(const Tp::ChannelDetailsList &channels, + const QDBusObjectPath &dispatchOperation, + const QVariantMap &properties, + const QDBusMessage &message); + +private Q_SLOTS: + void onReadyOpFinished(Tp::PendingOperation *); + +private: + struct InvocationData : RefCounted + { + InvocationData() : readyOp(0) {} + + PendingOperation *readyOp; + QString error, message; + + MethodInvocationContextPtr<> ctx; + QList<ChannelPtr> chans; + ChannelDispatchOperationPtr dispatchOp; + }; + QLinkedList<SharedPtr<InvocationData> > mInvocations; + +private: + ClientRegistrar *mRegistrar; + QDBusConnection mBus; + AbstractClientApprover *mClient; +}; + +class TP_QT_NO_EXPORT ClientHandlerAdaptor : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Client.Handler") + Q_CLASSINFO("D-Bus Introspection", "" +" <interface name=\"org.freedesktop.Telepathy.Client.Handler\" >\n" +" <property name=\"HandlerChannelFilter\" type=\"aa{sv}\" access=\"read\" />\n" +" <property name=\"BypassApproval\" type=\"b\" access=\"read\" />\n" +" <property name=\"Capabilities\" type=\"as\" access=\"read\" />\n" +" <property name=\"HandledChannels\" type=\"ao\" access=\"read\" />\n" +" <method name=\"HandleChannels\" >\n" +" <arg name=\"Account\" type=\"o\" direction=\"in\" />\n" +" <arg name=\"Connection\" type=\"o\" direction=\"in\" />\n" +" <arg name=\"Channels\" type=\"a(oa{sv})\" direction=\"in\" />\n" +" <arg name=\"Requests_Satisfied\" type=\"ao\" direction=\"in\" />\n" +" <arg name=\"User_Action_Time\" type=\"t\" direction=\"in\" />\n" +" <arg name=\"Handler_Info\" type=\"a{sv}\" direction=\"in\" />\n" +" </method>\n" +" </interface>\n" + "") + + Q_PROPERTY(Tp::ChannelClassList HandlerChannelFilter READ HandlerChannelFilter) + Q_PROPERTY(bool BypassApproval READ BypassApproval) + Q_PROPERTY(QStringList Capabilities READ Capabilities) + Q_PROPERTY(Tp::ObjectPathList HandledChannels READ HandledChannels) + +public: + ClientHandlerAdaptor(ClientRegistrar *registrar, + AbstractClientHandler *client, + QObject *parent); + virtual ~ClientHandlerAdaptor(); + + inline const ClientRegistrar *registrar() const + { + return mRegistrar; + } + +public: // Properties + inline Tp::ChannelClassList HandlerChannelFilter() const + { + return mClient->handlerFilter().bareClasses(); + } + + inline bool BypassApproval() const + { + return mClient->bypassApproval(); + } + + inline QStringList Capabilities() const + { + return mClient->handlerCapabilities().allTokens(); + } + + inline Tp::ObjectPathList HandledChannels() const + { + return FakeHandlerManager::instance()->handledChannels(mBus); + } + +public Q_SLOTS: // Methods + void HandleChannels(const QDBusObjectPath &account, + const QDBusObjectPath &connection, + const Tp::ChannelDetailsList &channels, + const Tp::ObjectPathList &requestsSatisfied, + qulonglong userActionTime, + const QVariantMap &handlerInfo, + const QDBusMessage &message); + +private Q_SLOTS: + void onReadyOpFinished(Tp::PendingOperation *); + +private: + struct InvocationData : RefCounted + { + InvocationData() : readyOp(0) {} + + PendingOperation *readyOp; + QString error, message; + + MethodInvocationContextPtr<> ctx; + AccountPtr acc; + ConnectionPtr conn; + QList<ChannelPtr> chans; + QList<ChannelRequestPtr> chanReqs; + QDateTime time; + AbstractClientHandler::HandlerInfo handlerInfo; + }; + QLinkedList<SharedPtr<InvocationData> > mInvocations; + +private: + static void onContextFinished(const MethodInvocationContextPtr<> &context, + const QList<ChannelPtr> &channels, ClientHandlerAdaptor *self); + + ClientRegistrar *mRegistrar; + QDBusConnection mBus; + AbstractClientHandler *mClient; + + static QHash<QPair<QString, QString>, QList<ClientHandlerAdaptor *> > mAdaptorsForConnection; +}; + +class TP_QT_NO_EXPORT ClientHandlerRequestsAdaptor : public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.freedesktop.Telepathy.Client.Interface.Requests") + Q_CLASSINFO("D-Bus Introspection", "" +" <interface name=\"org.freedesktop.Telepathy.Client.Interface.Requests\" >\n" +" <method name=\"AddRequest\" >\n" +" <arg name=\"Request\" type=\"o\" direction=\"in\" />\n" +" <arg name=\"Properties\" type=\"a{sv}\" direction=\"in\" />\n" +" </method>\n" +" <method name=\"RemoveRequest\" >\n" +" <arg name=\"Request\" type=\"o\" direction=\"in\" />\n" +" <arg name=\"Error\" type=\"s\" direction=\"in\" />\n" +" <arg name=\"Message\" type=\"s\" direction=\"in\" />\n" +" </method>\n" +" </interface>\n" + "") + +public: + ClientHandlerRequestsAdaptor(ClientRegistrar *registrar, + AbstractClientHandler *client, + QObject *parent); + virtual ~ClientHandlerRequestsAdaptor(); + + inline const ClientRegistrar *registrar() const + { + return mRegistrar; + } + +public Q_SLOTS: // Methods + void AddRequest(const QDBusObjectPath &request, + const QVariantMap &requestProperties, + const QDBusMessage &message); + void RemoveRequest(const QDBusObjectPath &request, + const QString &errorName, const QString &errorMessage, + const QDBusMessage &message); + +private: + ClientRegistrar *mRegistrar; + QDBusConnection mBus; + AbstractClientHandler *mClient; +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::ClientAdaptor*) +Q_DECLARE_METATYPE(Tp::ClientApproverAdaptor*) +Q_DECLARE_METATYPE(Tp::ClientHandlerAdaptor*) +Q_DECLARE_METATYPE(Tp::ClientHandlerRequestsAdaptor*) +Q_DECLARE_METATYPE(Tp::ClientObserverAdaptor*) + +#endif diff --git a/TelepathyQt/client-registrar.cpp b/TelepathyQt/client-registrar.cpp new file mode 100644 index 00000000..1c6b7609 --- /dev/null +++ b/TelepathyQt/client-registrar.cpp @@ -0,0 +1,1038 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ClientRegistrar> +#include "TelepathyQt/client-registrar-internal.h" + +#include "TelepathyQt/_gen/client-registrar.moc.hpp" +#include "TelepathyQt/_gen/client-registrar-internal.moc.hpp" + +#include "TelepathyQt/channel-factory.h" +#include "TelepathyQt/debug-internal.h" +#include "TelepathyQt/request-temporary-handler-internal.h" + +#include <TelepathyQt/Account> +#include <TelepathyQt/AccountManager> +#include <TelepathyQt/Channel> +#include <TelepathyQt/ChannelDispatchOperation> +#include <TelepathyQt/ChannelRequest> +#include <TelepathyQt/Connection> +#include <TelepathyQt/MethodInvocationContext> +#include <TelepathyQt/PendingComposite> +#include <TelepathyQt/PendingReady> + +namespace Tp +{ + +class HandleChannelsInvocationContext : public MethodInvocationContext<> +{ + Q_DISABLE_COPY(HandleChannelsInvocationContext) + +public: + typedef void (*FinishedCb)(const MethodInvocationContextPtr<> &context, + const QList<ChannelPtr> &channels, + void *data); + + static MethodInvocationContextPtr<> create(const QDBusConnection &bus, + const QDBusMessage &message, const QList<ChannelPtr> &channels, + FinishedCb finishedCb, void *finishedCbData) + { + return SharedPtr<MethodInvocationContext<> >( + new HandleChannelsInvocationContext(bus, message, channels, + finishedCb, finishedCbData)); + } + +private: + HandleChannelsInvocationContext(const QDBusConnection &connection, + const QDBusMessage &message, const QList<ChannelPtr> &channels, + FinishedCb finishedCb, void *finishedCbData) + : MethodInvocationContext<>(connection, message), + mChannels(channels), + mFinishedCb(finishedCb), + mFinishedCbData(finishedCbData) + { + } + + void onFinished() + { + if (mFinishedCb) { + mFinishedCb(MethodInvocationContextPtr<>(this), mChannels, mFinishedCbData); + } + } + + QList<ChannelPtr> mChannels; + FinishedCb mFinishedCb; + void *mFinishedCbData; +}; + +ClientAdaptor::ClientAdaptor(ClientRegistrar *registrar, const QStringList &interfaces, + QObject *parent) + : QDBusAbstractAdaptor(parent), + mRegistrar(registrar), + mInterfaces(interfaces) +{ +} + +ClientAdaptor::~ClientAdaptor() +{ +} + +ClientObserverAdaptor::ClientObserverAdaptor(ClientRegistrar *registrar, + AbstractClientObserver *client, + QObject *parent) + : QDBusAbstractAdaptor(parent), + mRegistrar(registrar), + mBus(registrar->dbusConnection()), + mClient(client) +{ +} + +ClientObserverAdaptor::~ClientObserverAdaptor() +{ +} + +void ClientObserverAdaptor::ObserveChannels(const QDBusObjectPath &accountPath, + const QDBusObjectPath &connectionPath, + const Tp::ChannelDetailsList &channelDetailsList, + const QDBusObjectPath &dispatchOperationPath, + const Tp::ObjectPathList &requestsSatisfied, + const QVariantMap &observerInfo, + const QDBusMessage &message) +{ + debug() << "ObserveChannels: account:" << accountPath.path() << + ", connection:" << connectionPath.path(); + + AccountFactoryConstPtr accFactory = mRegistrar->accountFactory(); + ConnectionFactoryConstPtr connFactory = mRegistrar->connectionFactory(); + ChannelFactoryConstPtr chanFactory = mRegistrar->channelFactory(); + ContactFactoryConstPtr contactFactory = mRegistrar->contactFactory(); + + SharedPtr<InvocationData> invocation(new InvocationData()); + + QList<PendingOperation *> readyOps; + + PendingReady *accReady = accFactory->proxy(TP_QT_ACCOUNT_MANAGER_BUS_NAME, + accountPath.path(), + connFactory, + chanFactory, + contactFactory); + invocation->acc = AccountPtr::qObjectCast(accReady->proxy()); + readyOps.append(accReady); + + QString connectionBusName = connectionPath.path().mid(1).replace( + QLatin1String("/"), QLatin1String(".")); + PendingReady *connReady = connFactory->proxy(connectionBusName, connectionPath.path(), + chanFactory, contactFactory); + invocation->conn = ConnectionPtr::qObjectCast(connReady->proxy()); + readyOps.append(connReady); + + foreach (const ChannelDetails &channelDetails, channelDetailsList) { + PendingReady *chanReady = chanFactory->proxy(invocation->conn, + channelDetails.channel.path(), channelDetails.properties); + ChannelPtr channel = ChannelPtr::qObjectCast(chanReady->proxy()); + invocation->chans.append(channel); + readyOps.append(chanReady); + } + + // Yes, we don't give the choice of making CDO and CR ready or not - however, readifying them is + // 0-1 D-Bus calls each, for CR mostly 0 - and their constructors start making them ready + // automatically, so we wouldn't save any D-Bus traffic anyway + + if (!dispatchOperationPath.path().isEmpty() && dispatchOperationPath.path() != QLatin1String("/")) { + QVariantMap props; + + // TODO: push to tp spec having all of the CDO immutable props be contained in observerInfo + // so we don't have to introspect the CDO either - then we can pretty much do: + // + // props = qdbus_cast<QVariantMap>( + // observerInfo.value(QLatin1String("dispatch-operation-properties"))); + // + // Currently something like the following can be used for testing the CDO "we've got + // everything we need" codepath: + // + // props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Account"), + // QVariant::fromValue(QDBusObjectPath(accountPath.path()))); + // props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Connection"), + // QVariant::fromValue(QDBusObjectPath(connectionPath.path()))); + // props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Interfaces"), + // QVariant::fromValue(QStringList())); + // props.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".PossibleHandlers"), + // QVariant::fromValue(QStringList())); + + invocation->dispatchOp = ChannelDispatchOperation::create(mBus, dispatchOperationPath.path(), + props, + invocation->chans, + accFactory, + connFactory, + chanFactory, + contactFactory); + readyOps.append(invocation->dispatchOp->becomeReady()); + } + + invocation->observerInfo = AbstractClientObserver::ObserverInfo(observerInfo); + + ObjectImmutablePropertiesMap reqPropsMap = qdbus_cast<ObjectImmutablePropertiesMap>( + observerInfo.value(QLatin1String("request-properties"))); + foreach (const QDBusObjectPath &reqPath, requestsSatisfied) { + ChannelRequestPtr channelRequest = ChannelRequest::create(invocation->acc, + reqPath.path(), reqPropsMap.value(reqPath)); + invocation->chanReqs.append(channelRequest); + readyOps.append(channelRequest->becomeReady()); + } + + invocation->ctx = MethodInvocationContextPtr<>(new MethodInvocationContext<>(mBus, message)); + + invocation->readyOp = new PendingComposite(readyOps, invocation->ctx); + connect(invocation->readyOp, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onReadyOpFinished(Tp::PendingOperation*))); + + mInvocations.append(invocation); + + debug() << "Preparing proxies for ObserveChannels of" << channelDetailsList.size() << "channels" + << "for client" << mClient; +} + +void ClientObserverAdaptor::onReadyOpFinished(Tp::PendingOperation *op) +{ + Q_ASSERT(!mInvocations.isEmpty()); + Q_ASSERT(op->isFinished()); + + for (QLinkedList<SharedPtr<InvocationData> >::iterator i = mInvocations.begin(); + i != mInvocations.end(); ++i) { + if ((*i)->readyOp != op) { + continue; + } + + (*i)->readyOp = 0; + + if (op->isError()) { + warning() << "Preparing proxies for ObserveChannels failed with" << op->errorName() + << op->errorMessage(); + (*i)->error = op->errorName(); + (*i)->message = op->errorMessage(); + } + + break; + } + + while (!mInvocations.isEmpty() && !mInvocations.first()->readyOp) { + SharedPtr<InvocationData> invocation = mInvocations.takeFirst(); + + if (!invocation->error.isEmpty()) { + // We guarantee that the proxies were ready - so we can't invoke the client if they + // weren't made ready successfully. Fix the introspection code if this happens :) + invocation->ctx->setFinishedWithError(invocation->error, invocation->message); + continue; + } + + debug() << "Invoking application observeChannels with" << invocation->chans.size() + << "channels on" << mClient; + + mClient->observeChannels(invocation->ctx, invocation->acc, invocation->conn, + invocation->chans, invocation->dispatchOp, invocation->chanReqs, + invocation->observerInfo); + } +} + +ClientApproverAdaptor::ClientApproverAdaptor(ClientRegistrar *registrar, + AbstractClientApprover *client, + QObject *parent) + : QDBusAbstractAdaptor(parent), + mRegistrar(registrar), + mBus(registrar->dbusConnection()), + mClient(client) +{ +} + +ClientApproverAdaptor::~ClientApproverAdaptor() +{ +} + +void ClientApproverAdaptor::AddDispatchOperation(const Tp::ChannelDetailsList &channelDetailsList, + const QDBusObjectPath &dispatchOperationPath, + const QVariantMap &properties, + const QDBusMessage &message) +{ + AccountFactoryConstPtr accFactory = mRegistrar->accountFactory(); + ConnectionFactoryConstPtr connFactory = mRegistrar->connectionFactory(); + ChannelFactoryConstPtr chanFactory = mRegistrar->channelFactory(); + ContactFactoryConstPtr contactFactory = mRegistrar->contactFactory(); + + QList<PendingOperation *> readyOps; + + QDBusObjectPath connectionPath = qdbus_cast<QDBusObjectPath>( + properties.value( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCH_OPERATION ".Connection"))); + debug() << "addDispatchOperation: connection:" << connectionPath.path(); + QString connectionBusName = connectionPath.path().mid(1).replace( + QLatin1String("/"), QLatin1String(".")); + PendingReady *connReady = connFactory->proxy(connectionBusName, connectionPath.path(), chanFactory, + contactFactory); + ConnectionPtr connection = ConnectionPtr::qObjectCast(connReady->proxy()); + readyOps.append(connReady); + + SharedPtr<InvocationData> invocation(new InvocationData); + + foreach (const ChannelDetails &channelDetails, channelDetailsList) { + PendingReady *chanReady = chanFactory->proxy(connection, channelDetails.channel.path(), + channelDetails.properties); + invocation->chans.append(ChannelPtr::qObjectCast(chanReady->proxy())); + readyOps.append(chanReady); + } + + invocation->dispatchOp = ChannelDispatchOperation::create(mBus, + dispatchOperationPath.path(), properties, invocation->chans, accFactory, connFactory, + chanFactory, contactFactory); + readyOps.append(invocation->dispatchOp->becomeReady()); + + invocation->ctx = MethodInvocationContextPtr<>(new MethodInvocationContext<>(mBus, message)); + + invocation->readyOp = new PendingComposite(readyOps, invocation->ctx); + connect(invocation->readyOp, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onReadyOpFinished(Tp::PendingOperation*))); + + mInvocations.append(invocation); +} + +void ClientApproverAdaptor::onReadyOpFinished(Tp::PendingOperation *op) +{ + Q_ASSERT(!mInvocations.isEmpty()); + Q_ASSERT(op->isFinished()); + + for (QLinkedList<SharedPtr<InvocationData> >::iterator i = mInvocations.begin(); + i != mInvocations.end(); ++i) { + if ((*i)->readyOp != op) { + continue; + } + + (*i)->readyOp = 0; + + if (op->isError()) { + warning() << "Preparing proxies for AddDispatchOperation failed with" << op->errorName() + << op->errorMessage(); + (*i)->error = op->errorName(); + (*i)->message = op->errorMessage(); + } + + break; + } + + while (!mInvocations.isEmpty() && !mInvocations.first()->readyOp) { + SharedPtr<InvocationData> invocation = mInvocations.takeFirst(); + + if (!invocation->error.isEmpty()) { + // We guarantee that the proxies were ready - so we can't invoke the client if they + // weren't made ready successfully. Fix the introspection code if this happens :) + invocation->ctx->setFinishedWithError(invocation->error, invocation->message); + continue; + } + + debug() << "Invoking application addDispatchOperation with CDO" + << invocation->dispatchOp->objectPath() << "on" << mClient; + + mClient->addDispatchOperation(invocation->ctx, invocation->dispatchOp); + } +} + +QHash<QPair<QString, QString>, QList<ClientHandlerAdaptor *> > ClientHandlerAdaptor::mAdaptorsForConnection; + +ClientHandlerAdaptor::ClientHandlerAdaptor(ClientRegistrar *registrar, + AbstractClientHandler *client, + QObject *parent) + : QDBusAbstractAdaptor(parent), + mRegistrar(registrar), + mBus(registrar->dbusConnection()), + mClient(client) +{ + QList<ClientHandlerAdaptor *> &handlerAdaptors = + mAdaptorsForConnection[qMakePair(mBus.name(), mBus.baseService())]; + handlerAdaptors.append(this); +} + +ClientHandlerAdaptor::~ClientHandlerAdaptor() +{ + QPair<QString, QString> busId = qMakePair(mBus.name(), mBus.baseService()); + QList<ClientHandlerAdaptor *> &handlerAdaptors = + mAdaptorsForConnection[busId]; + handlerAdaptors.removeOne(this); + if (handlerAdaptors.isEmpty()) { + mAdaptorsForConnection.remove(busId); + } +} + +void ClientHandlerAdaptor::HandleChannels(const QDBusObjectPath &accountPath, + const QDBusObjectPath &connectionPath, + const Tp::ChannelDetailsList &channelDetailsList, + const Tp::ObjectPathList &requestsSatisfied, + qulonglong userActionTime_t, + const QVariantMap &handlerInfo, + const QDBusMessage &message) +{ + debug() << "HandleChannels: account:" << accountPath.path() << + ", connection:" << connectionPath.path(); + + AccountFactoryConstPtr accFactory = mRegistrar->accountFactory(); + ConnectionFactoryConstPtr connFactory = mRegistrar->connectionFactory(); + ChannelFactoryConstPtr chanFactory = mRegistrar->channelFactory(); + ContactFactoryConstPtr contactFactory = mRegistrar->contactFactory(); + + SharedPtr<InvocationData> invocation(new InvocationData()); + QList<PendingOperation *> readyOps; + + RequestTemporaryHandler *tempHandler = dynamic_cast<RequestTemporaryHandler *>(mClient); + if (tempHandler) { + debug() << " This is a temporary handler for the Request & Handle API," + << "giving an early signal of the invocation"; + tempHandler->setDBusHandlerInvoked(); + } + + PendingReady *accReady = accFactory->proxy(TP_QT_ACCOUNT_MANAGER_BUS_NAME, + accountPath.path(), + connFactory, + chanFactory, + contactFactory); + invocation->acc = AccountPtr::qObjectCast(accReady->proxy()); + readyOps.append(accReady); + + QString connectionBusName = connectionPath.path().mid(1).replace( + QLatin1String("/"), QLatin1String(".")); + PendingReady *connReady = connFactory->proxy(connectionBusName, connectionPath.path(), + chanFactory, contactFactory); + invocation->conn = ConnectionPtr::qObjectCast(connReady->proxy()); + readyOps.append(connReady); + + foreach (const ChannelDetails &channelDetails, channelDetailsList) { + PendingReady *chanReady = chanFactory->proxy(invocation->conn, + channelDetails.channel.path(), channelDetails.properties); + ChannelPtr channel = ChannelPtr::qObjectCast(chanReady->proxy()); + invocation->chans.append(channel); + readyOps.append(chanReady); + } + + invocation->handlerInfo = AbstractClientHandler::HandlerInfo(handlerInfo); + + ObjectImmutablePropertiesMap reqPropsMap = qdbus_cast<ObjectImmutablePropertiesMap>( + handlerInfo.value(QLatin1String("request-properties"))); + foreach (const QDBusObjectPath &reqPath, requestsSatisfied) { + ChannelRequestPtr channelRequest = ChannelRequest::create(invocation->acc, + reqPath.path(), reqPropsMap.value(reqPath)); + invocation->chanReqs.append(channelRequest); + readyOps.append(channelRequest->becomeReady()); + } + + // FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690 + if (userActionTime_t != 0) { + invocation->time = QDateTime::fromTime_t((uint) userActionTime_t); + } + + invocation->ctx = HandleChannelsInvocationContext::create(mBus, message, + invocation->chans, + reinterpret_cast<HandleChannelsInvocationContext::FinishedCb>( + &ClientHandlerAdaptor::onContextFinished), + this); + + invocation->readyOp = new PendingComposite(readyOps, invocation->ctx); + connect(invocation->readyOp, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onReadyOpFinished(Tp::PendingOperation*))); + + mInvocations.append(invocation); + + debug() << "Preparing proxies for HandleChannels of" << channelDetailsList.size() << "channels" + << "for client" << mClient; +} + +void ClientHandlerAdaptor::onReadyOpFinished(Tp::PendingOperation *op) +{ + Q_ASSERT(!mInvocations.isEmpty()); + Q_ASSERT(op->isFinished()); + + for (QLinkedList<SharedPtr<InvocationData> >::iterator i = mInvocations.begin(); + i != mInvocations.end(); ++i) { + if ((*i)->readyOp != op) { + continue; + } + + (*i)->readyOp = 0; + + if (op->isError()) { + warning() << "Preparing proxies for HandleChannels failed with" << op->errorName() + << op->errorMessage(); + (*i)->error = op->errorName(); + (*i)->message = op->errorMessage(); + } + + break; + } + + while (!mInvocations.isEmpty() && !mInvocations.first()->readyOp) { + SharedPtr<InvocationData> invocation = mInvocations.takeFirst(); + + if (!invocation->error.isEmpty()) { + RequestTemporaryHandler *tempHandler = dynamic_cast<RequestTemporaryHandler *>(mClient); + if (tempHandler) { + debug() << " This is a temporary handler for the Request & Handle API, indicating failure"; + tempHandler->setDBusHandlerErrored(invocation->error, invocation->message); + } + + // We guarantee that the proxies were ready - so we can't invoke the client if they + // weren't made ready successfully. Fix the introspection code if this happens :) + invocation->ctx->setFinishedWithError(invocation->error, invocation->message); + continue; + } + + debug() << "Invoking application handleChannels with" << invocation->chans.size() + << "channels on" << mClient; + + mClient->handleChannels(invocation->ctx, invocation->acc, invocation->conn, + invocation->chans, invocation->chanReqs, invocation->time, invocation->handlerInfo); + } +} + +void ClientHandlerAdaptor::onContextFinished( + const MethodInvocationContextPtr<> &context, + const QList<ChannelPtr> &channels, ClientHandlerAdaptor *self) +{ + if (!context->isError()) { + debug() << "HandleChannels context finished successfully, " + "updating handled channels"; + + // register the channels in FakeHandlerManager so we report HandledChannels correctly + FakeHandlerManager::instance()->registerChannels(channels); + } +} + +ClientHandlerRequestsAdaptor::ClientHandlerRequestsAdaptor( + ClientRegistrar *registrar, + AbstractClientHandler *client, + QObject *parent) + : QDBusAbstractAdaptor(parent), + mRegistrar(registrar), + mBus(registrar->dbusConnection()), + mClient(client) +{ +} + +ClientHandlerRequestsAdaptor::~ClientHandlerRequestsAdaptor() +{ +} + +void ClientHandlerRequestsAdaptor::AddRequest( + const QDBusObjectPath &request, + const QVariantMap &requestProperties, + const QDBusMessage &message) +{ + debug() << "AddRequest:" << request.path(); + message.setDelayedReply(true); + mBus.send(message.createReply()); + mClient->addRequest(ChannelRequest::create(mBus, + request.path(), requestProperties, + mRegistrar->accountFactory(), + mRegistrar->connectionFactory(), + mRegistrar->channelFactory(), + mRegistrar->contactFactory())); +} + +void ClientHandlerRequestsAdaptor::RemoveRequest( + const QDBusObjectPath &request, + const QString &errorName, const QString &errorMessage, + const QDBusMessage &message) +{ + debug() << "RemoveRequest:" << request.path() << "-" << errorName + << "-" << errorMessage; + message.setDelayedReply(true); + mBus.send(message.createReply()); + mClient->removeRequest(ChannelRequest::create(mBus, + request.path(), QVariantMap(), + mRegistrar->accountFactory(), + mRegistrar->connectionFactory(), + mRegistrar->channelFactory(), + mRegistrar->contactFactory()), errorName, errorMessage); +} + +struct TP_QT_NO_EXPORT ClientRegistrar::Private +{ + Private(const QDBusConnection &bus, const AccountFactoryConstPtr &accFactory, + const ConnectionFactoryConstPtr &connFactory, const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) + : bus(bus), accFactory(accFactory), connFactory(connFactory), chanFactory(chanFactory), + contactFactory(contactFactory) + { + if (accFactory->dbusConnection().name() != bus.name()) { + warning() << " The D-Bus connection in the account factory is not the proxy connection"; + } + + if (connFactory->dbusConnection().name() != bus.name()) { + warning() << " The D-Bus connection in the connection factory is not the proxy connection"; + } + + if (chanFactory->dbusConnection().name() != bus.name()) { + warning() << " The D-Bus connection in the channel factory is not the proxy connection"; + } + } + + QDBusConnection bus; + + AccountFactoryConstPtr accFactory; + ConnectionFactoryConstPtr connFactory; + ChannelFactoryConstPtr chanFactory; + ContactFactoryConstPtr contactFactory; + + QHash<AbstractClientPtr, QString> clients; + QHash<AbstractClientPtr, QObject*> clientObjects; + QSet<QString> services; +}; + +/** + * \class ClientRegistrar + * \ingroup serverclient + * \headerfile TelepathyQt/client-registrar.h <TelepathyQt/ClientRegistrar> + * + * \brief The ClientRegistrar class is responsible for registering Telepathy + * clients (Observer, Approver, Handler). + * + * Clients should inherit AbstractClientObserver, AbstractClientApprover, + * AbstractClientHandler or some combination of these, by using multiple + * inheritance, and register themselves using registerClient(). + * + * See the individual classes descriptions for more details. + * + * \section cr_usage_sec Usage + * + * \subsection cr_create_sec Creating a client registrar object + * + * One way to create a ClientRegistrar object is to just call the create method. + * For example: + * + * \code ClientRegistrarPtr cr = ClientRegistrar::create(); \endcode + * + * You can also provide a D-Bus connection as a QDBusConnection: + * + * \code ClientRegistrarPtr cr = ClientRegistrar::create(QDBusConnection::systemBus()); \endcode + * + * \subsection cr_registering_sec Registering a client + * + * To register a client, just call registerClient() with a given AbstractClientPtr + * pointing to a valid AbstractClient instance. + * + * \code + * + * class MyClient : public AbstractClientObserver, public AbstractClientHandler + * { + * ... + * }; + * + * ... + * + * ClientRegistrarPtr cr = ClientRegistrar::create(); + * SharedPtr<MyClient> client = SharedPtr<MyClient>(new MyClient(...)); + * cr->registerClient(AbstractClientPtr::dynamicCast(client), "myclient"); + * + * \endcode + * + * \sa AbstractClientObserver, AbstractClientApprover, AbstractClientHandler + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Create a new client registrar object using the given \a bus. + * + * The instance will use an account factory creating Tp::Account objects with no features + * ready, a connection factory creating Tp::Connection objects with no features ready, and a channel + * factory creating stock Telepathy-Qt4 channel subclasses, as appropriate, with no features ready. + * + * \param bus QDBusConnection to use. + * \return A ClientRegistrarPtr object pointing to the newly created ClientRegistrar object. + */ +ClientRegistrarPtr ClientRegistrar::create(const QDBusConnection &bus) +{ + return create(bus, AccountFactory::create(bus), ConnectionFactory::create(bus), + ChannelFactory::create(bus), ContactFactory::create()); +} + +/** + * Create a new client registrar object using QDBusConnection::sessionBus() and the given factories. + * + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return A ClientRegistrarPtr object pointing to the newly created ClientRegistrar object. + */ +ClientRegistrarPtr ClientRegistrar::create( + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return create(QDBusConnection::sessionBus(), accountFactory, connectionFactory, channelFactory, + contactFactory); +} + +/** + * Create a new client registrar object using the given \a bus and the given factories. + * + * \param bus QDBusConnection to use. + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return A ClientRegistrarPtr object pointing to the newly created ClientRegistrar object. + */ +ClientRegistrarPtr ClientRegistrar::create(const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return ClientRegistrarPtr(new ClientRegistrar(bus, accountFactory, connectionFactory, + channelFactory, contactFactory)); +} + +/** + * Create a new client registrar object using the bus and factories of the given Account \a manager. + * + * Using this create method will enable (like any other way of passing the same factories to an AM + * and a registrar) getting the same Account/Connection etc. proxy instances from both + * AccountManager and AbstractClient implementations. + * + * \param manager The AccountManager the bus and factories of which should be used. + * \return A ClientRegistrarPtr object pointing to the newly ClientRegistrar object. + */ +ClientRegistrarPtr ClientRegistrar::create(const AccountManagerPtr &manager) +{ + if (!manager) { + return ClientRegistrarPtr(); + } + + return create(manager->dbusConnection(), manager->accountFactory(), + manager->connectionFactory(), manager->channelFactory(), manager->contactFactory()); +} + +/** + * Construct a new client registrar object using the given \a bus and the given factories. + * + * \param bus QDBusConnection to use. + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + */ +ClientRegistrar::ClientRegistrar(const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) + : Object(), + mPriv(new Private(bus, accountFactory, connectionFactory, channelFactory, contactFactory)) +{ +} + +/** + * Class destructor. + */ +ClientRegistrar::~ClientRegistrar() +{ + unregisterClients(); + delete mPriv; +} + +/** + * Return the D-Bus connection being used by this client registrar. + * + * \return A QDBusConnection object. + */ +QDBusConnection ClientRegistrar::dbusConnection() const +{ + return mPriv->bus; +} + +/** + * Get the account factory used by this client registrar. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the registrar would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the AccountFactory object. + */ +AccountFactoryConstPtr ClientRegistrar::accountFactory() const +{ + return mPriv->accFactory; +} + +/** + * Get the connection factory used by this client registrar. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the registrar would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ConnectionFactory object. + */ +ConnectionFactoryConstPtr ClientRegistrar::connectionFactory() const +{ + return mPriv->connFactory; +} + +/** + * Get the channel factory used by this client registrar. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the registrar would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ChannelFactory object. + */ +ChannelFactoryConstPtr ClientRegistrar::channelFactory() const +{ + return mPriv->chanFactory; +} + +/** + * Get the contact factory used by this client registrar. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the registrar would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ContactFactory object. + */ +ContactFactoryConstPtr ClientRegistrar::contactFactory() const +{ + return mPriv->contactFactory; +} + +/** + * Return the list of clients registered using registerClient() on this client + * registrar. + * + * \return A list of pointers to AbstractClient objects. + * \sa registerClient(), unregisterClient() + */ +QList<AbstractClientPtr> ClientRegistrar::registeredClients() const +{ + return mPriv->clients.keys(); +} + +/** + * Register a client on D-Bus. + * + * The client registrar will export the appropriate D-Bus interfaces, + * based on the abstract classes subclassed by \param client. + * + * If each of a client instance should be able to manipulate channels + * separately, set unique to true. + * + * The client name MUST be a non-empty string of ASCII digits, letters, dots + * and/or underscores, starting with a letter, and without sets of + * two consecutive dots or a dot followed by a digit. + * + * This method will do nothing if the client is already registered, and \c true + * will be returned. + * + * To unregister a client use unregisterClient(). + * + * \param client The client to register. + * \param clientName The client name used to register. + * \param unique Whether each of a client instance is able to manipulate + * channels separately. + * \return \c true if \a client was successfully registered, \c false otherwise. + * \sa registeredClients(), unregisterClient() + */ +bool ClientRegistrar::registerClient(const AbstractClientPtr &client, + const QString &clientName, bool unique) +{ + if (!client) { + warning() << "Unable to register a null client"; + return false; + } + + if (mPriv->clients.contains(client)) { + debug() << "Client already registered"; + return true; + } + + QString busName = QLatin1String("org.freedesktop.Telepathy.Client."); + busName.append(clientName); + if (unique) { + // o.f.T.Client.clientName.<unique_bus_name>_<pointer> should be enough to identify + // an unique identifier + busName.append(QString(QLatin1String(".%1_%2")) + .arg(mPriv->bus.baseService() + .replace(QLatin1String(":"), QLatin1String("_")) + .replace(QLatin1String("."), QLatin1String("_"))) + .arg((intptr_t) client.data(), 0, 16)); + } + + if (mPriv->services.contains(busName) || + !mPriv->bus.registerService(busName)) { + warning() << "Unable to register client: busName" << + busName << "already registered"; + return false; + } + + QObject *object = new QObject(this); + QStringList interfaces; + + AbstractClientHandler *handler = + dynamic_cast<AbstractClientHandler*>(client.data()); + if (handler) { + // export o.f.T.Client.Handler + new ClientHandlerAdaptor(this, handler, object); + interfaces.append( + QLatin1String("org.freedesktop.Telepathy.Client.Handler")); + if (handler->wantsRequestNotification()) { + // export o.f.T.Client.Interface.Requests + new ClientHandlerRequestsAdaptor(this, handler, object); + interfaces.append( + QLatin1String( + "org.freedesktop.Telepathy.Client.Interface.Requests")); + } + } + + AbstractClientObserver *observer = + dynamic_cast<AbstractClientObserver*>(client.data()); + if (observer) { + // export o.f.T.Client.Observer + new ClientObserverAdaptor(this, observer, object); + interfaces.append( + QLatin1String("org.freedesktop.Telepathy.Client.Observer")); + } + + AbstractClientApprover *approver = + dynamic_cast<AbstractClientApprover*>(client.data()); + if (approver) { + // export o.f.T.Client.Approver + new ClientApproverAdaptor(this, approver, object); + interfaces.append( + QLatin1String("org.freedesktop.Telepathy.Client.Approver")); + } + + if (interfaces.isEmpty()) { + warning() << "Client does not implement any known interface"; + // cleanup + mPriv->bus.unregisterService(busName); + return false; + } + + // export o.f.T.Client interface + new ClientAdaptor(this, interfaces, object); + + QString objectPath = QString(QLatin1String("/%1")).arg(busName); + objectPath.replace(QLatin1String("."), QLatin1String("/")); + if (!mPriv->bus.registerObject(objectPath, object)) { + // this shouldn't happen, but let's make sure + warning() << "Unable to register client: objectPath" << + objectPath << "already registered"; + // cleanup + delete object; + mPriv->bus.unregisterService(busName); + return false; + } + + if (handler) { + handler->setRegistered(true); + } + + debug() << "Client registered - busName:" << busName << + "objectPath:" << objectPath << "interfaces:" << interfaces; + + mPriv->services.insert(busName); + mPriv->clients.insert(client, objectPath); + mPriv->clientObjects.insert(client, object); + + return true; +} + +/** + * Unregister a client registered using registerClient() on this client + * registrar. + * + * If \a client was not registered previously, \c false will be returned. + * + * \param client The client to unregister. + * \return \c true if \a client was successfully unregistered, \c false otherwise. + * \sa registeredClients(), registerClient() + */ +bool ClientRegistrar::unregisterClient(const AbstractClientPtr &client) +{ + if (!mPriv->clients.contains(client)) { + warning() << "Trying to unregister an unregistered client"; + return false; + } + + AbstractClientHandler *handler = + dynamic_cast<AbstractClientHandler*>(client.data()); + if (handler) { + handler->setRegistered(false); + } + + QString objectPath = mPriv->clients.value(client); + mPriv->bus.unregisterObject(objectPath); + mPriv->clients.remove(client); + QObject *object = mPriv->clientObjects.value(client); + // delete object here and it's children (adaptors), to make sure if adaptor + // is keeping a static list of adaptors per connection, the list is updated. + delete object; + mPriv->clientObjects.remove(client); + + QString busName = objectPath.mid(1).replace(QLatin1String("/"), + QLatin1String(".")); + mPriv->bus.unregisterService(busName); + mPriv->services.remove(busName); + + debug() << "Client unregistered - busName:" << busName << + "objectPath:" << objectPath; + + return true; +} + +/** + * Unregister all clients registered using registerClient() on this client + * registrar. + * + * \sa registeredClients(), registerClient(), unregisterClient() + */ +void ClientRegistrar::unregisterClients() +{ + // copy the hash as it will be modified + QHash<AbstractClientPtr, QString> clients = mPriv->clients; + + QHash<AbstractClientPtr, QString>::const_iterator end = + clients.constEnd(); + QHash<AbstractClientPtr, QString>::const_iterator it = + clients.constBegin(); + while (it != end) { + unregisterClient(it.key()); + ++it; + } +} + +} // Tp diff --git a/TelepathyQt/client-registrar.h b/TelepathyQt/client-registrar.h new file mode 100644 index 00000000..631dd454 --- /dev/null +++ b/TelepathyQt/client-registrar.h @@ -0,0 +1,97 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_client_registrar_h_HEADER_GUARD_ +#define _TelepathyQt_client_registrar_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/AccountFactory> +#include <TelepathyQt/ChannelFactory> +#include <TelepathyQt/ConnectionFactory> +#include <TelepathyQt/ContactFactory> +#include <TelepathyQt/Object> +#include <TelepathyQt/Types> + +#include <QDBusConnection> +#include <QString> + +namespace Tp +{ + +class TP_QT_EXPORT ClientRegistrar : public Object +{ + Q_OBJECT + Q_DISABLE_COPY(ClientRegistrar) + +public: + static ClientRegistrarPtr create(const QDBusConnection &bus); + static ClientRegistrarPtr create( + const AccountFactoryConstPtr &accountFactory = + AccountFactory::create(QDBusConnection::sessionBus()), + const ConnectionFactoryConstPtr &connectionFactory = + ConnectionFactory::create(QDBusConnection::sessionBus()), + const ChannelFactoryConstPtr &channelFactory = + ChannelFactory::create(QDBusConnection::sessionBus()), + const ContactFactoryConstPtr &contactFactory = + ContactFactory::create()); + static ClientRegistrarPtr create(const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory); + static ClientRegistrarPtr create(const AccountManagerPtr &accountManager); + + ~ClientRegistrar(); + + QDBusConnection dbusConnection() const; + + AccountFactoryConstPtr accountFactory() const; + ConnectionFactoryConstPtr connectionFactory() const; + ChannelFactoryConstPtr channelFactory() const; + ContactFactoryConstPtr contactFactory() const; + + QList<AbstractClientPtr> registeredClients() const; + bool registerClient(const AbstractClientPtr &client, + const QString &clientName, bool unique = false); + bool unregisterClient(const AbstractClientPtr &client); + void unregisterClients(); + +private: + ClientRegistrar(const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory); + + struct Private; + friend struct Private; + Private *mPriv; + + static QHash<QPair<QString, QString>, ClientRegistrar *> registrarForConnection; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/client.cpp b/TelepathyQt/client.cpp new file mode 100644 index 00000000..27c1a749 --- /dev/null +++ b/TelepathyQt/client.cpp @@ -0,0 +1,26 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/Client> + +#include "TelepathyQt/_gen/cli-client-body.hpp" +#include "TelepathyQt/_gen/cli-client.moc.hpp" diff --git a/TelepathyQt/client.h b/TelepathyQt/client.h new file mode 100644 index 00000000..0835a8bd --- /dev/null +++ b/TelepathyQt/client.h @@ -0,0 +1,32 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_client_h_HEADER_GUARD_ +#define _TelepathyQt_client_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/_gen/cli-client.h> + +#endif diff --git a/TelepathyQt/client.xml b/TelepathyQt/client.xml new file mode 100644 index 00000000..d4d0f6de --- /dev/null +++ b/TelepathyQt/client.xml @@ -0,0 +1,14 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Client interfaces</tp:title> + +<xi:include href="../spec/Client.xml"/> +<xi:include href="../spec/Client_Observer.xml"/> +<xi:include href="../spec/Client_Approver.xml"/> +<xi:include href="../spec/Client_Handler.xml"/> + +<xi:include href="../spec/Client_Interface_Requests.xml"/> + +</tp:spec> diff --git a/TelepathyQt/connection-capabilities.cpp b/TelepathyQt/connection-capabilities.cpp new file mode 100644 index 00000000..c6e2526a --- /dev/null +++ b/TelepathyQt/connection-capabilities.cpp @@ -0,0 +1,303 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ConnectionCapabilities> + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Types> + +namespace Tp +{ + +/** + * \class ConnectionCapabilities + * \ingroup clientconn + * \headerfile TelepathyQt/connection-capabilities.h <TelepathyQt/ConnectionCapabilities> + * + * \brief The ConnectionCapabilities class represents the capabilities of a + * Connection. + */ + +/** + * Construct a new ConnectionCapabilities object. + */ +ConnectionCapabilities::ConnectionCapabilities() + : CapabilitiesBase() +{ +} + +/** + * Construct a new ConnectionCapabilities object using the give \a rccs. + * + * \param rccs RequestableChannelClassList representing the capabilities of a + * Connection. + */ +ConnectionCapabilities::ConnectionCapabilities(const RequestableChannelClassList &rccs) + : CapabilitiesBase(rccs, false) +{ +} + +/** + * Construct a new ConnectionCapabilities object using the give \a rccSpecs. + * + * \param rccSpecs RequestableChannelClassSpecList representing the capabilities of a + * Connection. + */ +ConnectionCapabilities::ConnectionCapabilities(const RequestableChannelClassSpecList &rccSpecs) + : CapabilitiesBase(rccSpecs, false) +{ +} + +/** + * Class destructor. + */ +ConnectionCapabilities::~ConnectionCapabilities() +{ +} + +/** + * Return true if named text chatrooms can be joined by providing a + * chatroom identifier. + * + * If the protocol is such that chatrooms can be joined, but only via + * a more elaborate D-Bus API than normal (because more information is needed), + * then this method will return false. + * + * \return \c true if Account::ensureTextChatroom() can be expected to work. + */ +bool ConnectionCapabilities::textChatrooms() const +{ + RequestableChannelClassSpecList rccSpecs = allClassSpecs(); + foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::textChatroom())) { + return true; + } + } + return false; +} + +/** + * Return whether creating conference media calls is supported. + * + * \return \c true if supported, \c false otherwise. + */ +bool ConnectionCapabilities::conferenceStreamedMediaCalls() const +{ + RequestableChannelClassSpecList rccSpecs = allClassSpecs(); + foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::conferenceStreamedMediaCall())) { + return true; + } + } + return false; +} + +/** + * Return whether creating conference media calls is supported. + * + * This method will also check whether inviting new contacts when creating a conference media call + * channel by providing additional members to initial invitees (as opposed to merging several + * channels into one new conference channel) is supported. + * + * If providing additional members is supported, it is also possible to request conference media + * calls with fewer than two (even zero) already established media calls. + * + * \return \c true if supported, \c false otherwise. + */ +bool ConnectionCapabilities::conferenceStreamedMediaCallsWithInvitees() const +{ + RequestableChannelClassSpecList rccSpecs = allClassSpecs(); + foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::conferenceStreamedMediaCallWithInvitees())) { + return true; + } + } + return false; +} + +/** + * Return whether creating conference text chats is supported. + * + * \return \c true if supported, \c false otherwise. + */ +bool ConnectionCapabilities::conferenceTextChats() const +{ + RequestableChannelClassSpecList rccSpecs = allClassSpecs(); + foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::conferenceTextChat())) { + return true; + } + } + return false; +} + +/** + * Return whether creating conference text chats is supported. + * + * This method will also check whether inviting new contacts when creating a conference text chat + * channel by providing additional members to initial invitees (as opposed to merging several + * channels into one new conference channel) is supported. + * + * If providing additional members is supported, it is also possible to request conference text + * chats with fewer than two (even zero) already established text chats. + * + * \return \c true if supported, \c false otherwise. + */ +bool ConnectionCapabilities::conferenceTextChatsWithInvitees() const +{ + RequestableChannelClassSpecList rccSpecs = allClassSpecs(); + foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::conferenceTextChatWithInvitees())) { + return true; + } + } + return false; +} + +/** + * Return whether creating conference text chat rooms is supported. + * + * \return \c true if supported, \c false otherwise. + */ +bool ConnectionCapabilities::conferenceTextChatrooms() const +{ + RequestableChannelClassSpecList rccSpecs = allClassSpecs(); + foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::conferenceTextChatroom())) { + return true; + } + } + return false; +} + +/** + * Return whether creating conference text chat rooms is supported. + * + * This method will also check whether inviting new contacts when creating a conference text chat + * room channel by providing additional members to initial invitees (as opposed to merging several + * channels into one new conference channel) is supported. + * + * If providing additional members is supported, it is also possible to request conference text + * chat rooms with fewer than two (even zero) already established text chat rooms. + * + * \return \c true if supported, \c false otherwise. + */ +bool ConnectionCapabilities::conferenceTextChatroomsWithInvitees() const +{ + RequestableChannelClassSpecList rccSpecs = allClassSpecs(); + foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::conferenceTextChatroomWithInvitees())) { + return true; + } + } + return false; +} + +/** + * \deprecated Use contactSearches() instead. + */ +bool ConnectionCapabilities::contactSearch() +{ + return contactSearches(); +} + +/** + * \deprecated Use contactSearchesWithSpecificServer() instead. + */ +bool ConnectionCapabilities::contactSearchWithSpecificServer() const +{ + return contactSearchesWithSpecificServer(); +} + +/** + * \deprecated Use contactSearchesWithLimit() instead. + */ +bool ConnectionCapabilities::contactSearchWithLimit() const +{ + return contactSearchesWithLimit(); +} + +/** + * Return whether creating a ContactSearch channel is supported. + * + * \return \c true if supported, \c false otherwise. + */ +bool ConnectionCapabilities::contactSearches() const +{ + RequestableChannelClassSpecList rccSpecs = allClassSpecs(); + foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::contactSearch())) { + return true; + } + } + return false; +} + +/** + * Return whether creating a ContactSearch channel specifying a server is supported. + * + * \return \c true if supported, \c false otherwise. + */ +bool ConnectionCapabilities::contactSearchesWithSpecificServer() const +{ + RequestableChannelClassSpecList rccSpecs = allClassSpecs(); + foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::contactSearchWithSpecificServer())) { + return true; + } + } + return false; +} + +/** + * Return whether creating a ContactSearch channel specifying a limit is supported. + * + * \return \c true if supported, \c false otherwise. + */ +bool ConnectionCapabilities::contactSearchesWithLimit() const +{ + RequestableChannelClassSpecList rccSpecs = allClassSpecs(); + foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::contactSearchWithLimit())) { + return true; + } + } + return false; +} + +/** + * Return whether creating a StreamTube channel by providing a contact identifier is supported. + * + * \return \c true if supported, \c false otherwise. + */ +bool ConnectionCapabilities::streamTubes() const +{ + RequestableChannelClassSpecList rccSpecs = allClassSpecs(); + foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) { + if (rccSpec.supports(RequestableChannelClassSpec::streamTube())) { + return true; + } + } + return false; +} + +} // Tp diff --git a/TelepathyQt/connection-capabilities.h b/TelepathyQt/connection-capabilities.h new file mode 100644 index 00000000..3ffcc288 --- /dev/null +++ b/TelepathyQt/connection-capabilities.h @@ -0,0 +1,77 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_connection_capabilities_h_HEADER_GUARD_ +#define _TelepathyQt_connection_capabilities_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/CapabilitiesBase> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class TestBackdoors; + +class TP_QT_EXPORT ConnectionCapabilities : public CapabilitiesBase +{ +public: + ConnectionCapabilities(); + virtual ~ConnectionCapabilities(); + + bool textChatrooms() const; + + bool conferenceStreamedMediaCalls() const; + bool conferenceStreamedMediaCallsWithInvitees() const; + bool conferenceTextChats() const; + bool conferenceTextChatsWithInvitees() const; + bool conferenceTextChatrooms() const; + bool conferenceTextChatroomsWithInvitees() const; + + bool contactSearches() const; + bool contactSearchesWithSpecificServer() const; + bool contactSearchesWithLimit() const; + + TP_QT_DEPRECATED bool contactSearch(); + TP_QT_DEPRECATED bool contactSearchWithSpecificServer() const; + TP_QT_DEPRECATED bool contactSearchWithLimit() const; + + bool streamTubes() const; + +protected: + friend class Account; + friend class Connection; + friend class ProtocolInfo; + friend class TestBackdoors; + + ConnectionCapabilities(const RequestableChannelClassList &rccs); + ConnectionCapabilities(const RequestableChannelClassSpecList &rccSpecs); +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::ConnectionCapabilities); + +#endif diff --git a/TelepathyQt/connection-factory.cpp b/TelepathyQt/connection-factory.cpp new file mode 100644 index 00000000..02867846 --- /dev/null +++ b/TelepathyQt/connection-factory.cpp @@ -0,0 +1,150 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ConnectionFactory> + +#include <TelepathyQt/Connection> +#include <TelepathyQt/DBusProxy> + +namespace Tp +{ + +/** + * \class ConnectionFactory + * \ingroup utils + * \headerfile TelepathyQt/connection-factory.h <TelepathyQt/ConnectionFactory> + * + * \brief The ConnectionFactory class is responsible for constructing Connection + * objects according to application-defined settings. + * + * The class is used by Account and other classes which construct Connection proxy + * instances to enable sharing instances of application-defined Connection + * subclasses with certain features always ready. + */ + +/** + * Create a new ConnectionFactory object. + * + * Optionally, the \a features to make ready on all constructed proxies can be specified. The + * default is to make no features ready. It should be noted that unlike Connection::becomeReady(), + * FeatureCore isn't assumed. If no features are specified, which is the default behavior, no + * Connection::becomeReady() call is made at all and the proxy won't be Connection::isReady(). + * + * \param bus The QDBusConnection for proxies constructed using this factory to use. + * \param features The features to make ready on constructed Connections. + * \return A ConnectionFactoryPtr object pointing to the newly created + * ConnectionFactory object. + */ +ConnectionFactoryPtr ConnectionFactory::create(const QDBusConnection &bus, + const Features &features) +{ + return ConnectionFactoryPtr(new ConnectionFactory(bus, features)); +} + +/** + * Construct a new ConnectionFactory object. + * + * As in create(), it should be noted that unlike Connection::becomeReady(), FeatureCore isn't + * assumed. If no \a features are specified, no Connection::becomeReady() call is made at all and + * the proxy won't be Connection::isReady(). + * + * \param bus The QDBusConnection for proxies constructed using this factory to use. + * \param features The features to make ready on constructed Connections. + */ +ConnectionFactory::ConnectionFactory(const QDBusConnection &bus, const Features &features) + : FixedFeatureFactory(bus) +{ + addFeatures(features); +} + +/** + * Class destructor. + */ +ConnectionFactory::~ConnectionFactory() +{ +} + +/** + * Constructs a Connection proxy and begins making it ready. + * + * If a valid proxy already exists in the factory cache for the given combination of \a busName and + * \a objectPath, it is returned instead. All newly created proxies are automatically cached until + * they're either DBusProxy::invalidated() or the last reference to them outside the factory has + * been dropped. + * + * The proxy can be accessed immediately after this function returns using PendingReady::proxy(). + * The ready operation only finishes, however, when the features specified by features(), if any, + * are made ready as much as possible. If the service doesn't support a given feature, they won't + * obviously be ready even if the operation finished successfully, as is the case for + * Connection::becomeReady(). + * + * \param busName The bus/service name of the D-Bus connection object the proxy is constructed for. + * \param objectPath The object path of the connection. + * \param chanFactory The channel factory to use for the Connection. + * \param contactFactory The channel factory to use for the Connection. + * \return A PendingReady operation with the proxy in PendingReady::proxy(). + */ +PendingReady *ConnectionFactory::proxy(const QString &busName, const QString &objectPath, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) const +{ + DBusProxyPtr proxy = cachedProxy(busName, objectPath); + if (proxy.isNull()) { + proxy = construct(busName, objectPath, chanFactory, contactFactory); + } + + return nowHaveProxy(proxy); +} + +/** + * Can be used by subclasses to override the Connection subclass constructed by the factory. + * + * This is automatically called by proxy() to construct proxy instances if no valid cached proxy is + * found. + * + * The default implementation constructs Tp::Connection objects. + * + * \param busName The bus/service name of the D-Bus Connection object the proxy is constructed for. + * \param objectPath The object path of the connection. + * \param chanFactory The channel factory to use for the Connection. + * \param contactFactory The channel factory to use for the Connection. + * \return A pointer to the constructed Connection object. + */ +ConnectionPtr ConnectionFactory::construct(const QString &busName, const QString &objectPath, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) const +{ + return Connection::create(dbusConnection(), busName, objectPath, chanFactory, contactFactory); +} + +/** + * Transforms well-known names to the corresponding unique names, as is appropriate for Connection + * + * \param uniqueOrWellKnown The name to transform. + * \return The unique name corresponding to \a uniqueOrWellKnown (which may be it itself). + */ +QString ConnectionFactory::finalBusNameFrom(const QString &uniqueOrWellKnown) const +{ + return StatefulDBusProxy::uniqueNameFrom(dbusConnection(), uniqueOrWellKnown); +} + +} diff --git a/TelepathyQt/connection-factory.h b/TelepathyQt/connection-factory.h new file mode 100644 index 00000000..70f3b503 --- /dev/null +++ b/TelepathyQt/connection-factory.h @@ -0,0 +1,78 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_connection_factory_h_HEADER_GUARD_ +#define _TelepathyQt_connection_factory_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> +#include <TelepathyQt/SharedPtr> +#include <TelepathyQt/Types> + +#include <TelepathyQt/Feature> +#include <TelepathyQt/FixedFeatureFactory> + +// For Q_DISABLE_COPY +#include <QtGlobal> + +#include <QString> + +class QDBusConnection; + +namespace Tp +{ + +class PendingReady; + +class TP_QT_EXPORT ConnectionFactory : public FixedFeatureFactory +{ +public: + static ConnectionFactoryPtr create(const QDBusConnection &bus, + const Features &features = Features()); + + virtual ~ConnectionFactory(); + + PendingReady *proxy(const QString &busName, const QString &objectPath, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) const; + +protected: + ConnectionFactory(const QDBusConnection &bus, const Features &features); + + virtual ConnectionPtr construct(const QString &busName, const QString &objectPath, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) const; + virtual QString finalBusNameFrom(const QString &uniqueOrWellKnown) const; + // Nothing we'd like to prepare() + // Fixed features + +private: + struct Private; + Private *mPriv; // Currently unused, just for future-proofing +}; + +} // Tp + +#endif diff --git a/TelepathyQt/connection-internal.h b/TelepathyQt/connection-internal.h new file mode 100644 index 00000000..2d210a5c --- /dev/null +++ b/TelepathyQt/connection-internal.h @@ -0,0 +1,61 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_connection_internal_h_HEADER_GUARD_ +#define _TelepathyQt_connection_internal_h_HEADER_GUARD_ + +#include <TelepathyQt/Connection> + +#include <TelepathyQt/PendingReady> + +#include <QSet> + +namespace Tp +{ + +class TP_QT_NO_EXPORT Connection::PendingConnect : public PendingReady +{ + Q_OBJECT + +public: + PendingConnect(const ConnectionPtr &connection, const Features &requestedFeatures); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onConnectReply(QDBusPendingCallWatcher *); + TP_QT_NO_EXPORT void onStatusChanged(Tp::ConnectionStatus newStatus); + TP_QT_NO_EXPORT void onBecomeReadyReply(Tp::PendingOperation *); + TP_QT_NO_EXPORT void onConnInvalidated(Tp::DBusProxy *proxy, const QString &error, const QString &message); + +private: + friend class ConnectionLowlevel; +}; + +class ConnectionHelper +{ +public: + static QString statusReasonToErrorName(Tp::ConnectionStatusReason reason, + Tp::ConnectionStatus oldStatus); +}; + +} // Tp + +#endif diff --git a/TelepathyQt/connection-lowlevel.h b/TelepathyQt/connection-lowlevel.h new file mode 100644 index 00000000..63a3429f --- /dev/null +++ b/TelepathyQt/connection-lowlevel.h @@ -0,0 +1,96 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_connection_lowlevel_h_HEADER_GUARD_ +#define _TelepathyQt_connection_lowlevel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class Connection; +class PendingChannel; +class PendingContactAttributes; +class PendingHandles; +class PendingOperation; +class PendingReady; + +class TP_QT_EXPORT ConnectionLowlevel : public QObject, public RefCounted +{ + Q_OBJECT + Q_DISABLE_COPY(ConnectionLowlevel) + +public: + ~ConnectionLowlevel(); + + bool isValid() const; + ConnectionPtr connection() const; + + PendingReady *requestConnect(const Features &requestedFeatures = Features()); + PendingOperation *requestDisconnect(); + + SimpleStatusSpecMap allowedPresenceStatuses() const; + uint maxPresenceStatusMessageLength() const; + + PendingOperation *setSelfPresence(const QString &status, const QString &statusMessage); + + PendingChannel *createChannel(const QVariantMap &request); + PendingChannel *createChannel(const QVariantMap &request, int timeout); + PendingChannel *ensureChannel(const QVariantMap &request); + PendingChannel *ensureChannel(const QVariantMap &request, int timeout); + + PendingHandles *requestHandles(HandleType handleType, const QStringList &names); + PendingHandles *referenceHandles(HandleType handleType, const UIntList &handles); + + PendingContactAttributes *contactAttributes(const UIntList &handles, + const QStringList &interfaces, bool reference = true); + QStringList contactAttributeInterfaces() const; + + void injectContactIds(const HandleIdentifierMap &contactIds); + void injectContactId(uint handle, const QString &contactId); + +private: + friend class Connection; + friend class ContactManager; + friend class PendingContacts; + + TP_QT_NO_EXPORT ConnectionLowlevel(Connection *parent); + + TP_QT_NO_EXPORT bool hasImmortalHandles() const; + + TP_QT_NO_EXPORT bool hasContactId(uint handle) const; + TP_QT_NO_EXPORT QString contactId(uint handle) const; + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/connection-manager-internal.h b/TelepathyQt/connection-manager-internal.h new file mode 100644 index 00000000..65083237 --- /dev/null +++ b/TelepathyQt/connection-manager-internal.h @@ -0,0 +1,159 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_connection_manager_internal_h_HEADER_GUARD_ +#define _TelepathyQt_connection_manager_internal_h_HEADER_GUARD_ + +#include <TelepathyQt/PendingStringList> + +#include <QDBusConnection> +#include <QLatin1String> +#include <QQueue> +#include <QSet> +#include <QString> +#include <QWeakPointer> + +namespace Tp +{ + +class ConnectionManager; +class ReadinessHelper; + +struct TP_QT_NO_EXPORT ConnectionManager::Private +{ + Private(ConnectionManager *parent, const QString &name, + const ConnectionFactoryConstPtr &connFactory, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory); + ~Private(); + + bool parseConfigFile(); + + static void introspectMain(Private *self); + void introspectProtocolsLegacy(); + void introspectParametersLegacy(); + + static QString makeBusName(const QString &name); + static QString makeObjectPath(const QString &name); + + class PendingNames; + class ProtocolWrapper; + + // Public object + ConnectionManager *parent; + ConnectionManagerLowlevelPtr lowlevel; + + QString name; + + // Instance of generated interface class + Client::ConnectionManagerInterface *baseInterface; + + // Mandatory properties interface proxy + Client::DBus::PropertiesInterface *properties; + + ReadinessHelper *readinessHelper; + + ConnectionFactoryConstPtr connFactory; + ChannelFactoryConstPtr chanFactory; + ContactFactoryConstPtr contactFactory; + + // Introspection + QQueue<QString> parametersQueue; + ProtocolInfoList protocols; + QSet<SharedPtr<ProtocolWrapper> > wrappers; +}; + +struct TP_QT_NO_EXPORT ConnectionManagerLowlevel::Private +{ + Private(ConnectionManager *cm) + : cm(QWeakPointer<ConnectionManager>(cm)) + { + } + + QWeakPointer<ConnectionManager> cm; +}; + +class TP_QT_NO_EXPORT ConnectionManager::Private::PendingNames : public PendingStringList +{ + Q_OBJECT + +public: + PendingNames(const QDBusConnection &bus); + ~PendingNames() {}; + +private Q_SLOTS: + void onCallFinished(QDBusPendingCallWatcher *); + void continueProcessing(); + +private: + void invokeMethod(const QLatin1String &method); + void parseResult(const QStringList &names); + + QQueue<QLatin1String> mMethodsQueue; + QSet<QString> mResult; + QDBusConnection mBus; +}; + +class TP_QT_NO_EXPORT ConnectionManager::Private::ProtocolWrapper : + public StatelessDBusProxy, public OptionalInterfaceFactory<ProtocolWrapper> +{ + Q_OBJECT + +public: + static const Feature FeatureCore; + + ProtocolWrapper(const QDBusConnection &bus, const QString &busName, + const QString &objectPath, + const QString &cmName, const QString &name, const QVariantMap &props); + ~ProtocolWrapper(); + + ProtocolInfo info() const { return mInfo; } + + inline Client::DBus::PropertiesInterface *propertiesInterface() const + { + return optionalInterface<Client::DBus::PropertiesInterface>(BypassInterfaceCheck); + } + +private Q_SLOTS: + void gotMainProperties(QDBusPendingCallWatcher *watcher); + void gotAvatarsProperties(QDBusPendingCallWatcher *watcher); + void gotPresenceProperties(QDBusPendingCallWatcher *watcher); + +private: + static void introspectMain(ProtocolWrapper *self); + void introspectAvatars(); + void introspectPresence(); + + void continueIntrospection(); + + void fillRCCs(); + bool receiveProperties(const QVariantMap &props); + + ReadinessHelper *mReadinessHelper; + ProtocolInfo mInfo; + QVariantMap mImmutableProps; + QQueue<void (ProtocolWrapper::*)()> introspectQueue; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/connection-manager-lowlevel.h b/TelepathyQt/connection-manager-lowlevel.h new file mode 100644 index 00000000..69c67330 --- /dev/null +++ b/TelepathyQt/connection-manager-lowlevel.h @@ -0,0 +1,64 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_connection_manager_lowlevel_h_HEADER_GUARD_ +#define _TelepathyQt_connection_manager_lowlevel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class PendingConnection; + +class TP_QT_EXPORT ConnectionManagerLowlevel : public QObject, public RefCounted +{ + Q_OBJECT + Q_DISABLE_COPY(ConnectionManagerLowlevel) + +public: + ~ConnectionManagerLowlevel(); + + bool isValid() const; + ConnectionManagerPtr connectionManager() const; + + PendingConnection *requestConnection(const QString &protocolName, + const QVariantMap ¶meters); + +private: + friend class ConnectionManager; + + TP_QT_NO_EXPORT ConnectionManagerLowlevel(ConnectionManager *parent); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/connection-manager.cpp b/TelepathyQt/connection-manager.cpp new file mode 100644 index 00000000..f5d2817d --- /dev/null +++ b/TelepathyQt/connection-manager.cpp @@ -0,0 +1,1076 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ConnectionManager> +#include <TelepathyQt/ConnectionManagerLowlevel> +#include "TelepathyQt/connection-manager-internal.h" + +#include "TelepathyQt/_gen/cli-connection-manager-body.hpp" +#include "TelepathyQt/_gen/cli-connection-manager.moc.hpp" +#include "TelepathyQt/_gen/connection-manager.moc.hpp" +#include "TelepathyQt/_gen/connection-manager-internal.moc.hpp" +#include "TelepathyQt/_gen/connection-manager-lowlevel.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/ConnectionCapabilities> +#include <TelepathyQt/DBus> +#include <TelepathyQt/PendingConnection> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/Constants> +#include <TelepathyQt/ManagerFile> +#include <TelepathyQt/Types> + +#include <QDBusConnectionInterface> +#include <QQueue> +#include <QStringList> +#include <QTimer> + +namespace +{ + +bool checkValidProtocolName(const QString &protocolName) +{ + if (!protocolName[0].isLetter()) { + return false; + } + + int length = protocolName.length(); + if (length <= 1) { + return true; + } + + QChar ch; + for (int i = 1; i < length; ++i) { + ch = protocolName[i]; + if (!ch.isLetterOrNumber() && ch != QLatin1Char('-')) { + return false; + } + } + + return true; +} + +} + +namespace Tp +{ + +ConnectionManager::Private::PendingNames::PendingNames(const QDBusConnection &bus) + : PendingStringList(SharedPtr<RefCounted>()), + mBus(bus) +{ + mMethodsQueue.enqueue(QLatin1String("ListNames")); + mMethodsQueue.enqueue(QLatin1String("ListActivatableNames")); + QTimer::singleShot(0, this, SLOT(continueProcessing())); +} + +void ConnectionManager::Private::PendingNames::onCallFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QStringList> reply = *watcher; + + if (!reply.isError()) { + parseResult(reply.value()); + continueProcessing(); + } else { + warning() << "Failure: error " << reply.error().name() << + ": " << reply.error().message(); + setFinishedWithError(reply.error()); + } + + watcher->deleteLater(); +} + +void ConnectionManager::Private::PendingNames::continueProcessing() +{ + if (!mMethodsQueue.isEmpty()) { + QLatin1String method = mMethodsQueue.dequeue(); + invokeMethod(method); + } + else { + debug() << "Success: list" << mResult; + setResult(mResult.toList()); + setFinished(); + } +} + +void ConnectionManager::Private::PendingNames::invokeMethod(const QLatin1String &method) +{ + QDBusPendingCall call = mBus.interface()->asyncCallWithArgumentList( + method, QList<QVariant>()); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onCallFinished(QDBusPendingCallWatcher*))); +} + +void ConnectionManager::Private::PendingNames::parseResult(const QStringList &names) +{ + foreach (const QString name, names) { + if (name.startsWith(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_MANAGER "."))) { + mResult << name.right(name.length() - 44); + } + } +} + +const Feature ConnectionManager::Private::ProtocolWrapper::FeatureCore = + Feature(QLatin1String(ConnectionManager::Private::ProtocolWrapper::staticMetaObject.className()), + 0, true); + +ConnectionManager::Private::ProtocolWrapper::ProtocolWrapper( + const QDBusConnection &bus, + const QString &busName, const QString &objectPath, + const QString &cmName, const QString &name, const QVariantMap &props) + : StatelessDBusProxy(bus, busName, objectPath, FeatureCore), + OptionalInterfaceFactory<ProtocolWrapper>(this), + mReadinessHelper(readinessHelper()), + mInfo(ProtocolInfo(cmName, name)), + mImmutableProps(props) +{ + fillRCCs(); + + ReadinessHelper::Introspectables introspectables; + + // As Protocol does not have predefined statuses let's simulate one (0) + ReadinessHelper::Introspectable introspectableCore( + QSet<uint>() << 0, // makesSenseForStatuses + Features(), // dependsOnFeatures + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &ProtocolWrapper::introspectMain, + this); + introspectables[FeatureCore] = introspectableCore; + + mReadinessHelper->addIntrospectables(introspectables); +} + +ConnectionManager::Private::ProtocolWrapper::~ProtocolWrapper() +{ +} + +void ConnectionManager::Private::ProtocolWrapper::introspectMain( + ConnectionManager::Private::ProtocolWrapper *self) +{ + Client::DBus::PropertiesInterface *properties = self->propertiesInterface(); + Q_ASSERT(properties != 0); + + if (self->receiveProperties(self->mImmutableProps)) { + debug() << "Got everything we want from the immutable props for" << + self->info().name(); + + if (self->hasInterface(TP_QT_IFACE_PROTOCOL_INTERFACE_AVATARS)) { + self->introspectQueue.enqueue(&ProtocolWrapper::introspectAvatars); + } else { + debug() << "Full functionality requires CM support for the Protocol.Avatars interface"; + } + + if (self->hasInterface(TP_QT_IFACE_PROTOCOL_INTERFACE_PRESENCE)) { + self->introspectQueue.enqueue(&ProtocolWrapper::introspectPresence); + } else { + debug() << "Full functionality requires CM support for the Protocol.Presence interface"; + } + + self->continueIntrospection(); + return; + } + + debug() << "Not enough immutable properties, calling Properties::GetAll(Protocol) for" << + self->info().name(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + properties->GetAll(TP_QT_IFACE_PROTOCOL), + self); + self->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotMainProperties(QDBusPendingCallWatcher*))); +} + +void ConnectionManager::Private::ProtocolWrapper::introspectAvatars() +{ + Client::DBus::PropertiesInterface *properties = propertiesInterface(); + Q_ASSERT(properties != 0); + + debug() << "Calling Properties::GetAll(Protocol.Avatars)"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + properties->GetAll(TP_QT_IFACE_PROTOCOL_INTERFACE_AVATARS), + this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotAvatarsProperties(QDBusPendingCallWatcher*))); +} + +void ConnectionManager::Private::ProtocolWrapper::introspectPresence() +{ + Client::DBus::PropertiesInterface *properties = propertiesInterface(); + Q_ASSERT(properties != 0); + + debug() << "Calling Properties::GetAll(Protocol.Presence)"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + properties->GetAll(TP_QT_IFACE_PROTOCOL_INTERFACE_PRESENCE), + this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotPresenceProperties(QDBusPendingCallWatcher*))); +} + +void ConnectionManager::Private::ProtocolWrapper::continueIntrospection() +{ + if (introspectQueue.isEmpty()) { + mReadinessHelper->setIntrospectCompleted(FeatureCore, true); + } else { + (this->*(introspectQueue.dequeue()))(); + } +} + +void ConnectionManager::Private::ProtocolWrapper::gotMainProperties( + QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + QVariantMap props; + + if (!reply.isError()) { + debug() << "Got reply to Properties.GetAll(Protocol)"; + + QVariantMap unqualifiedProps = reply.value(); + QVariantMap qualifiedProps; + foreach (QString unqualified, unqualifiedProps.keys()) { + qualifiedProps.insert( + QString(QLatin1String("%1.%2")). + arg(TP_QT_IFACE_PROTOCOL). + arg(unqualified), + unqualifiedProps.value(unqualified)); + } + receiveProperties(qualifiedProps); + } else { + warning().nospace() << + "Properties.GetAll(Protocol) failed: " << + reply.error().name() << ": " << reply.error().message(); + warning() << " Full functionality requires CM support for the Protocol interface"; + } + + if (hasInterface(TP_QT_IFACE_PROTOCOL_INTERFACE_AVATARS)) { + introspectQueue.enqueue(&ProtocolWrapper::introspectAvatars); + } else { + debug() << "Full functionality requires CM support for the Protocol.Avatars interface"; + } + + if (hasInterface(TP_QT_IFACE_PROTOCOL_INTERFACE_PRESENCE)) { + introspectQueue.enqueue(&ProtocolWrapper::introspectPresence); + } else { + debug() << "Full functionality requires CM support for the Protocol.Presence interface"; + } + + continueIntrospection(); + + watcher->deleteLater(); +} + +void ConnectionManager::Private::ProtocolWrapper::gotAvatarsProperties( + QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + QVariantMap props; + + if (!reply.isError()) { + debug() << "Got reply to Properties.GetAll(Protocol.Avatars)"; + props = reply.value(); + + QStringList supportedMimeTypes = qdbus_cast<QStringList>( + props[QLatin1String("SupportedAvatarMIMETypes")]); + uint minHeight = qdbus_cast<uint>(props[QLatin1String("MinimumAvatarHeight")]); + uint maxHeight = qdbus_cast<uint>(props[QLatin1String("MaximumAvatarHeight")]); + uint recommendedHeight = qdbus_cast<uint>( + props[QLatin1String("RecommendedAvatarHeight")]); + uint minWidth = qdbus_cast<uint>(props[QLatin1String("MinimumAvatarWidth")]); + uint maxWidth = qdbus_cast<uint>(props[QLatin1String("MaximumAvatarWidth")]); + uint recommendedWidth = qdbus_cast<uint>( + props[QLatin1String("RecommendedAvatarWidth")]); + uint maxBytes = qdbus_cast<uint>(props[QLatin1String("MaximumAvatarBytes")]); + mInfo.setAvatarRequirements(AvatarSpec(supportedMimeTypes, + minHeight, maxHeight, recommendedHeight, + minWidth, maxWidth, recommendedWidth, + maxBytes)); + } else { + warning().nospace() << + "Properties.GetAll(Protocol.Avatars) failed: " << + reply.error().name() << ": " << reply.error().message(); + warning() << " Full functionality requires CM support for the Protocol.Avatars interface"; + } + + continueIntrospection(); + + watcher->deleteLater(); +} + +void ConnectionManager::Private::ProtocolWrapper::gotPresenceProperties( + QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + QVariantMap props; + + if (!reply.isError()) { + debug() << "Got reply to Properties.GetAll(Protocol.Presence)"; + props = reply.value(); + mInfo.setAllowedPresenceStatuses(PresenceSpecList(qdbus_cast<SimpleStatusSpecMap>( + props[QLatin1String("Statuses")]))); + } else { + warning().nospace() << + "Properties.GetAll(Protocol.Presence) failed: " << + reply.error().name() << ": " << reply.error().message(); + warning() << " Full functionality requires CM support for the Protocol.Presence interface"; + } + + continueIntrospection(); + + watcher->deleteLater(); +} + +void ConnectionManager::Private::ProtocolWrapper::fillRCCs() +{ + RequestableChannelClassList classes; + + QVariantMap fixedProps; + QStringList allowedProps; + + // Text chatrooms + fixedProps.insert( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_TEXT)); + fixedProps.insert( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), + static_cast<uint>(HandleTypeRoom)); + + RequestableChannelClass textChatroom = {fixedProps, allowedProps}; + classes.append(textChatroom); + + // 1-1 text chats + fixedProps[QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")] = + static_cast<uint>(HandleTypeContact); + + RequestableChannelClass text = {fixedProps, allowedProps}; + classes.append(text); + + // Media calls + fixedProps[QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")] = + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA); + + RequestableChannelClass media = {fixedProps, allowedProps}; + classes.append(media); + + // Initially audio-only calls + allowedProps.push_back( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio")); + + RequestableChannelClass initialAudio = {fixedProps, allowedProps}; + classes.append(initialAudio); + + // Initially AV calls + allowedProps.push_back( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialVideo")); + + RequestableChannelClass initialAV = {fixedProps, allowedProps}; + classes.append(initialAV); + + // Initially video-only calls + allowedProps.removeAll( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_STREAMED_MEDIA ".InitialAudio")); + + RequestableChannelClass initialVideo = {fixedProps, allowedProps}; + classes.append(initialVideo); + + // That also settles upgrading calls, because the media classes don't have ImmutableStreams + + mInfo.setRequestableChannelClasses(classes); +} + +bool ConnectionManager::Private::ProtocolWrapper::receiveProperties(const QVariantMap &props) +{ + bool gotEverything = + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Interfaces")) && + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Parameters")) && + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".ConnectionInterfaces")) && + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".RequestableChannelClasses")) && + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".VCardField")) && + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".EnglishName")) && + props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Icon")); + + setInterfaces(qdbus_cast<QStringList>( + props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Interfaces")])); + mReadinessHelper->setInterfaces(interfaces()); + + ParamSpecList parameters = qdbus_cast<ParamSpecList>( + props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Parameters")]); + foreach (const ParamSpec &spec, parameters) { + mInfo.addParameter(spec); + } + + mInfo.setVCardField(qdbus_cast<QString>( + props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".VCardField")])); + QString englishName = qdbus_cast<QString>( + props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".EnglishName")]); + if (englishName.isEmpty()) { + QStringList words = mInfo.name().split(QLatin1Char('-')); + for (int i = 0; i < words.size(); ++i) { + words[i][0] = words[i].at(0).toUpper(); + } + englishName = words.join(QLatin1String(" ")); + } + mInfo.setEnglishName(englishName); + QString iconName = qdbus_cast<QString>( + props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".Icon")]); + if (iconName.isEmpty()) { + iconName = QString(QLatin1String("im-%1")).arg(mInfo.name()); + } + mInfo.setIconName(iconName); + + // Don't overwrite the everything-is-possible RCCs with an empty list if there is no RCCs key + if (props.contains(QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".RequestableChannelClasses"))) { + mInfo.setRequestableChannelClasses(qdbus_cast<RequestableChannelClassList>( + props[QLatin1String(TELEPATHY_INTERFACE_PROTOCOL ".RequestableChannelClasses")])); + } + + return gotEverything; +} + +ConnectionManager::Private::Private(ConnectionManager *parent, const QString &name, + const ConnectionFactoryConstPtr &connFactory, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) + : parent(parent), + lowlevel(ConnectionManagerLowlevelPtr(new ConnectionManagerLowlevel(parent))), + name(name), + baseInterface(new Client::ConnectionManagerInterface(parent)), + properties(parent->interface<Client::DBus::PropertiesInterface>()), + readinessHelper(parent->readinessHelper()), + connFactory(connFactory), + chanFactory(chanFactory), + contactFactory(contactFactory) +{ + debug() << "Creating new ConnectionManager:" << parent->busName(); + + if (connFactory->dbusConnection().name() != parent->dbusConnection().name()) { + warning() << " The D-Bus connection in the connection factory is not the proxy connection"; + } + + if (chanFactory->dbusConnection().name() != parent->dbusConnection().name()) { + warning() << " The D-Bus connection in the channel factory is not the proxy connection"; + } + + ReadinessHelper::Introspectables introspectables; + + // As ConnectionManager does not have predefined statuses let's simulate one (0) + ReadinessHelper::Introspectable introspectableCore( + QSet<uint>() << 0, // makesSenseForStatuses + Features(), // dependsOnFeatures + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectMain, + this); + introspectables[FeatureCore] = introspectableCore; + + readinessHelper->addIntrospectables(introspectables); +} + +ConnectionManager::Private::~Private() +{ + delete baseInterface; +} + +bool ConnectionManager::Private::parseConfigFile() +{ + ManagerFile f(name); + if (!f.isValid()) { + return false; + } + + foreach (const QString &protocol, f.protocols()) { + ProtocolInfo info(name, protocol); + + foreach (const ParamSpec &spec, f.parameters(protocol)) { + info.addParameter(spec); + } + info.setRequestableChannelClasses( + f.requestableChannelClasses(protocol)); + info.setVCardField(f.vcardField(protocol)); + info.setEnglishName(f.englishName(protocol)); + info.setIconName(f.iconName(protocol)); + info.setAllowedPresenceStatuses(f.allowedPresenceStatuses(protocol)); + info.setAvatarRequirements(f.avatarRequirements(protocol)); + + protocols.append(info); + } + + return true; +} + +void ConnectionManager::Private::introspectMain(ConnectionManager::Private *self) +{ + if (self->parseConfigFile()) { + self->readinessHelper->setIntrospectCompleted(FeatureCore, true); + return; + } + + warning() << "Error parsing config file for connection manager" + << self->name << "- introspecting"; + + debug() << "Calling Properties::GetAll(ConnectionManager)"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + self->properties->GetAll( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_MANAGER)), + self->parent); + self->parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotMainProperties(QDBusPendingCallWatcher*))); +} + +void ConnectionManager::Private::introspectProtocolsLegacy() +{ + debug() << "Calling ConnectionManager::ListProtocols"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + baseInterface->ListProtocols(), parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotProtocolsLegacy(QDBusPendingCallWatcher*))); +} + +void ConnectionManager::Private::introspectParametersLegacy() +{ + foreach (const QString &protocolName, parametersQueue) { + debug() << "Calling ConnectionManager::GetParameters(" << protocolName << ")"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + baseInterface->GetParameters(protocolName), parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotParametersLegacy(QDBusPendingCallWatcher*))); + } +} + +QString ConnectionManager::Private::makeBusName(const QString &name) +{ + return QString(TP_QT_CONNECTION_MANAGER_BUS_NAME_BASE).append(name); +} + +QString ConnectionManager::Private::makeObjectPath(const QString &name) +{ + return QString(TP_QT_CONNECTION_MANAGER_OBJECT_PATH_BASE).append(name); +} + +/** + * \class ConnectionManagerLowlevel + * \ingroup clientcm + * \headerfile TelepathyQt/connection-manager-lowlevel.h <TelepathyQt/ConnectionManagerLowlevel> + * + * \brief The ConnectionManagerLowlevel class extends ConnectionManager with + * support to low-level features. + */ + +ConnectionManagerLowlevel::ConnectionManagerLowlevel(ConnectionManager *cm) + : mPriv(new Private(cm)) +{ +} + +ConnectionManagerLowlevel::~ConnectionManagerLowlevel() +{ + delete mPriv; +} + +bool ConnectionManagerLowlevel::isValid() const +{ + return !mPriv->cm.isNull(); +} + +ConnectionManagerPtr ConnectionManagerLowlevel::connectionManager() const +{ + return ConnectionManagerPtr(mPriv->cm); +} + +/** + * \class ConnectionManager + * \ingroup clientcm + * \headerfile TelepathyQt/connection-manager.h <TelepathyQt/ConnectionManager> + * + * \brief The ConnectionManager class represents a Telepathy connection manager. + * + * Connection managers allow connections to be made on one or more protocols. + * + * Most client applications should use this functionality via the + * AccountManager, to allow connections to be shared between client + * applications. + */ + +/** + * Feature representing the core that needs to become ready to make the + * ConnectionManager object usable. + * + * Note that this feature must be enabled in order to use most ConnectionManager + * methods. + * See specific methods documentation for more details. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature ConnectionManager::FeatureCore = Feature(QLatin1String(ConnectionManager::staticMetaObject.className()), 0, true); + +/** + * Create a new ConnectionManager object. + * + * The instance will use a connection factory creating Tp::Connection objects with no features + * ready, and a channel factory creating stock Telepathy-Qt4 channel subclasses, as appropriate, + * with no features ready. + * + * \param bus QDBusConnection to use. + * \param name Name of the connection manager. + * \return A ConnectionManagerPtr object pointing to the newly created + * ConnectionManager object. + */ +ConnectionManagerPtr ConnectionManager::create(const QDBusConnection &bus, const QString &name) +{ + return ConnectionManagerPtr(new ConnectionManager(QDBusConnection::sessionBus(), name, + ConnectionFactory::create(bus), ChannelFactory::create(bus), + ContactFactory::create())); +} + +/** + * Create a new ConnectionManager using QDBusConnection::sessionBus() and the given factories. + * + * The channel factory is passed to any Connection objects created by this manager + * object. In fact, they're not used directly by ConnectionManager at all. + * + * A warning is printed if the factories are for a bus different from QDBusConnection::sessionBus(). + * + * \param name Name of the connection manager. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return A ConnectionManagerPtr object pointing to the newly created + * ConnectionManager object. + */ +ConnectionManagerPtr ConnectionManager::create(const QString &name, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return ConnectionManagerPtr(new ConnectionManager(QDBusConnection::sessionBus(), name, + connectionFactory, channelFactory, contactFactory)); +} + +/** + * Create a new ConnectionManager using the given \a bus and the given factories. + * + * The channel factory is passed to any Connection objects created by this manager + * object. In fact, they're not used directly by ConnectionManager at all. + * + * A warning is printed if the factories are for a bus different from QDBusConnection::sessionBus(). + * + * \param bus QDBusConnection to use. + * \param name Name of the connection manager. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return A ConnectionManagerPtr object pointing to the newly created + * ConnectionManager object. + */ +ConnectionManagerPtr ConnectionManager::create(const QDBusConnection &bus, + const QString &name, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return ConnectionManagerPtr(new ConnectionManager(bus, name, + connectionFactory, channelFactory, contactFactory)); +} + +/** + * Construct a new ConnectionManager object using the given \a bus. + * + * \param bus QDBusConnection to use. + * \param name Name of the connection manager. + */ +ConnectionManager::ConnectionManager(const QDBusConnection &bus, + const QString &name, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) + : StatelessDBusProxy(bus, Private::makeBusName(name), + Private::makeObjectPath(name), FeatureCore), + OptionalInterfaceFactory<ConnectionManager>(this), + mPriv(new Private(this, name, connectionFactory, channelFactory, contactFactory)) +{ +} + +/** + * Class destructor. + */ +ConnectionManager::~ConnectionManager() +{ + delete mPriv; +} + +/** + * Return the short name of the connection manager (e.g. "gabble"). + * + * \return The name of the connection manager. + */ +QString ConnectionManager::name() const +{ + return mPriv->name; +} + +/** + * Return the connection factory used by this manager. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the manager would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ConnectionFactory object. + */ +ConnectionFactoryConstPtr ConnectionManager::connectionFactory() const +{ + return mPriv->connFactory; +} + +/** + * Return the channel factory used by this manager. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the manager would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ChannelFactory object. + */ +ChannelFactoryConstPtr ConnectionManager::channelFactory() const +{ + return mPriv->chanFactory; +} + +/** + * Return the contact factory used by this manager. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the manager would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ContactFactory object. + */ +ContactFactoryConstPtr ConnectionManager::contactFactory() const +{ + return mPriv->contactFactory; +} + +/** + * Return a list of strings identifying the protocols supported by this + * connection manager, as described in the \telepathy_spec (e.g. "jabber"). + * + * These identifiers are not intended to be displayed to users directly; user + * interfaces are responsible for mapping them to localized strings. + * + * This method requires ConnectionManager::FeatureCore to be ready. + * + * \return A list of supported protocols. + */ +QStringList ConnectionManager::supportedProtocols() const +{ + QStringList protocols; + foreach (const ProtocolInfo &info, mPriv->protocols) { + protocols.append(info.name()); + } + return protocols; +} + +/** + * Return a list of protocols info for this connection manager. + * + * Note that the returned ProtocolInfoList contents should not be freed. + * + * This method requires ConnectionManager::FeatureCore to be ready. + * + * \return A list of á¹”rotocolInfo. + */ +const ProtocolInfoList &ConnectionManager::protocols() const +{ + return mPriv->protocols; +} + +/** + * Return whether this connection manager implements the protocol specified by + * \a protocolName. + * + * This method requires ConnectionManager::FeatureCore to be ready. + * + * \return \c true if the protocol is supported, \c false otherwise. + * \sa protocol(), protocols() + */ +bool ConnectionManager::hasProtocol(const QString &protocolName) const +{ + foreach (const ProtocolInfo &info, mPriv->protocols) { + if (info.name() == protocolName) { + return true; + } + } + return false; +} + +/** + * Return the ProtocolInfo object for the protocol specified by + * \a protocolName. + * + * This method requires ConnectionManager::FeatureCore to be ready. + * + * \param protocolName The name of the protocol. + * \return A ProtocolInfo object which will return \c for ProtocolInfo::isValid() if the protocol + * specified by \a protocolName is not supported. + * \sa hasProtocol() + */ +ProtocolInfo ConnectionManager::protocol(const QString &protocolName) const +{ + foreach (const ProtocolInfo &info, mPriv->protocols) { + if (info.name() == protocolName) { + return info; + } + } + return ProtocolInfo(); +} + +/** + * Request a Connection object representing a given account on a given protocol + * with the given parameters. + * + * Return a pending operation representing the Connection object which will + * succeed when the connection has been created or fail if an error occurred. + * + * \param protocol Name of the protocol to create the account for. + * \param parameters Account parameters. + * \return A PendingOperation which will emit PendingConnection::finished when + * the account has been created of failed its creation process. + */ +PendingConnection *ConnectionManagerLowlevel::requestConnection(const QString &protocol, + const QVariantMap ¶meters) +{ + if (!isValid()) { + return new PendingConnection(TP_QT_ERROR_NOT_AVAILABLE, + QLatin1String("The connection manager has been destroyed already")); + } + + return new PendingConnection(ConnectionManagerPtr(mPriv->cm), + protocol, parameters); +} + +/** + * Return a pending operation from which a list of all installed connection + * manager short names (such as "gabble" or "haze") can be retrieved if it + * succeeds. + * + * \return A PendingStringList which will emit PendingStringList::finished + * when this object has finished or failed getting the connection + * manager names. + */ +PendingStringList *ConnectionManager::listNames(const QDBusConnection &bus) +{ + return new ConnectionManager::Private::PendingNames(bus); +} + +ConnectionManagerLowlevelPtr ConnectionManager::lowlevel() +{ + return mPriv->lowlevel; +} + +ConnectionManagerLowlevelConstPtr ConnectionManager::lowlevel() const +{ + return mPriv->lowlevel; +} + +/** + * Return the Client::ConnectionManagerInterface for this ConnectionManager. + * 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::ConnectionManagerInterface for this + * ConnectionManager object. + */ +Client::ConnectionManagerInterface *ConnectionManager::baseInterface() const +{ + return mPriv->baseInterface; +} + +/**** Private ****/ +void ConnectionManager::gotMainProperties(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + QVariantMap props; + + if (!reply.isError()) { + debug() << "Got reply to Properties.GetAll(ConnectionManager)"; + props = reply.value(); + + // If Interfaces is not supported, the spec says to assume it's + // empty, so keep the empty list mPriv was initialized with + if (props.contains(QLatin1String("Interfaces"))) { + setInterfaces(qdbus_cast<QStringList>(props[QLatin1String("Interfaces")])); + mPriv->readinessHelper->setInterfaces(interfaces()); + } + } else { + warning().nospace() << + "Properties.GetAll(ConnectionManager) failed: " << + reply.error().name() << ": " << reply.error().message(); + + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, reply.error()); + watcher->deleteLater(); + return; + } + + ProtocolPropertiesMap protocolsMap = + qdbus_cast<ProtocolPropertiesMap>(props[QLatin1String("Protocols")]); + if (!protocolsMap.isEmpty()) { + ProtocolPropertiesMap::const_iterator i = protocolsMap.constBegin(); + ProtocolPropertiesMap::const_iterator end = protocolsMap.constEnd(); + while (i != end) { + QString protocolName = i.key(); + if (!checkValidProtocolName(protocolName)) { + warning() << "Protocol has an invalid name" << protocolName << "- ignoring"; + continue; + } + + QString escapedProtocolName = protocolName; + escapedProtocolName.replace(QLatin1Char('-'), QLatin1Char('_')); + QString protocolPath = QString( + QLatin1String("%1/%2")).arg(objectPath()).arg(escapedProtocolName); + SharedPtr<Private::ProtocolWrapper> wrapper = SharedPtr<Private::ProtocolWrapper>( + new Private::ProtocolWrapper( + dbusConnection(), busName(), protocolPath, + mPriv->name, protocolName, i.value())); + connect(wrapper->becomeReady(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onProtocolReady(Tp::PendingOperation*))); + mPriv->wrappers.insert(wrapper); + ++i; + } + } else { + mPriv->introspectProtocolsLegacy(); + } + + watcher->deleteLater(); +} + +void ConnectionManager::gotProtocolsLegacy(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QStringList> reply = *watcher; + QStringList protocolsNames; + + if (!reply.isError()) { + debug() << "Got reply to ConnectionManager.ListProtocols"; + protocolsNames = reply.value(); + + foreach (const QString &protocolName, protocolsNames) { + mPriv->protocols.append(ProtocolInfo(mPriv->name, protocolName)); + mPriv->parametersQueue.enqueue(protocolName); + } + + mPriv->introspectParametersLegacy(); + } else { + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, reply.error()); + + warning().nospace() << + "ConnectionManager.ListProtocols failed: " << + reply.error().name() << ": " << reply.error().message(); + + // FIXME shouldn't this invalidate the CM? + } + + watcher->deleteLater(); +} + +void ConnectionManager::gotParametersLegacy(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<ParamSpecList> reply = *watcher; + QString protocolName = mPriv->parametersQueue.dequeue(); + bool found = false; + int pos = 0; + foreach (const ProtocolInfo &info, mPriv->protocols) { + if (info.name() == protocolName) { + found = true; + break; + } + ++pos; + } + Q_ASSERT(found); + + if (!reply.isError()) { + debug() << QString(QLatin1String("Got reply to ConnectionManager.GetParameters(%1)")).arg(protocolName); + ParamSpecList parameters = reply.value(); + ProtocolInfo &info = mPriv->protocols[pos]; + foreach (const ParamSpec &spec, parameters) { + debug() << "Parameter" << spec.name << "has flags" << spec.flags + << "and signature" << spec.signature; + + info.addParameter(spec); + } + } else { + // let's remove this protocol as we can't get the params + mPriv->protocols.removeAt(pos); + + warning().nospace() << + QString(QLatin1String("ConnectionManager.GetParameters(%1) failed: ")).arg(protocolName) << + reply.error().name() << ": " << reply.error().message(); + } + + if (mPriv->parametersQueue.isEmpty()) { + if (!mPriv->protocols.isEmpty()) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true); + } else { + // we could not retrieve the params for any protocol, fail core. + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, reply.error()); + } + } + + watcher->deleteLater(); +} + +void ConnectionManager::onProtocolReady(Tp::PendingOperation *op) +{ + PendingReady *pr = qobject_cast<PendingReady*>(op); + SharedPtr<Private::ProtocolWrapper> wrapper = + SharedPtr<Private::ProtocolWrapper>::qObjectCast(pr->proxy()); + ProtocolInfo info = wrapper->info(); + + mPriv->wrappers.remove(wrapper); + + if (!op->isError()) { + mPriv->protocols.append(info); + } else { + warning().nospace() << "Protocol(" << info.name() << ")::becomeReady " + "failed: " << op->errorName() << ": " << op->errorMessage(); + } + + if (mPriv->wrappers.isEmpty()) { + if (!mPriv->protocols.isEmpty()) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true); + } else { + // we could not make any Protocol objects ready, fail core. + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, + op->errorName(), op->errorMessage()); + } + } +} + +} // Tp diff --git a/TelepathyQt/connection-manager.h b/TelepathyQt/connection-manager.h new file mode 100644 index 00000000..6e7ca00b --- /dev/null +++ b/TelepathyQt/connection-manager.h @@ -0,0 +1,125 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_connection_manager_h_HEADER_GUARD_ +#define _TelepathyQt_connection_manager_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/_gen/cli-connection-manager.h> + +#include <TelepathyQt/ChannelFactory> +#include <TelepathyQt/ConnectionFactory> +#include <TelepathyQt/Constants> +#include <TelepathyQt/ContactFactory> +#include <TelepathyQt/DBus> +#include <TelepathyQt/DBusProxy> +#include <TelepathyQt/OptionalInterfaceFactory> +#include <TelepathyQt/ProtocolInfo> +#include <TelepathyQt/ProtocolParameter> +#include <TelepathyQt/ReadinessHelper> +#include <TelepathyQt/SharedPtr> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class ConnectionManagerLowlevel; +class PendingConnection; +class PendingStringList; + +class TP_QT_EXPORT ConnectionManager : public StatelessDBusProxy, + public OptionalInterfaceFactory<ConnectionManager> +{ + Q_OBJECT + Q_DISABLE_COPY(ConnectionManager) + Q_PROPERTY(QString name READ name) + Q_PROPERTY(QStringList supportedProtocols READ supportedProtocols) + Q_PROPERTY(ProtocolInfoList protocols READ protocols) + +public: + static const Feature FeatureCore; + + static ConnectionManagerPtr create(const QDBusConnection &bus, + const QString &name); + static ConnectionManagerPtr create(const QString &name, + const ConnectionFactoryConstPtr &connectionFactory = + ConnectionFactory::create(QDBusConnection::sessionBus()), + const ChannelFactoryConstPtr &channelFactory = + ChannelFactory::create(QDBusConnection::sessionBus()), + const ContactFactoryConstPtr &contactFactory = + ContactFactory::create()); + static ConnectionManagerPtr create(const QDBusConnection &bus, + const QString &name, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory = + ContactFactory::create()); + + virtual ~ConnectionManager(); + + QString name() const; + + ConnectionFactoryConstPtr connectionFactory() const; + ChannelFactoryConstPtr channelFactory() const; + ContactFactoryConstPtr contactFactory() const; + + QStringList supportedProtocols() const; + const ProtocolInfoList &protocols() const; + bool hasProtocol(const QString &protocolName) const; + ProtocolInfo protocol(const QString &protocolName) const; + + static PendingStringList *listNames( + const QDBusConnection &bus = QDBusConnection::sessionBus()); + +#if defined(BUILDING_TP_QT) || defined(TP_QT_ENABLE_LOWLEVEL_API) + ConnectionManagerLowlevelPtr lowlevel(); + ConnectionManagerLowlevelConstPtr lowlevel() const; +#endif + +protected: + ConnectionManager(const QDBusConnection &bus, const QString &name, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory); + + Client::ConnectionManagerInterface *baseInterface() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *); + TP_QT_NO_EXPORT void gotProtocolsLegacy(QDBusPendingCallWatcher *); + TP_QT_NO_EXPORT void gotParametersLegacy(QDBusPendingCallWatcher *); + TP_QT_NO_EXPORT void onProtocolReady(Tp::PendingOperation *); + +private: + friend class PendingConnection; + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/connection-manager.xml b/TelepathyQt/connection-manager.xml new file mode 100644 index 00000000..597a78df --- /dev/null +++ b/TelepathyQt/connection-manager.xml @@ -0,0 +1,12 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Connection Manager interfaces</tp:title> + +<xi:include href="../spec/Connection_Manager.xml"/> +<xi:include href="../spec/Protocol.xml"/> +<xi:include href="../spec/Protocol_Interface_Avatars.xml"/> +<xi:include href="../spec/Protocol_Interface_Presence.xml"/> + +</tp:spec> diff --git a/TelepathyQt/connection.cpp b/TelepathyQt/connection.cpp new file mode 100644 index 00000000..e7a69a8d --- /dev/null +++ b/TelepathyQt/connection.cpp @@ -0,0 +1,2578 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/Connection> +#include <TelepathyQt/ConnectionLowlevel> +#include "TelepathyQt/connection-internal.h" + +#include "TelepathyQt/_gen/cli-connection.moc.hpp" +#include "TelepathyQt/_gen/cli-connection-body.hpp" +#include "TelepathyQt/_gen/connection.moc.hpp" +#include "TelepathyQt/_gen/connection-internal.moc.hpp" +#include "TelepathyQt/_gen/connection-lowlevel.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/ChannelFactory> +#include <TelepathyQt/ConnectionCapabilities> +#include <TelepathyQt/ContactFactory> +#include <TelepathyQt/ContactManager> +#include <TelepathyQt/PendingChannel> +#include <TelepathyQt/PendingContactAttributes> +#include <TelepathyQt/PendingContacts> +#include <TelepathyQt/PendingFailure> +#include <TelepathyQt/PendingHandles> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/PendingVariantMap> +#include <TelepathyQt/PendingVoid> +#include <TelepathyQt/ReferencedHandles> + +#include <QMap> +#include <QMetaObject> +#include <QMutex> +#include <QMutexLocker> +#include <QPair> +#include <QQueue> +#include <QString> +#include <QTimer> +#include <QtGlobal> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT Connection::Private +{ + Private(Connection *parent, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory); + ~Private(); + + void init(); + + static void introspectMain(Private *self); + void introspectMainFallbackStatus(); + void introspectMainFallbackInterfaces(); + void introspectMainFallbackSelfHandle(); + void introspectCapabilities(); + void introspectContactAttributeInterfaces(); + static void introspectSelfContact(Private *self); + static void introspectSimplePresence(Private *self); + static void introspectRoster(Private *self); + static void introspectRosterGroups(Private *self); + static void introspectBalance(Private *self); + static void introspectConnected(Private *self); + + void continueMainIntrospection(); + void setCurrentStatus(uint status); + void forceCurrentStatus(uint status); + void setInterfaces(const QStringList &interfaces); + + // Should always be used instead of directly using baseclass invalidate() + void invalidateResetCaps(const QString &errorName, const QString &errorMessage); + + struct HandleContext; + + // Public object + Connection *parent; + ConnectionLowlevelPtr lowlevel; + + // Factories + ChannelFactoryConstPtr chanFactory; + ContactFactoryConstPtr contactFactory; + + // Instance of generated interface class + Client::ConnectionInterface *baseInterface; + + // Mandatory properties interface proxy + Client::DBus::PropertiesInterface *properties; + + // Optional interface proxies + Client::ConnectionInterfaceSimplePresenceInterface *simplePresence; + + ReadinessHelper *readinessHelper; + + // Introspection + QQueue<void (Private::*)()> introspectMainQueue; + + // FeatureCore + // keep pendingStatus and pendingStatusReason until we emit statusChanged + // so Connection::status() and Connection::statusReason() are consistent + bool introspectingConnected; + + uint pendingStatus; + uint pendingStatusReason; + uint status; + uint statusReason; + ErrorDetails errorDetails; + + uint selfHandle; + + bool immortalHandles; + + ConnectionCapabilities caps; + + ContactManagerPtr contactManager; + + // FeatureSelfContact + bool introspectingSelfContact; + bool reintrospectSelfContactRequired; + ContactPtr selfContact; + QStringList contactAttributeInterfaces; + + // FeatureSimplePresence + SimpleStatusSpecMap simplePresenceStatuses; + uint maxPresenceStatusMessageLength; + + // FeatureAccountBalance + CurrencyAmount accountBalance; + + // misc + // (Bus connection name, service name) -> HandleContext + static QMap<QPair<QString, QString>, HandleContext *> handleContexts; + static QMutex handleContextsLock; + HandleContext *handleContext; + + QString cmName; + QString protocolName; +}; + +struct TP_QT_NO_EXPORT ConnectionLowlevel::Private +{ + Private(Connection *conn) + : conn(QWeakPointer<Connection>(conn)) + { + } + + QWeakPointer<Connection> conn; + HandleIdentifierMap contactsIds; +}; + +// Handle tracking +struct TP_QT_NO_EXPORT Connection::Private::HandleContext +{ + struct Type + { + QMap<uint, uint> refcounts; + QSet<uint> toRelease; + uint requestsInFlight; + bool releaseScheduled; + + Type() + : requestsInFlight(0), + releaseScheduled(false) + { + } + }; + + HandleContext() + : refcount(0) + { + } + + int refcount; + QMutex lock; + QMap<uint, Type> types; +}; + +Connection::Private::Private(Connection *parent, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) + : parent(parent), + lowlevel(ConnectionLowlevelPtr(new ConnectionLowlevel(parent))), + chanFactory(chanFactory), + contactFactory(contactFactory), + baseInterface(new Client::ConnectionInterface(parent)), + properties(parent->interface<Client::DBus::PropertiesInterface>()), + simplePresence(0), + readinessHelper(parent->readinessHelper()), + introspectingConnected(false), + pendingStatus((uint) -1), + pendingStatusReason(ConnectionStatusReasonNoneSpecified), + status((uint) -1), + statusReason(ConnectionStatusReasonNoneSpecified), + selfHandle(0), + immortalHandles(false), + contactManager(ContactManagerPtr(new ContactManager(parent))), + introspectingSelfContact(false), + reintrospectSelfContactRequired(false), + maxPresenceStatusMessageLength(0), + handleContext(0) +{ + accountBalance.amount = 0; + accountBalance.scale = 0; + + Q_ASSERT(properties != 0); + + if (chanFactory->dbusConnection().name() != parent->dbusConnection().name()) { + warning() << " The D-Bus connection in the channel factory is not the proxy connection"; + } + + init(); + + ReadinessHelper::Introspectables introspectables; + + ReadinessHelper::Introspectable introspectableCore( + QSet<uint>() << (uint) -1 << + ConnectionStatusDisconnected << + ConnectionStatusConnected, // makesSenseForStatuses + Features(), // dependsOnFeatures (none) + QStringList(), // dependsOnInterfaces (none) + (ReadinessHelper::IntrospectFunc) &Private::introspectMain, + this); + introspectables[FeatureCore] = introspectableCore; + + ReadinessHelper::Introspectable introspectableSelfContact( + QSet<uint>() << ConnectionStatusConnected, // makesSenseForStatuses + Features() << FeatureCore, // dependsOnFeatures (core) + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectSelfContact, + this); + introspectables[FeatureSelfContact] = introspectableSelfContact; + + ReadinessHelper::Introspectable introspectableSimplePresence( + QSet<uint>() << ConnectionStatusDisconnected << + ConnectionStatusConnected, // makesSenseForStatuses + Features() << FeatureCore, // dependsOnFeatures (core) + QStringList() << QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectSimplePresence, + this); + introspectables[FeatureSimplePresence] = introspectableSimplePresence; + + ReadinessHelper::Introspectable introspectableRoster( + QSet<uint>() << ConnectionStatusConnected, // makesSenseForStatuses + Features() << FeatureCore, // dependsOnFeatures (core) + QStringList() << QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectRoster, + this); + introspectables[FeatureRoster] = introspectableRoster; + + ReadinessHelper::Introspectable introspectableRosterGroups( + QSet<uint>() << ConnectionStatusConnected, // makesSenseForStatuses + Features() << FeatureRoster, // dependsOnFeatures (core) + QStringList() << QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectRosterGroups, + this); + introspectables[FeatureRosterGroups] = introspectableRosterGroups; + + ReadinessHelper::Introspectable introspectableBalance( + QSet<uint>() << ConnectionStatusConnected, // makesSenseForStatuses + Features() << FeatureCore, // dependsOnFeatures (core) + QStringList() << QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_BALANCE), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectBalance, + this); + introspectables[FeatureAccountBalance] = introspectableBalance; + + ReadinessHelper::Introspectable introspectableConnected( + QSet<uint>() << (uint) -1 << + ConnectionStatusDisconnected << + ConnectionStatusConnecting << + ConnectionStatusConnected, // makesSenseForStatuses + Features() << FeatureCore, // dependsOnFeatures (none) + QStringList(), // dependsOnInterfaces (none) + (ReadinessHelper::IntrospectFunc) &Private::introspectConnected, + this); + introspectables[FeatureConnected] = introspectableConnected; + + readinessHelper->addIntrospectables(introspectables); + readinessHelper->setCurrentStatus(status); + parent->connect(readinessHelper, + SIGNAL(statusReady(uint)), + SLOT(onStatusReady(uint))); + + // FIXME: QRegExp probably isn't the most efficient possible way to parse + // this :-) + QRegExp rx(QLatin1String("^" TP_QT_CONNECTION_OBJECT_PATH_BASE + "([_A-Za-z][_A-Za-z0-9]*)" // cap(1) is the CM + "/([_A-Za-z][_A-Za-z0-9]*)" // cap(2) is the protocol + "/([_A-Za-z][_A-Za-z0-9]*)" // account-specific part + )); + + if (rx.exactMatch(parent->objectPath())) { + cmName = rx.cap(1); + protocolName = rx.cap(2); + } else { + warning() << "Connection object path is not spec-compliant, " + "trying again with a different account-specific part check"; + + rx = QRegExp(QLatin1String("^" TP_QT_CONNECTION_OBJECT_PATH_BASE + "([_A-Za-z][_A-Za-z0-9]*)" // cap(1) is the CM + "/([_A-Za-z][_A-Za-z0-9]*)" // cap(2) is the protocol + "/([_A-Za-z0-9]*)" // account-specific part + )); + if (rx.exactMatch(parent->objectPath())) { + cmName = rx.cap(1); + protocolName = rx.cap(2); + } else { + warning() << "Not a valid Connection object path:" << + parent->objectPath(); + } + } +} + +Connection::Private::~Private() +{ + contactManager->resetRoster(); + + // Clear selfContact so its handle will be released cleanly before the + // handleContext + selfContact.reset(); + + QMutexLocker locker(&handleContextsLock); + // All handle contexts locked, so safe + if (!--handleContext->refcount) { + if (!immortalHandles) { + debug() << "Destroying HandleContext"; + + foreach (uint handleType, handleContext->types.keys()) { + HandleContext::Type type = handleContext->types[handleType]; + + if (!type.refcounts.empty()) { + debug() << " Still had references to" << + type.refcounts.size() << "handles, releasing now"; + baseInterface->ReleaseHandles(handleType, type.refcounts.keys()); + } + + if (!type.toRelease.empty()) { + debug() << " Was going to release" << + type.toRelease.size() << "handles, doing that now"; + baseInterface->ReleaseHandles(handleType, type.toRelease.toList()); + } + } + + } + + handleContexts.remove(qMakePair(baseInterface->connection().name(), + parent->objectPath())); + delete handleContext; + } else { + Q_ASSERT(handleContext->refcount > 0); + } +} + +void Connection::Private::init() +{ + debug() << "Connecting to ConnectionError()"; + parent->connect(baseInterface, + SIGNAL(ConnectionError(QString,QVariantMap)), + SLOT(onConnectionError(QString,QVariantMap))); + debug() << "Connecting to StatusChanged()"; + parent->connect(baseInterface, + SIGNAL(StatusChanged(uint,uint)), + SLOT(onStatusChanged(uint,uint))); + debug() << "Connecting to SelfHandleChanged()"; + parent->connect(baseInterface, + SIGNAL(SelfHandleChanged(uint)), + SLOT(onSelfHandleChanged(uint))); + + QMutexLocker locker(&handleContextsLock); + QString busConnectionName = baseInterface->connection().name(); + + if (handleContexts.contains(qMakePair(busConnectionName, parent->objectPath()))) { + debug() << "Reusing existing HandleContext for" << parent->objectPath(); + handleContext = handleContexts[ + qMakePair(busConnectionName, parent->objectPath())]; + } else { + debug() << "Creating new HandleContext for" << parent->objectPath(); + handleContext = new HandleContext; + handleContexts[ + qMakePair(busConnectionName, parent->objectPath())] = handleContext; + } + + // All handle contexts locked, so safe + ++handleContext->refcount; +} + +void Connection::Private::introspectMain(Connection::Private *self) +{ + debug() << "Calling Properties::GetAll(Connection)"; + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher( + self->properties->GetAll(QLatin1String(TELEPATHY_INTERFACE_CONNECTION)), + self->parent); + self->parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotMainProperties(QDBusPendingCallWatcher*))); +} + +void Connection::Private::introspectMainFallbackStatus() +{ + debug() << "Calling GetStatus()"; + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher(baseInterface->GetStatus(), + parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotStatus(QDBusPendingCallWatcher*))); +} + +void Connection::Private::introspectMainFallbackInterfaces() +{ + debug() << "Calling GetInterfaces()"; + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher(baseInterface->GetInterfaces(), + parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotInterfaces(QDBusPendingCallWatcher*))); +} + +void Connection::Private::introspectMainFallbackSelfHandle() +{ + debug() << "Calling GetSelfHandle()"; + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher(baseInterface->GetSelfHandle(), + parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotSelfHandle(QDBusPendingCallWatcher*))); +} + +void Connection::Private::introspectCapabilities() +{ + debug() << "Retrieving capabilities"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + properties->Get( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS), + QLatin1String("RequestableChannelClasses")), parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotCapabilities(QDBusPendingCallWatcher*))); +} + +void Connection::Private::introspectContactAttributeInterfaces() +{ + debug() << "Retrieving contact attribute interfaces"; + QDBusPendingCall call = + properties->Get( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS), + QLatin1String("ContactAttributeInterfaces")); + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher(call, parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotContactAttributeInterfaces(QDBusPendingCallWatcher*))); +} + +void Connection::Private::introspectSelfContact(Connection::Private *self) +{ + debug() << "Building self contact"; + + Q_ASSERT(!self->introspectingSelfContact); + + self->introspectingSelfContact = true; + self->reintrospectSelfContactRequired = false; + + PendingContacts *contacts = self->contactManager->contactsForHandles( + UIntList() << self->selfHandle); + self->parent->connect(contacts, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(gotSelfContact(Tp::PendingOperation*))); +} + +void Connection::Private::introspectSimplePresence(Connection::Private *self) +{ + Q_ASSERT(self->properties != 0); + + debug() << "Calling Properties::Get(" + "Connection.I.SimplePresence.Statuses)"; + QDBusPendingCall call = + self->properties->GetAll( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE)); + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher(call, self->parent); + self->parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotSimpleStatuses(QDBusPendingCallWatcher*))); +} + +void Connection::Private::introspectRoster(Connection::Private *self) +{ + debug() << "Introspecting roster"; + + PendingOperation *op = self->contactManager->introspectRoster(); + self->parent->connect(op, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onIntrospectRosterFinished(Tp::PendingOperation*))); +} + +void Connection::Private::introspectRosterGroups(Connection::Private *self) +{ + debug() << "Introspecting roster groups"; + + PendingOperation *op = self->contactManager->introspectRosterGroups(); + self->parent->connect(op, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onIntrospectRosterGroupsFinished(Tp::PendingOperation*))); +} + +void Connection::Private::introspectBalance(Connection::Private *self) +{ + debug() << "Introspecting balance"; + + // we already checked if balance interface exists, so bypass requests + // interface checking + Client::ConnectionInterfaceBalanceInterface *iface = + self->parent->interface<Client::ConnectionInterfaceBalanceInterface>(); + + debug() << "Connecting to Balance.BalanceChanged"; + self->parent->connect(iface, + SIGNAL(BalanceChanged(Tp::CurrencyAmount)), + SLOT(onBalanceChanged(Tp::CurrencyAmount))); + + debug() << "Retrieving balance"; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + self->properties->Get( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_BALANCE), + QLatin1String("AccountBalance")), self->parent); + self->parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotBalance(QDBusPendingCallWatcher*))); +} + +void Connection::Private::introspectConnected(Connection::Private *self) +{ + Q_ASSERT(!self->introspectingConnected); + self->introspectingConnected = true; + + if (self->pendingStatus == ConnectionStatusConnected) { + self->readinessHelper->setIntrospectCompleted(FeatureConnected, true); + self->introspectingConnected = false; + } +} + +void Connection::Private::continueMainIntrospection() +{ + if (!parent->isValid()) { + debug() << parent << "stopping main introspection, as it has been invalidated"; + return; + } + + if (introspectMainQueue.isEmpty()) { + readinessHelper->setIntrospectCompleted(FeatureCore, true); + } else { + (this->*(introspectMainQueue.dequeue()))(); + } +} + +void Connection::Private::setCurrentStatus(uint status) +{ + // ReadinessHelper waits for all in-flight introspection ops to finish for the current status + // before proceeding to a new one. + // + // Therefore we don't need any safeguarding here to prevent finishing introspection when there + // is a pending status change. However, we can speed up the process slightly by canceling any + // pending introspect ops from our local introspection queue when it's waiting for us. + + introspectMainQueue.clear(); + + if (introspectingConnected) { + // On the other hand, we have to finish the Connected introspection for now, as + // ReadinessHelper would otherwise wait indefinitely for it to land + debug() << "Finishing FeatureConnected for status" << this->status << + "to allow ReadinessHelper to introspect new status" << status; + readinessHelper->setIntrospectCompleted(FeatureConnected, true); + introspectingConnected = false; + } + + readinessHelper->setCurrentStatus(status); +} + +void Connection::Private::forceCurrentStatus(uint status) +{ + // only update the status if we did not get it from StatusChanged + if (pendingStatus == (uint) -1) { + debug() << "Got status:" << status; + pendingStatus = status; + // No need to re-run introspection as we just received the status. Let + // the introspection continue normally but update readinessHelper with + // the correct status. + readinessHelper->forceCurrentStatus(status); + } +} + +void Connection::Private::setInterfaces(const QStringList &interfaces) +{ + debug() << "Got interfaces:" << interfaces; + parent->setInterfaces(interfaces); + readinessHelper->setInterfaces(interfaces); +} + +void Connection::Private::invalidateResetCaps(const QString &error, const QString &message) +{ + caps.updateRequestableChannelClasses(RequestableChannelClassList()); + parent->invalidate(error, message); +} + +/** + * \class ConnectionLowlevel + * \ingroup clientconn + * \headerfile TelepathyQt/connection-lowlevel.h <TelepathyQt/ConnectionLowlevel> + * + * \brief The ConnectionLowlevel class extends Connection with support to + * low-level features. + */ + +ConnectionLowlevel::ConnectionLowlevel(Connection *conn) + : mPriv(new Private(conn)) +{ +} + +ConnectionLowlevel::~ConnectionLowlevel() +{ + delete mPriv; +} + +bool ConnectionLowlevel::isValid() const +{ + return !mPriv->conn.isNull(); +} + +ConnectionPtr ConnectionLowlevel::connection() const +{ + return ConnectionPtr(mPriv->conn); +} + +Connection::PendingConnect::PendingConnect(const ConnectionPtr &connection, + const Features &requestedFeatures) + : PendingReady(connection, requestedFeatures) +{ + if (!connection) { + // Called when the connection had already been destroyed + return; + } + + QDBusPendingCall call = connection->baseInterface()->Connect(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); + + connect(connection.data(), + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + this, + SLOT(onConnInvalidated(Tp::DBusProxy*,QString,QString))); + + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + this, + SLOT(onConnectReply(QDBusPendingCallWatcher*))); +} + +void Connection::PendingConnect::onConnectReply(QDBusPendingCallWatcher *watcher) +{ + ConnectionPtr connection = ConnectionPtr::qObjectCast(proxy()); + + if (watcher->isError()) { + debug() << "Connect failed with" << + watcher->error().name() << ": " << watcher->error().message(); + setFinishedWithError(watcher->error()); + connection->disconnect( + this, + SLOT(onConnInvalidated(Tp::DBusProxy*,QString,QString))); + } else { + if (connection->status() == ConnectionStatusConnected) { + onStatusChanged(ConnectionStatusConnected); + } else { + // Wait for statusChanged()! Connect returning just means that the connection has + // started to connect - spec quoted for truth: + // + // Connect () -> nothing + // Request that the connection be established. This will be done asynchronously and + // errors will be returned by emitting StatusChanged signals. + // + // Which should actually say progress and/or errors IMO, but anyway... + connect(connection.data(), + SIGNAL(statusChanged(Tp::ConnectionStatus)), + SLOT(onStatusChanged(Tp::ConnectionStatus))); + } + } + + watcher->deleteLater(); +} + +void Connection::PendingConnect::onStatusChanged(ConnectionStatus newStatus) +{ + ConnectionPtr connection = ConnectionPtr::qObjectCast(proxy()); + + if (newStatus == ConnectionStatusDisconnected) { + debug() << "Connection became disconnected while a PendingConnect was underway"; + setFinishedWithError(connection->invalidationReason(), connection->invalidationMessage()); + + connection->disconnect(this, + SLOT(onConnInvalidated(Tp::DBusProxy*,QString,QString))); + + return; + } + + if (newStatus == ConnectionStatusConnected) { + // OK, the connection is Connected now - finally, we'll get down to business + connect(connection->becomeReady(requestedFeatures()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onBecomeReadyReply(Tp::PendingOperation*))); + } +} + +void Connection::PendingConnect::onBecomeReadyReply(Tp::PendingOperation *op) +{ + ConnectionPtr connection = ConnectionPtr::qObjectCast(proxy()); + + // We don't care about future disconnects even if they happen before we are destroyed + // (which happens two mainloop iterations from now) + connection->disconnect(this, + SLOT(onStatusChanged(Tp::ConnectionStatus))); + connection->disconnect(this, + SLOT(onConnInvalidated(Tp::DBusProxy*,QString,QString))); + + if (op->isError()) { + debug() << "Connection->becomeReady failed with" << + op->errorName() << ": " << op->errorMessage(); + setFinishedWithError(op->errorName(), op->errorMessage()); + } else { + debug() << "Connected"; + + if (connection->isValid()) { + setFinished(); + } else { + debug() << " ... but the Connection was immediately invalidated!"; + setFinishedWithError(connection->invalidationReason(), connection->invalidationMessage()); + } + } +} + +void Connection::PendingConnect::onConnInvalidated(Tp::DBusProxy *proxy, const QString &error, + const QString &message) +{ + ConnectionPtr connection = ConnectionPtr::qObjectCast(this->proxy()); + + Q_ASSERT(proxy == connection.data()); + + if (!isFinished()) { + debug() << "Unable to connect. Connection invalidated"; + setFinishedWithError(error, message); + } + + connection->disconnect(this, + SLOT(onStatusChanged(Tp::ConnectionStatus))); +} + +QMap<QPair<QString, QString>, Connection::Private::HandleContext*> Connection::Private::handleContexts; +QMutex Connection::Private::handleContextsLock; + +/** + * \class Connection + * \ingroup clientconn + * \headerfile TelepathyQt/connection.h <TelepathyQt/Connection> + * + * \brief The Connection class represents a Telepathy connection. + * + * This models a connection to a single user account on a communication service. + * + * Contacts, and server-stored lists (such as subscribed contacts, + * block lists, or allow lists) on a service are all represented using the + * ContactManager object on the connection, which is valid only for the lifetime + * of the connection object. + * + * The remote object accessor functions on this object (status(), + * statusReason(), 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 connection self contact, it is necessary to + * enable the feature Connection::FeatureSelfContact. + * See the individual methods descriptions for more details. + * + * Connection features can be enabled by constructing a ConnectionFactory 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. + * + * As an addition to accessors, signals are emitted to indicate that properties have changed, + * for example statusChanged()(), selfContactChanged(), etc. + * + * \section conn_usage_sec Usage + * + * \subsection conn_create_sec Creating a connection object + * + * The easiest way to create connection objects is through Account. One can + * just use the Account::connection method to get an account active connection. + * + * If you already know the object path, you can just call create(). + * For example: + * + * \code ConnectionPtr conn = Connection::create(busName, objectPath); \endcode + * + * A ConnectionPtr object is returned, which will automatically keep + * track of object lifetime. + * + * You can also provide a D-Bus connection as a QDBusConnection: + * + * \code + * + * ConnectionPtr conn = Connection::create(QDBusConnection::sessionBus(), + * busName, objectPath); + * + * \endcode + * + * \subsection conn_ready_sec Making connection ready to use + * + * A Connection 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 onConnectionReady(Tp::PendingOperation*); + * + * private: + * ConnectionPtr conn; + * }; + * + * MyClass::MyClass(const QString &busName, const QString &objectPath, + * QObject *parent) + * : QObject(parent) + * conn(Connection::create(busName, objectPath)) + * { + * // connect and become ready + * connect(conn->requestConnect(), + * SIGNAL(finished(Tp::PendingOperation*)), + * SLOT(onConnectionReady(Tp::PendingOperation*))); + * } + * + * void MyClass::onConnectionReady(Tp::PendingOperation *op) + * { + * if (op->isError()) { + * qWarning() << "Account cannot become ready:" << + * op->errorName() << "-" << op->errorMessage(); + * return; + * } + * + * // Connection is now ready + * } + * + * \endcode + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Feature representing the core that needs to become ready to make the + * Connection object usable. + * + * Note that this feature must be enabled in order to use most Connection + * methods. + * See specific methods documentation for more details. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature Connection::FeatureCore = Feature(QLatin1String(Connection::staticMetaObject.className()), 0, true); + +/** + * Feature used to retrieve the connection self contact. + * + * See self contact specific methods' documentation for more details. + * + * \sa selfContact(), selfContactChanged() + */ +const Feature Connection::FeatureSelfContact = Feature(QLatin1String(Connection::staticMetaObject.className()), 1); + +/** + * Feature used to retrieve/keep track of the connection self presence. + * + * See simple presence specific methods' documentation for more details. + */ +const Feature Connection::FeatureSimplePresence = Feature(QLatin1String(Connection::staticMetaObject.className()), 2); + +/** + * Feature used to enable roster support on Connection::contactManager. + * + * See ContactManager roster specific methods' documentation for more details. + * + * \sa ContactManager::allKnownContacts() + */ +const Feature Connection::FeatureRoster = Feature(QLatin1String(Connection::staticMetaObject.className()), 4); + +/** + * Feature used to enable roster groups support on Connection::contactManager. + * + * See ContactManager roster groups specific methods' documentation for more + * details. + * + * \sa ContactManager::allKnownGroups() + */ +const Feature Connection::FeatureRosterGroups = Feature(QLatin1String(Connection::staticMetaObject.className()), 5); + +/** + * Feature used to retrieve/keep track of the connection account balance. + * + * See account balance specific methods' documentation for more details. + * + * \sa accountBalance(), accountBalanceChanged() + */ +const Feature Connection::FeatureAccountBalance = Feature(QLatin1String(Connection::staticMetaObject.className()), 6); + +/** + * When this feature is prepared, it means that the connection status() is + * ConnectionStatusConnected. + * + * Note that if ConnectionFactory is being used with FeatureConnected set, Connection objects will + * only be signalled by the library when the corresponding connection is in status() + * ConnectionStatusConnected. + */ +const Feature Connection::FeatureConnected = Feature(QLatin1String(Connection::staticMetaObject.className()), 7); + +/** + * Create a new connection object using QDBusConnection::sessionBus(). + * + * A warning is printed if the factories are not for QDBusConnection::sessionBus(). + * + * \param busName The connection well-known bus name (sometimes called a + * "service name"). + * \param objectPath The connection object path. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return A ConnectionPtr object pointing to the newly created Connection object. + */ +ConnectionPtr Connection::create(const QString &busName, + const QString &objectPath, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return ConnectionPtr(new Connection(QDBusConnection::sessionBus(), + busName, objectPath, channelFactory, contactFactory, + Connection::FeatureCore)); +} + +/** + * Create a new connection object using the given \a bus. + * + * A warning is printed if the factories are not for \a bus. + * + * \param bus QDBusConnection to use. + * \param busName The connection well-known bus name (sometimes called a + * "service name"). + * \param objectPath The connection object path. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \return A ConnectionPtr object pointing to the newly created Connection object. + */ +ConnectionPtr Connection::create(const QDBusConnection &bus, + const QString &busName, const QString &objectPath, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return ConnectionPtr(new Connection(bus, busName, objectPath, channelFactory, contactFactory, + Connection::FeatureCore)); +} + +/** + * Construct a new connection object using the given \a bus. + * + * A warning is printed if the factories are not for \a bus. + * + * \param bus QDBusConnection to use. + * \param busName The connection's well-known bus name (sometimes called a + * "service name"). + * \param objectPath The connection object path. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \param coreFeature The core feature of the Connection subclass. The corresponding introspectable + * should depend on Connection::FeatureCore. + */ +Connection::Connection(const QDBusConnection &bus, + const QString &busName, + const QString &objectPath, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory, + const Feature &coreFeature) + : StatefulDBusProxy(bus, busName, objectPath, coreFeature), + OptionalInterfaceFactory<Connection>(this), + mPriv(new Private(this, channelFactory, contactFactory)) +{ +} + +/** + * Class destructor. + */ +Connection::~Connection() +{ + delete mPriv; +} + +/** + * Return the channel factory used by this connection. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the account would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ChannelFactory object. + */ +ChannelFactoryConstPtr Connection::channelFactory() const +{ + return mPriv->chanFactory; +} + +/** + * Return the contact factory used by this connection. + * + * Only read access is provided. This allows constructing object instances and examining the object + * construction settings, but not changing settings. Allowing changes would lead to tricky + * situations where objects constructed at different times by the account would have unpredictably + * different construction settings (eg. subclass). + * + * \return A read-only pointer to the ContactFactory object. + */ +ContactFactoryConstPtr Connection::contactFactory() const +{ + return mPriv->contactFactory; +} + +/** + * Return the connection manager name of this connection. + * + * \return The connection manager name. + */ +QString Connection::cmName() const +{ + return mPriv->cmName; +} + +/** + * Return the protocol name of this connection. + * + * \return The protocol name. + */ +QString Connection::protocolName() const +{ + return mPriv->protocolName; +} + +/** + * Return the status of this connection. + * + * Change notification is via the statusChanged() signal. + * + * This method requires Connection::FeatureCore to be ready. + * + * \return The status as #ConnectionStatus. + * \sa statusChanged(), statusReason(), errorDetails() + */ +ConnectionStatus Connection::status() const +{ + return (ConnectionStatus) mPriv->status; +} + +/** + * Return the reason for this connection status. + * + * The validity and change rules are the same as for status(). + * + * The status reason should be only used as a fallback in error handling when the application + * doesn't understand an error name given as the invalidation reason, which may in some cases be + * domain/UI-specific. + * + * This method requires Connection::FeatureCore to be ready. + * + * \return The status reason as #ConnectionStatusReason. + * \sa invalidated(), invalidationReason() + */ +ConnectionStatusReason Connection::statusReason() const +{ + return (ConnectionStatusReason) mPriv->statusReason; +} + +struct TP_QT_NO_EXPORT Connection::ErrorDetails::Private : public QSharedData +{ + Private(const QVariantMap &details) + : details(details) {} + + QVariantMap details; +}; + +/** + * \class Connection::ErrorDetails + * \ingroup clientconn + * \headerfile TelepathyQt/connection.h <TelepathyQt/Connection> + * + * \brief The Connection::ErrorDetails class represents the details of a connection error. + * + * It contains detailed information about the reason for the connection going invalidated(). + * + * Some services may provide additional error information in the ConnectionError D-Bus signal, when + * a Connection is disconnected / has become unusable. If the service didn't provide any, or has not + * been invalidated yet, the instance will be invalid, as returned by isValid(). + * + * The information provided by invalidationReason() and this class should always be used in error + * handling in preference to statusReason(). The status reason can be used as a fallback, however, + * if the client doesn't understand what a particular value returned by invalidationReason() means, + * as it may be domain-specific with some services. + * + * Connection::errorDetails() can be used to return the instance containing the details for + * invalidating that connection after invalidated() has been emitted. + */ + +/** + * Constructs a new invalid ErrorDetails instance. + */ +Connection::ErrorDetails::ErrorDetails() + : mPriv(0) +{ +} + +/** + * Construct a error details instance with the given details. The instance will indicate that + * it is valid. + */ +Connection::ErrorDetails::ErrorDetails(const QVariantMap &details) + : mPriv(new Private(details)) +{ +} + +/** + * Copy constructor. + */ +Connection::ErrorDetails::ErrorDetails(const ErrorDetails &other) + : mPriv(other.mPriv) +{ +} + +/** + * Class destructor. + */ +Connection::ErrorDetails::~ErrorDetails() +{ +} + +/** + * Assigns all information (validity, details) from other to this. + */ +Connection::ErrorDetails &Connection::ErrorDetails::operator=( + const ErrorDetails &other) +{ + if (this->mPriv.constData() != other.mPriv.constData()) + this->mPriv = other.mPriv; + + return *this; +} + +/** + * \fn bool Connection::ErrorDetails::isValid() const + * + * Return whether or not the details are valid (have actually been received from the service). + * + * \return \c true if valid, \c false otherwise. + */ + +/** + * \fn bool Connection::ErrorDetails::hasDebugMessage() const + * + * Return whether or not the details specify a debug message. + * + * If present, the debug message will likely be the same string as the one returned by + * invalidationMessage(). + * + * The debug message is purely informational, offered for display for bug reporting purposes, and + * should not be attempted to be parsed. + * + * \return \c true if debug message is present, \c false otherwise. + * \sa debugMessage() + */ + +/** + * \fn QString Connection::ErrorDetails::debugMessage() const + * + * Return the debug message specified by the details, if any. + * + * If present, the debug message will likely be the same string as the one returned by + * invalidationMessage(). + * + * 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 given in the low-level ConnectionError signal. + * + * This is useful for accessing domain-specific additional details. + * + * \return The details of the connection error as QVariantMap. + */ +QVariantMap Connection::ErrorDetails::allDetails() const +{ + return isValid() ? mPriv->details : QVariantMap(); +} + +/** + * Return detailed information about the reason for the connection going invalidated(). + * + * Some services may provide additional error information in the ConnectionError D-Bus signal, when + * a Connection is disconnected / has become unusable. If the service didn't provide any, or has not + * been invalidated yet, an invalid instance is returned. + * + * The information provided by invalidationReason() and this method should always be used in error + * handling in preference to statusReason(). The status reason can be used as a fallback, however, + * if the client doesn't understand what a particular value returned by invalidationReason() means, + * as it may be domain-specific with some services. + * + * \return The error details as a Connection::ErrorDetails object. + * \sa status(), statusReason(), invalidationReason() + */ +const Connection::ErrorDetails &Connection::errorDetails() const +{ + if (isValid()) { + warning() << "Connection::errorDetails() used on" << objectPath() << "which is valid"; + } + + return mPriv->errorDetails; +} + +/** + * Return the handle representing the user on this connection. + * + * Note that if the connection is not yet in the ConnectionStatusConnected state, + * the value of this property may be zero. + * + * Change notification is via the selfHandleChanged() signal. + * + * This method requires Connection::FeatureCore to be ready. + * + * \return The user handle. + * \sa selfHandleChanged(), selfContact() + */ +uint Connection::selfHandle() const +{ + return mPriv->selfHandle; +} + +/** + * Return a dictionary of presence statuses valid for use in this connection. + * + * The value may have changed arbitrarily during the time the + * Connection spends in status ConnectionStatusConnecting, + * again staying fixed for the entire time in ConnectionStatusConnected. + * + * This method requires Connection::FeatureSimplePresence to be ready. + * + * \return The allowed statuses as a map from string identifiers to SimpleStatusSpec objects. + */ +SimpleStatusSpecMap ConnectionLowlevel::allowedPresenceStatuses() const +{ + if (!isValid()) { + warning() << "ConnectionLowlevel::selfHandle() " + "called for a connection which is already destroyed"; + return SimpleStatusSpecMap(); + } + + ConnectionPtr conn(mPriv->conn); + + if (!conn->isReady(Connection::FeatureSimplePresence)) { + warning() << "Trying to retrieve allowed presence statuses from connection, but " + "simple presence is not supported or was not requested. " + "Enable FeatureSimplePresence in this connection"; + } + + return conn->mPriv->simplePresenceStatuses; +} + +/** + * Return the maximum length for a presence status message. + * + * The value may have changed arbitrarily during the time the + * Connection spends in status ConnectionStatusConnecting, + * again staying fixed for the entire time in ConnectionStatusConnected. + * + * This method requires Connection::FeatureSimplePresence to be ready. + * + * \return The maximum length, or 0 if there is no limit. + */ +uint ConnectionLowlevel::maxPresenceStatusMessageLength() const +{ + if (!isValid()) { + warning() << "ConnectionLowlevel::maxPresenceStatusMessageLength() " + "called for a connection which is already destroyed"; + return 0; + } + + ConnectionPtr conn(mPriv->conn); + + if (!conn->isReady(Connection::FeatureSimplePresence)) { + warning() << "Trying to retrieve max presence status message length connection, but " + "simple presence is not supported or was not requested. " + "Enable FeatureSimplePresence in this connection"; + } + + return conn->mPriv->maxPresenceStatusMessageLength; +} + +/** + * Set the self presence status. + * + * This should generally only be called by an Account Manager. In typical usage, + * Account::setRequestedPresence() should be used instead. + * + * \a status must be one of the allowed statuses returned by + * allowedPresenceStatuses(). + * + * Note that clients SHOULD set the status message for the local user to the + * empty string, unless the user has actually provided a specific message (i.e. + * one that conveys more information than the ConnectionStatus). + * + * \param status The desired status. + * \param statusMessage The desired status message. + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + * \sa allowedPresenceStatuses() + */ +PendingOperation *ConnectionLowlevel::setSelfPresence(const QString &status, + const QString &statusMessage) +{ + if (!isValid()) { + warning() << "ConnectionLowlevel::selfHandle() called for a connection which is already destroyed"; + return new PendingFailure(TP_QT_ERROR_NOT_AVAILABLE, QLatin1String("Connection already destroyed"), + ConnectionPtr()); + } + + ConnectionPtr conn(mPriv->conn); + + if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE))) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Connection does not support SimplePresence"), conn); + } + + Client::ConnectionInterfaceSimplePresenceInterface *simplePresenceInterface = + conn->interface<Client::ConnectionInterfaceSimplePresenceInterface>(); + return new PendingVoid( + simplePresenceInterface->SetPresence(status, statusMessage), conn); +} + +/** + * Return the object representing the user on this connection. + * + * Note that if the connection is not yet in the ConnectionStatusConnected state, the value of this + * property may be null. + * + * Change notification is via the selfContactChanged() signal. + * + * This method requires Connection::FeatureSelfContact to be ready. + * + * \return A pointer to the Contact object, or a null ContactPtr if unknown. + * \sa selfContactChanged(), selfHandle() + */ +ContactPtr Connection::selfContact() const +{ + if (!isReady(FeatureSelfContact)) { + warning() << "Connection::selfContact() used, but becomeReady(FeatureSelfContact) " + "hasn't been completed!"; + } + + return mPriv->selfContact; +} + +/** + * Return the user's balance on the account corresponding to this connection. + * + * A negative amount may be possible on some services, and indicates that the user + * owes money to the service provider. + * + * Change notification is via the accountBalanceChanged() signal. + * + * This method requires Connection::FeatureAccountBalance to be ready. + * + * \return The account balance as #CurrencyAmount. + * \sa accountBalanceChanged() + */ +CurrencyAmount Connection::accountBalance() const +{ + if (!isReady(FeatureAccountBalance)) { + warning() << "Connection::accountBalance() used before connection " + "FeatureAccountBalance is ready"; + } + + return mPriv->accountBalance; +} + +/** + * Return the capabilities for this connection. + * + * User interfaces can use this information to show or hide UI components. + * + * This property cannot change after the connection has gone to state + * ConnectionStatusConnected, so there is no change notification. + * + * This method requires Connection::FeatureCore to be ready. + * + * @return The capabilities of this connection. + */ +ConnectionCapabilities Connection::capabilities() const +{ + if (!isReady(Connection::FeatureCore)) { + warning() << "Connection::capabilities() used before connection " + "FeatureCore is ready"; + } + + return mPriv->caps; +} + +void Connection::onStatusReady(uint status) +{ + Q_ASSERT(status == mPriv->pendingStatus); + + if (mPriv->status == status) { + return; + } + + mPriv->status = status; + mPriv->statusReason = mPriv->pendingStatusReason; + + if (isValid()) { + emit statusChanged((ConnectionStatus) mPriv->status); + } else { + debug() << this << " not emitting statusChanged because it has been invalidated"; + } +} + +void Connection::onStatusChanged(uint status, uint reason) +{ + debug() << "StatusChanged from" << mPriv->pendingStatus + << "to" << status << "with reason" << reason; + + if (mPriv->pendingStatus == status) { + warning() << "New status was the same as the old status! Ignoring" + "redundant StatusChanged"; + return; + } + + uint oldStatus = mPriv->pendingStatus; + mPriv->pendingStatus = status; + mPriv->pendingStatusReason = reason; + + switch (status) { + case ConnectionStatusConnected: + debug() << "Performing introspection for the Connected status"; + mPriv->setCurrentStatus(status); + break; + + case ConnectionStatusConnecting: + mPriv->setCurrentStatus(status); + break; + + case ConnectionStatusDisconnected: + { + QString errorName = ConnectionHelper::statusReasonToErrorName( + (ConnectionStatusReason) reason, + (ConnectionStatus) oldStatus); + + // TODO should we signal statusChanged to Disconnected here or just + // invalidate? + // Also none of the pendingOperations will finish. The + // user should just consider them to fail as the connection + // is invalid + onStatusReady(ConnectionStatusDisconnected); + mPriv->invalidateResetCaps(errorName, + QString(QLatin1String("ConnectionStatusReason = %1")).arg(uint(reason))); + } + break; + + default: + warning() << "Unknown connection status" << status; + break; + } +} + +void Connection::onConnectionError(const QString &error, + const QVariantMap &details) +{ + debug().nospace() << "Connection(" << objectPath() << ") got ConnectionError(" << error + << ") with " << details.size() << " details"; + + mPriv->errorDetails = details; + mPriv->invalidateResetCaps(error, + details.value(QLatin1String("debug-message")).toString()); +} + +void Connection::gotMainProperties(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + QVariantMap props; + + if (!reply.isError()) { + props = reply.value(); + } else { + warning().nospace() << "Properties::GetAll(Connection) failed with " << + reply.error().name() << ": " << reply.error().message(); + // let's try to fallback first before failing + } + + uint status = static_cast<uint>(-1); + if (props.contains(QLatin1String("Status")) + && ((status = qdbus_cast<uint>(props[QLatin1String("Status")])) <= + ConnectionStatusDisconnected)) { + mPriv->forceCurrentStatus(status); + } else { + // only introspect status if we did not got it from StatusChanged + if (mPriv->pendingStatus == (uint) -1) { + mPriv->introspectMainQueue.enqueue( + &Private::introspectMainFallbackStatus); + } + } + + if (props.contains(QLatin1String("Interfaces"))) { + mPriv->setInterfaces(qdbus_cast<QStringList>( + props[QLatin1String("Interfaces")])); + } else { + mPriv->introspectMainQueue.enqueue( + &Private::introspectMainFallbackInterfaces); + } + + if (props.contains(QLatin1String("SelfHandle"))) { + mPriv->selfHandle = qdbus_cast<uint>( + props[QLatin1String("SelfHandle")]); + } else { + mPriv->introspectMainQueue.enqueue( + &Private::introspectMainFallbackSelfHandle); + } + + if (props.contains(QLatin1String("HasImmortalHandles"))) { + mPriv->immortalHandles = qdbus_cast<bool>(props[QLatin1String("HasImmortalHandles")]); + } + + if (hasInterface(TP_QT_IFACE_CONNECTION_INTERFACE_REQUESTS)) { + mPriv->introspectMainQueue.enqueue( + &Private::introspectCapabilities); + } + + if (hasInterface(TP_QT_IFACE_CONNECTION_INTERFACE_CONTACTS)) { + mPriv->introspectMainQueue.enqueue( + &Private::introspectContactAttributeInterfaces); + } + + mPriv->continueMainIntrospection(); + + watcher->deleteLater(); +} + +void Connection::gotStatus(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<uint> reply = *watcher; + + if (!reply.isError()) { + mPriv->forceCurrentStatus(reply.value()); + + mPriv->continueMainIntrospection(); + } else { + warning().nospace() << "GetStatus() failed with " << + reply.error().name() << ": " << reply.error().message(); + mPriv->invalidateResetCaps(reply.error().name(), reply.error().message()); + } + + watcher->deleteLater(); +} + +void Connection::gotInterfaces(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QStringList> reply = *watcher; + + if (!reply.isError()) { + mPriv->setInterfaces(reply.value()); + } + else { + warning().nospace() << "GetInterfaces() failed with " << + reply.error().name() << ": " << reply.error().message() << + " - assuming no new interfaces"; + // let's not fail if GetInterfaces fail + } + + mPriv->continueMainIntrospection(); + + watcher->deleteLater(); +} + +void Connection::gotSelfHandle(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<uint> reply = *watcher; + + if (!reply.isError()) { + mPriv->selfHandle = reply.value(); + debug() << "Got self handle:" << mPriv->selfHandle; + + mPriv->continueMainIntrospection(); + } else { + warning().nospace() << "GetSelfHandle() failed with " << + reply.error().name() << ": " << reply.error().message(); + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, + false, reply.error()); + } + + watcher->deleteLater(); +} + +void Connection::gotCapabilities(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QDBusVariant> reply = *watcher; + + if (!reply.isError()) { + debug() << "Got capabilities"; + mPriv->caps.updateRequestableChannelClasses( + qdbus_cast<RequestableChannelClassList>(reply.value().variant())); + } else { + warning().nospace() << "Getting capabilities failed with " << + reply.error().name() << ": " << reply.error().message(); + // let's not fail if retrieving capabilities fail + } + + mPriv->continueMainIntrospection(); + + watcher->deleteLater(); +} + +void Connection::gotContactAttributeInterfaces(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QDBusVariant> reply = *watcher; + + if (!reply.isError()) { + debug() << "Got contact attribute interfaces"; + mPriv->contactAttributeInterfaces = qdbus_cast<QStringList>(reply.value().variant()); + } else { + warning().nospace() << "Getting contact attribute interfaces failed with " << + reply.error().name() << ": " << reply.error().message(); + // let's not fail if retrieving contact attribute interfaces fail + // TODO should we remove Contacts interface from interfaces? + } + + mPriv->continueMainIntrospection(); + + watcher->deleteLater(); +} + +void Connection::gotSimpleStatuses(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + + if (!reply.isError()) { + QVariantMap props = reply.value(); + + mPriv->simplePresenceStatuses = qdbus_cast<SimpleStatusSpecMap>( + props[QLatin1String("Statuses")]); + mPriv->maxPresenceStatusMessageLength = qdbus_cast<uint>( + props[QLatin1String("MaximumStatusMessageLength")]); + + debug() << "Got" << mPriv->simplePresenceStatuses.size() << + "simple presence statuses - max status message length is" << + mPriv->maxPresenceStatusMessageLength; + + mPriv->readinessHelper->setIntrospectCompleted(FeatureSimplePresence, true); + } + else { + warning().nospace() << "Getting simple presence statuses failed with " << + reply.error().name() << ":" << reply.error().message(); + mPriv->readinessHelper->setIntrospectCompleted(FeatureSimplePresence, false, reply.error()); + } + + watcher->deleteLater(); +} + +void Connection::gotSelfContact(PendingOperation *op) +{ + PendingContacts *pending = qobject_cast<PendingContacts *>(op); + + if (pending->isValid()) { + Q_ASSERT(pending->contacts().size() == 1); + ContactPtr contact = pending->contacts()[0]; + + if (mPriv->selfContact != contact) { + mPriv->selfContact = contact; + + if (!isReady(FeatureSelfContact)) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureSelfContact, true); + } + + emit selfContactChanged(); + } + } else { + warning().nospace() << "Getting self contact failed with " << + pending->errorName() << ":" << pending->errorMessage(); + + // check if the feature is already there, and for some reason introspectSelfContact + // failed when called the second time + if (!isReady(FeatureSelfContact)) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureSelfContact, false, + op->errorName(), op->errorMessage()); + } + + if (mPriv->selfContact) { + mPriv->selfContact.reset(); + emit selfContactChanged(); + } + } + + mPriv->introspectingSelfContact = false; + + if (mPriv->reintrospectSelfContactRequired) { + mPriv->introspectSelfContact(mPriv); + } +} + +void Connection::onIntrospectRosterFinished(PendingOperation *op) +{ + if (op->isError()) { + warning().nospace() << "Introspecting roster failed with " << + op->errorName() << ": " << op->errorMessage(); + mPriv->readinessHelper->setIntrospectCompleted(FeatureRoster, false, + op->errorName(), op->errorMessage()); + return; + } + + debug() << "Introspecting roster finished"; + mPriv->readinessHelper->setIntrospectCompleted(FeatureRoster, true); +} + +void Connection::onIntrospectRosterGroupsFinished(PendingOperation *op) +{ + if (op->isError()) { + warning().nospace() << "Introspecting roster groups failed with " << + op->errorName() << ": " << op->errorMessage(); + mPriv->readinessHelper->setIntrospectCompleted(FeatureRosterGroups, false, + op->errorName(), op->errorMessage()); + return; + } + + debug() << "Introspecting roster groups finished"; + mPriv->readinessHelper->setIntrospectCompleted(FeatureRosterGroups, true); +} + +void Connection::gotBalance(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariant> reply = *watcher; + + if (!reply.isError()) { + debug() << "Got balance"; + mPriv->accountBalance = qdbus_cast<CurrencyAmount>(reply.value()); + mPriv->readinessHelper->setIntrospectCompleted(FeatureAccountBalance, true); + } else { + warning().nospace() << "Getting balance failed with " << + reply.error().name() << ":" << reply.error().message(); + mPriv->readinessHelper->setIntrospectCompleted(FeatureAccountBalance, false, + reply.error().name(), reply.error().message()); + } + + watcher->deleteLater(); +} + +/** + * Return the Client::ConnectionInterface interface proxy object for this connection. + * 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::ConnectionInterface object for this + * Connection object. + */ +Client::ConnectionInterface *Connection::baseInterface() const +{ + return mPriv->baseInterface; +} + +/** + * Same as \c createChannel(request, -1) + */ +PendingChannel *ConnectionLowlevel::createChannel(const QVariantMap &request) +{ + return createChannel(request, -1); +} + +/** + * Asynchronously creates a channel satisfying the given request. + * + * In typical usage, only the Channel Dispatcher should call this. Ordinary + * applications should use the Account::createChannel() family of methods + * (which invoke the Channel Dispatcher's services). + * + * The request MUST contain the following keys: + * org.freedesktop.Telepathy.Channel.ChannelType + * org.freedesktop.Telepathy.Channel.TargetHandleType + * + * Upon completion, the reply to the request can be retrieved through the + * returned PendingChannel object. The object also provides access to the + * parameters with which the call was made and a signal to connect to get + * notification of the request finishing processing. See the documentation + * for that class for more info. + * + * \param request A dictionary containing the desirable properties. + * \param timeout The D-Bus timeout in milliseconds used for the method call. + * If timeout is -1, a default implementation-defined value that + * is suitable for inter-process communications (generally, + * 25 seconds) will be used. + * \return A PendingChannel which will emit PendingChannel::finished + * when the channel has been created, or an error occurred. + * \sa PendingChannel, ensureChannel(), + * Account::createChannel(), Account::createAndHandleChannel(), + * Account::ensureChannel(), Account::ensureAndHandleChannel() + */ +PendingChannel *ConnectionLowlevel::createChannel(const QVariantMap &request, + int timeout) +{ + if (!isValid()) { + return new PendingChannel(ConnectionPtr(), + TP_QT_ERROR_NOT_AVAILABLE, + QLatin1String("The connection has been destroyed")); + } + + ConnectionPtr conn(mPriv->conn); + + if (conn->mPriv->pendingStatus != ConnectionStatusConnected) { + warning() << "Calling createChannel with connection not yet connected"; + return new PendingChannel(conn, + QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection not yet connected")); + } + + if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS))) { + warning() << "Requests interface is not support by this connection"; + return new PendingChannel(conn, + QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Connection does not support Requests Interface")); + } + + if (!request.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"))) { + return new PendingChannel(conn, + QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Invalid 'request' argument")); + } + + debug() << "Creating a Channel"; + PendingChannel *channel = new PendingChannel(conn, request, true, timeout); + return channel; +} + +/** + * Same as \c ensureChannel(request, -1) + */ +PendingChannel *ConnectionLowlevel::ensureChannel(const QVariantMap &request) +{ + return ensureChannel(request, -1); +} + +/** + * Asynchronously ensures a channel exists satisfying the given request. + * + * In typical usage, only the Channel Dispatcher should call this. Ordinary + * applications should use the Account::ensureChannel() family of methods + * (which invoke the Channel Dispatcher's services). + * + * The request MUST contain the following keys: + * org.freedesktop.Telepathy.Channel.ChannelType + * org.freedesktop.Telepathy.Channel.TargetHandleType + * + * Upon completion, the reply to the request can be retrieved through the + * returned PendingChannel object. The object also provides access to the + * parameters with which the call was made and a signal to connect to get + * notification of the request finishing processing. See the documentation + * for that class for more info. + * + * \param request A dictionary containing the desirable properties. + * \param timeout The D-Bus timeout in milliseconds used for the method call. + * If timeout is -1, a default implementation-defined value that + * is suitable for inter-process communications (generally, + * 25 seconds) will be used. + * \return A PendingChannel which will emit PendingChannel::finished + * when the channel is ensured to exist, or an error occurred. + * \sa PendingChannel, createChannel(), + * Account::createChannel(), Account::createAndHandleChannel(), + * Account::ensureChannel(), Account::ensureAndHandleChannel() + */ +PendingChannel *ConnectionLowlevel::ensureChannel(const QVariantMap &request, + int timeout) +{ + if (!isValid()) { + return new PendingChannel(ConnectionPtr(), + TP_QT_ERROR_NOT_AVAILABLE, + QLatin1String("The connection has been destroyed")); + } + + ConnectionPtr conn(mPriv->conn); + + if (conn->mPriv->pendingStatus != ConnectionStatusConnected) { + warning() << "Calling ensureChannel with connection not yet connected"; + return new PendingChannel(conn, + QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection not yet connected")); + } + + if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS))) { + warning() << "Requests interface is not support by this connection"; + return new PendingChannel(conn, + QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Connection does not support Requests Interface")); + } + + if (!request.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"))) { + return new PendingChannel(conn, + QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Invalid 'request' argument")); + } + + debug() << "Creating a Channel"; + PendingChannel *channel = new PendingChannel(conn, request, false, timeout); + return channel; +} + +/** + * Request handles of the given type for the given entities (contacts, + * rooms, lists, etc.). + * + * Typically one doesn't need to request and use handles directly; instead, string identifiers + * and/or Contact objects are used in most APIs. File a bug for APIs in which there is no + * alternative to using handles. In particular however using low-level DBus interfaces for which + * there is no corresponding high-level (or one is implementing that abstraction) functionality does + * and will always require using bare handles. + * + * Upon completion, the reply to the request can be retrieved through the + * returned PendingHandles object. The object also provides access to the + * parameters with which the call was made and a signal to connect to to get + * notification of the request finishing processing. See the documentation + * for that class for more info. + * + * The returned PendingHandles object should be freed using + * its QObject::deleteLater() method after it is no longer used. However, + * all PendingHandles objects resulting from requests to a particular + * Connection will be freed when the Connection itself is freed. Conversely, + * this means that the PendingHandles object should not be used after the + * Connection is destroyed. + * + * \param handleType Type for the handles to request, as specified in + * #HandleType. + * \param names Names of the entities to request handles for. + * \return A PendingHandles which will emit PendingHandles::finished + * when the handles have been requested, or an error occurred. + * \sa PendingHandles + */ +PendingHandles *ConnectionLowlevel::requestHandles(HandleType handleType, const QStringList &names) +{ + debug() << "Request for" << names.length() << "handles of type" << handleType; + + if (!isValid()) { + return new PendingHandles(TP_QT_ERROR_NOT_AVAILABLE, + QLatin1String("The connection has been destroyed")); + } + + ConnectionPtr conn(mPriv->conn); + if (!hasImmortalHandles()) { + Connection::Private::HandleContext *handleContext = conn->mPriv->handleContext; + QMutexLocker locker(&handleContext->lock); + handleContext->types[handleType].requestsInFlight++; + } + + PendingHandles *pending = + new PendingHandles(conn, handleType, names); + return pending; +} + +/** + * Request a reference to the given handles. Handles not explicitly + * requested (via requestHandles()) but eg. observed in a signal need to be + * referenced to guarantee them staying valid. + * + * Typically one doesn't need to reference and use handles directly; instead, string identifiers + * and/or Contact objects are used in most APIs. File a bug for APIs in which there is no + * alternative to using handles. In particular however using low-level DBus interfaces for which + * there is no corresponding high-level (or one is implementing that abstraction) functionality does + * and will always require using bare handles. + * + * Upon completion, the reply to the operation can be retrieved through the + * returned PendingHandles object. The object also provides access to the + * parameters with which the call was made and a signal to connect to to get + * notification of the request finishing processing. See the documentation + * for that class for more info. + * + * The returned PendingHandles object should be freed using + * its QObject::deleteLater() method after it is no longer used. However, + * all PendingHandles objects resulting from requests to a particular + * Connection will be freed when the Connection itself is freed. Conversely, + * this means that the PendingHandles object should not be used after the + * Connection is destroyed. + * + * \sa PendingHandles + * + * \param handleType Type of the handles given, as specified in #HandleType. + * \param handles Handles to request a reference to. + * \return A PendingHandles which will emit PendingHandles::finished + * when the handles have been referenced, or an error occurred. + */ +PendingHandles *ConnectionLowlevel::referenceHandles(HandleType handleType, const UIntList &handles) +{ + debug() << "Reference of" << handles.length() << "handles of type" << handleType; + + if (!isValid()) { + return new PendingHandles(TP_QT_ERROR_NOT_AVAILABLE, + QLatin1String("The connection has been destroyed")); + } + + ConnectionPtr conn(mPriv->conn); + UIntList alreadyHeld; + UIntList notYetHeld; + if (!hasImmortalHandles()) { + Connection::Private::HandleContext *handleContext = conn->mPriv->handleContext; + QMutexLocker locker(&handleContext->lock); + + foreach (uint handle, handles) { + if (handleContext->types[handleType].refcounts.contains(handle) || + handleContext->types[handleType].toRelease.contains(handle)) { + alreadyHeld.push_back(handle); + } + else { + notYetHeld.push_back(handle); + } + } + + debug() << " Already holding" << alreadyHeld.size() << + "of the handles -" << notYetHeld.size() << "to go"; + } else { + alreadyHeld = handles; + } + + PendingHandles *pending = + new PendingHandles(conn, handleType, handles, alreadyHeld, notYetHeld); + return pending; +} + +/** + * Start an asynchronous request that the connection be connected. + * + * When using a full-fledged Telepathy setup with an Account Manager service, the Account methods + * Account::setRequestedPresence() and Account::reconnect() must be used instead. + * + * The returned PendingOperation will finish successfully when the connection + * has reached ConnectionStatusConnected and the requested \a features are all ready, or + * finish with an error if a fatal error occurs during that process. + * + * \param requestedFeatures The features which should be enabled + * \return A PendingReady which will emit PendingReady::finished + * when the Connection has reached #ConnectionStatusConnected, and initial setup + * for basic functionality, plus the given features, has succeeded or + * failed. + */ +PendingReady *ConnectionLowlevel::requestConnect(const Features &requestedFeatures) +{ + if (!isValid()) { + Connection::PendingConnect *pending = new Connection::PendingConnect(ConnectionPtr(), + requestedFeatures); + pending->setFinishedWithError(TP_QT_ERROR_NOT_AVAILABLE, + QLatin1String("The connection has been destroyed")); + return pending; + } + + return new Connection::PendingConnect(ConnectionPtr(mPriv->conn), requestedFeatures); +} + +/** + * Start an asynchronous request that the connection be disconnected. + * The returned PendingOperation object will signal the success or failure + * of this request; under normal circumstances, it can be expected to + * succeed. + * + * When using a full-fledged Telepathy setup with an Account Manager service, + * Account::setRequestedPresence() with Presence::offline() as an argument should generally be used + * instead. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + */ +PendingOperation *ConnectionLowlevel::requestDisconnect() +{ + if (!isValid()) { + return new PendingFailure(TP_QT_ERROR_NOT_AVAILABLE, + QLatin1String("The connection has been destroyed"), ConnectionPtr()); + } + + ConnectionPtr conn(mPriv->conn); + + return new PendingVoid(conn->baseInterface()->Disconnect(), conn); +} + +/** + * Requests attributes for contacts. Optionally, the handles of the contacts + * will be referenced automatically. Essentially, this method wraps + * ConnectionInterfaceContactsInterface::GetContactAttributes(), integrating it + * with the rest of the handle-referencing machinery. + * + * This is very low-level API the Contact/ContactManager API provides a higher level of abstraction + * for the same functionality. + * + * Upon completion, the reply to the request can be retrieved through the + * returned PendingContactAttributes object. The object also provides access to + * the parameters with which the call was made and a signal to connect to to get + * notification of the request finishing processing. See the documentation for + * that class for more info. + * + * If the remote object doesn't support the Contacts interface (as signified by + * the list returned by interfaces() not containing + * #TP_QT_IFACE_CONNECTION_INTERFACE_CONTACTS), the returned + * PendingContactAttributes instance will fail instantly with the error + * #TP_QT_ERROR_NOT_IMPLEMENTED. + * + * Similarly, if the connection isn't both connected and ready + * (<code>status() == ConnectionStatusConnected && isReady(Connection::FeatureCore)</code>), + * the returned PendingContactAttributes instance will fail instantly with the + * error #TP_QT_ERROR_NOT_AVAILABLE. + * + * This method requires Connection::FeatureCore to be ready. + * + * \sa PendingContactAttributes + * + * \param handles A list of handles of type HandleTypeContact + * \param interfaces D-Bus interfaces for which the client requires information + * \param reference Whether the handles should additionally be referenced. + * \return A PendingContactAttributes which will emit PendingContactAttributes::fininshed + * when the contact attributes have been retrieved, or an error occurred. + */ +PendingContactAttributes *ConnectionLowlevel::contactAttributes(const UIntList &handles, + const QStringList &interfaces, bool reference) +{ + debug() << "Request for attributes for" << handles.size() << "contacts"; + + if (!isValid()) { + PendingContactAttributes *pending = new PendingContactAttributes(ConnectionPtr(), + handles, interfaces, reference); + pending->failImmediately(TP_QT_ERROR_NOT_AVAILABLE, + QLatin1String("The connection has been destroyed")); + return pending; + } + + ConnectionPtr conn(mPriv->conn); + + PendingContactAttributes *pending = + new PendingContactAttributes(conn, + handles, interfaces, reference); + if (!conn->isReady(Connection::FeatureCore)) { + warning() << "ConnectionLowlevel::contactAttributes() used when not ready"; + pending->failImmediately(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("The connection isn't ready")); + return pending; + } else if (conn->mPriv->pendingStatus != ConnectionStatusConnected) { + warning() << "ConnectionLowlevel::contactAttributes() used with status" << conn->status() << "!= ConnectionStatusConnected"; + pending->failImmediately(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("The connection isn't Connected")); + return pending; + } else if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS))) { + warning() << "ConnectionLowlevel::contactAttributes() used without the remote object supporting" + << "the Contacts interface"; + pending->failImmediately(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("The connection doesn't support the Contacts interface")); + return pending; + } + + if (!hasImmortalHandles()) { + Connection::Private::HandleContext *handleContext = conn->mPriv->handleContext; + QMutexLocker locker(&handleContext->lock); + handleContext->types[HandleTypeContact].requestsInFlight++; + } + + Client::ConnectionInterfaceContactsInterface *contactsInterface = + conn->interface<Client::ConnectionInterfaceContactsInterface>(); + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher(contactsInterface->GetContactAttributes(handles, interfaces, + reference)); + pending->connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onCallFinished(QDBusPendingCallWatcher*))); + return pending; +} + +QStringList ConnectionLowlevel::contactAttributeInterfaces() const +{ + if (!isValid()) { + warning() << "ConnectionLowlevel::contactAttributeInterfaces() called for a destroyed Connection"; + return QStringList(); + } + + ConnectionPtr conn(mPriv->conn); + + if (conn->mPriv->pendingStatus != ConnectionStatusConnected) { + warning() << "ConnectionLowlevel::contactAttributeInterfaces() used with status" + << conn->status() << "!= ConnectionStatusConnected"; + } else if (!conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS))) { + warning() << "ConnectionLowlevel::contactAttributeInterfaces() used without the remote object supporting" + << "the Contacts interface"; + } + + return conn->mPriv->contactAttributeInterfaces; +} + +void ConnectionLowlevel::injectContactIds(const HandleIdentifierMap &contactIds) +{ + if (!hasImmortalHandles()) { + return; + } + + for (HandleIdentifierMap::const_iterator i = contactIds.constBegin(); + i != contactIds.constEnd(); ++i) { + uint handle = i.key(); + QString id = i.value(); + + if (!id.isEmpty()) { + QString currentId = mPriv->contactsIds.value(handle); + + if (!currentId.isEmpty() && id != currentId) { + warning() << "Trying to overwrite contact id from" << currentId << "to" << id + << "for the same handle" << handle << ", ignoring"; + } else { + mPriv->contactsIds.insert(handle, id); + } + } + } +} + +void ConnectionLowlevel::injectContactId(uint handle, const QString &contactId) +{ + HandleIdentifierMap contactIds; + contactIds.insert(handle, contactId); + injectContactIds(contactIds); +} + +bool ConnectionLowlevel::hasContactId(uint handle) const +{ + return mPriv->contactsIds.contains(handle); +} + +QString ConnectionLowlevel::contactId(uint handle) const +{ + return mPriv->contactsIds.value(handle); +} + +/** + * Return whether the handles last for the whole lifetime of the connection. + * + * \return \c true if handles are immortal, \c false otherwise. + */ +bool ConnectionLowlevel::hasImmortalHandles() const +{ + ConnectionPtr conn(mPriv->conn); + + return conn->mPriv->immortalHandles; +} + +/** + * Return the ContactManager object for this connection. + * + * The contact manager is responsible for all contact handling in this + * connection, including adding, removing, authorizing, etc. + * + * \return A pointer to the ContactManager object. + */ +ContactManagerPtr Connection::contactManager() const +{ + return mPriv->contactManager; +} + +ConnectionLowlevelPtr Connection::lowlevel() +{ + return mPriv->lowlevel; +} + +ConnectionLowlevelConstPtr Connection::lowlevel() const +{ + return mPriv->lowlevel; +} + +void Connection::refHandle(HandleType handleType, uint handle) +{ + if (mPriv->immortalHandles) { + return; + } + + Private::HandleContext *handleContext = mPriv->handleContext; + QMutexLocker locker(&handleContext->lock); + + if (handleContext->types[handleType].toRelease.contains(handle)) { + handleContext->types[handleType].toRelease.remove(handle); + } + + handleContext->types[handleType].refcounts[handle]++; +} + +void Connection::unrefHandle(HandleType handleType, uint handle) +{ + if (mPriv->immortalHandles) { + return; + } + + Private::HandleContext *handleContext = mPriv->handleContext; + QMutexLocker locker(&handleContext->lock); + + Q_ASSERT(handleContext->types.contains(handleType)); + Q_ASSERT(handleContext->types[handleType].refcounts.contains(handle)); + + if (!--handleContext->types[handleType].refcounts[handle]) { + handleContext->types[handleType].refcounts.remove(handle); + handleContext->types[handleType].toRelease.insert(handle); + + if (!handleContext->types[handleType].releaseScheduled) { + if (!handleContext->types[handleType].requestsInFlight) { + debug() << "Lost last reference to at least one handle of type" << + handleType << + "and no requests in flight for that type - scheduling a release sweep"; + QMetaObject::invokeMethod(this, "doReleaseSweep", + Qt::QueuedConnection, Q_ARG(uint, handleType)); + handleContext->types[handleType].releaseScheduled = true; + } + } + } +} + +void Connection::doReleaseSweep(uint handleType) +{ + if (mPriv->immortalHandles) { + return; + } + + Private::HandleContext *handleContext = mPriv->handleContext; + QMutexLocker locker(&handleContext->lock); + + Q_ASSERT(handleContext->types.contains(handleType)); + Q_ASSERT(handleContext->types[handleType].releaseScheduled); + + debug() << "Entering handle release sweep for type" << handleType; + handleContext->types[handleType].releaseScheduled = false; + + if (handleContext->types[handleType].requestsInFlight > 0) { + debug() << " There are requests in flight, deferring sweep to when they have been completed"; + return; + } + + if (handleContext->types[handleType].toRelease.isEmpty()) { + debug() << " No handles to release - every one has been resurrected"; + return; + } + + debug() << " Releasing" << handleContext->types[handleType].toRelease.size() << "handles"; + + mPriv->baseInterface->ReleaseHandles(handleType, handleContext->types[handleType].toRelease.toList()); + handleContext->types[handleType].toRelease.clear(); +} + +void Connection::handleRequestLanded(HandleType handleType) +{ + if (mPriv->immortalHandles) { + return; + } + + Private::HandleContext *handleContext = mPriv->handleContext; + QMutexLocker locker(&handleContext->lock); + + Q_ASSERT(handleContext->types.contains(handleType)); + Q_ASSERT(handleContext->types[handleType].requestsInFlight > 0); + + if (!--handleContext->types[handleType].requestsInFlight && + !handleContext->types[handleType].toRelease.isEmpty() && + !handleContext->types[handleType].releaseScheduled) { + debug() << "All handle requests for type" << handleType << + "landed and there are handles of that type to release - scheduling a release sweep"; + QMetaObject::invokeMethod(this, "doReleaseSweep", Qt::QueuedConnection, Q_ARG(uint, handleType)); + handleContext->types[handleType].releaseScheduled = true; + } +} + +void Connection::onSelfHandleChanged(uint handle) +{ + if (mPriv->selfHandle == handle) { + return; + } + + if (mPriv->pendingStatus != ConnectionStatusConnected || !mPriv->selfHandle) { + debug() << "Got a self handle change before we have the initial self handle, ignoring"; + return; + } + + debug() << "Connection self handle changed to" << handle; + mPriv->selfHandle = handle; + emit selfHandleChanged(handle); + + if (mPriv->introspectingSelfContact) { + // We're currently introspecting the SelfContact feature, but have started building the + // contact with the old handle, so we need to do it again with the new handle. + + debug() << "The self contact is being built, will rebuild with the new handle shortly"; + mPriv->reintrospectSelfContactRequired = true; + } else if (isReady(FeatureSelfContact)) { + // We've already introspected the SelfContact feature, so we can reinvoke the introspection + // logic directly to rebuild with the new handle. + + debug() << "Re-building self contact for handle" << handle; + Private::introspectSelfContact(mPriv); + } + + // If ReadinessHelper hasn't started introspecting SelfContact yet for the Connected state, we + // don't need to do anything. When it does start the introspection, it will do so using the + // correct handle. +} + +void Connection::onBalanceChanged(const Tp::CurrencyAmount &value) +{ + mPriv->accountBalance = value; + emit accountBalanceChanged(value); +} + +QString ConnectionHelper::statusReasonToErrorName(Tp::ConnectionStatusReason reason, + Tp::ConnectionStatus oldStatus) +{ + const char *errorName; + + switch (reason) { + case ConnectionStatusReasonNoneSpecified: + errorName = TELEPATHY_ERROR_DISCONNECTED; + break; + + case ConnectionStatusReasonRequested: + errorName = TELEPATHY_ERROR_CANCELLED; + break; + + case ConnectionStatusReasonNetworkError: + errorName = TELEPATHY_ERROR_NETWORK_ERROR; + break; + + case ConnectionStatusReasonAuthenticationFailed: + errorName = TELEPATHY_ERROR_AUTHENTICATION_FAILED; + break; + + case ConnectionStatusReasonEncryptionError: + errorName = TELEPATHY_ERROR_ENCRYPTION_ERROR; + break; + + case ConnectionStatusReasonNameInUse: + if (oldStatus == ConnectionStatusConnected) { + errorName = TELEPATHY_ERROR_CONNECTION_REPLACED; + } else { + errorName = TELEPATHY_ERROR_ALREADY_CONNECTED; + } + break; + + case ConnectionStatusReasonCertNotProvided: + errorName = TELEPATHY_ERROR_CERT_NOT_PROVIDED; + break; + + case ConnectionStatusReasonCertUntrusted: + errorName = TELEPATHY_ERROR_CERT_UNTRUSTED; + break; + + case ConnectionStatusReasonCertExpired: + errorName = TELEPATHY_ERROR_CERT_EXPIRED; + break; + + case ConnectionStatusReasonCertNotActivated: + errorName = TELEPATHY_ERROR_CERT_NOT_ACTIVATED; + break; + + case ConnectionStatusReasonCertHostnameMismatch: + errorName = TELEPATHY_ERROR_CERT_HOSTNAME_MISMATCH; + break; + + case ConnectionStatusReasonCertFingerprintMismatch: + errorName = TELEPATHY_ERROR_CERT_FINGERPRINT_MISMATCH; + break; + + case ConnectionStatusReasonCertSelfSigned: + errorName = TELEPATHY_ERROR_CERT_SELF_SIGNED; + break; + + case ConnectionStatusReasonCertOtherError: + errorName = TELEPATHY_ERROR_CERT_INVALID; + break; + + default: + errorName = TELEPATHY_ERROR_DISCONNECTED; + break; + } + + return QLatin1String(errorName); +} + +/** + * \fn void Connection::statusChanged(Tp::ConnectionStatus newStatus) + * + * Indicates that the connection's status has changed and that all previously requested features are + * now ready to use for the new status. + * + * Legitimate uses for this signal, apart from waiting for a given connection status to be ready, + * include updating an animation based on the connection being in ConnectionStatusConnecting, + * ConnectionStatusConnected and ConnectionStatusDisconnected, and otherwise showing progress + * indication to the user. It should, however, NEVER be used for error handling: + * + * This signal doesn't contain the status reason as an argument, because statusChanged() shouldn't + * be used for error-handling. There are numerous cases in which a Connection may become unusable + * without there being a status change to ConnectionStatusDisconnected. All of these cases, and + * being disconnected itself, are signaled by invalidated() with appropriate error names. On the + * other hand, the reason for the status going to ConnectionStatusConnecting or + * ConnectionStatusConnected will always be ConnectionStatusReasonRequested, so signaling that would + * be useless. + * + * The status reason, as returned by statusReason(), may however be used as a fallback for error + * handling in slots connected to the invalidated() signal, if the client doesn't understand a + * particular (likely domain-specific if so) error name given by invalidateReason(). + * + * \param newStatus The new status of the connection, as would be returned by status(). + */ + +/** + * \fn void Connection::selfHandleChanged(uint newHandle) + * + * Emitted when the value of selfHandle() changes. + * + * \param newHandle The new connection self handle. + * \sa selfHandle() + */ + +/** + * \fn void Connection::selfContactChanged() + * + * Emitted when the value of selfContact() changes. + * + * \sa selfContact() + */ + +/** + * \fn void Connection::accountBalanceChanged(const Tp::CurrencyAmount &accountBalance) + * + * Emitted when the value of accountBalance() changes. + * + * \param accountBalance The new user's balance of this connection. + * \sa accountBalance() + */ + +} // Tp diff --git a/TelepathyQt/connection.h b/TelepathyQt/connection.h new file mode 100644 index 00000000..0ca47a53 --- /dev/null +++ b/TelepathyQt/connection.h @@ -0,0 +1,249 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_connection_h_HEADER_GUARD_ +#define _TelepathyQt_connection_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/_gen/cli-connection.h> + +#include <TelepathyQt/ConnectionCapabilities> +#include <TelepathyQt/Contact> +#include <TelepathyQt/DBus> +#include <TelepathyQt/DBusProxy> +#include <TelepathyQt/OptionalInterfaceFactory> +#include <TelepathyQt/ReadinessHelper> +#include <TelepathyQt/Types> +#include <TelepathyQt/SharedPtr> + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Types> + +#include <QSet> +#include <QString> +#include <QStringList> + +namespace Tp +{ + +class Channel; +class ConnectionCapabilities; +class ConnectionLowlevel; +class Contact; +class ContactManager; +class PendingChannel; +class PendingContactAttributes; +class PendingHandles; +class PendingOperation; +class PendingReady; + +class TP_QT_EXPORT Connection : public StatefulDBusProxy, + public OptionalInterfaceFactory<Connection> +{ + Q_OBJECT + Q_DISABLE_COPY(Connection) + +public: + static const Feature FeatureCore; + static const Feature FeatureSelfContact; + static const Feature FeatureSimplePresence; + static const Feature FeatureRoster; + static const Feature FeatureRosterGroups; + static const Feature FeatureAccountBalance; // TODO unit tests for this + static const Feature FeatureConnected; + + static ConnectionPtr create(const QString &busName, + const QString &objectPath, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory); + static ConnectionPtr create(const QDBusConnection &bus, + const QString &busName, const QString &objectPath, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory); + + virtual ~Connection(); + + ChannelFactoryConstPtr channelFactory() const; + ContactFactoryConstPtr contactFactory() const; + + QString cmName() const; + QString protocolName() const; + + ConnectionStatus status() const; + ConnectionStatusReason statusReason() const; + + class ErrorDetails + { + public: + ErrorDetails(); + ErrorDetails(const QVariantMap &details); + ErrorDetails(const ErrorDetails &other); + ~ErrorDetails(); + + ErrorDetails &operator=(const ErrorDetails &other); + + bool isValid() const { return mPriv.constData() != 0; } + + bool hasDebugMessage() const + { + return allDetails().contains(QLatin1String("debug-message")); + } + + QString debugMessage() const + { + return qdbus_cast<QString>(allDetails().value(QLatin1String("debug-message"))); + } + + bool hasServerMessage() const + { + return allDetails().contains(QLatin1String("server-message")); + } + + QString serverMessage() const + { + return qdbus_cast<QString>(allDetails().value(QLatin1String("server-message"))); + } + + bool hasUserRequested() const + { + return allDetails().contains(QLatin1String("user-requested")); + } + + bool userRequested() const + { + return qdbus_cast<bool>(allDetails().value(QLatin1String("user-requested"))); + } + + bool hasExpectedHostname() const + { + return allDetails().contains(QLatin1String("expected-hostname")); + } + + QString expectedHostname() const + { + return qdbus_cast<QString>(allDetails().value(QLatin1String("expected-hostname"))); + } + + bool hasCertificateHostname() const + { + return allDetails().contains(QLatin1String("certificate-hostname")); + } + + QString certificateHostname() const + { + return qdbus_cast<QString>(allDetails().value(QLatin1String("certificate-hostname"))); + } + + QVariantMap allDetails() const; + + private: + friend class Connection; + + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; + }; + + const ErrorDetails &errorDetails() const; + + uint selfHandle() const; + ContactPtr selfContact() const; + + CurrencyAmount accountBalance() const; + + ConnectionCapabilities capabilities() const; + + ContactManagerPtr contactManager() const; + +#if defined(BUILDING_TP_QT) || defined(TP_QT_ENABLE_LOWLEVEL_API) + ConnectionLowlevelPtr lowlevel(); + ConnectionLowlevelConstPtr lowlevel() const; +#endif + +Q_SIGNALS: + void statusChanged(Tp::ConnectionStatus newStatus); + + void selfHandleChanged(uint newHandle); + // FIXME: might not need this when Renaming is fixed and mapped to Contacts + void selfContactChanged(); + + void accountBalanceChanged(const Tp::CurrencyAmount &accountBalance); + +protected: + Connection(const QDBusConnection &bus, const QString &busName, + const QString &objectPath, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory, + const Feature &coreFeature); + + Client::ConnectionInterface *baseInterface() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onStatusReady(uint status); + TP_QT_NO_EXPORT void onStatusChanged(uint status, uint reason); + TP_QT_NO_EXPORT void onConnectionError(const QString &error, const QVariantMap &details); + TP_QT_NO_EXPORT void gotMainProperties(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotStatus(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotInterfaces(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotSelfHandle(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotCapabilities(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotContactAttributeInterfaces(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotSimpleStatuses(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotSelfContact(Tp::PendingOperation *op); + + TP_QT_NO_EXPORT void onIntrospectRosterFinished(Tp::PendingOperation *op); + TP_QT_NO_EXPORT void onIntrospectRosterGroupsFinished(Tp::PendingOperation *op); + + TP_QT_NO_EXPORT void doReleaseSweep(uint handleType); + + TP_QT_NO_EXPORT void onSelfHandleChanged(uint); + + TP_QT_NO_EXPORT void gotBalance(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onBalanceChanged(const Tp::CurrencyAmount &); + +private: + class PendingConnect; + friend class ConnectionLowlevel; + friend class PendingChannel; + friend class PendingConnect; + friend class PendingContactAttributes; + friend class PendingContacts; + friend class PendingHandles; + friend class ReferencedHandles; + + TP_QT_NO_EXPORT void refHandle(HandleType handleType, uint handle); + TP_QT_NO_EXPORT void unrefHandle(HandleType handleType, uint handle); + TP_QT_NO_EXPORT void handleRequestLanded(HandleType handleType); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::Connection::ErrorDetails); + +#endif diff --git a/TelepathyQt/connection.xml b/TelepathyQt/connection.xml new file mode 100644 index 00000000..bf726f58 --- /dev/null +++ b/TelepathyQt/connection.xml @@ -0,0 +1,30 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Connection interfaces</tp:title> + +<xi:include href="../spec/Connection.xml"/> + +<xi:include href="../spec/Connection_Interface_Aliasing.xml"/> +<xi:include href="../spec/Connection_Interface_Anonymity.xml"/> +<xi:include href="../spec/Connection_Interface_Avatars.xml"/> +<xi:include href="../spec/Connection_Interface_Balance.xml"/> +<xi:include href="../spec/Connection_Interface_Capabilities.xml"/> +<xi:include href="../spec/Connection_Interface_Cellular.xml"/> +<xi:include href="../spec/Connection_Interface_Client_Types.xml"/> +<xi:include href="../spec/Connection_Interface_Contacts.xml"/> +<xi:include href="../spec/Connection_Interface_Contact_Blocking.xml"/> +<xi:include href="../spec/Connection_Interface_Contact_Capabilities.xml"/> +<xi:include href="../spec/Connection_Interface_Contact_Groups.xml"/> +<xi:include href="../spec/Connection_Interface_Contact_Info.xml"/> +<xi:include href="../spec/Connection_Interface_Contact_List.xml"/> +<xi:include href="../spec/Connection_Interface_Location.xml"/> +<xi:include href="../spec/Connection_Interface_Mail_Notification.xml"/> +<xi:include href="../spec/Connection_Interface_Power_Saving.xml"/> +<xi:include href="../spec/Connection_Interface_Presence.xml"/> +<xi:include href="../spec/Connection_Interface_Requests.xml"/> +<xi:include href="../spec/Connection_Interface_Service_Point.xml"/> +<xi:include href="../spec/Connection_Interface_Simple_Presence.xml"/> + +</tp:spec> diff --git a/TelepathyQt/constants.h b/TelepathyQt/constants.h new file mode 100644 index 00000000..a3146d13 --- /dev/null +++ b/TelepathyQt/constants.h @@ -0,0 +1,178 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_constants_h_HEADER_GUARD_ +#define _TelepathyQt_constants_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +/** + * \addtogroup typesconstants Types and constants + * + * Enumerated, flag, structure, list and mapping types and utility constants. + */ + +/** + * \defgroup utilityconsts Utility string constants + * \ingroup typesconstants + * + * Utility constants which aren't generated from the specification but are + * useful for working with the Telepathy protocol. + * @{ + */ + +/** + * The prefix for a connection manager's bus name, to which the CM's name (e.g. + * "gabble") should be appended. + */ +#define TP_QT_CONNECTION_MANAGER_BUS_NAME_BASE QLatin1String("org.freedesktop.Telepathy.ConnectionManager.") + +/** + * The prefix for a connection manager's object path, to which the CM's name + * (e.g. "gabble") should be appended. + */ +#define TP_QT_CONNECTION_MANAGER_OBJECT_PATH_BASE QLatin1String("/org/freedesktop/Telepathy/ConnectionManager/") + +/** + * The prefix for a connection's bus name, to which the CM's name (e.g. + * "gabble"), the protocol (e.g. "jabber") and an element + * representing the account should be appended. + */ +#define TP_QT_CONNECTION_BUS_NAME_BASE QLatin1String("org.freedesktop.Telepathy.Connection.") + +/** + * The prefix for a connection's object path, to which the CM's name (e.g. + * "gabble"), the protocol (e.g. "jabber") and an element + * representing the account should be appended. + */ +#define TP_QT_CONNECTION_OBJECT_PATH_BASE "/org/freedesktop/Telepathy/Connection/" + +/** + * The well-known bus name of the Account Manager. + * + * \see Tp::AccountManager + */ +#define TP_QT_ACCOUNT_MANAGER_BUS_NAME \ + (QLatin1String("org.freedesktop.Telepathy.AccountManager")) + +/** + * The object path of the Account Manager object. + * + * \see Tp::AccountManager + */ +#define TP_QT_ACCOUNT_MANAGER_OBJECT_PATH \ + (QLatin1String("/org/freedesktop/Telepathy/AccountManager")) + +/** + * The well-known bus name of the Channel Dispatcher. + */ +#define TP_QT_CHANNEL_DISPATCHER_BUS_NAME \ + (QLatin1String("org.freedesktop.Telepathy.ChannelDispatcher")) + +/** + * The object path of the Channel Dispatcherr object. + */ +#define TP_QT_CHANNEL_DISPATCHER_OBJECT_PATH \ + (QLatin1String("/org/freedesktop/Telepathy/ChannelDispatcher")) + +/** + * The prefix for an Account's object path, to which the CM's name (e.g. + * "gabble"), the protocol (e.g. "jabber") and an element + * identifying the particular account should be appended. + * + * \see Tp::Account + */ +#define TP_QT_ACCOUNT_OBJECT_PATH_BASE \ + (QLatin1String("/org/freedesktop/Telepathy/Account")) + +/** + * @} + */ + +#include <TelepathyQt/_gen/constants.h> + +/** + * \ingroup errorstrconsts + * + * The error name "org.freedesktop.DBus.Error.NameHasNoOwner" as a QLatin1String. + * + * Raised by the D-Bus daemon when looking up the owner of a well-known name, + * if no process owns that name. + * + * Also used by DBusProxy to indicate that the owner of a well-known name + * has disappeared (usually indicating that the process owning that name + * exited or crashed). + */ +#define TP_QT_DBUS_ERROR_NAME_HAS_NO_OWNER \ + (QLatin1String("org.freedesktop.DBus.Error.NameHasNoOwner")) + +/** + * \ingroup errorstrconsts + * + * The error name "org.freedesktop.DBus.Error.UnknownInterface" as a QLatin1String. + */ +#define TP_QT_DBUS_ERROR_UNKNOWN_INTERFACE \ + (QLatin1String("org.freedesktop.DBus.Error.UnknownInterface")) + +/** + * \ingroup errorstrconsts + * + * The error name "org.freedesktop.DBus.Error.UnknownMethod" as a QLatin1String. + * + * Raised by the D-Bus daemon when the method name invoked isn't + * known by the object you invoked it on. + */ +#define TP_QT_DBUS_ERROR_UNKNOWN_METHOD \ + (QLatin1String("org.freedesktop.DBus.Error.UnknownMethod")) + +/** + * \ingroup errorstrconsts + * + * The error name "org.freedesktop.Telepathy.Qt4.Error.ObjectRemoved" as a QLatin1String. + */ +#define TP_QT_ERROR_OBJECT_REMOVED \ + (QLatin1String("org.freedesktop.Telepathy.Qt4.Error.ObjectRemoved")) + +/** + * \ingroup errorstrconsts + * + * The error name "org.freedesktop.Telepathy.Qt4.Error.Inconsistent" as a QLatin1String. + */ +#define TP_QT_ERROR_INCONSISTENT \ + (QLatin1String("org.freedesktop.Telepathy.Qt4.Error.Inconsistent")) + +/** + * \ingroup errorstrconsts + * + * The error name "org.freedesktop.Telepathy.Qt4.Error.Orphaned" as a QLatin1String. + * + * This error is used when the "parent" proxy of an object gets invalidated. For example, a Channel + * whose corresponding Connection is invalidated invalidates itself with this error, as do leftover + * StreamTube connections when their parent StreamTubeChannel is invalidated. The invalidation + * reason of the parent proxy might provide more information on the cause of the error. + */ +#define TP_QT_ERROR_ORPHANED \ + (QLatin1String("org.freedesktop.Telepathy.Qt4.Error.Orphaned")) + +#endif diff --git a/TelepathyQt/contact-capabilities.cpp b/TelepathyQt/contact-capabilities.cpp new file mode 100644 index 00000000..7a59f254 --- /dev/null +++ b/TelepathyQt/contact-capabilities.cpp @@ -0,0 +1,127 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ContactCapabilities> + +#include <TelepathyQt/Types> + +namespace Tp +{ + +/** + * \class ContactCapabilities + * \ingroup clientconn + * \headerfile TelepathyQt/contact-capabilities.h <TelepathyQt/ContactCapabilities> + * + * \brief The ContactCapabilities class represents the capabilities of a + * Contact. + */ + +/** + * Construct a new ContactCapabilities object. + */ +ContactCapabilities::ContactCapabilities() + : CapabilitiesBase() +{ +} + +/** + * Construct a new ContactCapabilities object. + */ +ContactCapabilities::ContactCapabilities(bool specificToContact) + : CapabilitiesBase(specificToContact) +{ +} + +/** + * Construct a new ContactCapabilities object using the give \a rccs. + * + * \param rccs RequestableChannelClassList representing the capabilities of a + * contact. + */ +ContactCapabilities::ContactCapabilities(const RequestableChannelClassList &rccs, + bool specificToContact) + : CapabilitiesBase(rccs, specificToContact) +{ +} + +/** + * Construct a new ContactCapabilities object using the give \a rccSpecs. + * + * \param rccSpecs RequestableChannelClassList representing the capabilities of a + * contact. + */ +ContactCapabilities::ContactCapabilities(const RequestableChannelClassSpecList &rccSpecs, + bool specificToContact) + : CapabilitiesBase(rccSpecs, specificToContact) +{ +} + +/** + * Class destructor. + */ +ContactCapabilities::~ContactCapabilities() +{ +} + +/** + * Return whether creating a StreamTube channel, using the given \a service, by providing a + * contact identifier is supported. + * + * \return \c true if supported, \c false otherwise. + */ +bool ContactCapabilities::streamTubes(const QString &service) const +{ + RequestableChannelClassSpec streamTubeSpec = RequestableChannelClassSpec::streamTube(service); + RequestableChannelClassSpecList rccSpecs = allClassSpecs(); + foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) { + if (rccSpec.supports(streamTubeSpec)) { + return true; + } + } + return false; +} + +/** + * Return the supported StreamTube services. + * + * \return A list of supported StreamTube services. + */ +QStringList ContactCapabilities::streamTubeServices() const +{ + QSet<QString> ret; + + RequestableChannelClassSpecList rccSpecs = allClassSpecs(); + foreach (const RequestableChannelClassSpec &rccSpec, rccSpecs) { + if (rccSpec.channelType() == TP_QT_IFACE_CHANNEL_TYPE_STREAM_TUBE && + rccSpec.targetHandleType() == HandleTypeContact && + rccSpec.hasFixedProperty( + TP_QT_IFACE_CHANNEL_TYPE_STREAM_TUBE + QLatin1String(".Service"))) { + ret << rccSpec.fixedProperty( + TP_QT_IFACE_CHANNEL_TYPE_STREAM_TUBE + QLatin1String(".Service")).toString(); + } + } + + return ret.toList(); +} + +} // Tp diff --git a/TelepathyQt/contact-capabilities.h b/TelepathyQt/contact-capabilities.h new file mode 100644 index 00000000..52de21e2 --- /dev/null +++ b/TelepathyQt/contact-capabilities.h @@ -0,0 +1,66 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_contact_capabilities_h_HEADER_GUARD_ +#define _TelepathyQt_contact_capabilities_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/CapabilitiesBase> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class TestBackdoors; + +class TP_QT_EXPORT ContactCapabilities : public CapabilitiesBase +{ +public: + ContactCapabilities(); + virtual ~ContactCapabilities(); + + bool streamTubes(const QString &service) const; + QStringList streamTubeServices() const; + + // later: + // bool dbusTubes(const QString &service) const; + // QStringList dbusTubeServices() const; + +protected: + friend class Contact; + friend class TestBackdoors; + + ContactCapabilities(bool specificToContact); + ContactCapabilities(const RequestableChannelClassList &rccs, + bool specificToContact); + ContactCapabilities(const RequestableChannelClassSpecList &rccSpecs, + bool specificToContact); +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::ContactCapabilities); + +#endif diff --git a/TelepathyQt/contact-factory.cpp b/TelepathyQt/contact-factory.cpp new file mode 100644 index 00000000..cd93acde --- /dev/null +++ b/TelepathyQt/contact-factory.cpp @@ -0,0 +1,146 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ContactFactory> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ContactFactory::Private +{ + Features features; +}; + +/** + * \class ContactFactory + * \ingroup utils + * \headerfile TelepathyQt/contact-factory.h <TelepathyQt/ContactFactory> + * + * \brief The ContactFactory class is responsible for constructing Contact + * objects according to application-defined settings. + */ + +/** + * Creates a new ContactFactory. + * + * \param features The features to make ready on constructed contacts. + * \returns A pointer to the created factory. + */ +ContactFactoryPtr ContactFactory::create(const Features &features) +{ + return ContactFactoryPtr(new ContactFactory(features)); +} + +/** + * Class constructor. + * + * \param features The features to make ready on constructed contacts. + */ +ContactFactory::ContactFactory(const Features &features) + : mPriv(new Private) +{ + addFeatures(features); +} + +/** + * Class destructor. + */ +ContactFactory::~ContactFactory() +{ + delete mPriv; +} + +/** + * Gets the features this factory will make ready on constructed contacts. + * + * \return The set of features. + */ +Features ContactFactory::features() const +{ + Features features = mPriv->features; + // FeatureAvatarData depends on FeatureAvatarToken + if (features.contains(Contact::FeatureAvatarData) && + !features.contains(Contact::FeatureAvatarToken)) { + features.insert(Contact::FeatureAvatarToken); + } + + return features; +} + +/** + * Adds a single feature this factory will make ready on further constructed contacts. + * + * No feature removal is provided, to guard against uncooperative modules removing features other + * modules have set and depend on. + * + * \param feature The feature to add. + */ +void ContactFactory::addFeature(const Feature &feature) +{ + addFeatures(Features(feature)); +} + +/** + * Adds a set of features this factory will make ready on further constructed contacts. + * + * No feature removal is provided, to guard against uncooperative modules removing features other + * modules have set and depend on. + * + * \param features The features to add. + */ +void ContactFactory::addFeatures(const Features &features) +{ + mPriv->features.unite(features); +} + +/** + * Can be used by subclasses to override the Contact subclass constructed by the factory. + * + * The default implementation constructs Tp::Contact objects. + * + * \param manager The contact manager this contact belongs. + * \param handle The contact handle. + * \param features The desired contact features. + * \param attributes The desired contact attributes. + * \return A pointer to the constructed contact. + */ +ContactPtr ContactFactory::construct(Tp::ContactManager *manager, const ReferencedHandles &handle, + const Features &features, const QVariantMap &attributes) const +{ + ContactPtr contact = ContactPtr(new Contact(manager, handle, features, attributes)); + return contact; +} + +/** + * Can be used by subclasses to do arbitrary manipulation on constructed Contact objects. + * + * The default implementation does nothing. + * + * \param contact The contact to be prepared. + * \return A PendingOperation used to prepare the contact or NULL if there is nothing to prepare. + */ +PendingOperation *ContactFactory::prepare(const ContactPtr &contact) const +{ + return NULL; +} + +} diff --git a/TelepathyQt/contact-factory.h b/TelepathyQt/contact-factory.h new file mode 100644 index 00000000..d8355f2a --- /dev/null +++ b/TelepathyQt/contact-factory.h @@ -0,0 +1,76 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_contact_factory_h_HEADER_GUARD_ +#define _TelepathyQt_contact_factory_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Contact> +#include <TelepathyQt/Feature> +#include <TelepathyQt/Global> +#include <TelepathyQt/Types> + +#include <QSet> +#include <QtGlobal> + +namespace Tp +{ + +class ContactManager; +class ReferencedHandles; + +class TP_QT_EXPORT ContactFactory : public RefCounted +{ + Q_DISABLE_COPY(ContactFactory) + +public: + static ContactFactoryPtr create(const Features &features = Features()); + + virtual ~ContactFactory(); + + Features features() const; + + void addFeature(const Feature &feature); + void addFeatures(const Features &features); + +protected: + ContactFactory(const Features &features); + + virtual ContactPtr construct(ContactManager *manager, const ReferencedHandles &handle, + const Features &features, const QVariantMap &attributes) const; + virtual PendingOperation *prepare(const ContactPtr &contact) const; + +private: + friend class ContactManager; + friend class PendingContacts; + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/contact-manager-internal.h b/TelepathyQt/contact-manager-internal.h new file mode 100644 index 00000000..e3bba7f4 --- /dev/null +++ b/TelepathyQt/contact-manager-internal.h @@ -0,0 +1,389 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_contact_manager_internal_h_HEADER_GUARD_ +#define _TelepathyQt_contact_manager_internal_h_HEADER_GUARD_ + +#include <TelepathyQt/ContactManager> +#include <TelepathyQt/Global> +#include <TelepathyQt/PendingOperation> +#include <TelepathyQt/Types> + +#include <QList> +#include <QMap> +#include <QObject> +#include <QQueue> +#include <QString> +#include <QStringList> + +namespace Tp +{ + +class TP_QT_NO_EXPORT ContactManager::Roster : public QObject +{ + Q_OBJECT + +public: + Roster(ContactManager *manager); + virtual ~Roster(); + + ContactListState state() const; + + PendingOperation *introspect(); + PendingOperation *introspectGroups(); + void reset(); + + Contacts allKnownContacts() const; + QStringList allKnownGroups() const; + + PendingOperation *addGroup(const QString &group); + PendingOperation *removeGroup(const QString &group); + + Contacts groupContacts(const QString &group) const; + PendingOperation *addContactsToGroup(const QString &group, + const QList<ContactPtr> &contacts); + PendingOperation *removeContactsFromGroup(const QString &group, + const QList<ContactPtr> &contacts); + + bool canRequestPresenceSubscription() const; + bool subscriptionRequestHasMessage() const; + PendingOperation *requestPresenceSubscription( + const QList<ContactPtr> &contacts, const QString &message); + bool canRemovePresenceSubscription() const; + bool subscriptionRemovalHasMessage() const; + bool canRescindPresenceSubscriptionRequest() const; + bool subscriptionRescindingHasMessage() const; + PendingOperation *removePresenceSubscription( + const QList<ContactPtr> &contacts, const QString &message); + bool canAuthorizePresencePublication() const; + bool publicationAuthorizationHasMessage() const; + PendingOperation *authorizePresencePublication( + const QList<ContactPtr> &contacts, const QString &message); + bool publicationRejectionHasMessage() const; + bool canRemovePresencePublication() const; + bool publicationRemovalHasMessage() const; + PendingOperation *removePresencePublication( + const QList<ContactPtr> &contacts, const QString &message); + PendingOperation *removeContacts( + const QList<ContactPtr> &contacts, const QString &message); + + bool canBlockContacts() const; + bool canReportAbuse() const; + PendingOperation *blockContacts(const QList<ContactPtr> &contacts, bool value, bool reportAbuse); + +private Q_SLOTS: + void gotContactBlockingCapabilities(Tp::PendingOperation *op); + void gotContactBlockingBlockedContacts(QDBusPendingCallWatcher *watcher); + void onContactBlockingBlockedContactsChanged( + const Tp::HandleIdentifierMap &added, + const Tp::HandleIdentifierMap &removed); + + void gotContactListProperties(Tp::PendingOperation *op); + void gotContactListContacts(QDBusPendingCallWatcher *watcher); + void setStateSuccess(); + void onContactListStateChanged(uint state); + void onContactListContactsChangedWithId(const Tp::ContactSubscriptionMap &changes, + const Tp::HandleIdentifierMap &ids, const Tp::HandleIdentifierMap &removals); + void onContactListContactsChanged(const Tp::ContactSubscriptionMap &changes, + const Tp::UIntList &removals); + + void onContactListBlockedContactsConstructed(Tp::PendingOperation *op); + void onContactListNewContactsConstructed(Tp::PendingOperation *op); + void onContactListGroupsChanged(const Tp::UIntList &contacts, + const QStringList &added, const QStringList &removed); + void onContactListGroupsCreated(const QStringList &names); + void onContactListGroupRenamed(const QString &oldName, const QString &newName); + void onContactListGroupsRemoved(const QStringList &names); + + void onModifyFinished(Tp::PendingOperation *op); + void onModifyFinishSignaled(); + + void gotContactListChannelHandle(Tp::PendingOperation *op); + void gotContactListChannel(Tp::PendingOperation *op); + void onContactListChannelReady(); + + void gotContactListGroupsProperties(Tp::PendingOperation *op); + void onContactListContactsUpgraded(Tp::PendingOperation *op); + + void onNewChannels(const Tp::ChannelDetailsList &channelDetailsList); + void onContactListGroupChannelReady(Tp::PendingOperation *op); + void gotChannels(QDBusPendingCallWatcher *watcher); + + void onStoredChannelMembersChanged( + const Tp::Contacts &groupMembersAdded, + const Tp::Contacts &groupLocalPendingMembersAdded, + const Tp::Contacts &groupRemotePendingMembersAdded, + const Tp::Contacts &groupMembersRemoved, + const Tp::Channel::GroupMemberChangeDetails &details); + void onSubscribeChannelMembersChanged( + const Tp::Contacts &groupMembersAdded, + const Tp::Contacts &groupLocalPendingMembersAdded, + const Tp::Contacts &groupRemotePendingMembersAdded, + const Tp::Contacts &groupMembersRemoved, + const Tp::Channel::GroupMemberChangeDetails &details); + void onPublishChannelMembersChanged( + const Tp::Contacts &groupMembersAdded, + const Tp::Contacts &groupLocalPendingMembersAdded, + const Tp::Contacts &groupRemotePendingMembersAdded, + const Tp::Contacts &groupMembersRemoved, + const Tp::Channel::GroupMemberChangeDetails &details); + void onDenyChannelMembersChanged( + const Tp::Contacts &groupMembersAdded, + const Tp::Contacts &groupLocalPendingMembersAdded, + const Tp::Contacts &groupRemotePendingMembersAdded, + const Tp::Contacts &groupMembersRemoved, + const Tp::Channel::GroupMemberChangeDetails &details); + + void onContactListGroupMembersChanged( + const Tp::Contacts &groupMembersAdded, + const Tp::Contacts &groupLocalPendingMembersAdded, + const Tp::Contacts &groupRemotePendingMembersAdded, + const Tp::Contacts &groupMembersRemoved, + const Tp::Channel::GroupMemberChangeDetails &details); + void onContactListGroupRemoved(Tp::DBusProxy *proxy, + const QString &errorName, const QString &errorMessage); + +private: + struct ChannelInfo; + struct BlockedContactsChangedInfo; + struct UpdateInfo; + struct GroupsUpdateInfo; + struct GroupRenamedInfo; + class ModifyFinishOp; + class RemoveGroupOp; + + void introspectContactBlocking(); + void introspectContactBlockingBlockedContacts(); + void introspectContactList(); + void introspectContactListContacts(); + void processContactListChanges(); + void processContactListBlockedContactsChanged(); + void processContactListUpdates(); + void processContactListGroupsUpdates(); + void processContactListGroupsCreated(); + void processContactListGroupRenamed(); + void processContactListGroupsRemoved(); + void processFinishedModify(); + PendingOperation *queuedFinishVoid(const QDBusPendingCall &call); + void setContactListChannelsReady(); + void updateContactsBlockState(); + void updateContactsPresenceState(); + void computeKnownContactsChanges(const Contacts &added, + const Contacts &pendingAdded, const Contacts &remotePendingAdded, + const Contacts &removed, const Channel::GroupMemberChangeDetails &details); + void checkContactListGroupsReady(); + void setContactListGroupChannelsReady(); + QString addContactListGroupChannel(const ChannelPtr &contactListGroupChannel); + + ContactManager *contactManager; + + Contacts cachedAllKnownContacts; + + bool usingFallbackContactList; + bool hasContactBlockingInterface; + + PendingOperation *introspectPendingOp; + PendingOperation *introspectGroupsPendingOp; + uint pendingContactListState; + uint contactListState; + bool canReportAbusive; + bool gotContactBlockingInitialBlockedContacts; + bool canChangeContactList; + bool contactListRequestUsesMessage; + bool gotContactListInitialContacts; + bool gotContactListContactsChangedWithId; + bool groupsReintrospectionRequired; + QSet<QString> cachedAllKnownGroups; + bool contactListGroupPropertiesReceived; + QQueue<void (ContactManager::Roster::*)()> contactListChangesQueue; + QQueue<BlockedContactsChangedInfo> contactListBlockedContactsChangedQueue; + QQueue<UpdateInfo> contactListUpdatesQueue; + QQueue<GroupsUpdateInfo> contactListGroupsUpdatesQueue; + QQueue<QStringList> contactListGroupsCreatedQueue; + QQueue<GroupRenamedInfo> contactListGroupRenamedQueue; + QQueue<QStringList> contactListGroupsRemovedQueue; + bool processingContactListChanges; + + QMap<PendingOperation * /* actual */, ModifyFinishOp *> returnedModifyOps; + QQueue<ModifyFinishOp *> modifyFinishQueue; + + // old roster API + uint contactListChannelsReady; + QMap<uint, ChannelInfo> contactListChannels; + ChannelPtr subscribeChannel; + ChannelPtr publishChannel; + ChannelPtr storedChannel; + ChannelPtr denyChannel; + + // Number of things left to do before the Groups feature is ready + // 1 for Get("Channels") + 1 per channel not ready + uint featureContactListGroupsTodo; + QList<ChannelPtr> pendingContactListGroupChannels; + QMap<QString, ChannelPtr> contactListGroupChannels; + QList<ChannelPtr> removedContactListGroupChannels; + + // If RosterGroups introspection completing should advance the ContactManager state to Success + bool groupsSetSuccess; + + // Contact list contacts using the Conn.I.ContactList API + Contacts contactListContacts; + // Blocked contacts using the new ContactBlocking API + Contacts blockedContacts; +}; + +struct TP_QT_NO_EXPORT ContactManager::Roster::ChannelInfo +{ + enum Type { + TypeSubscribe = 0, + TypePublish, + TypeStored, + TypeDeny, + LastType + }; + + ChannelInfo() + : type((Type) -1) + { + } + + ChannelInfo(Type type) + : type(type) + { + } + + static QString identifierForType(Type type); + static uint typeForIdentifier(const QString &identifier); + + Type type; + ReferencedHandles handle; + ChannelPtr channel; +}; + +struct TP_QT_NO_EXPORT ContactManager::Roster::BlockedContactsChangedInfo +{ + BlockedContactsChangedInfo(const HandleIdentifierMap &added, + const HandleIdentifierMap &removed, + bool continueIntrospectionWhenFinished = false) + : added(added), + removed(removed), + continueIntrospectionWhenFinished(continueIntrospectionWhenFinished) + { + } + + HandleIdentifierMap added; + HandleIdentifierMap removed; + bool continueIntrospectionWhenFinished; +}; + +struct TP_QT_NO_EXPORT ContactManager::Roster::UpdateInfo +{ + UpdateInfo(const ContactSubscriptionMap &changes, const HandleIdentifierMap &ids, + const HandleIdentifierMap &removals) + : changes(changes), + ids(ids), + removals(removals) + { + } + + ContactSubscriptionMap changes; + HandleIdentifierMap ids; + HandleIdentifierMap removals; +}; + +struct TP_QT_NO_EXPORT ContactManager::Roster::GroupsUpdateInfo +{ + GroupsUpdateInfo(const UIntList &contacts, + const QStringList &groupsAdded, const QStringList &groupsRemoved) + : contacts(contacts), + groupsAdded(groupsAdded), + groupsRemoved(groupsRemoved) + { + } + + UIntList contacts; + QStringList groupsAdded; + QStringList groupsRemoved; +}; + +struct TP_QT_NO_EXPORT ContactManager::Roster::GroupRenamedInfo +{ + GroupRenamedInfo(const QString &oldName, const QString &newName) + : oldName(oldName), + newName(newName) + { + } + + QString oldName; + QString newName; +}; + +class TP_QT_NO_EXPORT ContactManager::Roster::ModifyFinishOp : public PendingOperation +{ + Q_OBJECT + +public: + ModifyFinishOp(const ConnectionPtr &conn); + ~ModifyFinishOp() {}; + + void setError(const QString &errorName, const QString &errorMessage); + void finish(); + +private: + QString errorName, errorMessage; +}; + +class TP_QT_NO_EXPORT ContactManager::Roster::RemoveGroupOp : public PendingOperation +{ + Q_OBJECT + +public: + RemoveGroupOp(const ChannelPtr &channel); + ~RemoveGroupOp() {}; + +private Q_SLOTS: + void onContactsRemoved(Tp::PendingOperation *); + void onChannelClosed(Tp::PendingOperation *); +}; + +class TP_QT_NO_EXPORT ContactManager::PendingRefreshContactInfo : public PendingOperation +{ + Q_OBJECT + +public: + PendingRefreshContactInfo(const ConnectionPtr &conn); + ~PendingRefreshContactInfo(); + + void addContact(Contact *contact); + + void refreshInfo(); + +private Q_SLOTS: + void onRefreshInfoFinished(Tp::PendingOperation *op); + +private: + ConnectionPtr mConn; + QSet<uint> mToRequest; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/contact-manager-roster.cpp b/TelepathyQt/contact-manager-roster.cpp new file mode 100644 index 00000000..36fd78c3 --- /dev/null +++ b/TelepathyQt/contact-manager-roster.cpp @@ -0,0 +1,2217 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "TelepathyQt/contact-manager-internal.h" + +#include "TelepathyQt/_gen/contact-manager-internal.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Connection> +#include <TelepathyQt/ConnectionLowlevel> +#include <TelepathyQt/ContactFactory> +#include <TelepathyQt/PendingChannel> +#include <TelepathyQt/PendingContacts> +#include <TelepathyQt/PendingFailure> +#include <TelepathyQt/PendingHandles> +#include <TelepathyQt/PendingVariant> +#include <TelepathyQt/PendingVariantMap> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/ReferencedHandles> + +namespace Tp +{ + +ContactManager::Roster::Roster(ContactManager *contactManager) + : QObject(), + contactManager(contactManager), + usingFallbackContactList(false), + hasContactBlockingInterface(false), + introspectPendingOp(0), + introspectGroupsPendingOp(0), + pendingContactListState((uint) -1), + contactListState((uint) -1), + canReportAbusive(false), + gotContactBlockingInitialBlockedContacts(false), + canChangeContactList(false), + contactListRequestUsesMessage(false), + gotContactListInitialContacts(false), + gotContactListContactsChangedWithId(false), + groupsReintrospectionRequired(false), + contactListGroupPropertiesReceived(false), + processingContactListChanges(false), + contactListChannelsReady(0), + featureContactListGroupsTodo(0), + groupsSetSuccess(false) +{ +} + +ContactManager::Roster::~Roster() +{ +} + +ContactListState ContactManager::Roster::state() const +{ + return (Tp::ContactListState) contactListState; +} + +PendingOperation *ContactManager::Roster::introspect() +{ + ConnectionPtr conn(contactManager->connection()); + + if (conn->hasInterface(TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_LIST)) { + debug() << "Connection.ContactList found, using it"; + + usingFallbackContactList = false; + + if (conn->hasInterface(TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_BLOCKING)) { + debug() << "Connection.ContactBlocking found. using it"; + hasContactBlockingInterface = true; + introspectContactBlocking(); + } else { + debug() << "Connection.ContactBlocking not found, falling back " + "to contact list deny channel"; + + debug() << "Requesting handle for deny channel"; + + contactListChannels.insert(ChannelInfo::TypeDeny, + ChannelInfo(ChannelInfo::TypeDeny)); + + PendingHandles *ph = conn->lowlevel()->requestHandles(HandleTypeList, + QStringList() << ChannelInfo::identifierForType( + ChannelInfo::TypeDeny)); + connect(ph, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(gotContactListChannelHandle(Tp::PendingOperation*))); + } + } else { + debug() << "Connection.ContactList not found, falling back to contact list channels"; + + usingFallbackContactList = true; + + for (uint i = 0; i < ChannelInfo::LastType; ++i) { + QString channelId = ChannelInfo::identifierForType( + (ChannelInfo::Type) i); + + debug() << "Requesting handle for" << channelId << "channel"; + + contactListChannels.insert(i, + ChannelInfo((ChannelInfo::Type) i)); + + PendingHandles *ph = conn->lowlevel()->requestHandles(HandleTypeList, + QStringList() << channelId); + connect(ph, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(gotContactListChannelHandle(Tp::PendingOperation*))); + } + } + + Q_ASSERT(!introspectPendingOp); + introspectPendingOp = new PendingOperation(conn); + return introspectPendingOp; +} + +PendingOperation *ContactManager::Roster::introspectGroups() +{ + ConnectionPtr conn(contactManager->connection()); + + Q_ASSERT(!introspectGroupsPendingOp); + + if (conn->hasInterface(TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_LIST)) { + if (!conn->hasInterface(TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) { + return new PendingFailure(TP_QT_ERROR_NOT_IMPLEMENTED, + QLatin1String("Roster groups not supported"), conn); + } + + debug() << "Connection.ContactGroups found, using it"; + + if (!gotContactListInitialContacts) { + debug() << "Initial ContactList contacts not retrieved. Postponing introspection"; + groupsReintrospectionRequired = true; + return new PendingSuccess(conn); + } + + Client::ConnectionInterfaceContactGroupsInterface *iface = + conn->interface<Client::ConnectionInterfaceContactGroupsInterface>(); + + connect(iface, + SIGNAL(GroupsChanged(Tp::UIntList,QStringList,QStringList)), + SLOT(onContactListGroupsChanged(Tp::UIntList,QStringList,QStringList))); + connect(iface, + SIGNAL(GroupsCreated(QStringList)), + SLOT(onContactListGroupsCreated(QStringList))); + connect(iface, + SIGNAL(GroupRenamed(QString,QString)), + SLOT(onContactListGroupRenamed(QString,QString))); + connect(iface, + SIGNAL(GroupsRemoved(QStringList)), + SLOT(onContactListGroupsRemoved(QStringList))); + + PendingVariantMap *pvm = iface->requestAllProperties(); + connect(pvm, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(gotContactListGroupsProperties(Tp::PendingOperation*))); + } else { + debug() << "Connection.ContactGroups not found, falling back to contact list group channels"; + + ++featureContactListGroupsTodo; // decremented in gotChannels + + // we already checked if requests interface exists, so bypass requests + // interface checking + Client::ConnectionInterfaceRequestsInterface *iface = + conn->interface<Client::ConnectionInterfaceRequestsInterface>(); + + debug() << "Connecting to Requests.NewChannels"; + connect(iface, + SIGNAL(NewChannels(Tp::ChannelDetailsList)), + SLOT(onNewChannels(Tp::ChannelDetailsList))); + + debug() << "Retrieving channels"; + Client::DBus::PropertiesInterface *properties = + contactManager->connection()->interface<Client::DBus::PropertiesInterface>(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + properties->Get( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_REQUESTS), + QLatin1String("Channels")), this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotChannels(QDBusPendingCallWatcher*))); + } + + if (groupsReintrospectionRequired) { + return NULL; + } + + Q_ASSERT(!introspectGroupsPendingOp); + introspectGroupsPendingOp = new PendingOperation(conn); + return introspectGroupsPendingOp; +} + +void ContactManager::Roster::reset() +{ + contactListChannels.clear(); + subscribeChannel.reset(); + publishChannel.reset(); + storedChannel.reset(); + denyChannel.reset(); + contactListGroupChannels.clear(); + removedContactListGroupChannels.clear(); +} + +Contacts ContactManager::Roster::allKnownContacts() const +{ + return cachedAllKnownContacts; +} + +QStringList ContactManager::Roster::allKnownGroups() const +{ + if (usingFallbackContactList) { + return contactListGroupChannels.keys(); + } + + return cachedAllKnownGroups.toList(); +} + +PendingOperation *ContactManager::Roster::addGroup(const QString &group) +{ + ConnectionPtr conn(contactManager->connection()); + + if (usingFallbackContactList) { + QVariantMap request; + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST)); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), + (uint) Tp::HandleTypeGroup); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID"), + group); + return conn->lowlevel()->ensureChannel(request); + } + + if (!conn->hasInterface(TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Not implemented"), + conn); + } + + Client::ConnectionInterfaceContactGroupsInterface *iface = + conn->interface<Client::ConnectionInterfaceContactGroupsInterface>(); + Q_ASSERT(iface); + return queuedFinishVoid(iface->AddToGroup(group, UIntList())); +} + +PendingOperation *ContactManager::Roster::removeGroup(const QString &group) +{ + ConnectionPtr conn(contactManager->connection()); + + if (usingFallbackContactList) { + if (!contactListGroupChannels.contains(group)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Invalid group"), + conn); + } + + ChannelPtr channel = contactListGroupChannels[group]; + return new RemoveGroupOp(channel); + } + + if (!conn->hasInterface(TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Not implemented"), + conn); + } + + Client::ConnectionInterfaceContactGroupsInterface *iface = + conn->interface<Client::ConnectionInterfaceContactGroupsInterface>(); + Q_ASSERT(iface); + return queuedFinishVoid(iface->RemoveGroup(group)); +} + +Contacts ContactManager::Roster::groupContacts(const QString &group) const +{ + if (usingFallbackContactList) { + if (!contactListGroupChannels.contains(group)) { + return Contacts(); + } + + ChannelPtr channel = contactListGroupChannels[group]; + return channel->groupContacts(); + } + + Contacts ret; + foreach (const ContactPtr &contact, allKnownContacts()) { + if (contact->groups().contains(group)) + ret << contact; + } + return ret; +} + +PendingOperation *ContactManager::Roster::addContactsToGroup(const QString &group, + const QList<ContactPtr> &contacts) +{ + ConnectionPtr conn(contactManager->connection()); + + if (usingFallbackContactList) { + if (!contactListGroupChannels.contains(group)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Invalid group"), + conn); + } + + ChannelPtr channel = contactListGroupChannels[group]; + return channel->groupAddContacts(contacts); + } + + if (!conn->hasInterface(TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Not implemented"), + conn); + } + + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; + } + + Client::ConnectionInterfaceContactGroupsInterface *iface = + conn->interface<Client::ConnectionInterfaceContactGroupsInterface>(); + Q_ASSERT(iface); + return queuedFinishVoid(iface->AddToGroup(group, handles)); +} + +PendingOperation *ContactManager::Roster::removeContactsFromGroup(const QString &group, + const QList<ContactPtr> &contacts) +{ + ConnectionPtr conn(contactManager->connection()); + + if (usingFallbackContactList) { + if (!contactListGroupChannels.contains(group)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Invalid group"), + conn); + } + + ChannelPtr channel = contactListGroupChannels[group]; + return channel->groupRemoveContacts(contacts); + } + + if (!conn->hasInterface(TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Not implemented"), + conn); + } + + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; + } + + Client::ConnectionInterfaceContactGroupsInterface *iface = + conn->interface<Client::ConnectionInterfaceContactGroupsInterface>(); + Q_ASSERT(iface); + return queuedFinishVoid(iface->RemoveFromGroup(group, handles)); +} + +bool ContactManager::Roster::canRequestPresenceSubscription() const +{ + if (usingFallbackContactList) { + return subscribeChannel && subscribeChannel->groupCanAddContacts(); + } + + return canChangeContactList; +} + +bool ContactManager::Roster::subscriptionRequestHasMessage() const +{ + if (usingFallbackContactList) { + return subscribeChannel && + (subscribeChannel->groupFlags() & ChannelGroupFlagMessageAdd); + } + + return contactListRequestUsesMessage; +} + +PendingOperation *ContactManager::Roster::requestPresenceSubscription( + const QList<ContactPtr> &contacts, const QString &message) +{ + ConnectionPtr conn(contactManager->connection()); + + if (usingFallbackContactList) { + if (!subscribeChannel) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Cannot subscribe to contacts' presence on this protocol"), + conn); + } + + return subscribeChannel->groupAddContacts(contacts, message); + } + + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; + } + + Client::ConnectionInterfaceContactListInterface *iface = + conn->interface<Client::ConnectionInterfaceContactListInterface>(); + Q_ASSERT(iface); + return queuedFinishVoid(iface->RequestSubscription(handles, message)); +} + +bool ContactManager::Roster::canRemovePresenceSubscription() const +{ + if (usingFallbackContactList) { + return subscribeChannel && subscribeChannel->groupCanRemoveContacts(); + } + + return canChangeContactList; +} + +bool ContactManager::Roster::subscriptionRemovalHasMessage() const +{ + if (usingFallbackContactList) { + return subscribeChannel && + (subscribeChannel->groupFlags() & ChannelGroupFlagMessageRemove); + } + + return false; +} + +bool ContactManager::Roster::canRescindPresenceSubscriptionRequest() const +{ + if (usingFallbackContactList) { + return subscribeChannel && subscribeChannel->groupCanRescindContacts(); + } + + return canChangeContactList; +} + +bool ContactManager::Roster::subscriptionRescindingHasMessage() const +{ + if (usingFallbackContactList) { + return subscribeChannel && + (subscribeChannel->groupFlags() & ChannelGroupFlagMessageRescind); + } + + return false; +} + +PendingOperation *ContactManager::Roster::removePresenceSubscription( + const QList<ContactPtr> &contacts, const QString &message) +{ + ConnectionPtr conn(contactManager->connection()); + + if (usingFallbackContactList) { + if (!subscribeChannel) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Cannot subscribe to contacts' presence on this protocol"), + conn); + } + + return subscribeChannel->groupRemoveContacts(contacts, message); + } + + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; + } + + Client::ConnectionInterfaceContactListInterface *iface = + conn->interface<Client::ConnectionInterfaceContactListInterface>(); + Q_ASSERT(iface); + return queuedFinishVoid(iface->Unsubscribe(handles)); +} + +bool ContactManager::Roster::canAuthorizePresencePublication() const +{ + if (usingFallbackContactList) { + // do not check for Channel::groupCanAddContacts as all contacts in local + // pending can be added, even if the Channel::groupFlags() does not contain + // the flag CanAdd + return (bool) publishChannel; + } + + return canChangeContactList; +} + +bool ContactManager::Roster::publicationAuthorizationHasMessage() const +{ + if (usingFallbackContactList) { + return subscribeChannel && + (subscribeChannel->groupFlags() & ChannelGroupFlagMessageAccept); + } + + return false; +} + +PendingOperation *ContactManager::Roster::authorizePresencePublication( + const QList<ContactPtr> &contacts, const QString &message) +{ + ConnectionPtr conn(contactManager->connection()); + + if (usingFallbackContactList) { + if (!publishChannel) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Cannot control publication of presence on this protocol"), + conn); + } + + return publishChannel->groupAddContacts(contacts, message); + } + + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; + } + + Client::ConnectionInterfaceContactListInterface *iface = + conn->interface<Client::ConnectionInterfaceContactListInterface>(); + Q_ASSERT(iface); + return queuedFinishVoid(iface->AuthorizePublication(handles)); +} + +bool ContactManager::Roster::publicationRejectionHasMessage() const +{ + if (usingFallbackContactList) { + return subscribeChannel && + (subscribeChannel->groupFlags() & ChannelGroupFlagMessageReject); + } + + return false; +} + +bool ContactManager::Roster::canRemovePresencePublication() const +{ + if (usingFallbackContactList) { + return publishChannel && publishChannel->groupCanRemoveContacts(); + } + + return canChangeContactList; +} + +bool ContactManager::Roster::publicationRemovalHasMessage() const +{ + if (usingFallbackContactList) { + return subscribeChannel && + (subscribeChannel->groupFlags() & ChannelGroupFlagMessageRemove); + } + + return false; +} + +PendingOperation *ContactManager::Roster::removePresencePublication( + const QList<ContactPtr> &contacts, const QString &message) +{ + ConnectionPtr conn(contactManager->connection()); + + if (usingFallbackContactList) { + if (!publishChannel) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Cannot control publication of presence on this protocol"), + conn); + } + + return publishChannel->groupRemoveContacts(contacts, message); + } + + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; + } + + Client::ConnectionInterfaceContactListInterface *iface = + conn->interface<Client::ConnectionInterfaceContactListInterface>(); + Q_ASSERT(iface); + return queuedFinishVoid(iface->Unpublish(handles)); +} + +PendingOperation *ContactManager::Roster::removeContacts( + const QList<ContactPtr> &contacts, const QString &message) +{ + ConnectionPtr conn(contactManager->connection()); + + if (usingFallbackContactList) { + /* If the CM implements stored channel correctly, it should have the + * wanted behaviour. Otherwise we have to to remove from publish + * and subscribe channels. + */ + + if (storedChannel && storedChannel->groupCanRemoveContacts()) { + debug() << "Removing contacts from stored list"; + return storedChannel->groupRemoveContacts(contacts, message); + } + + QList<PendingOperation*> operations; + + if (canRemovePresenceSubscription()) { + debug() << "Removing contacts from subscribe list"; + operations << removePresenceSubscription(contacts, message); + } + + if (canRemovePresencePublication()) { + debug() << "Removing contacts from publish list"; + operations << removePresencePublication(contacts, message); + } + + if (operations.isEmpty()) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Cannot remove contacts on this protocol"), + conn); + } + + return new PendingComposite(operations, conn); + } + + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; + } + + Client::ConnectionInterfaceContactListInterface *iface = + conn->interface<Client::ConnectionInterfaceContactListInterface>(); + Q_ASSERT(iface); + return queuedFinishVoid(iface->RemoveContacts(handles)); +} + +bool ContactManager::Roster::canBlockContacts() const +{ + if (!usingFallbackContactList && hasContactBlockingInterface) { + return true; + } else { + return (bool) denyChannel; + } +} + +bool ContactManager::Roster::canReportAbuse() const +{ + return canReportAbusive; +} + +PendingOperation *ContactManager::Roster::blockContacts( + const QList<ContactPtr> &contacts, bool value, bool reportAbuse) +{ + if (!contactManager->connection()->isValid()) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection is invalid"), + contactManager->connection()); + } else if (!contactManager->connection()->isReady(Connection::FeatureRoster)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection::FeatureRoster is not ready"), + contactManager->connection()); + } + + if (!usingFallbackContactList && hasContactBlockingInterface) { + ConnectionPtr conn(contactManager->connection()); + Client::ConnectionInterfaceContactBlockingInterface *iface = + conn->interface<Client::ConnectionInterfaceContactBlockingInterface>(); + + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles << contact->handle()[0]; + } + + Q_ASSERT(iface); + if(value) { + return queuedFinishVoid(iface->BlockContacts(handles, reportAbuse)); + } else { + return queuedFinishVoid(iface->UnblockContacts(handles)); + } + + } else { + ConnectionPtr conn(contactManager->connection()); + + if (!denyChannel) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("Cannot block contacts on this protocol"), + conn); + } + + if (value) { + return denyChannel->groupAddContacts(contacts); + } else { + return denyChannel->groupRemoveContacts(contacts); + } + } +} + +void ContactManager::Roster::gotContactBlockingCapabilities(PendingOperation *op) +{ + if (op->isError()) { + warning() << "Getting ContactBlockingCapabilities property failed with" << + op->errorName() << ":" << op->errorMessage(); + introspectContactList(); + return; + } + + debug() << "Got ContactBlockingCapabilities property"; + + PendingVariant *pv = qobject_cast<PendingVariant*>(op); + + uint contactBlockingCaps = pv->result().toUInt(); + canReportAbusive = + contactBlockingCaps & ContactBlockingCapabilityCanReportAbusive; + + introspectContactBlockingBlockedContacts(); +} + +void ContactManager::Roster::gotContactBlockingBlockedContacts( + QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<HandleIdentifierMap> reply = *watcher; + + if (watcher->isError()) { + warning() << "Getting initial ContactBlocking blocked " + "contacts failed with" << + watcher->error().name() << ":" << watcher->error().message(); + introspectContactList(); + return; + } + + debug() << "Got initial ContactBlocking blocked contacts"; + + gotContactBlockingInitialBlockedContacts = true; + + ConnectionPtr conn(contactManager->connection()); + HandleIdentifierMap contactIds = reply.value(); + + if (!contactIds.isEmpty()) { + conn->lowlevel()->injectContactIds(contactIds); + + //fake change event where all the contacts are added + contactListBlockedContactsChangedQueue.enqueue( + BlockedContactsChangedInfo(contactIds, HandleIdentifierMap(), true)); + contactListChangesQueue.enqueue( + &ContactManager::Roster::processContactListBlockedContactsChanged); + processContactListChanges(); + } else { + introspectContactList(); + } +} + +void ContactManager::Roster::onContactBlockingBlockedContactsChanged( + const HandleIdentifierMap &added, + const HandleIdentifierMap &removed) +{ + if (!gotContactBlockingInitialBlockedContacts) { + return; + } + + ConnectionPtr conn(contactManager->connection()); + conn->lowlevel()->injectContactIds(added); + conn->lowlevel()->injectContactIds(removed); + + contactListBlockedContactsChangedQueue.enqueue( + BlockedContactsChangedInfo(added, removed)); + contactListChangesQueue.enqueue( + &ContactManager::Roster::processContactListBlockedContactsChanged); + processContactListChanges(); +} + +void ContactManager::Roster::gotContactListProperties(PendingOperation *op) +{ + if (op->isError()) { + // We may have been in state Failure and then Success, and FeatureRoster is already ready + if (introspectPendingOp) { + introspectPendingOp->setFinishedWithError( + op->errorName(), op->errorMessage()); + introspectPendingOp = 0; + } + return; + } + + debug() << "Got ContactList properties"; + + PendingVariantMap *pvm = qobject_cast<PendingVariantMap*>(op); + + QVariantMap props = pvm->result(); + + canChangeContactList = qdbus_cast<uint>(props[QLatin1String("CanChangeContactList")]); + contactListRequestUsesMessage = qdbus_cast<uint>(props[QLatin1String("RequestUsesMessage")]); + + // only update the status if we did not get it from ContactListStateChanged + if (pendingContactListState == (uint) -1) { + uint state = qdbus_cast<uint>(props[QLatin1String("ContactListState")]); + onContactListStateChanged(state); + } +} + +void ContactManager::Roster::gotContactListContacts(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<ContactAttributesMap> reply = *watcher; + + if (watcher->isError()) { + warning() << "Failed introspecting ContactList contacts"; + + contactListState = ContactListStateFailure; + debug() << "Setting state to failure"; + emit contactManager->stateChanged((Tp::ContactListState) contactListState); + + // We may have been in state Failure and then Success, and FeatureRoster is already ready + if (introspectPendingOp) { + introspectPendingOp->setFinishedWithError( + reply.error()); + introspectPendingOp = 0; + } + return; + } + + debug() << "Got initial ContactList contacts"; + + gotContactListInitialContacts = true; + + ConnectionPtr conn(contactManager->connection()); + ContactAttributesMap attrsMap = reply.value(); + ContactAttributesMap::const_iterator begin = attrsMap.constBegin(); + ContactAttributesMap::const_iterator end = attrsMap.constEnd(); + for (ContactAttributesMap::const_iterator i = begin; i != end; ++i) { + uint bareHandle = i.key(); + QVariantMap attrs = i.value(); + + ContactPtr contact = contactManager->ensureContact(ReferencedHandles(conn, + HandleTypeContact, UIntList() << bareHandle), + conn->contactFactory()->features(), attrs); + cachedAllKnownContacts.insert(contact); + contactListContacts.insert(contact); + } + + if (contactManager->connection()->requestedFeatures().contains( + Connection::FeatureRosterGroups)) { + groupsSetSuccess = true; + } + + // We may have been in state Failure and then Success, and FeatureRoster is already ready + // In any case, if we're going to reintrospect Groups, we only advance to state success once + // that is finished. We connect to the op finishing already here to catch all the failure finish + // cases as well. + if (introspectPendingOp) { + if (!groupsSetSuccess) { + // Will emit stateChanged() signal when the op is finished in idle + // callback. This is to ensure FeatureRoster (and Groups) is marked ready. + connect(introspectPendingOp, + SIGNAL(finished(Tp::PendingOperation *)), + SLOT(setStateSuccess())); + } + + introspectPendingOp->setFinished(); + introspectPendingOp = 0; + } else if (!groupsSetSuccess) { + setStateSuccess(); + } else { + // Verify that Groups is actually going to set the state + // As far as I can see, this will always be the case. + Q_ASSERT(groupsReintrospectionRequired); + } + + if (groupsReintrospectionRequired) { + introspectGroups(); + } +} + +void ContactManager::Roster::setStateSuccess() +{ + if (contactManager->connection()->isValid()) { + debug() << "State is now success"; + contactListState = ContactListStateSuccess; + emit contactManager->stateChanged((Tp::ContactListState) contactListState); + } +} + +void ContactManager::Roster::onContactListStateChanged(uint state) +{ + if (pendingContactListState == state) { + // ignore redundant state changes + return; + } + + pendingContactListState = state; + + if (state == ContactListStateSuccess) { + introspectContactListContacts(); + return; + } + + contactListState = state; + + if (state == ContactListStateFailure) { + debug() << "State changed to failure, finishing roster introspection"; + } + + emit contactManager->stateChanged((Tp::ContactListState) state); + + if (state == ContactListStateFailure) { + // Consider it done here as the state may go from Failure to Success afterwards, in which + // case the contacts will appear. + Q_ASSERT(introspectPendingOp); + introspectPendingOp->setFinished(); + introspectPendingOp = 0; + } +} + +void ContactManager::Roster::onContactListContactsChangedWithId(const Tp::ContactSubscriptionMap &changes, + const Tp::HandleIdentifierMap &ids, const Tp::HandleIdentifierMap &removals) +{ + debug() << "Got ContactList.ContactsChangedWithID with" << changes.size() << + "changes and" << removals.size() << "removals"; + + gotContactListContactsChangedWithId = true; + + if (!gotContactListInitialContacts) { + debug() << "Ignoring ContactList changes until initial contacts are retrieved"; + return; + } + + ConnectionPtr conn(contactManager->connection()); + conn->lowlevel()->injectContactIds(ids); + + contactListUpdatesQueue.enqueue(UpdateInfo(changes, ids, removals)); + contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListUpdates); + processContactListChanges(); +} + +void ContactManager::Roster::onContactListContactsChanged(const Tp::ContactSubscriptionMap &changes, + const Tp::UIntList &removals) +{ + if (gotContactListContactsChangedWithId) { + return; + } + + debug() << "Got ContactList.ContactsChanged with" << changes.size() << + "changes and" << removals.size() << "removals"; + + if (!gotContactListInitialContacts) { + debug() << "Ignoring ContactList changes until initial contacts are retrieved"; + return; + } + + HandleIdentifierMap removalsMap; + foreach (uint handle, removals) { + removalsMap.insert(handle, QString()); + } + + contactListUpdatesQueue.enqueue(UpdateInfo(changes, HandleIdentifierMap(), removalsMap)); + contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListUpdates); + processContactListChanges(); +} + +void ContactManager::Roster::onContactListBlockedContactsConstructed(Tp::PendingOperation *op) +{ + BlockedContactsChangedInfo info = contactListBlockedContactsChangedQueue.dequeue(); + + if (op->isError()) { + if (info.continueIntrospectionWhenFinished) { + introspectContactList(); + } + processingContactListChanges = false; + processContactListChanges(); + return; + } + + Contacts newBlockedContacts; + Contacts unblockedContacts; + + HandleIdentifierMap::const_iterator begin = info.added.constBegin(); + HandleIdentifierMap::const_iterator end = info.added.constEnd(); + for (HandleIdentifierMap::const_iterator i = begin; i != end; ++i) { + uint bareHandle = i.key(); + + ContactPtr contact = contactManager->lookupContactByHandle(bareHandle); + if (!contact) { + warning() << "Unable to construct contact for handle" << bareHandle; + continue; + } + + debug() << "Contact" << contact->id() << "is now blocked"; + blockedContacts.insert(contact); + newBlockedContacts.insert(contact); + contact->setBlocked(true); + } + + begin = info.removed.constBegin(); + end = info.removed.constEnd(); + for (HandleIdentifierMap::const_iterator i = begin; i != end; ++i) { + uint bareHandle = i.key(); + + ContactPtr contact = contactManager->lookupContactByHandle(bareHandle); + if (!contact) { + warning() << "Unable to construct contact for handle" << bareHandle; + continue; + } + + debug() << "Contact" << contact->id() << "is now unblocked"; + blockedContacts.remove(contact); + unblockedContacts.insert(contact); + contact->setBlocked(false); + } + + // Perform the needed computation for allKnownContactsChanged + computeKnownContactsChanges(newBlockedContacts, Contacts(), + Contacts(), unblockedContacts, Channel::GroupMemberChangeDetails()); + + if (info.continueIntrospectionWhenFinished) { + introspectContactList(); + } + + processingContactListChanges = false; + processContactListChanges(); +} + +void ContactManager::Roster::onContactListNewContactsConstructed(Tp::PendingOperation *op) +{ + if (op->isError()) { + contactListUpdatesQueue.dequeue(); + processingContactListChanges = false; + processContactListChanges(); + return; + } + + UpdateInfo info = contactListUpdatesQueue.dequeue(); + + Tp::Contacts added; + Tp::Contacts removed; + + Tp::Contacts publishRequested; + + ContactSubscriptionMap::const_iterator begin = info.changes.constBegin(); + ContactSubscriptionMap::const_iterator end = info.changes.constEnd(); + for (ContactSubscriptionMap::const_iterator i = begin; i != end; ++i) { + uint bareHandle = i.key(); + ContactSubscriptions subscriptions = i.value(); + + ContactPtr contact = contactManager->lookupContactByHandle(bareHandle); + if (!contact) { + warning() << "Unable to construct contact for handle" << bareHandle; + continue; + } + + contactListContacts.insert(contact); + added << contact; + + Contact::PresenceState oldContactPublishState = contact->publishState(); + QString oldContactPublishStateMessage = contact->publishStateMessage(); + contact->setSubscriptionState((SubscriptionState) subscriptions.subscribe); + contact->setPublishState((SubscriptionState) subscriptions.publish, + subscriptions.publishRequest); + if (subscriptions.publish == SubscriptionStateAsk && + (oldContactPublishState != Contact::PresenceStateAsk || + oldContactPublishStateMessage != contact->publishStateMessage())) { + Channel::GroupMemberChangeDetails publishRequestDetails; + QVariantMap detailsMap; + detailsMap.insert(QLatin1String("message"), subscriptions.publishRequest); + publishRequestDetails = Channel::GroupMemberChangeDetails(ContactPtr(), detailsMap); + // FIXME (API/ABI break) remove both of these signals + emit contactManager->presencePublicationRequested(Contacts() << contact, + publishRequestDetails); + emit contactManager->presencePublicationRequested(Contacts() << contact, + subscriptions.publishRequest); + + publishRequested.insert(contact); + } + } + + if (!publishRequested.empty()) { + emit contactManager->presencePublicationRequested(publishRequested); + } + + foreach (uint bareHandle, info.removals.keys()) { + ContactPtr contact = contactManager->lookupContactByHandle(bareHandle); + if (!contact) { + warning() << "Unable to find removed contact with handle" << bareHandle; + continue; + } + + if (!contactListContacts.contains(contact)) { + warning() << "Contact" << contact->id() << "removed from ContactList " + "but it wasn't present, ignoring."; + continue; + } + + contactListContacts.remove(contact); + removed << contact; + } + + computeKnownContactsChanges(added, Contacts(), Contacts(), + removed, Channel::GroupMemberChangeDetails()); + + foreach (const Tp::ContactPtr &contact, removed) { + contact->setSubscriptionState(SubscriptionStateNo); + contact->setPublishState(SubscriptionStateNo); + } + + processingContactListChanges = false; + processContactListChanges(); +} + +void ContactManager::Roster::onContactListGroupsChanged(const Tp::UIntList &contacts, + const QStringList &added, const QStringList &removed) +{ + Q_ASSERT(usingFallbackContactList == false); + + if (!contactListGroupPropertiesReceived) { + return; + } + + contactListGroupsUpdatesQueue.enqueue(GroupsUpdateInfo(contacts, + added, removed)); + contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListGroupsUpdates); + processContactListChanges(); +} + +void ContactManager::Roster::onContactListGroupsCreated(const QStringList &names) +{ + Q_ASSERT(usingFallbackContactList == false); + + if (!contactListGroupPropertiesReceived) { + return; + } + + contactListGroupsCreatedQueue.enqueue(names); + contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListGroupsCreated); + processContactListChanges(); +} + +void ContactManager::Roster::onContactListGroupRenamed(const QString &oldName, const QString &newName) +{ + Q_ASSERT(usingFallbackContactList == false); + + if (!contactListGroupPropertiesReceived) { + return; + } + + contactListGroupRenamedQueue.enqueue(GroupRenamedInfo(oldName, newName)); + contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListGroupRenamed); + processContactListChanges(); +} + +void ContactManager::Roster::onContactListGroupsRemoved(const QStringList &names) +{ + Q_ASSERT(usingFallbackContactList == false); + + if (!contactListGroupPropertiesReceived) { + return; + } + + contactListGroupsRemovedQueue.enqueue(names); + contactListChangesQueue.enqueue(&ContactManager::Roster::processContactListGroupsRemoved); + processContactListChanges(); +} + +void ContactManager::Roster::onModifyFinished(Tp::PendingOperation *op) +{ + ModifyFinishOp *returned = returnedModifyOps.take(op); + + // Finished twice, or we didn't add the returned op at all? + Q_ASSERT(returned); + + if (op->isError()) { + returned->setError(op->errorName(), op->errorMessage()); + } + + modifyFinishQueue.enqueue(returned); + contactListChangesQueue.enqueue(&ContactManager::Roster::processFinishedModify); + processContactListChanges(); +} + +void ContactManager::Roster::gotContactListChannelHandle(PendingOperation *op) +{ + PendingHandles *ph = qobject_cast<PendingHandles*>(op); + Q_ASSERT(ph->namesRequested().size() == 1); + QString channelId = ph->namesRequested().first(); + uint type = ChannelInfo::typeForIdentifier(channelId); + + if (op->isError()) { + // let's not fail, because the contact lists are not supported + debug() << "Unable to retrieve handle for" << channelId << "channel, ignoring"; + contactListChannels.remove(type); + onContactListChannelReady(); + return; + } + + if (ph->invalidNames().size() == 1) { + // let's not fail, because the contact lists are not supported + debug() << "Unable to retrieve handle for" << channelId << "channel, ignoring"; + contactListChannels.remove(type); + onContactListChannelReady(); + return; + } + + Q_ASSERT(ph->handles().size() == 1); + + debug() << "Got handle for" << channelId << "channel"; + + if (!usingFallbackContactList) { + Q_ASSERT(type == ChannelInfo::TypeDeny); + } else { + Q_ASSERT(type != (uint) -1 && type < ChannelInfo::LastType); + } + + ReferencedHandles handle = ph->handles(); + contactListChannels[type].handle = handle; + + debug() << "Requesting channel for" << channelId << "channel"; + QVariantMap request; + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"), + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST)); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"), + (uint) HandleTypeList); + request.insert(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle"), + handle[0]); + ConnectionPtr conn(contactManager->connection()); + /* Request the channel passing INT_MAX as timeout (meaning no timeout), as + * some CMs may take too long to return from ensureChannel when still + * loading the contact list */ + connect(conn->lowlevel()->ensureChannel(request, INT_MAX), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(gotContactListChannel(Tp::PendingOperation*))); +} + +void ContactManager::Roster::gotContactListChannel(PendingOperation *op) +{ + if (op->isError()) { + debug() << "Unable to create channel, ignoring"; + onContactListChannelReady(); + return; + } + + PendingChannel *pc = qobject_cast<PendingChannel*>(op); + ChannelPtr channel = pc->channel(); + Q_ASSERT(channel); + uint handle = pc->targetHandle(); + Q_ASSERT(handle); + + for (uint i = 0; i < ChannelInfo::LastType; ++i) { + if (contactListChannels.contains(i) && + contactListChannels[i].handle.size() > 0 && + contactListChannels[i].handle[0] == handle) { + Q_ASSERT(!contactListChannels[i].channel); + contactListChannels[i].channel = channel; + + // deref connection refcount here as connection will keep a ref to channel and we don't + // want a contact list channel keeping a ref of connection, otherwise connection will + // leak, thus the channels. + channel->connection()->deref(); + + connect(channel->becomeReady(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onContactListChannelReady())); + } + } +} + +void ContactManager::Roster::onContactListChannelReady() +{ + if (!usingFallbackContactList) { + setContactListChannelsReady(); + + updateContactsBlockState(); + + if (denyChannel) { + cachedAllKnownContacts.unite(denyChannel->groupContacts()); + } + + introspectContactList(); + } else if (++contactListChannelsReady == ChannelInfo::LastType) { + if (contactListChannels.isEmpty()) { + contactListState = ContactListStateFailure; + debug() << "State is failure, roster not supported"; + emit contactManager->stateChanged((Tp::ContactListState) contactListState); + + Q_ASSERT(introspectPendingOp); + introspectPendingOp->setFinishedWithError(TP_QT_ERROR_NOT_IMPLEMENTED, + QLatin1String("Roster not supported")); + introspectPendingOp = 0; + return; + } + + setContactListChannelsReady(); + + updateContactsBlockState(); + + // Refresh the cache for the current known contacts + foreach (const ChannelInfo &contactListChannel, contactListChannels) { + ChannelPtr channel = contactListChannel.channel; + if (!channel) { + continue; + } + cachedAllKnownContacts.unite(channel->groupContacts()); + cachedAllKnownContacts.unite(channel->groupLocalPendingContacts()); + cachedAllKnownContacts.unite(channel->groupRemotePendingContacts()); + } + + updateContactsPresenceState(); + + Q_ASSERT(introspectPendingOp); + + if (!contactManager->connection()->requestedFeatures().contains( + Connection::FeatureRosterGroups)) { + // Will emit stateChanged() signal when the op is finished in idle + // callback. This is to ensure FeatureRoster is marked ready. + connect(introspectPendingOp, + SIGNAL(finished(Tp::PendingOperation *)), + SLOT(setStateSuccess())); + } else { + Q_ASSERT(!groupsSetSuccess); + groupsSetSuccess = true; + } + + introspectPendingOp->setFinished(); + introspectPendingOp = 0; + } +} + +void ContactManager::Roster::gotContactListGroupsProperties(PendingOperation *op) +{ + Q_ASSERT(introspectGroupsPendingOp != NULL); + + if (groupsSetSuccess) { + // Connect here, so we catch the following and the other failure cases + connect(introspectGroupsPendingOp, + SIGNAL(finished(Tp::PendingOperation *)), + SLOT(setStateSuccess())); + } + + if (op->isError()) { + warning() << "Getting contact list groups properties failed:" << op->errorName() << '-' + << op->errorMessage(); + + introspectGroupsPendingOp->setFinishedWithError( + op->errorName(), op->errorMessage()); + introspectGroupsPendingOp = 0; + + return; + } + + debug() << "Got contact list groups properties"; + PendingVariantMap *pvm = qobject_cast<PendingVariantMap*>(op); + + QVariantMap props = pvm->result(); + + cachedAllKnownGroups = qdbus_cast<QStringList>(props[QLatin1String("Groups")]).toSet(); + contactListGroupPropertiesReceived = true; + + processingContactListChanges = true; + PendingContacts *pc = contactManager->upgradeContacts( + contactManager->allKnownContacts().toList(), + Contact::FeatureRosterGroups); + connect(pc, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onContactListContactsUpgraded(Tp::PendingOperation*))); +} + +void ContactManager::Roster::onContactListContactsUpgraded(PendingOperation *op) +{ + Q_ASSERT(processingContactListChanges); + processingContactListChanges = false; + + Q_ASSERT(introspectGroupsPendingOp != NULL); + + if (op->isError()) { + warning() << "Upgrading contacts with group membership failed:" << op->errorName() << '-' + << op->errorMessage(); + + introspectGroupsPendingOp->setFinishedWithError( + op->errorName(), op->errorMessage()); + introspectGroupsPendingOp = 0; + processContactListChanges(); + return; + } + + introspectGroupsPendingOp->setFinished(); + introspectGroupsPendingOp = 0; + processContactListChanges(); +} + +void ContactManager::Roster::onNewChannels(const Tp::ChannelDetailsList &channelDetailsList) +{ + ConnectionPtr conn(contactManager->connection()); + + QString channelType; + uint handleType; + foreach (const ChannelDetails &channelDetails, channelDetailsList) { + channelType = channelDetails.properties.value( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString(); + if (channelType != QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_LIST)) { + continue; + } + + handleType = channelDetails.properties.value( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt(); + if (handleType != Tp::HandleTypeGroup) { + continue; + } + + ++featureContactListGroupsTodo; // decremented in onContactListGroupChannelReady + ChannelPtr channel = Channel::create(conn, + channelDetails.channel.path(), channelDetails.properties); + pendingContactListGroupChannels.append(channel); + + // deref connection refcount here as connection will keep a ref to channel and we don't + // want a contact list group channel keeping a ref of connection, otherwise connection will + // leak, thus the channels. + channel->connection()->deref(); + + connect(channel->becomeReady(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onContactListGroupChannelReady(Tp::PendingOperation*))); + } +} + +void ContactManager::Roster::onContactListGroupChannelReady(PendingOperation *op) +{ + --featureContactListGroupsTodo; // incremented in onNewChannels + + ConnectionPtr conn(contactManager->connection()); + + if (introspectGroupsPendingOp) { + checkContactListGroupsReady(); + } else { + PendingReady *pr = qobject_cast<PendingReady*>(op); + ChannelPtr channel = ChannelPtr::qObjectCast(pr->proxy()); + QString id = addContactListGroupChannel(channel); + emit contactManager->groupAdded(id); + pendingContactListGroupChannels.removeOne(channel); + } +} + +void ContactManager::Roster::gotChannels(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariant> reply = *watcher; + + if (!reply.isError()) { + debug() << "Got channels"; + onNewChannels(qdbus_cast<ChannelDetailsList>(reply.value())); + } else { + warning().nospace() << "Getting channels failed with " << + reply.error().name() << ":" << reply.error().message(); + } + + --featureContactListGroupsTodo; // incremented in introspectRosterGroups + + checkContactListGroupsReady(); + + watcher->deleteLater(); +} + +void ContactManager::Roster::onStoredChannelMembersChanged( + const Contacts &groupMembersAdded, + const Contacts &groupLocalPendingMembersAdded, + const Contacts &groupRemotePendingMembersAdded, + const Contacts &groupMembersRemoved, + const Channel::GroupMemberChangeDetails &details) +{ + if (!groupLocalPendingMembersAdded.isEmpty()) { + warning() << "Found local pending contacts on stored list"; + } + + if (!groupRemotePendingMembersAdded.isEmpty()) { + warning() << "Found remote pending contacts on stored list"; + } + + foreach (ContactPtr contact, groupMembersAdded) { + debug() << "Contact" << contact->id() << "on stored list"; + } + + foreach (ContactPtr contact, groupMembersRemoved) { + debug() << "Contact" << contact->id() << "removed from stored list"; + } + + // Perform the needed computation for allKnownContactsChanged + computeKnownContactsChanges(groupMembersAdded, + groupLocalPendingMembersAdded, groupRemotePendingMembersAdded, + groupMembersRemoved, details); +} + +void ContactManager::Roster::onSubscribeChannelMembersChanged( + const Contacts &groupMembersAdded, + const Contacts &groupLocalPendingMembersAdded, + const Contacts &groupRemotePendingMembersAdded, + const Contacts &groupMembersRemoved, + const Channel::GroupMemberChangeDetails &details) +{ + if (!groupLocalPendingMembersAdded.isEmpty()) { + warning() << "Found local pending contacts on subscribe list"; + } + + foreach (ContactPtr contact, groupMembersAdded) { + debug() << "Contact" << contact->id() << "on subscribe list"; + contact->setSubscriptionState(SubscriptionStateYes); + } + + foreach (ContactPtr contact, groupRemotePendingMembersAdded) { + debug() << "Contact" << contact->id() << "added to subscribe list"; + contact->setSubscriptionState(SubscriptionStateAsk); + } + + foreach (ContactPtr contact, groupMembersRemoved) { + debug() << "Contact" << contact->id() << "removed from subscribe list"; + contact->setSubscriptionState(SubscriptionStateNo); + } + + // Perform the needed computation for allKnownContactsChanged + computeKnownContactsChanges(groupMembersAdded, + groupLocalPendingMembersAdded, groupRemotePendingMembersAdded, + groupMembersRemoved, details); +} + +void ContactManager::Roster::onPublishChannelMembersChanged( + const Contacts &groupMembersAdded, + const Contacts &groupLocalPendingMembersAdded, + const Contacts &groupRemotePendingMembersAdded, + const Contacts &groupMembersRemoved, + const Channel::GroupMemberChangeDetails &details) +{ + if (!groupRemotePendingMembersAdded.isEmpty()) { + warning() << "Found remote pending contacts on publish list"; + } + + foreach (ContactPtr contact, groupMembersAdded) { + debug() << "Contact" << contact->id() << "on publish list"; + contact->setPublishState(SubscriptionStateYes); + } + + foreach (ContactPtr contact, groupLocalPendingMembersAdded) { + debug() << "Contact" << contact->id() << "added to publish list"; + contact->setPublishState(SubscriptionStateAsk, details.message()); + } + + foreach (ContactPtr contact, groupMembersRemoved) { + debug() << "Contact" << contact->id() << "removed from publish list"; + contact->setPublishState(SubscriptionStateNo); + } + + if (!groupLocalPendingMembersAdded.isEmpty()) { + emit contactManager->presencePublicationRequested(groupLocalPendingMembersAdded); + // FIXME (API/ABI break) remove both of these signals + emit contactManager->presencePublicationRequested(groupLocalPendingMembersAdded, + details); + emit contactManager->presencePublicationRequested(groupLocalPendingMembersAdded, + details.message()); + } + + // Perform the needed computation for allKnownContactsChanged + computeKnownContactsChanges(groupMembersAdded, + groupLocalPendingMembersAdded, groupRemotePendingMembersAdded, + groupMembersRemoved, details); +} + +void ContactManager::Roster::onDenyChannelMembersChanged( + const Contacts &groupMembersAdded, + const Contacts &groupLocalPendingMembersAdded, + const Contacts &groupRemotePendingMembersAdded, + const Contacts &groupMembersRemoved, + const Channel::GroupMemberChangeDetails &details) +{ + if (!groupLocalPendingMembersAdded.isEmpty()) { + warning() << "Found local pending contacts on deny list"; + } + + if (!groupRemotePendingMembersAdded.isEmpty()) { + warning() << "Found remote pending contacts on deny list"; + } + + foreach (ContactPtr contact, groupMembersAdded) { + debug() << "Contact" << contact->id() << "added to deny list"; + contact->setBlocked(true); + } + + foreach (ContactPtr contact, groupMembersRemoved) { + debug() << "Contact" << contact->id() << "removed from deny list"; + contact->setBlocked(false); + } + + // Perform the needed computation for allKnownContactsChanged + computeKnownContactsChanges(groupMembersAdded, Contacts(), + Contacts(), groupMembersRemoved, details); +} + +void ContactManager::Roster::onContactListGroupMembersChanged( + const Tp::Contacts &groupMembersAdded, + const Tp::Contacts &groupLocalPendingMembersAdded, + const Tp::Contacts &groupRemotePendingMembersAdded, + const Tp::Contacts &groupMembersRemoved, + const Tp::Channel::GroupMemberChangeDetails &details) +{ + ChannelPtr contactListGroupChannel = ChannelPtr( + qobject_cast<Channel*>(sender())); + QString id = contactListGroupChannel->immutableProperties().value( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID")).toString(); + + foreach (const ContactPtr &contact, groupMembersAdded) { + contact->setAddedToGroup(id); + } + foreach (const ContactPtr &contact, groupMembersRemoved) { + contact->setRemovedFromGroup(id); + } + + emit contactManager->groupMembersChanged(id, groupMembersAdded, + groupMembersRemoved, details); +} + +void ContactManager::Roster::onContactListGroupRemoved(Tp::DBusProxy *proxy, + const QString &errorName, const QString &errorMessage) +{ + Q_UNUSED(errorName); + Q_UNUSED(errorMessage); + + // Is it correct to assume that if an user-defined contact list + // gets invalidated it means it was removed? Spec states that if a + // user-defined contact list gets closed it was removed, and Channel + // invalidates itself when it gets closed. + ChannelPtr contactListGroupChannel = ChannelPtr(qobject_cast<Channel*>(proxy)); + QString id = contactListGroupChannel->immutableProperties().value( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID")).toString(); + contactListGroupChannels.remove(id); + removedContactListGroupChannels.append(contactListGroupChannel); + disconnect(contactListGroupChannel.data(), 0, 0, 0); + emit contactManager->groupRemoved(id); +} + +void ContactManager::Roster::introspectContactBlocking() +{ + debug() << "Requesting ContactBlockingCapabilities property"; + + ConnectionPtr conn(contactManager->connection()); + + Client::ConnectionInterfaceContactBlockingInterface *iface = + conn->interface<Client::ConnectionInterfaceContactBlockingInterface>(); + + PendingVariant *pv = iface->requestPropertyContactBlockingCapabilities(); + connect(pv, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(gotContactBlockingCapabilities(Tp::PendingOperation*))); +} + +void ContactManager::Roster::introspectContactBlockingBlockedContacts() +{ + ConnectionPtr conn(contactManager->connection()); + + Client::ConnectionInterfaceContactBlockingInterface *iface = + conn->interface<Client::ConnectionInterfaceContactBlockingInterface>(); + + Q_ASSERT(iface); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + iface->RequestBlockedContacts(), contactManager); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotContactBlockingBlockedContacts(QDBusPendingCallWatcher*))); + + connect(iface, + SIGNAL(BlockedContactsChanged(Tp::HandleIdentifierMap,Tp::HandleIdentifierMap)), + SLOT(onContactBlockingBlockedContactsChanged(Tp::HandleIdentifierMap,Tp::HandleIdentifierMap))); +} + +void ContactManager::Roster::introspectContactList() +{ + debug() << "Requesting ContactList properties"; + + ConnectionPtr conn(contactManager->connection()); + + Client::ConnectionInterfaceContactListInterface *iface = + conn->interface<Client::ConnectionInterfaceContactListInterface>(); + + connect(iface, + SIGNAL(ContactListStateChanged(uint)), + SLOT(onContactListStateChanged(uint))); + connect(iface, + SIGNAL(ContactsChangedWithID(Tp::ContactSubscriptionMap,Tp::HandleIdentifierMap,Tp::HandleIdentifierMap)), + SLOT(onContactListContactsChangedWithId(Tp::ContactSubscriptionMap,Tp::HandleIdentifierMap,Tp::HandleIdentifierMap))); + connect(iface, + SIGNAL(ContactsChanged(Tp::ContactSubscriptionMap,Tp::UIntList)), + SLOT(onContactListContactsChanged(Tp::ContactSubscriptionMap,Tp::UIntList))); + + PendingVariantMap *pvm = iface->requestAllProperties(); + connect(pvm, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(gotContactListProperties(Tp::PendingOperation*))); +} + +void ContactManager::Roster::introspectContactListContacts() +{ + ConnectionPtr conn(contactManager->connection()); + + Client::ConnectionInterfaceContactListInterface *iface = + conn->interface<Client::ConnectionInterfaceContactListInterface>(); + + Features features(conn->contactFactory()->features()); + Features supportedFeatures(contactManager->supportedFeatures()); + QSet<QString> interfaces; + foreach (const Feature &feature, features) { + contactManager->ensureTracking(feature); + + if (supportedFeatures.contains(feature)) { + // Only query interfaces which are reported as supported to not get an error + interfaces.insert(contactManager->featureToInterface(feature)); + } + } + interfaces.insert(TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_LIST); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + iface->GetContactListAttributes(interfaces.toList(), true), contactManager); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotContactListContacts(QDBusPendingCallWatcher*))); +} + +void ContactManager::Roster::processContactListChanges() +{ + if (processingContactListChanges || contactListChangesQueue.isEmpty()) { + return; + } + + processingContactListChanges = true; + (this->*(contactListChangesQueue.dequeue()))(); +} + +void ContactManager::Roster::processContactListBlockedContactsChanged() +{ + BlockedContactsChangedInfo info = contactListBlockedContactsChangedQueue.head(); + + UIntList contacts; + HandleIdentifierMap::const_iterator begin = info.added.constBegin(); + HandleIdentifierMap::const_iterator end = info.added.constEnd(); + for (HandleIdentifierMap::const_iterator i = begin; i != end; ++i) { + uint bareHandle = i.key(); + contacts << bareHandle; + } + + begin = info.removed.constBegin(); + end = info.removed.constEnd(); + for (HandleIdentifierMap::const_iterator i = begin; i != end; ++i) { + uint bareHandle = i.key(); + contacts << bareHandle; + } + + Features features; + if (contactManager->connection()->isReady(Connection::FeatureRosterGroups)) { + features << Contact::FeatureRosterGroups; + } + PendingContacts *pc = contactManager->contactsForHandles(contacts, features); + connect(pc, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onContactListBlockedContactsConstructed(Tp::PendingOperation*))); +} + +void ContactManager::Roster::processContactListUpdates() +{ + UpdateInfo info = contactListUpdatesQueue.head(); + + // construct Contact objects for all contacts in added to the contact list + UIntList contacts; + ContactSubscriptionMap::const_iterator begin = info.changes.constBegin(); + ContactSubscriptionMap::const_iterator end = info.changes.constEnd(); + for (ContactSubscriptionMap::const_iterator i = begin; i != end; ++i) { + uint bareHandle = i.key(); + contacts << bareHandle; + } + + Features features; + if (contactManager->connection()->isReady(Connection::FeatureRosterGroups)) { + features << Contact::FeatureRosterGroups; + } + PendingContacts *pc = contactManager->contactsForHandles(contacts, features); + connect(pc, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onContactListNewContactsConstructed(Tp::PendingOperation*))); +} + +void ContactManager::Roster::processContactListGroupsUpdates() +{ + GroupsUpdateInfo info = contactListGroupsUpdatesQueue.dequeue(); + + foreach (const QString &group, info.groupsAdded) { + Contacts contacts; + foreach (uint bareHandle, info.contacts) { + ContactPtr contact = contactManager->lookupContactByHandle(bareHandle); + if (!contact) { + warning() << "contact with handle" << bareHandle << "was added to a group but " + "never added to the contact list, ignoring"; + continue; + } + contacts << contact; + contact->setAddedToGroup(group); + } + + emit contactManager->groupMembersChanged(group, contacts, + Contacts(), Channel::GroupMemberChangeDetails()); + } + + foreach (const QString &group, info.groupsRemoved) { + Contacts contacts; + foreach (uint bareHandle, info.contacts) { + ContactPtr contact = contactManager->lookupContactByHandle(bareHandle); + if (!contact) { + warning() << "contact with handle" << bareHandle << "was removed from a group but " + "never added to the contact list, ignoring"; + continue; + } + contacts << contact; + contact->setRemovedFromGroup(group); + } + + emit contactManager->groupMembersChanged(group, Contacts(), + contacts, Channel::GroupMemberChangeDetails()); + } + + processingContactListChanges = false; + processContactListChanges(); +} + +void ContactManager::Roster::processContactListGroupsCreated() +{ + QStringList names = contactListGroupsCreatedQueue.dequeue(); + foreach (const QString &name, names) { + cachedAllKnownGroups.insert(name); + emit contactManager->groupAdded(name); + } + + processingContactListChanges = false; + processContactListChanges(); +} + +void ContactManager::Roster::processContactListGroupRenamed() +{ + GroupRenamedInfo info = contactListGroupRenamedQueue.dequeue(); + cachedAllKnownGroups.remove(info.oldName); + cachedAllKnownGroups.insert(info.newName); + emit contactManager->groupRenamed(info.oldName, info.newName); + + processingContactListChanges = false; + processContactListChanges(); +} + +void ContactManager::Roster::processContactListGroupsRemoved() +{ + QStringList names = contactListGroupsRemovedQueue.dequeue(); + foreach (const QString &name, names) { + cachedAllKnownGroups.remove(name); + emit contactManager->groupRemoved(name); + } + + processingContactListChanges = false; + processContactListChanges(); +} + +void ContactManager::Roster::processFinishedModify() +{ + ModifyFinishOp *op = modifyFinishQueue.dequeue(); + // Only continue processing changes (and thus, emitting change signals) when the op has signaled + // finish (it'll only do this after we've returned to the mainloop) + connect(op, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onModifyFinishSignaled())); + op->finish(); +} + +PendingOperation *ContactManager::Roster::queuedFinishVoid(const QDBusPendingCall &call) +{ + PendingOperation *actual = new PendingVoid(call, contactManager->connection()); + connect(actual, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onModifyFinished(Tp::PendingOperation*))); + ModifyFinishOp *toReturn = new ModifyFinishOp(contactManager->connection()); + returnedModifyOps.insert(actual, toReturn); + return toReturn; +} + +void ContactManager::Roster::onModifyFinishSignaled() +{ + processingContactListChanges = false; + processContactListChanges(); +} + +void ContactManager::Roster::setContactListChannelsReady() +{ + if (!usingFallbackContactList) { + Q_ASSERT(!contactListChannels.contains(ChannelInfo::TypeSubscribe)); + Q_ASSERT(!contactListChannels.contains(ChannelInfo::TypePublish)); + Q_ASSERT(!contactListChannels.contains(ChannelInfo::TypeStored)); + } + + if (contactListChannels.contains(ChannelInfo::TypeSubscribe)) { + subscribeChannel = contactListChannels[ChannelInfo::TypeSubscribe].channel; + } + + if (contactListChannels.contains(ChannelInfo::TypePublish)) { + publishChannel = contactListChannels[ChannelInfo::TypePublish].channel; + } + + if (contactListChannels.contains(ChannelInfo::TypeStored)) { + storedChannel = contactListChannels[ChannelInfo::TypeStored].channel; + } + + if (contactListChannels.contains(ChannelInfo::TypeDeny)) { + denyChannel = contactListChannels[ChannelInfo::TypeDeny].channel; + } + + uint type; + ChannelPtr channel; + const char *method; + for (QMap<uint, ChannelInfo>::const_iterator i = contactListChannels.constBegin(); + i != contactListChannels.constEnd(); ++i) { + type = i.key(); + channel = i.value().channel; + if (!channel) { + continue; + } + + if (type == ChannelInfo::TypeStored) { + method = SLOT(onStoredChannelMembersChanged( + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Channel::GroupMemberChangeDetails)); + } else if (type == ChannelInfo::TypeSubscribe) { + method = SLOT(onSubscribeChannelMembersChanged( + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Channel::GroupMemberChangeDetails)); + } else if (type == ChannelInfo::TypePublish) { + method = SLOT(onPublishChannelMembersChanged( + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Channel::GroupMemberChangeDetails)); + } else if (type == ChannelInfo::TypeDeny) { + method = SLOT(onDenyChannelMembersChanged( + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Channel::GroupMemberChangeDetails)); + } else { + continue; + } + + connect(channel.data(), + SIGNAL(groupMembersChanged( + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Channel::GroupMemberChangeDetails)), + method); + } +} + +void ContactManager::Roster::updateContactsBlockState() +{ + Q_ASSERT(!hasContactBlockingInterface); + + if (!denyChannel) { + return; + } + + Contacts denyContacts = denyChannel->groupContacts(); + foreach (const ContactPtr &contact, denyContacts) { + contact->setBlocked(true); + } +} + +void ContactManager::Roster::updateContactsPresenceState() +{ + if (!subscribeChannel && !publishChannel) { + return; + } + + Contacts subscribeContacts; + Contacts subscribeContactsRP; + + if (subscribeChannel) { + subscribeContacts = subscribeChannel->groupContacts(); + subscribeContactsRP = subscribeChannel->groupRemotePendingContacts(); + } + + Contacts publishContacts; + Contacts publishContactsLP; + if (publishChannel) { + publishContacts = publishChannel->groupContacts(); + publishContactsLP = publishChannel->groupLocalPendingContacts(); + } + + Contacts contacts = cachedAllKnownContacts; + foreach (ContactPtr contact, contacts) { + if (subscribeChannel) { + // not in "subscribe" -> No, in "subscribe" lp -> Ask, in "subscribe" current -> Yes + if (subscribeContacts.contains(contact)) { + contact->setSubscriptionState(SubscriptionStateYes); + } else if (subscribeContactsRP.contains(contact)) { + contact->setSubscriptionState(SubscriptionStateAsk); + } else { + contact->setSubscriptionState(SubscriptionStateNo); + } + } + + if (publishChannel) { + // not in "publish" -> No, in "subscribe" rp -> Ask, in "publish" current -> Yes + if (publishContacts.contains(contact)) { + contact->setPublishState(SubscriptionStateYes); + } else if (publishContactsLP.contains(contact)) { + contact->setPublishState(SubscriptionStateAsk, + publishChannel->groupLocalPendingContactChangeInfo(contact).message()); + } else { + contact->setPublishState(SubscriptionStateNo); + } + } + } +} + +void ContactManager::Roster::computeKnownContactsChanges(const Tp::Contacts& added, + const Tp::Contacts& pendingAdded, const Tp::Contacts& remotePendingAdded, + const Tp::Contacts& removed, const Channel::GroupMemberChangeDetails &details) +{ + // First of all, compute the real additions/removals based upon our cache + Tp::Contacts realAdded; + realAdded.unite(added); + realAdded.unite(pendingAdded); + realAdded.unite(remotePendingAdded); + realAdded.subtract(cachedAllKnownContacts); + Tp::Contacts realRemoved = removed; + realRemoved.intersect(cachedAllKnownContacts); + + // Check if realRemoved have been _really_ removed from all lists + foreach (const ChannelInfo &contactListChannel, contactListChannels) { + ChannelPtr channel = contactListChannel.channel; + if (!channel) { + continue; + } + realRemoved.subtract(channel->groupContacts()); + realRemoved.subtract(channel->groupLocalPendingContacts()); + realRemoved.subtract(channel->groupRemotePendingContacts()); + } + + // ...and from the Conn.I.ContactList / Conn.I.ContactBlocking contacts + realRemoved.subtract(contactListContacts); + realRemoved.subtract(blockedContacts); + + // Are there any real changes? + if (!realAdded.isEmpty() || !realRemoved.isEmpty()) { + // Yes, update our "cache" and emit the signal + cachedAllKnownContacts.unite(realAdded); + cachedAllKnownContacts.subtract(realRemoved); + emit contactManager->allKnownContactsChanged(realAdded, realRemoved, details); + } +} + +void ContactManager::Roster::checkContactListGroupsReady() +{ + if (featureContactListGroupsTodo != 0) { + return; + } + + if (groupsSetSuccess) { + Q_ASSERT(contactManager->state() != ContactListStateSuccess); + + if (introspectGroupsPendingOp) { + // Will emit stateChanged() signal when the op is finished in idle + // callback. This is to ensure FeatureRosterGroups is marked ready. + connect(introspectGroupsPendingOp, + SIGNAL(finished(Tp::PendingOperation *)), + SLOT(setStateSuccess())); + } else { + setStateSuccess(); + } + + groupsSetSuccess = false; + } + + setContactListGroupChannelsReady(); + if (introspectGroupsPendingOp) { + introspectGroupsPendingOp->setFinished(); + introspectGroupsPendingOp = 0; + } + pendingContactListGroupChannels.clear(); +} + +void ContactManager::Roster::setContactListGroupChannelsReady() +{ + Q_ASSERT(usingFallbackContactList == true); + Q_ASSERT(contactListGroupChannels.isEmpty()); + + foreach (const ChannelPtr &contactListGroupChannel, pendingContactListGroupChannels) { + addContactListGroupChannel(contactListGroupChannel); + } +} + +QString ContactManager::Roster::addContactListGroupChannel(const ChannelPtr &contactListGroupChannel) +{ + QString id = contactListGroupChannel->immutableProperties().value( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetID")).toString(); + contactListGroupChannels.insert(id, contactListGroupChannel); + connect(contactListGroupChannel.data(), + SIGNAL(groupMembersChanged( + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Channel::GroupMemberChangeDetails)), + SLOT(onContactListGroupMembersChanged( + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Contacts, + Tp::Channel::GroupMemberChangeDetails))); + connect(contactListGroupChannel.data(), + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + SLOT(onContactListGroupRemoved(Tp::DBusProxy*,QString,QString))); + + foreach (const ContactPtr &contact, contactListGroupChannel->groupContacts()) { + contact->setAddedToGroup(id); + } + + return id; +} + +/**** ContactManager::Roster::ChannelInfo ****/ +QString ContactManager::Roster::ChannelInfo::identifierForType(Type type) +{ + static QString identifiers[LastType] = { + QLatin1String("subscribe"), + QLatin1String("publish"), + QLatin1String("stored"), + QLatin1String("deny"), + }; + return identifiers[type]; +} + +uint ContactManager::Roster::ChannelInfo::typeForIdentifier(const QString &identifier) +{ + static QHash<QString, uint> types; + if (types.isEmpty()) { + types.insert(QLatin1String("subscribe"), TypeSubscribe); + types.insert(QLatin1String("publish"), TypePublish); + types.insert(QLatin1String("stored"), TypeStored); + types.insert(QLatin1String("deny"), TypeDeny); + } + if (types.contains(identifier)) { + return types[identifier]; + } + return (uint) -1; +} + +/**** ContactManager::Roster::ModifyFinishOp ****/ +ContactManager::Roster::ModifyFinishOp::ModifyFinishOp(const ConnectionPtr &conn) + : PendingOperation(conn) +{ +} + +void ContactManager::Roster::ModifyFinishOp::setError(const QString &errorName, const QString &errorMessage) +{ + Q_ASSERT(this->errorName.isEmpty()); + Q_ASSERT(this->errorMessage.isEmpty()); + + Q_ASSERT(!errorName.isEmpty()); + + this->errorName = errorName; + this->errorMessage = errorMessage; +} + +void ContactManager::Roster::ModifyFinishOp::finish() +{ + if (errorName.isEmpty()) { + setFinished(); + } else { + setFinishedWithError(errorName, errorMessage); + } +} + +/**** ContactManager::Roster::RemoveGroupOp ****/ +ContactManager::Roster::RemoveGroupOp::RemoveGroupOp(const ChannelPtr &channel) + : PendingOperation(channel) +{ + Contacts contacts = channel->groupContacts(); + if (!contacts.isEmpty()) { + connect(channel->groupRemoveContacts(contacts.toList()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onContactsRemoved(Tp::PendingOperation*))); + } else { + connect(channel->requestClose(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onChannelClosed(Tp::PendingOperation*))); + } +} + +void ContactManager::Roster::RemoveGroupOp::onContactsRemoved(PendingOperation *op) +{ + if (op->isError()) { + setFinishedWithError(op->errorName(), op->errorMessage()); + return; + } + + // Let's ignore possible errors and try to remove the group + ChannelPtr channel = ChannelPtr(qobject_cast<Channel*>((Channel *) _object().data())); + connect(channel->requestClose(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onChannelClosed(Tp::PendingOperation*))); +} + +void ContactManager::Roster::RemoveGroupOp::onChannelClosed(PendingOperation *op) +{ + if (!op->isError()) { + setFinished(); + } else { + setFinishedWithError(op->errorName(), op->errorMessage()); + } +} + +} // Tp diff --git a/TelepathyQt/contact-manager.cpp b/TelepathyQt/contact-manager.cpp new file mode 100644 index 00000000..9e4709d6 --- /dev/null +++ b/TelepathyQt/contact-manager.cpp @@ -0,0 +1,1592 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ContactManager> +#include "TelepathyQt/contact-manager-internal.h" + +#include "TelepathyQt/_gen/contact-manager.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/AvatarData> +#include <TelepathyQt/Connection> +#include <TelepathyQt/ConnectionLowlevel> +#include <TelepathyQt/ContactFactory> +#include <TelepathyQt/PendingChannel> +#include <TelepathyQt/PendingContactAttributes> +#include <TelepathyQt/PendingContacts> +#include <TelepathyQt/PendingFailure> +#include <TelepathyQt/PendingHandles> +#include <TelepathyQt/PendingVariantMap> +#include <TelepathyQt/ReferencedHandles> +#include <TelepathyQt/Utils> + +#include <QMap> +#include <QWeakPointer> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ContactManager::Private +{ + Private(ContactManager *parent, Connection *connection); + ~Private(); + + // avatar specific methods + bool buildAvatarFileName(QString token, bool createDir, + QString &avatarFileName, QString &mimeTypeFileName); + + ContactManager *parent; + QWeakPointer<Connection> connection; + ContactManager::Roster *roster; + + QMap<uint, QWeakPointer<Contact> > contacts; + + QMap<Feature, bool> tracking; + Features supportedFeatures; + + // avatar + QSet<ContactPtr> requestAvatarsQueue; + bool requestAvatarsIdle; + + // contact info + PendingRefreshContactInfo *refreshInfoOp; +}; + +ContactManager::Private::Private(ContactManager *parent, Connection *connection) + : parent(parent), + connection(connection), + roster(new ContactManager::Roster(parent)), + requestAvatarsIdle(false), + refreshInfoOp(0) +{ +} + +ContactManager::Private::~Private() +{ + delete refreshInfoOp; + delete roster; +} + +bool ContactManager::Private::buildAvatarFileName(QString token, bool createDir, + QString &avatarFileName, QString &mimeTypeFileName) +{ + QString cacheDir = QString(QLatin1String(qgetenv("XDG_CACHE_HOME"))); + if (cacheDir.isEmpty()) { + cacheDir = QString(QLatin1String("%1/.cache")).arg(QLatin1String(qgetenv("HOME"))); + } + + ConnectionPtr conn(parent->connection()); + QString path = QString(QLatin1String("%1/telepathy/avatars/%2/%3")). + arg(cacheDir).arg(conn->cmName()).arg(conn->protocolName()); + + if (createDir && !QDir().mkpath(path)) { + return false; + } + + avatarFileName = QString(QLatin1String("%1/%2")).arg(path).arg(escapeAsIdentifier(token)); + mimeTypeFileName = QString(QLatin1String("%1.mime")).arg(avatarFileName); + + return true; +} + +ContactManager::PendingRefreshContactInfo::PendingRefreshContactInfo(const ConnectionPtr &conn) + : PendingOperation(conn), + mConn(conn) +{ +} + +ContactManager::PendingRefreshContactInfo::~PendingRefreshContactInfo() +{ +} + +void ContactManager::PendingRefreshContactInfo::addContact(Contact *contact) +{ + mToRequest.insert(contact->handle()[0]); +} + +void ContactManager::PendingRefreshContactInfo::refreshInfo() +{ + Q_ASSERT(!mToRequest.isEmpty()); + + if (!mConn->isValid()) { + setFinishedWithError(TP_QT_ERROR_NOT_AVAILABLE, + QLatin1String("Connection is invalid")); + return; + } + + if (!mConn->hasInterface(TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_INFO)) { + setFinishedWithError(TP_QT_ERROR_NOT_IMPLEMENTED, + QLatin1String("Connection does not support ContactInfo interface")); + return; + } + + debug() << "Calling ContactInfo.RefreshContactInfo for" << mToRequest.size() << "handles"; + Client::ConnectionInterfaceContactInfoInterface *contactInfoInterface = + mConn->interface<Client::ConnectionInterfaceContactInfoInterface>(); + Q_ASSERT(contactInfoInterface); + PendingVoid *nested = new PendingVoid( + contactInfoInterface->RefreshContactInfo(mToRequest.toList()), + mConn); + connect(nested, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onRefreshInfoFinished(Tp::PendingOperation*))); +} + +void ContactManager::PendingRefreshContactInfo::onRefreshInfoFinished(PendingOperation *op) +{ + if (op->isError()) { + warning() << "ContactInfo.RefreshContactInfo failed with" << + op->errorName() << "-" << op->errorMessage(); + setFinishedWithError(op->errorName(), op->errorMessage()); + } else { + debug() << "Got reply to ContactInfo.RefreshContactInfo"; + setFinished(); + } +} + +/** + * \class ContactManager + * \ingroup clientconn + * \headerfile TelepathyQt/contact-manager.h <TelepathyQt/ContactManager> + * + * \brief The ContactManager class is responsible for managing contacts. + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Construct a new ContactManager object. + * + * \param connection The connection owning this ContactManager. + */ +ContactManager::ContactManager(Connection *connection) + : Object(), + mPriv(new Private(this, connection)) +{ +} + +/** + * Class destructor. + */ +ContactManager::~ContactManager() +{ + delete mPriv; +} + +/** + * Return the connection owning this ContactManager. + * + * \return A pointer to the Connection object. + */ +ConnectionPtr ContactManager::connection() const +{ + return ConnectionPtr(mPriv->connection); +} + +/** + * Return the features that are expected to work on contacts on this ContactManager connection. + * + * This method requires Connection::FeatureCore to be ready. + * + * \return The supported features as a set of Feature objects. + */ +Features ContactManager::supportedFeatures() const +{ + if (mPriv->supportedFeatures.isEmpty() && + connection()->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS))) { + Features allFeatures = Features() + << Contact::FeatureAlias + << Contact::FeatureAvatarToken + << Contact::FeatureAvatarData + << Contact::FeatureSimplePresence + << Contact::FeatureCapabilities + << Contact::FeatureLocation + << Contact::FeatureInfo + << Contact::FeatureRosterGroups; + QStringList interfaces = connection()->lowlevel()->contactAttributeInterfaces(); + foreach (const Feature &feature, allFeatures) { + if (interfaces.contains(featureToInterface(feature))) { + mPriv->supportedFeatures.insert(feature); + } + } + + debug() << mPriv->supportedFeatures.size() << "contact features supported using" << this; + } + + return mPriv->supportedFeatures; +} + +/** + * Return the progress made in retrieving the contact list. + * + * Change notification is via the stateChanged() signal. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return The contact list state as #ContactListState. + * \sa stateChanged() + */ +ContactListState ContactManager::state() const +{ + return mPriv->roster->state(); +} + +/** + * Return a list of relevant contacts (a reasonable guess as to what should + * be displayed as "the contact list"). + * + * This may include any or all of: contacts whose presence the user receives, + * contacts who are allowed to see the user's presence, contacts stored in + * some persistent contact list on the server, contacts who the user + * has blocked from communicating with them, or contacts who are relevant + * in some other way. + * + * User interfaces displaying a contact list will probably want to filter this + * list and display some suitable subset of it. + * + * On protocols where there is no concept of presence or a centrally-stored + * contact list (like IRC), this method may return an empty list. + * + * Change notification is via the allKnownContactsChanged() signal. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return A set of pointers to the Contact objects. + * \sa allKnownContactsChanged() + */ +Contacts ContactManager::allKnownContacts() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + warning() << "Calling allKnownContacts() before FeatureRoster is ready"; + return Contacts(); + } + + return mPriv->roster->allKnownContacts(); +} + +/** + * Return a list of user-defined contact list groups' names. + * + * Change notification is via the groupAdded(), groupRemoved() and groupRenamed() signals. + * + * This method requires Connection::FeatureRosterGroups to be ready. + * + * \return The list of user-defined contact list groups names. + * \sa groupMembersChanged(), groupAdded(), groupRemoved(), groupRenamed() + */ +QStringList ContactManager::allKnownGroups() const +{ + if (!connection()->isReady(Connection::FeatureRosterGroups)) { + return QStringList(); + } + + return mPriv->roster->allKnownGroups(); +} + +/** + * Attempt to add an user-defined contact list group named \a group. + * + * On some protocols (e.g. XMPP) empty groups are not represented on the server, + * so disconnecting from the server and reconnecting might cause empty groups to + * vanish. + * + * The returned pending operation will finish successfully if the group already + * exists. + * + * Change notification is via the groupAdded() signal. + * + * This method requires Connection::FeatureRosterGroups to be ready. + * + * \param group The group name. + * \return A PendingOperation which will emit PendingOperation::finished + * when an attempt has been made to add an user-defined contact list group. + * \sa allKnownGroups(), groupAdded(), addContactsToGroup() + */ +PendingOperation *ContactManager::addGroup(const QString &group) +{ + if (!connection()->isValid()) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection is invalid"), + connection()); + } else if (!connection()->isReady(Connection::FeatureRosterGroups)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection::FeatureRosterGroups is not ready"), + connection()); + } + + return mPriv->roster->addGroup(group); +} + +/** + * Attempt to remove an user-defined contact list group named \a group. + * + * Change notification is via the groupRemoved() signal. + * + * This method requires Connection::FeatureRosterGroups to be ready. + * + * \param group The group name. + * \return A PendingOperation which will emit PendingOperation::finished() + * when an attempt has been made to remove an user-defined contact list group. + * \sa allKnownGroups(), groupRemoved(), removeContactsFromGroup() + */ +PendingOperation *ContactManager::removeGroup(const QString &group) +{ + if (!connection()->isValid()) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection is invalid"), + connection()); + } else if (!connection()->isReady(Connection::FeatureRosterGroups)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection::FeatureRosterGroups is not ready"), + connection()); + } + + return mPriv->roster->removeGroup(group); +} + +/** + * Return the contacts in the given user-defined contact list group + * named \a group. + * + * Change notification is via the groupMembersChanged() signal. + * + * This method requires Connection::FeatureRosterGroups to be ready. + * + * \param group The group name. + * \return A set of pointers to the Contact objects, or an empty set if the group does not exist. + * \sa allKnownGroups(), groupMembersChanged() + */ +Contacts ContactManager::groupContacts(const QString &group) const +{ + if (!connection()->isReady(Connection::FeatureRosterGroups)) { + return Contacts(); + } + + return mPriv->roster->groupContacts(group); +} + +/** + * Attempt to add the given \a contacts to the user-defined contact list + * group named \a group. + * + * Change notification is via the groupMembersChanged() signal. + * + * This method requires Connection::FeatureRosterGroups to be ready. + * + * \param group The group name. + * \param contacts Contacts to add. + * \return A PendingOperation which will emit PendingOperation::finished() + * when an attempt has been made to add the contacts to the user-defined + * contact list group. + * \sa groupMembersChanged(), groupContacts() + */ +PendingOperation *ContactManager::addContactsToGroup(const QString &group, + const QList<ContactPtr> &contacts) +{ + if (!connection()->isValid()) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection is invalid"), + connection()); + } else if (!connection()->isReady(Connection::FeatureRosterGroups)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection::FeatureRosterGroups is not ready"), + connection()); + } + + return mPriv->roster->addContactsToGroup(group, contacts); +} + +/** + * Attempt to remove the given \a contacts from the user-defined contact list + * group named \a group. + * + * Change notification is via the groupMembersChanged() signal. + * + * This method requires Connection::FeatureRosterGroups to be ready. + * + * \param group The group name. + * \param contacts Contacts to remove. + * \return A PendingOperation which will PendingOperation::finished + * when an attempt has been made to remove the contacts from the user-defined + * contact list group. + * \sa groupMembersChanged(), groupContacts() + */ +PendingOperation *ContactManager::removeContactsFromGroup(const QString &group, + const QList<ContactPtr> &contacts) +{ + if (!connection()->isValid()) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection is invalid"), + connection()); + } else if (!connection()->isReady(Connection::FeatureRosterGroups)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection::FeatureRosterGroups is not ready"), + connection()); + } + + return mPriv->roster->removeContactsFromGroup(group, contacts); +} + +/** + * Return whether subscribing to additional contacts' presence is supported. + * + * In some protocols, the list of contacts whose presence can be seen is + * fixed, so we can't subscribe to the presence of additional contacts. + * + * Notably, in link-local XMPP, you can see the presence of everyone on the + * local network, and trying to add more subscriptions would be meaningless. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return \c true if Contact::requestPresenceSubscription() and + * requestPresenceSubscription() are likely to succeed, \c false otherwise. + * \sa requestPresenceSubscription(), subscriptionRequestHasMessage() + */ +bool ContactManager::canRequestPresenceSubscription() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + return mPriv->roster->canRequestPresenceSubscription(); +} + +/** + * Return whether a message can be sent when subscribing to contacts' + * presence. + * + * If no message will actually be sent, user interfaces should avoid prompting + * the user for a message, and use an empty string for the message argument. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return \c true if the message argument to Contact::requestPresenceSubscription() and + * requestPresenceSubscription() is actually used, \c false otherwise. + * \sa canRemovePresenceSubscription(), requestPresenceSubscription() + */ +bool ContactManager::subscriptionRequestHasMessage() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + return mPriv->roster->subscriptionRequestHasMessage(); +} + +/** + * Attempt to subscribe to the presence of the given contacts. + * + * This operation is sometimes called "adding contacts to the buddy + * list" or "requesting authorization". + * + * On most protocols, the contacts will need to give permission + * before the user will be able to receive their presence: if so, they will + * be in presence state Contact::PresenceStateAsk until they authorize + * or deny the request. + * + * The returned PendingOperation will return successfully when a request to + * subscribe to the contacts' presence has been submitted, or fail if this + * cannot happen. In particular, it does not wait for the contacts to give + * permission for the presence subscription. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \param contacts Contacts whose presence is desired + * \param message A message from the user which is either transmitted to the + * contacts, or ignored, depending on the protocol + * \return A PendingOperation which will PendingOperation::finished() + * when an attempt has been made to subscribe to the contacts' presence. + * \sa canRequestPresenceSubscription(), subscriptionRequestHasMessage() + */ +PendingOperation *ContactManager::requestPresenceSubscription( + const QList<ContactPtr> &contacts, const QString &message) +{ + if (!connection()->isValid()) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection is invalid"), + connection()); + } else if (!connection()->isReady(Connection::FeatureRoster)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection::FeatureRoster is not ready"), + connection()); + } + + return mPriv->roster->requestPresenceSubscription(contacts, message); +} + +/** + * Return whether the user can stop receiving the presence of a contact + * whose presence they have subscribed to. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return \c true if Contact::removePresenceSubscription() and + * removePresenceSubscription() are likely to succeed + * for contacts with subscription state Contact::PresenceStateYes, + * \c false otherwise. + * \sa removePresenceSubscription(), subscriptionRemovalHasMessage() + */ +bool ContactManager::canRemovePresenceSubscription() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + return mPriv->roster->canRemovePresenceSubscription(); +} + +/** + * Return whether a message can be sent when removing an existing subscription + * to the presence of a contact. + * + * If no message will actually be sent, user interfaces should avoid prompting + * the user for a message, and use an empty string for the message argument. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return \c true if the message argument to Contact::removePresenceSubscription() and + * removePresenceSubscription() is actually used, + * for contacts with subscription state Contact::PresenceStateYes, + * \c false otherwise. + * \sa canRemovePresencePublication(), removePresenceSubscription() + */ +bool ContactManager::subscriptionRemovalHasMessage() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + return mPriv->roster->subscriptionRemovalHasMessage(); +} + +/** + * Return whether the user can cancel a request to subscribe to a contact's + * presence before that contact has responded. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return \c true if Contact::removePresenceSubscription() and + * removePresenceSubscription() are likely to succeed + * for contacts with subscription state Contact::PresenceStateAsk, + * \c false otherwise. + * \sa removePresenceSubscription(), subscriptionRescindingHasMessage() + */ +bool ContactManager::canRescindPresenceSubscriptionRequest() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + return mPriv->roster->canRescindPresenceSubscriptionRequest(); +} + +/** + * Return whether a message can be sent when cancelling a request to + * subscribe to the presence of a contact. + * + * If no message will actually be sent, user interfaces should avoid prompting + * the user for a message, and use an empty string for the message argument. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return \c true if the message argument to Contact::removePresenceSubscription() and + * removePresenceSubscription() is actually used, + * for contacts with subscription state Contact::PresenceStateAsk, + * \c false otherwise. + * \sa canRescindPresenceSubscriptionRequest(), removePresenceSubscription() + */ +bool ContactManager::subscriptionRescindingHasMessage() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + return mPriv->roster->subscriptionRescindingHasMessage(); +} + +/** + * Attempt to stop receiving the presence of the given contacts, or cancel + * a request to subscribe to their presence that was previously sent. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \param contacts Contacts whose presence is no longer required + * \message A message from the user which is either transmitted to the + * contacts, or ignored, depending on the protocol + * \return A PendingOperation which will PendingOperation::finished() + * when an attempt has been made to remove any subscription to the contacts' presence. + * \sa canRemovePresenceSubscription(), canRescindPresenceSubscriptionRequest(), + * subscriptionRemovalHasMessage(), subscriptionRescindingHasMessage() + */ +PendingOperation *ContactManager::removePresenceSubscription( + const QList<ContactPtr> &contacts, const QString &message) +{ + if (!connection()->isValid()) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection is invalid"), + connection()); + } else if (!connection()->isReady(Connection::FeatureRoster)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection::FeatureRoster is not ready"), + connection()); + } + + return mPriv->roster->removePresenceSubscription(contacts, message); +} + +/** + * Return true if the publication of the user's presence to contacts can be + * authorized. + * + * This is always true, unless the protocol has no concept of authorizing + * publication (in which case contacts' publication status can never be + * Contact::PresenceStateAsk). + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return \c true if Contact::authorizePresencePublication() and + * authorizePresencePublication() are likely to succeed + * for contacts with subscription state Contact::PresenceStateAsk, + * \c false otherwise. + * \sa publicationAuthorizationHasMessage(), authorizePresencePublication() + */ +bool ContactManager::canAuthorizePresencePublication() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + return mPriv->roster->canAuthorizePresencePublication(); +} + +/** + * Return whether a message can be sent when authorizing a request from a + * contact that the user's presence is published to them. + * + * If no message will actually be sent, user interfaces should avoid prompting + * the user for a message, and use an empty string for the message argument. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return \c true if the message argument to Contact::authorizePresencePublication() and + * authorizePresencePublication() is actually used, + * for contacts with subscription state Contact::PresenceStateAsk, + * \c false otherwise. + * \sa canAuthorizePresencePublication(), authorizePresencePublication() + */ +bool ContactManager::publicationAuthorizationHasMessage() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + return mPriv->roster->publicationAuthorizationHasMessage(); +} + +/** + * If the given contacts have asked the user to publish presence to them, + * grant permission for this publication to take place. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \param contacts Contacts who should be allowed to receive the user's + * presence + * \message A message from the user which is either transmitted to the + * contacts, or ignored, depending on the protocol + * \return A PendingOperation which will emit PendingOperation::fininshed + * when an attempt has been made to authorize publication of the user's presence + * to the contacts. + * \sa canAuthorizePresencePublication(), publicationAuthorizationHasMessage() + */ +PendingOperation *ContactManager::authorizePresencePublication( + const QList<ContactPtr> &contacts, const QString &message) +{ + if (!connection()->isValid()) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection is invalid"), + connection()); + } else if (!connection()->isReady(Connection::FeatureRoster)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection::FeatureRoster is not ready"), + connection()); + } + + return mPriv->roster->authorizePresencePublication(contacts, message); +} + +/** + * Return whether a message can be sent when rejecting a request from a + * contact that the user's presence is published to them. + * + * If no message will actually be sent, user interfaces should avoid prompting + * the user for a message, and use an empty string for the message argument. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return \c true if the message argument to Contact::removePresencePublication() and + * removePresencePublication() is actually used, + * for contacts with subscription state Contact::PresenceStateAsk, + * \c false otherwise. + * \sa canRemovePresencePublication(), removePresencePublication() + */ +bool ContactManager::publicationRejectionHasMessage() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + return mPriv->roster->publicationRejectionHasMessage(); +} + +/** + * Return true if the publication of the user's presence to contacts can be + * removed, even after permission has been given. + * + * (Rejecting requests for presence to be published is always allowed.) + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return \c true if Contact::removePresencePublication() and + * removePresencePublication() are likely to succeed + * for contacts with subscription state Contact::PresenceStateYes, + * \c false otherwise. + * \sa publicationRemovalHasMessage(), removePresencePublication() + */ +bool ContactManager::canRemovePresencePublication() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + return mPriv->roster->canRemovePresencePublication(); +} + +/** + * Return whether a message can be sent when revoking earlier permission + * that the user's presence is published to a contact. + * + * If no message will actually be sent, user interfaces should avoid prompting + * the user for a message, and use an empty string for the message argument. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return \c true if the message argument to Contact::removePresencePublication and + * removePresencePublication() is actually used, + * for contacts with subscription state Contact::PresenceStateYes, + * \c false otherwise. + * \sa canRemovePresencePublication(), removePresencePublication() + */ +bool ContactManager::publicationRemovalHasMessage() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + return mPriv->roster->publicationRemovalHasMessage(); +} + +/** + * If the given contacts have asked the user to publish presence to them, + * deny this request (this should always succeed, unless a network error + * occurs). + * + * If the given contacts already have permission to receive + * the user's presence, attempt to revoke that permission (this might not + * be supported by the protocol - canRemovePresencePublication + * indicates whether it is likely to succeed). + * + * This method requires Connection::FeatureRoster to be ready. + * + * \param contacts Contacts who should no longer be allowed to receive the + * user's presence + * \message A message from the user which is either transmitted to the + * contacts, or ignored, depending on the protocol + * \return A PendingOperation which will emit PendingOperation::finished() + * when an attempt has been made to remove any publication of the user's presence to the + * contacts. + * \sa canRemovePresencePublication(), publicationRejectionHasMessage(), + * publicationRemovalHasMessage() + */ +PendingOperation *ContactManager::removePresencePublication( + const QList<ContactPtr> &contacts, const QString &message) +{ + if (!connection()->isValid()) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection is invalid"), + connection()); + } else if (!connection()->isReady(Connection::FeatureRoster)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection::FeatureRoster is not ready"), + connection()); + } + + return mPriv->roster->removePresencePublication(contacts, message); +} + +/** + * Remove completely contacts from the server. It has the same effect than + * calling removePresencePublication() and removePresenceSubscription(), + * but also remove from 'stored' list if it exists. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \param contacts Contacts who should be removed + * \message A message from the user which is either transmitted to the + * contacts, or ignored, depending on the protocol + * \return A PendingOperation which will emit PendingOperation::finished + * when an attempt has been made to remove any publication of the user's presence to + * the contacts. + */ +PendingOperation *ContactManager::removeContacts( + const QList<ContactPtr> &contacts, const QString &message) +{ + if (!connection()->isValid()) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection is invalid"), + connection()); + } else if (!connection()->isReady(Connection::FeatureRoster)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection::FeatureRoster is not ready"), + connection()); + } + + return mPriv->roster->removeContacts(contacts, message); +} + +/** + * Return whether this protocol has a list of blocked contacts. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return \c true if blockContacts() is likely to succeed, \c false otherwise. + */ +bool ContactManager::canBlockContacts() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + return mPriv->roster->canBlockContacts(); +} + +/** + * Return whether this protocol can report abusive contacts. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return \c true if reporting abuse when blocking contacts is supported, \c false otherwise. + */ +bool ContactManager::canReportAbuse() const +{ + if (!connection()->isReady(Connection::FeatureRoster)) { + return false; + } + + return mPriv->roster->canReportAbuse(); +} + +/** + * \deprecated Use blockContacts(const QList<ContactPtr> &contacts) instead. + */ +PendingOperation *ContactManager::blockContacts( + const QList<ContactPtr> &contacts, bool value) +{ + return mPriv->roster->blockContacts(contacts, value, false); +} + +/** + * Block the given contacts. Blocked contacts cannot send messages + * to the user; depending on the protocol, blocking a contact may + * have other effects. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \param contacts Contacts that should be blocked. + * \return A PendingOperation which will emit PendingOperation::finished() + * when an attempt has been made to take the requested action. + * \sa canBlockContacts(), unblockContacts(), blockContactsAndReportAbuse() + */ +PendingOperation *ContactManager::blockContacts(const QList<ContactPtr> &contacts) +{ + return mPriv->roster->blockContacts(contacts, true, false); +} + +/** + * Block the given contacts and additionally report abusive behaviour + * to the server. + * + * If reporting abusive behaviour is not supported by the protocol, + * this method has the same effect as blockContacts(). + * + * This method requires Connection::FeatureRoster to be ready. + * + * \param contacts Contacts who should be added to the list of blocked contacts. + * \return A PendingOperation which will emit PendingOperation::finished() + * when an attempt has been made to take the requested action. + * \sa canBlockContacts(), canReportAbuse(), blockContacts() + */ +PendingOperation *ContactManager::blockContactsAndReportAbuse( + const QList<ContactPtr> &contacts) +{ + return mPriv->roster->blockContacts(contacts, true, true); +} + +/** + * Unblock the given contacts. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \param contacts Contacts that should be unblocked. + * \return A PendingOperation which will emit PendingOperation::finished() + * when an attempt has been made to take the requested action. + * \sa canBlockContacts(), blockContacts(), blockContactsAndReportAbuse() + */ +PendingOperation *ContactManager::unblockContacts(const QList<ContactPtr> &contacts) +{ + return mPriv->roster->blockContacts(contacts, false, false); +} + +PendingContacts *ContactManager::contactsForHandles(const UIntList &handles, + const Features &features) +{ + QMap<uint, ContactPtr> satisfyingContacts; + QSet<uint> otherContacts; + Features missingFeatures; + + Features realFeatures(features); + realFeatures.unite(connection()->contactFactory()->features()); + // FeatureAvatarData depends on FeatureAvatarToken + if (realFeatures.contains(Contact::FeatureAvatarData) && + !realFeatures.contains(Contact::FeatureAvatarToken)) { + realFeatures.insert(Contact::FeatureAvatarToken); + } + + if (!connection()->isValid()) { + return new PendingContacts(ContactManagerPtr(this), handles, features, Features(), + QStringList(), satisfyingContacts, otherContacts, + QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection is invalid")); + } else if (!connection()->isReady(Connection::FeatureCore)) { + return new PendingContacts(ContactManagerPtr(this), handles, features, Features(), + QStringList(), satisfyingContacts, otherContacts, + QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection::FeatureCore is not ready")); + } + + ConnectionLowlevelPtr connLowlevel = connection()->lowlevel(); + + if (connLowlevel->hasImmortalHandles() && realFeatures.isEmpty()) { + // try to avoid a roundtrip if all handles have an id set and no feature was requested + foreach (uint handle, handles) { + if (connLowlevel->hasContactId(handle)) { + ContactPtr contact = ensureContact(handle, + connLowlevel->contactId(handle), realFeatures); + satisfyingContacts.insert(handle, contact); + } + } + } + + foreach (uint handle, handles) { + ContactPtr contact = lookupContactByHandle(handle); + if (contact) { + if ((realFeatures - contact->requestedFeatures()).isEmpty()) { + // Contact exists and has all the requested features + satisfyingContacts.insert(handle, contact); + } else { + // Contact exists but is missing features + otherContacts.insert(handle); + missingFeatures.unite(realFeatures - contact->requestedFeatures()); + } + } else { + // Contact doesn't exist - we need to get all of the features (same as unite(features)) + missingFeatures = realFeatures; + otherContacts.insert(handle); + } + } + + Features supported = supportedFeatures(); + QSet<QString> interfaces; + foreach (const Feature &feature, missingFeatures) { + ensureTracking(feature); + + if (supported.contains(feature)) { + // Only query interfaces which are reported as supported to not get an error + interfaces.insert(featureToInterface(feature)); + } + } + + PendingContacts *contacts = + new PendingContacts(ContactManagerPtr(this), handles, features, missingFeatures, + interfaces.toList(), satisfyingContacts, otherContacts); + return contacts; +} + +PendingContacts *ContactManager::contactsForHandles(const ReferencedHandles &handles, + const Features &features) +{ + return contactsForHandles(handles.toList(), features); +} + +PendingContacts *ContactManager::contactsForHandles(const HandleIdentifierMap &handles, + const Features &features) +{ + connection()->lowlevel()->injectContactIds(handles); + + return contactsForHandles(handles.keys(), features); +} + +PendingContacts *ContactManager::contactsForIdentifiers(const QStringList &identifiers, + const Features &features) +{ + if (!connection()->isValid()) { + return new PendingContacts(ContactManagerPtr(this), identifiers, features, + QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection is invalid")); + } else if (!connection()->isReady(Connection::FeatureCore)) { + return new PendingContacts(ContactManagerPtr(this), identifiers, features, + QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection::FeatureCore is not ready")); + } + + Features realFeatures(features); + realFeatures.unite(connection()->contactFactory()->features()); + PendingContacts *contacts = new PendingContacts(ContactManagerPtr(this), identifiers, + realFeatures); + return contacts; +} + +PendingContacts *ContactManager::upgradeContacts(const QList<ContactPtr> &contacts, + const Features &features) +{ + if (!connection()->isValid()) { + return new PendingContacts(ContactManagerPtr(this), contacts, features, + QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection is invalid")); + } else if (!connection()->isReady(Connection::FeatureCore)) { + return new PendingContacts(ContactManagerPtr(this), contacts, features, + QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Connection::FeatureCore is not ready")); + } + + return new PendingContacts(ContactManagerPtr(this), contacts, features); +} + +ContactPtr ContactManager::lookupContactByHandle(uint handle) +{ + ContactPtr contact; + + if (mPriv->contacts.contains(handle)) { + contact = ContactPtr(mPriv->contacts.value(handle)); + if (!contact) { + // Dangling weak pointer, remove it + mPriv->contacts.remove(handle); + } + } + + return contact; +} + +/** + * Start a request to retrieve the avatar for the given \a contacts. + * + * Force the request of the avatar data. This method returns directly, emitting + * Contact::avatarTokenChanged() and Contact::avatarDataChanged() signals once the token + * and data are fetched from the server. + * + * This is only useful if the avatar token is unknown; see Contact::isAvatarTokenKnown(). + * It happens in the case of offline XMPP contacts, because the server does not + * send the token for them and an explicit request of the avatar data is needed. + * + * This method requires Contact::FeatureAvatarData to be ready. + * + * \sa Contact::avatarData(), Contact::avatarDataChanged(), + * Contact::avatarToken(), Contact::avatarTokenChanged() + */ +void ContactManager::requestContactAvatars(const QList<ContactPtr> &contacts) +{ + if (contacts.isEmpty()) { + return; + } + + if (!mPriv->requestAvatarsIdle) { + mPriv->requestAvatarsIdle = true; + QTimer::singleShot(0, this, SLOT(doRequestAvatars())); + } + + mPriv->requestAvatarsQueue.unite(contacts.toSet()); +} + +/** + * \deprecated + * + * This was never intended to be public API. + * Use Contact::requestAvatarData() or ContactManager::requestContactAvatars() instead. + */ +void ContactManager::requestContactAvatar(Contact *contact) +{ + requestContactAvatars(QList<ContactPtr>() << ContactPtr(contact)); +} + +/** + * Refresh information for the given contact. + * + * Once the information is retrieved infoFieldsChanged() will be emitted. + * + * This method requires Contact::FeatureInfo to be ready. + * + * \return A PendingOperation, which will emit PendingOperation::finished + * when the call has finished. + * \sa infoFieldsChanged() + */ +PendingOperation *ContactManager::refreshContactInfo(const QList<ContactPtr> &contacts) +{ + if (!mPriv->refreshInfoOp) { + mPriv->refreshInfoOp = new PendingRefreshContactInfo(connection()); + QTimer::singleShot(0, this, SLOT(doRefreshInfo())); + } + + foreach (const ContactPtr &contact, contacts) { + mPriv->refreshInfoOp->addContact(contact.data()); + } + + return mPriv->refreshInfoOp; +} + +void ContactManager::onAliasesChanged(const AliasPairList &aliases) +{ + debug() << "Got AliasesChanged for" << aliases.size() << "contacts"; + + foreach (AliasPair pair, aliases) { + ContactPtr contact = lookupContactByHandle(pair.handle); + + if (contact) { + contact->receiveAlias(pair.alias); + } + } +} + +void ContactManager::doRequestAvatars() +{ + Q_ASSERT(mPriv->requestAvatarsIdle); + QSet<ContactPtr> contacts = mPriv->requestAvatarsQueue; + Q_ASSERT(contacts.size() > 0); + + mPriv->requestAvatarsQueue.clear(); + mPriv->requestAvatarsIdle = false; + + int found = 0; + UIntList notFound; + foreach (const ContactPtr &contact, contacts) { + if (!contact) { + continue; + } + + QString avatarFileName; + QString mimeTypeFileName; + bool success = (contact->isAvatarTokenKnown() && + mPriv->buildAvatarFileName(contact->avatarToken(), false, + avatarFileName, mimeTypeFileName)); + + /* Check if the avatar is already in the cache */ + if (success && QFile::exists(avatarFileName)) { + QFile mimeTypeFile(mimeTypeFileName); + mimeTypeFile.open(QIODevice::ReadOnly); + QString mimeType = QString(QLatin1String(mimeTypeFile.readAll())); + mimeTypeFile.close(); + + found++; + + contact->receiveAvatarData(AvatarData(avatarFileName, mimeType)); + + continue; + } + + notFound << contact->handle()[0]; + } + + if (found > 0) { + debug() << "Avatar(s) found in cache for" << found << "contact(s)"; + } + + if (found == contacts.size()) { + return; + } + + debug() << "Requesting avatar(s) for" << contacts.size() - found << "contact(s)"; + + Client::ConnectionInterfaceAvatarsInterface *avatarsInterface = + connection()->interface<Client::ConnectionInterfaceAvatarsInterface>(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + avatarsInterface->RequestAvatars(notFound), + this); + connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), watcher, + SLOT(deleteLater())); +} + +void ContactManager::onAvatarUpdated(uint handle, const QString &token) +{ + debug() << "Got AvatarUpdate for contact with handle" << handle; + + ContactPtr contact = lookupContactByHandle(handle); + if (contact) { + contact->receiveAvatarToken(token); + } +} + +void ContactManager::onAvatarRetrieved(uint handle, const QString &token, + const QByteArray &data, const QString &mimeType) +{ + QString avatarFileName; + QString mimeTypeFileName; + + debug() << "Got AvatarRetrieved for contact with handle" << handle; + + bool success = mPriv->buildAvatarFileName(token, true, avatarFileName, + mimeTypeFileName); + + if (success) { + debug() << "Write avatar in cache for handle" << handle; + debug() << "Filename:" << avatarFileName; + debug() << "MimeType:" << mimeType; + + QTemporaryFile mimeTypeFile(mimeTypeFileName); + mimeTypeFile.open(); + mimeTypeFile.write(mimeType.toLatin1()); + mimeTypeFile.setAutoRemove(false); + mimeTypeFile.rename(mimeTypeFileName); + + QTemporaryFile avatarFile(avatarFileName); + avatarFile.open(); + avatarFile.write(data); + avatarFile.setAutoRemove(false); + avatarFile.rename(avatarFileName); + } + + ContactPtr contact = lookupContactByHandle(handle); + if (contact) { + contact->setAvatarToken(token); + contact->receiveAvatarData(AvatarData(avatarFileName, mimeType)); + } +} + +void ContactManager::onPresencesChanged(const SimpleContactPresences &presences) +{ + debug() << "Got PresencesChanged for" << presences.size() << "contacts"; + + foreach (uint handle, presences.keys()) { + ContactPtr contact = lookupContactByHandle(handle); + + if (contact) { + contact->receiveSimplePresence(presences[handle]); + } + } +} + +void ContactManager::onCapabilitiesChanged(const ContactCapabilitiesMap &caps) +{ + debug() << "Got ContactCapabilitiesChanged for" << caps.size() << "contacts"; + + foreach (uint handle, caps.keys()) { + ContactPtr contact = lookupContactByHandle(handle); + + if (contact) { + contact->receiveCapabilities(caps[handle]); + } + } +} + +void ContactManager::onLocationUpdated(uint handle, const QVariantMap &location) +{ + debug() << "Got LocationUpdated for contact with handle" << handle; + + ContactPtr contact = lookupContactByHandle(handle); + + if (contact) { + contact->receiveLocation(location); + } +} + +void ContactManager::onContactInfoChanged(uint handle, const Tp::ContactInfoFieldList &info) +{ + debug() << "Got ContactInfoChanged for contact with handle" << handle; + + ContactPtr contact = lookupContactByHandle(handle); + + if (contact) { + contact->receiveInfo(info); + } +} + +void ContactManager::doRefreshInfo() +{ + PendingRefreshContactInfo *op = mPriv->refreshInfoOp; + Q_ASSERT(op); + mPriv->refreshInfoOp = 0; + op->refreshInfo(); +} + +ContactPtr ContactManager::ensureContact(const ReferencedHandles &handle, + const Features &features, const QVariantMap &attributes) +{ + uint bareHandle = handle[0]; + ContactPtr contact = lookupContactByHandle(bareHandle); + + if (!contact) { + contact = connection()->contactFactory()->construct(this, handle, features, attributes); + mPriv->contacts.insert(bareHandle, contact.data()); + } + + contact->augment(features, attributes); + + return contact; +} + +ContactPtr ContactManager::ensureContact(uint bareHandle, const QString &id, + const Features &features) +{ + ContactPtr contact = lookupContactByHandle(bareHandle); + + if (!contact) { + QVariantMap attributes; + attributes.insert(QLatin1String(TELEPATHY_INTERFACE_CONNECTION "/contact-id"), id); + + contact = connection()->contactFactory()->construct(this, + ReferencedHandles(connection(), HandleTypeContact, UIntList() << bareHandle), + features, attributes); + mPriv->contacts.insert(bareHandle, contact.data()); + + // do not call augment here as this is a fake contact + } + + return contact; +} + +QString ContactManager::featureToInterface(const Feature &feature) +{ + if (feature == Contact::FeatureAlias) { + return TP_QT_IFACE_CONNECTION_INTERFACE_ALIASING; + } else if (feature == Contact::FeatureAvatarToken) { + return TP_QT_IFACE_CONNECTION_INTERFACE_AVATARS; + } else if (feature == Contact::FeatureAvatarData) { + return TP_QT_IFACE_CONNECTION_INTERFACE_AVATARS; + } else if (feature == Contact::FeatureSimplePresence) { + return TP_QT_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE; + } else if (feature == Contact::FeatureCapabilities) { + return TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES; + } else if (feature == Contact::FeatureLocation) { + return TP_QT_IFACE_CONNECTION_INTERFACE_LOCATION; + } else if (feature == Contact::FeatureInfo) { + return TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_INFO; + } else if (feature == Contact::FeatureRosterGroups) { + return TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS; + } else { + warning() << "ContactManager doesn't know which interface corresponds to feature" + << feature; + return QString(); + } +} + +void ContactManager::ensureTracking(const Feature &feature) +{ + if (mPriv->tracking[feature]) { + return; + } + + ConnectionPtr conn(connection()); + + if (feature == Contact::FeatureAlias) { + Client::ConnectionInterfaceAliasingInterface *aliasingInterface = + conn->interface<Client::ConnectionInterfaceAliasingInterface>(); + + connect(aliasingInterface, + SIGNAL(AliasesChanged(Tp::AliasPairList)), + SLOT(onAliasesChanged(Tp::AliasPairList))); + } else if (feature == Contact::FeatureAvatarData) { + Client::ConnectionInterfaceAvatarsInterface *avatarsInterface = + conn->interface<Client::ConnectionInterfaceAvatarsInterface>(); + + connect(avatarsInterface, + SIGNAL(AvatarRetrieved(uint,QString,QByteArray,QString)), + SLOT(onAvatarRetrieved(uint,QString,QByteArray,QString))); + } else if (feature == Contact::FeatureAvatarToken) { + Client::ConnectionInterfaceAvatarsInterface *avatarsInterface = + conn->interface<Client::ConnectionInterfaceAvatarsInterface>(); + + connect(avatarsInterface, + SIGNAL(AvatarUpdated(uint,QString)), + SLOT(onAvatarUpdated(uint,QString))); + } else if (feature == Contact::FeatureCapabilities) { + Client::ConnectionInterfaceContactCapabilitiesInterface *contactCapabilitiesInterface = + conn->interface<Client::ConnectionInterfaceContactCapabilitiesInterface>(); + + connect(contactCapabilitiesInterface, + SIGNAL(ContactCapabilitiesChanged(Tp::ContactCapabilitiesMap)), + SLOT(onCapabilitiesChanged(Tp::ContactCapabilitiesMap))); + } else if (feature == Contact::FeatureInfo) { + Client::ConnectionInterfaceContactInfoInterface *contactInfoInterface = + conn->interface<Client::ConnectionInterfaceContactInfoInterface>(); + + connect(contactInfoInterface, + SIGNAL(ContactInfoChanged(uint,Tp::ContactInfoFieldList)), + SLOT(onContactInfoChanged(uint,Tp::ContactInfoFieldList))); + } else if (feature == Contact::FeatureLocation) { + Client::ConnectionInterfaceLocationInterface *locationInterface = + conn->interface<Client::ConnectionInterfaceLocationInterface>(); + + connect(locationInterface, + SIGNAL(LocationUpdated(uint,QVariantMap)), + SLOT(onLocationUpdated(uint,QVariantMap))); + } else if (feature == Contact::FeatureSimplePresence) { + Client::ConnectionInterfaceSimplePresenceInterface *simplePresenceInterface = + conn->interface<Client::ConnectionInterfaceSimplePresenceInterface>(); + + connect(simplePresenceInterface, + SIGNAL(PresencesChanged(Tp::SimpleContactPresences)), + SLOT(onPresencesChanged(Tp::SimpleContactPresences))); + } else if (feature == Contact::FeatureRosterGroups) { + // nothing to do here, but we don't want to warn + ; + } else { + warning() << " Unknown feature" << feature + << "when trying to figure out how to connect change notification!"; + } + + mPriv->tracking[feature] = true; +} + +PendingOperation *ContactManager::introspectRoster() +{ + return mPriv->roster->introspect(); +} + +PendingOperation *ContactManager::introspectRosterGroups() +{ + return mPriv->roster->introspectGroups(); +} + +void ContactManager::resetRoster() +{ + mPriv->roster->reset(); +} + +/** + * \fn void ContactManager::presencePublicationRequested(const Tp::Contacts &contacts) + * + * Emitted whenever some contacts request for presence publication. + * + * \param contacts A set of contacts which requested presence publication. + */ + +/** + * \fn void ContactManager::presencePublicationRequested(const Tp::Contacts &contacts, + * const QString &message) + * + * \deprecated Turned out this didn't make sense at all. There can be multiple contacts, but this + * signal carries just a single message. + * Use presencePublicationRequested(const Tp::Contacts &contacts) instead, + * and extract the messages from the individual Tp::Contact objects. + */ + +/** + * \fn void ContactManager::presencePublicationRequested(const Tp::Contacts &contacts, + * const Tp::Channel::GroupMemberChangeDetails &details) + * + * \deprecated Turned out this didn't make sense at all. There can be multiple contacts, but this + * signal carries just a single details. + * Use presencePublicationRequested(const Tp::Contacts &contacts) instead, + * and extract the details (message) from the individual Tp::Contact objects. + */ + +/** + * \fn void ContactManager::groupAdded(const QString &group) + * + * Emitted when a new contact list group is created. + * + * \param group The group name. + * \sa allKnownGroups() + */ + +/** + * \fn void ContactManager::groupRenamed(const QString &oldGroup, const QString &newGroup) + * + * Emitted when a new contact list group is renamed. + * + * \param oldGroup The old group name. + * \param newGroup The new group name. + * \sa allKnownGroups() + */ + +/** + * \fn void ContactManager::groupRemoved(const QString &group) + * + * Emitted when a contact list group is removed. + * + * \param group The group name. + * \sa allKnownGroups() + */ + +/** + * \fn void ContactManager::groupMembersChanged(const QString &group, + * const Tp::Contacts &groupMembersAdded, + * const Tp::Contacts &groupMembersRemoved, + * const Tp::Channel::GroupMemberChangeDetails &details) + * + * Emitted whenever some contacts got removed or added from + * a group. + * + * \param group The name of the group that changed. + * \param groupMembersAdded A set of contacts which were added to the group \a group. + * \param groupMembersRemoved A set of contacts which were removed from the group \a group. + * \param details The change details. + * \sa groupContacts() + */ + +/** + * \fn void ContactManager::allKnownContactsChanged(const Tp::Contacts &contactsAdded, + * const Tp::Contacts &contactsRemoved, + * const Tp::Channel::GroupMemberChangeDetails &details) + * + * Emitted whenever some contacts got removed or added from + * ContactManager's known contact list. It is useful for monitoring which contacts + * are currently known by ContactManager. + * + * Note that, in some protocols, this signal could stream newly added contacts + * with both presence subscription and publication state set to No. Be sure to watch + * over publication and/or subscription state changes if that is the case. + * + * \param contactsAdded A set of contacts which were added to the known contact list. + * \param contactsRemoved A set of contacts which were removed from the known contact list. + * \param details The change details. + * \sa allKnownContacts() + */ + +void ContactManager::connectNotify(const char *signalName) +{ + if (qstrcmp(signalName, SIGNAL(presencePublicationRequested(Tp::Contacts,Tp::Channel::GroupMemberChangeDetails))) == 0) { + warning() << "Connecting to deprecated signal presencePublicationRequested(Tp::Contacts,Tp::Channel::GroupMemberChangeDetails)"; + } else if (qstrcmp(signalName, SIGNAL(presencePublicationRequested(Tp::Contacts,QString))) == 0) { + warning() << "Connecting to deprecated signal presencePublicationRequested(Tp::Contacts,QString)"; + } +} + +} // Tp diff --git a/TelepathyQt/contact-manager.h b/TelepathyQt/contact-manager.h new file mode 100644 index 00000000..6f4e76cc --- /dev/null +++ b/TelepathyQt/contact-manager.h @@ -0,0 +1,201 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_contact_manager_h_HEADER_GUARD_ +#define _TelepathyQt_contact_manager_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Channel> +#include <TelepathyQt/Contact> +#include <TelepathyQt/Feature> +#include <TelepathyQt/Object> +#include <TelepathyQt/ReferencedHandles> +#include <TelepathyQt/Types> + +#include <QList> +#include <QSet> +#include <QString> +#include <QStringList> +#include <QVariantMap> + +namespace Tp +{ + +class Connection; +class PendingContacts; +class PendingOperation; + +class TP_QT_EXPORT ContactManager : public Object +{ + Q_OBJECT + Q_DISABLE_COPY(ContactManager) + +public: + virtual ~ContactManager(); + + ConnectionPtr connection() const; + + Features supportedFeatures() const; + + ContactListState state() const; + + Contacts allKnownContacts() const; + QStringList allKnownGroups() const; + + PendingOperation *addGroup(const QString &group); + PendingOperation *removeGroup(const QString &group); + + Contacts groupContacts(const QString &group) const; + PendingOperation *addContactsToGroup(const QString &group, + const QList<ContactPtr> &contacts); + PendingOperation *removeContactsFromGroup(const QString &group, + const QList<ContactPtr> &contacts); + + bool canRequestPresenceSubscription() const; + bool subscriptionRequestHasMessage() const; + PendingOperation *requestPresenceSubscription( + const QList<ContactPtr> &contacts, + const QString &message = QString()); + bool canRemovePresenceSubscription() const; + bool subscriptionRemovalHasMessage() const; + bool canRescindPresenceSubscriptionRequest() const; + bool subscriptionRescindingHasMessage() const; + PendingOperation *removePresenceSubscription( + const QList<ContactPtr> &contacts, + const QString &message = QString()); + bool canAuthorizePresencePublication() const; + bool publicationAuthorizationHasMessage() const; + PendingOperation *authorizePresencePublication( + const QList<ContactPtr> &contacts, + const QString &message = QString()); + bool publicationRejectionHasMessage() const; + bool canRemovePresencePublication() const; + bool publicationRemovalHasMessage() const; + PendingOperation *removePresencePublication( + const QList<ContactPtr> &contacts, + const QString &message = QString()); + PendingOperation *removeContacts( + const QList<ContactPtr> &contacts, + const QString &message = QString()); + + bool canBlockContacts() const; + bool canReportAbuse() const; + TP_QT_DEPRECATED PendingOperation *blockContacts( + const QList<ContactPtr> &contacts, bool value); + PendingOperation *blockContacts(const QList<ContactPtr> &contacts); + PendingOperation *blockContactsAndReportAbuse(const QList<ContactPtr> &contacts); + PendingOperation *unblockContacts(const QList<ContactPtr> &contacts); + + PendingContacts *contactsForHandles(const UIntList &handles, + const Features &features = Features()); + PendingContacts *contactsForHandles(const ReferencedHandles &handles, + const Features &features = Features()); + PendingContacts *contactsForHandles(const HandleIdentifierMap &handles, + const Features &features = Features()); + + PendingContacts *contactsForIdentifiers(const QStringList &identifiers, + const Features &features = Features()); + + PendingContacts *upgradeContacts(const QList<ContactPtr> &contacts, + const Features &features); + + ContactPtr lookupContactByHandle(uint handle); + + TP_QT_DEPRECATED void requestContactAvatar(Contact *contact); + void requestContactAvatars(const QList<ContactPtr> &contacts); + + PendingOperation *refreshContactInfo(const QList<ContactPtr> &contact); + +Q_SIGNALS: + void stateChanged(Tp::ContactListState state); + + void presencePublicationRequested(const Tp::Contacts &contacts); + // deprecated - carry redundant data which can be retrieved (meaningfully) from the Contacts + // themselves (note: multiple contacts, but just a single message/details!) + void presencePublicationRequested(const Tp::Contacts &contacts, const QString &message); + void presencePublicationRequested(const Tp::Contacts &contacts, + const Tp::Channel::GroupMemberChangeDetails &details); + + void groupAdded(const QString &group); + void groupRenamed(const QString &oldGroup, const QString &newGroup); + void groupRemoved(const QString &group); + + void groupMembersChanged(const QString &group, + const Tp::Contacts &groupMembersAdded, + const Tp::Contacts &groupMembersRemoved, + const Tp::Channel::GroupMemberChangeDetails &details); + + void allKnownContactsChanged(const Tp::Contacts &contactsAdded, + const Tp::Contacts &contactsRemoved, + const Tp::Channel::GroupMemberChangeDetails &details); + +protected: + // FIXME: (API/ABI break) Remove connectNotify + void connectNotify(const char *); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onAliasesChanged(const Tp::AliasPairList &); + TP_QT_NO_EXPORT void doRequestAvatars(); + TP_QT_NO_EXPORT void onAvatarUpdated(uint, const QString &); + TP_QT_NO_EXPORT void onAvatarRetrieved(uint, const QString &, const QByteArray &, const QString &); + TP_QT_NO_EXPORT void onPresencesChanged(const Tp::SimpleContactPresences &); + TP_QT_NO_EXPORT void onCapabilitiesChanged(const Tp::ContactCapabilitiesMap &); + TP_QT_NO_EXPORT void onLocationUpdated(uint, const QVariantMap &); + TP_QT_NO_EXPORT void onContactInfoChanged(uint, const Tp::ContactInfoFieldList &); + TP_QT_NO_EXPORT void doRefreshInfo(); + +private: + class PendingRefreshContactInfo; + class Roster; + friend class Connection; + friend class PendingContacts; + friend class PendingRefreshContactInfo; + friend class Roster; + + TP_QT_NO_EXPORT ContactManager(Connection *parent); + + TP_QT_NO_EXPORT ContactPtr ensureContact(const ReferencedHandles &handle, + const Features &features, + const QVariantMap &attributes); + TP_QT_NO_EXPORT ContactPtr ensureContact(uint bareHandle, + const QString &id, const Features &features); + + TP_QT_NO_EXPORT static QString featureToInterface(const Feature &feature); + TP_QT_NO_EXPORT void ensureTracking(const Feature &feature); + + TP_QT_NO_EXPORT PendingOperation *introspectRoster(); + TP_QT_NO_EXPORT PendingOperation *introspectRosterGroups(); + TP_QT_NO_EXPORT void resetRoster(); + + TP_QT_NO_EXPORT PendingOperation *refreshContactInfo(Contact *contact); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/contact-messenger.cpp b/TelepathyQt/contact-messenger.cpp new file mode 100644 index 00000000..fdb5608c --- /dev/null +++ b/TelepathyQt/contact-messenger.cpp @@ -0,0 +1,257 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 <TelepathyQt/ContactMessenger> + +#include "TelepathyQt/_gen/contact-messenger.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include "TelepathyQt/future-internal.h" + +#include <TelepathyQt/Account> +#include <TelepathyQt/ChannelDispatcher> +#include <TelepathyQt/ClientRegistrar> +#include <TelepathyQt/MessageContentPartList> +#include <TelepathyQt/PendingSendMessage> +#include <TelepathyQt/SimpleTextObserver> +#include <TelepathyQt/TextChannel> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ContactMessenger::Private +{ + Private(ContactMessenger *parent, const AccountPtr &account, const QString &contactIdentifier) + : parent(parent), + account(account), + contactIdentifier(contactIdentifier), + cdMessagesInterface(0) + { + } + + PendingSendMessage *sendMessage(const Message &message, MessageSendingFlags flags); + + ContactMessenger *parent; + AccountPtr account; + QString contactIdentifier; + SimpleTextObserverPtr observer; + TpFuture::Client::ChannelDispatcherInterfaceMessagesInterface *cdMessagesInterface; +}; + +PendingSendMessage *ContactMessenger::Private::sendMessage(const Message &message, + MessageSendingFlags flags) +{ + if (!cdMessagesInterface) { + cdMessagesInterface = new TpFuture::Client::ChannelDispatcherInterfaceMessagesInterface( + account->dbusConnection(), + TP_QT_CHANNEL_DISPATCHER_BUS_NAME, TP_QT_CHANNEL_DISPATCHER_OBJECT_PATH, parent); + } + + PendingSendMessage *op = new PendingSendMessage(ContactMessengerPtr(parent), message); + + // TODO: is there a way to avoid this? Ideally TpFuture classes should use Tp types. + TpFuture::MessagePartList parts; + foreach (const Tp::MessagePart &part, message.parts()) { + parts << static_cast<QMap<QString, QDBusVariant> >(part); + } + + connect(new QDBusPendingCallWatcher( + cdMessagesInterface->SendMessage(QDBusObjectPath(account->objectPath()), + contactIdentifier, parts, (uint) flags)), + SIGNAL(finished(QDBusPendingCallWatcher*)), + op, + SLOT(onCDMessageSent(QDBusPendingCallWatcher*))); + return op; +} + +/** + * \class ContactMessenger + * \ingroup clientaccount + * \headerfile TelepathyQt/contact-messenger.h <TelepathyQt/ContactMessenger> + * + * \brief The ContactMessenger class provides an easy way to send text messages to a contact + * and also track sent/receive text messages from the same contact. + */ + +/** + * Create a new ContactMessenger object. + * + * \param account The account this messenger is communicating with. + * \param contact The contact this messenger is communicating with. + * \return An ContactMessengerPtr object pointing to the newly created ContactMessenger object, + * or a null ContactMessengerPtr if \a contact is null. + */ +ContactMessengerPtr ContactMessenger::create(const AccountPtr &account, + const ContactPtr &contact) +{ + if (!contact) { + warning() << "Contact used to create a ContactMessenger object must be " + "valid"; + return ContactMessengerPtr(); + } + return ContactMessengerPtr(new ContactMessenger(account, contact->id())); +} + +/** + * Create a new ContactMessenger object. + * + * \param account The account this messenger is communicating with. + * \param contactIdentifier The identifier of the contact this messenger is communicating with. + * \return An ContactMessengerPtr object pointing to the newly created ContactMessenger object, + * or a null ContactMessengerPtr if \a contact is null. + */ +ContactMessengerPtr ContactMessenger::create(const AccountPtr &account, + const QString &contactIdentifier) +{ + if (contactIdentifier.isEmpty()) { + warning() << "Contact identifier used to create a ContactMessenger object must be " + "non-empty"; + return ContactMessengerPtr(); + } + return ContactMessengerPtr(new ContactMessenger(account, contactIdentifier)); +} + +/** + * Construct a new ContactMessenger object. + * + * \param account The account this messenger is communicating with. + * \param contactIdentifier The identifier of the contact this messenger is communicating with. + */ +ContactMessenger::ContactMessenger(const AccountPtr &account, const QString &contactIdentifier) + : mPriv(new Private(this, account, contactIdentifier)) +{ + mPriv->observer = SimpleTextObserver::create(account, contactIdentifier); + connect(mPriv->observer.data(), + SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString,Tp::TextChannelPtr)), + SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString,Tp::TextChannelPtr))); + connect(mPriv->observer.data(), + SIGNAL(messageReceived(Tp::ReceivedMessage,Tp::TextChannelPtr)), + SIGNAL(messageReceived(Tp::ReceivedMessage,Tp::TextChannelPtr))); +} + +/** + * Class destructor. + */ +ContactMessenger::~ContactMessenger() +{ + delete mPriv; +} + +/** + * Return the account this messenger is communicating with. + * + * \return A pointer to the Account object. + */ +AccountPtr ContactMessenger::account() const +{ + return mPriv->account; +} + +/** + * Return the identifier of the contact this messenger is communicating with. + * + * \return The identifier of the contact. + */ +QString ContactMessenger::contactIdentifier() const +{ + return mPriv->contactIdentifier; +} + +/** + * Return the list of text chats currently being observed. + * + * \return A list of pointers to TextChannel objects. + */ +QList<TextChannelPtr> ContactMessenger::textChats() const +{ + return mPriv->observer->textChats(); +} + +/** + * Send a message to the contact identified by contactIdentifier() using account(). + * + * Note that the return from this method isn't ordered in any sane way, meaning that + * messageSent() can be signalled either before or after the returned PendingSendMessage object + * finishes. + * + * \param text The message text. + * \param type The message type. + * \param flags The message flags. + * \return A PendingSendMessage which will emit PendingSendMessage::finished + * once the reply is received and that can be used to check whether sending the + * message succeeded or not. + */ +PendingSendMessage *ContactMessenger::sendMessage(const QString &text, + ChannelTextMessageType type, + MessageSendingFlags flags) +{ + Message message(type, text); + return mPriv->sendMessage(message, flags); +} + +/** + * Send a message to the contact identified by contactIdentifier() using account(). + * + * Note that the return from this method isn't ordered in any sane way, meaning that + * messageSent() can be signalled either before or after the returned PendingSendMessage object + * finishes. + * + * \param parts The message parts. + * \param flags The message flags. + * \return A PendingSendMessage which will emit PendingSendMessage::finished + * once the reply is received and that can be used to check whether sending the + * message succeeded or not. + */ +PendingSendMessage *ContactMessenger::sendMessage(const MessageContentPartList &parts, + MessageSendingFlags flags) +{ + Message message(parts.bareParts()); + return mPriv->sendMessage(message, flags); +} + +/** + * \fn void ContactMessenger::messageSent(const Tp::Message &message, + * Tp::MessageSendingFlags flags, const QString &sentMessageToken, + * const Tp::TextChannelPtr &channel); + * + * Emitted whenever a text message on account() is sent to the contact + * identified by contactIdentifier(). + * + * \param message The message sent. + * \param flags The flags of the message that was sent. + * \param sentMessageToken The token of the message that was sent. + * \param channel The channel from which the message was sent. + */ + +/** + * \fn void ContactMessenger::messageReceived(const Tp::ReceivedMessage &message, + * const Tp::TextChannelPtr &channel); + * + * Emitted whenever a text message on account() is received from the contact + * identified by contactIdentifier(). + * + * \param message The message received. + * \param channel The channel from which the message was received. + */ + +} // Tp diff --git a/TelepathyQt/contact-messenger.h b/TelepathyQt/contact-messenger.h new file mode 100644 index 00000000..41b93b87 --- /dev/null +++ b/TelepathyQt/contact-messenger.h @@ -0,0 +1,78 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +#ifndef _TelepathyQt_contact_messenger_h_HEADER_GUARD_ +#define _TelepathyQt_contact_messenger_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Message> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class PendingSendMessage; +class MessageContentPartList; + +class TP_QT_EXPORT ContactMessenger : public QObject, public RefCounted +{ + Q_OBJECT + Q_DISABLE_COPY(ContactMessenger) + +public: + static ContactMessengerPtr create(const AccountPtr &account, const ContactPtr &contact); + static ContactMessengerPtr create(const AccountPtr &account, const QString &contactIdentifier); + + virtual ~ContactMessenger(); + + AccountPtr account() const; + QString contactIdentifier() const; + + QList<TextChannelPtr> textChats() const; + + PendingSendMessage *sendMessage(const QString &text, + ChannelTextMessageType type = ChannelTextMessageTypeNormal, + MessageSendingFlags flags = 0); + PendingSendMessage *sendMessage(const MessageContentPartList &parts, + MessageSendingFlags flags = 0); + +Q_SIGNALS: + void messageSent(const Tp::Message &message, Tp::MessageSendingFlags flags, + const QString &sentMessageToken, const Tp::TextChannelPtr &channel); + void messageReceived(const Tp::ReceivedMessage &message, const Tp::TextChannelPtr &channel); + +private: + TP_QT_NO_EXPORT ContactMessenger(const AccountPtr &account, + const QString &contactIdentifier); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/contact-search-channel-internal.h b/TelepathyQt/contact-search-channel-internal.h new file mode 100644 index 00000000..dee83147 --- /dev/null +++ b/TelepathyQt/contact-search-channel-internal.h @@ -0,0 +1,52 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_contact_search_channel_internal_h_HEADER_GUARD_ +#define _TelepathyQt_contact_search_channel_internal_h_HEADER_GUARD_ + +#include <TelepathyQt/PendingOperation> + +#include <QDBusPendingCall> + +namespace Tp +{ + +class TP_QT_NO_EXPORT ContactSearchChannel::PendingSearch : public PendingOperation +{ + Q_OBJECT + +public: + PendingSearch(const ContactSearchChannelPtr &chan, QDBusPendingCall call); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onSearchStateChanged(Tp::ChannelContactSearchState state, const QString &errorName, + const Tp::ContactSearchChannel::SearchStateChangeDetails &details); + TP_QT_NO_EXPORT void watcherFinished(QDBusPendingCallWatcher*); + +private: + bool mFinished; + QDBusError mError; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/contact-search-channel.cpp b/TelepathyQt/contact-search-channel.cpp new file mode 100644 index 00000000..ece89fae --- /dev/null +++ b/TelepathyQt/contact-search-channel.cpp @@ -0,0 +1,676 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ContactSearchChannel> +#include "TelepathyQt/contact-search-channel-internal.h" + +#include "TelepathyQt/_gen/contact-search-channel.moc.hpp" +#include "TelepathyQt/_gen/contact-search-channel-internal.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Connection> +#include <TelepathyQt/ContactManager> +#include <TelepathyQt/PendingContacts> +#include <TelepathyQt/PendingFailure> +#include <TelepathyQt/Types> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ContactSearchChannel::Private +{ + Private(ContactSearchChannel *parent, + const QVariantMap &immutableProperties); + ~Private(); + + static void introspectMain(Private *self); + + void extractImmutableProperties(const QVariantMap &props); + + void processSignalsQueue(); + void processSearchStateChangeQueue(); + void processSearchResultQueue(); + + struct SearchStateChangeInfo + { + SearchStateChangeInfo(uint state, const QString &errorName, + const Tp::ContactSearchChannel::SearchStateChangeDetails &details) + : state(state), errorName(errorName), details(details) + { + } + + uint state; + QString errorName; + ContactSearchChannel::SearchStateChangeDetails details; + }; + + // Public object + ContactSearchChannel *parent; + + QVariantMap immutableProperties; + + Client::ChannelTypeContactSearchInterface *contactSearchInterface; + Client::DBus::PropertiesInterface *properties; + + ReadinessHelper *readinessHelper; + + // Introspection + uint searchState; + uint limit; + QStringList availableSearchKeys; + QString server; + + QQueue<void (Private::*)()> signalsQueue; + QQueue<SearchStateChangeInfo> searchStateChangeQueue; + QQueue<ContactSearchResultMap> searchResultQueue; + bool processingSignalsQueue; +}; + +ContactSearchChannel::Private::Private(ContactSearchChannel *parent, + const QVariantMap &immutableProperties) + : parent(parent), + immutableProperties(immutableProperties), + contactSearchInterface(parent->interface<Client::ChannelTypeContactSearchInterface>()), + properties(parent->interface<Client::DBus::PropertiesInterface>()), + readinessHelper(parent->readinessHelper()), + searchState(ChannelContactSearchStateNotStarted), + limit(0), + processingSignalsQueue(false) +{ + ReadinessHelper::Introspectables introspectables; + + ReadinessHelper::Introspectable introspectableCore( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << Channel::FeatureCore, // dependsOnFeatures (core) + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectMain, + this); + introspectables[FeatureCore] = introspectableCore; + + readinessHelper->addIntrospectables(introspectables); +} + +ContactSearchChannel::Private::~Private() +{ +} + +void ContactSearchChannel::Private::introspectMain(ContactSearchChannel::Private *self) +{ + /* we need to at least introspect SearchState here as it's not immutable */ + self->parent->connect(self->contactSearchInterface, + SIGNAL(SearchStateChanged(uint,QString,QVariantMap)), + SLOT(onSearchStateChanged(uint,QString,QVariantMap))); + self->parent->connect(self->contactSearchInterface, + SIGNAL(SearchResultReceived(Tp::ContactSearchResultMap)), + SLOT(onSearchResultReceived(Tp::ContactSearchResultMap))); + + QVariantMap props; + bool needIntrospectMainProps = false; + const unsigned numNames = 3; + const static QString names[numNames] = { + QLatin1String("Limit"), + QLatin1String("AvailableSearchKeys"), + QLatin1String("Server") + }; + const static QString qualifiedNames[numNames] = { + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Limit"), + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".AvailableSearchKeys"), + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Server") + }; + for (unsigned i = 0; i < numNames; ++i) { + const QString &qualified = qualifiedNames[i]; + if (!self->immutableProperties.contains(qualified)) { + needIntrospectMainProps = true; + break; + } + props.insert(names[i], self->immutableProperties.value(qualified)); + } + + if (needIntrospectMainProps) { + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher( + self->properties->GetAll( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_SEARCH)), + self->parent); + self->parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotProperties(QDBusPendingCallWatcher*))); + } else { + self->extractImmutableProperties(props); + + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher( + self->properties->Get( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_CONTACT_SEARCH), + QLatin1String("SearchState")), + self->parent); + self->parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotSearchState(QDBusPendingCallWatcher*))); + } +} + +void ContactSearchChannel::Private::extractImmutableProperties(const QVariantMap &props) +{ + limit = qdbus_cast<uint>(props[QLatin1String("Limit")]); + availableSearchKeys = qdbus_cast<QStringList>(props[QLatin1String("AvailableSearchKeys")]); + server = qdbus_cast<QString>(props[QLatin1String("Server")]); +} + +void ContactSearchChannel::Private::processSignalsQueue() +{ + if (processingSignalsQueue || signalsQueue.isEmpty()) { + return; + } + + processingSignalsQueue = true; + (this->*(signalsQueue.dequeue()))(); +} + +void ContactSearchChannel::Private::processSearchStateChangeQueue() +{ + const SearchStateChangeInfo &info = searchStateChangeQueue.dequeue(); + + searchState = info.state; + emit parent->searchStateChanged( + static_cast<ChannelContactSearchState>(info.state), info.errorName, + SearchStateChangeDetails(info.details)); + + processingSignalsQueue = false; + processSignalsQueue(); +} + +void ContactSearchChannel::Private::processSearchResultQueue() +{ + const ContactSearchResultMap &result = searchResultQueue.first(); + if (!result.isEmpty()) { + ContactManagerPtr manager = parent->connection()->contactManager(); + PendingContacts *pendingContacts = manager->contactsForIdentifiers( + result.keys()); + parent->connect(pendingContacts, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(gotSearchResultContacts(Tp::PendingOperation*))); + } else { + searchResultQueue.dequeue(); + + emit parent->searchResultReceived(SearchResult()); + + processingSignalsQueue = false; + processSignalsQueue(); + } +} + +struct TP_QT_NO_EXPORT ContactSearchChannel::SearchStateChangeDetails::Private : public QSharedData +{ + Private(const QVariantMap &details) + : details(details) {} + + QVariantMap details; +}; + +ContactSearchChannel::SearchStateChangeDetails::SearchStateChangeDetails(const QVariantMap &details) + : mPriv(new Private(details)) +{ +} + +ContactSearchChannel::SearchStateChangeDetails::SearchStateChangeDetails() +{ +} + +ContactSearchChannel::SearchStateChangeDetails::SearchStateChangeDetails( + const ContactSearchChannel::SearchStateChangeDetails &other) + : mPriv(other.mPriv) +{ +} + +ContactSearchChannel::SearchStateChangeDetails::~SearchStateChangeDetails() +{ +} + +ContactSearchChannel::SearchStateChangeDetails &ContactSearchChannel::SearchStateChangeDetails::operator=( + const ContactSearchChannel::SearchStateChangeDetails &other) +{ + this->mPriv = other.mPriv; + return *this; +} + +QVariantMap ContactSearchChannel::SearchStateChangeDetails::allDetails() const +{ + return isValid() ? mPriv->details : QVariantMap(); +} + +ContactSearchChannel::PendingSearch::PendingSearch(const ContactSearchChannelPtr &channel, + QDBusPendingCall call) + : PendingOperation(channel), + mFinished(false) +{ + connect(channel.data(), + SIGNAL(searchStateChanged(Tp::ChannelContactSearchState, const QString &, + const Tp::ContactSearchChannel::SearchStateChangeDetails &)), + SLOT(onSearchStateChanged(Tp::ChannelContactSearchState, const QString &, + const Tp::ContactSearchChannel::SearchStateChangeDetails &))); + connect(new QDBusPendingCallWatcher(call), + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(watcherFinished(QDBusPendingCallWatcher*))); +} + +void ContactSearchChannel::PendingSearch::onSearchStateChanged( + Tp::ChannelContactSearchState state, const QString &errorName, + const Tp::ContactSearchChannel::SearchStateChangeDetails &details) +{ + if (state != ChannelContactSearchStateNotStarted) { + if (mFinished) { + if (mError.isValid()) { + setFinishedWithError(mError); + } else { + setFinished(); + } + } + mFinished = true; + } +} + +void ContactSearchChannel::PendingSearch::watcherFinished(QDBusPendingCallWatcher *watcher) +{ + if (watcher->isError()) { + if (mFinished) { + setFinishedWithError(watcher->error()); + } else { + mError = watcher->error(); + } + } else { + if (mFinished) { + setFinished(); + } + } + mFinished = true; + + watcher->deleteLater(); +} + +/** + * \class ContactSearchChannel + * \ingroup clientchannel + * \headerfile TelepathyQt/contact-search-channel.h <TelepathyQt/ContactSearchChannel> + * + * \brief The ContactSearchChannel class represents a Telepathy channel of type + * ContactSearch. + */ + +/** + * \class ContactSearchChannel::SearchStateChangeDetails + * \ingroup wrappers + * \headerfile TelepathyQt/contact-search-channel.h <TelepathyQt/ContactSearchChannel> + * + * \brief The ContactSearchChannel::SearchStateChangeDetails class provides + * a wrapper around the details for a search state change. + * + * \sa ContactSearchChannel + */ + +/** + * Feature representing the core that needs to become ready to make the + * ContactSearchChannel object usable. + * + * Note that this feature must be enabled in order to use most + * ContactSearchChannel methods. + * See specific methods documentation for more details. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature ContactSearchChannel::FeatureCore = Feature(QLatin1String(ContactSearchChannel::staticMetaObject.className()), 0); + +/** + * Create a new ContactSearchChannel 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 ContactSearchChannelPtr object pointing to the newly created + * ContactSearchChannel object. + */ +ContactSearchChannelPtr ContactSearchChannel::create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties) +{ + return ContactSearchChannelPtr(new ContactSearchChannel(connection, objectPath, + immutableProperties, ContactSearchChannel::FeatureCore)); +} + +/** + * Construct a new ContactSearchChannel 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, if any. The corresponding introspectable should + * depend on ContactSearchChannel::FeatureCore. + */ +ContactSearchChannel::ContactSearchChannel(const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : Channel(connection, objectPath, immutableProperties, coreFeature), + mPriv(new Private(this, immutableProperties)) +{ +} + +/** + * Class destructor. + */ +ContactSearchChannel::~ContactSearchChannel() +{ + delete mPriv; +} + +/** + * Return the current search state of this channel. + * + * Change notification is via the searchStateChanged() signal. + * + * This method requires ContactSearchChannel::FeatureCore to be ready. + * + * \return The current search state as #ChannelContactSearchState. + * \sa searchStateChanged() + */ +ChannelContactSearchState ContactSearchChannel::searchState() const +{ + return static_cast<ChannelContactSearchState>(mPriv->searchState); +} + +/** + * Return the maximum number of results that should be returned by calling search(), where + * 0 represents no limit. + * + * For example, if the terms passed to search() match Antonius, Bridget and Charles and + * this property is 2, the search service will only return Antonius and Bridget. + * + * This method requires ContactSearchChannel::FeatureCore to be ready. + * + * \return The maximum number of results, or 0 if there is no limit. + * \sa availableSearchKeys(), search() + */ +uint ContactSearchChannel::limit() const +{ + return mPriv->limit; +} + +/** + * Return the set of search keys supported by this channel. + * + * Example values include [""] (for protocols where several address fields are implicitly searched) + * or ["x-n-given", "x-n-family", "nickname", "email"] (for XMPP XEP-0055, without extensibility via + * Data Forms). + * + * This method requires ContactSearchChannel::FeatureCore to be ready. + * + * \return The supported search keys. + * \sa limit(), search() + */ +QStringList ContactSearchChannel::availableSearchKeys() const +{ + return mPriv->availableSearchKeys; +} + +/** + * Return the DNS name of the server being searched by this channel. + * + * This method requires ContactSearchChannel::FeatureCore to be ready. + * + * \return For protocols which support searching for contacts on multiple servers with different DNS + * names (like XMPP), the DNS name of the server being searched by this channel, e.g. + * "characters.shakespeare.lit". Otherwise, an empty string. + */ +QString ContactSearchChannel::server() const +{ + return mPriv->server; +} + +/** + * Send a request to start a search for contacts on this connection. + * + * This may only be called while the searchState() is #ChannelContactSearchStateNotStarted; + * a valid search request will cause the searchStateChanged() signal to be emitted with the + * state #ChannelContactSearchStateInProgress. + * + * Search results are signalled by searchResultReceived(). + * + * This method requires ContactSearchChannel::FeatureCore to be ready. + * + * This is an overloaded method for search(const ContactSearchMap &searchTerms). + * + * \param searchKey The search key. + * \param searchTerm The search term. + * \return A PendingOperation which will emit PendingOperation::finished + * when the search has started. + * \sa searchState(), searchStateChanged(), searchResultReceived() + */ +PendingOperation *ContactSearchChannel::search(const QString &searchKey, const QString &searchTerm) +{ + ContactSearchMap searchTerms; + searchTerms.insert(searchKey, searchTerm); + return search(searchTerms); +} + +/** + * Send a request to start a search for contacts on this connection. + * + * This may only be called while the searchState() is #ChannelContactSearchStateNotStarted; + * a valid search request will cause the searchStateChanged() signal to be emitted with the + * state #ChannelContactSearchStateInProgress. + * + * Search results are signalled by searchResultReceived(). + * + * This method requires ContactSearchChannel::FeatureCore to be ready. + * + * \param terms The search terms. + * \return A PendingOperation which will emit PendingOperation::finished + * when the search has started. + * \sa searchState(), searchStateChanged(), searchResultReceived() + */ +PendingOperation *ContactSearchChannel::search(const ContactSearchMap &terms) +{ + if (!isReady(FeatureCore)) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + ContactSearchChannelPtr(this)); + } + + if (searchState() != ChannelContactSearchStateNotStarted) { + warning() << "ContactSearchChannel::search called with " + "searchState() != ChannelContactSearchStateNotStarted. Doing nothing"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Search already started"), + ContactSearchChannelPtr(this)); + } + + return new PendingSearch(ContactSearchChannelPtr(this), + mPriv->contactSearchInterface->Search(terms)); +} + +/** + * Request that a search which searchState() is ChannelContactSearchStateMoreAvailable + * move back to state ChannelContactSearchStateInProgress and continue listing up to limit() + * more results. + */ +void ContactSearchChannel::continueSearch() +{ + if (!isReady(FeatureCore)) { + return; + } + + if (searchState() != ChannelContactSearchStateMoreAvailable) { + warning() << "ContactSearchChannel::continueSearch called with " + "searchState() != ChannelContactSearchStateMoreAvailable. Doing nothing"; + return; + } + + (void) new PendingVoid(mPriv->contactSearchInterface->More(), + ContactSearchChannelPtr(this)); +} + +/** + * Stop the current search. + * + * This may not be called while the searchState() is #ChannelContactSearchStateNotStarted. + * If called while the searchState() is #ChannelContactSearchStateInProgress, + * searchStateChanged() will be emitted, with the state #ChannelContactSearchStateFailed and + * the error #TP_QT_ERROR_CANCELLED. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + * \sa searchState(), searchStateChanged() + */ +void ContactSearchChannel::stopSearch() +{ + if (!isReady(FeatureCore)) { + return; + } + + if (searchState() != ChannelContactSearchStateInProgress && + searchState() != ChannelContactSearchStateMoreAvailable) { + warning() << "ContactSearchChannel::stopSearch called with " + "searchState() != ChannelContactSearchStateInProgress or " + "ChannelContactSearchStateMoreAvailable. Doing nothing"; + return; + } + + (void) new PendingVoid(mPriv->contactSearchInterface->Stop(), + ContactSearchChannelPtr(this)); +} + +void ContactSearchChannel::gotProperties(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + + if (!reply.isError()) { + QVariantMap props = reply.value(); + mPriv->extractImmutableProperties(props); + + mPriv->searchState = qdbus_cast<uint>(props[QLatin1String("SearchState")]); + + debug() << "Got reply to Properties::GetAll(ContactSearchChannel)"; + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true); + } else { + warning().nospace() << "Properties::GetAll(ContactSearchChannel) failed " + "with " << reply.error().name() << ": " << reply.error().message(); + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, + reply.error()); + } + + watcher->deleteLater(); +} + +void ContactSearchChannel::gotSearchState(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariant> reply = *watcher; + + if (!reply.isError()) { + mPriv->searchState = qdbus_cast<uint>(reply.value()); + + debug() << "Got reply to Properties::Get(SearchState)"; + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true); + } else { + warning().nospace() << "Properties::Get(SearchState) failed " + "with " << reply.error().name() << ": " << reply.error().message(); + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, + reply.error()); + } + + watcher->deleteLater(); +} + +void ContactSearchChannel::onSearchStateChanged(uint state, const QString &error, + const QVariantMap &details) +{ + mPriv->searchStateChangeQueue.enqueue(Private::SearchStateChangeInfo(state, error, details)); + mPriv->signalsQueue.enqueue(&Private::processSearchStateChangeQueue); + mPriv->processSignalsQueue(); +} + +void ContactSearchChannel::onSearchResultReceived(const ContactSearchResultMap &result) +{ + mPriv->searchResultQueue.enqueue(result); + mPriv->signalsQueue.enqueue(&Private::processSearchResultQueue); + mPriv->processSignalsQueue(); +} + +void ContactSearchChannel::gotSearchResultContacts(PendingOperation *op) +{ + PendingContacts *pc = qobject_cast<PendingContacts *>(op); + + const ContactSearchResultMap &result = mPriv->searchResultQueue.dequeue(); + + if (!pc->isValid()) { + warning().nospace() << "Getting search result contacts " + "failed with " << pc->errorName() << ":" << + pc->errorMessage() << ". Ignoring search result"; + mPriv->processingSignalsQueue = false; + mPriv->processSignalsQueue(); + return; + } + + const QList<ContactPtr> &contacts = pc->contacts(); + Q_ASSERT(result.count() == contacts.count()); + + SearchResult ret; + uint i = 0; + for (ContactSearchResultMap::const_iterator it = result.constBegin(); + it != result.constEnd(); + ++it, ++i) { + ret.insert(contacts.at(i), Contact::InfoFields(it.value())); + } + emit searchResultReceived(ret); + + mPriv->processingSignalsQueue = false; + mPriv->processSignalsQueue(); +} + +/** + * \fn void ContactSearchChannel::searchStateChanged(Tp::ChannelContactSearchState state, + * const QString &errorName, + * const Tp::ContactSearchChannel::SearchStateChangeDetails &details) + * + * Emitted when the value of searchState() changes. + * + * \param state The new state. + * \param errorName The name of the error if any. + * \param details The details for the state change. + * \sa searchState() + */ + +/** + * \fn void ContactSearchChannel::searchResultReceived( + * const Tp::ContactSearchChannel::SearchResult &result) + * + * Emitted when a result for a search is received. It can be emitted multiple times + * until the searchState() goes to #ChannelContactSearchStateCompleted or + * #ChannelContactSearchStateFailed. + * + * \param result The search result. + * \sa searchState() + */ + +} // Tp diff --git a/TelepathyQt/contact-search-channel.h b/TelepathyQt/contact-search-channel.h new file mode 100644 index 00000000..29c8424c --- /dev/null +++ b/TelepathyQt/contact-search-channel.h @@ -0,0 +1,114 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_contact_search_channel_h_HEADER_GUARD_ +#define _TelepathyQt_contact_search_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Channel> +#include <TelepathyQt/Contact> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class TP_QT_EXPORT ContactSearchChannel : public Channel +{ + Q_OBJECT + Q_DISABLE_COPY(ContactSearchChannel) + +public: + static const Feature FeatureCore; + + class SearchStateChangeDetails + { + public: + SearchStateChangeDetails(); + SearchStateChangeDetails(const SearchStateChangeDetails &other); + ~SearchStateChangeDetails(); + + bool isValid() const { return mPriv.constData() != 0; } + + SearchStateChangeDetails &operator=(const SearchStateChangeDetails &other); + + bool hasDebugMessage() const { return allDetails().contains(QLatin1String("debug-message")); } + QString debugMessage() const { return qdbus_cast<QString>(allDetails().value(QLatin1String("debug-message"))); } + + QVariantMap allDetails() const; + + private: + friend class ContactSearchChannel; + + TP_QT_NO_EXPORT SearchStateChangeDetails(const QVariantMap &details); + + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; + }; + + typedef QHash<ContactPtr, Contact::InfoFields> SearchResult; + + static ContactSearchChannelPtr create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties); + + virtual ~ContactSearchChannel(); + + ChannelContactSearchState searchState() const; + uint limit() const; + QStringList availableSearchKeys() const; + QString server() const; + + PendingOperation *search(const QString &searchKey, const QString &searchTerm); + PendingOperation *search(const ContactSearchMap &searchTerms); + void continueSearch(); + void stopSearch(); + +Q_SIGNALS: + void searchStateChanged(Tp::ChannelContactSearchState state, const QString &errorName, + const Tp::ContactSearchChannel::SearchStateChangeDetails &details); + void searchResultReceived(const Tp::ContactSearchChannel::SearchResult &result); + +protected: + ContactSearchChannel(const ConnectionPtr &connection, const QString &objectPath, + const QVariantMap &immutableProperties, const Feature &coreFeature); + +private Q_SLOTS: + TP_QT_NO_EXPORT void gotProperties(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void gotSearchState(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onSearchStateChanged(uint state, const QString &error, const QVariantMap &details); + TP_QT_NO_EXPORT void onSearchResultReceived(const Tp::ContactSearchResultMap &result); + TP_QT_NO_EXPORT void gotSearchResultContacts(Tp::PendingOperation *op); + +private: + class PendingSearch; + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/contact.cpp b/TelepathyQt/contact.cpp new file mode 100644 index 00000000..64e6601d --- /dev/null +++ b/TelepathyQt/contact.cpp @@ -0,0 +1,1352 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/Contact> + +#include "TelepathyQt/_gen/contact.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/AvatarData> +#include <TelepathyQt/Connection> +#include <TelepathyQt/ConnectionCapabilities> +#include <TelepathyQt/Constants> +#include <TelepathyQt/ContactCapabilities> +#include <TelepathyQt/ContactManager> +#include <TelepathyQt/LocationInfo> +#include <TelepathyQt/PendingContactInfo> +#include <TelepathyQt/PendingVoid> +#include <TelepathyQt/Presence> +#include <TelepathyQt/ReferencedHandles> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT Contact::Private +{ + Private(Contact *parent, ContactManager *manager, + const ReferencedHandles &handle) + : parent(parent), + manager(manager), + handle(handle), + caps(manager->supportedFeatures().contains(Contact::FeatureCapabilities) ? + ContactCapabilities(true) : ContactCapabilities( + manager->connection()->capabilities().allClassSpecs(), false)), + isContactInfoKnown(false), isAvatarTokenKnown(false), + subscriptionState(SubscriptionStateUnknown), + publishState(SubscriptionStateUnknown), + blocked(false) + { + } + + void updateAvatarData(); + + Contact *parent; + + QWeakPointer<ContactManager> manager; + ReferencedHandles handle; + QString id; + + Features requestedFeatures; + Features actualFeatures; + + QString alias; + Presence presence; + ContactCapabilities caps; + LocationInfo location; + + bool isContactInfoKnown; + InfoFields info; + + bool isAvatarTokenKnown; + QString avatarToken; + AvatarData avatarData; + + SubscriptionState subscriptionState; + SubscriptionState publishState; + QString publishStateMessage; + bool blocked; + + QSet<QString> groups; +}; + +void Contact::Private::updateAvatarData() +{ + /* If token is NULL, it means that CM doesn't know the token. In that case we + * have to request the avatar data to get the token. This happens with XMPP + * for offline contacts. We don't want to bypass the avatar cache, so we won't + * update avatar. */ + if (avatarToken.isNull()) { + return; + } + + /* If token is empty (""), it means the contact has no avatar. */ + if (avatarToken.isEmpty()) { + debug() << "Contact" << parent->id() << "has no avatar"; + avatarData = AvatarData(); + emit parent->avatarDataChanged(avatarData); + return; + } + + parent->manager()->requestContactAvatars(QList<ContactPtr>() << ContactPtr(parent)); +} + +struct TP_QT_NO_EXPORT Contact::InfoFields::Private : public QSharedData +{ + Private(const ContactInfoFieldList &allFields) + : allFields(allFields) {} + + ContactInfoFieldList allFields; +}; + +/** + * \class Contact::InfoFields + * \ingroup clientconn + * \headerfile TelepathyQt/contact.h <TelepathyQt/Contact> + * + * \brief The Contact::InfoFields class represents the information of a + * Telepathy contact. + */ + +/** + * Construct a info fields instance with the given fields. The instance will indicate that + * it is valid. + */ +Contact::InfoFields::InfoFields(const ContactInfoFieldList &allFields) + : mPriv(new Private(allFields)) +{ +} + +/** + * Constructs a new invalid InfoFields instance. + */ +Contact::InfoFields::InfoFields() +{ +} + +/** + * Copy constructor. + */ +Contact::InfoFields::InfoFields(const Contact::InfoFields &other) + : mPriv(other.mPriv) +{ +} + +/** + * Class destructor. + */ +Contact::InfoFields::~InfoFields() +{ +} + +/** + * Assignment operator. + */ +Contact::InfoFields &Contact::InfoFields::operator=(const Contact::InfoFields &other) +{ + this->mPriv = other.mPriv; + return *this; +} + +/** + * Return a list containing all fields whose name are \a name. + * + * \param name The name used to match the fields. + * \return A list of ContactInfoField objects. + */ +ContactInfoFieldList Contact::InfoFields::fields(const QString &name) const +{ + if (!isValid()) { + return ContactInfoFieldList(); + } + + ContactInfoFieldList ret; + foreach (const ContactInfoField &field, mPriv->allFields) { + if (field.fieldName == name) { + ret.append(field); + } + } + return ret; +} + +/** + * Return a list containing all fields describing the contact information. + * + * \return The contact information as a list of ContactInfoField objects. + */ +ContactInfoFieldList Contact::InfoFields::allFields() const +{ + return isValid() ? mPriv->allFields : ContactInfoFieldList(); +} + +/** + * \class Contact + * \ingroup clientconn + * \headerfile TelepathyQt/contact.h <TelepathyQt/Contact> + * + * \brief The Contact class represents a Telepathy contact. + * + * The accessor functions on this object (id(), alias(), 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 contact avatar token, it is necessary to + * enable the feature Contact::FeatureAvatarToken. + * See the individual methods descriptions for more details. + * + * Contact features can be enabled by constructing a ContactFactory 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 ContactManager::upgradeContacts() with the + * additional features, and waiting for the resulting PendingOperation to finish. + * + * As an addition to accessors, signals are emitted to indicate that properties have + * changed, for example aliasChanged(), avatarTokenChanged(), etc. + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Feature used in order to access contact alias info. + * + * See alias specific methods' documentation for more details. + * + * \sa alias(), aliasChanged() + */ +const Feature Contact::FeatureAlias = Feature(QLatin1String(Contact::staticMetaObject.className()), 0, false); + +/** + * Feature used in order to access contact avatar data info. + * + * Enabling this feature will also enable FeatureAvatarToken. + * + * See avatar data specific methods' documentation for more details. + * + * \sa avatarData(), avatarDataChanged() + */ +const Feature Contact::FeatureAvatarData = Feature(QLatin1String(Contact::staticMetaObject.className()), 1, false); + +/** + * Feature used in order to access contact avatar token info. + * + * See avatar token specific methods' documentation for more details. + * + * \sa isAvatarTokenKnown(), avatarToken(), avatarTokenChanged() + */ +const Feature Contact::FeatureAvatarToken = Feature(QLatin1String(Contact::staticMetaObject.className()), 2, false); + +/** + * Feature used in order to access contact capabilities info. + * + * See capabilities specific methods' documentation for more details. + * + * \sa capabilities(), capabilitiesChanged() + */ +const Feature Contact::FeatureCapabilities = Feature(QLatin1String(Contact::staticMetaObject.className()), 3, false); + +/** + * Feature used in order to access contact info fields. + * + * See info fields specific methods' documentation for more details. + * + * \sa infoFields(), infoFieldsChanged() + */ +const Feature Contact::FeatureInfo = Feature(QLatin1String(Contact::staticMetaObject.className()), 4, false); + +/** + * Feature used in order to access contact location info. + * + * See location specific methods' documentation for more details. + * + * \sa location(), locationUpdated() + */ +const Feature Contact::FeatureLocation = Feature(QLatin1String(Contact::staticMetaObject.className()), 5, false); + +/** + * Feature used in order to access contact presence info. + * + * See presence specific methods' documentation for more details. + * + * \sa presence(), presenceChanged() + */ +const Feature Contact::FeatureSimplePresence = Feature(QLatin1String(Contact::staticMetaObject.className()), 6, false); + +/** + * Feature used in order to access contact roster groups. + * + * See roster groups specific methods' documentation for more details. + * + * \sa groups(), addedToGroup(), removedFromGroup() + */ +const Feature Contact::FeatureRosterGroups = Feature(QLatin1String(Contact::staticMetaObject.className()), 7, false); + +/** + * Construct a new Contact object. + * + * \param manager ContactManager owning this contact. + * \param handle The contact handle. + * \param requestedFeatures The contact requested features. + * \param attributes The contact attributes. + */ +Contact::Contact(ContactManager *manager, const ReferencedHandles &handle, + const Features &requestedFeatures, const QVariantMap &attributes) + : Object(), + mPriv(new Private(this, manager, handle)) +{ + mPriv->requestedFeatures.unite(requestedFeatures); + mPriv->id = qdbus_cast<QString>(attributes[ + QLatin1String(TELEPATHY_INTERFACE_CONNECTION "/contact-id")]); +} + +/** + * Class destructor. + */ +Contact::~Contact() +{ + debug() << "Contact" << id() << "destroyed"; + delete mPriv; +} + +/** + * Return the contact nanager owning this contact. + * + * \return A pointer to the ContactManager object. + */ +ContactManagerPtr Contact::manager() const +{ + return ContactManagerPtr(mPriv->manager); +} + +/** + * Return the handle of this contact. + * + * \return The handle as a ReferencedHandles object. + */ +ReferencedHandles Contact::handle() const +{ + return mPriv->handle; +} + +/** + * Return the identifier of this contact. + * + * \return The identifier. + */ +QString Contact::id() const +{ + return mPriv->id; +} + +/** + * Return the features requested on this contact. + * + * \return The requested features as a set of Feature objects. + */ +Features Contact::requestedFeatures() const +{ + return mPriv->requestedFeatures; +} + +/** + * Return the features that are actually enabled on this contact. + * + * \return The actual features as a set of Feature objects. + */ +Features Contact::actualFeatures() const +{ + return mPriv->actualFeatures; +} + +/** + * Return the alias of this contact. + * + * Change notification is via the aliasChanged() signal. + * + * This method requires Contact::FeatureAlias to be ready. + * + * \return The alias. + */ +QString Contact::alias() const +{ + if (!mPriv->requestedFeatures.contains(FeatureAlias)) { + warning() << "Contact::alias() used on" << this + << "for which FeatureAlias hasn't been requested - returning id"; + return id(); + } + + return mPriv->alias; +} + +/** + * Return whether the avatar token of this contact is known. + * + * This method requires Contact::FeatureAvatarToken to be ready. + * + * \return \c true if the avatar token is known, \c false otherwise. + * \sa avatarToken() + */ +bool Contact::isAvatarTokenKnown() const +{ + if (!mPriv->requestedFeatures.contains(FeatureAvatarToken)) { + warning() << "Contact::isAvatarTokenKnown() used on" << this + << "for which FeatureAvatarToken hasn't been requested - returning false"; + return false; + } + + return mPriv->isAvatarTokenKnown; +} + +/** + * Return the avatar token for this contact. + * + * Change notification is via the avatarTokenChanged() signal. + * + * This method requires Contact::FeatureAvatarToken to be ready. + * + * \return The avatar token. + * \sa isAvatarTokenKnown(), avatarTokenChanged(), avatarData() + */ +QString Contact::avatarToken() const +{ + if (!mPriv->requestedFeatures.contains(FeatureAvatarToken)) { + warning() << "Contact::avatarToken() used on" << this + << "for which FeatureAvatarToken hasn't been requested - returning \"\""; + return QString(); + } else if (!isAvatarTokenKnown()) { + warning() << "Contact::avatarToken() used on" << this + << "for which the avatar token is not (yet) known - returning \"\""; + return QString(); + } + + return mPriv->avatarToken; +} + +/** + * Return the actual avatar for this contact. + * + * Change notification is via the avatarDataChanged() signal. + * + * This method requires Contact::FeatureAvatarData to be ready. + * + * \return The avatar as an AvatarData object. + * \sa avatarDataChanged(), avatarToken() + */ +AvatarData Contact::avatarData() const +{ + if (!mPriv->requestedFeatures.contains(FeatureAvatarData)) { + warning() << "Contact::avatarData() used on" << this + << "for which FeatureAvatarData hasn't been requested - returning \"\""; + return AvatarData(); + } + + return mPriv->avatarData; +} + +/** + * Start a request to retrieve the avatar for this contact. + * + * Force the request of the avatar data. This method returns directly, emitting + * avatarTokenChanged() and avatarDataChanged() signals once the token and data are + * fetched from the server. + * + * This is only useful if the avatar token is unknown; see isAvatarTokenKnown(). + * It happens in the case of offline XMPP contacts, because the server does not + * send the token for them and an explicit request of the avatar data is needed. + * + * This method requires Contact::FeatureAvatarData to be ready. + * + * \sa avatarData(), avatarDataChanged(), avatarToken(), avatarTokenChanged() + */ +void Contact::requestAvatarData() +{ + if (!mPriv->requestedFeatures.contains(FeatureAvatarData)) { + warning() << "Contact::requestAvatarData() used on" << this + << "for which FeatureAvatarData hasn't been requested - returning \"\""; + return; + } + + return manager()->requestContactAvatars(QList<ContactPtr>() << ContactPtr(this)); +} + +/** + * Return the actual presence of this contact. + * + * Change notification is via the presenceChanged() signal. + * + * This method requires Contact::FeatureSimplePresence to be ready. + * + * \return The presence as a Presence object. + */ +Presence Contact::presence() const +{ + if (!mPriv->requestedFeatures.contains(FeatureSimplePresence)) { + warning() << "Contact::presence() used on" << this + << "for which FeatureSimplePresence hasn't been requested - returning Unknown"; + return Presence(); + } + + return mPriv->presence; +} + +/** + * Return the capabilities for this contact. + * + * User interfaces can use this information to show or hide UI components. + * + * If ContactManager::supportedFeatures() contains Contact::FeatureCapabilities, + * the returned object will be a ContactCapabilities object, where + * CapabilitiesBase::isSpecificToContact() will be \c true; if that feature + * isn't present, this returned object is the subset of + * Contact::manager()::connection()::capabilities() + * and CapabilitiesBase::isSpecificToContact() will be \c false. + * + * Change notification is via the capabilitiesChanged() signal. + * + * This method requires Contact::FeatureCapabilities to be ready. + * + * @return An object representing the contact capabilities. + */ +ContactCapabilities Contact::capabilities() const +{ + if (!mPriv->requestedFeatures.contains(FeatureCapabilities)) { + warning() << "Contact::capabilities() used on" << this + << "for which FeatureCapabilities hasn't been requested - returning 0"; + return ContactCapabilities(false); + } + + return mPriv->caps; +} + +/** + * Return the location for this contact. + * + * Change notification is via the locationUpdated() signal. + * + * This method requires Contact::FeatureLocation to be ready. + * + * \return The contact location as a LocationInfo object. + */ +LocationInfo Contact::location() const +{ + if (!mPriv->requestedFeatures.contains(FeatureLocation)) { + warning() << "Contact::location() used on" << this + << "for which FeatureLocation hasn't been requested - returning 0"; + return LocationInfo(); + } + + return mPriv->location; +} + +/** + * Return whether the info card for this contact has been received. + * + * With some protocols (notably XMPP) information is not pushed from the server + * and must be requested explicitely using refreshInfo() or requestInfo(). This + * method can be used to know if the information is received from the server + * or if an explicit request is needed. + * + * This method requires Contacat::FeatureInfo to be ready. + * + * \return \c true if the information is known; \c false otherwise. + */ +bool Contact::isContactInfoKnown() const +{ + if (!mPriv->requestedFeatures.contains(FeatureInfo)) { + warning() << "Contact::isContactInfoKnown() used on" << this + << "for which FeatureInfo hasn't been requested - returning false"; + return false; + } + + return mPriv->isContactInfoKnown; +} + +/** + * Return the information for this contact. + * + * Note that this method only return cached information. In order to refresh the + * information use refreshInfo(). + * + * Change notification is via the infoFieldsChanged() signal. + * + * This method requires Contact::FeatureInfo to be ready. + * + * \return The contact info as a Contact::InfoFields object. + */ +Contact::InfoFields Contact::infoFields() const +{ + if (!mPriv->requestedFeatures.contains(FeatureInfo)) { + warning() << "Contact::infoFields() used on" << this + << "for which FeatureInfo hasn't been requested - returning empty " + "InfoFields"; + return InfoFields(); + } + + return mPriv->info; +} + +/** + * Refresh information for the given contact. + * + * Once the information is retrieved infoFieldsChanged() will be emitted. + * + * This method requires Contact::FeatureInfo to be ready. + * + * \return A PendingOperation, which will emit PendingOperation::finished + * when the call has finished. + * \sa infoFieldsChanged() + */ +PendingOperation *Contact::refreshInfo() +{ + ConnectionPtr conn = manager()->connection(); + if (!mPriv->requestedFeatures.contains(FeatureInfo)) { + warning() << "Contact::refreshInfo() used on" << this + << "for which FeatureInfo hasn't been requested - failing"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("FeatureInfo needs to be ready in order to " + "use this method"), + ContactPtr(this)); + } + + return manager()->refreshContactInfo(QList<ContactPtr>() << ContactPtr(this)); +} + +/** + * Start a request to retrieve the information for this contact. + * + * This method is useful for UIs that don't care about notification of changes + * in the contact information but want to show the contact information + * (e.g. right-click on a contact and show the contact info). + * + * \return A PendingContactInfo, which will emit PendingContactInfo::finished + * when the information has been retrieved or an error occurred. + */ +PendingContactInfo *Contact::requestInfo() +{ + return new PendingContactInfo(ContactPtr(this)); +} + +/** + * Return whether the presence subscription state of this contact is known. + * + * \return \c true if the presence subscription state is known, \c false otherwise. + * \sa subscriptionState(), isSubscriptionRejected() + */ +bool Contact::isSubscriptionStateKnown() const +{ + return mPriv->subscriptionState != SubscriptionStateUnknown; +} + +/** + * Return whether a request to see this contact's presence was denied. + * + * \return \c if the a request to see the presence subscription was denied, \c false otherwise. + * \sa isSubscriptionStateKnown(), subscriptionState() + */ +bool Contact::isSubscriptionRejected() const +{ + return mPriv->subscriptionState == SubscriptionStateRemovedRemotely; +} + +/** + * Return the presence subscription state of this contact (i.e. whether the local user can retrieve + * information about this contact's presence). + * + * \return The presence subscription state as Contact::PresenceState. + * \sa isSubscriptionStateKnown(), isSubscriptionRejected() + */ +Contact::PresenceState Contact::subscriptionState() const +{ + return subscriptionStateToPresenceState(mPriv->subscriptionState); +} + +/** + * Return whether the presence publish state of this contact is known. + * + * \return \c true if the presence publish state is known, \c false otherwise. + * \sa publishState(), isPublishCancelled() + */ +bool Contact::isPublishStateKnown() const +{ + return mPriv->publishState != SubscriptionStateUnknown; +} + +/** + * Return whether a request to publish presence information to this contact was cancelled. + * + * \return \c true if a request to publish presence information was cancelled, + * \c false otherwise. + * \sa isPublishStateKnown(), publishState() + */ +bool Contact::isPublishCancelled() const +{ + return mPriv->publishState == SubscriptionStateRemovedRemotely; +} + +/** + * Return the presence publish state of this contact (i.e. whether this contact can retrieve + * information about the local user's presence). + * + * \return The presence publish state as Contact::PresenceState. + * \sa isSubscriptionStateKnown(), isSubscriptionRejected() + */ +Contact::PresenceState Contact::publishState() const +{ + return subscriptionStateToPresenceState(mPriv->publishState); +} + +/** + * If the publishState() is Contact::PresenceStateAsk, return an optional message that was sent + * by the contact asking to receive the local user's presence; omitted if none was given. + * + * \return The message that was sent by the contact asking to receive the local user's presence. + * \sa publishState() + */ +QString Contact::publishStateMessage() const +{ + return mPriv->publishStateMessage; +} + +/** + * Start a request that this contact allow the local user to subscribe to their presence (i.e. that + * this contact's subscribe attribute becomes Contact::PresenceStateYes) + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa subscriptionState(), removePresenceSubscription() + */ +PendingOperation *Contact::requestPresenceSubscription(const QString &message) +{ + return manager()->requestPresenceSubscription(QList<ContactPtr>() << ContactPtr(this), message); +} + +/** + * Start a request for the local user to stop receiving presence from this contact. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa subscriptionState(), requestPresenceSubscription() + */ +PendingOperation *Contact::removePresenceSubscription(const QString &message) +{ + return manager()->removePresenceSubscription(QList<ContactPtr>() << ContactPtr(this), message); +} + +/** + * Start a request to authorize this contact's request to see the local user presence + * (i.e. that this contact publish attribute becomes Contact::PresenceStateYes). + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa publishState(), removePresencePublication() + */ +PendingOperation *Contact::authorizePresencePublication(const QString &message) +{ + return manager()->authorizePresencePublication(QList<ContactPtr>() << ContactPtr(this), message); +} + +/** + * Start a request for the local user to stop sending presence to this contact. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the request has been made. + * \sa publishState(), authorizePresencePublication() + */ +PendingOperation *Contact::removePresencePublication(const QString &message) +{ + return manager()->removePresencePublication(QList<ContactPtr>() << ContactPtr(this), message); +} + +/** + * Return whether this contact is blocked. + * + * Change notification is via the blockStatusChanged() signal. + * + * \return \c true if blocked, \c false otherwise. + * \sa block() + */ +bool Contact::isBlocked() const +{ + return mPriv->blocked; +} + +/** + * \deprecated Use block() instead. + */ +PendingOperation *Contact::block(bool value) +{ + return value ? manager()->blockContacts(QList<ContactPtr>() << ContactPtr(this)) + : manager()->unblockContacts(QList<ContactPtr>() << ContactPtr(this)); +} + +/** + * Block this contact. Blocked contacts cannot send messages to the user; + * depending on the protocol, blocking a contact may have other effects. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when an attempt has been made to take the requested action. + * \sa blockAndReportAbuse(), unblock() + */ +PendingOperation *Contact::block() +{ + return manager()->blockContacts(QList<ContactPtr>() << ContactPtr(this)); +} + +/** + * Block this contact and additionally report abusive behaviour + * to the server. + * + * If reporting abusive behaviour is not supported by the protocol, + * this method has the same effect as block(). + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when an attempt has been made to take the requested action. + * \sa ContactManager::canReportAbuse(), block(), unblock() + */ +PendingOperation *Contact::blockAndReportAbuse() +{ + return manager()->blockContactsAndReportAbuse(QList<ContactPtr>() << ContactPtr(this)); +} + +/** + * Unblock this contact. + * + * This method requires Connection::FeatureRoster to be ready. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when an attempt has been made to take the requested action. + * \sa block(), blockAndReportAbuse() + */ +PendingOperation *Contact::unblock() +{ + return manager()->unblockContacts(QList<ContactPtr>() << ContactPtr(this)); +} + +/** + * Return the names of the user-defined roster groups to which the contact + * belongs. + * + * Change notification is via the addedToGroup() and removedFromGroup() signals. + * + * This method requires Connection::FeatureRosterGroups to be ready. + * + * \return A list of user-defined contact list groups names. + * \sa addToGroup(), removedFromGroup() + */ +QStringList Contact::groups() const +{ + return mPriv->groups.toList(); +} + +/** + * Attempt to add the contact to the user-defined contact list + * group named \a group. + * + * This method requires Connection::FeatureRosterGroups to be ready. + * + * \param group The group name. + * \return A PendingOperation which will emit PendingOperation::finished + * when an attempt has been made to to add the contact to the user-defined contact + * list group. + * \sa groups(), removeFromGroup() + */ +PendingOperation *Contact::addToGroup(const QString &group) +{ + return manager()->addContactsToGroup(group, QList<ContactPtr>() << ContactPtr(this)); +} + +/** + * Attempt to remove the contact from the user-defined contact list + * group named \a group. + * + * This method requires Connection::FeatureRosterGroups to be ready. + * + * \param group The group name. + * \return A PendingOperation which will emit PendingOperation::finished + * when an attempt has been made to to remote the contact to the user-defined contact + * list group. + * \sa groups(), addToGroup() + */ +PendingOperation *Contact::removeFromGroup(const QString &group) +{ + return manager()->removeContactsFromGroup(group, QList<ContactPtr>() << ContactPtr(this)); +} + +void Contact::augment(const Features &requestedFeatures, const QVariantMap &attributes) +{ + mPriv->requestedFeatures.unite(requestedFeatures); + + mPriv->id = qdbus_cast<QString>(attributes[ + QLatin1String(TELEPATHY_INTERFACE_CONNECTION "/contact-id")]); + + if (attributes.contains(TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_LIST + + QLatin1String("/subscribe"))) { + uint subscriptionState = qdbus_cast<uint>(attributes.value( + TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_LIST + QLatin1String("/subscribe"))); + setSubscriptionState((SubscriptionState) subscriptionState); + } + + if (attributes.contains(TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_LIST + + QLatin1String("/publish"))) { + uint publishState = qdbus_cast<uint>(attributes.value( + TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_LIST + QLatin1String("/publish"))); + QString publishRequest = qdbus_cast<QString>(attributes.value( + TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_LIST + QLatin1String("/publish-request"))); + setPublishState((SubscriptionState) publishState, publishRequest); + } + + foreach (const Feature &feature, requestedFeatures) { + QString maybeAlias; + SimplePresence maybePresence; + RequestableChannelClassList maybeCaps; + QVariantMap maybeLocation; + ContactInfoFieldList maybeInfo; + + if (feature == FeatureAlias) { + maybeAlias = qdbus_cast<QString>(attributes.value( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_ALIASING "/alias"))); + + if (!maybeAlias.isEmpty()) { + receiveAlias(maybeAlias); + } else if (mPriv->alias.isEmpty()) { + mPriv->alias = mPriv->id; + } + } else if (feature == FeatureAvatarData) { + if (manager()->supportedFeatures().contains(FeatureAvatarData)) { + mPriv->actualFeatures.insert(FeatureAvatarData); + mPriv->updateAvatarData(); + } + } else if (feature == FeatureAvatarToken) { + if (attributes.contains( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_AVATARS "/token"))) { + receiveAvatarToken(qdbus_cast<QString>(attributes.value( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_AVATARS "/token")))); + } else { + if (manager()->supportedFeatures().contains(FeatureAvatarToken)) { + // AvatarToken being supported but not included in the mapping indicates + // that the avatar token is not known - however, the feature is working fine + mPriv->actualFeatures.insert(FeatureAvatarToken); + } + // In either case, the avatar token can't be known + mPriv->isAvatarTokenKnown = false; + mPriv->avatarToken = QLatin1String(""); + } + } else if (feature == FeatureCapabilities) { + maybeCaps = qdbus_cast<RequestableChannelClassList>(attributes.value( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES "/capabilities"))); + + if (!maybeCaps.isEmpty()) { + receiveCapabilities(maybeCaps); + } else { + if (manager()->supportedFeatures().contains(FeatureCapabilities) && + mPriv->requestedFeatures.contains(FeatureCapabilities)) { + // Capabilities being supported but not updated in the + // mapping indicates that the capabilities is not known - + // however, the feature is working fine. + mPriv->actualFeatures.insert(FeatureCapabilities); + } + } + } else if (feature == FeatureInfo) { + maybeInfo = qdbus_cast<ContactInfoFieldList>(attributes.value( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACT_INFO "/info"))); + + if (!maybeInfo.isEmpty()) { + receiveInfo(maybeInfo); + } else { + if (manager()->supportedFeatures().contains(FeatureInfo) && + mPriv->requestedFeatures.contains(FeatureInfo)) { + // Info being supported but not updated in the + // mapping indicates that the info is not known - + // however, the feature is working fine + mPriv->actualFeatures.insert(FeatureInfo); + } + } + } else if (feature == FeatureLocation) { + maybeLocation = qdbus_cast<QVariantMap>(attributes.value( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_LOCATION "/location"))); + + if (!maybeLocation.isEmpty()) { + receiveLocation(maybeLocation); + } else { + if (manager()->supportedFeatures().contains(FeatureLocation) && + mPriv->requestedFeatures.contains(FeatureLocation)) { + // Location being supported but not updated in the + // mapping indicates that the location is not known - + // however, the feature is working fine + mPriv->actualFeatures.insert(FeatureLocation); + } + } + } else if (feature == FeatureSimplePresence) { + maybePresence = qdbus_cast<SimplePresence>(attributes.value( + QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE "/presence"))); + + if (!maybePresence.status.isEmpty()) { + receiveSimplePresence(maybePresence); + } else { + mPriv->presence.setStatus(ConnectionPresenceTypeUnknown, + QLatin1String("unknown"), QLatin1String("")); + } + } else if (feature == FeatureRosterGroups) { + QStringList groups = qdbus_cast<QStringList>(attributes.value( + TP_QT_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS + QLatin1String("/groups"))); + mPriv->groups = groups.toSet(); + } else { + warning() << "Unknown feature" << feature << "encountered when augmenting Contact"; + } + } +} + +void Contact::receiveAlias(const QString &alias) +{ + if (!mPriv->requestedFeatures.contains(FeatureAlias)) { + return; + } + + mPriv->actualFeatures.insert(FeatureAlias); + + if (mPriv->alias != alias) { + mPriv->alias = alias; + emit aliasChanged(alias); + } +} + +void Contact::receiveAvatarToken(const QString &token) +{ + setAvatarToken(token); + + if (mPriv->actualFeatures.contains(FeatureAvatarData)) { + mPriv->updateAvatarData(); + } +} + +void Contact::setAvatarToken(const QString &token) +{ + if (!mPriv->requestedFeatures.contains(FeatureAvatarToken)) { + return; + } + + mPriv->actualFeatures.insert(FeatureAvatarToken); + + if (!mPriv->isAvatarTokenKnown || mPriv->avatarToken != token) { + mPriv->isAvatarTokenKnown = true; + mPriv->avatarToken = token; + emit avatarTokenChanged(mPriv->avatarToken); + } +} + +void Contact::receiveAvatarData(const AvatarData &avatar) +{ + if (mPriv->avatarData.fileName != avatar.fileName) { + mPriv->avatarData = avatar; + emit avatarDataChanged(mPriv->avatarData); + } +} + +void Contact::receiveSimplePresence(const SimplePresence &presence) +{ + if (!mPriv->requestedFeatures.contains(FeatureSimplePresence)) { + return; + } + + mPriv->actualFeatures.insert(FeatureSimplePresence); + + if (mPriv->presence.status() != presence.status || + mPriv->presence.statusMessage() != presence.statusMessage) { + mPriv->presence.setStatus(presence); + emit presenceChanged(mPriv->presence); + } +} + +void Contact::receiveCapabilities(const RequestableChannelClassList &caps) +{ + if (!mPriv->requestedFeatures.contains(FeatureCapabilities)) { + return; + } + + mPriv->actualFeatures.insert(FeatureCapabilities); + + if (mPriv->caps.allClassSpecs().bareClasses() != caps) { + mPriv->caps.updateRequestableChannelClasses(caps); + emit capabilitiesChanged(mPriv->caps); + } +} + +void Contact::receiveLocation(const QVariantMap &location) +{ + if (!mPriv->requestedFeatures.contains(FeatureLocation)) { + return; + } + + mPriv->actualFeatures.insert(FeatureLocation); + + if (mPriv->location.allDetails() != location) { + mPriv->location.updateData(location); + emit locationUpdated(mPriv->location); + } +} + +void Contact::receiveInfo(const ContactInfoFieldList &info) +{ + if (!mPriv->requestedFeatures.contains(FeatureInfo)) { + return; + } + + mPriv->actualFeatures.insert(FeatureInfo); + mPriv->isContactInfoKnown = true; + + if (mPriv->info.allFields() != info) { + mPriv->info = InfoFields(info); + emit infoFieldsChanged(mPriv->info); + } +} + +Contact::PresenceState Contact::subscriptionStateToPresenceState(uint subscriptionState) +{ + switch (subscriptionState) { + case SubscriptionStateAsk: + return PresenceStateAsk; + case SubscriptionStateYes: + return PresenceStateYes; + default: + return PresenceStateNo; + } +} + +void Contact::setSubscriptionState(SubscriptionState state) +{ + if (mPriv->subscriptionState == state) { + return; + } + + mPriv->subscriptionState = state; + + // FIXME (API/ABI break) remove signal with details + emit subscriptionStateChanged(subscriptionStateToPresenceState(state), + Channel::GroupMemberChangeDetails()); + + emit subscriptionStateChanged(subscriptionStateToPresenceState(state)); +} + +void Contact::setPublishState(SubscriptionState state, const QString &message) +{ + if (mPriv->publishState == state && mPriv->publishStateMessage == message) { + return; + } + + mPriv->publishState = state; + mPriv->publishStateMessage = message; + + // FIXME (API/ABI break) remove signal with details + QVariantMap detailsMap; + detailsMap.insert(QLatin1String("message"), message); + emit publishStateChanged(subscriptionStateToPresenceState(state), + Channel::GroupMemberChangeDetails(ContactPtr(), detailsMap)); + + emit publishStateChanged(subscriptionStateToPresenceState(state), message); +} + +void Contact::setBlocked(bool value) +{ + if (mPriv->blocked == value) { + return; + } + + mPriv->blocked = value; + + // FIXME (API/ABI break) remove signal with details + emit blockStatusChanged(value, Channel::GroupMemberChangeDetails()); + + emit blockStatusChanged(value); +} + +void Contact::setAddedToGroup(const QString &group) +{ + if (!mPriv->groups.contains(group)) { + mPriv->groups.insert(group); + emit addedToGroup(group); + } +} + +void Contact::setRemovedFromGroup(const QString &group) +{ + if (mPriv->groups.remove(group)) { + emit removedFromGroup(group); + } +} + +/** + * \fn void Contact::aliasChanged(const QString &alias) + * + * Emitted when the value of alias() changes. + * + * \param alias The new alias of this contact. + * \sa alias() + */ + +/** + * \fn void Contact::avatarTokenChanged(const QString &avatarToken) + * + * Emitted when the value of avatarToken() changes. + * + * \param avatarToken The new avatar token of this contact. + * \sa avatarToken() + */ + +/** + * \fn void Contact::avatarDataChanged(const QString &avatarToken) + * + * Emitted when the value of avatarData() changes. + * + * \param avatarData The new avatar of this contact. + * \sa avatarData() + */ + +/** + * \fn void Contact::presenceChanged(const Tp::Presence &presence) + * + * Emitted when the value of presence() changes. + * + * \param presence The new presence of this contact. + * \sa presence() + */ + +/** + * \fn void Contact::capabilitiesChanged(const Tp::ContactCapabilities &caps) + * + * Emitted when the value of capabilities() changes. + * + * \param caps The new capabilities of this contact. + * \sa capabilities() + */ + +/** + * \fn void Contact::locationUpdated(const Tp::LocationInfo &location) + * + * Emitted when the value of location() changes. + * + * \param caps The new location of this contact. + * \sa location() + */ + +/** + * \fn void Contact::infoFieldsChanged(const Tp::Contact::InfoFields &infoFields) + * + * Emitted when the value of infoFields() changes. + * + * \param InfoFields The new info of this contact. + * \sa infoFields() + */ + +/** + * \fn void Contact::subscriptionStateChanged(Tp::Contact::PresenceState state) + * + * Emitted when the value of subscriptionState() changes. + * + * \param state The new subscription state of this contact. + * \sa subscriptionState() + */ + +/** + * \fn void Contact::subscriptionStateChanged(Tp::Contact::PresenceState state, + * const Tp::Channel::GroupMemberChangeDetails &details) + * + * \deprecated Use subscriptionStateChanged(Tp::Contact::PresenceState state) instead. + */ + +/** + * \fn void Contact::publishStateChanged(Tp::Contact::PresenceState state, const QString &message) + * + * Emitted when the value of publishState() changes. + * + * \param state The new publish state of this contact. + * \sa publishState() + */ + +/** + * \fn void Contact::publishStateChanged(Tp::Contact::PresenceState state, + * const Tp::Channel::GroupMemberChangeDetails &details) + * + * \deprecated Use publishStateChanged(Tp::Contact::PresenceState state, const QString &message) instead. + */ + +/** + * \fn void Contact::blockStatusChanged(bool blocked) + * + * Emitted when the value of isBlocked() changes. + * + * \param status The new block status of this contact. + * \sa isBlocked() + */ + +/** + * \fn void Contact::blockStatusChanged(bool blocked, + * const Tp::Channel::GroupMemberChangeDetails &details) + * + * \deprecated Use blockStatusChanged(bool blocked) instead. + */ + +/** + * \fn void Contact::addedToGroup(const QString &group) + * + * Emitted when this contact is added to \a group of the contact list. + * + * \param group The group name. + * \sa groups(), removedFromGroup() + */ + +/** + * \fn void Contact::removedFromGroup(const QString &group) + * + * Emitted when this contact is removed from \a group of the contact list. + * + * \param group The group name. + * \sa groups(), addedToGroup() + */ + +void Contact::connectNotify(const char *signalName) +{ + if (qstrcmp(signalName, SIGNAL(subscriptionStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails))) == 0) { + warning() << "Connecting to deprecated signal subscriptionStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)"; + } else if (qstrcmp(signalName, SIGNAL(publishStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails))) == 0) { + warning() << "Connecting to deprecated signal publishStateChanged(Tp::Contact::PresenceState,Tp::Channel::GroupMemberChangeDetails)"; + } else if (qstrcmp(signalName, SIGNAL(blockStatusChanged(bool,Tp::Channel::GroupMemberChangeDetails))) == 0) { + warning() << "Connecting to deprecated signal blockStatusChanged(bool,Tp::Channel::GroupMemberChangeDetails)"; + } +} + +} // Tp diff --git a/TelepathyQt/contact.h b/TelepathyQt/contact.h new file mode 100644 index 00000000..4d50f1e8 --- /dev/null +++ b/TelepathyQt/contact.h @@ -0,0 +1,246 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_contact_h_HEADER_GUARD_ +#define _TelepathyQt_contact_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Channel> +#include <TelepathyQt/Feature> +#include <TelepathyQt/Object> +#include <TelepathyQt/Types> + +#include <QSet> +#include <QVariantMap> + +namespace Tp +{ + +struct AvatarData; +class ContactCapabilities; +class LocationInfo; +class ContactManager; +class PendingContactInfo; +class PendingOperation; +class Presence; +class ReferencedHandles; + +class TP_QT_EXPORT Contact : public Object +{ + Q_OBJECT + Q_DISABLE_COPY(Contact) + +public: + static const Feature FeatureAlias; + static const Feature FeatureAvatarData; + static const Feature FeatureAvatarToken; + static const Feature FeatureCapabilities; + static const Feature FeatureInfo; + static const Feature FeatureLocation; + static const Feature FeatureSimplePresence; + + enum PresenceState { + PresenceStateNo, + PresenceStateAsk, + PresenceStateYes + }; + + class InfoFields + { + public: + InfoFields(); + InfoFields(const ContactInfoFieldList &fields); + InfoFields(const InfoFields &other); + ~InfoFields(); + + bool isValid() const { return mPriv.constData() != 0; } + + InfoFields &operator=(const InfoFields &other); + + ContactInfoFieldList fields(const QString &name) const; + + ContactInfoFieldList allFields() const; + + private: + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; + }; + + ~Contact(); + + ContactManagerPtr manager() const; + + ReferencedHandles handle() const; + + // TODO filter: exact, prefix, substring match + QString id() const; + + Features requestedFeatures() const; + Features actualFeatures() const; + + // TODO filter: exact, prefix, substring match + QString alias() const; + + bool isAvatarTokenKnown() const; + QString avatarToken() const; + AvatarData avatarData() const; + void requestAvatarData(); + + /* + * TODO filter: + * - exact match of presence().type(), presence().status() + * - ANY 1 of a number of presence types/statuses + * - presence().type() greater or less than a set value + * - have/don't have presence().message() AND exact/prefix/substring + */ + Presence presence() const; + + // TODO filter: the same as Account filtering by caps + ContactCapabilities capabilities() const; + + // TODO filter: is it available, how accurate, are they near me + LocationInfo location() const; + + // TODO filter: having a specific field, having ANY field, + // (field: exact, contents: exact/prefix/substring) + bool isContactInfoKnown() const; + InfoFields infoFields() const; + PendingOperation *refreshInfo(); + PendingContactInfo *requestInfo(); + + /* + * Filters on exact values of these, but also the "in your contact list at all or not" usecase + */ + bool isSubscriptionStateKnown() const; + bool isSubscriptionRejected() const; + PresenceState subscriptionState() const; + bool isPublishStateKnown() const; + bool isPublishCancelled() const; + PresenceState publishState() const; + QString publishStateMessage() const; + + PendingOperation *requestPresenceSubscription(const QString &message = QString()); + PendingOperation *removePresenceSubscription(const QString &message = QString()); + PendingOperation *authorizePresencePublication(const QString &message = QString()); + PendingOperation *removePresencePublication(const QString &message = QString()); + + /* + * Filter on being blocked or not + */ + bool isBlocked() const; + TP_QT_DEPRECATED PendingOperation *block(bool value); + PendingOperation *block(); + PendingOperation *blockAndReportAbuse(); + PendingOperation *unblock(); + + /* + * Filter on the groups they're in - to show a specific group only + * + * Also prefix/substring match on ANY of the groups of the contact + */ + QStringList groups() const; + PendingOperation *addToGroup(const QString &group); + PendingOperation *removeFromGroup(const QString &group); + +Q_SIGNALS: + void aliasChanged(const QString &alias); + + void avatarTokenChanged(const QString &avatarToken); + void avatarDataChanged(const Tp::AvatarData &avatarData); + + void presenceChanged(const Tp::Presence &presence); + + void capabilitiesChanged(const Tp::ContactCapabilities &caps); + + void locationUpdated(const Tp::LocationInfo &location); + + void infoFieldsChanged(const Tp::Contact::InfoFields &infoFields); + + void subscriptionStateChanged(Tp::Contact::PresenceState state); + // deprecated + void subscriptionStateChanged(Tp::Contact::PresenceState state, + const Tp::Channel::GroupMemberChangeDetails &details); + + void publishStateChanged(Tp::Contact::PresenceState state, const QString &message); + // deprecated + void publishStateChanged(Tp::Contact::PresenceState state, + const Tp::Channel::GroupMemberChangeDetails &details); + + void blockStatusChanged(bool blocked); + // deprecated + void blockStatusChanged(bool blocked, const Tp::Channel::GroupMemberChangeDetails &details); + + void addedToGroup(const QString &group); + void removedFromGroup(const QString &group); + + // TODO: consider how the Renaming interface should work and map to Contacts + // I guess it would be something like: + // void renamedTo(Tp::ContactPtr) + // with that contact getting the same features requested as the current one. Or would we rather + // want to signal that change right away with a handle? + +protected: + Contact(ContactManager *manager, const ReferencedHandles &handle, + const Features &requestedFeatures, const QVariantMap &attributes); + + virtual void augment(const Features &requestedFeatures, const QVariantMap &attributes); + + // FIXME: (API/ABI break) Remove connectNotify + void connectNotify(const char *); + +private: + static const Feature FeatureRosterGroups; + + TP_QT_NO_EXPORT void receiveAlias(const QString &alias); + TP_QT_NO_EXPORT void receiveAvatarToken(const QString &avatarToken); + TP_QT_NO_EXPORT void setAvatarToken(const QString &token); + TP_QT_NO_EXPORT void receiveAvatarData(const AvatarData &); + TP_QT_NO_EXPORT void receiveSimplePresence(const SimplePresence &presence); + TP_QT_NO_EXPORT void receiveCapabilities(const RequestableChannelClassList &caps); + TP_QT_NO_EXPORT void receiveLocation(const QVariantMap &location); + TP_QT_NO_EXPORT void receiveInfo(const ContactInfoFieldList &info); + + TP_QT_NO_EXPORT static PresenceState subscriptionStateToPresenceState(uint subscriptionState); + TP_QT_NO_EXPORT void setSubscriptionState(SubscriptionState state); + TP_QT_NO_EXPORT void setPublishState(SubscriptionState state, const QString &message = QString()); + TP_QT_NO_EXPORT void setBlocked(bool value); + + TP_QT_NO_EXPORT void setAddedToGroup(const QString &group); + TP_QT_NO_EXPORT void setRemovedFromGroup(const QString &group); + + struct Private; + friend class Connection; + friend class ContactFactory; + friend class ContactManager; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::Contact::InfoFields); + +#endif diff --git a/TelepathyQt/dbus-daemon.xml b/TelepathyQt/dbus-daemon.xml new file mode 100644 index 00000000..675b879c --- /dev/null +++ b/TelepathyQt/dbus-daemon.xml @@ -0,0 +1,80 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + + <tp:title>D-Bus Daemon</tp:title> + + <node name="/DBus_Daemon"> + <interface name="org.freedesktop.DBus"> + <method name="Hello"> + <arg direction="out" type="s"/> + </method> + <method name="RequestName"> + <arg direction="in" type="s" name="Name"/> + <arg direction="in" type="u" name="Flags"/> + <arg direction="out" type="u"/> + </method> + <method name="ReleaseName"> + <arg direction="in" type="s" name="Name"/> + <arg direction="out" type="u"/> + </method> + <method name="StartServiceByName"> + <arg direction="in" type="s" name="Service"/> + <arg direction="in" type="u" name="Flags"/> + <arg direction="out" type="u"/> + </method> + <method name="NameHasOwner"> + <arg direction="in" type="s" name="Name_To_Check"/> + <arg direction="out" type="b"/> + </method> + <method name="ListNames"> + <arg direction="out" type="as"/> + </method> + <method name="ListActivatableNames"> + <arg direction="out" type="as"/> + </method> + <method name="AddMatch"> + <arg direction="in" type="s" name="Rule"/> + </method> + <method name="RemoveMatch"> + <arg direction="in" type="s" name="Rule"/> + </method> + <method name="GetNameOwner"> + <arg direction="in" type="s" name="Name"/> + <arg direction="out" type="s"/> + </method> + <method name="ListQueuedOwners"> + <arg direction="in" type="s" name="Name"/> + <arg direction="out" type="as"/> + </method> + <method name="GetConnectionUnixUser"> + <arg direction="in" type="s" name="Connection_Name"/> + <arg direction="out" type="u"/> + </method> + <method name="GetConnectionUnixProcessID"> + <arg direction="in" type="s" name="Connection_Name"/> + <arg direction="out" type="u"/> + </method> + <method name="GetConnectionSELinuxSecurityContext"> + <arg direction="in" type="s" name="Connection_Name"/> + <arg direction="out" type="ay"/> + </method> + <method name="ReloadConfig"> + </method> + <method name="GetId"> + <arg direction="out" type="s"/> + </method> + <signal name="NameOwnerChanged"> + <arg type="s" name="Name"/> + <arg type="s" name="Old_Owner"/> + <arg type="s" name="New_Owner"/> + </signal> + <signal name="NameLost"> + <arg type="s" name="Name"/> + </signal> + <signal name="NameAcquired"> + <arg type="s" name="Name"/> + </signal> + </interface> + </node> + +</tp:spec> diff --git a/TelepathyQt/dbus-introspectable.xml b/TelepathyQt/dbus-introspectable.xml new file mode 100644 index 00000000..4617f407 --- /dev/null +++ b/TelepathyQt/dbus-introspectable.xml @@ -0,0 +1,16 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + + <tp:title>D-Bus introspectable object</tp:title> + + <node name="/Introspectable"> + <interface name="org.freedesktop.DBus.Introspectable"> + + <method name="Introspect"> + <arg direction="out" type="s" name="XML_Data"/> + </method> + + </interface> + </node> + +</tp:spec> diff --git a/TelepathyQt/dbus-peer.xml b/TelepathyQt/dbus-peer.xml new file mode 100644 index 00000000..54b25a28 --- /dev/null +++ b/TelepathyQt/dbus-peer.xml @@ -0,0 +1,19 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + + <tp:title>D-Bus peer</tp:title> + + <node name="/Peer"> + <interface name="org.freedesktop.DBus.Peer" tp:implement-service="no"> + + <method name="Ping"> + </method> + + <method name="GetMachineId"> + <arg direction="out" type="s" name="Machine_UUID"/> + </method> + + </interface> + </node> + +</tp:spec> diff --git a/TelepathyQt/dbus-properties.xml b/TelepathyQt/dbus-properties.xml new file mode 100644 index 00000000..e76b3981 --- /dev/null +++ b/TelepathyQt/dbus-properties.xml @@ -0,0 +1,29 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + + <tp:title>D-Bus Properties</tp:title> + + <node name="/Properties"> + <interface name="org.freedesktop.DBus.Properties"> + + <method name="Get"> + <arg direction="in" type="s" name="Interface_Name"/> + <arg direction="in" type="s" name="Property_Name"/> + <arg direction="out" type="v" name="Value"/> + </method> + + <method name="Set"> + <arg direction="in" type="s" name="Interface_Name"/> + <arg direction="in" type="s" name="Property_Name"/> + <arg direction="in" type="v" name="Value"/> + </method> + + <method name="GetAll"> + <arg direction="in" type="s" name="Interface_Name"/> + <arg direction="out" type="a{sv}" name="Properties"/> + </method> + + </interface> + </node> + +</tp:spec> diff --git a/TelepathyQt/dbus-proxy-factory-internal.h b/TelepathyQt/dbus-proxy-factory-internal.h new file mode 100644 index 00000000..d5fbdbb8 --- /dev/null +++ b/TelepathyQt/dbus-proxy-factory-internal.h @@ -0,0 +1,58 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BUILDING_TP_QT +#error "This file is a TpQt4 internal header not to be included by applications" +#endif + +#include <QObject> +#include <QPair> +#include <QString> + +#include <TelepathyQt/SharedPtr> + +namespace Tp +{ + +class DBusProxy; + +class TP_QT_NO_EXPORT DBusProxyFactory::Cache : public QObject +{ + Q_OBJECT + +public: + typedef QPair<QString /* serviceName */, QString /* objectPath */> Key; + + Cache(); + ~Cache(); + + DBusProxyPtr get(const Key &key) const; + void put(const DBusProxyPtr &proxy); + +private Q_SLOTS: + void onProxyInvalidated(Tp::DBusProxy *proxy); // The error itself is not interesting + +private: + QHash<Key, QWeakPointer<DBusProxy> > proxies; +}; + +} diff --git a/TelepathyQt/dbus-proxy-factory.cpp b/TelepathyQt/dbus-proxy-factory.cpp new file mode 100644 index 00000000..7020c4bc --- /dev/null +++ b/TelepathyQt/dbus-proxy-factory.cpp @@ -0,0 +1,295 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/DBusProxyFactory> +#include "TelepathyQt/dbus-proxy-factory-internal.h" + +#include "TelepathyQt/_gen/dbus-proxy-factory.moc.hpp" +#include "TelepathyQt/_gen/dbus-proxy-factory-internal.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/DBusProxy> +#include <TelepathyQt/ReadyObject> +#include <TelepathyQt/PendingReady> + +#include <QDBusConnection> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT DBusProxyFactory::Private +{ + Private(const QDBusConnection &bus) + : bus(bus), + cache(new Cache) + { + } + + ~Private() + { + delete cache; + } + + QDBusConnection bus; + Cache *cache; +}; + +/** + * \class DBusProxyFactory + * \ingroup utils + * \headerfile TelepathyQt/dbus-proxy-factory.h <TelepathyQt/DBusProxyFactory> + * + * \brief The DBusProxyFactory class is a base class for all D-Bus proxy factory + * classes. Handles proxy caching and making them ready as appropriate. + */ + +/** + * Construct a new DBusProxyFactory object. + * + * The intention for storing the bus here is that it generally doesn't make sense to construct + * proxies for multiple buses in the same context. Allowing that would lead to more complex keying + * needs in the cache, as well. + * + * \param bus The D-Bus bus connection for the objects constructed using this factory. + */ +DBusProxyFactory::DBusProxyFactory(const QDBusConnection &bus) + : mPriv(new Private(bus)) +{ +} + +/** + * Class destructor. + */ +DBusProxyFactory::~DBusProxyFactory() +{ + delete mPriv; +} + +/** + * Return the D-Bus connection all of the proxies from this factory communicate with. + * + * \return A QDBusConnection object. + */ +const QDBusConnection &DBusProxyFactory::dbusConnection() const +{ + return mPriv->bus; +} + +/** + * Return a cached proxy with the given \a busName and \a objectPath. + * + * If a proxy has not been previously put into the cache by nowHaveProxy for those identifying + * attributes, or a previously cached proxy has since been invalidated and/or destroyed, a \c Null + * shared pointer is returned instead. + * + * \param busName Bus name of the proxy to return. + * \param objectPath Object path of the proxy to return. + * \return A pointer to the DBusProxy object, if any. + */ +DBusProxyPtr DBusProxyFactory::cachedProxy(const QString &busName, + const QString &objectPath) const +{ + QString finalName = finalBusNameFrom(busName); + return mPriv->cache->get(Cache::Key(finalName, objectPath)); +} + +/** + * Should be called by subclasses when they have a proxy, be it a newly-constructed one or one from + * the cache. + * + * This function will then do the rest of the factory work, including caching the proxy if it's not + * cached already, doing any initialPrepare()/readyPrepare() work if appropriate, and making the + * features from featuresFor() ready if they aren't already. + * + * The returned PendingReady only finishes when the initialPrepare() and readyPrepare() operations + * for the proxy has completed, and the requested features have all been made ready (or found unable + * to be made ready). Note that this might have happened already before calling this function, if + * the proxy was not a newly created one, but was looked up from the cache. DBusProxyFactory handles + * the necessary subleties for this to work. + * + * Access to the proxy instance is allowed as soon as this method returns through + * PendingReady::proxy(), if the proxy is needed in a context where it's not required to be ready. + * + * \param proxy The proxy which the factory should now make sure is prepared and made ready. + * \return A PendingReady operation which will emit PendingReady::finished + * when the proxy is usable. + */ +PendingReady *DBusProxyFactory::nowHaveProxy(const DBusProxyPtr &proxy) const +{ + Q_ASSERT(!proxy.isNull()); + + mPriv->cache->put(proxy); + return new PendingReady(SharedPtr<DBusProxyFactory>((DBusProxyFactory*) this), + proxy, featuresFor(proxy)); +} + +/** + * \fn QString DBusProxyFactory::finalBusNameFrom(const QString &uniqueOrWellKnown) const + * + * "Normalize" a bus name according to the rules for the proxy class to construct. + * + * Should be implemented by subclasses to transform the application-specified name \a + * uniqueOrWellKnown to whatever the proxy constructed for that name would have in its + * DBusProxy::busName() in the end. + * + * For StatelessDBusProxy sub-classes this should mostly be an identity transform, while for + * StatefulDBusProxy sub-classes StatefulDBusProxy::uniqueNameFrom() or an equivalent thereof should + * be used in most cases. + * + * If this is not implemented correctly, caching won't work properly. + * + * \param uniqueOrWellKnown Any valid D-Bus service name, either unique or well-known. + * \return Whatever that name would turn to, when a proxy is constructed for it. + */ + +/** + * Allows subclasses to do arbitrary manipulation on the proxy before it is attempted to be made + * ready. + * + * If a non-\c NULL operation is returned, the completion of that operation is waited for before + * starting to make the object ready whenever nowHaveProxy() is called the first time around for a + * given proxy. + * + * \todo FIXME actually implement this... :) Currently just a vtable placeholder. + * \param proxy The just-constructed proxy to be prepared. + * \return \c NULL ie. nothing to do. + */ +PendingOperation *DBusProxyFactory::initialPrepare(const DBusProxyPtr &proxy) const +{ + // Nothing we could think about needs doing + return NULL; +} + +/** + * Allows subclasses to do arbitrary manipulation on the proxy after it has been made ready. + * + * If a non-\c NULL operation is returned, the completion of that operation is waited for before + * signaling that the object is ready for use after ReadyObject::becomeReady() for it has finished + * whenever nowHaveProxy() is called the first time around for a given proxy. + * + * \todo FIXME actually implement this... :) Currently just a vtable placeholder. + * \param proxy The just-readified proxy to be prepared. + * \return \c NULL ie. nothing to do. + */ +PendingOperation *DBusProxyFactory::readyPrepare(const DBusProxyPtr &proxy) const +{ + // Nothing we could think about needs doing + return NULL; +} + +/** + * \fn Features DBusProxyFactory::featuresFor(const SharedPtr<RefCounted> &proxy) const + * + * Return the features which should be made ready on a given proxy. + * + * This can be used to implement instance-specific features based on arbitrary criteria. + * FixedFeatureFactory implements this as a fixed set of features independent of the instance, + * however. + * + * It should be noted that if an empty set of features is returned, ReadyObject::becomeReady() is + * not called at all. In other words, any "core feature" is not automatically added to the requested + * features. This is to enable setting a factory to not make proxies ready at all, which is useful + * eg. in the case of account editing UIs which aren't interested in the state of Connection objects + * for the Account objects they're editing. + * + * \param proxy The proxy on which the returned features will be made ready. + * \return A list of Feature objects. + */ + +DBusProxyFactory::Cache::Cache() +{ +} + +DBusProxyFactory::Cache::~Cache() +{ +} + +DBusProxyPtr DBusProxyFactory::Cache::get(const Key &key) const +{ + DBusProxyPtr proxy(proxies.value(key)); + + if (proxy.isNull() || !proxy->isValid()) { + // Weak pointer invalidated or proxy invalidated during this mainloop iteration and we still + // haven't got the invalidated() signal for it + return DBusProxyPtr(); + } + + return proxy; +} + +void DBusProxyFactory::Cache::put(const DBusProxyPtr &proxy) +{ + if (proxy->busName().isEmpty()) { + debug() << "Not inserting proxy" << proxy.data() << "with no bus name to factory cache"; + return; + } else if (!proxy->isValid()) { + debug() << "Not inserting to factory cache invalid proxy - proxy is for" << + proxy->busName() << ',' << proxy->objectPath(); + return; + } + + Key key(proxy->busName(), proxy->objectPath()); + + DBusProxyPtr existingProxy(proxies.value(key)); + if (!existingProxy || existingProxy != proxy) { + // Disconnect the invalidated signal from the proxy we're replacing, so it won't uselessly + // cause the new (hopefully valid) proxy to be dropped from the cache if it arrives late. + // + // The window in which this makes a difference is very slim but existent; namely, somebody + // must request a proxy from the factory in the same mainloop iteration as an otherwise + // matching proxy has invalidated itself. The invalidation signal would be delivered and + // processed only during the next mainloop iteration. + if (existingProxy) { + Q_ASSERT(!existingProxy->isValid()); + existingProxy->disconnect( + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + this, + SLOT(onProxyInvalidated(Tp::DBusProxy*))); + + debug() << "Replacing invalidated proxy" << existingProxy.data() << "in cache for name" + << existingProxy->busName() << ',' << existingProxy->objectPath(); + } + + connect(proxy.data(), + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + SLOT(onProxyInvalidated(Tp::DBusProxy*))); + + debug() << "Inserting to factory cache proxy for" << key; + proxies.insert(key, QWeakPointer<DBusProxy>(proxy.data())); + } +} + +void DBusProxyFactory::Cache::onProxyInvalidated(Tp::DBusProxy *proxy) +{ + Key key(proxy->busName(), proxy->objectPath()); + + // Not having it would indicate invalidated() signaled twice for the same proxy, or us having + // connected to two proxies with the same key, neither of which should happen + Q_ASSERT(proxies.contains(key)); + + debug() << "Removing from factory cache invalidated proxy for" << key; + + proxies.remove(key); +} + +} diff --git a/TelepathyQt/dbus-proxy-factory.h b/TelepathyQt/dbus-proxy-factory.h new file mode 100644 index 00000000..1a7eaea7 --- /dev/null +++ b/TelepathyQt/dbus-proxy-factory.h @@ -0,0 +1,85 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_dbus_proxy_factory_h_HEADER_GUARD_ +#define _TelepathyQt_dbus_proxy_factory_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> +#include <TelepathyQt/SharedPtr> +#include <TelepathyQt/Types> + +// For Q_DISABLE_COPY +#include <QtGlobal> + +#include <QString> + +class QDBusConnection; + +namespace Tp +{ + +class Features; +class PendingReady; +class PendingOperation; + +class TP_QT_EXPORT DBusProxyFactory : public QObject, public RefCounted +{ + Q_OBJECT + Q_DISABLE_COPY(DBusProxyFactory) + +public: + virtual ~DBusProxyFactory(); + + const QDBusConnection &dbusConnection() const; + +protected: + DBusProxyFactory(const QDBusConnection &bus); + + DBusProxyPtr cachedProxy(const QString &busName, const QString &objectPath) const; + + PendingReady *nowHaveProxy(const DBusProxyPtr &proxy) const; + + // I don't want this to be non-pure virtual, because I want ALL subclasses to have to think + // about whether or not they need to uniquefy the name or not. If a subclass doesn't implement + // this while it should, matching with the cache for future requests and invalidation breaks. + virtual QString finalBusNameFrom(const QString &uniqueOrWellKnown) const = 0; + + virtual PendingOperation *initialPrepare(const DBusProxyPtr &proxy) const; + virtual PendingOperation *readyPrepare(const DBusProxyPtr &proxy) const; + + virtual Features featuresFor(const DBusProxyPtr &proxy) const = 0; + +private: + class Cache; + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/dbus-proxy.cpp b/TelepathyQt/dbus-proxy.cpp new file mode 100644 index 00000000..1496c454 --- /dev/null +++ b/TelepathyQt/dbus-proxy.cpp @@ -0,0 +1,393 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include <TelepathyQt/DBusProxy> + +#include "TelepathyQt/_gen/dbus-proxy.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Constants> + +#include <QDBusConnection> +#include <QDBusConnectionInterface> +#include <QDBusError> +#include <QDBusServiceWatcher> +#include <QTimer> + +namespace Tp +{ + +// ==== DBusProxy ====================================================== + +// Features in TpProxy but not here: +// * tracking which interfaces we have (in tpqt, subclasses do that) +// * being Introspectable, a Peer and a Properties implementation +// * disconnecting from signals when invalidated (probably has to be in the +// generated code) +// * making methods always raise an error when called after invalidated +// (has to be in the generated code) + +struct TP_QT_NO_EXPORT DBusProxy::Private +{ + Private(const QDBusConnection &dbusConnection, const QString &busName, + const QString &objectPath); + + QDBusConnection dbusConnection; + QString busName; + QString objectPath; + QString invalidationReason; + QString invalidationMessage; +}; + +DBusProxy::Private::Private(const QDBusConnection &dbusConnection, + const QString &busName, const QString &objectPath) + : dbusConnection(dbusConnection), + busName(busName), + objectPath(objectPath) +{ + debug() << "Creating new DBusProxy"; +} + +/** + * \class DBusProxy + * \ingroup clientproxies + * \headerfile TelepathyQt/dbus-proxy.h <TelepathyQt/DBusProxy> + * + * \brief The DBusProxy class is a base class representing a remote object available over D-Bus. + * + * All Telepathy-Qt4 client convenience classes that wrap Telepathy interfaces + * inherit from this class in order to provide basic D-Bus interface + * information. + */ + +/** + * Construct a new DBusProxy object. + * + * \param dbusConnection QDBusConnection to use. + * \param busName D-Bus bus name of the service that provides the remote object. + * \param objectPath The object path. + * \param featureCore The object core feature. + */ +DBusProxy::DBusProxy(const QDBusConnection &dbusConnection, + const QString &busName, const QString &objectPath, const Feature &featureCore) + : Object(), + ReadyObject(this, featureCore), + mPriv(new Private(dbusConnection, busName, objectPath)) +{ + if (!dbusConnection.isConnected()) { + invalidate(QLatin1String(TELEPATHY_ERROR_DISCONNECTED), + QLatin1String("DBus connection disconnected")); + } +} + +/** + * Class destructor. + */ +DBusProxy::~DBusProxy() +{ + delete mPriv; +} + +/** + * Return the D-Bus connection through which the remote object is + * accessed. + * + * \return A QDBusConnection object. + */ +QDBusConnection DBusProxy::dbusConnection() const +{ + return mPriv->dbusConnection; +} + +/** + * Return the D-Bus object path of the remote object within the service. + * + * \return The D-Bus object path. + */ +QString DBusProxy::objectPath() const +{ + return mPriv->objectPath; +} + +/** + * Return the D-Bus bus name (either a unique name or a well-known + * name) of the service that provides the remote object. + * + * \return The D-Bus bus name. + */ +QString DBusProxy::busName() const +{ + return mPriv->busName; +} + +/** + * Sets the D-Bus bus name. This is used by subclasses after converting + * well-known names to unique names. + * + * \param busName The D-Bus bus name to set. + */ +void DBusProxy::setBusName(const QString &busName) +{ + mPriv->busName = busName; +} + +/** + * Return whether this proxy is still valid (has not emitted invalidated()). + * + * \return \c true if still valid, \c false otherwise. + */ +bool DBusProxy::isValid() const +{ + return mPriv->invalidationReason.isEmpty(); +} + +/** + * Return the error name indicating the reason this proxy became invalid. + * + * \return A D-Bus error name, or QString() if this object is still valid. + */ +QString DBusProxy::invalidationReason() const +{ + return mPriv->invalidationReason; +} + +/** + * Return a debugging message indicating the reason this proxy became invalid. + * + * \return A debugging message, or QString() if this object is still valid. + */ +QString DBusProxy::invalidationMessage() const +{ + return mPriv->invalidationMessage; +} + +/** + * Called by subclasses when the DBusProxy should become invalid. + * + * This method takes care of setting the invalidationReason, + * invalidationMessage, and emitting the invalidated signal. + * + * \param reason A D-Bus error name (a string in a subset of ASCII, + * prefixed with a reversed domain name) + * \param message A debugging message associated with the error + */ +void DBusProxy::invalidate(const QString &reason, const QString &message) +{ + if (!isValid()) { + debug().nospace() << "Already invalidated by " + << mPriv->invalidationReason + << ", not replacing with " << reason + << " \"" << message << "\""; + return; + } + + Q_ASSERT(!reason.isEmpty()); + + debug().nospace() << "proxy invalidated: " << reason + << ": " << message; + + mPriv->invalidationReason = reason; + mPriv->invalidationMessage = message; + + Q_ASSERT(!isValid()); + + // Defer emitting the invalidated signal until we next + // return to the mainloop. + QTimer::singleShot(0, this, SLOT(emitInvalidated())); +} + +void DBusProxy::invalidate(const QDBusError &error) +{ + invalidate(error.name(), error.message()); +} + +void DBusProxy::emitInvalidated() +{ + Q_ASSERT(!isValid()); + + emit invalidated(this, mPriv->invalidationReason, mPriv->invalidationMessage); +} + +/** + * \fn void DBusProxy::invalidated(Tp::DBusProxy *proxy, + * const QString &errorName, const QString &errorMessage) + * + * Emitted when this object is no longer usable. + * + * After this signal is emitted, any D-Bus method calls on the object + * will fail, but it may be possible to retrieve information that has + * already been retrieved and cached. + * + * \param proxy This proxy. + * \param errorName The name of a D-Bus error describing the reason for the invalidation. + * \param errorMessage A debugging message associated with the error. + */ + +// ==== StatefulDBusProxy ============================================== + +struct TP_QT_NO_EXPORT StatefulDBusProxy::Private +{ + Private(const QString &originalName) + : originalName(originalName) {} + + QString originalName; +}; + +/** + * \class StatefulDBusProxy + * \ingroup clientproxies + * \headerfile TelepathyQt/dbus-proxy.h <TelepathyQt/StatefulDBusProxy> + * + * \brief The StatefulDBusProxy class is a base class representing a remote object whose API is + * stateful. + * + * These objects do not remain useful if the service providing them exits or + * crashes, so they emit invalidated() if this happens. + * + * Examples include the Connection and Channel classes. + */ + +/** + * Construct a new StatefulDBusProxy object. + * + * \param dbusConnection QDBusConnection to use. + * \param busName D-Bus bus name of the service that provides the remote object. + * \param objectPath The object path. + * \param featureCore The object core feature. + */ +StatefulDBusProxy::StatefulDBusProxy(const QDBusConnection &dbusConnection, + const QString &busName, const QString &objectPath, const Feature &featureCore) + : DBusProxy(dbusConnection, busName, objectPath, featureCore), + mPriv(new Private(busName)) +{ + QDBusServiceWatcher *serviceWatcher = new QDBusServiceWatcher(busName, + dbusConnection, QDBusServiceWatcher::WatchForUnregistration, this); + connect(serviceWatcher, + SIGNAL(serviceOwnerChanged(QString,QString,QString)), + SLOT(onServiceOwnerChanged(QString,QString,QString))); + + QString error, message; + QString uniqueName = uniqueNameFrom(dbusConnection, busName, error, message); + + if (uniqueName.isEmpty()) { + invalidate(error, message); + return; + } + + setBusName(uniqueName); +} + +/** + * Class destructor. + */ +StatefulDBusProxy::~StatefulDBusProxy() +{ + delete mPriv; +} + +QString StatefulDBusProxy::uniqueNameFrom(const QDBusConnection &bus, const QString &name) +{ + QString error, message; + QString uniqueName = uniqueNameFrom(bus, name, error, message); + if (uniqueName.isEmpty()) { + warning() << "StatefulDBusProxy::uniqueNameFrom(): Failed to get unique name of" << name; + warning() << " error:" << error << "message:" << message; + } + + return uniqueName; +} + +QString StatefulDBusProxy::uniqueNameFrom(const QDBusConnection &bus, const QString &name, + QString &error, QString &message) +{ + if (name.startsWith(QLatin1String(":"))) { + return name; + } + + // For a stateful interface, it makes no sense to follow name-owner + // changes, so we want to bind to the unique name. + QDBusReply<QString> reply = bus.interface()->serviceOwner(name); + if (reply.isValid()) { + return reply.value(); + } else { + error = reply.error().name(); + message = reply.error().message(); + return QString(); + } +} + +void StatefulDBusProxy::onServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) +{ + // We only want to invalidate this object if it is not already invalidated, + // and its (not any other object's) name owner changed signal is emitted. + if (isValid() && name == mPriv->originalName && newOwner.isEmpty()) { + invalidate(TP_QT_DBUS_ERROR_NAME_HAS_NO_OWNER, + QLatin1String("Name owner lost (service crashed?)")); + } +} + +// ==== StatelessDBusProxy ============================================= + +/** + * \class StatelessDBusProxy + * \ingroup clientproxies + * \headerfile TelepathyQt/dbus-proxy.h <TelepathyQt/DBusProxy> + * + * \brief The StatelessDBusProxy class is a base class representing a remote object whose API is + * basically stateless. + * + * These objects can remain valid even if the service providing them exits + * and is restarted. + * + * Examples include the AccountManager, Account and ConnectionManager. + */ + +/** + * Construct a new StatelessDBusProxy object. + * + * \param dbusConnection QDBusConnection to use. + * \param busName D-Bus bus name of the service that provides the remote object. + * \param objectPath The object path. + * \param featureCore The object core feature. + */ +StatelessDBusProxy::StatelessDBusProxy(const QDBusConnection &dbusConnection, + const QString &busName, const QString &objectPath, const Feature &featureCore) + : DBusProxy(dbusConnection, busName, objectPath, featureCore), + mPriv(0) +{ + if (busName.startsWith(QLatin1String(":"))) { + warning() << + "Using StatelessDBusProxy for a unique name does not make sense"; + } +} + +/** + * Class destructor. + */ +StatelessDBusProxy::~StatelessDBusProxy() +{ +} + +} // Tp diff --git a/TelepathyQt/dbus-proxy.h b/TelepathyQt/dbus-proxy.h new file mode 100644 index 00000000..0121d722 --- /dev/null +++ b/TelepathyQt/dbus-proxy.h @@ -0,0 +1,122 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_dbus_proxy_h_HEADER_GUARD_ +#define _TelepathyQt_dbus_proxy_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> +#include <TelepathyQt/Object> +#include <TelepathyQt/ReadyObject> + +class QDBusConnection; +class QDBusError; + +namespace Tp +{ + +class TestBackdoors; + +class TP_QT_EXPORT DBusProxy : public Object, public ReadyObject +{ + Q_OBJECT + Q_DISABLE_COPY(DBusProxy) + +public: + DBusProxy(const QDBusConnection &dbusConnection, const QString &busName, + const QString &objectPath, const Feature &featureCore); + virtual ~DBusProxy(); + + QDBusConnection dbusConnection() const; + QString busName() const; + QString objectPath() const; + + bool isValid() const; + QString invalidationReason() const; + QString invalidationMessage() const; + +Q_SIGNALS: + void invalidated(Tp::DBusProxy *proxy, + const QString &errorName, const QString &errorMessage); + +protected: + void setBusName(const QString &busName); + void invalidate(const QString &reason, const QString &message); + void invalidate(const QDBusError &error); + +private Q_SLOTS: + TP_QT_NO_EXPORT void emitInvalidated(); + +private: + friend class TestBackdoors; + + struct Private; + friend struct Private; + Private *mPriv; +}; + +class TP_QT_EXPORT StatelessDBusProxy : public DBusProxy +{ + Q_OBJECT + Q_DISABLE_COPY(StatelessDBusProxy) + +public: + StatelessDBusProxy(const QDBusConnection &dbusConnection, + const QString &busName, const QString &objectPath, const Feature &featureCore); + virtual ~StatelessDBusProxy(); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +class TP_QT_EXPORT StatefulDBusProxy : public DBusProxy +{ + Q_OBJECT + Q_DISABLE_COPY(StatefulDBusProxy) + +public: + StatefulDBusProxy(const QDBusConnection &dbusConnection, + const QString &busName, const QString &objectPath, const Feature &featureCore); + virtual ~StatefulDBusProxy(); + + static QString uniqueNameFrom(const QDBusConnection &bus, const QString &wellKnownOrUnique); + static QString uniqueNameFrom(const QDBusConnection &bus, const QString &wellKnownOrUnique, + QString &error, QString &message); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onServiceOwnerChanged(const QString &name, const QString &oldOwner, + const QString &newOwner); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/dbus.cpp b/TelepathyQt/dbus.cpp new file mode 100644 index 00000000..c146414e --- /dev/null +++ b/TelepathyQt/dbus.cpp @@ -0,0 +1,26 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/DBus> + +#include "TelepathyQt/_gen/cli-dbus-body.hpp" +#include "TelepathyQt/_gen/cli-dbus.moc.hpp" diff --git a/TelepathyQt/dbus.h b/TelepathyQt/dbus.h new file mode 100644 index 00000000..6e3e7176 --- /dev/null +++ b/TelepathyQt/dbus.h @@ -0,0 +1,54 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_dbus_h_HEADER_GUARD_ +#define _TelepathyQt_dbus_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +/** + * \addtogroup clientsideproxies Client-side proxies + * + * Proxy objects representing remote service objects accessed via D-Bus. + * + * In addition to providing direct access to methods, signals and properties + * exported by the remote objects, some of these proxies offer features like + * automatic inspection of remote object capabilities, property tracking, + * backwards compatibility helpers for older services and other utilities. + */ + +/** + * \defgroup clientdbus Generic D-Bus proxies + * \ingroup clientsideproxies + * + * Proxy objects representing well-known generic D-Bus interfaces on remote + * objects. Note that QDBus already has QDBusConnectionInterface for accessing + * the bus daemon, so in the parts where there is an overlap in the + * functionality, using the QDBus proxy should be given consideration instead + * of blindly using the proxy provided here. + */ + +#include <TelepathyQt/_gen/cli-dbus.h> + +#endif diff --git a/TelepathyQt/dbus.xml b/TelepathyQt/dbus.xml new file mode 100644 index 00000000..99af441c --- /dev/null +++ b/TelepathyQt/dbus.xml @@ -0,0 +1,12 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>D-Bus interfaces</tp:title> + +<xi:include href="dbus-daemon.xml"/> +<xi:include href="dbus-introspectable.xml"/> +<xi:include href="dbus-peer.xml"/> +<xi:include href="dbus-properties.xml"/> + +</tp:spec> diff --git a/TelepathyQt/debug-internal.h b/TelepathyQt/debug-internal.h new file mode 100644 index 00000000..c0076016 --- /dev/null +++ b/TelepathyQt/debug-internal.h @@ -0,0 +1,170 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_debug_HEADER_GUARD_ +#define _TelepathyQt_debug_HEADER_GUARD_ + +#include <QDebug> + +namespace Tp +{ + +class TP_QT_EXPORT Debug +{ +public: + inline Debug() : debug(0) { } + inline Debug(QtMsgType type) : type(type), debug(new QDebug(&msg)) { } + inline Debug(const Debug &a) : type(a.type), debug(a.debug ? new QDebug(&msg) : 0) + { + if (debug) { + (*debug) << qPrintable(a.msg); + } + } + + inline Debug &operator=(const Debug &a) + { + if (this != &a) { + type = a.type; + delete debug; + debug = 0; + + if (a.debug) { + debug = new QDebug(&msg); + (*debug) << qPrintable(a.msg); + } + } + + return *this; + } + + inline ~Debug() + { + if (!msg.isEmpty()) { + invokeDebugCallback(); + } + delete debug; + } + + inline Debug &space() + { + if (debug) { + debug->space(); + } + + return *this; + } + + inline Debug &nospace() + { + if (debug) { + debug->nospace(); + } + + return *this; + } + + inline Debug &maybeSpace() + { + if (debug) { + debug->maybeSpace(); + } + + return *this; + } + + template <typename T> + inline Debug &operator<<(T a) + { + if (debug) { + (*debug) << a; + } + + return *this; + } + +private: + + QString msg; + QtMsgType type; + QDebug *debug; + + void invokeDebugCallback(); +}; + +// The telepathy-farsight Qt 4 binding links to these - they're not API outside +// this source tarball, but they *are* ABI +TP_QT_EXPORT Debug enabledDebug(); +TP_QT_EXPORT Debug enabledWarning(); + +#ifdef ENABLE_DEBUG + +inline Debug debug() +{ + return enabledDebug(); +} + +inline Debug warning() +{ + return enabledWarning(); +} + +#else /* #ifdef ENABLE_DEBUG */ + +struct NoDebug +{ + template <typename T> + NoDebug& operator<<(const T&) + { + return *this; + } + + NoDebug& space() + { + return *this; + } + + NoDebug& nospace() + { + return *this; + } + + NoDebug& maybeSpace() + { + return *this; + } +}; + +inline NoDebug debug() +{ + return NoDebug(); +} + +inline NoDebug warning() +{ + return NoDebug(); +} + +#endif /* #ifdef ENABLE_DEBUG */ + +} // Tp + +#endif diff --git a/TelepathyQt/debug.cpp b/TelepathyQt/debug.cpp new file mode 100644 index 00000000..783b41d9 --- /dev/null +++ b/TelepathyQt/debug.cpp @@ -0,0 +1,187 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2009 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 + */ + +#define IN_TP_QT_HEADER +#include "debug.h" +#include "debug-internal.h" + +#include "config-version.h" + +/** + * \defgroup debug Common debug support + * + * TelepathyQt has an internal mechanism for displaying debugging output. It + * uses the Qt4 debugging subsystem, so if you want to redirect the messages, + * use qInstallMsgHandler() from <QtGlobal>. + * + * Debugging output is divided into two categories: normal debug output and + * warning messages. Normal debug output results in the normal operation of the + * library, warning messages are output only when something goes wrong. Each + * category can be invidually enabled. + */ + +namespace Tp +{ + +/** + * \fn void enableDebug(bool enable) + * \ingroup debug + * + * Enable or disable normal debug output from the library. If the library is not + * compiled with debug support enabled, this has no effect; no output is + * produced in any case. + * + * The default is <code>false</code> ie. no debug output. + * + * \param enable Whether debug output should be enabled or not. + */ + +/** + * \fn void enableWarnings(bool enable) + * \ingroup debug + * + * Enable or disable warning output from the library. If the library is not + * compiled with debug support enabled, this has no effect; no output is + * produced in any case. + * + * The default is <code>true</code> ie. warning output enabled. + * + * \param enable Whether warnings should be enabled or not. + */ + +/** + * \typedef DebugCallback + * \ingroup debug + * + * \code + * typedef QDebug (*DebugCallback)(const QString &libraryName, + * const QString &libraryVersion, + * QtMsgType type, + * const QString &msg) + * \endcode + */ + +/** + * \fn void setDebugCallback(DebugCallback cb) + * \ingroup debug + * + * Set the callback method that will handle the debug output. + * + * If \p cb is NULL this method will set the defaultDebugCallback instead. + * The default callback function will print the output using default Qt debug + * system. + * + * \param cb A function pointer to the callback method or NULL. + * \sa DebugCallback + */ + +#ifdef ENABLE_DEBUG + +namespace +{ +bool debugEnabled = false; +bool warningsEnabled = true; +DebugCallback debugCallback = NULL; +} + +void enableDebug(bool enable) +{ + debugEnabled = enable; +} + +void enableWarnings(bool enable) +{ + warningsEnabled = enable; +} + +void setDebugCallback(DebugCallback cb) +{ + debugCallback = cb; +} + +Debug enabledDebug() +{ + if (debugEnabled) { + return Debug(QtDebugMsg); + } else { + return Debug(); + } +} + +Debug enabledWarning() +{ + if (warningsEnabled) { + return Debug(QtWarningMsg); + } else { + return Debug(); + } +} + +void Debug::invokeDebugCallback() +{ + if (debugCallback) { + debugCallback(QLatin1String("tp-qt4"), QLatin1String(PACKAGE_VERSION), type, msg); + } else { + switch (type) { + case QtDebugMsg: + qDebug() << "tp-qt4 " PACKAGE_VERSION " DEBUG:" << qPrintable(msg); + break; + case QtWarningMsg: + qWarning() << "tp-qt4 " PACKAGE_VERSION " WARN:" << qPrintable(msg); + break; + default: + break; + } + } +} + +#else /* !defined(ENABLE_DEBUG) */ + +void enableDebug(bool enable) +{ +} + +void enableWarnings(bool enable) +{ +} + +void setDebugCallback(DebugCallback cb) +{ +} + +Debug enabledDebug() +{ + return Debug(); +} + +Debug enabledWarning() +{ + return Debug(); +} + +void Debug::invokeDebugCallback() +{ +} + +#endif /* !defined(ENABLE_DEBUG) */ + +} // Tp diff --git a/TelepathyQt/debug.h b/TelepathyQt/debug.h new file mode 100644 index 00000000..a6ed0d72 --- /dev/null +++ b/TelepathyQt/debug.h @@ -0,0 +1,46 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2009 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 + */ + +#ifndef _TelepathyQt_debug_h_HEADER_GUARD_ +#define _TelepathyQt_debug_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> + +namespace Tp +{ + +TP_QT_EXPORT void enableDebug(bool enable); +TP_QT_EXPORT void enableWarnings(bool enable); + +typedef void (*DebugCallback)(const QString &libraryName, + const QString &libraryVersion, + QtMsgType type, + const QString &msg); +TP_QT_EXPORT void setDebugCallback(DebugCallback cb); + +} // Tp + +#endif diff --git a/TelepathyQt/examples.dox b/TelepathyQt/examples.dox new file mode 100644 index 00000000..5f592702 --- /dev/null +++ b/TelepathyQt/examples.dox @@ -0,0 +1,154 @@ +/* + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008 Nokia Corporation + * + * 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 + */ + +/** + * \page accounts_example Accounts Example + * + * \li \subpage accounts_example_account_item_cpp + * \li \subpage accounts_example_account_item_h + * \li \subpage accounts_example_accounts_window_cpp + * \li \subpage accounts_example_accounts_window_h + * \li \subpage accounts_example_main + */ + +/** + * \page accounts_example_account_item_cpp accounts/account-item.cpp + * \include examples/accounts/account-item.cpp + */ + +/** + * \page accounts_example_account_item_h accounts/account-item.h + * \include examples/accounts/account-item.h + */ + +/** + * \page accounts_example_accounts_window_cpp accounts/accounts-window.cpp + * \include examples/accounts/accounts-window.cpp + */ + +/** + * \page accounts_example_accounts_window_h accounts/accounts-window.h + * \include examples/accounts/accounts-window.h + */ + +/** + * \page accounts_example_main accounts/main.cpp + * \include examples/accounts/main.cpp + */ + +/** + * \page contact_messenger_example Contact Messenger Example + * + * \li \subpage contact_messenger_example_sender_cpp + * \li \subpage contact_messenger_example_sender_h + */ + +/** + * \page contact_messenger_example_sender_cpp contact-messenger/sender.cpp + * \include examples/contact-messenger/sender.cpp + */ + +/** + * \page contact_messenger_example_sender_h contact-messenger/sender.h + * \include examples/contact-messenger/sender.h + */ + +/** + * \page protocols_example Protocols Example + * + * \li \subpage protocols_example_main + * \li \subpage protocols_example_cm_wrapper_cpp + * \li \subpage protocols_example_cm_wrapper_h + * \li \subpage protocols_example_protocols_cpp + * \li \subpage protocols_example_protocols_h + */ + +/** + * \page protocols_example_main protocols/main.cpp + * \include examples/protocols/main.cpp + */ + +/** + * \page protocols_example_cm_wrapper_cpp protocols/cm-wrapper.cpp + * \include examples/protocols/cm-wrapper.cpp + */ + +/** + * \page protocols_example_cm_wrapper_h protocols/cm-wrapper.h + * \include examples/protocols/cm-wrapper.h + */ + +/** + * \page protocols_example_protocols_cpp protocols/protocols.cpp + * \include examples/protocols/protocols.cpp + */ + +/** + * \page protocols_example_protocols_h protocols/protocols.h + * \include examples/protocols/protocols.h + */ + +/** + * \page roster_example Roster Example + * + * \li \subpage roster_example_main + * \li \subpage roster_example_roster_item_cpp + * \li \subpage roster_example_roster_item_h + * \li \subpage roster_example_roster_widget_cpp + * \li \subpage roster_example_roster_widget_h + * \li \subpage roster_example_roster_window_cpp + * \li \subpage roster_example_roster_window_h + */ + +/** + * \page roster_example_main roster/main.cpp + * \include examples/roster/main.cpp + */ + +/** + * \page roster_example_roster_item_cpp roster/roster-item.cpp + * \include examples/roster/roster-item.cpp + */ + +/** + * \page roster_example_roster_item_h roster/roster-item.h + * \include examples/roster/roster-item.h + */ + +/** + * \page roster_example_roster_widget_cpp roster/roster-widget.cpp + * \include examples/roster/roster-widget.cpp + */ + +/** + * \page roster_example_roster_widget_h roster/roster-widget.h + * \include examples/roster/roster-widget.h + */ + +/** + * \page roster_example_roster_window_cpp roster/roster-window.cpp + * \include examples/roster/roster-window.cpp + */ + +/** + * \page roster_example_roster_window_h roster/roster-window.h + * \include examples/roster/roster-window.h + */ diff --git a/TelepathyQt/fake-handler-manager-internal.cpp b/TelepathyQt/fake-handler-manager-internal.cpp new file mode 100644 index 00000000..e18ab425 --- /dev/null +++ b/TelepathyQt/fake-handler-manager-internal.cpp @@ -0,0 +1,165 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 "TelepathyQt/fake-handler-manager-internal.h" + +#include "TelepathyQt/_gen/fake-handler-manager-internal.moc.hpp" + +#include <TelepathyQt/Channel> + +namespace Tp +{ + +FakeHandler::FakeHandler(const QDBusConnection &bus) + : QObject(), + mBus(bus) +{ +} + +FakeHandler::~FakeHandler() +{ +} + +ObjectPathList FakeHandler::handledChannels() const +{ + ObjectPathList ret; + foreach (const Channel *channel, mChannels) { + ret << QDBusObjectPath(channel->objectPath()); + } + return ret; +} + +void FakeHandler::registerChannel(const ChannelPtr &channel) +{ + if (mChannels.contains(channel.data())) { + return; + } + + mChannels.insert(channel.data()); + connect(channel.data(), + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + SLOT(onChannelInvalidated(Tp::DBusProxy*))); + connect(channel.data(), + SIGNAL(destroyed(QObject*)), + SLOT(onChannelDestroyed(QObject*))); +} + +void FakeHandler::onChannelInvalidated(DBusProxy *channel) +{ + disconnect(channel, SIGNAL(destroyed(QObject*)), this, SLOT(onChannelDestroyed(QObject*))); + onChannelDestroyed(channel); +} + +void FakeHandler::onChannelDestroyed(QObject *obj) +{ + Channel *channel = reinterpret_cast<Channel*>(obj); + + Q_ASSERT(mChannels.contains(channel)); + + mChannels.remove(channel); + + if (mChannels.isEmpty()) { + // emit invalidated here instead of relying on QObject::destroyed as FakeHandlerManager + // may reuse this fake handler if FakeHandlerManager::registerChannel is called before the + // slot from QObject::destroyed is invoked (deleteLater()). + emit invalidated(this); + deleteLater(); + } +} + +FakeHandlerManager *FakeHandlerManager::mInstance = 0; + +FakeHandlerManager *FakeHandlerManager::instance() +{ + if (!mInstance) { + mInstance = new FakeHandlerManager(); + } + return mInstance; +} + +FakeHandlerManager::FakeHandlerManager() + : QObject() +{ +} + +FakeHandlerManager::~FakeHandlerManager() +{ + mInstance = 0; +} + +ObjectPathList FakeHandlerManager::handledChannels(const QDBusConnection &bus) const +{ + QPair<QString, QString> busUniqueId(bus.name(), bus.baseService()); + if (mFakeHandlers.contains(busUniqueId)) { + FakeHandler *fakeHandler = mFakeHandlers.value(busUniqueId); + return fakeHandler->handledChannels(); + } + return ObjectPathList(); +} + +void FakeHandlerManager::registerClientRegistrar(const ClientRegistrarPtr &cr) +{ + QDBusConnection bus(cr->dbusConnection()); + QPair<QString, QString> busUniqueId(bus.name(), bus.baseService()); + // keep one registrar around per bus so at least the handlers registered by it will be + // around until all channels on that bus gets invalidated/destroyed + if (!mClientRegistrars.contains(busUniqueId)) { + mClientRegistrars.insert(busUniqueId, cr); + } +} + +void FakeHandlerManager::registerChannels(const QList<ChannelPtr> &channels) +{ + foreach (const ChannelPtr &channel, channels) { + QDBusConnection bus(channel->dbusConnection()); + QPair<QString, QString> busUniqueId(bus.name(), bus.baseService()); + FakeHandler *fakeHandler = mFakeHandlers.value(busUniqueId); + if (!fakeHandler) { + fakeHandler = new FakeHandler(bus); + mFakeHandlers.insert(busUniqueId, fakeHandler); + connect(fakeHandler, + SIGNAL(invalidated(Tp::FakeHandler*)), + SLOT(onFakeHandlerInvalidated(Tp::FakeHandler*))); + } + fakeHandler->registerChannel(channel); + } +} + +void FakeHandlerManager::onFakeHandlerInvalidated(FakeHandler *fakeHandler) +{ + QDBusConnection bus(fakeHandler->dbusConnection()); + QPair<QString, QString> busUniqueId(bus.name(), bus.baseService()); + mFakeHandlers.remove(busUniqueId); + + // all channels for the bus represented by busUniqueId were already destroyed/invalidated, + // we can now free the CR (thus the handlers registered by it) registered for that bus + mClientRegistrars.remove(busUniqueId); + + if (mFakeHandlers.isEmpty()) { + // set mInstance to 0 here as we don't want instance() to return a already + // deleted instance + mInstance = 0; + deleteLater(); + } +} + +} diff --git a/TelepathyQt/fake-handler-manager-internal.h b/TelepathyQt/fake-handler-manager-internal.h new file mode 100644 index 00000000..356bac95 --- /dev/null +++ b/TelepathyQt/fake-handler-manager-internal.h @@ -0,0 +1,90 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +#ifndef _TelepathyQt_fake_handler_manager_internal_h_HEADER_GUARD_ +#define _TelepathyQt_fake_handler_manager_internal_h_HEADER_GUARD_ + +#include <QObject> + +#include <TelepathyQt/ClientRegistrar> +#include <TelepathyQt/Types> + +namespace Tp +{ + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +class FakeHandler : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(FakeHandler) + +public: + FakeHandler(const QDBusConnection &bus); + ~FakeHandler(); + + QDBusConnection dbusConnection() const { return mBus; } + ObjectPathList handledChannels() const; + void registerChannel(const ChannelPtr &channel); + +Q_SIGNALS: + void invalidated(Tp::FakeHandler *self); + +private Q_SLOTS: + void onChannelInvalidated(Tp::DBusProxy *channel); + void onChannelDestroyed(QObject *channel); + +private: + QDBusConnection mBus; + QSet<Channel*> mChannels; +}; + +class FakeHandlerManager : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(FakeHandlerManager) + +public: + static FakeHandlerManager *instance(); + + ~FakeHandlerManager(); + + ObjectPathList handledChannels(const QDBusConnection &bus) const; + void registerClientRegistrar(const ClientRegistrarPtr &cr); + void registerChannels(const QList<ChannelPtr> &channels); + +private Q_SLOTS: + void onFakeHandlerInvalidated(Tp::FakeHandler *fakeHandler); + +private: + FakeHandlerManager(); + + static FakeHandlerManager *mInstance; + QHash<QPair<QString, QString>, ClientRegistrarPtr> mClientRegistrars; + QHash<QPair<QString, QString>, FakeHandler *> mFakeHandlers; +}; + +#endif // DOXYGEN_SHOULD_SKIP_THIS + +} // Tp + +#endif diff --git a/TelepathyQt/feature.cpp b/TelepathyQt/feature.cpp new file mode 100644 index 00000000..1c442e9c --- /dev/null +++ b/TelepathyQt/feature.cpp @@ -0,0 +1,89 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/Feature> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT Feature::Private : public QSharedData +{ + Private(bool critical) : critical(critical) {} + + bool critical; +}; + +/** + * \class Feature + * \ingroup utils + * \headerfile TelepathyQt/feature.h <TelepathyQt/Feature> + * + * \brief The Feature class represents a feature that can be enabled + * on demand. + */ + +Feature::Feature() + : QPair<QString, uint>() +{ +} + +Feature::Feature(const QString &className, uint id, bool critical) + : QPair<QString, uint>(className, id), + mPriv(new Private(critical)) +{ +} + +Feature::Feature(const Feature &other) + : QPair<QString, uint>(other.first, other.second), + mPriv(other.mPriv) +{ +} + +Feature::~Feature() +{ +} + +Feature &Feature::operator=(const Feature &other) +{ + *this = other; + this->mPriv = other.mPriv; + return *this; +} + +bool Feature::isCritical() const +{ + if (!isValid()) { + return false; + } + + return mPriv->critical; +} + +/** + * \class Features + * \ingroup utils + * \headerfile TelepathyQt/feature.h <TelepathyQt/Features> + * + * \brief The Features class represents a list of Feature. + */ + +} // Tp diff --git a/TelepathyQt/feature.h b/TelepathyQt/feature.h new file mode 100644 index 00000000..07ab3a55 --- /dev/null +++ b/TelepathyQt/feature.h @@ -0,0 +1,94 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_feature_h_HEADER_GUARD_ +#define _TelepathyQt_feature_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> + +#include <QMetaType> +#include <QPair> +#include <QSet> +#include <QSharedDataPointer> +#include <QString> + +namespace Tp +{ + +class TP_QT_EXPORT Feature : public QPair<QString, uint> +{ +public: + Feature(); + Feature(const QString &className, uint id, bool critical = false); + Feature(const Feature &other); + ~Feature(); + + bool isValid() const { return mPriv.constData() != 0; } + + Feature &operator=(const Feature &other); + + bool isCritical() const; + +private: + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +class TP_QT_EXPORT Features : public QSet<Feature> +{ +public: + Features() { } + Features(const Feature &feature) { insert(feature); } + Features(const QSet<Feature> &s) : QSet<Feature>(s) { } +}; + +inline Features operator|(const Feature &feature1, const Feature &feature2) +{ + return Features() << feature1 << feature2; +} + +inline Features operator|(const Features &features, const Feature &feature) +{ + return Features(features) << feature; +} + +inline uint qHash(const Features &features) +{ + int ret = 0; + Q_FOREACH (const Feature &feature, features) { + int h = qHash(feature); + ret ^= h; + } + return ret; +} + +} // Tp + +Q_DECLARE_METATYPE(Tp::Feature); +Q_DECLARE_METATYPE(Tp::Features); + +#endif diff --git a/TelepathyQt/file-transfer-channel-creation-properties.cpp b/TelepathyQt/file-transfer-channel-creation-properties.cpp new file mode 100644 index 00000000..e7232b05 --- /dev/null +++ b/TelepathyQt/file-transfer-channel-creation-properties.cpp @@ -0,0 +1,433 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/FileTransferChannelCreationProperties> + +#include <TelepathyQt/Global> + +#include "TelepathyQt/debug-internal.h" + +#include <QSharedData> +#include <QFileInfo> +#include <QUrl> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT FileTransferChannelCreationProperties::Private : public QSharedData +{ + Private(const QString &suggestedFileName, const QString &contentType, + qulonglong size) + : contentType(contentType), + size(size), + contentHashType(FileHashTypeNone) + { + QFileInfo fileInfo(suggestedFileName); + this->suggestedFileName = fileInfo.fileName(); + } + + Private(const QString &path, const QString &contentType) + : contentType(contentType), + contentHashType(FileHashTypeNone) + { + QFileInfo fileInfo(path); + + if (fileInfo.exists()) { + // Set mandatory parameters + suggestedFileName = fileInfo.fileName(); + size = fileInfo.size(); + QUrl fileUri = QUrl::fromLocalFile(fileInfo.canonicalFilePath()); + uri = fileUri.toString(); + + // Set optional parameters + lastModificationTime = fileInfo.lastModified(); + } else { + warning() << path << "is not a local file."; + } + } + + /* mandatory parameters */ + QString suggestedFileName; + QString contentType; + qulonglong size; + + /* optional parameters */ + FileHashType contentHashType; + QString contentHash; + QString description; + QDateTime lastModificationTime; + QString uri; +}; + +/** + * \class FileTransferChannelCreationProperties + * \ingroup clientchannel + * \headerfile TelepathyQt/file-transfer-channel-creation-properties.h <TelepathyQt/FileTransferChannelCreationProperties> + * + * \brief The FileTransferChannelCreationProperties class represents the + * properties of a file transfer channel request. + */ + +/** + * Create an invalid FileTransferChannelCreationProperties. + */ +FileTransferChannelCreationProperties::FileTransferChannelCreationProperties() +{ +} + +/** + * Create a FileTransferChannelCreationProperties. + * + * If \a suggestedFileName or \a contentType are empty or if \a size is equal to + * zero, the channel request will fail. + * \a suggestedFileName will be cleaned of any path. + * + * \param suggestedFileName The name of the file on the sender's side. This is + * therefore given as a suggested filename for the + * receiver. + * \param contentType The content type (MIME) of the file. + * \param size The size of the content of the file. + * \sa setUri() + */ +FileTransferChannelCreationProperties::FileTransferChannelCreationProperties( + const QString &suggestedFileName, const QString &contentType, + qulonglong size) + : mPriv(new Private(suggestedFileName, contentType, size)) +{ +} + +/** + * Create a FileTransferChannelCreationProperties. + * + * This constructor accepts the path to a local file and sets the properties + * that can be deducted from the file. + * If \a path is not a local file the FileTransferChannelCreationProperties + * will be invalid. + * + * \param path The path to the local file to be sent. + */ +FileTransferChannelCreationProperties::FileTransferChannelCreationProperties( + const QString &path, const QString &contentType) + : mPriv(new Private(path, contentType)) +{ + if (mPriv->suggestedFileName.isEmpty()) { + mPriv = QSharedDataPointer<Private>(NULL); + } +} + +/** + * Copy constructor. + */ +FileTransferChannelCreationProperties::FileTransferChannelCreationProperties( + const FileTransferChannelCreationProperties &other) + : mPriv(other.mPriv) +{ +} + +/** + * Class destructor. + */ +FileTransferChannelCreationProperties::~FileTransferChannelCreationProperties() +{ +} + +FileTransferChannelCreationProperties &FileTransferChannelCreationProperties::operator=( + const FileTransferChannelCreationProperties &other) +{ + this->mPriv = other.mPriv; + return *this; +} + +bool FileTransferChannelCreationProperties::operator==( + const FileTransferChannelCreationProperties &other) const +{ + return mPriv == other.mPriv; +} + +/** + * Set the content hash of the file and its type for the request. + * + * \param contentHashType The type of content hash. + * \param contentHash The hash of the file, of type \a contentHashType. + * \return This FileTransferChannelCreationProperties. + * \sa hasContentHash(), contentHash(), contentHashType() + */ +FileTransferChannelCreationProperties &FileTransferChannelCreationProperties::setContentHash( + FileHashType contentHashType, const QString &contentHash) +{ + if (!isValid()) { + // there is no point in updating content hash if not valid, as we miss filename, content + // type and size + return *this; + } + + mPriv->contentHashType = contentHashType; + mPriv->contentHash = contentHash; + return *this; +} + +/** + * Set a description of the file for the request. + * + * \param description The description of the file. + * \return This FileTransferChannelCreationProperties. + * \sa hasDescription(), description() + */ +FileTransferChannelCreationProperties &FileTransferChannelCreationProperties::setDescription( + const QString &description) +{ + if (!isValid()) { + // there is no point in updating description if not valid, as we miss filename, content + // type and size + return *this; + } + + mPriv->description = description; + return *this; +} + +/** + * Set the last modification time of the file for the request. + * + * \param lastModificationTime The last modification time of the file. + * \return This FileTransferChannelCreationProperties. + * \sa hasLastModificationTime(), lastModificationTime() + */ +FileTransferChannelCreationProperties &FileTransferChannelCreationProperties::setLastModificationTime( + const QDateTime &lastModificationTime) +{ + if (!isValid()) { + // there is no point in updating last modification time if not valid, as we miss filename, + // content type and size + return *this; + } + + mPriv->lastModificationTime = lastModificationTime; + return *this; +} + +/** + * Set the URI of the file for the request. + * + * \param uri The URI of the file. + * \return This FileTransferChannelCreationProperties. + * \sa uri() + */ +FileTransferChannelCreationProperties &FileTransferChannelCreationProperties::setUri( + const QString &uri) +{ + if (!isValid()) { + // there is no point in updating uri if not valid, as we miss filename, content + // type and size + return *this; + } + + mPriv->uri = uri; + return *this; +} + +/** + * Return the suggested file name for the request. + * If the suggested file name is empty, the channel request will fail. + * + * \return The suggested file name for the request. + */ +QString FileTransferChannelCreationProperties::suggestedFileName() const +{ + if (!isValid()) { + return QString(); + } + + return mPriv->suggestedFileName; +} + +/** + * Return the content type (MIME) of the file for the request. + * If the content type is empty, the channel request will fail. + * + * \return The content type of the file. + */ +QString FileTransferChannelCreationProperties::contentType() const +{ + if (!isValid()) { + return QString(); + } + + return mPriv->contentType; +} + +/** + * Return the size of the contents of the file for the request. + * If size is zero, the channel request will fail. + * + * \return The size of the contents of file. + */ +qulonglong FileTransferChannelCreationProperties::size() const +{ + if (!isValid()) { + return 0; + } + + return mPriv->size; +} + +/** + * Return whether the request will have a content hash. + * + * \return \c true whether it will have a content hash, \c false otherwise. + * \sa contentHash(), contentHashType(), setContentHash() + */ +bool FileTransferChannelCreationProperties::hasContentHash() const +{ + if (!isValid()) { + return false; + } + + return (mPriv->contentHashType != FileHashTypeNone); +} + +/** + * Return the type of the content hash for the request. + * + * \return The type of the content hash. + * \sa hasContentHash(), contentHash(), setContentHash() + */ +FileHashType FileTransferChannelCreationProperties::contentHashType() const +{ + if (!isValid()) { + return FileHashTypeNone; + } + + return mPriv->contentHashType; +} + +/** + * Return the content hash of the file for the request. + * + * \return The hash of the contents of the file transfer, of type returned by + * contentHashType(). + * \sa hasContentHash(), contentHashType(), setContentHash() + */ +QString FileTransferChannelCreationProperties::contentHash() const +{ + if (!isValid()) { + return QString(); + } + + return mPriv->contentHash; +} + +/** + * Return whether the request will have a descriprion. + * + * \return \c true whether it will have description, \c false otherwise. + * \sa description(), setDescription() + */ +bool FileTransferChannelCreationProperties::hasDescription() const +{ + if (!isValid()) { + return false; + } + + return (!mPriv->description.isEmpty()); +} + +/** + * Return the description of the file for the request. + * + * \return The description of the file. + * \sa hasDescription(), setDescription() + */ +QString FileTransferChannelCreationProperties::description() const +{ + if (!isValid()) { + return QString(); + } + + return mPriv->description; +} + +/** + * Return whether the request will have a last modification time. + * + * \return \c true whether it will have a last modification time, \c false + * otherwise. + * \sa lastModificationTime(), setLastModificationTime() + */ +bool FileTransferChannelCreationProperties::hasLastModificationTime() const +{ + if (!isValid()) { + return false; + } + + return (mPriv->lastModificationTime.isValid()); +} + +/** + * Return the last modification time of the file for the request. + * + * \return The last modification time of the file. + * \sa hasLastModificationTime(), setLastModificationTime() + */ +QDateTime FileTransferChannelCreationProperties::lastModificationTime() const +{ + if (!isValid()) { + return QDateTime(); + } + + return mPriv->lastModificationTime; +} + +/** + * Return whether the request will have an URI. + * + * \return \c true whether it will have URI, \c false otherwise. + * \sa uri(), setUri() + */ +bool FileTransferChannelCreationProperties::hasUri() const +{ + if (!isValid()) { + return false; + } + + return (!mPriv->uri.isEmpty()); +} + +/** + * Return the URI of the file for the request. + * If the URI property is empty and the file transfer is handled by an handler + * that is not this process, then it won't be able to initiate the file + * transfer. + * + * \return The URI of the file. + * \sa setUri() + */ +QString FileTransferChannelCreationProperties::uri() const +{ + if (!isValid()) { + return QString(); + } + + return mPriv->uri; +} + +} // Tp diff --git a/TelepathyQt/file-transfer-channel-creation-properties.h b/TelepathyQt/file-transfer-channel-creation-properties.h new file mode 100644 index 00000000..a3904b4c --- /dev/null +++ b/TelepathyQt/file-transfer-channel-creation-properties.h @@ -0,0 +1,96 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_file_transfer_channel_creation_properties_h_HEADER_GUARD_ +#define _TelepathyQt_file_transfer_channel_creation_properties_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Global> + +#include <QDateTime> +#include <QMetaType> +#include <QSharedDataPointer> +#include <QString> + +namespace Tp +{ + +class TP_QT_EXPORT FileTransferChannelCreationProperties +{ +public: + FileTransferChannelCreationProperties(); + FileTransferChannelCreationProperties(const QString &suggestedFileName, + const QString &contentType, qulonglong size); + FileTransferChannelCreationProperties(const QString &path, + const QString &contentType); + FileTransferChannelCreationProperties( + const FileTransferChannelCreationProperties &other); + ~FileTransferChannelCreationProperties(); + + bool isValid() const { return mPriv.constData() != 0; } + + FileTransferChannelCreationProperties &operator=( + const FileTransferChannelCreationProperties &other); + bool operator==(const FileTransferChannelCreationProperties &other) const; + + FileTransferChannelCreationProperties &setContentHash( + FileHashType contentHashType, const QString &contentHash); + FileTransferChannelCreationProperties &setDescription( + const QString &description); + FileTransferChannelCreationProperties &setLastModificationTime( + const QDateTime &lastModificationTime); + FileTransferChannelCreationProperties &setUri(const QString &uri); + + /* mandatory parameters */ + QString suggestedFileName() const; + QString contentType() const; + qulonglong size() const; + + /* optional parameters */ + bool hasContentHash() const; + FileHashType contentHashType() const; + QString contentHash() const; + + bool hasDescription() const; + QString description() const; + + bool hasLastModificationTime() const; + QDateTime lastModificationTime() const; + + bool hasUri() const; + QString uri() const; + +private: + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::FileTransferChannelCreationProperties); + +#endif diff --git a/TelepathyQt/file-transfer-channel.cpp b/TelepathyQt/file-transfer-channel.cpp new file mode 100644 index 00000000..e81618fa --- /dev/null +++ b/TelepathyQt/file-transfer-channel.cpp @@ -0,0 +1,703 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/FileTransferChannel> + +#include "TelepathyQt/_gen/file-transfer-channel.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Connection> +#include <TelepathyQt/Types> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT FileTransferChannel::Private +{ + Private(FileTransferChannel *parent); + ~Private(); + + static void introspectProperties(Private *self); + + void extractProperties(const QVariantMap &props); + + // Public object + FileTransferChannel *parent; + + Client::ChannelTypeFileTransferInterface *fileTransferInterface; + Client::DBus::PropertiesInterface *properties; + + ReadinessHelper *readinessHelper; + + // Introspection + uint pendingState; + uint pendingStateReason; + uint state; + uint stateReason; + QString contentType; + QString fileName; + QString uri; + QString contentHash; + QString description; + QDateTime lastModificationTime; + FileHashType contentHashType; + qulonglong initialOffset; + qulonglong size; + qulonglong transferredBytes; + SupportedSocketMap availableSocketTypes; + + bool connected; + bool finished; +}; + +FileTransferChannel::Private::Private(FileTransferChannel *parent) + : parent(parent), + fileTransferInterface(parent->interface<Client::ChannelTypeFileTransferInterface>()), + properties(parent->interface<Client::DBus::PropertiesInterface>()), + readinessHelper(parent->readinessHelper()), + pendingState(FileTransferStateNone), + pendingStateReason(FileTransferStateChangeReasonNone), + state(pendingState), + stateReason(pendingStateReason), + contentHashType(FileHashTypeNone), + initialOffset(0), + size(0), + transferredBytes(0), + connected(false), + finished(false) +{ + parent->connect(fileTransferInterface, + SIGNAL(InitialOffsetDefined(qulonglong)), + SLOT(onInitialOffsetDefined(qulonglong))); + parent->connect(fileTransferInterface, + SIGNAL(FileTransferStateChanged(uint,uint)), + SLOT(onStateChanged(uint,uint))); + parent->connect(fileTransferInterface, + SIGNAL(TransferredBytesChanged(qulonglong)), + SLOT(onTransferredBytesChanged(qulonglong))); + + ReadinessHelper::Introspectables introspectables; + + ReadinessHelper::Introspectable introspectableCore( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << Channel::FeatureCore, // dependsOnFeatures (core) + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectProperties, + this); + introspectables[FeatureCore] = introspectableCore; + + readinessHelper->addIntrospectables(introspectables); +} + +FileTransferChannel::Private::~Private() +{ +} + +void FileTransferChannel::Private::introspectProperties( + FileTransferChannel::Private *self) +{ + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher( + self->properties->GetAll( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_TYPE_FILE_TRANSFER)), + self->parent); + self->parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotProperties(QDBusPendingCallWatcher*))); +} + +void FileTransferChannel::Private::extractProperties(const QVariantMap &props) +{ + pendingState = state = qdbus_cast<uint>(props[QLatin1String("State")]); + contentType = qdbus_cast<QString>(props[QLatin1String("ContentType")]); + fileName = qdbus_cast<QString>(props[QLatin1String("Filename")]); + uri = qdbus_cast<QString>(props[QLatin1String("URI")]); + contentHash = qdbus_cast<QString>(props[QLatin1String("ContentHash")]); + description = qdbus_cast<QString>(props[QLatin1String("Description")]); + lastModificationTime.setTime_t((uint) qdbus_cast<qulonglong>(props[QLatin1String("Date")])); + contentHashType = (FileHashType) qdbus_cast<uint>(props[QLatin1String("ContentHashType")]); + initialOffset = qdbus_cast<qulonglong>(props[QLatin1String("InitialOffset")]); + size = qdbus_cast<qulonglong>(props[QLatin1String("Size")]); + transferredBytes = qdbus_cast<qulonglong>(props[QLatin1String("TransferredBytes")]); + availableSocketTypes = qdbus_cast<SupportedSocketMap>(props[QLatin1String("AvailableSocketTypes")]); +} + +/** + * \class FileTransferChannel + * \ingroup clientchannel + * \headerfile TelepathyQt/file-transfer-channel.h <TelepathyQt/FileTransferChannel> + * + * \brief The FileTransferChannel class represents a Telepathy channel of type + * FileTransfer. + * + * For more specialized file transfer classes, please refer to + * OutgoingFileTransferChannel and IncomingFileTransferChannel. + * + * For more details, please refer to \telepathy_spec. + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Feature representing the core that needs to become ready to make the + * FileTransferChannel object usable. + * + * Note that this feature must be enabled in order to use most + * FileTransferChannel methods. + * See specific methods documentation for more details. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature FileTransferChannel::FeatureCore = Feature(QLatin1String(FileTransferChannel::staticMetaObject.className()), 0); + +/** + * Create a new FileTransferChannel 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 FileTransferChannelPtr object pointing to the newly created + * FileTransferChannel object. + */ +FileTransferChannelPtr FileTransferChannel::create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties) +{ + return FileTransferChannelPtr(new FileTransferChannel(connection, objectPath, + immutableProperties, FileTransferChannel::FeatureCore)); +} + +/** + * Construct a new FileTransferChannel 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, if any. The corresponding introspectable should + * depend on FileTransferChannel::FeatureCore. + */ +FileTransferChannel::FileTransferChannel(const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : Channel(connection, objectPath, immutableProperties, coreFeature), + mPriv(new Private(this)) +{ +} + +/** + * Class destructor. + */ +FileTransferChannel::~FileTransferChannel() +{ + delete mPriv; +} + +/** + * Return the state of the file transfer as described by #FileTransferState. + * + * Change notification is via the stateChanged() signal. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \return The state as #FileTransferState. + * \sa stateReason() + */ +FileTransferState FileTransferChannel::state() const +{ + if (!isReady(FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling state"; + } + + return (FileTransferState) mPriv->state; +} + +/** + * Return the reason for the state change as described by the #FileTransferStateChangeReason. + * + * Change notification is via the stateChanged() signal. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \return The state reason as #FileTransferStateChangeReason. + * \sa state() + */ +FileTransferStateChangeReason FileTransferChannel::stateReason() const +{ + if (!isReady(FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling stateReason"; + } + + return (FileTransferStateChangeReason) mPriv->stateReason; +} + +/** + * Return the name of the file on the sender's side. This is given as + * a suggested filename for the receiver. + * + * This property should be the basename of the file being sent. For example, if + * the sender sends the file /home/user/monkey.pdf then this property should be + * set to monkey.pdf. + * + * This property cannot change once the channel has been created. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \return The suggested filename for the receiver. + */ +QString FileTransferChannel::fileName() const +{ + if (!isReady(FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling fileName"; + } + + return mPriv->fileName; +} + +/** + * Return the file's MIME type. + * + * This property cannot change once the channel has been created. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \return The file's MIME type. + */ +QString FileTransferChannel::contentType() const +{ + if (!isReady(FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling contentType"; + } + + return mPriv->contentType; +} + +/** + * Return the size of the file. + * + * Note that the size is not guaranteed to be exactly right for + * incoming files. This is merely a hint and should not be used to know when the + * transfer finished. + * + * For unknown sizes the return value can be UINT64_MAX. + * + * This property cannot change once the channel has been created. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \return The file size. + */ +qulonglong FileTransferChannel::size() const +{ + if (!isReady(FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling size"; + } + + return mPriv->size; +} + +/** + * Return the URI of the file. + * + * On outgoing file transfers, this property cannot change after the channel + * is requested. For incoming file transfers, this property may be set by the + * channel handler before calling AcceptFile to inform observers where the + * incoming file will be saved. When the URI property is set, the signal + * IncomingFileTransferChannel::uriDefined() is emitted. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \return The file uri. + * \sa IncomingFileTransferChannel::uriDefined() + */ +QString FileTransferChannel::uri() const +{ + if (!isReady(FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling uri"; + } + + return mPriv->uri; +} + +/** + * Return the type of the contentHash(). + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \return The content hash type as #FileHashType. + * \sa contentHash() + */ +FileHashType FileTransferChannel::contentHashType() const +{ + if (!isReady(FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling contentHashType"; + } + + return mPriv->contentHashType; +} + +/** + * Return the hash of the contents of the file transfer, of type described in + * the value of the contentHashType(). + * + * Its value MUST correspond to the appropriate type of the contentHashType(). + * If the contentHashType() is set to #FileHashTypeNone, then the + * returned value is an empty string. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \return The hash of the contents. + * \sa contentHashType() + */ +QString FileTransferChannel::contentHash() const +{ + if (!isReady(FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling contentHash"; + } + + if (mPriv->contentHashType == FileHashTypeNone) { + return QString(); + } + + return mPriv->contentHash; +} + +/** + * Return the description of the file transfer. + * + * This property cannot change once the channel has been created. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \return The description. + */ +QString FileTransferChannel::description() const +{ + if (!isReady(FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling description"; + } + + return mPriv->description; +} + +/** + * Return the last modification time of the file being transferred. This cannot + * change once the channel has been created. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \return The file modification time as QDateTime. + */ +QDateTime FileTransferChannel::lastModificationTime() const +{ + if (!isReady(FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling lastModificationTime"; + } + + return mPriv->lastModificationTime; +} + +/** + * Return the offset in bytes from which the file will be sent. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \return The offset in bytes. + * \sa initialOffsetDefined() + */ +qulonglong FileTransferChannel::initialOffset() const +{ + if (!isReady(FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling initialOffset"; + } + + return mPriv->initialOffset; +} + +/** + * Return the number of bytes that have been transferred. + * + * Change notification is via the transferredBytesChanged() signal. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \return The number of bytes. + * \sa transferredBytesChanged() + */ +qulonglong FileTransferChannel::transferredBytes() const +{ + if (!isReady(FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling transferredBytes"; + } + + return mPriv->transferredBytes; +} + +/** + * Return a mapping from address types (members of #SocketAddressType) to arrays + * of access-control type (members of #SocketAccessControl) that the CM + * supports for sockets with that address type. + * + * For simplicity, if a CM supports offering a particular type of file transfer, + * it is assumed to support accepting it. All CMs support at least + * SocketAddressTypeIPv4. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \return The available socket types as a map from address types to arrays of access-control type. + */ +SupportedSocketMap FileTransferChannel::availableSocketTypes() const +{ + if (!isReady(FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling availableSocketTypes"; + } + + return mPriv->availableSocketTypes; +} + +/** + * Cancel a file transfer. + * + * \return A PendingOperation object which will emit PendingOperation::finished + * when the call has finished. + */ +PendingOperation *FileTransferChannel::cancel() +{ + return requestClose(); +} + +/** + * Protected virtual method called when the state becomes + * #FileTransferStateOpen. + * + * Specialized classes should reimplement this method and call setConnected() + * when the connection is established. + * + * \sa setConnected() + */ +void FileTransferChannel::connectToHost() +{ + // do nothing +} + +/** + * Return whether a connection has been established. + * + * \return \c true if the connections has been established, \c false otherwise. + * \sa setConnected() + */ +bool FileTransferChannel::isConnected() const +{ + return mPriv->connected; +} + +/** + * Indicate whether a connection has been established. + * + * Specialized classes that reimplement connectToHost() must call this method + * once the connection has been established or setFinished() if an error + * occurred. + * + * \sa isConnected(), connectToHost(), setFinished() + */ +void FileTransferChannel::setConnected() +{ + mPriv->connected = true; +} + +/** + * Return whether sending/receiving has finished. + * + * \return \c true if sending/receiving has finished, \c false otherwise. + */ +bool FileTransferChannel::isFinished() const +{ + return mPriv->finished; +} + +/** + * Protected virtual method called when an error occurred and the transfer + * should finish. + * + * Specialized classes should reimplement this method and close the IO devices + * and do all the needed cleanup. + * + * Note that for specialized classes that reimplement connectToHost() and set + * isConnected() to true, the state will not change to + * #FileTransferStateCompleted once the state change is received. + * + * When finished sending/receiving the specialized class MUST call this method + * and then the state will change to the latest pending state. + */ +void FileTransferChannel::setFinished() +{ + mPriv->finished = true; + + // do the actual state change, in case we are in + // FileTransferStateCompleted pendingState + changeState(); +} + +void FileTransferChannel::gotProperties(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + + if (!reply.isError()) { + QVariantMap props = reply.value(); + mPriv->extractProperties(props); + debug() << "Got reply to Properties::GetAll(FileTransferChannel)"; + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true); + } + else { + warning().nospace() << "Properties::GetAll(FileTransferChannel) failed " + "with " << reply.error().name() << ": " << reply.error().message(); + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, + reply.error()); + } +} + +void FileTransferChannel::changeState() +{ + if (mPriv->state == mPriv->pendingState) { + return; + } + + mPriv->state = mPriv->pendingState; + mPriv->stateReason = mPriv->pendingStateReason; + emit stateChanged((FileTransferState) mPriv->state, + (FileTransferStateChangeReason) mPriv->stateReason); +} + +void FileTransferChannel::onStateChanged(uint state, uint stateReason) +{ + if (state == (uint) mPriv->pendingState) { + return; + } + + debug() << "File transfer state changed to" << state << + "with reason" << stateReason; + mPriv->pendingState = (FileTransferState) state; + mPriv->pendingStateReason = (FileTransferStateChangeReason) stateReason; + + switch (state) { + case FileTransferStateOpen: + // try to connect to host, for handlers this + // connect to host, as the user called Accept/ProvideFile + // and have the host addr, for observers this will do nothing and + // everything will keep working + connectToHost(); + changeState(); + break; + case FileTransferStateCompleted: + //iIf already finished sending/receiving, just change the state, + // if not completed will only be set when: + // IncomingChannel: + // - The input socket closes + // OutgoingChannel: + // - Input EOF is reached or the output socket is closed + // + // we also check for connected as observers will never be connected + // and finished will never be set, but we need to work anyway. + if (mPriv->finished || !mPriv->connected) { + changeState(); + } + break; + case FileTransferStateCancelled: + // if already finished sending/receiving, just change the state, + // if not finish now and change the state + if (!mPriv->finished) { + setFinished(); + } else { + changeState(); + } + break; + default: + changeState(); + break; + } +} + +void FileTransferChannel::onInitialOffsetDefined(qulonglong initialOffset) +{ + mPriv->initialOffset = initialOffset; + emit initialOffsetDefined(initialOffset); +} + +void FileTransferChannel::onTransferredBytesChanged(qulonglong count) +{ + mPriv->transferredBytes = count; + emit transferredBytesChanged(count); +} + +void FileTransferChannel::onUriDefined(const QString &uri) +{ + mPriv->uri = uri; + // Signal is emitted only by IncomingFileTransferChannels +} + +/** + * \fn void FileTransferChannel::stateChanged(Tp::FileTransferState state, + * Tp::FileTransferStateChangeReason reason) + * + * Emitted when the value of state() changes. + * + * \param state The new state of this file transfer channel. + * \param reason The reason for the change of state. + * \sa state() + */ + +/** + * \fn void FileTransferChannel::initialOffsetDefined(qulonglong initialOffset) + * + * Emitted when the initial offset for the file transfer is + * defined. + * + * \param initialOffset The new initial offset for the file transfer. + * \sa initialOffset() + */ + +/** + * \fn void FileTransferChannel::transferredBytesChanged(qulonglong count); + * + * Emitted when the value of transferredBytes() changes. + * + * \param count The new number of bytes transferred. + * \sa transferredBytes() + */ + +} // Tp diff --git a/TelepathyQt/file-transfer-channel.h b/TelepathyQt/file-transfer-channel.h new file mode 100644 index 00000000..a904b840 --- /dev/null +++ b/TelepathyQt/file-transfer-channel.h @@ -0,0 +1,108 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_file_transfer_channel_h_HEADER_GUARD_ +#define _TelepathyQt_file_transfer_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Channel> + +namespace Tp +{ + +class TP_QT_EXPORT FileTransferChannel : public Channel +{ + Q_OBJECT + Q_DISABLE_COPY(FileTransferChannel) + +public: + static const Feature FeatureCore; + + static FileTransferChannelPtr create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties); + + virtual ~FileTransferChannel(); + + FileTransferState state() const; + FileTransferStateChangeReason stateReason() const; + + QString fileName() const; + QString contentType() const; + qulonglong size() const; + QString uri() const; + + FileHashType contentHashType() const; + QString contentHash() const; + + QString description() const; + + QDateTime lastModificationTime() const; + + qulonglong initialOffset() const; + + qulonglong transferredBytes() const; + + PendingOperation *cancel(); + +Q_SIGNALS: + void stateChanged(Tp::FileTransferState state, + Tp::FileTransferStateChangeReason reason); + void initialOffsetDefined(qulonglong initialOffset); + void transferredBytesChanged(qulonglong count); + +protected: + FileTransferChannel(const ConnectionPtr &connection, const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature = FileTransferChannel::FeatureCore); + + SupportedSocketMap availableSocketTypes() const; + + virtual void connectToHost(); + bool isConnected() const; + void setConnected(); + + bool isFinished() const; + virtual void setFinished(); + +private Q_SLOTS: + TP_QT_NO_EXPORT void gotProperties(QDBusPendingCallWatcher *watcher); + + TP_QT_NO_EXPORT void changeState(); + TP_QT_NO_EXPORT void onStateChanged(uint state, uint stateReason); + TP_QT_NO_EXPORT void onInitialOffsetDefined(qulonglong initialOffset); + TP_QT_NO_EXPORT void onTransferredBytesChanged(qulonglong count); + +protected Q_SLOTS: + TP_QT_NO_EXPORT void onUriDefined(const QString &uri); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/filter.dox b/TelepathyQt/filter.dox new file mode 100644 index 00000000..23aaaab1 --- /dev/null +++ b/TelepathyQt/filter.dox @@ -0,0 +1,30 @@ +/* + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +/** + * \class Tp::Filter + * \ingroup utils + * \headerfile TelepathyQt/filter.h <TelepathyQt/Filter> + * + * \brief The Filter class provides a base class to be used by specialized + * filters such as GenericCapabilityFilter, GenericPropertyFilter, etc. + */ diff --git a/TelepathyQt/filter.h b/TelepathyQt/filter.h new file mode 100644 index 00000000..33a5a8ca --- /dev/null +++ b/TelepathyQt/filter.h @@ -0,0 +1,63 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_filter_h_HEADER_GUARD_ +#define _TelepathyQt_filter_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/SharedPtr> +#include <TelepathyQt/Types> + +namespace Tp +{ + +template <class T> +class Filter : public RefCounted +{ + Q_DISABLE_COPY(Filter) + +public: + virtual ~Filter() {} + + virtual bool isValid() const { return false; } + + virtual bool matches(const SharedPtr<T> &t) const + { + Q_UNUSED(t); + + return false; + } + +protected: + Filter() {} + +private: + struct Private; + Private *mPriv; // Just a placeholder really +}; + +} // Tp + +#endif diff --git a/TelepathyQt/fixed-feature-factory.cpp b/TelepathyQt/fixed-feature-factory.cpp new file mode 100644 index 00000000..3c285c0c --- /dev/null +++ b/TelepathyQt/fixed-feature-factory.cpp @@ -0,0 +1,116 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/FixedFeatureFactory> + +#include "TelepathyQt/_gen/fixed-feature-factory.moc.hpp" + +#include <TelepathyQt/Feature> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT FixedFeatureFactory::Private +{ + Features features; +}; + +/** + * \class FixedFeatureFactory + * \ingroup utils + * \headerfile TelepathyQt/fixed-feature-factory.h <TelepathyQt/FixedFeatureFactory> + * + * \brief The FixedFeatureFactory class is a base class for all D-Bus proxy + * factories which want the same set of features for all constructed proxies. + */ + +/** + * Class constructor. + * + * The intention for storing the bus here is that it generally doesn't make sense to construct + * proxies for multiple buses in the same context. Allowing that would lead to more complex keying + * needs in the cache, as well. + * + * \param bus The D-Bus bus connection for the objects constructed using this factory. + */ +FixedFeatureFactory::FixedFeatureFactory(const QDBusConnection &bus) + : DBusProxyFactory(bus), mPriv(new Private) +{ +} + +/** + * Class destructor. + */ +FixedFeatureFactory::~FixedFeatureFactory() +{ + delete mPriv; +} + +/** + * Gets the features this factory will make ready on constructed proxies. + * + * \return The set of features. + */ +Features FixedFeatureFactory::features() const +{ + return mPriv->features; +} + +/** + * Adds a single feature this factory will make ready on further constructed proxies. + * + * No feature removal is provided, to guard against uncooperative modules removing features other + * modules have set and depend on. + * + * \param feature The feature to add. + */ +void FixedFeatureFactory::addFeature(const Feature &feature) +{ + addFeatures(Features(feature)); +} + +/** + * Adds a set of features this factory will make ready on further constructed proxies. + * + * No feature removal is provided, to guard against uncooperative modules removing features other + * modules have set and depend on. + * + * \param features The features to add. + */ +void FixedFeatureFactory::addFeatures(const Features &features) +{ + mPriv->features.unite(features); +} + +/** + * Fixed implementation of the per-proxy feature getter. + * + * \return features(), irrespective of the actual \a proxy. + */ +Features FixedFeatureFactory::featuresFor(const DBusProxyPtr &proxy) const +{ + Q_UNUSED(proxy); + + return features(); +} + +} diff --git a/TelepathyQt/fixed-feature-factory.h b/TelepathyQt/fixed-feature-factory.h new file mode 100644 index 00000000..62672c7c --- /dev/null +++ b/TelepathyQt/fixed-feature-factory.h @@ -0,0 +1,69 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_fixed_feature_factory_h_HEADER_GUARD_ +#define _TelepathyQt_fixed_feature_factory_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> +#include <TelepathyQt/SharedPtr> + +#include <TelepathyQt/DBusProxyFactory> + +class QDBusConnection; + +namespace Tp +{ + +class Feature; +class Features; + +class TP_QT_EXPORT FixedFeatureFactory : public DBusProxyFactory +{ + Q_OBJECT + Q_DISABLE_COPY(FixedFeatureFactory) + +public: + virtual ~FixedFeatureFactory(); + + Features features() const; + + void addFeature(const Feature &feature); + void addFeatures(const Features &features); + +protected: + FixedFeatureFactory(const QDBusConnection &bus); + + virtual Features featuresFor(const DBusProxyPtr &proxy) const; + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/future-channel-dispatcher.xml b/TelepathyQt/future-channel-dispatcher.xml new file mode 100644 index 00000000..0e7f67c5 --- /dev/null +++ b/TelepathyQt/future-channel-dispatcher.xml @@ -0,0 +1,20 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Channel Dispatcher extensions from the future</tp:title> + +<xi:include href="../spec/Channel_Dispatcher_Interface_Messages.xml"/> + +<tp:generic-types> + <tp:external-type name="Message_Part" type="a{sv}" from="Telepathy specification"/> + <tp:mapping name="Message_Part" array-name="Message_Part_List" array-depth="2"> + <tp:member name="Key" type="s"/> + <tp:member name="Value" type="v"/> + </tp:mapping> +</tp:generic-types> + +<xi:include href="../spec/generic-types.xml"/> +<xi:include href="../spec/errors.xml"/> + +</tp:spec> diff --git a/TelepathyQt/future-channel.xml b/TelepathyQt/future-channel.xml new file mode 100644 index 00000000..6cf1e494 --- /dev/null +++ b/TelepathyQt/future-channel.xml @@ -0,0 +1,13 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Channel extensions from the future</tp:title> + +<xi:include href="../spec/Channel_Interface_Mergeable_Conference.xml"/> +<xi:include href="../spec/Channel_Interface_Splittable.xml"/> + +<xi:include href="../spec/generic-types.xml"/> +<xi:include href="../spec/errors.xml"/> + +</tp:spec> diff --git a/TelepathyQt/future-interfaces.xml b/TelepathyQt/future-interfaces.xml new file mode 100644 index 00000000..8cc5d753 --- /dev/null +++ b/TelepathyQt/future-interfaces.xml @@ -0,0 +1,14 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Extensions from the future</tp:title> + +<xi:include href="future-channel.xml"/> +<xi:include href="future-channel-dispatcher.xml"/> +<xi:include href="future-misc.xml"/> + +<xi:include href="../spec/generic-types.xml"/> +<xi:include href="../spec/errors.xml"/> + +</tp:spec> diff --git a/TelepathyQt/future-internal.h b/TelepathyQt/future-internal.h new file mode 100644 index 00000000..e472a9c9 --- /dev/null +++ b/TelepathyQt/future-internal.h @@ -0,0 +1,36 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_future_internal_h_HEADER_GUARD_ +#define _TelepathyQt_future_internal_h_HEADER_GUARD_ + +#include "TelepathyQt/_gen/future-constants.h" +#include "TelepathyQt/_gen/future-types.h" + +#include "TelepathyQt/Channel" +#include "TelepathyQt/ChannelDispatcher" + +#include "TelepathyQt/_gen/future-channel.h" +#include "TelepathyQt/_gen/future-channel-dispatcher.h" +#include "TelepathyQt/_gen/future-misc.h" + +#endif diff --git a/TelepathyQt/future-misc.xml b/TelepathyQt/future-misc.xml new file mode 100644 index 00000000..b756661e --- /dev/null +++ b/TelepathyQt/future-misc.xml @@ -0,0 +1,7 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Miscellaneous extensions from the future</tp:title> + +</tp:spec> diff --git a/TelepathyQt/future.cpp b/TelepathyQt/future.cpp new file mode 100644 index 00000000..cefe8e5f --- /dev/null +++ b/TelepathyQt/future.cpp @@ -0,0 +1,32 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 "TelepathyQt/future-internal.h" + +#include "TelepathyQt/_gen/future-channel-body.hpp" +#include "TelepathyQt/_gen/future-channel.moc.hpp" + +#include "TelepathyQt/_gen/future-channel-dispatcher-body.hpp" +#include "TelepathyQt/_gen/future-channel-dispatcher.moc.hpp" + +#include "TelepathyQt/_gen/future-misc-body.hpp" +#include "TelepathyQt/_gen/future-misc.moc.hpp" diff --git a/TelepathyQt/generic-capability-filter.dox b/TelepathyQt/generic-capability-filter.dox new file mode 100644 index 00000000..53d70ae7 --- /dev/null +++ b/TelepathyQt/generic-capability-filter.dox @@ -0,0 +1,36 @@ +/* + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +/** + * \class Tp::GenericCapabilityFilter + * \ingroup utils + * \headerfile TelepathyQt/generic-capability-filter.h <TelepathyQt/GenericCapabilityFilter> + * + * \brief The GenericCapabilityFilter class provides a generic filter object to + * be used to filter objects by capabilities. + * + * The objects used in conjunction with this filter must implement a method + * called capabilities() returning a CapabilitiesBase (or a subclass of it) + * instance. + * Specialized classes such as AccountCapabilityFilter are also provided and + * should be used where appropriate. + */ diff --git a/TelepathyQt/generic-capability-filter.h b/TelepathyQt/generic-capability-filter.h new file mode 100644 index 00000000..ece57895 --- /dev/null +++ b/TelepathyQt/generic-capability-filter.h @@ -0,0 +1,114 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_generic_capability_filter_h_HEADER_GUARD_ +#define _TelepathyQt_generic_capability_filter_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/ConnectionCapabilities> +#include <TelepathyQt/Filter> +#include <TelepathyQt/Types> + +namespace Tp +{ + +template <class T> +class GenericCapabilityFilter : public Filter<T> +{ +public: + static SharedPtr<GenericCapabilityFilter<T> > create( + const RequestableChannelClassSpecList &rccSpecs = RequestableChannelClassSpecList()) + { + return SharedPtr<GenericCapabilityFilter<T> >(new GenericCapabilityFilter<T>( + rccSpecs)); + } + + inline virtual ~GenericCapabilityFilter() { } + + inline virtual bool isValid() const { return true; } + + inline virtual bool matches(const SharedPtr<T> &t) const + { + bool supportedRcc; + RequestableChannelClassSpecList objectRccSpecs = t->capabilities().allClassSpecs(); + Q_FOREACH (const RequestableChannelClassSpec &filterRccSpec, mFilter) { + supportedRcc = false; + + Q_FOREACH (const RequestableChannelClassSpec &objectRccSpec, objectRccSpecs) { + /* check if fixed properties match */ + if (filterRccSpec.fixedProperties() == objectRccSpec.fixedProperties()) { + supportedRcc = true; + + /* check if all allowed properties in the filter RCC + * are in the object RCC allowed properties */ + Q_FOREACH (const QString &value, filterRccSpec.allowedProperties()) { + if (!objectRccSpec.allowsProperty(value)) { + /* one of the properties in the filter RCC + * allowed properties is not in the object RCC + * allowed properties */ + supportedRcc = false; + break; + } + } + + /* this RCC is supported, no need to check anymore */ + if (supportedRcc) { + break; + } + } + } + + /* one of the filter RCC is not supported, this object + * won't match filter */ + if (!supportedRcc) { + return false; + } + } + + return true; + } + + inline RequestableChannelClassSpecList filter() const { return mFilter; } + + inline void addRequestableChannelClassSubset(const RequestableChannelClassSpec &rccSpec) + { + mFilter.append(rccSpec.bareClass()); + } + + inline void setRequestableChannelClassesSubset(const RequestableChannelClassSpecList &rccSpecs) + { + mFilter = rccSpecs.bareClasses(); + } + +private: + GenericCapabilityFilter(const RequestableChannelClassSpecList &rccSpecs) + : Filter<T>(), mFilter(rccSpecs) { } + + RequestableChannelClassSpecList mFilter; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/generic-property-filter.dox b/TelepathyQt/generic-property-filter.dox new file mode 100644 index 00000000..12a42451 --- /dev/null +++ b/TelepathyQt/generic-property-filter.dox @@ -0,0 +1,33 @@ +/* + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +/** + * \class Tp::GenericPropertyFilter + * \ingroup utils + * \headerfile TelepathyQt/generic-property-filter.h <TelepathyQt/GenericPropertyFilter> + * + * \brief The GenericPropertyFilter class provides a generic filter object + * to be used to filter objects by properties. + * + * Specialized classes such as AccountPropertyFilter are also provided and + * should be used where appropriate. + */ diff --git a/TelepathyQt/generic-property-filter.h b/TelepathyQt/generic-property-filter.h new file mode 100644 index 00000000..ac56b595 --- /dev/null +++ b/TelepathyQt/generic-property-filter.h @@ -0,0 +1,77 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_generic_property_filter_h_HEADER_GUARD_ +#define _TelepathyQt_generic_property_filter_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Filter> +#include <TelepathyQt/Types> + +namespace Tp +{ + +template <class T> +class GenericPropertyFilter : public Filter<T> +{ +public: + inline virtual ~GenericPropertyFilter() { } + + inline virtual bool isValid() const { return true; } + + inline virtual bool matches(const SharedPtr<T> &t) const + { + for (QVariantMap::const_iterator i = mFilter.constBegin(); + i != mFilter.constEnd(); ++i) { + QString propertyName = i.key(); + QVariant propertyValue = i.value(); + + if (t->property(propertyName.toLatin1().constData()) != propertyValue) { + return false; + } + } + + return true; + } + + inline QVariantMap filter() const { return mFilter; } + + inline void addProperty(const QString &propertyName, const QVariant &propertyValue) + { + mFilter.insert(propertyName, propertyValue); + } + + inline void setProperties(const QVariantMap &filter) { mFilter = filter; } + +protected: + inline GenericPropertyFilter() : Filter<T>() { } + +private: + QVariantMap mFilter; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/global.h b/TelepathyQt/global.h new file mode 100644 index 00000000..eb2111ec --- /dev/null +++ b/TelepathyQt/global.h @@ -0,0 +1,103 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_global_h_HEADER_GUARD_ +#define _TelepathyQt_global_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <QtGlobal> + +#ifdef BUILDING_TP_QT +# define TP_QT_EXPORT Q_DECL_EXPORT +#else +# define TP_QT_EXPORT Q_DECL_IMPORT +#endif + +#if !defined(Q_OS_WIN) && defined(QT_VISIBILITY_AVAILABLE) +# define TP_QT_NO_EXPORT __attribute__((visibility("hidden"))) +#endif + +#ifndef TP_QT_NO_EXPORT +# define TP_QT_NO_EXPORT +#endif + +/** + * @def TP_QT_DEPRECATED + * @ingroup TELEPATHY_QT4_MACROS + * + * The TP_QT_DEPRECATED macro can be used to trigger compile-time + * warnings with newer compilers when deprecated functions are used. + * + * For non-inline functions, the macro gets inserted at front of the + * function declaration, right before the return type: + * + * \code + * TP_QT_DEPRECATED void deprecatedFunctionA(); + * TP_QT_DEPRECATED int deprecatedFunctionB() const; + * \endcode + * + * For functions which are implemented inline, + * the TP_QT_DEPRECATED macro is inserted at the front, right before the + * return type, but after "static", "inline" or "virtual": + * + * \code + * TP_QT_DEPRECATED void deprecatedInlineFunctionA() { .. } + * virtual TP_QT_DEPRECATED int deprecatedInlineFunctionB() { .. } + * static TP_QT_DEPRECATED bool deprecatedInlineFunctionC() { .. } + * inline TP_QT_DEPRECATED bool deprecatedInlineFunctionD() { .. } + * \endcode + * + * You can also mark whole structs or classes as deprecated, by inserting the + * TP_QT_DEPRECATED macro after the struct/class keyword, but before the + * name of the struct/class: + * + * \code + * class TP_QT_DEPRECATED DeprecatedClass { }; + * struct TP_QT_DEPRECATED DeprecatedStruct { }; + * \endcode + * + * \note + * It does not make much sense to use the TP_QT_DEPRECATED keyword for a + * Qt signal; this is because usually get called by the class which they belong + * to, and one would assume that a class author does not use deprecated methods + * of his own class. The only exception to this are signals which are connected + * to other signals; they get invoked from moc-generated code. In any case, + * printing a warning message in either case is not useful. + * For slots, it can make sense (since slots can be invoked directly) but be + * aware that if the slots get triggered by a signal, they will get called from + * moc code as well and thus the warnings are useless. + * + * \note + * TP_QT_DEPRECATED cannot be used for constructors. + */ +#ifndef TP_QT_DEPRECATED +# ifdef TP_QT_DEPRECATED_WARNINGS +# define TP_QT_DEPRECATED Q_DECL_DEPRECATED +# else +# define TP_QT_DEPRECATED +# endif +#endif + +#endif diff --git a/TelepathyQt/groups.dox b/TelepathyQt/groups.dox new file mode 100644 index 00000000..bf7d34b3 --- /dev/null +++ b/TelepathyQt/groups.dox @@ -0,0 +1,115 @@ +/* + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * + * 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 + */ + +/** + * \defgroup clientsideproxies Client-side proxies + * + * Proxy objects representing remote service objects accessed via D-Bus. + * + * In addition to providing direct access to methods, signals and properties + * exported by the remote objects, some of these proxies offer features like + * automatic inspection of remote object capabilities, property tracking, + * backwards compatibility helpers for older services and other utilities. + */ + +/** + * \defgroup clientaccount Account proxies + * \ingroup clientsideproxies + * + * Proxy objects representing remote Telepathy account objects and their + * optional interfaces. + */ + +/** + * \defgroup clientam Account manager proxies + * \ingroup clientsideproxies + * + * Proxy objects representing remote Telepathy account manager objects and their + * optional interfaces. + */ + +/** + * \defgroup clientchannel Channel proxies + * \ingroup clientsideproxies + * + * Proxy objects representing remote Telepathy Channel objects and their + * optional interfaces. + */ + +/** + * \defgroup clientchanneldispatcher ChannelDispatcher proxies + * \ingroup clientsideproxies + * + * Proxy objects representing remote Telepathy ChannelDispatcher objects and + * their optional interfaces. + */ + +/** + * \defgroup clientchanneldispatchoperation ChannelDispatchOperation proxies + * \ingroup clientsideproxies + * + * Proxy objects representing remote Telepathy ChannelDispatchOperation objects + * and their optional interfaces. + */ + +/** + * \defgroup clientchannelrequest ChannelRequest proxies + * \ingroup clientsideproxies + * + * Proxy objects representing remote Telepathy ChannelRequest objects and their + * optional interfaces. + */ + +/** + * \defgroup clientclient Client proxies + * \ingroup clientsideproxies + * + * Proxy objects representing remote Telepathy Client objects (approvers, + * handlers and observers) and their optional interfaces. + */ + +/** + * \defgroup clientcm Connection manager proxies + * \ingroup clientsideproxies + * + * Proxy objects representing remote Telepathy ConnectionManager objects and + * their optional interfaces. + */ + +/** + * \defgroup clientconn Connection proxies + * \ingroup clientsideproxies + * + * Proxy objects representing remote Telepathy Connection objects and their + * optional interfaces. + */ + +/** + * \defgroup wrappers Wrapper classes + * + * Wrapper classes representing a Telepathy type. + */ + +/** + * \defgroup utils Utililty classes + * + * Utility classes. + */ diff --git a/TelepathyQt/handled-channel-notifier.cpp b/TelepathyQt/handled-channel-notifier.cpp new file mode 100644 index 00000000..47b2b55b --- /dev/null +++ b/TelepathyQt/handled-channel-notifier.cpp @@ -0,0 +1,103 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 <TelepathyQt/HandledChannelNotifier> + +#include "TelepathyQt/_gen/handled-channel-notifier.moc.hpp" + +#include "TelepathyQt/debug-internal.h" +#include "TelepathyQt/request-temporary-handler-internal.h" + +#include <TelepathyQt/ChannelRequestHints> +#include <TelepathyQt/ClientRegistrar> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT HandledChannelNotifier::Private +{ + Private(const ClientRegistrarPtr &cr, + const SharedPtr<RequestTemporaryHandler> &handler) + : cr(cr), + handler(handler), + channel(handler->channel()) + { + } + + ClientRegistrarPtr cr; + SharedPtr<RequestTemporaryHandler> handler; + ChannelPtr channel; // needed to keep channel alive, since RTH maintains only a weak ref +}; + +/** + * \class HandledChannelNotifier + * \ingroup clientchannel + * \headerfile TelepathyQt/handled-channel-notifier.h <TelepathyQt/HandledChannelNotifier> + * + * \brief The HandledChannelNotifier class can be used to keep track of + * channel() being re-requested. + * + * Instances of this class cannot be constructed directly; the only way to get + * one is trough PendingChannel. + */ + +HandledChannelNotifier::HandledChannelNotifier(const ClientRegistrarPtr &cr, + const SharedPtr<RequestTemporaryHandler> &handler) + : mPriv(new Private(cr, handler)) +{ + connect(mPriv->channel.data(), + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + SLOT(onChannelInvalidated())); + connect(handler.data(), + SIGNAL(channelReceived(Tp::ChannelPtr,QDateTime,Tp::ChannelRequestHints)), + SLOT(onChannelReceived(Tp::ChannelPtr,QDateTime,Tp::ChannelRequestHints))); +} + +HandledChannelNotifier::~HandledChannelNotifier() +{ + delete mPriv; +} + +ChannelPtr HandledChannelNotifier::channel() const +{ + return mPriv->channel; +} + +void HandledChannelNotifier::onChannelReceived(const Tp::ChannelPtr &channel, + const QDateTime &userActionTime, const Tp::ChannelRequestHints &requestHints) +{ + emit handledAgain(userActionTime, requestHints); +} + +void HandledChannelNotifier::onChannelInvalidated() +{ + deleteLater(); +} + +void HandledChannelNotifier::connectNotify(const char *signalName) +{ + if (qstrcmp(signalName, SIGNAL(handledAgain(QDateTime,Tp::ChannelRequestHints))) == 0) { + mPriv->handler->setQueueChannelReceived(false); + } +} + +} // Tp diff --git a/TelepathyQt/handled-channel-notifier.h b/TelepathyQt/handled-channel-notifier.h new file mode 100644 index 00000000..0dab6af7 --- /dev/null +++ b/TelepathyQt/handled-channel-notifier.h @@ -0,0 +1,75 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +#ifndef _TelepathyQt_handled_channel_notifier_h_HEADER_GUARD_ +#define _TelepathyQt_handled_channel_notifier_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Channel> +#include <TelepathyQt/Types> + +#include <QObject> + +namespace Tp +{ + +class ChannelRequestHints; +class RequestTemporaryHandler; + +class TP_QT_EXPORT HandledChannelNotifier : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(HandledChannelNotifier) + +public: + ~HandledChannelNotifier(); + + ChannelPtr channel() const; + +Q_SIGNALS: + void handledAgain(const QDateTime &userActionTime, const Tp::ChannelRequestHints &requestHints); + +protected: + void connectNotify(const char *); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onChannelReceived(const Tp::ChannelPtr &channel, + const QDateTime &userActionTime, const Tp::ChannelRequestHints &requestHints); + TP_QT_NO_EXPORT void onChannelInvalidated(); + +private: + friend class PendingChannel; + + HandledChannelNotifier(const ClientRegistrarPtr &cr, + const SharedPtr<RequestTemporaryHandler> &handler); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/incoming-file-transfer-channel.cpp b/TelepathyQt/incoming-file-transfer-channel.cpp new file mode 100644 index 00000000..3af20197 --- /dev/null +++ b/TelepathyQt/incoming-file-transfer-channel.cpp @@ -0,0 +1,390 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/IncomingFileTransferChannel> + +#include "TelepathyQt/_gen/incoming-file-transfer-channel.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Connection> +#include <TelepathyQt/PendingFailure> +#include <TelepathyQt/PendingVariant> +#include <TelepathyQt/Types> +#include <TelepathyQt/types-internal.h> + +#include <QIODevice> +#include <QTcpSocket> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT IncomingFileTransferChannel::Private +{ + Private(IncomingFileTransferChannel *parent); + ~Private(); + + // Public object + IncomingFileTransferChannel *parent; + + Client::ChannelTypeFileTransferInterface *fileTransferInterface; + + QIODevice *output; + QTcpSocket *socket; + SocketAddressIPv4 addr; + + qulonglong requestedOffset; + qint64 pos; +}; + +IncomingFileTransferChannel::Private::Private(IncomingFileTransferChannel *parent) + : parent(parent), + fileTransferInterface(parent->interface<Client::ChannelTypeFileTransferInterface>()), + output(0), + socket(0), + requestedOffset(0), + pos(0) +{ + parent->connect(fileTransferInterface, + SIGNAL(URIDefined(QString)), + SLOT(onUriDefined(QString))); + parent->connect(fileTransferInterface, + SIGNAL(URIDefined(QString)), + SIGNAL(uriDefined(QString))); +} + +IncomingFileTransferChannel::Private::~Private() +{ +} + +/** + * \class IncomingFileTransferChannel + * \ingroup clientchannel + * \headerfile TelepathyQt/incoming-file-transfer-channel.h <TelepathyQt/IncomingFileTransferChannel> + * + * \brief The IncomingFileTransferChannel class represents a Telepathy channel + * of type FileTransfer for incoming file transfers. + * + * For more details, please refer to \telepathy_spec. + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Feature representing the core that needs to become ready to make the + * IncomingFileTransferChannel object usable. + * + * This is currently the same as FileTransferChannel::FeatureCore, but may change to include more. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature IncomingFileTransferChannel::FeatureCore = + Feature(QLatin1String(FileTransferChannel::staticMetaObject.className()), 0); // FT::FeatureCore + +/** + * Create a new IncomingFileTransferChannel 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 IncomingFileTransferChannelPtr object pointing to the newly created + * IncomingFileTransfer object. + */ +IncomingFileTransferChannelPtr IncomingFileTransferChannel::create( + const ConnectionPtr &connection, const QString &objectPath, + const QVariantMap &immutableProperties) +{ + return IncomingFileTransferChannelPtr(new IncomingFileTransferChannel( + connection, objectPath, immutableProperties, + IncomingFileTransferChannel::FeatureCore)); +} + +/** + * Construct a new IncomingFileTransferChannel 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, if any. The corresponding introspectable should + * depend on IncomingFileTransferChannel::FeatureCore. + */ +IncomingFileTransferChannel::IncomingFileTransferChannel( + const ConnectionPtr &connection, const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : FileTransferChannel(connection, objectPath, immutableProperties, coreFeature), + mPriv(new Private(this)) +{ +} + +/** + * Class destructor. + */ +IncomingFileTransferChannel::~IncomingFileTransferChannel() +{ + delete mPriv; +} + +/** + * Set the URI where the file will be saved. + * + * This property may be set by the channel handler before calling AcceptFile to inform observers + * where the incoming file will be saved. When the URI property is set, the signal + * uriDefined() is emitted. + * + * This method requires IncomingFileTransferChannel::FeatureCore to be ready. + * + * \param uri The URI where the file will be saved. + * \return A PendingOperation object which will emit PendingOperation::finished + * when the call has finished. + * \sa FileTransferChannel::uri(), uriDefined() + */ +PendingOperation *IncomingFileTransferChannel::setUri(const QString& uri) +{ + if (!isReady(FileTransferChannel::FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling setUri"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + IncomingFileTransferChannelPtr(this)); + } + + if (state() != FileTransferStatePending) { + warning() << "setUri must be called before calling acceptFile"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Cannot set URI after calling acceptFile"), + IncomingFileTransferChannelPtr(this)); + } + + return mPriv->fileTransferInterface->setPropertyURI(uri); +} + +/** + * Accept a file transfer that's in the #FileTransferStatePending state(). + * + * The state will change to #FileTransferStateOpen as soon as the transfer + * starts. + * The given output device should not be closed/destroyed until the state() + * changes to #FileTransferStateCompleted or #FileTransferStateCancelled. + * + * Only the primary handler of a file transfer channel may call this method. + * + * This method requires IncomingFileTransferChannel::FeatureCore to be ready. + * + * \param offset The desired offset in bytes where the file transfer should + * start. The offset is taken from the beginning of the file. + * Specifying an offset of zero will start the transfer from the + * beginning of the file. The offset that is actually given in the + * initialOffset() method can differ from this argument where the + * requested offset is not supported. (For example, some + * protocols do not support offsets at all so the initialOffset() + * will always be 0.). + * \param output A QIODevice object where the data will be written to. The + * device should be ready to use when the state() changes to + * #FileTransferStateCompleted. + * If the transfer is cancelled, state() becomes + * #FileTransferStateCancelled, the data in \a output should be + * ignored + * \return A PendingOperation object which will emit PendingOperation::finished + * when the call has finished. + * \sa FileTransferChannel::stateChanged(), FileTransferChannel::state(), + * FileTransferChannel::stateReason(), FileTransferChannel::initialOffset() + */ +PendingOperation *IncomingFileTransferChannel::acceptFile(qulonglong offset, + QIODevice *output) +{ + if (!isReady(FileTransferChannel::FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling acceptFile"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + IncomingFileTransferChannelPtr(this)); + } + + // let's fail here direclty as we may only have one device to handle + if (mPriv->output) { + warning() << "File transfer can only be started once in the same " + "channel"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("File transfer can only be started once in the same channel"), + IncomingFileTransferChannelPtr(this)); + } + + if ((!output->isOpen() && !output->open(QIODevice::WriteOnly)) && + (!output->isWritable())) { + warning() << "Unable to open IO device for writing"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_PERMISSION_DENIED), + QLatin1String("Unable to open IO device for writing"), + IncomingFileTransferChannelPtr(this)); + } + + mPriv->output = output; + + mPriv->requestedOffset = offset; + + PendingVariant *pv = new PendingVariant( + mPriv->fileTransferInterface->AcceptFile(SocketAddressTypeIPv4, + SocketAccessControlLocalhost, QDBusVariant(QVariant(QString())), + offset), + IncomingFileTransferChannelPtr(this)); + connect(pv, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onAcceptFileFinished(Tp::PendingOperation*))); + return pv; +} + +void IncomingFileTransferChannel::onAcceptFileFinished(PendingOperation *op) +{ + if (op->isError()) { + warning() << "Error accepting file transfer " << + op->errorName() << ":" << op->errorMessage(); + invalidate(op->errorName(), op->errorMessage()); + return; + } + + PendingVariant *pv = qobject_cast<PendingVariant *>(op); + mPriv->addr = qdbus_cast<SocketAddressIPv4>(pv->result()); + debug().nospace() << "Got address " << mPriv->addr.address << + ":" << mPriv->addr.port; + + if (state() == FileTransferStateOpen) { + // now we have the address and we are already opened, + // connect to host + connectToHost(); + } +} + +void IncomingFileTransferChannel::connectToHost() +{ + if (isConnected() || mPriv->addr.address.isNull()) { + return; + } + + // we already have initialOffsetDefined, called before State became Open, so + // let's make sure everything is ok. + if (initialOffset() > mPriv->requestedOffset) { + // either the CM or the sender is doing something really wrong here, + // cancel the transfer. + warning() << "InitialOffset bigger than requested offset, " + "cancelling the transfer"; + cancel(); + invalidate(TP_QT_ERROR_INCONSISTENT, + QLatin1String("Initial offset bigger than requested offset")); + return; + } + + mPriv->pos = initialOffset(); + + mPriv->socket = new QTcpSocket(this); + + connect(mPriv->socket, SIGNAL(connected()), + SLOT(onSocketConnected())); + connect(mPriv->socket, SIGNAL(disconnected()), + SLOT(onSocketDisconnected())); + connect(mPriv->socket, SIGNAL(error(QAbstractSocket::SocketError)), + SLOT(onSocketError(QAbstractSocket::SocketError))); + connect(mPriv->socket, SIGNAL(readyRead()), + SLOT(doTransfer())); + + debug().nospace() << "Connecting to host " << + mPriv->addr.address << ":" << mPriv->addr.port << "..."; + mPriv->socket->connectToHost(mPriv->addr.address, mPriv->addr.port); +} + +void IncomingFileTransferChannel::onSocketConnected() +{ + debug() << "Connected to host"; + setConnected(); + + doTransfer(); +} + +void IncomingFileTransferChannel::onSocketDisconnected() +{ + debug() << "Disconnected from host"; + setFinished(); +} + +void IncomingFileTransferChannel::onSocketError(QAbstractSocket::SocketError error) +{ + setFinished(); +} + +void IncomingFileTransferChannel::doTransfer() +{ + QByteArray data; + while (mPriv->socket->bytesAvailable()) { + data = mPriv->socket->readAll(); + + // skip until we reach requetedOffset and start writing from there + if ((qulonglong) mPriv->pos < mPriv->requestedOffset) { + if ((qulonglong) data.length() <= (mPriv->requestedOffset - mPriv->pos)) { + break; + } + data = data.mid(mPriv->requestedOffset - mPriv->pos); + } + + mPriv->output->write(data); // never fails + } + + mPriv->pos += data.length(); +} + +void IncomingFileTransferChannel::setFinished() +{ + if (isFinished()) { + // it shouldn't happen but let's make sure + return; + } + + if (mPriv->socket) { + disconnect(mPriv->socket, SIGNAL(connected()), + this, SLOT(onSocketConnected())); + disconnect(mPriv->socket, SIGNAL(disconnected()), + this, SLOT(onSocketDisconnected())); + disconnect(mPriv->socket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(onSocketError(QAbstractSocket::SocketError))); + disconnect(mPriv->socket, SIGNAL(readyRead()), + this, SLOT(doTransfer())); + mPriv->socket->close(); + } + + if (mPriv->output) { + mPriv->output->close(); + } + + FileTransferChannel::setFinished(); +} + +/** + * \fn void IncomingFileTransferChannel::uriDefined(const QString &uri) + * + * Emitted when the value of uri() changes. + * + * \param uri The new URI of this file transfer channel. + * \sa FileTransferChannel::uri(), setUri() + */ + +} // Tp diff --git a/TelepathyQt/incoming-file-transfer-channel.h b/TelepathyQt/incoming-file-transfer-channel.h new file mode 100644 index 00000000..01937d3f --- /dev/null +++ b/TelepathyQt/incoming-file-transfer-channel.h @@ -0,0 +1,81 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_incoming_file_transfer_channel_h_HEADER_GUARD_ +#define _TelepathyQt_incoming_file_transfer_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/FileTransferChannel> + +#include <QAbstractSocket> + +namespace Tp +{ + +class TP_QT_EXPORT IncomingFileTransferChannel : public FileTransferChannel +{ + Q_OBJECT + Q_DISABLE_COPY(IncomingFileTransferChannel) + +public: + static const Feature FeatureCore; + + static IncomingFileTransferChannelPtr create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties); + + virtual ~IncomingFileTransferChannel(); + + PendingOperation *setUri(const QString& uri); + PendingOperation *acceptFile(qulonglong offset, QIODevice *output); + +Q_SIGNALS: + void uriDefined(const QString &uri); + +protected: + IncomingFileTransferChannel(const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature = IncomingFileTransferChannel::FeatureCore); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onAcceptFileFinished(Tp::PendingOperation *op); + + TP_QT_NO_EXPORT void onSocketConnected(); + TP_QT_NO_EXPORT void onSocketDisconnected(); + TP_QT_NO_EXPORT void onSocketError(QAbstractSocket::SocketError error); + TP_QT_NO_EXPORT void doTransfer(); + +private: + TP_QT_NO_EXPORT void connectToHost(); + TP_QT_NO_EXPORT void setFinished(); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/incoming-stream-tube-channel.cpp b/TelepathyQt/incoming-stream-tube-channel.cpp new file mode 100644 index 00000000..d73d011d --- /dev/null +++ b/TelepathyQt/incoming-stream-tube-channel.cpp @@ -0,0 +1,435 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/IncomingStreamTubeChannel> + +#include "TelepathyQt/_gen/incoming-stream-tube-channel.moc.hpp" + +#include "TelepathyQt/types-internal.h" +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/PendingFailure> +#include <TelepathyQt/PendingStreamTubeConnection> +#include <TelepathyQt/PendingVariant> +#include <TelepathyQt/Types> + +#include <QHostAddress> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT IncomingStreamTubeChannel::Private +{ + Private(IncomingStreamTubeChannel *parent); + + // Public object + IncomingStreamTubeChannel *parent; + static bool initRandom; +}; + +bool IncomingStreamTubeChannel::Private::initRandom = true; + +IncomingStreamTubeChannel::Private::Private(IncomingStreamTubeChannel *parent) + : parent(parent) +{ +} + +/** + * \class IncomingStreamTubeChannel + * \ingroup clientchannel + * \headerfile TelepathyQt/incoming-stream-tube-channel.h <TelepathyQt/IncomingStreamTubeChannel> + * + * \brief The IncomingStreamTubeChannel class represents an incoming Telepathy channel + * of type StreamTube. + * + * In particular, this class is meant to be used as a comfortable way for + * accepting incoming stream tubes. Tubes can be accepted as TCP and/or Unix sockets with various + * access control methods depending on what the service supports using the various overloads of + * acceptTubeAsTcpSocket() and acceptTubeAsUnixSocket(). + * + * Once a tube is successfully accepted and open (the PendingStreamTubeConnection returned from the + * accepting methods has finished), the application can connect to the socket the address of which + * can be retrieved from PendingStreamTubeConnection::ipAddress() and/or + * PendingStreamTubeConnection::localAddress() depending on which accepting method was used. + * Connecting to this socket will open a tunneled connection to the service listening at the + * offering end of the tube. + * + * For more details, please refer to \telepathy_spec. + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Feature representing the core that needs to become ready to make the + * IncomingStreamTubeChannel object usable. + * + * This is currently the same as StreamTubeChannel::FeatureCore, but may change to include more. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature IncomingStreamTubeChannel::FeatureCore = + Feature(QLatin1String(StreamTubeChannel::staticMetaObject.className()), 0); // ST::FeatureCore + +/** + * Create a new IncomingStreamTubeChannel 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 IncomingStreamTubeChannelPtr object pointing to the newly created + * IncomingStreamTubeChannel object. + */ +IncomingStreamTubeChannelPtr IncomingStreamTubeChannel::create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties) +{ + return IncomingStreamTubeChannelPtr(new IncomingStreamTubeChannel(connection, objectPath, + immutableProperties, IncomingStreamTubeChannel::FeatureCore)); +} + +/** + * Construct a new IncomingStreamTubeChannel 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, if any. The corresponding introspectable should + * depend on IncomingStreamTubeChannel::FeatureCore. + */ +IncomingStreamTubeChannel::IncomingStreamTubeChannel(const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : StreamTubeChannel(connection, objectPath, + immutableProperties, coreFeature), + mPriv(new Private(this)) +{ +} + +/** + * Class destructor. + */ +IncomingStreamTubeChannel::~IncomingStreamTubeChannel() +{ + delete mPriv; +} + +/** + * Accept an incoming stream tube as a TCP socket. + * + * This method accepts an incoming connection request for a stream tube. It can be called + * only if the tube is in the #TubeStateLocalPending state. + * + * The connection manager will open a TCP socket for the application to connect to. The address of + * the socket will be returned in PendingStreamTubeConnection::ipAddress() once the operation has + * finished successfully. + * + * This overload lets you specify an allowed address/port combination for connecting to the CM + * socket. Connections with other source addresses won't be accepted. The accessors + * supportsIPv4SocketsWithSpecifiedAddress() and supportsIPv6SocketsWithSpecifiedAddress() can be + * used to verify that the connection manager supports this kind of access control; otherwise, this + * method will always fail unless QHostAddress::Any or QHostAddress::AnyIPv6 is passed, in which + * case the behavior is identical to the always supported acceptTubeAsTcpSocket() overload. + * + * Note that when using QHostAddress::Any or QHostAddress::AnyIPv6, \a allowedPort is ignored. + * + * This method requires IncomingStreamTubeChannel::FeatureCore to be ready. + * + * \param allowedAddress An allowed address for connecting to the socket. + * \param allowedPort An allowed port for connecting to the socket. + * \return A PendingStreamTubeConnection which will emit PendingStreamTubeConnection::finished + * when the stream tube is ready to be used + * (hence in the #TubeStateOpen state). + */ +PendingStreamTubeConnection *IncomingStreamTubeChannel::acceptTubeAsTcpSocket( + const QHostAddress &allowedAddress, + quint16 allowedPort) +{ + if (!isReady(IncomingStreamTubeChannel::FeatureCore)) { + warning() << "IncomingStreamTubeChannel::FeatureCore must be ready before " + "calling acceptTubeAsTcpSocket"; + return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + IncomingStreamTubeChannelPtr(this)); + } + + // The tube must be in local pending state + if (state() != TubeChannelStateLocalPending) { + warning() << "You can accept tubes only when they are in LocalPending state"; + return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + IncomingStreamTubeChannelPtr(this)); + } + + QVariant controlParameter; + SocketAccessControl accessControl; + + // Now, let's check what we need to do with accessControl. There is just one special case, Port. + if (allowedAddress != QHostAddress::Any && allowedAddress != QHostAddress::AnyIPv6) { + // We need to have a valid QHostAddress AND Port. + if (allowedAddress.isNull() || allowedPort == 0) { + warning() << "You have to set a valid allowed address+port to use Port access control"; + return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("The supplied allowed address and/or port was invalid"), + IncomingStreamTubeChannelPtr(this)); + } + + accessControl = SocketAccessControlPort; + + // IPv4 or IPv6? + if (allowedAddress.protocol() == QAbstractSocket::IPv4Protocol) { + // IPv4 case + SocketAddressIPv4 addr; + addr.address = allowedAddress.toString(); + addr.port = allowedPort; + + controlParameter = QVariant::fromValue(addr); + } else if (allowedAddress.protocol() == QAbstractSocket::IPv6Protocol) { + // IPv6 case + SocketAddressIPv6 addr; + addr.address = allowedAddress.toString(); + addr.port = allowedPort; + + controlParameter = QVariant::fromValue(addr); + } else { + // We're handling an IPv4/IPv6 socket only + warning() << "acceptTubeAsTcpSocket can be called only with a QHostAddress " + "representing an IPv4 or IPv6 address"; + return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Invalid host given"), + IncomingStreamTubeChannelPtr(this)); + } + } else { + // We have to do no special stuff here + accessControl = SocketAccessControlLocalhost; + // Since QDBusMarshaller does not like null variants, just add an empty string. + controlParameter = QVariant(QString()); + } + + // Set the correct address type and access control + setAddressType(allowedAddress.protocol() == QAbstractSocket::IPv4Protocol ? + SocketAddressTypeIPv4 : + SocketAddressTypeIPv6); + setAccessControl(accessControl); + + // Fail early if the combination is not supported + if ((accessControl == SocketAccessControlLocalhost && + addressType() == SocketAddressTypeIPv4 && + !supportsIPv4SocketsOnLocalhost()) || + (accessControl == SocketAccessControlPort && + addressType() == SocketAddressTypeIPv4 && + !supportsIPv4SocketsWithSpecifiedAddress()) || + (accessControl == SocketAccessControlLocalhost && + addressType() == SocketAddressTypeIPv6 && + !supportsIPv6SocketsOnLocalhost()) || + (accessControl == SocketAccessControlPort && + addressType() == SocketAddressTypeIPv6 && + !supportsIPv6SocketsWithSpecifiedAddress())) { + warning() << "You requested an address type/access control combination " + "not supported by this channel"; + return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("The requested address type/access control " + "combination is not supported"), + IncomingStreamTubeChannelPtr(this)); + } + + // Perform the actual call + PendingVariant *pv = new PendingVariant( + interface<Client::ChannelTypeStreamTubeInterface>()->Accept( + addressType(), + accessControl, + QDBusVariant(controlParameter)), + IncomingStreamTubeChannelPtr(this)); + + PendingStreamTubeConnection *op = new PendingStreamTubeConnection(pv, addressType(), + false, 0, IncomingStreamTubeChannelPtr(this)); + return op; +} + +/** + * Accept an incoming stream tube as a TCP socket. + * + * This method accepts an incoming connection request for a stream tube. It can be called + * only if the tube is in the #TubeStateLocalPending state. + * + * The connection manager will open a TCP socket for the application to connect to. The address of + * the socket will be returned in PendingStreamTubeConnection::ipAddress() once the operation has + * finished successfully. + * + * Using this overload, the connection manager will accept every incoming connection from localhost. + * + * This accept method must be supported by all connection managers adhering to the \telepathy_spec. + * + * This method requires IncomingStreamTubeChannel::FeatureCore to be ready. + * + * \return A PendingStreamTubeConnection which will emit PendingStreamTubeConnection::finished + * when the stream tube is ready to be used + * (hence in the #TubeStateOpen state). + */ +PendingStreamTubeConnection *IncomingStreamTubeChannel::acceptTubeAsTcpSocket() +{ + return acceptTubeAsTcpSocket(QHostAddress::Any, 0); +} + +/** + * Accept an incoming stream tube as a Unix socket. + * + * This method accepts an incoming connection request for a stream tube. It can be called + * only if the tube is in the #TubeStateLocalPending state. + * + * An Unix socket (can be used with QLocalSocket or alike) will be opened by the connection manager + * as the local tube endpoint. This is only supported if supportsUnixSocketsOnLocalhost() is \c + * true. + * + * You can also specify whether the CM should require an SCM_CREDS or SCM_CREDENTIALS message + * upon connection instead of accepting every incoming connection from localhost. This provides + * additional security, but requires sending the byte retrieved from + * PendingStreamTubeConnection::credentialByte() in-line in the socket byte stream (in a credentials + * message if available on the platform), which might not be compatible with all protocols or + * libraries. Also, only connection managers for which supportsUnixSocketsWithCredentials() is \c + * true support this type of access control. + * + * This method requires IncomingStreamTubeChannel::FeatureCore to be ready. + * + * \param requireCredentials Whether the CM should require an SCM_CREDS or SCM_CREDENTIALS message + * upon connection. + * \return A PendingStreamTubeConnection which will emit PendingStreamTubeConnection::finished + * when the stream tube is ready to be used + * (hence in the #TubeStateOpen state). + * \sa StreamTubeChannel::supportsUnixSocketsOnLocalhost(), + * StreamTubeChannel::supportsUnixSocketsWithCredentials(), + * StreamTubeChannel::supportsAbstractUnixSocketsOnLocalhost(), + * StreamTubeChannel::supportsAbstractUnixSocketsWithCredentials() + */ +PendingStreamTubeConnection *IncomingStreamTubeChannel::acceptTubeAsUnixSocket( + bool requireCredentials) +{ + if (!isReady(IncomingStreamTubeChannel::FeatureCore)) { + warning() << "IncomingStreamTubeChannel::FeatureCore must be ready before " + "calling acceptTubeAsUnixSocket"; + return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + IncomingStreamTubeChannelPtr(this)); + } + + // The tube must be in local pending state + if (state() != TubeChannelStateLocalPending) { + warning() << "You can accept tubes only when they are in LocalPending state"; + return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + IncomingStreamTubeChannelPtr(this)); + } + + SocketAccessControl accessControl = requireCredentials ? + SocketAccessControlCredentials : + SocketAccessControlLocalhost; + setAddressType(SocketAddressTypeUnix); + setAccessControl(accessControl); + + // Fail early if the combination is not supported + if ((accessControl == SocketAccessControlLocalhost && + addressType() == SocketAddressTypeUnix && + !supportsUnixSocketsOnLocalhost()) || + (accessControl == SocketAccessControlCredentials && + addressType() == SocketAddressTypeUnix && + !supportsUnixSocketsWithCredentials()) || + (accessControl == SocketAccessControlLocalhost && + addressType() == SocketAddressTypeAbstractUnix && + !supportsAbstractUnixSocketsOnLocalhost()) || + (accessControl == SocketAccessControlCredentials && + addressType() == SocketAddressTypeAbstractUnix && + !supportsAbstractUnixSocketsWithCredentials())) { + warning() << "You requested an address type/access control combination " + "not supported by this channel"; + return new PendingStreamTubeConnection(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("The requested address type/access control " + "combination is not supported"), + IncomingStreamTubeChannelPtr(this)); + } + + QDBusVariant accessControlParam; + uchar credentialByte = 0; + if (accessControl == SocketAccessControlLocalhost) { + accessControlParam.setVariant(qVariantFromValue(static_cast<uint>(0))); + } else if (accessControl == SocketAccessControlCredentials) { + if (mPriv->initRandom) { + qsrand(QTime::currentTime().msec()); + mPriv->initRandom = false; + } + credentialByte = static_cast<uchar>(qrand()); + accessControlParam.setVariant(qVariantFromValue(credentialByte)); + } else { + Q_ASSERT(false); + } + + // Perform the actual call + PendingVariant *pv = new PendingVariant( + interface<Client::ChannelTypeStreamTubeInterface>()->Accept( + addressType(), + accessControl, + accessControlParam), + IncomingStreamTubeChannelPtr(this)); + + PendingStreamTubeConnection *op = new PendingStreamTubeConnection(pv, addressType(), + requireCredentials, credentialByte, IncomingStreamTubeChannelPtr(this)); + return op; +} + +/** + * Return the local address of the opened stream tube. + * + * Calling this method when the tube has not been opened will cause it + * to return an undefined value. The same will happen if the tube has been accepted as a TCP + * socket. Use ipAddress() if that is the case. + * + * \return Unix socket address if using an Unix socket, + * or an undefined value otherwise. + * \sa acceptTubeAsUnixSocket(), ipAddress() + */ +QString IncomingStreamTubeChannel::localAddress() const +{ + return StreamTubeChannel::localAddress(); +} + +/** + * Return the IP address/port combination of the opened stream tube. + * + * Calling this method when the tube has not been opened will cause it + * to return an undefined value. The same will happen if the tube has been accepted as an Unix + * socket. Use localAddress() if that is the case. + * + * \return Pair of IP address as QHostAddress and port if using a TCP socket, + * or an undefined value otherwise. + * \sa acceptTubeAsTcpSocket(), localAddress() + */ +QPair<QHostAddress, quint16> IncomingStreamTubeChannel::ipAddress() const +{ + return StreamTubeChannel::ipAddress(); +} + +void IncomingStreamTubeChannel::onNewLocalConnection(uint connectionId) +{ + addConnection(connectionId); +} + +} diff --git a/TelepathyQt/incoming-stream-tube-channel.h b/TelepathyQt/incoming-stream-tube-channel.h new file mode 100644 index 00000000..1dff19cb --- /dev/null +++ b/TelepathyQt/incoming-stream-tube-channel.h @@ -0,0 +1,80 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_incoming_stream_tube_channel_h_HEADER_GUARD_ +#define _TelepathyQt_incoming_stream_tube_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/StreamTubeChannel> + +#include <QtNetwork/QHostAddress> + +class QIODevice; + +namespace Tp +{ + +class PendingStreamTubeConnection; + +class TP_QT_EXPORT IncomingStreamTubeChannel : public StreamTubeChannel +{ + Q_OBJECT + Q_DISABLE_COPY(IncomingStreamTubeChannel) + +public: + static const Feature FeatureCore; + + static IncomingStreamTubeChannelPtr create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties); + + virtual ~IncomingStreamTubeChannel(); + + PendingStreamTubeConnection *acceptTubeAsTcpSocket(); + PendingStreamTubeConnection *acceptTubeAsTcpSocket(const QHostAddress &allowedAddress, + quint16 allowedPort); + PendingStreamTubeConnection *acceptTubeAsUnixSocket(bool requireCredentials = false); + + // FIXME: (API/ABI break) Remove ipAddress() and localAddress() (already in StreamTubeChannel) + QPair<QHostAddress, quint16> ipAddress() const; + QString localAddress() const; + +protected: + IncomingStreamTubeChannel(const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature = IncomingStreamTubeChannel::FeatureCore); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onNewLocalConnection(uint connectionId); + +private: + struct Private; + friend class PendingStreamTubeConnection; + friend struct Private; + Private *mPriv; +}; + +} + +#endif diff --git a/TelepathyQt/key-file.cpp b/TelepathyQt/key-file.cpp new file mode 100644 index 00000000..a5ca9f46 --- /dev/null +++ b/TelepathyQt/key-file.cpp @@ -0,0 +1,582 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2009 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 <TelepathyQt/KeyFile> + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Utils> + +#include <QtCore/QByteArray> +#include <QtCore/QFile> +#include <QtCore/QHash> +#include <QtCore/QString> +#include <QtCore/QStringList> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT KeyFile::Private +{ + Private(); + Private(const QString &fName); + + void setFileName(const QString &fName); + void setError(KeyFile::Status status, const QString &reason); + bool read(); + + bool validateKey(const QByteArray &data, int from, int to, QString &result); + + QStringList allGroups() const; + QStringList allKeys() const; + QStringList keys() const; + bool contains(const QString &key) const; + QString rawValue(const QString &key) const; + QString value(const QString &key) const; + QStringList valueAsStringList(const QString &key) const; + + QString fileName; + KeyFile::Status status; + QHash<QString, QHash<QString, QByteArray> > groups; + QString currentGroup; +}; + +KeyFile::Private::Private() + : status(KeyFile::None) +{ +} + +KeyFile::Private::Private(const QString &fName) + : fileName(fName), + status(KeyFile::NoError) +{ + read(); +} + +void KeyFile::Private::setFileName(const QString &fName) +{ + fileName = fName; + status = KeyFile::NoError; + currentGroup = QString(); + groups.clear(); + read(); +} + +void KeyFile::Private::setError(KeyFile::Status st, const QString &reason) +{ + warning() << QString(QLatin1String("ERROR: filename(%1) reason(%2)")) + .arg(fileName).arg(reason); + status = st; + groups.clear(); +} + +bool KeyFile::Private::read() +{ + QFile file(fileName); + if (!file.exists()) { + setError(KeyFile::NotFoundError, + QLatin1String("file does not exist")); + return false; + } + + if (!file.open(QFile::ReadOnly)) { + setError(KeyFile::AccessError, + QLatin1String("cannot open file for readonly access")); + return false; + } + + QByteArray data; + QByteArray group; + QString currentGroup; + QHash<QString, QByteArray> groupMap; + QByteArray rawValue; + int line = 0; + int idx; + while (!file.atEnd()) { + data = file.readLine().trimmed(); + line++; + + if (data.size() == 0) { + // skip empty lines + continue; + } + + char ch = data.at(0); + if (ch == '#') { + // skip comments + continue; + } + else if (ch == '[') { + if (groupMap.size()) { + groups[currentGroup] = groupMap; + groupMap.clear(); + } + + idx = data.indexOf(']'); + if (idx == -1) { + // line starts with [ and it's not a group + setError(KeyFile::FormatError, + QString(QLatin1String("invalid group at line %2 - missing ']'")) + .arg(line)); + return false; + } + + group = data.mid(1, idx - 1).trimmed(); + if (groups.contains(QLatin1String(group))) { + setError(KeyFile::FormatError, + QString(QLatin1String("duplicated group '%1' at line %2")) + .arg(QLatin1String(group)).arg(line)); + return false; + } + + currentGroup = QLatin1String(""); + if (!unescapeString(group, 0, group.size(), currentGroup)) { + setError(KeyFile::FormatError, + QString(QLatin1String("invalid group '%1' at line %2")) + .arg(currentGroup).arg(line)); + return false; + } + } + else { + idx = data.indexOf('='); + if (idx == -1) { + setError(KeyFile::FormatError, + QString(QLatin1String("format error at line %1 - missing '='")) + .arg(line)); + return false; + } + + // remove trailing spaces + char ch; + int idxKeyEnd = idx; + while ((ch = data.at(idxKeyEnd - 1)) == ' ' || ch == '\t') { + --idxKeyEnd; + } + + QString key; + if (!validateKey(data, 0, idxKeyEnd, key)) { + setError(KeyFile::FormatError, + QString(QLatin1String("invalid key '%1' at line %2")) + .arg(key).arg(line)); + return false; + } + + if (groupMap.contains(key)) { + setError(KeyFile::FormatError, + QString(QLatin1String("duplicated key '%1' on group '%2' at line %3")) + .arg(key).arg(currentGroup).arg(line)); + return false; + } + + data = data.mid(idx + 1).trimmed(); + rawValue = data.mid(0, data.size()); + groupMap[key] = rawValue; + } + } + + if (groupMap.size()) { + groups[currentGroup] = groupMap; + groupMap.clear(); + } + + return true; +} + +bool KeyFile::Private::validateKey(const QByteArray &data, int from, int to, QString &result) +{ + int i = from; + bool ret = true; + while (i < to) { + uint ch = data.at(i++); + // as an extension to the Desktop Entry spec, we allow " ", "_", "." and "@" + // as valid key characters - "_" and "." are needed for keys that are + // D-Bus property names, and GKeyFile and KConfigIniBackend also accept + // all four of those characters. + if (!((ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + (ch == ' ') || + (ch == '-') || (ch == '_') || + (ch == '.') || (ch == '@'))) { + ret = false; + } + result += ch; + } + return ret; +} + +QStringList KeyFile::Private::allGroups() const +{ + return groups.keys(); +} + +QStringList KeyFile::Private::allKeys() const +{ + QStringList keys; + QHash<QString, QHash<QString, QByteArray> >::const_iterator itrGroups = groups.begin(); + while (itrGroups != groups.end()) { + keys << itrGroups.value().keys(); + ++itrGroups; + } + return keys; +} + +QStringList KeyFile::Private::keys() const +{ + QHash<QString, QByteArray> groupMap = groups[currentGroup]; + return groupMap.keys(); +} + +bool KeyFile::Private::contains(const QString &key) const +{ + QHash<QString, QByteArray> groupMap = groups[currentGroup]; + return groupMap.contains(key); +} + +QString KeyFile::Private::rawValue(const QString &key) const +{ + QHash<QString, QByteArray> groupMap = groups[currentGroup]; + QByteArray rawValue = groupMap.value(key); + return QLatin1String(rawValue); +} + +QString KeyFile::Private::value(const QString &key) const +{ + QHash<QString, QByteArray> groupMap = groups[currentGroup]; + QString result; + QByteArray rawValue = groupMap.value(key); + if (unescapeString(rawValue, 0, rawValue.size(), result)) { + return result; + } + return QString(); +} + +QStringList KeyFile::Private::valueAsStringList(const QString &key) const +{ + QHash<QString, QByteArray> groupMap = groups[currentGroup]; + QStringList result; + QByteArray rawValue = groupMap.value(key); + if (unescapeStringList(rawValue, 0, rawValue.size(), result)) { + return result; + } + return QStringList(); +} + +/** + * \class KeyFile + * \ingroup utils + * \headerfile TelepathyQt/key-file.h <TelepathyQt/KeyFile> + * + * \brief The KeyFile class provides an easy way to read key-pair files such as + * INI style files and .desktop files. + * + * It follows the rules regarding string escaping as defined in + * http://standards.freedesktop.org/desktop-entry-spec/latest/index.html + * + * \todo Consider making this private, because the ConnectionManager and Account classes provide a + * nice view to the data parsed by this class anyway (and there's even a higher-level ManagerFile + * class in between) (fd.o #41655). + */ + +/** + * Create a KeyFile object used to read (key-pair) compliant files. + * + * The status will be KeyFile::None + * \sa setFileName() + */ +KeyFile::KeyFile() + : mPriv(new Private()) +{ +} + +/** + * Create a KeyFile object used to read (key-pair) compliant files. + * + * \param fileName Name of the file to be read. + */ +KeyFile::KeyFile(const QString &fileName) + : mPriv(new Private(fileName)) +{ +} + +/** + * Create a KeyFile object used to read (key-pair) compliant files. + */ +KeyFile::KeyFile(const KeyFile &other) + : mPriv(new Private()) +{ + mPriv->fileName = other.mPriv->fileName; + mPriv->status = other.mPriv->status; + mPriv->groups = other.mPriv->groups; + mPriv->currentGroup = other.mPriv->currentGroup; +} + +/** + * Class destructor. + */ +KeyFile::~KeyFile() +{ + delete mPriv; +} + +KeyFile &KeyFile::operator=(const KeyFile &other) +{ + mPriv->fileName = other.mPriv->fileName; + mPriv->status = other.mPriv->status; + mPriv->groups = other.mPriv->groups; + mPriv->currentGroup = other.mPriv->currentGroup; + return *this; +} + +/** + * Set the name of the file to be read. + * + * \param fileName Name of the file to be read. + */ +void KeyFile::setFileName(const QString &fileName) +{ + mPriv->setFileName(fileName); +} + +/** + * Return the name of the file associated with this object. + * + * \return Name of the file associated with this object. + */ +QString KeyFile::fileName() const +{ + return mPriv->fileName; +} + +/** + * Return a status code indicating the first error that was met by #KeyFile, + * or KeyFile::NoError if no error occurred. + * + * Make sure to use this method if you set the filename to be read using + * setFileName(). + * + * \return Status code. + * \sa setFileName() + */ +KeyFile::Status KeyFile::status() const +{ + return mPriv->status; +} + +/** + * Set the current group to be used while reading keys. + * + * Query functions such as keys(), contains() and value() are based on this + * group. + * + * By default a empty group is used as the group for global + * keys and is used as the default group if none is set. + * + * \param group Name of the group to be used. + * \sa group() + */ +void KeyFile::setGroup(const QString &group) +{ + mPriv->currentGroup = group; +} + +/** + * Return the current group. + * + * \return Name of the current group. + * \sa setGroup() + */ +QString KeyFile::group() const +{ + return mPriv->currentGroup; +} + +/** + * Return all groups in the desktop file. + * + * Global keys will be added to a empty group. + * + * \return List of all groups in the desktop file. + */ +QStringList KeyFile::allGroups() const +{ + return mPriv->allGroups(); +} + +/** + * Return all keys described in the desktop file. + * + * \return List of all keys in the desktop file. + */ +QStringList KeyFile::allKeys() const +{ + return mPriv->allKeys(); +} + +/** + * Return a list of keys in the current group. + * + * \return List of all keys in the current group. + * \sa group(), setGroup() + */ +QStringList KeyFile::keys() const +{ + return mPriv->keys(); +} + +/** + * Check if the current group contains a key named \a key. + * + * \return true if \a key exists, false otherwise. + * \sa group(), setGroup() + */ +bool KeyFile::contains(const QString &key) const +{ + return mPriv->contains(key); +} + +/** + * Get the raw value for the key in the current group named \a key. + * + * The raw value is the value as is in the key file. + * + * \return Value of \a key, empty string if not found. + * \sa group(), setGroup() + */ +QString KeyFile::rawValue(const QString &key) const +{ + return mPriv->rawValue(key); +} + +/** + * Get the value for the key in the current group named \a key. + * + * Escape sequences in the value are interpreted as defined in: + * http://standards.freedesktop.org/desktop-entry-spec/latest/ + * + * \return Value of \a key, empty string if not found or an error occurred. + * \sa group(), setGroup() + */ +QString KeyFile::value(const QString &key) const +{ + return mPriv->value(key); +} + +/** + * Get the value for the key in the current group named \a key as a list. + * + * Return a list containing all strings on this key separated by ';'. + * Escape sequences in the value are interpreted as defined in: + * http://standards.freedesktop.org/desktop-entry-spec/latest/ + * + * \return Value of \a key as a list, empty string list if not found or an error occurred. + * \sa group(), setGroup() + */ +QStringList KeyFile::valueAsStringList(const QString &key) const +{ + return mPriv->valueAsStringList(key); +} + +bool KeyFile::unescapeString(const QByteArray &data, int from, int to, QString &result) +{ + int i = from; + while (i < to) { + uint ch = data.at(i++); + + if (ch == '\\') { + if (i == to) { + result += QLatin1String("\\"); + return true; + } + + char nextCh = data.at(i++); + switch (nextCh) { + case 's': + result += QLatin1String(" "); + break; + case 'n': + result += QLatin1String("\n"); + break; + case 't': + result += QLatin1String("\t"); + break; + case 'r': + result += QLatin1String("\r"); + break; + case ';': + result += QLatin1String(";"); + break; + case '\\': + result += QLatin1String("\\"); + break; + default: + return false; + } + } else { + result += ch; + } + } + + return true; +} + +bool KeyFile::unescapeStringList(const QByteArray &data, int from, int to, QStringList &result) +{ + QByteArray value; + QList<QByteArray> valueList; + int i = from; + char ch; + while (i < to) { + ch = data.at(i++); + + if (ch == '\\') { + value += ch; + if (i < to) { + value += data.at(i++); + continue; + } else { + valueList << value; + break; + } + } else if (ch == ';') { + valueList << value; + value = ""; + } else { + value += ch; + if (i == to) { + valueList << value; + } + } + } + + foreach (value, valueList) { + QString str; + if (!unescapeString(value, 0, value.size(), str)) { + return false; + } + result << str; + } + + return true; +} + +} // Tp diff --git a/TelepathyQt/key-file.h b/TelepathyQt/key-file.h new file mode 100644 index 00000000..74d50d15 --- /dev/null +++ b/TelepathyQt/key-file.h @@ -0,0 +1,91 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_key_file_h_HEADER_GUARD_ +#define _TelepathyQt_key_file_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> + +#include <QMetaType> +#include <QtGlobal> + +class QString; +class QStringList; + +namespace Tp +{ + +class TP_QT_EXPORT KeyFile +{ +public: + enum Status { + None = 0, + NoError, + NotFoundError, + AccessError, + FormatError, + }; + + KeyFile(); + KeyFile(const KeyFile &other); + KeyFile(const QString &fileName); + ~KeyFile(); + + KeyFile &operator=(const KeyFile &other); + + void setFileName(const QString &fileName); + QString fileName() const; + + Status status() const; + + void setGroup(const QString &group); + QString group() const; + + QStringList allGroups() const; + QStringList allKeys() const; + QStringList keys() const; + bool contains(const QString &key) const; + + QString rawValue(const QString &key) const; + QString value(const QString &key) const; + QStringList valueAsStringList(const QString &key) const; + + static bool unescapeString(const QByteArray &data, int from, int to, + QString &result); + static bool unescapeStringList(const QByteArray &data, int from, int to, + QStringList &result); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} + +Q_DECLARE_METATYPE(Tp::KeyFile); + +#endif diff --git a/TelepathyQt/location-info.cpp b/TelepathyQt/location-info.cpp new file mode 100644 index 00000000..db7ac75b --- /dev/null +++ b/TelepathyQt/location-info.cpp @@ -0,0 +1,221 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/LocationInfo> + +#include <QDBusArgument> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT LocationInfo::Private : public QSharedData +{ + QVariantMap location; +}; + +/** + * \class LocationInfo + * \ingroup clientconn + * \headerfile TelepathyQt/location-info.h <TelepathyQt/LocationInfo> + * + * \brief The LocationInfo class represents the location of a + * Telepathy Contact. + */ + +/** + * Construct a new LocationInfo object. + */ +LocationInfo::LocationInfo() + : mPriv(new Private) +{ +} + +LocationInfo::LocationInfo(const QVariantMap &location) + : mPriv(new Private) +{ + mPriv->location = location; +} + +LocationInfo::LocationInfo(const LocationInfo &other) + : mPriv(other.mPriv) +{ +} + +/** + * Class destructor. + */ +LocationInfo::~LocationInfo() +{ +} + +LocationInfo &LocationInfo::operator=(const LocationInfo &other) +{ + this->mPriv = other.mPriv; + return *this; +} + +QString LocationInfo::countryCode() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("countrycode"))); +} + +QString LocationInfo::country() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("country"))); +} + +QString LocationInfo::region() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("region"))); +} + +QString LocationInfo::locality() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("locality"))); +} + +QString LocationInfo::area() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("area"))); +} + +QString LocationInfo::postalCode() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("postalcode"))); +} + +QString LocationInfo::street() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("street"))); +} + +QString LocationInfo::building() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("building"))); +} + +QString LocationInfo::floor() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("floor"))); +} + +QString LocationInfo::room() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("room"))); +} + +QString LocationInfo::text() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("text"))); +} + +QString LocationInfo::description() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("description"))); +} + +QString LocationInfo::uri() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("uri"))); +} + +QString LocationInfo::language() const +{ + return qdbus_cast<QString>(mPriv->location.value( + QLatin1String("language"))); +} + +double LocationInfo::latitude() const +{ + return qdbus_cast<double>(mPriv->location.value( + QLatin1String("lat"))); +} + +double LocationInfo::longitude() const +{ + return qdbus_cast<double>(mPriv->location.value( + QLatin1String("lon"))); +} + +double LocationInfo::altitude() const +{ + return qdbus_cast<double>(mPriv->location.value( + QLatin1String("alt"))); +} + +double LocationInfo::accuracy() const +{ + return qdbus_cast<double>(mPriv->location.value( + QLatin1String("accuracy"))); +} + +double LocationInfo::speed() const +{ + return qdbus_cast<double>(mPriv->location.value( + QLatin1String("speed"))); +} + +double LocationInfo::bearing() const +{ + return qdbus_cast<double>(mPriv->location.value( + QLatin1String("bearing"))); +} + +QDateTime LocationInfo::timestamp() const +{ + // FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690 + qlonglong t = qdbus_cast<qlonglong>(mPriv->location.value( + QLatin1String("timestamp"))); + if (t != 0) { + return QDateTime::fromTime_t((uint) t); + } + return QDateTime(); +} + +QVariantMap LocationInfo::allDetails() const +{ + return mPriv->location; +} + +void LocationInfo::updateData(const QVariantMap &location) +{ + if (!isValid()) { + mPriv = new Private; + } + + mPriv->location = location; +} + +} // Tp diff --git a/TelepathyQt/location-info.h b/TelepathyQt/location-info.h new file mode 100644 index 00000000..221fbed2 --- /dev/null +++ b/TelepathyQt/location-info.h @@ -0,0 +1,95 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_location_info_h_HEADER_GUARD_ +#define _TelepathyQt_location_info_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> + +#include <QDateTime> +#include <QSharedDataPointer> +#include <QString> +#include <QVariantMap> + +namespace Tp +{ + +class TP_QT_EXPORT LocationInfo +{ +public: + LocationInfo(); + LocationInfo(const QVariantMap &location); + LocationInfo(const LocationInfo &other); + virtual ~LocationInfo(); + + bool isValid() const { return mPriv.constData() != 0; } + + LocationInfo &operator=(const LocationInfo &other); + + QString countryCode() const; + QString country() const; + QString region() const; + QString locality() const; + QString area() const; + QString postalCode() const; + QString street() const; + + QString building() const; + QString floor() const; + QString room() const; + QString text() const; + QString description() const; + QString uri() const; + + QString language() const; + + double latitude() const; + double longitude() const; + double altitude() const; + double accuracy() const; + + double speed() const; + double bearing() const; + + QDateTime timestamp() const; + + QVariantMap allDetails() const; + +private: + friend class Contact; + + TP_QT_NO_EXPORT void updateData(const QVariantMap &location); + + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::LocationInfo); + +#endif diff --git a/TelepathyQt/main.dox b/TelepathyQt/main.dox new file mode 100644 index 00000000..d87e346e --- /dev/null +++ b/TelepathyQt/main.dox @@ -0,0 +1,133 @@ +/* + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008 Nokia Corporation + * + * 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 + */ + +/** + * \mainpage Telepathy-Qt4 + * + * \section Introduction + * + * Telepathy-Qt4 is a Qt4 high-level binding for \telepathy. + * + * \telepathy is a \dbus framework for unifying real time communication, including instant + * messaging, voice calls and video calls. It abstracts differences between protocols to + * provide a unified interface for applications. + * + * Releases can be found <a + * href="http://telepathy.freedesktop.org/releases/telepathy-qt4">here</a>. + * + * Development is done in the git repository found <a + * href="http://cgit.freedesktop.org/telepathy/telepathy-qt4/">here</a>. + * + * \li <a href="classes.html">All Classes</a> + * + * \section getting_started Getting Started + * \li \subpage installation + * + * \section examples Examples + * + * This is the list of examples in Telepathy-Qt4's examples directory. + * The examples demonstrate Telepathy-Qt4 features in small, self-contained + * programs. They are not all designed to be impressive when you run them, + * but their source code is carefully written to show good Telepathy-Qt4 + * programming practices. + * + * \li \subpage accounts_example + * \li \subpage contact_messenger_example + * \li \subpage protocols_example + * \li \subpage roster_example + * + * \section developer_resources Further Information + * \li \subpage bugreport + * \li \subpage mailing_lists + * \li \subpage async_model + * \li \subpage shared_ptr + */ + +/** + * \page installation Installation + * + * \section installation_from_source Installing from source on Linux + * + * \subsection installation_from_source_requirements Requirements + * + * Building Telepathy-Qt4 requires: + * \li Qt, including QtDBus <http://www.qtsoftware.com/> + * \li GNU make <http://www.gnu.org/software/make/> + * \li pkg-config <http://ftp.gnome.org/pub/GNOME/sources/pkg-config/> + * \li libxslt, xsltproc <http://xmlsoft.org/XSLT/> + * \li Python <http://www.python.org/> + * + * For the full set of regression tests to run, you'll also need: + * \li telepathy-glib <http://telepathy.freedesktop.org/releases/telepathy-glib/> + * + * and to build the example VoIP call UI (examples/call), you'll need: + * \li telepathy-glib <http://telepathy.freedesktop.org/releases/telepathy-glib/> + * \li telepathy-farsight + * <http://telepathy.freedesktop.org/releases/telepathy-farsight/> + * \li GStreamer <http://gstreamer.freedesktop.org/>\n + * + * Building also requires the cmake build system. + * + * \subsection installation_from_source_building Building + * + * After installing all dependencies, run: + * + * \verbatim + $ mkdir build; cd build + $ cmake .. + $ make + $ make install \endverbatim + */ + +/** + * \page bugreport How to report a bug + * + * Before reporting a bug, please check the <a + * href="https://bugs.freedesktop.org/query.cgi?product=Telepathy&component=telepathy-qt4"> + * Bug Tracker</a> to see if the issue is already known. + * + * Always include the following information in your bug report: + * \li The version of Telepathy-Qt4 you are using + * + * Please submit the bug report, feature request or "to-do" item + * <a + * href="https://bugs.freedesktop.org/enter_bug.cgi?product=Telepathy&component=telepathy-qt4"> + * here</a>. + */ + +/** + * \page mailing_lists Mailing Lists + * + * <a href="http://lists.freedesktop.org/mailman/listinfo/telepathy">General + * discussion list</a>\n + * This list should be used for general discussion about \telepathy usage, + * development. + * + * <a + * href="http://lists.freedesktop.org/mailman/listinfo/telepathy-commits"> + * Commits list</a>\n + * Subscribe to this list to follow the commits. + * + * <a + * href="http://lists.freedesktop.org/mailman/listinfo/telepathy-bugs"> + * Bugs list</a>\n + * Subscribe to this list to follow the bug reports. + */ diff --git a/TelepathyQt/manager-file.cpp b/TelepathyQt/manager-file.cpp new file mode 100644 index 00000000..e04c1e49 --- /dev/null +++ b/TelepathyQt/manager-file.cpp @@ -0,0 +1,618 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ManagerFile> + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Constants> +#include <TelepathyQt/KeyFile> + +#include <QtCore/QDir> +#include <QtCore/QHash> +#include <QtCore/QString> +#include <QtCore/QStringList> +#include <QtDBus/QDBusVariant> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ManagerFile::Private +{ + Private(); + Private(const QString &cnName); + + void init(); + bool parse(const QString &fileName); + bool isValid() const; + + bool hasParameter(const QString &protocol, const QString ¶mName) const; + ParamSpec *getParameter(const QString &protocol, const QString ¶mName); + QStringList protocols() const; + ParamSpecList parameters(const QString &protocol) const; + + QVariant valueForKey(const QString ¶m, const QString &dbusSignature); + + struct ProtocolInfo + { + ProtocolInfo() {} + ProtocolInfo(const ParamSpecList ¶ms, const PresenceSpecList &statuses) + : params(params), + statuses(statuses) + { + } + + ParamSpecList params; + QString vcardField; + QString englishName; + QString iconName; + RequestableChannelClassList rccs; + PresenceSpecList statuses; + AvatarSpec avatarRequirements; + }; + + QString cmName; + KeyFile keyFile; + QHash<QString, ProtocolInfo> protocolsMap; + bool valid; +}; + +ManagerFile::Private::Private() + : valid(false) +{ +} + +ManagerFile::Private::Private(const QString &cmName) + : cmName(cmName), + valid(false) +{ + init(); +} + +void ManagerFile::Private::init() +{ + // TODO: should we cache the configDirs anywhere? + QStringList configDirs; + + QString xdgDataHome = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME")); + if (xdgDataHome.isEmpty()) { + configDirs << QDir::homePath() + QLatin1String("/.local/share/data/telepathy/managers/"); + } + else { + configDirs << xdgDataHome + QLatin1String("/telepathy/managers/"); + } + + QString xdgDataDirsEnv = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")); + if (xdgDataDirsEnv.isEmpty()) { + configDirs << QLatin1String("/usr/local/share/telepathy/managers/"); + configDirs << QLatin1String("/usr/share/telepathy/managers/"); + } + else { + QStringList xdgDataDirs = xdgDataDirsEnv.split(QLatin1Char(':')); + foreach (const QString xdgDataDir, xdgDataDirs) { + configDirs << xdgDataDir + QLatin1String("/telepathy/managers/"); + } + } + + foreach (const QString configDir, configDirs) { + QString fileName = configDir + cmName + QLatin1String(".manager"); + if (QFile::exists(fileName)) { + debug() << "parsing manager file" << fileName; + protocolsMap.clear(); + if (!parse(fileName)) { + warning() << "error parsing manager file" << fileName; + continue; + } + valid = true; + return; + } + } +} + +bool ManagerFile::Private::parse(const QString &fileName) +{ + keyFile.setFileName(fileName); + if (keyFile.status() != KeyFile::NoError) { + return false; + } + + /* read supported protocols and parameters */ + QString protocol; + QStringList groups = keyFile.allGroups(); + foreach (const QString group, groups) { + if (group.startsWith(QLatin1String("Protocol "))) { + protocol = group.right(group.length() - 9); + keyFile.setGroup(group); + + ParamSpecList paramSpecList; + SimpleStatusSpecMap statuses; + QString param; + QStringList params = keyFile.keys(); + foreach (param, params) { + ParamSpec spec; + SimpleStatusSpec status; + spec.flags = 0; + + QStringList values = keyFile.value(param).split(QLatin1String(" ")); + + if (param.startsWith(QLatin1String("param-"))) { + spec.name = param.right(param.length() - 6); + + if (values.length() == 0) { + warning() << "param" << spec.name << "set but no signature defined"; + return false; + } + + if (spec.name.endsWith(QLatin1String("password"))) { + spec.flags |= ConnMgrParamFlagSecret; + } + + spec.signature = values[0]; + + if (values.contains(QLatin1String("secret"))) { + spec.flags |= ConnMgrParamFlagSecret; + } + + if (values.contains(QLatin1String("dbus-property"))) { + spec.flags |= ConnMgrParamFlagDBusProperty; + } + + if (values.contains(QLatin1String("required"))) { + spec.flags |= ConnMgrParamFlagRequired; + } + + if (values.contains(QLatin1String("register"))) { + spec.flags |= ConnMgrParamFlagRegister; + } + + paramSpecList.append(spec); + } else if (param.startsWith(QLatin1String("status-"))) { + QString statusName = param.right(param.length() - 7); + + if (values.length() == 0) { + warning() << "status" << statusName << "set but no type defined"; + return false; + } + + bool ok; + status.type = values[0].toUInt(&ok); + if (!ok) { + warning() << "status" << statusName << "set but type is not an uint"; + return false; + } + + if (values.contains(QLatin1String("settable"))) { + status.maySetOnSelf = true; + } else { + status.maySetOnSelf = false; + } + + if (values.contains(QLatin1String("message"))) { + status.canHaveMessage = true; + } else { + status.canHaveMessage = false; + } + + if (statuses.contains(statusName)) { + warning() << "status" << statusName << "defined more than once, " + "replacing it"; + } + + statuses.insert(statusName, status); + } + } + + protocolsMap.insert(protocol, ProtocolInfo(paramSpecList, PresenceSpecList(statuses))); + + /* now that we have all param-* created, let's find their default values */ + foreach (param, params) { + if (param.startsWith(QLatin1String("default-"))) { + QString paramName = param.right(param.length() - 8); + + if (!hasParameter(protocol, paramName)) { + warning() << "param" << paramName + << "has default value set, but not a definition"; + continue; + } + + ParamSpec *spec = getParameter(protocol, paramName); + + spec->flags |= ConnMgrParamFlagHasDefault; + + /* map based on the param dbus signature, otherwise use + * QString */ + QVariant value = valueForKey(param, spec->signature); + if (value.type() == QVariant::Invalid) { + warning() << "param" << paramName + << "has invalid signature"; + protocolsMap.clear(); + return false; + } + spec->defaultValue = QDBusVariant(value); + } + } + + ProtocolInfo &info = protocolsMap[protocol]; + info.vcardField = keyFile.value(QLatin1String("VCardField")); + info.englishName = keyFile.value(QLatin1String("EnglishName")); + if (info.englishName.isEmpty()) { + QStringList words = protocol.split(QLatin1Char('-')); + for (int i = 0; i < words.size(); ++i) { + words[i][0] = words[i].at(0).toUpper(); + } + info.englishName = words.join(QLatin1String(" ")); + } + + info.iconName = keyFile.value(QLatin1String("Icon")); + if (info.iconName.isEmpty()) { + info.iconName = QString(QLatin1String("im-%1")).arg(protocol); + } + + QStringList supportedMimeTypes = keyFile.valueAsStringList( + QLatin1String("SupportedAvatarMIMETypes")); + uint minHeight = keyFile.value(QLatin1String("MinimumAvatarHeight")).toUInt(); + uint maxHeight = keyFile.value(QLatin1String("MaximumAvatarHeight")).toUInt(); + uint recommendedHeight = keyFile.value( + QLatin1String("RecommendedAvatarHeight")).toUInt(); + uint minWidth = keyFile.value(QLatin1String("MinimumAvatarWidth")).toUInt(); + uint maxWidth = keyFile.value(QLatin1String("MaximumAvatarWidth")).toUInt(); + uint recommendedWidth = keyFile.value( + QLatin1String("RecommendedAvatarWidth")).toUInt(); + uint maxBytes = keyFile.value(QLatin1String("MaximumAvatarBytes")).toUInt(); + info.avatarRequirements = AvatarSpec(supportedMimeTypes, + minHeight, maxHeight, recommendedHeight, + minWidth, maxWidth, recommendedWidth, + maxBytes); + + QStringList rccGroups = keyFile.valueAsStringList( + QLatin1String("RequestableChannelClasses")); + RequestableChannelClass rcc; + foreach (const QString &rccGroup, rccGroups) { + keyFile.setGroup(rccGroup); + + foreach (const QString &key, keyFile.keys()) { + int spaceIdx = key.indexOf(QLatin1String(" ")); + if (spaceIdx == -1) { + continue; + } + + QString propertyName = key.mid(0, spaceIdx); + QString signature = key.mid(spaceIdx + 1); + QString param = keyFile.value(key); + QVariant value = valueForKey(key, signature); + rcc.fixedProperties.insert(propertyName, value); + } + + rcc.allowedProperties = keyFile.valueAsStringList( + QLatin1String("allowed")); + + info.rccs.append(rcc); + + rcc.fixedProperties.clear(); + rcc.allowedProperties.clear(); + } + } + } + + return true; +} + +bool ManagerFile::Private::isValid() const +{ + return ((keyFile.status() == KeyFile::NoError) && (valid)); +} + +bool ManagerFile::Private::hasParameter(const QString &protocol, + const QString ¶mName) const +{ + ParamSpecList paramSpecList = protocolsMap[protocol].params; + foreach (const ParamSpec ¶mSpec, paramSpecList) { + if (paramSpec.name == paramName) { + return true; + } + } + return false; +} + +ParamSpec *ManagerFile::Private::getParameter(const QString &protocol, + const QString ¶mName) +{ + ParamSpecList ¶mSpecList = protocolsMap[protocol].params; + for (int i = 0; i < paramSpecList.size(); ++i) { + ParamSpec ¶mSpec = paramSpecList[i]; + if (paramSpec.name == paramName) { + return ¶mSpec; + } + } + return NULL; +} + +QStringList ManagerFile::Private::protocols() const +{ + return protocolsMap.keys(); +} + +ParamSpecList ManagerFile::Private::parameters(const QString &protocol) const +{ + return protocolsMap.value(protocol).params; +} + +QVariant ManagerFile::Private::valueForKey(const QString ¶m, + const QString &dbusSignature) +{ + QString value = keyFile.rawValue(param); + return parseValueWithDBusSignature(value, dbusSignature); +} + + +/** + * \class ManagerFile + * \ingroup utils + * \headerfile TelepathyQt/manager-file.h <TelepathyQt/ManagerFile> + * + * \brief The ManagerFile class provides an easy way to read Telepathy manager + * files according to the \telepathy_spec. + * + * \todo Consider making this private, because the ConnectionManager and Account classes provide a + * nice view to the data parsed by this class anyway (fd.o #41655). + */ + +/** + * Create a ManagerFile object used to read .manager compliant files. + */ +ManagerFile::ManagerFile() + : mPriv(new Private()) +{ +} + +/** + * Create a ManagerFile object used to read .manager compliant files. + * + * \param cmName Name of the connection manager to read the file for. + */ +ManagerFile::ManagerFile(const QString &cmName) + : mPriv(new Private(cmName)) +{ +} + +/** + * Create a ManagerFile object used to read .manager compliant files. + */ +ManagerFile::ManagerFile(const ManagerFile &other) + : mPriv(new Private()) +{ + mPriv->cmName = other.mPriv->cmName; + mPriv->keyFile = other.mPriv->keyFile; + mPriv->protocolsMap = other.mPriv->protocolsMap; + mPriv->valid = other.mPriv->valid; +} + +/** + * Class destructor. + */ +ManagerFile::~ManagerFile() +{ + delete mPriv; +} + +ManagerFile &ManagerFile::operator=(const ManagerFile &other) +{ + mPriv->cmName = other.mPriv->cmName; + mPriv->keyFile = other.mPriv->keyFile; + mPriv->protocolsMap = other.mPriv->protocolsMap; + mPriv->valid = other.mPriv->valid; + return *this; +} + +/** + * Check whether or not a ManagerFile object is valid. If the file for the + * specified connection manager cannot be found it will be considered invalid. + * + * \return true if valid, false otherwise. + */ +bool ManagerFile::isValid() const +{ + return mPriv->isValid(); +} + +/** + * Return a list of all protocols defined in the manager file. + * + * \return List of all protocols defined in the file. + */ +QStringList ManagerFile::protocols() const +{ + return mPriv->protocols(); +} + +/** + * Return a list of parameters for the given \a protocol. + * + * \param protocol Name of the protocol to look for. + * \return List of ParamSpec of a specific protocol defined in the file, or an + * empty list if the protocol is not defined. + */ +ParamSpecList ManagerFile::parameters(const QString &protocol) const +{ + return mPriv->parameters(protocol); +} + +/** + * Return the name of the most common vCard field used for the given \a protocol's + * contact identifiers, normalized to lower case. + * + * \param protocol Name of the protocol to look for. + * \return The most common vCard field used for the given protocol's contact + * identifiers, or an empty string if there is no such field or the + * protocol is not defined. + */ +QString ManagerFile::vcardField(const QString &protocol) const +{ + return mPriv->protocolsMap.value(protocol).vcardField; +} + +/** + * Return the English-language name of the given \a protocol, such as "AIM" or "Yahoo!". + * + * The name can be used as a fallback if an application doesn't have a localized name for the + * protocol. + * + * If the manager file doesn't specify the english name, it is inferred from the protocol name, such + * that for example "google-talk" becomes "Google Talk", but "local-xmpp" becomes "Local Xmpp". + * + * \param protocol Name of the protocol to look for. + * \return An English-language name for the given \a protocol. + */ +QString ManagerFile::englishName(const QString &protocol) const +{ + return mPriv->protocolsMap.value(protocol).englishName; +} + +/** + * Return the name of an icon for the given \a protocol in the system's icon + * theme, such as "im-msn". + * + * If the manager file doesn't specify the icon name, "im-<protocolname>" is assumed. + * + * \param protocol Name of the protocol to look for. + * \return The likely name of an icon for the given \a protocol. + */ +QString ManagerFile::iconName(const QString &protocol) const +{ + return mPriv->protocolsMap.value(protocol).iconName; +} + +/** + * Return a list of channel classes which might be requestable from a connection + * to the given \a protocol. + * + * \param protocol Name of the protocol to look for. + * \return A list of channel classes which might be requestable from a + * connection to the given \a protocol or a default constructed + * RequestableChannelClassList instance if the protocol is not defined. + */ +RequestableChannelClassList ManagerFile::requestableChannelClasses( + const QString &protocol) const +{ + return mPriv->protocolsMap.value(protocol).rccs; +} + +/** + * Return a list of PresenceSpec representing the possible presence statuses + * from a connection to the given \a protocol. + * + * \param protocol Name of the protocol to look for. + * \return A list of PresenceSpec representing the possible presence statuses + * from a connection to the given \a protocol or an empty list + * if the protocol is not defined. + */ +PresenceSpecList ManagerFile::allowedPresenceStatuses(const QString &protocol) const +{ + return mPriv->protocolsMap.value(protocol).statuses; +} + +/** + * Return the requirements (size limits, supported MIME types, etc) + * for avatars used on the given \a protocol. + * + * \param protocol Name of the protocol to look for. + * \return The requirements for avatars used on the given \a protocol or an invalid + * AvatarSpec if the protocol is not defined. + */ +AvatarSpec ManagerFile::avatarRequirements(const QString &protocol) const +{ + return mPriv->protocolsMap.value(protocol).avatarRequirements; +} + +QVariant::Type ManagerFile::variantTypeFromDBusSignature(const QString &signature) +{ + QVariant::Type type; + if (signature == QLatin1String("b")) { + type = QVariant::Bool; + } else if (signature == QLatin1String("n") || signature == QLatin1String("i")) { + type = QVariant::Int; + } else if (signature == QLatin1String("q") || signature == QLatin1String("u")) { + type = QVariant::UInt; + } else if (signature == QLatin1String("x")) { + type = QVariant::LongLong; + } else if (signature == QLatin1String("t")) { + type = QVariant::ULongLong; + } else if (signature == QLatin1String("d")) { + type = QVariant::Double; + } else if (signature == QLatin1String("as")) { + type = QVariant::StringList; + } else if (signature == QLatin1String("s") || signature == QLatin1String("o")) { + type = QVariant::String; + } else { + type = QVariant::Invalid; + } + + return type; +} + +QVariant ManagerFile::parseValueWithDBusSignature(const QString &value, + const QString &dbusSignature) +{ + QVariant::Type type = variantTypeFromDBusSignature(dbusSignature); + + if (type == QVariant::Invalid) { + return QVariant(type); + } + + switch (type) { + case QVariant::Bool: + { + if (value.toLower() == QLatin1String("true") || + value == QLatin1String("1")) { + return QVariant(true); + } else { + return QVariant(false); + } + break; + } + case QVariant::Int: + return QVariant(value.toInt()); + case QVariant::UInt: + return QVariant(value.toUInt()); + case QVariant::LongLong: + return QVariant(value.toLongLong()); + case QVariant::ULongLong: + return QVariant(value.toULongLong()); + case QVariant::Double: + return QVariant(value.toDouble()); + case QVariant::StringList: + { + QStringList list; + QByteArray rawValue = value.toAscii(); + if (KeyFile::unescapeStringList(rawValue, 0, rawValue.size(), list)) { + return QVariant(list); + } else { + return QVariant(QVariant::Invalid); + } + } + default: + break; + } + return QVariant(value); +} + +} // Tp diff --git a/TelepathyQt/manager-file.h b/TelepathyQt/manager-file.h new file mode 100644 index 00000000..200c89d0 --- /dev/null +++ b/TelepathyQt/manager-file.h @@ -0,0 +1,78 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_manager_file_h_HEADER_GUARD_ +#define _TelepathyQt_manager_file_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/AvatarSpec> +#include <TelepathyQt/PresenceSpec> +#include <TelepathyQt/Types> + +#include <QMetaType> +#include <QVariant> + +namespace Tp +{ + +class TP_QT_EXPORT ManagerFile +{ +public: + ManagerFile(); + ManagerFile(const ManagerFile &other); + ManagerFile(const QString &cmName); + ~ManagerFile(); + + ManagerFile &operator=(const ManagerFile &other); + + QString cmName() const; + + bool isValid() const; + QStringList protocols() const; + ParamSpecList parameters(const QString &protocol) const; + QString vcardField(const QString &protocol) const; + QString englishName(const QString &protocol) const; + QString iconName(const QString &protocol) const; + RequestableChannelClassList requestableChannelClasses( + const QString &protocol) const; + PresenceSpecList allowedPresenceStatuses(const QString &protocol) const; + AvatarSpec avatarRequirements(const QString &protocol) const; + + static QVariant::Type variantTypeFromDBusSignature( + const QString &dbusSignature); + static QVariant parseValueWithDBusSignature(const QString &value, + const QString &dbusSignature); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} + +Q_DECLARE_METATYPE(Tp::ManagerFile); + +#endif diff --git a/TelepathyQt/media-session-handler.cpp b/TelepathyQt/media-session-handler.cpp new file mode 100644 index 00000000..b3eb4789 --- /dev/null +++ b/TelepathyQt/media-session-handler.cpp @@ -0,0 +1,26 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/MediaSessionHandler> + +#include "TelepathyQt/_gen/cli-media-session-handler-body.hpp" +#include "TelepathyQt/_gen/cli-media-session-handler.moc.hpp" diff --git a/TelepathyQt/media-session-handler.h b/TelepathyQt/media-session-handler.h new file mode 100644 index 00000000..9603f888 --- /dev/null +++ b/TelepathyQt/media-session-handler.h @@ -0,0 +1,50 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_media_session_handler_h_HEADER_GUARD_ +#define _TelepathyQt_media_session_handler_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +/** + * \addtogroup clientsideproxies Client-side proxies + * + * Proxy objects representing remote service objects accessed via D-Bus. + * + * In addition to providing direct access to methods, signals and properties + * exported by the remote objects, some of these proxies offer features like + * automatic inspection of remote object capabilities, property tracking, + * backwards compatibility helpers for older services and other utilities. + */ + +/** + * \defgroup clientmsesh Media session handler proxies + * \ingroup clientsideproxies + * + * Proxy objects representing remote Telepathy MediaSessionHandler objects. + */ + +#include <TelepathyQt/_gen/cli-media-session-handler.h> + +#endif diff --git a/TelepathyQt/media-session-handler.xml b/TelepathyQt/media-session-handler.xml new file mode 100644 index 00000000..8b051cd2 --- /dev/null +++ b/TelepathyQt/media-session-handler.xml @@ -0,0 +1,9 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Media session handler</tp:title> + +<xi:include href="../spec/Media_Session_Handler.xml"/> + +</tp:spec> diff --git a/TelepathyQt/media-stream-handler.cpp b/TelepathyQt/media-stream-handler.cpp new file mode 100644 index 00000000..1361716f --- /dev/null +++ b/TelepathyQt/media-stream-handler.cpp @@ -0,0 +1,26 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/MediaStreamHandler> + +#include "TelepathyQt/_gen/cli-media-stream-handler-body.hpp" +#include "TelepathyQt/_gen/cli-media-stream-handler.moc.hpp" diff --git a/TelepathyQt/media-stream-handler.h b/TelepathyQt/media-stream-handler.h new file mode 100644 index 00000000..635d8490 --- /dev/null +++ b/TelepathyQt/media-stream-handler.h @@ -0,0 +1,50 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_media_stream_handler_h_HEADER_GUARD_ +#define _TelepathyQt_media_stream_handler_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +/** + * \addtogroup clientsideproxies Client-side proxies + * + * Proxy objects representing remote service objects accessed via D-Bus. + * + * In addition to providing direct access to methods, signals and properties + * exported by the remote objects, some of these proxies offer features like + * automatic inspection of remote object capabilities, property tracking, + * backwards compatibility helpers for older services and other utilities. + */ + +/** + * \defgroup clientmstrh Media stream handler proxies + * \ingroup clientsideproxies + * + * Proxy objects representing remote Telepathy MediaStreamHandler objects. + */ + +#include <TelepathyQt/_gen/cli-media-stream-handler.h> + +#endif diff --git a/TelepathyQt/media-stream-handler.xml b/TelepathyQt/media-stream-handler.xml new file mode 100644 index 00000000..348d5a68 --- /dev/null +++ b/TelepathyQt/media-stream-handler.xml @@ -0,0 +1,9 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Media stream handler</tp:title> + +<xi:include href="../spec/Media_Stream_Handler.xml"/> + +</tp:spec> diff --git a/TelepathyQt/message-content-part.cpp b/TelepathyQt/message-content-part.cpp new file mode 100644 index 00000000..13363583 --- /dev/null +++ b/TelepathyQt/message-content-part.cpp @@ -0,0 +1,96 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 <TelepathyQt/MessageContentPart> + +#include <QSharedData> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT MessageContentPart::Private : public QSharedData +{ + Private(const MessagePart &mp) + : mp(mp) {} + + MessagePart mp; +}; + +/** + * \class MessageContentPart + * \ingroup wrappers + * \headerfile TelepathyQt/message-content-part.h <TelepathyQt/MessageContentPart> + * + * \brief The MessageContentPart class represents a Telepathy message part. + */ + +MessageContentPart::MessageContentPart() +{ +} + +MessageContentPart::MessageContentPart(const MessagePart &mp) + : mPriv(new Private(mp)) +{ +} + +MessageContentPart::MessageContentPart(const MessageContentPart &other) + : mPriv(other.mPriv) +{ +} + +MessageContentPart::~MessageContentPart() +{ +} + +MessageContentPart &MessageContentPart::operator=(const MessageContentPart &other) +{ + this->mPriv = other.mPriv; + return *this; +} + +bool MessageContentPart::operator==(const MessageContentPart &other) const +{ + if (!isValid() || !other.isValid()) { + if (!isValid() && !other.isValid()) { + return true; + } + return false; + } + + return mPriv->mp == other.mPriv->mp; +} + +MessagePart MessageContentPart::barePart() const +{ + return isValid() ? mPriv->mp : MessagePart(); +} + +/** + * \class MessageContentPartList + * \ingroup wrappers + * \headerfile TelepathyQt/message-content-part.h <TelepathyQt/MessageContentPartList> + * + * \brief The MessageContentPartList class represents a list of + * MessageContentPart. + */ + +} // Tp diff --git a/TelepathyQt/message-content-part.h b/TelepathyQt/message-content-part.h new file mode 100644 index 00000000..7ad289e6 --- /dev/null +++ b/TelepathyQt/message-content-part.h @@ -0,0 +1,96 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +#ifndef _TelepathyQt_message_content_part_h_HEADER_GUARD_ +#define _TelepathyQt_message_content_part_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class TP_QT_EXPORT MessageContentPart +{ +public: + MessageContentPart(); + MessageContentPart(const MessagePart &mp); + MessageContentPart(const MessageContentPart &other); + ~MessageContentPart(); + + bool isValid() const { return mPriv.constData() != 0; } + + MessageContentPart &operator=(const MessageContentPart &other); + bool operator==(const MessageContentPart &other) const; + + MessagePart barePart() const; + +private: + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +class TP_QT_EXPORT MessageContentPartList : + public QList<MessageContentPart> +{ +public: + MessageContentPartList() { } + MessageContentPartList(const MessagePart &mp) + { + append(MessageContentPart(mp)); + } + MessageContentPartList(const MessagePartList &mps) + { + Q_FOREACH (const MessagePart &mp, mps) { + append(MessageContentPart(mp)); + } + } + MessageContentPartList(const MessageContentPart &mcp) + { + append(mcp); + } + MessageContentPartList(const QList<MessageContentPart> &other) + : QList<MessageContentPart>(other) + { + } + + MessagePartList bareParts() const + { + MessagePartList list; + Q_FOREACH (const MessageContentPart &mcp, *this) { + list.append(mcp.barePart()); + } + return list; + } +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::MessageContentPart); +Q_DECLARE_METATYPE(Tp::MessageContentPartList); + +#endif diff --git a/TelepathyQt/message.cpp b/TelepathyQt/message.cpp new file mode 100644 index 00000000..1f6ca82b --- /dev/null +++ b/TelepathyQt/message.cpp @@ -0,0 +1,911 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/Message> +#include <TelepathyQt/ReceivedMessage> + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/TextChannel> + +#include <QDateTime> +#include <QPointer> +#include <QSet> + +namespace Tp +{ + +namespace +{ + +QVariant valueFromPart(const MessagePartList &parts, uint index, const char *key) +{ + return parts.at(index).value(QLatin1String(key)).variant(); +} + +uint uintOrZeroFromPart(const MessagePartList &parts, uint index, const char *key) +{ + return valueFromPart(parts, index, key).toUInt(); +} + +QString stringOrEmptyFromPart(const MessagePartList &parts, uint index, const char *key) +{ + QString s = valueFromPart(parts, index, key).toString(); + if (s.isNull()) { + s = QLatin1String(""); + } + return s; +} + +bool booleanFromPart(const MessagePartList &parts, uint index, const char *key, + bool assumeIfAbsent) +{ + QVariant v = valueFromPart(parts, index, key); + if (v.isValid() && v.type() == QVariant::Bool) { + return v.toBool(); + } + return assumeIfAbsent; +} + +MessagePartList partsFromPart(const MessagePartList &parts, uint index, const char *key) +{ + return qdbus_cast<MessagePartList>(valueFromPart(parts, index, key)); +} + +bool partContains(const MessagePartList &parts, uint index, const char *key) +{ + return parts.at(index).contains(QLatin1String(key)); +} + +} + +struct TP_QT_NO_EXPORT Message::Private : public QSharedData +{ + Private(const MessagePartList &parts); + ~Private(); + + uint senderHandle() const; + QString senderId() const; + uint pendingId() const; + void clearSenderHandle(); + + MessagePartList parts; + + // if the Text interface says "non-text" we still only have the text, + // because the interface can't tell us anything else... + bool forceNonText; + + // for received messages only + QWeakPointer<TextChannel> textChannel; + ContactPtr sender; +}; + +Message::Private::Private(const MessagePartList &parts) + : parts(parts), + forceNonText(false), + sender(0) +{ +} + +Message::Private::~Private() +{ +} + +inline uint Message::Private::senderHandle() const +{ + return uintOrZeroFromPart(parts, 0, "message-sender"); +} + +inline QString Message::Private::senderId() const +{ + return stringOrEmptyFromPart(parts, 0, "message-sender-id"); +} + +inline uint Message::Private::pendingId() const +{ + return uintOrZeroFromPart(parts, 0, "pending-message-id"); +} + +void Message::Private::clearSenderHandle() +{ + parts[0].remove(QLatin1String("message-sender")); +} + +/** + * \class Message + * \ingroup clientchannel + * \headerfile TelepathyQt/message.h <TelepathyQt/Message> + * + * \brief The Message class represents a Telepathy message in a TextChannel. + * + * This class is implicitly shared, like QString. + */ + +/** + * \internal Default constructor. + */ +Message::Message() + : mPriv(new Private(MessagePartList())) +{ +} + +/** + * Construct a new Message object. + * + * \param parts The parts of a message as defined by the \telepathy_spec. + * This list must have length at least 1. + */ +Message::Message(const MessagePartList &parts) + : mPriv(new Private(parts)) +{ + Q_ASSERT(parts.size() > 0); +} + +/** + * Construct a new Message object. + * + * \param timestamp The time the message was sent. + * \param type The message type. + * \param text The message body. + */ +Message::Message(uint timestamp, uint type, const QString &text) + : mPriv(new Private(MessagePartList() << MessagePart() << MessagePart())) +{ + mPriv->parts[0].insert(QLatin1String("message-sent"), + QDBusVariant(static_cast<qlonglong>(timestamp))); + mPriv->parts[0].insert(QLatin1String("message-type"), + QDBusVariant(type)); + + mPriv->parts[1].insert(QLatin1String("content-type"), + QDBusVariant(QLatin1String("text/plain"))); + mPriv->parts[1].insert(QLatin1String("content"), QDBusVariant(text)); +} + +/** + * Construct a new Message object. + * + * \param type The message type. + * \param text The message body. + */ +Message::Message(ChannelTextMessageType type, const QString &text) + : mPriv(new Private(MessagePartList() << MessagePart() << MessagePart())) +{ + mPriv->parts[0].insert(QLatin1String("message-type"), + QDBusVariant(static_cast<uint>(type))); + + mPriv->parts[1].insert(QLatin1String("content-type"), + QDBusVariant(QLatin1String("text/plain"))); + mPriv->parts[1].insert(QLatin1String("content"), QDBusVariant(text)); +} + +/** + * Copy constructor. + */ +Message::Message(const Message &other) + : mPriv(other.mPriv) +{ +} + +/** + * Assignment operator. + */ +Message &Message::operator=(const Message &other) +{ + if (this != &other) { + mPriv = other.mPriv; + } + + return *this; +} + +/** + * Equality operator. + */ +bool Message::operator==(const Message &other) const +{ + return this->mPriv == other.mPriv; +} + +/** + * Class destructor. + */ +Message::~Message() +{ +} + +/** + * Return the time the message was sent, or QDateTime() if that time is + * unknown. + * + * \return The timestamp as QDateTime. + */ +QDateTime Message::sent() const +{ + // FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690 + uint stamp = valueFromPart(mPriv->parts, 0, "message-sent").toUInt(); + if (stamp != 0) { + return QDateTime::fromTime_t(stamp); + } else { + return QDateTime(); + } +} + +/** + * Return the type of this message, or #ChannelTextMessageTypeNormal + * if the type is not recognised. + * + * \return The type as #ChannelTextMessageType. + */ +ChannelTextMessageType Message::messageType() const +{ + uint raw = valueFromPart(mPriv->parts, 0, "message-type").toUInt(); + + if (raw < static_cast<uint>(NUM_CHANNEL_TEXT_MESSAGE_TYPES)) { + return ChannelTextMessageType(raw); + } else { + return ChannelTextMessageTypeNormal; + } +} + +/** + * Return whether this message was truncated during delivery. + * + * \return \c true if truncated, \c false otherwise. + */ +bool Message::isTruncated() const +{ + for (int i = 1; i < size(); i++) { + if (booleanFromPart(mPriv->parts, i, "truncated", false)) { + return true; + } + } + return false; +} + +/** + * Return whether this message contains parts not representable as plain + * text. + * + * \return \c true if it cannot completely be represented as plain text, \c false + * otherwise. + */ +bool Message::hasNonTextContent() const +{ + if (mPriv->forceNonText || size() <= 1 || isSpecificToDBusInterface()) { + return true; + } + + QSet<QString> texts; + QSet<QString> textNeeded; + + for (int i = 1; i < size(); i++) { + QString altGroup = stringOrEmptyFromPart(mPriv->parts, i, "alternative"); + QString contentType = stringOrEmptyFromPart(mPriv->parts, i, "content-type"); + + if (contentType == QLatin1String("text/plain")) { + if (!altGroup.isEmpty()) { + // we can use this as an alternative for a non-text part + // with the same altGroup + texts << altGroup; + } + } else { + QString alt = stringOrEmptyFromPart(mPriv->parts, i, "alternative"); + if (altGroup.isEmpty()) { + // we can't possibly rescue this part by using a text/plain + // alternative, because it's not in any alternative group + return true; + } else { + // maybe we'll find a text/plain alternative for this + textNeeded << altGroup; + } + } + } + + textNeeded -= texts; + return !textNeeded.isEmpty(); +} + +/** + * Return the unique token identifying this message (e.g. the id attribute + * for XMPP messages), or an empty string if there is no suitable token. + * + * \return The non-empty message identifier, or an empty string if none. + */ +QString Message::messageToken() const +{ + return stringOrEmptyFromPart(mPriv->parts, 0, "message-token"); +} + +/** + * Return whether this message is specific to a D-Bus interface. This is + * \c false in almost all cases. + * + * If this function returns \c true, the message is specific to the interface + * indicated by dbusInterface(). Clients that don't understand that interface + * should not display the message. However, if the client would acknowledge + * an ordinary message, it must also acknowledge this interface-specific + * message. + * + * \return \c true if dbusInterface() would return a non-empty string, \c false otherwise. + * \sa dbusInterface() + */ +bool Message::isSpecificToDBusInterface() const +{ + return !dbusInterface().isEmpty(); +} + +/** + * Return the D-Bus interface to which this message is specific, or an + * empty string for normal messages. + * + * \return The D-Bus interface name, or an empty string. + * \sa isSpecificToDBusInterface() + */ +QString Message::dbusInterface() const +{ + return stringOrEmptyFromPart(mPriv->parts, 0, "interface"); +} + +/** + * Return the message body containing all "text/plain" parts. + * + * \return The body text. + */ +QString Message::text() const +{ + // Alternative-groups for which we've already emitted an alternative + QSet<QString> altGroupsUsed; + QString text; + + for (int i = 1; i < size(); i++) { + QString altGroup = stringOrEmptyFromPart(mPriv->parts, i, "alternative"); + QString contentType = stringOrEmptyFromPart(mPriv->parts, i, "content-type"); + + if (contentType == QLatin1String("text/plain")) { + if (!altGroup.isEmpty()) { + if (altGroupsUsed.contains(altGroup)) { + continue; + } else { + altGroupsUsed << altGroup; + } + } + + QVariant content = valueFromPart(mPriv->parts, i, "content"); + if (content.type() == QVariant::String) { + text += content.toString(); + } else { + // O RLY? + debug() << "allegedly text/plain part wasn't"; + } + } + } + + return text; +} + +/** + * Return the message's header part, as defined by the \telepathy_spec. + * + * This is provided for advanced clients that need to access + * additional information not available through the normal Message API. + * + * \return The header as a MessagePart object. The same thing as part(0). + */ +MessagePart Message::header() const +{ + return part(0); +} + +/** + * Return the number of parts in this message. + * + * \return 1 greater than the largest valid argument to part(). + * \sa part(), parts() + */ +int Message::size() const +{ + return mPriv->parts.size(); +} + +/** + * Return the message's part for \a index, as defined by the \telepathy_spec. + * + * This is provided for advanced clients that need to access + * additional information not available through the normal Message API. + * + * \param index The part to access, which must be strictly less than size(); + * part number 0 is the header, parts numbered 1 or greater + * are the body of the message. + * \return A MessagePart object. + */ +MessagePart Message::part(uint index) const +{ + return mPriv->parts.at(index); +} + +/** + * Return the list of message parts forming this message. + * + * \return The list of MessagePart objects. + */ +MessagePartList Message::parts() const +{ + return mPriv->parts; +} + +/** + * \class ReceivedMessage + * \ingroup clientchannel + * \headerfile TelepathyQt/message.h <TelepathyQt/ReceivedMessage> + * + * \brief The ReceivedMessage class is a subclass of Message, representing a + * received message only. + * + * It contains additional information that's generally only + * available on received messages. + */ + +/** + * \class ReceivedMessage::DeliveryDetails + * \ingroup clientchannel + * \headerfile TelepathyQt/message.h <TelepathyQt/ReceivedMessage> + * + * \brief The ReceivedMessage::DeliveryDetails class represents the details of a delivery report. + */ + +struct TP_QT_NO_EXPORT ReceivedMessage::DeliveryDetails::Private : public QSharedData +{ + Private(const MessagePartList &parts) + : parts(parts) + { + } + + MessagePartList parts; +}; + +/** + * Default constructor. + */ +ReceivedMessage::DeliveryDetails::DeliveryDetails() +{ +} + +/** + * Copy constructor. + */ +ReceivedMessage::DeliveryDetails::DeliveryDetails(const DeliveryDetails &other) + : mPriv(other.mPriv) +{ +} + +/** + * Construct a new ReceivedMessage::DeliveryDetails object. + * + * \param The message parts. + */ +ReceivedMessage::DeliveryDetails::DeliveryDetails(const MessagePartList &parts) + : mPriv(new Private(parts)) +{ +} + +/** + * Class destructor. + */ +ReceivedMessage::DeliveryDetails::~DeliveryDetails() +{ +} + +/** + * Assignment operator. + */ +ReceivedMessage::DeliveryDetails &ReceivedMessage::DeliveryDetails::operator=( + const DeliveryDetails &other) +{ + this->mPriv = other.mPriv; + return *this; +} + +/** + * Return the delivery status of a message. + * + * \return The delivery status as #DeliveryStatus. + */ +DeliveryStatus ReceivedMessage::DeliveryDetails::status() const +{ + if (!isValid()) { + return DeliveryStatusUnknown; + } + return static_cast<DeliveryStatus>(uintOrZeroFromPart(mPriv->parts, 0, "delivery-status")); +} + +/** + * Return whether this delivery report contains an identifier for the message to which it + * refers. + * + * \return \c true if an original message token is known, \c false otherwise. + * \sa originalToken() + */ +bool ReceivedMessage::DeliveryDetails::hasOriginalToken() const +{ + if (!isValid()) { + return false; + } + return partContains(mPriv->parts, 0, "delivery-token"); +} + +/** + * Return an identifier for the message to which this delivery report refers, or an empty string if + * hasOriginalToken() returns \c false. + * + * Clients may match this against the token produced by the TextChannel::send() method and + * TextChannel::messageSent() signal. A status report with no token could match any sent message, + * and a sent message with an empty token could match any status report. + * If multiple sent messages match, clients should use some reasonable heuristic. + * + * \return The message token if hasOriginalToken() returns \c true, an empty string otherwise. + * \sa hasOriginalToken(). + */ +QString ReceivedMessage::DeliveryDetails::originalToken() const +{ + if (!isValid()) { + return QString(); + } + return stringOrEmptyFromPart(mPriv->parts, 0, "delivery-token"); +} + +/** + * Return whether the delivery of the message this delivery report refers to, failed. + * + * \return \c true if the message delivery failed, \c false otherwise. + * \sa error() + */ +bool ReceivedMessage::DeliveryDetails::isError() const +{ + if (!isValid()) { + return false; + } + DeliveryStatus st(status()); + return st == DeliveryStatusTemporarilyFailed || st == DeliveryStatusPermanentlyFailed; +} + +/** + * Return the reason for the delivery failure if known. + * + * \return The reason as #ChannelTextSendError. + * \sa isError() + */ +ChannelTextSendError ReceivedMessage::DeliveryDetails::error() const +{ + if (!isValid()) { + return ChannelTextSendErrorUnknown; + } + return static_cast<ChannelTextSendError>(uintOrZeroFromPart(mPriv->parts, 0, "delivery-error")); +} + +/** + * Return whether this delivery report contains a debugging information on why the message it refers + * to could not be delivered. + * + * \return \c true if a debugging information is provided, \c false otherwise. + * \sa debugMessage() + */ +bool ReceivedMessage::DeliveryDetails::hasDebugMessage() const +{ + if (!isValid()) { + return false; + } + return partContains(mPriv->parts, 0, "delivery-error-message"); +} + +/** + * Return the debugging information on why the message this delivery report refers to could not be + * delivered. + * + * \return The debug string. + * \sa hasDebugMessage() + */ +QString ReceivedMessage::DeliveryDetails::debugMessage() const +{ + if (!isValid()) { + return QString(); + } + return stringOrEmptyFromPart(mPriv->parts, 0, "delivery-error-message"); +} + +/** + * Return the reason for the delivery failure if known, specified as a + * (possibly implementation-specific) D-Bus error. + * + * \return The D-Bus error string representing the error. + */ +QString ReceivedMessage::DeliveryDetails::dbusError() const +{ + if (!isValid()) { + return QString(); + } + QString ret = stringOrEmptyFromPart(mPriv->parts, 0, "delivery-dbus-error"); + if (ret.isEmpty()) { + switch (error()) { + case ChannelTextSendErrorOffline: + ret = TP_QT_ERROR_OFFLINE; + break; + case ChannelTextSendErrorInvalidContact: + ret = TP_QT_ERROR_DOES_NOT_EXIST; + break; + case ChannelTextSendErrorPermissionDenied: + ret = TP_QT_ERROR_PERMISSION_DENIED; + break; + case ChannelTextSendErrorTooLong: + ret = TP_QT_ERROR_INVALID_ARGUMENT; + break; + case ChannelTextSendErrorNotImplemented: + ret = TP_QT_ERROR_NOT_IMPLEMENTED; + break; + default: + ret = TP_QT_ERROR_NOT_AVAILABLE; + } + } + return ret; +} + +/** + * Return whether the message content for the message this delivery report refers to is known. + * + * \return \c true if the original message content is known, \c false otherwise. + * \sa echoedMessage() + */ +bool ReceivedMessage::DeliveryDetails::hasEchoedMessage() const +{ + if (!isValid()) { + return false; + } + return partContains(mPriv->parts, 0, "delivery-echo"); +} + +/** + * Return the Message object for the message this delivery report refers to, omitted if the message + * is unknown. + * + * <div class='rationale'> + * <h5>Rationale:</h5> + * <div> + * Some protocols, like XMPP, echo the failing message back to the sender. This is sometimes the + * only way to match it against the sent message, so we include it here. + * </div> + * </div> + * + * \return The Message object, or an empty Message object if hasEchoedMessage() + * returns \c false. + * \sa hasEchoedMessage() + */ +Message ReceivedMessage::DeliveryDetails::echoedMessage() const +{ + if (!isValid()) { + return Message(); + } + return Message(partsFromPart(mPriv->parts, 0, "delivery-echo")); +} + +/** + * \internal Default constructor. + */ +ReceivedMessage::ReceivedMessage() +{ +} + +/** + * Construct a new ReceivedMessage object. + * + * \param parts The parts of a message as defined by the \telepathy_spec. + * This list must have length at least 1. + * \param channel The channel owning this message. + */ +ReceivedMessage::ReceivedMessage(const MessagePartList &parts, + const TextChannelPtr &channel) + : Message(parts) +{ + if (!mPriv->parts[0].contains(QLatin1String("message-received"))) { + mPriv->parts[0].insert(QLatin1String("message-received"), + QDBusVariant(static_cast<qlonglong>( + QDateTime::currentDateTime().toTime_t()))); + } + mPriv->textChannel = QWeakPointer<TextChannel>(channel.data()); +} + +/** + * Copy constructor. + */ +ReceivedMessage::ReceivedMessage(const ReceivedMessage &other) + : Message(other) +{ +} + +/** + * Assignment operator. + */ +ReceivedMessage &ReceivedMessage::operator=(const ReceivedMessage &other) +{ + if (this != &other) { + mPriv = other.mPriv; + } + + return *this; +} + +/** + * Class destructor. + */ +ReceivedMessage::~ReceivedMessage() +{ +} + +/** + * Return the time the message was received. + * + * \return The timestamp as QDateTime, or QDateTime() if unknown. + */ +QDateTime ReceivedMessage::received() const +{ + // FIXME See http://bugs.freedesktop.org/show_bug.cgi?id=21690 + uint stamp = valueFromPart(mPriv->parts, 0, "message-received").toUInt(); + if (stamp != 0) { + return QDateTime::fromTime_t(stamp); + } else { + return QDateTime(); + } +} + +/** + * Return the contact who sent the message. + * + * \return A pointer to the Contact object. + * \sa senderNickname() + */ +ContactPtr ReceivedMessage::sender() const +{ + return mPriv->sender; +} + +/** + * Return the nickname chosen by the sender of the message, which can be different for each + * message in a conversation. + * + * \return The nickname. + * \sa sender() + */ +QString ReceivedMessage::senderNickname() const +{ + QString ret = stringOrEmptyFromPart(mPriv->parts, 0, "sender-nickname"); + if (ret.isEmpty() && mPriv->sender) { + ret = mPriv->sender->alias(); + } + return ret; +} + +/** + * If this message replaces a previous message, return the value of + * messageToken() for that previous message. Otherwise, return an empty string. + * + * For instance, a user interface could replace the superseded + * message with this message, or grey out the superseded message. + * + * \return The message token of the superseded message or an empty string if none. + */ +QString ReceivedMessage::supersededToken() const +{ + return stringOrEmptyFromPart(mPriv->parts, 0, "supersedes"); +} + +/** + * Return whether the incoming message was part of a replay of message + * history. + * + * If \c true, loggers can use this to improve their heuristics for elimination + * of duplicate messages (a simple, correct implementation would be to avoid + * logging any message that has this flag). + * + * \return \c true if the scrollback flag is set, \c false otherwise. + */ +bool ReceivedMessage::isScrollback() const +{ + return booleanFromPart(mPriv->parts, 0, "scrollback", false); +} + +/** + * Return whether the incoming message was seen in a previous channel during + * the lifetime of the connection, but was not acknowledged before that + * channel closed, causing the channel in which it now appears to open. + * + * If \c true, loggers should not log this message again. + * + * \return \c true if the rescued flag is set, \c false otherwise. + */ +bool ReceivedMessage::isRescued() const +{ + return booleanFromPart(mPriv->parts, 0, "rescued", false); +} + +/** + * Return whether the incoming message is a delivery report. + * + * \return \c true if a delivery report, \c false otherwise. + * \sa deliveryDetails() + */ +bool ReceivedMessage::isDeliveryReport() const +{ + return messageType() == ChannelTextMessageTypeDeliveryReport; +} + +/** + * Return the details of a delivery report. + * + * This method should only be used if isDeliveryReport() returns \c true. + * + * \return The delivery report as a ReceivedMessage::DeliveryDetails object. + * \sa isDeliveryReport() + */ +ReceivedMessage::DeliveryDetails ReceivedMessage::deliveryDetails() const +{ + return DeliveryDetails(parts()); +} + +/** + * Return whether this message is from \a channel. + * + * \return \c true if the message is from \a channel, \c false otherwise. + */ +bool ReceivedMessage::isFromChannel(const TextChannelPtr &channel) const +{ + return TextChannelPtr(mPriv->textChannel) == channel; +} + +uint ReceivedMessage::pendingId() const +{ + return mPriv->pendingId(); +} + +uint ReceivedMessage::senderHandle() const +{ + return mPriv->senderHandle(); +} + +QString ReceivedMessage::senderId() const +{ + return mPriv->senderId(); +} + +void ReceivedMessage::setForceNonText() +{ + mPriv->forceNonText = true; +} + +void ReceivedMessage::clearSenderHandle() +{ + mPriv->clearSenderHandle(); +} + +void ReceivedMessage::setSender(const ContactPtr &sender) +{ + mPriv->sender = sender; +} + +} // Tp diff --git a/TelepathyQt/message.h b/TelepathyQt/message.h new file mode 100644 index 00000000..c6a0211c --- /dev/null +++ b/TelepathyQt/message.h @@ -0,0 +1,173 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_message_h_HEADER_GUARD_ +#define _TelepathyQt_message_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <QSharedDataPointer> + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Contact> +#include <TelepathyQt/Types> + +class QDateTime; + +namespace Tp +{ + +class Contact; +class TextChannel; + +class TP_QT_EXPORT Message +{ +public: + Message(ChannelTextMessageType, const QString &); + Message(const Message &other); + ~Message(); + + Message &operator=(const Message &other); + bool operator==(const Message &other) const; + inline bool operator!=(const Message &other) const + { + return !(*this == other); + } + + // Convenient access to headers + + QDateTime sent() const; + + ChannelTextMessageType messageType() const; + + bool isTruncated() const; + + bool hasNonTextContent() const; + + QString messageToken() const; + + bool isSpecificToDBusInterface() const; + QString dbusInterface() const; + + QString text() const; + + // Direct access to the whole message (header and body) + + MessagePart header() const; + + int size() const; + MessagePart part(uint index) const; + MessagePartList parts() const; + +private: + friend class ContactMessenger; + friend class ReceivedMessage; + friend class TextChannel; + + TP_QT_NO_EXPORT Message(); + TP_QT_NO_EXPORT Message(const MessagePartList &parts); + TP_QT_NO_EXPORT Message(uint, uint, const QString &); + + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +class TP_QT_EXPORT ReceivedMessage : public Message +{ +public: + class DeliveryDetails + { + public: + DeliveryDetails(); + DeliveryDetails(const DeliveryDetails &other); + ~DeliveryDetails(); + + DeliveryDetails &operator=(const DeliveryDetails &other); + + bool isValid() const { return mPriv.constData() != 0; } + + DeliveryStatus status() const; + + bool hasOriginalToken() const; + QString originalToken() const; + + bool isError() const; + ChannelTextSendError error() const; + + bool hasDebugMessage() const; + QString debugMessage() const; + + QString dbusError() const; + + bool hasEchoedMessage() const; + Message echoedMessage() const; + + private: + friend class ReceivedMessage; + + TP_QT_NO_EXPORT DeliveryDetails(const MessagePartList &parts); + + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; + }; + + ReceivedMessage(const ReceivedMessage &other); + ReceivedMessage &operator=(const ReceivedMessage &other); + ~ReceivedMessage(); + + QDateTime received() const; + ContactPtr sender() const; + QString senderNickname() const; + + QString supersededToken() const; + + bool isScrollback() const; + bool isRescued() const; + + bool isDeliveryReport() const; + DeliveryDetails deliveryDetails() const; + + bool isFromChannel(const TextChannelPtr &channel) const; + +private: + friend class TextChannel; + + TP_QT_NO_EXPORT ReceivedMessage(const MessagePartList &parts, + const TextChannelPtr &channel); + TP_QT_NO_EXPORT ReceivedMessage(); + + TP_QT_NO_EXPORT uint senderHandle() const; + TP_QT_NO_EXPORT QString senderId() const; + TP_QT_NO_EXPORT uint pendingId() const; + + TP_QT_NO_EXPORT void setForceNonText(); + TP_QT_NO_EXPORT void clearSenderHandle(); + TP_QT_NO_EXPORT void setSender(const ContactPtr &sender); +}; + +} // Tp + +#endif diff --git a/TelepathyQt/method-invocation-context.dox b/TelepathyQt/method-invocation-context.dox new file mode 100644 index 00000000..dd7f4ab5 --- /dev/null +++ b/TelepathyQt/method-invocation-context.dox @@ -0,0 +1,41 @@ +/* + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +/** + * \class Tp::MethodInvocationContext + * \ingroup utils + * \headerfile TelepathyQt/method-invocation-context.h <TelepathyQt/MethodInvocationContext> + * + * \brief The MethodInvocationContext class provides a way for the service implementation to + * respond to method calls. + * + * The methods setFinished() and setFinishedWithError() can be used to indicate + * whether the method call succeeded or failed. + * + * If neither setFinished() nor setFinishedWithError() is called explicitly, + * the method call will be considered to have failed. + * + " In case an asynchronous operation needs to be performed when implementing a method call + * receiving a MethodInvocationContextPtr object, a reference to this object may be kept around + * until all asynchronous operations finish, and the appropriate finish method + * should be called to indicate whether the method call succeeded or failed later. + */ diff --git a/TelepathyQt/method-invocation-context.h b/TelepathyQt/method-invocation-context.h new file mode 100644 index 00000000..29c5f9fb --- /dev/null +++ b/TelepathyQt/method-invocation-context.h @@ -0,0 +1,192 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_method_invocation_context_h_HEADER_GUARD_ +#define _TelepathyQt_method_invocation_context_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <QtDBus> +#include <QtCore> + +namespace Tp +{ + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +namespace MethodInvocationContextTypes +{ + +struct Nil +{ +}; + +template<int Index, + typename T1, typename T2, typename T3, typename T4, + typename T5, typename T6, typename T7, typename T8> +struct Select +{ + typedef Select<Index - 1, T2, T3, T4, T5, T6, T7, T8, Nil> Next; + typedef typename Next::Type Type; +}; +template<typename T1, typename T2, typename T3, typename T4, + typename T5, typename T6, typename T7, typename T8> +struct Select<0, T1, T2, T3, T4, T5, T6, T7, T8> +{ + typedef T1 Type; +}; + +template<typename T1, typename T2, typename T3, typename T4, + typename T5, typename T6, typename T7, typename T8> +struct ForEach +{ + typedef ForEach<T2, T3, T4, T5, T6, T7, T8, Nil> Next; + enum { Total = Next::Total + 1 }; +}; +template<> +struct ForEach<Nil, Nil, Nil, Nil, Nil, Nil, Nil, Nil> +{ + enum { Total = 0 }; +}; + +} + +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +template<typename T1 = MethodInvocationContextTypes::Nil, typename T2 = MethodInvocationContextTypes::Nil, + typename T3 = MethodInvocationContextTypes::Nil, typename T4 = MethodInvocationContextTypes::Nil, + typename T5 = MethodInvocationContextTypes::Nil, typename T6 = MethodInvocationContextTypes::Nil, + typename T7 = MethodInvocationContextTypes::Nil, typename T8 = MethodInvocationContextTypes::Nil> +class MethodInvocationContext : public RefCounted +{ +#ifndef DOXYGEN_SHOULD_SKIP_THIS + template<int Index> + struct Select : MethodInvocationContextTypes::Select<Index, T1, T2, T3, T4, T5, T6, T7, T8> + { + }; + + typedef MethodInvocationContextTypes::ForEach<T1, T2, T3, T4, T5, T6, T7, T8> ForEach; + enum { Count = ForEach::Total }; +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +public: + MethodInvocationContext(const QDBusConnection &bus, const QDBusMessage &message) + : mBus(bus), mMessage(message), mFinished(false) + { + mMessage.setDelayedReply(true); + } + + virtual ~MethodInvocationContext() + { + if (!mFinished) { + setFinishedWithError(QString(), QString()); + } + } + + bool isFinished() const { return mFinished; } + bool isError() const { return !mErrorName.isEmpty(); } + QString errorName() const { return mErrorName; } + QString errorMessage() const { return mErrorMessage; } + + void setFinished(const T1 &t1 = T1(), const T2 &t2 = T2(), const T3 &t3 = T3(), + const T4 &t4 = T4(), const T5 &t5 = T5(), const T6 &t6 = T6(), + const T7 &t7 = T7(), const T8 &t8 = T8()) + { + if (mFinished) { + return; + } + + mFinished = true; + + setReplyValue(0, qVariantFromValue(t1)); + setReplyValue(1, qVariantFromValue(t2)); + setReplyValue(2, qVariantFromValue(t3)); + setReplyValue(3, qVariantFromValue(t4)); + setReplyValue(4, qVariantFromValue(t5)); + setReplyValue(5, qVariantFromValue(t6)); + setReplyValue(6, qVariantFromValue(t7)); + setReplyValue(7, qVariantFromValue(t8)); + + if (mReply.isEmpty()) { + mBus.send(mMessage.createReply()); + } else { + mBus.send(mMessage.createReply(mReply)); + } + onFinished(); + } + + void setFinishedWithError(const QString &errorName, + const QString &errorMessage) + { + if (mFinished) { + return; + } + + mFinished = true; + + if (errorName.isEmpty()) { + mErrorName = QLatin1String("org.freedesktop.Telepathy.Qt4.ErrorHandlingError"); + } else { + mErrorName = errorName; + } + mErrorMessage = errorMessage; + + mBus.send(mMessage.createErrorReply(mErrorName, mErrorMessage)); + onFinished(); + } + + template<int Index> inline + typename Select<Index>::Type argumentAt() const + { + Q_ASSERT(Index >= 0 && Index < Count); + return qdbus_cast<typename Select<Index>::Type>(mReply.value(Index)); + } + +protected: + virtual void onFinished() {} + +private: + Q_DISABLE_COPY(MethodInvocationContext) + + void setReplyValue(int index, const QVariant &value) + { + if (index >= Count) { + return; + } + mReply.insert(index, value); + } + + QDBusConnection mBus; + QDBusMessage mMessage; + bool mFinished; + QList<QVariant> mReply; + QString mErrorName; + QString mErrorMessage; +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::MethodInvocationContextTypes::Nil) + +#endif diff --git a/TelepathyQt/not-filter.dox b/TelepathyQt/not-filter.dox new file mode 100644 index 00000000..3c45a812 --- /dev/null +++ b/TelepathyQt/not-filter.dox @@ -0,0 +1,32 @@ +/* + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +/** + * \class Tp::NotFilter + * \ingroup utils + * \headerfile TelepathyQt/not-filter.h <TelepathyQt/NotFilter> + * + * \brief The NotFilter class provides a generic filter object to be used + * in conjunction of other filters. + * + * The NotFilter will match if its given filter does not match its criteria. + */ diff --git a/TelepathyQt/not-filter.h b/TelepathyQt/not-filter.h new file mode 100644 index 00000000..b31b38ee --- /dev/null +++ b/TelepathyQt/not-filter.h @@ -0,0 +1,73 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_not_filter_h_HEADER_GUARD_ +#define _TelepathyQt_not_filter_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Filter> +#include <TelepathyQt/Types> + +namespace Tp +{ + +template <class T> +class NotFilter : public Filter<T> +{ +public: + static SharedPtr<NotFilter<T> > create( + const SharedPtr<const Filter<T> > &filter = SharedPtr<const Filter<T> >()) + { + return SharedPtr<NotFilter<T> >(new NotFilter<T>(filter)); + } + + inline virtual ~NotFilter() { } + + inline virtual bool isValid() const + { + return mFilter && mFilter->isValid(); + } + + inline virtual bool matches(const SharedPtr<T> &t) const + { + if (!isValid()) { + return false; + } + + return !mFilter->matches(t); + } + + inline SharedPtr<const Filter<T> > filter() const { return mFilter; } + +private: + NotFilter(const SharedPtr<const Filter<T> > &filter) + : Filter<T>(), mFilter(filter) { } + + SharedPtr<const Filter<T> > mFilter; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/object.cpp b/TelepathyQt/object.cpp new file mode 100644 index 00000000..8d5555f4 --- /dev/null +++ b/TelepathyQt/object.cpp @@ -0,0 +1,65 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/Object> + +#include "TelepathyQt/_gen/object.moc.hpp" + +namespace Tp +{ + +/** + * \class Object + * \ingroup clientobject + * \headerfile TelepathyQt/object.h <TelepathyQt/Object> + * + * \brief The Object class provides an object with property notification. + */ + +/** + * Construct a new Object object. + */ +Object::Object() + : QObject() +{ +} + +/** + * Class destructor. + */ +Object::~Object() +{ +} + +/** + * Notify that a property named \a propertyName changed. + * + * This method will emit propertyChanged() for \a propertyName. + * + * \todo Use for more classes beyond Account. Most importantly Contact. + */ +void Object::notify(const char *propertyName) +{ + emit propertyChanged(QLatin1String(propertyName)); +} + +} // Tp diff --git a/TelepathyQt/object.h b/TelepathyQt/object.h new file mode 100644 index 00000000..7e24fb88 --- /dev/null +++ b/TelepathyQt/object.h @@ -0,0 +1,63 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_object_h_HEADER_GUARD_ +#define _TelepathyQt_object_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> +#include <TelepathyQt/RefCounted> + +#include <QObject> +#include <QString> + +namespace Tp +{ + +class TP_QT_EXPORT Object : public QObject, public RefCounted +{ + Q_OBJECT + Q_DISABLE_COPY(Object) + +public: + virtual ~Object(); + +Q_SIGNALS: + void propertyChanged(const QString &propertyName); + +protected: + Object(); + + void notify(const char *propertyName); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/optional-interface-factory.cpp b/TelepathyQt/optional-interface-factory.cpp new file mode 100644 index 00000000..4b0f8b8a --- /dev/null +++ b/TelepathyQt/optional-interface-factory.cpp @@ -0,0 +1,178 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2009 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 <TelepathyQt/OptionalInterfaceFactory> + +#include <TelepathyQt/AbstractInterface> + +#include "TelepathyQt/debug-internal.h" + +#include <QMap> +#include <QString> + +namespace Tp +{ + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +struct TP_QT_NO_EXPORT OptionalInterfaceCache::Private +{ + QObject *proxy; + QMap<QString, AbstractInterface*> interfaces; + + Private(QObject *proxy); +}; + +OptionalInterfaceCache::Private::Private(QObject *proxy) + : proxy(proxy) +{ +} + +/** + * Class constructor. + */ +OptionalInterfaceCache::OptionalInterfaceCache(QObject *proxy) + : mPriv(new Private(proxy)) +{ +} + +/** + * Class destructor. + * + * Frees all interface instances constructed by this factory. + */ +OptionalInterfaceCache::~OptionalInterfaceCache() +{ + delete mPriv; +} + +QObject *OptionalInterfaceCache::proxy() const +{ + return mPriv->proxy; +} + +AbstractInterface *OptionalInterfaceCache::getCached(const QString &name) const +{ + if (mPriv->interfaces.contains(name)) { + return mPriv->interfaces.value(name); + } else { + return 0; + } +} + +void OptionalInterfaceCache::cache(AbstractInterface *interface) const +{ + QString name = interface->interface(); + Q_ASSERT(!mPriv->interfaces.contains(name)); + + mPriv->interfaces[name] = interface; +} + +#endif /* ifndef DOXYGEN_SHOULD_SKIP_THIS */ + +/** + * \class OptionalInterfaceFactory + * \ingroup clientsideproxies + * \headerfile TelepathyQt/optional-interface-factory.h <TelepathyQt/OptionalInterfaceFactory> + * + * \brief The OptionalInterfaceFactory class is a helper class for + * high-level D-Bus proxy classes willing to offer access to shared + * instances of interface proxies for optional interfaces. + * + * To use this helper in a subclass of DBusProxy (say, ExampleObject), + * ExampleObject should inherit from + * OptionalInterfaceFactory<ExampleObject>, and call + * OptionalInterfaceFactory(this) in its constructor's initialization list. + * + * \tparam DBusProxySubclass A subclass of DBusProxy + */ + +/** + * \enum OptionalInterfaceFactory::InterfaceSupportedChecking + * + * Specifies if the interface being supported by the remote object should be + * checked by optionalInterface() and the convenience functions for it. + * + * \sa optionalInterface() + */ + +/** + * \var OptionalInterfaceFactory::InterfaceSupportedChecking OptionalInterfaceFactory::CheckInterfaceSupported + * + * Don't return an interface instance unless it can be guaranteed that the + * remote object actually implements the interface. + */ + +/** + * \var OptionalInterfaceFactory::InterfaceSupportedChecking OptionalInterfaceFactory::BypassInterfaceCheck + * + * Return an interface instance even if it can't be verified that the remote + * object supports the interface. + */ + +/** + * \fn OptionalInterfaceFactory::OptionalInterfaceFactory(DBusProxySubclass *this_) + * + * Class constructor. + * + * \param this_ The class to which this OptionalInterfaceFactory is + * attached + */ + +/** + * \fn OptionalInterfaceFactory::~OptionalInterfaceFactory() + * + * Class destructor. + * + * Frees all interface instances constructed by this factory. + */ + + /** + * \fn OptionalInterfaceFactory::interfaces() const; + * + * Return a list of interfaces supported by this object. + * + * \return List of supported interfaces. + */ + +/** + * \fn template <typename Interface> inline Interface *OptionalInterfaceFactory::interface() const + * + * Return a pointer to a valid instance of a interface class, associated + * with the same remote object as the given main interface instance. The + * given main interface must be of the class the optional interface is + * generated for (for eg. ChannelInterfaceGroupInterface this means + * ChannelInterface) or a subclass. + * + * First invocation of this method for a particular optional interface + * class will construct the instance; subsequent calls will return a + * pointer to the same instance. + * + * The returned instance is freed when the factory is destroyed; using + * it after destroying the factory will likely produce a crash. As the + * instance is shared, it should not be freed directly. + * + * \tparam Interface Class of the interface instance to get. + * \return A pointer to an optional interface instance. + */ + +} // Tp diff --git a/TelepathyQt/optional-interface-factory.h b/TelepathyQt/optional-interface-factory.h new file mode 100644 index 00000000..babbe47e --- /dev/null +++ b/TelepathyQt/optional-interface-factory.h @@ -0,0 +1,140 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2009 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 + */ + +#ifndef _TelepathyQt_optional_interface_factory_h_HEADER_GUARD_ +#define _TelepathyQt_optional_interface_factory_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> + +#include <QObject> +#include <QStringList> +#include <QtGlobal> + +namespace Tp +{ + +class AbstractInterface; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +class TP_QT_EXPORT OptionalInterfaceCache +{ + Q_DISABLE_COPY(OptionalInterfaceCache) + +public: + explicit OptionalInterfaceCache(QObject *proxy); + + ~OptionalInterfaceCache(); + +protected: + AbstractInterface *getCached(const QString &name) const; + void cache(AbstractInterface *interface) const; + QObject *proxy() const; + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +template <typename DBusProxySubclass> class OptionalInterfaceFactory +#ifndef DOXYGEN_SHOULD_SKIP_THIS + : private OptionalInterfaceCache +#endif +{ + Q_DISABLE_COPY(OptionalInterfaceFactory) + +public: + enum InterfaceSupportedChecking + { + CheckInterfaceSupported, + BypassInterfaceCheck + }; + + inline OptionalInterfaceFactory(DBusProxySubclass *this_) + : OptionalInterfaceCache(this_) + { + } + + inline ~OptionalInterfaceFactory() + { + } + + inline QStringList interfaces() const { return mInterfaces; } + + inline bool hasInterface(const QString &name) const + { + return mInterfaces.contains(name); + } + + template <class Interface> + inline Interface *optionalInterface( + InterfaceSupportedChecking check = CheckInterfaceSupported) const + { + // Check for the remote object supporting the interface + QString name(QLatin1String(Interface::staticInterfaceName())); + if (check == CheckInterfaceSupported && !mInterfaces.contains(name)) { + return 0; + } + + // If present or forced, delegate to OptionalInterfaceFactory + return interface<Interface>(); + } + + template <typename Interface> + inline Interface *interface() const + { + AbstractInterface* interfaceMustBeASubclassOfAbstractInterface = static_cast<Interface *>(NULL); + Q_UNUSED(interfaceMustBeASubclassOfAbstractInterface); + + // If there is a interface cached already, return it + QString name(QLatin1String(Interface::staticInterfaceName())); + AbstractInterface *cached = getCached(name); + if (cached) + return static_cast<Interface *>(cached); + + // Otherwise, cache and return a newly constructed proxy + Interface *interface = new Interface( + static_cast<DBusProxySubclass *>(proxy())); + cache(interface); + return interface; + } + +protected: + inline void setInterfaces(const QStringList &interfaces) + { + mInterfaces = interfaces; + } + +private: + QStringList mInterfaces; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/or-filter.dox b/TelepathyQt/or-filter.dox new file mode 100644 index 00000000..7829397c --- /dev/null +++ b/TelepathyQt/or-filter.dox @@ -0,0 +1,33 @@ +/* + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +/** + * \class Tp::OrFilter + * \ingroup utils + * \headerfile TelepathyQt/or-filter.h <TelepathyQt/OrFilter> + * + * \brief The OrFilter class provides a generic filter object to be used + * in conjunction of other filters. + * + * The OrFilter will match if any of its given list of filters matches + * their criteria. + */ diff --git a/TelepathyQt/or-filter.h b/TelepathyQt/or-filter.h new file mode 100644 index 00000000..c9b1c428 --- /dev/null +++ b/TelepathyQt/or-filter.h @@ -0,0 +1,83 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_or_filter_h_HEADER_GUARD_ +#define _TelepathyQt_or_filter_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Filter> +#include <TelepathyQt/Types> + +namespace Tp +{ + +template <class T> +class OrFilter : public Filter<T> +{ +public: + static SharedPtr<OrFilter<T> > create( + const QList<SharedPtr<const Filter<T> > > &filters = QList<SharedPtr<const Filter<T> > >()) + { + return SharedPtr<OrFilter<T> >(new OrFilter<T>(filters)); + } + + inline virtual ~OrFilter() { } + + inline virtual bool isValid() const + { + Q_FOREACH (const SharedPtr<const Filter<T> > &filter, mFilters) { + if (!filter || !filter->isValid()) { + return false; + } + } + return true; + } + + inline virtual bool matches(const SharedPtr<T> &t) const + { + if (!isValid()) { + return false; + } + + Q_FOREACH (const SharedPtr<const Filter<T> > &filter, mFilters) { + if (filter->matches(t)) { + return true; + } + } + return false; + } + + inline QList<SharedPtr<const Filter<T> > > filters() const { return mFilters; } + +private: + OrFilter(const QList<SharedPtr<const Filter<T> > > &filters) + : Filter<T>(), mFilters(filters) { } + + QList<SharedPtr<const Filter<T> > > mFilters; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/outgoing-file-transfer-channel.cpp b/TelepathyQt/outgoing-file-transfer-channel.cpp new file mode 100644 index 00000000..2fbd4504 --- /dev/null +++ b/TelepathyQt/outgoing-file-transfer-channel.cpp @@ -0,0 +1,371 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/OutgoingFileTransferChannel> + +#include "TelepathyQt/_gen/outgoing-file-transfer-channel.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Connection> +#include <TelepathyQt/PendingFailure> +#include <TelepathyQt/PendingVariant> +#include <TelepathyQt/Types> +#include <TelepathyQt/types-internal.h> + +#include <QIODevice> +#include <QTcpSocket> + +namespace Tp +{ + +static const int FT_BLOCK_SIZE = 16 * 1024; + +struct TP_QT_NO_EXPORT OutgoingFileTransferChannel::Private +{ + Private(OutgoingFileTransferChannel *parent); + ~Private(); + + // Public object + OutgoingFileTransferChannel *parent; + + Client::ChannelTypeFileTransferInterface *fileTransferInterface; + + // Introspection + QIODevice *input; + QTcpSocket *socket; + SocketAddressIPv4 addr; + + qint64 pos; +}; + +OutgoingFileTransferChannel::Private::Private(OutgoingFileTransferChannel *parent) + : parent(parent), + fileTransferInterface(parent->interface<Client::ChannelTypeFileTransferInterface>()), + input(0), + socket(0), + pos(0) +{ +} + +OutgoingFileTransferChannel::Private::~Private() +{ +} + +/** + * \class OutgoingFileTransferChannel + * \ingroup clientchannel + * \headerfile TelepathyQt/outgoing-file-transfer-channel.h <TelepathyQt/OutgoingFileTransferChannel> + * + * \brief The OutgoingFileTransferChannel class represents a Telepathy channel + * of type FileTransfer for outgoing file transfers. + * + * For more details, please refer to \telepathy_spec. + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Feature representing the core that needs to become ready to make the + * OutgoingFileTransferChannel object usable. + * + * This is currently the same as FileTransferChannel::FeatureCore, but may change to include more. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature OutgoingFileTransferChannel::FeatureCore = + Feature(QLatin1String(FileTransferChannel::staticMetaObject.className()), 0); // FT::FeatureCore + +/** + * Create a new OutgoingFileTransferChannel object. + * + * \param connection Connection owning this channel, and specifying the + * service. + * \param objectPath The channel object path. + * \param immutableProperties The channel immutable properties. + * \return An OutgoingFileTransferChannelPtr object pointing to the newly created + * OutgoingFileTransferChannel object. + */ +OutgoingFileTransferChannelPtr OutgoingFileTransferChannel::create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties) +{ + return OutgoingFileTransferChannelPtr(new OutgoingFileTransferChannel( + connection, objectPath, immutableProperties, + OutgoingFileTransferChannel::FeatureCore)); +} + +/** + * Construct a new OutgoingFileTransferChannel 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, if any. The corresponding introspectable should + * depend on OutgoingFileTransferChannel::FeatureCore. + */ +OutgoingFileTransferChannel::OutgoingFileTransferChannel( + const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : FileTransferChannel(connection, objectPath, immutableProperties, coreFeature), + mPriv(new Private(this)) +{ +} + +/** + * Class destructor. + */ +OutgoingFileTransferChannel::~OutgoingFileTransferChannel() +{ + delete mPriv; +} + +/** + * Provide the file for an outgoing file transfer which has been offered. + * + * The state will change to #FileTransferStateOpen as soon as the transfer + * starts. + * The given input device should not be destroyed until the state() + * changes to #FileTransferStateCompleted or #FileTransferStateCancelled. + * If input is a sequential device QIODevice::isSequential(), it should be + * closed when no more data is available, so that it's known when to stop reading. + * + * Only the primary handler of a file transfer channel may call this method. + * + * This method requires FileTransferChannel::FeatureCore to be ready. + * + * \param input A QIODevice object where the data will be read from. + * \return A PendingOperation object which will emit PendingOperation::finished + * when the call has finished. + * \sa stateChanged(), state(), stateReason() + */ +PendingOperation *OutgoingFileTransferChannel::provideFile(QIODevice *input) +{ + if (!isReady(FileTransferChannel::FeatureCore)) { + warning() << "FileTransferChannel::FeatureCore must be ready before " + "calling provideFile"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + OutgoingFileTransferChannelPtr(this)); + } + + // let's fail here direclty as we may only have one device to handle + if (mPriv->input) { + warning() << "File transfer can only be started once in the same " + "channel"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("File transfer can only be started once in the same channel"), + OutgoingFileTransferChannelPtr(this)); + } + + if ((!input->isOpen() && !input->open(QIODevice::ReadOnly)) && + !input->isReadable()) { + warning() << "Unable to open IO device for reading"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_PERMISSION_DENIED), + QLatin1String("Unable to open IO device for reading"), + OutgoingFileTransferChannelPtr(this)); + } + + mPriv->input = input; + connect(input, + SIGNAL(aboutToClose()), + SLOT(onInputAboutToClose())); + + PendingVariant *pv = new PendingVariant( + mPriv->fileTransferInterface->ProvideFile( + SocketAddressTypeIPv4, + SocketAccessControlLocalhost, + QDBusVariant(QVariant(QString()))), + OutgoingFileTransferChannelPtr(this)); + connect(pv, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onProvideFileFinished(Tp::PendingOperation*))); + return pv; +} + +void OutgoingFileTransferChannel::onProvideFileFinished(PendingOperation *op) +{ + if (op->isError()) { + warning() << "Error providing file transfer " << + op->errorName() << ":" << op->errorMessage(); + invalidate(op->errorName(), op->errorMessage()); + return; + } + + PendingVariant *pv = qobject_cast<PendingVariant *>(op); + mPriv->addr = qdbus_cast<SocketAddressIPv4>(pv->result()); + debug().nospace() << "Got address " << mPriv->addr.address << + ":" << mPriv->addr.port; + + if (state() == FileTransferStateOpen) { + connectToHost(); + } +} + +void OutgoingFileTransferChannel::connectToHost() +{ + if (isConnected() || mPriv->addr.address.isNull()) { + return; + } + + mPriv->socket = new QTcpSocket(this); + + connect(mPriv->socket, SIGNAL(connected()), + SLOT(onSocketConnected())); + connect(mPriv->socket, SIGNAL(disconnected()), + SLOT(onSocketDisconnected())); + connect(mPriv->socket, SIGNAL(error(QAbstractSocket::SocketError)), + SLOT(onSocketError(QAbstractSocket::SocketError))); + connect(mPriv->socket, SIGNAL(bytesWritten(qint64)), + SLOT(doTransfer())); + + debug().nospace() << "Connecting to host " << + mPriv->addr.address << ":" << mPriv->addr.port << "..."; + mPriv->socket->connectToHost(mPriv->addr.address, mPriv->addr.port); +} + +void OutgoingFileTransferChannel::onSocketConnected() +{ + debug() << "Connected to host"; + setConnected(); + + connect(mPriv->input, SIGNAL(readyRead()), + SLOT(doTransfer())); + + // for non sequential devices, let's seek to the initialOffset + if (!mPriv->input->isSequential()) { + if (mPriv->input->seek(initialOffset())) { + mPriv->pos = initialOffset(); + } + } + + debug() << "Starting transfer..."; + doTransfer(); +} + +void OutgoingFileTransferChannel::onSocketDisconnected() +{ + debug() << "Disconnected from host"; + setFinished(); +} + +void OutgoingFileTransferChannel::onSocketError(QAbstractSocket::SocketError error) +{ + debug() << "Socket error" << error; + setFinished(); +} + +void OutgoingFileTransferChannel::onInputAboutToClose() +{ + debug() << "Input closed"; + + // read all remaining data from input device and write to output device + if (isConnected()) { + QByteArray data; + data = mPriv->input->readAll(); + mPriv->socket->write(data); // never fails + } + + setFinished(); +} + +void OutgoingFileTransferChannel::doTransfer() +{ + // read FT_BLOCK_SIZE each time, as input can be a QFile, we don't want to + // block reading the whole file + char buffer[FT_BLOCK_SIZE]; + char *p = buffer; + + memset(buffer, 0, sizeof(buffer)); + qint64 len = mPriv->input->read(buffer, sizeof(buffer)); + + bool scheduleTransfer = false; + if (((qulonglong) mPriv->pos < initialOffset()) && (len > 0)) { + qint64 skip = (qint64) qMin(initialOffset() - mPriv->pos, + (qulonglong) len); + + debug() << "skipping" << skip << "bytes"; + if (skip == len) { + // nothing to write, all data read was skipped + // schedule a transfer, as readyRead may never be called and + // bytesWriiten will not + scheduleTransfer = true; + goto end; + } + + p += skip; + len -= skip; + } + + if (len > 0) { + mPriv->socket->write(p, len); // never fails + } + +end: + if (len == -1 || (!mPriv->input->isSequential() && mPriv->input->atEnd())) { + // error or EOF + setFinished(); + return; + } + + mPriv->pos += len; + + if (scheduleTransfer) { + QMetaObject::invokeMethod(this, SLOT(doTransfer()), + Qt::QueuedConnection); + } +} + +void OutgoingFileTransferChannel::setFinished() +{ + if (isFinished()) { + // it shouldn't happen but let's make sure + return; + } + + if (mPriv->socket) { + disconnect(mPriv->socket, SIGNAL(connected()), + this, SLOT(onSocketConnected())); + disconnect(mPriv->socket, SIGNAL(disconnected()), + this, SLOT(onSocketDisconnected())); + disconnect(mPriv->socket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(onSocketError(QAbstractSocket::SocketError))); + disconnect(mPriv->socket, SIGNAL(bytesWritten(qint64)), + this, SLOT(doTransfer())); + mPriv->socket->close(); + } + + if (mPriv->input) { + disconnect(mPriv->input, SIGNAL(aboutToClose()), + this, SLOT(onInputAboutToClose())); + disconnect(mPriv->input, SIGNAL(readyRead()), + this, SLOT(doTransfer())); + mPriv->input->close(); + } + + FileTransferChannel::setFinished(); +} + +} // Tp diff --git a/TelepathyQt/outgoing-file-transfer-channel.h b/TelepathyQt/outgoing-file-transfer-channel.h new file mode 100644 index 00000000..37daa842 --- /dev/null +++ b/TelepathyQt/outgoing-file-transfer-channel.h @@ -0,0 +1,78 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_outgoing_file_transfer_channel_h_HEADER_GUARD_ +#define _TelepathyQt_outgoing_file_transfer_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/FileTransferChannel> + +#include <QAbstractSocket> + +namespace Tp +{ + +class TP_QT_EXPORT OutgoingFileTransferChannel : public FileTransferChannel +{ + Q_OBJECT + Q_DISABLE_COPY(OutgoingFileTransferChannel) + +public: + static const Feature FeatureCore; + + static OutgoingFileTransferChannelPtr create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties); + + virtual ~OutgoingFileTransferChannel(); + + PendingOperation *provideFile(QIODevice *input); + +protected: + OutgoingFileTransferChannel(const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature = OutgoingFileTransferChannel::FeatureCore); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onProvideFileFinished(Tp::PendingOperation *op); + + TP_QT_NO_EXPORT void onSocketConnected(); + TP_QT_NO_EXPORT void onSocketDisconnected(); + TP_QT_NO_EXPORT void onSocketError(QAbstractSocket::SocketError error); + TP_QT_NO_EXPORT void onInputAboutToClose(); + TP_QT_NO_EXPORT void doTransfer(); + +private: + TP_QT_NO_EXPORT void connectToHost(); + TP_QT_NO_EXPORT void setFinished(); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/outgoing-stream-tube-channel-internal.h b/TelepathyQt/outgoing-stream-tube-channel-internal.h new file mode 100644 index 00000000..b7fe41c8 --- /dev/null +++ b/TelepathyQt/outgoing-stream-tube-channel-internal.h @@ -0,0 +1,122 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_outgoing_stream_tube_channel_internal_h_HEADER_GUARD_ +#define _TelepathyQt_outgoing_stream_tube_channel_internal_h_HEADER_GUARD_ + +#include <TelepathyQt/OutgoingStreamTubeChannel> +#include <TelepathyQt/PendingOperation> + +namespace Tp +{ + +class PendingVoid; + +class TP_QT_NO_EXPORT PendingOpenTube : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingOpenTube) + +public: + PendingOpenTube(PendingVoid *offerOperation, + const QVariantMap ¶meters, + const OutgoingStreamTubeChannelPtr &object); + ~PendingOpenTube(); + +private Q_SLOTS: + void onTubeStateChanged(Tp::TubeChannelState state); + void onOfferFinished(Tp::PendingOperation *operation); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +class TP_QT_NO_EXPORT QueuedContactFactory : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(QueuedContactFactory) + +public: + QueuedContactFactory(ContactManagerPtr contactManager, QObject* parent = 0); + ~QueuedContactFactory(); + + QUuid appendNewRequest(const UIntList &handles); + +Q_SIGNALS: + void contactsRetrieved(QUuid uuid, QList<Tp::ContactPtr> contacts); + +private Q_SLOTS: + void onPendingContactsFinished(Tp::PendingOperation *operation); + +private: + struct Entry { + QUuid uuid; + UIntList handles; + }; + + void processNextRequest(); + + bool m_isProcessing; + ContactManagerPtr m_manager; + QQueue<Entry> m_queue; +}; + +struct TP_QT_NO_EXPORT PendingOpenTube::Private +{ + Private(const QVariantMap ¶meters, PendingOpenTube *parent); + + // Public object + PendingOpenTube *parent; + + OutgoingStreamTubeChannelPtr tube; + QVariantMap parameters; +}; + +struct TP_QT_NO_EXPORT OutgoingStreamTubeChannel::Private +{ + Private(OutgoingStreamTubeChannel *parent); + + OutgoingStreamTubeChannel *parent; + + QHash<uint, Tp::ContactPtr> contactsForConnections; + QHash<QPair<QHostAddress, quint16>, uint> connectionsForSourceAddresses; + QHash<uchar, uint> connectionsForCredentials; + + QHash<QUuid, QPair<uint, QDBusVariant> > pendingNewConnections; + + struct ClosedConnection { + uint id; + QString error, message; + + ClosedConnection() : id(~0U) {} + ClosedConnection(uint id, const QString &error, const QString &message) + : id(id), error(error), message(message) {} + }; + QHash<QUuid, ClosedConnection> pendingClosedConnections; + + QueuedContactFactory *queuedContactFactory; +}; + +} + +#endif diff --git a/TelepathyQt/outgoing-stream-tube-channel.cpp b/TelepathyQt/outgoing-stream-tube-channel.cpp new file mode 100644 index 00000000..22af7b8f --- /dev/null +++ b/TelepathyQt/outgoing-stream-tube-channel.cpp @@ -0,0 +1,821 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/OutgoingStreamTubeChannel> +#include "TelepathyQt/outgoing-stream-tube-channel-internal.h" + +#include "TelepathyQt/_gen/outgoing-stream-tube-channel.moc.hpp" +#include "TelepathyQt/_gen/outgoing-stream-tube-channel-internal.moc.hpp" + +#include "TelepathyQt/debug-internal.h" +#include "TelepathyQt/types-internal.h" + +#include <TelepathyQt/Connection> +#include <TelepathyQt/ContactManager> +#include <TelepathyQt/PendingContacts> +#include <TelepathyQt/PendingFailure> +#include <TelepathyQt/Types> + +#include <QHostAddress> +#include <QTcpServer> +#include <QLocalServer> + +namespace Tp +{ + +PendingOpenTube::Private::Private(const QVariantMap ¶meters, PendingOpenTube *parent) + : parent(parent), + parameters(parameters) +{ +} + +PendingOpenTube::PendingOpenTube( + PendingVoid *offerOperation, + const QVariantMap ¶meters, + const OutgoingStreamTubeChannelPtr &object) + : PendingOperation(object), + mPriv(new Private(parameters, this)) +{ + mPriv->tube = object; + + // FIXME: connect to channel invalidation here also + + debug() << "Calling StreamTube.Offer"; + if (offerOperation->isFinished()) { + onOfferFinished(offerOperation); + } else { + // Connect the pending void + connect(offerOperation, SIGNAL(finished(Tp::PendingOperation*)), + this, SLOT(onOfferFinished(Tp::PendingOperation*))); + } +} + +PendingOpenTube::~PendingOpenTube() +{ + delete mPriv; +} + +void PendingOpenTube::onOfferFinished(PendingOperation *op) +{ + if (op->isError()) { + warning().nospace() << "StreamTube.Offer failed with " << + op->errorName() << ": " << op->errorMessage(); + setFinishedWithError(op->errorName(), op->errorMessage()); + return; + } + + debug() << "StreamTube.Offer returned successfully"; + + // It might have been already opened - check + if (mPriv->tube->state() != TubeChannelStateOpen) { + debug() << "Awaiting tube to be opened"; + // Wait until the tube gets opened on the other side + connect(mPriv->tube.data(), + SIGNAL(stateChanged(Tp::TubeChannelState)), + SLOT(onTubeStateChanged(Tp::TubeChannelState))); + } + + onTubeStateChanged(mPriv->tube->state()); +} + +void PendingOpenTube::onTubeStateChanged(TubeChannelState state) +{ + if (state == TubeChannelStateOpen) { + debug() << "Tube is now opened"; + // Inject the parameters into the tube + mPriv->tube->setParameters(mPriv->parameters); + // The tube is ready: let's notify + setFinished(); + } else { + if (state != TubeChannelStateRemotePending) { + warning() << "Offering tube failed with" << TP_QT_ERROR_CONNECTION_REFUSED; + // Something happened + setFinishedWithError(TP_QT_ERROR_CONNECTION_REFUSED, + QLatin1String("The connection to this tube was refused")); + } else { + debug() << "Awaiting remote to accept the tube"; + } + } +} + +QueuedContactFactory::QueuedContactFactory(Tp::ContactManagerPtr contactManager, QObject* parent) + : QObject(parent), + m_isProcessing(false), + m_manager(contactManager) +{ +} + +QueuedContactFactory::~QueuedContactFactory() +{ +} + +void QueuedContactFactory::processNextRequest() +{ + if (m_isProcessing || m_queue.isEmpty()) { + // Return, nothing to do + return; + } + + m_isProcessing = true; + + Entry entry = m_queue.dequeue(); + + // TODO: pass id hints to ContactManager if we ever gain support to retrieve contact ids + // from NewRemoteConnection. + PendingContacts *pc = m_manager->contactsForHandles(entry.handles); + pc->setProperty("__TpQt4__QueuedContactFactoryUuid", entry.uuid.toString()); + connect(pc, SIGNAL(finished(Tp::PendingOperation*)), + this, SLOT(onPendingContactsFinished(Tp::PendingOperation*))); +} + +QUuid QueuedContactFactory::appendNewRequest(const Tp::UIntList &handles) +{ + // Create a new entry + Entry entry; + entry.uuid = QUuid::createUuid(); + entry.handles = handles; + m_queue.enqueue(entry); + + // Check if we can process a request + processNextRequest(); + + // Return the UUID + return entry.uuid; +} + +void QueuedContactFactory::onPendingContactsFinished(PendingOperation *op) +{ + PendingContacts *pc = qobject_cast<PendingContacts*>(op); + + QUuid uuid = QUuid(pc->property("__TpQt4__QueuedContactFactoryUuid").toString()); + + emit contactsRetrieved(uuid, pc->contacts()); + + // No longer processing + m_isProcessing = false; + + // Go for next one + processNextRequest(); +} + +OutgoingStreamTubeChannel::Private::Private(OutgoingStreamTubeChannel *parent) + : parent(parent), + queuedContactFactory(new QueuedContactFactory(parent->connection()->contactManager(), parent)) +{ +} + +/** + * \class OutgoingStreamTubeChannel + * \ingroup clientchannel + * \headerfile TelepathyQt/outgoing-stream-tube-channel.h <TelepathyQt/OutgoingStreamTubeChannel> + * + * \brief The OutgoingStreamTubeChannel class represents an outgoing Telepathy channel + * of type StreamTube. + * + * Outgoing (locally initiated/requested) tubes are initially in the #TubeChannelStateNotOffered state. The + * various offer methods in this class can be used to offer a local listening TCP or Unix socket for + * the tube's target to connect to, at which point the tube becomes #TubeChannelStateRemotePending. + * If the target accepts the connection request, the state goes #TubeChannelStateOpen and the + * connection manager will start tunneling any incoming connections from the recipient side to the + * local service. + */ + +/** + * Feature representing the core that needs to become ready to make the + * OutgoingStreamTubeChannel object usable. + * + * This is currently the same as StreamTubeChannel::FeatureCore, but may change to include more. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature OutgoingStreamTubeChannel::FeatureCore = + Feature(QLatin1String(StreamTubeChannel::staticMetaObject.className()), 0); // ST::FeatureCore + +/** + * Create a new OutgoingStreamTubeChannel 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 OutgoingStreamTubeChannelPtr object pointing to the newly created + * OutgoingStreamTubeChannel object. + */ +OutgoingStreamTubeChannelPtr OutgoingStreamTubeChannel::create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties) +{ + return OutgoingStreamTubeChannelPtr(new OutgoingStreamTubeChannel(connection, objectPath, + immutableProperties, OutgoingStreamTubeChannel::FeatureCore)); +} + +/** + * Construct a new OutgoingStreamTubeChannel 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, if any. The corresponding introspectable should + * depend on OutgoingStreamTubeChannel::FeatureCore. + */ +OutgoingStreamTubeChannel::OutgoingStreamTubeChannel(const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : StreamTubeChannel(connection, objectPath, + immutableProperties, coreFeature), + mPriv(new Private(this)) +{ + connect(mPriv->queuedContactFactory, + SIGNAL(contactsRetrieved(QUuid,QList<Tp::ContactPtr>)), + this, + SLOT(onContactsRetrieved(QUuid,QList<Tp::ContactPtr>))); +} + +/** + * Class destructor. + */ +OutgoingStreamTubeChannel::~OutgoingStreamTubeChannel() +{ + delete mPriv; +} + +/** + * Offer a TCP socket over this stream tube. + * + * This method offers a TCP socket over this tube. The socket's address is given as + * a QHostAddress and a numerical port in native byte order. + * + * If your application uses QTcpServer as the local TCP server implementation, you can use the + * offerTcpSocket(const QTcpServer *, const QVariantMap &) overload instead to more easily pass the + * server's listen address. + * + * It is guaranteed that when the PendingOperation returned by this method will be completed, + * the tube will be opened and ready to be used. + * + * Connection managers adhering to the \telepathy_spec should always support offering IPv4 TCP + * sockets. IPv6 sockets are only supported if supportsIPv6SocketsOnLocalhost() is \c true. + * + * Note that the library will try to use #SocketAccessControlPort access control whenever possible, + * as it allows to map connections to users based on their source addresses. If + * supportsIPv4SocketsWithSpecifiedAddress() or supportsIPv6SocketsWithSpecifiedAddress() for IPv4 + * and IPv6 sockets respectively is \c false, this feature is not available, and the + * connectionsForSourceAddresses() map won't contain useful distinct keys. + * + * Arbitrary parameters can be associated with the offer to bootstrap legacy protocols; these will + * in particular be available as IncomingStreamTubeChannel::parameters() for a tube receiver + * implemented using TelepathyQt in the other end. + * + * This method requires OutgoingStreamTubeChannel::FeatureCore to be ready. + * + * \param address A valid IPv4 or IPv6 address pointing to an existing socket. + * \param port The port the socket is listening for connections to. + * \param parameters A dictionary of arbitrary parameters to send with the tube offer. + * \return A PendingOperation which will emit PendingOperation::finished + * when the stream tube is ready to be used + * (hence in the #TubeStateOpen state). + */ +PendingOperation *OutgoingStreamTubeChannel::offerTcpSocket( + const QHostAddress &address, + quint16 port, + const QVariantMap ¶meters) +{ + if (!isReady(OutgoingStreamTubeChannel::FeatureCore)) { + warning() << "OutgoingStreamTubeChannel::FeatureCore must be ready before " + "calling offerTube"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + OutgoingStreamTubeChannelPtr(this)); + } + + // The tube must be not offered + if (state() != TubeChannelStateNotOffered) { + warning() << "You can not expose more than a socket for each Stream Tube"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel busy"), + OutgoingStreamTubeChannelPtr(this)); + } + + SocketAccessControl accessControl = SocketAccessControlLocalhost; + // Check if port is supported + + // In this specific overload, we're handling an IPv4/IPv6 socket + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + // IPv4 case + SocketAccessControl accessControl; + // Do some heuristics to find out the best access control.We always prefer port for tracking + // connections and source addresses. + if (supportsIPv4SocketsWithSpecifiedAddress()) { + accessControl = SocketAccessControlPort; + } else if (supportsIPv4SocketsOnLocalhost()) { + accessControl = SocketAccessControlLocalhost; + } else { + // There are no combinations supported for this socket + warning() << "You requested an address type/access control combination " + "not supported by this channel"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("The requested address type/access control " + "combination is not supported"), + OutgoingStreamTubeChannelPtr(this)); + } + + setAddressType(SocketAddressTypeIPv4); + setAccessControl(accessControl); + setIpAddress(qMakePair<QHostAddress, quint16>(address, port)); + + SocketAddressIPv4 addr; + addr.address = address.toString(); + addr.port = port; + + PendingVoid *pv = new PendingVoid( + interface<Client::ChannelTypeStreamTubeInterface>()->Offer( + SocketAddressTypeIPv4, + QDBusVariant(QVariant::fromValue(addr)), + accessControl, + parameters), + OutgoingStreamTubeChannelPtr(this)); + PendingOpenTube *op = new PendingOpenTube(pv, parameters, + OutgoingStreamTubeChannelPtr(this)); + return op; + } else if (address.protocol() == QAbstractSocket::IPv6Protocol) { + // IPv6 case + // Do some heuristics to find out the best access control.We always prefer port for tracking + // connections and source addresses. + if (supportsIPv6SocketsWithSpecifiedAddress()) { + accessControl = SocketAccessControlPort; + } else if (supportsIPv6SocketsOnLocalhost()) { + accessControl = SocketAccessControlLocalhost; + } else { + // There are no combinations supported for this socket + warning() << "You requested an address type/access control combination " + "not supported by this channel"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("The requested address type/access control " + "combination is not supported"), + OutgoingStreamTubeChannelPtr(this)); + } + + setAddressType(SocketAddressTypeIPv6); + setAccessControl(accessControl); + setIpAddress(qMakePair<QHostAddress, quint16>(address, port)); + + SocketAddressIPv6 addr; + addr.address = address.toString(); + addr.port = port; + + PendingVoid *pv = new PendingVoid( + interface<Client::ChannelTypeStreamTubeInterface>()->Offer( + SocketAddressTypeIPv6, + QDBusVariant(QVariant::fromValue(addr)), + accessControl, + parameters), + OutgoingStreamTubeChannelPtr(this)); + PendingOpenTube *op = new PendingOpenTube(pv, parameters, + OutgoingStreamTubeChannelPtr(this)); + return op; + } else { + // We're handling an IPv4/IPv6 socket only + warning() << "offerTube can be called only with a QHostAddress representing " + "an IPv4 or IPv6 address"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Invalid host given"), + OutgoingStreamTubeChannelPtr(this)); + } + +} + +/** + * Offer a TCP socket over this stream tube. + * + * Otherwise identical to offerTcpSocket(const QHostAddress &, quint16, const QVariantMap &), but + * allows passing the local service's address in an already listening QTcpServer. + * + * \param server A valid QTcpServer, which should be already listening for incoming connections. + * \param parameters A dictionary of arbitrary parameters to send with the tube offer. + * \return A PendingOperation which will emit PendingOperation::finished + * when the stream tube is ready to be used + * (hence in the #TubeStateOpen state). + */ +PendingOperation *OutgoingStreamTubeChannel::offerTcpSocket( + const QTcpServer *server, + const QVariantMap ¶meters) +{ + // In this overload, we're handling a superset of QHostAddress. + // Let's redirect the call to QHostAddress's overload + return offerTcpSocket(server->serverAddress(), server->serverPort(), + parameters); +} + +/** + * Offer an Unix socket over this stream tube. + * + * This method offers an Unix socket over this stream tube. The socket address is given as a + * a QString, which should contain the path to the socket. Abstract Unix sockets are also supported, + * and are given as addresses prefixed with a \c NUL byte. + * + * If your application uses QLocalServer as the local Unix server implementation, you can use the + * offerUnixSocket(const QLocalServer *, const QVariantMap &, bool) overload instead to more easily + * pass the server's listen address. + * + * Note that only connection managers for which supportsUnixSocketsOnLocalhost() or + * supportsAbstractUnixSocketsOnLocalhost() is \c true support exporting Unix sockets. + * + * If supportsUnixSocketsWithCredentials() or supportsAbstractUnixSocketsWithCredentials(), as + * appropriate, returns \c true, the \c requireCredentials parameter can be set to \c true to make + * the connection manager pass an SCM_CREDS or SCM_CREDENTIALS message as supported by the platform + * when making a new connection. This enables preventing other local users from connecting to the + * service, but might not be possible to use with all protocols as the message is in-band in the + * data stream. + * + * Arbitrary parameters can be associated with the offer to bootstrap legacy protocols; these will + * in particular be available as IncomingStreamTubeChannel::parameters() for a tube receiver + * implemented using TelepathyQt in the other end. + * + * This method requires OutgoingStreamTubeChannel::FeatureCore to be ready. + * + * \param address A valid path to an existing Unix socket or abstract Unix socket. + * \param parameters A dictionary of arbitrary parameters to send with the tube offer. + * \param requireCredentials Whether the server requires a SCM_CREDS or SCM_CREDENTIALS message + * upon connection. + * \return A PendingOperation which will emit PendingOperation::finished + * when the stream tube is ready to be used + * (hence in the #TubeStateOpen state). + */ +PendingOperation *OutgoingStreamTubeChannel::offerUnixSocket( + const QString &socketAddress, + const QVariantMap ¶meters, + bool requireCredentials) +{ + SocketAccessControl accessControl = requireCredentials ? + SocketAccessControlCredentials : + SocketAccessControlLocalhost; + + if (!isReady(OutgoingStreamTubeChannel::FeatureCore)) { + warning() << "OutgoingStreamTubeChannel::FeatureCore must be ready before " + "calling offerTube"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel not ready"), + OutgoingStreamTubeChannelPtr(this)); + } + + // The tube must be not offered + if (state() != TubeChannelStateNotOffered) { + warning() << "You can not expose more than a socket for each Stream Tube"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Channel busy"), OutgoingStreamTubeChannelPtr(this)); + } + + // In this specific overload, we're handling an Unix/AbstractUnix socket + if (socketAddress.startsWith(QLatin1Char('\0'))) { + // Abstract Unix socket case + // Check if the combination type/access control is supported + if ((accessControl == SocketAccessControlLocalhost && + !supportsAbstractUnixSocketsOnLocalhost()) || + (accessControl == SocketAccessControlCredentials && + !supportsAbstractUnixSocketsWithCredentials()) ) { + warning() << "You requested an address type/access control combination " + "not supported by this channel"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("The requested address type/access control " + "combination is not supported"), + OutgoingStreamTubeChannelPtr(this)); + } + + setAddressType(SocketAddressTypeAbstractUnix); + setAccessControl(accessControl); + setLocalAddress(socketAddress); + + PendingVoid *pv = new PendingVoid( + interface<Client::ChannelTypeStreamTubeInterface>()->Offer( + SocketAddressTypeAbstractUnix, + QDBusVariant(QVariant(socketAddress.toLatin1())), + accessControl, + parameters), + OutgoingStreamTubeChannelPtr(this)); + PendingOpenTube *op = new PendingOpenTube(pv, parameters, + OutgoingStreamTubeChannelPtr(this)); + return op; + } else { + // Unix socket case + // Check if the combination type/access control is supported + if ((accessControl == SocketAccessControlLocalhost && + !supportsUnixSocketsOnLocalhost()) || + (accessControl == SocketAccessControlCredentials && + !supportsUnixSocketsWithCredentials()) || + (accessControl != SocketAccessControlLocalhost && + accessControl != SocketAccessControlCredentials) ) { + warning() << "You requested an address type/access control combination " + "not supported by this channel"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("The requested address type/access control " + "combination is not supported"), + OutgoingStreamTubeChannelPtr(this)); + } + + setAddressType(SocketAddressTypeUnix); + setAccessControl(accessControl); + setLocalAddress(socketAddress); + + PendingVoid *pv = new PendingVoid( + interface<Client::ChannelTypeStreamTubeInterface>()->Offer( + SocketAddressTypeUnix, + QDBusVariant(QVariant(socketAddress.toLatin1())), + accessControl, + parameters), + OutgoingStreamTubeChannelPtr(this)); + PendingOpenTube *op = new PendingOpenTube(pv, parameters, + OutgoingStreamTubeChannelPtr(this)); + return op; + } +} + +/** + * Offer an Unix socket over the tube. + * + * Otherwise identical to offerUnixSocket(const QString &, const QVariantMap &, bool), but allows + * passing the local service's address as an already listening QLocalServer. + * + * This method requires OutgoingStreamTubeChannel::FeatureCore to be ready. + * + * \param server A valid QLocalServer, which should be already listening for incoming connections. + * \param parameters A dictionary of arbitrary parameters to send with the tube offer. + * \param requireCredentials Whether the server should require a SCM_CRED or SCM_CREDENTIALS message + * upon connection. + * \return A PendingOperation which will emit PendingOperation::finished + * when the stream tube is ready to be used + * (hence in the #TubeStateOpen state). + * \sa StreamTubeChannel::supportsUnixSocketsOnLocalhost(), + * StreamTubeChannel::supportsUnixSocketsWithCredentials(), + * StreamTubeChannel::supportsAbstractUnixSocketsOnLocalhost(), + * StreamTubeChannel::supportsAbstractUnixSocketsWithCredentials() + */ +PendingOperation *OutgoingStreamTubeChannel::offerUnixSocket( + const QLocalServer *server, + const QVariantMap ¶meters, + bool requireCredentials) +{ + // In this overload, we're handling a superset of a local socket + // Let's redirect the call to QString's overload + return offerUnixSocket(server->fullServerName(), parameters, requireCredentials); +} + +/** + * Return a map from a source address to the corresponding connections ids. + * + * The connection ids retrieved here can be used to map a source address + * which connected to your socket to a connection ID (for error reporting) and further, to a contact + * (by using contactsForConnections()). + * + * This method is only useful if a TCP socket was offered on this tube and the connection manager + * supports #SocketAccessControlPort, which can be discovered using + * supportsIPv4SocketsWithSpecifiedAddress() and supportsIPv6SocketsWithSpecifiedAddress() for IPv4 + * and IPv6 sockets respectively. + * + * Note that this function will only return valid data after the tube has been opened. + * + * This method requires StreamTubeChannel::FeatureConnectionMonitoring to be ready. + * + * \return The map from source addresses as (QHostAddress, port in native byte order) pairs to the + * corresponding connection ids. + * \sa connectionsForCredentials() + */ +QHash<QPair<QHostAddress, quint16>, uint> OutgoingStreamTubeChannel::connectionsForSourceAddresses() const +{ + if (addressType() != SocketAddressTypeIPv4 && addressType() != SocketAddressTypeIPv6) { + warning() << "OutgoingStreamTubeChannel::connectionsForSourceAddresses() makes sense " + "just when offering a TCP socket"; + return QHash<QPair<QHostAddress, quint16>, uint>(); + } + + if (isValid() || !isDroppingConnections() || + !requestedFeatures().contains(StreamTubeChannel::FeatureConnectionMonitoring)) { + if (!isReady(StreamTubeChannel::FeatureConnectionMonitoring)) { + warning() << "StreamTubeChannel::FeatureConnectionMonitoring must be ready before " + " calling connectionsForSourceAddresses"; + return QHash<QPair<QHostAddress, quint16>, uint>(); + } + + if (state() != TubeChannelStateOpen) { + warning() << "OutgoingStreamTubeChannel::connectionsForSourceAddresses() makes sense " + "just when the tube is open"; + return QHash<QPair<QHostAddress, quint16>, uint>(); + } + } + + return mPriv->connectionsForSourceAddresses; +} + +/** + * Return a map from a credential byte to the corresponding connections ids. + * + * The connection ids retrieved here can be used to map a source address + * which connected to your socket to a connection ID (for error reporting) and further, to a contact + * (by using contactsForConnections()). + * + * This method is only useful if this tube was offered using an Unix socket and passing credential + * bytes was enabled (\c requireCredentials == true). + * + * Note that this function will only return valid data after the tube has been opened. + * + * This method requires StreamTubeChannel::FeatureConnectionMonitoring to be ready. + * + * \return The map from credential bytes to the corresponding connection ids. + * \sa connectionsForSourceAddresses() + */ +QHash<uchar, uint> OutgoingStreamTubeChannel::connectionsForCredentials() const +{ + if (addressType() != SocketAddressTypeUnix && addressType() != SocketAddressTypeAbstractUnix) { + warning() << "OutgoingStreamTubeChannel::connectionsForCredentials() makes sense " + "just when offering an Unix socket"; + return QHash<uchar, uint>(); + } + + if (accessControl() != SocketAccessControlCredentials) { + warning() << "OutgoingStreamTubeChannel::connectionsForCredentials() makes sense " + "just when offering an Unix socket requiring credentials"; + return QHash<uchar, uint>(); + } + + if (isValid() || !isDroppingConnections() || + !requestedFeatures().contains(StreamTubeChannel::FeatureConnectionMonitoring)) { + if (!isReady(StreamTubeChannel::FeatureConnectionMonitoring)) { + warning() << "StreamTubeChannel::FeatureConnectionMonitoring must be ready before " + "calling OutgoingStreamTubeChannel::connectionsForCredentials()"; + return QHash<uchar, uint>(); + } + + if (state() != TubeChannelStateOpen) { + warning() << "OutgoingStreamTubeChannel::connectionsForCredentials() makes sense " + "just when the tube is opened"; + return QHash<uchar, uint>(); + } + } + + return mPriv->connectionsForCredentials; +} + +/** + * Return a map from connection ids to the associated contact. + * + * Note that this function will only return valid data after the tube has been opened. + * + * This method requires StreamTubeChannel::FeatureConnectionMonitoring to be ready. + + * \return The map from connection ids to pointer to Contact objects. + * \sa connectionsForSourceAddresses(), connectionsForCredentials(), + * StreamTubeChannel::addressType() + */ +QHash<uint, ContactPtr> OutgoingStreamTubeChannel::contactsForConnections() const +{ + if (isValid() || !isDroppingConnections() || + !requestedFeatures().contains(StreamTubeChannel::FeatureConnectionMonitoring)) { + if (!isReady(StreamTubeChannel::FeatureConnectionMonitoring)) { + warning() << "StreamTubeChannel::FeatureConnectionMonitoring must be ready before " + "calling contactsForConnections"; + return QHash<uint, ContactPtr>(); + } + + if (state() != TubeChannelStateOpen) { + warning() << "OutgoingStreamTubeChannel::contactsForConnections() makes sense " + "just when the tube is open"; + return QHash<uint, ContactPtr>(); + } + } + + return mPriv->contactsForConnections; +} + +void OutgoingStreamTubeChannel::onNewRemoteConnection( + uint contactId, + const QDBusVariant ¶meter, + uint connectionId) +{ + // Request the handles from our queued contact factory + QUuid uuid = mPriv->queuedContactFactory->appendNewRequest(UIntList() << contactId); + + // Add a pending connection + mPriv->pendingNewConnections.insert(uuid, qMakePair(connectionId, parameter)); +} + +void OutgoingStreamTubeChannel::onContactsRetrieved( + const QUuid &uuid, + const QList<Tp::ContactPtr> &contacts) +{ + if (!isValid()) { + debug() << "Invalidated OutgoingStreamTubeChannel not emitting queued connection event"; + return; + } + + if (!mPriv->pendingNewConnections.contains(uuid)) { + if (mPriv->pendingClosedConnections.contains(uuid)) { + // closed connection + Private::ClosedConnection conn = mPriv->pendingClosedConnections.take(uuid); + + // First, do removeConnection() so connectionClosed is emitted, and anybody connected to it + // (like StreamTubeServer) has a chance to recover the source address / contact + removeConnection(conn.id, conn.error, conn.message); + + // Remove stuff from our hashes + mPriv->contactsForConnections.remove(conn.id); + + QHash<QPair<QHostAddress, quint16>, uint>::iterator srcAddrIter = + mPriv->connectionsForSourceAddresses.begin(); + while (srcAddrIter != mPriv->connectionsForSourceAddresses.end()) { + if (srcAddrIter.value() == conn.id) { + srcAddrIter = mPriv->connectionsForSourceAddresses.erase(srcAddrIter); + } else { + ++srcAddrIter; + } + } + + QHash<uchar, uint>::iterator credIter = mPriv->connectionsForCredentials.begin(); + while (credIter != mPriv->connectionsForCredentials.end()) { + if (credIter.value() == conn.id) { + credIter = mPriv->connectionsForCredentials.erase(credIter); + } else { + ++credIter; + } + } + } else { + warning() << "No pending connections found in OSTC" << objectPath() << "for contacts" + << contacts; + } + + return; + } + + // new connection + QPair<uint, QDBusVariant> connectionProperties = mPriv->pendingNewConnections.take(uuid); + + // Add it to our connections hash + foreach (const Tp::ContactPtr &contact, contacts) { + mPriv->contactsForConnections.insert(connectionProperties.first, contact); + } + + QPair<QHostAddress, quint16> address; + address.first = QHostAddress::Null; + + // Now let's try to track the parameter + if (addressType() == SocketAddressTypeIPv4) { + // Try a qdbus_cast to our address struct: we're shielded from crashes + // thanks to our specification + SocketAddressIPv4 addr = + qdbus_cast<Tp::SocketAddressIPv4>(connectionProperties.second.variant()); + address.first = QHostAddress(addr.address); + address.second = addr.port; + } else if (addressType() == SocketAddressTypeIPv6) { + SocketAddressIPv6 addr = + qdbus_cast<Tp::SocketAddressIPv6>(connectionProperties.second.variant()); + address.first = QHostAddress(addr.address); + address.second = addr.port; + } else if (addressType() == SocketAddressTypeUnix || + addressType() == SocketAddressTypeAbstractUnix) { + if (accessControl() == SocketAccessControlCredentials) { + uchar credentialByte = qdbus_cast<uchar>(connectionProperties.second.variant()); + mPriv->connectionsForCredentials.insertMulti(credentialByte, connectionProperties.first); + } + } + + if (address.first != QHostAddress::Null) { + // We can map it to a source address as well + mPriv->connectionsForSourceAddresses.insertMulti(address, connectionProperties.first); + } + + // Time for us to emit the signal + addConnection(connectionProperties.first); +} + +// This replaces the base class onConnectionClosed() slot, but unlike a virtual function, is ABI +// compatible +void OutgoingStreamTubeChannel::onConnectionClosed(uint connectionId, + const QString &errorName, const QString &errorMessage) +{ + // Insert a fake request to our queued contact factory to make the close events properly ordered + // with new connection events + QUuid uuid = mPriv->queuedContactFactory->appendNewRequest(UIntList()); + + // Add a pending connection close + mPriv->pendingClosedConnections.insert(uuid, + Private::ClosedConnection(connectionId, errorName, errorMessage)); +} + +} diff --git a/TelepathyQt/outgoing-stream-tube-channel.h b/TelepathyQt/outgoing-stream-tube-channel.h new file mode 100644 index 00000000..0a89a97b --- /dev/null +++ b/TelepathyQt/outgoing-stream-tube-channel.h @@ -0,0 +1,89 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_outgoing_stream_tube_channel_h_HEADER_GUARD_ +#define _TelepathyQt_outgoing_stream_tube_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/StreamTubeChannel> +#include <TelepathyQt/PendingOperation> + +class QHostAddress; +class QTcpServer; +class QLocalServer; + +namespace Tp +{ + +class TP_QT_EXPORT OutgoingStreamTubeChannel : public StreamTubeChannel +{ + Q_OBJECT + Q_DISABLE_COPY(OutgoingStreamTubeChannel) + +public: + static const Feature FeatureCore; + + static OutgoingStreamTubeChannelPtr create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties); + + virtual ~OutgoingStreamTubeChannel(); + + PendingOperation *offerTcpSocket(const QHostAddress &address, quint16 port, + const QVariantMap ¶meters = QVariantMap()); + PendingOperation *offerTcpSocket(const QTcpServer *server, + const QVariantMap ¶meters = QVariantMap()); + + PendingOperation *offerUnixSocket(const QString &socketAddress, + const QVariantMap ¶meters = QVariantMap(), bool requireCredentials = false); + PendingOperation *offerUnixSocket(const QLocalServer *server, + const QVariantMap ¶meters = QVariantMap(), bool requireCredentials = false); + + QHash<uint, Tp::ContactPtr> contactsForConnections() const; + + QHash<QPair<QHostAddress,quint16>, uint> connectionsForSourceAddresses() const; + QHash<uchar, uint> connectionsForCredentials() const; + +protected: + OutgoingStreamTubeChannel(const ConnectionPtr &connection, const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature = OutgoingStreamTubeChannel::FeatureCore); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onNewRemoteConnection(uint contactId, + const QDBusVariant ¶meter, uint connectionId); + TP_QT_NO_EXPORT void onContactsRetrieved(const QUuid &uuid, + const QList<Tp::ContactPtr> &contacts); + TP_QT_NO_EXPORT void onConnectionClosed(uint connectionId, + const QString &errorName, const QString &errorMessage); + +private: + struct Private; + friend struct PendingOpenTube; + friend struct Private; + Private *mPriv; +}; + +} + +#endif diff --git a/TelepathyQt/pending-account.cpp b/TelepathyQt/pending-account.cpp new file mode 100644 index 00000000..22f2d3d7 --- /dev/null +++ b/TelepathyQt/pending-account.cpp @@ -0,0 +1,184 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/PendingAccount> + +#include "TelepathyQt/_gen/pending-account.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/AccountManager> +#include <TelepathyQt/PendingReady> + +#include <QDBusObjectPath> +#include <QDBusPendingCallWatcher> +#include <QDBusPendingReply> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingAccount::Private +{ + AccountPtr account; +}; + +/** + * \class PendingAccount + * \ingroup clientaccount + * \headerfile TelepathyQt/pending-account.h <TelepathyQt/PendingAccount> + * + * \brief The PendingAccount class represents the parameters of and the reply to + * an asynchronous account request. + * + * Instances of this class cannot be constructed directly; the only way to get + * one is via AccountManager. + * + * See \ref async_model + */ + +/** + * Construct a new PendingAccount object. + * + * \param manager AccountManager to use. + * \param connectionManager Name of the connection manager to create the account + * for. + * \param protocol Name of the protocol to create the account for. + * \param displayName Account display name. + * \param parameters Account parameters. + * \param properties An optional map from fully qualified D-Bus property + * names such as "org.freedesktop.Telepathy.Account.Enabled" + * to their values. + */ +PendingAccount::PendingAccount(const AccountManagerPtr &manager, + const QString &connectionManager, const QString &protocol, + const QString &displayName, const QVariantMap ¶meters, + const QVariantMap &properties) + : PendingOperation(manager), + mPriv(new Private) +{ + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + manager->baseInterface()->CreateAccount(connectionManager, + protocol, displayName, parameters, properties), this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onCallFinished(QDBusPendingCallWatcher*))); +} + +/** + * Class destructor. + */ +PendingAccount::~PendingAccount() +{ + delete mPriv; +} + +/** + * Return the account manager through which the request was made. + * + * \return A pointer to the AccountManager object. + */ +AccountManagerPtr PendingAccount::manager() const +{ + return AccountManagerPtr(qobject_cast<AccountManager *>((AccountManager*) _object().data())); +} + +/** + * Return the newly created account. + * + * \return A pointer to an Account object, or a null AccountPtr if an error occurred. + */ +AccountPtr PendingAccount::account() const +{ + if (!isFinished()) { + warning() << "PendingAccount::account called before finished, returning 0"; + return AccountPtr(); + } else if (!isValid()) { + warning() << "PendingAccount::account called when not valid, returning 0"; + return AccountPtr(); + } + + return mPriv->account; +} + +void PendingAccount::onCallFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QDBusObjectPath> reply = *watcher; + + if (!reply.isError()) { + QString objectPath = reply.value().path(); + + debug() << "Got reply to AccountManager.CreateAccount - object path:" << objectPath; + + PendingReady *readyOp = manager()->accountFactory()->proxy(manager()->busName(), + objectPath, manager()->connectionFactory(), + manager()->channelFactory(), manager()->contactFactory()); + mPriv->account = AccountPtr::qObjectCast(readyOp->proxy()); + connect(readyOp, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onAccountBuilt(Tp::PendingOperation*))); + } else { + debug().nospace() << + "CreateAccount failed: " << + reply.error().name() << ": " << reply.error().message(); + setFinishedWithError(reply.error()); + } + + watcher->deleteLater(); +} + +void PendingAccount::onAccountBuilt(Tp::PendingOperation *op) +{ + Q_ASSERT(op->isFinished()); + + if (op->isError()) { + warning() << "Making account ready using the factory failed:" << + op->errorName() << op->errorMessage(); + setFinishedWithError(op->errorName(), op->errorMessage()); + } else { + // AM is stateless, so the only way for it to become invalid is in the introspection phase, + // and a PendingAccount should never be created if AM introspection hasn't succeeded + Q_ASSERT(!manager().isNull() && manager()->isValid()); + + if (manager()->allAccounts().contains(mPriv->account)) { + setFinished(); + debug() << "New account" << mPriv->account->objectPath() << "built"; + } else { + // Have to wait for the AM to pick up the change and signal it so the world can be + // assumed to be ~round when we finish + connect(manager().data(), + SIGNAL(newAccount(Tp::AccountPtr)), + SLOT(onNewAccount(Tp::AccountPtr))); + } + } +} + +void PendingAccount::onNewAccount(const AccountPtr &account) +{ + if (account != mPriv->account) { + return; + } + + debug() << "Account" << account->objectPath() << "added to AM, finishing PendingAccount"; + setFinished(); +} + +} // Tp diff --git a/TelepathyQt/pending-account.h b/TelepathyQt/pending-account.h new file mode 100644 index 00000000..788d3464 --- /dev/null +++ b/TelepathyQt/pending-account.h @@ -0,0 +1,75 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_pending_account_h_HEADER_GUARD_ +#define _TelepathyQt_pending_account_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Account> +#include <TelepathyQt/PendingOperation> + +#include <QString> +#include <QVariantMap> + +class QDBusPendingCallWatcher; + +namespace Tp +{ + +class AccountManager; + +class TP_QT_EXPORT PendingAccount : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingAccount); + +public: + ~PendingAccount(); + + AccountManagerPtr manager() const; + + AccountPtr account() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onCallFinished(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onAccountBuilt(Tp::PendingOperation *readyOp); + TP_QT_NO_EXPORT void onNewAccount(const Tp::AccountPtr &account); + +private: + friend class AccountManager; + + TP_QT_NO_EXPORT PendingAccount(const AccountManagerPtr &manager, + const QString &connectionManager, const QString &protocol, + const QString &displayName, const QVariantMap ¶meters, + const QVariantMap &properties = QVariantMap()); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-channel-request-internal.h b/TelepathyQt/pending-channel-request-internal.h new file mode 100644 index 00000000..076e2b22 --- /dev/null +++ b/TelepathyQt/pending-channel-request-internal.h @@ -0,0 +1,73 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_pending_channel_request_internal_h_HEADER_GUARD_ +#define _TelepathyQt_pending_channel_request_internal_h_HEADER_GUARD_ + +#include <TelepathyQt/ChannelRequest> +#include <TelepathyQt/PendingOperation> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class TP_QT_NO_EXPORT PendingChannelRequestCancelOperation : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingChannelRequestCancelOperation) + +public: + PendingChannelRequestCancelOperation() + : PendingOperation(SharedPtr<Object>()) + { + } + + ~PendingChannelRequestCancelOperation() + { + } + + void go(const ChannelRequestPtr &channelRequest) + { + Q_ASSERT(mChannelRequest.isNull()); + mChannelRequest = channelRequest; + connect(mChannelRequest->cancel(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onCancelOperationFinished(Tp::PendingOperation*))); + } + +private Q_SLOTS: + void onCancelOperationFinished(Tp::PendingOperation *op) + { + if (op->isError()) { + setFinishedWithError(op->errorName(), op->errorMessage()); + return; + } + setFinished(); + } + +private: + ChannelRequestPtr mChannelRequest; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-channel-request.cpp b/TelepathyQt/pending-channel-request.cpp new file mode 100644 index 00000000..56c37ccb --- /dev/null +++ b/TelepathyQt/pending-channel-request.cpp @@ -0,0 +1,276 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/PendingChannelRequest> +#include "TelepathyQt/pending-channel-request-internal.h" + +#include "TelepathyQt/_gen/pending-channel-request.moc.hpp" +#include "TelepathyQt/_gen/pending-channel-request-internal.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Account> +#include <TelepathyQt/AccountFactory> +#include <TelepathyQt/ChannelDispatcher> +#include <TelepathyQt/ChannelFactory> +#include <TelepathyQt/ChannelRequest> +#include <TelepathyQt/ChannelRequestHints> +#include <TelepathyQt/ConnectionFactory> +#include <TelepathyQt/ContactFactory> +#include <TelepathyQt/PendingFailure> +#include <TelepathyQt/PendingReady> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingChannelRequest::Private +{ + Private(const QDBusConnection &dbusConnection) + : dbusConnection(dbusConnection), + cancelOperation(0) + { + } + + QDBusConnection dbusConnection; + ChannelRequestPtr channelRequest; + PendingChannelRequestCancelOperation *cancelOperation; +}; + +/** + * \class PendingChannelRequest + * \ingroup clientchannelrequest + * \headerfile TelepathyQt/pending-channel-request.h <TelepathyQt/PendingChannelRequest> + * + * \brief The PendingChannelRequest class represents the parameters of and + * the reply to an asynchronous ChannelRequest request. + * + * Instances of this class cannot be constructed directly; the only way to get + * one is trough Account. + * + * See \ref async_model + */ + +/** + * Construct a new PendingChannelRequest object. + * + * \param account Account to use. + * \param requestedProperties A dictionary containing the desirable properties. + * \param userActionTime The time at which user action occurred, or QDateTime() + * if this channel request is for some reason not + * involving user action. + * \param preferredHandler Either the well-known bus name (starting with + * org.freedesktop.Telepathy.Client.) of the preferred + * handler for this channel, or an empty string to + * indicate that any handler would be acceptable. + * \param create Whether createChannel or ensureChannel should be called. + * \param account The account the request was made through. + */ +PendingChannelRequest::PendingChannelRequest(const AccountPtr &account, + const QVariantMap &requestedProperties, const QDateTime &userActionTime, + const QString &preferredHandler, bool create, const ChannelRequestHints &hints) + : PendingOperation(account), + mPriv(new Private(account->dbusConnection())) +{ + QString channelDispatcherObjectPath = + QString(QLatin1String("/%1")).arg(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_DISPATCHER)); + channelDispatcherObjectPath.replace(QLatin1String("."), QLatin1String("/")); + Client::ChannelDispatcherInterface *channelDispatcherInterface = + account->dispatcherInterface(); + + QDBusPendingCallWatcher *watcher = 0; + if (create) { + if (hints.isValid()) { + if (account->supportsRequestHints()) { + watcher = new QDBusPendingCallWatcher( + channelDispatcherInterface->CreateChannelWithHints( + QDBusObjectPath(account->objectPath()), + requestedProperties, + userActionTime.isNull() ? 0 : userActionTime.toTime_t(), + preferredHandler, hints.allHints()), this); + } else { + warning() << "Hints passed to channel request won't have an effect" + << "because the Channel Dispatcher service in use is too old"; + } + } + + if (!watcher) { + watcher = new QDBusPendingCallWatcher( + channelDispatcherInterface->CreateChannel( + QDBusObjectPath(account->objectPath()), + requestedProperties, + userActionTime.isNull() ? 0 : userActionTime.toTime_t(), + preferredHandler), this); + } + } else { + if (hints.isValid()) { + if (account->supportsRequestHints()) { + watcher = new QDBusPendingCallWatcher( + channelDispatcherInterface->EnsureChannelWithHints( + QDBusObjectPath(account->objectPath()), + requestedProperties, + userActionTime.isNull() ? 0 : userActionTime.toTime_t(), + preferredHandler, hints.allHints()), this); + } else { + warning() << "Hints passed to channel request won't have an effect" + << "because the Channel Dispatcher service in use is too old"; + } + } + + if (!watcher) { + watcher = new QDBusPendingCallWatcher( + channelDispatcherInterface->EnsureChannel( + QDBusObjectPath(account->objectPath()), + requestedProperties, + userActionTime.isNull() ? 0 : userActionTime.toTime_t(), + preferredHandler), this); + } + } + + // TODO: This is a Qt bug fixed upstream, should be in the next Qt release. + // We should not need to check watcher->isFinished() here, remove the + // check when we depend on the fixed Qt version. + if (watcher->isFinished()) { + onWatcherFinished(watcher); + } else { + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onWatcherFinished(QDBusPendingCallWatcher*))); + } +} + +/** + * Construct a new PendingChannelRequest object that always fails. + * + * \param account Account to use. + * \param errorName The name of a D-Bus error. + * \param errorMessage The error message. + */ +PendingChannelRequest::PendingChannelRequest(const AccountPtr &account, + const QString &errorName, const QString &errorMessage) + : PendingOperation(ConnectionPtr()), + mPriv(new Private(account->dbusConnection())) +{ + setFinishedWithError(errorName, errorMessage); +} + +/** + * Class destructor. + */ +PendingChannelRequest::~PendingChannelRequest() +{ + delete mPriv; +} + +/** + * Return the account through which the request was made. + * + * \return A pointer to the Account object. + */ +AccountPtr PendingChannelRequest::account() const +{ + return AccountPtr(qobject_cast<Account*>((Account*) _object().data())); +} + +ChannelRequestPtr PendingChannelRequest::channelRequest() const +{ + return mPriv->channelRequest; +} + +PendingOperation *PendingChannelRequest::cancel() +{ + if (isFinished()) { + // CR has already succeeded or failed, so let's just fail here + return new PendingFailure(TP_QT_DBUS_ERROR_UNKNOWN_METHOD, + QLatin1String("ChannnelRequest already finished"), + _object()); + } + + if (!mPriv->cancelOperation) { + mPriv->cancelOperation = new PendingChannelRequestCancelOperation(); + connect(mPriv->cancelOperation, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onCancelOperationFinished(Tp::PendingOperation*))); + + if (mPriv->channelRequest) { + mPriv->cancelOperation->go(mPriv->channelRequest); + } + } + + return mPriv->cancelOperation; +} + +void PendingChannelRequest::onWatcherFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QDBusObjectPath> reply = *watcher; + + if (!reply.isError()) { + QDBusObjectPath objectPath = reply.argumentAt<0>(); + debug() << "Got reply to ChannelDispatcher.Ensure/CreateChannel " + "- object path:" << objectPath.path(); + + if (!account().isNull()) { + mPriv->channelRequest = ChannelRequest::create(account(), + objectPath.path(), QVariantMap()); + } + + if (mPriv->cancelOperation) { + mPriv->cancelOperation->go(mPriv->channelRequest); + } else { + emit channelRequestCreated(mPriv->channelRequest); + + connect(mPriv->channelRequest.data(), + SIGNAL(failed(QString,QString)), + SLOT(setFinishedWithError(QString,QString))); + connect(mPriv->channelRequest.data(), + SIGNAL(succeeded(Tp::ChannelPtr)), + SLOT(setFinished())); + + connect(mPriv->channelRequest->proceed(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onProceedOperationFinished(Tp::PendingOperation*))); + } + } else { + debug().nospace() << "Ensure/CreateChannel failed:" << + reply.error().name() << ": " << reply.error().message(); + setFinishedWithError(reply.error()); + } + + watcher->deleteLater(); +} + +void PendingChannelRequest::onProceedOperationFinished(PendingOperation *op) +{ + if (op->isError()) { + setFinishedWithError(op->errorName(), op->errorMessage()); + } +} + +void PendingChannelRequest::onCancelOperationFinished(PendingOperation *op) +{ + mPriv->cancelOperation = 0; + if (!isFinished()) { + setFinishedWithError(QLatin1String(TELEPATHY_ERROR_CANCELLED), + QLatin1String("ChannelRequest cancelled")); + } +} + +} // Tp diff --git a/TelepathyQt/pending-channel-request.h b/TelepathyQt/pending-channel-request.h new file mode 100644 index 00000000..1c62c21c --- /dev/null +++ b/TelepathyQt/pending-channel-request.h @@ -0,0 +1,84 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_pending_channel_request_h_HEADER_GUARD_ +#define _TelepathyQt_pending_channel_request_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> +#include <TelepathyQt/PendingOperation> +#include <TelepathyQt/Types> + +#include <QDateTime> +#include <QString> +#include <QVariantMap> + +class QDBusPendingCallWatcher; + +namespace Tp +{ + +class Account; +class ChannelRequestHints; + +class TP_QT_EXPORT PendingChannelRequest : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingChannelRequest) + +public: + ~PendingChannelRequest(); + + AccountPtr account() const; + + ChannelRequestPtr channelRequest() const; + + PendingOperation *cancel(); + +Q_SIGNALS: + void channelRequestCreated(const Tp::ChannelRequestPtr &channelRequest); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onWatcherFinished(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onProceedOperationFinished(Tp::PendingOperation *op); + TP_QT_NO_EXPORT void onCancelOperationFinished(Tp::PendingOperation *op); + +private: + friend class Account; + + TP_QT_NO_EXPORT PendingChannelRequest(const AccountPtr &account, + const QVariantMap &requestedProperties, const QDateTime &userActionTime, + const QString &preferredHandler, bool create, const ChannelRequestHints &hints); + TP_QT_NO_EXPORT PendingChannelRequest(const AccountPtr &account, + const QString &errorName, const QString &errorMessage); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-channel.cpp b/TelepathyQt/pending-channel.cpp new file mode 100644 index 00000000..3207700b --- /dev/null +++ b/TelepathyQt/pending-channel.cpp @@ -0,0 +1,555 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/PendingChannel> + +#include "TelepathyQt/_gen/pending-channel.moc.hpp" + +#include "TelepathyQt/debug-internal.h" +#include "TelepathyQt/fake-handler-manager-internal.h" +#include "TelepathyQt/request-temporary-handler-internal.h" + +#include <TelepathyQt/Channel> +#include <TelepathyQt/ChannelFactory> +#include <TelepathyQt/ClientRegistrar> +#include <TelepathyQt/Connection> +#include <TelepathyQt/ConnectionLowlevel> +#include <TelepathyQt/Constants> +#include <TelepathyQt/HandledChannelNotifier> +#include <TelepathyQt/PendingChannelRequest> +#include <TelepathyQt/PendingReady> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingChannel::Private +{ + class FakeAccountFactory; + + ConnectionPtr connection; + bool create; + bool yours; + QString channelType; + uint handleType; + uint handle; + QVariantMap immutableProperties; + ChannelPtr channel; + + ClientRegistrarPtr cr; + SharedPtr<RequestTemporaryHandler> handler; + HandledChannelNotifier *notifier; + static uint numHandlers; +}; + +uint PendingChannel::Private::numHandlers = 0; + +class TP_QT_NO_EXPORT PendingChannel::Private::FakeAccountFactory : public AccountFactory +{ +public: + static AccountFactoryPtr create(const AccountPtr &account) + { + return AccountFactoryPtr(new FakeAccountFactory(account)); + } + + ~FakeAccountFactory() { } + + AccountPtr account() const { return mAccount; } + +protected: + AccountPtr construct(const QString &busName, const QString &objectPath, + const ConnectionFactoryConstPtr &connFactory, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) const + { + if (mAccount->objectPath() != objectPath) { + warning() << "Account received by the fake factory is different from original account"; + } + return mAccount; + } + +private: + FakeAccountFactory(const AccountPtr &account) + : AccountFactory(account->dbusConnection(), Features()), + mAccount(account) + { + } + + AccountPtr mAccount; +}; + +/** + * \class PendingChannel + * \ingroup clientchannel + * \headerfile TelepathyQt/pending-channel.h <TelepathyQt/PendingChannel> + * + * \brief The PendingChannel class represents the parameters of and the reply to + * an asynchronous channel request. + * + * Instances of this class cannot be constructed directly; the only way to get + * one is trough Connection or Account. + * + * See \ref async_model + */ + +/** + * Construct a new PendingChannel object that will fail immediately. + * + * \param connection Connection to use. + * \param errorName The error name. + * \param errorMessage The error message. + */ +PendingChannel::PendingChannel(const ConnectionPtr &connection, const QString &errorName, + const QString &errorMessage) + : PendingOperation(connection), + mPriv(new Private) +{ + mPriv->connection = connection; + mPriv->yours = false; + mPriv->handleType = 0; + mPriv->handle = 0; + mPriv->notifier = 0; + mPriv->create = false; + + setFinishedWithError(errorName, errorMessage); +} + +/** + * Construct a new PendingChannel object. + * + * \param connection Connection to use. + * \param request A dictionary containing the desirable properties. + * \param create Whether createChannel or ensureChannel should be called. + */ +PendingChannel::PendingChannel(const ConnectionPtr &connection, + const QVariantMap &request, bool create, int timeout) + : PendingOperation(connection), + mPriv(new Private) +{ + mPriv->connection = connection; + mPriv->yours = create; + mPriv->channelType = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString(); + mPriv->handleType = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt(); + mPriv->handle = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle")).toUInt(); + mPriv->notifier = 0; + mPriv->create = create; + + Client::ConnectionInterfaceRequestsInterface *requestsInterface = + connection->interface<Client::ConnectionInterfaceRequestsInterface>(); + if (create) { + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + requestsInterface->CreateChannel(request, timeout), this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onConnectionCreateChannelFinished(QDBusPendingCallWatcher*))); + } + else { + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + requestsInterface->EnsureChannel(request, timeout), this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onConnectionEnsureChannelFinished(QDBusPendingCallWatcher*))); + } +} + +PendingChannel::PendingChannel(const AccountPtr &account, + const QVariantMap &request, const QDateTime &userActionTime, + bool create) + : PendingOperation(account), + mPriv(new Private) +{ + mPriv->yours = true; + mPriv->channelType = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString(); + mPriv->handleType = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt(); + mPriv->handle = request.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle")).toUInt(); + + mPriv->cr = ClientRegistrar::create( + Private::FakeAccountFactory::create(account), + account->connectionFactory(), + account->channelFactory(), + account->contactFactory()); + mPriv->handler = RequestTemporaryHandler::create(account); + mPriv->notifier = 0; + mPriv->create = create; + + QString handlerName = QString(QLatin1String("TpQt4RaH_%1_%2")) + .arg(account->dbusConnection().baseService() + .replace(QLatin1String(":"), QLatin1String("_")) + .replace(QLatin1String("."), QLatin1String("_"))) + .arg(Private::numHandlers++); + if (!mPriv->cr->registerClient(mPriv->handler, handlerName, false)) { + warning() << "Unable to register handler" << handlerName; + setFinishedWithError(TP_QT_ERROR_NOT_AVAILABLE, + QLatin1String("Unable to register handler")); + return; + } + + connect(mPriv->handler.data(), + SIGNAL(error(QString,QString)), + SLOT(onHandlerError(QString,QString))); + connect(mPriv->handler.data(), + SIGNAL(channelReceived(Tp::ChannelPtr,QDateTime,Tp::ChannelRequestHints)), + SLOT(onHandlerChannelReceived(Tp::ChannelPtr))); + + handlerName = QString(QLatin1String("org.freedesktop.Telepathy.Client.%1")).arg(handlerName); + + debug() << "Requesting channel through account using handler" << handlerName; + PendingChannelRequest *pcr; + if (create) { + pcr = account->createChannel(request, userActionTime, handlerName, ChannelRequestHints()); + } else { + pcr = account->ensureChannel(request, userActionTime, handlerName, ChannelRequestHints()); + } + connect(pcr, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onAccountCreateChannelFinished(Tp::PendingOperation*))); +} + +/** + * Construct a new PendingChannel object that will fail immediately. + * + * \param errorName The name of a D-Bus error. + * \param errorMessage The error message. + */ +PendingChannel::PendingChannel(const QString &errorName, const QString &errorMessage) + : PendingOperation(ConnectionPtr()), mPriv(new Private) +{ + setFinishedWithError(errorName, errorMessage); +} + +/** + * Class destructor. + */ +PendingChannel::~PendingChannel() +{ + delete mPriv; +} + +/** + * Return the connection through which the channel request was made. + * + * Note that if this channel request was created through Account, a null ConnectionPtr will be + * returned. + * + * \return A pointer to the Connection object. + */ +ConnectionPtr PendingChannel::connection() const +{ + return mPriv->connection; +} + +/** + * Return whether this channel belongs to this process. + * + * If \c false, the caller must assume that some other process is + * handling this channel; if \c true, the caller should handle it + * themselves or delegate it to another client. + * + * \return \c true if it belongs, \c false otherwise. + */ +bool PendingChannel::yours() const +{ + if (!isFinished()) { + warning() << "PendingChannel::yours called before finished, returning undefined value"; + } + else if (!isValid()) { + warning() << "PendingChannel::yours called when not valid, returning undefined value"; + } + + return mPriv->yours; +} + +/** + * Return the channel type specified in the channel request. + * + * \return The D-Bus interface name for the type of the channel. + */ +const QString &PendingChannel::channelType() const +{ + return mPriv->channelType; +} + +/** + * If the channel request has finished, return the handle type of the resulting + * channel. Otherwise, return the handle type that was requested. + * + * (One example of a request producing a different target handle type is that + * on protocols like MSN, one-to-one conversations don't really exist, and if + * you request a text channel with handle type HandleTypeContact, what you + * will actually get is a text channel with handle type HandleTypeNone, with + * the requested contact as a member.) + * + * \return The target handle type as #HandleType. + * \sa targetHandle() + */ +uint PendingChannel::targetHandleType() const +{ + return mPriv->handleType; +} + +/** + * If the channel request has finished, return the target handle of the + * resulting channel. Otherwise, return the target handle that was requested + * (which might be different in some situations - see targetHandleType()). + * + * \return An integer representing the target handle, which is of the type + * targetHandleType() indicates. + * \sa targetHandleType() + */ +uint PendingChannel::targetHandle() const +{ + return mPriv->handle; +} + +/** + * If this channel request has finished, return the immutable properties of + * the resulting channel. Otherwise, return an empty map. + * + * The keys and values in this map are defined by the \telepathy_spec, + * or by third-party extensions to that specification. + * These are the properties that cannot change over the lifetime of the + * channel; they're announced in the result of the request, for efficiency. + * This map should be passed to the constructor of Channel or its subclasses + * (such as TextChannel). + * + * These properties can also be used to process channels in a way that does + * not require the creation of a Channel object - for instance, a + * ChannelDispatcher implementation should be able to classify and process + * channels based on their immutable properties, without needing to create + * Channel objects. + * + * \return The immutable properties as QVariantMap. + */ +QVariantMap PendingChannel::immutableProperties() const +{ + QVariantMap props = mPriv->immutableProperties; + + // This is a reasonable guess - if it's Yours it's guaranteedly Requested by us, and if it's not + // it could be either Requested by somebody else but also an incoming channel just as well. + if (!props.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested"))) { + debug() << "CM didn't provide Requested in channel immutable props, guessing" + << mPriv->yours; + props[QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested")] = + mPriv->yours; + } + + // Also, the spec says that if the channel was Requested by the local user, InitiatorHandle must + // be the Connection's self handle + if (!props.contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorHandle"))) { + if (qdbus_cast<bool>(props.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".Requested")))) { + if (connection() && connection()->isReady(Connection::FeatureCore)) { + debug() << "CM didn't provide InitiatorHandle in channel immutable props, but we " + "know it's the conn's self handle (and have it)"; + props[QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".InitiatorHandle")] = + connection()->selfHandle(); + } + } + } + + return props; +} + +/** + * Return the channel resulting from the channel request. + * + * \return A pointer to the Channel object. + */ +ChannelPtr PendingChannel::channel() const +{ + if (!isFinished()) { + warning() << "PendingChannel::channel called before finished, returning 0"; + return ChannelPtr(); + } else if (!isValid()) { + warning() << "PendingChannel::channel called when not valid, returning 0"; + return ChannelPtr(); + } + + return mPriv->channel; +} + +/** + * If this channel request has finished and was created through Account, + * return a HandledChannelNotifier object that will keep track of channel() being re-requested. + * + * \return A HandledChannelNotifier instance, or 0 if an error occurred. + */ +HandledChannelNotifier *PendingChannel::handledChannelNotifier() const +{ + if (!isFinished()) { + warning() << "PendingChannel::handledChannelNotifier called before finished, returning 0"; + return 0; + } else if (!isValid()) { + warning() << "PendingChannel::handledChannelNotifier called when not valid, returning 0"; + return 0; + } + + if (mPriv->cr && !mPriv->notifier) { + mPriv->notifier = new HandledChannelNotifier(mPriv->cr, mPriv->handler); + } + return mPriv->notifier; +} + +void PendingChannel::onConnectionCreateChannelFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QDBusObjectPath, QVariantMap> reply = *watcher; + + if (!reply.isError()) { + QString objectPath = reply.argumentAt<0>().path(); + QVariantMap map = reply.argumentAt<1>(); + + debug() << "Got reply to Connection.CreateChannel - object path:" << objectPath; + + PendingReady *channelReady = + connection()->channelFactory()->proxy(connection(), objectPath, map); + mPriv->channel = ChannelPtr::qObjectCast(channelReady->proxy()); + + mPriv->immutableProperties = map; + mPriv->channelType = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString(); + mPriv->handleType = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt(); + mPriv->handle = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle")).toUInt(); + + connect(channelReady, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onChannelReady(Tp::PendingOperation*))); + } else { + debug().nospace() << "CreateChannel failed:" << + reply.error().name() << ": " << reply.error().message(); + setFinishedWithError(reply.error()); + } + + watcher->deleteLater(); +} + +void PendingChannel::onConnectionEnsureChannelFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<bool, QDBusObjectPath, QVariantMap> reply = *watcher; + + if (!reply.isError()) { + mPriv->yours = reply.argumentAt<0>(); + QString objectPath = reply.argumentAt<1>().path(); + QVariantMap map = reply.argumentAt<2>(); + + debug() << "Got reply to Connection.EnsureChannel - object path:" << objectPath; + + PendingReady *channelReady = + connection()->channelFactory()->proxy(connection(), objectPath, map); + mPriv->channel = ChannelPtr::qObjectCast(channelReady->proxy()); + + mPriv->immutableProperties = map; + mPriv->channelType = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType")).toString(); + mPriv->handleType = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")).toUInt(); + mPriv->handle = map.value(QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandle")).toUInt(); + + connect(channelReady, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onChannelReady(Tp::PendingOperation*))); + } else { + debug().nospace() << "EnsureChannel failed:" << + reply.error().name() << ": " << reply.error().message(); + setFinishedWithError(reply.error()); + } + + watcher->deleteLater(); +} + +void PendingChannel::onChannelReady(PendingOperation *op) +{ + if (!op->isError()) { + setFinished(); + } else { + debug() << "Making the channel ready for" << this << "failed with" << op->errorName() + << ":" << op->errorMessage(); + setFinishedWithError(op->errorName(), op->errorMessage()); + } +} + +void PendingChannel::onHandlerError(const QString &errorName, const QString &errorMessage) +{ + if (isFinished()) { + return; + } + + warning() << "Creating/ensuring channel failed with" << errorName + << ":" << errorMessage; + setFinishedWithError(errorName, errorMessage); +} + +void PendingChannel::onHandlerChannelReceived(const ChannelPtr &channel) +{ + if (isFinished()) { + warning() << "Handler received the channel but this operation already finished due " + "to failure in the channel request"; + return; + } + + mPriv->handleType = channel->targetHandleType(); + mPriv->handle = channel->targetHandle(); + mPriv->immutableProperties = channel->immutableProperties(); + mPriv->channel = channel; + + // register the CR in FakeHandlerManager so that at least one handler per bus stays alive + // until all channels requested using R&H gets invalidated/destroyed. + // This is important in case Mission Control happens to restart while + // the channel is still in use, since it will close each channel it + // doesn't find a handler for it. + FakeHandlerManager::instance()->registerClientRegistrar(mPriv->cr); + + setFinished(); +} + +void PendingChannel::onAccountCreateChannelFinished(PendingOperation *op) +{ + if (isFinished()) { + if (isError()) { + warning() << "Creating/ensuring channel finished with a failure after the internal " + "handler already got a channel, ignoring"; + } + return; + } + + if (op->isError()) { + warning() << "Creating/ensuring channel failed with" << op->errorName() + << ":" << op->errorMessage(); + setFinishedWithError(op->errorName(), op->errorMessage()); + return; + } + + if (!mPriv->handler->isDBusHandlerInvoked()) { + // Our handler hasn't be called but the channel request is complete. + // That means another handler handled the channels so we don't own it. + if (mPriv->create) { + warning() << "Creating/ensuring channel failed with" << TP_QT_ERROR_SERVICE_CONFUSED + << ":" << QLatin1String("CD.CreateChannel/WithHints returned successfully and " + "the handler didn't receive the channel yet"); + setFinishedWithError(TP_QT_ERROR_SERVICE_CONFUSED, + QLatin1String("CD.CreateChannel/WithHints returned successfully and " + "the handler didn't receive the channel yet")); + } else { + warning() << "Creating/ensuring channel failed with" << TP_QT_ERROR_NOT_YOURS + << ":" << QLatin1String("Another handler is handling this channel"); + setFinishedWithError(TP_QT_ERROR_NOT_YOURS, + QLatin1String("Another handler is handling this channel")); + } + return; + } +} + +} // Tp diff --git a/TelepathyQt/pending-channel.h b/TelepathyQt/pending-channel.h new file mode 100644 index 00000000..2480e431 --- /dev/null +++ b/TelepathyQt/pending-channel.h @@ -0,0 +1,103 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_pending_channel_h_HEADER_GUARD_ +#define _TelepathyQt_pending_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Channel> +#include <TelepathyQt/PendingOperation> + +#include <QString> +#include <QVariantMap> + +#include <QDBusPendingCallWatcher> + +namespace Tp +{ + +class Connection; +class HandledChannelNotifier; + +class TP_QT_EXPORT PendingChannel : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingChannel) + +public: + ~PendingChannel(); + + ConnectionPtr connection() const; + + bool yours() const; + + const QString &channelType() const; + + uint targetHandleType() const; + + uint targetHandle() const; + + QVariantMap immutableProperties() const; + + ChannelPtr channel() const; + + HandledChannelNotifier *handledChannelNotifier() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onConnectionCreateChannelFinished( + QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onConnectionEnsureChannelFinished( + QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onChannelReady(Tp::PendingOperation *op); + + TP_QT_NO_EXPORT void onHandlerError(const QString &errorName, + const QString &errorMessage); + TP_QT_NO_EXPORT void onHandlerChannelReceived( + const Tp::ChannelPtr &channel); + TP_QT_NO_EXPORT void onAccountCreateChannelFinished( + Tp::PendingOperation *op); + +private: + friend class Account; + friend class ConnectionLowlevel; + + TP_QT_NO_EXPORT PendingChannel(const ConnectionPtr &connection, + const QString &errorName, const QString &errorMessage); + TP_QT_NO_EXPORT PendingChannel(const ConnectionPtr &connection, + const QVariantMap &request, bool create, int timeout = -1); + TP_QT_NO_EXPORT PendingChannel(const AccountPtr &account, + const QVariantMap &request, const QDateTime &userActionTime, + bool create); + TP_QT_NO_EXPORT PendingChannel(const QString &errorName, + const QString &errorMessage); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-connection.cpp b/TelepathyQt/pending-connection.cpp new file mode 100644 index 00000000..e1e16728 --- /dev/null +++ b/TelepathyQt/pending-connection.cpp @@ -0,0 +1,171 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/PendingConnection> + +#include "TelepathyQt/_gen/pending-connection.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/ChannelFactory> +#include <TelepathyQt/ConnectionManager> +#include <TelepathyQt/Connection> +#include <TelepathyQt/ContactFactory> +#include <TelepathyQt/PendingReady> + +#include <QDBusObjectPath> +#include <QDBusPendingCallWatcher> +#include <QDBusPendingReply> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingConnection::Private +{ + ConnectionPtr connection; +}; + +/** + * \class PendingConnection + * \ingroup clientconn + * \headerfile TelepathyQt/pending-connection.h <TelepathyQt/PendingConnection> + * + * \brief The PendingConnection class represents the parameters of and the reply + * to an asynchronous connection request. + * + * Instances of this class cannot be constructed directly; the only way to get + * one is via ConnectionManager. + * + * See \ref async_model + */ + +/** + * Construct a new PendingConnection object. + * + * \param manager ConnectionManager to use. + * \param protocol Name of the protocol to create the connection for. + * \param parameters Connection parameters. + */ +PendingConnection::PendingConnection(const ConnectionManagerPtr &manager, + const QString &protocol, const QVariantMap ¶meters) + : PendingOperation(manager), + mPriv(new Private) +{ + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + manager->baseInterface()->RequestConnection(protocol, + parameters), this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onCallFinished(QDBusPendingCallWatcher*))); +} + +/** + * Construct a new PendingConnection object that will fail immediately. + * + * \param error Name of the error to fail with. + * \param errorMessage Detail message for the error. + */ +PendingConnection::PendingConnection(const QString &error, const QString &errorMessage) + : PendingOperation(ConnectionManagerPtr()), + mPriv(new Private) +{ + setFinishedWithError(error, errorMessage); +} + +/** + * Class destructor. + */ +PendingConnection::~PendingConnection() +{ + delete mPriv; +} + +/** + * Return the connection manager through which the request was made. + * + * \return A pointer to the ConnectionManager object. + */ +ConnectionManagerPtr PendingConnection::manager() const +{ + return ConnectionManagerPtr(qobject_cast<ConnectionManager*>((ConnectionManager*) _object().data())); +} + +/** + * Return the connection resulting from the connection request. + * + * \return A pointer to the Connection object. + */ +ConnectionPtr PendingConnection::connection() const +{ + if (!isFinished()) { + warning() << "PendingConnection::connection called before finished, returning 0"; + return ConnectionPtr(); + } else if (!isValid()) { + warning() << "PendingConnection::connection called when not valid, returning 0"; + return ConnectionPtr(); + } + + return mPriv->connection; +} + +void PendingConnection::onCallFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QString, QDBusObjectPath> reply = *watcher; + + if (!reply.isError()) { + QString busName = reply.argumentAt<0>(); + QString objectPath = reply.argumentAt<1>().path(); + + debug() << "Got reply to ConnectionManager.CreateConnection - bus name:" << + busName << "- object path:" << objectPath; + + PendingReady *readyOp = manager()->connectionFactory()->proxy(busName, + objectPath, manager()->channelFactory(), manager()->contactFactory()); + mPriv->connection = ConnectionPtr::qObjectCast(readyOp->proxy()); + connect(readyOp, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onConnectionBuilt(Tp::PendingOperation*))); + } else { + debug().nospace() << + "CreateConnection failed: " << + reply.error().name() << ": " << reply.error().message(); + setFinishedWithError(reply.error()); + } + + watcher->deleteLater(); +} + +void PendingConnection::onConnectionBuilt(Tp::PendingOperation *op) +{ + Q_ASSERT(op->isFinished()); + + if (op->isError()) { + warning() << "Making connection ready using the factory failed:" << + op->errorName() << op->errorMessage(); + setFinishedWithError(op->errorName(), op->errorMessage()); + } else { + setFinished(); + debug() << "New connection" << mPriv->connection->objectPath() << "built"; + } +} + +} // Tp diff --git a/TelepathyQt/pending-connection.h b/TelepathyQt/pending-connection.h new file mode 100644 index 00000000..1f9c6c09 --- /dev/null +++ b/TelepathyQt/pending-connection.h @@ -0,0 +1,73 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_pending_connection_h_HEADER_GUARD_ +#define _TelepathyQt_pending_connection_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Connection> +#include <TelepathyQt/PendingOperation> + +#include <QString> +#include <QVariantMap> + +class QDBusPendingCallWatcher; + +namespace Tp +{ + +class ConnectionManager; + +class TP_QT_EXPORT PendingConnection : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingConnection); + +public: + ~PendingConnection(); + + ConnectionManagerPtr manager() const; + + ConnectionPtr connection() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onCallFinished(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onConnectionBuilt(Tp::PendingOperation *op); + +private: + friend class ConnectionManagerLowlevel; + + TP_QT_NO_EXPORT PendingConnection(const ConnectionManagerPtr &manager, + const QString &protocol, const QVariantMap ¶meters); + TP_QT_NO_EXPORT PendingConnection(const QString &error, const QString &errorMessage); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-contact-attributes.cpp b/TelepathyQt/pending-contact-attributes.cpp new file mode 100644 index 00000000..2b9cbd17 --- /dev/null +++ b/TelepathyQt/pending-contact-attributes.cpp @@ -0,0 +1,219 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/PendingContactAttributes> + +#include "TelepathyQt/_gen/pending-contact-attributes.moc.hpp" + +#include <TelepathyQt/Connection> +#include <TelepathyQt/ReferencedHandles> + +#include "TelepathyQt/debug-internal.h" + +namespace Tp +{ + +/** + * \class PendingContactAttributes + * \ingroup clientconn + * \headerfile TelepathyQt/pending-contact-attributes.h <TelepathyQt/PendingContactAttributes> + * + * \brief The PendingContactAttributes class represents the parameters of and + * the reply to an asynchronous request for raw contact attributes, as used in + * the ConnectionLowlevel::contactAttributes() low-level convenience method wrapping the + * Client::ConnectionInterfaceContactsInterface::GetContactAttributes() D-Bus + * method. + * + * See \ref async_model + */ + +struct TP_QT_NO_EXPORT PendingContactAttributes::Private +{ + UIntList contactsRequested; + QStringList interfacesRequested; + bool shouldReference; + ReferencedHandles validHandles; + UIntList invalidHandles; + ContactAttributesMap attributes; +}; + +PendingContactAttributes::PendingContactAttributes(const ConnectionPtr &connection, + const UIntList &handles, const QStringList &interfaces, bool reference) + : PendingOperation(connection), + mPriv(new Private) +{ + mPriv->contactsRequested = handles; + mPriv->interfacesRequested = interfaces; + mPriv->shouldReference = reference; +} + +/** + * Class destructor. + */ +PendingContactAttributes::~PendingContactAttributes() +{ + delete mPriv; +} + +/** + * Return the connection through which the request was made. + * + * \return A pointer to the Connection object. + */ +ConnectionPtr PendingContactAttributes::connection() const +{ + return ConnectionPtr(qobject_cast<Connection*>((Connection*) _object().data())); +} + +/** + * Return the contacts for which attributes were requested. + * + * \return Reference to a list with the handles of the contacts. + */ +const UIntList &PendingContactAttributes::contactsRequested() const +{ + return mPriv->contactsRequested; +} + +/** + * Return the interfaces the corresponding attributes of which were requested. + * + * \return Reference to a list of D-Bus interface names. + */ +const QStringList &PendingContactAttributes::interfacesRequested() const +{ + return mPriv->interfacesRequested; +} + +/** + * Return whether it was requested that the contact handles should be referenced in addition to + * fetching their attributes. This corresponds to the <code>reference</code> argument to + * Connection::contactAttributes(). + * + * \return Whether the handles should be referenced or not. + */ +bool PendingContactAttributes::shouldReference() const +{ + return mPriv->shouldReference; +} + +/** + * If referencing the handles was requested (as indicated by shouldReference()), returns the + * now-referenced handles resulting from the operation. If the operation has not (yet) finished + * successfully (isFinished() returns <code>false</code>), or referencing was not requested, the + * return value is undefined. + * + * Even if referencing was requested, the list will not always contain all of the handles in + * contactsRequested(), only the ones which were valid. The valid handles will be in the same order + * as in contactsRequested(), though. + * + * \return ReferencedHandles instance containing the handles. + */ +ReferencedHandles PendingContactAttributes::validHandles() const +{ + if (!isFinished()) { + warning() << "PendingContactAttributes::validHandles() called before finished"; + } else if (isError()) { + warning() << "PendingContactAttributes::validHandles() called when errored"; + } else if (!shouldReference()) { + warning() << "PendingContactAttributes::validHandles() called but weren't asked to" + << "reference handles"; + } + + return mPriv->validHandles; +} + +/** + * Return the handles which were found to be invalid while processing the operation. If the + * operation has not (yet) finished successfully (isFinished() returns <code>false</code>), the + * return value is undefined. + * + * \return A list with the invalid handles. + */ +UIntList PendingContactAttributes::invalidHandles() const +{ + if (!isFinished()) { + warning() << "PendingContactAttributes::validHandles() called before finished"; + } else if (isError()) { + warning() << "PendingContactAttributes::validHandles() called when errored"; + } + + return mPriv->invalidHandles; +} + +/** + * Return a dictionary mapping the valid contact handles in contactsRequested() (when also + * referencing, this means the contents of validHandles()) to contact attributes. If the operation + * has not (yet) finished successfully (isFinished() returns <code>false</code>), the return value + * is undefined. + * + * \return Mapping from handles to variant maps containing the attributes. + */ +ContactAttributesMap PendingContactAttributes::attributes() const +{ + if (!isFinished()) { + warning() << "PendingContactAttributes::validHandles() called before finished"; + } else if (isError()) { + warning() << "PendingContactAttributes::validHandles() called when errored"; + } + + return mPriv->attributes; +} + +void PendingContactAttributes::onCallFinished(QDBusPendingCallWatcher* watcher) +{ + QDBusPendingReply<ContactAttributesMap> reply = *watcher; + + if (reply.isError()) { + debug().nospace() << "GetCAs: error " << reply.error().name() << ": " << reply.error().message(); + setFinishedWithError(reply.error()); + } else { + mPriv->attributes = reply.value(); + + UIntList validHandles; + foreach (uint contact, mPriv->contactsRequested) { + if (mPriv->attributes.contains(contact)) { + validHandles << contact; + } else { + mPriv->invalidHandles << contact; + } + } + + if (shouldReference()) { + mPriv->validHandles = ReferencedHandles(connection(), HandleTypeContact, + validHandles); + } + + setFinished(); + } + + connection()->handleRequestLanded(HandleTypeContact); + + watcher->deleteLater(); +} + +void PendingContactAttributes::failImmediately(const QString &error, const QString &errorMessage) +{ + setFinishedWithError(error, errorMessage); +} + +} // Tp diff --git a/TelepathyQt/pending-contact-attributes.h b/TelepathyQt/pending-contact-attributes.h new file mode 100644 index 00000000..2f1fa14a --- /dev/null +++ b/TelepathyQt/pending-contact-attributes.h @@ -0,0 +1,77 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_pending_contact_attributes_h_HEADER_GUARD_ +#define _TelepathyQt_pending_contact_attributes_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/PendingOperation> +#include <TelepathyQt/Types> +#include <TelepathyQt/Constants> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class ReferencedHandles; + +class TP_QT_EXPORT PendingContactAttributes : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingContactAttributes) + +public: + ~PendingContactAttributes(); + + ConnectionPtr connection() const; + + const UIntList &contactsRequested() const; + const QStringList &interfacesRequested() const; + bool shouldReference() const; + + ReferencedHandles validHandles() const; + UIntList invalidHandles() const; + ContactAttributesMap attributes() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onCallFinished(QDBusPendingCallWatcher *watcher); + +private: + friend class ConnectionLowlevel; + + TP_QT_NO_EXPORT PendingContactAttributes(const ConnectionPtr &connection, + const UIntList &handles, + const QStringList &interfaces, bool reference); + + TP_QT_NO_EXPORT void failImmediately(const QString &error, const QString &errorMessage); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-contact-info.cpp b/TelepathyQt/pending-contact-info.cpp new file mode 100644 index 00000000..9a4c08d8 --- /dev/null +++ b/TelepathyQt/pending-contact-info.cpp @@ -0,0 +1,128 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/PendingContactInfo> + +#include "TelepathyQt/_gen/pending-contact-info.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Connection> +#include <TelepathyQt/ContactManager> + +#include <QDBusPendingReply> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingContactInfo::Private +{ + Contact::InfoFields info; +}; + +/** + * \class PendingContactInfo + * \ingroup clientconn + * \headerfile TelepathyQt/pending-contact-info.h <TelepathyQt/PendingContactInfo> + * + * \brief The PendingContactInfo class represents the parameters of and the + * reply to an asynchronous contact info request. + * + * Instances of this class cannot be constructed directly; the only way to get + * one is via Contact. + * + * See \ref async_model + */ + +/** + * Construct a new PendingContactInfo object. + * + * \param contact Contact to use. + */ +PendingContactInfo::PendingContactInfo(const ContactPtr &contact) + : PendingOperation(contact), + mPriv(new Private) +{ + ConnectionPtr connection = contact->manager()->connection(); + Client::ConnectionInterfaceContactInfoInterface *contactInfoInterface = + connection->interface<Client::ConnectionInterfaceContactInfoInterface>(); + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + contactInfoInterface->RequestContactInfo( + contact->handle()[0]), this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onCallFinished(QDBusPendingCallWatcher*))); +} + +/** + * Class destructor. + */ +PendingContactInfo::~PendingContactInfo() +{ + delete mPriv; +} + +/** + * Return the contact through which the request was made. + * + * \return A pointer to the Contact object. + */ +ContactPtr PendingContactInfo::contact() const +{ + return ContactPtr(qobject_cast<Contact*>((Contact*) _object().data())); +} + +/** + * Return the information for contact(). + * + * \return The contact infor as a Contact::InfoFields object. + */ +Contact::InfoFields PendingContactInfo::infoFields() const +{ + if (!isFinished()) { + warning() << "PendingContactInfo::info called before finished"; + } else if (!isValid()) { + warning() << "PendingContactInfo::info called when not valid"; + } + + return mPriv->info; +} + +void PendingContactInfo::onCallFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<Tp::ContactInfoFieldList> reply = *watcher; + + if (!reply.isError()) { + mPriv->info = Contact::InfoFields(reply.value()); + debug() << "Got reply to ContactInfo.RequestContactInfo"; + setFinished(); + } else { + debug().nospace() << + "ContactInfo.RequestContactInfo failed: " << + reply.error().name() << ": " << reply.error().message(); + setFinishedWithError(reply.error()); + } + + watcher->deleteLater(); +} + +} // Tp diff --git a/TelepathyQt/pending-contact-info.h b/TelepathyQt/pending-contact-info.h new file mode 100644 index 00000000..15e0686e --- /dev/null +++ b/TelepathyQt/pending-contact-info.h @@ -0,0 +1,66 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_pending_contact_info_h_HEADER_GUARD_ +#define _TelepathyQt_pending_contact_info_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Contact> +#include <TelepathyQt/PendingOperation> +#include <TelepathyQt/Types> + +class QDBusPendingCallWatcher; + +namespace Tp +{ + +class TP_QT_EXPORT PendingContactInfo : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingContactInfo); + +public: + ~PendingContactInfo(); + + ContactPtr contact() const; + + Contact::InfoFields infoFields() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onCallFinished(QDBusPendingCallWatcher *watcher); + +private: + friend class Contact; + + TP_QT_NO_EXPORT PendingContactInfo(const ContactPtr &contact); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-contacts.cpp b/TelepathyQt/pending-contacts.cpp new file mode 100644 index 00000000..6bbf68c4 --- /dev/null +++ b/TelepathyQt/pending-contacts.cpp @@ -0,0 +1,468 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/PendingContacts> +#include "TelepathyQt/_gen/pending-contacts.moc.hpp" + +#include <TelepathyQt/Connection> +#include <TelepathyQt/ConnectionLowlevel> +#include <TelepathyQt/ContactManager> +#include <TelepathyQt/ContactFactory> +#include <TelepathyQt/PendingContactAttributes> +#include <TelepathyQt/PendingHandles> +#include <TelepathyQt/ReferencedHandles> + +#include "TelepathyQt/debug-internal.h" + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingContacts::Private +{ + enum RequestType + { + ForHandles, + ForIdentifiers, + Upgrade + }; + + Private(PendingContacts *parent, const ContactManagerPtr &manager, const UIntList &handles, + const Features &features, const Features &missingFeatures, + const QMap<uint, ContactPtr> &satisfyingContacts) + : parent(parent), + manager(manager), + features(features), + missingFeatures(missingFeatures), + satisfyingContacts(satisfyingContacts), + requestType(ForHandles), + handles(handles), + nested(0) + { + } + + Private(PendingContacts *parent, const ContactManagerPtr &manager, const QStringList &identifiers, + const Features &features) + : parent(parent), + manager(manager), + features(features), + requestType(ForIdentifiers), + identifiers(identifiers), + nested(0) + { + } + + Private(PendingContacts *parent, + const ContactManagerPtr &manager, const QList<ContactPtr> &contactsToUpgrade, + const Features &features) + : parent(parent), + manager(manager), + features(features), + requestType(Upgrade), + contactsToUpgrade(contactsToUpgrade), + nested(0) + { + } + + void setFinished(); + + // Public object + PendingContacts *parent; + + // Generic parameters + ContactManagerPtr manager; + Features features; + Features missingFeatures; + QMap<uint, ContactPtr> satisfyingContacts; + + // Request type specific parameters + RequestType requestType; + UIntList handles; + QStringList identifiers; + QList<ContactPtr> contactsToUpgrade; + PendingContacts *nested; + + // Results + QList<ContactPtr> contacts; + UIntList invalidHandles; + QStringList validIds; + QHash<QString, QPair<QString, QString> > invalidIds; + + ReferencedHandles handlesToInspect; +}; + +void PendingContacts::Private::setFinished() +{ + ConnectionLowlevelPtr connLowlevel = manager->connection()->lowlevel(); + UIntList handles = invalidHandles; + foreach (uint handle, handles) { + if (connLowlevel->hasContactId(handle)) { + satisfyingContacts.insert(handle, manager->ensureContact(handle, + connLowlevel->contactId(handle), missingFeatures)); + invalidHandles.removeOne(handle); + } + } + + parent->setFinished(); +} + +/** + * \class PendingContacts + * \ingroup clientconn + * \headerfile TelepathyQt/pending-contacts.h <TelepathyQt/PendingContacts> + * + * \brief The PendingContacts class is used by ContactManager when + * creating/updating Contact objects. + * + * See \ref async_model + */ + +PendingContacts::PendingContacts(const ContactManagerPtr &manager, + const UIntList &handles, + const Features &features, + const Features &missingFeatures, + const QStringList &interfaces, + const QMap<uint, ContactPtr> &satisfyingContacts, + const QSet<uint> &otherContacts, + const QString &errorName, + const QString &errorMessage) + : PendingOperation(manager->connection()), + mPriv(new Private(this, manager, handles, features, missingFeatures, satisfyingContacts)) +{ + if (!errorName.isEmpty()) { + setFinishedWithError(errorName, errorMessage); + return; + } + + if (!otherContacts.isEmpty()) { + ConnectionPtr conn = manager->connection(); + if (conn->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CONNECTION_INTERFACE_CONTACTS))) { + PendingContactAttributes *attributes = + conn->lowlevel()->contactAttributes(otherContacts.toList(), + interfaces, true); + + connect(attributes, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onAttributesFinished(Tp::PendingOperation*))); + } else { + // fallback to just create the contacts + PendingHandles *handles = conn->lowlevel()->referenceHandles(HandleTypeContact, + otherContacts.toList()); + connect(handles, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onReferenceHandlesFinished(Tp::PendingOperation*))); + } + } else { + allAttributesFetched(); + } +} + +PendingContacts::PendingContacts(const ContactManagerPtr &manager, + const QStringList &identifiers, const Features &features, + const QString &errorName, const QString &errorMessage) + : PendingOperation(manager->connection()), + mPriv(new Private(this, manager, identifiers, features)) +{ + if (!errorName.isEmpty()) { + setFinishedWithError(errorName, errorMessage); + return; + } + + ConnectionPtr conn = manager->connection(); + PendingHandles *handles = conn->lowlevel()->requestHandles(HandleTypeContact, identifiers); + + connect(handles, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onRequestHandlesFinished(Tp::PendingOperation*))); +} + +PendingContacts::PendingContacts(const ContactManagerPtr &manager, + const QList<ContactPtr> &contacts, const Features &features, + const QString &errorName, const QString &errorMessage) + : PendingOperation(manager->connection()), + mPriv(new Private(this, manager, contacts, features)) +{ + if (!errorName.isEmpty()) { + setFinishedWithError(errorName, errorMessage); + return; + } + + UIntList handles; + foreach (const ContactPtr &contact, contacts) { + handles.push_back(contact->handle()[0]); + } + + mPriv->nested = manager->contactsForHandles(handles, features); + connect(mPriv->nested, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onNestedFinished(Tp::PendingOperation*))); +} + +/** + * Class destructor. + */ +PendingContacts::~PendingContacts() +{ + delete mPriv; +} + +ContactManagerPtr PendingContacts::manager() const +{ + return mPriv->manager; +} + +Features PendingContacts::features() const +{ + return mPriv->features; +} + +bool PendingContacts::isForHandles() const +{ + return mPriv->requestType == Private::ForHandles; +} + +UIntList PendingContacts::handles() const +{ + if (!isForHandles()) { + warning() << "Tried to get handles from" << this << "which is not for handles!"; + } + + return mPriv->handles; +} + +bool PendingContacts::isForIdentifiers() const +{ + return mPriv->requestType == Private::ForIdentifiers; +} + +QStringList PendingContacts::identifiers() const +{ + if (!isForIdentifiers()) { + warning() << "Tried to get identifiers from" << this << "which is not for identifiers!"; + } + + return mPriv->identifiers; +} + +bool PendingContacts::isUpgrade() const +{ + return mPriv->requestType == Private::Upgrade; +} + +QList<ContactPtr> PendingContacts::contactsToUpgrade() const +{ + if (!isUpgrade()) { + warning() << "Tried to get contacts to upgrade from" << this << "which is not an upgrade!"; + } + + return mPriv->contactsToUpgrade; +} + +QList<ContactPtr> PendingContacts::contacts() const +{ + if (!isFinished()) { + warning() << "PendingContacts::contacts() called before finished"; + } else if (isError()) { + warning() << "PendingContacts::contacts() called when errored"; + } + + return mPriv->contacts; +} + +UIntList PendingContacts::invalidHandles() const +{ + if (!isFinished()) { + warning() << "PendingContacts::invalidHandles() called before finished"; + } else if (isError()) { + warning() << "PendingContacts::invalidHandles() called when errored"; + } else if (!isForHandles()) { + warning() << "PendingContacts::invalidHandles() called for" << this << "which is for IDs!"; + } + + return mPriv->invalidHandles; +} + +QStringList PendingContacts::validIdentifiers() const +{ + if (!isFinished()) { + warning() << "PendingContacts::validIdentifiers called before finished"; + } else if (!isValid()) { + warning() << "PendingContacts::validIdentifiers called when not valid"; + } + + return mPriv->validIds; +} + +QHash<QString, QPair<QString, QString> > PendingContacts::invalidIdentifiers() const +{ + if (!isFinished()) { + warning() << "PendingContacts::invalidIdentifiers called before finished"; + } + + return mPriv->invalidIds; +} + +void PendingContacts::onAttributesFinished(PendingOperation *operation) +{ + PendingContactAttributes *pendingAttributes = + qobject_cast<PendingContactAttributes *>(operation); + + if (pendingAttributes->isError()) { + debug() << "PendingAttrs error" << pendingAttributes->errorName() + << "message" << pendingAttributes->errorMessage(); + setFinishedWithError(pendingAttributes->errorName(), pendingAttributes->errorMessage()); + return; + } + + ReferencedHandles validHandles = pendingAttributes->validHandles(); + ContactAttributesMap attributes = pendingAttributes->attributes(); + + foreach (uint handle, mPriv->handles) { + if (!mPriv->satisfyingContacts.contains(handle)) { + int indexInValid = validHandles.indexOf(handle); + if (indexInValid >= 0) { + ReferencedHandles referencedHandle = validHandles.mid(indexInValid, 1); + QVariantMap handleAttributes = attributes[handle]; + mPriv->satisfyingContacts.insert(handle, manager()->ensureContact(referencedHandle, + mPriv->missingFeatures, handleAttributes)); + } else { + mPriv->invalidHandles.push_back(handle); + } + } + } + + allAttributesFetched(); +} + +void PendingContacts::onRequestHandlesFinished(PendingOperation *operation) +{ + PendingHandles *pendingHandles = qobject_cast<PendingHandles *>(operation); + + mPriv->validIds = pendingHandles->validNames(); + mPriv->invalidIds = pendingHandles->invalidNames(); + + if (pendingHandles->isError()) { + debug() << "RequestHandles error" << operation->errorName() + << "message" << operation->errorMessage(); + setFinishedWithError(operation->errorName(), operation->errorMessage()); + return; + } + + mPriv->nested = manager()->contactsForHandles(pendingHandles->handles(), features()); + connect(mPriv->nested, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onNestedFinished(Tp::PendingOperation*))); +} + +void PendingContacts::onReferenceHandlesFinished(PendingOperation *operation) +{ + PendingHandles *pendingHandles = qobject_cast<PendingHandles *>(operation); + + if (pendingHandles->isError()) { + debug() << "ReferenceHandles error" << operation->errorName() + << "message" << operation->errorMessage(); + setFinishedWithError(operation->errorName(), operation->errorMessage()); + return; + } + + ReferencedHandles validHandles = pendingHandles->handles(); + UIntList invalidHandles = pendingHandles->invalidHandles(); + ConnectionPtr conn = mPriv->manager->connection(); + mPriv->handlesToInspect = ReferencedHandles(conn, HandleTypeContact, UIntList()); + foreach (uint handle, mPriv->handles) { + if (!mPriv->satisfyingContacts.contains(handle)) { + int indexInValid = validHandles.indexOf(handle); + if (indexInValid >= 0) { + ReferencedHandles referencedHandle = validHandles.mid(indexInValid, 1); + mPriv->handlesToInspect.append(referencedHandle); + } else { + mPriv->invalidHandles.push_back(handle); + } + } + } + + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher( + conn->baseInterface()->InspectHandles(HandleTypeContact, + mPriv->handlesToInspect.toList()), + this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onInspectHandlesFinished(QDBusPendingCallWatcher*))); +} + +void PendingContacts::onNestedFinished(PendingOperation *operation) +{ + Q_ASSERT(operation == mPriv->nested); + + if (operation->isError()) { + debug() << " error" << operation->errorName() + << "message" << operation->errorMessage(); + setFinishedWithError(operation->errorName(), operation->errorMessage()); + return; + } + + mPriv->contacts = mPriv->nested->contacts(); + mPriv->nested = 0; + mPriv->setFinished(); +} + +void PendingContacts::onInspectHandlesFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QStringList> reply = *watcher; + + if (reply.isError()) { + debug().nospace() << "InspectHandles: error " << reply.error().name() << ": " + << reply.error().message(); + setFinishedWithError(reply.error()); + return; + } + + QStringList names = reply.value(); + int i = 0; + ConnectionPtr conn = mPriv->manager->connection(); + foreach (uint handle, mPriv->handlesToInspect) { + QVariantMap handleAttributes; + handleAttributes.insert(QLatin1String(TELEPATHY_INTERFACE_CONNECTION "/contact-id"), + names[i++]); + ReferencedHandles referencedHandle(conn, HandleTypeContact, + UIntList() << handle); + mPriv->satisfyingContacts.insert(handle, manager()->ensureContact(referencedHandle, + mPriv->missingFeatures, handleAttributes)); + } + + allAttributesFetched(); + + watcher->deleteLater(); +} + +void PendingContacts::allAttributesFetched() +{ + foreach (uint handle, mPriv->handles) { + if (mPriv->satisfyingContacts.contains(handle)) { + mPriv->contacts.push_back(mPriv->satisfyingContacts[handle]); + } + } + + mPriv->setFinished(); +} + +} // Tp diff --git a/TelepathyQt/pending-contacts.h b/TelepathyQt/pending-contacts.h new file mode 100644 index 00000000..0e03a8f2 --- /dev/null +++ b/TelepathyQt/pending-contacts.h @@ -0,0 +1,108 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_pending_contacts_h_HEADER_GUARD_ +#define _TelepathyQt_pending_contacts_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/PendingOperation> + +#include <QHash> +#include <QList> +#include <QMap> +#include <QSet> +#include <QStringList> + +#include <TelepathyQt/Types> +#include <TelepathyQt/Contact> + +namespace Tp +{ + +class ContactManager; + +class TP_QT_EXPORT PendingContacts : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingContacts); + +public: + ~PendingContacts(); + + ContactManagerPtr manager() const; + Features features() const; + + bool isForHandles() const; + UIntList handles() const; + + bool isForIdentifiers() const; + QStringList identifiers() const; + + bool isUpgrade() const; + QList<ContactPtr> contactsToUpgrade() const; + + QList<ContactPtr> contacts() const; + UIntList invalidHandles() const; + QStringList validIdentifiers() const; + QHash<QString, QPair<QString, QString> > invalidIdentifiers() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onAttributesFinished(Tp::PendingOperation *); + TP_QT_NO_EXPORT void onRequestHandlesFinished(Tp::PendingOperation *); + TP_QT_NO_EXPORT void onReferenceHandlesFinished(Tp::PendingOperation *); + TP_QT_NO_EXPORT void onNestedFinished(Tp::PendingOperation *); + TP_QT_NO_EXPORT void onInspectHandlesFinished(QDBusPendingCallWatcher *); + +private: + friend class ContactManager; + + // If errorName is non-empty, these will fail instantly + TP_QT_NO_EXPORT PendingContacts(const ContactManagerPtr &manager, const UIntList &handles, + const Features &features, + const Features &missingFeatures, + const QStringList &interfaces, + const QMap<uint, ContactPtr> &satisfyingContacts, + const QSet<uint> &otherContacts, + const QString &errorName = QString(), + const QString &errorMessage = QString()); + TP_QT_NO_EXPORT PendingContacts(const ContactManagerPtr &manager, const QStringList &identifiers, + const Features &features, + const QString &errorName = QString(), + const QString &errorMessage = QString()); + TP_QT_NO_EXPORT PendingContacts(const ContactManagerPtr &manager, const QList<ContactPtr> &contacts, + const Features &features, + const QString &errorName = QString(), + const QString &errorMessage = QString()); + + TP_QT_NO_EXPORT void allAttributesFetched(); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-handles.cpp b/TelepathyQt/pending-handles.cpp new file mode 100644 index 00000000..4a6625d2 --- /dev/null +++ b/TelepathyQt/pending-handles.cpp @@ -0,0 +1,490 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/PendingHandles> + +#include "TelepathyQt/_gen/pending-handles.moc.hpp" + +#include <TelepathyQt/Connection> +#include <TelepathyQt/ReferencedHandles> + +#include "TelepathyQt/debug-internal.h" + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingHandles::Private +{ + HandleType handleType; + bool isRequest; + QStringList namesRequested; + UIntList handlesToReference; + ReferencedHandles handles; + ReferencedHandles alreadyHeld; + UIntList invalidHandles; + QStringList validNames; + QHash<QString, QPair<QString, QString> > invalidNames; + + // one to one requests (ids) + QHash<QDBusPendingCallWatcher *, uint> handlesForWatchers; + QHash<QDBusPendingCallWatcher *, QString> idsForWatchers; + QHash<QString, uint> handlesForIds; + int requestsFinished; +}; + +/** + * \class PendingHandles + * \ingroup clientconn + * \headerfile TelepathyQt/pending-handles.h <TelepathyQt/PendingHandles> + * + * \brief The PendingHandles class represents the parameters of and the reply to + * an asynchronous handle request/hold. + * + * Instances of this class cannot be constructed directly; the only way to get + * one is to use Connection::requestHandles() or Connection::referenceHandles(). + * + * See \ref async_model + */ + +PendingHandles::PendingHandles(const ConnectionPtr &connection, HandleType handleType, + const QStringList &names) + : PendingOperation(connection), + mPriv(new Private) +{ + debug() << "PendingHandles(request)"; + + mPriv->handleType = handleType; + mPriv->isRequest = true; + mPriv->namesRequested = names; + mPriv->requestsFinished = 0; + + // try to request all handles at once + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher( + connection->baseInterface()->RequestHandles(mPriv->handleType, names), + this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onRequestHandlesFinished(QDBusPendingCallWatcher*))); +} + +PendingHandles::PendingHandles(const ConnectionPtr &connection, HandleType handleType, + const UIntList &handles, const UIntList &alreadyHeld, + const UIntList ¬YetHeld) + : PendingOperation(connection), + mPriv(new Private) +{ + debug() << "PendingHandles(reference)"; + + mPriv->handleType = handleType; + mPriv->isRequest = false; + mPriv->handlesToReference = handles; + mPriv->alreadyHeld = ReferencedHandles(connection, mPriv->handleType, alreadyHeld); + mPriv->requestsFinished = 0; + + if (notYetHeld.isEmpty()) { + debug() << " All handles already held, finishing up instantly"; + mPriv->handles = mPriv->alreadyHeld; + setFinished(); + } else { + debug() << " Calling HoldHandles"; + + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher( + connection->baseInterface()->HoldHandles(mPriv->handleType, notYetHeld), + this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onHoldHandlesFinished(QDBusPendingCallWatcher*))); + } +} + +PendingHandles::PendingHandles(const QString &errorName, const QString &errorMessage) + : PendingOperation(ConnectionPtr()), mPriv(new Private) +{ + setFinishedWithError(errorName, errorMessage); +} + +/** + * Class destructor. + */ +PendingHandles::~PendingHandles() +{ + delete mPriv; +} + +/** + * Return the connection through which the operation was made. + * + * \return A pointer to the Connection object. + */ +ConnectionPtr PendingHandles::connection() const +{ + return ConnectionPtr(qobject_cast<Connection*>((Connection*) _object().data())); +} + +/** + * Return the handle type specified in the operation. + * + * \return The target handle type as #HandleType. + */ +HandleType PendingHandles::handleType() const +{ + return mPriv->handleType; +} + +/** + * Return whether the operation was a handle request (as opposed to a + * reference of existing handles). + * + * \return \c true if the operation was a request (== !isReference()), \c false otherwise. + * \sa isReference() + */ +bool PendingHandles::isRequest() const +{ + return mPriv->isRequest; +} + +/** + * Return whether the operation was a handle reference (as opposed to a + * request for new handles). + * + * \return \c true if the operation was a reference (== !isRequest()), \c false otherwise. + * \sa isRequest() + */ +bool PendingHandles::isReference() const +{ + return !mPriv->isRequest; +} + +/** + * If the operation was a request (as returned by isRequest()), returns the + * names of the entities for which handles were requested for. Otherwise, + * returns an empty list. + * + * \return Reference to a list of the names of the entities. + */ +const QStringList &PendingHandles::namesRequested() const +{ + return mPriv->namesRequested; +} + +QStringList PendingHandles::validNames() const +{ + if (!isFinished()) { + warning() << "PendingHandles::validNames called before finished"; + return QStringList(); + } else if (!isValid()) { + warning() << "PendingHandles::validNames called when not valid"; + return QStringList(); + } + + return mPriv->validNames; +} + +QHash<QString, QPair<QString, QString> > PendingHandles::invalidNames() const +{ + if (!isFinished()) { + warning() << "PendingHandles::invalidNames called before finished"; + return QHash<QString, QPair<QString, QString> >(); + } + + return mPriv->invalidNames; +} + +/** + * If the operation was a reference (as returned by isReference()), returns + * the handles which were to be referenced. Otherwise, returns an empty + * list. + * + * \return Reference to a list of the handles specified to be referenced. + */ +const UIntList &PendingHandles::handlesToReference() const +{ + return mPriv->handlesToReference; +} + +/** + * Return the now-referenced handles resulting from the operation. If the + * operation has not (yet) finished successfully (isFinished() returns + * <code>false</code>), the return value is undefined. + * + * For requests of new handles, <code>handles()[i]</code> will be the handle + * corresponding to the entity name <code>namesToRequest()[i]</code>. For + * references of existing handles, <code>handles()[i] == + * handlesToReference()[i]</code> will be true for any <code>i</code>. + * + * \return ReferencedHandles object containing the handles. + */ +ReferencedHandles PendingHandles::handles() const +{ + if (!isFinished()) { + warning() << "PendingHandles::handles() called before finished"; + return ReferencedHandles(); + } else if (!isValid()) { + warning() << "PendingHandles::handles() called when not valid"; + return ReferencedHandles(); + } + + return mPriv->handles; +} + +UIntList PendingHandles::invalidHandles() const +{ + if (!isFinished()) { + warning() << "PendingHandles::invalidHandles called before finished"; + } + + return mPriv->invalidHandles; +} + +void PendingHandles::onRequestHandlesFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<UIntList> reply = *watcher; + + if (reply.isError()) { + QDBusError error = reply.error(); + if (error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_HANDLE) && + error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT) && + error.name() != QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE)) { + // do not fallback + foreach (const QString &name, mPriv->namesRequested) { + mPriv->invalidNames.insert(name, + QPair<QString, QString>(error.name(), + error.message())); + } + setFinishedWithError(error); + connection()->handleRequestLanded(mPriv->handleType); + watcher->deleteLater(); + return; + } + + if (mPriv->namesRequested.size() == 1) { + debug().nospace() << " Failure: error " << + reply.error().name() << ": " << + reply.error().message(); + + mPriv->invalidNames.insert(mPriv->namesRequested.first(), + QPair<QString, QString>(error.name(), + error.message())); + setFinished(); + connection()->handleRequestLanded(mPriv->handleType); + watcher->deleteLater(); + return; + } + + // try to request one handles at a time + foreach (const QString &name, mPriv->namesRequested) { + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher( + connection()->baseInterface()->RequestHandles( + mPriv->handleType, + QStringList() << name), + this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onRequestHandlesFallbackFinished(QDBusPendingCallWatcher*))); + mPriv->idsForWatchers.insert(watcher, name); + } + } else { + debug() << "Received reply to RequestHandles"; + mPriv->handles = ReferencedHandles(connection(), + mPriv->handleType, reply.value()); + mPriv->validNames.append(mPriv->namesRequested); + setFinished(); + connection()->handleRequestLanded(mPriv->handleType); + } +} + +void PendingHandles::onHoldHandlesFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<void> reply = *watcher; + + debug() << "Received reply to HoldHandles"; + + if (reply.isError()) { + debug().nospace() << " Failure: error " << + reply.error().name() << ": " << + reply.error().message(); + + QDBusError error = reply.error(); + if (error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_HANDLE) && + error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT) && + error.name() != QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE)) { + // do not fallback + mPriv->invalidHandles = mPriv->handlesToReference; + setFinishedWithError(error); + watcher->deleteLater(); + return; + } + + if (mPriv->handlesToReference.size() == 1) { + debug().nospace() << " Failure: error " << + reply.error().name() << ": " << + reply.error().message(); + + mPriv->invalidHandles = mPriv->handlesToReference; + setFinished(); + watcher->deleteLater(); + return; + } + + // try to request one handles at a time + foreach (uint handle, mPriv->handlesToReference) { + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher( + connection()->baseInterface()->HoldHandles( + mPriv->handleType, + UIntList() << handle), + this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onHoldHandlesFallbackFinished(QDBusPendingCallWatcher*))); + mPriv->handlesForWatchers.insert(watcher, handle); + } + } else { + mPriv->handles = ReferencedHandles(connection(), + mPriv->handleType, mPriv->handlesToReference); + setFinished(); + } + + watcher->deleteLater(); +} + +void PendingHandles::onRequestHandlesFallbackFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<UIntList> reply = *watcher; + + Q_ASSERT(mPriv->idsForWatchers.contains(watcher)); + QString id = mPriv->idsForWatchers.value(watcher); + + debug() << "Received reply to RequestHandles(" << id << ")"; + + if (reply.isError()) { + debug().nospace() << " Failure: error " << reply.error().name() << ": " + << reply.error().message(); + + // if the error is disconnected for example, fail immediately + QDBusError error = reply.error(); + if (error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_HANDLE) && + error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT) && + error.name() != QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE)) { + foreach (const QString &name, mPriv->namesRequested) { + mPriv->invalidNames.insert(name, + QPair<QString, QString>(error.name(), + error.message())); + } + setFinishedWithError(error); + connection()->handleRequestLanded(mPriv->handleType); + watcher->deleteLater(); + return; + } + + mPriv->invalidNames.insert(id, + QPair<QString, QString>(reply.error().name(), + reply.error().message())); + } else { + Q_ASSERT(reply.value().size() == 1); + uint handle = reply.value().first(); + mPriv->handlesForIds.insert(id, handle); + } + + if (++mPriv->requestsFinished == mPriv->namesRequested.size()) { + if (mPriv->handlesForIds.size() == 0) { + // all requests failed + setFinished(); + } else { + // all requests either failed or finished successfully + + // we need to return the handles in the same order as requested + UIntList handles; + foreach (const QString &name, mPriv->namesRequested) { + if (!mPriv->invalidNames.contains(name)) { + Q_ASSERT(mPriv->handlesForIds.contains(name)); + handles.append(mPriv->handlesForIds.value(name)); + mPriv->validNames.append(name); + } + } + mPriv->handles = ReferencedHandles(connection(), + mPriv->handleType, handles); + + setFinished(); + } + + debug() << " namesRequested:" << mPriv->namesRequested; + debug() << " invalidNames :" << mPriv->invalidNames; + debug() << " validNames :" << mPriv->validNames; + + connection()->handleRequestLanded(mPriv->handleType); + } + + watcher->deleteLater(); +} + +void PendingHandles::onHoldHandlesFallbackFinished(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<void> reply = *watcher; + + Q_ASSERT(mPriv->handlesForWatchers.contains(watcher)); + uint handle = mPriv->handlesForWatchers.value(watcher); + + debug() << "Received reply to HoldHandles(" << handle << ")"; + + if (reply.isError()) { + debug().nospace() << " Failure: error " << reply.error().name() << ": " + << reply.error().message(); + + // if the error is disconnected for example, fail immediately + QDBusError error = reply.error(); + if (error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_HANDLE) && + error.name() != QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT) && + error.name() != QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE)) { + mPriv->invalidHandles = mPriv->handlesToReference; + setFinishedWithError(error); + watcher->deleteLater(); + return; + } + + mPriv->invalidHandles.append(handle); + } + + if (++mPriv->requestsFinished == mPriv->namesRequested.size()) { + // we need to return the handles in the same order as requested + UIntList handles; + foreach (uint handle, mPriv->handlesToReference) { + if (!mPriv->invalidHandles.contains(handle)) { + handles.append(handle); + } + } + + if (handles.size() != 0) { + mPriv->handles = ReferencedHandles(connection(), + mPriv->handleType, handles); + } + + setFinished(); + } + + watcher->deleteLater(); +} + +} // Tp diff --git a/TelepathyQt/pending-handles.h b/TelepathyQt/pending-handles.h new file mode 100644 index 00000000..4a5dc0c2 --- /dev/null +++ b/TelepathyQt/pending-handles.h @@ -0,0 +1,96 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_pending_handles_h_HEADER_GUARD_ +#define _TelepathyQt_pending_handles_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Constants> +#include <TelepathyQt/PendingOperation> +#include <TelepathyQt/Types> + +#include <QHash> +#include <QString> +#include <QStringList> + +#include <TelepathyQt/Types> + +namespace Tp +{ + +class PendingHandles; +class ReferencedHandles; + +class TP_QT_EXPORT PendingHandles : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingHandles) + +public: + ~PendingHandles(); + + ConnectionPtr connection() const; + + HandleType handleType() const; + + bool isRequest() const; + + bool isReference() const; + + const QStringList &namesRequested() const; + + QStringList validNames() const; + + QHash<QString, QPair<QString, QString> > invalidNames() const; + + const UIntList &handlesToReference() const; + + ReferencedHandles handles() const; + + UIntList invalidHandles() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onRequestHandlesFinished(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onHoldHandlesFinished(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onRequestHandlesFallbackFinished(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onHoldHandlesFallbackFinished(QDBusPendingCallWatcher *watcher); + +private: + friend class ConnectionLowlevel; + + TP_QT_NO_EXPORT PendingHandles(const ConnectionPtr &connection, HandleType handleType, + const QStringList &names); + TP_QT_NO_EXPORT PendingHandles(const ConnectionPtr &connection, HandleType handleType, + const UIntList &handles, const UIntList &alreadyHeld, const UIntList ¬YetHeld); + TP_QT_NO_EXPORT PendingHandles(const QString &errorName, const QString &errorMessage); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-operation.cpp b/TelepathyQt/pending-operation.cpp new file mode 100644 index 00000000..139821b3 --- /dev/null +++ b/TelepathyQt/pending-operation.cpp @@ -0,0 +1,424 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2009 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 <TelepathyQt/PendingOperation> + +#define IN_TP_QT_HEADER +#include "simple-pending-operations.h" +#undef IN_TP_QT_HEADER + +#include "TelepathyQt/_gen/pending-operation.moc.hpp" +#include "TelepathyQt/_gen/simple-pending-operations.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <QDBusPendingCall> +#include <QDBusPendingCallWatcher> +#include <QTimer> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingOperation::Private +{ + Private(const SharedPtr<RefCounted> &object) + : object(object), + finished(false) + { + } + + SharedPtr<RefCounted> object; + QString errorName; + QString errorMessage; + bool finished; +}; + +/** + * \class PendingOperation + * \headerfile TelepathyQt/pending-operation.h <TelepathyQt/PendingOperation> + * + * \brief The PendingOperation class is a base class for pending asynchronous + * operations. + * + * This class represents an incomplete asynchronous operation, such as a + * D-Bus method call. When the operation has finished, it emits + * finished(). The slot or slots connected to the finished() signal may obtain + * additional information from the pending operation. + * + * In simple cases, like a D-Bus method with no 'out' arguments or for which + * all 'out' arguments are to be ignored (so the possible results are + * success with no extra information, or failure with an error code), the + * trivial subclass PendingVoid can be used. + * + * For pending operations that produce a result, another subclass of + * PendingOperation can be used, with additional methods that provide that + * result to the library user. + * + * After finished() is emitted, the PendingOperation is automatically + * deleted using deleteLater(), so library users must not explicitly + * delete this object. + * + * The design is loosely based on KDE's KJob. + * + * See \ref async_model + */ + +/** + * Construct a new PendingOperation object. + * + * \param object The object on which this pending operation takes place. + */ +PendingOperation::PendingOperation(const SharedPtr<RefCounted> &object) + : QObject(), + mPriv(new Private(object)) +{ +} + +/** + * Class destructor. + */ +PendingOperation::~PendingOperation() +{ + if (!mPriv->finished) { + warning() << this << + "still pending when it was deleted - finished will " + "never be emitted"; + } + + delete mPriv; +} + +/** + * Return the object on which this pending operation takes place. + * + * \return A pointer to a RefCounted object. + * \deprecated Will be made protected in the next API break, because using it outside the + * PendingOperation requires unsafe type conversions, and it's not always clear just which + * object the operation "takes place on", and we don't want to commit to keeping the objects + * fixed. + */ +SharedPtr<RefCounted> PendingOperation::object() const +{ + return _object(); +} + +// Temporary, to allow internal access to the object not warn of deprecation +SharedPtr<RefCounted> PendingOperation::_object() const +{ + return mPriv->object; +} + +void PendingOperation::emitFinished() +{ + Q_ASSERT(mPriv->finished); + emit finished(this); + deleteLater(); +} + +/** + * Record that this pending operation has finished successfully, and + * emit the finished() signal next time the event loop runs. + */ +void PendingOperation::setFinished() +{ + if (mPriv->finished) { + if (!mPriv->errorName.isEmpty()) { + warning() << this << "trying to finish with success, but already" + " failed with" << mPriv->errorName << ":" << mPriv->errorMessage; + } else { + warning() << this << "trying to finish with success, but already" + " succeeded"; + } + return; + } + + mPriv->finished = true; + Q_ASSERT(isValid()); + QTimer::singleShot(0, this, SLOT(emitFinished())); +} + +/** + * Record that this pending operation has finished with an error, and + * emit the finished() signal next time the event loop runs. + * + * \param name The D-Bus error name, which must be non-empty. + * \param message The debugging message. + */ +void PendingOperation::setFinishedWithError(const QString &name, + const QString &message) +{ + if (mPriv->finished) { + if (mPriv->errorName.isEmpty()) { + warning() << this << "trying to fail with" << name << + "but already failed with" << errorName() << ":" << + errorMessage(); + } else { + warning() << this << "trying to fail with" << name << + "but already succeeded"; + } + return; + } + + if (name.isEmpty()) { + warning() << this << "should be given a non-empty error name"; + mPriv->errorName = QLatin1String("org.freedesktop.Telepathy.Qt4.ErrorHandlingError"); + } else { + mPriv->errorName = name; + } + + mPriv->errorMessage = message; + mPriv->finished = true; + Q_ASSERT(isError()); + QTimer::singleShot(0, this, SLOT(emitFinished())); +} + +/** + * Record that this pending operation has finished with an error, and + * emit the finished() signal next time the event loop runs. + * + * \param error The error. + * \sa finished() + */ +void PendingOperation::setFinishedWithError(const QDBusError &error) +{ + setFinishedWithError(error.name(), error.message()); +} + +/** + * Return whether or not the request completed successfully. If the + * request has not yet finished processing (isFinished() returns + * \c false), this cannot yet be known, and \c false + * will be returned. + * + * Equivalent to <code>(isFinished() && !isError())</code>. + * + * \return \c true if the request has finished processing and + * has completed successfully, \c false otherwise. + */ +bool PendingOperation::isValid() const +{ + return (mPriv->finished && mPriv->errorName.isEmpty()); +} + +/** + * Return whether or not the request has finished processing. + * + * The signal finished() is emitted when this changes from \c false + * to true. + * + * Equivalent to <code>(isValid() || isError())</code>. + * + * \return \c true if the request has finished, \c false otherwise. + * \sa finished() + */ +bool PendingOperation::isFinished() const +{ + return mPriv->finished; +} + +/** + * Return whether or not the request resulted in an error. + * + * If the request has not yet finished processing (isFinished() returns + * \c false), this cannot yet be known, and \c false + * will be returned. + * + * Equivalent to <code>(isFinished() && !isValid())</code>. + * + * \return \c true if the request has finished processing and + * has resulted in an error, \c false otherwise. + */ +bool PendingOperation::isError() const +{ + return (mPriv->finished && !mPriv->errorName.isEmpty()); +} + +/** + * If isError() returns \c true, returns the D-Bus error with which + * the operation failed. If the operation succeeded or has not yet + * finished, returns an empty string. + * + * \return A D-Bus error name, or an empty string. + */ +QString PendingOperation::errorName() const +{ + return mPriv->errorName; +} + +/** + * If isError() would return \c true, returns a debugging message associated + * with the error, which may be an empty string. Otherwise, return an + * empty string. + * + * \return A debugging message, or an empty string. + */ +QString PendingOperation::errorMessage() const +{ + return mPriv->errorMessage; +} + +/** + * \fn void PendingOperation::finished(Tp::PendingOperation* operation) + * + * Emitted when the pending operation finishes, i.e. when isFinished() + * changes from \c false to \c true. + * + * \param operation This operation object, from which further information + * may be obtained. + */ + +/** + * \class PendingSuccess + * \ingroup utils + * \headerfile TelepathyQt/simple-pending-operations.h <TelepathyQt/PendingSuccess> + * + * \brief The PendingSuccess class represents PendingOperation that is always + * successful. + */ + +/** + * \class PendingFailure + * \ingroup utils + * \headerfile TelepathyQt/simple-pending-operations.h <TelepathyQt/PendingFailure> + * + * \brief The PendingFailure class represents a PendingOperation that always + * fails with the error passed to the constructor. + */ + +/** + * \class PendingVoid + * \ingroup utils + * \headerfile TelepathyQt/simple-pending-operations.h <TelepathyQt/PendingVoid> + * + * \brief The PendingVoid class is a generic subclass of PendingOperation + * representing a pending D-Bus method call that does not return anything + * (or returns a result that is not interesting). + */ + +/** + * Construct a new PendingVoid object. + * + * \param object The object on which this pending operation takes place. + * \param call A pending call as returned by the auto-generated low level + * Telepathy API; if the method returns anything, the return + * value(s) will be ignored. + */ +PendingVoid::PendingVoid(QDBusPendingCall call, const SharedPtr<RefCounted> &object) + : PendingOperation(object) +{ + connect(new QDBusPendingCallWatcher(call), + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(watcherFinished(QDBusPendingCallWatcher*))); +} + +void PendingVoid::watcherFinished(QDBusPendingCallWatcher *watcher) +{ + if (watcher->isError()) { + setFinishedWithError(watcher->error()); + } else { + setFinished(); + } + + watcher->deleteLater(); +} + +struct TP_QT_NO_EXPORT PendingComposite::Private +{ + Private(bool failOnFirstError, uint nOperations) + : failOnFirstError(failOnFirstError), + error(false), + nOperations(nOperations), + nOperationsFinished(0) + { + } + + bool failOnFirstError; + bool error; + QString errorName; + QString errorMessage; + uint nOperations; + uint nOperationsFinished; +}; + +/** + * \class PendingComposite + * \ingroup utils + * \headerfile TelepathyQt/simple-pending-operations.h <TelepathyQt/PendingComposite> + * + * \brief The PendingComposite class is a PendingOperation that can be used + * to track multiple pending operations at once. + */ + +PendingComposite::PendingComposite(const QList<PendingOperation*> &operations, + const SharedPtr<RefCounted> &object) + : PendingOperation(object), + mPriv(new Private(true, operations.size())) +{ + foreach (PendingOperation *operation, operations) { + connect(operation, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onOperationFinished(Tp::PendingOperation*))); + } +} + +PendingComposite::PendingComposite(const QList<PendingOperation*> &operations, + bool failOnFirstError, const SharedPtr<RefCounted> &object) + : PendingOperation(object), + mPriv(new Private(failOnFirstError, operations.size())) +{ + foreach (PendingOperation *operation, operations) { + connect(operation, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onOperationFinished(Tp::PendingOperation*))); + } +} + +PendingComposite::~PendingComposite() +{ + delete mPriv; +} + +void PendingComposite::onOperationFinished(Tp::PendingOperation *op) +{ + if (op->isError()) { + if (mPriv->failOnFirstError) { + setFinishedWithError(op->errorName(), op->errorMessage()); + return; + } else if (!mPriv->error) { + /* only save the first error that will be used on setFinishedWithError when all + * pending operations finish */ + mPriv->error = true; + mPriv->errorName = op->errorName(); + mPriv->errorMessage = op->errorMessage(); + } + } + + if (++mPriv->nOperationsFinished == mPriv->nOperations) { + if (!mPriv->error) { + setFinished(); + } else { + setFinishedWithError(mPriv->errorName, mPriv->errorMessage); + } + } +} + +} // Tp diff --git a/TelepathyQt/pending-operation.h b/TelepathyQt/pending-operation.h new file mode 100644 index 00000000..55ab454e --- /dev/null +++ b/TelepathyQt/pending-operation.h @@ -0,0 +1,89 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2009 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 + */ + +#ifndef _TelepathyQt_pending_operation_h_HEADER_GUARD_ +#define _TelepathyQt_pending_operation_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> +#include <TelepathyQt/RefCounted> +#include <TelepathyQt/SharedPtr> + +#include <QObject> + +class QDBusError; +class QDBusPendingCall; +class QDBusPendingCallWatcher; + +namespace Tp +{ + +class ReadinessHelper; + +class TP_QT_EXPORT PendingOperation : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(PendingOperation) + +public: + virtual ~PendingOperation(); + + TP_QT_DEPRECATED SharedPtr<RefCounted> object() const; + + bool isFinished() const; + + bool isValid() const; + + bool isError() const; + QString errorName() const; + QString errorMessage() const; + +Q_SIGNALS: + void finished(Tp::PendingOperation *operation); + +protected: + PendingOperation(const SharedPtr<RefCounted> &object); + TP_QT_NO_EXPORT SharedPtr<RefCounted> _object() const; // TODO: turn this into _object() + +protected Q_SLOTS: + void setFinished(); + void setFinishedWithError(const QString &name, const QString &message); + void setFinishedWithError(const QDBusError &error); + +private Q_SLOTS: + TP_QT_NO_EXPORT void emitFinished(); + +private: + friend class ContactManager; + friend class ReadinessHelper; + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-ready.cpp b/TelepathyQt/pending-ready.cpp new file mode 100644 index 00000000..e8620a43 --- /dev/null +++ b/TelepathyQt/pending-ready.cpp @@ -0,0 +1,148 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/PendingReady> + +#include "TelepathyQt/_gen/pending-ready.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/DBusProxy> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingReady::Private +{ + Private(const DBusProxyPtr &proxy, + const Features &requestedFeatures) + : proxy(proxy), + requestedFeatures(requestedFeatures) + { + } + + DBusProxyPtr proxy; + Features requestedFeatures; +}; + +/** + * \class PendingReady + * \ingroup utils + * \headerfile TelepathyQt/pending-ready.h <TelepathyQt/PendingReady> + * + * \brief The PendingReady class represents the features requested and the reply + * to a request for an object to become ready. + * + * Instances of this class cannot be constructed directly; the only way to get + * one is via ReadyObject::becomeReady() or a DBusProxyFactory subclass. + * + * See \ref async_model + */ + +/** + * Construct a new PendingReady object. + * + * \todo Actually make it do the prepare ops. Currently they aren't taken into account in any way. + * + * \param object The object that will become ready. + * \param requestedFeatures Features to be made ready on the object. + */ +PendingReady::PendingReady(const SharedPtr<RefCounted> &object, + const Features &requestedFeatures) + : PendingOperation(object), + mPriv(new Private(DBusProxyPtr(dynamic_cast<DBusProxy*>((DBusProxy*) object.data())), + requestedFeatures)) +{ + // This is a PendingReady created by ReadinessHelper, and will be set ready by it - so should + // not do anything ourselves here. +} + +/** + * Construct a new PendingReady object. + * + * \todo Actually make it do the prepare ops. Currently they aren't taken into account in any way. + * + * \param factory The factory the request was made with. + * \param proxy The proxy that will become ready. + * \param requestedFeatures Features to be made ready on the object. + */ +PendingReady::PendingReady(const SharedPtr<DBusProxyFactory> &factory, + const DBusProxyPtr &proxy, + const Features &requestedFeatures) + : PendingOperation(factory), + mPriv(new Private(proxy, requestedFeatures)) +{ + if (requestedFeatures.isEmpty()) { + setFinished(); + return; + } + + connect(proxy->becomeReady(requestedFeatures), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onNestedFinished(Tp::PendingOperation*))); +} + +/** + * Class destructor. + */ +PendingReady::~PendingReady() +{ + delete mPriv; +} + +/** + * Return the proxy that should become ready. + * + * \return A pointer to the DBusProxy object if the operation was + * created by a proxy object or a DBusProxyFactory, + * otherwise a null DBusProxyPtr. + */ +DBusProxyPtr PendingReady::proxy() const +{ + return mPriv->proxy; +} + +/** + * Return the features that were requested to become ready on the + * object. + * + * \return The requested features as a set of Feature objects. + */ +Features PendingReady::requestedFeatures() const +{ + return mPriv->requestedFeatures; +} + +void PendingReady::onNestedFinished(Tp::PendingOperation *nested) +{ + Q_ASSERT(nested->isFinished()); + + if (nested->isValid()) { + setFinished(); + } else { + warning() << "Nested PendingReady for" << _object() << "failed with" + << nested->errorName() << ":" << nested->errorMessage(); + setFinishedWithError(nested->errorName(), nested->errorMessage()); + } +} + +} // Tp diff --git a/TelepathyQt/pending-ready.h b/TelepathyQt/pending-ready.h new file mode 100644 index 00000000..7d0627ae --- /dev/null +++ b/TelepathyQt/pending-ready.h @@ -0,0 +1,71 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_pending_ready_h_HEADER_GUARD_ +#define _TelepathyQt_pending_ready_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/DBusProxyFactory> +#include <TelepathyQt/PendingOperation> +#include <TelepathyQt/ReadinessHelper> +#include <TelepathyQt/SharedPtr> + +#include <QSet> + +namespace Tp +{ + +class TP_QT_EXPORT PendingReady: public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingReady); + +public: + ~PendingReady(); + + DBusProxyPtr proxy() const; + + Features requestedFeatures() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onNestedFinished(Tp::PendingOperation *); + +private: + friend class Connection; + friend class DBusProxyFactory; + friend class ReadinessHelper; + + TP_QT_NO_EXPORT PendingReady(const SharedPtr<RefCounted> &object, const Features &requestedFeatures); + TP_QT_NO_EXPORT PendingReady(const SharedPtr<DBusProxyFactory> &factory, + const DBusProxyPtr &proxy, const Features &requestedFeatures); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-send-message.cpp b/TelepathyQt/pending-send-message.cpp new file mode 100644 index 00000000..d339b571 --- /dev/null +++ b/TelepathyQt/pending-send-message.cpp @@ -0,0 +1,153 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/PendingSendMessage> + +#include "TelepathyQt/_gen/pending-send-message.moc.hpp" + +#include <TelepathyQt/ContactMessenger> +#include <TelepathyQt/Message> +#include <TelepathyQt/TextChannel> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingSendMessage::Private +{ + Private(const Message &message) + : message(message) + { + } + + QString token; + Message message; +}; + +/** + * \class PendingSendMessage + * \ingroup clientchannel + * \headerfile TelepathyQt/pending-send-message.h <TelepathyQt/PendingSendMessage> + * + * \brief The PendingSendMessage class represents the parameters of and the + * reply to an asynchronous message send request. + * + * See \ref async_model + */ + +PendingSendMessage::PendingSendMessage(const TextChannelPtr &channel, const Message &message) + : PendingOperation(channel), + mPriv(new Private(message)) +{ +} + +PendingSendMessage::PendingSendMessage(const ContactMessengerPtr &messenger, const Message &message) + : PendingOperation(messenger), + mPriv(new Private(message)) +{ +} + +PendingSendMessage::~PendingSendMessage() +{ + delete mPriv; +} + +/** + * Return the channel used to send the message if this instance was created using + * TextChannel. If it was created using ContactMessenger, return a null TextChannelPtr. + * + * \return A pointer to the TextChannel object, or a null TextChannelPtr if created using + * ContactMessenger. + */ +TextChannelPtr PendingSendMessage::channel() const +{ + return TextChannelPtr(qobject_cast<TextChannel*>((TextChannel*) _object().data())); +} + +/** + * Return the contact messenger used to send the message if this instance was created using + * ContactMessenger. If it was created using TextChannel, return a null ContactMessengerPtr. + * + * \return A pointer to the ContactMessenger object, or a null ContactMessengerPtr if created using + * TextChannel. + */ +ContactMessengerPtr PendingSendMessage::messenger() const +{ + return ContactMessengerPtr(qobject_cast<ContactMessenger*>((ContactMessenger*) _object().data())); +} + +QString PendingSendMessage::sentMessageToken() const +{ + return mPriv->token; +} + +Message PendingSendMessage::message() const +{ + return mPriv->message; +} + +void PendingSendMessage::onTextSent(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<> reply = *watcher; + + if (reply.isError()) { + setFinishedWithError(reply.error()); + } else { + setFinished(); + } + watcher->deleteLater(); +} + +void PendingSendMessage::onMessageSent(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QString> reply = *watcher; + + if (reply.isError()) { + setFinishedWithError(reply.error()); + } else { + mPriv->token = reply.value(); + setFinished(); + } + watcher->deleteLater(); +} + +void PendingSendMessage::onCDMessageSent(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<QString> reply = *watcher; + + if (reply.isError()) { + QDBusError error = reply.error(); + if (error.name() == TP_QT_DBUS_ERROR_UNKNOWN_METHOD || + error.name() == TP_QT_DBUS_ERROR_UNKNOWN_INTERFACE) { + setFinishedWithError(TP_QT_ERROR_NOT_IMPLEMENTED, + QLatin1String("Channel Dispatcher implementation (e.g. mission-control), " + "does not support interface CD.I.Messages")); + } else { + setFinishedWithError(error); + } + } else { + mPriv->token = reply.value(); + setFinished(); + } + watcher->deleteLater(); +} + +} // Tp diff --git a/TelepathyQt/pending-send-message.h b/TelepathyQt/pending-send-message.h new file mode 100644 index 00000000..fb9a8a3a --- /dev/null +++ b/TelepathyQt/pending-send-message.h @@ -0,0 +1,77 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_pending_send_message_h_HEADER_GUARD_ +#define _TelepathyQt_pending_send_message_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/PendingOperation> +#include <TelepathyQt/Types> + +class QDBusPendingCallWatcher; +class QString; + +namespace Tp +{ + +class Message; + +class TP_QT_EXPORT PendingSendMessage : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingSendMessage) + +public: + ~PendingSendMessage(); + + TextChannelPtr channel() const; + + ContactMessengerPtr messenger() const; + + QString sentMessageToken() const; + Message message() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onTextSent(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onMessageSent(QDBusPendingCallWatcher *watcher); + TP_QT_NO_EXPORT void onCDMessageSent(QDBusPendingCallWatcher *watcher); + +private: + friend class TextChannel; + friend class ContactMessenger; + + TP_QT_NO_EXPORT PendingSendMessage(const TextChannelPtr &channel, + const Message &message); + TP_QT_NO_EXPORT PendingSendMessage(const ContactMessengerPtr &messenger, + const Message &message); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-stream-tube-connection.cpp b/TelepathyQt/pending-stream-tube-connection.cpp new file mode 100644 index 00000000..5759d923 --- /dev/null +++ b/TelepathyQt/pending-stream-tube-connection.cpp @@ -0,0 +1,272 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/PendingStreamTubeConnection> + +#include "TelepathyQt/_gen/pending-stream-tube-connection.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/IncomingStreamTubeChannel> +#include <TelepathyQt/PendingVariant> +#include <TelepathyQt/Types> +#include "TelepathyQt/types-internal.h" + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingStreamTubeConnection::Private +{ + Private(PendingStreamTubeConnection *parent); + ~Private(); + + // Public object + PendingStreamTubeConnection *parent; + + IncomingStreamTubeChannelPtr tube; + SocketAddressType type; + QHostAddress hostAddress; + quint16 port; + QString socketPath; + bool requiresCredentials; + uchar credentialByte; +}; + +PendingStreamTubeConnection::Private::Private(PendingStreamTubeConnection *parent) + : parent(parent), + requiresCredentials(false), + credentialByte(0) +{ + +} + +PendingStreamTubeConnection::Private::~Private() +{ +} + +/** + * \class PendingStreamTubeConnection + * \ingroup clientchannel + * \headerfile TelepathyQt/incoming-stream-tube-channel.h <TelepathyQt/PendingStreamTubeConnection> + * + * \brief The PendingStreamTubeConnection class represents an asynchronous + * operation for accepting an incoming stream tube. + * + * See \ref async_model + */ + +PendingStreamTubeConnection::PendingStreamTubeConnection( + PendingVariant *acceptOperation, + SocketAddressType type, + bool requiresCredentials, + uchar credentialByte, + const IncomingStreamTubeChannelPtr &channel) + : PendingOperation(channel), + mPriv(new Private(this)) +{ + mPriv->tube = channel; + mPriv->type = type; + mPriv->requiresCredentials = requiresCredentials; + mPriv->credentialByte = credentialByte; + + /* keep track of channel invalidation */ + connect(channel.data(), + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + SLOT(onChannelInvalidated(Tp::DBusProxy*,QString,QString))); + + debug() << "Calling StreamTube.Accept"; + if (acceptOperation->isFinished()) { + onAcceptFinished(acceptOperation); + } else { + connect(acceptOperation, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onAcceptFinished(Tp::PendingOperation*))); + } +} + +PendingStreamTubeConnection::PendingStreamTubeConnection( + const QString& errorName, + const QString& errorMessage, + const IncomingStreamTubeChannelPtr &channel) + : PendingOperation(channel), + mPriv(new PendingStreamTubeConnection::Private(this)) +{ + setFinishedWithError(errorName, errorMessage); +} + +/** + * Class destructor. + */ +PendingStreamTubeConnection::~PendingStreamTubeConnection() +{ + delete mPriv; +} + +/** + * Return the type of the opened stream tube socket. + * + * \return The socket type as #SocketAddressType. + * \see localAddress(), ipAddress() + */ +SocketAddressType PendingStreamTubeConnection::addressType() const +{ + return mPriv->tube->addressType(); +} + +/** + * Return the local address of the opened stream tube socket. + * + * This method will return a meaningful value only if the incoming stream tube + * was accepted as an Unix socket. + * + * \return Unix socket address if using an Unix socket, + * or an undefined value otherwise. + * \see addressType(), ipAddress() + */ +QString PendingStreamTubeConnection::localAddress() const +{ + return mPriv->tube->localAddress(); +} + +/** + * Return the IP address/port combination of the opened stream tube socket. + * + * This method will return a meaningful value only if the incoming stream tube + * was accepted as a TCP socket. + * + * \return Pair of IP address as QHostAddress and port if using a TCP socket, + * or an undefined value otherwise. + * \see addressType(), localAddress() + */ +QPair<QHostAddress, quint16> PendingStreamTubeConnection::ipAddress() const +{ + return mPriv->tube->ipAddress(); +} + +/** + * Return whether sending a credential byte once connecting to the socket is required. + * + * Note that if this method returns \c true, one should send a SCM_CREDS or SCM_CREDENTIALS + * and the credentialByte() once connected. If SCM_CREDS or SCM_CREDENTIALS cannot be sent, + * the credentialByte() should still be sent. + * + * \return \c true if sending credentials is required, \c false otherwise. + * \sa credentialByte() + */ +bool PendingStreamTubeConnection::requiresCredentials() const +{ + return mPriv->requiresCredentials; +} + +/** + * Return the credential byte to send once connecting to the socket if requiresCredentials() is \c + * true. + * + * \return The credential byte. + * \sa requiresCredentials() + */ +uchar PendingStreamTubeConnection::credentialByte() const +{ + return mPriv->credentialByte; +} + +void PendingStreamTubeConnection::onChannelInvalidated(DBusProxy *proxy, + const QString &errorName, const QString &errorMessage) +{ + Q_UNUSED(proxy); + + if (isFinished()) { + return; + } + + warning().nospace() << "StreamTube.Accept failed because channel was invalidated with " << + errorName << ": " << errorMessage; + + setFinishedWithError(errorName, errorMessage); +} + +void PendingStreamTubeConnection::onAcceptFinished(PendingOperation *op) +{ + if (isFinished()) { + return; + } + + if (op->isError()) { + warning().nospace() << "StreamTube.Accept failed with " << + op->errorName() << ": " << op->errorMessage(); + setFinishedWithError(op->errorName(), op->errorMessage()); + return; + } + + debug() << "StreamTube.Accept returned successfully"; + + PendingVariant *pv = qobject_cast<PendingVariant *>(op); + // Build the address + if (mPriv->type == SocketAddressTypeIPv4) { + SocketAddressIPv4 addr = qdbus_cast<SocketAddressIPv4>(pv->result()); + debug().nospace() << "Got address " << addr.address << ":" << addr.port; + mPriv->hostAddress = QHostAddress(addr.address); + mPriv->port = addr.port; + } else if (mPriv->type == SocketAddressTypeIPv6) { + SocketAddressIPv6 addr = qdbus_cast<SocketAddressIPv6>(pv->result()); + debug().nospace() << "Got address " << addr.address << ":" << addr.port; + mPriv->hostAddress = QHostAddress(addr.address); + mPriv->port = addr.port; + } else { + // Unix socket + mPriv->socketPath = QLatin1String(qdbus_cast<QByteArray>(pv->result())); + debug() << "Got socket " << mPriv->socketPath; + } + + // It might have been already opened - check + if (mPriv->tube->state() == TubeChannelStateOpen) { + onTubeStateChanged(mPriv->tube->state()); + } else { + // Wait until the tube gets opened on the other side + connect(mPriv->tube.data(), SIGNAL(stateChanged(Tp::TubeChannelState)), + this, SLOT(onTubeStateChanged(Tp::TubeChannelState))); + } +} + +void PendingStreamTubeConnection::onTubeStateChanged(TubeChannelState state) +{ + debug() << "Tube state changed to " << state; + if (state == TubeChannelStateOpen) { + // The tube is ready, populate its properties + if (mPriv->type == SocketAddressTypeIPv4 || mPriv->type == SocketAddressTypeIPv6) { + mPriv->tube->setIpAddress(qMakePair<QHostAddress, quint16>(mPriv->hostAddress, + mPriv->port)); + } else { + // Unix socket + mPriv->tube->setLocalAddress(mPriv->socketPath); + } + + // Mark the operation as finished + setFinished(); + } else if (state != TubeChannelStateLocalPending) { + // Something happened + setFinishedWithError(QLatin1String("Connection refused"), + QLatin1String("The connection to this tube was refused")); + } +} + +} diff --git a/TelepathyQt/pending-stream-tube-connection.h b/TelepathyQt/pending-stream-tube-connection.h new file mode 100644 index 00000000..2796e906 --- /dev/null +++ b/TelepathyQt/pending-stream-tube-connection.h @@ -0,0 +1,81 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_pending_stream_tube_connection_h_HEADER_GUARD_ +#define _TelepathyQt_pending_stream_tube_connection_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Constants> +#include <TelepathyQt/PendingOperation> +#include <TelepathyQt/Types> + +#include <QPair> + +class QHostAddress; + +namespace Tp +{ + +class PendingVariant; +class IncomingStreamTubeChannel; + +class TP_QT_EXPORT PendingStreamTubeConnection : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingStreamTubeConnection) + +public: + virtual ~PendingStreamTubeConnection(); + + SocketAddressType addressType() const; + + QPair<QHostAddress, quint16> ipAddress() const; + QString localAddress() const; + + bool requiresCredentials() const; + uchar credentialByte() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onChannelInvalidated(Tp::DBusProxy *proxy, + const QString &errorName, const QString &errorMessage); + TP_QT_NO_EXPORT void onAcceptFinished(Tp::PendingOperation *op); + TP_QT_NO_EXPORT void onTubeStateChanged(Tp::TubeChannelState state); + +private: + TP_QT_NO_EXPORT PendingStreamTubeConnection(PendingVariant *acceptOperation, + SocketAddressType type, bool requiresCredentials, uchar credentialByte, + const IncomingStreamTubeChannelPtr &channel); + TP_QT_NO_EXPORT PendingStreamTubeConnection( + const QString &errorName, const QString &errorMessage, + const IncomingStreamTubeChannelPtr &channel); + + struct Private; + friend class IncomingStreamTubeChannel; + friend struct Private; + Private *mPriv; +}; + +} + +#endif // TP_PENDING_STREAM_TUBE_CONNECTION_H diff --git a/TelepathyQt/pending-string-list.cpp b/TelepathyQt/pending-string-list.cpp new file mode 100644 index 00000000..9776ffc9 --- /dev/null +++ b/TelepathyQt/pending-string-list.cpp @@ -0,0 +1,100 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/PendingStringList> + +#include "TelepathyQt/_gen/pending-string-list.moc.hpp" +#include "TelepathyQt/debug-internal.h" + +#include <QDBusPendingReply> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingStringList::Private +{ + QStringList result; +}; + +/** + * \class PendingStringList + * \ingroup utils + * \headerfile TelepathyQt/pending-string-list.h <TelepathyQt/PendingStringList> + * + * \brief The PendingStringList class is a generic subclass of PendingOperation + * representing a pending D-Bus method call that returns a string list. + * + * See \ref async_model + */ + +PendingStringList::PendingStringList(const SharedPtr<RefCounted> &object) + : PendingOperation(object), + mPriv(new Private) +{ +} + +PendingStringList::PendingStringList(QDBusPendingCall call, const SharedPtr<RefCounted> &object) + : PendingOperation(object), + mPriv(new Private) +{ + connect(new QDBusPendingCallWatcher(call), + SIGNAL(finished(QDBusPendingCallWatcher*)), + this, + SLOT(watcherFinished(QDBusPendingCallWatcher*))); +} + +/** + * Class destructor. + */ +PendingStringList::~PendingStringList() +{ + delete mPriv; +} + +QStringList PendingStringList::result() const +{ + return mPriv->result; +} + +void PendingStringList::setResult(const QStringList &result) +{ + mPriv->result = result; +} + +void PendingStringList::watcherFinished(QDBusPendingCallWatcher* watcher) +{ + QDBusPendingReply<QStringList> reply = *watcher; + + if (!reply.isError()) { + debug() << "Got reply to PendingStringList call"; + setResult(reply.value()); + setFinished(); + } else { + debug().nospace() << "PendingStringList call failed: " << + reply.error().name() << ": " << reply.error().message(); + setFinishedWithError(reply.error()); + } + + watcher->deleteLater(); +} + +} // Tp diff --git a/TelepathyQt/pending-string-list.h b/TelepathyQt/pending-string-list.h new file mode 100644 index 00000000..ddd29871 --- /dev/null +++ b/TelepathyQt/pending-string-list.h @@ -0,0 +1,63 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_pending_string_list_h_HEADER_GUARD_ +#define _TelepathyQt_pending_string_list_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/PendingOperation> + +#include <QStringList> + +namespace Tp +{ + +class TP_QT_EXPORT PendingStringList : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingStringList); + +public: + PendingStringList(const SharedPtr<RefCounted> &object); + PendingStringList(QDBusPendingCall call, const SharedPtr<RefCounted> &object); + ~PendingStringList(); + + QStringList result() const; + +protected: + void setResult(const QStringList &result); + +private Q_SLOTS: + TP_QT_NO_EXPORT void watcherFinished(QDBusPendingCallWatcher *watcher); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-variant-map.cpp b/TelepathyQt/pending-variant-map.cpp new file mode 100644 index 00000000..effa1467 --- /dev/null +++ b/TelepathyQt/pending-variant-map.cpp @@ -0,0 +1,91 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/PendingVariantMap> + +#include "TelepathyQt/_gen/pending-variant-map.moc.hpp" +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Global> + +#include <QDBusPendingReply> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingVariantMap::Private +{ + QVariantMap result; +}; + +/** + * \class PendingVariantMap + * \ingroup utils + * \headerfile TelepathyQt/pending-variant-map.h <TelepathyQt/PendingVariantMap> + * + * \brief The PendingVariantMap class is a generic subclass of PendingOperation + * representing a pending D-Bus method call that returns a variant map. + * + * See \ref async_model + */ + +PendingVariantMap::PendingVariantMap(QDBusPendingCall call, const SharedPtr<RefCounted> &object) + : PendingOperation(object), + mPriv(new Private) +{ + connect(new QDBusPendingCallWatcher(call), + SIGNAL(finished(QDBusPendingCallWatcher*)), + this, + SLOT(watcherFinished(QDBusPendingCallWatcher*))); +} + +/** + * Class destructor. + */ +PendingVariantMap::~PendingVariantMap() +{ + delete mPriv; +} + +QVariantMap PendingVariantMap::result() const +{ + return mPriv->result; +} + +void PendingVariantMap::watcherFinished(QDBusPendingCallWatcher* watcher) +{ + QDBusPendingReply<QVariantMap> reply = *watcher; + + if (!reply.isError()) { + debug() << "Got reply to PendingVariantMap call"; + mPriv->result = reply.value(); + setFinished(); + } else { + debug().nospace() << "PendingVariantMap call failed: " << + reply.error().name() << ": " << reply.error().message(); + setFinishedWithError(reply.error()); + } + + watcher->deleteLater(); +} + +} // Tp diff --git a/TelepathyQt/pending-variant-map.h b/TelepathyQt/pending-variant-map.h new file mode 100644 index 00000000..035c2a87 --- /dev/null +++ b/TelepathyQt/pending-variant-map.h @@ -0,0 +1,60 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009-2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009-2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_pending_variant_map_h_HEADER_GUARD_ +#define _TelepathyQt_pending_variant_map_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> +#include <TelepathyQt/PendingOperation> + +#include <QVariant> + +namespace Tp +{ + +class TP_QT_EXPORT PendingVariantMap : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingVariantMap); + +public: + PendingVariantMap(QDBusPendingCall call, const SharedPtr<RefCounted> &object); + ~PendingVariantMap(); + + QVariantMap result() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void watcherFinished(QDBusPendingCallWatcher*); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/pending-variant.cpp b/TelepathyQt/pending-variant.cpp new file mode 100644 index 00000000..f9868fea --- /dev/null +++ b/TelepathyQt/pending-variant.cpp @@ -0,0 +1,91 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/PendingVariant> + +#include "TelepathyQt/_gen/pending-variant.moc.hpp" +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Global> + +#include <QDBusPendingReply> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT PendingVariant::Private +{ + QVariant result; +}; + +/** + * \class PendingVariant + * \ingroup utils + * \headerfile TelepathyQt/pending-variant.h <TelepathyQt/PendingVariant> + * + * \brief The PendingVariant class is a generic subclass of PendingOperation + * representing a pending D-Bus method call that returns a variant. + * + * See \ref async_model + */ + +PendingVariant::PendingVariant(QDBusPendingCall call, const SharedPtr<RefCounted> &object) + : PendingOperation(object), + mPriv(new Private) +{ + connect(new QDBusPendingCallWatcher(call), + SIGNAL(finished(QDBusPendingCallWatcher*)), + this, + SLOT(watcherFinished(QDBusPendingCallWatcher*))); +} + +/** + * Class destructor. + */ +PendingVariant::~PendingVariant() +{ + delete mPriv; +} + +QVariant PendingVariant::result() const +{ + return mPriv->result; +} + +void PendingVariant::watcherFinished(QDBusPendingCallWatcher* watcher) +{ + QDBusPendingReply<QDBusVariant> reply = *watcher; + + if (!reply.isError()) { + debug() << "Got reply to PendingVariant call"; + mPriv->result = reply.value().variant(); + setFinished(); + } else { + debug().nospace() << "PendingVariant call failed: " << + reply.error().name() << ": " << reply.error().message(); + setFinishedWithError(reply.error()); + } + + watcher->deleteLater(); +} + +} // Tp diff --git a/TelepathyQt/pending-variant.h b/TelepathyQt/pending-variant.h new file mode 100644 index 00000000..0a4ff868 --- /dev/null +++ b/TelepathyQt/pending-variant.h @@ -0,0 +1,60 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_pending_variant_h_HEADER_GUARD_ +#define _TelepathyQt_pending_variant_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> +#include <TelepathyQt/PendingOperation> + +#include <QVariant> + +namespace Tp +{ + +class TP_QT_EXPORT PendingVariant : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingVariant); + +public: + PendingVariant(QDBusPendingCall call, const SharedPtr<RefCounted> &object); + ~PendingVariant(); + + QVariant result() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void watcherFinished(QDBusPendingCallWatcher*); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/presence.cpp b/TelepathyQt/presence.cpp new file mode 100644 index 00000000..556e9561 --- /dev/null +++ b/TelepathyQt/presence.cpp @@ -0,0 +1,335 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/Presence> + +#include "TelepathyQt/debug-internal.h" + +namespace Tp +{ + +struct TP_QT_NO_EXPORT Presence::Private : public QSharedData +{ + Private(const SimplePresence &sp) + : sp(sp) + { + } + + Private(ConnectionPresenceType type, const QString &status, const QString &statusMessage) + { + sp.type = type; + sp.status = status; + sp.statusMessage = statusMessage; + } + + SimplePresence sp; +}; + +/** + * \class Presence + * \ingroup wrappers + * \headerfile TelepathyQt/presence.h <TelepathyQt/Presence> + * + * \brief The Presence class represents a Telepathy simple presence. + */ + +Presence::Presence() +{ +} + +Presence::Presence(const SimplePresence &sp) + : mPriv(new Private(sp)) +{ +} + +Presence::Presence(ConnectionPresenceType type, const QString &status, const QString &statusMessage) + : mPriv(new Private(type, status, statusMessage)) +{ +} + +Presence::Presence(const Presence &other) + : mPriv(other.mPriv) +{ +} + +Presence::~Presence() +{ +} + +Presence Presence::available(const QString &statusMessage) +{ + return Presence(ConnectionPresenceTypeAvailable, QLatin1String("available"), statusMessage); +} + +Presence Presence::away(const QString &statusMessage) +{ + return Presence(ConnectionPresenceTypeAway, QLatin1String("away"), statusMessage); +} + +Presence Presence::brb(const QString &statusMessage) +{ + return Presence(ConnectionPresenceTypeAway, QLatin1String("brb"), statusMessage); +} + +Presence Presence::busy(const QString &statusMessage) +{ + return Presence(ConnectionPresenceTypeBusy, QLatin1String("busy"), statusMessage); +} + +Presence Presence::xa(const QString &statusMessage) +{ + return Presence(ConnectionPresenceTypeExtendedAway, QLatin1String("xa"), statusMessage); +} + +Presence Presence::hidden(const QString &statusMessage) +{ + return Presence(ConnectionPresenceTypeHidden, QLatin1String("hidden"), statusMessage); +} + +Presence Presence::offline(const QString &statusMessage) +{ + return Presence(ConnectionPresenceTypeOffline, QLatin1String("offline"), statusMessage); +} + +Presence &Presence::operator=(const Presence &other) +{ + this->mPriv = other.mPriv; + return *this; +} + +bool Presence::operator==(const Presence &other) const +{ + if (!isValid() || !other.isValid()) { + if (!isValid() && !other.isValid()) { + return true; + } + return false; + } + + return mPriv->sp == other.mPriv->sp; +} + +bool Presence::operator!=(const Presence &other) const +{ + if (!isValid() || !other.isValid()) { + if (!isValid() && !other.isValid()) { + return false; + } + return true; + } + + return mPriv->sp != other.mPriv->sp; +} + +ConnectionPresenceType Presence::type() const +{ + if (!isValid()) { + return ConnectionPresenceTypeUnknown; + } + + return (ConnectionPresenceType) mPriv->sp.type; +} + +QString Presence::status() const +{ + if (!isValid()) { + return QString(); + } + + return mPriv->sp.status; +} + +QString Presence::statusMessage() const +{ + if (!isValid()) { + return QString(); + } + + return mPriv->sp.statusMessage; +} + +void Presence::setStatus(const SimplePresence &value) +{ + if (!isValid()) { + mPriv = new Private(value); + return; + } + + mPriv->sp = value; +} + +void Presence::setStatus(ConnectionPresenceType type, const QString &status, + const QString &statusMessage) +{ + if (!isValid()) { + mPriv = new Private(type, status, statusMessage); + return; + } + + mPriv->sp.type = type; + mPriv->sp.status = status; + mPriv->sp.statusMessage = statusMessage; +} + +SimplePresence Presence::barePresence() const +{ + if (!isValid()) { + return SimplePresence(); + } + + return mPriv->sp; +} + +struct TP_QT_NO_EXPORT PresenceSpec::Private : public QSharedData +{ + Private(const QString &status, const SimpleStatusSpec &spec) + : status(status), + spec(spec) + { + } + + QString status; + SimpleStatusSpec spec; +}; + +/** + * \class PresenceSpec + * \ingroup wrappers + * \headerfile TelepathyQt/presence.h <TelepathyQt/PresenceSpec> + * + * \brief The PresenceSpec class represents a Telepathy presence information + * supported by a protocol. + */ + +PresenceSpec::PresenceSpec() +{ +} + +PresenceSpec::PresenceSpec(const QString &status, const SimpleStatusSpec &spec) + : mPriv(new Private(status, spec)) +{ +} + +PresenceSpec::PresenceSpec(const PresenceSpec &other) + : mPriv(other.mPriv) +{ +} + +PresenceSpec::~PresenceSpec() +{ +} + +PresenceSpec &PresenceSpec::operator=(const PresenceSpec &other) +{ + this->mPriv = other.mPriv; + return *this; +} + +bool PresenceSpec::operator==(const PresenceSpec &other) const +{ + if (!isValid() || !other.isValid()) { + if (!isValid() && !other.isValid()) { + return true; + } + return false; + } + + return (mPriv->status == other.mPriv->status) && + (mPriv->spec == other.mPriv->spec); +} + +bool PresenceSpec::operator!=(const PresenceSpec &other) const +{ + if (!isValid() || !other.isValid()) { + if (!isValid() && !other.isValid()) { + return false; + } + return true; + } + + return (mPriv->status != other.mPriv->status) && + (mPriv->spec != other.mPriv->spec); +} + +bool PresenceSpec::operator<(const PresenceSpec &other) const +{ + if (!isValid()) { + return false; + } + + if (!other.isValid()) { + return true; + } + + return (mPriv->status < other.mPriv->status); +} + +Presence PresenceSpec::presence(const QString &statusMessage) const +{ + if (!isValid()) { + return Presence(); + } + + if (!canHaveStatusMessage() && !statusMessage.isEmpty()) { + warning() << "Passing a status message to PresenceSpec with " + "canHaveStatusMessage() being false"; + } + + return Presence((ConnectionPresenceType) mPriv->spec.type, mPriv->status, statusMessage); +} + +bool PresenceSpec::maySetOnSelf() const +{ + if (!isValid()) { + return false; + } + + return mPriv->spec.maySetOnSelf; +} + +bool PresenceSpec::canHaveStatusMessage() const +{ + if (!isValid()) { + return false; + } + + return mPriv->spec.canHaveMessage; +} + +SimpleStatusSpec PresenceSpec::bareSpec() const +{ + if (!isValid()) { + return SimpleStatusSpec(); + } + + return mPriv->spec; +} + +/** + * \class PresenceSpecList + * \ingroup wrappers + * \headerfile TelepathyQt/presence.h <TelepathyQt/PresenceSpecList> + * + * \brief The PresenceSpecList class represents a list of PresenceSpec. + */ + +} // Tp diff --git a/TelepathyQt/presence.h b/TelepathyQt/presence.h new file mode 100644 index 00000000..4e5421cd --- /dev/null +++ b/TelepathyQt/presence.h @@ -0,0 +1,135 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_presence_h_HEADER_GUARD_ +#define _TelepathyQt_presence_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class TP_QT_EXPORT Presence +{ +public: + Presence(); + Presence(const SimplePresence &sp); + Presence(ConnectionPresenceType type, const QString &status, const QString &statusMessage); + Presence(const Presence &other); + ~Presence(); + + static Presence available(const QString &statusMessage = QString()); + static Presence away(const QString &statusMessage = QString()); + static Presence brb(const QString &statusMessage = QString()); + static Presence busy(const QString &statusMessage = QString()); + static Presence xa(const QString &statusMessage = QString()); + static Presence hidden(const QString &statusMessage = QString()); + static Presence offline(const QString &statusMessage = QString()); + + bool isValid() const { return mPriv.constData() != 0; } + + Presence &operator=(const Presence &other); + bool operator==(const Presence &other) const; + bool operator!=(const Presence &other) const; + + ConnectionPresenceType type() const; + QString status() const; + QString statusMessage() const; + void setStatus(const SimplePresence &value); + void setStatus(ConnectionPresenceType type, const QString &status, + const QString &statusMessage); + + SimplePresence barePresence() const; + +private: + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +class TP_QT_EXPORT PresenceSpec +{ +public: + PresenceSpec(); + PresenceSpec(const QString &status, const SimpleStatusSpec &spec); + PresenceSpec(const PresenceSpec &other); + ~PresenceSpec(); + + bool isValid() const { return mPriv.constData() != 0; } + + PresenceSpec &operator=(const PresenceSpec &other); + bool operator==(const PresenceSpec &other) const; + bool operator!=(const PresenceSpec &other) const; + bool operator<(const PresenceSpec &other) const; + + Presence presence(const QString &statusMessage = QString()) const; + bool maySetOnSelf() const; + bool canHaveStatusMessage() const; + + SimpleStatusSpec bareSpec() const; + +private: + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +class TP_QT_EXPORT PresenceSpecList : public QList<PresenceSpec> +{ +public: + PresenceSpecList() { } + PresenceSpecList(const SimpleStatusSpecMap &specMap) + { + SimpleStatusSpecMap::const_iterator i = specMap.constBegin(); + SimpleStatusSpecMap::const_iterator end = specMap.end(); + for (; i != end; ++i) { + QString status = i.key(); + SimpleStatusSpec spec = i.value(); + append(PresenceSpec(status, spec)); + } + } + PresenceSpecList(const QList<PresenceSpec> &other) + : QList<PresenceSpec>(other) + { + } + + QMap<QString, PresenceSpec> toMap() const + { + QMap<QString, PresenceSpec> ret; + Q_FOREACH (const PresenceSpec &spec, *this) { + ret.insert(spec.presence().status(), spec); + } + return ret; + } +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::Presence); +Q_DECLARE_METATYPE(Tp::PresenceSpec); + +#endif diff --git a/TelepathyQt/profile-manager.cpp b/TelepathyQt/profile-manager.cpp new file mode 100644 index 00000000..46391b6e --- /dev/null +++ b/TelepathyQt/profile-manager.cpp @@ -0,0 +1,332 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ProfileManager> + +#include "TelepathyQt/_gen/profile-manager.moc.hpp" +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/ConnectionManager> +#include <TelepathyQt/PendingComposite> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/PendingStringList> +#include <TelepathyQt/Profile> +#include <TelepathyQt/ReadinessHelper> + +#include <QFile> +#include <QFileInfo> +#include <QString> +#include <QStringList> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ProfileManager::Private +{ + Private(ProfileManager *parent, const QDBusConnection &bus); + + static void introspectMain(Private *self); + static void introspectFakeProfiles(Private *self); + + ProfileManager *parent; + ReadinessHelper *readinessHelper; + QDBusConnection bus; + QHash<QString, ProfilePtr> profiles; + QList<ConnectionManagerPtr> cms; +}; + +ProfileManager::Private::Private(ProfileManager *parent, const QDBusConnection &bus) + : parent(parent), + readinessHelper(parent->readinessHelper()), + bus(bus) +{ + ReadinessHelper::Introspectables introspectables; + + ReadinessHelper::Introspectable introspectableCore( + QSet<uint>() << 0, // makesSenseForStatuses + Features(), // dependsOnFeatures + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectMain, + this); + introspectables[FeatureCore] = introspectableCore; + + ReadinessHelper::Introspectable introspectableFakeProfiles( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << FeatureCore, // dependsOnFeatures + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectFakeProfiles, + this); + introspectables[FeatureFakeProfiles] = introspectableFakeProfiles; + + readinessHelper->addIntrospectables(introspectables); +} + +void ProfileManager::Private::introspectMain(ProfileManager::Private *self) +{ + QStringList searchDirs = Profile::searchDirs(); + + foreach (const QString searchDir, searchDirs) { + QDir dir(searchDir); + dir.setFilter(QDir::Files); + + QFileInfoList list = dir.entryInfoList(); + for (int i = 0; i < list.size(); ++i) { + QFileInfo fi = list.at(i); + + if (fi.completeSuffix() != QLatin1String("profile")) { + continue; + } + + QString fileName = fi.absoluteFilePath(); + QString serviceName = fi.baseName(); + + if (self->profiles.contains(serviceName)) { + debug() << "Profile for service" << serviceName << "already " + "exists. Ignoring profile file:" << fileName; + continue; + } + + ProfilePtr profile = Profile::createForFileName(fileName); + if (!profile->isValid()) { + continue; + } + + if (profile->type() != QLatin1String("IM")) { + debug() << "Ignoring profile for service" << serviceName << + ": type != IM. Profile file:" << fileName; + continue; + } + + debug() << "Found profile for service" << serviceName << + "- profile file:" << fileName; + self->profiles.insert(serviceName, profile); + } + } + + self->readinessHelper->setIntrospectCompleted(FeatureCore, true); +} + +void ProfileManager::Private::introspectFakeProfiles(ProfileManager::Private *self) +{ + PendingStringList *pendingCmNames = ConnectionManager::listNames(self->bus); + self->parent->connect(pendingCmNames, + SIGNAL(finished(Tp::PendingOperation *)), + SLOT(onCmNamesRetrieved(Tp::PendingOperation *))); +} + +/** + * \class ProfileManager + * \headerfile TelepathyQt/profile-manager.h <TelepathyQt/ProfileManager> + * + * \brief The ProfileManager class provides helper methods to retrieve Profile + * objects. + */ + +/** + * Feature representing the core that needs to become ready to make the ProfileManager + * object usable. + * + * Note that this feature must be enabled in order to use all ProfileManager methods. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature ProfileManager::FeatureCore = Feature(QLatin1String(ProfileManager::staticMetaObject.className()), 0, true); + +/** + * Enabling this feature will make ProfileManager create fake Profile objects to all protocols + * supported on the installed connection managers, even if they don't have .profile files installed + * making use of them. + * + * Fake profiles are identified by Profile::isFake() returning \c true. + * + * The fake profile will contain the following info: + * - Profile::type() will return "IM" + * - Profile::provider() will return an empty string + * - Profile::serviceName() will return cmName-protocolName + * - Profile::name() and Profile::protocolName() will return protocolName + * - Profile::iconName() will return "im-protocolName" + * - Profile::cmName() will return cmName + * - Profile::parameters() will return a list matching CM default parameters for protocol with name + * protocolName. + * - Profile::presences() will return an empty list and + * Profile::allowOtherPresences() will return \c true, meaning that CM + * presences should be used + * - Profile::unsupportedChannelClassSpecs() will return an empty list + * + * Where cmName and protocolName are the name of the connection manager and the name of the protocol + * for which this fake Profile is created, respectively. + */ +const Feature ProfileManager::FeatureFakeProfiles = Feature(QLatin1String(ProfileManager::staticMetaObject.className()), 1); + +/** + * Create a new ProfileManager object. + */ +ProfileManagerPtr ProfileManager::create(const QDBusConnection &bus) +{ + return ProfileManagerPtr(new ProfileManager(bus)); +} + +/** + * Construct a new ProfileManager object. + */ +ProfileManager::ProfileManager(const QDBusConnection &bus) + : Object(), + ReadyObject(this, FeatureCore), + mPriv(new Private(this, bus)) +{ +} + +/** + * Class destructor. + */ +ProfileManager::~ProfileManager() +{ + delete mPriv; +} + +/** + * Return a list of all available profiles. + * + * \return A list of all available profiles. + */ +QList<ProfilePtr> ProfileManager::profiles() const +{ + return mPriv->profiles.values(); +} + +/** + * Return a list of all available profiles for a given connection manager. + * + * \param cmName Connection manager name. + * \return A list of all available profiles for a given connection manager. + */ +QList<ProfilePtr> ProfileManager::profilesForCM(const QString &cmName) const +{ + QList<ProfilePtr> ret; + foreach (const ProfilePtr &profile, mPriv->profiles) { + if (profile->cmName() == cmName) { + ret << profile; + } + } + return ret; +} + +/** + * Return a list of all available profiles for a given \a protocol. + * + * \param protocolName Protocol name. + * \return A list of all available profiles for a given \a protocol. + */ +QList<ProfilePtr> ProfileManager::profilesForProtocol( + const QString &protocolName) const +{ + QList<ProfilePtr> ret; + foreach (const ProfilePtr &profile, mPriv->profiles) { + if (profile->protocolName() == protocolName) { + ret << profile; + } + } + return ret; +} + +/** + * Return the profile for a given \a service. + * + * \param serviceName Service name. + * \return The profile for \a service. + */ +ProfilePtr ProfileManager::profileForService(const QString &serviceName) const +{ + return mPriv->profiles.value(serviceName); +} + +void ProfileManager::onCmNamesRetrieved(Tp::PendingOperation *op) +{ + if (op->isError()) { + warning().nospace() << "Getting available CMs failed with " << + op->errorName() << ":" << op->errorMessage(); + mPriv->readinessHelper->setIntrospectCompleted(FeatureFakeProfiles, false, + op->errorName(), op->errorMessage()); + return; + } + + PendingStringList *pendingCmNames = qobject_cast<PendingStringList *>(op); + QStringList cmNames(pendingCmNames->result()); + if (cmNames.isEmpty()) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureFakeProfiles, true); + return; + } + + QList<PendingOperation *> ops; + foreach (const QString &cmName, cmNames) { + ConnectionManagerPtr cm = ConnectionManager::create(mPriv->bus, cmName); + mPriv->cms.append(cm); + ops.append(cm->becomeReady()); + } + + PendingComposite *pc = new PendingComposite(ops, false, ProfileManagerPtr(this)); + connect(pc, + SIGNAL(finished(Tp::PendingOperation *)), + SLOT(onCMsReady(Tp::PendingOperation *))); +} + +void ProfileManager::onCMsReady(Tp::PendingOperation *op) +{ + if (op->isError()) { + warning() << "Failed introspecting all CMs, trying to create fake profiles anyway"; + } + + ProfilePtr profile; + foreach (const ConnectionManagerPtr &cm, mPriv->cms) { + if (!cm->isReady()) { + continue; + } + + foreach (const QString &protocolName, cm->supportedProtocols()) { + /* check if there is a profile whose service name is protocolName, and if found, + * check if the profile is for cm, if not check if there is a profile whose service + * name is cm-protocolName, and if not found create one named cm-protocolName. */ + profile = profileForService(protocolName); + if (profile && profile->cmName() == cm->name()) { + continue; + } + + QString serviceName = QString(QLatin1String("%1-%2")).arg(cm->name()).arg(protocolName); + profile = profileForService(serviceName); + if (profile) { + continue; + } + + profile = ProfilePtr(new Profile( + serviceName, + cm->name(), + protocolName, + cm->protocol(protocolName))); + mPriv->profiles.insert(serviceName, profile); + } + } + + mPriv->readinessHelper->setIntrospectCompleted(FeatureFakeProfiles, true); +} + +} // Tp diff --git a/TelepathyQt/profile-manager.h b/TelepathyQt/profile-manager.h new file mode 100644 index 00000000..c5566339 --- /dev/null +++ b/TelepathyQt/profile-manager.h @@ -0,0 +1,75 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_profile_manager_h_HEADER_GUARD_ +#define _TelepathyQt_profile_manager_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Object> +#include <TelepathyQt/Profile> +#include <TelepathyQt/ReadyObject> +#include <TelepathyQt/Types> + +#include <QDBusConnection> +#include <QObject> + +namespace Tp +{ + +class PendingOperation; + +class TP_QT_EXPORT ProfileManager : public Object, public ReadyObject +{ + Q_OBJECT + Q_DISABLE_COPY(ProfileManager); + +public: + static const Feature FeatureCore; + static const Feature FeatureFakeProfiles; + + static ProfileManagerPtr create(const QDBusConnection &bus = QDBusConnection::sessionBus()); + + ~ProfileManager(); + + QList<ProfilePtr> profiles() const; + QList<ProfilePtr> profilesForCM(const QString &cmName) const; + QList<ProfilePtr> profilesForProtocol(const QString &protocolName) const; + ProfilePtr profileForService(const QString &serviceName) const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void onCmNamesRetrieved(Tp::PendingOperation *op); + TP_QT_NO_EXPORT void onCMsReady(Tp::PendingOperation *op); + +private: + ProfileManager(const QDBusConnection &bus); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/profile.cpp b/TelepathyQt/profile.cpp new file mode 100644 index 00000000..ff487b75 --- /dev/null +++ b/TelepathyQt/profile.cpp @@ -0,0 +1,1216 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/Profile> + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/ManagerFile> +#include <TelepathyQt/Utils> +#include <TelepathyQt/ProtocolInfo> +#include <TelepathyQt/ProtocolParameter> + +#include <QFile> +#include <QFileInfo> +#include <QStringList> +#include <QXmlAttributes> +#include <QXmlDefaultHandler> +#include <QXmlInputSource> +#include <QXmlSimpleReader> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT Profile::Private +{ + Private(); + + void setServiceName(const QString &serviceName); + void setFileName(const QString &fileName); + + void lookupProfile(); + bool parse(QFile *file); + void invalidate(); + + struct Data + { + Data(); + + void clear(); + + QString type; + QString provider; + QString name; + QString iconName; + QString cmName; + QString protocolName; + Profile::ParameterList parameters; + bool allowOtherPresences; + Profile::PresenceList presences; + RequestableChannelClassSpecList unsupportedChannelClassSpecs; + }; + + class XmlHandler; + + QString serviceName; + bool valid; + bool fake; + bool allowNonIMType; + Data data; +}; + +Profile::Private::Data::Data() + : allowOtherPresences(false) +{ +} + +void Profile::Private::Data::clear() +{ + type = QString(); + provider = QString(); + name = QString(); + iconName = QString(); + protocolName = QString(); + parameters = Profile::ParameterList(); + allowOtherPresences = false; + presences = Profile::PresenceList(); + unsupportedChannelClassSpecs = RequestableChannelClassSpecList(); +} + + +class TP_QT_NO_EXPORT Profile::Private::XmlHandler : + public QXmlDefaultHandler +{ +public: + XmlHandler(const QString &serviceName, bool allowNonIMType, Profile::Private::Data *outputData); + + bool startElement(const QString &namespaceURI, const QString &localName, + const QString &qName, const QXmlAttributes &attributes); + bool endElement(const QString &namespaceURI, const QString &localName, + const QString &qName); + bool characters(const QString &str); + bool fatalError(const QXmlParseException &exception); + QString errorString() const; + +private: + bool attributeValueAsBoolean(const QXmlAttributes &attributes, + const QString &qName); + + QString mServiceName; + bool allowNonIMType; + Profile::Private::Data *mData; + QStack<QString> mElements; + QString mCurrentText; + Profile::Parameter mCurrentParameter; + RequestableChannelClass mCurrentCC; + QString mCurrentPropertyName; + QString mCurrentPropertyType; + QString mErrorString; + bool mMetServiceTag; + + static const QString xmlNs; + + static const QString elemService; + static const QString elemName; + static const QString elemParams; + static const QString elemParam; + static const QString elemPresences; + static const QString elemPresence; + static const QString elemUnsupportedCCs; + static const QString elemCC; + static const QString elemProperty; + + static const QString elemAttrId; + static const QString elemAttrName; + static const QString elemAttrType; + static const QString elemAttrProvider; + static const QString elemAttrManager; + static const QString elemAttrProtocol; + static const QString elemAttrIcon; + static const QString elemAttrLabel; + static const QString elemAttrMandatory; + static const QString elemAttrAllowOthers; + static const QString elemAttrMessage; + static const QString elemAttrDisabled; +}; + +const QString Profile::Private::XmlHandler::xmlNs = QLatin1String("http://telepathy.freedesktop.org/wiki/service-profile-v1"); + +const QString Profile::Private::XmlHandler::elemService = QLatin1String("service"); +const QString Profile::Private::XmlHandler::elemName = QLatin1String("name"); +const QString Profile::Private::XmlHandler::elemParams = QLatin1String("parameters"); +const QString Profile::Private::XmlHandler::elemParam = QLatin1String("parameter"); +const QString Profile::Private::XmlHandler::elemPresences = QLatin1String("presences"); +const QString Profile::Private::XmlHandler::elemPresence = QLatin1String("presence"); +const QString Profile::Private::XmlHandler::elemUnsupportedCCs = QLatin1String("unsupported-channel-classes"); +const QString Profile::Private::XmlHandler::elemCC = QLatin1String("channel-class"); +const QString Profile::Private::XmlHandler::elemProperty = QLatin1String("property"); + +const QString Profile::Private::XmlHandler::elemAttrId = QLatin1String("id"); +const QString Profile::Private::XmlHandler::elemAttrName = QLatin1String("name"); +const QString Profile::Private::XmlHandler::elemAttrType = QLatin1String("type"); +const QString Profile::Private::XmlHandler::elemAttrProvider = QLatin1String("provider"); +const QString Profile::Private::XmlHandler::elemAttrManager = QLatin1String("manager"); +const QString Profile::Private::XmlHandler::elemAttrProtocol = QLatin1String("protocol"); +const QString Profile::Private::XmlHandler::elemAttrLabel = QLatin1String("label"); +const QString Profile::Private::XmlHandler::elemAttrMandatory = QLatin1String("mandatory"); +const QString Profile::Private::XmlHandler::elemAttrAllowOthers = QLatin1String("allow-others"); +const QString Profile::Private::XmlHandler::elemAttrIcon = QLatin1String("icon"); +const QString Profile::Private::XmlHandler::elemAttrMessage = QLatin1String("message"); +const QString Profile::Private::XmlHandler::elemAttrDisabled = QLatin1String("disabled"); + +Profile::Private::XmlHandler::XmlHandler(const QString &serviceName, + bool allowNonIMType, + Profile::Private::Data *outputData) + : mServiceName(serviceName), + allowNonIMType(allowNonIMType), + mData(outputData), + mMetServiceTag(false) +{ +} + +bool Profile::Private::XmlHandler::startElement(const QString &namespaceURI, + const QString &localName, const QString &qName, + const QXmlAttributes &attributes) +{ + if (!mMetServiceTag && qName != elemService) { + mErrorString = QLatin1String("the file is not a profile file"); + return false; + } + + if (namespaceURI != xmlNs) { + // ignore all elements with unknown xmlns + debug() << "Ignoring unknown xmlns" << namespaceURI; + return true; + } + +#define CHECK_ELEMENT_IS_CHILD_OF(parentElement) \ + if (mElements.top() != parentElement) { \ + mErrorString = QString(QLatin1String("element '%1' is not a " \ + "child of element '%2'")) \ + .arg(qName) \ + .arg(parentElement); \ + return false; \ + } +#define CHECK_ELEMENT_ATTRIBUTES_COUNT(value) \ + if (attributes.count() != value) { \ + mErrorString = QString(QLatin1String("element '%1' contains more " \ + "than %2 attributes")) \ + .arg(qName) \ + .arg(value); \ + return false; \ + } +#define CHECK_ELEMENT_HAS_ATTRIBUTE(attribute) \ + if (attributes.index(attribute) == -1) { \ + mErrorString = QString(QLatin1String("mandatory attribute '%1' " \ + "missing on element '%2'")) \ + .arg(attribute) \ + .arg(qName); \ + return false; \ + } +#define CHECK_ELEMENT_ATTRIBUTES(allowedAttrs) \ + for (int i = 0; i < attributes.count(); ++i) { \ + bool valid = false; \ + QString attrName = attributes.qName(i); \ + foreach (const QString &allowedAttr, allowedAttrs) { \ + if (attrName == allowedAttr) { \ + valid = true; \ + break; \ + } \ + } \ + if (!valid) { \ + mErrorString = QString(QLatin1String("invalid attribute '%1' on " \ + "element '%2'")) \ + .arg(attrName) \ + .arg(qName); \ + return false; \ + } \ + } + + if (qName == elemService) { + CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrId); + CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrType); + CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrManager); + CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrProtocol); + + QStringList allowedAttrs = QStringList() << + elemAttrId << elemAttrType << elemAttrManager << + elemAttrProtocol << elemAttrProvider << elemAttrIcon; + CHECK_ELEMENT_ATTRIBUTES(allowedAttrs); + + if (attributes.value(elemAttrId) != mServiceName) { + mErrorString = QString(QLatin1String("the '%1' attribute of the " + "element '%2' does not match the file name")) + .arg(elemAttrId) + .arg(elemService); + return false; + } + + mMetServiceTag = true; + mData->type = attributes.value(elemAttrType); + if (mData->type != QLatin1String("IM") && !allowNonIMType) { + mErrorString = QString(QLatin1String("unknown value of element " + "'type': %1")) + .arg(mCurrentText); + return false; + } + mData->provider = attributes.value(elemAttrProvider); + mData->cmName = attributes.value(elemAttrManager); + mData->protocolName = attributes.value(elemAttrProtocol); + mData->iconName = attributes.value(elemAttrIcon); + } else if (qName == elemParams) { + CHECK_ELEMENT_IS_CHILD_OF(elemService); + CHECK_ELEMENT_ATTRIBUTES_COUNT(0); + } else if (qName == elemParam) { + CHECK_ELEMENT_IS_CHILD_OF(elemParams); + CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrName); + QStringList allowedAttrs = QStringList() << elemAttrName << + elemAttrType << elemAttrMandatory << elemAttrLabel; + CHECK_ELEMENT_ATTRIBUTES(allowedAttrs); + + QString paramType = attributes.value(elemAttrType); + if (paramType.isEmpty()) { + paramType = QLatin1String("s"); + } + mCurrentParameter.setName(attributes.value(elemAttrName)); + mCurrentParameter.setDBusSignature(QDBusSignature(paramType)); + mCurrentParameter.setLabel(attributes.value(elemAttrLabel)); + mCurrentParameter.setMandatory(attributeValueAsBoolean(attributes, + elemAttrMandatory)); + } else if (qName == elemPresences) { + CHECK_ELEMENT_IS_CHILD_OF(elemService); + QStringList allowedAttrs = QStringList() << elemAttrAllowOthers; + CHECK_ELEMENT_ATTRIBUTES(allowedAttrs); + + mData->allowOtherPresences = attributeValueAsBoolean(attributes, + elemAttrAllowOthers); + } else if (qName == elemPresence) { + CHECK_ELEMENT_IS_CHILD_OF(elemPresences); + CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrId); + QStringList allowedAttrs = QStringList() << elemAttrId << + elemAttrLabel << elemAttrIcon << elemAttrMessage << + elemAttrDisabled; + CHECK_ELEMENT_ATTRIBUTES(allowedAttrs); + + mData->presences.append(Profile::Presence( + attributes.value(elemAttrId), + attributes.value(elemAttrLabel), + attributes.value(elemAttrIcon), + attributes.value(elemAttrMessage), + attributeValueAsBoolean(attributes, elemAttrDisabled))); + } else if (qName == elemUnsupportedCCs) { + CHECK_ELEMENT_IS_CHILD_OF(elemService); + CHECK_ELEMENT_ATTRIBUTES_COUNT(0); + } else if (qName == elemCC) { + CHECK_ELEMENT_IS_CHILD_OF(elemUnsupportedCCs); + CHECK_ELEMENT_ATTRIBUTES_COUNT(0); + } else if (qName == elemProperty) { + CHECK_ELEMENT_IS_CHILD_OF(elemCC); + CHECK_ELEMENT_ATTRIBUTES_COUNT(2); + CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrName); + CHECK_ELEMENT_HAS_ATTRIBUTE(elemAttrType); + + mCurrentPropertyName = attributes.value(elemAttrName); + mCurrentPropertyType = attributes.value(elemAttrType); + } else { + if (qName != elemName) { + Tp::warning() << "Ignoring unknown element" << qName; + } else { + // check if we are inside <service> + CHECK_ELEMENT_IS_CHILD_OF(elemService); + // no element here supports attributes + CHECK_ELEMENT_ATTRIBUTES_COUNT(0); + } + } + +#undef CHECK_ELEMENT_IS_CHILD_OF +#undef CHECK_ELEMENT_ATTRIBUTES_COUNT +#undef CHECK_ELEMENT_HAS_ATTRIBUTE +#undef CHECK_ELEMENT_ATTRIBUTES + + mElements.push(qName); + mCurrentText.clear(); + return true; +} + +bool Profile::Private::XmlHandler::endElement(const QString &namespaceURI, + const QString &localName, const QString &qName) +{ + if (namespaceURI != xmlNs) { + // ignore all elements with unknown xmlns + debug() << "Ignoring unknown xmlns" << namespaceURI; + return true; + } else if (qName == elemName) { + mData->name = mCurrentText; + } else if (qName == elemParam) { + mCurrentParameter.setValue(ManagerFile::parseValueWithDBusSignature(mCurrentText, + mCurrentParameter.dbusSignature().signature())); + mData->parameters.append(Profile::Parameter(mCurrentParameter)); + } else if (qName == elemCC) { + mData->unsupportedChannelClassSpecs.append(RequestableChannelClassSpec(mCurrentCC)); + mCurrentCC.fixedProperties.clear(); + } else if (qName == elemProperty) { + mCurrentCC.fixedProperties[mCurrentPropertyName] = + ManagerFile::parseValueWithDBusSignature(mCurrentText, + mCurrentPropertyType); + } + + mElements.pop(); + return true; +} + +bool Profile::Private::XmlHandler::characters(const QString &str) +{ + mCurrentText += str; + return true; +} + +bool Profile::Private::XmlHandler::fatalError( + const QXmlParseException &exception) +{ + mErrorString = QString(QLatin1String("parse error at line %1, column %2: " + "%3")) + .arg(exception.lineNumber()) + .arg(exception.columnNumber()) + .arg(exception.message()); + return false; +} + +QString Profile::Private::XmlHandler::errorString() const +{ + return mErrorString; +} + +bool Profile::Private::XmlHandler::attributeValueAsBoolean( + const QXmlAttributes &attributes, const QString &qName) +{ + QString tmpStr = attributes.value(qName); + if (tmpStr == QLatin1String("1") || + tmpStr == QLatin1String("true")) { + return true; + } else { + return false; + } +} + + +Profile::Private::Private() + : valid(false), + fake(false), + allowNonIMType(false) +{ +} + +void Profile::Private::setServiceName(const QString &serviceName_) +{ + invalidate(); + + allowNonIMType = false; + serviceName = serviceName_; + lookupProfile(); +} + +void Profile::Private::setFileName(const QString &fileName) +{ + invalidate(); + + allowNonIMType = true; + QFileInfo fi(fileName); + serviceName = fi.baseName(); + + debug() << "Loading profile file" << fileName; + + QFile file(fileName); + if (!file.exists()) { + warning() << QString(QLatin1String("Error parsing profile file %1: file does not exist")) + .arg(file.fileName()); + return; + } + + if (!file.open(QFile::ReadOnly)) { + warning() << QString(QLatin1String("Error parsing profile file %1: " + "cannot open file for readonly access")) + .arg(file.fileName()); + return; + } + + if (parse(&file)) { + debug() << "Profile file" << fileName << "loaded successfully"; + } +} + +void Profile::Private::lookupProfile() +{ + debug() << "Searching profile for service" << serviceName; + + QStringList searchDirs = Profile::searchDirs(); + bool found = false; + foreach (const QString searchDir, searchDirs) { + QString fileName = searchDir + serviceName + QLatin1String(".profile"); + + QFile file(fileName); + if (!file.exists()) { + continue; + } + + if (!file.open(QFile::ReadOnly)) { + continue; + } + + if (parse(&file)) { + debug() << "Profile for service" << serviceName << "found:" << fileName; + found = true; + break; + } + } + + if (!found) { + debug() << "Cannot find valid profile for service" << serviceName; + } +} + +bool Profile::Private::parse(QFile *file) +{ + invalidate(); + + fake = false; + QFileInfo fi(file->fileName()); + XmlHandler xmlHandler(serviceName, allowNonIMType, &data); + + QXmlSimpleReader xmlReader; + xmlReader.setContentHandler(&xmlHandler); + xmlReader.setErrorHandler(&xmlHandler); + + QXmlInputSource xmlInputSource(file); + if (!xmlReader.parse(xmlInputSource)) { + warning() << QString(QLatin1String("Error parsing profile file %1: %2")) + .arg(file->fileName()) + .arg(xmlHandler.errorString()); + invalidate(); + return false; + } + + valid = true; + return true; +} + +void Profile::Private::invalidate() +{ + valid = false; + data.clear(); +} + +/** + * \class Profile + * \ingroup utils + * \headerfile TelepathyQt/profile.h <TelepathyQt/Profile> + * + * \brief The Profile class provides an easy way to read Telepathy profile + * files according to http://telepathy.freedesktop.org/wiki/service-profile-v1. + * + * Note that profiles with xml element <type> different than "IM" are considered + * invalid. + */ + +/** + * Create a new Profile object used to read .profiles compliant files. + * + * \param serviceName The profile service name. + * \return A ProfilePtr object pointing to the newly created Profile object. + */ +ProfilePtr Profile::createForServiceName(const QString &serviceName) +{ + ProfilePtr profile = ProfilePtr(new Profile()); + profile->setServiceName(serviceName); + return profile; +} + +/** + * Create a new Profile object used to read .profiles compliant files. + * + * \param fileName The profile file name. + * \return A ProfilePtr object pointing to the newly created Profile object. + */ +ProfilePtr Profile::createForFileName(const QString &fileName) +{ + ProfilePtr profile = ProfilePtr(new Profile()); + profile->setFileName(fileName); + return profile; +} + +/** + * Construct a new Profile object used to read .profiles compliant files. + * + * \param serviceName The profile service name. + */ +Profile::Profile() + : mPriv(new Private()) +{ +} + +/** + * Construct a fake profile using the given \a serviceName, \a cmName, + * \a protocolName and \a protocolInfo. + * + * - isFake() will return \c true + * - type() will return "IM" + * - provider() will return an empty string + * - serviceName() will return \a serviceName + * - name() and protocolName() will return \a protocolName + * - iconName() will return "im-\a protocolName" + * - cmName() will return \a cmName + * - parameters() will return a list matching CM default parameters + * - presences() will return an empty list and allowOtherPresences will return + * \c true, meaning that CM presences should be used + * - unsupportedChannelClassSpecs() will return an empty list + * + * \param serviceName The service name. + * \param cmName The connection manager name. + * \param protocolName The protocol name. + * \param protocolInfo The protocol info for the protocol \a protocolName. + */ +Profile::Profile(const QString &serviceName, const QString &cmName, + const QString &protocolName, const ProtocolInfo &protocolInfo) + : mPriv(new Private()) +{ + mPriv->serviceName = serviceName; + + mPriv->data.type = QString(QLatin1String("IM")); + // provider is empty + mPriv->data.name = protocolName; + mPriv->data.iconName = QString(QLatin1String("im-%1")).arg(protocolName); + mPriv->data.cmName = cmName; + mPriv->data.protocolName = protocolName; + + foreach (const ProtocolParameter &protocolParam, protocolInfo.parameters()) { + if (!protocolParam.defaultValue().isNull()) { + mPriv->data.parameters.append(Profile::Parameter( + protocolParam.name(), + protocolParam.dbusSignature(), + protocolParam.defaultValue(), + QString(), // label + false)); // mandatory + } + } + + // parameters will be the same as CM parameters + // set allow others to true meaning that the standard CM presences are + // supported + mPriv->data.allowOtherPresences = true; + // presences will be the same as CM presences + // unsupported channel classes is empty + + mPriv->valid = true; + mPriv->fake = true; +} + +/** + * Class destructor. + */ +Profile::~Profile() +{ + delete mPriv; +} + +/** + * Return the unique name of the service to which this profile applies. + * + * \return The unique name of the service. + */ +QString Profile::serviceName() const +{ + return mPriv->serviceName; +} + +/** + * Return whether this profile is valid. + * + * \return \c true if valid, otherwise \c false. + */ +bool Profile::isValid() const +{ + return mPriv->valid; +} + +/** + * Return whether this profile is fake. + * + * Fake profiles are profiles created for services not providing a .profile + * file. + * + * \return \c true if fake, otherwise \c false. + */ +bool Profile::isFake() const +{ + return mPriv->fake; +} + +/** + * Return the type of the service to which this profile applies. + * + * In general, services of interest of Telepathy should be of type 'IM'. + * Other service types exist but are unlikely to affect Telepathy in any way. + * + * \return The type of the service. + */ +QString Profile::type() const +{ + return mPriv->data.type; +} + +/** + * Return the name of the vendor/organisation/provider who actually runs the + * service to which this profile applies. + * + * \return The provider of the service. + */ +QString Profile::provider() const +{ + return mPriv->data.provider; +} + +/** + * Return the human-readable name for the service to which this profile applies. + * + * \return The Human-readable name of the service. + */ +QString Profile::name() const +{ + return mPriv->data.name; +} + +/** + * Return the base name of the icon for the service to which this profile + * applies. + * + * \return The base name of the icon for the service. + */ +QString Profile::iconName() const +{ + return mPriv->data.iconName; +} + +/** + * Return the connection manager name for the service to which this profile + * applies. + * + * \return The connection manager name for the service. + */ +QString Profile::cmName() const +{ + return mPriv->data.cmName; +} + +/** + * Return the protocol name for the service to which this profile applies. + * + * \return The protocol name for the service. + */ +QString Profile::protocolName() const +{ + return mPriv->data.protocolName; +} + +/** + * Return a list of parameters defined for the service to which this profile + * applies. + * + * \return A list of Profile::Parameter. + */ +Profile::ParameterList Profile::parameters() const +{ + return mPriv->data.parameters; +} + +/** + * Return whether this profile defines the parameter named \a name. + * + * \return \c true if parameter is defined, otherwise \c false. + */ +bool Profile::hasParameter(const QString &name) const +{ + foreach (const Parameter ¶meter, mPriv->data.parameters) { + if (parameter.name() == name) { + return true; + } + } + return false; +} + +/** + * Return the parameter for a given \a name. + * + * \return A Profile::Parameter. + */ +Profile::Parameter Profile::parameter(const QString &name) const +{ + foreach (const Parameter ¶meter, mPriv->data.parameters) { + if (parameter.name() == name) { + return parameter; + } + } + return Profile::Parameter(); +} + +/** + * Return whether the standard CM presences not defined in presences() are + * supported. + * + * \return \c true if standard CM presences are supported, otherwise \c false. + */ +bool Profile::allowOtherPresences() const +{ + return mPriv->data.allowOtherPresences; +} + +/** + * Return a list of presences defined for the service to which this profile + * applies. + * + * \return A list of Profile::Presence. + */ +Profile::PresenceList Profile::presences() const +{ + return mPriv->data.presences; +} + +/** + * Return whether this profile defines the presence with id \a id. + * + * \return \c true if presence is defined, otherwise \c false. + */ +bool Profile::hasPresence(const QString &id) const +{ + foreach (const Presence &presence, mPriv->data.presences) { + if (presence.id() == id) { + return true; + } + } + return false; +} + +/** + * Return the presence for a given \a id. + * + * \return A Profile::Presence. + */ +Profile::Presence Profile::presence(const QString &id) const +{ + foreach (const Presence &presence, mPriv->data.presences) { + if (presence.id() == id) { + return presence; + } + } + return Profile::Presence(); +} + +/** + * A list of channel classes not supported by the service to which this profile + * applies. + * + * \return A list of RequestableChannelClassSpec. + */ +RequestableChannelClassSpecList Profile::unsupportedChannelClassSpecs() const +{ + return mPriv->data.unsupportedChannelClassSpecs; +} + +void Profile::setServiceName(const QString &serviceName) +{ + mPriv->setServiceName(serviceName); +} + +void Profile::setFileName(const QString &fileName) +{ + mPriv->setFileName(fileName); +} + +QStringList Profile::searchDirs() +{ + QStringList ret; + + QString xdgDataHome = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME")); + if (xdgDataHome.isEmpty()) { + ret << QDir::homePath() + QLatin1String("/.local/share/data/telepathy/profiles/"); + } else { + ret << xdgDataHome + QLatin1String("/telepathy/profiles/"); + } + + QString xdgDataDirsEnv = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")); + if (xdgDataDirsEnv.isEmpty()) { + ret << QLatin1String("/usr/local/share/telepathy/profiles/"); + ret << QLatin1String("/usr/share/telepathy/profiles/"); + } else { + QStringList xdgDataDirs = xdgDataDirsEnv.split(QLatin1Char(':')); + foreach (const QString xdgDataDir, xdgDataDirs) { + ret << xdgDataDir + QLatin1String("/telepathy/profiles/"); + } + } + + return ret; +} + + +struct TP_QT_NO_EXPORT Profile::Parameter::Private +{ + QString name; + QDBusSignature dbusSignature; + QVariant value; + QString label; + bool mandatory; +}; + +/** + * \class Profile::Parameter + * \ingroup utils + * \headerfile TelepathyQt/profile.h <TelepathyQt/Profile> + * + * \brief The Profile::Parameter class represents a parameter defined in + * .profile files. + */ + +/** + * Construct a new Profile::Parameter object. + */ +Profile::Parameter::Parameter() + : mPriv(new Private) +{ + mPriv->mandatory = false; +} + +/** + * Construct a new Profile::Parameter object that is a copy of \a other. + */ +Profile::Parameter::Parameter(const Parameter &other) + : mPriv(new Private) +{ + mPriv->name = other.mPriv->name; + mPriv->dbusSignature = other.mPriv->dbusSignature; + mPriv->value = other.mPriv->value; + mPriv->label = other.mPriv->label; + mPriv->mandatory = other.mPriv->mandatory; +} + +/** + * Construct a new Profile::Parameter object. + * + * \param name The parameter name. + * \param dbusSignature The parameter D-Bus signature. + * \param value The parameter value. + * \param label The parameter label. + * \param mandatory Whether this parameter is mandatory. + */ +Profile::Parameter::Parameter(const QString &name, + const QDBusSignature &dbusSignature, + const QVariant &value, + const QString &label, + bool mandatory) + : mPriv(new Private) +{ + mPriv->name = name; + mPriv->dbusSignature = dbusSignature; + mPriv->value = value; + mPriv->label = label; + mPriv->mandatory = mandatory; +} + +/** + * Class destructor. + */ +Profile::Parameter::~Parameter() +{ + delete mPriv; +} + +/** + * Return the name of this parameter. + * + * \return The name of this parameter. + */ +QString Profile::Parameter::name() const +{ + return mPriv->name; +} + +void Profile::Parameter::setName(const QString &name) +{ + mPriv->name = name; +} + +/** + * Return the D-Bus signature of this parameter. + * + * \return The D-Bus signature of this parameter. + */ +QDBusSignature Profile::Parameter::dbusSignature() const +{ + return mPriv->dbusSignature; +} + +void Profile::Parameter::setDBusSignature(const QDBusSignature &dbusSignature) +{ + mPriv->dbusSignature = dbusSignature; +} + +/** + * Return the QVariant::Type of this parameter, constructed using + * dbusSignature(). + * + * \return The QVariant::Type of this parameter. + */ +QVariant::Type Profile::Parameter::type() const +{ + return ManagerFile::variantTypeFromDBusSignature(mPriv->dbusSignature.signature()); +} + +/** + * Return the value of this parameter. + * + * If mandatory() returns \c true, the value must not be modified and should be + * used as is when creating accounts for this profile. + * + * \return The value of this parameter. + */ +QVariant Profile::Parameter::value() const +{ + return mPriv->value; +} + +void Profile::Parameter::setValue(const QVariant &value) +{ + mPriv->value = value; +} + +/** + * Return the human-readable label of this parameter. + * + * \return The human-readable label of this parameter. + */ +QString Profile::Parameter::label() const +{ + return mPriv->label; +} + +void Profile::Parameter::setLabel(const QString &label) +{ + mPriv->label = label; +} + +/** + * Return whether this parameter is mandatory, or whether the value returned by + * value() should be used as is when creating accounts for this profile. + * + * \return \c true if mandatory, otherwise \c false. + */ +bool Profile::Parameter::isMandatory() const +{ + return mPriv->mandatory; +} + +void Profile::Parameter::setMandatory(bool mandatory) +{ + mPriv->mandatory = mandatory; +} + +Profile::Parameter &Profile::Parameter::operator=(const Profile::Parameter &other) +{ + mPriv->name = other.mPriv->name; + mPriv->dbusSignature = other.mPriv->dbusSignature; + mPriv->value = other.mPriv->value; + mPriv->label = other.mPriv->label; + mPriv->mandatory = other.mPriv->mandatory; + return *this; +} + + +struct TP_QT_NO_EXPORT Profile::Presence::Private +{ + QString id; + QString label; + QString iconName; + QString message; + bool disabled; +}; + +/** + * \class Profile::Presence + * \ingroup utils + * \headerfile TelepathyQt/profile.h <TelepathyQt/Profile> + * + * \brief The Profile::Presence class represents a presence defined in + * .profile files. + */ + +/** + * Construct a new Profile::Presence object. + */ +Profile::Presence::Presence() + : mPriv(new Private) +{ + mPriv->disabled = false; +} + +/** + * Construct a new Profile::Presence object that is a copy of \a other. + */ +Profile::Presence::Presence(const Presence &other) + : mPriv(new Private) +{ + mPriv->id = other.mPriv->id; + mPriv->label = other.mPriv->label; + mPriv->iconName = other.mPriv->iconName; + mPriv->message = other.mPriv->message; + mPriv->disabled = other.mPriv->disabled; +} + +/** + * Construct a new Profile::Presence object. + * + * \param id The presence id. + * \param label The presence label. + * \param iconName The presence icon name. + * \param message The presence message. + * \param disabled Whether this presence is supported. + */ +Profile::Presence::Presence(const QString &id, + const QString &label, + const QString &iconName, + const QString &message, + bool disabled) + : mPriv(new Private) +{ + mPriv->id = id; + mPriv->label = label; + mPriv->iconName = iconName; + mPriv->message = message; + mPriv->disabled = disabled; +} + +/** + * Class destructor. + */ +Profile::Presence::~Presence() +{ + delete mPriv; +} + +/** + * Return the Telepathy presence id for this presence. + * + * \return The Telepathy presence id for this presence. + */ +QString Profile::Presence::id() const +{ + return mPriv->id; +} + +void Profile::Presence::setId(const QString &id) +{ + mPriv->id = id; +} + +/** + * Return the label that should be used for this presence. + * + * \return The label for this presence. + */ +QString Profile::Presence::label() const +{ + return mPriv->label; +} + +void Profile::Presence::setLabel(const QString &label) +{ + mPriv->label = label; +} + +/** + * Return the icon name of this presence. + * + * \return The icon name of this presence. + */ +QString Profile::Presence::iconName() const +{ + return mPriv->iconName; +} + +void Profile::Presence::setIconName(const QString &iconName) +{ + mPriv->iconName = iconName; +} + +/** + * Return whether user-defined text-message can be attached to this presence. + * + * \return \c true if user-defined text-message can be attached to this presence, \c false + * otherwise. + */ +bool Profile::Presence::canHaveStatusMessage() const +{ + if (mPriv->message == QLatin1String("1") || + mPriv->message == QLatin1String("true")) { + return true; + } + + return false; +} + +/** + * \deprecated Use canHaveStatusMessage() instead. + */ +QString Profile::Presence::message() const +{ + return mPriv->message; +} + +void Profile::Presence::setMessage(const QString &message) +{ + mPriv->message = message; +} + +/** + * Return whether this presence is supported for the service to which this + * profile applies. + * + * \return \c true if supported, otherwise \c false. + */ +bool Profile::Presence::isDisabled() const +{ + return mPriv->disabled; +} + +void Profile::Presence::setDisabled(bool disabled) +{ + mPriv->disabled = disabled; +} + +Profile::Presence &Profile::Presence::operator=(const Profile::Presence &other) +{ + mPriv->id = other.mPriv->id; + mPriv->label = other.mPriv->label; + mPriv->iconName = other.mPriv->iconName; + mPriv->message = other.mPriv->message; + mPriv->disabled = other.mPriv->disabled; + return *this; +} + +} // Tp diff --git a/TelepathyQt/profile.h b/TelepathyQt/profile.h new file mode 100644 index 00000000..bce78969 --- /dev/null +++ b/TelepathyQt/profile.h @@ -0,0 +1,176 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_profile_h_HEADER_GUARD_ +#define _TelepathyQt_profile_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/RequestableChannelClassSpec> +#include <TelepathyQt/Types> + +#include <QDBusSignature> +#include <QObject> +#include <QString> +#include <QVariant> + +namespace Tp +{ + +class ProtocolInfo; + +class TP_QT_EXPORT Profile : public RefCounted +{ + Q_DISABLE_COPY(Profile); + +public: + static ProfilePtr createForServiceName(const QString &serviceName); + static ProfilePtr createForFileName(const QString &fileName); + + ~Profile(); + + QString serviceName() const; + + bool isValid() const; + + bool isFake() const; + + QString type() const; + QString provider() const; + QString name() const; + QString iconName() const; + QString cmName() const; + QString protocolName() const; + + class Parameter + { + public: + Parameter(); + Parameter(const Parameter &other); + Parameter(const QString &name, + const QDBusSignature &dbusSignature, + const QVariant &value, + const QString &label, + bool mandatory); + ~Parameter(); + + QString name() const; + QDBusSignature dbusSignature() const; + QVariant::Type type() const; + QVariant value() const; + QString label() const; + + bool isMandatory() const; + + // TODO Add matches(Tp::Presence) method + + Parameter &operator=(const Parameter &other); + + private: + friend class Profile; + + TP_QT_NO_EXPORT void setName(const QString &name); + TP_QT_NO_EXPORT void setDBusSignature(const QDBusSignature &dbusSignature); + TP_QT_NO_EXPORT void setValue(const QVariant &value); + TP_QT_NO_EXPORT void setLabel(const QString &label); + TP_QT_NO_EXPORT void setMandatory(bool mandatory); + + struct Private; + friend struct Private; + Private *mPriv; + }; + typedef QList<Parameter> ParameterList; + + ParameterList parameters() const; + bool hasParameter(const QString &name) const; + Parameter parameter(const QString &name) const; + + class Presence + { + public: + Presence(); + Presence(const Presence &other); + Presence(const QString &id, + const QString &label, + const QString &iconName, + const QString &message, + bool disabled); + ~Presence(); + + QString id() const; + QString label() const; + QString iconName() const; + bool canHaveStatusMessage() const; + TP_QT_DEPRECATED QString message() const; + + bool isDisabled() const; + + Presence &operator=(const Presence &other); + + private: + friend class Profile; + + TP_QT_NO_EXPORT void setId(const QString &id); + TP_QT_NO_EXPORT void setLabel(const QString &label); + TP_QT_NO_EXPORT void setIconName(const QString &iconName); + TP_QT_NO_EXPORT void setMessage(const QString &message); + TP_QT_NO_EXPORT void setDisabled(bool disabled); + + struct Private; + friend struct Private; + Private *mPriv; + }; + typedef QList<Presence> PresenceList; + + bool allowOtherPresences() const; + PresenceList presences() const; + bool hasPresence(const QString &id) const; + Presence presence(const QString &id) const; + + RequestableChannelClassSpecList unsupportedChannelClassSpecs() const; + +private: + friend class Account; + friend class ProfileManager; + + TP_QT_NO_EXPORT Profile(); + TP_QT_NO_EXPORT Profile(const QString &serviceName, const QString &cmName, + const QString &protocolName, const ProtocolInfo &protocolInfo); + + TP_QT_NO_EXPORT void setServiceName(const QString &serviceName); + TP_QT_NO_EXPORT void setFileName(const QString &fileName); + + TP_QT_NO_EXPORT static QStringList searchDirs(); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::Profile::Parameter); +Q_DECLARE_METATYPE(Tp::Profile::Presence); + +#endif diff --git a/TelepathyQt/properties.cpp b/TelepathyQt/properties.cpp new file mode 100644 index 00000000..1b61da47 --- /dev/null +++ b/TelepathyQt/properties.cpp @@ -0,0 +1,26 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/Properties> + +#include "TelepathyQt/_gen/cli-properties-body.hpp" +#include "TelepathyQt/_gen/cli-properties.moc.hpp" diff --git a/TelepathyQt/properties.h b/TelepathyQt/properties.h new file mode 100644 index 00000000..3d23199b --- /dev/null +++ b/TelepathyQt/properties.h @@ -0,0 +1,51 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_properties_h_HEADER_GUARD_ +#define _TelepathyQt_properties_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +/** + * \addtogroup clientsideproxies Client-side proxies + * + * Proxy objects representing remote service objects accessed via D-Bus. + * + * In addition to providing direct access to methods, signals and properties + * exported by the remote objects, some of these proxies offer features like + * automatic inspection of remote object capabilities, property tracking, + * backwards compatibility helpers for older services and other utilities. + */ + +/** + * \defgroup clientprops Telepathy Properties proxy + * \ingroup clientsideproxies + * + * Proxy object representing the Telepathy Properties interface on remote + * objects. + */ + +#include <TelepathyQt/_gen/cli-properties.h> + +#endif diff --git a/TelepathyQt/properties.xml b/TelepathyQt/properties.xml new file mode 100644 index 00000000..2f59b89c --- /dev/null +++ b/TelepathyQt/properties.xml @@ -0,0 +1,9 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>The Telepathy properties interface</tp:title> + +<xi:include href="../spec/Properties_Interface.xml"/> + +</tp:spec> diff --git a/TelepathyQt/protocol-info.cpp b/TelepathyQt/protocol-info.cpp new file mode 100644 index 00000000..385ea828 --- /dev/null +++ b/TelepathyQt/protocol-info.cpp @@ -0,0 +1,374 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ProtocolInfo> + +#include <TelepathyQt/ConnectionCapabilities> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ProtocolInfo::Private : public QSharedData +{ + Private() + { + } + + Private(const QString &cmName, const QString &name) + : cmName(cmName), + name(name), + iconName(QString(QLatin1String("im-%1")).arg(name)) + { + } + + QString cmName; + QString name; + ProtocolParameterList params; + ConnectionCapabilities caps; + QString vcardField; + QString englishName; + QString iconName; + PresenceSpecList statuses; + AvatarSpec avatarRequirements; +}; + +/** + * \class ProtocolInfo + * \ingroup clientcm + * \headerfile TelepathyQt/protocol-info.h <TelepathyQt/ProtocolInfo> + * + * \brief The ProtocolInfo class represents a <a + * href="http://telepathy.freedesktop.org/spec/Protocol.html">Telepathy Protocol</a>. + */ + +ProtocolInfo::ProtocolInfo() +{ +} + +/** + * Construct a new ProtocolInfo object. + * + * \param cmName Connection manager name. + * \param name Protocol name. + */ +ProtocolInfo::ProtocolInfo(const QString &cmName, const QString &name) + : mPriv(new Private(cmName, name)) +{ +} + +ProtocolInfo::ProtocolInfo(const ProtocolInfo &other) + : mPriv(other.mPriv) +{ +} + +/** + * Class destructor. + */ +ProtocolInfo::~ProtocolInfo() +{ +} + +ProtocolInfo &ProtocolInfo::operator=(const ProtocolInfo &other) +{ + this->mPriv = other.mPriv; + return *this; +} + +/** + * Return the short name of the connection manager (e.g. "gabble") for this protocol. + * + * \return The name of the connection manager for this protocol. + */ +QString ProtocolInfo::cmName() const +{ + if (!isValid()) { + return QString(); + } + + return mPriv->cmName; +} + +/** + * Return the string identifying this protocol as described in the \telepathy_spec + * (e.g. "jabber"). + * + * This identifier is not intended to be displayed to users directly; user + * interfaces are responsible for mapping them to localized strings. + * + * \return A string identifying this protocol. + */ +QString ProtocolInfo::name() const +{ + if (!isValid()) { + return QString(); + } + + return mPriv->name; +} + +/** + * Return all supported parameters for this protocol. The parameters' names + * may either be the well-known strings specified by the \telepathy_spec + * (e.g. "account" and "password"), or implementation-specific strings. + * + * \return A list of parameters for this protocol. + */ +ProtocolParameterList ProtocolInfo::parameters() const +{ + if (!isValid()) { + return ProtocolParameterList(); + } + + return mPriv->params; +} + +/** + * Return whether a given parameter can be passed to the connection + * manager when creating a connection to this protocol. + * + * \param name The name of a parameter. + * \return true if the given parameter exists. + */ +bool ProtocolInfo::hasParameter(const QString &name) const +{ + if (!isValid()) { + return false; + } + + foreach (const ProtocolParameter ¶m, mPriv->params) { + if (param.name() == name) { + return true; + } + } + return false; +} + +/** + * Return whether it might be possible to register new accounts on this + * protocol, by setting the special parameter named + * <code>register</code> to <code>true</code>. + * + * \return The same thing as hasParameter("register"). + * \sa hasParameter() + */ +bool ProtocolInfo::canRegister() const +{ + if (!isValid()) { + return false; + } + + return hasParameter(QLatin1String("register")); +} + +/** + * Return the capabilities that are expected to be available from a connection + * to this protocol, i.e. those for which Connection::createChannel() can + * reasonably be expected to succeed. + * User interfaces can use this information to show or hide UI components. + * + * @return An object representing the capabilities expected to be available from + * a connection to this protocol. + */ +ConnectionCapabilities ProtocolInfo::capabilities() const +{ + if (!isValid()) { + return ConnectionCapabilities(); + } + + return mPriv->caps; +} + +/** + * Return the name of the most common vCard field used for this protocol's + * contact identifiers, normalized to lower case. + * + * One valid use of this field is to answer the question: given a contact's + * vCard containing an X-JABBER field, how can you communicate with the contact? + * By iterating through protocols looking for an x-jabber VCardField, one can + * build up a list of protocols that handle x-jabber, then offer the user a list + * of accounts for those protocols and/or the option to create a new account for + * one of those protocols. + * It is not necessarily valid to interpret contacts' identifiers as values of + * this vCard field. For instance, telepathy-sofiasip supports contacts whose + * identifiers are of the form sip:jenny@example.com or tel:8675309, which would + * not normally both be represented by any single vCard field. + * + * \return The most common vCard field used for this protocol's contact + * identifiers, or an empty string if there is no such field. + */ +QString ProtocolInfo::vcardField() const +{ + if (!isValid()) { + return QString(); + } + + return mPriv->vcardField; +} + +/** + * Return the English-language name of this protocol, such as "AIM" or "Yahoo!". + * + * The name can be used as a fallback if an application doesn't have a localized name for this + * protocol. + * + * If the manager file or the CM service doesn't specify the english name, it is inferred from this + * protocol name, such that for example "google-talk" becomes "Google Talk", but "local-xmpp" + * becomes "Local Xmpp". + * + * \return An English-language name for this protocol. + */ +QString ProtocolInfo::englishName() const +{ + if (!isValid()) { + return QString(); + } + + return mPriv->englishName; +} + +/** + * Return the name of an icon for this protocol in the system's icon theme, such as "im-msn". + * + * If the manager file or the CM service doesn't specify the icon name, "im-<protocolname>" is + * assumed. + * + * \return The likely name of an icon for this protocol. + */ +QString ProtocolInfo::iconName() const +{ + if (!isValid()) { + return QString(); + } + + return mPriv->iconName; +} + +/** + * Return a list of PresenceSpec representing the possible presence statuses + * from a connection to this protocol. + * + * \return A list of PresenceSpec representing the possible presence statuses + * from a connection to this protocol. + */ +PresenceSpecList ProtocolInfo::allowedPresenceStatuses() const +{ + if (!isValid()) { + return PresenceSpecList(); + } + + return mPriv->statuses; +} + +/** + * Return the requirements (size limits, supported MIME types, etc) + * for avatars used on to this protocol. + * + * \return The requirements for avatars used on this protocol. + */ +AvatarSpec ProtocolInfo::avatarRequirements() const +{ + if (!isValid()) { + return AvatarSpec(); + } + + return mPriv->avatarRequirements; +} + +void ProtocolInfo::addParameter(const ParamSpec &spec) +{ + if (!isValid()) { + mPriv = new Private; + } + + QVariant defaultValue; + if (spec.flags & ConnMgrParamFlagHasDefault) { + defaultValue = spec.defaultValue.variant(); + } + + uint flags = spec.flags; + if (spec.name.endsWith(QLatin1String("password"))) { + flags |= ConnMgrParamFlagSecret; + } + + ProtocolParameter param(spec.name, + QDBusSignature(spec.signature), + defaultValue, + (ConnMgrParamFlag) flags); + mPriv->params.append(param); +} + +void ProtocolInfo::setVCardField(const QString &vcardField) +{ + if (!isValid()) { + mPriv = new Private; + } + + mPriv->vcardField = vcardField; +} + +void ProtocolInfo::setEnglishName(const QString &englishName) +{ + if (!isValid()) { + mPriv = new Private; + } + + mPriv->englishName = englishName; +} + +void ProtocolInfo::setIconName(const QString &iconName) +{ + if (!isValid()) { + mPriv = new Private; + } + + mPriv->iconName = iconName; +} + +void ProtocolInfo::setRequestableChannelClasses( + const RequestableChannelClassList &caps) +{ + if (!isValid()) { + mPriv = new Private; + } + + mPriv->caps.updateRequestableChannelClasses(caps); +} + +void ProtocolInfo::setAllowedPresenceStatuses(const PresenceSpecList &statuses) +{ + if (!isValid()) { + mPriv = new Private; + } + + mPriv->statuses = statuses; +} + +void ProtocolInfo::setAvatarRequirements(const AvatarSpec &avatarRequirements) +{ + if (!isValid()) { + mPriv = new Private; + } + + mPriv->avatarRequirements = avatarRequirements; +} + +} // Tp diff --git a/TelepathyQt/protocol-info.h b/TelepathyQt/protocol-info.h new file mode 100644 index 00000000..36d27ca9 --- /dev/null +++ b/TelepathyQt/protocol-info.h @@ -0,0 +1,102 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_protocol_info_h_HEADER_GUARD_ +#define _TelepathyQt_protocol_info_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/AvatarSpec> +#include <TelepathyQt/Global> +#include <TelepathyQt/PresenceSpec> +#include <TelepathyQt/ProtocolParameter> +#include <TelepathyQt/Types> + +#include <QSharedDataPointer> +#include <QString> +#include <QList> + +namespace Tp +{ + +class ConnectionCapabilities; + +class TP_QT_EXPORT ProtocolInfo +{ +public: + ProtocolInfo(); + ProtocolInfo(const ProtocolInfo &other); + ~ProtocolInfo(); + + bool isValid() const { return mPriv.constData() != 0; } + + ProtocolInfo &operator=(const ProtocolInfo &other); + + QString cmName() const; + + QString name() const; + + ProtocolParameterList parameters() const; + bool hasParameter(const QString &name) const; + + bool canRegister() const; + + ConnectionCapabilities capabilities() const; + + QString vcardField() const; + + QString englishName() const; + + QString iconName() const; + + PresenceSpecList allowedPresenceStatuses() const; + + AvatarSpec avatarRequirements() const; + +private: + friend class ConnectionManager; + + TP_QT_NO_EXPORT ProtocolInfo(const QString &cmName, const QString &name); + + TP_QT_NO_EXPORT void addParameter(const ParamSpec &spec); + TP_QT_NO_EXPORT void setVCardField(const QString &vcardField); + TP_QT_NO_EXPORT void setEnglishName(const QString &englishName); + TP_QT_NO_EXPORT void setIconName(const QString &iconName); + TP_QT_NO_EXPORT void setRequestableChannelClasses(const RequestableChannelClassList &caps); + TP_QT_NO_EXPORT void setAllowedPresenceStatuses(const PresenceSpecList &statuses); + TP_QT_NO_EXPORT void setAvatarRequirements(const AvatarSpec &avatarRequirements); + + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +typedef QList<ProtocolInfo> ProtocolInfoList; + +} // Tp + +Q_DECLARE_METATYPE(Tp::ProtocolInfo); +Q_DECLARE_METATYPE(Tp::ProtocolInfoList); + +#endif diff --git a/TelepathyQt/protocol-parameter.cpp b/TelepathyQt/protocol-parameter.cpp new file mode 100644 index 00000000..3f7f3476 --- /dev/null +++ b/TelepathyQt/protocol-parameter.cpp @@ -0,0 +1,176 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/ProtocolParameter> + +#include <TelepathyQt/ManagerFile> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ProtocolParameter::Private : public QSharedData +{ + Private(const QString &name, const QDBusSignature &dbusSignature, QVariant::Type type, + const QVariant &defaultValue, ConnMgrParamFlag flags) + : name(name), dbusSignature(dbusSignature), type(type), defaultValue(defaultValue), + flags(flags) {} + + QString name; + QDBusSignature dbusSignature; + QVariant::Type type; + QVariant defaultValue; + ConnMgrParamFlag flags; +}; + +/** + * \class ProtocolParameter + * \ingroup clientcm + * \headerfile TelepathyQt/protocol-parameter.h <TelepathyQt/ProtocolParameter> + * + * \brief The ProtocolParameter class represents a Telepathy protocol parameter. + */ + + +ProtocolParameter::ProtocolParameter() +{ +} + +ProtocolParameter::ProtocolParameter(const QString &name, + const QDBusSignature &dbusSignature, + QVariant defaultValue, + ConnMgrParamFlag flags) + : mPriv(new Private(name, dbusSignature, + ManagerFile::variantTypeFromDBusSignature(dbusSignature.signature()), defaultValue, + flags)) +{ +} + +ProtocolParameter::ProtocolParameter(const ProtocolParameter &other) + : mPriv(other.mPriv) +{ +} + +ProtocolParameter::~ProtocolParameter() +{ +} + +ProtocolParameter &ProtocolParameter::operator=(const ProtocolParameter &other) +{ + this->mPriv = other.mPriv; + return *this; +} + +bool ProtocolParameter::operator==(const ProtocolParameter &other) const +{ + if (!isValid() || !other.isValid()) { + if (!isValid() && !other.isValid()) { + return true; + } + return false; + } + + return (mPriv->name == other.name()); +} + +bool ProtocolParameter::operator==(const QString &name) const +{ + if (!isValid()) { + return false; + } + + return (mPriv->name == name); +} + +bool ProtocolParameter::operator<(const Tp::ProtocolParameter& other) const +{ + return mPriv->name < other.name(); +} + +QString ProtocolParameter::name() const +{ + if (!isValid()) { + return QString(); + } + + return mPriv->name; +} + +QDBusSignature ProtocolParameter::dbusSignature() const +{ + if (!isValid()) { + return QDBusSignature(); + } + + return mPriv->dbusSignature; +} + +QVariant::Type ProtocolParameter::type() const +{ + if (!isValid()) { + return QVariant::Invalid; + } + + return mPriv->type; +} + +QVariant ProtocolParameter::defaultValue() const +{ + if (!isValid()) { + return QVariant(); + } + + return mPriv->defaultValue; +} + +bool ProtocolParameter::isRequired() const +{ + if (!isValid()) { + return false; + } + + return mPriv->flags & ConnMgrParamFlagRequired; +} + +bool ProtocolParameter::isSecret() const +{ + if (!isValid()) { + return false; + } + + return mPriv->flags & ConnMgrParamFlagSecret; +} + +bool ProtocolParameter::isRequiredForRegistration() const +{ + if (!isValid()) { + return false; + } + + return mPriv->flags & ConnMgrParamFlagRegister; +} + +uint qHash(const ProtocolParameter& parameter) +{ + return qHash(parameter.name()); +} + +} // Tp diff --git a/TelepathyQt/protocol-parameter.h b/TelepathyQt/protocol-parameter.h new file mode 100644 index 00000000..0b017edc --- /dev/null +++ b/TelepathyQt/protocol-parameter.h @@ -0,0 +1,87 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_protocol_parameter_h_HEADER_GUARD_ +#define _TelepathyQt_protocol_parameter_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Global> + +#include <QDBusSignature> +#include <QSharedDataPointer> +#include <QString> +#include <QVariant> + +namespace Tp +{ + +class TP_QT_EXPORT ProtocolParameter +{ +public: + ProtocolParameter(); + ProtocolParameter(const ProtocolParameter &other); + ~ProtocolParameter(); + + bool isValid() const { return mPriv.constData() != 0; } + + ProtocolParameter &operator=(const ProtocolParameter &other); + bool operator==(const ProtocolParameter &other) const; + bool operator==(const QString &name) const; + bool operator<(const ProtocolParameter &other) const; + + QString name() const; + QDBusSignature dbusSignature() const; + QVariant::Type type() const; + QVariant defaultValue() const; + + bool isRequired() const; + bool isSecret() const; + bool isRequiredForRegistration() const; + +private: + friend class ConnectionManager; + friend class ProtocolInfo; + + TP_QT_NO_EXPORT ProtocolParameter(const QString &name, + const QDBusSignature &dbusSignature, + QVariant defaultValue, + ConnMgrParamFlag flags); + + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +typedef QList<ProtocolParameter> ProtocolParameterList; + +uint qHash(const ProtocolParameter ¶meter); + +} // Tp + +Q_DECLARE_METATYPE(Tp::ProtocolParameter); +Q_DECLARE_METATYPE(Tp::ProtocolParameterList); + +#endif diff --git a/TelepathyQt/readiness-helper.cpp b/TelepathyQt/readiness-helper.cpp new file mode 100644 index 00000000..1bc810ab --- /dev/null +++ b/TelepathyQt/readiness-helper.cpp @@ -0,0 +1,684 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/ReadinessHelper> + +#include "TelepathyQt/_gen/readiness-helper.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Constants> +#include <TelepathyQt/DBusProxy> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/RefCounted> +#include <TelepathyQt/SharedPtr> + +#include <QDBusError> +#include <QSharedData> +#include <QTimer> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ReadinessHelper::Introspectable::Private : public QSharedData +{ + Private(const QSet<uint> &makesSenseForStatuses, + const Features &dependsOnFeatures, + const QStringList &dependsOnInterfaces, + IntrospectFunc introspectFunc, + void *introspectFuncData, + bool critical) + : makesSenseForStatuses(makesSenseForStatuses), + dependsOnFeatures(dependsOnFeatures), + dependsOnInterfaces(dependsOnInterfaces), + introspectFunc(introspectFunc), + introspectFuncData(introspectFuncData), + critical(critical) {} + + QSet<uint> makesSenseForStatuses; + Features dependsOnFeatures; + QStringList dependsOnInterfaces; + IntrospectFunc introspectFunc; + void *introspectFuncData; + bool critical; +}; + +ReadinessHelper::Introspectable::Introspectable() + : mPriv(new Private(QSet<uint>(), Features(), QStringList(), 0, 0, false)) +{ +} + +ReadinessHelper::Introspectable::Introspectable(const QSet<uint> &makesSenseForStatuses, + const Features &dependsOnFeatures, const QStringList &dependsOnInterfaces, + IntrospectFunc introspectFunc, void *introspectFuncData, bool critical) + : mPriv(new Private(makesSenseForStatuses, dependsOnFeatures, dependsOnInterfaces, + introspectFunc, introspectFuncData, critical)) +{ +} + +ReadinessHelper::Introspectable::Introspectable(const Introspectable &other) + : mPriv(other.mPriv) +{ +} + +ReadinessHelper::Introspectable::~Introspectable() +{ +} + +ReadinessHelper::Introspectable &ReadinessHelper::Introspectable::operator=( + const Introspectable &other) +{ + mPriv = other.mPriv; + return *this; +} + +struct TP_QT_NO_EXPORT ReadinessHelper::Private +{ + Private(ReadinessHelper *parent, + RefCounted *object, + uint currentStatus, + const Introspectables &introspectables); + Private(ReadinessHelper *parent, + DBusProxy *proxy, + uint currentStatus, + const Introspectables &introspectables); + ~Private(); + + void setCurrentStatus(uint newStatus); + void setIntrospectCompleted(const Feature &feature, bool success, + const QString &errorName = QString(), + const QString &errorMessage = QString()); + void iterateIntrospection(); + Features depsFor(const Feature &feature); // Recursive dependencies for a feature + + void abortOperations(const QString &errorName, const QString &errorMessage); + + ReadinessHelper *parent; + RefCounted *object; + DBusProxy *proxy; + uint currentStatus; + QStringList interfaces; + Introspectables introspectables; + QSet<uint> supportedStatuses; + Features supportedFeatures; + Features satisfiedFeatures; + Features requestedFeatures; + Features missingFeatures; + Features pendingFeatures; + Features inFlightFeatures; + QHash<Feature, QPair<QString, QString> > missingFeaturesErrors; + QList<PendingReady *> pendingOperations; + + bool pendingStatusChange; + uint pendingStatus; +}; + +ReadinessHelper::Private::Private( + ReadinessHelper *parent, + RefCounted *object, + uint currentStatus, + const Introspectables &introspectables) + : parent(parent), + object(object), + proxy(0), + currentStatus(currentStatus), + introspectables(introspectables), + pendingStatusChange(false), + pendingStatus(-1) +{ + for (Introspectables::const_iterator i = introspectables.constBegin(); + i != introspectables.constEnd(); ++i) { + Feature feature = i.key(); + Introspectable introspectable = i.value(); + Q_ASSERT(introspectable.mPriv->introspectFunc != 0); + supportedStatuses += introspectable.mPriv->makesSenseForStatuses; + supportedFeatures += feature; + } +} + +ReadinessHelper::Private::Private( + ReadinessHelper *parent, + DBusProxy *proxy, + uint currentStatus, + const Introspectables &introspectables) + : parent(parent), + object(proxy), + proxy(proxy), + currentStatus(currentStatus), + introspectables(introspectables), + pendingStatusChange(false), + pendingStatus(-1) +{ + Q_ASSERT(proxy != 0); + + for (Introspectables::const_iterator i = introspectables.constBegin(); + i != introspectables.constEnd(); ++i) { + Feature feature = i.key(); + Introspectable introspectable = i.value(); + Q_ASSERT(introspectable.mPriv->introspectFunc != 0); + supportedStatuses += introspectable.mPriv->makesSenseForStatuses; + supportedFeatures += feature; + } +} + +ReadinessHelper::Private::~Private() +{ + const static QString messageDestroyed(QLatin1String("Destroyed")); + + abortOperations(TP_QT_ERROR_CANCELLED, messageDestroyed); +} + +void ReadinessHelper::Private::setCurrentStatus(uint newStatus) +{ + if (currentStatus == newStatus) { + return; + } + + if (inFlightFeatures.isEmpty()) { + currentStatus = newStatus; + satisfiedFeatures.clear(); + missingFeatures.clear(); + + // Make all features that were requested for the new status pending again + pendingFeatures = requestedFeatures; + + // becomeReady ensures that the recursive dependencies of the requested features are already + // in the requested set, so we don't have to re-add them here + + if (supportedStatuses.contains(currentStatus)) { + QTimer::singleShot(0, parent, SLOT(iterateIntrospection())); + } else { + emit parent->statusReady(currentStatus); + } + } else { + debug() << "status changed while introspection process was running"; + pendingStatusChange = true; + pendingStatus = newStatus; + } +} + +void ReadinessHelper::Private::setIntrospectCompleted(const Feature &feature, + bool success, const QString &errorName, const QString &errorMessage) +{ + debug() << "ReadinessHelper::setIntrospectCompleted: feature:" << feature << + "- success:" << success; + if (pendingStatusChange) { + debug() << "ReadinessHelper::setIntrospectCompleted called while there is " + "a pending status change - ignoring"; + + inFlightFeatures.remove(feature); + + // ignore all introspection completed as the state changed + if (!inFlightFeatures.isEmpty()) { + return; + } + pendingStatusChange = false; + setCurrentStatus(pendingStatus); + return; + } + + Q_ASSERT(pendingFeatures.contains(feature)); + Q_ASSERT(inFlightFeatures.contains(feature)); + + if (success) { + satisfiedFeatures.insert(feature); + } + else { + missingFeatures.insert(feature); + missingFeaturesErrors.insert(feature, + QPair<QString, QString>(errorName, errorMessage)); + if (errorName.isEmpty()) { + warning() << "ReadinessHelper::setIntrospectCompleted: Feature" << + feature << "introspection failed but no error message was given"; + } + } + + pendingFeatures.remove(feature); + inFlightFeatures.remove(feature); + + QTimer::singleShot(0, parent, SLOT(iterateIntrospection())); +} + +void ReadinessHelper::Private::iterateIntrospection() +{ + if (proxy && !proxy->isValid()) { + debug() << "ReadinessHelper: not iterating as the proxy is invalidated"; + return; + } + + // When there's a pending status change, we MUST NOT + // - finish PendingReadys (as they'd not be finished in the new status) + // - claim a status as being ready (because the new one isn't) + // and SHOULD NOT + // - fire new introspection jobs (as that would just delay the pending status change even more) + // and NEED NOT + // - flag features as missing (as the completed features will be cleared anyway when starting + // introspection for the new status) + // + // So we can safely skip the rest of this function here. + if (pendingStatusChange) { + debug() << "ReadinessHelper: not iterating as a status change is pending"; + return; + } + + // Flag the currently pending reverse dependencies of any previously discovered missing features + // as missing + foreach (const Feature &feature, pendingFeatures) { + if (!depsFor(feature).intersect(missingFeatures).isEmpty()) { + missingFeatures.insert(feature); + missingFeaturesErrors.insert(feature, + QPair<QString, QString>(QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Feature depends on other features that are not available"))); + } + } + + const Features completedFeatures = satisfiedFeatures + missingFeatures; + + // check if any pending operations for becomeReady should finish now + // based on their requested features having nothing more than what + // satisfiedFeatures + missingFeatures has + QString errorName; + QString errorMessage; + foreach (PendingReady *operation, pendingOperations) { + if ((operation->requestedFeatures() - completedFeatures).isEmpty()) { + if (parent->isReady(operation->requestedFeatures(), &errorName, &errorMessage)) { + operation->setFinished(); + } else { + operation->setFinishedWithError(errorName, errorMessage); + } + + // Remove the operation from tracking, so we don't double-finish it + // + // Qt foreach makes a copy of the container, which will be detached at this point, so + // this is perfectly safe + pendingOperations.removeOne(operation); + } + } + + if ((requestedFeatures - completedFeatures).isEmpty()) { + // Otherwise, we'd emit statusReady with currentStatus although we are supposed to be + // introspecting the pendingStatus and only when that is complete, emit statusReady + Q_ASSERT(!pendingStatusChange); + + // all requested features satisfied or missing + emit parent->statusReady(currentStatus); + return; + } + + // update pendingFeatures with the difference of requested and + // satisfied + missing + pendingFeatures -= completedFeatures; + + // find out which features don't have dependencies that are still pending + Features readyToIntrospect; + foreach (const Feature &feature, pendingFeatures) { + // missing doesn't have to be considered here anymore + if ((introspectables[feature].mPriv->dependsOnFeatures - satisfiedFeatures).isEmpty()) { + readyToIntrospect.insert(feature); + } + } + + // now readyToIntrospect should contain all the features which have + // all their feature dependencies satisfied + foreach (const Feature &feature, readyToIntrospect) { + if (inFlightFeatures.contains(feature)) { + continue; + } + + inFlightFeatures.insert(feature); + + Introspectable introspectable = introspectables[feature]; + + if (!introspectable.mPriv->makesSenseForStatuses.contains(currentStatus)) { + // No-op satisfy features for which nothing has to be done in + // the current state + setIntrospectCompleted(feature, true); + return; // will be called with a single-shot soon again + } + + foreach (const QString &interface, introspectable.mPriv->dependsOnInterfaces) { + if (!interfaces.contains(interface)) { + // If a feature is ready to introspect and depends on a interface + // that is not present the feature can't possibly be satisfied + debug() << "feature" << feature << "depends on interfaces" << + introspectable.mPriv->dependsOnInterfaces << ", but interface" << interface << + "is not present"; + setIntrospectCompleted(feature, false, + QLatin1String(TELEPATHY_ERROR_NOT_AVAILABLE), + QLatin1String("Feature depend on interfaces that are not available")); + return; // will be called with a single-shot soon again + } + } + + // yes, with the dependency info, we can even parallelize + // introspection of several features at once, reducing total round trip + // time considerably with many independent features! + (*(introspectable.mPriv->introspectFunc))(introspectable.mPriv->introspectFuncData); + } +} + +Features ReadinessHelper::Private::depsFor(const Feature &feature) +{ + Features deps; + + foreach (Feature dep, introspectables[feature].mPriv->dependsOnFeatures) { + deps += dep; + deps += depsFor(dep); + } + + return deps; +} + +void ReadinessHelper::Private::abortOperations(const QString &errorName, + const QString &errorMessage) +{ + foreach (PendingReady *operation, pendingOperations) { + operation->setFinishedWithError(errorName, errorMessage); + } + pendingOperations.clear(); +} + +/** + * \class ReadinessHelper + * \ingroup utils + * \headerfile TelepathyQt/readiness-helper.h <TelepathyQt/ReadinessHelper> + * + * \brief The ReadinessHelper class is a helper class used by the introspection + * process. + */ + +/** + * \class ReadinessHelper::Introspectable + * \ingroup utils + * \headerfile TelepathyQt/readiness-helper.h <TelepathyQt/ReadinessHelper> + * + * \brief The ReadinessHelper::Introspectable class represents a introspectable + * used by ReadinessHelper. + */ + +ReadinessHelper::ReadinessHelper(RefCounted *object, + uint currentStatus, + const Introspectables &introspectables, + QObject *parent) + : QObject(parent), + mPriv(new Private(this, object, currentStatus, introspectables)) +{ +} + +ReadinessHelper::ReadinessHelper(DBusProxy *proxy, + uint currentStatus, + const Introspectables &introspectables, + QObject *parent) + : QObject(parent), + mPriv(new Private(this, proxy, currentStatus, introspectables)) +{ +} + +ReadinessHelper::~ReadinessHelper() +{ + delete mPriv; +} + +void ReadinessHelper::addIntrospectables(const Introspectables &introspectables) +{ + // QMap::unite will create multiple items if the key is already in the map + // so let's make sure we don't duplicate keys + for (Introspectables::const_iterator i = introspectables.constBegin(); + i != introspectables.constEnd(); ++i) { + Feature feature = i.key(); + if (mPriv->introspectables.contains(feature)) { + warning() << "ReadinessHelper::addIntrospectables: trying to add an " + "introspectable for feature" << feature << "but introspectable " + "for this feature already exists"; + } else { + Introspectable introspectable = i.value(); + mPriv->introspectables.insert(feature, introspectable); + mPriv->supportedStatuses += introspectable.mPriv->makesSenseForStatuses; + mPriv->supportedFeatures += feature; + } + } + + debug() << "ReadinessHelper: new supportedStatuses =" << mPriv->supportedStatuses; + debug() << "ReadinessHelper: new supportedFeatures =" << mPriv->supportedFeatures; +} + +uint ReadinessHelper::currentStatus() const +{ + return mPriv->currentStatus; +} + +void ReadinessHelper::setCurrentStatus(uint currentStatus) +{ + mPriv->setCurrentStatus(currentStatus); +} + +/** + * Force the current internal status to \a currentStatus. + * + * Note that this method will not start a new introspection or restart the + * current one in case one is running. + * + * This is useful for example when the status is unknown initially but it will + * become known in the first introspection run and there is no need to re-run + * the introspection. + * + * \param currentStatus The status to set. + */ +void ReadinessHelper::forceCurrentStatus(uint currentStatus) +{ + mPriv->currentStatus = currentStatus; +} + +QStringList ReadinessHelper::interfaces() const +{ + return mPriv->interfaces; +} + +void ReadinessHelper::setInterfaces(const QStringList &interfaces) +{ + mPriv->interfaces = interfaces; +} + +Features ReadinessHelper::requestedFeatures() const +{ + return mPriv->requestedFeatures; +} + +Features ReadinessHelper::actualFeatures() const +{ + return mPriv->satisfiedFeatures; +} + +Features ReadinessHelper::missingFeatures() const +{ + return mPriv->missingFeatures; +} + +bool ReadinessHelper::isReady(const Feature &feature, + QString *errorName, QString *errorMessage) const +{ + if (mPriv->proxy && !mPriv->proxy->isValid()) { + if (errorName) { + *errorName = mPriv->proxy->invalidationReason(); + } + if (errorMessage) { + *errorMessage = mPriv->proxy->invalidationMessage(); + } + return false; + } + + if (!mPriv->supportedFeatures.contains(feature)) { + if (errorName) { + *errorName = QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT); + } + if (errorMessage) { + *errorMessage = QLatin1String("Unsupported feature"); + } + return false; + } + + bool ret = true; + + if (feature.isCritical()) { + if (!mPriv->satisfiedFeatures.contains(feature)) { + ret = false; + } + } else { + if (!mPriv->satisfiedFeatures.contains(feature) && + !mPriv->missingFeatures.contains(feature)) { + ret = false; + } + } + + if (!ret) { + QPair<QString, QString> error = mPriv->missingFeaturesErrors[feature]; + if (errorName) { + *errorName = error.first; + } + if (errorMessage) { + *errorMessage = error.second; + } + } + + return ret; +} + +bool ReadinessHelper::isReady(const Features &features, QString *errorName, QString *errorMessage) const +{ + if (mPriv->proxy && !mPriv->proxy->isValid()) { + if (errorName) { + *errorName = mPriv->proxy->invalidationReason(); + } + if (errorMessage) { + *errorMessage = mPriv->proxy->invalidationMessage(); + } + return false; + } + + Q_ASSERT(!features.isEmpty()); + + foreach (const Feature &feature, features) { + if (!isReady(feature, errorName, errorMessage)) { + return false; + } + } + return true; +} + +PendingReady *ReadinessHelper::becomeReady(const Features &requestedFeatures) +{ + Q_ASSERT(!requestedFeatures.isEmpty()); + + if (mPriv->proxy) { + connect(mPriv->proxy, + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + this, + SLOT(onProxyInvalidated(Tp::DBusProxy*,QString,QString)), + Qt::UniqueConnection); + + if (!mPriv->proxy->isValid()) { + PendingReady *operation = new PendingReady(SharedPtr<RefCounted>(mPriv->object), + requestedFeatures); + operation->setFinishedWithError(mPriv->proxy->invalidationReason(), + mPriv->proxy->invalidationMessage()); + return operation; + } + } + + Features supportedFeatures = mPriv->supportedFeatures; + if (supportedFeatures.intersect(requestedFeatures) != requestedFeatures) { + warning() << "ReadinessHelper::becomeReady called with invalid features: requestedFeatures =" << + requestedFeatures << "- supportedFeatures =" << mPriv->supportedFeatures; + PendingReady *operation = new PendingReady(SharedPtr<RefCounted>(mPriv->object), + requestedFeatures); + operation->setFinishedWithError( + QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Requested features contains unsupported feature")); + return operation; + } + + if (mPriv->proxy && !mPriv->proxy->isValid()) { + PendingReady *operation = new PendingReady(SharedPtr<RefCounted>(mPriv->object), + requestedFeatures); + operation->setFinishedWithError(mPriv->proxy->invalidationReason(), + mPriv->proxy->invalidationMessage()); + return operation; + } + + PendingReady *operation; + foreach (operation, mPriv->pendingOperations) { + if (operation->requestedFeatures() == requestedFeatures) { + return operation; + } + } + + // Insert the dependencies of the requested features too + Features requestedWithDeps = requestedFeatures; + foreach (const Feature &feature, requestedFeatures) { + requestedWithDeps.unite(mPriv->depsFor(feature)); + } + + mPriv->requestedFeatures += requestedWithDeps; + mPriv->pendingFeatures += requestedWithDeps; // will be updated in iterateIntrospection + + operation = new PendingReady(SharedPtr<RefCounted>(mPriv->object), requestedFeatures); + mPriv->pendingOperations.append(operation); + // Only we finish these PendingReadys, so we don't need destroyed or finished handling for them + // - we already know when that happens, as we caused it! + + QTimer::singleShot(0, this, SLOT(iterateIntrospection())); + + return operation; +} + +void ReadinessHelper::setIntrospectCompleted(const Feature &feature, bool success, + const QString &errorName, const QString &errorMessage) +{ + if (mPriv->proxy && !mPriv->proxy->isValid()) { + // proxy became invalid, ignore here + return; + } + mPriv->setIntrospectCompleted(feature, success, errorName, errorMessage); +} + +void ReadinessHelper::setIntrospectCompleted(const Feature &feature, bool success, + const QDBusError &error) +{ + setIntrospectCompleted(feature, success, error.name(), error.message()); +} + +void ReadinessHelper::iterateIntrospection() +{ + mPriv->iterateIntrospection(); +} + +void ReadinessHelper::onProxyInvalidated(DBusProxy *proxy, + const QString &errorName, const QString &errorMessage) +{ + // clear satisfied and missing features as we have public methods to get them + mPriv->satisfiedFeatures.clear(); + mPriv->missingFeatures.clear(); + + mPriv->abortOperations(errorName, errorMessage); +} + +} // Tp diff --git a/TelepathyQt/readiness-helper.h b/TelepathyQt/readiness-helper.h new file mode 100644 index 00000000..d570d172 --- /dev/null +++ b/TelepathyQt/readiness-helper.h @@ -0,0 +1,130 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_readiness_helper_h_HEADER_GUARD_ +#define _TelepathyQt_readiness_helper_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Feature> + +#include <QMap> +#include <QSet> +#include <QSharedDataPointer> +#include <QStringList> + +class QDBusError; + +namespace Tp +{ + +class DBusProxy; +class PendingOperation; +class PendingReady; +class RefCounted; + +class TP_QT_EXPORT ReadinessHelper : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(ReadinessHelper) + +public: + typedef void (*IntrospectFunc)(void *data); + + struct Introspectable { + public: + Introspectable(); + Introspectable(const QSet<uint> &makesSenseForStatuses, + const Features &dependsOnFeatures, + const QStringList &dependsOnInterfaces, + IntrospectFunc introspectFunc, + void *introspectFuncData, + bool critical = false); + Introspectable(const Introspectable &other); + ~Introspectable(); + + Introspectable &operator=(const Introspectable &other); + + private: + friend class ReadinessHelper; + + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; + }; + typedef QMap<Feature, Introspectable> Introspectables; + + ReadinessHelper(RefCounted *object, + uint currentStatus = 0, + const Introspectables &introspectables = Introspectables(), + QObject *parent = 0); + ReadinessHelper(DBusProxy *proxy, + uint currentStatus = 0, + const Introspectables &introspectables = Introspectables(), + QObject *parent = 0); + ~ReadinessHelper(); + + void addIntrospectables(const Introspectables &introspectables); + + uint currentStatus() const; + void setCurrentStatus(uint currentStatus); + void forceCurrentStatus(uint currentStatus); + + QStringList interfaces() const; + void setInterfaces(const QStringList &interfaces); + + Features requestedFeatures() const; + Features actualFeatures() const; + Features missingFeatures() const; + + bool isReady(const Feature &feature, + QString *errorName = 0, QString *errorMessage = 0) const; + bool isReady(const Features &features, + QString *errorName = 0, QString *errorMessage = 0) const; + PendingReady *becomeReady(const Features &requestedFeatures); + + void setIntrospectCompleted(const Feature &feature, bool success, + const QString &errorName = QString(), + const QString &errorMessage = QString()); + void setIntrospectCompleted(const Feature &feature, bool success, + const QDBusError &error); + +Q_SIGNALS: + void statusReady(uint status); + +private Q_SLOTS: + TP_QT_NO_EXPORT void iterateIntrospection(); + + TP_QT_NO_EXPORT void onProxyInvalidated(Tp::DBusProxy *proxy, + const QString &errorName, const QString &errorMessage); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/ready-object.cpp b/TelepathyQt/ready-object.cpp new file mode 100644 index 00000000..4c7037ff --- /dev/null +++ b/TelepathyQt/ready-object.cpp @@ -0,0 +1,161 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/ReadyObject> + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/ReadinessHelper> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ReadyObject::Private +{ + Private(ReadyObject *parent, RefCounted *object, Feature featureCore); + Private(ReadyObject *parent, DBusProxy *proxy, Feature featureCore); + ~Private(); + + ReadyObject *parent; + const Features coreFeatures; + ReadinessHelper *readinessHelper; +}; + +ReadyObject::Private::Private(ReadyObject *parent, RefCounted *object, + Feature featureCore) + : parent(parent), + coreFeatures(Features() << featureCore), + readinessHelper(new ReadinessHelper(object)) +{ +} + +ReadyObject::Private::Private(ReadyObject *parent, DBusProxy *proxy, + Feature featureCore) + : parent(parent), + coreFeatures(Features() << featureCore), + readinessHelper(new ReadinessHelper(proxy)) +{ +} + +ReadyObject::Private::~Private() +{ + delete readinessHelper; +} + +/** + * \class ReadyObject + * \ingroup clientreadiness + * \headerfile TelepathyQt/ready-object.h> <TelepathyQt/ReadyObject> + */ + +/** + * Construct a new ReadyObject object. + * + * \param object The RefCounted the object refers to. + * \param featureCore The core feature of the object. + */ +ReadyObject::ReadyObject(RefCounted *object, const Feature &featureCore) + : mPriv(new Private(this, object, featureCore)) +{ +} + +/** + * Construct a new ReadyObject object. + * + * \param proxy The DBusProxy the object refers to. + * \param featureCore The core feature of the object. + */ +ReadyObject::ReadyObject(DBusProxy *proxy, const Feature &featureCore) + : mPriv(new Private(this, proxy, featureCore)) +{ +} + +/** + * Class destructor. + */ +ReadyObject::~ReadyObject() +{ + delete mPriv; +} + +/** + * Return whether this object has finished its initial setup. + * + * This is mostly useful as a sanity check, in code that shouldn't be run + * until the object is ready. To wait for the object to be ready, call + * becomeReady() and connect to the finished signal on the result. + * + * \param features The features which should be tested + * \return \c true if the object has finished its initial setup for basic + * functionality plus the given features + */ +bool ReadyObject::isReady(const Features &features) const +{ + if (features.isEmpty()) { + return mPriv->readinessHelper->isReady(mPriv->coreFeatures); + } + return mPriv->readinessHelper->isReady(features); +} + +/** + * Return a pending operation which will succeed when this object finishes + * its initial setup, or will fail if a fatal error occurs during this + * initial setup. + * + * If an empty set is used FeatureCore will be considered as the requested + * feature. + * + * \param requestedFeatures The features which should be enabled + * \return A PendingReady object which will emit finished + * when this object has finished or failed initial setup for basic + * functionality plus the given features + */ +PendingReady *ReadyObject::becomeReady(const Features &requestedFeatures) +{ + if (requestedFeatures.isEmpty()) { + return mPriv->readinessHelper->becomeReady(mPriv->coreFeatures); + } + return mPriv->readinessHelper->becomeReady(requestedFeatures); +} + +Features ReadyObject::requestedFeatures() const +{ + return mPriv->readinessHelper->requestedFeatures(); +} + +Features ReadyObject::actualFeatures() const +{ + return mPriv->readinessHelper->actualFeatures(); +} + +Features ReadyObject::missingFeatures() const +{ + return mPriv->readinessHelper->missingFeatures(); +} + +ReadinessHelper *ReadyObject::readinessHelper() const +{ + return mPriv->readinessHelper; +} + +} // Tp diff --git a/TelepathyQt/ready-object.h b/TelepathyQt/ready-object.h new file mode 100644 index 00000000..d963f9e8 --- /dev/null +++ b/TelepathyQt/ready-object.h @@ -0,0 +1,69 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_ready_object_h_HEADER_GUARD_ +#define _TelepathyQt_ready_object_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Feature> + +#include <QObject> + +namespace Tp +{ + +class DBusProxy; +class PendingReady; +class ReadinessHelper; +class RefCounted; + +class TP_QT_EXPORT ReadyObject +{ + Q_DISABLE_COPY(ReadyObject) + +public: + ReadyObject(RefCounted *object, const Feature &featureCore); + ReadyObject(DBusProxy *proxy, const Feature &featureCore); + virtual ~ReadyObject(); + + virtual bool isReady(const Features &features = Features()) const; + virtual PendingReady *becomeReady(const Features &requestedFeatures = Features()); + + virtual Features requestedFeatures() const; + virtual Features actualFeatures() const; + virtual Features missingFeatures() const; + +protected: + ReadinessHelper *readinessHelper() const; + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/referenced-handles.cpp b/TelepathyQt/referenced-handles.cpp new file mode 100644 index 00000000..475e5c2e --- /dev/null +++ b/TelepathyQt/referenced-handles.cpp @@ -0,0 +1,333 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2009 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 <TelepathyQt/ReferencedHandles> + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Connection> + +#include <QPointer> +#include <QSharedData> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT ReferencedHandles::Private : public QSharedData +{ + QWeakPointer<Connection> connection; + HandleType handleType; + UIntList handles; + + Private() + { + handleType = HandleTypeNone; + } + + Private(const ConnectionPtr &conn, HandleType handleType, + const UIntList &handles) + : connection(conn.data()), handleType(handleType), handles(handles) + { + Q_ASSERT(!conn.isNull()); + Q_ASSERT(handleType != 0); + + foreach (uint handle, handles) { + conn->refHandle(handleType, handle); + } + } + + Private(const Private &a) + : QSharedData(a), + connection(a.connection), + handleType(a.handleType), + handles(a.handles) + { + if (!handles.isEmpty()) { + ConnectionPtr conn(connection); + if (!conn) { + debug() << " Destroyed after Connection, so the Connection " + "has already released the handles"; + return; + } + + for (const_iterator i = handles.constBegin(); i != handles.constEnd(); ++i) { + conn->refHandle(handleType, *i); + } + } + } + + ~Private() + { + if (!handles.isEmpty()) { + ConnectionPtr conn(connection); + if (!conn) { + debug() << " Destroyed after Connection, so the Connection " + "has already released the handles"; + return; + } + + for (const_iterator i = handles.constBegin(); i != handles.constEnd(); ++i) { + conn->unrefHandle(handleType, *i); + } + } + } + +private: + void operator=(const Private&); +}; + +/** + * \class ReferencedHandles + * \ingroup clientconn + * \headerfile TelepathyQt/referenced-handles.h <TelepathyQt/ReferencedHandles> + * + * \brief Helper container for safe management of handle lifetimes. Every handle + * in a ReferencedHandles container is guaranteed to be valid (and stay valid, + * as long it's in at least one ReferencedHandles container). + * + * The class offers a QList-style API. However, from the mutable operations, + * only the operations for which the validity guarantees can be preserved are + * provided. This means no functions which can add an arbitrary handle to the + * container are included - the only way to add handles to the container is to + * reference them using Connection::referenceHandles() and appending the + * resulting ReferenceHandles instance. + * + * ReferencedHandles is a implicitly shared class. + */ + +ReferencedHandles::ReferencedHandles() + : mPriv(new Private) +{ +} + +ReferencedHandles::ReferencedHandles(const ReferencedHandles &other) + : mPriv(other.mPriv) +{ +} + +ReferencedHandles::~ReferencedHandles() +{ +} + +ConnectionPtr ReferencedHandles::connection() const +{ + return ConnectionPtr(mPriv->connection); +} + +HandleType ReferencedHandles::handleType() const +{ + return mPriv->handleType; +} + +uint ReferencedHandles::at(int i) const +{ + return mPriv->handles[i]; +} + +uint ReferencedHandles::value(int i, uint defaultValue) const +{ + return mPriv->handles.value(i, defaultValue); +} + +ReferencedHandles::const_iterator ReferencedHandles::begin() const +{ + return mPriv->handles.begin(); +} + +ReferencedHandles::const_iterator ReferencedHandles::end() const +{ + return mPriv->handles.end(); +} + +bool ReferencedHandles::contains(uint handle) const +{ + return mPriv->handles.contains(handle); +} + +int ReferencedHandles::count(uint handle) const +{ + return mPriv->handles.count(handle); +} + +int ReferencedHandles::indexOf(uint handle, int from) const +{ + return mPriv->handles.indexOf(handle, from); +} + +bool ReferencedHandles::isEmpty() const +{ + return mPriv->handles.isEmpty(); +} + +int ReferencedHandles::lastIndexOf(uint handle, int from) const +{ + return mPriv->handles.lastIndexOf(handle, from); +} + +ReferencedHandles ReferencedHandles::mid(int pos, int length) const +{ + return ReferencedHandles(connection(), handleType(), + mPriv->handles.mid(pos, length)); +} + +int ReferencedHandles::size() const +{ + return mPriv->handles.size(); +} + +void ReferencedHandles::clear() +{ + if (!mPriv->handles.empty()) { + ConnectionPtr conn(mPriv->connection); + if (conn) { + foreach (uint handle, mPriv->handles) { + conn->unrefHandle(handleType(), handle); + } + } else { + warning() << "Connection already destroyed in " + "ReferencedHandles::clear() so can't unref!"; + } + } + + mPriv->handles.clear(); +} + +void ReferencedHandles::move(int from, int to) +{ + mPriv->handles.move(from, to); +} + +int ReferencedHandles::removeAll(uint handle) +{ + int count = mPriv->handles.removeAll(handle); + + if (count > 0) { + ConnectionPtr conn(mPriv->connection); + if (conn) { + for (int i = 0; i < count; ++i) { + conn->unrefHandle(handleType(), handle); + } + } else { + warning() << "Connection already destroyed in " + "ReferencedHandles::removeAll() with handle ==" << + handle << "so can't unref!"; + } + } + + return count; +} + +void ReferencedHandles::removeAt(int i) +{ + ConnectionPtr conn(mPriv->connection); + if (conn) { + conn->unrefHandle(handleType(), at(i)); + } else { + warning() << "Connection already destroyed in " + "ReferencedHandles::removeAt() with i ==" << + i << "so can't unref!"; + } + + mPriv->handles.removeAt(i); +} + +bool ReferencedHandles::removeOne(uint handle) +{ + bool wasThere = mPriv->handles.removeOne(handle); + + if (wasThere) { + ConnectionPtr conn(mPriv->connection); + if (conn) { + conn->unrefHandle(handleType(), handle); + } else { + warning() << "Connection already destroyed in " + "ReferencedHandles::removeOne() with handle ==" << + handle << "so can't unref!"; + } + } + + return wasThere; +} + +void ReferencedHandles::swap(int i, int j) +{ + mPriv->handles.swap(i, j); +} + +uint ReferencedHandles::takeAt(int i) +{ + ConnectionPtr conn(mPriv->connection); + if (conn) { + conn->unrefHandle(handleType(), at(i)); + } else { + warning() << "Connection already destroyed in " + "ReferencedHandles::takeAt() with i ==" << + i << "so can't unref!"; + } + + return mPriv->handles.takeAt(i); +} + +ReferencedHandles ReferencedHandles::operator+(const ReferencedHandles &another) const +{ + if (connection() != another.connection() || + handleType() != another.handleType()) { + warning() << "Tried to concatenate ReferencedHandles instances " + "with different connection and/or handle type"; + return *this; + } + + return ReferencedHandles(connection(), handleType(), + mPriv->handles + another.mPriv->handles); +} + +ReferencedHandles &ReferencedHandles::operator=( + const ReferencedHandles &another) +{ + mPriv = another.mPriv; + return *this; +} + +bool ReferencedHandles::operator==(const ReferencedHandles &another) const +{ + return connection() == another.connection() && + handleType() == another.handleType() && + mPriv->handles == another.mPriv->handles; +} + +bool ReferencedHandles::operator==(const UIntList &list) const +{ + return mPriv->handles == list; +} + +UIntList ReferencedHandles::toList() const +{ + return mPriv->handles; +} + +ReferencedHandles::ReferencedHandles(const ConnectionPtr &connection, + HandleType handleType, const UIntList &handles) + : mPriv(new Private(connection, handleType, handles)) +{ +} + +} // Tp diff --git a/TelepathyQt/referenced-handles.h b/TelepathyQt/referenced-handles.h new file mode 100644 index 00000000..71dd0e2f --- /dev/null +++ b/TelepathyQt/referenced-handles.h @@ -0,0 +1,264 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2009 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 + */ + +#ifndef _TelepathyQt_referenced_handles_h_HEADER_GUARD_ +#define _TelepathyQt_referenced_handles_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Types> + +#ifndef QT_NO_STL +# include <list> +#endif + +#include <QList> +#include <QSet> +#include <QSharedDataPointer> +#include <QVector> + +namespace Tp +{ + +class Connection; + +class TP_QT_EXPORT ReferencedHandles +{ +public: + typedef UIntList::const_iterator const_iterator; + typedef UIntList::ConstIterator ConstIterator; + typedef UIntList::const_pointer const_pointer; + typedef UIntList::const_reference const_reference; + typedef UIntList::difference_type difference_type; + typedef UIntList::pointer pointer; + typedef UIntList::reference reference; + typedef UIntList::size_type size_type; + typedef UIntList::value_type value_type; + + ReferencedHandles(); + ReferencedHandles(const ReferencedHandles &other); + ~ReferencedHandles(); + + ConnectionPtr connection() const; + HandleType handleType() const; + + uint at(int i) const; + + inline uint back() const + { + return last(); + } + + inline uint first() const + { + return at(0); + } + + inline uint front() const + { + return first(); + } + + inline uint last() const + { + return at(size() - 1); + } + + uint value(int i, uint defaultValue = 0) const; + + const_iterator begin() const; + + inline const_iterator constBegin() const + { + return begin(); + } + + inline const_iterator constEnd() const + { + return end(); + } + + const_iterator end() const; + + bool contains(uint handle) const; + + int count(uint handle) const; + + inline int count() const + { + return size(); + } + + inline bool empty() const + { + return isEmpty(); + } + + inline bool endsWith(uint handle) const + { + return !isEmpty() && last() == handle; + } + + int indexOf(uint handle, int from = 0) const; + + bool isEmpty() const; + + int lastIndexOf(uint handle, int from = -1) const; + + inline int length() const + { + return count(); + } + + ReferencedHandles mid(int pos, int length = -1) const; + + int size() const; + + inline bool startsWith(uint handle) const + { + return !isEmpty() && first() == handle; + } + + inline void append(const ReferencedHandles& another) + { + *this = *this + another; + } + + void clear(); + void move(int from, int to); + + inline void pop_back() + { + return removeLast(); + } + + inline void pop_front() + { + return removeFirst(); + } + + int removeAll(uint handle); + + void removeAt(int i); + + inline void removeFirst() + { + return removeAt(0); + } + + inline void removeLast() + { + return removeAt(size() - 1); + } + + bool removeOne(uint handle); + + void swap(int i, int j); + + uint takeAt(int i); + + inline uint takeFirst() + { + return takeAt(0); + } + + inline uint takeLast() + { + return takeAt(size() - 1); + } + + bool operator!=(const ReferencedHandles& another) const + { + return !(*this == another); + } + + bool operator!=(const UIntList& another) const + { + return !(*this == another); + } + + ReferencedHandles operator+(const ReferencedHandles& another) const; + + inline ReferencedHandles& operator+=(const ReferencedHandles& another) + { + return *this = (*this + another); + } + + ReferencedHandles& operator<<(const ReferencedHandles& another) + { + return *this += another; + } + + ReferencedHandles& operator=(const ReferencedHandles& another); + + bool operator==(const ReferencedHandles& another) const; + bool operator==(const UIntList& list) const; + + inline uint operator[](int i) const + { + return at(i); + } + + UIntList toList() const; + + inline QSet<uint> toSet() const + { + return toList().toSet(); + } + +#ifndef QT_NO_STL + inline std::list<uint> toStdList() const + { + return toList().toStdList(); + } +#endif + + inline QVector<uint> toVector() const + { + return toList().toVector(); + } + +private: + // For access to the "prime" constructor + friend class ContactManager; + friend class PendingContactAttributes; + friend class PendingContacts; + friend class PendingHandles; + + TP_QT_NO_EXPORT ReferencedHandles(const ConnectionPtr &connection, + HandleType handleType, const UIntList& handles); + + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +typedef QListIterator<uint> ReferencedHandlesIterator; + +} // Tp + +Q_DECLARE_METATYPE(Tp::ReferencedHandles); + +#endif diff --git a/TelepathyQt/request-temporary-handler-internal.cpp b/TelepathyQt/request-temporary-handler-internal.cpp new file mode 100644 index 00000000..14b47923 --- /dev/null +++ b/TelepathyQt/request-temporary-handler-internal.cpp @@ -0,0 +1,135 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 "TelepathyQt/request-temporary-handler-internal.h" + +#include "TelepathyQt/_gen/request-temporary-handler-internal.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/ChannelClassSpecList> + +namespace Tp +{ + +SharedPtr<RequestTemporaryHandler> RequestTemporaryHandler::create(const AccountPtr &account) +{ + return SharedPtr<RequestTemporaryHandler>(new RequestTemporaryHandler(account)); +} + +RequestTemporaryHandler::RequestTemporaryHandler(const AccountPtr &account) + : AbstractClient(), + QObject(), + AbstractClientHandler(ChannelClassSpecList(), AbstractClientHandler::Capabilities(), false), + mAccount(account), + mQueueChannelReceived(true), + dbusHandlerInvoked(false) +{ +} + +RequestTemporaryHandler::~RequestTemporaryHandler() +{ +} + +void RequestTemporaryHandler::handleChannels( + const MethodInvocationContextPtr<> &context, + const AccountPtr &account, + const ConnectionPtr &connection, + const QList<ChannelPtr> &channels, + const QList<ChannelRequestPtr> &requestsSatisfied, + const QDateTime &userActionTime, + const HandlerInfo &handlerInfo) +{ + Q_ASSERT(dbusHandlerInvoked); + + QString errorMessage; + + ChannelPtr oldChannel = channel(); + if (channels.size() != 1 || requestsSatisfied.size() != 1) { + errorMessage = QLatin1String("Only one channel and one channel request should be given " + "to HandleChannels"); + } else if (account != mAccount) { + errorMessage = QLatin1String("Account received is not the same as the account which made " + "the request"); + } else if (oldChannel && oldChannel != channels.first()) { + errorMessage = QLatin1String("Received a channel that is not the same as the first " + "one received"); + } + + if (!errorMessage.isEmpty()) { + warning() << "Handling channel failed with" << TP_QT_ERROR_SERVICE_CONFUSED << ":" << + errorMessage; + + // Only emit error if we didn't receive any channel yet. + if (!oldChannel) { + emit error(TP_QT_ERROR_SERVICE_CONFUSED, errorMessage); + } + context->setFinishedWithError(TP_QT_ERROR_SERVICE_CONFUSED, errorMessage); + return; + } + + ChannelRequestPtr channelRequest = requestsSatisfied.first(); + + if (!oldChannel) { + mChannel = QWeakPointer<Channel>(channels.first().data()); + emit channelReceived(channel(), userActionTime, channelRequest->hints()); + } else { + if (mQueueChannelReceived) { + mChannelReceivedQueue.enqueue(qMakePair(userActionTime, channelRequest->hints())); + } else { + emit channelReceived(oldChannel, userActionTime, channelRequest->hints()); + } + } + + context->setFinished(); +} + +void RequestTemporaryHandler::setQueueChannelReceived(bool queue) +{ + mQueueChannelReceived = queue; + if (!queue) { + processChannelReceivedQueue(); + } +} + +void RequestTemporaryHandler::setDBusHandlerInvoked() +{ + dbusHandlerInvoked = true; +} + +void RequestTemporaryHandler::setDBusHandlerErrored(const QString &errorName, const QString &errorMessage) +{ + Q_ASSERT(dbusHandlerInvoked); + if (!channel()) { + emit error(errorName, errorMessage); + } +} + +void RequestTemporaryHandler::processChannelReceivedQueue() +{ + while (!mChannelReceivedQueue.isEmpty()) { + QPair<QDateTime, ChannelRequestHints> info = mChannelReceivedQueue.dequeue(); + emit channelReceived(channel(), info.first, info.second); + } +} + +} // Tp diff --git a/TelepathyQt/request-temporary-handler-internal.h b/TelepathyQt/request-temporary-handler-internal.h new file mode 100644 index 00000000..43eb87d3 --- /dev/null +++ b/TelepathyQt/request-temporary-handler-internal.h @@ -0,0 +1,91 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +#ifndef _TelepathyQt_request_temporary_handler_internal_h_HEADER_GUARD_ +#define _TelepathyQt_request_temporary_handler_internal_h_HEADER_GUARD_ + +#include <QWeakPointer> + +#include <TelepathyQt/AbstractClientHandler> +#include <TelepathyQt/Account> +#include <TelepathyQt/Channel> + +namespace Tp +{ + +class TP_QT_NO_EXPORT RequestTemporaryHandler : public QObject, public AbstractClientHandler +{ + Q_OBJECT + +public: + static SharedPtr<RequestTemporaryHandler> create(const AccountPtr &account); + + ~RequestTemporaryHandler(); + + AccountPtr account() const { return mAccount; } + ChannelPtr channel() const { return ChannelPtr(mChannel); } + + /** + * Handlers we request ourselves never go through the approvers but this + * handler shouldn't get any channels we didn't request - hence let's make + * this always false to leave slightly less room for the CD to get confused and + * give some channel we didn't request to us, without even asking an approver + * first. Though if the CD isn't confused it shouldn't really matter - our filter + * is empty anyway. + */ + bool bypassApproval() const { return false; } + + void handleChannels(const MethodInvocationContextPtr<> &context, + const AccountPtr &account, + const ConnectionPtr &connection, + const QList<ChannelPtr> &channels, + const QList<ChannelRequestPtr> &requestsSatisfied, + const QDateTime &userActionTime, + const HandlerInfo &handlerInfo); + + void setQueueChannelReceived(bool queue); + + void setDBusHandlerInvoked(); + void setDBusHandlerErrored(const QString &errorName, const QString &errorMessage); + + bool isDBusHandlerInvoked() const { return dbusHandlerInvoked; } + +Q_SIGNALS: + void error(const QString &errorName, const QString &errorMessage); + void channelReceived(const Tp::ChannelPtr &channel, const QDateTime &userActionTime, + const Tp::ChannelRequestHints &requestHints); + +private: + RequestTemporaryHandler(const AccountPtr &account); + + void processChannelReceivedQueue(); + + AccountPtr mAccount; + QWeakPointer<Channel> mChannel; + bool mQueueChannelReceived; + QQueue<QPair<QDateTime, ChannelRequestHints> > mChannelReceivedQueue; + bool dbusHandlerInvoked; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/requestable-channel-class-spec.cpp b/TelepathyQt/requestable-channel-class-spec.cpp new file mode 100644 index 00000000..bf8bbc4e --- /dev/null +++ b/TelepathyQt/requestable-channel-class-spec.cpp @@ -0,0 +1,494 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/RequestableChannelClassSpec> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT RequestableChannelClassSpec::Private : public QSharedData +{ + Private(const RequestableChannelClass &rcc) + : rcc(rcc) {} + + RequestableChannelClass rcc; +}; + +/** + * \class RequestableChannelClassSpec + * \ingroup wrappers + * \headerfile TelepathyQt/requestable-channel-class-spec.h <TelepathyQt/RequestableChannelClassSpec> + * + * \brief The RequestableChannelClassSpec class represents a Telepathy + * requestable channel class. + */ + +RequestableChannelClassSpec::RequestableChannelClassSpec(const RequestableChannelClass &rcc) + : mPriv(new Private(rcc)) +{ +} + +RequestableChannelClassSpec::RequestableChannelClassSpec() +{ +} + +RequestableChannelClassSpec::RequestableChannelClassSpec(const RequestableChannelClassSpec &other) + : mPriv(other.mPriv) +{ +} + +RequestableChannelClassSpec::~RequestableChannelClassSpec() +{ +} + +RequestableChannelClassSpec RequestableChannelClassSpec::textChat() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_TEXT); + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"), + (uint) HandleTypeContact); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::textChatroom() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_TEXT); + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"), + (uint) HandleTypeRoom); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::streamedMediaCall() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA); + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"), + (uint) HandleTypeContact); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::streamedMediaAudioCall() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA); + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"), + (uint) HandleTypeContact); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".InitialAudio")); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::streamedMediaVideoCall() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA); + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"), + (uint) HandleTypeContact); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".InitialVideo")); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::streamedMediaVideoCallWithAudio() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA); + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"), + (uint) HandleTypeContact); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".InitialAudio")); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA + QLatin1String(".InitialVideo")); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::fileTransfer() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_FILE_TRANSFER); + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"), + (uint) HandleTypeContact); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::conferenceTextChat() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_TEXT); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::conferenceTextChatWithInvitees() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_TEXT); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles")); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::conferenceTextChatroom() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_TEXT); + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"), + (uint) HandleTypeRoom); + + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::conferenceTextChatroomWithInvitees() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_TEXT); + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"), + (uint) HandleTypeRoom); + + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles")); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::conferenceStreamedMediaCall() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::conferenceStreamedMediaCallWithInvitees() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialChannels")); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_INTERFACE_CONFERENCE + QLatin1String(".InitialInviteeHandles")); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::contactSearch() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_CONTACT_SEARCH); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::contactSearchWithSpecificServer() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_CONTACT_SEARCH); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_TYPE_CONTACT_SEARCH + QLatin1String(".Server")); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::contactSearchWithLimit() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_CONTACT_SEARCH); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_TYPE_CONTACT_SEARCH + QLatin1String(".Limit")); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::contactSearchWithSpecificServerAndLimit() +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_CONTACT_SEARCH); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_TYPE_CONTACT_SEARCH + QLatin1String(".Server")); + rcc.allowedProperties.append( + TP_QT_IFACE_CHANNEL_TYPE_CONTACT_SEARCH + QLatin1String(".Limit")); + spec = RequestableChannelClassSpec(rcc); + } + + return spec; +} + +RequestableChannelClassSpec RequestableChannelClassSpec::streamTube(const QString &service) +{ + static RequestableChannelClassSpec spec; + + if (!spec.isValid()) { + RequestableChannelClass rcc; + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".ChannelType"), + TP_QT_IFACE_CHANNEL_TYPE_STREAM_TUBE); + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL + QLatin1String(".TargetHandleType"), + (uint) HandleTypeContact); + spec = RequestableChannelClassSpec(rcc); + } + + if (service.isEmpty()) { + return spec; + } + + RequestableChannelClass rcc = spec.bareClass(); + rcc.fixedProperties.insert(TP_QT_IFACE_CHANNEL_TYPE_STREAM_TUBE + QLatin1String(".Service"), + service); + return RequestableChannelClassSpec(rcc); +} + +RequestableChannelClassSpec &RequestableChannelClassSpec::operator=(const RequestableChannelClassSpec &other) +{ + this->mPriv = other.mPriv; + return *this; +} + +bool RequestableChannelClassSpec::operator==(const RequestableChannelClassSpec &other) const +{ + if (!isValid() || !other.isValid()) { + if (!isValid() && !other.isValid()) { + return true; + } + return false; + } + + return mPriv->rcc == other.mPriv->rcc; +} + +bool RequestableChannelClassSpec::supports(const RequestableChannelClassSpec &other) const +{ + if (!isValid()) { + return false; + } + + if (mPriv->rcc.fixedProperties == other.fixedProperties()) { + foreach (const QString &prop, other.allowedProperties()) { + if (!mPriv->rcc.allowedProperties.contains(prop)) { + return false; + } + } + return true; + } + return false; +} + +QString RequestableChannelClassSpec::channelType() const +{ + if (!isValid()) { + return QString(); + } + return qdbus_cast<QString>(mPriv->rcc.fixedProperties.value( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".ChannelType"))); +} + +bool RequestableChannelClassSpec::hasTargetHandleType() const +{ + if (!isValid()) { + return false; + } + return mPriv->rcc.fixedProperties.contains( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType")); +} + +HandleType RequestableChannelClassSpec::targetHandleType() const +{ + if (!hasTargetHandleType()) { + return (HandleType) -1; + } + return (HandleType) qdbus_cast<uint>(mPriv->rcc.fixedProperties.value( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL ".TargetHandleType"))); +} + +bool RequestableChannelClassSpec::hasFixedProperty(const QString &name) const +{ + if (!isValid()) { + return false; + } + return mPriv->rcc.fixedProperties.contains(name); +} + +QVariant RequestableChannelClassSpec::fixedProperty(const QString &name) const +{ + if (!isValid()) { + return QVariant(); + } + return mPriv->rcc.fixedProperties.value(name); +} + +QVariantMap RequestableChannelClassSpec::fixedProperties() const +{ + if (!isValid()) { + return QVariantMap(); + } + return mPriv->rcc.fixedProperties; +} + +bool RequestableChannelClassSpec::allowsProperty(const QString &name) const +{ + if (!isValid()) { + return false; + } + return mPriv->rcc.allowedProperties.contains(name); +} + +QStringList RequestableChannelClassSpec::allowedProperties() const +{ + if (!isValid()) { + return QStringList(); + } + return mPriv->rcc.allowedProperties; +} + +RequestableChannelClass RequestableChannelClassSpec::bareClass() const +{ + return isValid() ? mPriv->rcc : RequestableChannelClass(); +} + +/** + * \class RequestableChannelClassSpecList + * \ingroup wrappers + * \headerfile TelepathyQt/requestable-channel-class-spec.h <TelepathyQt/RequestableChannelClassSpecList> + * + * \brief The RequestableChannelClassSpecList class represents a list of + * RequestableChannelClassSpec. + */ + +} // Tp diff --git a/TelepathyQt/requestable-channel-class-spec.h b/TelepathyQt/requestable-channel-class-spec.h new file mode 100644 index 00000000..752f2e4c --- /dev/null +++ b/TelepathyQt/requestable-channel-class-spec.h @@ -0,0 +1,134 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_requestable_channel_class_spec_h_HEADER_GUARD_ +#define _TelepathyQt_requestable_channel_class_spec_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class TP_QT_EXPORT RequestableChannelClassSpec +{ +public: + RequestableChannelClassSpec(); + RequestableChannelClassSpec(const RequestableChannelClass &rcc); + RequestableChannelClassSpec(const RequestableChannelClassSpec &other); + ~RequestableChannelClassSpec(); + + static RequestableChannelClassSpec textChat(); + static RequestableChannelClassSpec textChatroom(); + + static RequestableChannelClassSpec streamedMediaCall(); + static RequestableChannelClassSpec streamedMediaAudioCall(); + static RequestableChannelClassSpec streamedMediaVideoCall(); + static RequestableChannelClassSpec streamedMediaVideoCallWithAudio(); + + static RequestableChannelClassSpec fileTransfer(); + + static RequestableChannelClassSpec conferenceTextChat(); + static RequestableChannelClassSpec conferenceTextChatWithInvitees(); + static RequestableChannelClassSpec conferenceTextChatroom(); + static RequestableChannelClassSpec conferenceTextChatroomWithInvitees(); + static RequestableChannelClassSpec conferenceStreamedMediaCall(); + static RequestableChannelClassSpec conferenceStreamedMediaCallWithInvitees(); + + static RequestableChannelClassSpec contactSearch(); + static RequestableChannelClassSpec contactSearchWithSpecificServer(); + static RequestableChannelClassSpec contactSearchWithLimit(); + static RequestableChannelClassSpec contactSearchWithSpecificServerAndLimit(); + + static RequestableChannelClassSpec streamTube(const QString &service = QString()); + + bool isValid() const { return mPriv.constData() != 0; } + + RequestableChannelClassSpec &operator=(const RequestableChannelClassSpec &other); + bool operator==(const RequestableChannelClassSpec &other) const; + + bool supports(const RequestableChannelClassSpec &spec) const; + + QString channelType() const; + + bool hasTargetHandleType() const; + HandleType targetHandleType() const; + + bool hasFixedProperty(const QString &name) const; + QVariant fixedProperty(const QString &name) const; + QVariantMap fixedProperties() const; + + bool allowsProperty(const QString &name) const; + QStringList allowedProperties() const; + + RequestableChannelClass bareClass() const; + +private: + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; +}; + +class TP_QT_EXPORT RequestableChannelClassSpecList : + public QList<RequestableChannelClassSpec> +{ +public: + RequestableChannelClassSpecList() { } + RequestableChannelClassSpecList(const RequestableChannelClass &rcc) + { + append(RequestableChannelClassSpec(rcc)); + } + RequestableChannelClassSpecList(const RequestableChannelClassList &rccs) + { + Q_FOREACH (const RequestableChannelClass &rcc, rccs) { + append(RequestableChannelClassSpec(rcc)); + } + } + RequestableChannelClassSpecList(const RequestableChannelClassSpec &rccSpec) + { + append(rccSpec); + } + RequestableChannelClassSpecList(const QList<RequestableChannelClassSpec> &other) + : QList<RequestableChannelClassSpec>(other) + { + } + + RequestableChannelClassList bareClasses() const + { + RequestableChannelClassList list; + Q_FOREACH (const RequestableChannelClassSpec &rccSpec, *this) { + list.append(rccSpec.bareClass()); + } + return list; + } +}; + +} // Tp + +Q_DECLARE_METATYPE(Tp::RequestableChannelClassSpec); +Q_DECLARE_METATYPE(Tp::RequestableChannelClassSpecList); + +#endif diff --git a/TelepathyQt/room-list-channel.cpp b/TelepathyQt/room-list-channel.cpp new file mode 100644 index 00000000..9c7aec55 --- /dev/null +++ b/TelepathyQt/room-list-channel.cpp @@ -0,0 +1,106 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/RoomListChannel> + +#include "TelepathyQt/_gen/room-list-channel.moc.hpp" +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Connection> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT RoomListChannel::Private +{ + inline Private(); + inline ~Private(); +}; + +RoomListChannel::Private::Private() +{ +} + +RoomListChannel::Private::~Private() +{ +} + +/** + * \class RoomListChannel + * \ingroup clientchannel + * \headerfile TelepathyQt/room-list-channel.h <TelepathyQt/RoomListChannel> + * + * \brief The RoomListChannel class represents a Telepathy Channel of type RoomList. + * + * Note that this subclass of Channel will eventually provide a high-level API for the + * RoomList interface. Until then, it's just a Channel. + * + * For more details, please refer to \telepathy_spec. + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Create a new RoomListChannel 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 RoomListChannelPtr object pointing to the newly created + * RoomListChannel object. + */ +RoomListChannelPtr RoomListChannel::create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties) +{ + return RoomListChannelPtr(new RoomListChannel(connection, objectPath, + immutableProperties)); +} + +/** + * Construct a new RoomListChannel 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, if any. The corresponding introspectable should + * should depend on Channel::FeatureCore. + */ +RoomListChannel::RoomListChannel(const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : Channel(connection, objectPath, immutableProperties, coreFeature), + mPriv(new Private()) +{ +} + +/** + * Class destructor. + */ +RoomListChannel::~RoomListChannel() +{ + delete mPriv; +} + +} // Tp diff --git a/TelepathyQt/room-list-channel.h b/TelepathyQt/room-list-channel.h new file mode 100644 index 00000000..1c9a47a6 --- /dev/null +++ b/TelepathyQt/room-list-channel.h @@ -0,0 +1,59 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_room_list_channel_h_HEADER_GUARD_ +#define _TelepathyQt_room_list_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Channel> + +namespace Tp +{ + +class TP_QT_EXPORT RoomListChannel : public Channel +{ + Q_OBJECT + Q_DISABLE_COPY(RoomListChannel) + +public: + static RoomListChannelPtr create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties); + + virtual ~RoomListChannel(); + +protected: + RoomListChannel(const ConnectionPtr &connection, const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature = Channel::FeatureCore); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/shared-ptr.dox b/TelepathyQt/shared-ptr.dox new file mode 100644 index 00000000..ce21dc63 --- /dev/null +++ b/TelepathyQt/shared-ptr.dox @@ -0,0 +1,111 @@ +/* + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2011 Nokia Corporation + * + * 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 + */ + +/** + * \page shared_ptr Shared Pointer Usage + * + * \section shared_ptr_overview Overview + * + * The Qt parent/child object model does not fit well with Telepathy-Qt4 object + * model, where in some places we either don't know the object parent or we + * can't use a parent, as the object can stay alive without it. + * + * To avoid memory leaks, caused by objects that got instantiated and don't have + * any parent, we decided to make some of our objects reference counted, by + * making them inherit SharedData. + * + * Making the object reference counted, does not guarantee that it will get + * deleted when nobody is referencing it. + * + * When instantiating new classes that inherits SharedData the reference count + * is 0, this is referred to as the floating state. Again this may lead to + * memory leaks, caused by objects in the floating state that never get deleted. + * + * So the solution is to put the object in a SharedPtr as soon as possible, + * letting the SharedPtr manage the object lifetime. + * + * The pattern used is that classes inherit SharedData and are used + * together with SharedPtr. When the reference count hits 0, the object + * is deleted. + * + * In order to assure that the object is put in a SharedPtr as soon as possible, + * our objects inheriting SharedData will have the constructor either private + * or protected, in case we want to support custom classes, and will have a + * public static create method that will return a SharedPtr pointing to the + * object instance. + * + * Note that when developing custom classes, this pattern should be followed, + * to avoid objects in floating state, avoiding memory leaks. + */ + +/** + * \class Tp::RefCounted + * \ingroup utils + * \headerfile TelepathyQt/shared-ptr.h <TelepathyQt/RefCounted> + * + * \brief The RefCounted class is a base class for shared objects used by + * SharedPtr. + * + * See \ref shared_ptr + */ + +/** + * \class Tp::SharedPtr + * \ingroup utils + * \headerfile TelepathyQt/shared-ptr.h <TelepathyQt/SharedPtr> + * + * \brief The SharedPtr class is a pointer to an explicitly shared object. + * + * See \ref shared_ptr + */ + +/** + * \fn static SharedPtr<T> Tp::SharedPtr<T>::dynamicCast(const SharedPtr<X> &) + * + * Casts the pointer given by src to a pointer pointing to an object of type T. The cast will + * succeed if the C++ runtime type identification mechanism considers the type T to be the actual + * runtime type of the object pointed to by src or one of its (possibly indirect) parent classes. + * Otherwise, a null pointer is returned. + * + * Note that this also allows down-casting a baseclass pointer to a subclass pointer. + * + * This cast method should not be used for QObject-derived classes, as Qt provides a more portable + * and efficient type identification mechanism, which is used by qObjectCast(). + * + * This cast method requires the C++ dynamic runtime type identification facility to be enabled + * (which might be disabled by eg. the -fno-rtti flag of the GNU G++ compiler). + */ + +/** + * \fn static SharedPtr<T> Tp::SharedPtr<T>::qObjectCast(const SharedPtr<X> &) + * + * Casts the pointer given by src to a pointer pointing to an object of type T. The cast will + * succeed if the Qt runtime type identification mechanism considers the type T to be the actual + * runtime type of the object pointed to by src or one of its (possibly indirect) parent classes. + * Otherwise, a null pointer is returned. + * + * Note that this also allows down-casting a baseclass pointer to a subclass pointer. + * + * This cast method MUST not be used for classes not derived from QObject. However, dynamicCast() + * provides the same semantics for all classes, provided the C++ runtime type identification + * facility is enabled. This method, on the other hand, doesn't require the standard C++ facility + * and is probably also faster for the types it can be used with. + */ diff --git a/TelepathyQt/shared-ptr.h b/TelepathyQt/shared-ptr.h new file mode 100644 index 00000000..69119980 --- /dev/null +++ b/TelepathyQt/shared-ptr.h @@ -0,0 +1,152 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_shared_ptr_h_HEADER_GUARD_ +#define _TelepathyQt_shared_ptr_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> + +#include <QHash> +#include <QWeakPointer> + +namespace Tp +{ + +class RefCounted; +template <class T> class SharedPtr; + +class TP_QT_EXPORT RefCounted +{ + Q_DISABLE_COPY(RefCounted) + +public: + inline RefCounted() : strongref(0) { } + inline virtual ~RefCounted() { } + + inline void ref() const { strongref.ref(); } + inline bool deref() const { return strongref.deref(); } + + mutable QAtomicInt strongref; +}; + +template <class T> +class SharedPtr +{ + typedef bool (SharedPtr<T>::*UnspecifiedBoolType)() const; + +public: + inline SharedPtr() : d(0) { } + explicit inline SharedPtr(T *d) : d(d) { if (d) { d->ref(); } } + template <typename Subclass> + inline SharedPtr(const SharedPtr<Subclass> &o) : d(o.data()) { if (d) { d->ref(); } } + inline SharedPtr(const SharedPtr<T> &o) : d(o.d) { if (d) { d->ref(); } } + explicit inline SharedPtr(const QWeakPointer<T> &o) + { + if (o.data() && o.data()->strongref > 0) { + d = static_cast<T*>(o.data()); + d->ref(); + } else { + d = 0; + } + } + inline ~SharedPtr() + { + if (d && !d->deref()) { + T *saved = d; + d = 0; + delete saved; + } + } + + inline void reset() + { + SharedPtr<T>().swap(*this); + } + + inline T *data() const { return d; } + inline const T *constData() const { return d; } + inline T *operator->() { return d; } + inline T *operator->() const { return d; } + + inline bool isNull() const { return !d; } + inline bool operator!() const { return isNull(); } + operator UnspecifiedBoolType() const { return !isNull() ? &SharedPtr<T>::operator! : 0; } + + inline bool operator==(const SharedPtr<T> &o) const { return d == o.d; } + inline bool operator!=(const SharedPtr<T> &o) const { return d != o.d; } + inline bool operator==(const T *ptr) const { return d == ptr; } + inline bool operator!=(const T *ptr) const { return d != ptr; } + + inline SharedPtr<T> &operator=(const SharedPtr<T> &o) + { + SharedPtr<T>(o).swap(*this); + return *this; + } + + inline void swap(SharedPtr<T> &o) + { + T *tmp = d; + d = o.d; + o.d = tmp; + } + + template <class X> + static inline SharedPtr<T> staticCast(const SharedPtr<X> &src) + { + return SharedPtr<T>(static_cast<T*>(src.data())); + } + + template <class X> + static inline SharedPtr<T> dynamicCast(const SharedPtr<X> &src) + { + return SharedPtr<T>(dynamic_cast<T*>(src.data())); + } + + template <class X> + static inline SharedPtr<T> constCast(const SharedPtr<X> &src) + { + return SharedPtr<T>(const_cast<T*>(src.data())); + } + + template <class X> + static inline SharedPtr<T> qObjectCast(const SharedPtr<X> &src) + { + return SharedPtr<T>(qobject_cast<T*>(src.data())); + } + +private: + T *d; +}; + +template<typename T> +inline uint qHash(const SharedPtr<T> &ptr) +{ + return QT_PREPEND_NAMESPACE(qHash<T>(ptr.data())); +} + +} // Tp + +#endif diff --git a/TelepathyQt/simple-call-observer.cpp b/TelepathyQt/simple-call-observer.cpp new file mode 100644 index 00000000..a62b8a40 --- /dev/null +++ b/TelepathyQt/simple-call-observer.cpp @@ -0,0 +1,298 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 <TelepathyQt/SimpleCallObserver> + +#include "TelepathyQt/_gen/simple-call-observer.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Account> +#include <TelepathyQt/ChannelClassSpec> +#include <TelepathyQt/ChannelClassSpecList> +#include <TelepathyQt/Connection> +#include <TelepathyQt/Contact> +#include <TelepathyQt/ContactManager> +#include <TelepathyQt/PendingContacts> +#include <TelepathyQt/PendingComposite> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/PendingSuccess> +#include <TelepathyQt/SimpleObserver> +#include <TelepathyQt/StreamedMediaChannel> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT SimpleCallObserver::Private +{ + Private(SimpleCallObserver *parent, const AccountPtr &account, + const QString &contactIdentifier, bool requiresNormalization, + CallDirection direction); + + SimpleCallObserver *parent; + AccountPtr account; + QString contactIdentifier; + CallDirection direction; + SimpleObserverPtr observer; +}; + +SimpleCallObserver::Private::Private(SimpleCallObserver *parent, + const AccountPtr &account, + const QString &contactIdentifier, bool requiresNormalization, + CallDirection direction) + : parent(parent), + account(account), + contactIdentifier(contactIdentifier), + direction(direction) +{ + debug() << "Creating a new SimpleCallObserver"; + ChannelClassSpec channelFilter = ChannelClassSpec::streamedMediaCall(); + if (direction == CallDirectionIncoming) { + channelFilter.setRequested(false); + } else if (direction == CallDirectionOutgoing) { + channelFilter.setRequested(true); + } + + observer = SimpleObserver::create(account, ChannelClassSpecList() << channelFilter, + contactIdentifier, requiresNormalization, QList<ChannelClassFeatures>()); + + parent->connect(observer.data(), + SIGNAL(newChannels(QList<Tp::ChannelPtr>)), + SLOT(onNewChannels(QList<Tp::ChannelPtr>))); + parent->connect(observer.data(), + SIGNAL(channelInvalidated(Tp::ChannelPtr,QString,QString)), + SLOT(onChannelInvalidated(Tp::ChannelPtr,QString,QString))); +} + +/** + * \class SimpleCallObserver + * \ingroup utils + * \headerfile TelepathyQt/simple-call-observer.h <TelepathyQt/SimpleCallObserver> + * + * \brief The SimpleCallObserver class provides an easy way to track calls + * in an account and can be optionally filtered by a contact and/or + * call direction. + */ + +/** + * Create a new SimpleCallObserver object. + * + * Events will be signalled for all calls in \a account that respect \a direction. + * + * \param account The account used to listen to events. + * \param direction The direction of the calls used to filter events. + * \return An SimpleCallObserverPtr object pointing to the newly created + * SimpleCallObserver object. + */ +SimpleCallObserverPtr SimpleCallObserver::create(const AccountPtr &account, + CallDirection direction) +{ + return create(account, QString(), false, direction); +} + +/** + * Create a new SimpleCallObserver object. + * + * Events will be signalled for all calls in \a account established with \a contact and + * that respect \a direction. + * + * \param account The account used to listen to events. + * \param contact The contact used to filter events. + * \param direction The direction of the calls used to filter events. + * \return An SimpleCallObserverPtr object pointing to the newly created + * SimpleCallObserver object. + */ +SimpleCallObserverPtr SimpleCallObserver::create(const AccountPtr &account, + const ContactPtr &contact, + CallDirection direction) +{ + if (contact) { + return create(account, contact->id(), false, direction); + } + return create(account, QString(), false, direction); +} + +/** + * Create a new SimpleCallObserver object. + * + * Events will be signalled for all calls in \a account established with a contact identified by \a + * contactIdentifier and that respect \a direction. + * + * \param account The account used to listen to events. + * \param contactIdentifier The identifier of the contact used to filter events. + * \param direction The direction of the calls used to filter events. + * \return An SimpleCallObserverPtr object pointing to the newly created + * SimpleCallObserver object. + */ +SimpleCallObserverPtr SimpleCallObserver::create(const AccountPtr &account, + const QString &contactIdentifier, + CallDirection direction) +{ + return create(account, contactIdentifier, true, direction); +} + +SimpleCallObserverPtr SimpleCallObserver::create(const AccountPtr &account, + const QString &contactIdentifier, bool requiresNormalization, + CallDirection direction) +{ + return SimpleCallObserverPtr( + new SimpleCallObserver(account, contactIdentifier, + requiresNormalization, direction)); +} + +/** + * Construct a new SimpleCallObserver object. + * + * \param account The account used to listen to events. + * \param contactIdentifier The identifier of the contact used to filter events. + * \param requiresNormalization Whether \a contactIdentifier needs to be + * normalized. + * \param direction The direction of the calls used to filter events. + * \return An SimpleCallObserverPtr object pointing to the newly created + * SimpleCallObserver object. + */ +SimpleCallObserver::SimpleCallObserver(const AccountPtr &account, + const QString &contactIdentifier, bool requiresNormalization, + CallDirection direction) + : mPriv(new Private(this, account, contactIdentifier, requiresNormalization, direction)) +{ +} + +/** + * Class destructor. + */ +SimpleCallObserver::~SimpleCallObserver() +{ + delete mPriv; +} + +/** + * Return the account used to listen to events. + * + * \return A pointer to the Account object. + */ +AccountPtr SimpleCallObserver::account() const +{ + return mPriv->account; +} + +/** + * Return the identifier of the contact used to filter events, or an empty string if none was + * provided at construction. + * + * \return The identifier of the contact. + */ +QString SimpleCallObserver::contactIdentifier() const +{ + return mPriv->contactIdentifier; +} + +/** + * Return the direction of the calls used to filter events. + * + * \return The direction of the calls as SimpleCallObserver::CallDirection. + */ +SimpleCallObserver::CallDirection SimpleCallObserver::direction() const +{ + return mPriv->direction; +} + +/** + * Return the list of streamed media calls currently being observed. + * + * \return A list of pointers to StreamedMediaChannel objects. + */ +QList<StreamedMediaChannelPtr> SimpleCallObserver::streamedMediaCalls() const +{ + QList<StreamedMediaChannelPtr> ret; + foreach (const ChannelPtr &channel, mPriv->observer->channels()) { + StreamedMediaChannelPtr smChannel = StreamedMediaChannelPtr::qObjectCast(channel); + if (smChannel) { + ret << smChannel; + } + } + return ret; +} + +void SimpleCallObserver::onNewChannels(const QList<ChannelPtr> &channels) +{ + foreach (const ChannelPtr &channel, channels) { + StreamedMediaChannelPtr smChannel = StreamedMediaChannelPtr::qObjectCast(channel); + if (!smChannel) { + if (channel->channelType() != TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA) { + warning() << "Channel received to observe is not of type StreamedMedia, service " + "confused. Ignoring channel"; + } else { + warning() << "Channel received to observe is not a subclass of " + "StreamedMediaChannel. ChannelFactory set on this observer's account must " + "construct StreamedMediaChannel subclasses for channels of type StreamedMedia. " + "Ignoring channel"; + } + continue; + } + + emit streamedMediaCallStarted(StreamedMediaChannelPtr::qObjectCast(channel)); + } +} + +void SimpleCallObserver::onChannelInvalidated(const ChannelPtr &channel, + const QString &errorName, const QString &errorMessage) +{ + StreamedMediaChannelPtr smChannel = StreamedMediaChannelPtr::qObjectCast(channel); + if (!smChannel) { + if (channel->channelType() != TP_QT_IFACE_CHANNEL_TYPE_STREAMED_MEDIA) { + warning() << "Channel received by this observer is not of type StreamedMedia, service " + "confused. Ignoring channel"; + } else { + warning() << "Channel received by this observer is not a subclass of " + "StreamedMediaChannel. ChannelFactory set on this observer's account must " + "construct StreamedMediaChannel subclasses for channels of type StreamedMedia. " + "Ignoring channel"; + } + return; + } + emit streamedMediaCallEnded(smChannel, errorName, errorMessage); +} + +/** + * \fn void SimpleCallObserver::streamedMediaCallStarted(const Tp::StreamedMediaChannelPtr &channel) + * + * Emitted whenever a streamed media call that matches this observer's criteria is + * started. + * + * \param channel The channel representing the streamed media call that started. + */ + +/** + * \fn void SimpleCallObserver::streamedMediaCallEnded(const Tp::StreamedMediaChannelPtr &channel, + * const QString &errorName, const QString &errorMessage) + * + * Emitted whenever a streamed media call that matches this observer's criteria has + * ended. + * + * \param channel The channel representing the streamed media call that ended. + * \param errorName A D-Bus error name (a string in a subset + * of ASCII, prefixed with a reversed domain name). + * \param errorMessage A debugging message associated with the error. + */ + +} // Tp diff --git a/TelepathyQt/simple-call-observer.h b/TelepathyQt/simple-call-observer.h new file mode 100644 index 00000000..988d092a --- /dev/null +++ b/TelepathyQt/simple-call-observer.h @@ -0,0 +1,95 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +#ifndef _TelepathyQt_simple_call_observer_h_HEADER_GUARD_ +#define _TelepathyQt_simple_call_observer_h_HEADER_GUARD_ + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Types> + +#include <QObject> + +namespace Tp +{ + +class PendingOperation; + +class TP_QT_EXPORT SimpleCallObserver : public QObject, + public RefCounted +{ + Q_OBJECT + Q_DISABLE_COPY(SimpleCallObserver) + Q_FLAGS(CallDirection CallDirections) + +public: + enum CallDirection { + CallDirectionIncoming = 0x01, + CallDirectionOutgoing = 0x02, + CallDirectionBoth = CallDirectionIncoming | CallDirectionOutgoing + }; + Q_DECLARE_FLAGS(CallDirections, CallDirection) + + static SimpleCallObserverPtr create(const AccountPtr &account, + CallDirection direction = CallDirectionBoth); + static SimpleCallObserverPtr create(const AccountPtr &account, + const ContactPtr &contact, + CallDirection direction = CallDirectionBoth); + static SimpleCallObserverPtr create(const AccountPtr &account, + const QString &contactIdentifier, + CallDirection direction = CallDirectionBoth); + + virtual ~SimpleCallObserver(); + + AccountPtr account() const; + QString contactIdentifier() const; + CallDirection direction() const; + + QList<StreamedMediaChannelPtr> streamedMediaCalls() const; + +Q_SIGNALS: + void streamedMediaCallStarted(const Tp::StreamedMediaChannelPtr &channel); + void streamedMediaCallEnded(const Tp::StreamedMediaChannelPtr &channel, + const QString &errorName, const QString &errorMessage); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onNewChannels(const QList<Tp::ChannelPtr> &channels); + TP_QT_NO_EXPORT void onChannelInvalidated(const Tp::ChannelPtr &channel, + const QString &errorName, const QString &errorMessage); + +private: + TP_QT_NO_EXPORT static SimpleCallObserverPtr create( + const AccountPtr &account, + const QString &contactIdentifier, bool requiresNormalization, + CallDirection direction); + + TP_QT_NO_EXPORT SimpleCallObserver(const AccountPtr &account, + const QString &contactIdentifier, bool requiresNormalization, + CallDirection direction); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/simple-observer-internal.h b/TelepathyQt/simple-observer-internal.h new file mode 100644 index 00000000..78900cf0 --- /dev/null +++ b/TelepathyQt/simple-observer-internal.h @@ -0,0 +1,261 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 <TelepathyQt/Account> +#include <TelepathyQt/AccountFactory> +#include <TelepathyQt/Channel> +#include <TelepathyQt/ChannelClassFeatures> +#include <TelepathyQt/ChannelClassSpec> +#include <TelepathyQt/ChannelClassSpecList> +#include <TelepathyQt/ClientRegistrar> +#include <TelepathyQt/Types> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT SimpleObserver::Private +{ + Private(SimpleObserver *parent, + const AccountPtr &account, + const ChannelClassSpecList &channelFilter, + const QString &contactIdentifier, + bool requiresNormalization, + const QList<ChannelClassFeatures> &extraChannelFeatures); + + bool filterChannel(const AccountPtr &channelAccount, const ChannelPtr &channel); + void insertChannels(const AccountPtr &channelsAccount, const QList<ChannelPtr> &channels); + void removeChannel(const AccountPtr &channelAccount, const ChannelPtr &channel, + const QString &errorName, const QString &errorMessage); + + void processChannelsQueue(); + void processNewChannelsQueue(); + void processChannelsInvalidationQueue(); + + class FakeAccountFactory; + class Observer; + class ChannelWrapper; + struct NewChannelsInfo; + struct ChannelInvalidationInfo; + + SimpleObserver *parent; + AccountPtr account; + ChannelClassSpecList channelFilter; + QString contactIdentifier; + QString normalizedContactIdentifier; + QList<ChannelClassFeatures> extraChannelFeatures; + ClientRegistrarPtr cr; + SharedPtr<Observer> observer; + QSet<ChannelPtr> channels; + QQueue<void (SimpleObserver::Private::*)()> channelsQueue; + QQueue<ChannelInvalidationInfo> channelsInvalidationQueue; + QQueue<NewChannelsInfo> newChannelsQueue; + static QHash<QPair<QString, QSet<ChannelClassSpec> >, QWeakPointer<Observer> > observers; + static uint numObservers; +}; + +class TP_QT_NO_EXPORT SimpleObserver::Private::FakeAccountFactory : + public AccountFactory +{ + Q_OBJECT + +public: + static SharedPtr<FakeAccountFactory> create(const QDBusConnection &bus) + { + return SharedPtr<FakeAccountFactory>(new FakeAccountFactory(bus)); + } + + ~FakeAccountFactory() { } + +private: + friend class Observer; + + FakeAccountFactory(const QDBusConnection &bus) + : AccountFactory(bus, Features()) + { + } + + AccountPtr construct(const QString &busName, const QString &objectPath, + const ConnectionFactoryConstPtr &connFactory, + const ChannelFactoryConstPtr &chanFactory, + const ContactFactoryConstPtr &contactFactory) const + { + if (mAccounts.contains(objectPath)) { + return mAccounts.value(objectPath); + } + return AccountFactory::construct(busName, objectPath, connFactory, + chanFactory, contactFactory); + } + + QHash<QString, AccountPtr> accounts() const { return mAccounts; } + void registerAccount(const AccountPtr &account) + { + mAccounts.insert(account->objectPath(), account); + } + + QHash<QString, AccountPtr> mAccounts; +}; + +class TP_QT_NO_EXPORT SimpleObserver::Private::Observer : public QObject, + public AbstractClientObserver +{ + Q_OBJECT + Q_DISABLE_COPY(Observer) + +public: + struct ContextInfo + { + ContextInfo() {} + ContextInfo(const MethodInvocationContextPtr<> &context, + const AccountPtr &account, + const QList<ChannelPtr> &channels) + : context(context), + account(account), + channels(channels) + { + } + + MethodInvocationContextPtr<> context; + AccountPtr account; + QList<ChannelPtr> channels; + }; + + Observer(const QWeakPointer<ClientRegistrar> &cr, + const SharedPtr<FakeAccountFactory> &fakeAccountFactory, + const ChannelClassSpecList &channelFilter, + const QString &observerName); + ~Observer(); + + QWeakPointer<ClientRegistrar> clientRegistrar() const { return mCr; } + SharedPtr<FakeAccountFactory> fakeAccountFactory() const { return mFakeAccountFactory; } + + QString observerName() const { return mObserverName; } + + QSet<ChannelClassFeatures> extraChannelFeatures() const { return mExtraChannelFeatures; } + void registerExtraChannelFeatures(const QList<ChannelClassFeatures> &features) + { + mExtraChannelFeatures.unite(features.toSet()); + } + + QSet<AccountPtr> accounts() const { return mAccounts; } + void registerAccount(const AccountPtr &account) + { + mAccounts.insert(account); + mFakeAccountFactory->registerAccount(account); + } + + QHash<ChannelPtr, ChannelWrapper*> channels() const { return mChannels; } + + void observeChannels( + const MethodInvocationContextPtr<> &context, + const AccountPtr &account, + const ConnectionPtr &connection, + const QList<ChannelPtr> &channels, + const ChannelDispatchOperationPtr &dispatchOperation, + const QList<ChannelRequestPtr> &requestsSatisfied, + const ObserverInfo &observerInfo); + +Q_SIGNALS: + void newChannels(const Tp::AccountPtr &channelsAccount, const QList<Tp::ChannelPtr> &channels); + void channelInvalidated(const Tp::AccountPtr &channelAccount, const Tp::ChannelPtr &channel, + const QString &errorName, const QString &errorMessage); + +private Q_SLOTS: + void onChannelInvalidated(const Tp::AccountPtr &channelAccount, const Tp::ChannelPtr &channel, + const QString &errorName, const QString &errorMessage); + void onChannelsReady(Tp::PendingOperation *op); + +private: + Features featuresFor(const ChannelClassSpec &channelClass) const; + + QWeakPointer<ClientRegistrar> mCr; + SharedPtr<FakeAccountFactory> mFakeAccountFactory; + QString mObserverName; + QSet<ChannelClassFeatures> mExtraChannelFeatures; + QSet<AccountPtr> mAccounts; + QHash<ChannelPtr, ChannelWrapper*> mChannels; + QHash<ChannelPtr, ChannelWrapper*> mIncompleteChannels; + QHash<PendingOperation*, ContextInfo*> mObserveChannelsInfo; +}; + +class TP_QT_NO_EXPORT SimpleObserver::Private::ChannelWrapper : + public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(ChannelWrapper) + +public: + ChannelWrapper(const AccountPtr &channelAccount, const ChannelPtr &channel, + const Features &extraChannelFeatures, QObject *parent); + ~ChannelWrapper() { } + + AccountPtr channelAccount() const { return mChannelAccount; } + ChannelPtr channel() const { return mChannel; } + Features extraChannelFeatures() const { return mExtraChannelFeatures; } + + PendingOperation *becomeReady(); + +Q_SIGNALS: + void channelInvalidated(const Tp::AccountPtr &channelAccount, const Tp::ChannelPtr &channel, + const QString &errorName, const QString &errorMessage); + +private Q_SLOTS: + void onChannelInvalidated(Tp::DBusProxy *proxy, const QString &errorName, + const QString &errorMessage); + +private: + AccountPtr mChannelAccount; + ChannelPtr mChannel; + Features mExtraChannelFeatures; +}; + +struct TP_QT_NO_EXPORT SimpleObserver::Private::NewChannelsInfo +{ + NewChannelsInfo(); + NewChannelsInfo(const AccountPtr &channelsAccount, const QList<ChannelPtr> &channels) + : channelsAccount(channelsAccount), + channels(channels) + { + } + + AccountPtr channelsAccount; + QList<ChannelPtr> channels; +}; + +struct TP_QT_NO_EXPORT SimpleObserver::Private::ChannelInvalidationInfo +{ + ChannelInvalidationInfo(); + ChannelInvalidationInfo(const AccountPtr &channelAccount, const ChannelPtr &channel, + const QString &errorName, const QString &errorMessage) + : channelAccount(channelAccount), + channel(channel), + errorName(errorName), + errorMessage(errorMessage) + { + } + + AccountPtr channelAccount; + ChannelPtr channel; + QString errorName; + QString errorMessage; +}; + +} // Tp diff --git a/TelepathyQt/simple-observer.cpp b/TelepathyQt/simple-observer.cpp new file mode 100644 index 00000000..44bfff19 --- /dev/null +++ b/TelepathyQt/simple-observer.cpp @@ -0,0 +1,643 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 <TelepathyQt/SimpleObserver> +#include "TelepathyQt/simple-observer-internal.h" + +#include "TelepathyQt/_gen/simple-observer.moc.hpp" +#include "TelepathyQt/_gen/simple-observer-internal.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/ChannelClassSpec> +#include <TelepathyQt/ChannelClassSpecList> +#include <TelepathyQt/Connection> +#include <TelepathyQt/Contact> +#include <TelepathyQt/ContactManager> +#include <TelepathyQt/PendingContacts> +#include <TelepathyQt/PendingComposite> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/PendingSuccess> + +namespace Tp +{ + +QHash<QPair<QString, QSet<ChannelClassSpec> >, QWeakPointer<SimpleObserver::Private::Observer> > SimpleObserver::Private::observers; +uint SimpleObserver::Private::numObservers = 0; + +SimpleObserver::Private::Private(SimpleObserver *parent, + const AccountPtr &account, + const ChannelClassSpecList &channelFilter, + const QString &contactIdentifier, + bool requiresNormalization, + const QList<ChannelClassFeatures> &extraChannelFeatures) + : parent(parent), + account(account), + channelFilter(channelFilter), + contactIdentifier(contactIdentifier), + extraChannelFeatures(extraChannelFeatures) +{ + QSet<ChannelClassSpec> normalizedChannelFilter = channelFilter.toSet(); + QPair<QString, QSet<ChannelClassSpec> > observerUniqueId( + account->dbusConnection().baseService(), normalizedChannelFilter); + observer = SharedPtr<Observer>(observers.value(observerUniqueId)); + if (!observer) { + SharedPtr<FakeAccountFactory> fakeAccountFactory = FakeAccountFactory::create( + account->dbusConnection()); + + cr = ClientRegistrar::create( + AccountFactoryPtr::qObjectCast(fakeAccountFactory), + account->connectionFactory(), + account->channelFactory(), + account->contactFactory()); + + QString observerName = QString(QLatin1String("TpQt4SO_%1_%2")) + .arg(account->dbusConnection().baseService() + .replace(QLatin1String(":"), QLatin1String("_")) + .replace(QLatin1String("."), QLatin1String("_"))) + .arg(numObservers++); + observer = SharedPtr<Observer>(new Observer(cr.data(), fakeAccountFactory, + normalizedChannelFilter.toList(), observerName)); + if (!cr->registerClient(observer, observerName, false)) { + warning() << "Unable to register observer" << observerName; + observer.reset(); + cr.reset(); + return; + } + + debug() << "Observer" << observerName << "registered"; + observers.insert(observerUniqueId, observer.data()); + } else { + debug() << "Observer" << observer->observerName() << + "already registered and matches filter, using it"; + cr = ClientRegistrarPtr(observer->clientRegistrar()); + } + + observer->registerExtraChannelFeatures(extraChannelFeatures); + observer->registerAccount(account); + + if (contactIdentifier.isEmpty() || !requiresNormalization) { + normalizedContactIdentifier = contactIdentifier; + } else { + parent->connect(account.data(), + SIGNAL(connectionChanged(Tp::ConnectionPtr)), + SLOT(onAccountConnectionChanged(Tp::ConnectionPtr))); + } + + parent->connect(observer.data(), + SIGNAL(newChannels(Tp::AccountPtr,QList<Tp::ChannelPtr>)), + SLOT(onNewChannels(Tp::AccountPtr,QList<Tp::ChannelPtr>))); + parent->connect(observer.data(), + SIGNAL(channelInvalidated(Tp::AccountPtr,Tp::ChannelPtr,QString,QString)), + SLOT(onChannelInvalidated(Tp::AccountPtr,Tp::ChannelPtr,QString,QString))); +} + +bool SimpleObserver::Private::filterChannel(const AccountPtr &channelAccount, + const ChannelPtr &channel) +{ + if (channelAccount != account) { + return false; + } + + if (contactIdentifier.isEmpty()) { + return true; + } + + QString targetId = channel->immutableProperties().value( + TP_QT_IFACE_CHANNEL + QLatin1String(".TargetID")).toString(); + if (targetId != normalizedContactIdentifier) { + // we didn't filter per contact, let's filter here + return false; + } + return true; +} + +void SimpleObserver::Private::insertChannels(const AccountPtr &channelsAccount, + const QList<ChannelPtr> &newChannels) +{ + QSet<ChannelPtr> match; + foreach (const ChannelPtr &channel, newChannels) { + if (!channels.contains(channel) && filterChannel(channelsAccount, channel)) { + match.insert(channel); + } + } + + if (match.isEmpty()) { + return; + } + + channels.unite(match); + emit parent->newChannels(match.toList()); +} + +void SimpleObserver::Private::removeChannel(const AccountPtr &channelAccount, + const ChannelPtr &channel, + const QString &errorName, const QString &errorMessage) +{ + if (!channels.contains(channel) || !filterChannel(channelAccount, channel)) { + return; + } + + channels.remove(channel); + emit parent->channelInvalidated(channel, errorName, errorMessage); +} + +void SimpleObserver::Private::processChannelsQueue() +{ + if (channelsQueue.isEmpty()) { + return; + } + + while (!channelsQueue.isEmpty()) { + (this->*(channelsQueue.dequeue()))(); + } +} + +void SimpleObserver::Private::processNewChannelsQueue() +{ + NewChannelsInfo info = newChannelsQueue.dequeue(); + insertChannels(info.channelsAccount, info.channels); +} + +void SimpleObserver::Private::processChannelsInvalidationQueue() +{ + ChannelInvalidationInfo info = channelsInvalidationQueue.dequeue(); + removeChannel(info.channelAccount, info.channel, info.errorName, info.errorMessage); +} + +SimpleObserver::Private::Observer::Observer(const QWeakPointer<ClientRegistrar> &cr, + const SharedPtr<FakeAccountFactory> &fakeAccountFactory, + const ChannelClassSpecList &channelFilter, + const QString &observerName) + : AbstractClient(), + QObject(), + AbstractClientObserver(channelFilter, true), + mCr(cr), + mFakeAccountFactory(fakeAccountFactory), + mObserverName(observerName) +{ +} + +SimpleObserver::Private::Observer::~Observer() +{ + // no need to delete channel wrappers here as they have 'this' as parent + + // no need to delete context infos here as this observer will never be deleted before all + // PendingComposites finish (hold reference to this), which will properly delete them + + // no need to unregister this observer here as the client registrar destructor will + // unregister it +} + +void SimpleObserver::Private::Observer::observeChannels( + const MethodInvocationContextPtr<> &context, + const AccountPtr &account, + const ConnectionPtr &connection, + const QList<ChannelPtr> &channels, + const ChannelDispatchOperationPtr &dispatchOperation, + const QList<ChannelRequestPtr> &requestsSatisfied, + const ObserverInfo &observerInfo) +{ + if (!mAccounts.contains(account)) { + context->setFinished(); + return; + } + + QList<PendingOperation*> readyOps; + QList<ChannelPtr> newChannels; + + foreach (const ChannelPtr &channel, channels) { + if (mIncompleteChannels.contains(channel) || + mChannels.contains(channel)) { + // we are already observing this channel + continue; + } + + // this shouldn't happen, but in any case + if (!channel->isValid()) { + warning() << "Channel received to observe is invalid. " + "Ignoring channel"; + continue; + } + + SimpleObserver::Private::ChannelWrapper *wrapper = + new SimpleObserver::Private::ChannelWrapper(account, channel, + featuresFor(ChannelClassSpec(channel->immutableProperties())), this); + mIncompleteChannels.insert(channel, wrapper); + connect(wrapper, + SIGNAL(channelInvalidated(Tp::AccountPtr,Tp::ChannelPtr,QString,QString)), + SLOT(onChannelInvalidated(Tp::AccountPtr,Tp::ChannelPtr,QString,QString))); + + newChannels.append(channel); + readyOps.append(wrapper->becomeReady()); + } + + if (readyOps.isEmpty()) { + context->setFinished(); + return; + } + + PendingComposite *pc = new PendingComposite(readyOps, + false /* failOnFirstError */, SharedPtr<Observer>(this)); + mObserveChannelsInfo.insert(pc, new ContextInfo(context, account, newChannels)); + connect(pc, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onChannelsReady(Tp::PendingOperation*))); +} + +void SimpleObserver::Private::Observer::onChannelInvalidated(const AccountPtr &channelAccount, + const ChannelPtr &channel, const QString &errorName, const QString &errorMessage) +{ + if (mIncompleteChannels.contains(channel)) { + // we are still handling the channel, wait for onChannelsReady that will properly remove + // it from mChannels + return; + } + emit channelInvalidated(channelAccount, channel, errorName, errorMessage); + Q_ASSERT(mChannels.contains(channel)); + delete mChannels.take(channel); +} + +void SimpleObserver::Private::Observer::onChannelsReady(PendingOperation *op) +{ + ContextInfo *info = mObserveChannelsInfo.value(op); + + foreach (const ChannelPtr &channel, info->channels) { + Q_ASSERT(mIncompleteChannels.contains(channel)); + ChannelWrapper *wrapper = mIncompleteChannels.take(channel); + mChannels.insert(channel, wrapper); + } + emit newChannels(info->account, info->channels); + + foreach (const ChannelPtr &channel, info->channels) { + ChannelWrapper *wrapper = mChannels.value(channel); + if (!channel->isValid()) { + mChannels.remove(channel); + emit channelInvalidated(info->account, channel, channel->invalidationReason(), + channel->invalidationMessage()); + delete wrapper; + } + } + + mObserveChannelsInfo.remove(op); + info->context->setFinished(); + delete info; +} + +Features SimpleObserver::Private::Observer::featuresFor( + const ChannelClassSpec &channelClass) const +{ + Features features; + + foreach (const ChannelClassFeatures &spec, mExtraChannelFeatures) { + if (spec.first.isSubsetOf(channelClass)) { + features.unite(spec.second); + } + } + + return features; +} + +SimpleObserver::Private::ChannelWrapper::ChannelWrapper(const AccountPtr &channelAccount, + const ChannelPtr &channel, const Features &extraChannelFeatures, QObject *parent) + : QObject(parent), + mChannelAccount(channelAccount), + mChannel(channel), + mExtraChannelFeatures(extraChannelFeatures) +{ + connect(channel.data(), + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + SLOT(onChannelInvalidated(Tp::DBusProxy*,QString,QString))); +} + +PendingOperation *SimpleObserver::Private::ChannelWrapper::becomeReady() +{ + PendingOperation *op; + + if (!mChannel->isReady(mExtraChannelFeatures)) { + // The channel factory passed to the Account used by SimpleObserver does + // not contain the extra features, request them + op = mChannel->becomeReady(mExtraChannelFeatures); + } else { + op = new PendingSuccess(mChannel); + } + + return op; +} + +void SimpleObserver::Private::ChannelWrapper::onChannelInvalidated(DBusProxy *proxy, + const QString &errorName, const QString &errorMessage) +{ + Q_ASSERT(proxy == mChannel.data()); + emit channelInvalidated(mChannelAccount, mChannel, errorName, errorMessage); +} + +/** + * \class SimpleObserver + * \ingroup utils + * \headerfile TelepathyQt/simple-observer.h <TelepathyQt/SimpleObserver> + * + * \brief The SimpleObserver class provides an easy way to track channels + * in an account and can be optionally filtered by a contact. + */ + +/** + * Create a new SimpleObserver object. + * + * Events will be signalled for all channels in \a account that match + * \a channelFilter for all contacts. + * + * \param channelFilter A specification of the channels in which this observer + * is interested. + * \param account The account used to listen to events. + * \param extraChannelFeatures Extra channel features to be enabled. All channels emitted in + * newChannels() will have the extra features that match their + * immutable properties enabled. + * \return An SimpleObserverPtr object pointing to the newly created SimpleObserver object. + */ +SimpleObserverPtr SimpleObserver::create( + const AccountPtr &account, + const ChannelClassSpecList &channelFilter, + const QList<ChannelClassFeatures> &extraChannelFeatures) +{ + return create(account, channelFilter, extraChannelFeatures); +} + +/** + * Create a new SimpleObserver object. + * + * Events will be signalled for all channels in \a account established with + * \a contact, if not null, and that match \a channelFilter. + * + * \param channelFilter A specification of the channels in which this observer + * is interested. + * \param account The account used to listen to events. + * \param contact The contact used to filter events. + * \param extraChannelFeatures Extra channel features to be enabled. All channels emitted in + * newChannels() will have the extra features that match their + * immutable properties enabled. + * \return An SimpleObserverPtr object pointing to the newly created SimpleObserver object. + */ +SimpleObserverPtr SimpleObserver::create( + const AccountPtr &account, + const ChannelClassSpecList &channelFilter, + const ContactPtr &contact, + const QList<ChannelClassFeatures> &extraChannelFeatures) +{ + if (contact) { + return create(account, channelFilter, contact->id(), false, extraChannelFeatures); + } + return create(account, channelFilter, QString(), false, extraChannelFeatures); +} + +/** + * Create a new SimpleObserver object. + * + * Events will be signalled for all channels in \a account established + * with a contact identified by \a contactIdentifier, if non-empty, and that match + * \a channelFilter. + * + * \param channelFilter A specification of the channels in which this observer + * is interested. + * \param account The account used to listen to events. + * \param contactIdentifier The identifier of the contact used to filter events. + * \param extraChannelFeatures Extra channel features to be enabled. All channels emitted in + * newChannels() will have the extra features that match their + * immutable properties enabled. + * \return An SimpleObserverPtr object pointing to the newly created SimpleObserver object. + */ +SimpleObserverPtr SimpleObserver::create( + const AccountPtr &account, + const ChannelClassSpecList &channelFilter, + const QString &contactIdentifier, + const QList<ChannelClassFeatures> &extraChannelFeatures) +{ + return create(account, channelFilter, contactIdentifier, true, extraChannelFeatures); +} + +SimpleObserverPtr SimpleObserver::create( + const AccountPtr &account, + const ChannelClassSpecList &channelFilter, + const QString &contactIdentifier, + bool requiresNormalization, + const QList<ChannelClassFeatures> &extraChannelFeatures) +{ + return SimpleObserverPtr(new SimpleObserver(account, channelFilter, contactIdentifier, + requiresNormalization, extraChannelFeatures)); +} + +/** + * Construct a new SimpleObserver object. + * + * \param cr The ClientRegistrar used to register this observer. + * \param channelFilter The channel filter used by this observer. + * \param account The account used to listen to events. + * \param extraChannelFeatures Extra channel features to be enabled. All channels emitted in + * newChannels() will have the extra features that match their + * immutable properties enabled. + * \return An SimpleObserverPtr object pointing to the newly created SimpleObserver object. + */ +SimpleObserver::SimpleObserver(const AccountPtr &account, + const ChannelClassSpecList &channelFilter, + const QString &contactIdentifier, bool requiresNormalization, + const QList<ChannelClassFeatures> &extraChannelFeatures) + : mPriv(new Private(this, account, channelFilter, contactIdentifier, + requiresNormalization, extraChannelFeatures)) +{ + if (mPriv->observer) { + // populate our channels list with current observer channels + QHash<AccountPtr, QList<ChannelPtr> > channels; + foreach (const Private::ChannelWrapper *wrapper, mPriv->observer->channels()) { + channels[wrapper->channelAccount()].append(wrapper->channel()); + } + + QHash<AccountPtr, QList<ChannelPtr> >::const_iterator it = channels.constBegin(); + QHash<AccountPtr, QList<ChannelPtr> >::const_iterator end = channels.constEnd(); + for (; it != end; ++it) { + onNewChannels(it.key(), it.value()); + } + + if (requiresNormalization) { + debug() << "Contact id requires normalization. " + "Queueing events until it is normalized"; + onAccountConnectionChanged(account->connection()); + } + } +} + +/** + * Class destructor. + */ +SimpleObserver::~SimpleObserver() +{ + delete mPriv; +} + +/** + * Return the account used to listen to events. + * + * \return A pointer to the Account object. + */ +AccountPtr SimpleObserver::account() const +{ + return mPriv->account; +} + +/** + * Return a specification of the channels that this observer is interested. + * + * \return The specification of the channels as a list of ChannelClassSpec objects. + */ +ChannelClassSpecList SimpleObserver::channelFilter() const +{ + return mPriv->channelFilter; +} + +/** + * Return the extra channel features to be enabled based on the channels immutable properties. + * + * \return The features as a list of ChannelClassFeatures objects. + */ +QList<ChannelClassFeatures> SimpleObserver::extraChannelFeatures() const +{ + return mPriv->extraChannelFeatures; +} + +/** + * Return the channels being observed. + * + * \return A list of pointers to Channel objects. + */ +QList<ChannelPtr> SimpleObserver::channels() const +{ + return mPriv->channels.toList(); +} + +/** + * Return the identifier of the contact used to filter events, or an empty string if none was + * provided at construction. + * + * \return The identifier of the contact. + */ +QString SimpleObserver::contactIdentifier() const +{ + return mPriv->contactIdentifier; +} + +void SimpleObserver::onAccountConnectionChanged(const Tp::ConnectionPtr &connection) +{ + if (connection) { + connect(connection->becomeReady(Connection::FeatureConnected), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onAccountConnectionConnected())); + } +} + +void SimpleObserver::onAccountConnectionConnected() +{ + ConnectionPtr conn = mPriv->account->connection(); + + // check here again as the account connection may have changed and the op failed + if (!conn || conn->status() != ConnectionStatusConnected) { + return; + } + + debug() << "Normalizing contact id" << mPriv->contactIdentifier; + ContactManagerPtr contactManager = conn->contactManager(); + connect(contactManager->contactsForIdentifiers(QStringList() << mPriv->contactIdentifier), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onContactConstructed(Tp::PendingOperation*))); +} + +void SimpleObserver::onContactConstructed(Tp::PendingOperation *op) +{ + if (op->isError()) { + // what should we do here? retry? wait for a new connection? + warning() << "Normalizing contact id failed with" << + op->errorName() << " : " << op->errorMessage(); + return; + } + + PendingContacts *pc = qobject_cast<PendingContacts*>(op); + Q_ASSERT((pc->contacts().size() + pc->invalidIdentifiers().size()) == 1); + if (!pc->invalidIdentifiers().isEmpty()) { + warning() << "Normalizing contact id failed with invalid id" << + mPriv->contactIdentifier; + return; + } + + ContactPtr contact = pc->contacts().first(); + debug() << "Contact id" << mPriv->contactIdentifier << + "normalized to" << contact->id(); + mPriv->normalizedContactIdentifier = contact->id(); + mPriv->processChannelsQueue(); + + // disconnect all account signals we are handling + disconnect(mPriv->account.data(), 0, this, 0); +} + +void SimpleObserver::onNewChannels(const AccountPtr &channelsAccount, + const QList<ChannelPtr> &channels) +{ + if (!mPriv->contactIdentifier.isEmpty() && mPriv->normalizedContactIdentifier.isEmpty()) { + mPriv->newChannelsQueue.append(Private::NewChannelsInfo(channelsAccount, channels)); + mPriv->channelsQueue.append(&SimpleObserver::Private::processNewChannelsQueue); + return; + } + + mPriv->insertChannels(channelsAccount, channels); +} + +void SimpleObserver::onChannelInvalidated(const AccountPtr &channelAccount, + const ChannelPtr &channel, const QString &errorName, const QString &errorMessage) +{ + if (!mPriv->contactIdentifier.isEmpty() && mPriv->normalizedContactIdentifier.isEmpty()) { + mPriv->channelsInvalidationQueue.append(Private::ChannelInvalidationInfo(channelAccount, + channel, errorName, errorMessage)); + mPriv->channelsQueue.append(&SimpleObserver::Private::processChannelsInvalidationQueue); + return; + } + + mPriv->removeChannel(channelAccount, channel, errorName, errorMessage); +} + +/** + * \fn void SimpleObserver::newChannels(const QList<Tp::ChannelPtr> &channels) + * + * Emitted whenever new channels that match this observer's criteria are created. + * + * \param channels The new channels. + */ + +/** + * \fn void SimpleObserver::channelInvalidated(const Tp::ChannelPtr &channel, + * const QString &errorName, const QString &errorMessage) + * + * Emitted whenever a channel that is being observed is invalidated. + * + * \param channel The channel that was invalidated. + * \param errorName A D-Bus error name (a string in a subset + * of ASCII, prefixed with a reversed domain name). + * \param errorMessage A debugging message associated with the error. + */ + +} // Tp diff --git a/TelepathyQt/simple-observer.h b/TelepathyQt/simple-observer.h new file mode 100644 index 00000000..e460d747 --- /dev/null +++ b/TelepathyQt/simple-observer.h @@ -0,0 +1,105 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +#ifndef _TelepathyQt_simple_observer_h_HEADER_GUARD_ +#define _TelepathyQt_simple_observer_h_HEADER_GUARD_ + +#include <TelepathyQt/AbstractClientObserver> +#include <TelepathyQt/ChannelClassFeatures> +#include <TelepathyQt/Types> + +#include <QObject> + +namespace Tp +{ + +class PendingOperation; + +class TP_QT_EXPORT SimpleObserver : public QObject, public RefCounted +{ + Q_OBJECT + Q_DISABLE_COPY(SimpleObserver) + +public: + static SimpleObserverPtr create(const AccountPtr &account, + const ChannelClassSpecList &channelFilter, + const QList<ChannelClassFeatures> &extraChannelFeatures = + QList<ChannelClassFeatures>()); + static SimpleObserverPtr create(const AccountPtr &account, + const ChannelClassSpecList &channelFilter, + const ContactPtr &contact, + const QList<ChannelClassFeatures> &extraChannelFeatures = + QList<ChannelClassFeatures>()); + static SimpleObserverPtr create(const AccountPtr &account, + const ChannelClassSpecList &channelFilter, + const QString &contactIdentifier, + const QList<ChannelClassFeatures> &extraChannelFeatures = + QList<ChannelClassFeatures>()); + + virtual ~SimpleObserver(); + + AccountPtr account() const; + ChannelClassSpecList channelFilter() const; + QString contactIdentifier() const; + QList<ChannelClassFeatures> extraChannelFeatures() const; + + QList<ChannelPtr> channels() const; + +Q_SIGNALS: + void newChannels(const QList<Tp::ChannelPtr> &channels); + void channelInvalidated(const Tp::ChannelPtr &channel, const QString &errorName, + const QString &errorMessage); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onAccountConnectionChanged(const Tp::ConnectionPtr &connection); + TP_QT_NO_EXPORT void onAccountConnectionConnected(); + TP_QT_NO_EXPORT void onContactConstructed(Tp::PendingOperation *op); + + TP_QT_NO_EXPORT void onNewChannels(const Tp::AccountPtr &channelsAccount, + const QList<Tp::ChannelPtr> &channels); + TP_QT_NO_EXPORT void onChannelInvalidated(const Tp::AccountPtr &channelAccount, + const Tp::ChannelPtr &channel, const QString &errorName, const QString &errorMessage); + +private: + friend class SimpleCallObserver; + friend class SimpleTextObserver; + + TP_QT_NO_EXPORT static SimpleObserverPtr create(const AccountPtr &account, + const ChannelClassSpecList &channelFilter, + const QString &contactIdentifier, + bool requiresNormalization, + const QList<ChannelClassFeatures> &extraChannelFeatures); + + TP_QT_NO_EXPORT SimpleObserver(const AccountPtr &account, + const ChannelClassSpecList &channelFilter, + const QString &contactIdentifier, + bool requiresNormalization, + const QList<ChannelClassFeatures> &extraChannelFeatures); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/simple-pending-operations.h b/TelepathyQt/simple-pending-operations.h new file mode 100644 index 00000000..dce87852 --- /dev/null +++ b/TelepathyQt/simple-pending-operations.h @@ -0,0 +1,110 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2009 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 + */ + +#ifndef _TelepathyQt_pending_operations_h_HEADER_GUARD_ +#define _TelepathyQt_pending_operations_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <QObject> + +#include <TelepathyQt/PendingOperation> + +namespace Tp +{ + +class TP_QT_EXPORT PendingSuccess : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingSuccess) + +public: + PendingSuccess(const SharedPtr<RefCounted> &object) + : PendingOperation(object) + { + setFinished(); + } +}; + +class TP_QT_EXPORT PendingFailure : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingFailure) + +public: + PendingFailure(const QString &name, const QString &message, + const SharedPtr<RefCounted> &object) + : PendingOperation(object) + { + setFinishedWithError(name, message); + } + + PendingFailure(const QDBusError &error, + const SharedPtr<RefCounted> &object) + : PendingOperation(object) + { + setFinishedWithError(error); + } +}; + +class TP_QT_EXPORT PendingVoid : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingVoid) + +public: + PendingVoid(QDBusPendingCall call, const SharedPtr<RefCounted> &object); + +private Q_SLOTS: + TP_QT_NO_EXPORT void watcherFinished(QDBusPendingCallWatcher*); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +class TP_QT_EXPORT PendingComposite : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingComposite) + +public: + PendingComposite(const QList<PendingOperation*> &operations, const SharedPtr<RefCounted> &object); + PendingComposite(const QList<PendingOperation*> &operations, bool failOnFirstError, + const SharedPtr<RefCounted> &object); + ~PendingComposite(); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onOperationFinished(Tp::PendingOperation *); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/simple-stream-tube-handler.cpp b/TelepathyQt/simple-stream-tube-handler.cpp new file mode 100644 index 00000000..7df9e4ea --- /dev/null +++ b/TelepathyQt/simple-stream-tube-handler.cpp @@ -0,0 +1,254 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 "TelepathyQt/simple-stream-tube-handler.h" + +#include "TelepathyQt/_gen/simple-stream-tube-handler.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Account> +#include <TelepathyQt/ChannelClassSpec> +#include <TelepathyQt/PendingComposite> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/StreamTubeChannel> + +namespace Tp +{ + +namespace +{ + ChannelClassSpecList buildFilter(const QStringList &p2pServices, + const QStringList &roomServices, bool requested) + { + ChannelClassSpecList filter; + + // Convert to QSet to weed out duplicates + foreach (const QString &service, p2pServices.toSet()) + { + filter.append(requested ? + ChannelClassSpec::outgoingStreamTube(service) : + ChannelClassSpec::incomingStreamTube(service)); + } + + // Convert to QSet to weed out duplicates + foreach (const QString &service, roomServices.toSet()) + { + filter.append(requested ? + ChannelClassSpec::outgoingRoomStreamTube(service) : + ChannelClassSpec::incomingRoomStreamTube(service)); + } + + return filter; + } +} + +SharedPtr<SimpleStreamTubeHandler> SimpleStreamTubeHandler::create( + const QStringList &p2pServices, + const QStringList &roomServices, + bool requested, + bool monitorConnections, + bool bypassApproval) +{ + return SharedPtr<SimpleStreamTubeHandler>( + new SimpleStreamTubeHandler( + p2pServices, + roomServices, + requested, + monitorConnections, + bypassApproval)); +} + +SimpleStreamTubeHandler::SimpleStreamTubeHandler( + const QStringList &p2pServices, + const QStringList &roomServices, + bool requested, + bool monitorConnections, + bool bypassApproval) + : AbstractClient(), + AbstractClientHandler(buildFilter(p2pServices, roomServices, requested)), + mMonitorConnections(monitorConnections), + mBypassApproval(bypassApproval) +{ +} + +SimpleStreamTubeHandler::~SimpleStreamTubeHandler() +{ + if (!mTubes.empty()) { + debug() << "~SSTubeHandler(): Closing" << mTubes.size() << "leftover tubes"; + + foreach (const StreamTubeChannelPtr &tube, mTubes.keys()) { + tube->requestClose(); + } + } +} + +void SimpleStreamTubeHandler::handleChannels( + const MethodInvocationContextPtr<> &context, + const AccountPtr &account, + const ConnectionPtr &connection, + const QList<ChannelPtr> &channels, + const QList<ChannelRequestPtr> &requestsSatisfied, + const QDateTime &userActionTime, + const HandlerInfo &handlerInfo) +{ + debug() << "SimpleStreamTubeHandler::handleChannels() invoked for " << + channels.size() << "channels on account" << account->objectPath(); + + SharedPtr<InvocationData> invocation(new InvocationData()); + QList<PendingOperation *> readyOps; + + foreach (const ChannelPtr &chan, channels) { + StreamTubeChannelPtr tube = StreamTubeChannelPtr::qObjectCast(chan); + + if (!tube) { + // TODO: if Channel ever starts utilizing its immutable props for the immutable + // accessors, use Channel::channelType() here + const QString channelType = + chan->immutableProperties()[QLatin1String( + TELEPATHY_INTERFACE_CHANNEL ".ChannelType")].toString(); + + if (channelType != TP_QT_IFACE_CHANNEL_TYPE_STREAM_TUBE) { + debug() << "We got a non-StreamTube channel" << chan->objectPath() << + "of type" << channelType << ", ignoring"; + } else { + warning() << "The channel factory used for a simple StreamTube handler must" << + "construct StreamTubeChannel subclasses for stream tubes"; + } + continue; + } + + Features features = StreamTubeChannel::FeatureStreamTube; + if (mMonitorConnections) { + features.insert(StreamTubeChannel::FeatureConnectionMonitoring); + } + readyOps.append(tube->becomeReady(features)); + + invocation->tubes.append(tube); + } + + invocation->ctx = context; + invocation->acc = account; + invocation->time = userActionTime; + + if (!requestsSatisfied.isEmpty()) { + invocation->hints = requestsSatisfied.first()->hints(); + } + + mInvocations.append(invocation); + + if (invocation->tubes.isEmpty()) { + warning() << "SSTH::HandleChannels got no suitable channels, admitting we're Confused"; + invocation->readyOp = 0; + invocation->error = TP_QT_ERROR_CONFUSED; + invocation->message = QLatin1String("Got no suitable channels"); + onReadyOpFinished(0); + } else { + invocation->readyOp = new PendingComposite(readyOps, SharedPtr<SimpleStreamTubeHandler>(this)); + connect(invocation->readyOp, + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onReadyOpFinished(Tp::PendingOperation*))); + } +} + +void SimpleStreamTubeHandler::onReadyOpFinished(Tp::PendingOperation *op) +{ + Q_ASSERT(!mInvocations.isEmpty()); + Q_ASSERT(!op || op->isFinished()); + + for (QLinkedList<SharedPtr<InvocationData> >::iterator i = mInvocations.begin(); + op != 0 && i != mInvocations.end(); ++i) { + if ((*i)->readyOp != op) { + continue; + } + + (*i)->readyOp = 0; + + if (op->isError()) { + warning() << "Preparing proxies for SSTubeHandler failed with" << op->errorName() + << op->errorMessage(); + (*i)->error = op->errorName(); + (*i)->message = op->errorMessage(); + } + + break; + } + + while (!mInvocations.isEmpty() && !mInvocations.first()->readyOp) { + SharedPtr<InvocationData> invocation = mInvocations.takeFirst(); + + if (!invocation->error.isEmpty()) { + // We guarantee that the proxies were ready - so we can't invoke the client if they + // weren't made ready successfully. Fix the introspection code if this happens :) + invocation->ctx->setFinishedWithError(invocation->error, invocation->message); + continue; + } + + debug() << "Emitting SSTubeHandler::invokedForTube for" << invocation->tubes.size() + << "tubes"; + + foreach (const StreamTubeChannelPtr &tube, invocation->tubes) { + if (!tube->isValid()) { + debug() << "Skipping already invalidated tube" << tube->objectPath(); + continue; + } + + if (!mTubes.contains(tube)) { + connect(tube.data(), + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + SLOT(onTubeInvalidated(Tp::DBusProxy*,QString,QString))); + + mTubes.insert(tube, invocation->acc); + } + + emit invokedForTube( + invocation->acc, + tube, + invocation->time, + invocation->hints); + } + + invocation->ctx->setFinished(); + } +} + +void SimpleStreamTubeHandler::onTubeInvalidated(DBusProxy *proxy, + const QString &errorName, const QString &errorMessage) +{ + StreamTubeChannelPtr tube(qobject_cast<StreamTubeChannel *>(proxy)); + + Q_ASSERT(!tube.isNull()); + Q_ASSERT(mTubes.contains(tube)); + + debug() << "Tube" << tube->objectPath() << "invalidated - " << errorName << ':' << errorMessage; + + AccountPtr acc = mTubes.value(tube); + mTubes.remove(tube); + + emit tubeInvalidated( + acc, + tube, + errorName, + errorMessage); +} + +} // Tp diff --git a/TelepathyQt/simple-stream-tube-handler.h b/TelepathyQt/simple-stream-tube-handler.h new file mode 100644 index 00000000..d5b78dcb --- /dev/null +++ b/TelepathyQt/simple-stream-tube-handler.h @@ -0,0 +1,120 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +#ifndef _TelepathyQt_simple_stream_tube_handler_h_HEADER_GUARD_ +#define _TelepathyQt_simple_stream_tube_handler_h_HEADER_GUARD_ + +#include <TelepathyQt/AbstractClientHandler> +#include <TelepathyQt/ChannelRequestHints> +#include <TelepathyQt/RefCounted> +#include <TelepathyQt/Types> + +#include <QDateTime> +#include <QLinkedList> +#include <QHash> +#include <QQueue> +#include <QSet> + +namespace Tp +{ + +class PendingOperation; + +class TP_QT_NO_EXPORT SimpleStreamTubeHandler : public QObject, + public AbstractClientHandler +{ + Q_OBJECT + Q_DISABLE_COPY(SimpleStreamTubeHandler) + +public: + static SharedPtr<SimpleStreamTubeHandler> create( + const QStringList &p2pServices, + const QStringList &roomServices, + bool requested, + bool monitorConnections, + bool bypassApproval = false); + ~SimpleStreamTubeHandler(); + + bool monitorsConnections() const + { + return mMonitorConnections; + } + + bool bypassApproval() const + { + return mBypassApproval; + } + + void handleChannels(const MethodInvocationContextPtr<> &context, + const AccountPtr &account, + const ConnectionPtr &connection, + const QList<ChannelPtr> &channels, + const QList<ChannelRequestPtr> &requestsSatisfied, + const QDateTime &userActionTime, + const HandlerInfo &handlerInfo); + +Q_SIGNALS: + void invokedForTube( + const Tp::AccountPtr &account, + const Tp::StreamTubeChannelPtr &tube, + const QDateTime &userActionTime, + const Tp::ChannelRequestHints &requestHints); + void tubeInvalidated( + const Tp::AccountPtr &account, + const Tp::StreamTubeChannelPtr &tube, + const QString &errorName, + const QString &errorMessage); + +private Q_SLOTS: + void onReadyOpFinished(Tp::PendingOperation *); + void onTubeInvalidated(Tp::DBusProxy *, const QString &, const QString &); + +private: + SimpleStreamTubeHandler( + const QStringList &p2pServices, + const QStringList &roomServices, + bool requested, + bool monitorConnections, + bool bypassApproval); + + bool mMonitorConnections; + + struct InvocationData : RefCounted { + InvocationData() : readyOp(0) {} + + PendingOperation *readyOp; + QString error, message; + + MethodInvocationContextPtr<> ctx; + AccountPtr acc; + QList<StreamTubeChannelPtr> tubes; + QDateTime time; + ChannelRequestHints hints; + }; + QLinkedList<SharedPtr<InvocationData> > mInvocations; + QHash<StreamTubeChannelPtr, AccountPtr> mTubes; + bool mBypassApproval; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/simple-text-observer-internal.h b/TelepathyQt/simple-text-observer-internal.h new file mode 100644 index 00000000..91b5e1d9 --- /dev/null +++ b/TelepathyQt/simple-text-observer-internal.h @@ -0,0 +1,70 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 <TelepathyQt/Account> +#include <TelepathyQt/Message> +#include <TelepathyQt/SimpleObserver> +#include <TelepathyQt/TextChannel> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT SimpleTextObserver::Private +{ + Private(SimpleTextObserver *parent, const AccountPtr &account, + const QString &contactIdentifier, bool requiresNormalization); + ~Private(); + + class TextChannelWrapper; + + SimpleTextObserver *parent; + AccountPtr account; + QString contactIdentifier; + SimpleObserverPtr observer; + QHash<ChannelPtr, TextChannelWrapper*> channels; +}; + +class TP_QT_NO_EXPORT SimpleTextObserver::Private::TextChannelWrapper : + public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(TextChannelWrapper) + +public: + TextChannelWrapper(const Tp::TextChannelPtr &channel); + ~TextChannelWrapper() { } + +Q_SIGNALS: + void channelMessageSent(const Tp::Message &message, Tp::MessageSendingFlags flags, + const QString &sentMessageToken, const Tp::TextChannelPtr &channel); + void channelMessageReceived(const Tp::ReceivedMessage &message, const Tp::TextChannelPtr &channel); + +private Q_SLOTS: + void onChannelMessageSent(const Tp::Message &message, Tp::MessageSendingFlags flags, + const QString &sentMessageToken); + void onChannelMessageReceived(const Tp::ReceivedMessage &message); + +private: + TextChannelPtr mChannel; +}; + +} // Tp diff --git a/TelepathyQt/simple-text-observer.cpp b/TelepathyQt/simple-text-observer.cpp new file mode 100644 index 00000000..2de0b6e1 --- /dev/null +++ b/TelepathyQt/simple-text-observer.cpp @@ -0,0 +1,298 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 <TelepathyQt/SimpleTextObserver> +#include "TelepathyQt/simple-text-observer-internal.h" + +#include "TelepathyQt/_gen/simple-text-observer.moc.hpp" +#include "TelepathyQt/_gen/simple-text-observer-internal.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/ChannelClassSpec> +#include <TelepathyQt/ChannelClassSpecList> +#include <TelepathyQt/Connection> +#include <TelepathyQt/Contact> +#include <TelepathyQt/ContactManager> +#include <TelepathyQt/Message> +#include <TelepathyQt/PendingContacts> +#include <TelepathyQt/PendingComposite> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/PendingSuccess> + +namespace Tp +{ + +SimpleTextObserver::Private::Private(SimpleTextObserver *parent, + const AccountPtr &account, + const QString &contactIdentifier, bool requiresNormalization) + : parent(parent), + account(account), + contactIdentifier(contactIdentifier) +{ + debug() << "Creating a new SimpleTextObserver"; + ChannelClassSpec channelFilter = ChannelClassSpec::textChat(); + observer = SimpleObserver::create(account, ChannelClassSpecList() << channelFilter, + contactIdentifier, requiresNormalization, + QList<ChannelClassFeatures>() << ChannelClassFeatures(channelFilter, + TextChannel::FeatureMessageQueue | TextChannel::FeatureMessageSentSignal)); + + parent->connect(observer.data(), + SIGNAL(newChannels(QList<Tp::ChannelPtr>)), + SLOT(onNewChannels(QList<Tp::ChannelPtr>))); + parent->connect(observer.data(), + SIGNAL(channelInvalidated(Tp::ChannelPtr,QString,QString)), + SLOT(onChannelInvalidated(Tp::ChannelPtr))); +} + +SimpleTextObserver::Private::~Private() +{ + foreach (TextChannelWrapper *wrapper, channels) { + delete wrapper; + } +} + +SimpleTextObserver::Private::TextChannelWrapper::TextChannelWrapper(const TextChannelPtr &channel) + : mChannel(channel) +{ + connect(mChannel.data(), + SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString)), + SLOT(onChannelMessageSent(Tp::Message,Tp::MessageSendingFlags,QString))); + connect(mChannel.data(), + SIGNAL(messageReceived(Tp::ReceivedMessage)), + SLOT(onChannelMessageReceived(Tp::ReceivedMessage))); +} + +void SimpleTextObserver::Private::TextChannelWrapper::onChannelMessageSent( + const Tp::Message &message, Tp::MessageSendingFlags flags, + const QString &sentMessageToken) +{ + emit channelMessageSent(message, flags, sentMessageToken, mChannel); +} + +void SimpleTextObserver::Private::TextChannelWrapper::onChannelMessageReceived( + const Tp::ReceivedMessage &message) +{ + emit channelMessageReceived(message, mChannel); +} + +/** + * \class SimpleTextObserver + * \ingroup utils + * \headerfile TelepathyQt/simple-text-observer.h <TelepathyQt/SimpleTextObserver> + * + * \brief The SimpleTextObserver class provides an easy way to track sent/received text messages + * in an account and can be optionally filtered by a contact. + */ + +/** + * Create a new SimpleTextObserver object. + * + * Events will be signalled for all messages sent/received by all contacts in \a account. + * + * \param account The account used to listen to events. + * \return An SimpleTextObserverPtr object pointing to the newly created SimpleTextObserver object. + */ +SimpleTextObserverPtr SimpleTextObserver::create(const AccountPtr &account) +{ + return create(account, QString(), false); +} + +/** + * Create a new SimpleTextObserver object. + * + * If \a contact is not null, events will be signalled for all messages sent/received by \a + * contact, otherwise this method works the same as create(const Tp::AccountPtr &). + * + * \param account The account used to listen to events. + * \param contact The contact used to filter events. + * \return An SimpleTextObserverPtr object pointing to the newly created SimpleTextObserver object. + */ +SimpleTextObserverPtr SimpleTextObserver::create(const AccountPtr &account, + const ContactPtr &contact) +{ + if (contact) { + return create(account, contact->id(), false); + } + return create(account, QString(), false); +} + +/** + * Create a new SimpleTextObserver object. + * + * If \a contactIdentifier is non-empty, events will be signalled for all messages sent/received + * by a contact identified by \a contactIdentifier, otherwise this method works the same as + * create(const Tp::AccountPtr &). + * + * \param account The account used to listen to events. + * \param contactIdentifier The identifier of the contact used to filter events. + * \return An SimpleTextObserverPtr object pointing to the newly created SimpleTextObserver object. + */ +SimpleTextObserverPtr SimpleTextObserver::create(const AccountPtr &account, + const QString &contactIdentifier) +{ + return create(account, contactIdentifier, true); +} + +SimpleTextObserverPtr SimpleTextObserver::create(const AccountPtr &account, + const QString &contactIdentifier, bool requiresNormalization) +{ + return SimpleTextObserverPtr( + new SimpleTextObserver(account, contactIdentifier, requiresNormalization)); +} + +/** + * Construct a new SimpleTextObserver object. + * + * \param account The account used to listen to events. + * \param contactIdentifier The identifier of the contact used to filter events. + * \param requiresNormalization Whether \a contactIdentifier needs to be normalized. + * \return An SimpleTextObserverPtr object pointing to the newly created SimpleTextObserver object. + */ +SimpleTextObserver::SimpleTextObserver(const AccountPtr &account, + const QString &contactIdentifier, bool requiresNormalization) + : mPriv(new Private(this, account, contactIdentifier, requiresNormalization)) +{ + if (!mPriv->observer->channels().isEmpty()) { + onNewChannels(mPriv->observer->channels()); + } +} + +/** + * Class destructor. + */ +SimpleTextObserver::~SimpleTextObserver() +{ + delete mPriv; +} + +/** + * Return the account used to listen to events. + * + * \return A pointer to the Account object. + */ +AccountPtr SimpleTextObserver::account() const +{ + return mPriv->account; +} + +/** + * Return the identifier of the contact used to filter events, or an empty string if none was + * provided at construction. + * + * \return The identifier of the contact. + */ +QString SimpleTextObserver::contactIdentifier() const +{ + return mPriv->contactIdentifier; +} + +/** + * Return the list of text chats currently being observed. + * + * \return A list of pointers to TextChannel objects. + */ +QList<TextChannelPtr> SimpleTextObserver::textChats() const +{ + QList<TextChannelPtr> ret; + foreach (const ChannelPtr &channel, mPriv->observer->channels()) { + TextChannelPtr textChannel = TextChannelPtr::qObjectCast(channel); + if (textChannel) { + ret << textChannel; + } + } + return ret; +} + +void SimpleTextObserver::onNewChannels(const QList<ChannelPtr> &channels) +{ + foreach (const ChannelPtr &channel, channels) { + TextChannelPtr textChannel = TextChannelPtr::qObjectCast(channel); + if (!textChannel) { + if (channel->channelType() != TP_QT_IFACE_CHANNEL_TYPE_TEXT) { + warning() << "Channel received to observe is not of type Text, service confused. " + "Ignoring channel"; + } else { + warning() << "Channel received to observe is not a subclass of TextChannel. " + "ChannelFactory set on this observer's account must construct TextChannel " + "subclasses for channels of type Text. Ignoring channel"; + } + continue; + } + + if (mPriv->channels.contains(channel)) { + // we are already observing this channel + continue; + } + + Private::TextChannelWrapper *wrapper = new Private::TextChannelWrapper(textChannel); + mPriv->channels.insert(channel, wrapper); + connect(wrapper, + SIGNAL(channelMessageSent(Tp::Message,Tp::MessageSendingFlags,QString,Tp::TextChannelPtr)), + SIGNAL(messageSent(Tp::Message,Tp::MessageSendingFlags,QString,Tp::TextChannelPtr))); + connect(wrapper, + SIGNAL(channelMessageReceived(Tp::ReceivedMessage,Tp::TextChannelPtr)), + SIGNAL(messageReceived(Tp::ReceivedMessage,Tp::TextChannelPtr))); + + foreach (const ReceivedMessage &message, textChannel->messageQueue()) { + emit messageReceived(message, textChannel); + } + } +} + +void SimpleTextObserver::onChannelInvalidated(const ChannelPtr &channel) +{ + // it may happen that the channel received in onNewChannels is not a text channel somehow, thus + // the channel won't be added to mPriv->channels + if (mPriv->channels.contains(channel)) { + delete mPriv->channels.take(channel); + } +} + +/** + * \fn void SimpleTextObserver::messageSent(const Tp::Message &message, + * Tp::MessageSendingFlags flags, const QString &sentMessageToken, + * const Tp::TextChannelPtr &channel); + * + * Emitted whenever a text message on account() is sent. + * If contactIdentifier() is non-empty, only messages sent to the contact identified by it will + * be signalled. + * + * \param message The message sent. + * \param flags The message flags, + * \param sentMessageToken The message token. + * \param channel The channel which received the message. + */ + +/** + * \fn void SimpleTextObserver::messageReceived(const Tp::ReceivedMessage &message, + * const Tp::TextChannelPtr &channel); + * + * Emitted whenever a text message on account() is received. + * If contactIdentifier() is non-empty, only messages received by the contact identified by it will + * be signalled. + * + * \param message The message received. + * \param channel The channel which received the message. + */ + +} // Tp diff --git a/TelepathyQt/simple-text-observer.h b/TelepathyQt/simple-text-observer.h new file mode 100644 index 00000000..87b16736 --- /dev/null +++ b/TelepathyQt/simple-text-observer.h @@ -0,0 +1,80 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +#ifndef _TelepathyQt_simple_text_observer_h_HEADER_GUARD_ +#define _TelepathyQt_simple_text_observer_h_HEADER_GUARD_ + +#include <TelepathyQt/Constants> +#include <TelepathyQt/Types> + +#include <QObject> + +namespace Tp +{ + +class Message; +class PendingOperation; +class ReceivedMessage; + +class TP_QT_EXPORT SimpleTextObserver : public QObject, public RefCounted +{ + Q_OBJECT + Q_DISABLE_COPY(SimpleTextObserver) + +public: + static SimpleTextObserverPtr create(const AccountPtr &account); + static SimpleTextObserverPtr create(const AccountPtr &account, + const ContactPtr &contact); + static SimpleTextObserverPtr create(const AccountPtr &account, + const QString &contactIdentifier); + + virtual ~SimpleTextObserver(); + + AccountPtr account() const; + QString contactIdentifier() const; + + QList<TextChannelPtr> textChats() const; + +Q_SIGNALS: + void messageSent(const Tp::Message &message, Tp::MessageSendingFlags flags, + const QString &sentMessageToken, const Tp::TextChannelPtr &channel); + void messageReceived(const Tp::ReceivedMessage &message, const Tp::TextChannelPtr &channel); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onNewChannels(const QList<Tp::ChannelPtr> &channels); + TP_QT_NO_EXPORT void onChannelInvalidated(const Tp::ChannelPtr &channel); + +private: + TP_QT_NO_EXPORT static SimpleTextObserverPtr create(const AccountPtr &account, + const QString &contactIdentifier, bool requiresNormalization); + + TP_QT_NO_EXPORT SimpleTextObserver(const AccountPtr &account, + const QString &contactIdentifier, bool requiresNormalization); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/stable-interfaces.xml b/TelepathyQt/stable-interfaces.xml new file mode 100644 index 00000000..fb9a0aca --- /dev/null +++ b/TelepathyQt/stable-interfaces.xml @@ -0,0 +1,26 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>Telepathy D-Bus Interface Specification, TelepathyQt copy</tp:title> +<tp:version>0.17.7</tp:version> + +<xi:include href="connection-manager.xml"/> +<xi:include href="connection.xml"/> +<xi:include href="channel.xml"/> +<xi:include href="channel-dispatcher.xml"/> +<xi:include href="channel-dispatch-operation.xml"/> +<xi:include href="channel-request.xml"/> +<xi:include href="media-session-handler.xml"/> +<xi:include href="media-stream-handler.xml"/> +<xi:include href="dbus.xml"/> +<xi:include href="properties.xml"/> +<xi:include href="account-manager.xml"/> +<xi:include href="account.xml"/> +<xi:include href="client.xml"/> +<xi:include href="tls-certificate.xml"/> + +<xi:include href="../spec/generic-types.xml"/> +<xi:include href="../spec/errors.xml"/> + +</tp:spec> diff --git a/TelepathyQt/stream-tube-channel.cpp b/TelepathyQt/stream-tube-channel.cpp new file mode 100644 index 00000000..8a8b83c5 --- /dev/null +++ b/TelepathyQt/stream-tube-channel.cpp @@ -0,0 +1,740 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/StreamTubeChannel> + +#include "TelepathyQt/_gen/stream-tube-channel.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Connection> +#include <TelepathyQt/ContactManager> +#include <TelepathyQt/PendingContacts> +#include <TelepathyQt/PendingVariantMap> + +#include <QHostAddress> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT StreamTubeChannel::Private +{ + Private(StreamTubeChannel *parent); + + static void introspectStreamTube(Private *self); + static void introspectConnectionMonitoring(Private *self); + + void extractStreamTubeProperties(const QVariantMap &props); + + // Public object + StreamTubeChannel *parent; + + ReadinessHelper *readinessHelper; + + // Introspection + SupportedSocketMap socketTypes; + QString serviceName; + + QSet<uint> connections; + QPair<QHostAddress, quint16> ipAddress; + QString unixAddress; + SocketAddressType addressType; + SocketAccessControl accessControl; + bool droppingConnections; +}; + +StreamTubeChannel::Private::Private(StreamTubeChannel *parent) + : parent(parent), + readinessHelper(parent->readinessHelper()), + addressType(SocketAddressTypeUnix), + accessControl(SocketAccessControlLocalhost), + droppingConnections(false) +{ + ReadinessHelper::Introspectables introspectables; + + ReadinessHelper::Introspectable introspectableStreamTube( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << TubeChannel::FeatureCore, // dependsOnFeatures (core) + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &StreamTubeChannel::Private::introspectStreamTube, + this); + introspectables[StreamTubeChannel::FeatureCore] = introspectableStreamTube; + + ReadinessHelper::Introspectable introspectableConnectionMonitoring( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << StreamTubeChannel::FeatureCore, // dependsOnFeatures (core) + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) + &StreamTubeChannel::Private::introspectConnectionMonitoring, + this); + introspectables[StreamTubeChannel::FeatureConnectionMonitoring] = + introspectableConnectionMonitoring; + + parent->connect( + parent, + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + SLOT(dropConnections())); + + readinessHelper->addIntrospectables(introspectables); +} + +void StreamTubeChannel::Private::introspectStreamTube( + StreamTubeChannel::Private *self) +{ + StreamTubeChannel *parent = self->parent; + + debug() << "Introspecting stream tube properties"; + Client::ChannelTypeStreamTubeInterface *streamTubeInterface = + parent->interface<Client::ChannelTypeStreamTubeInterface>(); + + PendingVariantMap *pvm = streamTubeInterface->requestAllProperties(); + parent->connect(pvm, + SIGNAL(finished(Tp::PendingOperation *)), + SLOT(gotStreamTubeProperties(Tp::PendingOperation *))); +} + +void StreamTubeChannel::Private::introspectConnectionMonitoring( + StreamTubeChannel::Private *self) +{ + StreamTubeChannel *parent = self->parent; + + Client::ChannelTypeStreamTubeInterface *streamTubeInterface = + parent->interface<Client::ChannelTypeStreamTubeInterface>(); + + parent->connect(streamTubeInterface, + SIGNAL(ConnectionClosed(uint,QString,QString)), + SLOT(onConnectionClosed(uint,QString,QString))); + + if (parent->isRequested()) { + parent->connect(streamTubeInterface, + SIGNAL(NewRemoteConnection(uint,QDBusVariant,uint)), + SLOT(onNewRemoteConnection(uint,QDBusVariant,uint))); + } else { + parent->connect(streamTubeInterface, + SIGNAL(NewLocalConnection(uint)), + SLOT(onNewLocalConnection(uint))); + } + + self->readinessHelper->setIntrospectCompleted( + StreamTubeChannel::FeatureConnectionMonitoring, true); +} + +void StreamTubeChannel::Private::extractStreamTubeProperties(const QVariantMap &props) +{ + serviceName = qdbus_cast<QString>(props[QLatin1String("Service")]); + socketTypes = qdbus_cast<SupportedSocketMap>(props[QLatin1String("SupportedSocketTypes")]); +} + +/** + * \class StreamTubeChannel + * \ingroup clientchannel + * \headerfile TelepathyQt/stream-tube-channel.h <TelepathyQt/StreamTubeChannel> + * + * \brief The StreamTubeChannel class represents a Telepathy channel of type StreamTube. + * + * It provides a transport for reliable and ordered data transfer, similar to SOCK_STREAM sockets. + * + * StreamTubeChannel is an intermediate base class; OutgoingStreamTubeChannel and + * IncomingStreamTubeChannel are the specialized classes used for locally and remotely initiated + * tubes respectively. + * + * For more details, please refer to \telepathy_spec. + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Feature representing the core that needs to become ready to make the + * StreamTubeChannel object usable. + * + * Note that this feature must be enabled in order to use most + * StreamTubeChannel methods. + * See specific methods documentation for more details. + */ +const Feature StreamTubeChannel::FeatureCore = + Feature(QLatin1String(StreamTubeChannel::staticMetaObject.className()), 0); + +/** + * \deprecated Use StreamTubeChannel::FeatureCore instead. + */ +const Feature StreamTubeChannel::FeatureStreamTube = StreamTubeChannel::FeatureCore; + +/** + * Feature used in order to monitor connections to this stream tube. + * + * See connection monitoring specific methods' documentation for more details. + * + * \sa newConnection(), connectionClosed() + */ +const Feature StreamTubeChannel::FeatureConnectionMonitoring = + Feature(QLatin1String(StreamTubeChannel::staticMetaObject.className()), 1); + +/** + * Create a new StreamTubeChannel channel. + * + * \param connection Connection owning this channel, and specifying the + * service. + * \param objectPath The channel object path. + * \param immutableProperties The channel immutable properties. + * \return A StreamTubeChannelPtr object pointing to the newly created + * StreamTubeChannel object. + */ +StreamTubeChannelPtr StreamTubeChannel::create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties) +{ + return StreamTubeChannelPtr(new StreamTubeChannel(connection, objectPath, + immutableProperties, StreamTubeChannel::FeatureCore)); +} + +/** + * Construct a new StreamTubeChannel 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, if any. The corresponding introspectable should + * depend on StreamTubeChannel::FeatureCore. + */ +StreamTubeChannel::StreamTubeChannel(const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : TubeChannel(connection, objectPath, immutableProperties, coreFeature), + mPriv(new Private(this)) +{ +} + +/** + * Class destructor. + */ +StreamTubeChannel::~StreamTubeChannel() +{ + delete mPriv; +} + +/** + * Return the service name which will be used over this stream tube. This should be a + * well-known TCP service name, for instance "rsync" or "daap". + * + * This method requires StreamTubeChannel::FeatureCore to be ready. + * + * \return The service name. + */ +QString StreamTubeChannel::service() const +{ + if (!isReady(FeatureCore)) { + warning() << "StreamTubeChannel::service() used with " + "FeatureCore not ready"; + return QString(); + } + + return mPriv->serviceName; +} + +/** + * Return whether this stream tube is capable to accept or offer an IPv4 socket accepting all + * incoming connections coming from localhost. + * + * Note that the \telepathy_spec implies that any connection manager, if capable of providing + * stream tubes, must at least support IPv4 sockets with localhost access control. + * For this reason, this method should always return \c true. + * + * This method requires StreamTubeChannel::FeatureCore to be ready. + * + * \return \c true if the stream tube is capable to accept or offer an IPv4 socket + * accepting all incoming connections coming from localhost, \c false otherwise. + * \sa IncomingStreamTubeChannel::acceptTubeAsTcpSocket(), + * OutgoingStreamTubeChannel::offerTcpSocket(), + * supportsIPv4SocketsWithSpecifiedAddress() + */ +bool StreamTubeChannel::supportsIPv4SocketsOnLocalhost() const +{ + if (!isReady(FeatureCore)) { + warning() << "StreamTubeChannel::supportsIPv4SocketsOnLocalhost() used with " + "FeatureCore not ready"; + return false; + } + + return mPriv->socketTypes.value(SocketAddressTypeIPv4).contains(SocketAccessControlLocalhost); +} + +/** + * Return whether this stream tube is capable to accept an IPv4 socket accepting all + * incoming connections coming from a specific address for incoming tubes or whether + * this stream tube is capable of mapping connections to the socket's source address for outgoing + * tubes. + * + * For incoming tubes, when this capability is available, the stream tube can be accepted specifying + * an IPv4 address. Every connection coming from any other address than the specified one will be + * rejected. + * + * For outgoing tubes, when this capability is available, one can keep track of incoming connections + * by enabling StreamTubeChannel::FeatureConnectionMonitoring (possibly before + * opening the stream tube itself), and checking OutgoingStreamTubeChannel::contactsForConnections() + * or OutgoingStreamTubeChannel::connectionsForSourceAddresses(). + * + * Note that it is strongly advised to call this method before attempting to call + * IncomingStreamTubeChannel::acceptTubeAsTcpSocket() or + * OutgoingStreamTubeChannel::offerTcpSocket() with a specified address to prevent failures, + * as the spec implies this feature is not compulsory for connection managers. + * + * This method requires StreamTubeChannel::FeatureCore to be ready. + * + * \return \c true if the stream tube is capable to accept an IPv4 socket accepting all + * incoming connections coming from a specific address for incoming tubes or + * the stream tube is capable of mapping connections to the socket's source address for + * outgoing tubes, \c false otherwise. + * \sa IncomingStreamTubeChannel::acceptTubeAsTcpSocket(), + * OutgoingStreamTubeChannel::offerTcpSocket(), + * OutgoingStreamTubeChannel::connectionsForSourceAddresses(), + * OutgoingStreamTubeChannel::contactsForConnections(), + * supportsIPv4SocketsOnLocalhost() + */ +bool StreamTubeChannel::supportsIPv4SocketsWithSpecifiedAddress() const +{ + if (!isReady(FeatureCore)) { + warning() << "StreamTubeChannel::supportsIPv4SocketsWithSpecifiedAddress() used with " + "FeatureCore not ready"; + return false; + } + + return mPriv->socketTypes.value(SocketAddressTypeIPv4).contains(SocketAccessControlPort); +} + +/** + * Return whether this stream tube is capable to accept or offer an IPv6 socket accepting all + * incoming connections coming from localhost. + * + * Note that it is strongly advised to call this method before attempting to call + * IncomingStreamTubeChannel::acceptTubeAsTcpSocket() or + * OutgoingStreamTubeChannel::offerTcpSocket() with a specified address to prevent failures, + * as the spec implies this feature is not compulsory for connection managers. + * + * This method requires StreamTubeChannel::FeatureCore to be ready. + * + * \return \c true if the stream tube is capable to accept or offer an IPv6 socket + * accepting all incoming connections coming from localhost, \c false otherwise. + * \sa IncomingStreamTubeChannel::acceptTubeAsTcpSocket(), + * OutgoingStreamTubeChannel::offerTcpSocket(), + * supportsIPv6SocketsWithSpecifiedAddress() + */ +bool StreamTubeChannel::supportsIPv6SocketsOnLocalhost() const +{ + if (!isReady(FeatureCore)) { + warning() << "StreamTubeChannel::supportsIPv6SocketsOnLocalhost() used with " + "FeatureCore not ready"; + return false; + } + + return mPriv->socketTypes.value(SocketAddressTypeIPv6).contains(SocketAccessControlLocalhost); +} + +/** + * Return whether this stream tube is capable to accept an IPv6 socket accepting all + * incoming connections coming from a specific address for incoming tubes or whether + * this stream tube is capable of mapping connections to the socket's source address for outgoing + * tubes. + * + * For incoming tubes, when this capability is available, the stream tube can be accepted specifying + * an IPv6 address. Every connection coming from any other address than the specified one will be + * rejected. + * + * For outgoing tubes, when this capability is available, one can keep track of incoming connections + * by enabling StreamTubeChannel::FeatureConnectionMonitoring (possibly before + * opening the stream tube itself), and checking OutgoingStreamTubeChannel::contactsForConnections() + * or OutgoingStreamTubeChannel::connectionsForSourceAddresses(). + * + * Note that it is strongly advised to call this method before attempting to call + * IncomingStreamTubeChannel::acceptTubeAsTcpSocket() or + * OutgoingStreamTubeChannel::offerTcpSocket() with a specified address to prevent failures, + * as the spec implies this feature is not compulsory for connection managers. + * + * This method requires StreamTubeChannel::FeatureCore to be ready. + * + * \return \c true if the stream tube is capable to accept an IPv6 socket accepting all + * incoming connections coming from a specific address for incoming tubes or + * the stream tube is capable of mapping connections to the socket's source address for + * outgoing tubes, \c false otherwise. + * \sa IncomingStreamTubeChannel::acceptTubeAsTcpSocket(), + * OutgoingStreamTubeChannel::offerTcpSocket(), + * OutgoingStreamTubeChannel::connectionsForSourceAddresses(), + * OutgoingStreamTubeChannel::contactsForConnections(), + * supportsIPv6SocketsOnLocalhost() + */ +bool StreamTubeChannel::supportsIPv6SocketsWithSpecifiedAddress() const +{ + if (!isReady(FeatureCore)) { + warning() << "StreamTubeChannel::supportsIPv6SocketsWithSpecifiedAddress() used with " + "FeatureCore not ready"; + return false; + } + + return mPriv->socketTypes.value(SocketAddressTypeIPv6).contains(SocketAccessControlPort); +} + +/** + * Return whether this stream tube is capable to accept or offer an Unix socket accepting all + * incoming connections coming from localhost. + * + * Note that it is strongly advised to call this method before attempting to call + * IncomingStreamTubeChannel::acceptTubeAsUnixSocket() or + * OutgoingStreamTubeChannel::offerUnixSocket() without credentials enabled, as the spec implies + * this feature is not compulsory for connection managers. + * + * This method requires StreamTubeChannel::FeatureCore to be ready. + * + * \return \c true if the stream tube is capable to accept or offer an Unix socket + * accepting all incoming connections coming from localhost, \c false otherwise. + * \sa IncomingStreamTubeChannel::acceptTubeAsUnixSocket(), + * OutgoingStreamTubeChannel::offerUnixSocket(), + * supportsUnixSocketsWithCredentials() + * supportsAbstractUnixSocketsOnLocalhost(), + * supportsAbstractUnixSocketsWithCredentials(), + */ +bool StreamTubeChannel::supportsUnixSocketsOnLocalhost() const +{ + if (!isReady(FeatureCore)) { + warning() << "StreamTubeChannel::supportsUnixSocketsOnLocalhost() used with " + "FeatureCore not ready"; + return false; + } + + return mPriv->socketTypes.value(SocketAddressTypeUnix).contains(SocketAccessControlLocalhost); +} + +/** + * Return whether this stream tube is capable to accept or offer an Unix socket which will require + * credentials upon connection. + * + * When this capability is available and enabled, the connecting process must send a byte when + * it first connects, which is not considered to be part of the data stream. + * If the operating system uses sendmsg() with SCM_CREDS or SCM_CREDENTIALS to pass + * credentials over sockets, the connecting process must do so if possible; + * if not, it must still send the byte. + * + * The listening process will disconnect the connection unless it can determine + * by OS-specific means that the connecting process has the same user ID as the listening process. + * + * Note that it is strongly advised to call this method before attempting to call + * IncomingStreamTubeChannel::acceptTubeAsUnixSocket() or + * OutgoingStreamTubeChannel::offerUnixSocket() with credentials enabled, as the spec implies + * this feature is not compulsory for connection managers. + * + * This method requires StreamTubeChannel::FeatureCore to be ready. + * + * \return \c true if the stream tube is capable to accept or offer an Unix socket + * which will require credentials upon connection, \c false otherwise. + * \sa IncomingStreamTubeChannel::acceptTubeAsUnixSocket(), + * OutgoingStreamTubeChannel::offerUnixSocket(), + * supportsUnixSocketsOnLocalhost(), + * supportsAbstractUnixSocketsOnLocalhost(), + * supportsAbstractUnixSocketsWithCredentials(), + */ +bool StreamTubeChannel::supportsUnixSocketsWithCredentials() const +{ + if (!isReady(FeatureCore)) { + warning() << "StreamTubeChannel::supportsUnixSocketsWithCredentials() used with " + "FeatureCore not ready"; + return false; + } + + return mPriv->socketTypes[SocketAddressTypeUnix].contains(SocketAccessControlCredentials); +} + +/** + * Return whether this stream tube is capable to accept or offer an abstract Unix socket accepting + * all incoming connections coming from localhost. + * + * Note that it is strongly advised to call this method before attempting to call + * IncomingStreamTubeChannel::acceptTubeAsUnixSocket() or + * OutgoingStreamTubeChannel::offerUnixSocket() without credentials enabled, as the spec implies + * this feature is not compulsory for connection managers. + * + * This method requires StreamTubeChannel::FeatureCore to be ready. + * + * \return \c true if the stream tube is capable to accept or offer an abstract Unix socket + * accepting all incoming connections coming from localhost, \c false otherwise. + * \sa IncomingStreamTubeChannel::acceptTubeAsUnixSocket(), + * OutgoingStreamTubeChannel::offerUnixSocket(), + * supportsUnixSocketsOnLocalhost(), + * supportsUnixSocketsWithCredentials(), + * supportsAbstractUnixSocketsWithCredentials() + */ +bool StreamTubeChannel::supportsAbstractUnixSocketsOnLocalhost() const +{ + if (!isReady(FeatureCore)) { + warning() << "StreamTubeChannel::supportsAbstractUnixSocketsOnLocalhost() used with " + "FeatureCore not ready"; + return false; + } + + return mPriv->socketTypes[SocketAddressTypeAbstractUnix].contains(SocketAccessControlLocalhost); +} + +/** + * Return whether this stream tube is capable to accept or offer an abstract Unix socket which will + * require credentials upon connection. + * + * When this capability is available and enabled, the connecting process must send a byte when + * it first connects, which is not considered to be part of the data stream. + * If the operating system uses sendmsg() with SCM_CREDS or SCM_CREDENTIALS to pass + * credentials over sockets, the connecting process must do so if possible; + * if not, it must still send the byte. + * + * The listening process will disconnect the connection unless it can determine + * by OS-specific means that the connecting process has the same user ID as the listening process. + * + * Note that it is strongly advised to call this method before attempting to call + * IncomingStreamTubeChannel::acceptTubeAsUnixSocket() or + * OutgoingStreamTubeChannel::offerUnixSocket() with credentials enabled, as the spec implies + * this feature is not compulsory for connection managers. + * + * This method requires StreamTubeChannel::FeatureCore to be ready. + * + * \return \c true if the stream tube is capable to accept or offer an abstract Unix socket + * which will require credentials upon connection, \c false otherwise. + * \sa IncomingStreamTubeChannel::acceptTubeAsUnixSocket(), + * OutgoingStreamTubeChannel::offerUnixSocket(), + * supportsUnixSocketsOnLocalhost(), + * supportsUnixSocketsWithCredentials(), + * supportsAbstractUnixSocketsOnLocalhost() + */ +bool StreamTubeChannel::supportsAbstractUnixSocketsWithCredentials() const +{ + if (!isReady(FeatureCore)) { + warning() << "StreamTubeChannel::supportsAbstractUnixSocketsWithCredentials() used with " + "FeatureCore not ready"; + return false; + } + + return mPriv->socketTypes[SocketAddressTypeAbstractUnix].contains(SocketAccessControlCredentials); +} + +/** + * Return all the known active connections since StreamTubeChannel::FeatureConnectionMonitoring has + * been enabled. + * + * For this method to return all known connections, you need to make + * StreamTubeChannel::FeatureConnectionMonitoring ready before accepting or offering the stream + * tube. + * + * This method requires StreamTubeChannel::FeatureConnectionMonitoring to be ready. + * + * \return The list of active connection ids. + * \sa newConnection(), connectionClosed() + */ +UIntList StreamTubeChannel::connections() const +{ + if (!isReady(FeatureConnectionMonitoring)) { + warning() << "StreamTubeChannel::connections() used with " + "FeatureConnectionMonitoring not ready"; + return UIntList(); + } + + return mPriv->connections.toList(); +} + +/** + * Return the type of the tube's local endpoint socket. + * + * Note that this function will return a valid value only after state() has gone #TubeStateOpen. + * + * \return The socket type as #SocketAddressType. + * \sa localAddress(), ipAddress() + */ +SocketAddressType StreamTubeChannel::addressType() const +{ + return mPriv->addressType; +} + +/** + * Return the access control used by this stream tube. + * + * Note that this function will only return a valid value after state() has gone #TubeStateOpen. + * + * \return The access control as #SocketAccessControl. + * \sa addressType() + */ +SocketAccessControl StreamTubeChannel::accessControl() const +{ + return mPriv->accessControl; +} + +/** + * Return the IP address/port combination used by this stream tube. + * + * This method will return a meaningful value only if the local endpoint socket for the tube is a + * TCP socket, i.e. addressType() is #SocketAddressTypeIPv4 or #SocketAddressTypeIPv6. + * + * Note that this function will return a valid value only after state() has gone #TubeStateOpen. + * + * \return Pair of IP address as QHostAddress and port if using a TCP socket, + * or an undefined value otherwise. + * \sa localAddress() + */ +QPair<QHostAddress, quint16> StreamTubeChannel::ipAddress() const +{ + if (state() != TubeChannelStateOpen) { + warning() << "Tube not open, returning invalid IP address"; + return qMakePair<QHostAddress, quint16>(QHostAddress::Null, 0); + } + + return mPriv->ipAddress; +} + +/** + * Return the local address used by this stream tube. + * + * This method will return a meaningful value only if the local endpoint socket for the tube is an + * UNIX socket, i.e. addressType() is #SocketAddressTypeUnix or #SocketAddressTypeAbstractUnix. + * + * Note that this function will return a valid value only after state() has gone #TubeStateOpen. + * + * \return Unix socket address if using an Unix socket, + * or an undefined value otherwise. + * \sa ipAddress() + */ +QString StreamTubeChannel::localAddress() const +{ + if (state() != TubeChannelStateOpen) { + warning() << "Tube not open, returning invalid local socket address"; + return QString(); + } + + return mPriv->unixAddress; +} + +/** + * \deprecated This method never did anything useful when called from outside, + * and now does nothing at all. It will be removed in the next API/ABI break. + */ +void StreamTubeChannel::setBaseTubeType(uint type) +{ + Q_UNUSED(type); +} + +void StreamTubeChannel::setConnections(UIntList connections) +{ + // This is rather sub-optimal: we'll do a O(n) replace of the old connections list every time a + // connection is added, so O(n^2) in total for adding n connections + mPriv->connections = QSet<uint>::fromList(connections); +} + +void StreamTubeChannel::addConnection(uint connection) +{ + if (!mPriv->connections.contains(connection)) { + mPriv->connections.insert(connection); + emit newConnection(connection); + } else { + warning() << "Tried to add connection" << connection << "on StreamTube" << objectPath() + << "but it already was there"; + } +} + +void StreamTubeChannel::removeConnection(uint connection, const QString &error, + const QString &message) +{ + if (mPriv->connections.contains(connection)) { + mPriv->connections.remove(connection); + emit connectionClosed(connection, error, message); + } else { + warning() << "Tried to remove connection" << connection << "from StreamTube" << objectPath() + << "but it wasn't there"; + } +} + +void StreamTubeChannel::setAddressType(SocketAddressType type) +{ + mPriv->addressType = type; +} + +void StreamTubeChannel::setAccessControl(SocketAccessControl accessControl) +{ + mPriv->accessControl = accessControl; +} + +void StreamTubeChannel::setIpAddress(const QPair<QHostAddress, quint16> &address) +{ + mPriv->ipAddress = address; +} + +void StreamTubeChannel::setLocalAddress(const QString &address) +{ + mPriv->unixAddress = address; +} + +bool StreamTubeChannel::isDroppingConnections() const +{ + return mPriv->droppingConnections; +} + +void StreamTubeChannel::gotStreamTubeProperties(PendingOperation *op) +{ + if (!op->isError()) { + PendingVariantMap *pvm = qobject_cast<PendingVariantMap *>(op); + + mPriv->extractStreamTubeProperties(pvm->result()); + + debug() << "Got reply to Properties::GetAll(StreamTubeChannel)"; + mPriv->readinessHelper->setIntrospectCompleted(StreamTubeChannel::FeatureCore, true); + } + else { + warning().nospace() << "Properties::GetAll(StreamTubeChannel) failed " + "with " << op->errorName() << ": " << op->errorMessage(); + mPriv->readinessHelper->setIntrospectCompleted(StreamTubeChannel::FeatureCore, false, + op->errorName(), op->errorMessage()); + } +} + +void StreamTubeChannel::onConnectionClosed(uint connId, const QString &error, + const QString &message) +{ + removeConnection(connId, error, message); +} + +void StreamTubeChannel::dropConnections() +{ + if (!mPriv->connections.isEmpty()) { + debug() << "StreamTubeChannel invalidated with" << mPriv->connections.size() + << "connections remaining, synthesizing close events"; + mPriv->droppingConnections = true; + foreach (uint connId, mPriv->connections) { + removeConnection(connId, TP_QT_ERROR_ORPHANED, + QLatin1String("parent tube invalidated, streams closing")); + } + mPriv->droppingConnections = false; + } +} + +/** + * \fn void StreamTubeChannel::connectionClosed(uint connectionId, + * const QString &errorName, const QString &errorMessage) + * + * Emitted when a connection on this stream tube has been closed. + * + * \param connectionId The unique ID associated with the connection that was closed. + * \param errorName The name of a D-Bus error describing the error that occurred. + * \param errorMessage A debugging message associated with the error. + * \sa newConnection(), connections() + */ + +} // Tp diff --git a/TelepathyQt/stream-tube-channel.h b/TelepathyQt/stream-tube-channel.h new file mode 100644 index 00000000..38ec6c43 --- /dev/null +++ b/TelepathyQt/stream-tube-channel.h @@ -0,0 +1,108 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_stream_tube_channel_h_HEADER_GUARD_ +#define _TelepathyQt_stream_tube_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/TubeChannel> + +class QHostAddress; + +namespace Tp +{ + +class TP_QT_EXPORT StreamTubeChannel : public TubeChannel +{ + Q_OBJECT + Q_DISABLE_COPY(StreamTubeChannel) + +public: + static const Feature FeatureCore; + // FIXME (API/ABI break) Remove FeatureStreamTube in favour of FeatureCore + static const Feature FeatureStreamTube; + static const Feature FeatureConnectionMonitoring; + + static StreamTubeChannelPtr create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties); + + virtual ~StreamTubeChannel(); + + QString service() const; + + bool supportsIPv4SocketsOnLocalhost() const; + bool supportsIPv4SocketsWithSpecifiedAddress() const; + + bool supportsIPv6SocketsOnLocalhost() const; + bool supportsIPv6SocketsWithSpecifiedAddress() const; + + bool supportsUnixSocketsOnLocalhost() const; + bool supportsUnixSocketsWithCredentials() const; + + bool supportsAbstractUnixSocketsOnLocalhost() const; + bool supportsAbstractUnixSocketsWithCredentials() const; + + // API/ABI break TODO: return QSet<uint> instead + UIntList connections() const; + + SocketAddressType addressType() const; + + QPair< QHostAddress, quint16 > ipAddress() const; + QString localAddress() const; + +Q_SIGNALS: + void newConnection(uint connectionId); + void connectionClosed(uint connectionId, const QString &errorName, + const QString &errorMessage); + +protected: + StreamTubeChannel(const ConnectionPtr &connection, const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature = StreamTubeChannel::FeatureCore); + + TP_QT_DEPRECATED void setBaseTubeType(uint type); + TP_QT_DEPRECATED void setConnections(UIntList connections); // -> {add,remove}Connection + void addConnection(uint connection); + void removeConnection(uint connection, const QString &error, const QString &message); + void setAddressType(SocketAddressType type); + SocketAccessControl accessControl() const; + void setAccessControl(SocketAccessControl accessControl); + void setIpAddress(const QPair<QHostAddress, quint16> &address); + void setLocalAddress(const QString &address); + bool isDroppingConnections() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void gotStreamTubeProperties(Tp::PendingOperation *op); + TP_QT_NO_EXPORT void onConnectionClosed(uint, const QString &, const QString &); + TP_QT_NO_EXPORT void dropConnections(); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} + +#endif diff --git a/TelepathyQt/stream-tube-client-internal.h b/TelepathyQt/stream-tube-client-internal.h new file mode 100644 index 00000000..b190e816 --- /dev/null +++ b/TelepathyQt/stream-tube-client-internal.h @@ -0,0 +1,61 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 <TelepathyQt/Account> +#include <TelepathyQt/ClientRegistrar> +#include <TelepathyQt/IncomingStreamTubeChannel> +#include <TelepathyQt/StreamTubeClient> + +namespace Tp +{ + +class TP_QT_NO_EXPORT StreamTubeClient::TubeWrapper : + public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(TubeWrapper) + +public: + TubeWrapper(const AccountPtr &acc, const IncomingStreamTubeChannelPtr &tube, + const QHostAddress &sourceAddress, quint16 sourcePort, StreamTubeClient *parent); + TubeWrapper(const AccountPtr &acc, const IncomingStreamTubeChannelPtr &tube, + bool requireCredentials, StreamTubeClient *parent); + ~TubeWrapper() { } + + AccountPtr mAcc; + IncomingStreamTubeChannelPtr mTube; + QHostAddress mSourceAddress; + quint16 mSourcePort; + +Q_SIGNALS: + void acceptFinished(TubeWrapper *wrapper, Tp::PendingStreamTubeConnection *conn); + void newConnection(TubeWrapper *wrapper, uint conn); + void connectionClosed(TubeWrapper *wrapper, uint conn, const QString &error, + const QString &message); + +private Q_SLOTS: + void onTubeAccepted(Tp::PendingOperation *); + void onNewConnection(uint); + void onConnectionClosed(uint, const QString &, const QString &); +}; + +} // Tp diff --git a/TelepathyQt/stream-tube-client.cpp b/TelepathyQt/stream-tube-client.cpp new file mode 100644 index 00000000..85ee2001 --- /dev/null +++ b/TelepathyQt/stream-tube-client.cpp @@ -0,0 +1,1048 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 <TelepathyQt/StreamTubeClient> + +#include "TelepathyQt/stream-tube-client-internal.h" +#include "TelepathyQt/_gen/stream-tube-client.moc.hpp" +#include "TelepathyQt/_gen/stream-tube-client-internal.moc.hpp" + +#include "TelepathyQt/debug-internal.h" +#include "TelepathyQt/simple-stream-tube-handler.h" + +#include <TelepathyQt/AccountManager> +#include <TelepathyQt/ClientRegistrar> +#include <TelepathyQt/IncomingStreamTubeChannel> +#include <TelepathyQt/PendingStreamTubeConnection> +#include <TelepathyQt/StreamTubeChannel> + +#include <QAbstractSocket> +#include <QHash> + +namespace Tp +{ + +/** + * \class StreamTubeClient::TcpSourceAddressGenerator + * \ingroup serverclient + * \headerfile TelepathyQt/stream-tube-client.h <TelepathyQt/StreamTubeClient> + * + * \brief The StreamTubeClient::TcpSourceAddressGenerator abstract interface allows using socket + * source address/port based access control for connecting to tubes accepted as TCP sockets. + * + * By default, every application on the local computer is allowed to connect to the socket created + * by the protocol backend as the local endpoint of the tube. This is not always desirable, as that + * includes even other users. + * + * Note that since every TCP connection must have an unique source address, only one simultaneous + * connection can be made through each tube for which this type of access control has been used. + */ + +/** + * \fn QPair<QHostAddress, quint16> StreamTubeClient::TcpSourceAddressGenerator::nextSourceAddress(const AccountPtr &, const + * IncomingStreamTubeChannelPtr &) + * + * Return the source address from which connections will be allowed to the given \a tube once it has + * been accepted. + * + * Returning the pair (QHostAddress::Any, 0) makes the protocol backend allow connections from any + * address on the local computer. This can be used on a tube-by-tube basis if for some tubes its + * known that multiple connections need to be made, so a single source address doesn't suffice. + * + * The \a account and \a tube parameters can be inspected to make the decision; typically by looking + * at the tube's service type, parameters and/or initiator contact. + * + * The general pattern for implementing this method is: + * <ol> + * <li>Determine whether \a tube needs to allow multiple connections, and if so, skip source address + * access control completely</li> + * <li>Otherwise, create a socket and bind it to a free address</li> + * <li>Return this socket's address</li> + * <li>Keep the socket bound so that no other process can (accidentally or maliciously) take the + * address until it's used to connect to the tube when StreamTubeClient::tubeAcceptedAsTcp() is + * emitted for the tube</li> + * </ol> + * + * \param account The account from which the tube originates. + * \param tube The tube channel which is going to be accepted by the StreamTubeClient. + * + * \return A pair containing the host address and port allowed to connect. + */ + +/** + * \fn StreamTubeClient::TcpSourceAddressGenerator::~TcpSourceAddressGenerator + * + * Class destructor. Protected, because StreamTubeClient never deletes a TcpSourceAddressGenerator passed + * to it. + */ + +struct TP_QT_NO_EXPORT StreamTubeClient::Tube::Private : public QSharedData +{ + // empty placeholder for now +}; + +/** + * \class StreamTubeClient::Tube + * \ingroup serverclient + * \headerfile TelepathyQt/stream-tube-client.h <TelepathyQt/StreamTubeClient> + * + * \brief The StreamTubeClient::Tube class represents a tube being handled by the client. + */ + +/** + * Constructs a new invalid Tube instance. + */ +StreamTubeClient::Tube::Tube() +{ + // invalid instance +} + +/** + * Constructs a Tube instance for the given tube \a channel from the given \a account. + * + * \param account A pointer to the account the online connection of which the tube originates from. + * \param channel A pointer to the tube channel object. + */ +StreamTubeClient::Tube::Tube( + const AccountPtr &account, + const IncomingStreamTubeChannelPtr &channel) + : QPair<AccountPtr, IncomingStreamTubeChannelPtr>(account, channel), mPriv(new Private) +{ +} + +/** + * Copy constructor. + */ +StreamTubeClient::Tube::Tube( + const Tube &other) + : QPair<AccountPtr, IncomingStreamTubeChannelPtr>(other.account(), other.channel()), + mPriv(other.mPriv) +{ +} + +/** + * Class destructor. + */ +StreamTubeClient::Tube::~Tube() +{ + // mPriv deleted automatically +} + +/** + * Assignment operator. + */ +StreamTubeClient::Tube &StreamTubeClient::Tube::operator=( + const Tube &other) +{ + if (&other == this) { + return *this; + } + + first = other.account(); + second = other.channel(); + mPriv = other.mPriv; + + return *this; +} + +/** + * \fn bool StreamTubeClient::Tube::isValid() const + * + * Return whether or not the tube is valid or is just the null object created using the default + * constructor. + * + * \return \c true if valid, \c false otherwise. + */ + +/** + * \fn AccountPtr StreamTubeClient::Tube::account() const + * + * Return the account from which the tube originates. + * + * \return A pointer to the account object. + */ + +/** + * \fn IncomingStreamTubeChannelPtr StreamTubeClient::Tube::channel() const + * + * Return the actual tube channel. + * + * \return A pointer to the channel. + */ + +struct TP_QT_NO_EXPORT StreamTubeClient::Private +{ + Private(const ClientRegistrarPtr ®istrar, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &maybeClientName, + bool monitorConnections, + bool bypassApproval) + : registrar(registrar), + handler(SimpleStreamTubeHandler::create( + p2pServices, roomServices, false, monitorConnections, bypassApproval)), + clientName(maybeClientName), + isRegistered(false), + acceptsAsTcp(false), acceptsAsUnix(false), + tcpGenerator(0), requireCredentials(false) + { + if (clientName.isEmpty()) { + clientName = QString::fromLatin1("TpQt4STubeClient_%1_%2") + .arg(registrar->dbusConnection().baseService() + .replace(QLatin1Char(':'), QLatin1Char('_')) + .replace(QLatin1Char('.'), QLatin1Char('_'))) + .arg((intptr_t) this, 0, 16); + } + } + + void ensureRegistered() + { + if (isRegistered) { + return; + } + + debug() << "Register StreamTubeClient with name " << clientName; + + if (registrar->registerClient(handler, clientName)) { + isRegistered = true; + } else { + warning() << "StreamTubeClient" << clientName + << "registration failed"; + } + } + + ClientRegistrarPtr registrar; + SharedPtr<SimpleStreamTubeHandler> handler; + QString clientName; + bool isRegistered; + + bool acceptsAsTcp, acceptsAsUnix; + TcpSourceAddressGenerator *tcpGenerator; + bool requireCredentials; + + QHash<StreamTubeChannelPtr, TubeWrapper *> tubes; +}; + +StreamTubeClient::TubeWrapper::TubeWrapper( + const AccountPtr &acc, + const IncomingStreamTubeChannelPtr &tube, + const QHostAddress &sourceAddress, + quint16 sourcePort, + StreamTubeClient *parent) + : QObject(parent), mAcc(acc), mTube(tube), mSourceAddress(sourceAddress), mSourcePort(sourcePort) +{ + if (sourcePort != 0) { + if ((sourceAddress.protocol() == QAbstractSocket::IPv4Protocol && + !tube->supportsIPv4SocketsWithSpecifiedAddress()) || + (sourceAddress.protocol() == QAbstractSocket::IPv6Protocol && + !tube->supportsIPv6SocketsWithSpecifiedAddress())) { + debug() << "StreamTubeClient falling back to Localhost AC for tube" << + tube->objectPath(); + mSourceAddress = sourceAddress.protocol() == QAbstractSocket::IPv4Protocol ? + QHostAddress::Any : QHostAddress::AnyIPv6; + mSourcePort = 0; + } + } + + connect(tube->acceptTubeAsTcpSocket(mSourceAddress, mSourcePort), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onTubeAccepted(Tp::PendingOperation*))); + connect(tube.data(), + SIGNAL(newConnection(uint)), + SLOT(onNewConnection(uint))); + connect(tube.data(), + SIGNAL(connectionClosed(uint,QString,QString)), + SLOT(onConnectionClosed(uint,QString,QString))); +} + +StreamTubeClient::TubeWrapper::TubeWrapper( + const AccountPtr &acc, + const IncomingStreamTubeChannelPtr &tube, + bool requireCredentials, + StreamTubeClient *parent) + : QObject(parent), mAcc(acc), mTube(tube), mSourcePort(0) +{ + if (requireCredentials && !tube->supportsUnixSocketsWithCredentials()) { + debug() << "StreamTubeClient falling back to Localhost AC for tube" << tube->objectPath(); + requireCredentials = false; + } + + connect(tube->acceptTubeAsUnixSocket(requireCredentials), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onTubeAccepted(Tp::PendingOperation*))); + connect(tube.data(), + SIGNAL(newConnection(uint)), + SLOT(onNewConnection(uint))); + connect(tube.data(), + SIGNAL(connectionClosed(uint,QString,QString)), + SLOT(onConnectionClosed(uint,QString,QString))); +} + +void StreamTubeClient::TubeWrapper::onTubeAccepted(Tp::PendingOperation *op) +{ + emit acceptFinished(this, qobject_cast<Tp::PendingStreamTubeConnection *>(op)); +} + +void StreamTubeClient::TubeWrapper::onNewConnection(uint conn) +{ + emit newConnection(this, conn); +} + +void StreamTubeClient::TubeWrapper::onConnectionClosed(uint conn, const QString &error, + const QString &message) +{ + emit connectionClosed(this, conn, error, message); +} + +/** + * \class StreamTubeClient + * \ingroup serverclient + * \headerfile TelepathyQt/stream-tube-client.h <TelepathyQt/StreamTubeClient> + * + * \brief The StreamTubeClient class is a Handler implementation for incoming %Stream %Tube channels, + * allowing an application to easily get notified about services they can connect to offered to them + * over Telepathy Tubes without worrying about the channel dispatching details. + * + * Telepathy Tubes is a technology for connecting arbitrary applications together through the IM + * network (and sometimes with direct peer-to-peer connections), such that issues like firewall/NAT + * traversal are automatically handled. Stream Tubes in particular offer properties similar to + * SOCK_STREAM sockets. The StreamTubeClient class negotiates tubes offered to us so that an + * application can connect such bytestream sockets of theirs to them. The StreamTubeServer class is + * the counterpart, offering services from a bytestream socket server to tubes requested to be + * initiated. + * + * Both peer-to-peer (\c TargetHandleType == \ref HandleTypeContact) and group (\c TargetHandleType + * == \ref HandleTypeRoom) channels are supported, and it's possible to specify the tube services to + * handle for each separately. There must be at least one service in total declared, as it never + * makes sense to handle stream tubes without considering the protocol of the service offered + * through them. + * + * %Connection monitoring allows fine-grained error reporting for connections made through tubes, + * and observing connections being made and broken even if the application code running + * StreamTubeClient can't easily get this information from the code actually connecting through it. + * Such a setting might occur e.g. when a wrapper application is developed to connect some existing + * "black box" networked application through TCP tubes by launching it with the appropriate command + * line arguments or alike for accepted tubes. + * + * Enabling connection monitoring adds a small overhead and latency to handling each incoming tube + * and signaling each new incoming connection over them, though, so use it only when needed. + * + * A service activated Handler can be implemented using StreamTubeClient by passing a predefined \a + * clientName manually to the chosen create() method, and installing Telepathy \c .client and D-Bus + * \c .service files declaring the implemented tube services as channel classes and a path to the + * executable. If this is not needed, the \a clientName can be omitted, in which case a random + * unique client name is generated and used instead. However, then the tube client application must + * already be running for remote contacts to be able to offer services to us over tubes. + * + * Whether the Handler application implemented using StreamTubeClient is service activatable or not, + * incoming channels will typically first be given to an Approver, if there is one for tube services + * corresponding to the tube in question. Only if the Approver decides that the tube communication + * should be allowed (usually by asking the user), or if there is no matching Approver at all, is + * the channel given to the actual Handler tube client. This can be overridden by setting \a + * bypassApproval to \c true, which skips approval for the given services completely and directs + * them straight to the Handler. + * + * StreamTubeClient shares Account, Connection and Channel proxies and Contact objects with the + * rest of the application as long as a reference to the AccountManager, ClientRegistrar, or the + * factories used elsewhere is passed to the create() method. A stand-alone tube client Handler can + * get away without passing these however, or just passing select factories to make the desired + * features prepared and subclasses employed for these objects for their own convenience. + * + * Whichever method is used, the ChannelFactory (perhaps indirectly) given must construct + * IncomingStreamTubeChannel instances or subclasses thereof for all channel classes corresponding + * to the tube services the client should be able to connect to. This is the default; overriding it + * without obeying these constraints using ChannelFactory::setSubclassForIncomingStreamTubes() or + * the related methods for room tubes prevents StreamTubeClient from operating correctly. + * + * \todo Coin up a small Python script or alike to easily generate the .client and .service files. + * (fd.o #41614) + */ + +/** + * Create a new StreamTubeClient, which will register itself on the session bus using an internal + * ClientRegistrar and use the given factories. + * + * \param p2pServices Names of the tube services to accept on peer-to-peer tube channels. + * \param roomServices Names of the tube services to accept on room/group tube channels. + * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix). + * \param monitorConnections Whether to enable connection monitoring or not. + * \param bypassApproval \c true to skip approval, \c false to invoke an Approver for incoming + * channels if there is one. + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + */ +StreamTubeClientPtr StreamTubeClient::create( + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections, + bool bypassApproval, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return create( + QDBusConnection::sessionBus(), + accountFactory, + connectionFactory, + channelFactory, + contactFactory, + p2pServices, + roomServices, + clientName, + monitorConnections, + bypassApproval); +} + +/** + * Create a new StreamTubeClient, which will register itself on the given \a bus using an internal + * ClientRegistrar and use the given factories. + * + * The factories must all be created for the given \a bus. + * + * \param bus Connection to the bus to register on. + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels. + * \param roomServices Names of the tube services to handle on room/group tube channels. + * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix). + * \param monitorConnections Whether to enable connection monitoring or not. + * \param bypassApproval \c true to skip approval, \c false to invoke an Approver for incoming + * channels if there is one. + */ +StreamTubeClientPtr StreamTubeClient::create( + const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections, + bool bypassApproval) +{ + return create( + ClientRegistrar::create( + bus, + accountFactory, + connectionFactory, + channelFactory, + contactFactory), + p2pServices, + roomServices, + clientName, + monitorConnections, + bypassApproval); +} + +/** + * Create a new StreamTubeClient, which will register itself on the bus of and share objects with + * the given \a accountManager, creating an internal ClientRegistrar. + * + * \param accountManager A pointer to the account manager to link up with. + * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels. + * \param roomServices Names of the tube services to handle on room/group tube channels. + * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix). + * \param monitorConnections Whether to enable connection monitoring or not. + * \param bypassApproval \c true to skip approval, \c false to invoke an Approver for incoming + * channels if there is one. + */ +StreamTubeClientPtr StreamTubeClient::create( + const AccountManagerPtr &accountManager, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections, + bool bypassApproval) +{ + return create( + accountManager->dbusConnection(), + accountManager->accountFactory(), + accountManager->connectionFactory(), + accountManager->channelFactory(), + accountManager->contactFactory(), + p2pServices, + roomServices, + clientName, + monitorConnections, + bypassApproval); +} + +/** + * Create a new StreamTubeClient, which will register itself on the bus of and using the given + * client \a registrar, and share objects with it. + * + * \param registrar The client registrar to use. + * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels. + * \param roomServices Names of the tube services to handle on room/group tube channels. + * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix). + * \param monitorConnections Whether to enable connection monitoring or not. + * \param bypassApproval \c true to skip approval, \c false to invoke an Approver for incoming + * channels if there is one. + */ +StreamTubeClientPtr StreamTubeClient::create( + const ClientRegistrarPtr ®istrar, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections, + bool bypassApproval) +{ + if (p2pServices.isEmpty() && roomServices.isEmpty()) { + warning() << "Tried to create a StreamTubeClient with no services, returning NULL"; + return StreamTubeClientPtr(); + } + + return StreamTubeClientPtr( + new StreamTubeClient(registrar, p2pServices, roomServices, clientName, + monitorConnections, bypassApproval)); +} + +StreamTubeClient::StreamTubeClient( + const ClientRegistrarPtr ®istrar, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections, + bool bypassApproval) + : mPriv(new Private(registrar, p2pServices, roomServices, clientName, monitorConnections, bypassApproval)) +{ + connect(mPriv->handler.data(), + SIGNAL(invokedForTube( + Tp::AccountPtr, + Tp::StreamTubeChannelPtr, + QDateTime, + Tp::ChannelRequestHints)), + SLOT(onInvokedForTube( + Tp::AccountPtr, + Tp::StreamTubeChannelPtr, + QDateTime, + Tp::ChannelRequestHints))); +} + +/** + * Class destructor. + */ +StreamTubeClient::~StreamTubeClient() +{ + if (isRegistered()) { + mPriv->registrar->unregisterClient(mPriv->handler); + } + + delete mPriv; +} + +/** + * Return the client registrar used by the client to register itself as a Telepathy channel Handler + * %Client. + * + * This is the registrar originally passed to + * create(const ClientRegistrarPtr &, const QStringList &, const QStringList &, const QString &, bool, bool) + * if that was used, and an internally constructed one otherwise. In any case, it can be used to + * e.g. register further clients, just like any other ClientRegistrar. + * + * \return A pointer to the registrar. + */ +ClientRegistrarPtr StreamTubeClient::registrar() const +{ + return mPriv->registrar; +} + +/** + * Return the Telepathy %Client name of the client. + * + * \return The name, without the \c org.freedesktop.Telepathy.Client. prefix of the full D-Bus service name. + */ +QString StreamTubeClient::clientName() const +{ + return mPriv->clientName; +} + +/** + * Return whether the client has been successfully registered or not. + * + * Registration is attempted, at the latest, when the client is first set to accept incoming tubes, + * either as TCP sockets (setToAcceptAsTcp()) or Unix ones (setToAcceptAsUnix()). It can fail e.g. + * because the connection to the bus has failed, or a predefined \a clientName has been passed to + * create(), and a %Client with the same name is already registered. Typically, failure registering + * would be a fatal error for a stand-alone tube handler, but only a warning event for an + * application serving other purposes. In any case, a high-quality user of the API will check the + * return value of this accessor after choosing the desired address family. + * + * \return \c true if the client has been successfully registered, \c false if not. + */ +bool StreamTubeClient::isRegistered() const +{ + return mPriv->isRegistered; +} + +/** + * Return whether connection monitoring is enabled on this client. + * + * For technical reasons, connection monitoring can't be enabled when the client is already running, + * so there is no corresponding setter method. It has to be enabled by passing \c true as the \a + * monitorConnections parameter to the create() method. + * + * If connection monitoring isn't enabled, newConnection() and connectionClosed() won't be + * emitted and connections() won't be populated. + * + * \return \c true if monitoring is enabled, \c false if not. + */ +bool StreamTubeClient::monitorsConnections() const +{ + return mPriv->handler->monitorsConnections(); +} + +/** + * Return whether the client is currently set to accept incoming tubes as TCP sockets. + * + * \return \c true if the client will accept tubes as TCP sockets, \c false if it will accept them + * as Unix ones or hasn't been set to accept at all yet. + */ +bool StreamTubeClient::acceptsAsTcp() const +{ + return mPriv->acceptsAsTcp; +} + +/** + * Return the TCP source address generator, if any, set by setToAcceptAsTcp() previously. + * + * \return A pointer to the generator instance. + */ +StreamTubeClient::TcpSourceAddressGenerator *StreamTubeClient::tcpGenerator() const +{ + if (!acceptsAsTcp()) { + warning() << "StreamTubeClient::tcpGenerator() used, but not accepting as TCP, returning 0"; + return 0; + } + + return mPriv->tcpGenerator; +} + +/** + * Return whether the client is currently set to accept incoming tubes as Unix sockets. + * + * \return \c true if the client will accept tubes as Unix sockets, \c false if it will accept them + * as TCP ones or hasn't been set to accept at all yet. + */ +bool StreamTubeClient::acceptsAsUnix() const +{ + return mPriv->acceptsAsUnix; +} + +/** + * Set the client to accept tubes received to handle in the future in a fashion which will yield a + * TCP socket as the local endpoint to connect to. + * + * A source address generator can optionally be set. If non-null, it will be invoked for each new + * tube received to handle and an attempt is made to restrict connections to the tube's local socket + * endpoint to those from that source address. + * + * However, if the protocol backend doesn't actually support source address based access control, + * tubeAcceptedAsTcp() will be emitted with QHostAddress::Any as the allowed source address to + * signal that it doesn't matter where we connect from, but more importantly, that anybody else on + * the same host could have, and can, connect to the tube. The tube can be closed at this point if + * this would be unacceptable security-wise. To totally prevent the tube from being accepted in the + * first place, one can close it already when tubeOffered() is emitted for it - support for the + * needed security mechanism can be queried using its supportsIPv4SocketsWithSpecifiedAddress() + * accessor. + * + * The handler is registered on the bus at the latest when this method or setToAcceptAsUnix() is + * called for the first time, so one should check the return value of isRegistered() at that point + * to verify that was successful. + * + * \param generator A pointer to the source address generator to use, or 0 to allow all + * connections from the local host. + * + * \todo Make it possible to set the tube client to auto-close tubes if the desired access control + * level is not achieved, as an alternative to the current best-effort behavior. + */ +void StreamTubeClient::setToAcceptAsTcp(TcpSourceAddressGenerator *generator) +{ + mPriv->tcpGenerator = generator; + mPriv->acceptsAsTcp = true; + mPriv->acceptsAsUnix = false; + + mPriv->ensureRegistered(); +} + +/** + * Set the client to accept tubes received to handle in the future in a fashion which will yield a + * Unix socket as the local endpoint to connect to. + * + * If that doesn't cause problems for the payload protocol, it's possible to increase security by + * restricting the processes allowed to connect to the local endpoint socket to those from the same + * user ID as the protocol backend is running as by setting \a requireCredentials to \c true. This + * requires transmitting a single byte, signaled as the \a credentialByte parameter to the + * tubeAcceptedAsUnix() signal, in a \c SCM_CREDS or SCM_CREDENTIALS message, whichever is supported + * by the platform, as the first thing after having connected to the socket. Even if the platform + * doesn't implement either concept, the byte must still be sent. + * + * However, not all protocol backends support the credential passing based access control on all the + * platforms they can run on. If a tube is offered through such a backend, tubeAcceptedAsUnix() will + * be emitted with \a requiresCredentials set to \c false, to signal that a credential byte should + * NOT be sent for that tube, and that any local process can or could have connected to the tube + * already. The tube can be closed at this point if this would be unacceptable security-wise. To + * totally prevent the tube from being accepted in the first place, one can close it already when + * tubeOffered() is emitted for it - support for the needed security mechanism can be queried using + * its supportsIPv4SocketsWithSpecifiedAddress() + * accessor. + * + * The handler is registered on the bus at the latest when this method or setToAcceptAsTcp() is + * called for the first time, so one should check the return value of isRegistered() at that point + * to verify that was successful. + * + * \param requireCredentials \c true to try and restrict connecting by UID, \c false to allow all + * connections. + * + * \todo Make it possible to set the tube client to auto-close tubes if the desired access control + * level is not achieved, as an alternative to the current best-effort behavior. + */ +void StreamTubeClient::setToAcceptAsUnix(bool requireCredentials) +{ + mPriv->tcpGenerator = 0; + mPriv->acceptsAsTcp = false; + mPriv->acceptsAsUnix = true; + mPriv->requireCredentials = requireCredentials; + + mPriv->ensureRegistered(); +} + +/** + * Return the tubes currently handled by the client. + * + * \return A list of Tube structures containing pointers to the account and tube channel for each + * tube. + */ +QList<StreamTubeClient::Tube> StreamTubeClient::tubes() const +{ + QList<Tube> tubes; + + foreach (TubeWrapper *wrapper, mPriv->tubes.values()) { + tubes.push_back(Tube(wrapper->mAcc, wrapper->mTube)); + } + + return tubes; +} + +/** + * Return the ongoing connections established through tubes signaled by this client. + * + * The returned mapping has for each Tube a structure containing pointers to the account and tube + * channel objects as keys, with the integer identifiers for the current connections on them as the + * values. The IDs are unique amongst the connections active on a single tube at any given time, but + * not globally. + * + * This is effectively a state recovery accessor corresponding to the change notification signals + * newConnection() and connectionClosed(). + * + * The mapping is only populated if connection monitoring was requested when creating the client (so + * monitorsConnections() returns \c true). + * + * \return The connections in a mapping with Tube structures containing pointers to the account and + * channel objects for each tube as keys, and the sets of numerical IDs as values. + */ +QHash<StreamTubeClient::Tube, QSet<uint> > StreamTubeClient::connections() const +{ + QHash<Tube, QSet<uint> > conns; + if (!monitorsConnections()) { + warning() << "StreamTubeClient::connections() used, but connection monitoring is disabled"; + return conns; + } + + foreach (const Tube &tube, tubes()) { + if (!tube.channel()->isValid()) { + // The tube has been invalidated, so skip it to avoid warnings + // We're going to get rid of the wrapper in the next mainloop iteration when we get the + // invalidation signal + continue; + } + + QSet<uint> tubeConns = QSet<uint>::fromList(tube.channel()->connections()); + if (!tubeConns.empty()) { + conns.insert(tube, tubeConns); + } + } + + return conns; +} + +void StreamTubeClient::onInvokedForTube( + const AccountPtr &acc, + const StreamTubeChannelPtr &tube, + const QDateTime &time, + const ChannelRequestHints &hints) +{ + Q_ASSERT(isRegistered()); // our SSTH shouldn't be receiving any channels unless it's registered + Q_ASSERT(!tube->isRequested()); + Q_ASSERT(tube->isValid()); // SSTH won't emit invalid tubes + + if (mPriv->tubes.contains(tube)) { + debug() << "Ignoring StreamTubeClient reinvocation for tube" << tube->objectPath(); + return; + } + + IncomingStreamTubeChannelPtr incoming = IncomingStreamTubeChannelPtr::qObjectCast(tube); + + if (!incoming) { + warning() << "The ChannelFactory used by StreamTubeClient must construct" << + "IncomingStreamTubeChannel subclasses for Requested=false StreamTubes"; + tube->requestClose(); + return; + } + + TubeWrapper *wrapper = 0; + + if (mPriv->acceptsAsTcp) { + QPair<QHostAddress, quint16> srcAddr = + qMakePair(QHostAddress(QHostAddress::Any), quint16(0)); + + if (mPriv->tcpGenerator) { + srcAddr = mPriv->tcpGenerator->nextSourceAddress(acc, incoming); + } + + wrapper = new TubeWrapper(acc, incoming, srcAddr.first, srcAddr.second, this); + } else { + Q_ASSERT(mPriv->acceptsAsUnix); // we should only be registered when we're set to accept as either TCP or Unix + wrapper = new TubeWrapper(acc, incoming, mPriv->requireCredentials, this); + } + + connect(wrapper, + SIGNAL(acceptFinished(TubeWrapper*,Tp::PendingStreamTubeConnection*)), + SLOT(onAcceptFinished(TubeWrapper*,Tp::PendingStreamTubeConnection*))); + connect(tube.data(), + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + SLOT(onTubeInvalidated(Tp::DBusProxy*,QString,QString))); + + if (monitorsConnections()) { + connect(wrapper, + SIGNAL(newConnection(TubeWrapper*,uint)), + SLOT(onNewConnection(TubeWrapper*,uint))); + connect(wrapper, + SIGNAL(connectionClosed(TubeWrapper*,uint,QString,QString)), + SLOT(onConnectionClosed(TubeWrapper*,uint,QString,QString))); + } + + mPriv->tubes.insert(tube, wrapper); + + emit tubeOffered(acc, incoming); +} + +void StreamTubeClient::onAcceptFinished(TubeWrapper *wrapper, PendingStreamTubeConnection *conn) +{ + Q_ASSERT(wrapper != NULL); + Q_ASSERT(conn != NULL); + + if (!mPriv->tubes.contains(wrapper->mTube)) { + debug() << "StreamTubeClient ignoring Accept result for invalidated tube" + << wrapper->mTube->objectPath(); + return; + } + + if (conn->isError()) { + warning() << "StreamTubeClient couldn't accept tube" << wrapper->mTube->objectPath() << '-' + << conn->errorName() << ':' << conn->errorMessage(); + + if (wrapper->mTube->isValid()) { + wrapper->mTube->requestClose(); + } + + wrapper->mTube->disconnect(this); + emit tubeClosed(wrapper->mAcc, wrapper->mTube, conn->errorName(), conn->errorMessage()); + mPriv->tubes.remove(wrapper->mTube); + wrapper->deleteLater(); + return; + } + + debug() << "StreamTubeClient accepted tube" << wrapper->mTube->objectPath(); + + if (conn->addressType() == SocketAddressTypeIPv4 + || conn->addressType() == SocketAddressTypeIPv6) { + QPair<QHostAddress, quint16> addr = conn->ipAddress(); + emit tubeAcceptedAsTcp(addr.first, addr.second, wrapper->mSourceAddress, + wrapper->mSourcePort, wrapper->mAcc, wrapper->mTube); + } else { + emit tubeAcceptedAsUnix(conn->localAddress(), conn->requiresCredentials(), + conn->credentialByte(), wrapper->mAcc, wrapper->mTube); + } +} + +void StreamTubeClient::onTubeInvalidated(Tp::DBusProxy *proxy, const QString &error, + const QString &message) +{ + StreamTubeChannelPtr tube(qobject_cast<StreamTubeChannel *>(proxy)); + Q_ASSERT(!tube.isNull()); + + TubeWrapper *wrapper = mPriv->tubes.value(tube); + if (!wrapper) { + // Accept finish with error already removed it + return; + } + + debug() << "Client StreamTube" << tube->objectPath() << "invalidated - " << error << ':' + << message; + + emit tubeClosed(wrapper->mAcc, wrapper->mTube, error, message); + mPriv->tubes.remove(tube); + delete wrapper; +} + +void StreamTubeClient::onNewConnection( + TubeWrapper *wrapper, + uint conn) +{ + Q_ASSERT(monitorsConnections()); + emit newConnection(wrapper->mAcc, wrapper->mTube, conn); +} + +void StreamTubeClient::onConnectionClosed( + TubeWrapper *wrapper, + uint conn, + const QString &error, + const QString &message) +{ + Q_ASSERT(monitorsConnections()); + emit connectionClosed(wrapper->mAcc, wrapper->mTube, conn, error, message); +} + +/** + * \fn void StreamTubeClient::tubeOffered(const AccountPtr &account, const + * IncomingStreamTubeChannelPtr &tube) + * + * Emitted when one of the services we're interested in connecting to has been offered by us as a + * tube, which we've began handling. + * + * This is emitted before invoking the TcpSourceAddressGenerator, if any, for the tube. + * + * \param account A pointer to the account through which the tube was offered. + * \param tube A pointer to the actual tube channel. + */ + +/** + * \fn void StreamTubeClient::tubeAcceptedAsTcp(const QHostAddress &listenAddress, quint16 + * listenPort, const QHostAddress &sourceAddress, quint16 sourcePort, const AccountPtr &account, + * const IncomingStreamTubeChannelPtr &tube) + * + * Emitted when a tube offered to us (previously announced with tubeOffered()) has been successfully + * accepted and a TCP socket established as the local endpoint, as specified by setToAcceptAsTcp(). + * + * The allowed source address and port are signaled here if there was a TcpSourceAddressGenerator set + * at the time of accepting this tube, it yielded a non-zero address, and the protocol backend + * supports source address based access control. This enables the application to use the correct one + * of the sockets it currently has bound to the generated addresses. + * + * \param listenAddress The listen address of the local endpoint socket. + * \param listenPort The listen port of the local endpoint socket. + * \param sourceAddress The host address allowed to connect to the tube, or QHostAddress::Any if + * source address based access control is not in use. + * \param sourcePort The port from which connections are allowed to the tube, or 0 if source address + * based access control is not in use. + * \param account A pointer to the account object through which the tube was offered. + * \param tube A pointer to the actual tube channel object. + */ + +// Explicitly specifying Tp:: for this signal's argument types works around a doxygen bug causing it +// to confuse the doc here being for StreamTubeServer::tubeClosed :/ +/** + * \fn void StreamTubeClient::tubeClosed(const Tp::AccountPtr &account, const + * Tp::IncomingStreamTubeChannelPtr &tube, const QString &error, const QString &message) + * + * Emitted when a tube we've been handling (previously announced with tubeOffered()) has + * encountered an error or has otherwise been closed from further communication. + * + * \param account A pointer to the account through which the tube was offered. + * \param tube A pointer to the actual tube channel. + * \param error The D-Bus error name corresponding to the reason for the closure. + * \param message A freeform debug message associated with the error. + */ + +/** + * \fn void StreamTubeClient::tubeAcceptedAsUnix(const QHostAddress &listenAddress, quint16 + * listenPort, const QHostAddress &sourceAddress, quint16 sourcePort, const AccountPtr &account, + * const IncomingStreamTubeChannelPtr &tube) + * + * Emitted when a tube offered to us (previously announced with tubeOffered()) has been successfully + * accepted and a Unix socket established as the local endpoint, as specified by setToAcceptAsUnix(). + * + * The credential byte which should be sent after connecting to the tube (in a SCM_CREDENTIALS or + * SCM_CREDS message if supported by the platform) will be signaled here if the client was set to + * attempt requiring credentials at the time of accepting this tube, and the protocol backend + * supports credential passing based access control. Otherwise, \a requiresCredentials will be false + * and no byte or associated out-of-band credentials metadata should be sent. + * + * \param listenAddress The listen address of the local endpoint socket. + * \param requiresCredentials \c true if \a credentialByte should be sent after connecting to the + * socket, \c false if not. + * \param credentialByte The byte to send if \a requiresCredentials is \c true. + * \param account A pointer to the account object through which the tube was offered. + * \param tube A pointer to the actual tube channel object. + */ + +/** + * \fn void StreamTubeClient::newConnection(const AccountPtr &account, const + * IncomingStreamTubeChannelPtr &tube, uint connectionId) + * + * Emitted when a new connection has been made to the local endpoint socket for \a tube. + * + * This can be used to later associate connection errors reported by connectionClosed() with the + * corresponding application sockets. However, establishing the association generally requires + * connecting only one socket at a time, waiting for newConnection() to be emitted, and only then + * proceeding, as there is no identification for the connections unlike the incoming connections in + * StreamTubeServer. + * + * Note that the connection IDs are only unique within a given tube, so identification of the tube + * channel must also be recorded together with the ID to establish global uniqueness. Even then, the + * a connection ID can be reused after the previous connection identified by it having been + * signaled as closed with connectionClosed(). + * + * This is only emitted if connection monitoring was enabled when creating the StreamTubeClient. + * + * \param account A pointer to the account through which the tube was offered. + * \param tube A pointer to the tube channel through which the connection has been made. + * \param connectionId The integer ID of the new connection. + */ + +/** + * \fn void StreamTubeClient::connectionClosed(const AccountPtr &account, const + * IncomingStreamTubeChannelPtr &tube, uint connectionId, const QString &error, const + * QString &message) + * + * Emitted when a connection (previously announced with newConnection()) through one of our + * handled tubes has been closed due to an error or by a graceful disconnect (in which case the + * error is ::TP_QT_ERROR_CANCELLED). + * + * This is only emitted if connection monitoring was enabled when creating the StreamTubeClient. + * + * \param account A pointer to the account through which the tube was offered. + * \param tube A pointer to the tube channel through which the connection had been made. + * \param connectionId The integer ID of the connection closed. + * \param error The D-Bus error name corresponding to the reason for the closure. + * \param message A freeform debug message associated with the error. + */ + +} // Tp diff --git a/TelepathyQt/stream-tube-client.h b/TelepathyQt/stream-tube-client.h new file mode 100644 index 00000000..3d3f32ac --- /dev/null +++ b/TelepathyQt/stream-tube-client.h @@ -0,0 +1,217 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +#ifndef _TelepathyQt_stream_tube_client_h_HEADER_GUARD_ +#define _TelepathyQt_stream_tube_client_h_HEADER_GUARD_ + +#include <TelepathyQt/AccountFactory> +#include <TelepathyQt/ChannelFactory> +#include <TelepathyQt/ConnectionFactory> +#include <TelepathyQt/ContactFactory> +#include <TelepathyQt/RefCounted> +#include <TelepathyQt/Types> + +class QHostAddress; + +namespace Tp +{ + +class PendingStreamTubeConnection; + +class TP_QT_EXPORT StreamTubeClient : public QObject, public RefCounted +{ + Q_OBJECT + Q_DISABLE_COPY(StreamTubeClient) + + class TubeWrapper; + +public: + + class TcpSourceAddressGenerator + { + public: + virtual QPair<QHostAddress, quint16> + nextSourceAddress(const AccountPtr &account, const IncomingStreamTubeChannelPtr &tube) = 0; + + protected: + virtual ~TcpSourceAddressGenerator() {} + }; + + class Tube : public QPair<AccountPtr, IncomingStreamTubeChannelPtr> + { + public: + Tube(); + Tube(const AccountPtr &account, const IncomingStreamTubeChannelPtr &channel); + Tube(const Tube &other); + ~Tube(); + + bool isValid() const { return mPriv.constData() != 0; } + + Tube &operator=(const Tube &other); + + const AccountPtr &account() const + { + return first; + } + + const IncomingStreamTubeChannelPtr &channel() const + { + return second; + } + + private: + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; + }; + + static StreamTubeClientPtr create( + const QStringList &p2pServices, + const QStringList &roomServices = QStringList(), + const QString &clientName = QString(), + bool monitorConnections = false, + bool bypassApproval = false, + const AccountFactoryConstPtr &accountFactory = + AccountFactory::create(QDBusConnection::sessionBus()), + const ConnectionFactoryConstPtr &connectionFactory = + ConnectionFactory::create(QDBusConnection::sessionBus()), + const ChannelFactoryConstPtr &channelFactory = + ChannelFactory::create(QDBusConnection::sessionBus()), + const ContactFactoryConstPtr &contactFactory = + ContactFactory::create()); + + static StreamTubeClientPtr create( + const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory, + const QStringList &p2pServices, + const QStringList &roomServices = QStringList(), + const QString &clientName = QString(), + bool monitorConnections = false, + bool bypassApproval = false); + + static StreamTubeClientPtr create( + const AccountManagerPtr &accountManager, + const QStringList &p2pServices, + const QStringList &roomServices = QStringList(), + const QString &clientName = QString(), + bool monitorConnections = false, + bool bypassApproval = false); + + static StreamTubeClientPtr create( + const ClientRegistrarPtr ®istrar, + const QStringList &p2pServices, + const QStringList &roomServices = QStringList(), + const QString &clientName = QString(), + bool monitorConnections = false, + bool bypassApproval = false); + + virtual ~StreamTubeClient(); + + ClientRegistrarPtr registrar() const; + QString clientName() const; + bool isRegistered() const; + bool monitorsConnections() const; + + bool acceptsAsTcp() const; + TcpSourceAddressGenerator *tcpGenerator() const; + bool acceptsAsUnix() const; + + void setToAcceptAsTcp(TcpSourceAddressGenerator *generator = 0); + void setToAcceptAsUnix(bool requireCredentials = false); + + QList<Tube> tubes() const; + QHash<Tube, QSet<uint> > connections() const; + +Q_SIGNALS: + void tubeOffered( + const Tp::AccountPtr &account, + const Tp::IncomingStreamTubeChannelPtr &tube); + void tubeClosed( + const Tp::AccountPtr &account, + const Tp::IncomingStreamTubeChannelPtr &tube, + const QString &error, + const QString &message); + + void tubeAcceptedAsTcp( + const QHostAddress &listenAddress, + quint16 listenPort, + const QHostAddress &sourceAddress, + quint16 sourcePort, + const Tp::AccountPtr &account, + const Tp::IncomingStreamTubeChannelPtr &tube); + void tubeAcceptedAsUnix( + const QString &listenAddress, + bool requiresCredentials, + uchar credentialByte, + const Tp::AccountPtr &account, + const Tp::IncomingStreamTubeChannelPtr &tube); + + void newConnection( + const Tp::AccountPtr &account, + const Tp::IncomingStreamTubeChannelPtr &tube, + uint connectionId); + void connectionClosed( + const Tp::AccountPtr &account, + const Tp::IncomingStreamTubeChannelPtr &tube, + uint connectionId, + const QString &error, + const QString &message); + +private Q_SLOTS: + + TP_QT_NO_EXPORT void onInvokedForTube( + const Tp::AccountPtr &account, + const Tp::StreamTubeChannelPtr &tube, + const QDateTime &userActionTime, + const Tp::ChannelRequestHints &requestHints); + + TP_QT_NO_EXPORT void onAcceptFinished(TubeWrapper *, Tp::PendingStreamTubeConnection *); + TP_QT_NO_EXPORT void onTubeInvalidated(Tp::DBusProxy *, const QString &, const QString &); + + TP_QT_NO_EXPORT void onNewConnection( + TubeWrapper *wrapper, + uint conn); + TP_QT_NO_EXPORT void onConnectionClosed( + TubeWrapper *wrapper, + uint conn, + const QString &error, + const QString &message); + +private: + TP_QT_NO_EXPORT StreamTubeClient( + const ClientRegistrarPtr ®istrar, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections, + bool bypassApproval); + + struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/stream-tube-server-internal.h b/TelepathyQt/stream-tube-server-internal.h new file mode 100644 index 00000000..224f4b1e --- /dev/null +++ b/TelepathyQt/stream-tube-server-internal.h @@ -0,0 +1,56 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 <TelepathyQt/StreamTubeServer> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class TP_QT_NO_EXPORT StreamTubeServer::TubeWrapper : + public QObject +{ + Q_OBJECT + Q_DISABLE_COPY(TubeWrapper) + +public: + TubeWrapper(const AccountPtr &acc, const OutgoingStreamTubeChannelPtr &tube, + const QHostAddress &exportedAddr, quint16 exportedPort, const QVariantMap ¶ms, + StreamTubeServer *parent); + ~TubeWrapper() { } + + AccountPtr mAcc; + OutgoingStreamTubeChannelPtr mTube; + +Q_SIGNALS: + void offerFinished(TubeWrapper *wrapper, Tp::PendingOperation *op); + void newConnection(TubeWrapper *wrapper, uint conn); + void connectionClosed(TubeWrapper *wrapper, uint conn, const QString &error, + const QString &message); + +private Q_SLOTS: + void onTubeOffered(Tp::PendingOperation *); + void onNewConnection(uint); + void onConnectionClosed(uint, const QString &, const QString &); +}; + +} // Tp diff --git a/TelepathyQt/stream-tube-server.cpp b/TelepathyQt/stream-tube-server.cpp new file mode 100644 index 00000000..56e0afe2 --- /dev/null +++ b/TelepathyQt/stream-tube-server.cpp @@ -0,0 +1,1134 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 <TelepathyQt/StreamTubeServer> +#include "TelepathyQt/stream-tube-server-internal.h" + +#include "TelepathyQt/_gen/stream-tube-server.moc.hpp" +#include "TelepathyQt/_gen/stream-tube-server-internal.moc.hpp" + +#include "TelepathyQt/debug-internal.h" +#include "TelepathyQt/simple-stream-tube-handler.h" + +#include <QScopedPointer> +#include <QSharedData> +#include <QTcpServer> + +#include <TelepathyQt/AccountManager> +#include <TelepathyQt/ClientRegistrar> +#include <TelepathyQt/OutgoingStreamTubeChannel> +#include <TelepathyQt/StreamTubeChannel> + +namespace Tp +{ + +/** + * \class StreamTubeServer::ParametersGenerator + * \ingroup serverclient + * \headerfile TelepathyQt/stream-tube-server.h <TelepathyQt/StreamTubeServer> + * + * \brief The StreamTubeServer::ParametersGenerator abstract interface allows sending a different + * set of parameters with each tube offer. + * + * %Tube parameters are arbitrary data sent with the tube offer, which can be retrieved in the + * receiving end with IncomingStreamTubeChannel::parameters(). They can be used to transfer + * e.g. session identification information, authentication credentials or alike, for bootstrapping + * the protocol used for communicating over the tube. + * + * For usecases where the parameters don't need to change between each tube, just passing a fixed + * set of parameters to a suitable StreamTubeServer::exportTcpSocket() overload is usually more + * convenient than implementing a ParametersGenerator. Note that StreamTubeServer::exportTcpSocket() + * can be called multiple times to change the parameters for future tubes when e.g. configuration + * settings have been changed, so a ParametersGenerator only needs to be implemented if each and + * every tube must have a different set of parameters. + */ + +/** + * \fn QVariantMap StreamTubeServer::ParametersGenerator::nextParameters(const AccountPtr &, const + * OutgoingStreamTubeChannelPtr &, const ChannelRequestHints &) + * + * Return the parameters to send when offering the given \a tube. + * + * \param account The account from which the tube originates. + * \param tube The tube channel which is going to be offered by the StreamTubeServer. + * \param hints The hints associated with the request that led to the creation of this tube, if any. + * + * \return Parameters to send with the offer, or an empty QVariantMap if none are needed for this + * tube. + */ + +/** + * \fn StreamTubeServer::ParametersGenerator::~ParametersGenerator + * + * Class destructor. Protected, because StreamTubeServer never deletes a ParametersGenerator passed + * to it. + */ + +class TP_QT_NO_EXPORT FixedParametersGenerator : public StreamTubeServer::ParametersGenerator +{ +public: + + FixedParametersGenerator(const QVariantMap ¶ms) : mParams(params) {} + + QVariantMap nextParameters(const AccountPtr &, const OutgoingStreamTubeChannelPtr &, + const ChannelRequestHints &) + { + return mParams; + } + +private: + + QVariantMap mParams; +}; + +struct TP_QT_NO_EXPORT StreamTubeServer::RemoteContact::Private : public QSharedData +{ + // empty placeholder for now +}; + +/** + * \class StreamTubeServer::RemoteContact + * \ingroup serverclient + * \headerfile TelepathyQt/stream-tube-server.h <TelepathyQt/StreamTubeServer> + * + * \brief The StreamTubeServer::RemoteContact class represents a contact from which a socket + * connection to our exported socket originates. + */ + +/** + * Constructs a new invalid RemoteContact instance. + */ +StreamTubeServer::RemoteContact::RemoteContact() +{ + // invalid instance +} + +/** + * Constructs a new RemoteContact for the given \a contact object from the given \a account. + * + * \param account A pointer to the account which this contact can be reached through. + * \param contact A pointer to the contact object. + */ +StreamTubeServer::RemoteContact::RemoteContact( + const AccountPtr &account, + const ContactPtr &contact) + : QPair<AccountPtr, ContactPtr>(account, contact), mPriv(new Private) +{ +} + +/** + * Copy constructor. + */ +StreamTubeServer::RemoteContact::RemoteContact( + const RemoteContact &other) + : QPair<AccountPtr, ContactPtr>(other.account(), other.contact()), mPriv(other.mPriv) +{ +} + +/** + * Class destructor. + */ +StreamTubeServer::RemoteContact::~RemoteContact() +{ + // mPriv deleted automatically +} + +/** + * Assignment operator. + */ +StreamTubeServer::RemoteContact &StreamTubeServer::RemoteContact::operator=( + const RemoteContact &other) +{ + if (&other == this) { + return *this; + } + + first = other.account(); + second = other.contact(); + mPriv = other.mPriv; + + return *this; +} + +/** + * \fn bool StreamTubeServer::RemoteContact::isValid() const + * + * Return whether or not the contact is valid or is just the null object created using the default + * constructor. + * + * \return \c true if valid, \c false otherwise. + */ + +/** + * \fn AccountPtr StreamTubeServer::RemoteContact::account() const + * + * Return the account through which the contact can be reached. + * + * \return A pointer to the account object. + */ + +/** + * \fn ContactPtr StreamTubeServer::RemoteContact::contact() const + * + * Return the actual contact object. + * + * \return A pointer to the object. + */ + +struct TP_QT_NO_EXPORT StreamTubeServer::Tube::Private : public QSharedData +{ + // empty placeholder for now +}; + +/** + * \class StreamTubeServer::Tube + * \ingroup serverclient + * \headerfile TelepathyQt/stream-tube-server.h <TelepathyQt/StreamTubeServer> + * + * \brief The StreamTubeServer::Tube class represents a tube being handled by the server. + */ + +/** + * Constructs a new invalid Tube instance. + */ +StreamTubeServer::Tube::Tube() +{ + // invalid instance +} + +/** + * Constructs a Tube instance for the given tube \a channel originating from the given \a account. + * + * \param account A pointer to the account object. + * \param channel A pointer to the tube channel object. + */ +StreamTubeServer::Tube::Tube( + const AccountPtr &account, + const OutgoingStreamTubeChannelPtr &channel) + : QPair<AccountPtr, OutgoingStreamTubeChannelPtr>(account, channel), mPriv(new Private) +{ +} + +/** + * Copy constructor. + */ +StreamTubeServer::Tube::Tube( + const Tube &other) + : QPair<AccountPtr, OutgoingStreamTubeChannelPtr>(other.account(), other.channel()), + mPriv(other.mPriv) +{ +} + +/** + * Class destructor. + */ +StreamTubeServer::Tube::~Tube() +{ + // mPriv deleted automatically +} + +/** + * Assignment operator. + */ +StreamTubeServer::Tube &StreamTubeServer::Tube::operator=( + const Tube &other) +{ + if (&other == this) { + return *this; + } + + first = other.account(); + second = other.channel(); + mPriv = other.mPriv; + + return *this; +} + +/** + * \fn bool StreamTubeServer::Tube::isValid() const + * + * Return whether or not the tube is valid or is just the null object created using the default + * constructor. + * + * \return \c true if valid, \c false otherwise. + */ + +/** + * \fn AccountPtr StreamTubeServer::Tube::account() const + * + * Return the account from which the tube originates. + * + * \return A pointer to the account object. + */ + +/** + * \fn OutgoingStreamTubeChannelPtr StreamTubeServer::Tube::channel() const + * + * Return the actual tube channel. + * + * \return A pointer to the channel. + */ + +struct StreamTubeServer::Private +{ + Private(const ClientRegistrarPtr ®istrar, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &maybeClientName, + bool monitorConnections) + : registrar(registrar), + handler(SimpleStreamTubeHandler::create(p2pServices, roomServices, true, monitorConnections)), + clientName(maybeClientName), + isRegistered(false), + exportedPort(0), + generator(0) + { + if (clientName.isEmpty()) { + clientName = QString::fromLatin1("TpQt4STubeServer_%1_%2") + .arg(registrar->dbusConnection().baseService() + .replace(QLatin1Char(':'), QLatin1Char('_')) + .replace(QLatin1Char('.'), QLatin1Char('_'))) + .arg((intptr_t) this, 0, 16); + } + } + + void ensureRegistered() + { + if (isRegistered) { + return; + } + + debug() << "Register StreamTubeServer with name " << clientName; + + if (registrar->registerClient(handler, clientName)) { + isRegistered = true; + } else { + warning() << "StreamTubeServer" << clientName + << "registration failed"; + } + } + + ClientRegistrarPtr registrar; + SharedPtr<SimpleStreamTubeHandler> handler; + QString clientName; + bool isRegistered; + + QHostAddress exportedAddr; + quint16 exportedPort; + ParametersGenerator *generator; + QScopedPointer<FixedParametersGenerator> fixedGenerator; + + QHash<StreamTubeChannelPtr, TubeWrapper *> tubes; + +}; + +StreamTubeServer::TubeWrapper::TubeWrapper(const AccountPtr &acc, + const OutgoingStreamTubeChannelPtr &tube, const QHostAddress &exportedAddr, + quint16 exportedPort, const QVariantMap ¶ms, StreamTubeServer *parent) + : QObject(parent), mAcc(acc), mTube(tube) +{ + connect(tube->offerTcpSocket(exportedAddr, exportedPort, params), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onTubeOffered(Tp::PendingOperation*))); + connect(tube.data(), + SIGNAL(newConnection(uint)), + SLOT(onNewConnection(uint))); + connect(tube.data(), + SIGNAL(connectionClosed(uint,QString,QString)), + SLOT(onConnectionClosed(uint,QString,QString))); +} + +void StreamTubeServer::TubeWrapper::onTubeOffered(Tp::PendingOperation *op) +{ + emit offerFinished(this, op); +} + +void StreamTubeServer::TubeWrapper::onNewConnection(uint conn) +{ + emit newConnection(this, conn); +} + +void StreamTubeServer::TubeWrapper::onConnectionClosed(uint conn, const QString &error, + const QString &message) +{ + emit connectionClosed(this, conn, error, message); +} + +/** + * \class StreamTubeServer + * \ingroup serverclient + * \headerfile TelepathyQt/stream-tube-server.h <TelepathyQt/StreamTubeServer> + * + * \brief The StreamTubeServer class is a Handler implementation for outgoing %Stream %Tube channels, + * allowing an application to easily export a TCP network server over Telepathy Tubes without + * worrying about the channel dispatching details. + * + * Telepathy Tubes is a technology for connecting arbitrary applications together through the IM + * network (and sometimes with direct peer-to-peer connections), such that issues like firewall/NAT + * traversal are automatically handled. Stream Tubes in particular offer properties similar to + * SOCK_STREAM sockets. The StreamTubeServer class exports such a bytestream socket \b server over + * the tubes it \em handles as a Telepathy Handler %Client; the StreamTubeClient class is the + * counterpart, enabling TCP/UNIX socket clients to connect to services from such exported servers + * offered to them via tubes. + * + * Both peer-to-peer (\c TargetHandleType == \ref HandleTypeContact) and group (\c TargetHandleType + * == \ref HandleTypeRoom) channels are supported, and it's possible to specify the tube services to + * handle for each separately. It is also possible to not advertise handling capability for ANY tube + * service; instead just using the StreamTubeServer to handle tubes on an one-off basis by passing + * its corresponding %Client service name as the \a preferredHandler when requesting tubes via the + * Account::createStreamTube() methods (or equivalent). + * + * %Connection monitoring allows associating incoming connections on the exported server socket with + * the corresponding remote contacts. This allows an application to show the details of and/or + * initiate further communication with the remote contacts, without considering the actual tube + * channels the connections are being made through at all (in particular, their + * Channel::targetContact() accessor for peer-to-peer and the + * OutgoingStreamTubeChannel::connectionsForSourceAddresses() accessor for group tubes). + * + * Enabling connection monitoring adds a small overhead and latency to handling each incoming tube + * and signaling each new incoming connection over them, though, so use it only when needed. + * Additionally, some protocol backends or environments they're running in might not support the + * ::SocketAccessControlPort mechanism, in which case the source address won't be reported for + * connections through them. Even in this case, the remote contacts can be associated by accepting + * one incoming socket connection at a time, and waiting for the corresponding contact to be + * signaled (although its source address will be invalid, it's the only possibility given its the + * only accepted connection). However, it's not necessary to do this e.g. with the Gabble XMPP + * backend, because it fully supports the required mechanism. + * + * A service activated Handler can be implemented using StreamTubeServer by passing a predefined \a + * clientName manually to the chosen create() method, and installing Telepathy \c .client and D-Bus + * \c .service files declaring the implemented tube services as channel classes and a path to the + * executable. If this is not needed, the \a clientName can be omitted, in which case a random + * unique client name is generated and used instead. + * + * StreamTubeServer shares Account, Connection and Channel proxies and Contact objects with the + * rest of the application as long as a reference to the AccountManager, ClientRegistrar, or the + * factories used elsewhere is passed to the create() method. A stand-alone tube service Handler can + * get away without passing these however, or just passing select factories to make the desired + * features prepared and subclasses employed for these objects for their own convenience. + * + * Whichever method is used, the ChannelFactory (perhaps indirectly) given must construct + * OutgoingStreamTubeChannel instances or subclasses thereof for all channel classes corresponding + * to the tube services to handle. This is the default; overriding it without obeying these + * constraints using ChannelFactory::setSubclassForOutgoingStreamTubes() or the related methods + * for room tubes prevents StreamTubeServer from operating correctly. + * + * \todo Coin up a small Python script or alike to easily generate the .client and .service files. + * (fd.o #41614) + * \todo Support exporting Unix sockets as well. (fd.o #41615) + */ + +/** + * Create a new StreamTubeServer, which will register itself on the session bus using an internal + * ClientRegistrar and use the given factories. + * + * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels. + * \param roomServices Names of the tube services to handle on room/group tube channels. + * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix). + * \param monitorConnections Whether to enable connection monitoring or not. + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + */ +StreamTubeServerPtr StreamTubeServer::create( + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory) +{ + return create( + QDBusConnection::sessionBus(), + accountFactory, + connectionFactory, + channelFactory, + contactFactory, + p2pServices, + roomServices, + clientName, + monitorConnections); +} + +/** + * Create a new StreamTubeServer, which will register itself on the given \a bus using an internal + * ClientRegistrar and use the given factories. + * + * The factories must all be created for the given \a bus. + * + * \param bus Connection to the bus to register on. + * \param accountFactory The account factory to use. + * \param connectionFactory The connection factory to use. + * \param channelFactory The channel factory to use. + * \param contactFactory The contact factory to use. + * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels. + * \param roomServices Names of the tube services to handle on room/group tube channels. + * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix). + * \param monitorConnections Whether to enable connection monitoring or not. + */ +StreamTubeServerPtr StreamTubeServer::create( + const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections) +{ + return create( + ClientRegistrar::create( + bus, + accountFactory, + connectionFactory, + channelFactory, + contactFactory), + p2pServices, + roomServices, + clientName, + monitorConnections); +} + +/** + * Create a new StreamTubeServer, which will register itself on the bus of and share objects with + * the given \a accountManager, creating an internal ClientRegistrar. + * + * \param accountManager A pointer to the account manager to link up with. + * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels. + * \param roomServices Names of the tube services to handle on room/group tube channels. + * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix). + * \param monitorConnections Whether to enable connection monitoring or not. + */ +StreamTubeServerPtr StreamTubeServer::create( + const AccountManagerPtr &accountManager, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections) +{ + return create( + accountManager->dbusConnection(), + accountManager->accountFactory(), + accountManager->connectionFactory(), + accountManager->channelFactory(), + accountManager->contactFactory(), + p2pServices, + roomServices, + clientName, + monitorConnections); +} + +/** + * Create a new StreamTubeServer, which will register itself on the bus of and using the given + * client \a registrar, and share objects with it. + * + * \param registrar The client registrar to use. + * \param p2pServices Names of the tube services to handle on peer-to-peer tube channels. + * \param roomServices Names of the tube services to handle on room/group tube channels. + * \param clientName The client name (without the \c org.freedesktop.Telepathy.Client. prefix). + * \param monitorConnections Whether to enable connection monitoring or not. + */ +StreamTubeServerPtr StreamTubeServer::create( + const ClientRegistrarPtr ®istrar, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections) +{ + return StreamTubeServerPtr( + new StreamTubeServer(registrar, p2pServices, roomServices, clientName, + monitorConnections)); +} + +StreamTubeServer::StreamTubeServer( + const ClientRegistrarPtr ®istrar, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections) + : mPriv(new Private(registrar, p2pServices, roomServices, clientName, monitorConnections)) +{ + connect(mPriv->handler.data(), + SIGNAL(invokedForTube( + Tp::AccountPtr, + Tp::StreamTubeChannelPtr, + QDateTime, + Tp::ChannelRequestHints)), + SLOT(onInvokedForTube( + Tp::AccountPtr, + Tp::StreamTubeChannelPtr, + QDateTime, + Tp::ChannelRequestHints))); +} + +/** + * Class destructor. + */ +StreamTubeServer::~StreamTubeServer() +{ + if (isRegistered()) { + mPriv->registrar->unregisterClient(mPriv->handler); + } + + delete mPriv; +} + +/** + * Return the client registrar used by the server to register itself as a Handler client. + * + * This is the registrar originally passed to + * create(const ClientRegistrarPtr &, const QStringList &, const QStringList &, const QString &, bool) + * if that was used, and an internally constructed one otherwise. In any case, it can be used to + * e.g. register further clients like any other ClientRegistrar. + * + * \return A pointer to the registrar. + */ +ClientRegistrarPtr StreamTubeServer::registrar() const +{ + return mPriv->registrar; +} + +/** + * Return the Telepathy %Client name of the server. + * + * \return The name, without the \c org.freedesktop.Telepathy.Client. prefix of the full D-Bus service name. + */ +QString StreamTubeServer::clientName() const +{ + return mPriv->clientName; +} + +/** + * Return whether the server has been successfully registered or not. + * + * Registration is attempted, at the latest, when a socket is first exported using exportTcpSocket(). + * It can fail e.g. because the connection to the bus has failed, or a predefined \a clientName has + * been passed to create(), and a %Client with the same name is already registered. Typically, failure + * registering would be a fatal error for a stand-alone tube handler, but only a warning event for + * an application serving other purposes. In any case, a high-quality user of the API will check the + * return value of this accessor after exporting their socket. + * + * \return \c true if the server has been successfully registered, \c false if not. + */ +bool StreamTubeServer::isRegistered() const +{ + return mPriv->isRegistered; +} + +/** + * Return whether connection monitoring is enabled on this server. + * + * For technical reasons, connection monitoring can't be enabled when the server is already running, + * so there is no corresponding setter method. It has to be enabled by passing \c true as the \a + * monitorConnections parameter to the create() method. + * + * If connection monitoring isn't enabled, newTcpConnection() and tcpConnectionClosed() won't be + * emitted and tcpConnections() won't be populated. + * + * \return \c true if monitoring is enabled, \c false if not. + */ +bool StreamTubeServer::monitorsConnections() const +{ + return mPriv->handler->monitorsConnections(); +} + +/** + * Return the host address and port of the currently exported TCP socket, if any. + * + * QHostAddress::Null is reported as the address and 0 as the port if no TCP socket has yet been + * successfully exported. + * + * \return The host address and port values in a pair structure. + */ +QPair<QHostAddress, quint16> StreamTubeServer::exportedTcpSocketAddress() const +{ + return qMakePair(mPriv->exportedAddr, mPriv->exportedPort); +} + +/** + * Return the fixed parameters, if any, which are sent along when offering the exported socket on + * all handled tubes. + * + * To prevent accidentally leaving the current parameters to be sent when offering a different + * socket, or vice versa, the parameters can only be set together with the socket using + * exportTcpSocket(). Parameters often contain sensitive information such as session identifiers or + * authentication credentials, which could then be used to maliciously access the service listening + * on the other socket. + * + * If a custom dynamic ParametersGenerator was passed to exportTcpSocket() instead of a set of fixed + * parameters, an empty set of parameters is returned. + * + * \return The parameters in a string-variant map. + */ +QVariantMap StreamTubeServer::exportedParameters() const +{ + if (!mPriv->generator) { + return QVariantMap(); + } + + FixedParametersGenerator *generator = + dynamic_cast<FixedParametersGenerator *>(mPriv->generator); + + if (generator) { + return generator->nextParameters(AccountPtr(), OutgoingStreamTubeChannelPtr(), + ChannelRequestHints()); + } else { + return QVariantMap(); + } +} + +/** + * Set the server to offer the socket listening at the given (\a address, \a port) combination as the + * local endpoint of tubes handled in the future. + * + * A fixed set of protocol bootstrapping \a parameters can optionally be set to be sent along with all + * tube offers until the next call to exportTcpSocket(). See the ParametersGenerator documentation + * for an in-depth description of the parameter transfer mechanism, and a more flexible way to vary + * the parameters between each handled tube. + * + * The handler is registered on the bus at the latest when this method or another exportTcpSocket() + * overload is called for the first time, so one should check the return value of isRegistered() at + * that point to verify that was successful. + * + * \param address The listen address of the socket. + * \param port The port of the socket. + * \param parameters The bootstrapping parameters in a string-value map. + */ +void StreamTubeServer::exportTcpSocket( + const QHostAddress &address, + quint16 port, + const QVariantMap ¶meters) +{ + if (address.isNull() || port == 0) { + warning() << "Attempted to export null TCP socket address or zero port, ignoring"; + return; + } + + mPriv->exportedAddr = address; + mPriv->exportedPort = port; + + mPriv->generator = 0; + if (!parameters.isEmpty()) { + mPriv->fixedGenerator.reset(new FixedParametersGenerator(parameters)); + mPriv->generator = mPriv->fixedGenerator.data(); + } + + mPriv->ensureRegistered(); +} + +/** + * Set the StreamTubeServer to offer the already listening TCP \a server as the local endpoint of tubes + * handled in the future. + * + * This is just a convenience wrapper around + * exportTcpSocket(const QHostAddress &, quint16, const QVariantMap &) to be used when the TCP + * server code is implemented using the QtNetwork facilities. + * + * A fixed set of protocol bootstrapping \a parameters can optionally be set to be sent along with all + * tube offers until the next call to exportTcpSocket(). See the ParametersGenerator documentation + * for an in-depth description of the parameter transfer mechanism, and a more flexible way to vary + * the parameters between each handled tube. + * + * \param server A pointer to the TCP server. + * \param parameters The bootstrapping parameters in a string-value map. + */ +void StreamTubeServer::exportTcpSocket( + const QTcpServer *server, + const QVariantMap ¶meters) +{ + if (!server->isListening()) { + warning() << "Attempted to export non-listening QTcpServer, ignoring"; + return; + } + + if (server->serverAddress() == QHostAddress::Any) { + return exportTcpSocket(QHostAddress::LocalHost, server->serverPort(), parameters); + } else if (server->serverAddress() == QHostAddress::AnyIPv6) { + return exportTcpSocket(QHostAddress::LocalHostIPv6, server->serverPort(), parameters); + } else { + return exportTcpSocket(server->serverAddress(), server->serverPort(), parameters); + } +} + +/** + * Set the server to offer the socket listening at the given \a address - \a port combination as the + * local endpoint of tubes handled in the future, sending the parameters from the given \a generator + * along with the offers. + * + * The handler is registered on the bus at the latest when this method or another exportTcpSocket() + * overload is called for the first time, so one should check the return value of isRegistered() at + * that point to verify that was successful. + * + * \param address The listen address of the socket. + * \param port The port of the socket. + * \param generator A pointer to the bootstrapping parameters generator. + */ +void StreamTubeServer::exportTcpSocket( + const QHostAddress &address, + quint16 port, + ParametersGenerator *generator) +{ + if (address.isNull() || port == 0) { + warning() << "Attempted to export null TCP socket address or zero port, ignoring"; + return; + } + + mPriv->exportedAddr = address; + mPriv->exportedPort = port; + mPriv->generator = generator; + + mPriv->ensureRegistered(); +} + +/** + * Set the server to offer the already listening TCP \a server as the local endpoint of tubes + * handled in the future, sending the parameters from the given \a generator along with the offers. + * + * This is just a convenience wrapper around + * exportTcpSocket(const QHostAddress &, quint16, ParametersGenerator *) to be used when the TCP + * server code is implemented using the QtNetwork facilities. + * + * \param server A pointer to the TCP server. + * \param generator A pointer to the bootstrapping parameters generator. + */ +void StreamTubeServer::exportTcpSocket( + const QTcpServer *server, + ParametersGenerator *generator) +{ + if (!server->isListening()) { + warning() << "Attempted to export non-listening QTcpServer, ignoring"; + return; + } + + if (server->serverAddress() == QHostAddress::Any) { + return exportTcpSocket(QHostAddress::LocalHost, server->serverPort(), generator); + } else if (server->serverAddress() == QHostAddress::AnyIPv6) { + return exportTcpSocket(QHostAddress::LocalHostIPv6, server->serverPort(), generator); + } else { + return exportTcpSocket(server->serverAddress(), server->serverPort(), generator); + } +} + +/** + * Return the tubes currently handled by the server. + * + * \return A list of Tube structures containing pointers to the account and tube channel for each + * tube. + */ +QList<StreamTubeServer::Tube> StreamTubeServer::tubes() const +{ + QList<Tube> tubes; + + foreach (TubeWrapper *wrapper, mPriv->tubes.values()) { + tubes.push_back(Tube(wrapper->mAcc, wrapper->mTube)); + } + + return tubes; +} + +/** + * Return the ongoing TCP connections over tubes handled by this server. + * + * The returned mapping has the connection source addresses as keys and the contacts along with the + * accounts which can be used to reach them as values. Connections through protocol backends which + * don't support SocketAccessControlPort will be included as the potentially many values for the + * null source address key, the pair (\c QHostAddress::Null, 0). + * + * This is effectively a state recovery accessor corresponding to the change notification signals + * newTcpConnection() and tcpConnectionClosed(). + * + * The mapping is only populated if connection monitoring was requested when creating the server (so + * monitorsConnections() returns \c true). + * + * \return The connections in a mapping with pairs of their source host addresses and ports as keys + * and structures containing pointers to the account and remote contacts they're from as values. + */ +QHash<QPair<QHostAddress, quint16>, + StreamTubeServer::RemoteContact> + StreamTubeServer::tcpConnections() const +{ + QHash<QPair<QHostAddress /* sourceAddress */, quint16 /* sourcePort */>, RemoteContact> conns; + if (!monitorsConnections()) { + warning() << "StreamTubeServer::tcpConnections() used, but connection monitoring is disabled"; + return conns; + } + + foreach (const Tube &tube, tubes()) { + // Ignore invalid and non-Open tubes to prevent a few useless warnings in corner cases where + // a tube is still being opened, or has been invalidated but we haven't processed that event + // yet. + if (!tube.channel()->isValid() || tube.channel()->state() != TubeChannelStateOpen) { + continue; + } + + if (tube.channel()->addressType() != SocketAddressTypeIPv4 && + tube.channel()->addressType() != SocketAddressTypeIPv6) { + continue; + } + + QHash<QPair<QHostAddress,quint16>, uint> srcAddrConns = + tube.channel()->connectionsForSourceAddresses(); + QHash<uint, ContactPtr> connContacts = + tube.channel()->contactsForConnections(); + + QPair<QHostAddress, quint16> srcAddr; + foreach (srcAddr, srcAddrConns.keys()) { + ContactPtr contact = connContacts.take(srcAddrConns.value(srcAddr)); + conns.insert(srcAddr, RemoteContact(tube.account(), contact)); + } + + // The remaining values in our copy of connContacts are those which didn't have a + // corresponding source address, probably because the service doesn't properly implement + // Port AC + foreach (const ContactPtr &contact, connContacts.values()) { + // Insert them with an invalid source address as the key + conns.insertMulti(qMakePair(QHostAddress(QHostAddress::Null), quint16(0)), + RemoteContact(tube.account(), contact)); + } + } + + return conns; +} + +void StreamTubeServer::onInvokedForTube( + const AccountPtr &acc, + const StreamTubeChannelPtr &tube, + const QDateTime &time, + const ChannelRequestHints &hints) +{ + Q_ASSERT(isRegistered()); // our SSTH shouldn't be receiving any channels unless it's registered + Q_ASSERT(tube->isRequested()); + Q_ASSERT(tube->isValid()); // SSTH won't emit invalid tubes + + OutgoingStreamTubeChannelPtr outgoing = OutgoingStreamTubeChannelPtr::qObjectCast(tube); + + if (outgoing) { + emit tubeRequested(acc, outgoing, time, hints); + } else { + warning() << "The ChannelFactory used by StreamTubeServer must construct" << + "OutgoingStreamTubeChannel subclasses for Requested=true StreamTubes"; + tube->requestClose(); + return; + } + + if (!mPriv->tubes.contains(tube)) { + debug().nospace() << "Offering socket " << mPriv->exportedAddr << ":" << mPriv->exportedPort + << " on tube " << tube->objectPath(); + + QVariantMap params; + if (mPriv->generator) { + params = mPriv->generator->nextParameters(acc, outgoing, hints); + } + + Q_ASSERT(!mPriv->exportedAddr.isNull() && mPriv->exportedPort != 0); + + TubeWrapper *wrapper = + new TubeWrapper(acc, outgoing, mPriv->exportedAddr, mPriv->exportedPort, params, this); + + connect(wrapper, + SIGNAL(offerFinished(TubeWrapper*,Tp::PendingOperation*)), + SLOT(onOfferFinished(TubeWrapper*,Tp::PendingOperation*))); + connect(tube.data(), + SIGNAL(invalidated(Tp::DBusProxy*,QString,QString)), + SLOT(onTubeInvalidated(Tp::DBusProxy*,QString,QString))); + + if (monitorsConnections()) { + connect(wrapper, + SIGNAL(newConnection(TubeWrapper*,uint)), + SLOT(onNewConnection(TubeWrapper*,uint))); + connect(wrapper, + SIGNAL(connectionClosed(TubeWrapper*,uint,QString,QString)), + SLOT(onConnectionClosed(TubeWrapper*,uint,QString,QString))); + } + + mPriv->tubes.insert(outgoing, wrapper); + } +} + +void StreamTubeServer::onOfferFinished( + TubeWrapper *wrapper, + Tp::PendingOperation *op) +{ + OutgoingStreamTubeChannelPtr tube = wrapper->mTube; + + if (op->isError()) { + warning() << "Offer() failed, closing tube" << tube->objectPath() << '-' << + op->errorName() << ':' << op->errorMessage(); + + if (wrapper->mTube->isValid()) { + wrapper->mTube->requestClose(); + } + + wrapper->mTube->disconnect(this); + emit tubeClosed(wrapper->mAcc, wrapper->mTube, op->errorName(), op->errorMessage()); + mPriv->tubes.remove(wrapper->mTube); + wrapper->deleteLater(); + } else { + debug() << "Tube" << tube->objectPath() << "offered successfully"; + } +} + +void StreamTubeServer::onTubeInvalidated( + Tp::DBusProxy *proxy, + const QString &error, + const QString &message) +{ + OutgoingStreamTubeChannelPtr tube(qobject_cast<OutgoingStreamTubeChannel *>(proxy)); + Q_ASSERT(!tube.isNull()); + + TubeWrapper *wrapper = mPriv->tubes.value(tube); + if (!wrapper) { + // Offer finish with error already removed it + return; + } + + debug() << "Tube" << tube->objectPath() << "invalidated with" << error << ':' << message; + + emit tubeClosed(wrapper->mAcc, wrapper->mTube, error, message); + mPriv->tubes.remove(tube); + delete wrapper; +} + +void StreamTubeServer::onNewConnection( + TubeWrapper *wrapper, + uint conn) +{ + Q_ASSERT(monitorsConnections()); + + if (wrapper->mTube->addressType() == SocketAddressTypeIPv4 + || wrapper->mTube->addressType() == SocketAddressTypeIPv6) { + QHash<QPair<QHostAddress,quint16>, uint> srcAddrConns = + wrapper->mTube->connectionsForSourceAddresses(); + QHash<uint, Tp::ContactPtr> connContacts = + wrapper->mTube->contactsForConnections(); + + QPair<QHostAddress, quint16> srcAddr = srcAddrConns.key(conn); + emit newTcpConnection(srcAddr.first, srcAddr.second, wrapper->mAcc, + connContacts.value(conn), wrapper->mTube); + } else { + // No UNIX socket should ever have been offered yet + Q_ASSERT(false); + } +} + +void StreamTubeServer::onConnectionClosed( + TubeWrapper *wrapper, + uint conn, + const QString &error, + const QString &message) +{ + Q_ASSERT(monitorsConnections()); + + if (wrapper->mTube->addressType() == SocketAddressTypeIPv4 + || wrapper->mTube->addressType() == SocketAddressTypeIPv6) { + QHash<QPair<QHostAddress,quint16>, uint> srcAddrConns = + wrapper->mTube->connectionsForSourceAddresses(); + QHash<uint, Tp::ContactPtr> connContacts = + wrapper->mTube->contactsForConnections(); + + QPair<QHostAddress, quint16> srcAddr = srcAddrConns.key(conn); + emit tcpConnectionClosed(srcAddr.first, srcAddr.second, wrapper->mAcc, + connContacts.value(conn), error, message, wrapper->mTube); + } else { + // No UNIX socket should ever have been offered yet + Q_ASSERT(false); + } +} + +/** + * \fn void StreamTubeServer::tubeRequested(const AccountPtr &account, const + * OutgoingStreamTubeChannelPtr &tube, const QDateTime &userActionTime, const ChannelRequestHints + * &hints) + * + * Emitted when a tube has been requested for one of our services, and we've began handling it. + * + * This is emitted before invoking the ParametersGenerator, if any, for the tube. + * + * \param account A pointer to the account from which the tube was requested from. + * \param tube A pointer to the actual tube channel. + * \param userActionTime The time the request occurred at, if it was an user action. Should be used + * for focus stealing prevention. + * \param hints The hints passed to the request, if any. + */ + +/** + * \fn void StreamTubeServer::tubeClosed(const AccountPtr &account, const + * OutgoingStreamTubeChannelPtr &tube, const QString &error, const QString &message) + * + * Emitted when a tube we've been handling (previously announced with tubeRequested()) has + * encountered an error or has otherwise been closed from further communication. + * + * \param account A pointer to the account from which the tube was requested from. + * \param tube A pointer to the actual tube channel. + * \param error The D-Bus error name corresponding to the reason for the closure. + * \param message A freeform debug message associated with the error. + */ + +/** + * \fn void StreamTubeServer::newTcpConnection(const QHostAddress &sourceAddress, quint16 + * sourcePort, const AccountPtr &account, const ContactPtr &contact, const + * OutgoingStreamTubeChannelPtr &tube) + * + * Emitted when we have picked up a new TCP connection to the (current or previous) exported server + * socket. This can be used to associate connections the protocol backend relays to the exported + * socket with the remote contact who originally initiated them in the other end of the tube. + * + * This is only emitted if connection monitoring was enabled when creating the StreamTubeServer. + * Additionally, if the protocol backend the connection is from doesn't support the + * ::SocketAccessControlPort mechanism, the source address and port will always be invalid. + * + * \param sourceAddress The source address of the connection, or QHostAddress::Null if it can't be + * resolved. + * \param sourcePort The source port of the connection, or 0 if it can't be resolved. + * \param account A pointer to the account through which the remote contact can be reached. + * \param contact A pointer to the remote contact object. + * \param tube A pointer to the tube channel through which the connection has been made. + */ + +/** + * \fn void StreamTubeServer::tcpConnectionClosed(const QHostAddress &sourceAddress, quint16 + * sourcePort, const AccountPtr &account, const ContactPtr &contact, conts QString &error, const + * QString &message, const OutgoingStreamTubeChannelPtr &tube) + * + * Emitted when a TCP connection (previously announced with newTcpConnection()) through one of our + * handled tubes has been closed due to an error or by a graceful disconnect (in which case the + * error is ::TP_QT_ERROR_DISCONNECTED). + * + * This is only emitted if connection monitoring was enabled when creating the StreamTubeServer. + * Additionally, if the protocol backend the connection is from doesn't support the + * ::SocketAccessControlPort mechanism, the source address and port will always be invalid. + * + * \param sourceAddress The source address of the connection, or QHostAddress::Null if it couldn't + * be resolved. + * \param sourcePort The source port of the connection, or 0 if it couldn't be resolved. + * \param account A pointer to the account through which the remote contact can be reached. + * \param contact A pointer to the remote contact object. + * \param error The D-Bus error name corresponding to the reason for the closure. + * \param message A freeform debug message associated with the error. + * \param tube A pointer to the tube channel through which the connection has been made. + */ + +} // Tp diff --git a/TelepathyQt/stream-tube-server.h b/TelepathyQt/stream-tube-server.h new file mode 100644 index 00000000..670b4a5f --- /dev/null +++ b/TelepathyQt/stream-tube-server.h @@ -0,0 +1,253 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2011 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 + */ + +#ifndef _TelepathyQt_stream_tube_server_h_HEADER_GUARD_ +#define _TelepathyQt_stream_tube_server_h_HEADER_GUARD_ + +#include <QPair> +#include <QSharedDataPointer> + +#include <TelepathyQt/AccountFactory> +#include <TelepathyQt/ChannelFactory> +#include <TelepathyQt/ConnectionFactory> +#include <TelepathyQt/ContactFactory> +#include <TelepathyQt/RefCounted> +#include <TelepathyQt/Types> + +class QHostAddress; +class QTcpServer; + +namespace Tp +{ + +class TP_QT_EXPORT StreamTubeServer : public QObject, public RefCounted +{ + Q_OBJECT + Q_DISABLE_COPY(StreamTubeServer) + + class TubeWrapper; + +public: + + class ParametersGenerator + { + public: + virtual QVariantMap + nextParameters(const AccountPtr &account, const OutgoingStreamTubeChannelPtr &tube, + const ChannelRequestHints &hints) = 0; + + protected: + virtual ~ParametersGenerator() {} + }; + + class RemoteContact : public QPair<AccountPtr, ContactPtr> + { + public: + RemoteContact(); + RemoteContact(const AccountPtr &account, const ContactPtr &contact); + RemoteContact(const RemoteContact &other); + ~RemoteContact(); + + bool isValid() const { return mPriv.constData() != 0; } + + RemoteContact &operator=(const RemoteContact &other); + + const AccountPtr &account() const + { + return first; + } + + const ContactPtr &contact() const + { + return second; + } + + private: + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; + }; + + class Tube : public QPair<AccountPtr, OutgoingStreamTubeChannelPtr> + { + public: + Tube(); + Tube(const AccountPtr &account, const OutgoingStreamTubeChannelPtr &channel); + Tube(const Tube &other); + ~Tube(); + + bool isValid() const { return mPriv.constData() != 0; } + + Tube &operator=(const Tube &other); + + const AccountPtr &account() const + { + return first; + } + + const OutgoingStreamTubeChannelPtr &channel() const + { + return second; + } + + private: + struct Private; + friend struct Private; + QSharedDataPointer<Private> mPriv; + }; + + static StreamTubeServerPtr create( + const QStringList &p2pServices, + const QStringList &roomServices = QStringList(), + const QString &clientName = QString(), + bool monitorConnections = false, + const AccountFactoryConstPtr &accountFactory = + AccountFactory::create(QDBusConnection::sessionBus()), + const ConnectionFactoryConstPtr &connectionFactory = + ConnectionFactory::create(QDBusConnection::sessionBus()), + const ChannelFactoryConstPtr &channelFactory = + ChannelFactory::create(QDBusConnection::sessionBus()), + const ContactFactoryConstPtr &contactFactory = + ContactFactory::create()); + + static StreamTubeServerPtr create( + const QDBusConnection &bus, + const AccountFactoryConstPtr &accountFactory, + const ConnectionFactoryConstPtr &connectionFactory, + const ChannelFactoryConstPtr &channelFactory, + const ContactFactoryConstPtr &contactFactory, + const QStringList &p2pServices, + const QStringList &roomServices = QStringList(), + const QString &clientName = QString(), + bool monitorConnections = false); + + static StreamTubeServerPtr create( + const AccountManagerPtr &accountManager, + const QStringList &p2pServices, + const QStringList &roomServices = QStringList(), + const QString &clientName = QString(), + bool monitorConnections = false); + + static StreamTubeServerPtr create( + const ClientRegistrarPtr ®istrar, + const QStringList &p2pServices, + const QStringList &roomServices = QStringList(), + const QString &clientName = QString(), + bool monitorConnections = false); + + virtual ~StreamTubeServer(); + + ClientRegistrarPtr registrar() const; + QString clientName() const; + bool isRegistered() const; + bool monitorsConnections() const; + + QPair<QHostAddress, quint16> exportedTcpSocketAddress() const; + QVariantMap exportedParameters() const; + + void exportTcpSocket( + const QHostAddress &address, + quint16 port, + const QVariantMap ¶meters = QVariantMap()); + void exportTcpSocket( + const QTcpServer *server, + const QVariantMap ¶meters = QVariantMap()); + + void exportTcpSocket( + const QHostAddress &address, + quint16 port, + ParametersGenerator *generator); + void exportTcpSocket( + const QTcpServer *server, + ParametersGenerator *generator); + + QList<Tube> tubes() const; + + QHash<QPair<QHostAddress, quint16>, RemoteContact> tcpConnections() const; + +Q_SIGNALS: + + void tubeRequested( + const Tp::AccountPtr &account, + const Tp::OutgoingStreamTubeChannelPtr &tube, + const QDateTime &userActionTime, + const Tp::ChannelRequestHints &hints); + void tubeClosed( + const Tp::AccountPtr &account, + const Tp::OutgoingStreamTubeChannelPtr &tube, + const QString &error, + const QString &message); + + void newTcpConnection( + const QHostAddress &sourceAddress, + quint16 sourcePort, + const Tp::AccountPtr &account, + const Tp::ContactPtr &contact, + const Tp::OutgoingStreamTubeChannelPtr &tube); + void tcpConnectionClosed( + const QHostAddress &sourceAddress, + quint16 sourcePort, + const Tp::AccountPtr &account, + const Tp::ContactPtr &contact, + const QString &error, + const QString &message, + const Tp::OutgoingStreamTubeChannelPtr &tube); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onInvokedForTube( + const Tp::AccountPtr &account, + const Tp::StreamTubeChannelPtr &tube, + const QDateTime &userActionTime, + const Tp::ChannelRequestHints &requestHints); + + TP_QT_NO_EXPORT void onOfferFinished( + TubeWrapper *wrapper, + Tp::PendingOperation *op); + TP_QT_NO_EXPORT void onTubeInvalidated( + Tp::DBusProxy *proxy, + const QString &error, + const QString &message); + + TP_QT_NO_EXPORT void onNewConnection( + TubeWrapper *wrapper, + uint conn); + TP_QT_NO_EXPORT void onConnectionClosed( + TubeWrapper *wrapper, + uint conn, + const QString &error, + const QString &message); + +private: + TP_QT_NO_EXPORT StreamTubeServer( + const ClientRegistrarPtr ®istrar, + const QStringList &p2pServices, + const QStringList &roomServices, + const QString &clientName, + bool monitorConnections); + + struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/streamed-media-channel.cpp b/TelepathyQt/streamed-media-channel.cpp new file mode 100644 index 00000000..6901719d --- /dev/null +++ b/TelepathyQt/streamed-media-channel.cpp @@ -0,0 +1,1539 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/StreamedMediaChannel> + +#include "TelepathyQt/_gen/streamed-media-channel.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Connection> +#include <TelepathyQt/ContactManager> +#include <TelepathyQt/PendingComposite> +#include <TelepathyQt/PendingContacts> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/PendingVoid> + +#include <QHash> + +namespace Tp +{ + +/* ====== PendingStreamedMediaStreams ====== */ +struct TP_QT_NO_EXPORT PendingStreamedMediaStreams::Private +{ + StreamedMediaStreams streams; + uint numStreams; + uint streamsReady; +}; + +/** + * \class PendingStreamedMediaStreams + * \ingroup clientchannel + * \headerfile TelepathyQt/streamed-media-channel.h <TelepathyQt/PendingStreamedMediaStreams> + * + * \brief Class containing the result of an asynchronous streamed media stream creation + * request. + * + * Instances of this class cannot be constructed directly; the only way to get + * one is via StreamedMediaChannel. + * + * See \ref async_model + */ + +/** + * Construct a new PendingStreamedMediaStreams object. + * + * \param channel StreamedMediaChannel to use. + * \param contact The contact who the media stream is with. + * \param types A list of stream types to request. + */ +PendingStreamedMediaStreams::PendingStreamedMediaStreams(const StreamedMediaChannelPtr &channel, + const ContactPtr &contact, + const QList<MediaStreamType> &types) + : PendingOperation(channel), + mPriv(new Private) +{ + mPriv->numStreams = types.size(); + mPriv->streamsReady = 0; + + UIntList l; + foreach (MediaStreamType type, types) { + l << type; + } + + Client::ChannelTypeStreamedMediaInterface *streamedMediaInterface = + channel->interface<Client::ChannelTypeStreamedMediaInterface>(); + QDBusPendingCallWatcher *watcher = + new QDBusPendingCallWatcher( + streamedMediaInterface->RequestStreams( + contact->handle()[0], l), this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotStreams(QDBusPendingCallWatcher*))); +} + +/** + * Class destructor. + */ +PendingStreamedMediaStreams::~PendingStreamedMediaStreams() +{ + delete mPriv; +} + +/** + * Return the channel through which the request was made. + * + * \return A pointer to the StreamedMediaChannel object. + */ +StreamedMediaChannelPtr PendingStreamedMediaStreams::channel() const +{ + return StreamedMediaChannelPtr(qobject_cast<StreamedMediaChannel*>( + (StreamedMediaChannel*) _object().data())); +} + +/** + * Return a list of the newly created StreamedMediaStreamPtr objects. + * + * \return A list of pointers to StreamedMediaStream objects, or an empty list if an error occurred. + */ +StreamedMediaStreams PendingStreamedMediaStreams::streams() const +{ + if (!isFinished()) { + warning() << "PendingStreamedMediaStreams::streams called before finished, " + "returning empty list"; + return StreamedMediaStreams(); + } else if (!isValid()) { + warning() << "PendingStreamedMediaStreams::streams called when not valid, " + "returning empty list"; + return StreamedMediaStreams(); + } + + return mPriv->streams; +} + +void PendingStreamedMediaStreams::gotStreams(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<MediaStreamInfoList> reply = *watcher; + if (reply.isError()) { + warning().nospace() << "StreamedMedia::RequestStreams()" + " failed with " << reply.error().name() << ": " << + reply.error().message(); + setFinishedWithError(reply.error()); + watcher->deleteLater(); + return; + } + + debug() << "Got reply to StreamedMedia::RequestStreams()"; + + MediaStreamInfoList list = reply.value(); + foreach (const MediaStreamInfo &streamInfo, list) { + StreamedMediaStreamPtr stream = channel()->lookupStreamById( + streamInfo.identifier); + if (!stream) { + stream = channel()->addStream(streamInfo); + } else { + channel()->onStreamDirectionChanged(streamInfo.identifier, + streamInfo.direction, streamInfo.pendingSendFlags); + channel()->onStreamStateChanged(streamInfo.identifier, + streamInfo.state); + } + mPriv->streams.append(stream); + connect(channel().data(), + SIGNAL(streamRemoved(Tp::StreamedMediaStreamPtr)), + SLOT(onStreamRemoved(Tp::StreamedMediaStreamPtr))); + connect(stream->becomeReady(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onStreamReady(Tp::PendingOperation*))); + } + + watcher->deleteLater(); +} + +void PendingStreamedMediaStreams::onStreamRemoved(const StreamedMediaStreamPtr &stream) +{ + if (isFinished()) { + return; + } + + if (mPriv->streams.contains(stream)) { + // the stream was removed before becoming ready + setFinishedWithError(QLatin1String(TELEPATHY_ERROR_CANCELLED), + QLatin1String("Stream removed before ready")); + } +} + +void PendingStreamedMediaStreams::onStreamReady(PendingOperation *op) +{ + if (isFinished()) { + return; + } + + if (op->isError()) { + setFinishedWithError(op->errorName(), op->errorMessage()); + return; + } + + mPriv->streamsReady++; + debug() << "PendingStreamedMediaStreams:"; + debug() << " Streams count:" << mPriv->numStreams; + debug() << " Streams ready:" << mPriv->streamsReady; + if (mPriv->streamsReady == mPriv->numStreams) { + debug() << "All streams are ready"; + setFinished(); + } +} + +/* ====== StreamedMediaStream ====== */ +struct TP_QT_NO_EXPORT StreamedMediaStream::Private +{ + Private(StreamedMediaStream *parent, const StreamedMediaChannelPtr &channel, + const MediaStreamInfo &info); + + static void introspectContact(Private *self); + + PendingOperation *updateDirection(bool send, bool receive); + SendingState localSendingStateFromDirection(); + SendingState remoteSendingStateFromDirection(); + + StreamedMediaStream *parent; + QPointer<StreamedMediaChannel> channel; + ReadinessHelper *readinessHelper; + + uint id; + uint type; + uint contactHandle; + ContactPtr contact; + uint direction; + uint pendingSend; + uint state; +}; + +StreamedMediaStream::Private::Private(StreamedMediaStream *parent, + const StreamedMediaChannelPtr &channel, + const MediaStreamInfo &streamInfo) + : parent(parent), + channel(channel.data()), + readinessHelper(parent->readinessHelper()), + id(streamInfo.identifier), + type(streamInfo.type), + contactHandle(streamInfo.contact), + direction(MediaStreamDirectionNone), + pendingSend(0), + state(MediaStreamStateDisconnected) +{ + ReadinessHelper::Introspectables introspectables; + + ReadinessHelper::Introspectable introspectableCore( + QSet<uint>() << 0, // makesSenseForStatuses + Features(), // dependsOnFeatures + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectContact, + this); + introspectables[FeatureCore] = introspectableCore; + + readinessHelper->addIntrospectables(introspectables); +} + +void StreamedMediaStream::Private::introspectContact(StreamedMediaStream::Private *self) +{ + debug() << "Introspecting stream"; + if (self->contactHandle == 0) { + debug() << "Stream ready"; + self->readinessHelper->setIntrospectCompleted(FeatureCore, true); + return; + } + + debug() << "Introspecting stream contact"; + ContactManagerPtr contactManager = + self->parent->channel()->connection()->contactManager(); + debug() << "contact manager" << contactManager; + // TODO: pass id hints to ContactManager if we ever gain support to retrieve contact ids + // from MediaStreamInfo or something similar. + self->parent->connect(contactManager->contactsForHandles( + UIntList() << self->contactHandle), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(gotContact(Tp::PendingOperation*))); +} + +PendingOperation *StreamedMediaStream::Private::updateDirection( + bool send, bool receive) +{ + uint newDirection = 0; + + if (send) { + newDirection |= MediaStreamDirectionSend; + } + + if (receive) { + newDirection |= MediaStreamDirectionReceive; + } + + Client::ChannelTypeStreamedMediaInterface *streamedMediaInterface = + parent->channel()->interface<Client::ChannelTypeStreamedMediaInterface>(); + return new PendingVoid( + streamedMediaInterface->RequestStreamDirection( + id, newDirection), + StreamedMediaStreamPtr(parent)); +} + +StreamedMediaStream::SendingState StreamedMediaStream::Private::localSendingStateFromDirection() +{ + if (pendingSend & MediaStreamPendingLocalSend) { + return SendingStatePendingSend; + } + if (direction & MediaStreamDirectionSend) { + return SendingStateSending; + } + return SendingStateNone; +} + +StreamedMediaStream::SendingState StreamedMediaStream::Private::remoteSendingStateFromDirection() +{ + if (pendingSend & MediaStreamPendingRemoteSend) { + return SendingStatePendingSend; + } + if (direction & MediaStreamDirectionReceive) { + return SendingStateSending; + } + return SendingStateNone; +} + +/** + * \class StreamedMediaStream + * \ingroup clientchannel + * \headerfile TelepathyQt/streamed-media-channel.h <TelepathyQt/StreamedMediaStream> + * + * \brief The StreamedMediaStream class represents a Telepathy streamed media + * stream. + * + * Instances of this class cannot be constructed directly; the only way to get + * one is via StreamedMediaChannel. + */ + +/** + * Feature representing the core that needs to become ready to make the + * StreamedMediaStream object usable. + * + * Note that this feature must be enabled in order to use most StreamedMediaStream + * methods. See specific methods documentation for more details. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature StreamedMediaStream::FeatureCore = Feature(QLatin1String(StreamedMediaStream::staticMetaObject.className()), 0); + +/** + * Construct a new StreamedMediaStream object. + * + * \param channel The channel ownding this media stream. + * \param streamInfo The stream info of this media stream. + */ +StreamedMediaStream::StreamedMediaStream(const StreamedMediaChannelPtr &channel, + const MediaStreamInfo &streamInfo) + : Object(), + ReadyObject(this, FeatureCore), + mPriv(new Private(this, channel, streamInfo)) +{ + gotDirection(streamInfo.direction, streamInfo.pendingSendFlags); + gotStreamState(streamInfo.state); +} + +/** + * Class destructor. + */ +StreamedMediaStream::~StreamedMediaStream() +{ + delete mPriv; +} + +/** + * Return the channel owning this media stream. + * + * \return A pointer to the StreamedMediaChannel object. + */ +StreamedMediaChannelPtr StreamedMediaStream::channel() const +{ + return StreamedMediaChannelPtr(mPriv->channel); +} + +/** + * Return the id of this media stream. + * + * \return An integer representing the media stream id. + */ +uint StreamedMediaStream::id() const +{ + return mPriv->id; +} + +/** + * Return the contact who this media stream is with. + * + * \return A pointer to the Contact object. + */ +ContactPtr StreamedMediaStream::contact() const +{ + return mPriv->contact; +} + +/** + * Return the state of this media stream. + * + * \return The state as #MediaStreamState. + */ +MediaStreamState StreamedMediaStream::state() const +{ + return (MediaStreamState) mPriv->state; +} + +/** + * Return the type of this media stream. + * + * \return The type as #MediaStreamType. + */ +MediaStreamType StreamedMediaStream::type() const +{ + return (MediaStreamType) mPriv->type; +} + +/** + * Return whether media is being sent on this media stream. + * + * \return \c true if media is being sent, \c false otherwise. + * \sa localSendingStateChanged() + */ +bool StreamedMediaStream::sending() const +{ + return mPriv->direction & MediaStreamDirectionSend; +} + +/** + * Return whether media is being received on this media stream. + * + * \return \c true if media is being received, \c false otherwise. + * \sa remoteSendingStateChanged() + */ +bool StreamedMediaStream::receiving() const +{ + return mPriv->direction & MediaStreamDirectionReceive; +} + +/** + * Return whether the local user has been asked to send media by the + * remote user on this media stream. + * + * \return \c true if the local user has been asked to send media by the + * remote user, \c false otherwise. + * \sa localSendingStateChanged() + */ +bool StreamedMediaStream::localSendingRequested() const +{ + return mPriv->pendingSend & MediaStreamPendingLocalSend; +} + +/** + * Return whether the remote user has been asked to send media by the local + * user on this media stream. + * + * \return \c true if the remote user has been asked to send media by the + * local user, \c false otherwise. + * \sa remoteSendingStateChanged() + */ +bool StreamedMediaStream::remoteSendingRequested() const +{ + return mPriv->pendingSend & MediaStreamPendingRemoteSend; +} + +/** + * Return the direction of this media stream. + * + * \return The direction as #MediaStreamDirection. + * \sa localSendingState(), remoteSendingState(), + * localSendingStateChanged(), remoteSendingStateChanged(), + * sending(), receiving() + */ +MediaStreamDirection StreamedMediaStream::direction() const +{ + return (MediaStreamDirection) mPriv->direction; +} + +/** + * Return the pending send flags of this media stream. + * + * \return The pending send flags as #MediaStreamPendingSend. + * \sa localSendingStateChanged() + */ +MediaStreamPendingSend StreamedMediaStream::pendingSend() const +{ + return (MediaStreamPendingSend) mPriv->pendingSend; +} + +/** + * Request a change in the direction of this media stream. In particular, this + * might be useful to stop sending media of a particular type, or inform the + * peer that you are no longer using media that is being sent to you. + * + * \param direction The new direction. + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + * \sa localSendingStateChanged(), remoteSendingStateChanged() + */ +PendingOperation *StreamedMediaStream::requestDirection( + MediaStreamDirection direction) +{ + StreamedMediaChannelPtr chan(channel()); + Client::ChannelTypeStreamedMediaInterface *streamedMediaInterface = + chan->interface<Client::ChannelTypeStreamedMediaInterface>(); + return new PendingVoid( + streamedMediaInterface->RequestStreamDirection( + mPriv->id, direction), + StreamedMediaStreamPtr(this)); +} + +/** + * Start sending a DTMF tone on this media stream. + * + * Where possible, the tone will continue until stopDTMFTone() is called. + * On certain protocols, it may only be possible to send events with a predetermined + * length. In this case, the implementation may emit a fixed-length tone, + * and the stopDTMFTone() method call should return #TP_QT_ERROR_NOT_AVAILABLE. + * + * If the channel() does not support the #TP_QT_IFACE_CHANNEL_INTERFACE_DTMF + * interface, the resulting PendingOperation will fail with error code + * #TP_QT_ERROR_NOT_IMPLEMENTED. + + * \param event A numeric event code from the #DTMFEvent enum. + * \return A PendingOperation which will emit PendingOperation::finished + * when the request finishes. + * \sa stopDTMFTone() + */ +PendingOperation *StreamedMediaStream::startDTMFTone(DTMFEvent event) +{ + StreamedMediaChannelPtr chan(channel()); + if (!chan->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_DTMF))) { + warning() << "StreamedMediaStream::startDTMFTone() used with no dtmf interface"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("StreamedMediaChannel does not support dtmf interface"), + StreamedMediaStreamPtr(this)); + } + + Client::ChannelInterfaceDTMFInterface *dtmfInterface = + chan->interface<Client::ChannelInterfaceDTMFInterface>(); + return new PendingVoid( + dtmfInterface->StartTone(mPriv->id, event), + StreamedMediaStreamPtr(this)); +} + +/** + * Stop sending any DTMF tone which has been started using the startDTMFTone() + * method. + * + * If there is no current tone, the resulting PendingOperation will + * finish successfully. + * + * If continuous tones are not supported by this media stream, the resulting + * PendingOperation will fail with error code #TP_QT_ERROR_NOT_AVAILABLE. + * + * If the channel() does not support the #TP_QT_IFACE_CHANNEL_INTERFACE_DTMF + * interface, the resulting PendingOperation will fail with error code + * #TP_QT_ERROR_NOT_IMPLEMENTED. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the request finishes. + * \sa startDTMFTone() + */ +PendingOperation *StreamedMediaStream::stopDTMFTone() +{ + StreamedMediaChannelPtr chan(channel()); + if (!chan->interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_DTMF))) { + warning() << "StreamedMediaStream::stopDTMFTone() used with no dtmf interface"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("StreamedMediaChannel does not support dtmf interface"), + StreamedMediaStreamPtr(this)); + } + + Client::ChannelInterfaceDTMFInterface *dtmfInterface = + chan->interface<Client::ChannelInterfaceDTMFInterface>(); + return new PendingVoid( + dtmfInterface->StopTone(mPriv->id), + StreamedMediaStreamPtr(this)); +} + +/** + * Request a change in the direction of this media stream. + * + * In particular, this might be useful to stop sending media of a particular type, + * or inform the peer that you are no longer using media that is being sent to you. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + * \sa requestDirection(Tp::MediaStreamDirection direction), + * localSendingStateChanged(), remoteSendingStateChanged() + */ +PendingOperation *StreamedMediaStream::requestDirection(bool send, bool receive) +{ + uint dir = MediaStreamDirectionNone; + if (send) { + dir |= MediaStreamDirectionSend; + } + if (receive) { + dir |= MediaStreamDirectionReceive; + } + + return requestDirection((MediaStreamDirection) dir); +} + +/** + * Return the media stream local sending state. + * + * \return The local sending state as StreamedMediaStream::SendingState. + * \sa localSendingStateChanged() + */ +StreamedMediaStream::SendingState StreamedMediaStream::localSendingState() const +{ + return mPriv->localSendingStateFromDirection(); +} + +/** + * Return the media stream remote sending state. + * + * \return The remote sending state as StreamedMediaStream::SendingState. + * \sa remoteSendingStateChanged() + */ +StreamedMediaStream::SendingState StreamedMediaStream::remoteSendingState() const +{ + return mPriv->remoteSendingStateFromDirection(); +} + +/** + * Request that media starts or stops being sent on this media stream. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + * \sa localSendingStateChanged(), requestDirection() + */ +PendingOperation *StreamedMediaStream::requestSending(bool send) +{ + return mPriv->updateDirection( + send, + mPriv->direction & MediaStreamDirectionReceive); +} + +/** + * Request that the remote contact stops or starts sending on this media stream. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + * \sa remoteSendingStateChanged(), requestDirection() + */ +PendingOperation *StreamedMediaStream::requestReceiving(bool receive) +{ + return mPriv->updateDirection( + mPriv->direction & MediaStreamDirectionSend, + receive); +} + +/** + * \fn void StreamedMediaStream::localSendingStateChanged( + * Tp::StreamedMediaStream::SendingState localSendingState) + * + * Emitted when the local sending state of this media stream changes. + * + * \param localSendingState The new local sending state of this media stream. + * \sa localSendingState() + */ + +/** + * \fn void MediaStream::remoteSendingStateChanged( + * Tp::MediaStream::SendingState &remoteSendingState) + * + * Emitted when the remote sending state of this media stream changes. + * + * \param remoteSendingState The new remote sending state of this media stream. + * \sa remoteSendingState() + */ + +void StreamedMediaStream::gotContact(PendingOperation *op) +{ + PendingContacts *pc = qobject_cast<PendingContacts *>(op); + Q_ASSERT(pc->isForHandles()); + + if (op->isError()) { + warning().nospace() << "Gathering media stream contact failed: " + << op->errorName() << ": " << op->errorMessage(); + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, + op->errorName(), op->errorMessage()); + return; + } + + QList<ContactPtr> contacts = pc->contacts(); + UIntList invalidHandles = pc->invalidHandles(); + if (contacts.size()) { + Q_ASSERT(contacts.size() == 1); + Q_ASSERT(invalidHandles.size() == 0); + mPriv->contact = contacts.first(); + + debug() << "Got stream contact"; + debug() << "Stream ready"; + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, true); + } else { + Q_ASSERT(invalidHandles.size() == 1); + warning().nospace() << "Error retrieving media stream contact (invalid handle)"; + mPriv->readinessHelper->setIntrospectCompleted(FeatureCore, false, + QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Invalid contact handle")); + } +} + +void StreamedMediaStream::gotDirection(uint direction, uint pendingSend) +{ + if (direction == mPriv->direction && + pendingSend == mPriv->pendingSend) { + return; + } + + SendingState oldLocalState = mPriv->localSendingStateFromDirection(); + SendingState oldRemoteState = mPriv->remoteSendingStateFromDirection(); + + mPriv->direction = direction; + mPriv->pendingSend = pendingSend; + + if (!isReady()) { + return; + } + + SendingState localSendingState = + mPriv->localSendingStateFromDirection(); + if (localSendingState != oldLocalState) { + emit localSendingStateChanged(localSendingState); + } + + SendingState remoteSendingState = + mPriv->remoteSendingStateFromDirection(); + if (remoteSendingState != oldRemoteState) { + emit remoteSendingStateChanged(remoteSendingState); + } +} + +void StreamedMediaStream::gotStreamState(uint state) +{ + if (state == mPriv->state) { + return; + } + + mPriv->state = state; +} + +/* ====== StreamedMediaChannel ====== */ +struct TP_QT_NO_EXPORT StreamedMediaChannel::Private +{ + Private(StreamedMediaChannel *parent); + ~Private(); + + static void introspectStreams(Private *self); + static void introspectLocalHoldState(Private *self); + + // Public object + StreamedMediaChannel *parent; + + // Mandatory properties interface proxy + Client::DBus::PropertiesInterface *properties; + + ReadinessHelper *readinessHelper; + + // Introspection + StreamedMediaStreams incompleteStreams; + StreamedMediaStreams streams; + + LocalHoldState localHoldState; + LocalHoldStateReason localHoldStateReason; +}; + +StreamedMediaChannel::Private::Private(StreamedMediaChannel *parent) + : parent(parent), + properties(parent->interface<Client::DBus::PropertiesInterface>()), + readinessHelper(parent->readinessHelper()), + localHoldState(LocalHoldStateUnheld), + localHoldStateReason(LocalHoldStateReasonNone) +{ + ReadinessHelper::Introspectables introspectables; + + ReadinessHelper::Introspectable introspectableStreams( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << Channel::FeatureCore, // dependsOnFeatures (core) + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectStreams, + this); + introspectables[FeatureStreams] = introspectableStreams; + + ReadinessHelper::Introspectable introspectableLocalHoldState( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << Channel::FeatureCore, // dependsOnFeatures (core) + QStringList() << QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_HOLD), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectLocalHoldState, + this); + introspectables[FeatureLocalHoldState] = introspectableLocalHoldState; + + readinessHelper->addIntrospectables(introspectables); +} + +StreamedMediaChannel::Private::~Private() +{ +} + +void StreamedMediaChannel::Private::introspectStreams(StreamedMediaChannel::Private *self) +{ + StreamedMediaChannel *parent = self->parent; + Client::ChannelTypeStreamedMediaInterface *streamedMediaInterface = + parent->interface<Client::ChannelTypeStreamedMediaInterface>(); + + parent->connect(streamedMediaInterface, + SIGNAL(StreamAdded(uint,uint,uint)), + SLOT(onStreamAdded(uint,uint,uint))); + parent->connect(streamedMediaInterface, + SIGNAL(StreamRemoved(uint)), + SLOT(onStreamRemoved(uint))); + parent->connect(streamedMediaInterface, + SIGNAL(StreamDirectionChanged(uint,uint,uint)), + SLOT(onStreamDirectionChanged(uint,uint,uint))); + parent->connect(streamedMediaInterface, + SIGNAL(StreamStateChanged(uint,uint)), + SLOT(onStreamStateChanged(uint,uint))); + parent->connect(streamedMediaInterface, + SIGNAL(StreamError(uint,uint,QString)), + SLOT(onStreamError(uint,uint,QString))); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + streamedMediaInterface->ListStreams(), parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher *)), + SLOT(gotStreams(QDBusPendingCallWatcher *))); +} + +void StreamedMediaChannel::Private::introspectLocalHoldState(StreamedMediaChannel::Private *self) +{ + StreamedMediaChannel *parent = self->parent; + Client::ChannelInterfaceHoldInterface *holdInterface = + parent->interface<Client::ChannelInterfaceHoldInterface>(); + + parent->connect(holdInterface, + SIGNAL(HoldStateChanged(uint,uint)), + SLOT(onLocalHoldStateChanged(uint,uint))); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + holdInterface->GetHoldState(), parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotLocalHoldState(QDBusPendingCallWatcher*))); +} + +/** + * \class StreamedMediaChannel + * \ingroup clientchannel + * \headerfile TelepathyQt/streamed-media-channel.h <TelepathyQt/StreamedMediaChannel> + * + * \brief The StreamedMediaChannel class represents a Telepathy channel of type StreamedMedia. + * + * For more details, please refer to \telepathy_spec. + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Feature representing the core that needs to become ready to make the + * StreamedMediaChannel object usable. + * + * This is currently the same as Channel::FeatureCore, but may change to include more. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + * \sa awaitingLocalAnswer(), awaitingRemoteAnswer(), acceptCall(), hangupCall(), handlerStreamingRequired() + */ +const Feature StreamedMediaChannel::FeatureCore = Feature(QLatin1String(Channel::staticMetaObject.className()), 0, true); + +/** + * Feature used in order to access media stream specific methods. + * + * See media stream specific methods' documentation for more details. + * \sa streams(), streamsForType(), + * requestStream(), requestStreams(), streamAdded() + * removeStream(), removeStreams(), streamRemoved(), + * streamDirectionChanged(), streamStateChanged(), streamError() + */ +const Feature StreamedMediaChannel::FeatureStreams = Feature(QLatin1String(StreamedMediaChannel::staticMetaObject.className()), 0); + +/** + * Feature used in order to access local hold state info. + * + * See local hold state specific methods' documentation for more details. + * \sa localHoldState(), localHoldStateReason(), requestHold(), localHoldStateChanged() + */ +const Feature StreamedMediaChannel::FeatureLocalHoldState = Feature(QLatin1String(StreamedMediaChannel::staticMetaObject.className()), 1); + +/** + * Create a new StreamedMediaChannel 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 StreamedMediaChannelPtr object pointing to the newly created + * StreamedMediaChannel object. + */ +StreamedMediaChannelPtr StreamedMediaChannel::create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties) +{ + return StreamedMediaChannelPtr(new StreamedMediaChannel(connection, + objectPath, immutableProperties, StreamedMediaChannel::FeatureCore)); +} + +/** + * Construct a new StreamedMediaChannel 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, if any. The corresponding introspectable should + * depend on StreamedMediaChannel::FeatureCore. + */ +StreamedMediaChannel::StreamedMediaChannel(const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : Channel(connection, objectPath, immutableProperties, coreFeature), + mPriv(new Private(this)) +{ +} + +/** + * Class destructor. + */ +StreamedMediaChannel::~StreamedMediaChannel() +{ + delete mPriv; +} + +/** + * Return a list of media streams in this channel. + * + * This methods requires StreamedMediaChannel::FeatureStreams to be ready. + * + * \return A list of pointers to StreamedMediaStream objects. + * \sa streamAdded(), streamRemoved(), streamsForType(), requestStreams() + */ +StreamedMediaStreams StreamedMediaChannel::streams() const +{ + return mPriv->streams; +} + +/** + * Return a list of media streams in this channel for the given type \a type. + * + * This methods requires StreamedMediaChannel::FeatureStreams to be ready. + * + * \param type The interested type. + * \return A list of pointers to StreamedMediaStream objects. + * \sa streamAdded(), streamRemoved(), streams(), requestStreams() + */ +StreamedMediaStreams StreamedMediaChannel::streamsForType(MediaStreamType type) const +{ + StreamedMediaStreams ret; + foreach (const StreamedMediaStreamPtr &stream, mPriv->streams) { + if (stream->type() == type) { + ret << stream; + } + } + return ret; +} + +/** + * Return whether this channel is awaiting local answer. + * + * This method requires StreamedMediaChannel::FeatureCore to be ready. + * + * \return \c true if awaiting local answer, \c false otherwise. + * \sa awaitingRemoteAnswer(), acceptCall() + */ +bool StreamedMediaChannel::awaitingLocalAnswer() const +{ + return groupSelfHandleIsLocalPending(); +} + +/** + * Return whether this channel is awaiting remote answer. + * + * This method requires StreamedMediaChannel::FeatureCore to be ready. + * + * \return \c true if awaiting remote answer, \c false otherwise. + * \sa awaitingLocalAnswer() + */ +bool StreamedMediaChannel::awaitingRemoteAnswer() const +{ + return !groupRemotePendingContacts().isEmpty(); +} + +/** + * Accept an incoming call. + * + * This method requires StreamedMediaChannel::FeatureCore to be ready. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + * \sa awaitingLocalAnswer(), hangupCall() + */ +PendingOperation *StreamedMediaChannel::acceptCall() +{ + return groupAddSelfHandle(); +} + +/** + * Remove the specified media stream from this channel. + * + * This methods requires StreamedMediaChannel::FeatureStreams to be ready. + * + * \param stream Media stream to remove. + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + * \sa streamRemoved(), streams(), streamsForType() + */ +PendingOperation *StreamedMediaChannel::removeStream(const StreamedMediaStreamPtr &stream) +{ + if (!stream) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Unable to remove a null stream"), + StreamedMediaChannelPtr(this)); + } + + // StreamedMedia.RemoveStreams will trigger StreamedMedia.StreamRemoved + // that will proper remove the stream + Client::ChannelTypeStreamedMediaInterface *streamedMediaInterface = + interface<Client::ChannelTypeStreamedMediaInterface>(); + return new PendingVoid( + streamedMediaInterface->RemoveStreams( + UIntList() << stream->id()), + StreamedMediaChannelPtr(this)); +} + +/** + * Remove the specified media streams from this channel. + * + * This methods requires StreamedMediaChannel::FeatureStreams to be ready. + * + * \param streams List of media streams to remove. + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + * \sa streamRemoved(), streams(), streamsForType() + */ +PendingOperation *StreamedMediaChannel::removeStreams(const StreamedMediaStreams &streams) +{ + UIntList ids; + foreach (const StreamedMediaStreamPtr &stream, streams) { + if (!stream) { + continue; + } + ids << stream->id(); + } + + if (ids.isEmpty()) { + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_INVALID_ARGUMENT), + QLatin1String("Unable to remove invalid streams"), + StreamedMediaChannelPtr(this)); + } + + Client::ChannelTypeStreamedMediaInterface *streamedMediaInterface = + interface<Client::ChannelTypeStreamedMediaInterface>(); + return new PendingVoid( + streamedMediaInterface->RemoveStreams(ids), + StreamedMediaChannelPtr(this)); +} + +/** + * Request that media streams be established to exchange the given type \a type + * of media with the given contact \a contact. + * + * This methods requires StreamedMediaChannel::FeatureStreams to be ready. + * + * \return A PendingStreamedMediaStreams which will emit PendingStreamedMediaStreams::finished + * when the call has finished. + * \sa streamAdded(), streams(), streamsForType() + */ +PendingStreamedMediaStreams *StreamedMediaChannel::requestStream( + const ContactPtr &contact, + MediaStreamType type) +{ + return new PendingStreamedMediaStreams(StreamedMediaChannelPtr(this), + contact, + QList<MediaStreamType>() << type); +} + +/** + * Request that media streams be established to exchange the given types \a + * types of media with the given contact \a contact. + * + * This methods requires StreamedMediaChannel::FeatureStreams to be ready. + * + * \return A PendingStreamedMediaStreams which will emit PendingStreamedMediaStreams::finished + * when the call has finished. + * \sa streamAdded(), streams(), streamsForType() + */ +PendingStreamedMediaStreams *StreamedMediaChannel::requestStreams( + const ContactPtr &contact, + QList<MediaStreamType> types) +{ + return new PendingStreamedMediaStreams(StreamedMediaChannelPtr(this), + contact, types); +} + +/** + * Request that the call is ended. + * + * This method requires StreamedMediaChannel::FeatureCore to be ready. + * + * \return A PendingOperation which will emit PendingOperation::finished + * when the call has finished. + */ +PendingOperation *StreamedMediaChannel::hangupCall() +{ + return requestLeave(); +} + +/** + * Check whether media streaming by the handler is required for this channel. + * + * For channels with the #TP_QT_IFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING interface, + * the main handler of the channel is responsible for doing the actual streaming, for instance by + * calling createFarsightChannel(channel) from TelepathyQt-Farsight library + * and using the telepathy-farsight API on the resulting TfChannel. + * + * This method requires StreamedMediaChannel::FeatureCore to be ready. + * + * \return \c true if required, \c false otherwise. + */ +bool StreamedMediaChannel::handlerStreamingRequired() const +{ + return interfaces().contains( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_MEDIA_SIGNALLING)); +} + +/** + * Return the local hold state for this channel. + * + * Whether the local user has placed this channel on hold. + * + * This method requires StreamedMediaChannel::FeatureHoldState to be ready. + * + * \return The local hold state as #LocalHoldState. + * \sa requestHold(), localHoldStateChanged() + */ +LocalHoldState StreamedMediaChannel::localHoldState() const +{ + if (!isReady(FeatureLocalHoldState)) { + warning() << "StreamedMediaChannel::localHoldState() used with FeatureLocalHoldState not ready"; + } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_HOLD))) { + warning() << "StreamedMediaChannel::localHoldStateReason() used with no hold interface"; + } + + return mPriv->localHoldState; +} + +/** + * Return the reason why localHoldState() changed to its current value. + * + * This method requires StreamedMediaChannel::FeatureLocalHoldState to be ready. + * + * \return The local hold state reason as #LocalHoldStateReason. + * \sa requestHold(), localHoldStateChanged() + */ +LocalHoldStateReason StreamedMediaChannel::localHoldStateReason() const +{ + if (!isReady(FeatureLocalHoldState)) { + warning() << "StreamedMediaChannel::localHoldStateReason() used with FeatureLocalHoldState not ready"; + } else if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_HOLD))) { + warning() << "StreamedMediaChannel::localHoldStateReason() used with no hold interface"; + } + + return mPriv->localHoldStateReason; +} + +/** + * Request that the channel be put on hold (be instructed not to send any media + * streams to you) or be taken off hold. + * + * If the CM can immediately tell that the requested state + * change could not possibly succeed, the resulting PendingOperation will fail + * with error code #TP_QT_ERROR_NOT_AVAILABLE. + * If the requested state is the same as the current state, the resulting + * PendingOperation will finish successfully. + * + * Otherwise, the channel's local hold state will change to + * #LocalHoldStatePendingHold or #LocalHoldStatePendingUnhold (as + * appropriate), then the resulting PendingOperation will finish successfully. + * + * The eventual success or failure of the request is indicated by a subsequent + * localHoldStateChanged() signal, changing the local hold state to + * #LocalHoldStateHeld or #LocalHoldStateUnheld. + * + * If the channel has multiple streams, and the connection manager succeeds in + * changing the hold state of one stream but fails to change the hold state of + * another, it will attempt to revert all streams to their previous hold + * states. + * + * If the channel does not support the #TP_QT_IFACE_CHANNEL_INTERFACE_HOLD + * interface, the PendingOperation will fail with error code + * #TP_QT_ERROR_NOT_IMPLEMENTED. + * + * \param hold A boolean indicating whether or not the channel should be on hold + * \return A PendingOperation which will emit PendingOperation::finished + * when the request finishes. + * \sa localHoldState(), localHoldStateReason(), localHoldStateChanged() + */ +PendingOperation *StreamedMediaChannel::requestHold(bool hold) +{ + if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_HOLD))) { + warning() << "StreamedMediaChannel::requestHold() used with no hold interface"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("StreamedMediaChannel does not support hold interface"), + StreamedMediaChannelPtr(this)); + } + + Client::ChannelInterfaceHoldInterface *holdInterface = + interface<Client::ChannelInterfaceHoldInterface>(); + return new PendingVoid(holdInterface->RequestHold(hold), + StreamedMediaChannelPtr(this)); +} + +/** + * \fn void StreamedMediaChannel::streamAdded(const Tp::StreamedMediaStreamPtr &stream) + * + * Emitted when a media stream is added to this channel. + * + * \param stream The media stream that was added. + * \sa streams(), streamsForType(), streamRemoved() + */ + +/** + * \fn void StreamedMediaChannel::streamRemoved(const Tp::StreamedMediaStreamPtr &stream) + * + * Emitted when a media stream is removed from this channel. + * + * \param stream The media stream that was removed. + * \sa streams(), streamsForType(), streamAdded() + */ + +/** + * \fn void StreamedMediaChannel::streamDirectionChanged( + * const Tp::StreamedMediaStreamPtr &stream, Tp::MediaStreamDirection direction, + * Tp::MediaStreamPendingSend pendingSend) + * + * Emitted when a media stream direction changes. + * + * \param stream The media stream which the direction changed. + * \param direction The new direction of the stream that changed. + * \param pendingSend The new pending send flags of the stream that changed. + * \sa StreamedMediaStream::direction() + */ + +/** + * \fn void StreamedMediaChannel::streamStateChanged( + * const Tp::StreamedMediaStreamPtr &stream, Tp::MediaStreamState state) + * + * Emitted when a media stream state changes. + * + * \param stream The media stream which the state changed. + * \param state The new state of the stream that changed. + * \sa StreamedMediaStream::state() + */ + +/** + * \fn void StreamedMediaChannel::streamError( + * const Tp::StreamedMediaStreamPtr &stream, + * Tp::StreamedMediaStreamError errorCode, const QString &errorMessage) + * + * Emitted when an error occurs on a media stream. + * + * \param stream The media stream which the error occurred. + * \param errorCode The error code. + * \param errorMessage The error message. + */ + +/** + * \fn void StreamedMediaChannel::localHoldStateChanged(Tp::LocalHoldState state, Tp::LocalHoldStateReason reason); + * + * Emitted when the local hold state of this channel changes. + * + * \param state The new local hold state of this channel. + * \param reason The reason why the change occurred. + * \sa localHoldState(), localHoldStateReason() + */ + +void StreamedMediaChannel::onStreamReady(PendingOperation *op) +{ + PendingReady *pr = qobject_cast<PendingReady*>(op); + StreamedMediaStreamPtr stream = StreamedMediaStreamPtr::qObjectCast(pr->proxy()); + + if (op->isError()) { + mPriv->incompleteStreams.removeOne(stream); + if (!isReady(FeatureStreams) && mPriv->incompleteStreams.size() == 0) { + // let's not fail because a stream could not become ready + mPriv->readinessHelper->setIntrospectCompleted(FeatureStreams, true); + } + return; + } + + // the stream was removed before become ready + if (!mPriv->incompleteStreams.contains(stream)) { + if (!isReady(FeatureStreams) && mPriv->incompleteStreams.size() == 0) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureStreams, true); + } + return; + } + + mPriv->incompleteStreams.removeOne(stream); + mPriv->streams.append(stream); + + if (isReady(FeatureStreams)) { + emit streamAdded(stream); + } + + if (!isReady(FeatureStreams) && mPriv->incompleteStreams.size() == 0) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureStreams, true); + } +} + +void StreamedMediaChannel::gotStreams(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<MediaStreamInfoList> reply = *watcher; + if (reply.isError()) { + warning().nospace() << "StreamedMedia.ListStreams failed with" << + reply.error().name() << ": " << reply.error().message(); + + mPriv->readinessHelper->setIntrospectCompleted(FeatureStreams, + false, reply.error()); + watcher->deleteLater(); + return; + } + + debug() << "Got reply to StreamedMedia::ListStreams()"; + + MediaStreamInfoList streamInfoList = reply.value(); + if (streamInfoList.size() > 0) { + foreach (const MediaStreamInfo &streamInfo, streamInfoList) { + StreamedMediaStreamPtr stream = lookupStreamById( + streamInfo.identifier); + if (!stream) { + addStream(streamInfo); + } else { + onStreamDirectionChanged(streamInfo.identifier, + streamInfo.direction, streamInfo.pendingSendFlags); + onStreamStateChanged(streamInfo.identifier, + streamInfo.state); + } + } + } else { + mPriv->readinessHelper->setIntrospectCompleted(FeatureStreams, true); + } + + watcher->deleteLater(); +} + +void StreamedMediaChannel::onStreamAdded(uint streamId, + uint contactHandle, uint streamType) +{ + if (lookupStreamById(streamId)) { + debug() << "Received StreamedMedia.StreamAdded for an existing " + "stream, ignoring"; + return; + } + + MediaStreamInfo streamInfo = { + streamId, + contactHandle, + streamType, + MediaStreamStateDisconnected, + MediaStreamDirectionReceive, + MediaStreamPendingLocalSend + }; + addStream(streamInfo); +} + +void StreamedMediaChannel::onStreamRemoved(uint streamId) +{ + debug() << "Received StreamedMedia.StreamRemoved for stream" << + streamId; + + StreamedMediaStreamPtr stream = lookupStreamById(streamId); + if (!stream) { + return; + } + bool incomplete = mPriv->incompleteStreams.contains(stream); + if (incomplete) { + mPriv->incompleteStreams.removeOne(stream); + } else { + mPriv->streams.removeOne(stream); + } + + if (isReady(FeatureStreams) && !incomplete) { + emit streamRemoved(stream); + } + + // the stream was added/removed before become ready + if (!isReady(FeatureStreams) && + mPriv->streams.size() == 0 && + mPriv->incompleteStreams.size() == 0) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureStreams, true); + } +} + +void StreamedMediaChannel::onStreamDirectionChanged(uint streamId, + uint streamDirection, uint streamPendingFlags) +{ + debug() << "Received StreamedMedia.StreamDirectionChanged for stream" << + streamId << "with direction changed to" << streamDirection; + + StreamedMediaStreamPtr stream = lookupStreamById(streamId); + if (!stream) { + return; + } + + uint oldDirection = stream->direction(); + uint oldPendingFlags = stream->pendingSend(); + + stream->gotDirection(streamDirection, streamPendingFlags); + + if (oldDirection != streamDirection || + oldPendingFlags != streamPendingFlags) { + emit streamDirectionChanged(stream, + (MediaStreamDirection) streamDirection, + (MediaStreamPendingSend) streamPendingFlags); + } +} + +void StreamedMediaChannel::onStreamStateChanged(uint streamId, + uint streamState) +{ + debug() << "Received StreamedMedia.StreamStateChanged for stream" << + streamId << "with state changed to" << streamState; + + StreamedMediaStreamPtr stream = lookupStreamById(streamId); + if (!stream) { + return; + } + + uint oldState = stream->state(); + + stream->gotStreamState(streamState); + + if (oldState != streamState) { + emit streamStateChanged(stream, (MediaStreamState) streamState); + } +} + +void StreamedMediaChannel::onStreamError(uint streamId, + uint errorCode, const QString &errorMessage) +{ + debug() << "Received StreamedMedia.StreamError for stream" << + streamId << "with error code" << errorCode << + "and message:" << errorMessage; + + StreamedMediaStreamPtr stream = lookupStreamById(streamId); + if (!stream) { + return; + } + + emit streamError(stream, (MediaStreamError) errorCode, errorMessage); +} + +void StreamedMediaChannel::gotLocalHoldState(QDBusPendingCallWatcher *watcher) +{ + QDBusPendingReply<uint, uint> reply = *watcher; + if (reply.isError()) { + warning().nospace() << "StreamedMedia::Hold::GetHoldState()" + " failed with " << reply.error().name() << ": " << + reply.error().message(); + + debug() << "Ignoring error getting hold state and assuming we're not " + "on hold"; + onLocalHoldStateChanged(mPriv->localHoldState, + mPriv->localHoldStateReason); + watcher->deleteLater(); + return; + } + + debug() << "Got reply to StreamedMedia::Hold::GetHoldState()"; + onLocalHoldStateChanged(reply.argumentAt<0>(), reply.argumentAt<1>()); + watcher->deleteLater(); +} + +void StreamedMediaChannel::onLocalHoldStateChanged(uint localHoldState, + uint localHoldStateReason) +{ + bool changed = false; + if (mPriv->localHoldState != static_cast<LocalHoldState>(localHoldState) || + mPriv->localHoldStateReason != static_cast<LocalHoldStateReason>(localHoldStateReason)) { + changed = true; + } + + mPriv->localHoldState = static_cast<LocalHoldState>(localHoldState); + mPriv->localHoldStateReason = static_cast<LocalHoldStateReason>(localHoldStateReason); + + if (!isReady(FeatureLocalHoldState)) { + mPriv->readinessHelper->setIntrospectCompleted(FeatureLocalHoldState, true); + } else { + if (changed) { + emit localHoldStateChanged(mPriv->localHoldState, + mPriv->localHoldStateReason); + } + } +} + +StreamedMediaStreamPtr StreamedMediaChannel::addStream(const MediaStreamInfo &streamInfo) +{ + StreamedMediaStreamPtr stream = StreamedMediaStreamPtr( + new StreamedMediaStream(StreamedMediaChannelPtr(this), streamInfo)); + + mPriv->incompleteStreams.append(stream); + connect(stream->becomeReady(), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onStreamReady(Tp::PendingOperation*))); + return stream; +} + +StreamedMediaStreamPtr StreamedMediaChannel::lookupStreamById(uint streamId) +{ + foreach (const StreamedMediaStreamPtr &stream, mPriv->streams) { + if (stream->id() == streamId) { + return stream; + } + } + + foreach (const StreamedMediaStreamPtr &stream, mPriv->incompleteStreams) { + if (stream->id() == streamId) { + return stream; + } + } + + return StreamedMediaStreamPtr(); +} + +} // Tp diff --git a/TelepathyQt/streamed-media-channel.h b/TelepathyQt/streamed-media-channel.h new file mode 100644 index 00000000..594e3a28 --- /dev/null +++ b/TelepathyQt/streamed-media-channel.h @@ -0,0 +1,228 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_streamed_media_channel_h_HEADER_GUARD_ +#define _TelepathyQt_streamed_media_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Channel> +#include <TelepathyQt/PendingOperation> +#include <TelepathyQt/Object> +#include <TelepathyQt/SharedPtr> +#include <TelepathyQt/Types> + +namespace Tp +{ + +class StreamedMediaChannel; + +typedef QList<StreamedMediaStreamPtr> StreamedMediaStreams; + +class TP_QT_EXPORT PendingStreamedMediaStreams : public PendingOperation +{ + Q_OBJECT + Q_DISABLE_COPY(PendingStreamedMediaStreams) + +public: + ~PendingStreamedMediaStreams(); + + StreamedMediaChannelPtr channel() const; + + StreamedMediaStreams streams() const; + +private Q_SLOTS: + TP_QT_NO_EXPORT void gotStreams(QDBusPendingCallWatcher *op); + + TP_QT_NO_EXPORT void onStreamRemoved(const Tp::StreamedMediaStreamPtr &stream); + TP_QT_NO_EXPORT void onStreamReady(Tp::PendingOperation *op); + +private: + friend class StreamedMediaChannel; + + TP_QT_NO_EXPORT PendingStreamedMediaStreams(const StreamedMediaChannelPtr &channel, + const ContactPtr &contact, + const QList<MediaStreamType> &types); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +class TP_QT_EXPORT StreamedMediaStream : public Object, private ReadyObject +{ + Q_OBJECT + Q_DISABLE_COPY(StreamedMediaStream) + +public: + enum SendingState { + SendingStateNone = 0, + SendingStatePendingSend = 1, + SendingStateSending = 2 + }; + + ~StreamedMediaStream(); + + StreamedMediaChannelPtr channel() const; + + uint id() const; + + ContactPtr contact() const; + + MediaStreamState state() const; + MediaStreamType type() const; + + SendingState localSendingState() const; + SendingState remoteSendingState() const; + + bool sending() const; + bool receiving() const; + + bool localSendingRequested() const; + bool remoteSendingRequested() const; + + MediaStreamDirection direction() const; + MediaStreamPendingSend pendingSend() const; + + PendingOperation *requestSending(bool send); + PendingOperation *requestReceiving(bool receive); + + PendingOperation *requestDirection(MediaStreamDirection direction); + PendingOperation *requestDirection(bool send, bool receive); + + PendingOperation *startDTMFTone(DTMFEvent event); + PendingOperation *stopDTMFTone(); + +Q_SIGNALS: + void localSendingStateChanged(Tp::StreamedMediaStream::SendingState localSendingState); + void remoteSendingStateChanged(Tp::StreamedMediaStream::SendingState remoteSendingState); + +private Q_SLOTS: + TP_QT_NO_EXPORT void gotContact(Tp::PendingOperation *op); + +private: + friend class PendingStreamedMediaStreams; + friend class StreamedMediaChannel; + + static const Feature FeatureCore; + + TP_QT_NO_EXPORT StreamedMediaStream(const StreamedMediaChannelPtr &channel, const MediaStreamInfo &info); + + TP_QT_NO_EXPORT void gotDirection(uint direction, uint pendingSend); + TP_QT_NO_EXPORT void gotStreamState(uint state); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +class TP_QT_EXPORT StreamedMediaChannel : public Channel +{ + Q_OBJECT + Q_DISABLE_COPY(StreamedMediaChannel) + Q_ENUMS(StateChangeReason) + +public: + static const Feature FeatureCore; + static const Feature FeatureStreams; + static const Feature FeatureLocalHoldState; + + enum StateChangeReason { + StateChangeReasonUnknown = 0, + StateChangeReasonUserRequested = 1 + }; + + static StreamedMediaChannelPtr create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties); + + virtual ~StreamedMediaChannel(); + + bool awaitingLocalAnswer() const; + bool awaitingRemoteAnswer() const; + + PendingOperation *acceptCall(); + PendingOperation *hangupCall(); + + StreamedMediaStreams streams() const; + StreamedMediaStreams streamsForType(MediaStreamType type) const; + + PendingStreamedMediaStreams *requestStream(const ContactPtr &contact, MediaStreamType type); + PendingStreamedMediaStreams *requestStreams(const ContactPtr &contact, QList<MediaStreamType> types); + + PendingOperation *removeStream(const StreamedMediaStreamPtr &stream); + PendingOperation *removeStreams(const StreamedMediaStreams &streams); + + bool handlerStreamingRequired() const; + + LocalHoldState localHoldState() const; + LocalHoldStateReason localHoldStateReason() const; + PendingOperation *requestHold(bool hold); + +Q_SIGNALS: + void streamAdded(const Tp::StreamedMediaStreamPtr &stream); + void streamRemoved(const Tp::StreamedMediaStreamPtr &stream); + void streamDirectionChanged(const Tp::StreamedMediaStreamPtr &stream, + Tp::MediaStreamDirection direction, + Tp::MediaStreamPendingSend pendingSend); + void streamStateChanged(const Tp::StreamedMediaStreamPtr &stream, + Tp::MediaStreamState state); + void streamError(const Tp::StreamedMediaStreamPtr &stream, + Tp::MediaStreamError errorCode, + const QString &errorMessage); + + void localHoldStateChanged(Tp::LocalHoldState state, + Tp::LocalHoldStateReason reason); + +protected: + StreamedMediaChannel(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties, + const Feature &coreFeature = StreamedMediaChannel::FeatureCore); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onStreamReady(Tp::PendingOperation *op); + + TP_QT_NO_EXPORT void gotStreams(QDBusPendingCallWatcher *); + TP_QT_NO_EXPORT void onStreamAdded(uint, uint, uint); + TP_QT_NO_EXPORT void onStreamRemoved(uint); + TP_QT_NO_EXPORT void onStreamDirectionChanged(uint, uint, uint); + TP_QT_NO_EXPORT void onStreamStateChanged(uint streamId, uint streamState); + TP_QT_NO_EXPORT void onStreamError(uint, uint, const QString &); + + TP_QT_NO_EXPORT void gotLocalHoldState(QDBusPendingCallWatcher *); + TP_QT_NO_EXPORT void onLocalHoldStateChanged(uint, uint); + +private: + friend class PendingStreamedMediaStreams; + + StreamedMediaStreamPtr addStream(const MediaStreamInfo &streamInfo); + StreamedMediaStreamPtr lookupStreamById(uint streamId); + + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/test-backdoors.cpp b/TelepathyQt/test-backdoors.cpp new file mode 100644 index 00000000..bf4b53a8 --- /dev/null +++ b/TelepathyQt/test-backdoors.cpp @@ -0,0 +1,50 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2009 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 <TelepathyQt/test-backdoors.h> + +#include <TelepathyQt/DBusProxy> + +namespace Tp +{ + +void TestBackdoors::invalidateProxy(DBusProxy *proxy, const QString &reason, const QString &message) +{ + Q_ASSERT(proxy != 0); + Q_ASSERT(proxy->isValid()); + + proxy->invalidate(reason, message); +} + +ConnectionCapabilities TestBackdoors::createConnectionCapabilities( + const RequestableChannelClassSpecList &rccSpecs) +{ + return ConnectionCapabilities(rccSpecs); +} + +ContactCapabilities TestBackdoors::createContactCapabilities( + const RequestableChannelClassSpecList &rccSpecs, bool specificToContact) +{ + return ContactCapabilities(rccSpecs, specificToContact); +} + +} // Tp diff --git a/TelepathyQt/test-backdoors.h b/TelepathyQt/test-backdoors.h new file mode 100644 index 00000000..a1b84887 --- /dev/null +++ b/TelepathyQt/test-backdoors.h @@ -0,0 +1,59 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008-2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2008-2009 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 + */ + +#ifndef _TelepathyQt_test_backdoors_h_HEADER_GUARD_ +#define _TelepathyQt_test_backdoors_h_HEADER_GUARD_ + +#ifdef IN_TP_QT_HEADER +#error "This file is an internal header and should never be included by a public one" +#endif + +#include <TelepathyQt/Global> +#include <TelepathyQt/ConnectionCapabilities> +#include <TelepathyQt/ContactCapabilities> + +#include <QString> + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +namespace Tp +{ + +class DBusProxy; + +// Exported so the tests can use it even if they link dynamically +// The header is not installed though, so this should be considered private API +struct TP_QT_EXPORT TestBackdoors +{ + static void invalidateProxy(DBusProxy *proxy, const QString &reason, const QString &message); + + static ConnectionCapabilities createConnectionCapabilities( + const RequestableChannelClassSpecList &rccSpecs); + static ContactCapabilities createContactCapabilities( + const RequestableChannelClassSpecList &rccSpecs, bool specificToContact); +}; + +} // Tp + +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +#endif diff --git a/TelepathyQt/text-channel.cpp b/TelepathyQt/text-channel.cpp new file mode 100644 index 00000000..83eab19b --- /dev/null +++ b/TelepathyQt/text-channel.cpp @@ -0,0 +1,1277 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 <TelepathyQt/TextChannel> + +#include "TelepathyQt/_gen/text-channel.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/Connection> +#include <TelepathyQt/ConnectionLowlevel> +#include <TelepathyQt/ContactManager> +#include <TelepathyQt/Message> +#include <TelepathyQt/PendingContacts> +#include <TelepathyQt/PendingFailure> +#include <TelepathyQt/PendingReady> +#include <TelepathyQt/ReceivedMessage> +#include <TelepathyQt/ReferencedHandles> + +#include <QDateTime> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT TextChannel::Private +{ + Private(TextChannel *parent); + ~Private(); + + static void introspectMessageQueue(Private *self); + static void introspectMessageCapabilities(Private *self); + static void introspectMessageSentSignal(Private *self); + static void enableChatStateNotifications(Private *self); + + void updateInitialMessages(); + void updateCapabilities(); + + void processMessageQueue(); + void processChatStateQueue(); + + void contactLost(uint handle); + void contactFound(ContactPtr contact); + + // Public object + TextChannel *parent; + + Client::ChannelTypeTextInterface *textInterface; + Client::DBus::PropertiesInterface *properties; + + ReadinessHelper *readinessHelper; + + // FeatureMessageCapabilities and FeatureMessageQueue + QVariantMap props; + bool getAllInFlight; + bool gotProperties; + + // requires FeatureMessageCapabilities + QStringList supportedContentTypes; + MessagePartSupportFlags messagePartSupport; + DeliveryReportingSupportFlags deliveryReportingSupport; + + // FeatureMessageQueue + bool initialMessagesReceived; + struct MessageEvent + { + MessageEvent(const ReceivedMessage &message) + : isMessage(true), message(message), + removed(0) + { } + MessageEvent(uint removed) + : isMessage(false), message(), removed(removed) + { } + + bool isMessage; + ReceivedMessage message; + uint removed; + }; + QList<ReceivedMessage> messages; + QList<MessageEvent *> incompleteMessages; + QHash<QDBusPendingCallWatcher *, UIntList> acknowledgeBatches; + + // FeatureChatState + struct ChatStateEvent + { + ChatStateEvent(uint contactHandle, uint state) + : contactHandle(contactHandle), state(state) + { } + + ContactPtr contact; + uint contactHandle; + uint state; + }; + QList<ChatStateEvent *> chatStateQueue; + QHash<ContactPtr, ChannelChatState> chatStates; + + QSet<uint> awaitingContacts; +}; + +TextChannel::Private::Private(TextChannel *parent) + : parent(parent), + textInterface(parent->interface<Client::ChannelTypeTextInterface>()), + properties(parent->interface<Client::DBus::PropertiesInterface>()), + readinessHelper(parent->readinessHelper()), + getAllInFlight(false), + gotProperties(false), + messagePartSupport(0), + deliveryReportingSupport(0), + initialMessagesReceived(false) +{ + ReadinessHelper::Introspectables introspectables; + + ReadinessHelper::Introspectable introspectableMessageQueue( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << Channel::FeatureCore, // dependsOnFeatures (core) + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectMessageQueue, + this); + introspectables[FeatureMessageQueue] = introspectableMessageQueue; + + ReadinessHelper::Introspectable introspectableMessageCapabilities( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << Channel::FeatureCore, // dependsOnFeatures (core) + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectMessageCapabilities, + this); + introspectables[FeatureMessageCapabilities] = introspectableMessageCapabilities; + + ReadinessHelper::Introspectable introspectableMessageSentSignal( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << Channel::FeatureCore, // dependsOnFeatures (core) + QStringList(), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::introspectMessageSentSignal, + this); + introspectables[FeatureMessageSentSignal] = introspectableMessageSentSignal; + + ReadinessHelper::Introspectable introspectableChatState( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << Channel::FeatureCore, // dependsOnFeatures (core) + QStringList() << QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_CHAT_STATE), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &Private::enableChatStateNotifications, + this); + introspectables[FeatureChatState] = introspectableChatState; + + readinessHelper->addIntrospectables(introspectables); +} + +TextChannel::Private::~Private() +{ + foreach (MessageEvent *e, incompleteMessages) { + delete e; + } + + foreach (ChatStateEvent *e, chatStateQueue) { + delete e; + } +} + +void TextChannel::Private::introspectMessageQueue( + TextChannel::Private *self) +{ + TextChannel *parent = self->parent; + + if (parent->hasMessagesInterface()) { + Client::ChannelInterfaceMessagesInterface *messagesInterface = + parent->interface<Client::ChannelInterfaceMessagesInterface>(); + + // FeatureMessageQueue needs signal connections + Get (but we + // might as well do GetAll and reduce the number of code paths) + parent->connect(messagesInterface, + SIGNAL(MessageReceived(Tp::MessagePartList)), + SLOT(onMessageReceived(Tp::MessagePartList))); + parent->connect(messagesInterface, + SIGNAL(PendingMessagesRemoved(Tp::UIntList)), + SLOT(onPendingMessagesRemoved(Tp::UIntList))); + + if (!self->gotProperties && !self->getAllInFlight) { + self->getAllInFlight = true; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + self->properties->GetAll( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_MESSAGES)), + parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotProperties(QDBusPendingCallWatcher*))); + } else if (self->gotProperties) { + self->updateInitialMessages(); + } + } else { + // FeatureMessageQueue needs signal connections + ListPendingMessages + parent->connect(self->textInterface, + SIGNAL(Received(uint,uint,uint,uint,uint,QString)), + SLOT(onTextReceived(uint,uint,uint,uint,uint,const QString))); + + // we present SendError signals as if they were incoming + // messages, to be consistent with Messages + parent->connect(self->textInterface, + SIGNAL(SendError(uint,uint,uint,QString)), + SLOT(onTextSendError(uint,uint,uint,QString))); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + self->textInterface->ListPendingMessages(false), parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotPendingMessages(QDBusPendingCallWatcher*))); + } +} + +void TextChannel::Private::introspectMessageCapabilities( + TextChannel::Private *self) +{ + TextChannel *parent = self->parent; + + if (parent->hasMessagesInterface()) { + if (!self->gotProperties && !self->getAllInFlight) { + self->getAllInFlight = true; + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + self->properties->GetAll( + QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_MESSAGES)), + parent); + parent->connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(gotProperties(QDBusPendingCallWatcher*))); + } else if (self->gotProperties) { + self->updateCapabilities(); + } + } else { + self->supportedContentTypes = + (QStringList(QLatin1String("text/plain"))); + parent->readinessHelper()->setIntrospectCompleted( + FeatureMessageCapabilities, true); + } +} + +void TextChannel::Private::introspectMessageSentSignal( + TextChannel::Private *self) +{ + TextChannel *parent = self->parent; + + if (parent->hasMessagesInterface()) { + Client::ChannelInterfaceMessagesInterface *messagesInterface = + parent->interface<Client::ChannelInterfaceMessagesInterface>(); + + parent->connect(messagesInterface, + SIGNAL(MessageSent(Tp::MessagePartList,uint,QString)), + SLOT(onMessageSent(Tp::MessagePartList,uint,QString))); + } else { + parent->connect(self->textInterface, + SIGNAL(Sent(uint,uint,QString)), + SLOT(onTextSent(uint,uint,QString))); + } + + self->readinessHelper->setIntrospectCompleted(FeatureMessageSentSignal, true); +} + +void TextChannel::Private::enableChatStateNotifications( + TextChannel::Private *self) +{ + TextChannel *parent = self->parent; + Client::ChannelInterfaceChatStateInterface *chatStateInterface = + parent->interface<Client::ChannelInterfaceChatStateInterface>(); + + parent->connect(chatStateInterface, + SIGNAL(ChatStateChanged(uint,uint)), + SLOT(onChatStateChanged(uint,uint))); + + // FIXME fd.o#24882 - Download contacts' initial chat states + + self->readinessHelper->setIntrospectCompleted(FeatureChatState, true); +} + +void TextChannel::Private::updateInitialMessages() +{ + if (!readinessHelper->requestedFeatures().contains(FeatureMessageQueue) || + readinessHelper->isReady(Features() << FeatureMessageQueue)) { + return; + } + + Q_ASSERT(!initialMessagesReceived); + initialMessagesReceived = true; + + MessagePartListList messages = qdbus_cast<MessagePartListList>( + props[QLatin1String("PendingMessages")]); + if (messages.isEmpty()) { + debug() << "Message queue empty: FeatureMessageQueue is now ready"; + readinessHelper->setIntrospectCompleted(FeatureMessageQueue, true); + } else { + foreach (const MessagePartList &message, messages) { + parent->onMessageReceived(message); + } + } +} + +void TextChannel::Private::updateCapabilities() +{ + if (!readinessHelper->requestedFeatures().contains(FeatureMessageCapabilities) || + readinessHelper->isReady(Features() << FeatureMessageCapabilities)) { + return; + } + + supportedContentTypes = qdbus_cast<QStringList>( + props[QLatin1String("SupportedContentTypes")]); + if (supportedContentTypes.isEmpty()) { + supportedContentTypes << QLatin1String("text/plain"); + } + messagePartSupport = MessagePartSupportFlags(qdbus_cast<uint>( + props[QLatin1String("MessagePartSupportFlags")])); + deliveryReportingSupport = DeliveryReportingSupportFlags( + qdbus_cast<uint>(props[QLatin1String("DeliveryReportingSupport")])); + readinessHelper->setIntrospectCompleted(FeatureMessageCapabilities, true); +} + +void TextChannel::Private::processMessageQueue() +{ + // Proceed as far as we can with the processing of incoming messages + // and message-removal events; message IDs aren't necessarily globally + // unique, so we need to process them in the correct order relative + // to incoming messages + while (!incompleteMessages.isEmpty()) { + const MessageEvent *e = incompleteMessages.first(); + debug() << "MessageEvent:" << reinterpret_cast<const void *>(e); + + if (e->isMessage) { + if (e->message.senderHandle() != 0 && + !e->message.sender()) { + // the message doesn't have a sender Contact, but needs one. + // We'll have to stop processing here, and come back to it + // when we have more Contact objects + break; + } + + // if we reach here, the message is ready + debug() << "Message is usable, copying to main queue"; + messages << e->message; + emit parent->messageReceived(e->message); + } else { + // forget about the message(s) with ID e->removed (there should be + // at most one under normal circumstances) + int i = 0; + while (i < messages.size()) { + if (messages.at(i).pendingId() == e->removed) { + emit parent->pendingMessageRemoved(messages.at(i)); + messages.removeAt(i); + } else { + i++; + } + } + } + + debug() << "Dropping first event"; + delete incompleteMessages.takeFirst(); + } + + if (incompleteMessages.isEmpty()) { + if (readinessHelper->requestedFeatures().contains(FeatureMessageQueue) && + !readinessHelper->isReady(Features() << FeatureMessageQueue)) { + debug() << "incompleteMessages empty for the first time: " + "FeatureMessageQueue is now ready"; + readinessHelper->setIntrospectCompleted(FeatureMessageQueue, true); + } + return; + } + + // What Contact objects do we need in order to proceed, ignoring those + // for which we've already sent a request? + HandleIdentifierMap contactsRequired; + foreach (const MessageEvent *e, incompleteMessages) { + if (e->isMessage) { + uint handle = e->message.senderHandle(); + if (handle != 0 && !e->message.sender() + && !awaitingContacts.contains(handle)) { + contactsRequired.insert(handle, e->message.senderId()); + } + } + } + + if (contactsRequired.isEmpty()) { + return; + } + + ConnectionPtr conn = parent->connection(); + conn->lowlevel()->injectContactIds(contactsRequired); + + parent->connect(conn->contactManager()->contactsForHandles( + contactsRequired.keys()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onContactsFinished(Tp::PendingOperation*))); + + awaitingContacts |= contactsRequired.keys().toSet(); +} + +void TextChannel::Private::processChatStateQueue() +{ + while (!chatStateQueue.isEmpty()) { + const ChatStateEvent *e = chatStateQueue.first(); + debug() << "ChatStateEvent:" << reinterpret_cast<const void *>(e); + + if (e->contact.isNull()) { + // the chat state Contact object wasn't retrieved yet, but needs + // one. We'll have to stop processing here, and come back to it + // when we have more Contact objects + break; + } + + chatStates.insert(e->contact, (ChannelChatState) e->state); + + // if we reach here, the Contact object is ready + emit parent->chatStateChanged(e->contact, (ChannelChatState) e->state); + + debug() << "Dropping first event"; + delete chatStateQueue.takeFirst(); + } + + // What Contact objects do we need in order to proceed, ignoring those + // for which we've already sent a request? + QSet<uint> contactsRequired; + foreach (const ChatStateEvent *e, chatStateQueue) { + if (!e->contact && + !awaitingContacts.contains(e->contactHandle)) { + contactsRequired << e->contactHandle; + } + } + + if (contactsRequired.isEmpty()) { + return; + } + + // TODO: pass id hints to ContactManager if we ever gain support to retrieve contact ids + // from ChatState. + parent->connect(parent->connection()->contactManager()->contactsForHandles( + contactsRequired.toList()), + SIGNAL(finished(Tp::PendingOperation*)), + SLOT(onContactsFinished(Tp::PendingOperation*))); + + awaitingContacts |= contactsRequired; +} + +void TextChannel::Private::contactLost(uint handle) +{ + // we're not going to get a Contact object for this handle, so mark the + // messages from that handle as "unknown sender" + foreach (MessageEvent *e, incompleteMessages) { + if (e->isMessage && e->message.senderHandle() == handle + && !e->message.sender()) { + e->message.clearSenderHandle(); + } + } + + // there is no point in sending chat state notifications for unknown + // contacts, removing chat state events from queue that refer to this handle + foreach (ChatStateEvent *e, chatStateQueue) { + if (e->contactHandle == handle) { + chatStateQueue.removeOne(e); + delete e; + } + } +} + +void TextChannel::Private::contactFound(ContactPtr contact) +{ + uint handle = contact->handle().at(0); + + foreach (MessageEvent *e, incompleteMessages) { + if (e->isMessage && e->message.senderHandle() == handle + && !e->message.sender()) { + e->message.setSender(contact); + } + } + + foreach (ChatStateEvent *e, chatStateQueue) { + if (e->contactHandle == handle) { + e->contact = contact; + } + } +} + +/** + * \class TextChannel + * \ingroup clientchannel + * \headerfile TelepathyQt/text-channel.h <TelepathyQt/TextChannel> + * + * \brief The TextChannel class represents a Telepathy channel of type Text. + * + * For more details, please refer to \telepathy_spec. + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Feature representing the core that needs to become ready to make the + * TextChannel object usable. + * + * This is currently the same as Channel::FeatureCore, but may change to include more. + * + * When calling isReady(), becomeReady(), this feature is implicitly added + * to the requested features. + */ +const Feature TextChannel::FeatureCore = Feature(QLatin1String(Channel::staticMetaObject.className()), 0, true); + +/** + * Feature used in order to access the message queue info. + * + * See message queue methods' documentation for more details. + * + * \sa messageQueue(), messageReceived(), pendingMessageRemoved(), forget(), acknowledge() + */ +const Feature TextChannel::FeatureMessageQueue = Feature(QLatin1String(TextChannel::staticMetaObject.className()), 0); + +/** + * Feature used in order to access message capabilities info. + * + * See message capabilities methods' documentation for more details. + * + * \sa supportedContentTypes(), messagePartSupport(), deliveryReportingSupport() + */ +const Feature TextChannel::FeatureMessageCapabilities = Feature(QLatin1String(TextChannel::staticMetaObject.className()), 1); + +/** + * Feature used in order to receive notification when a message is sent. + * + * \sa messageSent() + */ +const Feature TextChannel::FeatureMessageSentSignal = Feature(QLatin1String(TextChannel::staticMetaObject.className()), 2); + +/** + * Feature used in order to keep track of chat state changes. + * + * See chat state methods' documentation for more details. + * + * \sa chatState(), chatStateChanged() + */ +const Feature TextChannel::FeatureChatState = Feature(QLatin1String(TextChannel::staticMetaObject.className()), 3); + +/** + * \fn void TextChannel::messageSent(const Tp::Message &message, + * Tp::MessageSendingFlags flags, + * const QString &sentMessageToken) + * + * Emitted when a message is sent, if the TextChannel::FeatureMessageSentSignal + * has been enabled. + * + * This signal is emitted regardless of whether the message is sent by this + * client, or another client using the same channel via D-Bus. + * + * \param message A message. This may differ slightly from what the client + * requested to send, for instance if it has been altered due + * to limitations of the instant messaging protocol used. + * \param flags #MessageSendingFlags that were in effect when the message was + * sent. Clients can use these in conjunction with + * deliveryReportingSupport() to determine whether delivery + * reporting can be expected. + * \param sentMessageToken Either an empty QString, or an opaque token used + * to match the message to any delivery reports. + */ + +/** + * \fn void TextChannel::messageReceived(const Tp::ReceivedMessage &message) + * + * Emitted when a message is added to messageQueue(), if the + * TextChannel::FeatureMessageQueue Feature has been enabled. + * + * This occurs slightly later than the message being received over D-Bus; + * see messageQueue() for details. + * + * \param message The message received. + * \sa messageQueue(), acknowledge(), forget() + */ + +/** + * \fn void TextChannel::pendingMessageRemoved( + * const Tp::ReceivedMessage &message) + * + * Emitted when a message is removed from messageQueue(), if the + * TextChannel::FeatureMessageQueue Feature has been enabled. See messageQueue() for the + * circumstances in which this happens. + * + * \param message The message removed. + * \sa messageQueue(), acknowledge(), forget() + */ + +/** + * \fn void TextChannel::chatStateChanged(const Tp::ContactPtr &contact, + * ChannelChatState state) + * + * Emitted when the state of a member of the channel has changed, if the + * TextChannel::FeatureChatState feature has been enabled. + * + * Local state changes are also emitted here. + * + * \param contact The contact whose chat state changed. + * \param state The new chat state for \a contact. + * \sa chatState() + */ + +/** + * Create a new TextChannel 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 TextChannelPtr object pointing to the newly created + * TextChannel object. + */ +TextChannelPtr TextChannel::create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties) +{ + return TextChannelPtr(new TextChannel(connection, objectPath, + immutableProperties, TextChannel::FeatureCore)); +} + +/** + * Construct a new TextChannel 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, if any. The corresponding introspectable should + * depend on TextChannel::FeatureCore. + */ +TextChannel::TextChannel(const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : Channel(connection, objectPath, immutableProperties, coreFeature), + mPriv(new Private(this)) +{ +} + +/** + * Class destructor. + */ +TextChannel::~TextChannel() +{ + delete mPriv; +} + +/** + * Return whether this channel supports the Messages interface. + * + * If the interface is not supported, some advanced functionality will be unavailable. + * + * This method requires TextChannel::FeatureCore to be ready. + * + * \return \c true if the Messages interface is supported, \c false otherwise. + */ +bool TextChannel::hasMessagesInterface() const +{ + return interfaces().contains(QLatin1String( + TELEPATHY_INTERFACE_CHANNEL_INTERFACE_MESSAGES)); +} + +/** + * Return whether this channel supports the ChatState interface. + * + * If the interface is not supported, requestChatState() will fail and all contacts' chat states + * will appear to be #ChannelChatStateInactive. + * + * This method requires TextChannel::FeatureCore to be ready. + * + * \return \c true if the ChatState interface is supported, \c false otherwise. + * \sa requestChatState(), chatStateChanged() + */ +bool TextChannel::hasChatStateInterface() const +{ + return interfaces().contains(QLatin1String( + TELEPATHY_INTERFACE_CHANNEL_INTERFACE_CHAT_STATE)); +} + +/** + * Return whether contacts can be invited into this channel using + * inviteContacts() (which is equivalent to Channel::groupAddContacts()). + * + * Whether this is the case depends on the underlying protocol, the type of channel, + * and the user's privileges (in some chatrooms, only a privileged user + * can invite other contacts). + * + * This is an alias for Channel::groupCanAddContacts(), to indicate its meaning more + * clearly for Text channels. + * + * This method requires Channel::FeatureCore to be ready. + * + * \return \c true if contacts can be invited, \c false otherwise. + * \sa inviteContacts(), Channel::groupCanAddContacts(), Channel::groupAddContacts() + */ +bool TextChannel::canInviteContacts() const +{ + return groupCanAddContacts(); +} + +/* <!--x--> in the block below is used to escape the star-slash sequence */ +/** + * Return a list of supported MIME content types for messages on this channel. + * + * For a simple text channel this will be a list containing one item, + * "text/plain". + * + * This list may contain the special value "*<!--x-->/<!--x-->*", which + * indicates that any content type is supported. + * + * This method requires TextChannel::FeatureMessageCapabilities to be ready. + * + * \return The list of MIME content types. + */ +QStringList TextChannel::supportedContentTypes() const +{ + return mPriv->supportedContentTypes; +} + +/** + * Return a set of flags indicating support for multi-part messages on this + * channel. + * + * This is zero on simple text channels, or greater than zero if + * there is partial or full support for multi-part messages. + * + * This method requires TextChannel::FeatureMessageCapabilities to be ready. + * + * \return The flags as #MessagePartSupportFlags. + */ +MessagePartSupportFlags TextChannel::messagePartSupport() const +{ + return mPriv->messagePartSupport; +} + +/** + * Return a set of flags indicating support for delivery reporting on this + * channel. + * + * This is zero if there are no particular guarantees, or greater + * than zero if delivery reports can be expected under certain circumstances. + * + * This method requires TextChannel::FeatureMessageCapabilities to be ready. + * + * \return The flags as #DeliveryReportingSupportFlags. + */ +DeliveryReportingSupportFlags TextChannel::deliveryReportingSupport() const +{ + return mPriv->deliveryReportingSupport; +} + +/** + * Return a list of messages received in this channel. + * + * Messages are added to this list when they are received from the instant + * messaging service; the messageReceived() signal is emitted. + * + * There is a small delay between the message being received over D-Bus and + * becoming available to users of this C++ API, since a small amount of + * additional information needs to be fetched. However, the relative ordering + * of all the messages in a channel is preserved. + * + * Messages are removed from this list when they are acknowledged with the + * acknowledge() or forget() methods. On channels where hasMessagesInterface() + * returns \c true, they will also be removed when acknowledged by a different + * client. In either case, the pendingMessageRemoved() signal is emitted. + * + * This method requires TextChannel::FeatureMessageQueue to be ready. + * + * \return A list of ReceivedMessage objects. + * \sa messageReceived() + */ +QList<ReceivedMessage> TextChannel::messageQueue() const +{ + return mPriv->messages; +} + +/** + * Return the current chat state for \a contact. + * + * If hasChatStateInterface() returns \c false, this method will always return + * #ChannelChatStateInactive. + * + * This method requires TextChannel::FeatureChatState to be ready. + * + * \return The contact chat state as #ChannelChatState. + */ +ChannelChatState TextChannel::chatState(const ContactPtr &contact) const +{ + if (!isReady(FeatureChatState)) { + warning() << "TextChannel::chatState() used with " + "FeatureChatState not ready"; + return ChannelChatStateInactive; + } + + if (mPriv->chatStates.contains(contact)) { + return mPriv->chatStates.value(contact); + } + return ChannelChatStateInactive; +} + +void TextChannel::onAcknowledgePendingMessagesReply( + QDBusPendingCallWatcher *watcher) +{ + UIntList ids = mPriv->acknowledgeBatches.value(watcher); + QDBusPendingReply<> reply = *watcher; + + if (reply.isError()) { + // One of the IDs was bad, and we can't know which one. Recover by + // doing as much as possible, and hope for the best... + debug() << "Recovering from AcknowledgePendingMessages failure for: " + << ids; + foreach (uint id, ids) { + mPriv->textInterface->AcknowledgePendingMessages(UIntList() << id); + } + } + + mPriv->acknowledgeBatches.remove(watcher); + watcher->deleteLater(); +} + +/** + * Acknowledge that received messages have been displayed to the user. + * + * Note that this method should only be called by the main handler of a channel, usually + * meaning the user interface process that displays the channel to the user + * (when a channel dispatcher is used, the handler must acknowledge messages, + * and other approvers or observers must not acknowledge messages). + * + * Processes other than the main handler of a channel can free memory used + * by the library by calling forget() instead. + * + * This method requires TextChannel::FeatureMessageQueue to be ready. + * + * \param messages A list of received messages that have now been displayed. + * \sa forget(), messageQueue(), messageReceived(), pendingMessageRemoved() + */ +void TextChannel::acknowledge(const QList<ReceivedMessage> &messages) +{ + UIntList ids; + + foreach (const ReceivedMessage &m, messages) { + if (m.isFromChannel(TextChannelPtr(this))) { + ids << m.pendingId(); + } else { + warning() << "message did not come from this channel, ignoring"; + } + } + + if (ids.isEmpty()) { + return; + } + + // we're going to acknowledge these messages (or as many as possible, if + // we lose a race with another acknowledging process), so let's remove + // them from the list immediately + forget(messages); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher( + mPriv->textInterface->AcknowledgePendingMessages(ids), + this); + connect(watcher, + SIGNAL(finished(QDBusPendingCallWatcher*)), + SLOT(onAcknowledgePendingMessagesReply(QDBusPendingCallWatcher*))); + mPriv->acknowledgeBatches[watcher] = ids; +} + +/** + * Remove messages from the message queue without acknowledging them. + * + * Note that this method frees memory used by the library, but + * does not free the corresponding memory in the CM process. + * It should be used by clients that are not the main handler for a channel; + * the main handler for a channel should use acknowledge() instead. + * + * This method requires TextChannel::FeatureMessageQueue to be ready. + * + * \param messages A list of received messages that have now been processed. + * \sa acknowledge(), messageQueue(), messageReceived(), pendingMessageRemoved() + */ +void TextChannel::forget(const QList<ReceivedMessage> &messages) +{ + foreach (const ReceivedMessage &m, messages) { + if (!m.isFromChannel(TextChannelPtr(this))) { + warning() << "message did not come from this channel, ignoring"; + } else if (mPriv->messages.removeOne(m)) { + emit pendingMessageRemoved(m); + } + } +} + +/** + * Request that a message be sent on this channel. + * + * When the message has been submitted for delivery, + * this method will return and the messageSent() signal will be emitted. + * + * If the message cannot be submitted for delivery, the returned pending operation will fail and no + * signal is emitted. + * + * This method requires TextChannel::FeatureCore to be ready. + * + * \param text The message body. + * \param type The message type. + * \param flags Flags affecting how the message is sent. + * Note that the channel may ignore some or all flags, depending on + * deliveryReportingSupport(); the flags that were handled by the CM are provided in + * messageSent(). + * \return A PendingOperation which will emit PendingOperation::finished + * when the message has been submitted for delivery. + * \sa messageSent() + */ +PendingSendMessage *TextChannel::send(const QString &text, + ChannelTextMessageType type, MessageSendingFlags flags) +{ + Message m(type, text); + PendingSendMessage *op = new PendingSendMessage(TextChannelPtr(this), m); + + if (hasMessagesInterface()) { + Client::ChannelInterfaceMessagesInterface *messagesInterface = + interface<Client::ChannelInterfaceMessagesInterface>(); + + connect(new QDBusPendingCallWatcher( + messagesInterface->SendMessage(m.parts(), + (uint) flags)), + SIGNAL(finished(QDBusPendingCallWatcher*)), + op, + SLOT(onMessageSent(QDBusPendingCallWatcher*))); + } else { + connect(new QDBusPendingCallWatcher(mPriv->textInterface->Send(type, text)), + SIGNAL(finished(QDBusPendingCallWatcher*)), + op, + SLOT(onTextSent(QDBusPendingCallWatcher*))); + } + return op; +} + +/** + * Request that a message be sent on this channel. + * + * When the message has been submitted for delivery, + * this method will return and the messageSent() signal will be emitted. + * + * If the message cannot be submitted for delivery, the returned pending operation will fail and no + * signal is emitted. + * + * This method requires TextChannel::FeatureCore to be ready. + * + * \param part The message parts. + * \param flags Flags affecting how the message is sent. + * Note that the channel may ignore some or all flags, depending on + * deliveryReportingSupport(); the flags that were handled by the CM are provided in + * messageSent(). + * \return A PendingOperation which will emit PendingOperation::finished + * when the message has been submitted for delivery. + * \sa messageSent() + */ +PendingSendMessage *TextChannel::send(const MessagePartList &parts, + MessageSendingFlags flags) +{ + Message m(parts); + PendingSendMessage *op = new PendingSendMessage(TextChannelPtr(this), m); + + if (hasMessagesInterface()) { + Client::ChannelInterfaceMessagesInterface *messagesInterface = + interface<Client::ChannelInterfaceMessagesInterface>(); + + connect(new QDBusPendingCallWatcher( + messagesInterface->SendMessage(m.parts(), + (uint) flags)), + SIGNAL(finished(QDBusPendingCallWatcher*)), + op, + SLOT(onMessageSent(QDBusPendingCallWatcher*))); + } else { + connect(new QDBusPendingCallWatcher(mPriv->textInterface->Send( + m.messageType(), m.text())), + SIGNAL(finished(QDBusPendingCallWatcher*)), + op, + SLOT(onTextSent(QDBusPendingCallWatcher*))); + } + return op; +} + +/** + * Set the local chat state and notify other members of the channel that it has + * changed. + * + * Note that only the primary handler of the channel should set its chat + * state. + * + * This method requires TextChannel::FeatureCore to be ready. + * + * \param state The new state. + * \sa chatStateChanged() + */ +PendingOperation *TextChannel::requestChatState(ChannelChatState state) +{ + if (!interfaces().contains(QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_CHAT_STATE))) { + warning() << "TextChannel::requestChatState() used with no chat " + "state interface"; + return new PendingFailure(QLatin1String(TELEPATHY_ERROR_NOT_IMPLEMENTED), + QLatin1String("TextChannel does not support chat state interface"), + TextChannelPtr(this)); + } + + Client::ChannelInterfaceChatStateInterface *chatStateInterface = + interface<Client::ChannelInterfaceChatStateInterface>(); + return new PendingVoid(chatStateInterface->SetChatState( + (uint) state), TextChannelPtr(this)); +} + +void TextChannel::onMessageSent(const MessagePartList &parts, + uint flags, + const QString &sentMessageToken) +{ + emit messageSent(Message(parts), MessageSendingFlag(flags), + sentMessageToken); +} + +void TextChannel::onContactsFinished(PendingOperation *op) +{ + PendingContacts *pc = qobject_cast<PendingContacts *>(op); + UIntList failed; + + Q_ASSERT(pc->isForHandles()); + + foreach (uint handle, pc->handles()) { + mPriv->awaitingContacts -= handle; + } + + if (pc->isError()) { + warning().nospace() << "Gathering contacts failed: " + << pc->errorName() << ": " << pc->errorMessage(); + foreach (uint handle, pc->handles()) { + mPriv->contactLost(handle); + } + } else { + foreach (const ContactPtr &contact, pc->contacts()) { + mPriv->contactFound(contact); + } + foreach (uint handle, pc->invalidHandles()) { + mPriv->contactLost(handle); + } + } + + // all contacts for messages and chat state events we were asking about + // should now be ready + mPriv->processMessageQueue(); + mPriv->processChatStateQueue(); +} + +void TextChannel::onMessageReceived(const MessagePartList &parts) +{ + if (!mPriv->initialMessagesReceived) { + return; + } + + mPriv->incompleteMessages << new Private::MessageEvent( + ReceivedMessage(parts, TextChannelPtr(this))); + mPriv->processMessageQueue(); +} + +void TextChannel::onPendingMessagesRemoved(const UIntList &ids) +{ + if (!mPriv->initialMessagesReceived) { + return; + } + foreach (uint id, ids) { + mPriv->incompleteMessages << new Private::MessageEvent(id); + } + mPriv->processMessageQueue(); +} + +void TextChannel::onTextSent(uint timestamp, uint type, const QString &text) +{ + emit messageSent(Message(timestamp, type, text), 0, + QLatin1String("")); +} + +void TextChannel::onTextReceived(uint id, uint timestamp, uint sender, + uint type, uint flags, const QString &text) +{ + if (!mPriv->initialMessagesReceived) { + return; + } + + MessagePart header; + + if (timestamp == 0) { + timestamp = QDateTime::currentDateTime().toTime_t(); + } + header.insert(QLatin1String("message-received"), + QDBusVariant(static_cast<qlonglong>(timestamp))); + + header.insert(QLatin1String("pending-message-id"), QDBusVariant(id)); + header.insert(QLatin1String("message-sender"), QDBusVariant(sender)); + header.insert(QLatin1String("message-type"), QDBusVariant(type)); + + if (flags & ChannelTextMessageFlagScrollback) { + header.insert(QLatin1String("scrollback"), QDBusVariant(true)); + } + if (flags & ChannelTextMessageFlagRescued) { + header.insert(QLatin1String("rescued"), QDBusVariant(true)); + } + + MessagePart body; + + body.insert(QLatin1String("content-type"), + QDBusVariant(QLatin1String("text/plain"))); + body.insert(QLatin1String("content"), QDBusVariant(text)); + + if (flags & ChannelTextMessageFlagTruncated) { + header.insert(QLatin1String("truncated"), QDBusVariant(true)); + } + + MessagePartList parts; + parts << header; + parts << body; + + ReceivedMessage m(parts, TextChannelPtr(this)); + + if (flags & ChannelTextMessageFlagNonTextContent) { + // set the "you are not expected to understand this" flag + m.setForceNonText(); + } + + mPriv->incompleteMessages << new Private::MessageEvent(m); + mPriv->processMessageQueue(); +} + +void TextChannel::onTextSendError(uint error, uint timestamp, uint type, + const QString &text) +{ + if (!mPriv->initialMessagesReceived) { + return; + } + + MessagePart header; + + header.insert(QLatin1String("message-received"), + QDBusVariant(static_cast<qlonglong>( + QDateTime::currentDateTime().toTime_t()))); + header.insert(QLatin1String("message-type"), + QDBusVariant(static_cast<uint>( + ChannelTextMessageTypeDeliveryReport))); + + // we can't tell whether it's a temporary or permanent failure here, + // so guess based on the delivery-error + uint deliveryStatus; + switch (error) { + case ChannelTextSendErrorOffline: + case ChannelTextSendErrorPermissionDenied: + deliveryStatus = DeliveryStatusTemporarilyFailed; + break; + + case ChannelTextSendErrorInvalidContact: + case ChannelTextSendErrorTooLong: + case ChannelTextSendErrorNotImplemented: + deliveryStatus = DeliveryStatusPermanentlyFailed; + break; + + case ChannelTextSendErrorUnknown: + default: + deliveryStatus = DeliveryStatusTemporarilyFailed; + break; + } + + header.insert(QLatin1String("delivery-status"), + QDBusVariant(deliveryStatus)); + header.insert(QLatin1String("delivery-error"), QDBusVariant(error)); + + MessagePart echoHeader; + echoHeader.insert(QLatin1String("message-sent"), + QDBusVariant(timestamp)); + echoHeader.insert(QLatin1String("message-type"), + QDBusVariant(type)); + + MessagePart echoBody; + echoBody.insert(QLatin1String("content-type"), + QDBusVariant(QLatin1String("text/plain"))); + echoBody.insert(QLatin1String("content"), QDBusVariant(text)); + + MessagePartList echo; + echo << echoHeader; + echo << echoBody; + header.insert(QLatin1String("delivery-echo"), + QDBusVariant(QVariant::fromValue(echo))); + + MessagePartList parts; + parts << header; +} + +void TextChannel::gotProperties(QDBusPendingCallWatcher *watcher) +{ + Q_ASSERT(mPriv->getAllInFlight); + mPriv->getAllInFlight = false; + mPriv->gotProperties = true; + + QDBusPendingReply<QVariantMap> reply = *watcher; + if (reply.isError()) { + warning().nospace() << "Properties::GetAll(Channel.Interface.Messages)" + " failed with " << reply.error().name() << ": " << + reply.error().message(); + + ReadinessHelper *readinessHelper = mPriv->readinessHelper; + if (readinessHelper->requestedFeatures().contains(FeatureMessageQueue) && + !readinessHelper->isReady(Features() << FeatureMessageQueue)) { + readinessHelper->setIntrospectCompleted(FeatureMessageQueue, false, reply.error()); + } + + if (readinessHelper->requestedFeatures().contains(FeatureMessageCapabilities) && + !readinessHelper->isReady(Features() << FeatureMessageCapabilities)) { + readinessHelper->setIntrospectCompleted(FeatureMessageCapabilities, false, reply.error()); + } + return; + } + + debug() << "Properties::GetAll(Channel.Interface.Messages) returned"; + mPriv->props = reply.value(); + + mPriv->updateInitialMessages(); + mPriv->updateCapabilities(); + + watcher->deleteLater(); +} + +void TextChannel::gotPendingMessages(QDBusPendingCallWatcher *watcher) +{ + Q_ASSERT(!mPriv->initialMessagesReceived); + mPriv->initialMessagesReceived = true; + + QDBusPendingReply<PendingTextMessageList> reply = *watcher; + if (reply.isError()) { + warning().nospace() << "Properties::GetAll(Channel.Interface.Messages)" + " failed with " << reply.error().name() << ": " << + reply.error().message(); + + // TODO should we fail here? + mPriv->readinessHelper->setIntrospectCompleted(FeatureMessageQueue, false, reply.error()); + return; + } + + debug() << "Text::ListPendingMessages returned"; + PendingTextMessageList list = reply.value(); + + if (!list.isEmpty()) { + foreach (const PendingTextMessage &message, list) { + onTextReceived(message.identifier, message.unixTimestamp, + message.sender, message.messageType, message.flags, + message.text); + } + // processMessageQueue sets FeatureMessageQueue ready when the queue is empty for the first + // time + } else { + mPriv->readinessHelper->setIntrospectCompleted(FeatureMessageQueue, true); + } + + watcher->deleteLater(); +} + +void TextChannel::onChatStateChanged(uint contactHandle, uint state) +{ + mPriv->chatStateQueue.append(new Private::ChatStateEvent( + contactHandle, state)); + mPriv->processChatStateQueue(); +} + +} // Tp diff --git a/TelepathyQt/text-channel.h b/TelepathyQt/text-channel.h new file mode 100644 index 00000000..4cf4241e --- /dev/null +++ b/TelepathyQt/text-channel.h @@ -0,0 +1,139 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2009 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2009 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 + */ + +#ifndef _TelepathyQt_text_channel_h_HEADER_GUARD_ +#define _TelepathyQt_text_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Channel> +#include <TelepathyQt/PendingOperation> +#include <TelepathyQt/PendingSendMessage> + +namespace Tp +{ + +class Message; +class ReceivedMessage; + +class TP_QT_EXPORT TextChannel : public Channel +{ + Q_OBJECT + Q_DISABLE_COPY(TextChannel) + +public: + static const Feature FeatureCore; + static const Feature FeatureMessageQueue; + static const Feature FeatureMessageCapabilities; + static const Feature FeatureMessageSentSignal; + static const Feature FeatureChatState; + + static TextChannelPtr create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties); + + virtual ~TextChannel(); + + bool hasMessagesInterface() const; + bool hasChatStateInterface() const; + bool canInviteContacts() const; + + // requires FeatureMessageCapabilities + QStringList supportedContentTypes() const; + MessagePartSupportFlags messagePartSupport() const; + DeliveryReportingSupportFlags deliveryReportingSupport() const; + + // requires FeatureMessageQueue + QList<ReceivedMessage> messageQueue() const; + + // requires FeatureChatState + ChannelChatState chatState(const ContactPtr &contact) const; + +public Q_SLOTS: + void acknowledge(const QList<ReceivedMessage> &messages); + + void forget(const QList<ReceivedMessage> &messages); + + PendingSendMessage *send(const QString &text, + ChannelTextMessageType type = ChannelTextMessageTypeNormal, + MessageSendingFlags flags = 0); + + PendingSendMessage *send(const MessagePartList &parts, + MessageSendingFlags flags = 0); + + inline PendingOperation *inviteContacts( + const QList<ContactPtr> &contacts, + const QString &message = QString()) + { + return groupAddContacts(contacts, message); + } + + PendingOperation *requestChatState(ChannelChatState state); + +Q_SIGNALS: + // FeatureMessageSentSignal + void messageSent(const Tp::Message &message, + Tp::MessageSendingFlags flags, + const QString &sentMessageToken); + + // FeatureMessageQueue + void messageReceived(const Tp::ReceivedMessage &message); + void pendingMessageRemoved( + const Tp::ReceivedMessage &message); + + // FeatureChatState + void chatStateChanged(const Tp::ContactPtr &contact, + Tp::ChannelChatState state); + +protected: + TextChannel(const ConnectionPtr &connection, const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature = TextChannel::FeatureCore); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onContactsFinished(Tp::PendingOperation *); + TP_QT_NO_EXPORT void onAcknowledgePendingMessagesReply(QDBusPendingCallWatcher *); + + TP_QT_NO_EXPORT void onMessageSent(const Tp::MessagePartList &, uint, + const QString &); + TP_QT_NO_EXPORT void onMessageReceived(const Tp::MessagePartList &); + TP_QT_NO_EXPORT void onPendingMessagesRemoved(const Tp::UIntList &); + + TP_QT_NO_EXPORT void onTextSent(uint, uint, const QString &); + TP_QT_NO_EXPORT void onTextReceived(uint, uint, uint, uint, uint, const QString &); + TP_QT_NO_EXPORT void onTextSendError(uint, uint, uint, const QString &); + + TP_QT_NO_EXPORT void gotProperties(QDBusPendingCallWatcher *); + TP_QT_NO_EXPORT void gotPendingMessages(QDBusPendingCallWatcher *); + + TP_QT_NO_EXPORT void onChatStateChanged(uint, uint); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} // Tp + +#endif diff --git a/TelepathyQt/tls-certificate.cpp b/TelepathyQt/tls-certificate.cpp new file mode 100644 index 00000000..8ba4f1b5 --- /dev/null +++ b/TelepathyQt/tls-certificate.cpp @@ -0,0 +1,26 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#define IN_TP_QT_HEADER +#include <TelepathyQt/tls-certificate.h> + +#include "TelepathyQt/_gen/cli-tls-certificate-body.hpp" +#include "TelepathyQt/_gen/cli-tls-certificate.moc.hpp" diff --git a/TelepathyQt/tls-certificate.h b/TelepathyQt/tls-certificate.h new file mode 100644 index 00000000..5b2c1c78 --- /dev/null +++ b/TelepathyQt/tls-certificate.h @@ -0,0 +1,31 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_tls_certificate_h_HEADER_GUARD_ +#define _TelepathyQt_tls_certificate_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/_gen/cli-tls-certificate.h> + +#endif diff --git a/TelepathyQt/tls-certificate.xml b/TelepathyQt/tls-certificate.xml new file mode 100644 index 00000000..0ba3d2d0 --- /dev/null +++ b/TelepathyQt/tls-certificate.xml @@ -0,0 +1,9 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>TLSCertificate</tp:title> + +<xi:include href="../spec/Authentication_TLS_Certificate.xml"/> + +</tp:spec> diff --git a/TelepathyQt/tube-channel.cpp b/TelepathyQt/tube-channel.cpp new file mode 100644 index 00000000..0e53b770 --- /dev/null +++ b/TelepathyQt/tube-channel.cpp @@ -0,0 +1,281 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/TubeChannel> + +#include "TelepathyQt/_gen/tube-channel.moc.hpp" + +#include "TelepathyQt/debug-internal.h" + +#include <TelepathyQt/PendingVariantMap> + +namespace Tp +{ + +struct TP_QT_NO_EXPORT TubeChannel::Private +{ + Private(TubeChannel *parent); + + static void introspectTube(TubeChannel::Private *self); + + void extractTubeProperties(const QVariantMap &props); + + // Public object + TubeChannel *parent; + + ReadinessHelper *readinessHelper; + + // Introspection + TubeChannelState state; + QVariantMap parameters; +}; + +TubeChannel::Private::Private(TubeChannel *parent) + : parent(parent), + readinessHelper(parent->readinessHelper()), + state((TubeChannelState) -1) +{ + ReadinessHelper::Introspectables introspectables; + + ReadinessHelper::Introspectable introspectableTube( + QSet<uint>() << 0, // makesSenseForStatuses + Features() << Channel::FeatureCore, // dependsOnFeatures (core) + QStringList() << QLatin1String(TELEPATHY_INTERFACE_CHANNEL_INTERFACE_TUBE), // dependsOnInterfaces + (ReadinessHelper::IntrospectFunc) &TubeChannel::Private::introspectTube, + this); + introspectables[TubeChannel::FeatureCore] = introspectableTube; + + readinessHelper->addIntrospectables(introspectables); +} + +void TubeChannel::Private::introspectTube(TubeChannel::Private *self) +{ + TubeChannel *parent = self->parent; + + debug() << "Introspecting tube properties"; + Client::ChannelInterfaceTubeInterface *tubeInterface = + parent->interface<Client::ChannelInterfaceTubeInterface>(); + + parent->connect(tubeInterface, + SIGNAL(TubeChannelStateChanged(uint)), + SLOT(onTubeChannelStateChanged(uint))); + + PendingVariantMap *pvm = tubeInterface->requestAllProperties(); + parent->connect(pvm, + SIGNAL(finished(Tp::PendingOperation *)), + SLOT(gotTubeProperties(Tp::PendingOperation *))); +} + +void TubeChannel::Private::extractTubeProperties(const QVariantMap &props) +{ + state = (Tp::TubeChannelState) qdbus_cast<uint>(props[QLatin1String("State")]); + parameters = qdbus_cast<QVariantMap>(props[QLatin1String("Parameters")]); +} + +/** + * \class TubeChannel + * \ingroup clientchannel + * \headerfile TelepathyQt/tube-channel.h <TelepathyQt/TubeChannel> + * + * \brief The TubeChannel class is a base class for all tube types. + * + * A tube is a mechanism for arbitrary data transfer between two or more IM users, + * used to allow applications on the users' systems to communicate without having + * to establish network connections themselves. + * + * Note that TubeChannel should never be instantiated directly, instead one of its + * subclasses (e.g. IncomingStreamTubeChannel or OutgoingStreamTubeChannel) should be used. + * + * See \ref async_model, \ref shared_ptr + */ + +/** + * Feature representing the core that needs to become ready to make the + * TubeChannel object usable. + * + * Note that this feature must be enabled in order to use most + * TubeChannel methods. + * See specific methods documentation for more details. + */ +const Feature TubeChannel::FeatureCore = Feature(QLatin1String(TubeChannel::staticMetaObject.className()), 0); + +/** + * \deprecated Use TubeChannel::FeatureCore instead. + */ +const Feature TubeChannel::FeatureTube = TubeChannel::FeatureCore; + +/** + * Create a new TubeChannel channel. + * + * \param connection Connection owning this channel, and specifying the + * service. + * \param objectPath The channel object path. + * \param immutableProperties The channel immutable properties. + * \return A TubeChannelPtr object pointing to the newly created + * TubeChannel object. + */ +TubeChannelPtr TubeChannel::create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties) +{ + return TubeChannelPtr(new TubeChannel(connection, objectPath, + immutableProperties)); +} + +/** + * Construct a new TubeChannel 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, if any. The corresponding introspectable should + * depend on TubeChannel::FeatureCore. + */ +TubeChannel::TubeChannel(const ConnectionPtr &connection, + const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature) + : Channel(connection, objectPath, immutableProperties, coreFeature), + mPriv(new Private(this)) +{ +} + +/** + * Class destructor. + */ +TubeChannel::~TubeChannel() +{ + delete mPriv; +} + +/** + * Return the parameters associated with this tube, if any. + * + * The parameters are populated when an outgoing tube is offered, but they are most useful in the + * receiving end, where the parameters passed to the offer can be extracted for the tube's entire + * lifetime to bootstrap legacy protocols. All parameters are passed unchanged. + * + * This method requires TubeChannel::FeatureCore to be ready. + * + * \return The parameters as QVariantMap. + */ +QVariantMap TubeChannel::parameters() const +{ + if (!isReady(FeatureCore)) { + warning() << "TubeChannel::parameters() used with FeatureCore not ready"; + return QVariantMap(); + } + + return mPriv->parameters; +} + +/** + * Return the state of this tube. + * + * Change notification is via the stateChanged() signal. + * + * This method requires TubeChannel::FeatureCore to be ready. + * + * \return The state as #TubeChannelState. + * \sa stateChanged() + */ +TubeChannelState TubeChannel::state() const +{ + if (!isReady(FeatureCore)) { + warning() << "TubeChannel::state() used with FeatureCore not ready"; + return TubeChannelStateNotOffered; + } + + return mPriv->state; +} + +/** + * \deprecated Use state() instead. + */ +TubeChannelState TubeChannel::tubeState() const +{ + return state(); +} + +void TubeChannel::setParameters(const QVariantMap ¶meters) +{ + mPriv->parameters = parameters; +} + +void TubeChannel::onTubeChannelStateChanged(uint newState) +{ + if (newState == mPriv->state) { + return; + } + + uint oldState = mPriv->state; + + debug() << "Tube state changed to" << newState; + mPriv->state = (Tp::TubeChannelState) newState; + + /* only emit stateChanged if we already received the state from initial introspection */ + if (oldState != (uint) -1) { + emit stateChanged((Tp::TubeChannelState) newState); + // FIXME (API/ABI break) Remove tubeStateChanged call + emit tubeStateChanged((Tp::TubeChannelState) newState); + } +} + +void TubeChannel::gotTubeProperties(PendingOperation *op) +{ + if (!op->isError()) { + PendingVariantMap *pvm = qobject_cast<PendingVariantMap *>(op); + + mPriv->extractTubeProperties(pvm->result()); + + debug() << "Got reply to Properties::GetAll(TubeChannel)"; + mPriv->readinessHelper->setIntrospectCompleted(TubeChannel::FeatureCore, true); + } else { + warning().nospace() << "Properties::GetAll(TubeChannel) failed " + "with " << op->errorName() << ": " << op->errorMessage(); + mPriv->readinessHelper->setIntrospectCompleted(TubeChannel::FeatureCore, false, + op->errorName(), op->errorMessage()); + } +} + +/** + * \fn void TubeChannel::stateChanged(Tp::TubeChannelState state) + * + * Emitted when the value of state() changes. + * + * \sa state The new state of this tube. + */ + +/** + * \fn void TubeChannel::tubeStateChanged(Tp::TubeChannelState state) + * + * \deprecated Use stateChanged() instead. + */ + +void TubeChannel::connectNotify(const char *signalName) +{ + if (qstrcmp(signalName, SIGNAL(tubeStateChanged(Tp::TubeChannelState))) == 0) { + warning() << "Connecting to deprecated signal tubeStateChanged(Tp::TubeChannelState)"; + } +} + + +} // Tp diff --git a/TelepathyQt/tube-channel.h b/TelepathyQt/tube-channel.h new file mode 100644 index 00000000..42379203 --- /dev/null +++ b/TelepathyQt/tube-channel.h @@ -0,0 +1,80 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010-2011 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_tube_channel_h_HEADER_GUARD_ +#define _TelepathyQt_tube_channel_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Channel> + +namespace Tp +{ + +class TP_QT_EXPORT TubeChannel : public Channel +{ + Q_OBJECT + Q_DISABLE_COPY(TubeChannel) + +public: + static const Feature FeatureCore; + // FIXME (API/ABI break) Remove FeatureTube in favour of FeatureCore + static const Feature FeatureTube; + + static TubeChannelPtr create(const ConnectionPtr &connection, + const QString &objectPath, const QVariantMap &immutableProperties); + + virtual ~TubeChannel(); + + TubeChannelState state() const; + TP_QT_DEPRECATED TubeChannelState tubeState() const; + + QVariantMap parameters() const; + +Q_SIGNALS: + void stateChanged(Tp::TubeChannelState state); + void tubeStateChanged(Tp::TubeChannelState state); + +protected: + TubeChannel(const ConnectionPtr &connection, const QString &objectPath, + const QVariantMap &immutableProperties, + const Feature &coreFeature = TubeChannel::FeatureCore); + + void setParameters(const QVariantMap ¶meters); + + // FIXME (API/ABI break) Remove connectNotify + void connectNotify(const char *); + +private Q_SLOTS: + TP_QT_NO_EXPORT void onTubeChannelStateChanged(uint newstate); + TP_QT_NO_EXPORT void gotTubeProperties(Tp::PendingOperation *op); + +private: + struct Private; + friend struct Private; + Private *mPriv; +}; + +} + +#endif diff --git a/TelepathyQt/types-internal.h b/TelepathyQt/types-internal.h new file mode 100644 index 00000000..f6ed06b5 --- /dev/null +++ b/TelepathyQt/types-internal.h @@ -0,0 +1,156 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_types_internal_h_HEADER_GUARD_ +#define _TelepathyQt_types_internal_h_HEADER_GUARD_ + +#include <QDBusVariant> +#include <QDBusArgument> +#include <TelepathyQt/Types> + +namespace Tp +{ + +/** + * Private structure used to circumvent QtDBus' strict demarshalling + */ +struct TP_QT_EXPORT SUSocketAddress +{ + /** + * A dotted-quad IPv4 address literal: four ASCII decimal numbers, each + * between 0 and 255 inclusive, e.g. "192.168.0.1". + */ + QString address; + /** + * The TCP or UDP port number. + */ + uint port; +}; + +TP_QT_EXPORT bool operator==(const SUSocketAddress& v1, const SUSocketAddress& v2); +TP_QT_EXPORT inline bool operator!=(const SUSocketAddress& v1, const SUSocketAddress& v2) +{ + return !operator==(v1, v2); +} +TP_QT_EXPORT QDBusArgument& operator<<(QDBusArgument& arg, const SUSocketAddress& val); +TP_QT_EXPORT const QDBusArgument& operator>>(const QDBusArgument& arg, SUSocketAddress& val); + +} // Tp + +// specialise for Tp::SocketAddressIPv4, allowing it to be used in place of QDBusVariant +template<> inline Tp::SocketAddressIPv4 qdbus_cast<Tp::SocketAddressIPv4>(const QDBusArgument &arg, +Tp::SocketAddressIPv4 *) +{ + if (arg.currentSignature() == QLatin1String("(su)")) { + // Use Tp::SUSocketAddress + Tp::SUSocketAddress saddr = qdbus_cast<Tp::SUSocketAddress>(arg); + Tp::SocketAddressIPv4 addr; + addr.address = saddr.address; + addr.port = saddr.port; + return addr; + } else if (arg.currentSignature() == QLatin1String("(sq)")) { + // Keep it standard + Tp::SocketAddressIPv4 item; + arg >> item; + return item; + } else { + // This should never happen... + return Tp::SocketAddressIPv4(); + } +} + +template<> inline Tp::SocketAddressIPv4 qdbus_cast<Tp::SocketAddressIPv4>(const QVariant &v, Tp::SocketAddressIPv4 *) +{ + int id = v.userType(); + if (id == qMetaTypeId<QDBusArgument>()) { + QDBusArgument arg = qvariant_cast<QDBusArgument>(v); + + if (arg.currentSignature() == QLatin1String("(su)")) { + // Use Tp::SUSocketAddress + Tp::SUSocketAddress saddr = qdbus_cast<Tp::SUSocketAddress>(arg); + Tp::SocketAddressIPv4 addr; + addr.address = saddr.address; + addr.port = saddr.port; + return addr; + } else if (arg.currentSignature() == QLatin1String("(sq)")) { + // Keep it standard + Tp::SocketAddressIPv4 item; + arg >> item; + return item; + } else { + // This should never happen... + return Tp::SocketAddressIPv4(); + } + } else + return qvariant_cast<Tp::SocketAddressIPv4>(v); +} + +// specialise for Tp::SocketAddressIPv6, allowing it to be used in place of QDBusVariant +template<> inline Tp::SocketAddressIPv6 qdbus_cast<Tp::SocketAddressIPv6>(const QDBusArgument &arg, +Tp::SocketAddressIPv6 *) +{ + if (arg.currentSignature() == QLatin1String("(su)")) { + // Use Tp::SUSocketAddress + Tp::SUSocketAddress saddr = qdbus_cast<Tp::SUSocketAddress>(arg); + Tp::SocketAddressIPv6 addr; + addr.address = saddr.address; + addr.port = saddr.port; + return addr; + } else if (arg.currentSignature() == QLatin1String("(sq)")) { + // Keep it standard + Tp::SocketAddressIPv6 item; + arg >> item; + return item; + } else { + // This should never happen... + return Tp::SocketAddressIPv6(); + } +} + +template<> inline Tp::SocketAddressIPv6 qdbus_cast<Tp::SocketAddressIPv6>(const QVariant &v, Tp::SocketAddressIPv6 *) +{ + int id = v.userType(); + if (id == qMetaTypeId<QDBusArgument>()) { + QDBusArgument arg = qvariant_cast<QDBusArgument>(v); + + if (arg.currentSignature() == QLatin1String("(su)")) { + // Use Tp::SUSocketAddress + Tp::SUSocketAddress saddr = qdbus_cast<Tp::SUSocketAddress>(arg); + Tp::SocketAddressIPv6 addr; + addr.address = saddr.address; + addr.port = saddr.port; + return addr; + } else if (arg.currentSignature() == QLatin1String("(sq)")) { + // Keep it standard + Tp::SocketAddressIPv6 item; + arg >> item; + return item; + } else { + // This should never happen... + return Tp::SocketAddressIPv6(); + } + } else + return qvariant_cast<Tp::SocketAddressIPv6>(v); +} + +Q_DECLARE_METATYPE(Tp::SUSocketAddress) + +#endif diff --git a/TelepathyQt/types.cpp b/TelepathyQt/types.cpp new file mode 100644 index 00000000..e9a606a0 --- /dev/null +++ b/TelepathyQt/types.cpp @@ -0,0 +1,73 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 <TelepathyQt/Types> + +#include <TelepathyQt/types-internal.h> + +#include "TelepathyQt/_gen/types-body.hpp" + +#include "TelepathyQt/future-internal.h" +#include "TelepathyQt/_gen/future-types-body.hpp" + +namespace Tp { +/** + * \\ingroup types + * \headerfile TelepathyQt/types.h <TelepathyQt/Types> + * + * Register the types used by the library with the QtDBus type system. + * + * Call this function to register the types used before using anything else in + * the library. + */ +void registerTypes() +{ + qDBusRegisterMetaType<Tp::SUSocketAddress>(); + + Tp::_registerTypes(); + TpFuture::_registerTypes(); +} + +bool operator==(const SUSocketAddress& v1, const SUSocketAddress& v2) +{ + return ((v1.address == v2.address) + && (v1.port == v2.port) + ); +} + +QDBusArgument& operator<<(QDBusArgument& arg, const SUSocketAddress& val) +{ + arg.beginStructure(); + arg << val.address << val.port; + arg.endStructure(); + return arg; +} + +const QDBusArgument& operator>>(const QDBusArgument& arg, SUSocketAddress& val) +{ + arg.beginStructure(); + arg >> val.address >> val.port; + arg.endStructure(); + return arg; +} + +} diff --git a/TelepathyQt/types.h b/TelepathyQt/types.h new file mode 100644 index 00000000..ff502402 --- /dev/null +++ b/TelepathyQt/types.h @@ -0,0 +1,171 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/> + * @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 + */ + +#ifndef _TelepathyQt_types_h_HEADER_GUARD_ +#define _TelepathyQt_types_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/_gen/types.h> + +#include <TelepathyQt/Global> +#include <TelepathyQt/SharedPtr> +#include <TelepathyQt/MethodInvocationContext> + +#include <QDBusVariant> + +namespace Tp +{ + +TP_QT_EXPORT void registerTypes(); + +template <typename T> class Filter; +template <typename T> class GenericCapabilityFilter; +template <typename T> class GenericPropertyFilter; + +class AbstractClient; +class AbstractClientApprover; +class AbstractClientHandler; +class AbstractClientObserver; +class Account; +typedef GenericCapabilityFilter<Account> AccountCapabilityFilter; +class AccountFactory; +typedef Filter<Account> AccountFilter; +class AccountManager; +class AccountPropertyFilter; +class AccountSet; +class Channel; +class ChannelDispatchOperation; +class ChannelFactory; +class ChannelRequest; +class ClientObject; +class ClientRegistrar; +class Connection; +class ConnectionFactory; +class ConnectionLowlevel; +class ConnectionManager; +class ConnectionManagerLowlevel; +class Contact; +class ContactFactory; +class ContactManager; +class ContactMessenger; +class ContactSearchChannel; +class DBusProxy; +class FileTransferChannel; +class IncomingFileTransferChannel; +class IncomingStreamTubeChannel; +class OutgoingFileTransferChannel; +class OutgoingStreamTubeChannel; +class Profile; +class ProfileManager; +class RoomListChannel; +class SimpleObserver; +class SimpleCallObserver; +class SimpleTextObserver; +class StreamedMediaChannel; +class StreamedMediaStream; +class StreamTubeChannel; +class StreamTubeClient; +class StreamTubeServer; +class TextChannel; +class TubeChannel; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + +typedef SharedPtr<AbstractClient> AbstractClientPtr; +typedef SharedPtr<AbstractClientApprover> AbstractClientApproverPtr; +typedef SharedPtr<AbstractClientHandler> AbstractClientHandlerPtr; +typedef SharedPtr<AbstractClientObserver> AbstractClientObserverPtr; +typedef SharedPtr<Account> AccountPtr; +typedef SharedPtr<AccountCapabilityFilter> AccountCapabilityFilterPtr; +typedef SharedPtr<const AccountCapabilityFilter> AccountCapabilityFilterConstPtr; +typedef SharedPtr<AccountFactory> AccountFactoryPtr; +typedef SharedPtr<const AccountFactory> AccountFactoryConstPtr; +typedef SharedPtr<AccountFilter> AccountFilterPtr; +typedef SharedPtr<const AccountFilter> AccountFilterConstPtr; +typedef SharedPtr<AccountManager> AccountManagerPtr; +typedef SharedPtr<AccountPropertyFilter> AccountPropertyFilterPtr; +typedef SharedPtr<const AccountPropertyFilter> AccountPropertyFilterConstPtr; +typedef SharedPtr<AccountSet> AccountSetPtr; +typedef SharedPtr<Channel> ChannelPtr; +typedef SharedPtr<ChannelDispatchOperation> ChannelDispatchOperationPtr; +typedef SharedPtr<ChannelFactory> ChannelFactoryPtr; +typedef SharedPtr<const ChannelFactory> ChannelFactoryConstPtr; +typedef SharedPtr<ChannelRequest> ChannelRequestPtr; +typedef SharedPtr<ClientObject> ClientObjectPtr; +typedef SharedPtr<ClientRegistrar> ClientRegistrarPtr; +typedef SharedPtr<Connection> ConnectionPtr; +typedef SharedPtr<ConnectionFactory> ConnectionFactoryPtr; +typedef SharedPtr<const ConnectionFactory> ConnectionFactoryConstPtr; +typedef SharedPtr<ConnectionLowlevel> ConnectionLowlevelPtr; +typedef SharedPtr<const ConnectionLowlevel> ConnectionLowlevelConstPtr; +typedef SharedPtr<ConnectionManager> ConnectionManagerPtr; +typedef SharedPtr<ConnectionManagerLowlevel> ConnectionManagerLowlevelPtr; +typedef SharedPtr<const ConnectionManagerLowlevel> ConnectionManagerLowlevelConstPtr; +typedef SharedPtr<Contact> ContactPtr; +typedef QSet<ContactPtr> Contacts; +typedef SharedPtr<ContactFactory> ContactFactoryPtr; +typedef SharedPtr<const ContactFactory> ContactFactoryConstPtr; +typedef SharedPtr<ContactManager> ContactManagerPtr; +typedef SharedPtr<ContactMessenger> ContactMessengerPtr; +typedef SharedPtr<ContactSearchChannel> ContactSearchChannelPtr; +typedef SharedPtr<DBusProxy> DBusProxyPtr; +typedef SharedPtr<FileTransferChannel> FileTransferChannelPtr; +typedef SharedPtr<IncomingFileTransferChannel> IncomingFileTransferChannelPtr; +typedef SharedPtr<IncomingStreamTubeChannel> IncomingStreamTubeChannelPtr; +typedef SharedPtr<OutgoingFileTransferChannel> OutgoingFileTransferChannelPtr; +typedef SharedPtr<OutgoingStreamTubeChannel> OutgoingStreamTubeChannelPtr; +typedef SharedPtr<Profile> ProfilePtr; +typedef SharedPtr<ProfileManager> ProfileManagerPtr; +typedef SharedPtr<RoomListChannel> RoomListChannelPtr; +typedef SharedPtr<SimpleObserver> SimpleObserverPtr; +typedef SharedPtr<SimpleCallObserver> SimpleCallObserverPtr; +typedef SharedPtr<SimpleTextObserver> SimpleTextObserverPtr; +typedef SharedPtr<StreamedMediaChannel> StreamedMediaChannelPtr; +typedef SharedPtr<StreamedMediaStream> StreamedMediaStreamPtr; +typedef SharedPtr<StreamTubeChannel> StreamTubeChannelPtr; +typedef SharedPtr<StreamTubeClient> StreamTubeClientPtr; +typedef SharedPtr<StreamTubeServer> StreamTubeServerPtr; +typedef SharedPtr<TextChannel> TextChannelPtr; +typedef SharedPtr<TubeChannel> TubeChannelPtr; + +template<typename T1 = MethodInvocationContextTypes::Nil, typename T2 = MethodInvocationContextTypes::Nil, + typename T3 = MethodInvocationContextTypes::Nil, typename T4 = MethodInvocationContextTypes::Nil, + typename T5 = MethodInvocationContextTypes::Nil, typename T6 = MethodInvocationContextTypes::Nil, + typename T7 = MethodInvocationContextTypes::Nil, typename T8 = MethodInvocationContextTypes::Nil> +class MethodInvocationContextPtr : public SharedPtr<MethodInvocationContext<T1, T2, T3, T4, T5, T6, T7, T8> > +{ +public: + inline MethodInvocationContextPtr() { } + explicit inline MethodInvocationContextPtr(MethodInvocationContext<T1, T2, T3, T4, T5, T6, T7, T8> *d) + : SharedPtr<MethodInvocationContext<T1, T2, T3, T4, T5, T6, T7, T8> >(d) { } + inline MethodInvocationContextPtr(const SharedPtr<MethodInvocationContext<T1, T2, T3, T4, T5, T6, T7, T8> > &o) + : SharedPtr<MethodInvocationContext<T1, T2, T3, T4, T5, T6, T7, T8> >(o) { } +}; + +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + +} // Tp + +#endif diff --git a/TelepathyQt/utils.cpp b/TelepathyQt/utils.cpp new file mode 100644 index 00000000..d3874770 --- /dev/null +++ b/TelepathyQt/utils.cpp @@ -0,0 +1,120 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <TelepathyQt/Utils> + +#include <QByteArray> +#include <QString> + +/** + * \defgroup utility functions + */ + +namespace Tp +{ + +static inline bool isBad(char c, bool isFirst) +{ + return ((c < 'a' || c > 'z') && + (c < 'A' || c > 'Z') && + (c < '0' || c > '9' || isFirst)); +} + +/** + * Escape an arbitrary string so it follows the rules for a C identifier, + * and hence an object path component, interface element component, + * bus name component or member name in D-Bus. + * + * This is a reversible encoding, so it preserves distinctness. + * + * The escaping consists of replacing all non-alphanumerics, and the first + * character if it's a digit, with an underscore and two lower-case hex + * digits: + * + * "0123abc_xyz\x01\xff" -> _30123abc_5fxyz_01_ff + * + * i.e. similar to URI encoding, but with _ taking the role of %, and a + * smaller allowed set. As a special case, "" is escaped to "_" (just for + * completeness, really). + * + * \param string The string to be escaped. + * \return the escaped string. + */ +QString escapeAsIdentifier(const QString &string) +{ + bool bad = false; + QByteArray op; + QByteArray utf8; + const char *name; + const char *ptr, *firstOk; + + /* This function is copy/pasted from tp_escape_as_identified from + * telepathy-glib. */ + + /* fast path for empty name */ + if (string.isEmpty()) { + return QString::fromLatin1("_"); + } + + utf8 = string.toUtf8(); + name = utf8.constData(); + for (ptr = name; *ptr; ptr++) { + if (isBad(*ptr, ptr == name)) { + bad = true; + break; + } + } + + /* fast path if it's clean */ + if (!bad) { + return string; + } + + /* If strictly less than ptr, firstOk is the first uncopied safe character. + */ + firstOk = name; + for (ptr = name; *ptr; ptr++) { + if (isBad(*ptr, ptr == name)) { + char buf[4] = { 0, }; + + /* copy preceding safe characters if any */ + if (firstOk < ptr) { + op.append(firstOk, ptr - firstOk); + } + + /* escape the unsafe character */ + qsnprintf(buf, sizeof (buf), "_%02x", (unsigned char)(*ptr)); + op.append(buf); + + /* restart after it */ + firstOk = ptr + 1; + } + } + /* copy trailing safe characters if any */ + if (firstOk < ptr) { + op.append(firstOk, ptr - firstOk); + } + + return QString::fromLatin1(op.constData()); +} + +} // Tp diff --git a/TelepathyQt/utils.h b/TelepathyQt/utils.h new file mode 100644 index 00000000..02c2b35d --- /dev/null +++ b/TelepathyQt/utils.h @@ -0,0 +1,41 @@ +/** + * This file is part of TelepathyQt + * + * @copyright Copyright (C) 2010 Collabora Ltd. <http://www.collabora.co.uk/> + * @copyright Copyright (C) 2010 Nokia Corporation + * @license LGPL 2.1 + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TelepathyQt_utils_h_HEADER_GUARD_ +#define _TelepathyQt_utils_h_HEADER_GUARD_ + +#ifndef IN_TP_QT_HEADER +#error IN_TP_QT_HEADER +#endif + +#include <TelepathyQt/Global> + +#include <QString> + +namespace Tp +{ + +TP_QT_EXPORT QString escapeAsIdentifier(const QString &string); + +} // Tp + +#endif |