summaryrefslogtreecommitdiffstats
path: root/docs/brc.txt
blob: 954ea5f2c2981f1887daef5f39d9e6f2285df384 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
 
        BRC (v2) documentation by scw   08/05/2003
                    06/12/2007 revised by kcwu
                    12/02/2007 v3 by piaip
 
源起:
     這篇文章主要是介紹 brc_* 的函式,這組函式是 pttbbs 用來紀錄文章已讀/未讀
 的工具,但因為內部的儲存方式十分 tricky 連帶使的內容相當難懂。為了重現及修正
 其中的一個 bug,筆者有幸弄清了其中運作方式,並為其撰寫說明,希望對管理者有幫
 助。
 
什麼人該看這篇文章?
     1. pttbbs 的系統管理者。如果您要對這部份進行修改或抓蟲,希望這篇文章能對
        您有所助益。
     2. 想要研究這種用極少空間記下極大資訊的方法的人。
 
BRC 是什麼?如何運作?
     brc_* 是定義在 pttbbs/mbbsd/board.c 中的一組函式,負責紀錄文章已讀/未讀,
 它的特點是用的空間極少。可以在 24k 以內的空間記下一個人在全站的文章已讀/
 未讀。當然,這樣的方法不可能真正完美,但是對於使用上已經足夠了。為什麼說是不
 完美呢?這跟紀錄的儲存方式有關。
     紀錄檔在 home/[first charactor of id]/[id]/.brc2。檔案格式如下:
 
 FILE     := RECORDS ;
 RECORDS  := RECORDS RECORD | ;
 RECORD   := BRC_BID BRC_DATA ;
 BRC_DATA := BRC_NUM BRC_LIST ;
 BRC_LIST := NUM NUM ... NUM ;  (共 BRC_NUM 個 NUM)
 BRC_BID 是 board bid, sizeof(brcbid_t)=2 bytes.
 BRC_NUM    是對這個板的儲存量,sizeof(brcnbrd_t)=2 bytes 以 binary 方式儲存,其值 <= MAX_NUM (80)
 BRC_LIST   是對這個板的紀錄,剛好有 BRC_NUM 個 sizeof(time4_t)=4 bytes integers。
 另外在 24576 bytes (#define BRC_MAXSIZE 24576) 之外的資料不會被用到。
 
     在下面會看到,BRC_BID 跟 BRC_NUM 跟 BRC_LIST 都會放在相應的變數中, brc_currbid & brc_num & brc_list 。
     判定一個檔案是否已經讀過的方法是在 brc_list 中搜尋檔案建立的時間,也就是
 檔名 M.xxxxxxxxxx.A.yyy 中 xxxxxxxxx 的那個數字。如果這個數字有在 brc_list 中
 出現就是已讀,要不如果 brc_list 中所有的數字都比這個檔案的建立時間大(也就是
 這個檔案的建立時間在所有 brc_list 中的時間點之前)也是已讀,最後為了節省空間
 還有一個判定(其實這個判定是第一個做的),如果檔案建立時間在 login 時間的一年
 之前,一律是已讀。
     這樣可以看出為什麼這個方法不是真正完美但是已經足夠。不完美的原因有三個:
 首先, brc_num <= 80 也就是 brc_list 最多存八十個數,這表示除了很久以前的文章
 外,只會有八十篇是已讀的。第二就是所有一年前的文章都會被判為已讀。最後,如果一
 個人看的板太多,讓 .brc2 大小超過 BRC_MAXSIZE 有些板的紀錄就會不見( 24576
 bytes 最少可以存 73 個板的資料,這還是用全部板 brc_num 都是 80 計算的)。但這
 三個小缺點影響應該不大吧?

v3 說明

     Dec 2007 開始 ptt brc 引入 v3 格式,與前板不相容。所謂的 v3 是由於 BBS
 「推文」系統被濫用而產生的需求。推文的形式是在原文後附加新的一行文,但舊
 BRC 系統無法分辨此類更動;若想讓人分辨是否有新推文各家作法不一,常見的是比
 照 edit_post 把檔案改名。這種方式除了效率不彰外,還有並非每個人都想看新推文
 的問題。
     考量許久後,從 BRC 下手還是正解。 在每個 BRC 記錄上多新增一個 modified
 time 即可。 此方法可同時適用於分離檔案與原文附加的推文系統。代價是 BRC 大小
 加倍成長,不過這似乎不是什麼大問題。
     由於 v2 v3 實際差異並不大,這裡的說明保留以 v2 為主。 麻煩自己查 svn
 就知道 v3 改了哪邊了。

 - ptt2 更換 brc V3 前夕, MAX_BOARD 到達上限 (42000),討論後決定順便把 brc V3
 設計成 brcbid_t = int32。

 
BRC v2 實作
 
 interface: (in proto.h)
 
  int brc_initialize();
  void brc_finalize();

  int brc_unread(int bid, char *fname, int bnum, int *blist);
      判斷一篇文章是否已讀。
      傳入值:文章檔名 (fname) 以及 brc_num (bnum) 和 brc_list (blist)。
      傳回值:如果由 bnum 和 blist 判斷本篇文章未讀傳回 1。
              否則傳回 0。
      額外效果:無。
  
  int brc_initial_board(char *boardname);
      初始化在一個板的已讀未讀狀態。
      傳入值:要初始化的板名。
      傳回值:若找到之前的紀錄傳回新的 brc_num,否則傳回 0。
      額外效果:如果傳入的看板就是目前看板會直接傳回 brc_num, 不做別的事。否則
      本函式會先將目前的 brc data 寫回 brc_buf 中,更改 currboard ,取得
      currbid 和 currbrdattr 後再讀取並更新 brc_num 及 brc_list。如果在使用者
      的 brc_buf 中沒有關於這個板的紀錄,會設定 brc_num = 1,brc_list[0] = 1
      並傳回 0。
  
  void brc_update();
      將目前的 brc data 寫入 brc_buf 中。
      額外效果:如果 brc data 未被更改或使用者權限不足則不會有動作。
  
  void brc_addlist(char *fname);
      將文章標示為已讀。使用前需先 brc_initial_board()
      傳入值:要標示為已讀的文章檔名。
  
 constant definition:
  
  #define BRC_MAXSIZE     24576
      .brc2 的有效大小。
  
  #define BRC_MAXNUM      80
      brc_num 的最大值。
  
 private variables: (in board.c)
  
  static time_t brc_expire_time;
      brc_list 中值的下限,時間在此之前的一律當作已讀。會在 init_brdbuf 中被設
      定為 login_start_time - 365 * 86400。
  
  static char   brc_buf[BRC_MAXSIZE];
      呼叫 read_brc_buf 後 .brc2 的前 BRC_MAXSIZE bytes 會被置入這個 buffer 中。

  static int    brc_size;
      呼叫 read_brc_buf 後 brc_buf 中的有效字元數。

  static int    brc_changed = 0;
      從上次讀取 .brc2 到當時為止,brc_num 與 brc_list 是否改變過。

  static int    brc_currbid;

  static int    brc_num;
      brc_list 中的有效數字個數。

  static int    brc_list[BRC_MAXNUM];
      已讀文章的存檔時間。
  

  static void read_brc_buf();
      從 .brc2 中讀取最多 BRC_MAXSIZE bytes 並存入 brc_buf 中,將存入的字元
      數存在 brc_size 中。

  static char * brc_putrecord(char *ptr, char *endp, brcbid_t bid, brcnbrd_t num, const time4_t *list);
      與 brc_getrecord() 的作用正好相反,將資料寫入 puffer 中。
      傳入值:ptr 指向要寫入的 buffer,bid, num, list 分別是要寫入的資料。
      傳回值:指向寫入的 record 下一個字元的指標。
      額外效果:若資料是合法的 (num > 0 && list[0] > brc_expire_time) 且空間足夠,
      資料會被寫入 ptr, endp 之間。

  static int brc_unread_time(time_t ftime, int bnum, int *blist);
      跟 brc_unread() 類似,只是傳入的是檔案建立的時間。
      傳入值:文章的建立時間 (ftime) 及 brc_num (bnum) 和 brc_list (blist)。
      傳回值:如果由 bnum 和 blist 判斷本篇文章未讀傳回 1。
              否則傳回 0。