Commit a499f01e authored by Yuanle Song's avatar Yuanle Song
Browse files

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.
parent f0977ae0
Loading
Loading
Loading
Loading

.gitignore

0 → 100644
+2 −0
Original line number Diff line number Diff line
DEST/
*.deb

create-deb.sh

0 → 100755
+47 −0
Original line number Diff line number Diff line
#!/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 <sylecn@gmail.com>" \
    --deb-priority optional \
    --url "https://gitlab.emacsos.com/sylecn/zero-pinyin-service" \
    -C "$DEST" .
+193 −13
Original line number Diff line number Diff line
@@ -5,12 +5,17 @@
#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:";

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);
	}
}

/**
+1 −1
Original line number Diff line number Diff line
# -*- mode: conf -*-
project('zero-pinyin-service', ['c', 'cpp'],
  version: '0.6.1',
  version: '0.7.0',
  license: 'GPL',
  default_options: [
    'warning_level=2',
+52 −1
Original line number Diff line number Diff line
* 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: