Project: finder, src: docid.c
追加されたドキュメントにドキュメント ID を振っていくロジック。
#include "docid.h"
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
/* 内部で使用する構造体 */
typedef struct DOC_OPTION {
unsigned long long lastid; // 最後に生成するべき ID
off_t parent; // offset == 0 の場合は最後の entry のオフセット
unsigned char level; // このノードのレベル; 0 で leaf node
unsigned char next; // 空いている最小の配列要素
unsigned char dummy[6];
} DOC_OPTION;
#define MAX_DOC_ENTRY (253ULL)
typedef struct DOC_ENTRY {
DOC_OPTION option;
off_t offset[MAX_DOC_ENTRY];
} DOC_ENTRY;
/* entry 内の配列の隣接する要素の「近さ」 */
static unsigned long long DIV_DOC_ENTRY[] = {
1, // level:0
MAX_DOC_ENTRY, // level:1
MAX_DOC_ENTRY * MAX_DOC_ENTRY, // level:2
MAX_DOC_ENTRY * MAX_DOC_ENTRY * MAX_DOC_ENTRY, // level:3
MAX_DOC_ENTRY * MAX_DOC_ENTRY * MAX_DOC_ENTRY * MAX_DOC_ENTRY // level:4
};
/**
* 新しい entry を確保する
*/
static void createElbowRoom(int fd, off_t *poff, DOC_ENTRY *e) {
if (*poff != 0) {
off_t parent = e->option.parent, child;
DOC_ENTRY ep, ec;
lseek(fd, parent, SEEK_SET);
read(fd, &ep, sizeof(ep));
// 親に空きがない場合、再帰で空きを探索する
if (ep.option.next >= MAX_DOC_ENTRY) {
createElbowRoom(fd, &parent, &ep);
}
child = lseek(fd, 0, SEEK_END);
memset(&ec, 0, sizeof(ec));
ec.option.level = ep.option.level - 1;
ec.option.parent = parent;
write(fd, &ec, sizeof(ec));
ep.offset[ep.option.next++] = child;
lseek(fd, parent, SEEK_SET);
write(fd, &ep, sizeof(ep));
memcpy(e, &ec, sizeof(*e));
*poff = child;
} else {
// root entry の場合は root の内容を退避して新しい entry を作成する
DOC_ENTRY ep, ec;
off_t child = lseek(fd, 0, SEEK_END);
write(fd, e, sizeof(*e));
memset(&ep, 0, sizeof(ep));
ep.option.level = e->option.level + 1;
ep.option.lastid = e->option.lastid;
ep.offset[ep.option.next++] = child;
memset(&ec, 0, sizeof(ec));
ec.option.level = e->option.level;
child = lseek(fd, 0, SEEK_END);
write(fd, &ec, sizeof(ec));
ep.offset[ep.option.next++] = child;
lseek(fd, 0, SEEK_SET);
write(fd, &ep, sizeof(ep));
*poff = child;
memcpy(e, &ec, sizeof(*e));
}
}
/**
* text を DB に登録し、ドキュメント ID を取得する
*/
unsigned long long createDocId(int fd, const char *text, int len) {
DOC_ENTRY e;
unsigned long long id;
off_t nextOffset, textOffset;
if (lseek(fd, 0, SEEK_END) == (off_t)0) {
// 新しいファイル
memset(&e, 0, sizeof(e));
write(fd, &e, sizeof(e));
}
// テキストを登録する
textOffset = lseek(fd, 0, SEEK_END);
write(fd, &len, sizeof(len));
write(fd, text, len);
// root を読み込む
lseek(fd, 0, SEEK_SET);
read(fd, &e, sizeof(e));
id = e.option.lastid++;
lseek(fd, 0, SEEK_SET);
write(fd, &e, sizeof(e));
// 次の空きエントリを読み込む
nextOffset = e.option.parent;
lseek(fd, nextOffset, SEEK_SET);
read(fd, &e, sizeof(e));
if (e.option.next >= MAX_DOC_ENTRY) {
DOC_ENTRY root;
// entry を分割する
createElbowRoom(fd, &nextOffset, &e);
// root entry に nextOffset を登録する
lseek(fd, 0, SEEK_SET);
read(fd, &root, sizeof(root));
root.option.parent = nextOffset;
lseek(fd, 0, SEEK_SET);
write(fd, &root, sizeof(root));
}
// テキストオフセットを保存する
e.offset[e.option.next++] = textOffset;
lseek(fd, nextOffset, SEEK_SET);
write(fd, &e, sizeof(e));
return id;
}
/**
* ドキュメント ID に対応する text を取得する
* 取得できない場合は NULL を返す
*/
char *getDocFromId(int fd, unsigned long long id, int *plen) {
DOC_ENTRY e;
unsigned long long div, mod, cur = id;
int len;
char *text;
unsigned char level;
// id が現在 DB に含まれている最大の ID より大きかったらエラー
lseek(fd, 0, SEEK_SET);
if (read(fd, &e, sizeof(e)) < sizeof(e)) {
return NULL;
}
if (e.option.lastid <= id) {
return NULL;
}
// entry をたどっていく
level = e.option.level;
while (level > 0) {
div = cur / DIV_DOC_ENTRY[level];
mod = cur % DIV_DOC_ENTRY[level];
lseek(fd, e.offset[div], SEEK_SET);
read(fd, &e, sizeof(e));
cur = mod;
level = e.option.level;
}
lseek(fd, e.offset[cur], SEEK_SET);
read(fd, &len, sizeof(len));
text = (char *)malloc(len + 1);
read(fd, text, len);
text[len] = '\0';
if (plen != NULL) {
*plen = len;
}
return text;
}
/**
* 次の登録に使用するドキュメント ID を返す
* ドキュメント ID は 0 から開始する
*/
unsigned long long getLastDocId(int fd) {
DOC_ENTRY e;
lseek(fd, 0, SEEK_SET);
if (read(fd, &e, sizeof(e)) < sizeof(e)) {
e.option.lastid = 0;
}
return e.option.lastid;
}
#ifdef DEBUG
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
void testSize(int fd) {
unsigned long long lastid = getLastDocId(fd);
printf("last doc id is:%llu\n", lastid);
}
void testGetId(int fd, int id) {
int len;
char *doc = getDocFromId(fd, id, &len);
if (doc == NULL) {
printf("id:%d not archived\n", id);
} else {
printf("id:%d title:%.*s\n", id, len, doc);
}
}
void testArchive(int fd, char *title) {
unsigned long long docid = createDocId(fd, title, strlen(title));
printf("title archived as id:%llu\n", docid);
}
int main(int argc, char **argv) {
int opt, action = 0, fd;
char *arg;
char *dbname = "./title.docdb";
while ((opt = getopt(argc, argv, "d:a:g:s")) != -1) {
switch (opt) {
case 'd':
dbname = optarg;
break;
case 'a':
case 'g':
action = opt;
arg = optarg;
break;
case 's':
action = opt;
break;
default:
printf("unknown option: usage: docid [-s / -a <string> / -g <id> ]\n");
exit(1);
}
}
fd = open(dbname, O_RDWR | O_CREAT, 0644);
if (fd < 0) {
printf("failed to open %s\n", dbname);
exit(1);
}
switch(action) {
case 's':
testSize(fd);
break;
case 'a':
testArchive(fd, arg);
break;
case 'g':
testGetId(fd, atoi(arg));
break;
default:
printf("unknown option: usage: docid [-s / -a <string> / -g <id> ]\n");
}
close(fd);
return 0;
}
#endif // DEBUG