330 lines
7.7 KiB
C++
330 lines
7.7 KiB
C++
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
#include <scriptpp/scrvect.hpp>
|
|
#include <scriptpp/cmd.hpp>
|
|
|
|
|
|
// please note these macros are also set and used in tcgi_rpl.cpp
|
|
|
|
// the name for the file within a comment directory to store the max id
|
|
#ifndef HINT_FNAME
|
|
#define HINT_FNAME "_hints"
|
|
#endif
|
|
|
|
#ifndef MAX_COMMENT_ID
|
|
#define MAX_COMMENT_ID 50000
|
|
#endif
|
|
|
|
|
|
#if 0
|
|
// the absolute maximum for the comment id
|
|
// how many times we try to pick greater id in case the id
|
|
// is already occupied, despite the information from hints and/or scan
|
|
#ifndef CID_RETRIES
|
|
#define CID_RETRIES 100
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "database.hpp"
|
|
#include "filters.hpp"
|
|
#include "dbforum.hpp"
|
|
|
|
int CommentNode::ChildCount() const
|
|
{
|
|
if(!children)
|
|
return 0;
|
|
int i;
|
|
for(i = 0; i < children_array_size; i++)
|
|
if(!children[i])
|
|
return i;
|
|
return children_array_size; /* shouldn't happen, actually... */
|
|
}
|
|
|
|
void CommentNode::AddChild(int cid)
|
|
{
|
|
if(!children) {
|
|
children_array_size = 8;
|
|
children = new int[children_array_size];
|
|
*children = 0;
|
|
}
|
|
int cnt = ChildCount();
|
|
if(cnt + 2 > children_array_size) { /* 2 is the new child and the 0 */
|
|
int newsize = children_array_size * 2;
|
|
while(cnt + 2 > newsize)
|
|
newsize *= 2;
|
|
int *tmp = new int[newsize];
|
|
int i;
|
|
for(i = 0; i < cnt; i++)
|
|
tmp[i] = children[i];
|
|
tmp[i] = 0;
|
|
delete[] children;
|
|
children = tmp;
|
|
children_array_size = newsize;
|
|
}
|
|
children[cnt] = cid;
|
|
children[cnt+1] = 0;
|
|
}
|
|
|
|
CommentTree::CommentTree(const ScriptVector &auxp)
|
|
: comments(0), comments_array_size(8), max_id(0), aux_params(auxp)
|
|
{
|
|
comments = new CommentNode*[comments_array_size];
|
|
*comments = new CommentNode;
|
|
(*comments)->id = 0;
|
|
(*comments)->parent = 0;
|
|
int i;
|
|
for(i = 1; i < comments_array_size; i++)
|
|
comments[i] = 0;
|
|
}
|
|
|
|
CommentTree::~CommentTree()
|
|
{
|
|
int i;
|
|
for(i = 0; i <= max_id; i++)
|
|
if(comments[i])
|
|
delete comments[i];
|
|
delete[] comments;
|
|
}
|
|
|
|
void CommentTree::ProvideCommentSlot(int id)
|
|
{
|
|
if(comments_array_size > id)
|
|
return;
|
|
|
|
int newsize = comments_array_size * 2;
|
|
while(newsize <= id)
|
|
newsize *= 2;
|
|
|
|
CommentNode **tmp = new CommentNode*[newsize];
|
|
int i;
|
|
for(i = 0; i <= max_id; i++)
|
|
tmp[i] = comments[i];
|
|
for(; i < newsize; i++)
|
|
tmp[i] = 0;
|
|
delete[] comments;
|
|
comments = tmp;
|
|
comments_array_size = newsize;
|
|
}
|
|
|
|
void CommentTree::AddComment(CommentNode *cmt)
|
|
{
|
|
int id = cmt->id;
|
|
if(comments_array_size > id && comments[id]) {
|
|
// this is perfectly okay, in case one of the children was read
|
|
// before its parent comment
|
|
// assert(comments[id]->id == 0)
|
|
// we ``replant'' the children array
|
|
if(cmt->children) // this is a bit of paranoia, mustn't happen
|
|
delete[] cmt->children;
|
|
cmt->children = comments[id]->children;
|
|
cmt->children_array_size = comments[id]->children_array_size;
|
|
comments[id]->children = 0; // so it won't be deleted
|
|
delete comments[id];
|
|
} else {
|
|
ProvideCommentSlot(id);
|
|
}
|
|
comments[id] = cmt;
|
|
if(id > max_id)
|
|
max_id = id;
|
|
ProvideCommentSlot(cmt->parent);
|
|
if(!comments[cmt->parent]) { // need temp. object to store children
|
|
comments[cmt->parent] = new CommentNode;
|
|
comments[cmt->parent]->id = 0;
|
|
}
|
|
comments[cmt->parent]->AddChild(id);
|
|
}
|
|
|
|
const CommentNode* CommentTree::GetComment(int id) const
|
|
{
|
|
if(id > max_id)
|
|
return 0;
|
|
return comments[id];
|
|
}
|
|
|
|
#if 0
|
|
bool CommentTree::SetAuxData(int id, const ScriptVariable &ad)
|
|
{
|
|
if(id < 0 || id > max_id || !comments[id])
|
|
return false;
|
|
comments[id]->auxdata = ad;
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static int read_hint(const char *hintpath)
|
|
{
|
|
int h, r;
|
|
FILE *f;
|
|
f = fopen(hintpath, "r");
|
|
if(!f)
|
|
return -1;
|
|
r = fscanf(f, "%d", &h);
|
|
fclose(f);
|
|
if(r != 1)
|
|
return -1;
|
|
return h;
|
|
}
|
|
|
|
static void write_hint(const char *hintpath, int val)
|
|
{
|
|
FILE *f;
|
|
f = fopen(hintpath, "w");
|
|
if(!f)
|
|
return;
|
|
fprintf(f, "%d\n", val);
|
|
fclose(f);
|
|
}
|
|
|
|
CommentDir::CommentDir(const ScriptVariable &pt, const ScriptVector &auxp)
|
|
: path(pt), aux_params(auxp), tree(0), max_id(-1)
|
|
{
|
|
int hint;
|
|
ScriptVariable hintpath = path + "/" HINT_FNAME;
|
|
hint = read_hint(hintpath.c_str());
|
|
if(hint >= 0) {
|
|
max_id = hint;
|
|
} else {
|
|
bool r = ScanTheTree();
|
|
if(r) {
|
|
max_id = tree->GetMaxId();
|
|
write_hint(hintpath.c_str(), max_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
CommentDir::~CommentDir()
|
|
{
|
|
if(tree)
|
|
delete tree;
|
|
}
|
|
|
|
CommentTree *CommentDir::GetTree()
|
|
{
|
|
if(!tree)
|
|
ScanTheTree();
|
|
return tree;
|
|
}
|
|
|
|
static void
|
|
read_comment_file_to_tree(int id, const char *path, CommentTree *tree)
|
|
{
|
|
FILE *s = fopen(path, "r");
|
|
if(!s)
|
|
return;
|
|
|
|
CommentNode *node = new CommentNode;
|
|
node->id = id;
|
|
node->parent = -1;
|
|
|
|
int c;
|
|
while((c = fgetc(s)) != EOF) {
|
|
if(!node->parser.FeedChar(c)) // ok, int
|
|
break;
|
|
}
|
|
fclose(s);
|
|
|
|
const ScriptVector& hdr = node->parser.GetHeaders();
|
|
int i;
|
|
for(i = 0; i < hdr.Length()-1; i+=2) {
|
|
if(hdr[i] == "parent") {
|
|
long n;
|
|
if(hdr[i+1].GetLong(n, 10) && n > 0 && n <= MAX_COMMENT_ID) {
|
|
node->parent = n;
|
|
} else {
|
|
// XXX error handling here?
|
|
node->parent = 0;
|
|
}
|
|
} else
|
|
if(hdr[i] == "flags") {
|
|
ScriptVector flg(hdr[i+1], ",", " \t\r\n");
|
|
int ln = flg.Length();
|
|
int i;
|
|
for(i = 0; i < ln; i++)
|
|
if(flg[i] == "hidden") {
|
|
node->hidden = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(node->parent == -1) // XXX not found; error?
|
|
node->parent = 0;
|
|
tree->AddComment(node);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool CommentDir::ScanTheTree()
|
|
{
|
|
if(tree) {
|
|
delete tree;
|
|
tree = 0;
|
|
}
|
|
|
|
ReadDir dir(path.c_str());
|
|
if(!dir.OpenOk())
|
|
return false;
|
|
|
|
tree = new CommentTree(aux_params);
|
|
|
|
const char *nm;
|
|
while((nm = dir.Next())) {
|
|
ScriptVariable v(nm);
|
|
long id;
|
|
if(!v.GetLong(id, 10))
|
|
continue;
|
|
read_comment_file_to_tree(id, (path+"/"+v).c_str(), tree);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
#if 0
|
|
int CommentDir::SaveNewComment(int parent, long long date,
|
|
const ScriptVariable &from,
|
|
const ScriptVariable &title,
|
|
const ScriptVariable &text,
|
|
const ScriptVector *aux_params)
|
|
{
|
|
if(max_id < 0) // this probably means the directory is inaccessible
|
|
return -1;
|
|
int fd;
|
|
int new_id = max_id;
|
|
do {
|
|
new_id++;
|
|
if(new_id > MAX_COMMENT_ID)
|
|
return -1;
|
|
ScriptNumber fname(new_id);
|
|
fd = open(fname.c_str(), O_WRONLY|O_CREAT|O_EXCL, 0666);
|
|
} while(fd == -1 && errno == EEXIST && new_id - max_id < CID_RETRIES);
|
|
if(fd == -1)
|
|
return -1;
|
|
|
|
ScriptVariable content =
|
|
ScriptVariable(1024, "id: %d\nparent: %d\ndate: %d\n",
|
|
new_id, parent, date) +
|
|
"from: " + from + "\n" +
|
|
"title: " + title + "\n";
|
|
if(aux_params) {
|
|
int i;
|
|
int len = aux_params->Length();
|
|
for(i = 0; i < len-1; i += 2)
|
|
content += (*aux_params)[i] + ": " + (*aux_params)[i+1] + "\n";
|
|
}
|
|
content += "\n";
|
|
content += text;
|
|
write(fd, content.c_str(), content.Length());
|
|
close(fd);
|
|
return new_id;
|
|
}
|
|
#endif
|