/*-------------------------------------------------------*/
/* announce.c	( NTHU CS MapleBBS Ver 2.36 )		 */
/*-------------------------------------------------------*/
/* target : 精華區閱讀、編選				 */
/* create : 95/03/29				 	 */
/* update : 95/12/15				 	 */
/*-------------------------------------------------------*/
/*
 * $Log: announce.c,v $
 * Revision 1.3  1996/08/27 15:30:39  bbsroot
 * Bug fixed: BM was not recognised in previous version. Hence BM cannot
 * maintain the Announce file directories. Now BM can (only) maintain them
 * by entering the Announce function through their own boards.
 *
 * Revision 1.2  1996/08/26 15:40:06  bbsroot
 * Working version with access control.
 *
 * Revision 1.1  1996/08/24 07:33:03  bbsroot
 * Initial revision
 *
 */


#include "bbs.h"
#include <time.h>

#define MAXITEMS	512	/* 一層目錄中最多有幾項？ */
#define PATHLEN		128

#define ADDITEM		0
#define	ADDGROUP	1


enum
{
  NOBODY, MANAGER, SYSOP
};


typedef struct
{
  char title[63];
  char fdate[9];		/* [mm/dd/yy] */
  char fname[32];
  char access[2];
}      ITEM;


typedef struct
{
  ITEM *item[MAXITEMS];
  char mtitle[STRLEN];
  char *path;
  int num, page, now, level;
}      MENU;


static int a_fmode = 0;
static ITEM copybuf;
static char copyfile[PATHLEN];
static char *mytitle = BOARDNAME "佈告欄";
static char *err_dup_fn = "系統中已有同名檔案存在了！";
static int a_user_access_level = 1;


static void
a_timestamp(buf, time)
  char *buf;
  time_t *time;
{
  struct tm *pt = localtime(time);
  sprintf(buf, "%02d/%02d/%02d", pt->tm_mon + 1, pt->tm_mday, pt->tm_year);
}


static void
a_additem(pm, myitem)
  MENU *pm;
  ITEM *myitem;
{
  if (pm->num < MAXITEMS)
  {
    ITEM *newitem = (ITEM *) malloc(sizeof(ITEM));
    memcpy(newitem, myitem, sizeof(ITEM));
    pm->item[(pm->num)++] = newitem;
  }
}


static void
a_savenames(pm)
  MENU *pm;
{
  FILE *fn;
  ITEM *item;
  char fpath[PATHLEN];
  int n;

  sprintf(fpath, "%s%s", pm->path, fn_mandex);
  if (fn = fopen(fpath, "w"))
  {
    fprintf(fn, "# Title=%s\n\n", pm->mtitle);
    n = 0;
    while (n < pm->num)
    {
      item = pm->item[n++];
      fprintf(fn, "Name=%s\nDate=%s\nAccess=%s\nPath=%s\n\n",
	item->title, item->fdate, item->access, item->fname);
    }
    fclose(fn);
    chmod(fpath, 0644);
  }
}


static void
a_loadnames(pm)
  MENU *pm;
{
  FILE *fn;
  char buf[256], *ptr;
  ITEM item;
  int dirty;
  int access;

  pm->num = access = 0;
  /* access 若沒有定義，當做是第一級使用者 */
  sprintf(buf, "%s%s", pm->path, fn_mandex);
  if (fn = fopen(buf, "r"))
  {
    dirty = item.fdate[0] = 0;

    while (fgets(buf, 255, fn))
    {
      if (ptr = strchr(buf, '\n'))
	*ptr = '\0';
      ptr = buf + 5;
      if (!strncmp(buf, "Name=", 5))
      {
	strcpy(item.title, ptr);
      }
      else if (!strncmp(buf, "Date=", 5))
      {
	strcpy(item.fdate, ptr);
      }
      /*** 註：以下的 if statement 在系統穩定後可去掉 -- 這是自動去掉 "~/" */
      else if (!strncmp(buf, "Path=~/", 7))
      {
	dirty = 2;		/* 確保會更新 .Names 檔案 */
	strcpy(item.fname, buf + 7);
	if (item.fdate[0] == '\0')
	{
	  struct stat st;

	  sprintf(buf, "%s/%s", pm->path, item.fname);
	  if (stat(buf, &st) == 0)
	  {
	    a_timestamp(item.fdate, &st.st_mtime);
	    dirty = 2;
	  }
	}
	if (!access) {
	  sprintf(item.access, "1");
	  access = 1;
	  dirty = 2;
	}
	if (a_user_access_level >= access) {
	    a_additem(pm, &item);
	    access = 1;
	}
	item.fdate[0] = '\0';
      }
      /*** 註：到這裡 ******************************************************/
      else if (!strncmp(buf, "Path=", 5))
      {
	strcpy(item.fname, ptr);
	if (item.fdate[0] == '\0')
	{
	  struct stat st;

	  sprintf(buf, "%s/%s", pm->path, item.fname);
	  if (stat(buf, &st) == 0)
	  {
	    a_timestamp(item.fdate, &st.st_mtime);
	    dirty = 2;
	  }
	}
	/*** 註：以下的 if statement 在系統穩定後可去掉 */
	if (!access) {		/* 沒有定義 Access= */
	  sprintf(item.access, "1");
	  access = 1;
	  dirty = 2;		/* 確保會更新檔案   */
	}
	/* 視 user 的使用等級給與閱讀權 */
	if (a_user_access_level >= access) {
	    a_additem(pm, &item);
	    access = 1;
	}
	item.fdate[0] = '\0';
      }
      else if (!strncmp(buf, "Access=", 7))
      {
	strcpy(item.access, buf + 7);
	item.access[1] = 0;
	access = atoi(item.access);

	/*** 註：以下的 if statement 在系統穩定後可去掉 ***/
	if (!access) {		/* 出現空的 Access= */
	  access = 1;		/* 設訂為 Access=1  */
	  sprintf(item.access, "1");
	  dirty = 2;		/* 確保會更新檔案   */
	}
      }
      else if (!strncmp(buf, "# Title=", 8))
      {
	strcpy(pm->mtitle, buf + 8);
	dirty++;
      }
    }
    fclose(fn);

    if (pm->mtitle[0] == '\0')
      strcpy(pm->mtitle, mytitle);
    if (dirty >= 2)
      a_savenames(pm);
  }
}


static void
a_showmenu(pm)
  MENU *pm;
{
  static char *mytype[] = {"日    期", "檔案名稱"};
  char *title;
  int n, max;
  ITEM *item;

  showtitle("精華文章", pm->mtitle);
  prints("  [1;36m編號    標      題%56s[0m", mytype[a_fmode]);

  if (pm->num)
  {
    n = pm->page;
    max = n + p_lines;
    if (max > pm->num)
      max = pm->num;
    while (n < max)
    {
      item = pm->item[n++];
      title = item->title;
      if (a_fmode)
      {
	prints("\n%5d. %-52.51s%s", n, title, item->fname);
	if (title[1] == (char) 0xbb)
	  outc('/');
      }
      else
      {

#ifdef	INFO_COLOR
	if (title[1] == (char) 0xbb)	/* JhLin: directory */
	  prints("\n%5d. [1;33m%s[0m", n, title);
	else
#endif
	  prints("\n%5d. %-60s[%s]", n, title, item->fdate);
      }
    }
  }
  else
  {
    //outs("\n  《精華區》尚在吸取天地間的日月精華中... :)");
      outs("\n  《精華區》正在努力建立之中............. :)");
  }

  move(b_lines, 1);
  outs(pm->level ?
    "[34;46m 【板  主】 [31;47m  (h)[30m說明  [31m(q/←)[30m離開  [31m(a)[30m新增文章  [31m(g)[30m新增目錄  [31m(e)[30m編輯檔案  [0m" :
    "[34;46m 【功\能鍵】 [31;47m  (h)[30m說明  [31m(q/←)[30m離開  [31m(k↑j↓)[30m移動游標  [31m(enter/→)[30m讀取資料  [0m");
}


static void
a_showhelp(level)
  int level;
{
  clear();
  outs("[36m【 " BOARDNAME "公佈欄使用說明 】[m\n\n\
[←][q]         離開到上一層目錄\n\
[↑][k]         上一個選項\n\
[↓][j]         下一個選項\n\
[→][r][enter]  進入目錄／讀取文章\n\
[^B][PgUp]      上頁選單\n\
[^F][PgDn][Spc] 下頁選單\n\
[##]            移到該選項\n\n\
[F]             將文章寄回 Internet 郵箱\n\
[U]             將文章 uuencode 後寄回郵箱\n");

  if (level >= MANAGER)
  {
    outs("\n[36m【 板主專用鍵 】[m\n\
[a/g]           收錄精華文章/開闢目錄\n\
[m/d]           移動/刪除文章\n\
[t/e]           修改文章標題/內容\n\
[f/n]           查詢/更改檔名\n\
[c/p]           拷貝/粘貼文章\n");
  }

  if (level >= SYSOP)
  {
    outs("\n[36m【 站長專用鍵 】[m\n\
[v]             編輯 .Names\n");
  }
  pressanykey();
}


static void
a_newitem(pm, mode)
  MENU *pm;
{
  static char *mesg[2] = {
    "[新增文章] 請輸入檔案名稱：",	/* ADDITEM */
  "[新增目錄] 請輸入目錄名稱："};	/* ADDGROUP */

  char fpath[PATHLEN];
  ITEM item;
  time_t dtime;

  pm->page = 9999;
  if (!getdata(t_lines - 2, 1, mesg[mode], item.fname, 32, DOECHO))
    return;
  if (invalid_fname(item.fname))
  {
    outs(err_filename);
    igetch();
  }
  else
  {
    sprintf(fpath, "%s/%s", pm->path, item.fname);
    if (dashf(fpath) || dashd(fpath))
    {
      outs(err_dup_fn);
      igetch();
    }
    else
    {
      strcpy(item.title, "◇ ");/* A1BA/BB ◆ */
      if (!getdata(b_lines - 1, 1, "請輸入標題：", &item.title[3], 60, DOECHO))
	return;
      if (pm->level == SYSOP) {
        if (!getdata(b_lines - 1, 1, "請輸入可閱\讀者最低級數：",
		item.access, 2, DOECHO))
	  return;
      } else {
	sprintf(item.access, "%s", "1");	/* 板主開設的檔案為第一級可讀取 */
      }
      if (mode)
      {				/* ADDGROUP */
	mkdir(fpath, 0755);
	item.title[1] = 0xbb;
      }
      else
      {				/* ADDITEM */
	vedit(fpath, 0);
	chmod(fpath, 0644);
      }
      time(&dtime);
      a_timestamp(item.fdate, &dtime);
      a_additem(pm, &item);
      a_savenames(pm);
    }
  }
}


static void
a_moveitem(pm)
  MENU *pm;
{
  ITEM *tmp;
  char newnum[4];
  int num, n;
  char genbuf[200];

  sprintf(genbuf, "請輸入第 %d 選項的新次序：", pm->now + 1);
  if (!getdata(b_lines - 1, 1, genbuf, newnum, 4, DOECHO))
    return;
  num = (newnum[0] == '$') ? 9999 : atoi(newnum) - 1;
  if (num >= pm->num)
    num = pm->num - 1;
  else if (num < 0)
    num = 0;
  tmp = pm->item[pm->now];
  if (num > pm->now)
  {
    for (n = pm->now; n < num; n++)
      pm->item[n] = pm->item[n + 1];
  }
  else
  {
    for (n = pm->now; n > num; n--)
      pm->item[n] = pm->item[n - 1];
  }
  pm->item[num] = tmp;
  pm->now = num;
  a_savenames(pm);
}


static void
a_delete(pm)
  MENU *pm;
{
  ITEM *item;
  char fpath[PATHLEN];
  char ans[4];
  char genbuf[200];
  int n;

  item = pm->item[pm->now];
  sprintf(fpath, "%s/%s", pm->path, item->fname);

  if (dashf(fpath))
  {
    getdata(b_lines - 1, 1, "您確定要刪除此檔案嗎(Y/N)？[N] ", ans, 3, LCECHO);
    if (ans[0] != 'y')
      return;
    unlink(fpath);
  }
  else if (dashd(fpath))
  {
    getdata(b_lines - 1, 1, "您確定要刪除整個目錄嗎(Y/N)？[N] ", ans, 3, LCECHO);
    if (ans[0] != 'y')
      return;
    sprintf(genbuf, "/bin/rm -rf %s", fpath);
    system(genbuf);
  }

  free(item);
  (pm->num)--;
  for (n = pm->now; n < pm->num; n++)
    pm->item[n] = pm->item[n + 1];
  a_savenames(pm);
}


static void
a_newname(pm)
  MENU *pm;
{
  ITEM *item;
  char fname[32];
  char fpath[PATHLEN];
  char *mesg;
  char genbuf[200];

  item = pm->item[pm->now];
  if (!getdata(b_lines - 1, 1, "新檔名：", fname, 32, DOECHO))
    return;
  if (invalid_fname(fname))
  {
    mesg = err_filename;
  }
  else
  {
    sprintf(fpath, "%s/%s", pm->path, fname);
    if (dashf(fname) || dashd(fname))
    {
      mesg = err_dup_fn;
    }
    else
    {
      sprintf(genbuf, "%s/%s", pm->path, item->fname);
      if (!Rename(genbuf, fpath))
      {
	strcpy(item->fname, fname);
	a_savenames(pm);
	return;
      }
      mesg = "檔名更改失敗！";
    }
  }
  outs(mesg);
  igetch();
}


static void
a_copyitem(pm)
  MENU *pm;
{
  ITEM *item;

  item = pm->item[pm->now];
  memcpy(&copybuf, item, sizeof(ITEM));
  sprintf(copyfile, "%s/%s", pm->path, copybuf.fname);
  outmsg("檔案標記完成。[注意] 拷貝後才能刪除原文!");
  igetch();
}


static void
a_pasteitem(pm)
  MENU *pm;
{
  char newpath[PATHLEN];
  char ans[2];
  char genbuf[200];

  move(b_lines - 1, 1);
  if (copyfile[0])
  {
    sprintf(newpath, "%s/%s", pm->path, copybuf.fname);
    if (dashf(newpath) || dashd(newpath))
    {
      prints("檔案/目錄雷同：%s", copybuf.fname);
    }
    else if (strstr(newpath, copyfile))
    {
      outs("將目錄拷進自己的子目錄中，會造成無窮迴圈！");
    }
    else
    {
      sprintf(genbuf, "確定要拷貝[%s]嗎(Y/N)？[N] ", copybuf.title);
      getdata(b_lines - 1, 1, genbuf, ans, 3, LCECHO);
      if (ans[0] == 'y')
      {
	if (copybuf.title[1] == (char) 0xbb)
	{
	  sprintf(genbuf, "/bin/cp -r %s %s", copyfile, newpath);
	  system(genbuf);
	}
	else
	  Link(copyfile, newpath);
	a_additem(pm, &copybuf);
	a_savenames(pm);
      }
      return;
    }
  }
  else
  {
    outs("請先執行 copy 命令後再 paste");
  }
  igetch();
}


static void
a_forward(path, pitem, mode)
  char *path;
  ITEM *pitem;
  int mode;
{
  fileheader fhdr;

  strcpy(fhdr.filename, pitem->fname);
  strcpy(fhdr.title, pitem->title);
  switch (doforward(path, &fhdr, mode))
  {
  case 0:
    outmsg(msg_fwd_ok);
    break;
  case -1:
    outmsg(msg_fwd_err1);
    break;
  case -2:
    outmsg(msg_fwd_err2);
    break;
  }
}


/*
woju
*/
search_title(MENU me, int rev_flag)
{
   int i;
   char buf[100];
   static char search_str[40];
   int pos;

   sprintf(buf, "%c[%s] ", rev_flag ? '?' : '/', search_str);
   getdata(b_lines - 1, 0, buf, buf, 40, DOECHO);

   if (*buf)
      strcpy(search_str, buf);

   if (!*search_str)
      return me.now;

   str_lower(buf, search_str);

   for (i = 1; i <= me.num; i++)
      if (strstr_lower(me.item[pos = (me.now + (rev_flag ? me.num - i : i)) % me.num]->title, buf))
         return pos;

   return me.now;
}



void
a_menu(maintitle, path, lastlevel)
  char *maintitle;
  char *path;
  int lastlevel;
{
  MENU me;
  char fname[PATHLEN];
  char genbuf[200];
  int ch;

  /* ----------------------------------------------------- */
  /* 查詢使用者的分級數                                    */
  /* ----------------------------------------------------- */
  if (HAS_PERM(PERM_LEVEL3)) a_user_access_level = 3;		/* 第三級 */
  else if (HAS_PERM(PERM_LEVEL2)) a_user_access_level = 2;	/* 第二級 */
  else a_user_access_level = 1;					/* 第一級 */

  setutmpmode(ANNOUNCE);

  me.path = path;
  strcpy(me.mtitle, maintitle);
  a_loadnames(&me);

  /* 精華區-tree 中部份結構屬於 cuser ==> BM */

  if (!(me.level = lastlevel))
  {
    char *ptr;

    a_fmode = 0;

    /***     從 currBM 測，看使用者是否是板主    ***/
    /* 註：因無法從最上層的 .Names 辨認板主，只能  */
    /*     由板內按 Z 進入精華區才能夠辨認出板主。 */
    /*     請參閱 Announce() function 之說明。     */
    if (ptr = strstr(currBM, "："))
      me.level = is_BM(ptr + 2);
  }

  me.page = 9999;
  me.now = 0;
  for (;;)
  {
    if (me.now >= me.num && me.num > 0)
      me.now = me.num - 1;
    else if (me.now < 0)
      me.now = 0;

    if (me.now < me.page || me.now >= me.page + p_lines)
    {
      me.page = me.now - (me.now % p_lines);
      a_showmenu(&me);
    }

    ch = cursor_key(2 + me.now - me.page, 0);

    if (ch == 'q' || ch == 'Q' || ch == KEY_LEFT)
      break;

    if (ch >= '1' && ch <= '9')
    {
      if ((ch = search_num(ch, me.num)) != -1)
	me.now = ch;
      me.page = 9999;
      continue;
    }

    switch (ch)
    {
    case KEY_UP:
    case 'k':
      if (--me.now < 0)
	me.now = me.num - 1;
      break;

    case KEY_DOWN:
    case 'j':
      if (++me.now >= me.num)
	me.now = 0;
      break;

    case KEY_PGUP:
    case Ctrl('B'):
      if (me.now >= p_lines)
	me.now -= p_lines;
      else if (me.now > 0)
	me.now = 0;
      else
	me.now = me.num - 1;
      break;

    case ' ':
    case KEY_PGDN:
    case Ctrl('F'):
      if (me.now < me.num - p_lines)
	me.now += p_lines;
      else if (me.now < me.num - 1)
	me.now = me.num - 1;
      else
	me.now = 0;
      break;

    case '0':
       me.now = 0;
       break;
    case '?':
    case '/':
       me.now = search_title(me, ch == '?');
       me.page = 9999;
       break;
    case '$':
       me.now = me.num -1;
       break;
    case 'h':
      a_showhelp(me.level);
      me.page = 9999;
      break;
/*
woju
*/
    case Ctrl('R'): if (currutmp->msgs[0].last_pid) {
	 show_last_call_in();
	 my_write(currutmp->msgs[0].last_pid, "熱訊送回去：");
	 me.page = 9999;
      }
      break;

    case Ctrl('U'):
      t_users();
      me.page = 9999;
      break;
       
    case Ctrl('I'):
      t_idle();
      me.page = 9999;
      break;

    case 's':
       ReadSelect();
       me.page = 9999;
       break;

    case '\n':
    case '\r':
    case KEY_RIGHT:
    case 'r':
      if (me.now < me.num)
      {
	sprintf(fname, "%s/%s", path, me.item[me.now]->fname);
	if (dashf(fname)) {
/*
woju
*/
	  int more_result;

	  while (more_result = more(fname, YEA)) {
	     if (more_result == 1) {
                if (--me.now < 0) {
	           me.now = 0;
		   break;
		}
	     }
	     else if (more_result == 3) {
                if (++me.now >= me.num) {
	           me.now = me.num - 1;
		   break;
		}
	     }
	     else
		break;
	     sprintf(fname, "%s/%s", path, me.item[me.now]->fname);
	     if (!dashf(fname))
		break;
	  }
        }
	else if (dashd(fname))
	  a_menu(me.item[me.now]->title, fname, me.level);
	me.page = 9999;
      }
      break;

    case 'F':
    case 'U':
    case 'Z':
      sprintf(fname, "%s/%s", path, me.item[me.now]->fname);
      if (me.now < me.num && HAS_PERM(PERM_BASIC) && dashf(fname))
      {
	if (ch == 'Z')
	  z_download(fname);
	else
	  a_forward(path, me.item[me.now], ch == 'U');
      }
      else
	outmsg("無法轉寄此項目");
      
      me.page = 9999;
      refresh();
      sleep(1);
    }

    if (me.level >= MANAGER)
    {
      switch (ch)
      {
      case 'a':
	a_newitem(&me, ADDITEM);
	break;
      case 'g':
	a_newitem(&me, ADDGROUP);
	break;
      case 'p':
	a_pasteitem(&me);
	me.page = 9999;
      }

      if (me.num)
	switch (ch)
	{
	case 'm':
	  a_moveitem(&me);
	  me.page = 9999;
	  break;

	case 'D':
	  me.page = -1;
	case 'd':
	  a_delete(&me);
	  me.page = 9999;
	  break;

	case 't':
	  if (getdata(b_lines - 1, 1, "新標題：", genbuf, 60, DOECHO))
	  {
	    ITEM *item = me.item[me.now];
	    char *p = item->title;
	
/* modify by Timothy	
	    strcpy(p + 3, genbuf);
*/
	    if (!strncmp("◇ ", p, 3) || !strncmp("◆ ", p, 3))
	    {
	    	strcpy(p+3, genbuf);
	    } else {
	    	strcpy(p, genbuf);
	    }
	    a_savenames(&me);
	    if (p[1] == (char) 0xbb)
	    {
	      FILE *fn;

	      sprintf(fname, "%s/%s%s", path, item->fname, fn_mandex);
	      if (fn = fopen(fname, "a+"))
	      {
		fprintf(fn, "\n# Title=%s\n", p);
		fclose(fn);
	      }
	    }
	  }
	  me.page = 9999;
	  break;

	case 'e':
	case 'E':
	  sprintf(fname, "%s/%s", path, me.item[me.now]->fname);
	  if (dashf(fname))
	  {
	    vedit(fname, 0);
	    me.page = 9999;
	  }
	  break;

	case 'f':
	  a_fmode ^= 1;
	  me.page = 9999;
	  break;

	case 'n':
	  a_newname(&me);
	  me.page = 9999;
	  break;

	case 'c':
	  a_copyitem(&me);
	  me.page = 9999;
	  break;
	}
    }

    if ((me.level == SYSOP) && (ch == 'v'))
    {
      sprintf(fname, "%s%s", path, fn_mandex);
      vedit(fname, 0);
      me.page = 9999;
    }
  }
  for (ch = 0; ch < me.num; ch++)
    free(me.item[ch]);
}


int
Announce()
{
  a_fmode = 0;
  currBM[0] = '\0';	/* 剛讀過自己的板，被認為是板主，若再由外的 Announce  */
			/* 功能進入精華區時，會變成是最上層精華區的板主。這裡 */
			/* 將板主行列 currBM = "板主：XXXX/XXXX... " 先清除。 */
  a_menu(mytitle, "man", (HAS_PERM(PERM_SYSOP) ? SYSOP : NOBODY));
  return 0;
}
