summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpiaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2010-10-29 22:25:48 +0800
committerpiaip <piaip@63ad8ddf-47c3-0310-b6dd-a9e9d9715204>2010-10-29 22:25:48 +0800
commitfccf503a0804f0258bc2cda65921c2cb45bb2e4a (patch)
treefbb1bd3c7ad67cc89b6366324bd72ffede63a39d
parent1a149a5b96b90fb904b2d34857e43e11f7ec108f (diff)
downloadpttbbs-fccf503a0804f0258bc2cda65921c2cb45bb2e4a.tar
pttbbs-fccf503a0804f0258bc2cda65921c2cb45bb2e4a.tar.gz
pttbbs-fccf503a0804f0258bc2cda65921c2cb45bb2e4a.tar.bz2
pttbbs-fccf503a0804f0258bc2cda65921c2cb45bb2e4a.tar.lz
pttbbs-fccf503a0804f0258bc2cda65921c2cb45bb2e4a.tar.xz
pttbbs-fccf503a0804f0258bc2cda65921c2cb45bb2e4a.tar.zst
pttbbs-fccf503a0804f0258bc2cda65921c2cb45bb2e4a.zip
New system: Time Capsule (Magical Index)
Successor of edit history, powered by Panty&Stocking Browser, the Time Capsule provides integrated interface for manipulating deleted objects (recycle bin) and tracking modification history. git-svn-id: http://opensvn.csie.org/pttbbs/trunk@5179 63ad8ddf-47c3-0310-b6dd-a9e9d9715204
-rw-r--r--pttbbs/include/common.h5
-rw-r--r--pttbbs/include/config.h8
-rw-r--r--pttbbs/include/modes.h1
-rw-r--r--pttbbs/include/proto.h19
-rw-r--r--pttbbs/mbbsd/Makefile2
-rw-r--r--pttbbs/mbbsd/bbs.c220
-rw-r--r--pttbbs/mbbsd/mail.c30
-rw-r--r--pttbbs/mbbsd/psb.c280
-rw-r--r--pttbbs/mbbsd/read.c3
-rw-r--r--pttbbs/mbbsd/timecap.c339
10 files changed, 754 insertions, 153 deletions
diff --git a/pttbbs/include/common.h b/pttbbs/include/common.h
index 7609d1d0..fa614eba 100644
--- a/pttbbs/include/common.h
+++ b/pttbbs/include/common.h
@@ -53,7 +53,6 @@
#define SZ_RECENTPAY (16000)
#endif
-
// 自訂刪除文章時出現的標題與檔案
#ifndef FN_SAFEDEL
#ifdef USE_EDIT_HISTORY
@@ -69,6 +68,10 @@
#endif
#define FN_EDITHISTORY ".history"
+#ifndef SAFE_ARTICLE_DELETE_NUSER
+#define SAFE_ARTICLE_DELETE_NUSER (2)
+#endif
+
#define MSG_DEL_CANCEL "取消刪除"
#define MSG_BIG_BOY "我是大帥哥! ^o^Y"
#define MSG_BIG_GIRL "世紀大美女 *^-^*"
diff --git a/pttbbs/include/config.h b/pttbbs/include/config.h
index 401a8d11..b74f6e2a 100644
--- a/pttbbs/include/config.h
+++ b/pttbbs/include/config.h
@@ -111,6 +111,14 @@
#define BN_UNANONYMOUS "UnAnonymous"
#endif
+#ifndef RECYCLE_BIN_NAME
+#define RECYCLE_BIN_NAME "資源回收筒" // "垃圾桶"
+#endif
+
+#ifndef TIME_CAPSULE_NAME
+#define TIME_CAPSULE_NAME "Magical Index" // "Time Capsule"
+#endif
+
/* Environment */
#ifndef RELAY_SERVER_IP /* 寄站外信的 mail server */
#define RELAY_SERVER_IP "127.0.0.1"
diff --git a/pttbbs/include/modes.h b/pttbbs/include/modes.h
index 21485f9b..c35d6ae1 100644
--- a/pttbbs/include/modes.h
+++ b/pttbbs/include/modes.h
@@ -28,6 +28,7 @@
#define RET_SELECTBRD (992)
#define RET_DOREPLYALL (991)
#define RET_SELECTAID (990)
+#define RET_RECYCLEBIN (989)
/* user 操作狀態與模式 */
#define IDLE 0
diff --git a/pttbbs/include/proto.h b/pttbbs/include/proto.h
index bb0913c8..3502fb91 100644
--- a/pttbbs/include/proto.h
+++ b/pttbbs/include/proto.h
@@ -159,7 +159,9 @@ 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_view_edit_history(const char *base, const char *subject,
+ int maxrev, int current_as_base);
+int psb_recycle_bin(const char *base, const char *title);
int psb_admin_edit();
/* chc */
@@ -650,6 +652,21 @@ int term_init(void);
void term_resize(int w, int h);
void bell(void);
+/* timecap (time capsule) */
+int timecapsule_add_revision(const char *filename);
+int timecapsule_get_max_revision_number(const char *filename);
+int timecapsule_get_max_archive_number(const char *filename, size_t szrefblob);
+int timecapsule_archive(const char *filename, const void *ref, size_t szref);
+int timecapsule_get_by_revision(const char *filename, int rev, char *rev_path,
+ size_t sz_rev_path);
+int timecapsule_archive_new_revision(const char *filename,
+ const void *ref, size_t szref,
+ char *archived_path, size_t sz_archived_path);
+int timecapsule_get_archive_by_index(const char *filename, int idx,
+ void *refblob, size_t szrefblob);
+int timecapsule_get_archive_blobs(const char *filename, int idx, int nblobs,
+ void *blobsptr, size_t szblob);
+
/* ordersong */
int ordersong(void);
int topsong(void);
diff --git a/pttbbs/mbbsd/Makefile b/pttbbs/mbbsd/Makefile
index 31f0d71a..190bc114 100644
--- a/pttbbs/mbbsd/Makefile
+++ b/pttbbs/mbbsd/Makefile
@@ -15,7 +15,7 @@ TALKOBJS = friend.o talk.o ccw.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
+PLUGOBJS = calendar.o ordersong.o gamble.o angel.o timecap.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 xyz.o var.o vote.o voteboard.o \
diff --git a/pttbbs/mbbsd/bbs.c b/pttbbs/mbbsd/bbs.c
index 2bb60392..4b49a2fc 100644
--- a/pttbbs/mbbsd/bbs.c
+++ b/pttbbs/mbbsd/bbs.c
@@ -136,49 +136,6 @@ modify_dir_lite(
return 0;
}
-#ifdef USE_EDIT_HISTORY
-static int
-add_to_post_history(
- const char *direct, const char *basename,
- const char *old_path, const char *new_path)
-{
- char hist_file[PATHLEN];
- char hist_num[STRLEN];
- int fd = 0, last_index = 0;
-
- setdirpath(hist_file, direct, FN_EDITHISTORY "/");
- if (!dashd(hist_file))
- Mkdir(hist_file);
- strlcat(hist_file, basename, sizeof(hist_file));
-
- if ((fd = OpenCreate(hist_file, O_RDWR)) >= 0) {
- // XXX if somebody just die inside... locks everyone!
- flock(fd, LOCK_EX);
- read(fd, &last_index, sizeof(last_index));
- last_index++;
- lseek(fd, 0, SEEK_SET);
- write(fd, &last_index, sizeof(last_index));
- flock(fd, LOCK_UN);
- close(fd);
-
- if (last_index == 1) {
- char * const p = hist_file + strlen(hist_file);
- // rev 0->1, let's make a copy of original version
- strlcat(hist_file, ".000", sizeof(hist_file));
- HardLink(old_path, hist_file);
- *p = 0;
- }
-
- // now build the history file
- sprintf(hist_num, ".%03d", last_index);
- strlcat(hist_file, hist_num, sizeof(hist_file));
- HardLink(new_path, hist_file);
- return 0;
- }
- return -1;
-}
-#endif
-
static void
check_locked(fileheader_t *fhdr)
{
@@ -840,6 +797,67 @@ outgo_post(const fileheader_t *fh, const char *board, const char *userid, const
}
}
+#ifdef USE_TIME_CAPSULE
+
+static void
+innd_cancel_post(const fileheader_t *fh, const char *fpath, const char *userid)
+{
+ // deal with innd
+ FILE *fp = fopen(fpath, "r");
+ char buf[STRLEN*2];
+ char *s, *e, *nick = "";
+
+ // searching one line is enough.
+ if (fp &&
+ fgets(buf, sizeof(buf), fp) &&
+ (strncmp(buf, str_author1, LEN_AUTHOR1) == 0 ||
+ strncmp(buf, str_author2, LEN_AUTHOR2) == 0) &&
+ (s = strchr(buf, '(')) &&
+ (e = strrchr(buf, ')'))) {
+ *s ++ = 0;
+ *e = 0;
+ nick = s;
+ }
+ if (fp) {
+ fclose(fp);
+ // record to NNTP
+ log_filef("innd/cancel.bntp", LOG_CREAT,
+ "%s\t%s\t%s\t%s\t%s\n",
+ currboard, fh->filename, userid, nick, fh->title);
+ }
+}
+
+static int
+cancelpost2(const fileheader_t *fh, char *newpath, size_t sznewpath) {
+ char fpath[PATHLEN];
+ int ret = 0;
+
+ if(!fh->filename[0])
+ return -1;
+
+ setbfile(fpath, currboard, fh->filename);
+ // if (!dashf(fpath)) return -1;
+ log_filef(fpath, LOG_CREAT, "\n※ Deleted by: %s (%s) %s",
+ cuser.userid, fromhost, Cdatelite(&now));
+
+ if (!timecapsule_archive_new_revision(
+ fpath, fh, sizeof(*fh), newpath, sznewpath))
+ ret = -1;
+
+ // the file should be already in time capsule
+ if (unlink(fpath) != 0)
+ ret = -1;
+
+ // should we use cuser.userid, or userid in post?
+ // I don't know, simply following the old way in cancelpost...
+ if (!(currbrdattr & BRD_NOTRAN))
+ innd_cancel_post(fh, fpath, cuser.userid);
+
+ return ret;
+}
+
+#else
+
static int
cancelpost(const fileheader_t *fh, int by_BM, char *newpath)
{
@@ -891,6 +909,8 @@ cancelpost(const fileheader_t *fh, int by_BM, char *newpath)
return ret;
}
+#endif
+
static void
do_deleteCrossPost(const fileheader_t *fh, char bname[])
{
@@ -916,7 +936,8 @@ do_deleteCrossPost(const fileheader_t *fh, char bname[])
if( (i=getindex(bdir, &newfh, 0))>0)
{
#ifdef SAFE_ARTICLE_DELETE
- if(bp && !(currmode & MODE_DIGEST) && bp->nuser > 30 )
+ if(bp && !(currmode & MODE_DIGEST) &&
+ bp->nuser >= SAFE_ARTICLE_DELETE_NUSER)
safe_article_delete(i, &newfh, bdir, NULL);
else
#endif
@@ -1785,11 +1806,9 @@ edit_post(int ent, fileheader_t * fhdr, const char *direct)
}
// OK to save file.
-
-#ifdef USE_EDIT_HISTORY
- add_to_post_history(direct, fhdr->filename, genbuf, fpath);
+#ifdef USE_TIME_CAPSULE
+ timecapsule_add_revision(genbuf);
#endif
-
// piaip Wed Jan 9 11:11:33 CST 2008
// in order to prevent calling system 'mv' all the
// time, it is better to unlink() first, which
@@ -3116,7 +3135,8 @@ del_range(int ent, const fileheader_t *fhdr, const char *direct)
outmsg("處理中,請稍後...");
refresh();
#ifdef SAFE_ARTICLE_DELETE
- if(bp && !(currmode & MODE_DIGEST) && bp->nuser > 30)
+ if(bp && !(currmode & MODE_DIGEST) &&
+ bp->nuser >= SAFE_ARTICLE_DELETE_NUSER)
ret = safe_article_delete_range(direct, inum1, inum2);
else
#endif
@@ -3149,7 +3169,7 @@ static int
del_post(int ent, fileheader_t * fhdr, char *direct)
{
char reason[PROPER_TITLE_LEN] = "";
- char genbuf[100], newpath[PATHLEN];
+ char genbuf[100], newpath[PATHLEN] = "";
int not_owned, is_anon, tusernum, del_ok = 0, as_badpost = 0;
boardheader_t *bp;
@@ -3274,7 +3294,8 @@ del_post(int ent, fileheader_t * fhdr, char *direct)
if(
#ifdef SAFE_ARTICLE_DELETE
- ((reason[0] || bp->nuser > 30) && !(currmode & MODE_DIGEST) &&
+ ((reason[0] || bp->nuser >= SAFE_ARTICLE_DELETE_NUSER) &&
+ !(currmode & MODE_DIGEST) &&
!safe_article_delete(ent, fhdr, direct, reason[0] ? reason : NULL)) ||
#endif
// XXX TODO delete_record is really really dangerous -
@@ -3286,7 +3307,11 @@ del_post(int ent, fileheader_t * fhdr, char *direct)
// was closed.
setbtotal(currbid);
+#ifdef USE_TIME_CAPSULE
+ del_ok = (cancelpost2(fhdr, newpath, sizeof(newpath)) == 0) ? 1 : 0;
+#else
del_ok = (cancelpost(fhdr, not_owned, newpath) == 0) ? 1 : 0;
+#endif
deleteCrossPost(fhdr, bp->brdname);
#ifdef ASSESS
@@ -3298,10 +3323,10 @@ del_post(int ent, fileheader_t * fhdr, char *direct)
// do nothing
}
// case 2, got error in file deletion (already deleted, also skip badpost)
- else if (!del_ok)
+ else if (!del_ok || !*newpath)
{
move_ansi(1, 40); clrtoeol();
- outs("此檔已被別人刪除(跳過劣文設定)");
+ outs("已刪或刪除錯誤(跳過劣文設定)");
pressanykey();
}
// case 3, post older than one week (TODO use macro for the duration)
@@ -3369,6 +3394,9 @@ del_post(int ent, fileheader_t * fhdr, char *direct)
cuser.numposts, del_fee);
}
+ if (!del_ok)
+ vmsg("刪除過程發生錯誤,請向" BN_BUGREPORT "報告");
+
return DIRCHANGED;
} // delete_record
} // genbuf[0] == 'y'
@@ -3575,75 +3603,71 @@ view_postinfo(int ent, const fileheader_t * fhdr, const char *direct, int crs_ln
return FULLUPDATE;
}
-#ifdef USE_EDIT_HISTORY
+#ifdef USE_TIME_CAPSULE
static int
-view_post_history(int ent, const fileheader_t * fhdr, const char *direct)
+view_posthistory(int ent, const fileheader_t * fhdr, const char *direct)
{
- const char *err_no_history = "抱歉,此篇文章暫無編輯歷史記錄可供檢視";
- char hist_file[PATHLEN];
- int fd, maxhist = 0;
+ char fpath[PATHLEN];
+ const char *err_no_history = "此篇文章暫無編輯歷史記錄。"
+ "要進資源回收筒請再按一次 ~";
+ int maxrev = 0;
+ int current_as_base = 1;
// TODO allow author?
if (!(currmode & MODE_BOARD))
return DONOTHING;
- if ((!fhdr) ||
- ((fhdr->filename[0] == '.' || !fhdr->filename[0]) &&
-#ifdef FN_SAFEDEL_PREFIX_LEN
- (strncmp(fhdr->filename, FN_SAFEDEL, FN_SAFEDEL_PREFIX_LEN) != 0)
-#else
- (strcmp(fhdr->filename, FN_SAFEDEL) != 0)
-#endif
- )) {
- vmsg(err_no_history);
+ if (!fhdr || !fhdr->filename[0]) {
+ if (vmsg(err_no_history) == '~')
+ psb_recycle_bin(direct, currboard);
return FULLUPDATE;
}
- // build history index file name
- setdirpath(hist_file, direct, FN_EDITHISTORY "/");
- // XXX there are, well, unfortunately two kinds of deleted filename here:
- // M.12345678.AAA ->
- // - (old) .d<NUL>2345678.AAA # planned to be removed in the future
- // - (new) .d12345678.AAA
- 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));
+ // assert(FN_SAFEDEL[0] == '.')
+ if ((fhdr->filename[0] == '.')) {
+ if (
#ifdef FN_SAFEDEL_PREFIX_LEN
- } else if (strncmp(FN_SAFEDEL, fhdr->filename, FN_SAFEDEL_PREFIX_LEN) == 0) {
- assert(FN_SAFEDEL_PREFIX_LEN == 2); // current pattern: 2 = M.
- strlcat(hist_file, "M.", sizeof(hist_file));
- strlcat(hist_file, fhdr->filename + FN_SAFEDEL_PREFIX_LEN, sizeof(hist_file));
+ (strncmp(fhdr->filename, FN_SAFEDEL, FN_SAFEDEL_PREFIX_LEN) != 0)
+#else
+ (strcmp(fhdr->filename, FN_SAFEDEL) != 0)
#endif
- } else {
- strlcat(hist_file, fhdr->filename, sizeof(hist_file));
+ ) {
+ if (vmsg(err_no_history) == '~')
+ psb_recycle_bin(direct, currboard);
+ return FULLUPDATE;
+ }
+ current_as_base = 0;
}
- if (!dashf(hist_file) ||
- (fd = open(hist_file, O_RDONLY)) < 0) {
- vmsg(err_no_history);
- return FULLUPDATE;
- }
- read(fd, &maxhist, sizeof(maxhist));
- close(fd);
- if (maxhist < 1) {
- vmsg(err_no_history);
+ setbfile(fpath, currboard, fhdr->filename);
+ if (!current_as_base) {
+ char *prefix = strrchr(fpath, '/');
+ assert(prefix);
+ // hard-coded file name conversion
+ *++prefix = 'M';
+ *++prefix = '.';
+ }
+ maxrev = timecapsule_get_max_revision_number(fpath);
+ if (maxrev < 1) {
+ if (vmsg(err_no_history) == '~')
+ psb_recycle_bin(direct, currboard);
return FULLUPDATE;
}
- psb_view_edit_history(hist_file, fhdr->title, maxhist+1);
+ if (RET_RECYCLEBIN ==
+ psb_view_edit_history(fpath, fhdr->title, maxrev, current_as_base))
+ psb_recycle_bin(direct, currboard);
return FULLUPDATE;
}
-#else // USE_EDIT_HISTORY
+#else // USE_TIME_CAPSULE
static int
-view_post_history(int ent, const fileheader_t * fhdr, const char *direct) {
+view_posthistory(int ent, const fileheader_t * fhdr, const char *direct) {
return DONOTHING;
}
-#endif // USE_EDIT_HISTORY
+#endif // USE_TIME_CAPSULE
#ifdef OUTJOBSPOOL
/* 看板備份 */
@@ -4156,7 +4180,7 @@ const onekey_t read_comms[] = {
{ 0, NULL }, // '{' 123
{ 0, NULL }, // '|' 124
{ 0, NULL }, // '}' 125
- { 1, view_post_history }, // '~' 126
+ { 1, view_posthistory }, // '~' 126
};
int
diff --git a/pttbbs/mbbsd/mail.c b/pttbbs/mbbsd/mail.c
index 84c4d69c..4a0358ae 100644
--- a/pttbbs/mbbsd/mail.c
+++ b/pttbbs/mbbsd/mail.c
@@ -1108,8 +1108,9 @@ mailtitle(void)
}
showtitle("郵件選單", BBSName);
- prints("[←]離開[↑↓]選擇[→]閱\讀信件 [O]站外信:%s [h]求助\n" ,
- REJECT_OUTTAMAIL(cuser) ? ANSI_COLOR(31) "關" ANSI_RESET : "開");
+ prints("[←]離開[↑↓]選擇[→]閱\讀信件 [O]站外信:%s [h]求助 %s\n" ,
+ REJECT_OUTTAMAIL(cuser) ? ANSI_COLOR(31) "關" ANSI_RESET : "開",
+ ANSI_COLOR(1;33) "[~]" RECYCLE_BIN_NAME "(新)" ANSI_RESET);
vbarf(ANSI_REVERSE " 編號 %s 作 者 信 件 標 題\t%s ",
(showmail_mode == SHOWMAIL_SUM) ? "大 小":"日 期",
buf);
@@ -1225,9 +1226,10 @@ mail_del(int ent, const fileheader_t * fhdr, const char *direct)
if (!delete_record(direct, sizeof(*fhdr), ent)) {
setupmailusage();
setdirpath(genbuf, direct, fhdr->filename);
-#ifdef USE_RECYCLE
- RcyAddFile(fhdr, 0, genbuf);
-#endif // USE_RECYCLE
+#ifdef USE_TIME_CAPSULE
+ timecapsule_archive_new_revision(
+ genbuf, fhdr, sizeof(*fhdr), NULL, 0);
+#endif // USE_TIME_CAPSULE
unlink(genbuf);
loadmailusage();
return DIRCHANGED;
@@ -1842,6 +1844,22 @@ mail_waterball(int ent GCC_UNUSED, fileheader_t * fhdr, const char *direct GCC_U
return FULLUPDATE;
}
#endif
+
+#ifdef USE_TIME_CAPSULE
+static int
+mail_recycle_bin(int ent, fileheader_t * fhdr, const char *direct) {
+ psb_recycle_bin(direct, "個人信箱");
+ return FULLUPDATE;
+}
+#else // USE_TIME_CAPSULE
+static int
+mail_recycle_bin(int ent GCC_UNUSED,
+ fileheader_t * fhdr GCC_UNUSED,
+ const char *direct GCC_UNUSED) {
+ return DONOTHING;
+}
+#endif // USE_TIME_CAPSULE
+
static const onekey_t mail_comms[] = {
{ 0, NULL }, // Ctrl('A')
{ 0, NULL }, // Ctrl('B')
@@ -1937,7 +1955,7 @@ static const onekey_t mail_comms[] = {
{ 0, NULL }, // '{' 123
{ 0, NULL }, // '|' 124
{ 0, NULL }, // '}' 125
- { 0, NULL }, // '~' 126
+ { 1, mail_recycle_bin }, // '~' 126
};
int
diff --git a/pttbbs/mbbsd/psb.c b/pttbbs/mbbsd/psb.c
index adff89b3..38bb1a44 100644
--- a/pttbbs/mbbsd/psb.c
+++ b/pttbbs/mbbsd/psb.c
@@ -63,8 +63,11 @@ psb_default_renderer(int i, int curr, int total, int rows, void *ctx) {
static int
psb_default_cursor(int y, int curr, void * ctx) {
- outs("=>");
- // cursor_show(y, 0);
+#ifdef USE_PFTERM
+ outs("●\b");
+#else
+ cursor_show(y, 0);
+#endif
return 0;
}
@@ -195,17 +198,23 @@ psb_main(PSB_CTX *psbctx)
}
///////////////////////////////////////////////////////////////////////////
-// View Edit History
+// Time Capsule: Edit History
+
+#define PVEH_LIMIT_NUMBER (199)
typedef struct {
const char *subject;
const char *filebase;
+ int leave_for_recycle_bin;
+ int rev_base;
+ int base_as_current;
+ time4_t *timestamps;
} pveh_ctx;
static int
pveh_header(void *ctx) {
pveh_ctx *cx = (pveh_ctx*) ctx;
- vs_hdr2barf(" 【檢視文章編輯歷史】 \t %s", cx->subject);
+ vs_hdr2barf(" 【" TIME_CAPSULE_NAME ": 編輯歷史】 \t %s", cx->subject);
move(1, 0);
outs("請注意本系統不會永久保留所有的編輯歷史。");
outs("\n");
@@ -215,20 +224,20 @@ pveh_header(void *ctx) {
static int
pveh_footer(void *ctx) {
vs_footer(" 編輯歷史 ",
- " (↑/↓/PgUp/PgDn/0-9)移動 (Enter/r/→)選擇 \t(q/←)跳出");
+ " (↑/↓/PgUp/PgDn/0-9)移動 (Enter/r/→)選擇 (~)" RECYCLE_BIN_NAME
+ "\t(q/←)跳出");
move(b_lines-1, 0);
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 void
+pveh_solve_rev_filename(int rev, int i, char *fname, size_t sz_fname,
+ pveh_ctx *cx) {
+ if (cx->base_as_current && i == 0)
+ strlcpy(fname, cx->filebase, sz_fname);
+ else
+ timecapsule_get_by_revision(
+ cx->filebase, rev + cx->rev_base, fname, sz_fname);
}
static int
@@ -237,19 +246,31 @@ pveh_renderer(int i, int curr, int total, int rows, void *ctx) {
char fname[PATHLEN];
time4_t ftime = 0;
pveh_ctx *cx = (pveh_ctx*) ctx;
+ int rev = total - i; // i/curr = 0 based, rev = 1 based
+
+ if (cx->timestamps[i] == 0) {
+ pveh_solve_rev_filename(rev, i, fname, sizeof(fname), cx);
+ ftime = dasht(fname);
+ if (!ftime)
+ ftime++;
+ cx->timestamps[i] = ftime;
+ } else {
+ ftime = cx->timestamps[i];
+ }
- snprintf(fname, sizeof(fname), "%s.%03d", cx->filebase, i);
- ftime = dasht(fname);
if (ftime != -1)
subject = Cdate(&ftime);
else
subject = "(記錄已過保留期限/已清除)";
- prints(" %s%s 版本: #%08d ",
+ prints(" %s%s 版本: ",
(i == curr) ? ANSI_COLOR(1;41;37) : "",
- (ftime == -1) ? ANSI_COLOR(1;30) : "",
- i + 1);
- prints(" 時間: %-47s" ANSI_RESET "\n", subject);
+ (ftime == -1) ? ANSI_COLOR(1;30) : "");
+ if (cx->base_as_current && i == 0)
+ outs("[目前版本]");
+ else
+ prints("#%09d", rev + cx->rev_base);
+ prints(" 時間: %-*s" ANSI_RESET "\n", t_columns - 31, subject);
return 0;
}
@@ -257,27 +278,66 @@ static int
pveh_input_processor(int key, int curr, int total, int rows, void *ctx) {
char fname[PATHLEN];
pveh_ctx *cx = (pveh_ctx*) ctx;
+ int rev = total - curr; // see renderer
switch (key) {
case KEY_ENTER:
case KEY_RIGHT:
case 'r':
- snprintf(fname, sizeof(fname), "%s.%03d", cx->filebase, curr);
+ pveh_solve_rev_filename(rev, curr, fname, sizeof(fname), cx);
more(fname, YEA);
return PSB_NOP;
+
+ case '~':
+ cx->leave_for_recycle_bin = 1;
+ return PSB_EOF;
}
return PSB_NA;
}
+static int
+pveh_welcome() {
+ // warning screen!
+ static char is_first_enter_pveh = 1;
+
+ if (is_first_enter_pveh) {
+ is_first_enter_pveh = 0;
+ clear();
+ move(2, 0);
+ outs(ANSI_COLOR(1;31)
+" 歡迎使用 Time Capsule 的編輯歷史瀏覽系統!\n\n" ANSI_RESET
+" 提醒您: (1) 此系統尚在實驗性開放中,站方未來會決定是否繼續提供此功\能。\n\n"
+" (2) 所有的資料僅供參考,站方不保證此處為完整的電磁記錄。\n\n"
+" (3) 所有的資料都可能不定期由系統清除掉。\n"
+" 無編輯歷史不能代表沒有編輯過,也可能是被清除了\n\n"
+" Mini FAQ:\n\n"
+" Q: 怎樣才會有歷史記錄 (增加版本數)?\n"
+" A: 在系統更新後每次使用 E 編輯文章並存檔就會有記錄。推文不會增加記錄版本數\n\n"
+" Q: 通常歷史會保留多久?\n"
+" A: 仍在評估中,或許\會是兩週到一個月\n\n"
+" Q: 檔案被刪了也可以看歷史嗎?\n"
+" A: 屍體還在看板上可以直接對<本文已被刪除>按,不然就先進"
+ RECYCLE_BIN_NAME "(~)再找\n"
+ );
+ doupdate();
+ pressanykey();
+ }
+ return 0;
+}
+
+
int
-psb_view_edit_history(const char *base, const char *subject, int max_hist) {
+psb_view_edit_history(const char *base, const char *subject,
+ int maxrev, int current_as_base) {
pveh_ctx pvehctx = {
.subject = subject,
.filebase = base,
+ .rev_base = 0,
+ .base_as_current = current_as_base,
};
PSB_CTX ctx = {
.curr = 0,
- .total = max_hist,
+ .total = maxrev + pvehctx.base_as_current,
.header_lines = 3,
.footer_lines = 2,
.allow_pbs_version_message = 1,
@@ -285,35 +345,172 @@ psb_view_edit_history(const char *base, const char *subject, int max_hist) {
.header = pveh_header,
.footer = pveh_footer,
.renderer = pveh_renderer,
- .cursor = pveh_cursor,
.input_processor = pveh_input_processor,
};
+ pveh_welcome();
+
+ if (maxrev > PVEH_LIMIT_NUMBER) {
+ pvehctx.rev_base = maxrev - PVEH_LIMIT_NUMBER;
+ ctx.total -= pvehctx.rev_base;
+ }
+
+ pvehctx.timestamps = (time4_t*) malloc (sizeof(time4_t) * ctx.total);
+ if (!pvehctx.timestamps) {
+ vmsgf("內部錯誤,請至" BN_BUGREPORT "看板報告,謝謝");
+ return FULLUPDATE;
+ }
+ // load on demand!
+ memset(pvehctx.timestamps, 0, sizeof(time4_t) * ctx.total);
+
+ psb_main(&ctx);
+ free(pvehctx.timestamps);
+ return (pvehctx.leave_for_recycle_bin ?
+ RET_RECYCLEBIN :
+ FULLUPDATE);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Time Capsule: Recycle Bin
+#define PVRB_LIMIT_NUMBER (1000)
+
+typedef struct {
+ const char *dirbase;
+ const char *subject;
+ int viewbase;
+ fileheader_t *records;
+} pvrb_ctx;
+
+static int
+pvrb_header(void *ctx) {
+ pvrb_ctx *cx = (pvrb_ctx*) ctx;
+ vs_hdr2barf(" 【" TIME_CAPSULE_NAME ": " RECYCLE_BIN_NAME "】 \t %s",
+ cx->subject);
+ move(1, 0);
+ outs("請注意此處的檔案將不定期清除。");
+ outs("\n");
+ return 0;
+}
+
+static int
+pvrb_footer(void *ctx) {
+ vs_footer(" 已刪檔案 ",
+ " (↑/↓/PgUp/PgDn/0-9)移動 (Enter/r/→)選擇 \t(q/←)跳出");
+ move(b_lines-1, 0);
+ return 0;
+}
+
+static int
+pvrb_renderer(int i, int curr, int total, int rows, void *ctx) {
+ pvrb_ctx *cx = (pvrb_ctx*) ctx;
+ fileheader_t *fh = &cx->records[total - i - 1];
+
+ // quick display, but lack of recommend counter...
+ prints(" %s %06d %-5.5s %-12.12s %-*.*s" ANSI_RESET "\n",
+ (i == curr) ? ANSI_COLOR(46;30) : "",
+ i+1, fh->date, fh->owner, t_columns-33, t_columns-33, fh->title);
+ return 0;
+}
+
+static int
+pvrb_input_processor(int key, int curr, int total, int rows, void *ctx) {
+ char fname[PATHLEN];
+ int maxrev;
+ pvrb_ctx *cx = (pvrb_ctx*) ctx;
+ fileheader_t *fh = &cx->records[total - curr - 1];
+
+ switch (key) {
+ case KEY_ENTER:
+ case KEY_RIGHT:
+ case 'r':
+ setdirpath(fname, cx->dirbase, fh->filename);
+ maxrev = timecapsule_get_max_revision_number(fname);
+ if (maxrev == 1) {
+ char revfname[PATHLEN];
+ timecapsule_get_by_revision(
+ fname, 1, revfname, sizeof(revfname));
+ more(revfname, YEA);
+ } else if (maxrev > 1) {
+ psb_view_edit_history(fname, fh->title, maxrev, 0);
+ } else {
+ vmsg("抱歉,本文歷史資料已被系統清除。");
+ }
+ return PSB_NOP;
+ }
+ return PSB_NA;
+}
+
+static int
+pvrb_welcome() {
// warning screen!
- static char is_first_enter_pveh = 1;
- if (is_first_enter_pveh) {
- is_first_enter_pveh = 0;
- clear();
+ static char is_first_enter_pvrb = 1;
+
+ if (is_first_enter_pvrb) {
+ is_first_enter_pvrb = 0;
+ clear(); SOLVE_ANSI_CACHE();
move(2, 0);
- outs(
-" 歡迎使用文章編輯歷史瀏覽系統!\n\n"
+ outs(ANSI_COLOR(1;36)
+" 歡迎使用 " TIME_CAPSULE_NAME " " RECYCLE_BIN_NAME "!\n\n" ANSI_RESET
" 提醒您: (1) 此系統尚在實驗性開放中,站方未來會決定是否繼續提供此功\能。\n\n"
" (2) 所有的資料僅供參考,站方不保證此處為完整的電磁記錄。\n\n"
" (3) 所有的資料都可能不定期由系統清除掉。\n"
-" 無編輯歷史不能代表沒有編輯過,也可能是被清除了\n\n"
+" 大D 與 ^D 刪除的內容不會保留。\n\n"
" Mini FAQ:\n\n"
-" Q: 怎樣才會有歷史記錄 (增加版本數)?\n"
-" A: 在系統更新後每次使用 E 編輯文章並存檔就會有記錄。推文不會增加記錄版本數\n\n"
-" Q: 通常歷史會保留多久?\n"
-" A: 仍在評估中,或許\會是一週到一個月以上\n\n"
-" Q: 檔案被刪了也可以看歷史嗎?\n"
-" A: 屍體還在看板上就可以(直接對<本文已被刪除>按),但在 deleted 看板是無效的\n"
+" Q: 通常檔案會保留多久?\n"
+" A: 仍在評估中,或許\會是兩週到一個月,另外篇數也會有上限。\n\n"
+" Q: 哪些地方有回收筒可用?\n"
+" A: 目前開放個人信箱(所有用戶)跟看板(板主限定)。 精華區暫不支援。\n\n"
);
+ doupdate();
pressanykey();
}
-
+ return 0;
+}
+int
+psb_recycle_bin(const char *base, const char *title) {
+ int nrecords = 0, viewbase = 0;
+ pvrb_ctx pvrbctx = {
+ .dirbase = base,
+ .subject = title,
+ };
+ PSB_CTX ctx = {
+ .curr = 0,
+ .total = 0, // maxrev + pvrbctx.base_as_current,
+ .header_lines = 3,
+ .footer_lines = 2,
+ .allow_pbs_version_message = 1,
+ .ctx = (void*)&pvrbctx,
+ .header = pvrb_header,
+ .footer = pvrb_footer,
+ .renderer = pvrb_renderer,
+ .input_processor = pvrb_input_processor,
+ };
+
+ nrecords = timecapsule_get_max_archive_number(base, sizeof(fileheader_t));
+ if (!nrecords) {
+ vmsg("目前" RECYCLE_BIN_NAME "內無任何內容。");
+ return 0;
+ }
+
+ pvrb_welcome();
+
+ // truncate on large size
+ if (nrecords > PVRB_LIMIT_NUMBER) {
+ viewbase = nrecords - PVRB_LIMIT_NUMBER;
+ nrecords -= viewbase;
+ }
+ ctx.total = nrecords;
+
+ pvrbctx.records = (fileheader_t*) malloc (sizeof(fileheader_t) * nrecords);
+ if (!pvrbctx.records) {
+ vmsgf("內部錯誤,請至" BN_BUGREPORT "看板報告,謝謝");
+ return 0;
+ }
+ timecapsule_get_archive_blobs(base, viewbase, nrecords, pvrbctx.records,
+ sizeof(fileheader_t));
psb_main(&ctx);
+ free(pvrbctx.records);
return 0;
}
@@ -357,12 +554,6 @@ pae_renderer(int i, int curr, int total, int rows, void *ctx) {
}
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;
@@ -417,7 +608,6 @@ psb_admin_edit() {
.header = pae_header,
.footer = pae_footer,
.renderer = pae_renderer,
- .cursor = pae_cursor,
.input_processor = pae_input_processor,
};
diff --git a/pttbbs/mbbsd/read.c b/pttbbs/mbbsd/read.c
index 97312ae7..022a3e77 100644
--- a/pttbbs/mbbsd/read.c
+++ b/pttbbs/mbbsd/read.c
@@ -179,7 +179,8 @@ TagPruner(int bid)
if (vans("刪除所有標記[N]?") != 'y')
return READ_REDRAW;
#ifdef SAFE_ARTICLE_DELETE
- if(bp && !(currmode & MODE_DIGEST) && bp->nuser > 30)
+ if(bp && !(currmode & MODE_DIGEST) &&
+ bp->nuser >= SAFE_ARTICLE_DELETE_NUSER)
safe_delete_range(currdirect, 0, 0);
else
#endif
diff --git a/pttbbs/mbbsd/timecap.c b/pttbbs/mbbsd/timecap.c
new file mode 100644
index 00000000..9e8dae39
--- /dev/null
+++ b/pttbbs/mbbsd/timecap.c
@@ -0,0 +1,339 @@
+/* $Id $ */
+#include "bbs.h"
+
+// Time Capsule / Magical Index
+//
+// Management of edit history and deleted (archived) objects
+//
+// 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.
+// --------------------------------------------------------------------------
+//
+// The Time Capsule provides an interface to "append" objects into a managed
+// data pool. There's no diff, compress, incremental, or expiring because the
+// time capsule is designed to be very fast and least impact to original system.
+// You should do those by a post-processing cron job (ex, compress or remove
+// files periodically).
+//
+// Currently any objects can perform two kinds of actions in Time Capsule:
+// - Revision: Record a new by with numerical (auto-inc) number.
+// - Archive: Append blob information in an index file
+//
+// For BBS,
+// editing posts should perform a Revision
+// deleting posts should perform a Revision and then Archive.
+//
+// NOTE: Revision index is now using "add one byte for each rev", that helps us
+// to get the rev by dashs instead of reading content. However that only
+// works fine if the revisions are small. The statistics shown that most
+// revisions are less than 100 so it's working fine for a traditional BBS,
+// but not for a wiki-like system.
+
+///////////////////////////////////////////////////////////////////////////
+// Constants
+
+enum TIME_CAPSULE_ACTION_TYPE {
+ TIME_CAPSULE_ACTION_REVISION = 1,
+ TIME_CAPSULE_ACTION_ARCHIVE,
+};
+
+#define TIME_CAPSULE_BASE_FOLDER_NAME ".timecap/"
+#define TIME_CAPSULE_ARCHIVE_INDEX_NAME "archive.idx"
+#define TIME_CAPSULE_REVISION_INDEX_NAME ".rev"
+
+///////////////////////////////////////////////////////////////////////////
+// Core Function
+
+static int
+timecap_get_max_revision(const char *capsule_index) {
+ off_t sz = dashs(capsule_index);
+ // TODO we can change implementation to read int instead of dashs
+ return sz > 0 ? (int)sz : 0;
+}
+
+static int
+timecap_get_max_archive(const char *archive_index, size_t ref_blob_size) {
+ off_t sz = dashs(archive_index);
+ if (!ref_blob_size || sz < 1)
+ return 0;
+ // TODO what if ref_blob_size%sz != 0 ?
+ // Maybe we should keep some metadata in the archive in the future
+ return sz / ref_blob_size;
+}
+
+static int
+timecap_get_archive_content(const char *archive_index, int index, int nblobs,
+ void *blobsptr, size_t szblob) {
+ int fd;
+ off_t srcsz = dashs(archive_index),
+ offset = (off_t) index * szblob,
+ readsz = nblobs * szblob;
+
+ assert(blobsptr && szblob && nblobs);
+ if (offset < 0 || srcsz < offset || srcsz < offset + readsz)
+ return 0;
+
+ fd = open(archive_index, O_RDONLY);
+ if (fd < 0)
+ return 0;
+ if (lseek(fd, offset, SEEK_SET) != offset ||
+ read(fd, (char*)blobsptr, readsz) != readsz) {
+ close(fd);
+ return 0;
+ }
+ close(fd);
+ return 1;
+}
+
+static int
+timecap_convert_revision_filename(int rev,
+ char *rev_index_path,
+ size_t sz_index_path) {
+ char revstr[STRLEN];
+ char *s = strrchr(rev_index_path, '.');
+ if (!s++)
+ return 0;
+
+ *s = 0;
+ sprintf(revstr, "%03d", rev);
+ strlcat(rev_index_path, revstr, sz_index_path);
+ return 1;
+}
+
+static int
+timecap_add_revision(const char *object_path, const char *capsule_index) {
+ int rev, fd;
+ char capsule_path[PATHLEN];
+ const char nul = 0;
+
+ strlcpy(capsule_path, capsule_index, sizeof(capsule_path));
+ // solve index and revision
+ rev = timecap_get_max_revision(capsule_index);
+ if (rev++ < 0)
+ rev = 1;
+ assert(rev != 0);
+
+ timecap_convert_revision_filename(rev, capsule_path, sizeof(capsule_path));
+ fd = OpenCreate(capsule_index, O_WRONLY | O_EXLOCK | O_APPEND);
+ if (fd < 0)
+ return 0;
+
+ // we don't use Link because time capsule are supposed to not
+ // causing extra disk usage.
+ if (link(object_path, capsule_path) != 0 ||
+ write(fd, &nul, 1) != 1) {
+ close(fd);
+ return 0;
+ }
+
+ close(fd);
+ return rev;
+}
+
+static int
+timecap_add_archive(const char *capsule_index,
+ const void *ref_blob, size_t ref_blob_size)
+{
+ int fd;
+
+ if (!ref_blob || !ref_blob_size)
+ return 0;
+
+ // check if the index file is broken or incompatible.
+ // if (dashs(capsule_index) % ref_blob_size)
+ // return 0;
+
+ // solve new store name
+ fd = OpenCreate(capsule_index, O_WRONLY | O_EXLOCK | O_APPEND);
+ if (fd < 0)
+ return 0;
+
+ // the blob must provide enough information to get the object name.
+ if (write(fd, ref_blob, ref_blob_size) != ref_blob_size) {
+ close(fd);
+ return 0;
+ }
+ close(fd);
+ return 1;
+}
+
+static int
+timecap_solve_base_folder(int create, const char *object_path,
+ char *folder, size_t sz_folder) {
+ // currently we only support file objects
+ if (create && !dashf(object_path))
+ return 0;
+
+ assert(sz_folder >= PATHLEN); // default size in setdirpath
+ setdirpath(folder, object_path, TIME_CAPSULE_BASE_FOLDER_NAME);
+ if (!dashd(folder) && (!create || Mkdir(folder) != 0))
+ return 0;
+
+ return 1;
+}
+
+static int
+timecap_solve_revision_index(int create, const char *object_path,
+ char *index_path, size_t sz_index_path) {
+ const char *object_name = strrchr(object_path, '/');
+ if (!object_name++ || !*object_name)
+ return 0;
+
+ if (!timecap_solve_base_folder(create, object_path,
+ index_path, sz_index_path))
+ return 0;
+
+ strlcat(index_path, object_name, sz_index_path);
+ strlcat(index_path, TIME_CAPSULE_REVISION_INDEX_NAME, sz_index_path);
+ return 1;
+}
+
+static int
+timecap_solve_archive_index(int create, const char *object_path,
+ char *index_path, size_t sz_index_path) {
+ if (!timecap_solve_base_folder(create, object_path,
+ index_path, sz_index_path))
+ return 0;
+
+ strlcat(index_path, TIME_CAPSULE_ARCHIVE_INDEX_NAME, sz_index_path);
+ return 1;
+}
+
+static int
+timecap_add_object(const char *object_path,
+ enum TIME_CAPSULE_ACTION_TYPE action_type,
+ const void *ref_blob,
+ size_t ref_blob_size) {
+ char capsule_index[PATHLEN];
+
+ // make sure the base folder can be created.
+ switch (action_type) {
+ case TIME_CAPSULE_ACTION_REVISION:
+ if (!timecap_solve_revision_index(1, object_path, capsule_index,
+ sizeof(capsule_index)))
+ return 0;
+ return timecap_add_revision(object_path, capsule_index);
+
+ case TIME_CAPSULE_ACTION_ARCHIVE:
+ if (!timecap_solve_archive_index(1, object_path, capsule_index,
+ sizeof(capsule_index)))
+ return 0;
+ return timecap_add_archive(capsule_index, ref_blob, ref_blob_size);
+ }
+ assert(!"unknown time capsule reference type");
+ return 0;
+}
+
+int
+timecap_query_object_max_number(const char *object_path,
+ enum TIME_CAPSULE_ACTION_TYPE action_type,
+ size_t ref_blob_size) {
+ char capsule_index[PATHLEN];
+
+ switch(action_type) {
+ case TIME_CAPSULE_ACTION_REVISION:
+ if (!timecap_solve_revision_index(0, object_path, capsule_index,
+ sizeof(capsule_index)))
+ return 0;
+ return timecap_get_max_revision(capsule_index);
+
+ case TIME_CAPSULE_ACTION_ARCHIVE:
+ if (!timecap_solve_archive_index(0, object_path, capsule_index,
+ sizeof(capsule_index)))
+ return 0;
+ return timecap_get_max_archive(capsule_index, ref_blob_size);
+ }
+ assert(!"unknown time capsule reference type");
+ return 0;
+}
+
+int
+timecap_query_object_refblobs(const char *object_path,
+ int index,
+ int nblobs,
+ void *ref_blob,
+ size_t ref_blob_size) {
+ char capsule_index[PATHLEN];
+
+ if (!timecap_solve_archive_index(0, object_path, capsule_index,
+ sizeof(capsule_index)))
+ return 0;
+ return timecap_get_archive_content(
+ capsule_index, index, nblobs, ref_blob, ref_blob_size);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Export API
+
+int
+timecapsule_add_revision(const char *filename) {
+ return timecap_add_object(filename, TIME_CAPSULE_ACTION_REVISION, NULL, 0);
+}
+
+int
+timecapsule_get_max_revision_number(const char *filename) {
+ return timecap_query_object_max_number(
+ filename, TIME_CAPSULE_ACTION_REVISION, 0);
+}
+
+int
+timecapsule_get_max_archive_number(const char *filename, size_t szrefblob) {
+ return timecap_query_object_max_number(
+ filename, TIME_CAPSULE_ACTION_ARCHIVE, szrefblob);
+}
+
+int
+timecapsule_archive(const char *filename, const void *ref, size_t szref) {
+ return timecap_add_object(
+ filename, TIME_CAPSULE_ACTION_ARCHIVE, ref, szref);
+}
+
+int
+timecapsule_get_by_revision(const char *filename, int rev,
+ char *rev_path, size_t sz_rev_path) {
+ if (!timecap_solve_revision_index(0, filename, rev_path, sz_rev_path))
+ return 0;
+ timecap_convert_revision_filename(rev, rev_path, sz_rev_path);
+ return 1;
+}
+
+int
+timecapsule_get_archive_blobs(const char *filename, int idx, int nblobs,
+ void *blobsptr, size_t szblob) {
+ return timecap_query_object_refblobs(
+ filename, idx, nblobs, blobsptr, szblob);
+}
+
+int
+timecapsule_archive_new_revision(const char *filename,
+ const void *ref, size_t szref,
+ char *archived_path, size_t sz_archived_path) {
+ char capsule_index[PATHLEN];
+ int rev;
+
+ if (!timecap_solve_revision_index(1, filename, capsule_index,
+ sizeof(capsule_index)))
+ return 0;
+ rev = timecap_add_revision(filename, capsule_index);
+ if (!rev)
+ return 0;
+
+ if (archived_path) {
+ strlcpy(archived_path, capsule_index, sz_archived_path);
+ timecap_convert_revision_filename(rev, archived_path, sz_archived_path);
+ }
+ return timecapsule_archive(filename, ref, szref);
+}
+