/* * This file is not part of telepathy mission control * It is an example of an old-style McdTransport plugin * * Copyright © 2011 Collabora Ltd. * * This file 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. * */ #include #include #define DEMO_TYPE_TRANSPORT_PLUGIN (demo_transport_plugin_get_type ()) #define DEMO_TRANSPORT_PLUGIN(obj) \ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ DEMO_TYPE_TRANSPORT_PLUGIN, DemoTransportPlugin)) #define DEMO_TRANSPORT_PLUGIN_CLASS(klass) \ (G_TYPE_CHECK_CLASS_CAST ((klass), \ DEMO_TYPE_TRANSPORT_PLUGIN, DemoTransportPluginClass)) #define DEMO_IS_TRANSPORT_PLUGIN(obj) \ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), DEMO_TYPE_TRANSPORT_PLUGIN)) #define DEMO_IS_TRANSPORT_PLUGIN_CLASS(klass) \ (G_TYPE_CHECK_CLASS_TYPE ((klass), DEMO_TYPE_TRANSPORT_PLUGIN)) #define DEMO_TRANSPORT_PLUGIN_GET_CLASS(obj) \ (G_TYPE_INSTANCE_GET_CLASS ((obj), \ DEMO_TYPE_TRANSPORT_PLUGIN, DemoTransportPluginClass)) static GType demo_transport_plugin_get_type (void) G_GNUC_CONST; typedef struct _DemoTransportPluginClass DemoTransportPluginClass; typedef struct _DemoTransportPlugin DemoTransportPlugin; struct _DemoTransportPluginClass { GObjectClass parent_class; }; /* this is the data structure associated with your plugin: it's up to you * * what you put in here, no one else gets to look inside: */ struct _DemoTransportPlugin { GObject parent_instance; SomeThing *thing; /* some object or pointer representing our network transport framework */ GList *transports; GList *connecting_accounts; GHashTable *connections; guint conn_count; }; /* This is the per-transport data structure defined by the plugin * * the plugin is responsible for constructing and managing this data * * structure, its internals are opaque to all other parts of MC */ struct _McdTransport { /* put whatever data structures you need here: These are examples */ gchar *name; McdTransportStatus status; }; static void transport_plugin_init (McdTransportPluginIface *iface); static void account_connection_cb (McdAccount *account, GHashTable *parameters, gpointer userdata); static void connection_event(SomeThing *thing, SomeData *data, DemoTransportPlugin *plugin); static guint connections_after_event (DemoTransportPlugin *plugin, const gchar *ap_name, McdTransportStatus status); /* this symbol is looked for by the MCD plugin framework and * invoked to initialise the plugin: */ void mcd_plugin_init (McdPlugin *plugin) { McdTransportPlugin *transport_plugin; transport_plugin = g_object_new (DEMO_TYPE_TRANSPORT_PLUGIN, NULL); /* tell the MCD framework what kind of thingf we are: */ mcd_plugin_register_transport (plugin, transport_plugin); /* accounts use this callback to ask us for a network to be brought up */ mcd_plugin_register_account_connection (plugin, account_connection_cb, MCD_ACCOUNT_CONNECTION_PRIORITY_TRANSPORT, transport_plugin); } static const gchar * demo_transport_plugin_get_name (McdTransportPlugin *plugin) { return "demo-transport-plugin"; } static const GList * demo_transport_plugin_get_transports (McdTransportPlugin *plugin) { return DEMO_TRANSPORT_PLUGIN (plugin)->transports; } /* we don't use account conditions any more, so it's best to no-op * * this particular plugin member: */ static gboolean demo_transport_plugin_check_conditions (McdTransportPlugin *plugin, McdTransport *transport, const GHashTable *conditions) { return TRUE; } static const gchar * demo_transport_plugin_get_transport_name (McdTransportPlugin *plugin, McdTransport *transport) { return transport->name; } static McdTransportStatus demo_transport_plugin_get_transport_status (McdTransportPlugin *plugin, McdTransport *transport) { return transport->status; } static McdTransport * demo_transport_plugin_get_connected_transport (DemoTransportPlugin *plugin) { GList *list; for (list = plugin->transports; list != NULL; list = list->next) { McdTransport *tp = list->data; if (tp->status == MCD_TRANSPORT_STATUS_CONNECTED) return tp; } return NULL; } static void transport_plugin_init (McdTransportPluginIface *iface) { iface->get_name = demo_transport_plugin_get_name; iface->get_transports = demo_transport_plugin_get_transports; iface->check_conditions = demo_transport_plugin_check_conditions; iface->get_transport_name = demo_transport_plugin_get_transport_name; iface->get_transport_status = demo_transport_plugin_get_transport_status; } static void demo_transport_free (McdTransport *transport) { g_free (transport->name); g_slice_free (McdTransport, transport); } static McdTransport * demo_transport_new () { McdTransport *transport = g_slice_new0(McdTransport); return transport; } static void dispose (GObject *object) { DemoTransportPlugin *plugin = DEMO_TRANSPORT_PLUGIN (object); some_thing_free (plugin->thing); G_OBJECT_CLASS (demo_transport_plugin_parent_class)->dispose (object); } static void finalize (GObject *object) { DemoTransportPlugin *plugin = DEMO_TRANSPORT_PLUGIN (object); g_list_foreach (plugin->transports, (GFunc) demo_transport_free, NULL); g_list_free (plugin->transports); G_OBJECT_CLASS (demo_transport_plugin_parent_class)->finalize (object); } static void demo_transport_plugin_init (DemoTransportPlugin *plugin) { GError *error = NULL; plugin->conn_count = 0; plugin->connections = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); /* this is where we actually start talking to our network framework */ plugin->thing = some_thing_new (); /* you should hook up to whatever signalling your transport framework * * (NM, ConnMan, whatever) uses here: this is an example only: */ g_signal_connect (plugin->thing, "network-up-down-sideways-event", G_CALLBACK (connection_event), plugin); } static void demo_transport_plugin_class_init (DemoTransportPluginClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->dispose = dispose; object_class->finalize = finalize; } /* now the callbacks that duct tape everything together: */ /* called by accounts when we tell them to _proceed: */ /* NOTE: _proceed doesn't mean they wil succeed, it just means we tried to get a network connection and got a reply: he replay can be "No" */ static void on_connection_process (McdAccount *account, gboolean success, DemoTransportPlugin *plugin) { McdTransport *tp; GHashTable *parameters; /* our connection request completed we are no longer waiting: */ plugin->connecting_accounts = g_list_remove (plugin->connecting_accounts, account); /* don't need to be attached to this callback any more: */ g_signal_handlers_disconnect_by_func (account, on_connection_process, plugin); /* The data we stapled to the side of the account when it made its initial connectivity request (demo_transport_plugin_notify_account) */ parameters = g_object_steal_data (G_OBJECT (account), DEMO_TRANSPORT_DATA); tp = demo_transport_plugin_get_connected_transport (plugin); if (parameters != NULL && success && tp != NULL) { /* do any extra tweaking of the account (keepalive params etc) here */ } /* unref all the callback data */ g_hash_table_unref (parameters); g_object_unref (account); } /* this tells our transport framework we would like a network connection * * and we also track which accounts ahave asked us for a connection here */ static void demo_transport_plugin_notify_account (DemoTransportPlugin *plugin, McdAccount *account, GHashTable *parameters) { /* NOTE: you might want to check: mcd_account_connection_is_user_initiated () It tells you if this is a UI-triggered connection attempt or something MC is doing behind the scenes without prompting from the user: In case you care about such things (eg if your transport manager has an intrusive UI that you don't want to spam the user with) */ MAKE_CONNECTION_REQUEST_CALL_TO_NM_CONNMAN_ETC_HERE (plugin); /* the account was already on our list of connecting accounts */ if (g_list_find (plugin->connecting_accounts, account)) return; /* hook our connection callback up to the account */ g_object_ref (account); g_signal_connect (account, "connection-process", G_CALLBACK (on_connection_process), plugin); /* remember that we were asked to connect for this account */ plugin->connecting_accounts = g_list_prepend (plugin->connecting_accounts, account); /* staple the connection data to the account for the callback we set above */ g_object_set_data_full (G_OBJECT (account), DEMO_TRANSPORT_DATA, g_hash_table_ref (parameters), (GDestroyNotify) g_hash_table_unref); } /* this gets called when accounts attempt to go online and wants networking */ static void account_connection_cb (McdAccount *account, GHashTable *parameters, gpointer userdata) { DemoTransportPlugin *plugin = DEMO_TRANSPORT_PLUGIN (userdata); const gchar *protocol = mcd_account_get_protocol_name (account); McdTransport *tp; /* we have to build in any exceptions here: the current framework doesn't understand different connection types, so typically we have to exclude non-IP protocols (eg telephony ones) here */ if (protocol != NULL && (g_str_equal (protocol, "tel"))) { mcd_account_connection_proceed (account, TRUE); return; } /* so, we got this far - it's an account that needs IP: find a transport */ tp = demo_transport_plugin_get_connected_transport (plugin); if (tp == NULL) { /* make a network connectivity requst here. We will get woken up * * when a connection becomes available (if we've done our job right) */ demo_transport_plugin_notify_account (plugin, account, parameters); } else { /* if we have any extra work to do, like tweaking keepalive parameters based on the network type, we should do it here: */ /* associate the account with the transport and tell it to proceed: */ mcd_account_connection_bind_transport (account, tp); mcd_account_connection_proceed (account, TRUE); } } /* this callback will have to be altered to fit the API that comes with your particular variant of SomeThing: In our example it is connected to the signal "network-up-down-sideways-event" fired by SomeThing. */ static void connection_event(SomeThing *thing, SomeData *data, DemoTransportPlugin *plugin); { McdTransport *transport = NULL; McdTransportStatus status; GList *list; const gchar *label; gboolean can_route = FALSE; /* do we have _any_ live connections? */ gboolean transient = FALSE; /* is this a short-lived status? */ gboolean this_can_route = FALSE; /* is _this_ event's connection live? */ const gchar *iap_name = some_thing_get_connection_name (data); /* you will need to write _some_thing_connection_event_get_status to map the status from this signal/whatever to McdTransportStatus */ status = _some_thing_connection_event_get_status (event); can_route = connections_after_event (plugin, iap_name, status) > 0; switch (status) { case MCD_TRANSPORT_STATUS_CONNECTED; label = "MCD_TRANSPORT_STATUS_CONNECTED"; this_can_route = TRUE; break; case MCD_TRANSPORT_STATUS_DISCONNECTED; label = "MCD_TRANSPORT_STATUS_DISCONNECTED"; break; case MCD_TRANSPORT_STATUS_DISCONNECTING; label = "MCD_TRANSPORT_STATUS_DISCONNECTING"; break; case MCD_TRANSPORT_STATUS_CONNECTING; label = "MCD_TRANSPORT_STATUS_CONNECTING"; transient = TRUE; break; default: g_warning ("unsupported status %u", status); return; } if (iap_name != NULL) { for (list = plugin->transports; list != NULL; list = list->next) { McdTransport *tp = list->data; if (strcmp (tp->name, iap_name) == 0) { transport = tp; break; } } /* we have no record of this transport and this particular event * * relates to a transport which is currently connected: */ if (!transport && this_can_route) { /* create the transport */ transport = demo_transport_new(); transport->name = g_strdup (iap_name); plugin->transports = g_list_prepend (plugin->transports, transport); } if (transport != NULL) { transport->status = status; g_signal_emit_by_name (plugin, "status-changed", transport, status); } } /* transient means we get another signal later, noop for */ if (transient) { g_debug ("Network in transient state %s, no action taken", label); } else { GList *next; McdTransport *alt_transport = NULL; if (!this_can_route) { alt_transport = demo_transport_plugin_get_connected_transport (plugin); } for (list = plugin->connecting_accounts; list != NULL; list = next) { McdAccount *account = MCD_ACCOUNT (list->data); /* you'll need to cobble together a suitable some_reason (): */ TpConnectionStatusReason why = some_reason (data); g_debug ("connection of account %s can %sproceed", mcd_account_get_unique_name (account), status == can_route ? "" : "not "); /* Since the mcd_account_connection_proceed() can emit * "connection-process" which in turn can manipulate our list * destrying the current element, we must save the pointer to the * next one. */ next = list->next; /* if this transport went online, bind waiting accounts to it: * * if not, then it's kind of weird that we had any waiting * * accounts, but bind them to one of the live transports */ if (this_can_route) { g_assert (transport != NULL); mcd_account_connection_bind_transport (account, transport); } else if (alt_transport != NULL) { mcd_account_connection_bind_transport (account, alt_transport); } mcd_account_connection_proceed_with_reason (account, can_route, why); } } } /* track [dis]connects, so we can tell if we have working connection(s): */ static guint connections_after_event (DemoTransportPlugin *plugin, const gchar *ap_name, McdTransportStatus status) { gpointer stored = g_hash_table_lookup (plugin->connections, ap_name); gboolean new_connection_status = status == MCD_TRANSPORT_STATUS_CONNECTED; gboolean old_connection_status = stored == GUINT_TO_POINTER (TRUE); /* this is where we keep count of whether the account went on or offline */ if (new_connection_status) g_hash_table_insert (plugin->connections, g_strdup (ap_name), GUINT_TO_POINTER (TRUE)); else g_hash_table_remove (plugin->connections, ap_name); /* no change in status: nothing further to do here */ if (new_connection_status == old_connection_status) return plugin->conn_count; /* new_connection_status => we went online, otherwise we are offline. * * Also: it shouldn't be possible for count to go -ve: don't allow it: */ if (new_connection_status) plugin->conn_count++; else if (plugin->conn_count > 0) plugin->conn_count--; return plugin->conn_count; }