Newer
Older
#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>
#include <uuid.h>
static const int MAX_PHRASE_LEN = 16;
static const char *SQLITE3_MEMORY_DB = ":memory:";
on_handle_get_candidates(ZeroPinyinService *object,
GDBusMethodInvocation *invocation,
const gchar *preedit_str,
guint fetch_size,
AppData *appdata)
if (preedit_str == NULL || fetch_size == 0) {
invocation,
"org.gtk.GDBus.Failed",
"Bad param");
g_info("get_candidates for preedit_str=%s fetch_size=%u",
preedit_str, fetch_size);
GVariant *result = NULL;
GVariantBuilder *candidates_builder = NULL;
GVariantBuilder *matched_lengths_builder = NULL;
GVariantBuilder *candidates_pinyin_indices = NULL;
/* 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"));
candidates_pinyin_indices = g_variant_builder_new(G_VARIANT_TYPE("aa(ii)"));
get_candidates(appdata->db, preedit_str, fetch_size, zero_pinyin_service_get_fuzzy_flag(object), candidates_builder, matched_lengths_builder, candidates_pinyin_indices);
result = g_variant_new("(asauaa(ii))", candidates_builder, matched_lengths_builder, candidates_pinyin_indices);
g_assert_nonnull(result);
g_dbus_method_invocation_return_value(invocation, result);
g_variant_builder_unref(candidates_builder);
g_variant_builder_unref(matched_lengths_builder);
g_variant_builder_unref(candidates_pinyin_indices);
on_handle_commit_candidate(ZeroPinyinService *object,
GDBusMethodInvocation *invocation,
const gchar *candidate,
GVariant *candidate_pinyin_indices,
AppData *appdata)
commit_candidate(appdata->db, candidate, candidate_pinyin_indices);
g_dbus_method_invocation_return_value(invocation, NULL);
on_handle_delete_candidate(ZeroPinyinService *object,
GDBusMethodInvocation *invocation,
const char *candidate,
AppData *appdata)
g_dbus_method_invocation_return_value(invocation, NULL);
g_debug("delete single character %s is a no-op", candidate);
g_dbus_method_invocation_return_value(invocation, NULL);
/* 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);
g_warning("insert phrase to not_phrase table failed");
/* 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);
g_warning("delete phrase from py_phrase_%u table failed", table_suffix);
g_dbus_method_invocation_return_value(invocation, NULL);
on_handle_quit(ZeroPinyinService *object,
GDBusMethodInvocation *invocation,
AppData *appdata)
g_application_quit(appdata->app);
g_dbus_method_invocation_return_value(invocation, NULL);
on_bus_acquired(GDBusConnection *connection,
const gchar *name,
gpointer user_data)
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),
ZERO_PINYIN_OBJECT_PATH,
&err);
if (err) {
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));
g_message("interface exported at %s", ZERO_PINYIN_OBJECT_PATH);
on_name_acquired(GDBusConnection *connection,
const gchar *name,
gpointer user_data)
on_name_lost(GDBusConnection *connection,
const gchar *name,
gpointer 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));
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);
}
/**
* handle SIGTERM gracefully.
*/
static gboolean
AppData *appdata = (AppData *) user_data;
g_application_quit(appdata->app);
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
/**
* return HOME dir, get value from HOME env variable.
*/
static const gchar *
get_home_dir(AppData *appdata)
{
const gchar *result;
result = g_environ_getenv(appdata->env, "HOME");
return result;
}
/**
* return TRUE if file exists
*/
static gboolean
file_exists(const char *filename)
{
return g_file_test(filename, G_FILE_TEST_EXISTS);
}
/**
* Return the file path of the main db file. main db is the main word/phrase
* dababase in libpyzy db format. Without main db, char/phrase query will not
* work at all.
*
* if not main db found, return NULL.
*
* returned gchar* should be freed with g_free().
*/
static gchar *
get_maindb_file(const gchar *home_dir)
{
/* TODO make db path configurable */
const gchar *user_main_db = ".cache/ibus/pinyin/main.db";
const gchar *open_phrase_db = "/usr/share/pyzy/db/open-phrase.db";
const gchar *android_db = "/usr/share/pyzy/db/android.db";
gchar *home_dir_maindb = NULL;
home_dir_maindb = g_strconcat(home_dir, "/", user_main_db, NULL);
if (file_exists(home_dir_maindb)) {
return home_dir_maindb;
}
if (file_exists(open_phrase_db)) {
return g_strdup(open_phrase_db);
}
if (file_exists(android_db)) {
return g_strdup(android_db);
}
return NULL;
}
/**
* return userdb file path.
* "~/.cache/ibus/pinyin/user-1.0.db"
*
* returned gchar* should be freed with g_free().
*/
static gchar *
get_userdb_file(const gchar *home_dir)
{
/* TODO make db path configurable */
return g_strconcat(home_dir, "/.cache/ibus/pinyin/user-1.0.db", NULL);
}
/**
* return a usable userdb file, it's either the path returned by
* `get_userdb_file()' or ':memory:' if that file doesn't exist and can't be
* created.
*
* returned gchar* should be freed with g_free().
*/
static gchar *
get_userdb_file_create(const gchar *home_dir)
{
gchar *userdb_file = NULL;
userdb_file = get_userdb_file(home_dir);
if (file_exists(userdb_file)) {
return userdb_file;
}
/* try create the file */
gchar *parent_dir = NULL;
parent_dir = g_path_get_dirname(userdb_file);
gint r = g_mkdir_with_parents(parent_dir, 0750);
if (r != 0) {
g_warning("create dir %s failed: %d (%s)",
parent_dir, r, g_strerror(r));
return g_strdup(SQLITE3_MEMORY_DB);
}
sqlite3 *userdb = NULL;
g_message("creating user db at %s", userdb_file);
r = sqlite3_open(userdb_file, &userdb);
if (r != SQLITE_OK) {
sqlite3_close(userdb);
return g_strdup(SQLITE3_MEMORY_DB);
}
sqlite3_close(userdb);
return userdb_file;
}
/**
* initialize user db.
* create tables, index and populate data into desc table.
*
* Returns: true on success, false otherwise.
*/
static gboolean
init_userdb(sqlite3 *userdb, const char *schema, AppData *appdata)
{
gboolean rb;
GString *sql;
gchar uuid_str[37];
uuid_t uuid;
gchar *snippet;
/* original libpyzy user db schema */
uuid_generate_random(uuid);
uuid_unparse_lower(uuid, uuid_str);
/* uuid = g_uuid_string_random(); */
sql = g_string_sized_new(200);
snippet = sqlite3_mprintf(
"BEGIN TRANSACTION;\n"
"CREATE TABLE IF NOT EXISTS %s.desc (name PRIMARY KEY, value TEXT);\n"
"INSERT OR IGNORE INTO %s.desc VALUES ('version', '1.2.0');\n"
"INSERT OR IGNORE INTO %s.desc VALUES ('uuid', %Q);\n"
"INSERT OR IGNORE INTO %s.desc VALUES ('hostname', %Q);\n"
"INSERT OR IGNORE INTO %s.desc VALUES ('username', %Q);\n"
"INSERT OR IGNORE INTO %s.desc VALUES ('create-time', datetime());\n"
"INSERT OR IGNORE INTO %s.desc VALUES ('attach-time', datetime());\n", schema, schema, schema, uuid_str, schema, g_environ_getenv(appdata->env, "HOSTNAME"), schema, g_environ_getenv(appdata->env, "USER"), schema, schema);
sql = g_string_append(sql, snippet);
sqlite3_free(snippet);
/* create phrase tables */
for (gint i = 0; i < MAX_PHRASE_LEN; i++) {
g_string_append_printf(sql, "CREATE TABLE IF NOT EXISTS %s.py_phrase_%d (user_freq, phrase TEXT, freq INTEGER", schema, i);
for (gint j = 0; j <= i; j++)
g_string_append_printf(sql, ", s%d INTEGER, y%d INTEGER", j, j);
sql = g_string_append(sql, ");\n");
}
/* create index */
g_string_append_printf(
sql,
"CREATE UNIQUE INDEX IF NOT EXISTS %s.index_0_0 ON py_phrase_0(s0,y0,phrase);\n"
"CREATE UNIQUE INDEX IF NOT EXISTS %s.index_1_0 ON py_phrase_1(s0,y0,s1,y1,phrase);\n"
"CREATE INDEX IF NOT EXISTS %s.index_1_1 ON py_phrase_1(s0,s1,y1);\n", schema, schema, schema);
for (gint i = 2; i < MAX_PHRASE_LEN; i++) {
g_string_append_printf(sql, "CREATE UNIQUE INDEX IF NOT EXISTS %s.index_%d_0 ON py_phrase_%d(s0,y0", schema, i, i);
for (gint j = 1; j <= i; j++)
g_string_append_printf(sql, ",s%d,y%d", j, j);
sql = g_string_append(sql, ",phrase);\n");
g_string_append_printf(sql, "CREATE INDEX IF NOT EXISTS %s.index_%d_1 ON py_phrase_%d(s0,s1,s2,y2);\n", schema, i, i);
}
/* zero-pinyin-service addition */
g_string_append_printf(sql, "CREATE TABLE IF NOT EXISTS %s.not_phrase (phrase TEXT UNIQUE);\n", schema);
sql = g_string_append(sql, "COMMIT;");
rb = sqlite3_exec_simple(userdb, sql->str);
g_string_free(sql, TRUE);
if (! rb) {
g_warning("init userdb failed, query will not work.");
return FALSE;
}
return TRUE;
}
/**
* init appdata->db
*/
gboolean rb = FALSE;
const gchar *home_dir;
gchar *maindb_file = NULL;
gchar *userdb_file = NULL;
home_dir = get_home_dir(appdata);
if (ri != SQLITE_OK) {
g_warning("sqlite3_open :memory: db failed, query will not work.");
goto db_fail;
}
maindb_file = get_maindb_file(home_dir);
g_info("using maindb file: %s", maindb_file);
sql = sqlite3_mprintf("ATTACH %Q AS maindb", maindb_file);
g_free(maindb_file);
g_warning("attach maindb failed, query will not work.");
goto attach_fail;
}
userdb_file = get_userdb_file_create(home_dir);
sql = sqlite3_mprintf("ATTACH %Q AS userdb", userdb_file);
g_free(userdb_file);
g_warning("attach userdb failed, query will not work.");
goto attach_fail;
}
init_userdb(db, "userdb", appdata);
appdata->db = db;
return;
attach_fail:
db_fail:
appdata->db = NULL;
}
/**
* allow graceful shutdown by Ctrl-C and SIGTERM.
*/
static void
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)
appdata->env = g_get_environ();
config_db(appdata);
config_dbus_service(appdata);
setup_sigint_sigterm_handler(appdata);
g_application_hold(app);
}
static void
on_activate(GApplication *app,
AppData *appdata)
}
static void
on_shutdown(GApplication *app,
AppData *appdata)
if (appdata->owner_id > 0) {
appdata->owner_id = 0;
}
if (appdata->db != NULL) {
appdata->db = NULL;
}
if (appdata->env != NULL) {
g_strfreev(appdata->env);
}
* provides zero-pinyin-service dbus service.
* it's a console app (GApplication) based on glib and gio.
app = g_application_new("com.emacsos.zero.ZeroPinyinServiceApp",
G_APPLICATION_FLAGS_NONE);
g_assert_nonnull(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);
status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);