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

Yuanle Song's avatar
Yuanle Song committed
	GApplication *app;
	guint owner_id;
	ZeroPinyinService *interface;
Yuanle Song's avatar
Yuanle Song committed

static gboolean
on_handle_get_candidates (ZeroPinyinService *object,
			  GDBusMethodInvocation *invocation,
			  const gchar *preedit_str,
			  guint fetch_size,
			  AppData *appdata)
Yuanle Song's avatar
Yuanle Song committed
{
	if (preedit_str == NULL || fetch_size == 0) {
		g_dbus_method_invocation_return_dbus_error (
			invocation,
			"org.gtk.GDBus.Failed",
			"Bad param");
	}
	g_message ("get_candidates for preedit_str=%s fetch_size=%u",
		   preedit_str, fetch_size);
Yuanle Song's avatar
Yuanle Song committed

	GVariant *result = NULL;
	GVariantBuilder *candidates_builder = NULL;
	GVariantBuilder *matched_lengths_builder = NULL;
Yuanle Song's avatar
Yuanle Song committed

        /* test data */
	/* get_candidates_test (preedit_str, fetch_size, candidates_builder, matched_lengths_builder); */
	candidates_builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
	matched_lengths_builder = g_variant_builder_new (G_VARIANT_TYPE ("au"));
	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);

Yuanle Song's avatar
Yuanle Song committed
}

static gboolean
on_handle_delete_candidate (ZeroPinyinService *object,
			    GDBusMethodInvocation *invocation,
			    const char *candidate,
			    AppData *appdata)
{
	if (! candidate) {
		g_dbus_method_invocation_return_value (invocation, NULL);
		return TRUE;
	}
	guint len = g_utf8_strlen (candidate, -1);
	if (len == 1) {
		g_message ("delete single character %s is a no-op", candidate);
		g_dbus_method_invocation_return_value (invocation, NULL);
		return TRUE;
	}
	g_message ("delete candidate %s", candidate);

	/* insert phrase to userdb.not_phrase table. */
	char *sql = NULL;
	gboolean rb = FALSE;
	sql = sqlite3_mprintf ("INSERT INTO userdb.not_phrase (phrase) VALUES (%Q);", candidate);
	rb = sqlite3_exec_simple (appdata->db, sql);
	if (! rb) {
		g_warning ("insert phrase to not_phrase table failed");
	}
	sqlite3_free (sql);

	/* delete phrase from userdb.py_phrase_x table. */
	guint table_suffix = len - 1;
	sql = sqlite3_mprintf ("DELETE FROM userdb.py_phrase_%u WHERE phrase = %Q;", table_suffix, candidate);
	rb = sqlite3_exec_simple (appdata->db, sql);
	if (! rb) {
		g_warning ("delete phrase from py_phrase_%u table failed", table_suffix);
	}
	sqlite3_free (sql);

	g_dbus_method_invocation_return_value (invocation, NULL);
	return TRUE;
}

Yuanle Song's avatar
Yuanle Song committed
static gboolean
on_handle_quit (ZeroPinyinService *object,
		GDBusMethodInvocation *invocation,
		AppData *appdata)
Yuanle Song's avatar
Yuanle Song committed
{
	g_application_quit (appdata->app);
	g_dbus_method_invocation_return_value (invocation, NULL);
Yuanle Song's avatar
Yuanle Song committed
	return TRUE;
}

static void
on_bus_acquired (GDBusConnection *connection,
		 const gchar *name,
		 gpointer user_data)
{
	AppData *appdata = (AppData*) user_data;
Yuanle Song's avatar
Yuanle Song committed
	GError *err = NULL;

	g_message ("on_bus_acquired() name=%s", name);

	appdata->interface = zero_pinyin_service_skeleton_new ();
	g_signal_connect (appdata->interface,
			  "handle-get-candidates",
			  G_CALLBACK (on_handle_get_candidates),
			  appdata);
	g_signal_connect (appdata->interface,
			  "handle-delete-candidate",
			  G_CALLBACK (on_handle_delete_candidate),
			  appdata);
	g_signal_connect (appdata->interface,
			  "handle-quit",
			  G_CALLBACK (on_handle_quit),
			  appdata);
	g_dbus_interface_skeleton_export (
		G_DBUS_INTERFACE_SKELETON (appdata->interface),
Yuanle Song's avatar
Yuanle Song committed
		connection,
		ZERO_PINYIN_OBJECT_PATH,
		&err);
	if (err) {
		g_warning ("export interface at %s failed: %s",
			   ZERO_PINYIN_OBJECT_PATH, err->message);
Yuanle Song's avatar
Yuanle Song committed
		g_error_free (err);
		g_application_quit (G_APPLICATION (appdata->app));
Yuanle Song's avatar
Yuanle Song committed
		return;
	}
	g_message ("interface exported at %s", ZERO_PINYIN_OBJECT_PATH);
Yuanle Song's avatar
Yuanle Song committed
}

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)
{
	AppData *appdata = (AppData*) user_data;
	/* this won't happen if this is the only app that tries to take the
	 * name, because GApplication already have primary instance
	 * concept. None primary instance will just send 'activate' signal to
	 * primary instance and exit. They will not try to register ibus at
	 * all. */
	g_message ("on_name_lost() name=%s exiting", name);
	g_application_quit (G_APPLICATION (appdata->app));
Yuanle Song's avatar
Yuanle Song committed
}

static void
config_dbus_service (AppData *appdata)
{
	appdata->owner_id = g_bus_own_name (
		G_BUS_TYPE_SESSION,
		ZERO_PINYIN_WELL_KNOWN_NAME,
		G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT|G_BUS_NAME_OWNER_FLAGS_REPLACE,
		on_bus_acquired,
		on_name_acquired,
		on_name_lost,
		appdata,
		NULL);
Yuanle Song's avatar
Yuanle Song committed
	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;
}

/**
 * init appdata->db
 */
Yuanle Song's avatar
Yuanle Song committed
static void
config_db (AppData* appdata)
Yuanle Song's avatar
Yuanle Song committed
{
	static const char* SQLITE3_MEMORY_DB = ":memory:";
	sqlite3* db = NULL;
	gchar* sql = NULL;
	ri = sqlite3_open (SQLITE3_MEMORY_DB, &db);
	if (ri != SQLITE_OK) {
		g_warning ("sqlite3_open :memory: db failed, query will not work.");
		goto db_fail;
	}
	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");
	rb = sqlite3_exec_simple (db, sql);
	if (! rb) {
		g_warning ("attach maindb failed, query will not work.");
		goto attach_fail;
	}
	sqlite3_free (sql);

	/* TODO make db path configurable */
	sql = sqlite3_mprintf ("ATTACH %Q AS userdb", "/home/sylecn/.cache/ibus/pinyin/user-1.0.db");
	rb = sqlite3_exec_simple (db, sql);
	if (! rb) {
		g_warning ("attach userdb failed, query will not work.");
		goto attach_fail;
	}
	sqlite3_free (sql);

	sql = "CREATE TABLE IF NOT EXISTS userdb.not_phrase (phrase TEXT UNIQUE);";
	rb = sqlite3_exec_simple (db, sql);
	if (! rb) {
		g_warning ("create userdb.not_phrase table failed, query will not work.");
		sql = NULL;
		goto attach_fail;
	}

	return;

attach_fail:
	sqlite3_free (sql);
	sqlite3_close (db);
db_fail:
	appdata->db = NULL;
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);
}

on_startup (GApplication* app,
	    AppData* appdata)
{
	g_message ("zero-pinyin-service startup()");
	config_db (appdata);
	config_dbus_service (appdata);
	setup_sigint_sigterm_handler (appdata);
	g_application_hold (app);
}

static void
on_activate (GApplication *app,
	     AppData *appdata)
{
	g_message ("zero-pinyin-service activate()");
}

static void
on_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->db != NULL) {
		sqlite3_close (appdata->db);
		appdata->db = NULL;
	}
}

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

	setlocale (LC_ALL, "");

	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;

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

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