summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormhsin <mhsin@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2007-12-17 16:14:27 +0800
committermhsin <mhsin@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2007-12-17 16:14:27 +0800
commitd023f75854cf77c29a1d2b52635bab90dc370fa1 (patch)
treebbdf07c2b6e1b80750f611588d433adb1e32a509
parentec4a554115d214ba754951deb570742a5c0bb2e2 (diff)
downloadpttbbs-d023f75854cf77c29a1d2b52635bab90dc370fa1.tar
pttbbs-d023f75854cf77c29a1d2b52635bab90dc370fa1.tar.gz
pttbbs-d023f75854cf77c29a1d2b52635bab90dc370fa1.tar.bz2
pttbbs-d023f75854cf77c29a1d2b52635bab90dc370fa1.tar.lz
pttbbs-d023f75854cf77c29a1d2b52635bab90dc370fa1.tar.xz
pttbbs-d023f75854cf77c29a1d2b52635bab90dc370fa1.tar.zst
pttbbs-d023f75854cf77c29a1d2b52635bab90dc370fa1.zip
* Add Article IDentification System(AIDS)
git-svn-id: http://opensvn.csie.org/pttbbs/trunk/pttbbs@3700 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
-rw-r--r--docs/aids.txt89
-rw-r--r--include/common.h3
-rw-r--r--include/proto.h5
-rw-r--r--mbbsd/bbs.c50
-rw-r--r--mbbsd/read.c128
-rw-r--r--mbbsd/stuff.c154
6 files changed, 421 insertions, 8 deletions
diff --git a/docs/aids.txt b/docs/aids.txt
new file mode 100644
index 00000000..870c1646
--- /dev/null
+++ b/docs/aids.txt
@@ -0,0 +1,89 @@
+
+
+ Article IDentification System(AIDS) Design Document
+
+
+Revision History
+
+ * 0.01 2007/12/14 mhsin (未完成)
+
+
+Problem
+
+ BBS 使用者常常有一個需要:「告訴他人某篇特定文章,請那個人去閱讀」,可稱
+ 之為「文章識別系統(Article IDentification System, AIDS)」。
+ 最常見的 AIDS 為結合板名與文章編號:「xxxx 板的第 yyyy 篇文章(如果明顯
+ 可辨別為某個板,則直接指定文章編號)」,但是只要該篇文章之前的任何一篇文
+ 章被刪除並清理索引空位,之後的文章皆會向前遞補,因而造成原本指定的文章編
+ 號指向另一篇文章(或是沒有該編號)。
+ 因此較保險的做法是增加資訊,例如文章編號加上作者、標題,但這會造成描述一
+ 篇文章需要佔用大量的空間,顯得極不實用。
+
+ 理想的 AIDS 至少要能達成以下幾點:
+
+ * 判別文章消失、指示錯誤
+ 使用不隨時間改變(time-invariant)並且達到一定程度唯一性(uniqueness)
+ 的文章識別子(Article IDentifier, AID)。
+ 通常只要做到「看板內唯一」即可,原因是使用習慣上常常會給定看板。
+
+ * 識別子夠短、適合在各種環境使用
+ 識別子必須夠短,並且便於使用者在 BBS 內轉述、甚至轉述到其他如網頁
+ 或 IRC 等媒體。
+ 依目前的使用習慣,10 個字元的長度應該是上限。
+ 而使用的符號也應盡量使用單純的英數字,加上部分在各種環境較不需要
+ escape 的符號。
+
+ * 能有效率地從識別子找到原文章
+ 避免使用額外的空間。
+
+
+Proposed Scheme
+
+ Environment
+
+ 目前 pttbbs 所使用的文章及文摘檔名為:M(文章)或 G(文摘)、接著十進
+ 位的 31-bit 數字(事實上是一個 32-bit time_t 的正數部分)、接著 ".A"、
+ 再接著 "." 及一個三位的大寫十六進位數字(舊版程式產生出來的不包含最後
+ 著四個字元),以 regular expression 拮取:
+
+ /^(M|G)\.(\d+)\.A(?:\.([0-9A-F]{3}))?$/
+ $type = $1
+ $v1 = $2
+ $v2 = (defined($3) ? hex($3) : 0)
+
+ AID(Article IDentifier)
+
+ 為一個最大 64-bit 的無號整數,現階段(可稱為 AIDv1)只使用 48-bit。
+ AIDv1 中各欄位的意義:
+ * info: 4-bit
+ 額外資訊。
+ 0: 一般文章("M")
+ 1: 文摘("G")
+ * v1: 32-bit
+ 即 time_t 部分。
+ * v2: 12-bit
+ 最後的三位十六進位數字。若不存在(舊檔名)則為 0。
+
+ AID 另外有幾種表現形態:
+ * AIDu(AID uncompressed)
+ 即 AID,以 uncompressed 強調其為原始形態。
+ * AIDc(AID compressed)
+ 以類似 Base64 的表現方式(即每 6-bit 以一個字元表示),用字串表
+ 示 AIDu。
+ 目前以 0-9, A-Z, a-z, -, _ 共 64 個符號代表 6-bit 中 0~64 的值。
+ AIDv1 使用 48-bit,故轉成 AIDc 時為 8 個字元長。
+ AIDu 與 AIDc 可完全互相轉換。
+
+ Resolve AIDu to filename
+
+ 從 AIDu 得到檔名只需將 $type, $v1, $v2 分別轉換回 "M" 或 "G"、十進位數
+ 字、三位十六進位數字,再組合成檔名即可。
+ 但由於 AIDv1 不記錄檔名是否為舊版程式所產生($3 部分不存在,$v2 被設為
+ 0),故解析出如 M.123456789.A.000 這樣的檔名,若檔案不存在,則會去掉最
+ 後四個字元,以 M.123456789.A 再嘗試一次。(理論上這兩個檔案幾乎不可能
+ 同時存在)
+
+
+
+
+
diff --git a/include/common.h b/include/common.h
index 2f01b82f..e8ab5da9 100644
--- a/include/common.h
+++ b/include/common.h
@@ -92,6 +92,9 @@
#define STR_POST1 "看板:"
#define STR_POST2 "站內:"
+/* AID */
+#define AID_DISPLAYNAME "文章代碼(AID)"
+
/* LONG MESSAGES */
#define MSG_SELECT_BOARD ANSI_COLOR(7) "【 選擇看板 】" ANSI_RESET "\n" \
"請輸入看板名稱(按空白鍵自動搜尋):"
diff --git a/include/proto.h b/include/proto.h
index 247b47cd..cc69cda3 100644
--- a/include/proto.h
+++ b/include/proto.h
@@ -641,6 +641,11 @@ int qsort_intcompar(const void *a, const void *b);
void pressanykey_or_callangel(void);
#endif
void syncnow(void);
+unsigned long fn2aidu(char *fn);
+char *aidu2aidc(char *buf, unsigned long aidu);
+char *aidu2fn(char *buf, unsigned long aidu);
+unsigned long aidc2aidu(char *aidc);
+int search_aidu(char *bfile, unsigned long aidu);
/* syspost */
int post_msg(const char* bname, const char* title, const char *msg, const char* author);
diff --git a/mbbsd/bbs.c b/mbbsd/bbs.c
index 44415c46..8056ae55 100644
--- a/mbbsd/bbs.c
+++ b/mbbsd/bbs.c
@@ -3108,30 +3108,64 @@ lock_post(int ent, fileheader_t * fhdr, const char *direct)
}
static int
-view_postmoney(int ent, const fileheader_t * fhdr, const char *direct)
+view_postinfo(int ent, const fileheader_t * fhdr, const char *direct)
{
+ unsigned long aidu = 0;
+
+ if(fhdr->filename[0] == '.')
+ return DONOTHING;
+
+ move(17, 0);
+ clrtobot();
+ prints("-------------------------------------------------------------------------------\n");
+ prints("\n%7d", ent);
+ prints(" %-13.12s", fhdr->owner);
+ prints(" %s\n\n", fhdr->title);
+
+ aidu = fn2aidu(fhdr->filename);
+ if(aidu > 0)
+ {
+ char aidc[10];
+
+ aidu2aidc(aidc, aidu);
+#ifdef DEBUG
+ prints(" fn: %s\n", fhdr->filename);
+ prints("AIDu: %012lX\n", aidu);
+ prints("AIDc: %s\n", aidc);
+#endif
+ prints(" 此篇文章的" AID_DISPLAYNAME "為: " ANSI_COLOR(1) "#%s" ANSI_RESET "\n", aidc);
+ }
+ else
+ {
+ prints("\n");
+ }
+
if(fhdr->filemode & FILE_ANONYMOUS)
/* When the file is anonymous posted, fhdr->multi.anon_uid is author.
* see do_general() */
- vmsgf("匿名管理編號: %d (同一人號碼會一樣)",
- fhdr->multi.anon_uid + (int)currutmp->pid);
+ prints(" 匿名管理編號: %d (同一人號碼會一樣)",
+ fhdr->multi.anon_uid + (int)currutmp->pid);
else {
int m = query_file_money(fhdr);
if(m < 0)
- m = vmsgf("特殊文章,無價格記錄。");
+ prints(" 特殊文章,無價格記錄。");
else
- m = vmsgf("這一篇文章值 %d 銀", m);
+ prints(" 這一篇文章值 %d 銀", m);
+ }
+ {
+ int r = pressanykey();
+ /* TODO: 多加一個 LISTMODE_AID? */
/* QQ: enable money listing mode */
- if (m == 'Q')
+ if (r == 'Q')
{
currlistmode = (currlistmode == LISTMODE_MONEY) ?
LISTMODE_DATE : LISTMODE_MONEY;
vmsg((currlistmode == LISTMODE_MONEY) ?
"開啟文章價格列表模式" : "停止列出文章價格");
}
- }
+ }
return FULLUPDATE;
}
@@ -3573,7 +3607,7 @@ const onekey_t read_comms[] = {
{ 0, NULL }, // 'N'
{ 0, b_moved_to_config }, // 'O'
{ 0, NULL }, // 'P'
- { 1, view_postmoney }, // 'Q'
+ { 1, view_postinfo }, // 'Q'
{ 0, b_results }, // 'R'
{ 0, NULL }, // 'S'
{ 1, edit_title }, // 'T'
diff --git a/mbbsd/read.c b/mbbsd/read.c
index 9137d304..883f75d5 100644
--- a/mbbsd/read.c
+++ b/mbbsd/read.c
@@ -677,6 +677,8 @@ select_read(const keeploc_t * locmem, int sr_mode)
return READ_REDRAW;
}
+static int newdirect_new_ln = -1;
+
static int
i_read_key(const onekey_t * rcmdlist, keeploc_t * locmem,
int bid, int bottom_line)
@@ -726,6 +728,127 @@ i_read_key(const onekey_t * rcmdlist, keeploc_t * locmem,
mode =
(currmode & MODE_DIGEST) ? board_digest() : DOQUIT;
break;
+ case '#':
+ {
+ char aidc[100];
+ unsigned long aidu = 0;
+ char dirfile[PATHLEN];
+ char *sp;
+ int n = -1;
+
+ if(!getdata(b_lines, 0, "搜尋" AID_DISPLAYNAME ": #", aidc, 20, LCECHO))
+ {
+ move(b_lines, 0);
+ clrtoeol();
+ mode = FULLUPDATE;
+ break;
+ }
+
+ if((currmode & MODE_SELECT) ||
+ (currstat == RMAIL))
+ {
+ move(21, 0);
+ clrtobot();
+ move(22, 0);
+ prints("此狀態下無法使用搜尋" AID_DISPLAYNAME "功\能");
+ pressanykey();
+ mode = FULLUPDATE;
+ break;
+ }
+
+ /* strip leading spaces and '#' */
+ sp = aidc;
+ while(*sp == ' ')
+ sp ++;
+ if(*sp == '#')
+ sp ++;
+
+ if((aidu = aidc2aidu(sp)) > 0)
+ {
+ /* search bottom */
+ /* FIXME: 置底文但沒列在 .DIR.bottom 的在這段會搜不到,
+ 在下一段 search board 時才會搜到本體。難解。 */
+ {
+ char buf[FNLEN];
+
+ snprintf(buf, FNLEN, "%s.bottom", FN_DIR);
+ setbfile(dirfile, currboard, buf);
+ if((n = search_aidu(dirfile, aidu)) >= 0)
+ {
+ n += getbtotal(currbid);
+ /* 不可用 bottom_line,因為如果是在 digest mode,
+ bottom_line 會是文摘的數目,而不是真正的文章數 */
+ if(currmode & MODE_DIGEST)
+ {
+ newdirect_new_ln = n;
+
+ new_ln = locmem->crs_ln;
+ /* dirty hack for crs_ln = 1, then HOME pressed */
+
+ default_ch = KEY_TAB;
+ mode = DONOTHING;
+ break;
+ }
+ }
+ }
+ if(n < 0)
+ /* search board */
+ {
+ setbfile(dirfile, currboard, FN_DIR);
+ n = search_aidu(dirfile, aidu);
+ if(n >= 0 && (currmode & MODE_DIGEST))
+ /* switch to normal read mode */
+ {
+ newdirect_new_ln = n;
+
+ new_ln = locmem->crs_ln;
+ /* dirty hack for crs_ln = 1, then HOME pressed */
+
+ default_ch = KEY_TAB;
+ mode = DONOTHING;
+ break;
+ }
+ }
+ if(n < 0)
+ /* search digest */
+ {
+ setbfile(dirfile, currboard, fn_mandex);
+ n = search_aidu(dirfile, aidu);
+ if(n >= 0 && !(currmode & MODE_DIGEST))
+ /* switch to digest mode */
+ {
+ newdirect_new_ln = n;
+
+ new_ln = locmem->crs_ln;
+ /* dirty hack for crs_ln = 1, then HOME pressed */
+
+ default_ch = KEY_TAB;
+ mode = DONOTHING;
+ break;
+ }
+ }
+ } /* if(aidu > 0) */
+ if(n < 0)
+ {
+ move(21, 0);
+ clrtobot();
+ move(22, 0);
+ if(aidu <= 0)
+ prints("不合法的" AID_DISPLAYNAME ",請確定輸入是正確的");
+ else
+ prints("找不到這個" AID_DISPLAYNAME ",可能是文章已消失,或是你找錯看板了");
+ pressanykey();
+ mode = FULLUPDATE;
+ } /* if(n < 0) */
+ else
+ {
+ new_ln = n + 1;
+ move(b_lines, 0);
+ clrtoeol();
+ mode = DONOTHING;
+ }
+ }
+ break;
case Ctrl('L'):
redoscr();
break;
@@ -1082,6 +1205,11 @@ i_read(int cmdmode, const char *direct, void (*dotitle) (),
int num;
num = last_line - p_lines + 1;
locmem = getkeep(currdirect, num < 1 ? 1 : num, last_line);
+ if(newdirect_new_ln >= 0)
+ {
+ locmem->crs_ln = newdirect_new_ln + 1;
+ newdirect_new_ln = -1;
+ }
}
recbase = -1;
/* no break */
diff --git a/mbbsd/stuff.c b/mbbsd/stuff.c
index fa4c424a..af55ca01 100644
--- a/mbbsd/stuff.c
+++ b/mbbsd/stuff.c
@@ -663,3 +663,157 @@ uintbsearch(const unsigned int key, const unsigned int *base0, const int nmemb)
return (NULL);
}
+unsigned long fn2aidu(char *fn)
+{
+ unsigned long aidu = 0;
+ unsigned long type = 0;
+ unsigned long v1 = 0;
+ unsigned long v2 = 0;
+ char *sp = fn;
+
+ if(fn == NULL)
+ return 0;
+
+ switch(*(sp ++))
+ {
+ case 'M':
+ type = 0;
+ break;
+ case 'G':
+ type = 1;
+ break;
+ default:
+ return 0;
+ break;
+ }
+
+ if(*(sp ++) != '.')
+ return 0;
+ v1 = strtoul(sp, &sp, 10);
+ if(sp == NULL)
+ return 0;
+ if(*sp != '.' || *(sp + 1) != 'A')
+ return 0;
+ sp += 2;
+ if(*(sp ++) == '.')
+ {
+ v2 = strtoul(sp, &sp, 16);
+ if(sp == NULL)
+ return 0;
+ }
+ aidu = ((type & 0xf) << 44) | ((v1 & 0xffffffff) << 12) | (v2 & 0xfff);
+
+ return aidu;
+}
+
+/* IMPORTANT:
+ * size of buf must be at least 8+1 bytes
+ */
+char *aidu2aidc(char *buf, unsigned long aidu)
+{
+ const char aidu2aidc_table[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";
+ const int aidu2aidc_tablesize = sizeof(aidu2aidc_table) - 1;
+ char *sp = buf + 8;
+ unsigned long v;
+
+ *(sp --) = '\0';
+ while(sp >= buf)
+ {
+ /* FIXME: 能保證 aidu2aidc_tablesize 是 2 的冪次的話,
+ 這裡可以改用 bitwise operation 做 */
+ v = aidu % aidu2aidc_tablesize;
+ aidu = aidu / aidu2aidc_tablesize;
+ *(sp --) = aidu2aidc_table[v];
+ }
+ return buf;
+}
+
+/* IMPORTANT:
+ * size of fn must be at least FNLEN bytes
+ */
+char *aidu2fn(char *fn, unsigned long aidu)
+{
+ unsigned long type = ((aidu >> 44) & 0xf);
+ unsigned long v1 = ((aidu >> 12) & 0xffffffff);
+ unsigned long v2 = (aidu & 0xfff);
+
+ if(fn == NULL)
+ return NULL;
+ snprintf(fn, FNLEN - 1, "%c.%ld.A.%03lX", ((type == 0) ? 'M' : 'G'), v1, v2);
+ fn[FNLEN - 1] = '\0';
+ return fn;
+}
+
+unsigned long aidc2aidu(char *aidc)
+{
+ char *sp = aidc;
+ unsigned long aidu = 0;
+
+ if(aidc == NULL)
+ return 0;
+
+ while(*sp != '\0' && /* ignore trailing spaces */ *sp != ' ')
+ {
+ unsigned long v = 0;
+ /* FIXME: 查表法會不會比較快? */
+ if(*sp >= '0' && *sp <= '9')
+ v = *sp - '0';
+ else if(*sp >= 'A' && *sp <= 'Z')
+ v = *sp - 'A' + 10;
+ else if(*sp >= 'a' && *sp <= 'z')
+ v = *sp - 'a' + 36;
+ else if(*sp == '-')
+ v = 62;
+ else if(*sp == '_')
+ v = 63;
+ else
+ return 0;
+ aidu <<= 6;
+ aidu |= (v & 0x3f);
+ sp ++;
+ }
+
+ return aidu;
+}
+
+int search_aidu(char *bfile, unsigned long aidu)
+{
+ char fn[FNLEN];
+ int fd;
+ fileheader_t fhs[64];
+ int len, i;
+ int pos = 0;
+ int found = 0;
+ int lastpos = 0;
+
+ if(aidu2fn(fn, aidu) == NULL)
+ return -1;
+ if((fd = open(bfile, O_RDONLY, 0)) < 0)
+ return -1;
+
+ while(!found && (len = read(fd, fhs, sizeof(fhs))) > 0)
+ {
+ len /= sizeof(fileheader_t);
+ for(i = 0; i < len; i ++)
+ {
+ int l = strlen(fhs[i].filename);
+ if(strncmp(fhs[i].filename, fn, l) == 0)
+ {
+ if(fhs[i].filemode & FILE_BOTTOM)
+ {
+ lastpos = pos;
+ }
+ else
+ {
+ found = 1;
+ break;
+ }
+ }
+ pos ++;
+ }
+ }
+ close(fd);
+
+ return (found ? pos : (lastpos ? lastpos : -1));
+}
+