diff options
Diffstat (limited to 'src-worddic/record.c')
-rw-r--r-- | src-worddic/record.c | 2074 |
1 files changed, 2074 insertions, 0 deletions
diff --git a/src-worddic/record.c b/src-worddic/record.c new file mode 100644 index 0000000..66e85f3 --- /dev/null +++ b/src-worddic/record.c @@ -0,0 +1,2074 @@ +/* + * 学習の履歴などを管理するためのデータベース + * 文字列(xstr)をキーにして高速に行(row)を検索することができる. + * 複数のセクションをもつことができ,学習の違うフェーズなどに対応する + * (セクション * 文字列 -> 行) + * 各行は文字列か数を持つ配列になっている + * + * 「パトリシア・トライ」というデータ構造を使用している。 + * 自然言語の検索などを扱っている教科書を参照のこと + */ +/* + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/* + * Funded by IPA未踏ソフトウェア創造事業 2002 1/18 + * Funded by IPA未踏ソフトウェア創造事業 2005 + * Copyright (C) 2005 YOSHIDA Yuichi + * Copyright (C) 2000-2006 TABATA Yusuke + * Copyright (C) 2000-2003 UGAWA Tomoharu + * Copyright (C) 2001-2002 TAKAI Kosuke + */ +/* + * パーソナリティ""は匿名パーソナリティであり, + * ファイルへの読み書きは行わない. + */ +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "config.h" +#include <anthy/anthy.h> +#include <anthy/dic.h> +#include <anthy/alloc.h> +#include <anthy/conf.h> +#include <anthy/ruleparser.h> +#include <anthy/record.h> +#include <anthy/logger.h> +#include <anthy/prediction.h> + +#include "dic_main.h" +#include "dic_personality.h" + +/* 個人辞書をセーブするファイル名のsuffix */ +#define ENCODING_SUFFIX ".utf8" + + +enum val_type { + RT_EMPTY, RT_VAL, RT_XSTR, RT_XSTRP +}; + +/* 値 */ +struct record_val { + enum val_type type; + union { + xstr str; + int val; + xstr* strp; + } u; +}; + +/* 行 */ +struct record_row { + xstr key; + int nr_vals; + struct record_val *vals; +}; + +/* trie node管理用 */ +struct trie_node { + struct trie_node *l; + struct trie_node *r; + int bit; + struct record_row row; + struct trie_node *lru_prev, *lru_next; /* 両端ループ */ + int dirty; /* LRU のための used, sused ビット */ +}; + +/* trie treeのroot */ +struct trie_root { + struct trie_node root; + allocator node_ator; +}; + +#define LRU_USED 0x01 +#define LRU_SUSED 0x02 +#define PROTECT 0x04 /* 差分書き出し時に使う(LRUとは関係ない) + * 差分書き出しでは、ファイルに書き出す前に + * ファイル上に他のプロセスが記録した更新を + * 読み込む。それによって、これから追加しよ + * うとするノードが消されるのを防ぐ + */ +/* + * LRU: + * USED: メモリ上で使われた + * SUSED: 保存された used ビット + * + * LRUリスト上では、 USED は必ずリスト先頭に並んでいるが、 SUSED は + * フラグなしのノードと混在している可能性がある。 + * + * n個を残すように指定された時の動作 + * 1. used > n + * LRU リストの先頭から n 番目以降を消す + * 2. used + sused > n + * used -> 残す + * sused -> sused フラグを落す + * それ以外 -> 消す + * 3. それ以外 + * 全て残す + * ファイルに書き出す時に、 used || sused -> sused として書き出す + */ + +/** セクション */ +struct record_section { + const char *name; + struct trie_root cols; + struct record_section *next; + int lru_nr_used, lru_nr_sused; /* LRU 用 */ +}; + +/** データベース */ +struct record_stat { + struct record_section section_list; /* sectionのリスト*/ + struct record_section *cur_section; + struct trie_root xstrs; /* xstr を intern するための trie */ + struct trie_node *cur_row; + int row_dirty; /* cur_row が保存の必要があるか */ + int encoding; + /**/ + int is_anon; + const char *id; /* パーソナリティのid */ + char *base_fn; /* 基本ファイル 絶対パス */ + char *journal_fn; /* 差分ファイル 絶対パス */ + /**/ + time_t base_timestamp; /* 基本ファイルのタイムスタンプ */ + int last_update; /* 差分ファイルの最後に読んだ位置 */ + time_t journal_timestamp; /* 差分ファイルのタイムスタンプ */ +}; + +/* 差分が100KB越えたら基本ファイルへマージ */ +#define FILE2_LIMIT 102400 + + +/* + * xstr の intern: + * 個人ごと( record_stat ごと)に文字列を intern する。これは、 + * メモリの節約の他に、データベースの flush 時にデータベースに + * 由来する xstr が無効になるのを防ぐ目的がある。 + * したがって、データベースの flush 時でも xstr の intern 用 + * のデータベース xstrs はそのまま保存する。 + * + * xstrs: xstr の intern 用のデータベース + * row の key を intern された xstr として使う。 + * row に value は持たない。 + * (将来的には参照カウンタをつけてもいいかも) + * 参照: intern_xstr() + */ + +/* + * 差分書き出し: + * データベースの保存、複数の anthy ライブラリをリンクした + * プロセスの学習履歴の同期のために、学習履歴の更新情報を + * 逐一ファイルに書き出す。 + * + * ・基本ファイル 古い anthy の学習履歴と同じ形式。 + * 差分情報を適用する元となるファイル。 + * 基本的には起動時だけに読み込む。 + * このプログラム中でファイル1,baseと呼ぶことがある。 + * ・差分ファイル 基本ファイルに対する更新情報。 + * データベースに対する更新がコミットされるたびに + * 読み書きされる。 + * このプログラム中でファイル2,journalと呼ぶことがある。 + * 基本方針: + * データベースに対する更新がコミットされると、まず差分ファイル + * に他のプロセスが追加した更新情報を読み込み、その後に自分の + * コミットした更新を差分ファイルに書き出す。 + * これらはロックファイルを用いてアトミックに行われる。また、 + * 基本ファイル、差分ファイルとも、ロックを取っている間しか + * オープンしていてはいけない。 + * 追加と削除: + * 追加はすでにメモリ上で更新された row をコミットによって + * メモリに書き出すため、 + * 1. コミット対象 row 以外を差分ファイルの情報で + * 2. コミット対象 row を差分ファイルに書き出し + * とする。削除はまだメモリ上に row が残っている状態でコミット + * が行われる(削除要求をコミットとして扱う)ため、 + * 1. 削除の情報を差分ファイルに書き出し + * 2. 差分ファイルの読み込みにより削除要求も実行する + * とする。 + * 基本ファイルの更新: + * 差分ファイルがある程度肥大化すると、差分ファイルの情報を + * 基本ファイルに反映して差分ファイルを空にする。 + * 更新するプロセス: + * 差分ファイルに書き出しを行った後、差分ファイルの大きさを調べ、 + * 肥大化していれば、そのときのメモリ上のデータベース(これには + * 全ての差分ファイルの更新が適用されている)を基本ファイルに + * 書き出す。 + * それ以外のプロセス: + * 差分ファイルを読む前に、基本ファイルが更新されているかを + * ファイルのタイムスタンプで調べ、更新されていれば、コミット + * された更新情報を直ちに更新ファイルに追加し、メモリ上の + * データベースを flush した後基本ファイル、差分ファイルを + * 読み込み直す。 + * データベースの flush により、 + * ・cur_row が無効になる (NULL になる) + * ・cur_section の有効性は保存される(sectionは解放しない) + * ・xstr は intern していれば保存される + * (すべての xstr は intern されているはず) + * 結局、次の様になる: + * if (基本ファイルが更新されている) { + * 差分ファイルへコミットされた更新を書き出す; + * データベースのフラッシュ; + * 基本ファイルの読込と差分ファイルの最終読込位置クリア; + * 差分ファイルの読込と差分ファイルの最終読込位置更新; + * } else { + * if (追加) { + * 差分ファイルの読込と差分ファイルの最終読込位置更新; + * 差分ファイルへの書き出し; + * } else { + * 差分ファイルへの書き出し; + * 差分ファイルの読込と差分ファイルの最終読込位置更新; + * } + * } + * if (差分ファイルが大きい) { + * 基本ファイルへの書き出し; + * 差分ファイルのクリア; + * } + */ + +static allocator record_ator; + +/* trie操作用 */ +static void init_trie_root(struct trie_root *n); +static int trie_key_nth_bit(xstr* key, int n); +static int trie_key_first_diff_bit_1byte(xchar c1, xchar c2); +static int trie_key_first_diff_bit(xstr *k1, xstr *k2); +static int trie_key_cmp(xstr *k1, xstr *k2); +static void trie_key_dup(xstr *dst, xstr *src); +static void trie_row_init(struct record_row *rc); +static void trie_row_free(struct record_row *rc); +static struct trie_node *trie_find(struct trie_root *root, xstr *key); +static struct trie_node *trie_insert(struct trie_root *root, xstr *key, + int dirty, int *nr_used, int *nr_sused); +static void trie_remove(struct trie_root *root, xstr *key, + int *nr_used, int *nr_sused); +static struct trie_node *trie_first(struct trie_root *root); +static struct trie_node *trie_next(struct trie_root *root, + struct trie_node *cur); +static void trie_remove_all(struct trie_root *root, + int *nr_used, int *nr_sused); +static void trie_remove_old(struct trie_root *root, int count, + int* nr_used, int* nr_sused); +static void trie_mark_used(struct trie_root *root, struct trie_node *n, + int *nr_used, int *nr_sused); + + +/* + * トライの実装 + * struct trie_nodeのうちrow以外の部分とrow.keyを使用 + * 削除の時はtrie_row_freeを使ってrowの内容を解放 + */ + +#define PUTNODE(x) ((x) == &root->root ? printf("root\n") : anthy_putxstrln(&(x)->row.key)) +static int +debug_trie_dump(FILE* fp, struct trie_node* n, int encoding) +{ + int cnt = 0; + char buf[1024]; + + if (n->l->bit > n->bit) { + cnt = debug_trie_dump(fp, n->l, encoding); + } else { + if (n->l->row.key.len == -1) { + if (fp) { + fprintf(fp, "root\n"); + } + } else { + if (fp) { + anthy_sputxstr(buf, &n->l->row.key, encoding); + fprintf(fp, "%s\n", buf); + } + cnt = 1; + } + } + + if (n->r->bit > n->bit) { + return cnt + debug_trie_dump(fp, n->r, encoding); + } else { + if (n->r->row.key.len == -1) { + if(fp) { + fprintf(fp, "root\n"); + } + } else { + if(fp) { + anthy_sputxstr(buf, &n->r->row.key, encoding); + fprintf(fp, "%s\n", buf); + } + return cnt + 1; + } + } + + return cnt; +} + +static void +init_trie_root(struct trie_root *root) +{ + struct trie_node* n; + root->node_ator = anthy_create_allocator(sizeof(struct trie_node), NULL); + n = &root->root; + n->l = n; + n->r = n; + n->bit = 0; + n->lru_next = n; + n->lru_prev = n; + n->dirty = 0; + trie_row_init(&n->row); + n->row.key.len = -1; +} + +/* + * bit0: 0 + * bit1: headのキーだけ0 + * bit2: 文字列のビット0 + * bit3: 文字列のビット1 + * ... + * 文字列長を越えると0 + */ +static int +trie_key_nth_bit(xstr* key, int n) +{ + switch (n) { + case 0: + return 0; + case 1: + return key->len + 1; /* key->len == -1 ? 0 : non-zero */ + default: + { + int pos; + n -= 2; + pos = n / (sizeof(xchar) << 3); + if (pos >= key->len) { + return 0; + } + return key->str[pos] & (1 << (n % (sizeof(xchar) << 3))); + } + } +} + +/* c1 == c2 では呼んではいけない */ +static int +trie_key_first_diff_bit_1byte(xchar c1, xchar c2) +{ + int i; + int ptn; + for (i = 0, ptn = c1 ^ c2; !(ptn & 1); i++, ptn >>= 1 ) + ; + return i; +} + +/* + * k1 == k2 では呼んではいけない + * ki->str[0 .. (ki->len - 1)]に0はないと仮定 + */ +#define MIN(a,b) ((a)<(b)?(a):(b)) +static int +trie_key_first_diff_bit(xstr *k1, xstr *k2) +{ + int len; + int i; + + len = MIN(k1->len, k2->len); + if (len == -1) { + return 1; + } + for ( i = 0 ; i < len ; i++ ){ + if (k1->str[i] != k2->str[i]) { + return (2 + (i * (sizeof(xchar) << 3)) + + trie_key_first_diff_bit_1byte(k1->str[i], k2->str[i])); + } + } + if (k1->len < k2->len) { + return (2 + (i * (sizeof(xchar) << 3)) + + trie_key_first_diff_bit_1byte(0, k2->str[i])); + } else { + return (2 + (i * (sizeof(xchar) << 3)) + + trie_key_first_diff_bit_1byte(k1->str[i], 0)); + } +} +#undef MIN + +static int +trie_key_cmp(xstr *k1, xstr *k2) +{ + if (k1->len == -1 || k2->len == -1) { + return k1->len - k2->len; + } + return anthy_xstrcmp(k1, k2); +} + +static void +trie_key_dup(xstr *dst, xstr *src) +{ + dst->str = anthy_xstr_dup_str(src); + dst->len = src->len; +} + +/* + * 見つからなければ 0 + */ +static struct trie_node * +trie_find(struct trie_root *root, xstr *key) +{ + struct trie_node *p; + struct trie_node *q; + + p = &root->root; + q = p->l; + while (p->bit < q->bit) { + p = q; + q = trie_key_nth_bit(key, p->bit) ? p->r : p->l; + } + return trie_key_cmp(&q->row.key,key) ? NULL : q; +} + +/* + * 最長マッチのための補助関数 + * key で探索して、始めて一致しなくなったノードを返す。 + */ +static struct trie_node * +trie_find_longest (struct trie_root* root, xstr *key) +{ + struct trie_node *p; + struct trie_node *q; + + p = &root->root; + q = p->l; + while (p->bit < q->bit) { + p = q; + q = trie_key_nth_bit(key, p->bit) ? p->r : p->l; + } + + return q; +} + +/* + * 追加したノードを返す + * すでに同じキーをもつノードがあるときは、追加せずに0を返す + */ +static struct trie_node * +trie_insert(struct trie_root *root, xstr *key, + int dirty, int *nr_used, int *nr_sused) +{ + struct trie_node *n; + struct trie_node *p; + struct trie_node *q; + int i; + + p = &root->root; + q = p->l; + while (p->bit < q->bit) { + p = q; + q = trie_key_nth_bit(key, p->bit) ? p->r : p->l; + } + if (trie_key_cmp(&q->row.key,key) == 0) { + /* USED > SUSED > 0 で強い方を残す */ + if (dirty == LRU_USED) { + trie_mark_used(root, q, nr_used, nr_sused); + } else if (q->dirty == 0) { + q->dirty = dirty; + } + return 0; + } + i = trie_key_first_diff_bit(&q->row.key, key); + p = &root->root; + q = p->l; + while (p->bit < q->bit && i > q->bit) { + p = q; + q = trie_key_nth_bit(key, p->bit) ? p->r : p->l; + } + n = anthy_smalloc(root->node_ator); + trie_row_init(&n->row); + trie_key_dup(&n->row.key, key); + n->bit = i; + if (trie_key_nth_bit(key, i)) { + n->l = q; + n->r = n; + } else { + n->l = n; + n->r = q; + } + if (p->l == q) { + p->l = n; + } else { + p->r = n; + } + + /* LRU の処理 */ + if (dirty == LRU_USED) { + root->root.lru_next->lru_prev = n; + n->lru_prev = &root->root; + n->lru_next = root->root.lru_next; + root->root.lru_next = n; + (*nr_used)++; + } else { + root->root.lru_prev->lru_next = n; + n->lru_next = &root->root; + n->lru_prev = root->root.lru_prev; + root->root.lru_prev = n; + if (dirty == LRU_SUSED) { + (*nr_sused)++; + } + } + n->dirty = dirty; + return n; +} + +/* + * ノードを見つけると削除する + * 内部でtrie_row_freeを呼び、キーを含むデータ部分をfreeする + * + * データとノードを削除する。 + * 削除対象のデータは削除対象のノードに格納されているとは + * 限らないことに注意。 + * 1. 削除対象の葉を持つノードに削除対象の葉が含まれているとき + * 削除対象のノードは、子への枝のうち、生きのこる枝を親に渡して死ぬ + * 2. 削除対象の葉を持つノードの祖先に削除対象の葉が含まれているとき + * 1. に加えて、削除対象の葉をもつノードを殺して、代わりに削除 + * 対象のノードを削除対象の葉をもつノードの位置に移動させ生かす + */ +static void +trie_remove(struct trie_root *root, xstr *key, + int *nr_used, int *nr_sused) +{ + struct trie_node *p; + struct trie_node *q; + struct trie_node **pp = NULL; /* gcc の warning 回避 */ + struct trie_node **qq; + p = &root->root; + qq = &p->l; + q = *qq; + while (p->bit < q->bit) { + pp = qq; + p = q; + qq = trie_key_nth_bit(key,p->bit) ? &p->r : &p->l; + q = *qq; + } + if (trie_key_cmp(&q->row.key, key) != 0) { + return ; + } + if (p != q) { + /* case 2. */ + struct trie_node *r; + struct trie_node *s; + r = &root->root; + s = r->l; + while (s != q) { + r = s; + s = trie_key_nth_bit(key, r->bit) ? r->r : r->l; + } + *pp = (p->r == q) ? p->l : p->r; + p->l = q->l; + p->r = q->r; + p->bit = q->bit; + if (trie_key_nth_bit(key, r->bit)) { + r->r = p; + } else { + r->l = p; + } + p = q; + } else { + *pp = (p->r == q) ? p->l : p->r; + } + p->lru_prev->lru_next = p->lru_next; + p->lru_next->lru_prev = p->lru_prev; + if (p->dirty == LRU_USED) { + (*nr_used)--; + } else if (p->dirty == LRU_SUSED) { + (*nr_sused)--; + } + trie_row_free(&p->row); + anthy_sfree(root->node_ator, p); +} + +/* head以外のノードがなければ 0 を返す */ +static struct trie_node * +trie_first (struct trie_root *root) +{ + return root->root.lru_next == &root->root ? + NULL : root->root.lru_next; +} + +/* 次のノードがなければ 0 を返す */ +static struct trie_node * +trie_next (struct trie_root *root, + struct trie_node *cur) +{ + return cur->lru_next == &root->root ? 0 : cur->lru_next; +} + +/* + * head以外全てのノードを削除する + * 内部でtrie_row_freeを呼び、キーを含むデータ部分をfreeする + */ +static void +trie_remove_all (struct trie_root *root, + int *nr_used, int *nr_sused) +{ + struct trie_node* p; + for (p = root->root.lru_next; p != &root->root; p = p->lru_next) { + trie_row_free(&p->row); + } + anthy_free_allocator(root->node_ator); + init_trie_root(root); + *nr_used = 0; + *nr_sused = 0; +} + +/* + * LRU リストの先頭から count 番目までを残して残りを解放する + */ +static void +trie_remove_old (struct trie_root *root, int count, + int *nr_used, int *nr_sused) +{ + struct trie_node *p; + struct trie_node *q; + + if (*nr_used > count) { + for (p = root->root.lru_next; count; count--, p = p->lru_next) + ; + /* p から head までを消す */ + for ( ; p != &root->root; p = q) { + q = p->lru_next; + trie_remove(root, &p->row.key, nr_used, nr_sused); + } + } else if (*nr_used + *nr_sused > count) { + for (p = root->root.lru_next; p->dirty == LRU_USED; p = p->lru_next) + ; + /* + * p から root まで sused -> dirty := 0 + * それ以外 -> 消す + */ + for ( ; p != &root->root; p = q) { + q = p->lru_next; + if (p->dirty == LRU_SUSED) { + p->dirty = 0; + } else { + trie_remove(root, &p->row.key, nr_used, nr_sused); + } + } + *nr_sused = 0; + } +} + +static void +trie_mark_used (struct trie_root *root, struct trie_node *n, + int *nr_used, int *nr_sused) +{ + switch(n->dirty) { + case LRU_USED: + break; + case LRU_SUSED: + (*nr_sused)--; + /* fall through */ + default: + n->dirty = LRU_USED; + (*nr_used)++; + break; + } + n->lru_prev->lru_next = n->lru_next; + n->lru_next->lru_prev = n->lru_prev; + root->root.lru_next->lru_prev = n; + n->lru_next = root->root.lru_next; + root->root.lru_next = n; + n->lru_prev = &root->root; +} + +/* + * トライの実装はここまで + */ + +static xstr * +do_get_index_xstr(struct record_stat *rec) +{ + if (!rec->cur_row) { + return 0; + } + return &rec->cur_row->row.key; +} + +static struct record_section* +do_select_section(struct record_stat *rst, const char *name, int flag) +{ + struct record_section *rsc; + + for (rsc = rst->section_list.next; rsc; rsc = rsc->next) { + if (!strcmp(name, rsc->name)) { + return rsc; + } + } + + if (flag) { + rsc = malloc(sizeof(struct record_section)); + rsc->name = strdup(name); + rsc->next = rst->section_list.next; + rst->section_list.next = rsc; + rsc->lru_nr_used = 0; + rsc->lru_nr_sused = 0; + init_trie_root(&rsc->cols); + return rsc; + } + + return NULL; +} + +static struct trie_node* +do_select_longest_row(struct record_section *rsc, xstr *name) +{ + struct trie_node *mark, *found; + xstr xs; + int i; + + if ((NULL == name) || (NULL == name->str) || (name->len < 1) || (0 == name->str[0])) { + /* 辞書もしくは学習データが壊れていた時の対策 */ + return NULL; + } + + mark = trie_find_longest(&rsc->cols, name); + xs.str = name->str; + for (i = (mark->row.key.len <= name->len) ? mark->row.key.len : name->len; i > 1; i--) { /* 不正なメモリアクセスの修正 */ + /* ルートノードは i == 1 でマッチするので除外 + * trie_key_nth_bit 参照 + */ + xs.len = i; + found = trie_find(&rsc->cols, &xs); + if (found) { + return found; + } + } + return NULL; +} + +static struct trie_node* +do_select_row(struct record_section* rsc, xstr *name, + int flag, int dirty) +{ + struct trie_node *node; + + if (flag) { + node = trie_insert(&rsc->cols, name, dirty, + &rsc->lru_nr_used, &rsc->lru_nr_sused); + if (node) { + node->row.nr_vals = 0; + node->row.vals = 0; + } else { + node = trie_find(&rsc->cols, name); + } + } else { + node = trie_find(&rsc->cols, name); + } + return node; +} + +static void +do_mark_row_used(struct record_section* rsc, struct trie_node* node) +{ + trie_mark_used(&rsc->cols, node, &rsc->lru_nr_used, &rsc->lru_nr_sused); +} + +static void +do_truncate_section(struct record_stat *s, int count) +{ + if (!s->cur_section) { + return; + } + + trie_remove_old(&s->cur_section->cols, count, + &s->cur_section->lru_nr_used, + &s->cur_section->lru_nr_sused); +} + + +static struct trie_node* +do_select_first_row(struct record_section *rsc) +{ + return trie_first(&rsc->cols); +} + +static struct trie_node* +do_select_next_row(struct record_section *rsc, + struct trie_node* node) +{ + return trie_next(&rsc->cols, node); +} + + +static int +do_get_nr_values(struct trie_node *node) +{ + if (!node) + return 0; + return node->row.nr_vals; +} + +static struct record_val * +get_nth_val_ent(struct trie_node *node, int n, int f) +{ + struct record_row *col; + col = &node->row; + if (n < 0) { + return NULL; + } + if (n < do_get_nr_values(node)) { + return &col->vals[n]; + } + if (f) { + int i; + col->vals = realloc(col->vals, sizeof(struct record_val)*(n + 1)); + for (i = col->nr_vals; i < n+1; i++) { + col->vals[i].type = RT_EMPTY; + } + col->nr_vals = n + 1; + return &col->vals[n]; + } + return NULL; +} + +static void +free_val_contents(struct record_val* v) +{ + switch (v->type) { + case RT_XSTR: + anthy_free_xstr_str(&v->u.str); + break; + case RT_XSTRP: + case RT_VAL: + case RT_EMPTY: + default: + break; + } +} + +static void +do_set_nth_value(struct trie_node *node, int nth, int val) +{ + struct record_val *v = get_nth_val_ent(node, nth, 1); + if (!v) { + return ; + } + free_val_contents(v); + v->type = RT_VAL; + v->u.val = val; +} + +static int +do_get_nth_value(struct trie_node *node, int n) +{ + struct record_val *v = get_nth_val_ent(node, n, 0); + if (v && v->type == RT_VAL) { + return v->u.val; + } + return 0; +} + +static xstr* +intern_xstr (struct trie_root* xstrs, xstr* xs) +{ + struct trie_node* node; + int dummy; + + if ((NULL == xs) || (NULL == xs->str) || (xs->len < 1) || (0 == xs->str[0])) { + /* 辞書もしくは学習データが壊れていた時の対策 */ + return NULL; + } + node = trie_find(xstrs, xs); + if (!node) + node = trie_insert(xstrs, xs, 0, &dummy, &dummy); + return &node->row.key; +} + +static void +do_set_nth_xstr (struct trie_node *node, int nth, xstr *xs, + struct trie_root* xstrs) +{ + struct record_val *v = get_nth_val_ent(node, nth, 1); + if (!v){ + return ; + } + free_val_contents(v); + v->type = RT_XSTRP; + v->u.strp = intern_xstr(xstrs, xs); +} + +static void +do_truncate_row (struct trie_node* node, int n) +{ + int i; + if (n < node->row.nr_vals) { + for (i = n; i < node->row.nr_vals; i++) { + free_val_contents(node->row.vals + i); + } + node->row.vals = realloc(node->row.vals, + sizeof(struct record_val)* n); + node->row.nr_vals = n; + } +} + +static void +do_remove_row (struct record_section* rsc, + struct trie_node* node) +{ + xstr* xs; + xs = anthy_xstr_dup(&node->row.key); + trie_remove(&rsc->cols, &node->row.key, + &rsc->lru_nr_used, &rsc->lru_nr_sused); + + anthy_free_xstr(xs); +} + +static xstr * +do_get_nth_xstr(struct trie_node *node, int n) +{ + struct record_val *v = get_nth_val_ent(node, n, 0); + if (v) { + if (v->type == RT_XSTR) { + return &v->u.str; + } else if (v->type == RT_XSTRP) { + return v->u.strp; + } + } + return 0; +} + +static void +lock_record (struct record_stat* rs) +{ + if (rs->is_anon) { + return ; + } + anthy_priv_dic_lock(); +} + +static void +unlock_record (struct record_stat* rs) +{ + if (rs->is_anon) { + return ; + } + anthy_priv_dic_unlock(); +} + +/* 再読み込みの必要があるかをチェックする + * 必要があれば返り値が1になる */ +static int +check_base_record_uptodate(struct record_stat *rst) +{ + struct stat st; + if (rst->is_anon) { + return 1; + } + anthy_check_user_dir(); + if (stat(rst->base_fn, &st) < 0) { + return 0; + } else if (st.st_mtime != rst->base_timestamp) { + return 0; + } + return 1; +} + + +/* + * row format: + * ROW := OPERATION SECTION KEY VALUE* + * OPERATION := "ADD" (追加またはLRU更新) + * "DEL" (削除) + * SECTION := (文字列) + * KEY := TD + * VALUE := TD + * TD := TYPE DATA (空白をあけずに書く) + * TYPE := "S" (xstr) + * "N" (number) + * DATA := (型ごとにシリアライズしたもの) + */ + +static char* +read_1_token (FILE* fp, int* eol) +{ + int c; + char* s; + int in_quote; + int len; + + in_quote = 0; + s = NULL; + len = 0; + while (1) { + c = fgetc(fp); + switch (c) { + case EOF: case '\n': + goto out; + case '\\': + c = fgetc(fp); + if (c == EOF || c == '\n') { + goto out; + } + break; + case '\"': + in_quote = !in_quote; + continue; + case ' ': case '\t': case '\r': + if (in_quote) { + break; + } + if (s != NULL) { + goto out; + } + break; + default: + break; + } + + s = (char*) realloc(s, len + 2); + s[len] = c; + len ++; + } +out: + if (s) { + s[len] = '\0'; + } + *eol = (c == '\n'); + return s; +} + +/* journalからADDの行を読む */ +static void +read_add_row(FILE *fp, struct record_stat* rst, + struct record_section* rsc) +{ + int n; + xstr* xs; + char *token; + int eol; + struct trie_node* node; + + token = read_1_token(fp, &eol); + if (!token) { + return ; + } + + xs = anthy_cstr_to_xstr(/* xstr 型を表す S を読み捨てる */ + token + 1, + rst->encoding); + node = do_select_row(rsc, xs, 1, LRU_USED); + anthy_free_xstr(xs); + free(token); + + if (node->dirty & PROTECT) { + /* 保存すべき row なので、差分ファイルを読み捨てる */ + while (!eol) { + free(read_1_token(fp, &eol)); + } + return ; + } + + n = 0; + while (!eol) { + token = read_1_token(fp, &eol); + if (token) { + switch(*token) { + /* String 文字列 */ + case 'S': + { + xstr* xs; + xs = anthy_cstr_to_xstr(token + 1, rst->encoding); + do_set_nth_xstr(node, n, xs, &rst->xstrs); + anthy_free_xstr(xs); + } + break; + /* Number 数値 */ + case 'N': + do_set_nth_value(node, n, atoi(token + 1)); + break; + } + free(token); + n++; + } + } + do_truncate_row(node, n); +} + +/* journalからDELの行を読む */ +static void +read_del_row(FILE *fp, struct record_stat* rst, + struct record_section* rsc) +{ + struct trie_node* node; + char* token; + xstr* xs; + int eol; + + token = read_1_token(fp, &eol); + if (!token) { + return ; + } + + xs = anthy_cstr_to_xstr(/* xstr 型を表す S を読み飛ばす */ + token + 1, + rst->encoding); + if ((node = do_select_row(rsc, xs, 0, 0)) != NULL) { + do_remove_row(rsc, node); + } + anthy_free_xstr(xs); + free(token); +} + +/** 差分ファイルから1行読み込む */ +static void +read_1_row(struct record_stat* rst, FILE* fp, char *op) +{ + char* sec_name; + struct record_section* rsc; + int eol; + + sec_name = read_1_token(fp, &eol); + if (!sec_name || eol) { + free(sec_name); + return ; + } + rsc = do_select_section(rst, sec_name, 1); + free(sec_name); + if (!rsc) { + return ; + } + + if (strcmp(op, "ADD") == 0) { + read_add_row(fp, rst, rsc); + } else if (strcmp(op, "DEL") == 0) { + read_del_row(fp, rst, rsc); + } +} + +/* + * journal(差分)ファイルを読む + */ +static void +read_journal_record(struct record_stat* rs) +{ + FILE* fp; + struct stat st; + + if (rs->is_anon) { + return ; + } + fp = fopen(rs->journal_fn, "r"); + if (fp == NULL) { + return; + } + if (fstat(fileno(fp), &st) == -1) { + fclose(fp); + return ; + } + if (st.st_size < rs->last_update) { + /* ファイルサイズが小さくなっているので、 + * 最初から読み込む */ + fseek(fp, 0, SEEK_SET); + } else { + fseek(fp, rs->last_update, SEEK_SET); + } + rs->journal_timestamp = st.st_mtime; + while (!feof(fp)) { + char *op; + int eol; + op = read_1_token(fp, &eol); + if (op && !eol) { + read_1_row(rs, fp, op); + } + free(op); + } + rs->last_update = ftell(fp); + fclose(fp); +} + +static void +write_string(FILE* fp, const char* str) +{ + fprintf(fp, "%s", str); +} + +/* ダブルクオートもしくはバックスラッシュにバックスラッシュを付ける */ +static void +write_quote_string(FILE* fp, const char* str) +{ + const char* p; + + for (p = str; *p; p++) { + if (*p == '\"' || *p == '\\') { + fputc('\\', fp); + } + fputc(*p, fp); + } +} + +static void +write_quote_xstr(FILE* fp, xstr* xs, int encoding) +{ + char* buf; + + if ((NULL == xs) || (NULL == xs->str) || (xs->len < 1) || (0 == xs->str[0])) { + /* 辞書もしくは学習データが壊れていた時の対策 */ + return; + } + + buf = (char*) alloca(xs->len * 6 + 2); /* EUC またはUTF8を仮定 */ + anthy_sputxstr(buf, xs, encoding); + write_quote_string(fp, buf); +} + +static void +write_number(FILE* fp, int x) +{ + fprintf(fp, "%d", x); +} + +/* journalに1行追記する */ +static void +commit_add_row(struct record_stat* rst, + const char* sname, struct trie_node* node) +{ + FILE* fp; + int i; + + fp = fopen(rst->journal_fn, "a"); + if (fp == NULL) { + return; + } + + write_string(fp, "ADD \""); + write_quote_string(fp, sname); + write_string(fp, "\" S\""); + write_quote_xstr(fp, &node->row.key, rst->encoding); + write_string(fp, "\""); + + for (i = 0; i < node->row.nr_vals; i++) { + switch (node->row.vals[i].type) { + case RT_EMPTY: + write_string(fp, " E"); + break; + case RT_VAL: + write_string(fp, " N"); + write_number(fp, node->row.vals[i].u.val); + break; + case RT_XSTR: + write_string(fp, " S\""); + write_quote_xstr(fp, &node->row.vals[i].u.str, rst->encoding); + write_string(fp, "\""); + break; + case RT_XSTRP: + write_string(fp, " S\""); + write_quote_xstr(fp, node->row.vals[i].u.strp, rst->encoding); + write_string(fp, "\""); + break; + } + } + write_string(fp, "\n"); + rst->last_update = ftell(fp); + fclose(fp); +} + +/* 全ての row を解放する */ +static void +clear_record(struct record_stat* rst) +{ + struct record_section *rsc; + for (rsc = rst->section_list.next; rsc; rsc = rsc->next) { + trie_remove_all(&rsc->cols, &rsc->lru_nr_used, &rsc->lru_nr_sused); + } + rst->cur_row = NULL; +} + +/* 基本ファイルを読む */ +static void +read_session(struct record_stat *rst) +{ + char **tokens; + int nr; + int in_section = 0; + while (!anthy_read_line(&tokens, &nr)) { + xstr *xs; + int i; + int dirty = 0; + struct trie_node* node; + + if (!strcmp(tokens[0], "---") && nr > 1) { + /* セクションの切れ目 */ + in_section = 1; + rst->cur_section = do_select_section(rst, tokens[1], 1); + goto end; + } + if (!in_section || nr < 2) { + /* セクションが始まってない or 行が不完全 */ + goto end; + } + /* 行頭のLRUのマークを読む */ + if (tokens[0][0] == '-') { + dirty = 0; + } else if (tokens[0][0] == '+') { + dirty = LRU_SUSED; + } + /* 次にindex */ + xs = anthy_cstr_to_xstr(&tokens[0][1], rst->encoding); + node = do_select_row(rst->cur_section, xs, 1, dirty); + anthy_free_xstr(xs); + if (!node) { + goto end; + } + rst->cur_row = node; + /**/ + for (i = 1; i < nr; i++) { + if (tokens[i][0] == '"') { + char *str; + str = strdup(&tokens[i][1]); + str[strlen(str) - 1] = 0; + xs = anthy_cstr_to_xstr(str, rst->encoding); + free(str); + do_set_nth_xstr(rst->cur_row, i-1, xs, &rst->xstrs); + anthy_free_xstr(xs); + }else if (tokens[i][0] == '*') { + /* EMPTY entry */ + get_nth_val_ent(rst->cur_row, i-1, 1); + } else { + do_set_nth_value(rst->cur_row, i-1, atoi(tokens[i])); + } + } + end: + anthy_free_line(); + } +} + +/* いまのデータベースを解放した後にファイルから読み込む */ +static void +read_base_record(struct record_stat *rst) +{ + struct stat st; + if (rst->is_anon) { + clear_record(rst); + return ; + } + anthy_check_user_dir(); + + if (anthy_open_file(rst->base_fn) == -1) { + return ; + } + + clear_record(rst); + read_session(rst); + anthy_close_file(); + if (stat(rst->base_fn, &st) == 0) { + rst->base_timestamp = st.st_mtime; + } + rst->last_update = 0; +} + +static FILE * +open_tmp_in_recorddir(void) +{ + char *pn; + const char *hd; + const char *sid; + sid = anthy_conf_get_str("SESSION-ID"); + hd = anthy_conf_get_str("HOME"); + pn = alloca(strlen(hd)+strlen(sid) + 10); + sprintf(pn, "%s/.anthy/%s", hd, sid); + return fopen(pn, "w"); +} + +/* + * 一時ファイルからbaseファイルへrenameする + */ +static void +update_file(const char *fn) +{ + const char *hd; + char *tmp_fn; + const char *sid; + hd = anthy_conf_get_str("HOME"); + sid = anthy_conf_get_str("SESSION-ID"); + tmp_fn = alloca(strlen(hd)+strlen(sid) + 10); + + sprintf(tmp_fn, "%s/.anthy/%s", hd, sid); + if (rename(tmp_fn, fn)){ + anthy_log(0, "Failed to update record file %s -> %s.\n", tmp_fn, fn); + } +} + +/* カラムを保存する */ +static void +save_a_row(FILE *fp, struct record_stat* rst, + struct record_row *c, int dirty) +{ + int i; + char *buf = alloca(c->key.len * 6 + 2); + /* LRUのマークを出力 */ + if (dirty == 0) { + fputc('-', fp); + } else { + fputc('+', fp); + } + anthy_sputxstr(buf, &c->key, rst->encoding); + /* index を出力 */ + fprintf(fp, "%s ", buf); + /**/ + for (i = 0; i < c->nr_vals; i++) { + struct record_val *val = &c->vals[i]; + switch (val->type) { + case RT_EMPTY: + fprintf(fp, "* "); + break; + case RT_XSTR: + /* should not happen */ + fprintf(fp, "\""); + write_quote_xstr(fp, &val->u.str, rst->encoding); + fprintf(fp, "\" "); + abort(); + break; + case RT_XSTRP: + fprintf(fp, "\""); + write_quote_xstr(fp, val->u.strp, rst->encoding); + fprintf(fp, "\" "); + break; + case RT_VAL: + fprintf(fp, "%d ", val->u.val); + break; + default: + anthy_log(0, "Faild to save an unkonwn record. (in record.c)\n"); + break; + } + } + fprintf(fp, "\n"); +} + +static void +update_base_record(struct record_stat* rst) +{ + struct record_section *sec; + struct trie_node *col; + FILE *fp; + struct stat st; + + /* 一時ファイルを作ってrecordを書き出す */ + anthy_check_user_dir(); + fp = open_tmp_in_recorddir(); + if (!fp) { + anthy_log(0, "Failed to open temporaly session file.\n"); + return ; + } + /* 各セクションに対して */ + for (sec = rst->section_list.next; + sec; sec = sec->next) { + if (!trie_first(&sec->cols)) { + /*このセクションは空*/ + continue; + } + /* セクション境界の文字列 */ + fprintf(fp, "--- %s\n", sec->name); + /* 各カラムを保存する */ + for (col = trie_first(&sec->cols); col; + col = trie_next(&sec->cols, col)) { + save_a_row(fp, rst, &col->row, col->dirty); + } + } + fclose(fp); + + /* 本来の名前にrenameする */ + update_file(rst->base_fn); + + if (stat(rst->base_fn, &st) == 0) { + rst->base_timestamp = st.st_mtime; + } + /* journalファイルを消す */ + unlink(rst->journal_fn); + rst->last_update = 0; +} + +static void +commit_del_row(struct record_stat* rst, + const char* sname, struct trie_node* node) +{ + FILE* fp; + + fp = fopen(rst->journal_fn, "a"); + if (fp == NULL) { + return; + } + write_string(fp, "DEL \""); + write_quote_string(fp, sname); + write_string(fp, "\" S\""); + write_quote_xstr(fp, &node->row.key, rst->encoding); + write_string(fp, "\""); + write_string(fp, "\n"); + fclose(fp); +} + +/* + * sync_add: ADD の書き込み + * sync_del_and_del: DEL の書き込みと削除 + * どちらも書き込みの前に、他のプロセスによってディスク上に保存された + * 更新をメモリ上に読み込む。 + * このとき、データベースをフラッシュする可能性もある。データベースの + * フラッシュがあると、 cur_row と全ての xstr は無効になる。 + * ただし、 cur_section の有効性は保存される。 + */ +static void +sync_add(struct record_stat* rst, struct record_section* rsc, + struct trie_node* node) +{ + lock_record(rst); + if (check_base_record_uptodate(rst)) { + node->dirty |= PROTECT; + /* 差分ファイルだけ読む */ + read_journal_record(rst); + node->dirty &= ~PROTECT; + commit_add_row(rst, rsc->name, node); + } else { + /* 再読み込み */ + commit_add_row(rst, rsc->name, node); + read_base_record(rst); + read_journal_record(rst); + } + if (rst->last_update > FILE2_LIMIT) { + update_base_record(rst); + } + unlock_record(rst); +} + +static void +sync_del_and_del(struct record_stat* rst, struct record_section* rsc, + struct trie_node* node) +{ + lock_record(rst); + commit_del_row(rst, rsc->name, node); + if (!check_base_record_uptodate(rst)) { + read_base_record(rst); + } + read_journal_record(rst); + if (rst->last_update > FILE2_LIMIT) { + update_base_record(rst); + } + unlock_record(rst); +} + + +/* + * prediction関係 + */ + +static int +read_prediction_node(struct trie_node *n, struct prediction_t* predictions, int index) +{ + int i; + int nr_predictions = do_get_nr_values(n); + for (i = 0; i < nr_predictions; i += 2) { + time_t t = do_get_nth_value(n, i); + xstr* xs = do_get_nth_xstr(n, i + 1); + if (t && xs) { + if (predictions) { + predictions[index].timestamp = t; + predictions[index].src_str = anthy_xstr_dup(&n->row.key); + predictions[index].str = anthy_xstr_dup(xs); + } + ++index; + } + } + return index; +} + + +/* + * trie中をたどり、prefixがマッチしたらread_prediction_nodeを + * 呼んでpredictionsの配列に結果を追加する。 + */ +static int +traverse_record_for_prediction(xstr* key, struct trie_node *n, + struct prediction_t* predictions, int index) +{ + if (n->l->bit > n->bit) { + index = traverse_record_for_prediction(key, n->l, predictions, index); + } else { + if (n->l->row.key.len != -1) { + if (anthy_xstrncmp(&n->l->row.key, key, key->len) == 0) { + index = read_prediction_node(n->l, predictions, index); + } + } + } + if (n->r->bit > n->bit) { + index = traverse_record_for_prediction(key, n->r, predictions, index); + } else { + if (n->r->row.key.len != -1) { + if (anthy_xstrncmp(&n->r->row.key, key, key->len) == 0) { + index = read_prediction_node(n->r, predictions, index); + } + } + } + return index; +} + +/* + * key で探索 + * key の文字列長を越えるか、ノードが無くなったら探索打ち切り + * trieのkeyが格納されているところでななくて葉を返す + */ +static struct trie_node * +trie_find_for_prediction (struct trie_root* root, xstr *key) +{ + struct trie_node *p; + struct trie_node *q; + + p = &root->root; + q = p->l; + + while (p->bit < q->bit) { + if (q->bit >= 2) { + if ((q->bit - 2) / (int)(sizeof(xchar) << 3) >= key->len) { + break; + } + } + p = q; + q = trie_key_nth_bit(key, p->bit) ? p->r : p->l; + } + return p; +} + +static int +prediction_cmp(const void* lhs, const void* rhs) +{ + struct prediction_t *lpre = (struct prediction_t*)lhs; + struct prediction_t *rpre = (struct prediction_t*)rhs; + return rpre->timestamp - lpre->timestamp; +} + +int +anthy_traverse_record_for_prediction(xstr* key, struct prediction_t* predictions) +{ + struct trie_node* mark; + int nr_predictions; + if (anthy_select_section("PREDICTION", 0)) { + return 0; + } + + /* 指定された文字列をprefixに持つnodeを探す */ + mark = trie_find_for_prediction(&anthy_current_record->cur_section->cols, key); + if (!mark) { + return 0; + } + nr_predictions = traverse_record_for_prediction(key, mark, predictions, 0); + if (predictions) { + /* タイムスタンプで予測候補をソートする */ + qsort(predictions, nr_predictions, sizeof(struct prediction_t), prediction_cmp); + } + return nr_predictions; +} + +/* Wrappers begin.. */ +int +anthy_select_section(const char *name, int flag) +{ + struct record_stat* rst; + struct record_section* rsc; + + rst = anthy_current_record; + if (rst->row_dirty && rst->cur_section && rst->cur_row) { + sync_add(rst, rst->cur_section, rst->cur_row); + } + rst->cur_row = NULL; + rst->row_dirty = 0; + rsc = do_select_section(rst, name, flag); + if (!rsc) { + return -1; + } + rst->cur_section = rsc; + return 0; +} + +int +anthy_select_row(xstr *name, int flag) +{ + struct record_stat* rst; + struct trie_node* node; + + rst = anthy_current_record; + if (!rst->cur_section) { + return -1; + } + if (rst->row_dirty && rst->cur_row) { + sync_add(rst, rst->cur_section, rst->cur_row); + rst->row_dirty = 0; + } + node = do_select_row(rst->cur_section, name, flag, LRU_USED); + if (!node) { + return -1; + } + rst->cur_row = node; + rst->row_dirty = flag; + return 0; +} + +int +anthy_select_longest_row(xstr *name) +{ + struct record_stat* rst; + struct trie_node* node; + + rst = anthy_current_record; + if (!rst->cur_section) + return -1; + + if (rst->row_dirty && rst->cur_row) { + sync_add(rst, rst->cur_section, rst->cur_row); + rst->row_dirty = 0; + } + node = do_select_longest_row(rst->cur_section, name); + if (!node) { + return -1; + } + + rst->cur_row = node; + rst->row_dirty = 0; + return 0; +} + +void +anthy_truncate_section(int count) +{ + do_truncate_section(anthy_current_record, count); +} + +void +anthy_truncate_row(int nth) +{ + struct trie_node *cur_row = anthy_current_record->cur_row; + if (!cur_row) { + return ; + } + do_truncate_row(cur_row, nth); + +} + +int +anthy_mark_row_used(void) +{ + struct record_stat* rst = anthy_current_record; + if (!rst->cur_row) { + return -1; + } + + do_mark_row_used(rst->cur_section, rst->cur_row); + sync_add(rst, rst->cur_section, rst->cur_row); + rst->row_dirty = 0; + return 0; +} + +void +anthy_set_nth_value(int nth, int val) +{ + struct record_stat* rst; + + rst = anthy_current_record; + if (!rst->cur_row) { + return; + } + do_set_nth_value(rst->cur_row, nth, val); + rst->row_dirty = 1; +} + +void +anthy_set_nth_xstr(int nth, xstr *xs) +{ + struct record_stat* rst = anthy_current_record; + if (!rst->cur_row) { + return; + } + do_set_nth_xstr(rst->cur_row, nth, xs, &rst->xstrs); + rst->row_dirty = 1; +} + +int +anthy_get_nr_values(void) +{ + return do_get_nr_values(anthy_current_record->cur_row); +} + +int +anthy_get_nth_value(int n) +{ + return do_get_nth_value(anthy_current_record->cur_row, n); +} + +xstr * +anthy_get_nth_xstr(int n) +{ + return do_get_nth_xstr(anthy_current_record->cur_row, n); +} + +int +anthy_select_first_row(void) +{ + struct record_stat* rst; + struct trie_node* node; + + rst = anthy_current_record; + if (!rst->cur_section) + return -1; + + if (rst->row_dirty && rst->cur_row) { + sync_add(rst, rst->cur_section, rst->cur_row); + rst->row_dirty = 0; + } + node = do_select_first_row(rst->cur_section); + if (!node) { + return -1; + } + rst->cur_row = node; + rst->row_dirty = 0; + return 0; +} + +int +anthy_select_next_row(void) +{ + struct record_stat* rst; + struct trie_node* node; + + rst = anthy_current_record; + if (!rst->cur_section || !rst->cur_row) + return -1; + + /* sync_add() で cur_row が無効になることがあるので、 + * たとえ row_dirty でも sync_add() しない + */ + rst->row_dirty = 0; + node = do_select_next_row(rst->cur_section, rst->cur_row); + if (!node) + return -1; + rst->cur_row = node; + rst->row_dirty = 0; + return 0; +} + +xstr * +anthy_get_index_xstr(void) +{ + return do_get_index_xstr(anthy_current_record); +} +/*..Wrappers end*/ + +/* + * trie_row_init は何回よんでもいい + */ +static void +trie_row_init(struct record_row* rc) +{ + rc->nr_vals = 0; + rc->vals = NULL; +} + +static void +trie_row_free(struct record_row *rc) +{ + int i; + for (i = 0; i < rc->nr_vals; i++) + free_val_contents(rc->vals + i); + free(rc->vals); + free(rc->key.str); +} + +/* あるセクションのデータを全て解放する */ +static void +free_section(struct record_stat *r, struct record_section *rs) +{ + struct record_section *s; + trie_remove_all(&rs->cols, &rs->lru_nr_used, &rs->lru_nr_sused); + if (r->cur_section == rs) { + r->cur_row = 0; + r->cur_section = 0; + } + for (s = &r->section_list; s && s->next; s = s->next) { + if (s->next == rs) { + s->next = s->next->next; + } + } + if (rs->name){ + free((void *)rs->name); + } + free(rs); +} + +/* すべてのデータを解放する */ +static void +free_record(struct record_stat *rst) +{ + struct record_section *rsc; + for (rsc = rst->section_list.next; rsc; ){ + struct record_section *tmp; + tmp = rsc; + rsc = rsc->next; + free_section(rst, tmp); + } + rst->section_list.next = NULL; +} + +void +anthy_release_section(void) +{ + struct record_stat* rst; + + rst = anthy_current_record; + if (!rst->cur_section) { + return ; + } + free_section(rst, rst->cur_section); + rst->cur_section = 0; +} + +void +anthy_release_row(void) +{ + struct record_stat* rst; + + rst = anthy_current_record; + if (!rst->cur_section || !rst->cur_row) { + return; + } + + rst->row_dirty = 0; + /* sync_del_and_del で削除もする */ + sync_del_and_del(rst, rst->cur_section, rst->cur_row); + rst->cur_row = NULL; +} + +static void +check_record_encoding(struct record_stat *rst) +{ + FILE *fp; + if (anthy_open_file(rst->base_fn) == 0) { + /* EUCの履歴ファイルがあった */ + anthy_close_file(); + return ; + } + fp = fopen(rst->journal_fn, "r"); + if (fp) { + /* EUCの差分ファイルがあった */ + fclose(fp); + return ; + } + rst->encoding = ANTHY_UTF8_ENCODING; + strcat(rst->base_fn, ENCODING_SUFFIX); + strcat(rst->journal_fn, ENCODING_SUFFIX); +} + +static void +record_dtor(void *p) +{ + int dummy; + struct record_stat *rst = (struct record_stat*) p; + free_record(rst); + if (rst->id) { + free(rst->base_fn); + free(rst->journal_fn); + } + trie_remove_all(&rst->xstrs, &dummy, &dummy); +} + +void +anthy_reload_record(void) +{ + struct stat st; + struct record_stat *rst = anthy_current_record; + + if (stat(rst->journal_fn, &st) == 0 && + rst->journal_timestamp == st.st_mtime) { + return ; + } + + lock_record(rst); + read_base_record(rst); + read_journal_record(rst); + unlock_record(rst); +} + +void +anthy_init_record(void) +{ + record_ator = anthy_create_allocator(sizeof(struct record_stat), + record_dtor); +} + +static void +setup_filenames(const char *id, struct record_stat *rst) +{ + const char *home = anthy_conf_get_str("HOME"); + int base_len = strlen(home) + strlen(id) + 10; + + /* 基本ファイル */ + rst->base_fn = (char*) malloc(base_len + + strlen("/.anthy/last-record1_")); + sprintf(rst->base_fn, "%s/.anthy/last-record1_%s", + home, id); + /* 差分ファイル */ + rst->journal_fn = (char*) malloc(base_len + + strlen("/.anthy/last-record2_")); + sprintf(rst->journal_fn, "%s/.anthy/last-record2_%s", + home, id); +} + +struct record_stat * +anthy_create_record(const char *id) +{ + struct record_stat *rst; + + if (!id) { + return NULL; + } + + rst = anthy_smalloc(record_ator); + rst->id = id; + rst->section_list.next = 0; + init_trie_root(&rst->xstrs); + rst->cur_section = 0; + rst->cur_row = 0; + rst->row_dirty = 0; + rst->encoding = 0; + + /* ファイル名の文字列を作る */ + setup_filenames(id, rst); + + rst->last_update = 0; + + if (!strcmp(id, ANON_ID)) { + rst->is_anon = 1; + } else { + rst->is_anon = 0; + anthy_check_user_dir(); + } + + /* ファイルから読み込む */ + lock_record(rst); + check_record_encoding(rst); + read_base_record(rst); + read_journal_record(rst); + unlock_record(rst); + + return rst; +} + +void +anthy_release_record(struct record_stat *rs) +{ + anthy_sfree(record_ator, rs); +} |