Implementing a device type
About
The device type (TnyDevice) is used by the framework for knowing about the connectivity state of the device. This can be online and offline. Upon state changes must this device signal the rest of the framework about this event and must it start returning the correct new state when its API is called.
Although the LGPL license of tinymail does not require you to do this, you are encouraged to contribute device implementations for your own architecture, situation, framework or platform to the upstream Tinymail project. The added value of getting it in the LGPL upstream repository is that the people working on Tinymail will verify your implementation for correctness. You can also read this page of course. Be careful when implementing this type as it's not an easy one and as it influences the behaviour of being online and offline in your application. If that state is wrongly advertised, the application wont be online, or wont be offline.
Using tools/
../tools/gtypeinterface-h-files-to-c-file.pl TnyMyDevice \ ../libtinymail/tny-device.h > \ tny-my-device.c
Will generate you something like this (tny-my-device.c)
/* Your copyright here */
#include <config.h>
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <tny-my-device.h>
#include "tny-my-device-priv.h"
static GObjectClass *parent_class = NULL;
static gboolean
tny_my_device_is_online (TnyDevice *self)
{
return gboolean_new ()
}
static void
tny_my_device_force_online (TnyDevice *self)
{
}
static void
tny_my_device_force_offline (TnyDevice *self)
{
}
static void
tny_my_device_reset (TnyDevice *self)
{
}
static void
tny_my_device_connection_changed (TnyDevice *self, gboolean online)
{
}
static void
tny_my_device_finalize (GObject *object)
{
parent_class->finalize (object);
}
static void
tny_device_init (TnyDeviceIface *klass)
{
klass->is_online_func = tny_my_device_is_online;
klass->force_online_func = tny_my_device_force_online;
klass->force_offline_func = tny_my_device_force_offline;
klass->reset_func = tny_my_device_reset;
klass->connection_changed = tny_my_device_connection_changed;
}
static void
tny_my_device_class_init (TnyMyDeviceClass *klass)
{
GObjectClass *object_class;
parent_class = g_type_class_peek_parent (klass);
object_class = (GObjectClass*) klass;
object_class->finalize = tny_my_device_finalize;
}
GType
tny_my_device_get_type (void)
{
static GType type = 0;
if (G_UNLIKELY(type == 0))
{
static const GTypeInfo info =
{
sizeof (TnyMyDeviceClass),
NULL, /* base_init */
NULL, /* base_finalize */
(GClassInitFunc) tny_my_device_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (TnyMyDevice),
0, /* n_preallocs */
tny_my_device_instance_init,
NULL
};
static const GInterfaceInfo tny_device_info =
{
(GInterfaceInitFunc) tny_device_init, /* interface_init */
NULL, /* interface_finalize */
NULL /* interface_data */
}
type = g_type_register_static (G_TYPE_OBJECT,
"TnyMyDevice",
&info, 0);
g_type_add_interface_static (type, TNY_DEVICE,
&tny_device_info
}
return type;
}
Implementing the methods
In the following methods and APIs, the _my_ part of the API is implementation specific. Without that part, you get the official and public API of Tinymail.
tny_my_device_is_online
This one should always return whether or not the device is online at the moment of calling the method.
static gboolean
tny_my_device_is_online (TnyDevice *self)
{
if (is_forced)
return forced;
return my_special_xxyyzz_network_card_is_online (network_card_instance);
}
tny_my_device_force_online
This one must force self to act as if it is online. As a result must tny_my_device_is_online return true after this method until tny_my_device_reset happened.
Important note: the emissions here are pseudo code. You must protect them with the GDK lock and as much as possible try to get them to run in the mainloop as the handlers can contain Gtk+ code. Don't wrap the emissions with the GDK lock if not necessary, do wrap them if necessary (wrap them in g_idle_add(_full) and g_timeout_add(_full) callbacks and in threads). Consider reading this page carefully about the subject of GDK locking.
static void
tny_my_device_force_online (TnyDevice *self)
{
gboolean emit = is_forced?!forced:!real_online;
is_forced = TRUE;
forced = TRUE;
if (emit)
g_signal_emit (self, tny_device_signals [TNY_DEVICE_CONNECTION_CHANGED], 0, forced);
}
tny_my_device_force_offline
This one must force self to act as if it is online. As a result must tny_my_device_is_online return false after this method until tny_my_device_reset happened.
static void
tny_my_device_force_offline (TnyDevice *self)
{
gboolean emit = is_forced?forced:real_online;
is_forced = TRUE;
forced = FALSE;
if (emit)
g_signal_emit (self, tny_device_signals [TNY_DEVICE_CONNECTION_CHANGED], 0, forced);
}
tny_my_device_reset
This one must reset the force and online status of self to the current real online status of the device.
static void
tny_my_device_reset (TnyDevice *self)
{
gboolean online = tny_device_is_online (self);
is_forced = FALSE;
if (forced != online)
if (online)
tny_my_device_on_online (self);
else
tny_my_device_on_offline (self);
/* etc etc */
}
Emitting the event when the connectivity status changes
UNLESS the force_online or force_offline had been used, whenever the status changes, must your tny_my_device_is_online start returning the new status. Those "force" methods set the state of your type to be forced until the reset has happened. No events should attempt to change that state. If the force methods where not used and after the reset must your instance also emit the TNY_DEVICE_CONNECTION_CHANGED signal with as only parameter whether or not the device is online. Note that at signal emission it's possible that the mainloop of the application is not yet running. In that case it's allowed to emit the signal in the context (the thread) where the state changed. Make sure that you secure the GDK lock if necessary.
It's not important that the state-changed signal emission happens in real time with the actual state of the device. It's important that you only emit the connection-changed signal at the time the device is really online and that there's a fairly good reason to believe that the connecting with for example an IMAP server will fully succeed. This means that hostname lookups must succeed, that the device has an IP address, etcetera. A good example of a too early emission would be to emit at the exact timing when the wifi or GPRS unit gets powered on: the power-on event of such a component does not necessarily mean that we can already connect with for example an IMAP server on the Internet. It's quite possible that we need to request an IP address and get it from the network's routers first. Or not? I guess that depends on the technologies being used. It's a reason by itself why this type is abstract and therefore implementable by you.
static void
tny_my_device_on_online (TnyDevice *self)
{
if (is_forced)
return;
g_signal_emit (self, tny_device_signals [TNY_DEVICE_CONNECTION_CHANGED], 0, TRUE);
return;
}
static void
tny_my_device_on_offline (TnyDevice *self)
{
if (is_forced)
return;
g_signal_emit (self, tny_device_signals [TNY_DEVICE_CONNECTION_CHANGED], 0, FALSE);
return;
}
A summary on forced state and handling the real state of the hardware
- The tny_device_is_online must always return the current status of the device. This can be the forced state, or a real state
- If tny_device_force_online has been called, tny_device_is_online must return online for as long as the state is forced
- If tny_device_force_offline has been called, tny_device_is_online must return offline for as long as the state is forced
- If tny_device_reset has been called, the state is no longer forced
- A real state change does not change the forced state. Only a reset changes the forced state.
- The connection-changed signal may happen if there's reason to believe that a connection attempt with an account will succeed. If it's knowingly not possible to connect to the accounts, there's no reason to emit the signal.
The public .h file (tny-my-device.h)
#ifndef TNY_MY_DEVICE_H
#define TNY_MY_DEVICE_H
/* Your copyright */
#include <glib.h>
#include <glib-object.h>
#include <tny-device.h>
G_BEGIN_DECLS
#define TNY_TYPE_MY_DEVICE (tny_my_device_get_type ())
#define TNY_MY_DEVICE(obj) \
(G_TYPE_CHECK_INSTANCE_CAST ((obj), TNY_TYPE_MY_DEVICE, TnyMyDevice))
#define TNY_MY_DEVICE_CLASS(vtable) \
(G_TYPE_CHECK_CLASS_CAST ((vtable), TNY_TYPE_MY_DEVICE, TnyMyDeviceClass))
#define TNY_IS_MY_DEVICE(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE ((obj), TNY_TYPE_MY_DEVICE))
#define TNY_IS_MY_DEVICE_CLASS(vtable) \
(G_TYPE_CHECK_CLASS_TYPE ((vtable), TNY_TYPE_MY_DEVICE))
#define TNY_MY_DEVICE_GET_CLASS(inst) \
(G_TYPE_INSTANCE_GET_CLASS ((inst), TNY_TYPE_MY_DEVICE, TnyMyDeviceClass))
/* This is an abstract type */
typedef struct _TnyMyDevice TnyMyDevice;
typedef struct _TnyMyDeviceClass TnyMyDeviceClass;
struct _TnyMyDevice
{
GObject parent;
};
struct _TnyMyDeviceClass
{
GObjectClass parent;
};
GType tny_my_device_get_type (void);
TnyDevice* tny_my_device_new (void);
G_END_DECLS
#endif
Adjusting Makefile.am
libtinymail_my_1_0_headers = \
tny-my-account-store.h \
tny-my-password-dialog.h \
+ tny-my-device.h \
tny-my-platform-factory.h
libtinymail_my_1_0_la_SOURCES = \
$(libtinymail_my_1_0_headers) \
tny-my-account-store.c \
tny-my-device-priv.h \
+ tny-my-device.c \
tny-my-password-dialog.c \
tny-my-platform-factory.c
