office-gobmx/tubes/source/conference.cxx
Matúš Kukan 7f6b2321a5 tubes: use channel's signal closed to know when end the collaboration
Change-Id: I11e0aa2db3b41a166e23c85fd040f883e0d3be08
2012-08-14 19:39:46 +02:00

594 lines
18 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* Version: MPL 1.1 / GPLv3+ / LGPLv3+
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License or as specified alternatively below. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Major Contributor(s):
* Copyright (C) 2012 Red Hat, Inc., Eike Rathke <erack@redhat.com>
*
* All Rights Reserved.
*
* For minor contributions see the git repository.
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 3 or later (the "GPLv3+"), or
* the GNU Lesser General Public License Version 3 or later (the "LGPLv3+"),
* in which case the provisions of the GPLv3+ or the LGPLv3+ are applicable
* instead of those above.
*/
#include <tubes/conference.hxx>
#include <tubes/collaboration.hxx>
#include <tubes/constants.h>
#include <tubes/file-transfer-helper.h>
#include <tubes/manager.hxx>
#include <gtk/gtk.h>
#include <telepathy-glib/telepathy-glib.h>
#if defined SAL_LOG_INFO
namespace
{
struct InfoLogger
{
const void* mpThat;
const char* mpMethod;
explicit InfoLogger( const void* pThat, const char* pMethod )
:
mpThat( pThat),
mpMethod( pMethod)
{
SAL_INFO( "tubes.method", mpThat << " entering " << mpMethod);
}
~InfoLogger()
{
SAL_INFO( "tubes.method", mpThat << " leaving " << mpMethod);
}
};
}
#define INFO_LOGGER_F(s) InfoLogger aLogger(0,(s))
#define INFO_LOGGER(s) InfoLogger aLogger(this,(s))
#else
#define INFO_LOGGER_F(s)
#define INFO_LOGGER(s)
#endif // SAL_LOG_INFO
class TeleConferenceImpl
{
public:
guint maObjectRegistrationId;
GDBusConnection* mpTube;
bool mbTubeOfferedHandlerInvoked : 1;
TeleConferenceImpl() :
mpTube( NULL ),
mbTubeOfferedHandlerInvoked( false )
{}
~TeleConferenceImpl() {}
};
static void TeleConference_MethodCallHandler(
GDBusConnection* /*pConnection*/,
const gchar* pSender,
const gchar* /*pObjectPath*/,
const gchar* pInterfaceName,
const gchar* pMethodName,
GVariant* pParameters,
GDBusMethodInvocation* pInvocation,
void* pUserData)
{
INFO_LOGGER_F( "TeleConference_MethodCallHandler");
TeleConference* pConference = reinterpret_cast<TeleConference*>(pUserData);
SAL_WARN_IF( !pConference, "tubes", "TeleConference_MethodCallHandler: no conference");
if (!pConference)
return;
if (tp_strdiff (pMethodName, LIBO_TUBES_DBUS_MSG_METHOD))
{
g_dbus_method_invocation_return_error ( pInvocation,
G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD,
"Unknown method '%s' on interface %s",
pMethodName, pInterfaceName );
return;
}
if (!g_variant_is_of_type ( pParameters, G_VARIANT_TYPE ("(ay)")))
{
g_dbus_method_invocation_return_error ( pInvocation,
G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS,
"'%s' takes an array of bytes, not %s",
pMethodName,
g_variant_get_type_string (pParameters));
return;
}
GVariant *ay;
g_variant_get( pParameters, "(@ay)", &ay);
const char* pPacketData = reinterpret_cast<const char*>( g_variant_get_data( ay));
gsize nPacketSize = g_variant_get_size( ay);
SAL_WARN_IF( !pPacketData, "tubes", "TeleConference_MethodCallHandler: couldn't get packet data");
if (!pPacketData)
return;
SAL_INFO( "tubes", "TeleConference_MethodCallHandler: received packet from sender "
<< (pSender ? pSender : "(null)") << " with size " << nPacketSize);
OString aPacket( pPacketData, nPacketSize );
if (pConference->getCollaboration())
pConference->getCollaboration()->PacketReceived( aPacket );
// Master needs to send the packet back to impose ordering,
// so the slave can execute his command.
if (pConference->isMaster())
pConference->sendPacket( aPacket );
g_dbus_method_invocation_return_value( pInvocation, 0 );
g_variant_unref( ay);
}
static void TeleConference_ChannelCloseHandler(
TpChannel* /*proxy*/,
const GError* pError,
gpointer pUserData,
GObject* /*weak_object*/
)
{
INFO_LOGGER_F( "TeleConference_ChannelCloseHandler");
TeleConference* pConference = reinterpret_cast<TeleConference*>(pUserData);
SAL_WARN_IF( !pConference, "tubes", "TeleConference_ChannelCloseHandler: no conference");
if (!pConference)
return;
if (pError)
{
SAL_WARN( "tubes", "TeleConference_ChannelCloseHandler: entered with error: " << pError->message);
pConference->finalize();
return;
}
pConference->finalize();
}
static void TeleConference_TubeOfferedHandler(
GObject* pSource,
GAsyncResult* pResult,
gpointer pUserData)
{
INFO_LOGGER_F( "TeleConference_TubeOfferedHandler");
TeleConference* pConference = reinterpret_cast<TeleConference*>(pUserData);
SAL_WARN_IF( !pConference, "tubes", "TeleConference_TubeOfferedHandler: no conference");
if (!pConference)
return;
pConference->setTubeOfferedHandlerInvoked( true);
TpDBusTubeChannel* pChannel = TP_DBUS_TUBE_CHANNEL( pSource);
GError* pError = NULL;
GDBusConnection* pTube = tp_dbus_tube_channel_offer_finish(
pChannel, pResult, &pError);
// "can't find contact ... presence" means contact is not a contact.
/* FIXME: detect and handle */
SAL_WARN_IF( !pTube, "tubes", "TeleConference_TubeOfferedHandler: entered with error: " << pError->message);
if (pError) {
g_error_free( pError);
return;
}
pConference->setTube( pTube);
}
static void TeleConference_TubeAcceptedHandler(
GObject* pSource,
GAsyncResult* pResult,
gpointer pUserData)
{
INFO_LOGGER_F( "TeleConference_TubeAcceptedHandler");
TeleConference* pConference = reinterpret_cast<TeleConference*>(pUserData);
SAL_WARN_IF( !pConference, "tubes", "TeleConference_TubeAcceptedHandler: no conference");
if (!pConference)
return;
pConference->setTubeOfferedHandlerInvoked( true);
TpDBusTubeChannel* pChannel = TP_DBUS_TUBE_CHANNEL( pSource);
GError* pError = NULL;
GDBusConnection* pTube = tp_dbus_tube_channel_accept_finish(
pChannel, pResult, &pError);
SAL_WARN_IF( !pTube, "tubes", "TeleConference_TubeAcceptedHandler: entered with error: " << pError->message);
if (pError) {
g_error_free( pError);
return;
}
GHashTable* pParameters = tp_dbus_tube_channel_get_parameters( pChannel);
const char* sUuid = tp_asv_get_string( pParameters, LIBO_TUBES_UUID);
pConference->setUuid( OString( sUuid));
pConference->setTube( pTube);
}
TeleConference::TeleConference( TpAccount* pAccount,
TpDBusTubeChannel* pChannel, const OString sUuid, bool bMaster )
:
mpCollaboration( NULL ),
mpAccount( NULL ),
mpChannel( NULL ),
msUuid( sUuid ),
mbMaster( bMaster ),
pImpl( new TeleConferenceImpl() )
{
setChannel( pAccount, pChannel );
}
TeleConference::~TeleConference()
{
// We're destructed from finalize()
delete pImpl;
}
static void channel_closed_cb( TpChannel *channel, gpointer user_data, GObject * /* weak_object */ )
{
Collaboration* pCollaboration = reinterpret_cast<Collaboration*> (user_data);
if (TeleManager::existsCollaboration( pCollaboration ))
{
GtkWidget *dialog = gtk_message_dialog_new( NULL, static_cast<GtkDialogFlags> (0),
GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
"Contact %s lost, you'll now be working locally.",
tp_contact_get_alias (tp_channel_get_target_contact (channel)) );
g_signal_connect_swapped (dialog, "response",
G_CALLBACK (gtk_widget_destroy), dialog);
gtk_widget_show_all (dialog);
pCollaboration->EndCollaboration();
}
}
void TeleConference::setChannel( TpAccount *pAccount, TpDBusTubeChannel* pChannel )
{
OSL_ENSURE( !mpChannel, "TeleConference::setChannel: already have channel");
if (mpChannel)
g_object_unref( mpChannel);
if (mpAccount)
g_object_unref( mpAccount);
mpChannel = pChannel;
if (mpChannel)
g_object_ref( mpChannel);
mpAccount = pAccount;
if (mpAccount)
g_object_ref( mpAccount);
}
bool TeleConference::spinUntilTubeEstablished()
{
while (!isTubeOfferedHandlerInvoked())
{
g_main_context_iteration( NULL, TRUE );
}
bool bOpen = pImpl->mpTube != NULL;
SAL_INFO( "tubes", "TeleConference::spinUntilTubeEstablished: tube open: " << bOpen);
return bOpen;
}
bool TeleConference::acceptTube()
{
INFO_LOGGER( "TeleConference::acceptTube");
SAL_WARN_IF( !mpChannel, "tubes", "TeleConference::acceptTube: no channel setup");
SAL_WARN_IF( pImpl->mpTube, "tubes", "TeleConference::acceptTube: already tubed");
if (!mpChannel || pImpl->mpTube)
return false;
tp_dbus_tube_channel_accept_async( mpChannel,
TeleConference_TubeAcceptedHandler,
this);
return spinUntilTubeEstablished();
}
bool TeleConference::offerTube()
{
INFO_LOGGER( "TeleConference::offerTube");
OSL_ENSURE( mpChannel, "TeleConference::offerTube: no channel");
if (!mpChannel)
return false;
GHashTable* pParameters = tp_asv_new (
LIBO_TUBES_UUID, G_TYPE_STRING, msUuid.getStr(),
NULL);
tp_dbus_tube_channel_offer_async(
mpChannel,
pParameters,
TeleConference_TubeOfferedHandler,
this);
return spinUntilTubeEstablished();
}
bool TeleConference::setTube( GDBusConnection* pTube)
{
INFO_LOGGER( "TeleConference::setTube");
OSL_ENSURE( !pImpl->mpTube, "TeleConference::setTube: already tubed");
pImpl->mpTube = pTube;
GDBusNodeInfo *introspection_data;
static const GDBusInterfaceVTable interface_vtable =
{
TeleConference_MethodCallHandler,
NULL,
NULL,
{ NULL },
};
static const gchar introspection_xml[] =
"<node>"
" <interface name='" LIBO_TUBES_DBUS_INTERFACE "'>"
" <method name='" LIBO_TUBES_DBUS_MSG_METHOD "'>"
" <arg type='ay' name='packet' direction='in'/>"
" </method>"
" </interface>"
"</node>";
introspection_data = g_dbus_node_info_new_for_xml (introspection_xml, NULL);
g_assert (introspection_data != NULL);
pImpl->maObjectRegistrationId = g_dbus_connection_register_object( pImpl->mpTube,
LIBO_TUBES_DBUS_PATH, introspection_data->interfaces[0],
&interface_vtable, this, NULL, NULL);
g_assert (pImpl->maObjectRegistrationId > 0);
g_dbus_node_info_unref (introspection_data);
return true;
}
void TeleConference::setTubeOfferedHandlerInvoked( bool b )
{
pImpl->mbTubeOfferedHandlerInvoked = b;
}
bool TeleConference::isTubeOfferedHandlerInvoked() const
{
return pImpl->mbTubeOfferedHandlerInvoked;
}
bool TeleConference::isReady() const
{
return mpChannel && pImpl->mpTube;
}
void TeleConference::close()
{
INFO_LOGGER( "TeleConference::close");
if (mpChannel)
tp_cli_channel_call_close( TP_CHANNEL( mpChannel), 5000, TeleConference_ChannelCloseHandler, this, NULL, NULL);
else
finalize();
}
void TeleConference::finalize()
{
INFO_LOGGER( "TeleConference::finalize");
TeleManager::unregisterDemoConference( this );
if (mpChannel)
{
g_object_unref( mpChannel);
mpChannel = NULL;
}
if (mpAccount)
{
g_object_unref( mpAccount);
mpAccount = NULL;
}
if (pImpl->mpTube)
{
g_dbus_connection_unregister_object( pImpl->mpTube, pImpl->maObjectRegistrationId);
g_dbus_connection_close_sync( pImpl->mpTube, NULL, NULL );
g_object_unref( pImpl->mpTube );
pImpl->mpTube = NULL;
}
//! *this gets destructed here!
}
bool TeleConference::sendPacket( const OString& rPacket )
{
INFO_LOGGER( "TeleConference::sendPacket");
if (!mpChannel && !pImpl->mpTube)
{
TeleManager::broadcastPacket( rPacket );
return true;
}
SAL_WARN_IF( !pImpl->mpTube, "tubes", "TeleConference::sendPacket: no tube");
if (!pImpl->mpTube)
return false;
/* FIXME: in GLib 2.32 we can use g_variant_new_fixed_array(). It does
* essentially this.
*/
void* pData = g_memdup( rPacket.getStr(), rPacket.getLength() );
GVariant *pParameters = g_variant_new_from_data( G_VARIANT_TYPE("(ay)"),
pData, rPacket.getLength(),
FALSE,
g_free, pData);
g_dbus_connection_call( pImpl->mpTube,
NULL, /* bus name; in multi-user case we'd address this to the master. */
LIBO_TUBES_DBUS_PATH,
LIBO_TUBES_DBUS_INTERFACE,
LIBO_TUBES_DBUS_MSG_METHOD,
pParameters, /* consumes the floating reference */
NULL,
G_DBUS_CALL_FLAGS_NONE,
-1, NULL, NULL, NULL);
// If we started the session, we can execute commands immediately.
if (mbMaster && mpCollaboration)
mpCollaboration->PacketReceived( rPacket );
return true;
}
bool TeleConference::isMaster() const
{
return mbMaster;
}
Collaboration* TeleConference::getCollaboration() const
{
return mpCollaboration;
}
void TeleConference::setCollaboration( Collaboration* pCollaboration )
{
mpCollaboration = pCollaboration;
if (mpChannel)
{
GError *error = NULL;
if (!tp_cli_channel_connect_to_closed( TP_CHANNEL (mpChannel),
channel_closed_cb, mpCollaboration, NULL, NULL, &error ))
{
SAL_WARN( "tubes", "Error when connecting to signal closed: " << error->message );
g_error_free (error);
}
}
}
void TeleConference::invite( TpContact *pContact )
{
INFO_LOGGER( "TeleConference::invite" );
TpHandle aHandle = tp_contact_get_handle( pContact );
GArray handles = { reinterpret_cast<gchar *> (&aHandle), 1 };
tp_cli_channel_interface_group_call_add_members( TP_CHANNEL( mpChannel ),
-1, &handles, NULL, NULL, NULL, NULL, NULL );
}
namespace {
class SendFileRequest {
public:
SendFileRequest( TeleConference::FileSentCallback pCallback, void* pUserData, const char* sUuid )
: mpCallback(pCallback)
, mpUserData(pUserData)
, msUuid(sUuid)
{}
TeleConference::FileSentCallback mpCallback;
void* mpUserData;
const char* msUuid;
};
}
static void TeleConference_TransferDone( EmpathyFTHandler *handler, TpFileTransferChannel *, gpointer user_data)
{
SendFileRequest *request = reinterpret_cast<SendFileRequest *>(user_data);
if (request->mpCallback)
request->mpCallback(true, request->mpUserData);
delete request;
g_object_unref (handler);
}
static void TeleConference_TransferError( EmpathyFTHandler *handler, const GError *error, gpointer user_data)
{
SendFileRequest *request = reinterpret_cast<SendFileRequest *>(user_data);
SAL_INFO( "tubes", "TeleConference_TransferError: " << error->message);
if (request->mpCallback)
request->mpCallback(false, request->mpUserData);
delete request;
g_object_unref (handler);
}
static void TeleConference_FTReady( EmpathyFTHandler *handler, GError *error, gpointer user_data)
{
SendFileRequest *request = reinterpret_cast<SendFileRequest *>(user_data);
if ( error != 0 )
{
if (request->mpCallback)
request->mpCallback(error == 0, request->mpUserData);
delete request;
g_object_unref (handler);
}
else
{
g_signal_connect(handler, "transfer-done",
G_CALLBACK (TeleConference_TransferDone), request);
g_signal_connect(handler, "transfer-error",
G_CALLBACK (TeleConference_TransferError), request);
empathy_ft_handler_set_service_name(handler, TeleManager::getFullServiceName().getStr());
empathy_ft_handler_set_description(handler, request->msUuid);
empathy_ft_handler_start_transfer(handler);
}
}
// TODO: move sending file to TeleManager
extern void TeleManager_fileReceived( const OUString&, const OString& );
void TeleConference::sendFile( TpContact* pContact, const OUString& localUri, FileSentCallback pCallback, void* pUserData)
{
INFO_LOGGER( "TeleConference::sendFile");
if (!pContact)
{
// used in demo mode
TeleManager_fileReceived( localUri, "demo" );
return;
}
SAL_WARN_IF( ( !mpAccount || !mpChannel), "tubes",
"can't send a file before the tube is set up");
if ( !mpAccount || !mpChannel)
return;
GFile *pSource = g_file_new_for_uri(
OUStringToOString( localUri, RTL_TEXTENCODING_UTF8).getStr() );
SendFileRequest *pReq = new SendFileRequest( pCallback, pUserData, msUuid.getStr() );
empathy_ft_handler_new_outgoing( mpAccount,
pContact,
pSource,
0,
TeleConference_FTReady, pReq);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */