7f6b2321a5
Change-Id: I11e0aa2db3b41a166e23c85fd040f883e0d3be08
594 lines
18 KiB
C++
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: */
|