diff options
Diffstat (limited to 'console/read.c')
-rw-r--r-- | console/read.c | 1371 |
1 files changed, 1371 insertions, 0 deletions
diff --git a/console/read.c b/console/read.c new file mode 100644 index 00000000..97621725 --- /dev/null +++ b/console/read.c @@ -0,0 +1,1371 @@ +/* $Id$ */ +#include "bbs.h" + +static int headers_size = 0; +static fileheader_t *headers = NULL; +static int last_line; // PTT: last_line 游標可指的最後一個 + +#include <sys/mman.h> + +/* ----------------------------------------------------- */ +/* Tag List 標籤 */ +/* ----------------------------------------------------- */ +static TagItem *TagList = NULL; /* ascending list */ + +/** + * @param locus + * @return void + */ +void +UnTagger(int locus) +{ + if (locus > TagNum || TagNum <= 0) + return; + + TagNum--; + + if (TagNum > locus) + memmove(&TagList[locus], &TagList[locus + 1], + (TagNum - locus) * sizeof(TagItem)); +} + +int +Tagger(time4_t chrono, int recno, int mode) +{ + int head, tail, posi = 0, comp; + + if(TagList == NULL) { + TagList = malloc(sizeof(TagItem)*(MAXTAGS+1)); + } + + for (head = 0, tail = TagNum - 1, comp = 1; head <= tail;) { + posi = (head + tail) >> 1; + comp = TagList[posi].chrono - chrono; + if (!comp) { + break; + } else if (comp < 0) { + head = posi + 1; + } else { + tail = posi - 1; + } + } + + if (mode == TAG_NIN) { + if (!comp && recno) /* 絕對嚴謹:連 recno 一起比對 */ + comp = recno - TagList[posi].recno; + return comp; + + } + if (!comp) { + if (mode != TAG_TOGGLE || TagNum <= 0) + return NA; + + TagNum--; + memmove(&TagList[posi], &TagList[posi + 1], + (TagNum - posi) * sizeof(TagItem)); + } else if (TagNum < MAXTAGS) { + TagItem *tagp; + + memmove(&TagList[head+1], &TagList[head], sizeof(TagItem)*(TagNum-head)); + tagp = &TagList[head]; + tagp->chrono = chrono; + tagp->recno = recno; + TagNum++; + } else { + bell(); + return 0; /* full */ + } + return YEA; +} + +#if 0 +static void +EnumTagName(char *fname, int locus) /* unused */ +{ + snprintf(fname, sizeof(fname), "M.%d.A", (int)TagList[locus].chrono); +} +#endif + +void +EnumTagFhdr(fileheader_t * fhdr, char *direct, int locus) +{ + get_record(direct, fhdr, sizeof(fileheader_t), TagList[locus].recno); +} + +/* -1 : 取消 */ +/* 0 : single article */ +/* ow: whole tag list */ + +int +AskTag(const char *msg) +{ + int num; + + num = TagNum; + switch (getans("◆ %s A)文章 T)標記 Q)uit?", msg)) { + case 'q': + num = -1; + break; + case 'a': + num = 0; + } + return num; +} + + +#include <sys/mman.h> + +#define BATCH_SIZE 65536 + +static char * +f_map(const char *fpath, int *fsize) +{ + int fd, size; + struct stat st; + char *map; + + if ((fd = open(fpath, O_RDONLY)) < 0) + return (char *)-1; + + if (fstat(fd, &st) || !S_ISREG(st.st_mode) || (size = st.st_size) <= 0) { + close(fd); + return (char *)-1; + } + map = (char *)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + close(fd); + *fsize = size; + return map; +} + + +static int +TagThread(const char *direct) +{ + int fsize, count; + char *title, *fimage; + fileheader_t *head, *tail; + + fimage = f_map(direct, &fsize); + if (fimage == (char *)-1) + return DONOTHING; + + head = (fileheader_t *) fimage; + tail = (fileheader_t *) (fimage + fsize); + count = 0; + do { + count++; + title = subject(head->title); + if (!strncmp(currtitle, title, TTLEN)) { + if (!Tagger(atoi(head->filename + 2), count, TAG_INSERT)) + break; + } + } while (++head < tail); + + munmap(fimage, fsize); + return FULLUPDATE; +} + + +int +TagPruner(int bid) +{ + boardheader_t *bp=NULL; + assert(bid >= 0); /* bid == 0 means in mailbox */ + if (bid){ + bp = getbcache(bid); + if (strcmp(bp->brdname, GLOBAL_SECURITY) == 0) + return DONOTHING; + } + if (TagNum && ((currstat != READING) || (currmode & MODE_BOARD))) { + if (getans("刪除所有標記[N]?") != 'y') + return READ_REDRAW; +#ifdef SAFE_ARTICLE_DELETE + if(bp && !(currmode & MODE_DIGEST) && bp->nuser>30 ) + safe_delete_range(currdirect, 0, 0); + else +#endif + delete_range(currdirect, 0, 0); + TagNum = 0; + if (bid) + setbtotal(bid); + else if(currstat == RMAIL) + setupmailusage(); + + return NEWDIRECT; + } + return DONOTHING; +} + + +/* ----------------------------------------------------- */ +/* cursor & reading record position control */ +/* ----------------------------------------------------- */ +keeploc_t * +getkeep(const char *s, int def_topline, int def_cursline) +{ + /* 為省記憶體, 且避免 malloc/free 不成對, getkeep 最好不要 malloc, + * 只記 s 的 hash 值, + * fvn1a-32bit collision 機率約小於十萬分之一 */ + /* 原本使用 link list, 可是一方面會造成 malloc/free 不成對, + * 一方面 size 小, malloc space overhead 就高, 因此改成 link block, + * 以 KEEPSLOT 為一個 block 的 link list. + * 只有第一個 block 可能沒滿. */ + /* TODO LRU recycle? 麻煩在於別處可能把 keeploc_t pointer 記著... */ +#define KEEPSLOT 10 + struct keepsome { + unsigned char used; + keeploc_t arr[KEEPSLOT]; + struct keepsome *next; + }; + static struct keepsome preserv_keepblock; + static struct keepsome *keeplist = &preserv_keepblock; + struct keeploc_t *p; + unsigned key=StringHash(s); + int i; + + if (def_cursline >= 0) { + struct keepsome *kl=keeplist; + while(kl) { + for(i=0; i<kl->used; i++) + if(key == kl->arr[i].hashkey) { + p = &kl->arr[i]; + if (p->crs_ln < 1) + p->crs_ln = 1; + return p; + } + kl=kl->next; + } + } else + def_cursline = -def_cursline; + + if(keeplist->used==KEEPSLOT) { + struct keepsome *kl; + kl = (struct keepsome*)malloc(sizeof(struct keepsome)); + memset(kl, 0, sizeof(struct keepsome)); + kl->next = keeplist; + keeplist = kl; + } + p = &keeplist->arr[keeplist->used]; + keeplist->used++; + p->hashkey = key; + p->top_ln = def_topline; + p->crs_ln = def_cursline; + return p; +} + +void +fixkeep(const char *s, int first) +{ + keeploc_t *k; + + k = getkeep(s, 1, 1); + if (k->crs_ln >= first) { + k->crs_ln = (first == 1 ? 1 : first - 1); + k->top_ln = (first < 11 ? 1 : first - 10); + } +} + +/* calc cursor pos and show cursor correctly */ +static int +cursor_pos(keeploc_t * locmem, int val, int from_top, int isshow) +{ + int top=locmem->top_ln; + if (!last_line){ + cursor_show(3 , 0); + return DONOTHING; + } + if (val > last_line) + val = last_line; + if (val <= 0) + val = 1; + if (val >= top && val < top + headers_size) { + if(isshow){ + if(locmem->crs_ln >= top) + cursor_clear(3 + locmem->crs_ln - top, 0); + cursor_show(3 + val - top, 0); + } + locmem->crs_ln = val; + return DONOTHING; + } + locmem->top_ln = val - from_top; + if (locmem->top_ln <= 0) + locmem->top_ln = 1; + locmem->crs_ln = val; + return isshow ? PARTUPDATE : HEADERS_RELOAD; +} + +/** + * 根據 stypen 選擇上/下一篇文章 + * + * @param locmem 用來存在某看板游標位置的 structure。 + * @param stypen 游標移動的方法 + * CURSOR_FIRST, CURSOR_NEXT, CURSOR_PREV: + * 與游標目前位置的文章同標題 的 第一篇/下一篇/前一篇 文章。 + * RELATE_FIRST, RELATE_NEXT, RELATE_PREV: + * 與目前正閱讀的文章同標題 的 第一篇/下一篇/前一篇 文章。 + * NEWPOST_NEXT, NEWPOST_PREV: + * 下一個/前一個 thread 的第一篇。 + * AUTHOR_NEXT, AUTHOR_PREV: + * XXX 這功能目前好像沒用到? + * + * @return 新的游標位置 + */ +static int +thread(const keeploc_t * locmem, int stypen) +{ + fileheader_t fh; + int pos = locmem->crs_ln, jump = THREAD_SEARCH_RANGE, new_ln; + int fd = -1, amatch = -1; + int step = (stypen & RS_FORWARD) ? 1 : -1; + char *key; + + if(locmem->crs_ln==0) + return locmem->crs_ln; + + STATINC(STAT_THREAD); + if (stypen & RS_AUTHOR) + key = headers[pos - locmem->top_ln].owner; + else if (stypen & RS_CURRENT) + key = subject(currtitle); + else + key = subject(headers[pos - locmem->top_ln].title ); + + for( new_ln = pos + step ; + new_ln > 0 && new_ln <= last_line && --jump > 0; + new_ln += step ) { + + int rk = + get_record_keep(currdirect, &fh, sizeof(fileheader_t), new_ln, &fd); + + if(fd < 0 || rk < 0) + { + new_ln = pos; + break; + } + + if( stypen & RS_TITLE ){ + if( stypen & RS_FIRST ){ + if( !strncmp(fh.title, key, PROPER_TITLE_LEN) ) + break; + else if( !strncmp(&fh.title[4], key, PROPER_TITLE_LEN) ) { + amatch = new_ln; + jump = THREAD_SEARCH_RANGE; + /* 當搜尋同主題第一篇, 連續找不到多少篇才停 */ + } + } + else if( !strncmp(subject(fh.title), key, PROPER_TITLE_LEN) ) + break; + } + else if( stypen & RS_NEWPOST ){ + if( strncmp(fh.title, "Re:", 3) ) + break; + } + else{ // RS_AUTHOR + if( strcmp(subject(fh.owner), key) == EQUSTR ) + break; + } + } + + if( fd != -1 ) + close(fd); + + if( jump <= 0 || new_ln <= 0 || new_ln > last_line ) + new_ln = (amatch == -1 ? pos : amatch); //didn't find + + return new_ln; +} + +#ifdef INTERNET_EMAIL +static void +mail_forward(const fileheader_t * fhdr, const char *direct, int mode) +{ + int i; + char buf[STRLEN]; + char *p; + + strlcpy(buf, direct, sizeof(buf)); + if ((p = strrchr(buf, '/'))) + *p = '\0'; + switch (i = doforward(buf, fhdr, mode)) { + case 0: + vmsg(msg_fwd_ok); + break; + case -1: + vmsg(msg_fwd_err1); + break; + case -2: +#ifndef DEBUG_FWDADDRERR + vmsg(msg_fwd_err2); +#endif + break; + case -4: + vmsg("信箱已滿"); + break; + default: + break; + } +} +#endif + +// return: 1 - found, 0 - fail. +inline static int +dbcs_strcasestr(const char* pool, const char *ptr) +{ +#if 0 + // old method + int len = strlen(ptr); + + while(*pool) + { + // FIXME 用 strncasecmp 還是會錯 + if(strncasecmp(pool, ptr, len) == 0) + return 1; + /* else */ + if(*pool < 0) + { + pool ++; + if(*pool == 0) + return 0; + } + pool ++; + } + return 0; + +#endif + + int i = 0, i2 = 0, found = 0, + szpool = strlen(pool), + szptr = strlen(ptr); + + for (i = 0; i <= szpool-szptr; i++) + { + found = 1; + + // compare szpool[i..szptr] with ptr + for (i2 = 0; i2 < szptr; i2++) + { + if (pool[i + i2] > 0) + { + // ascii + if (ptr[i2] < 0 || + tolower(ptr[i2]) != tolower(pool[i+i2])) + { + // printf("break on ascii (i=%d, i2=%d).\n", i, i2); + found = 0; + break; + } + } else { + // non-ascii + if (ptr[i2] != pool[i+i2] || + ptr[i2+1] != pool[i+i2+1]) + { + // printf("break on non-ascii (i=%d, i2=%d).\n", i, i2); + found = 0; + break; + } + i2 ++; + } + } + + if (found) break; + + // next iteration: if target is DBCS, skip one more byte. + if (pool[i] < 0) + i++; + } + return found; +} + +static int +select_read(const keeploc_t * locmem, int sr_mode) +{ +#define READSIZE 64 // 8192 / sizeof(fileheader_t) + time4_t filetime; + fileheader_t fhs[READSIZE]; + char newdirect[MAXPATHLEN]; + int first_select; + char genbuf[MAXPATHLEN], *p = strstr(currdirect, "SR."); + static int _mode = 0; + int reload, inc; + int len, fd, fr, i, count = 0, reference = 0; + int filemode; + /* selection condition */ + char keyword[TTLEN + 1] = ""; + int n_recommend = 0, n_money = 0; + + + if(locmem->crs_ln == 0) + return locmem->crs_ln; + + first_select = p==NULL; + + STATINC(STAT_SELECTREAD); + if(sr_mode & RS_AUTHOR) + { + if(!getdata(b_lines, 0, + currmode & MODE_SELECT ? "增加條件 作者: ":"搜尋作者: ", + keyword, IDLEN+1, LCECHO)) + return READ_REDRAW; + } + else if(sr_mode & RS_KEYWORD) + { + if(!getdata(b_lines, 0, + currmode & MODE_SELECT ? "增加條件 標題: ":"搜尋標題: ", + keyword, TTLEN, DOECHO)) + return READ_REDRAW; +#ifdef KEYWORD_LOG + log_file("keyword_search_log", LOG_CREAT | LOG_VF, + "%s:%s\n", currboard, keyword); +#endif + } + else if(sr_mode & RS_KEYWORD_EXCLUDE) + { + if(!(currmode & MODE_SELECT) || + !getdata(b_lines, 0, "增加條件 排除標題: ", + keyword, TTLEN, DOECHO)) + return READ_REDRAW; + } + else if (sr_mode & RS_RECOMMEND) + { + if(currstat == RMAIL || ( + !getdata(b_lines, 0, + (currmode & MODE_SELECT) ? + "增加條件 推文數: ": + "搜尋推文數高於多少" +#ifndef OLDRECOMMEND + " (<0則搜噓文數) " +#endif // OLDRECOMMEND + "的文章: ", + keyword, 7, LCECHO) || (n_recommend = atoi(keyword)) == 0 )) + return READ_REDRAW; + } + else if (sr_mode & RS_MONEY) + { + if(currstat == RMAIL || ( + !getdata(b_lines, 0, + (currmode & MODE_SELECT) ? + "增加條件 文章價格: ":"搜尋價格高於多少的文章: ", + keyword, 7, LCECHO) || (n_money = atoi(keyword)) <= 0 )) + return READ_REDRAW; + strcat(keyword, "M"); + } + else { + // Ptt: only once for these modes. + if(!first_select && _mode & sr_mode & (RS_TITLE | RS_NEWPOST | RS_MARK)) + return DONOTHING; + + if(sr_mode & RS_TITLE) { + fileheader_t *fh = &headers[locmem->crs_ln - locmem->top_ln]; + strcpy(keyword, subject(fh->title)); + } + } + + if(first_select) + _mode = sr_mode; + else + _mode |= sr_mode; + + snprintf(genbuf, sizeof(genbuf), "%s%X.%X.%X", + first_select ? "SR.":p, + sr_mode, (int)strlen(keyword), DBCS_StringHash(keyword)); + if( strlen(genbuf) > MAXPATHLEN - 50 ) + return READ_REDRAW; // avoid overflow + + if (currstat == RMAIL) + sethomefile(newdirect, cuser.userid, genbuf); + else + setbfile(newdirect, currboard, genbuf); + + filetime = dasht(newdirect); + count = dashs(newdirect) / sizeof(fileheader_t); + + if (currstat != RMAIL && currboard[0] && currbid > 0) + { + time4_t filecreate = dashc(newdirect); + boardheader_t *bp = getbcache(currbid); + assert(bp); + + if (bp->SRexpire) + { + if (bp->SRexpire > now) // invalid expire time. + bp->SRexpire = now; + + if (bp->SRexpire > filecreate) + filetime = -1; + } + } + + if(filetime<0 || now-filetime>60*60) { + reload = 1; + inc = 0; + } else if(now-filetime > 3*60) { + reload = 1; + inc = 1; + } else { + /* use cached data */ + reload = 0; + inc = 0; + } + + /* mark and recommend shouldn't incremental select */ + if(sr_mode & (RS_MARK | RS_RECOMMEND)) + inc = 0; + + if(reload) { + if( (fr = open(currdirect, O_RDONLY, 0)) != -1 ) { + if(inc) { + /* find incremental selection start point */ + int idx; + sprintf(fhs[0].filename, "X.%d", (int)filetime); + idx = getindex(currdirect, &fhs[0], 0); + if(idx<0) { + reference = -idx; + } else if(idx==0) { + inc = 0; + } else { + reference = idx; + } + } + if(inc) { + filemode = O_APPEND | O_RDWR; + } else { + filemode = O_CREAT | O_RDWR; + count = 0; + reference = 0; + } + + if( (fd = open(newdirect, filemode, 0600)) == -1 ) { + close(fr); + return READ_REDRAW; + } + + if(reference>0) + lseek(fr, reference*sizeof(fileheader_t), SEEK_SET); + +#ifdef DEBUG + vmsgf("search: %s", currdirect); +#endif + while( (len = read(fr, fhs, sizeof(fhs))) > 0 ){ + len /= sizeof(fileheader_t); + for( i = 0 ; i < len ; ++i ){ + reference++; + if( (sr_mode & RS_MARK) && + !(fhs[i].filemode & FILE_MARKED) ) + continue; + else if((sr_mode & RS_NEWPOST) && + !strncmp(fhs[i].title, "Re:", 3)) + continue; + else if((sr_mode & RS_AUTHOR) && + !dbcs_strcasestr(fhs[i].owner, keyword)) + continue; + else if((sr_mode & RS_KEYWORD) && + !dbcs_strcasestr(fhs[i].title, keyword)) + continue; + else if(sr_mode & RS_KEYWORD_EXCLUDE && + dbcs_strcasestr(fhs[i].title, keyword)) + continue; + else if((sr_mode & RS_TITLE) && + strcasecmp(subject(fhs[i].title), keyword)) + continue; + else if ((sr_mode & RS_RECOMMEND) && + (n_recommend > 0 ? + (fhs[i].recommend < n_recommend) : + (fhs[i].recommend > n_recommend) )) + continue; + /* please put money test in last */ + else if ((sr_mode & RS_MONEY) && + query_file_money(fhs+i) < n_money) + continue; + + if(first_select) { + fhs[i].multi.refer.flag = 1; + fhs[i].multi.refer.ref = reference; + } + ++count; + write(fd, &fhs[i], sizeof(fileheader_t)); + } + } // end while + close(fr); + ftruncate(fd, count*sizeof(fileheader_t)); + close(fd); + } + } + + if(count) { + strlcpy(currdirect, newdirect, sizeof(currdirect)); + currmode |= MODE_SELECT; + currsrmode |= sr_mode; + return NEWDIRECT; + } + 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) +{ + int mode = DONOTHING, num, new_top=10; + int ch, new_ln = locmem->crs_ln, lastmode = DONOTHING; + char direct[60]; + static char default_ch = 0; + + do { + if( (mode = cursor_pos(locmem, new_ln, new_top, default_ch ? 0 : 1)) + != DONOTHING ) + return mode; + + if( !default_ch ) + ch = igetch(); + else{ + if(new_ln != locmem->crs_ln) {// move fault + default_ch=0; + return FULLUPDATE; + } + ch = default_ch; + } + + new_top = 10; // default 10 + switch (ch) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if( (num = search_num(ch, last_line)) != -1 ) + new_ln = num + 1; + break; + case 'q': + case 'e': + case KEY_LEFT: + if(currmode & MODE_SELECT && locmem->crs_ln>0){ + char genbuf[PATHLEN]; + fileheader_t *fhdr = &headers[locmem->crs_ln - locmem->top_ln]; + board_select(); + setbdir(genbuf, currboard); + locmem = getkeep(genbuf, 0, 1); + locmem->crs_ln = fhdr->multi.refer.ref; + num = locmem->crs_ln - p_lines + 1; + locmem->top_ln = num < 1 ? 1 : num; + mode = NEWDIRECT; + } + else + mode = + (currmode & MODE_DIGEST) ? board_digest() : DOQUIT; + break; + case '#': + { + char aidc[100]; + aidu_t 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'): + redrawwin(); + refresh(); + break; + + case Ctrl('H'): + mode = select_read(locmem, RS_NEWPOST); + break; + + case 'Z': + mode = select_read(locmem, RS_RECOMMEND); + break; + + case 'a': + mode = select_read(locmem, RS_AUTHOR); + break; + + case 'A': + mode = select_read(locmem, RS_MONEY); + break; + + case 'G': + mode = select_read(locmem, RS_MARK); + break; + + case '/': + case '?': + mode = select_read(locmem, RS_KEYWORD); + break; + + case 'S': + mode = select_read(locmem, RS_TITLE); + break; + + case '!': + mode = select_read(locmem, RS_KEYWORD_EXCLUDE); + break; + + case '=': + new_ln = thread(locmem, RELATE_FIRST); + break; + + case '\\': + new_ln = thread(locmem, CURSOR_FIRST); + break; + + case ']': + new_ln = thread(locmem, RELATE_NEXT); + break; + + case '+': + new_ln = thread(locmem, CURSOR_NEXT); + break; + + case '[': + new_ln = thread(locmem, RELATE_PREV); + break; + + case '-': + new_ln = thread(locmem, CURSOR_PREV); + break; + + case '<': + case ',': + new_ln = thread(locmem, NEWPOST_PREV); + break; + + case '.': + case '>': + new_ln = thread(locmem, NEWPOST_NEXT); + break; + + case 'p': + case 'k': + case KEY_UP: + if (locmem->crs_ln <= 1) { + new_ln = last_line; + new_top = p_lines-1; + } else { + new_ln = locmem->crs_ln - 1; + new_top = p_lines - 2; + } + break; + + case 'n': + case 'j': + case KEY_DOWN: + new_ln = locmem->crs_ln + 1; + new_top = 1; + break; + + case ' ': + case KEY_PGDN: + case 'N': + case Ctrl('F'): + new_ln = locmem->top_ln + p_lines; + new_top = 0; + break; + + case KEY_PGUP: + case Ctrl('B'): + case 'P': + new_ln = locmem->top_ln - p_lines; + new_top = 0; + break; + + /* add home(top entry) support? */ + case KEY_HOME: + new_ln = 0; + new_top = 0; + break; + + case KEY_END: + case '$': + new_ln = last_line; + new_top = p_lines-1; + break; + + case 'F': + case 'U': + if (HasUserPerm(PERM_FORWARD) && locmem->crs_ln>0) { + mail_forward(&headers[locmem->crs_ln - locmem->top_ln], + currdirect, ch /* == 'U' */ ); + /* by CharlieL */ + // mode = READ_REDRAW; + return FULLUPDATE; + } + break; + + case Ctrl('Q'): + if(locmem->crs_ln>0) + mode = my_query(headers[locmem->crs_ln - locmem->top_ln].owner); + break; + + case Ctrl('S'): + if (HasUserPerm(PERM_ACCOUNTS|PERM_SYSOP) && locmem->crs_ln>0) { + int id; + userec_t muser; + + strlcpy(currauthor, + headers[locmem->crs_ln - locmem->top_ln].owner, + sizeof(currauthor)); + stand_title("使用者設定"); + move(1, 0); + if ((id = getuser(headers[locmem->crs_ln - locmem->top_ln].owner, &muser))) { + user_display(&muser, 1); + if( HasUserPerm(PERM_ACCOUNTS) ) + uinfo_query(&muser, 1, id); + else + pressanykey(); + } + mode = FULLUPDATE; + } + break; + + /* rocker.011018: 採用新的tag模式 */ + case 't': + if(locmem->crs_ln == 0) + break; + /* 將原本在 Read() 裡面的 "TagNum = 0" 移至此處 */ + if ((currstat & RMAIL && TagBoard != 0) || + (!(currstat & RMAIL) && TagBoard != bid)) { + if (currstat & RMAIL) + TagBoard = 0; + else + TagBoard = bid; + TagNum = 0; + } + /* rocker.011112: 解決再select mode標記文章的問題 */ + if (Tagger(atoi(headers[locmem->crs_ln - locmem->top_ln].filename + 2), + (currmode & MODE_SELECT) ? + (headers[locmem->crs_ln - locmem->top_ln].multi.refer.ref) : + locmem->crs_ln, TAG_TOGGLE)) + { +// (*doentry) (locmem->crs_ln, &headers[locmem->crs_ln-locmem->top_ln]); + locmem->crs_ln ++; + // new_ln = locmem->crs_ln + 1; + // new_top = 1; + // mode = FULLUPDATE; + // mode = PART_REDRAW; + mode = PARTUPDATE; + } + break; + + case Ctrl('C'): + if (TagNum) { + TagNum = 0; + mode = FULLUPDATE; + } + break; + + case Ctrl('T'): + /* XXX duplicated code, copy from case 't' */ + if ((currstat & RMAIL && TagBoard != 0) || + (!(currstat & RMAIL) && TagBoard != bid)) { + if (currstat & RMAIL) + TagBoard = 0; + else + TagBoard = bid; + TagNum = 0; + } + mode = TagThread(currdirect); + break; + + case Ctrl('D'): + mode = TagPruner(bid); + break; + + case '\n': + case '\r': + case 'l': + case KEY_RIGHT: + ch = 'r'; + default: + if( ch == 'h' && currmode & (MODE_DIGEST) ) + break; + if (ch > 0 && ch <= onekey_size) { + int (*func)() = rcmdlist[ch - 1].func; + if(rcmdlist[ch - 1].needitem && locmem->crs_ln == 0) + break; + if (func != NULL){ + num = locmem->crs_ln - bottom_line; + + if(!rcmdlist[ch - 1].needitem) + mode = (*func)(); + else if( num > 0 ){ + sprintf(direct,"%s.bottom", currdirect); + mode= (*func)(num, &headers[locmem->crs_ln-locmem->top_ln], + direct, locmem->crs_ln - locmem->top_ln); + } + else + mode = (*func)(locmem->crs_ln, + &headers[locmem->crs_ln - locmem->top_ln], + currdirect, locmem->crs_ln - locmem->top_ln); + if(mode == READ_SKIP) + mode = lastmode; + + // 以下這幾種 mode 要再處理游標 + if(mode == READ_PREV || mode == READ_NEXT || + mode == RELATE_PREV || mode == RELATE_FIRST || + mode == AUTHOR_NEXT || mode == AUTHOR_PREV || + mode == RELATE_NEXT){ + lastmode = mode; + + switch(mode){ + case READ_PREV: + new_ln = locmem->crs_ln - 1; + break; + case READ_NEXT: + new_ln = locmem->crs_ln + 1; + break; + case RELATE_PREV: + new_ln = thread(locmem, RELATE_PREV); + break; + case RELATE_NEXT: + new_ln = thread(locmem, RELATE_NEXT); + /* XXX: 讀到最後一篇要跳出來 */ + if( new_ln == locmem->crs_ln ){ + default_ch = 0; + return FULLUPDATE; + } + break; + case RELATE_FIRST: + new_ln = thread(locmem, RELATE_FIRST); + break; + case AUTHOR_PREV: + new_ln = thread(locmem, AUTHOR_PREV); + break; + case AUTHOR_NEXT: + new_ln = thread(locmem, AUTHOR_NEXT); + break; + } + mode = DONOTHING; default_ch = 'r'; + } + else { + default_ch = 0; + lastmode = DONOTHING; + } + } //end if (func != NULL) + } // ch > 0 && ch <= onekey_size + break; + } // end switch + } while (mode == DONOTHING); + return mode; +} + +// recbase: 顯示位置的開頭 +// headers_size:要顯示幾行 +// last_line: 全板 .DIR + 置底 的有效數目 +// bottom_line: 全板 .DIR (無置底) 的有效數目 + +// XXX never return -1! + +static int +get_records_and_bottom(char *direct, fileheader_t* headers, + int recbase, int headers_size, int last_line, int bottom_line) +{ + // n: 置底除外的可顯示數目 + int n = bottom_line - recbase + 1, rv = 0; + + if( last_line < 1) // 完全沒東西 + return 0; + + // 不顯示置底的情形 + if( n >= headers_size || (currmode & (MODE_SELECT | MODE_DIGEST)) ) + { + rv = get_records(direct, headers, sizeof(fileheader_t), + recbase, headers_size); + return rv > 0 ? rv : 0; + } + + //////// 顯示本文+置底: //////// + + // 讀取 .DIR 本文 + if (n > 0) + { + n = get_records(direct, headers, sizeof(fileheader_t), recbase, n); + if (n < 0) n = 0; + rv += n; // rv 為有效本文數 + + recbase = 1; + } else { + // n <= 0 + recbase = 1 + (-n); + } + + // 讀取置底 (注意 recbase 可能超過 bottom_line, 也就是以置底第 n 個開始) + n = last_line - bottom_line +1 - (recbase-1); + if (rv + n > headers_size) + n = headers_size - rv; + + if (n > 0) { + char directbottom[PATHLEN]; + snprintf(directbottom, sizeof(directbottom), "%s.bottom", direct); + n = get_records(directbottom, headers+rv, sizeof(fileheader_t), recbase, n); + if (n < 0) n = 0; + rv += n; + } + + return rv; +} + +void +i_read(int cmdmode, const char *direct, void (*dotitle) (), + void (*doentry) (), const onekey_t * rcmdlist, int bidcache) +{ + keeploc_t *locmem = NULL; + int recbase = 0, mode; + int entries = 0; + char currdirect0[PATHLEN]; + int last_line0 = last_line; + int bottom_line = 0; + fileheader_t *headers0 = headers; + int headers_size0 = headers_size; + + strlcpy(currdirect0, currdirect, sizeof(currdirect0)); +#define FHSZ sizeof(fileheader_t) + /* Ptt: 這邊 headers 可以針對看板的最後 60 篇做 cache */ + headers_size = p_lines; + headers = (fileheader_t *) calloc(headers_size, FHSZ); + assert(headers != NULL); + strlcpy(currdirect, direct, sizeof(currdirect)); + mode = NEWDIRECT; + + do { + /* 依據 mode 顯示 fileheader */ + setutmpmode(cmdmode); + switch (mode) { + case DONOTHING: + break; + + case NEWDIRECT: /* 第一次載入此目錄 */ + case DIRCHANGED: + if (bidcache > 0 && !(currmode & (MODE_SELECT | MODE_DIGEST))){ + if( (last_line = getbtotal(currbid)) == 0 ){ + setbtotal(currbid); + setbottomtotal(currbid); + last_line = getbtotal(currbid); + } + bottom_line = last_line; + last_line += getbottomtotal(currbid); + } + else + bottom_line = last_line = get_num_records(currdirect, FHSZ); + + if (mode == NEWDIRECT) { + int num; + num = last_line - p_lines + 1; + locmem = getkeep(currdirect, num < 1 ? 1 : num, + bottom_line ? bottom_line : last_line); + if(newdirect_new_ln >= 0) + { + locmem->crs_ln = newdirect_new_ln + 1; + newdirect_new_ln = -1; + } + } + recbase = -1; + /* no break */ + + default: // for any unknown keys + case FULLUPDATE: + (*dotitle) (); + /* no break */ + + case PARTUPDATE: + if (headers_size != p_lines) { + headers_size = p_lines; + headers = (fileheader_t *) realloc(headers, headers_size*FHSZ); + assert(headers); + } + + /* In general, records won't be reloaded in PARTUPDATE state. + * But since a board is often changed and cached, it is always + * reloaded here. */ + if (bidcache > 0 && !(currmode & (MODE_SELECT | MODE_DIGEST))) { + int rec_num; + bottom_line = getbtotal(currbid); + rec_num = bottom_line + getbottomtotal(currbid); + if (last_line != rec_num) { + last_line = rec_num; + recbase = -1; + } + } + + if (recbase != locmem->top_ln) { //headers reload + recbase = locmem->top_ln; + if (recbase > last_line) { + recbase = last_line - headers_size + 1; + if (recbase < 1) + recbase = 1; + locmem->top_ln = recbase; + } + /* XXX if entries return -1 or black-hole */ + entries = get_records_and_bottom(currdirect, + headers, recbase, headers_size, last_line, bottom_line); + } + if (locmem->crs_ln > last_line) + locmem->crs_ln = last_line; + move(3, 0); + clrtobot(); + /* no break */ + case PART_REDRAW: + move(3, 0); + if( last_line == 0 ) + outs(" 沒有文章..."); + else { + int i; + for( i = 0; i < entries ; i++ ) + (*doentry) (locmem->top_ln + i, &headers[i]); + } + /* no break */ + case READ_REDRAW: + if(curredit & EDIT_ITEM) + outmsglr(ANSI_COLOR(44) " 私人收藏 " ANSI_COLOR(30;47), 10, + " 繼續? ", 7); + else if (curredit & EDIT_MAIL) + outmsglr(MSG_MAILER, MSG_MAILER_LEN, "", 0); + else + outmsglr(MSG_POSTER, MSG_POSTER_LEN, "", 0); + break; + + case TITLE_REDRAW: + (*dotitle) (); + break; + + case HEADERS_RELOAD: + if (recbase != locmem->top_ln) { + recbase = locmem->top_ln; + if (recbase > last_line) { + recbase = last_line - p_lines + 1; + if (recbase < 1) + recbase = 1; + locmem->top_ln = recbase; + } + if(headers_size != p_lines) { + headers_size = p_lines; + headers = (fileheader_t *) realloc(headers, headers_size*FHSZ); + assert(headers); + } + /* XXX if entries return -1 */ + entries = + get_records_and_bottom(currdirect, headers, recbase, + headers_size, last_line, bottom_line); + } + break; + } //end switch + mode = i_read_key(rcmdlist, locmem, currbid, bottom_line); + } while (mode != DOQUIT); +#undef FHSZ + + free(headers); + last_line = last_line0; + headers = headers0; + headers_size = headers_size0; + strlcpy(currdirect, currdirect0, sizeof(currdirect)); + return; +} |