/* vi: set et sw=4 ts=4 cino=t0,(0: */ /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */ /* * This file is part of mission-control * * Copyright (C) 2008-2009 Nokia Corporation. * Copyright (C) 2009 Collabora Ltd. * * Contact: Alberto Mardegan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * version 2.1 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "mcd-account.h" #include "mcd-account-priv.h" #include "mcd-account-manager.h" #include "mcd-dispatcher-priv.h" #include "mcd-channel-priv.h" #include "mcd-misc.h" static void online_request_cb (McdAccount *account, gpointer userdata, const GError *error) { McdChannel *channel = MCD_CHANNEL (userdata); McdConnection *connection; if (error) { g_warning ("got error: %s", error->message); mcd_channel_take_error (channel, g_error_copy (error)); g_object_unref (channel); return; } DEBUG ("called"); connection = mcd_account_get_connection (account); g_return_if_fail (connection != NULL); g_return_if_fail (mcd_account_get_connection_status (account) == TP_CONNECTION_STATUS_CONNECTED); if (mcd_channel_get_status (channel) == MCD_CHANNEL_STATUS_FAILED) { DEBUG ("channel %p is failed", channel); g_object_unref (channel); return; } /* the connection will take ownership of the channel if and only if it * has no parent; we expect it to have no parent, and the connection will * become its parent */ g_assert (mcd_mission_get_parent ((McdMission *) channel) == NULL); mcd_connection_request_channel (connection, channel); } static McdChannel * get_channel_from_request (McdAccount *account, const gchar *request_id) { McdConnection *connection; const GList *channels, *list; connection = mcd_account_get_connection (account); if (connection) { channels = mcd_operation_get_missions (MCD_OPERATION (connection)); for (list = channels; list != NULL; list = list->next) { McdChannel *channel = MCD_CHANNEL (list->data); if (g_strcmp0 (_mcd_channel_get_request_path (channel), request_id) == 0) return channel; } } /* if we don't have a connection in connected state yet, the channel might * be in the online requests queue */ list = _mcd_account_get_online_requests (account); while (list) { McdOnlineRequestData *data = list->data; if (data->callback == online_request_cb) { McdChannel *channel = MCD_CHANNEL (data->user_data); if (g_strcmp0 (_mcd_channel_get_request_path (channel), request_id) == 0) return channel; } list = list->next; } return NULL; } static void on_channel_status_changed (McdChannel *channel, McdChannelStatus status, McdAccount *account) { const GError *error; if (status == MCD_CHANNEL_STATUS_FAILED) { gchar *err_string; error = mcd_channel_get_error (channel); g_warning ("Channel request %s failed, error: %s", _mcd_channel_get_request_path (channel), error->message); err_string = _mcd_build_error_string (error); /* FIXME: ideally the McdChannel should emit this signal itself, and * the Account.Interface.ChannelRequests should catch and re-emit it */ tp_svc_channel_request_emit_failed (channel, err_string, error->message); mc_svc_account_interface_channelrequests_emit_failed (account, _mcd_channel_get_request_path (channel), err_string, error->message); g_free (err_string); g_object_unref (channel); } else if (status == MCD_CHANNEL_STATUS_DISPATCHED) { /* FIXME: ideally the McdChannel should emit this signal itself, and * the Account.Interface.ChannelRequests should catch and re-emit it */ tp_svc_channel_request_emit_succeeded (channel); mc_svc_account_interface_channelrequests_emit_succeeded (account, _mcd_channel_get_request_path (channel)); g_object_unref (channel); } } McdChannel * _mcd_account_create_request (McdAccount *account, GHashTable *properties, gint64 user_time, const gchar *preferred_handler, gboolean use_existing, gboolean proceeding, GError **error) { McdChannel *channel; GHashTable *props; TpDBusDaemon *dbus_daemon = mcd_account_manager_get_dbus_daemon ( mcd_account_get_account_manager (account)); DBusGConnection *dgc = tp_proxy_get_dbus_connection (dbus_daemon); if (!mcd_account_check_request (account, properties, error)) { return NULL; } /* We MUST deep-copy the hash-table, as we don't know how dbus-glib will * free it */ props = _mcd_deepcopy_asv (properties); channel = mcd_channel_new_request (account, dgc, props, user_time, preferred_handler, use_existing, proceeding); g_hash_table_unref (props); /* FIXME: this isn't ideal - if the account is deleted, Proceed will fail, * whereas what we want to happen is that Proceed will succeed but * immediately cause a failure to be signalled. It'll do for now though. */ /* we use connect_after, to make sure that other signals (such as * RemoveRequest) are emitted before the Failed signal */ /* WARNING: on_channel_status_changed unrefs the McdChannel (!), so we * give it an extra reference, so that we can return a ref from this * function */ g_signal_connect_after (g_object_ref (channel), "status-changed", G_CALLBACK (on_channel_status_changed), account); return channel; } const McdDBusProp account_channelrequests_properties[] = { { 0 }, }; void _mcd_account_proceed_with_request (McdAccount *account, McdChannel *channel) { /* Put the account online if necessary, and when that's finished, * make the actual request. This is the equivalent of Proceed() in the * new API. * * (The callback releases this reference.) */ _mcd_account_online_request (account, online_request_cb, g_object_ref (channel)); } static void account_request_common (McdAccount *account, GHashTable *properties, gint64 user_time, const gchar *preferred_handler, DBusGMethodInvocation *context, gboolean use_existing) { GError *error = NULL; const gchar *request_id; McdChannel *channel; McdDispatcher *dispatcher; channel = _mcd_account_create_request (account, properties, user_time, preferred_handler, use_existing, TRUE /* proceeding */, &error); if (error) { g_assert (channel == NULL); dbus_g_method_return_error (context, error); g_error_free (error); return; } _mcd_account_proceed_with_request (account, channel); request_id = _mcd_channel_get_request_path (channel); DEBUG ("returning %s", request_id); if (use_existing) mc_svc_account_interface_channelrequests_return_from_ensure_channel (context, request_id); else mc_svc_account_interface_channelrequests_return_from_create (context, request_id); dispatcher = mcd_master_get_dispatcher (mcd_master_get_default ()); _mcd_dispatcher_add_request (dispatcher, account, channel); /* we still have a ref returned by _mcd_account_create_request(), which * is no longer necessary at this point */ g_object_unref (channel); } static void account_request_create (McSvcAccountInterfaceChannelRequests *self, GHashTable *properties, guint64 user_time, const gchar *preferred_handler, DBusGMethodInvocation *context) { account_request_common (MCD_ACCOUNT (self), properties, user_time, preferred_handler, context, FALSE); } static void account_request_ensure_channel (McSvcAccountInterfaceChannelRequests *self, GHashTable *properties, guint64 user_time, const gchar *preferred_handler, DBusGMethodInvocation *context) { account_request_common (MCD_ACCOUNT (self), properties, user_time, preferred_handler, context, TRUE); } static void account_request_cancel (McSvcAccountInterfaceChannelRequests *self, const gchar *request_id, DBusGMethodInvocation *context) { GError *error = NULL; McdChannel *channel; DEBUG ("called for %s", request_id); g_return_if_fail (request_id != NULL); channel = get_channel_from_request (MCD_ACCOUNT (self), request_id); if (!channel) { error = g_error_new (TP_ERRORS, TP_ERROR_INVALID_ARGUMENT, "Request %s not found", request_id); dbus_g_method_return_error (context, error); g_error_free (error); return; } if (!_mcd_channel_request_cancel (channel, &error)) { dbus_g_method_return_error (context, error); g_error_free (error); return; } mc_svc_account_interface_channelrequests_return_from_cancel (context); } void account_channelrequests_iface_init (McSvcAccountInterfaceChannelRequestsClass *iface, gpointer iface_data) { #define IMPLEMENT(x) mc_svc_account_interface_channelrequests_implement_##x (\ iface, account_request_##x) IMPLEMENT(create); IMPLEMENT(ensure_channel); IMPLEMENT(cancel); #undef IMPLEMENT } gboolean mcd_account_check_request (McdAccount *account, GHashTable *request, GError **error) { gboolean (*impl) (McdAccount *account, GHashTable *request, GError **error); g_return_val_if_fail (MCD_IS_ACCOUNT (account), FALSE); g_return_val_if_fail (request != NULL, FALSE); impl = MCD_ACCOUNT_GET_CLASS (account)->check_request; if (impl == NULL) return TRUE; return impl (account, request, error); } /* Default implementation of check_request */ gboolean _mcd_account_check_request_real (McdAccount *account, GHashTable *request, GError **error) { if (mcd_master_has_low_memory (mcd_master_get_default ())) { g_set_error (error, MC_ERROR, MC_LOWMEM_ERROR, "Insufficient memory"); return FALSE; } return TRUE; }