diff options
-rw-r--r-- | docs/aids.txt | 89 | ||||
-rw-r--r-- | include/common.h | 3 | ||||
-rw-r--r-- | include/proto.h | 5 | ||||
-rw-r--r-- | mbbsd/bbs.c | 50 | ||||
-rw-r--r-- | mbbsd/read.c | 128 | ||||
-rw-r--r-- | mbbsd/stuff.c | 154 |
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)); +} + |