From a499f01ec3203f38bcec611106943c098140e988 Mon Sep 17 00:00:00 2001 From: Yuanle Song Date: Sat, 31 Aug 2019 16:57:43 +0800 Subject: [PATCH] v0.7.0 make db path portable. add create-deb.sh - make db path portable. - reuse libpyzy main db and user db logic. - auto create user db (tables, indices) if it doesn't exist. - added create-deb.sh, it can create deb file for debian. --- .gitignore | 2 + create-deb.sh | 47 ++++++++++++ main.c | 206 ++++++++++++++++++++++++++++++++++++++++++++++---- meson.build | 2 +- operational | 53 ++++++++++++- 5 files changed, 295 insertions(+), 15 deletions(-) create mode 100644 .gitignore create mode 100755 create-deb.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..790ab50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +DEST/ +*.deb diff --git a/create-deb.sh b/create-deb.sh new file mode 100755 index 0000000..491a779 --- /dev/null +++ b/create-deb.sh @@ -0,0 +1,47 @@ +#!/bin/sh +set -x +set -e +print_help_and_exit() { + echo "Usage: ./create-deb.sh +create deb for debian. requires fpm tool." + exit 1 +} + +# main() +if [ "$1" = "--help" ]; then + print_help_and_exit +fi + +DEST=${DEST:-DEST} +VERSION=${VERSION:-0.6.1} + +ninja -C release/ +mkdir -p \ + "$DEST"/usr/share/dbus-1/interfaces/ \ + "$DEST"/usr/share/dbus-1/services/ \ + "$DEST"/usr/bin/ + +cp 'com.emacsos.zero.ZeroPinyinService1.ZeroPinyinServiceInterface.xml' "$DEST"/usr/share/dbus-1/interfaces/ +cp 'com.emacsos.zero.ZeroPinyinService1.service' "$DEST"/usr/share/dbus-1/services/ +sed -i -E 's:/home/sylecn/bin/sbin/zero-pinyin-service:/usr/bin/zero-pinyin-service:' "$DEST"/usr/share/dbus-1/services/com.emacsos.zero.ZeroPinyinService1.service +cp release/zero-pinyin-service "$DEST"/usr/bin/ + +fpm -f -t deb -s dir -n zero-pinyin-service -v "$VERSION" \ + -d libglib2.0-0 \ + -d libsqlite3-0 \ + -d libstdc++6 \ + -d libgcc1 \ + -d libc6 \ + -d libpcre3 \ + -d zlib1g \ + -d libselinux1 \ + -d libmount1 \ + -d libffi6 \ + -d libblkid1 \ + -d libuuid1 \ + --description "provide pinyin input engine for zero-el pinyin" \ + --vendor sylecn \ + --maintainer "Yuanle Song " \ + --deb-priority optional \ + --url "https://gitlab.emacsos.com/sylecn/zero-pinyin-service" \ + -C "$DEST" . diff --git a/main.c b/main.c index deb2627..afd2ac0 100644 --- a/main.c +++ b/main.c @@ -5,12 +5,17 @@ #include "zero-pinyin-service-generated.h" #include "../sqlite3_util.h" #include +#include + +static const int MAX_PHRASE_LEN = 16; +static const char *SQLITE3_MEMORY_DB = ":memory:"; typedef struct { GApplication *app; guint owner_id; ZeroPinyinService *interface; sqlite3 *db; + gchar **env; } AppData; static gboolean @@ -209,6 +214,176 @@ on_sigterm_received(gpointer user_data) return G_SOURCE_REMOVE; } +/** + * 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 */ @@ -217,9 +392,14 @@ config_db(AppData *appdata) { gint ri = 0; gboolean rb = FALSE; - static const char *SQLITE3_MEMORY_DB = ":memory:"; sqlite3 *db = NULL; gchar *sql = NULL; + const gchar *home_dir; + gchar *maindb_file = NULL; + gchar *userdb_file = NULL; + + home_dir = get_home_dir(appdata); + ri = sqlite3_open(SQLITE3_MEMORY_DB, &db); if (ri != SQLITE_OK) { g_warning("sqlite3_open :memory: db failed, query will not work."); @@ -227,9 +407,10 @@ config_db(AppData *appdata) } 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"); + 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); rb = sqlite3_exec_simple(db, sql); if (! rb) { g_warning("attach maindb failed, query will not work."); @@ -237,8 +418,9 @@ config_db(AppData *appdata) } sqlite3_free(sql); - /* TODO make db path configurable */ - sql = sqlite3_mprintf("ATTACH %Q AS userdb", "/home/sylecn/.cache/ibus/pinyin/user-1.0.db"); + userdb_file = get_userdb_file_create(home_dir); + sql = sqlite3_mprintf("ATTACH %Q AS userdb", userdb_file); + g_free(userdb_file); rb = sqlite3_exec_simple(db, sql); if (! rb) { g_warning("attach userdb failed, query will not work."); @@ -246,13 +428,7 @@ config_db(AppData *appdata) } 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; - } + init_userdb(db, "userdb", appdata); appdata->db = db; return; @@ -287,6 +463,7 @@ on_startup(GApplication *app, AppData *appdata) { g_message("zero-pinyin-service startup()"); + appdata->env = g_get_environ(); config_db(appdata); config_dbus_service(appdata); setup_sigint_sigterm_handler(appdata); @@ -313,6 +490,9 @@ on_shutdown(GApplication *app, sqlite3_close(appdata->db); appdata->db = NULL; } + if (appdata->env != NULL) { + g_strfreev(appdata->env); + } } /** diff --git a/meson.build b/meson.build index 323b85e..29e08f9 100644 --- a/meson.build +++ b/meson.build @@ -1,6 +1,6 @@ # -*- mode: conf -*- project('zero-pinyin-service', ['c', 'cpp'], - version: '0.6.1', + version: '0.7.0', license: 'GPL', default_options: [ 'warning_level=2', diff --git a/operational b/operational index 9a87f82..9f6b387 100644 --- a/operational +++ b/operational @@ -1,8 +1,18 @@ * COMMENT -*- mode: org -*- #+Date: 2019-04-05 -Time-stamp: <2019-04-17> +Time-stamp: <2019-08-31> #+STARTUP: content * notes :entry: +** 2019-08-31 ibus-pinyin userdb inference notice. +zero-pinyin-service reuse ibus-pinyin's userdb at +~/.cache/ibus/pinyin/user-1.0.db + +This is generally not a problem. But if ibus-pinyin changed their table schema +in the future, zero-pinyin-service may require update. + +zero-pinyin-service also store user phrase in this db. So user phrases are +shared between zero-pinyin and ibus-pinyin. + ** 2019-04-05 zero-pinyin-service file structure :doc: - zero-pinyin-service - main.c @@ -42,9 +52,50 @@ Time-stamp: <2019-04-17> * later :entry: * current :entry: ** +** 2019-08-31 how to format C code? do it before git commit. +see ~/c/gtk-im-module/, it uses myastyle-pre-commit-check in git pre-commit +~/bin/myastyle-pre-commit-check + +** 2019-08-31 honor XDG cache dir. +~/.cache/ibus ** 2019-04-17 make flags configurable at runtime. - add dbus method to set flags. - make the method work. use gobject property maybe. - set default flags to my flags. reflect this in UI/config file. * done :entry: +** 2019-08-31 choose maindb like my patched libpyzy. +- here is patched libpyzy maindb logic: + files.push_back (m_user_data_dir + "/main.db"); + files.push_back (PKGDATADIR"/db/local.db"); + files.push_back (PKGDATADIR"/db/open-phrase.db"); + files.push_back (PKGDATADIR"/db/android.db"); + return first_existing_file (files); + + m_user_data_dir default is ~/.cache/ibus/pinyin/ + PKGDATADIR default is /usr/share/pyzy/ + + in zero-pinyin-service, just use the first existing file: + ~/.cache/ibus/pinyin/main.db + /usr/share/pyzy/db/open-phrase.db + /usr/share/pyzy/db/android.db + +- should I reuse the ibus-pinyin userdb file? + ~/.cache/ibus/pinyin/user-1.0.db + + yes. ibus-pinyin is not going away. + + DONE document this behavior in zero-el and zero-pinyin-service. + +- init_userdb() + sqlite3_mprintf() + https://www.sqlite.org/c3ref/mprintf.html + + additional non-standard formats (%q, %Q, %w, and %z). + | | in | out | used for | + |----+------+---------+----------------------------------------------------------| + | %q | ab'c | ab''c | SQL string literal | + | %Q | ab'c | 'ab''c' | SQL string literal | + | %w | ab"c | ab""c | SQL identifier name | + | %z | abc | abc | like %s, but sqlite3_free() is called on param after use | + * wontfix :entry: -- GitLab