summaryrefslogblamecommitdiffstats
path: root/mbbsd/read.c
blob: 341e2320c96331e8e86e58e61689a2f84af83078 (plain) (tree)
1
2
3
4
5
6
7
8
9
          
                
 
                            
                                    
                                                               
 

                     


                                                           
                                                            




               
    
                   
 
                                      
               
 
             
 
                       
                                                     
                                                   


   
                                           

                                               
 
                         
                                                      

     










                                                                
 



                                                                 
 

                
                                              


                      
                                                   

                                                  
                             
 
                                                                                 
                              

                              





                                          

 
     

                                                
 
                                                                         
 
      

    


                                                                         






                        
                       
 


                        
                                                       






                 






                             

                                    
 

                             
              


                                         
 



                                                                             
                                                                 

                  
               



          
                             



                                   
 


                                   
 










                                                                     
 

                          




                  
 
                            


                                                       
                                                  

                             
                                                                       
                                            
                               
                          
                                                            



                                                
                   

                           
                                  
                             
 


                         





                                                           
                
                                                         
 






                                                                       
                                                                        







                                                          
                        
                               












                                               
         
          
                                     










                                                               

                             
             

 
    
                                 


                      
                         
                             





                                                  
          
                                                                 
 
                            



                           
                        
                        
                 
                
                                                 
                   

                                                          

                                          
                             


                                    
                            

                           
                                                

 















                                                                        
          
                                            
 
                    
                                                                     
                                 
                                                  
                 


                              
    
                         
                           
                                                  
                                 
                                 
        
                                                            
 


                                                         









                                                                                

                                    
                                                               
                          
                                                                          
                                    

                                                                  
                 
             
                                                                         
                      
         
                                       

                                             

                           
                                                          
                      

         
 

                  
 

                                                             
 
                  

 
                     
           
                                                                     




                                
                                      
                                
                  
                                             
           
                         

              
                           

              
                        
                           
      
              

                         
              


              


      
          
                                                  
 
                                                   
                    
                                 
                           
                    
                                                        
                        






                                                



                             
                          
 
                            


                                    
                                                                         
                                            




                                     
                                                                         

                                         
                  

                                                               
      
           
                                         
           

                                                  
                                            
                                                            
                                           

                                   

                                    
                                      
                                     
                                            
                                     




                                       
                                                    
                                                                             

                                   

                                
                                      
                                     
                                           
                                                                  
                                                                          
                                   
                                  
           

                                         
                                                                                           








                                                                         


                       
   
                                                 
                                   
                                                                     
                                      
                                             

                         
                                                    
       

                                              
                               

                                                   















                                                          








                                         
               


                                                        
                                                     


























                                                                
                                  
            
 

                                                                   
 


                                           



                                                           
                                              

                                                         


                                                         
                                                     

                                                        
                                                    
                                                              
                                
                                                     
                                                              
                                
                                                           
                                                              
                                
                                                             
                                                                 
                                
                                                        


                                                          
                                


                                                             
                                
 
                                     


                                                          
                           


                                                            
                     

                                                     
        

    
              

                                                          
                             

                        
                      

 

                                 
          
                                                          
                                    
 
                                              
                                                              
                                

        















                                                                            
                       
                               

                                                               




                                                                                                         



                              




                                                                     


                      
                                                           
                                     



                                                                               
                                                       




                                                   
                        

                                                                       


                             
                              



                                    
                                                                                      















































































































                                                                                              
                       
                        
                      
                  
 
                       
                                                   
                  




                                                     
                 


                                                  



                                                 
                 
                            
                                                  











                                                          

                  

                 


                                                   
                 


                                                 
                 
                                                           

                  
                 


                                                  
                  


                                                  
                 


                                                 
                 


                                                 
                 


                                                 
                 


                                                 

                 


                                                  

                 


                                                  

                 
                    






                                             























                                               





                                              







                                
                                                                


                                                                       

                                      
             


                       

                                                                                


                       
                                                                            





                                                                       
                                     
                           
                                                                                             
                                            



                                                    



                                  
 

                                                

                                   

                                                              
                                                           








                                                                                   
                                                                                   
                                                   








                                                                                     
                  

                   

                       
                              
         
              

                   








                                                           

                                     
 
                   

                              
 
                   



                   
                                                   
                  
                                          


                                                                

                                                    
                   


                                              
                                       
                                                            
                                                                               
                                                                           

                    
                                                   
                                                                             
                                                                                
                                     

                                    
                                               


                                                                  

                                        
 
                                 










                                                             




                                                       









                                                              

                                                       
                 

                                   
                                         


                                         
              
                       




                          
                                


                






                                             
          
                                                            
                                                                                   
 

                                                  
 
                                     
                 

                       
                                                                       





                                                                
                                    






                                                                           




                           

     

                                                                            




                                      
                                                                          
                                                                                    



                         
              
 
 
    
                                                           
                                                                   
 
                                  
                                      
                                
                                         
                                           
                                    
                                       
                                                 
                                     
 
                                                          
                                    
                                                            

                                                          
                            
                                                    

                     
        








                                                                        

                                       
                       


                       
                                              
                        


                                                                           
                                            
                                                   
                 
                                        
                                                      
             
                
                                                                            

                                    
                        
                                              

                                                                




                                                        

                         
                          
 
                                        
                        
                          
                          
 
                        




                                                                               
 








                                                                            


                                 
 
                                                             
                                         
                                          
                                                           
                                    


                                             
                                                            
                                                            
                                                                                   
             
                                           


                                           
                          

                         
                                
                                          

                      

                                                                 
             
                          
                         
                                     
                                       
                                                                                            
                
                                       
                                                                                            
                  
 

                          










                                                      




                                                                                   
                                              

                                                                        
                                                                                 

                  
                      
                                                                  
                                              

            

                           
                       
                                 
                                                         

           
/* $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 (vansf("◆ %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, BN_SECURITY) == 0)
        return DONOTHING;
    }
    if (TagNum && ((currstat != READING) || (currmode & MODE_BOARD))) {
    if (vans("刪除所有標記[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

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[PATHLEN];
   int first_select;
   char genbuf[PATHLEN], *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, DOECHO))
                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)
          {
          // TTLEN width exceed default screen
          // let's use TTLEN-4 here.
             if(!(currmode & MODE_SELECT) ||
                !getdata(b_lines, 0, "增加條件 排除標題: ", 
                 keyword, TTLEN-4, 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
         "的文章: ",
         // 因為有負數所以暫時不能用 NUMECHO
                 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, NUMECHO) || (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 | RS_SOLVED))
       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) > PATHLEN - 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 | RS_SOLVED))
       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;
           if( (sr_mode & RS_SOLVED) &&
               !(fhs[i].filemode & FILE_SOLVED) )
               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;
    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 Ctrl('Z'):
        // notify new usage
        // only BM will need this information.
        if ((currstat != RMAIL) && (currmode & MODE_BOARD))
        {
        move(b_lines-2, 0); clrtobot();
        outs(ANSI_COLOR(1;33) "置底的功\能鍵已改為 _ (shift-) 或 Ctrl-X。\n"
            "原 Ctrl-Z 現在是快速切換鍵,可在下列區域中切換 (按下對應按鍵即可):" ANSI_RESET);
        }
        mode = FULLUPDATE;
        if (ZA_Select())
        mode = DOQUIT;
        break;
        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, DOECHO))
          {
            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':
        // special types
        switch(vans( currmode & MODE_SELECT ? 
            "增加條件 標記(m/s)[m]: ":
            "搜尋標記(m/s)[m]: "))
        {
        case 's':
            mode = select_read(locmem, RS_SOLVED);
            break;

        default:
        case 'm':
            mode = select_read(locmem, RS_MARK);
            break;
        }
        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));
        vs_hdr("使用者設定");
        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 KEY_ENTER:
    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 ){
            char    direct[60];
                    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

    // ZA support
    if (ZA_Waiting())
        mode = DOQUIT;

    } 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;
    time4_t     enter_time = now;

    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 {
    /* 檢查權限是否已改 */
    if (currbid > 0 && getbcache(currbid)->perm_reload > enter_time)
    {
        boardheader_t *bp = getbcache(currbid);
        if(!HasBoardPerm(bp))
        break;
        enter_time = bp->perm_reload;
    }

    /* 依據 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_MAIL)
        vs_footer(" 鴻雁往返 ",
            " (R)回信 (x)站內轉寄 (y)回群組信 (d/D)刪信 (^P)寄發新信 \t(←/q)離開");
        else
        vs_footer(" 文章選讀 ",
            " (y)回應(X)推文(x)轉錄 (=[]<>)相關主題(/?a)搜尋標題/作者 (b)進板畫面");
        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 && !ZA_Waiting());
#undef  FHSZ

    free(headers);
    last_line = last_line0;
    headers = headers0;
    headers_size = headers_size0;
    strlcpy(currdirect, currdirect0, sizeof(currdirect));
    return;
}