summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpiaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2010-10-23 11:23:31 +0800
committerpiaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2010-10-23 11:23:31 +0800
commit5ff0600fafe917a9b692283f18d6b666af214538 (patch)
tree254d63fd599481f2266a9eff103633d2db0395d6
parentf85382c5c198704453473598273a5f451eacc20e (diff)
downloadpttbbs-5ff0600fafe917a9b692283f18d6b666af214538.tar
pttbbs-5ff0600fafe917a9b692283f18d6b666af214538.tar.gz
pttbbs-5ff0600fafe917a9b692283f18d6b666af214538.tar.bz2
pttbbs-5ff0600fafe917a9b692283f18d6b666af214538.tar.lz
pttbbs-5ff0600fafe917a9b692283f18d6b666af214538.tar.xz
pttbbs-5ff0600fafe917a9b692283f18d6b666af214538.tar.zst
pttbbs-5ff0600fafe917a9b692283f18d6b666af214538.zip
New UI subsystem: Panty & Stocking Browser (psb).
First two clients: view_edit_history and admin_edit were also included. git-svn-id: http://opensvn.csie.org/pttbbs/trunk@5143 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
-rw-r--r--pttbbs/include/proto.h4
-rw-r--r--pttbbs/include/pttstruct.h2
-rw-r--r--pttbbs/mbbsd/Makefile10
-rw-r--r--pttbbs/mbbsd/admin.c144
-rw-r--r--pttbbs/mbbsd/bbs.c51
-rw-r--r--pttbbs/mbbsd/mail.c4
-rw-r--r--pttbbs/mbbsd/psb.c460
7 files changed, 526 insertions, 149 deletions
diff --git a/pttbbs/include/proto.h b/pttbbs/include/proto.h
index 7ed488c9..8bca64ee 100644
--- a/pttbbs/include/proto.h
+++ b/pttbbs/include/proto.h
@@ -150,6 +150,10 @@ int card_99(void);
int ccw_talk(int fd, int destuid); // common chat window: private talk
int ccw_chat(int fd); // common chat window: chatroom
+/* psb (panty and stocking browser) */
+int psb_view_edit_history(const char *base, const char *subject, int max_hist);
+int psb_admin_edit();
+
/* chc */
void chc(int s, ChessGameMode mode);
int chc_main(void);
diff --git a/pttbbs/include/pttstruct.h b/pttbbs/include/pttstruct.h
index 3e8d7310..0f73f262 100644
--- a/pttbbs/include/pttstruct.h
+++ b/pttbbs/include/pttstruct.h
@@ -450,7 +450,7 @@ typedef struct menu_t {
/* Used to pass commands to the readmenu.
* direct mapping, indexed by ascii code. */
-#define onekey_size ((int) 'z')
+#define onekey_size ((int) '~')
/* keymap, 若 needitem = 0 表示不需要 item, func 的 type 應為 int (*)(void).
* 否則應為 int (*)(int ent, const fileheader_t *fhdr, const char *direct) */
typedef struct {
diff --git a/pttbbs/mbbsd/Makefile b/pttbbs/mbbsd/Makefile
index 6b7b0beb..a77dee71 100644
--- a/pttbbs/mbbsd/Makefile
+++ b/pttbbs/mbbsd/Makefile
@@ -8,19 +8,19 @@ SRCROOT= ..
#######################################################################
PROG= mbbsd
-COREOBJS = bbs.o announce.o read.o board.o cache.o cal.o brc.o mail.o record.o fav.o
+COREOBJS = bbs.o announce.o read.o board.o brc.o mail.o record.o fav.o
ACCOBJS = user.o register.o passwd.o emaildb.o
NETOBJS = mbbsd.o io.o term.o telnet.o
TALKOBJS = friend.o talk.o ccw.o
-UTILOBJS = stuff.o kaede.o convert.o name.o syspost.o
+UTILOBJS = stuff.o kaede.o convert.o name.o syspost.o cache.o cal.o
+UIOBJS = menu.o vtuikit.o psb.o
PAGEROBJS= more.o pmore.o
PLUGOBJS = calendar.o ordersong.o gamble.o angel.o
CHESSOBJS= chess.o chc.o chc_tab.o ch_go.o ch_gomo.o ch_dark.o ch_reversi.o
GAMEOBJS = card.o chicken.o
-OBJS:= admin.o assess.o edit.o menu.o xyz.o var.o vtuikit.o \
- vote.o voteboard.o \
+OBJS:= admin.o assess.o edit.o xyz.o var.o vote.o voteboard.o \
$(COREOBJS) $(ACCOBJS) $(NETOBJS) $(TALKOBJS) $(UTILOBJS) \
- $(PAGEROBJS) $(PLUGOBJS) \
+ $(UIOBJS) $(PAGEROBJS) $(PLUGOBJS) \
$(CHESSOBJS) $(GAMEOBJS)
#######################################################################
diff --git a/pttbbs/mbbsd/admin.c b/pttbbs/mbbsd/admin.c
index 854477b6..f3511a17 100644
--- a/pttbbs/mbbsd/admin.c
+++ b/pttbbs/mbbsd/admin.c
@@ -746,153 +746,11 @@ m_board(void)
return 0;
}
-static void
-str_unify_blank(char *s)
-{
- while(*s)
- {
- if (*s == '\t') *s = ' ';
- s++;
- }
-}
-
-// 偷懶一下,寫死最大上限。
-#define MAX_ENTRIES (100)
-#define min(a,b) ((a)<(b) ? (a) : (b))
-
-// TODO 哪天把這種 UI 寫的更 general 一點...
-
/* 設定系統檔案 */
int
x_file(void)
{
- char *entries[MAX_ENTRIES] = {NULL};
- int centries = 0, i = 0;
- char *fn, *v;
- int sel = 0, page = 0;
- char buf[PATHLEN];
- FILE *fp = NULL;
-
- fp = fopen(FN_CONF_EDITABLE, "rt");
- if (!fp)
- {
- // you can find a sample in sample/etc/editable
- vmsgf("未設定可編輯檔案列表[%s],請洽系統站長。", FN_CONF_EDITABLE);
- return 0;
- }
-
- // load the editable file.
- // format: filename [ \t]* description
- while (centries < MAX_ENTRIES && fgets(buf, sizeof(buf), fp))
- {
- if (!buf[0] || buf[0] == '#' || buf[0] == '.'
- || buf[0] == '/' || buf[0] == ' ')
- continue;
- str_unify_blank(buf); // replace all \t to ' ' in buf.
- v = strchr(buf, ' '); // find if description exists.
- if (v == NULL) continue;
- fn = strstr(buf, ".."); // see if someone trying to crack
- if (fn && fn < v) continue;
- // reject anything outside etc/ folder.
- if (strncmp(buf, "etc/", strlen("etc/")) != 0)
- continue;
- chomp(buf);
- entries[centries++] = strdup(buf);
- }
- fclose(fp);
- if (centries == 0)
- {
- vmsg("無可編輯檔案,請洽系統站長。");
- return 0;
- }
-
- // edit the files!
- while (sel >= 0)
- {
- const int rows = t_lines-2;
-
- // display.
- clear(); showtitle("系統檔案", "編輯系統檔案");
- for (i = page*rows; i < min(centries, (page+1)*rows); i++)
- {
- // parse entry
- strlcpy(buf, entries[i], sizeof(buf));
- fn = buf;
- v = strchr(fn, ' ');
- *v++ = 0;
- while (*v == ' ') v++;
- if (strlen(fn) > 30)
- strcpy(fn+30-2, "..");
- prints(" %3d. %s"
- "%-36.36s "
- ANSI_COLOR(0;1) "%-30.30s"
- ANSI_RESET "\n",
- i+1,
- dashf(fn) ? ANSI_COLOR(1;32) : ANSI_COLOR(1;30;40),
- v, fn);
- }
- vs_footer(" 編輯系統檔案 ",
- " (jk/↑↓/0-9)移動 (Enter/→)編輯 (d)刪除 \t(q/←)跳出");
- cursor_show(1+sel-page*rows, 0);
- switch((i = vkey()))
- {
- case 'q': case KEY_LEFT:
- sel = -1; continue;
- case KEY_HOME: sel = 0; break;
- case KEY_END: sel = centries-1; break;
- case KEY_PGDN: sel += rows;
- if (sel >= centries) sel = centries-1;
- break;
- case KEY_PGUP: sel -= rows;
- if (sel < 0) sel = 0;
- break;
- case 'k': case KEY_UP:
- if (sel > 0) sel--; break;
- case 'j': case KEY_DOWN:
- if (sel < centries-1) sel++; break;
-
- case 'd':
- strlcpy(buf, entries[sel], sizeof(buf));
- v = strchr(buf, ' '); *v++ = 0;
- i = vansf("確定要刪除 %s 嗎? (y/N) ", v);
- if (i == 'y')
- unlink(buf);
- vmsgf("系統檔案[%s]: %s", buf, !dashf(buf) ?
- "刪除成功\ " : "未刪除");
- break;
-
- case KEY_ENTER: case KEY_RIGHT:
- strlcpy(buf, entries[sel], sizeof(buf));
- v = strchr(buf, ' '); *v++ = 0;
- i = veditfile(buf);
- // log file change
- if (i != EDIT_ABORTED)
- {
- log_filef("log/etc_edit.log", LOG_CREAT,
- "%s %s %s # %s\n", Cdate(&now),
- cuser.userid, buf, v);
- }
- vmsgf("系統檔案[%s]: %s", buf, (i == -1) ?
- "未改變" : "更新完畢");
- break;
-
- default:
- if (i >= '0' && i <= '9')
- {
- sel = search_num(i, centries-1);
- if (sel < 0) sel = 0;
- }
- break;
- }
- // change page if required.
- page = sel / rows;
- }
-
- // free the entries
- for (i = 0; i < centries; i++)
- free(entries[i]);
-
- return FULLUPDATE;
+ return psb_admin_edit();
}
static int add_board_record(const boardheader_t *board)
diff --git a/pttbbs/mbbsd/bbs.c b/pttbbs/mbbsd/bbs.c
index 68245c04..1b112a23 100644
--- a/pttbbs/mbbsd/bbs.c
+++ b/pttbbs/mbbsd/bbs.c
@@ -3541,6 +3541,53 @@ view_postinfo(int ent, const fileheader_t * fhdr, const char *direct, int crs_ln
return FULLUPDATE;
}
+static int
+view_post_history(int ent, const fileheader_t * fhdr, const char *direct)
+{
+ const char *err_no_history = "抱歉,此篇文章暫無編輯歷史記錄可供檢視";
+ char hist_file[PATHLEN];
+ int fd, maxhist = 0;
+
+ // TODO allow author?
+ if (!(currmode & MODE_BOARD))
+ return DONOTHING;
+
+ if ((!fhdr) ||
+ ((fhdr->filename[0] == '.' || !fhdr->filename[0]) &&
+ (strcmp(fhdr->filename, FN_SAFEDEL) != 0))) {
+ vmsg(err_no_history);
+ return FULLUPDATE;
+ }
+
+ // build history index file name
+ setdirpath(hist_file, direct, FN_EDITHISTORY "/");
+ if (strcmp(FN_SAFEDEL, fhdr->filename) == 0) {
+ assert(strlen(FN_SAFEDEL) == strlen("M."));
+ // M.1 is a dirty hack, anyway..
+ strlcat(hist_file, "M.1", sizeof(hist_file));
+ strlcat(hist_file, fhdr->filename + strlen(FN_SAFEDEL) + 1, sizeof(hist_file));
+ } else {
+ strlcat(hist_file, fhdr->filename, sizeof(hist_file));
+ }
+
+ // TODO check if hist_file is a valid file (directoy gives fake result)
+
+ fd = open(hist_file, O_RDONLY);
+ if (fd < 0) {
+ vmsg(err_no_history);
+ return FULLUPDATE;
+ }
+ read(fd, &maxhist, sizeof(maxhist));
+ close(fd);
+ if (maxhist < 1) {
+ vmsg(err_no_history);
+ return FULLUPDATE;
+ }
+
+ psb_view_edit_history(hist_file, fhdr->title, maxhist+1);
+ return FULLUPDATE;
+}
+
#ifdef OUTJOBSPOOL
/* 看板備份 */
static int
@@ -4049,6 +4096,10 @@ const onekey_t read_comms[] = {
{ 1, old_cross_post }, // 'x'
{ 1, reply_post }, // 'y'
{ 0, b_man }, // 'z' 122
+ { 0, NULL }, // '{' 123
+ { 0, NULL }, // '|' 124
+ { 0, NULL }, // '}' 125
+ { 1, view_post_history }, // '~' 126
};
int
diff --git a/pttbbs/mbbsd/mail.c b/pttbbs/mbbsd/mail.c
index 0ddc60fc..84c4d69c 100644
--- a/pttbbs/mbbsd/mail.c
+++ b/pttbbs/mbbsd/mail.c
@@ -1934,6 +1934,10 @@ static const onekey_t mail_comms[] = {
{ 1, m_forward }, // 'x'
{ 1, multi_reply }, // 'y'
{ 0, mail_man }, // 'z' 122
+ { 0, NULL }, // '{' 123
+ { 0, NULL }, // '|' 124
+ { 0, NULL }, // '}' 125
+ { 0, NULL }, // '~' 126
};
int
diff --git a/pttbbs/mbbsd/psb.c b/pttbbs/mbbsd/psb.c
new file mode 100644
index 00000000..7c16c53b
--- /dev/null
+++ b/pttbbs/mbbsd/psb.c
@@ -0,0 +1,460 @@
+/* $Id$ */
+#include "bbs.h"
+
+// Panty & Stocking Browser
+//
+// A generic framework for displaying pre-generated data by a simplified
+// page-view user interface.
+//
+// Author: Hung-Te Lin (piaip)
+// --------------------------------------------------------------------------
+// Copyright (c) 2010 Hung-Te Lin <piaip@csie.ntu.edu.tw>
+// All rights reserved.
+// Distributed under BSD license (GPL compatible).
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// --------------------------------------------------------------------------
+
+///////////////////////////////////////////////////////////////////////////
+// Constant
+#define PSB_EOF (-1)
+#define PSB_NA (-2)
+#define PSB_NOP (-3)
+
+///////////////////////////////////////////////////////////////////////////
+// Data Structure
+typedef struct {
+ int curr, total, header_lines, footer_lines;
+ int key;
+ void *ctx;
+ int (*header)(void *ctx);
+ int (*footer)(void *ctx);
+ int (*renderer)(int i, int curr, int total, int rows, void *ctx);
+ int (*cursor)(int y, int curr, void *ctx);
+ int (*input_processor)(int key, int curr, int total, int rows, void *ctx);
+} PSB_CTX;
+
+static int
+psb_default_header(void *ctx) {
+ vs_hdr2bar("Panty & Stocking Browser", BBSNAME);
+ return 0;
+}
+
+static int
+psb_default_footer(void *ctx) {
+ vs_footer(" PSB 1.0 ",
+ " (↑/↓/PgUp/PgDn/0-9)Move (Enter/→)Select \t(q/←)Quit");
+ return 0;
+}
+
+static int
+psb_default_renderer(int i, int curr, int total, int rows, void *ctx) {
+ prints(" %s(Demo) %5d / %5d Item\n", (i == curr) ? "*" : " ", i, total);
+ return 0;
+}
+
+static int
+psb_default_cursor(int y, int curr, void * ctx) {
+ outs("=>");
+ // cursor_show(y, 0);
+ return 0;
+}
+
+static int
+psb_default_input_processor(int key, int curr, int total, int rows, void *ctx) {
+ switch(key) {
+ case 'q':
+ case KEY_LEFT:
+ return PSB_EOF;
+
+ case KEY_HOME:
+ case '0':
+ return 0;
+
+ case KEY_END:
+ case '$':
+ return total-1;
+
+ case KEY_PGUP:
+ case Ctrl('B'):
+ case 'N':
+ if (curr / rows > 0)
+ return curr - rows;
+ return 0;
+
+ case KEY_PGDN:
+ case Ctrl('F'):
+ case 'P':
+ if (curr + rows < total)
+ return curr + rows;
+ return total - 1;
+
+ case KEY_UP:
+ case Ctrl('P'):
+ case 'p':
+ case 'k':
+ return (curr > 0) ? curr-1 : curr;
+
+ case KEY_DOWN:
+ case Ctrl('N'):
+ case 'n':
+ case 'j':
+ return (curr + 1 < total) ? curr + 1 : curr;
+
+ default:
+ if (key >= '0' && key <= '9') {
+ int newval = search_num(key, total);
+ if (newval >= 0 && newval < total)
+ return newval;
+ return curr;
+ }
+ break;
+ }
+ return PSB_NA;
+}
+
+static void
+psb_init_defaults(PSB_CTX *psbctx) {
+ // pre-setup
+ assert(psbctx);
+ if (!psbctx->header)
+ psbctx->header = psb_default_header;
+ if (!psbctx->footer)
+ psbctx->footer = psb_default_footer;
+ if (!psbctx->renderer)
+ psbctx->renderer = psb_default_renderer;
+ if (!psbctx->cursor)
+ psbctx->cursor = psb_default_cursor;
+ if (!psbctx->footer)
+ psbctx->footer = psb_default_footer;
+
+ assert(psbctx->curr >= 0 &&
+ psbctx->total >= 0 &&
+ psbctx->curr < psbctx->total);
+ assert(psbctx->header_lines > 0 &&
+ psbctx->footer_lines);
+}
+
+int
+psb_main(PSB_CTX *psbctx)
+{
+ psb_init_defaults(psbctx);
+
+ while (1) {
+ int i;
+ int rows = t_lines - psbctx->header_lines - psbctx->footer_lines;
+ int base;
+
+ assert(rows > 0);
+ base = psbctx->curr / rows * rows;
+ clear();
+ SOLVE_ANSI_CACHE();
+ psbctx->header(psbctx->ctx);
+ for (i = 0; i < rows; i++) {
+ move(psbctx->header_lines + i, 0);
+ SOLVE_ANSI_CACHE();
+ if (base + i < psbctx->total)
+ psbctx->renderer(base + i, psbctx->curr, psbctx->total,
+ rows, psbctx->ctx);
+ }
+ move(t_lines - psbctx->footer_lines, 0);
+ SOLVE_ANSI_CACHE();
+ psbctx->footer(psbctx->ctx);
+ i = psbctx->header_lines + psbctx->curr - base;
+ move(i, 0);
+ psbctx->cursor(i, psbctx->curr, psbctx->ctx);
+ psbctx->key = vkey();
+
+ i = PSB_NA;
+ if (psbctx->input_processor)
+ i = psbctx->input_processor(psbctx->key, psbctx->curr,
+ psbctx->total, rows, psbctx->ctx);
+ if (i == PSB_NA)
+ i = psb_default_input_processor(psbctx->key, psbctx->curr,
+ psbctx->total, rows, psbctx->ctx);
+ if (i == PSB_EOF)
+ break;
+ if (i == PSB_NOP)
+ continue;
+
+ if (i >=0 && i < psbctx->total)
+ psbctx->curr = i;
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// View Edit History
+
+typedef struct {
+ const char *subject;
+ const char *filebase;
+} pveh_ctx;
+
+static int
+pveh_header(void *ctx) {
+ pveh_ctx *cx = (pveh_ctx*) ctx;
+ vs_hdr2barf(" 【檢視文章編輯歷史】 \t %s", cx->subject);
+ move(1, 0);
+ outs("請注意本系統不會永久保留所有的編輯歷史。");
+ outs("\n");
+ return 0;
+}
+
+static int
+pveh_footer(void *ctx) {
+ vs_footer(" 編輯歷史 ",
+ " (↑/↓/PgUp/PgDn/0-9)移動 (Enter/r/→)選擇 \t(q/←)跳出");
+ return 0;
+}
+
+static int
+pveh_cursor(int y, int curr, void *ctx) {
+ // (y, 0) before drawing
+#ifdef USE_PFTERM
+ outs("●\b");
+#else
+ cursor_show(y, 0);
+#endif
+ return 0;
+}
+
+static int
+pveh_renderer(int i, int curr, int total, int rows, void *ctx) {
+ const char *subject = "";
+ char fname[PATHLEN];
+ time4_t ftime = 0;
+ pveh_ctx *cx = (pveh_ctx*) ctx;
+
+ snprintf(fname, sizeof(fname), "%s.%03d", cx->filebase, i);
+ ftime = dasht(fname);
+ if (ftime != -1)
+ subject = Cdate(&ftime);
+ else
+ subject = "(記錄已過保留期限/已清除)";
+
+ prints(" %s%s 版本: #%08d ",
+ (i == curr) ? ANSI_COLOR(1;41;37) : "",
+ (ftime == -1) ? ANSI_COLOR(1;30) : "",
+ i + 1);
+ prints(" 時間: %-47s" ANSI_RESET "\n", subject);
+ return 0;
+}
+
+static int
+pveh_input_processor(int key, int curr, int total, int rows, void *ctx) {
+ char fname[PATHLEN];
+ pveh_ctx *cx = (pveh_ctx*) ctx;
+
+ switch (key) {
+ case KEY_ENTER:
+ case KEY_RIGHT:
+ case 'r':
+ snprintf(fname, sizeof(fname), "%s.%03d", cx->filebase, curr);
+ more(fname, YEA);
+ return PSB_NOP;
+ }
+ return PSB_NA;
+}
+
+int
+psb_view_edit_history(const char *base, const char *subject, int max_hist) {
+ pveh_ctx pvehctx = {
+ .subject = subject,
+ .filebase = base,
+ };
+ PSB_CTX ctx = {
+ .curr = 0,
+ .total = max_hist,
+ .header_lines = 3,
+ .footer_lines = 2,
+ .ctx = (void*)&pvehctx,
+ .header = pveh_header,
+ .footer = pveh_footer,
+ .renderer = pveh_renderer,
+ .cursor = pveh_cursor,
+ .input_processor = pveh_input_processor,
+ };
+
+ // warning screen!
+ static char is_first_enter_pveh = 1;
+ if (is_first_enter_pveh) {
+ is_first_enter_pveh = 0;
+ clear();
+ move(5, 0);
+ outs(
+" 歡迎使用文章編輯歷史瀏覽系統!\n\n"
+" 提醒您: (1) 此系統尚在實驗性開放中,站方未來會決定是否繼續提供此功\能。\n\n"
+" (2) 所有的資料僅供參考,站方不保證此處為完整的電磁記錄。\n\n"
+" (3) 所有的資料都可能不定期由系統清除掉。\n"
+" 無編輯歷史不能代表沒有編輯過,也可能是被清除了\n\n"
+ );
+ pressanykey();
+ }
+
+
+ psb_main(&ctx);
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Admin Edit
+
+// still 偷懶...
+#define MAX_PAE_ENTRIES (256)
+
+typedef struct {
+ char *descs[MAX_PAE_ENTRIES];
+ char *files[MAX_PAE_ENTRIES];
+} pae_ctx;
+
+static int
+pae_header(void *ctx) {
+ vs_hdr2bar(" 【系統檔案】 ", " 編輯系統檔案");
+ outs("請選取要編輯的檔案後按 Enter 開始修改\n");
+ vbarf(ANSI_REVERSE
+ "%5s %-36s%-30s", "編號", "名 稱", "檔 名");
+ return 0;
+}
+
+static int
+pae_footer(void *ctx) {
+ vs_footer(" 編輯系統檔案 ",
+ " (↑↓/0-9)移動 (Enter/e/r/→)編輯 (DEL/d)刪除 \t(q/←)跳出");
+ return 0;
+}
+
+static int
+pae_renderer(int i, int curr, int total, int rows, void *ctx) {
+ pae_ctx *cx = (pae_ctx*) ctx;
+ prints(" %3d %s%s%-36.36s " ANSI_COLOR(1;37) "%-30.30s" ANSI_RESET "\n",
+ i+1,
+ (i == curr) ? ANSI_COLOR(41) : "",
+ dashf(cx->files[i]) ? ANSI_COLOR(1;36) : ANSI_COLOR(1;30),
+ cx->descs[i], cx->files[i]);
+ return 0;
+}
+
+static int
+pae_cursor(int y, int curr, void *ctx) {
+ cursor_show(y, 0);
+ return 0;
+}
+
+static int
+pae_input_processor(int key, int curr, int total, int rows, void *ctx) {
+ int result;
+ pae_ctx *cx = (pae_ctx*) ctx;
+
+ switch(key) {
+ case KEY_DEL:
+ case 'd':
+ if (vansf("確定要刪除 %s 嗎? (y/N) ", cx->descs[curr]) == 'y')
+ unlink(cx->files[curr]);
+ vmsgf("系統檔案[%s]: %s", cx->files[curr],
+ !dashf(cx->files[curr]) ? "刪除成功\ " : "未刪除");
+ return PSB_NOP;
+
+ case KEY_ENTER:
+ case KEY_RIGHT:
+ case 'r':
+ case 'e':
+ case 'E':
+ result = veditfile(cx->files[curr]);
+ // log file change
+ if (result == EDIT_ABORTED)
+ {
+ log_filef("log/etc_edit.log",
+ LOG_CREAT,
+ "%s %s %s # %s\n",
+ Cdate(&now),
+ cuser.userid,
+ cx->files[curr],
+ cx->descs[curr]);
+ }
+ vmsgf("系統檔案[%s]: %s",
+ cx->files[curr],
+ (result == EDIT_ABORTED) ? "未改變" : "更新完畢");
+ return PSB_NOP;
+ }
+ return PSB_NA;
+}
+
+int
+psb_admin_edit() {
+ int i;
+ char buf[PATHLEN*2];
+ FILE *fp;
+ pae_ctx paectx = { };
+ PSB_CTX ctx = {
+ .curr = 0,
+ .total = 0,
+ .header_lines = 4,
+ .footer_lines = 2,
+ .ctx = (void*)&paectx,
+ .header = pae_header,
+ .footer = pae_footer,
+ .renderer = pae_renderer,
+ .cursor = pae_cursor,
+ .input_processor = pae_input_processor,
+ };
+
+ fp = fopen(FN_CONF_EDITABLE, "rt");
+ if (!fp) {
+ // you can find a sample in sample/etc/editable
+ vmsgf("未設定可編輯檔案列表[%s],請洽系統站長。", FN_CONF_EDITABLE);
+ return 0;
+ }
+
+ // load the editable file.
+ // format: filename [ \t]* description
+ while (ctx.total < MAX_PAE_ENTRIES &&
+ fgets(buf, sizeof(buf), fp)) {
+ char *k = buf, *v = buf;
+ if (!*buf || strchr("#./ \t\n\r", *buf))
+ continue;
+
+ // change \t to ' '.
+ while (*v) if (*v++ == '\t') *(v-1) = ' ';
+ v = strchr(buf, ' ');
+ if (v == NULL)
+ continue;
+
+ // see if someone is trying to crack
+ k = strstr(buf, "..");
+ if (k && k < v)
+ continue;
+
+ // reject anything outside etc/ folder.
+ if (strncmp(buf, "etc/", strlen("etc/")) != 0)
+ continue;
+
+ // adjust spaces
+ chomp(buf);
+ k = buf; *v++ = 0;
+ while (*v == ' ') v++;
+ trim(k);
+ trim(v);
+
+ // add into context
+ paectx.files[ctx.total] = strdup(k);
+ paectx.descs[ctx.total] = strdup(v);
+ ctx.total++;
+ }
+
+ psb_main(&ctx);
+
+ for (i = 0; i < ctx.total; i++) {
+ free(paectx.files[i]);
+ free(paectx.descs[i]);
+ }
+ return 0;
+}
+