Skip to content
main.c 9 KiB
Newer Older
Yuanle Song's avatar
Yuanle Song committed
#include <gio/gio.h>
#include <glib-unix.h>
#include "zero-pinyin-service.h"
#include "../sqlite3_util.h"
#include <sqlite3.h>
Yuanle Song's avatar
Yuanle Song committed

Yuanle Song's avatar
Yuanle Song committed
	GApplication *app;
	GDBusNodeInfo *introspection_data;
	guint owner_id;
	sqlite3 *db;
	gboolean init_done;
} AppData;
Yuanle Song's avatar
Yuanle Song committed

static AppData appdata;

static const gchar introspection_xml[] =
  "<node>"
  "  <interface name='com.emacsos.zero.ZeroPinyinService'>"
  "    <method name='GetCandidates'>"
  "      <arg type='s' name='preedit_str'/>"
  "      <arg type='u' name='fetch_size'/>"
Yuanle Song's avatar
Yuanle Song committed
  "      <arg type='as' name='candidates' direction='out'/>"
  "      <arg type='au' name='matched_preedit_str_lengths' direction='out'/>"
  "    </method>"
  "    <method name='Quit'/>"
  "  </interface>"
  "</node>";

static void
handle_method_get_candidates (GDBusMethodInvocation *invocation,
			      AppData *appdata,
			      GVariant *parameters)
{
	gchar *preedit_str = NULL;
Yuanle Song's avatar
Yuanle Song committed
	GVariant *result = NULL;
	GVariantBuilder *candidates_builder = NULL;
	GVariantBuilder *matched_lengths_builder = NULL;

	g_variant_get (parameters, "(su)", &preedit_str, &fetch_size);
Yuanle Song's avatar
Yuanle Song committed
	g_return_if_fail (preedit_str != NULL);
	g_return_if_fail (fetch_size > 0);
	if (preedit_str == NULL || fetch_size == 0) {
		g_dbus_method_invocation_return_dbus_error (
			invocation,
			"org.gtk.GDBus.Failed",
			"Bad param");
		if (preedit_str)
			g_free (preedit_str);
		return;
	}

	g_message ("get_candidates for preedit_str=%s fetch_size=%u",
		   preedit_str, fetch_size);
Yuanle Song's avatar
Yuanle Song committed

	candidates_builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
	matched_lengths_builder = g_variant_builder_new (G_VARIANT_TYPE ("au"));

	/* test data */
	/* get_candidates_test (preedit_str, fetch_size, candidates_builder, matched_lengths_builder); */
	get_candidates (appdata->db, preedit_str, fetch_size, candidates_builder, matched_lengths_builder);
Yuanle Song's avatar
Yuanle Song committed

	result = g_variant_new ("(asau)", candidates_builder, matched_lengths_builder);
	g_assert_nonnull (result);

	/* result is a GVarient tuple of two dbus arrays */
	g_dbus_method_invocation_return_value (invocation, result);

	g_variant_builder_unref (candidates_builder);
	g_variant_builder_unref (matched_lengths_builder);
	g_free (preedit_str);
}

static void
handle_method_call (GDBusConnection       *connection,
                    const gchar           *sender,
                    const gchar           *object_path,
                    const gchar           *interface_name,
                    const gchar           *method_name,
                    GVariant              *parameters,
                    GDBusMethodInvocation *invocation,
                    gpointer               user_data)
{
	AppData *appdata;
	gchar *errmsg = NULL;

	appdata = (AppData*) user_data;

	g_message ("got method call: %s", method_name);
	if (g_strcmp0 (method_name, "GetCandidates") == 0) {
		handle_method_get_candidates (invocation, appdata, parameters);
	} else if (g_strcmp0 (method_name, "Quit") == 0) {
		g_application_quit (appdata->app);
	} else {
		g_warning ("method not found: %s", method_name);

		errmsg = g_strdup_printf ("Method not found: %s", method_name);
		g_dbus_method_invocation_return_dbus_error (
			invocation,
			"org.gtk.GDBus.Failed",
			errmsg);
		g_free (errmsg);
	}
}

static GVariant *
handle_get_property (GDBusConnection  *connection,
                     const gchar      *sender,
                     const gchar      *object_path,
                     const gchar      *interface_name,
                     const gchar      *property_name,
                     GError          **error,
                     gpointer          user_data)
{
	return NULL;
}

static gboolean
handle_set_property (GDBusConnection  *connection,
                     const gchar      *sender,
                     const gchar      *object_path,
                     const gchar      *interface_name,
                     const gchar      *property_name,
                     GVariant         *value,
                     GError          **error,
                     gpointer          user_data)
{
	return TRUE;
}

static const GDBusInterfaceVTable interface_vtable = {
	handle_method_call,
	handle_get_property,
	handle_set_property
};

static void
on_bus_acquired (GDBusConnection *connection,
		 const gchar *name,
		 gpointer user_data)
{
	AppData *appdata = NULL;
	GError *err = NULL;
	guint registration_id = 0;
	static const gchar* object_path = "/com/emacsos/zero/ZeroPinyinService";

	g_message ("on_bus_acquired() name=%s", name);
	appdata = (AppData*) user_data;
	registration_id = g_dbus_connection_register_object (
		connection,
		object_path,
		appdata->introspection_data->interfaces[0],
		&interface_vtable,
		appdata,  /* user_data */
		NULL,  /* user_data_free_func */
		&err); /* GError** */
	if (err != NULL) {
		g_warning ("object register failed: %s. exiting now", err->message);
		g_error_free (err);
		g_application_quit (appdata->app);
		return;
	}
	if (registration_id > 0) {
		g_message ("object registered at %s", object_path);
	}
	g_assert_cmpint (registration_id, >, 0);
}

static void
on_name_acquired (GDBusConnection *connection,
                  const gchar     *name,
                  gpointer         user_data)
{
	g_message ("on_name_acquired() name=%s", name);
}

static void
on_name_lost (GDBusConnection *connection,
              const gchar     *name,
              gpointer         user_data)
{
	g_message ("on_name_lost() name=%s", name);
}

static void
config_dbus_service (AppData *appdata)
{
	appdata->introspection_data = g_dbus_node_info_new_for_xml (
		introspection_xml, NULL);
	g_assert (appdata->introspection_data != NULL);
	appdata->owner_id = g_bus_own_name (G_BUS_TYPE_SESSION,
					    "com.emacsos.zero.ZeroPinyinService",
					    G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT|G_BUS_NAME_OWNER_FLAGS_REPLACE,
Yuanle Song's avatar
Yuanle Song committed
					    on_bus_acquired,
					    on_name_acquired,
					    on_name_lost,
					    appdata,
					    NULL);
	g_assert_cmpint (appdata->owner_id, >, 0);
}

/**
 * handle SIGTERM gracefully.
 */
static gboolean
on_sigterm_received (gpointer user_data)
{
	AppData *appdata = (AppData*) user_data;
	g_application_quit (appdata->app);
	return G_SOURCE_REMOVE;
}

static void
config_db (AppData* appdata)
Yuanle Song's avatar
Yuanle Song committed
{
	gint ri = 0;
	gboolean r = FALSE;
	static const char* SQLITE3_MEMORY_DB = ":memory:";
	sqlite3* db = NULL;
	gchar* sql = NULL;
	ri = sqlite3_open (SQLITE3_MEMORY_DB, &db);
	g_assert_cmpint (ri, ==, SQLITE_OK);
	g_assert_nonnull (db);

	/* TODO make db path configurable */
	/* TODO remove user name in db path */
	sql = sqlite3_mprintf ("ATTACH %Q AS maindb", "/home/sylecn/.cache/ibus/pinyin/main.db");
	r = sqlite3_exec_simple (db, sql);
	g_assert (r);
	sqlite3_free (sql);

	/* TODO make db path configurable */
	sql = sqlite3_mprintf ("ATTACH %Q AS userdb", "/home/sylecn/.cache/ibus/pinyin/user-1.0.db");
	r = sqlite3_exec_simple (db, sql);
	g_assert (r);
	sqlite3_free (sql);

	appdata->db = db;
Yuanle Song's avatar
Yuanle Song committed
}

static void
zero_pinyin_service_init (GApplication *app,
			  AppData *appdata)
Yuanle Song's avatar
Yuanle Song committed
{
	appdata->introspection_data = NULL;
	appdata->owner_id = 0;
	appdata->db = NULL;
	config_dbus_service (appdata);
	config_db (appdata);
	appdata->init_done = TRUE;
Yuanle Song's avatar
Yuanle Song committed
}

/**
 * allow graceful shutdown by Ctrl-C and SIGTERM.
 */
static void
setup_sigint_sigterm_handler (AppData *appdata)
{
	GSource *source = NULL;
	source = g_unix_signal_source_new (SIGTERM);
	g_source_set_callback (source, on_sigterm_received, appdata, NULL);
	g_source_attach (source, NULL);
	g_source_unref (source);

	source = g_unix_signal_source_new (SIGINT);
	g_source_set_callback (source, on_sigterm_received, appdata, NULL);
	g_source_attach (source, NULL);
	g_source_unref (source);
}

static void
startup (GApplication* app,
	 AppData* appdata)
{
	g_message ("zero-pinyin-service startup()");
	setup_sigint_sigterm_handler (appdata);

	g_assert_false (appdata->init_done);
	zero_pinyin_service_init (app, appdata);
	g_application_hold (app);
}

static void
activate (GApplication *app,
	  AppData *appdata)
{
	g_message ("zero-pinyin-service activate()");
	/* as a pure service, activate is a no-op. */
}

static void
shutdown (GApplication *app,
	  AppData *appdata)
{
	g_message ("zero-pinyin-service shutdown()");
	if (appdata->owner_id > 0) {
		g_bus_unown_name (appdata->owner_id);
		appdata->owner_id = 0;
	}
	if (appdata->introspection_data != NULL) {
		g_dbus_node_info_unref (appdata->introspection_data);
		appdata->introspection_data = NULL;
	}
	if (appdata->db != NULL) {
		sqlite3_close (appdata->db);
		appdata->db = NULL;
	}
}

Yuanle Song's avatar
Yuanle Song committed
/**
 * provide zero-pinyin-service dbus service.
 * it's a console app based on glib and gio.
 */
int
main (int argc, char *argv[])
{
	GApplication *app = NULL;
	int status = 0;

	app = g_application_new ("com.emacsos.zero.ZeroPinyinServiceApp",
Yuanle Song's avatar
Yuanle Song committed
				 G_APPLICATION_FLAGS_NONE);
	g_assert_nonnull (app);
	appdata.app = app;
	appdata.init_done = FALSE;
Yuanle Song's avatar
Yuanle Song committed

	g_signal_connect (app, "startup", G_CALLBACK (startup), &appdata);
	g_signal_connect (app, "activate", G_CALLBACK (activate), &appdata);
Yuanle Song's avatar
Yuanle Song committed
	g_signal_connect (app, "shutdown", G_CALLBACK (shutdown), &appdata);

	status = g_application_run (G_APPLICATION (app), argc, argv);
	g_object_unref (app);
	return status;
}