Skip to content
main.c 9.06 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
Yuanle Song's avatar
Yuanle Song committed
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) {
Yuanle Song's avatar
Yuanle Song committed
		g_dbus_method_invocation_return_dbus_error(
			invocation,
			"org.gtk.GDBus.Failed",
			"Bad param");
Yuanle Song's avatar
Yuanle Song committed
	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;
	GVariantBuilder *candidates_pinyin_indices = NULL;
Yuanle Song's avatar
Yuanle Song committed

Yuanle Song's avatar
Yuanle Song committed
	/* test data */
	/* get_candidates_test (preedit_str, fetch_size, candidates_builder, matched_lengths_builder); */
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"));
	candidates_pinyin_indices = g_variant_builder_new(G_VARIANT_TYPE("aa(ii)"));
	get_candidates(appdata->db, preedit_str, fetch_size, candidates_builder, matched_lengths_builder, candidates_pinyin_indices);
Yuanle Song's avatar
Yuanle Song committed

Yuanle Song's avatar
Yuanle Song committed
	result = g_variant_new("(asauaa(ii))", candidates_builder, matched_lengths_builder, candidates_pinyin_indices);
	g_assert_nonnull(result);
Yuanle Song's avatar
Yuanle Song committed

	/* result is a GVarient tuple of two dbus arrays */
Yuanle Song's avatar
Yuanle Song committed
	g_dbus_method_invocation_return_value(invocation, result);
Yuanle Song's avatar
Yuanle Song committed

Yuanle Song's avatar
Yuanle Song committed
	g_variant_builder_unref(candidates_builder);
	g_variant_builder_unref(matched_lengths_builder);
	g_variant_builder_unref(candidates_pinyin_indices);
Yuanle Song's avatar
Yuanle Song committed

Yuanle Song's avatar
Yuanle Song committed
}

static gboolean
Yuanle Song's avatar
Yuanle Song committed
on_handle_commit_candidate(ZeroPinyinService *object,
			   GDBusMethodInvocation *invocation,
			   const gchar *candidate,
			   GVariant *candidate_pinyin_indices,
			   AppData *appdata)
Yuanle Song's avatar
Yuanle Song committed
	commit_candidate(appdata->db, candidate, candidate_pinyin_indices);
	g_dbus_method_invocation_return_value(invocation, NULL);
	return TRUE;
}

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

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

	/* delete phrase from userdb.py_phrase_x table. */
	guint table_suffix = len - 1;
Yuanle Song's avatar
Yuanle Song committed
	sql = sqlite3_mprintf("DELETE FROM userdb.py_phrase_%u WHERE phrase = %Q;", table_suffix, candidate);
	rb = sqlite3_exec_simple(appdata->db, sql);
	if (! rb) {
Yuanle Song's avatar
Yuanle Song committed
		g_warning("delete phrase from py_phrase_%u table failed", table_suffix);
Yuanle Song's avatar
Yuanle Song committed
	sqlite3_free(sql);
Yuanle Song's avatar
Yuanle Song committed
	g_dbus_method_invocation_return_value(invocation, NULL);
Yuanle Song's avatar
Yuanle Song committed
static gboolean
Yuanle Song's avatar
Yuanle Song committed
on_handle_quit(ZeroPinyinService *object,
	       GDBusMethodInvocation *invocation,
	       AppData *appdata)
Yuanle Song's avatar
Yuanle Song committed
{
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
Yuanle Song's avatar
Yuanle Song committed
on_bus_acquired(GDBusConnection *connection,
		const gchar *name,
		gpointer user_data)
Yuanle Song's avatar
Yuanle Song committed
{
Yuanle Song's avatar
Yuanle Song committed
	AppData *appdata = (AppData *) user_data;
Yuanle Song's avatar
Yuanle Song committed
	GError *err = NULL;

Yuanle Song's avatar
Yuanle Song committed
	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-commit-candidate",
			 G_CALLBACK(on_handle_commit_candidate),
			 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) {
Yuanle Song's avatar
Yuanle Song committed
		g_warning("export interface at %s failed: %s",
			  ZERO_PINYIN_OBJECT_PATH, err->message);
		g_error_free(err);
		g_application_quit(G_APPLICATION(appdata->app));
Yuanle Song's avatar
Yuanle Song committed
		return;
	}
Yuanle Song's avatar
Yuanle Song committed
	g_message("interface exported at %s", ZERO_PINYIN_OBJECT_PATH);
Yuanle Song's avatar
Yuanle Song committed
}

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

static void
Yuanle Song's avatar
Yuanle Song committed
on_name_lost(GDBusConnection *connection,
	     const gchar     *name,
	     gpointer         user_data)
Yuanle Song's avatar
Yuanle Song committed
{
Yuanle Song's avatar
Yuanle Song committed
	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. */
Yuanle Song's avatar
Yuanle Song committed
	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
Yuanle Song's avatar
Yuanle Song committed
config_dbus_service(AppData *appdata)
Yuanle Song's avatar
Yuanle Song committed
{
Yuanle Song's avatar
Yuanle Song committed
	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);
	g_assert_cmpint(appdata->owner_id, >, 0);
Yuanle Song's avatar
Yuanle Song committed
}

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

/**
 * init appdata->db
 */
Yuanle Song's avatar
Yuanle Song committed
static void
Yuanle Song's avatar
Yuanle Song committed
config_db(AppData *appdata)
Yuanle Song's avatar
Yuanle Song committed
{
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);
Yuanle Song's avatar
Yuanle Song committed
		g_warning("sqlite3_open :memory: db failed, query will not work.");
Yuanle Song's avatar
Yuanle Song committed
	g_assert_nonnull(db);

	/* TODO make db path configurable */
	/* TODO remove user name in db path */
Yuanle Song's avatar
Yuanle Song committed
	sql = sqlite3_mprintf("ATTACH %Q AS maindb", "/home/sylecn/.cache/ibus/pinyin/main.db");
	rb = sqlite3_exec_simple(db, sql);
Yuanle Song's avatar
Yuanle Song committed
		g_warning("attach maindb failed, query will not work.");
Yuanle Song's avatar
Yuanle Song committed
	sqlite3_free(sql);

	/* TODO make db path configurable */
Yuanle Song's avatar
Yuanle Song committed
	sql = sqlite3_mprintf("ATTACH %Q AS userdb", "/home/sylecn/.cache/ibus/pinyin/user-1.0.db");
	rb = sqlite3_exec_simple(db, sql);
Yuanle Song's avatar
Yuanle Song committed
		g_warning("attach userdb failed, query will not work.");
Yuanle Song's avatar
Yuanle Song committed
	sqlite3_free(sql);

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

Yuanle Song's avatar
Yuanle Song committed
	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
Yuanle Song's avatar
Yuanle Song committed
setup_sigint_sigterm_handler(AppData *appdata)
Yuanle Song's avatar
Yuanle Song committed
{
	GSource *source = NULL;
Yuanle Song's avatar
Yuanle Song committed
	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);
Yuanle Song's avatar
Yuanle Song committed
}

Yuanle Song's avatar
Yuanle Song committed
on_startup(GApplication *app,
	   AppData *appdata)
Yuanle Song's avatar
Yuanle Song committed
	g_message("zero-pinyin-service startup()");
	config_db(appdata);
	config_dbus_service(appdata);
	setup_sigint_sigterm_handler(appdata);
	g_application_hold(app);
Yuanle Song's avatar
Yuanle Song committed
on_activate(GApplication *app,
	    AppData *appdata)
Yuanle Song's avatar
Yuanle Song committed
	g_message("zero-pinyin-service activate()");
Yuanle Song's avatar
Yuanle Song committed
on_shutdown(GApplication *app,
	    AppData *appdata)
Yuanle Song's avatar
Yuanle Song committed
	g_message("zero-pinyin-service shutdown()");
	if (appdata->owner_id > 0) {
Yuanle Song's avatar
Yuanle Song committed
		g_bus_unown_name(appdata->owner_id);
		appdata->owner_id = 0;
	}
	if (appdata->db != NULL) {
Yuanle Song's avatar
Yuanle Song committed
		sqlite3_close(appdata->db);
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
Yuanle Song's avatar
Yuanle Song committed
main(int argc, char *argv[])
Yuanle Song's avatar
Yuanle Song committed
{
	static AppData appdata = {0};
Yuanle Song's avatar
Yuanle Song committed
	GApplication *app = NULL;
	int status = 0;

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

Yuanle Song's avatar
Yuanle Song committed
	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

Yuanle Song's avatar
Yuanle Song committed
	status = g_application_run(G_APPLICATION(app), argc, argv);
	g_object_unref(app);
Yuanle Song's avatar
Yuanle Song committed
	return status;
}