commit a5cbca0f8581f45c8306a426c39c43385f5d99c6
parent bc8b6750bc52d8a386acba249066c3ca94269ee5
Author: libredev <libredev@ircforever.org>
Date: Mon, 13 Mar 2023 19:31:37 +0530
update comment system
Diffstat:
M | .gitignore | | | 7 | +++++++ |
M | Makefile | | | 14 | ++++++++++---- |
D | comment.c | | | 144 | ------------------------------------------------------------------------------- |
D | comment.db | | | 8 | -------- |
D | comment.h | | | 21 | --------------------- |
D | comment.html | | | 19 | ------------------- |
M | config.mk | | | 11 | ++++++++--- |
M | header.html | | | 2 | +- |
M | main.c | | | 301 | +++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------- |
M | style.css | | | 18 | +++++++++++------- |
A | thread.c | | | 266 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | thread.h | | | 31 | +++++++++++++++++++++++++++++++ |
A | thread.html | | | 12 | ++++++++++++ |
A | util.c | | | 118 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | util.h | | | 22 | ++++++++++++++++++++++ |
D | utils.c | | | 179 | ------------------------------------------------------------------------------- |
D | utils.h | | | 29 | ----------------------------- |
17 files changed, 682 insertions(+), 520 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1 +1,8 @@
+.gitignore
index.cgi
+make.sh
+thread.db
+tmp
+trash/
+test.c
+a.out
diff --git a/Makefile b/Makefile
@@ -4,9 +4,9 @@
include config.mk
BIN = index.cgi
-INC = comment.h utils.h http.h
-SRC = main.c comment.c utils.c pdjson/pdjson.c
-DEP = header.html footer.html comment.html style.css music.svg comment.db
+INC = thread.h util.h http.h
+SRC = main.c thread.c util.c pdjson/pdjson.c
+DEP = header.html footer.html thread.html style.css music.svg
all: ${BIN}
@@ -20,8 +20,14 @@ clean:
rm -f ${BIN}
install: ${BIN} ${DEP}
- mkdir -p ${INSTALL_DIR}
+ mkdir -p ${INSTALL_DIR}/threads
+ mkdir -p ${INSTALL_DIR}/tmp
+ chown -R ${HTTPD_USER}:${HTTPD_GROUP} ${INSTALL_DIR}/threads
+ chown -R ${HTTPD_USER}:${HTTPD_GROUP} ${INSTALL_DIR}/tmp
cp ${BIN} ${DEP} ${INSTALL_DIR}
+ for f in ${DEP}; do\
+ sed "s^TITLE^${TITLE}^g" < $$f > ${INSTALL_DIR}/$$f;\
+ done
uninstall:
rm -f ${INSTALL_DIR}/${BIN}
diff --git a/comment.c b/comment.c
@@ -1,144 +0,0 @@
-/* This work is in the public domain. See COPYING file for details. */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "comment.h"
-#include "utils.h"
-
-enum {
- HANDLE_NAME,
- HANDLE_EMAIL,
- HANDLE_BODY,
- HANDLE_DELIMITER,
-};
-
-struct comment_list *
-comment_list_new(char *path)
-{
- struct comment_list *head, *tail;
- FILE *file;
- char line[512];
- int state, lineno, len;
-
- /* init struct */
- head = tail = NULL;
- state = HANDLE_NAME;
- lineno = 0;
-
- if ((file = fopen(path, "rb")) == NULL)
- fatal("%s:", path);
-
- while(fgets(line, sizeof(line), file) != NULL) {
- /* if line doesn't end with newline character */
- if (line[strlen(line) - 1] != '\n' && state != HANDLE_BODY) {
- fatal("%s:%d: name or email is too big", path, lineno);
- } else {
- lineno++;
- if (state == HANDLE_BODY) {
- if (strcmp(line, "---\n") == 0) {
- tail->body[strlen(tail->body)-1] = '\0';
- state = HANDLE_NAME;
- continue;
- }
- } else {
- line[strlen(line) - 1] = '\0';
- }
- }
- switch (state) {
- case HANDLE_NAME:
- if (head == NULL) {
- head = tail = ecalloc(1, sizeof(*tail));
- } else {
- tail->next = ecalloc(1, sizeof(*tail));
- tail = tail->next;
- }
- tail->name = strlen(line) == 0 ? NULL : strdup(line);
- state = HANDLE_EMAIL;
- break;
- case HANDLE_EMAIL:
- tail->email = strlen(line) == 0 ? NULL : strdup(line);
- state = HANDLE_BODY;
- break;
- case HANDLE_BODY:
- if (tail->body == NULL)
- len = strlen(line) + 1;
- else
- len = strlen(tail->body) + strlen(line) + 1;
- tail->body = erealloc(tail->body, len * sizeof(char));
- strcat(tail->body, line);
- break;
- }
- }
- fclose(file);
- if (state != HANDLE_NAME)
- fatal("%s:%d: incomplete comment", path, lineno);
- return head;
-}
-
-void
-comment_list_free(struct comment_list *clist)
-{
- struct comment_list *it, *itn;
-
- for (it = clist; it != NULL;) {
- free(it->name);
- free(it->email);
- free(it->body);
- itn = it->next;
- free(it);
- it = itn;
- }
-}
-
-void
-comment_list_print(struct comment_list *clist)
-{
- struct comment_list *it;
-
- puts("<h2> Comments </h2>");
- for (it = clist; it != NULL; it = it->next) {
-
- puts("<div class='comment'>");
- printf("<p>");
-
- if (it->name == NULL)
- printf("<b>Anonymous</b>");
- else
- printf("<b>%s</b>", it->name);
-
- if (it->email != NULL)
- printf(" <%s>", it->email);
-
- printf("</p>\n");
-
- printf("<pre>%s</pre>\n", it->body);
- puts("</div>");
- }
-}
-
-void
-comment_write(struct input_list *ilist, char *path)
-{
- FILE *file;
- char *name, *email, *body;
-
- if ((file = fopen(path, "a")) == NULL)
- fatal("%s:", path);
-
- /* first get all the values before writing to the file */
- name = input_list_find(ilist, "name");
- email = input_list_find(ilist, "email");
- body = input_list_find(ilist, "body");
- if (body == NULL)
- fatal("comment_write: body can't be empty");
-
- /* now write the values on the file */
- name == NULL ? fprintf(file, "\n") : fprintf(file, "%s\n", name);
- email == NULL ? fprintf(file, "\n") : fprintf(file, "%s\n", email);
- fprintf(file, "%s\n", body);
- fprintf(file, "---\n");
-
- fclose(file);
-}
diff --git a/comment.db b/comment.db
@@ -1,8 +0,0 @@
-libredev
-libredev@ircforever.org
-I love FREEDOM.
----
-
-
-This is anonymous comment.
----
diff --git a/comment.h b/comment.h
@@ -1,21 +0,0 @@
-/* This work is in the public domain. See COPYING file for details. */
-
-#ifndef COMMENT_H
-#define COMMENT_H
-
-struct input_list;
-
-struct comment_list {
- char *name;
- char *email;
- char *body;
- struct comment_list *next;
-};
-
-struct comment_list *
- comment_list_new(char *);
-void comment_list_free(struct comment_list *);
-void comment_list_print(struct comment_list *);
-void comment_write(struct input_list *, char *);
-
-#endif /* COMMENT_H */
diff --git a/comment.html b/comment.html
@@ -1,19 +0,0 @@
-<!-- This work is in the public domain. See COPYING file for details. -->
-
-<br>
-<h2>Add a comment</h2>
-<form>
- <fieldset>
- <p><b>NOTE: By commenting, you agree to release the comment under
- <a href="https://creativecommons.org/publicdomain/zero/1.0/">CC0 1.0</a>.
- </b></p>
- <label for="name">Name (optional):</label><br>
- <input type="text" name="name" id="name"><br><br>
- <label for="email">Email (optional):</label><br>
- <input type="email" name="email" id="email" size="30"><br><br>
- <label for="comment">Comment:</label><br>
- <textarea name="body" id="body" rows="8" required></textarea><br><br>
- <input type="submit" value="Submit">
- </fieldset>
-</form>
-<br>
diff --git a/config.mk b/config.mk
@@ -1,10 +1,15 @@
# This work is in the public domain. See COPYING file for details.
-INSTALL_DIR=/var/www/htdocs/radio.ircforever.org
-STATUS_JSON=http://theinterlude.live:8000/status-json.xsl
+NAME=Example Radio
+TITLE=Example Radio | https://git.ircforever.org/radio
+ICECAST_URL=http://radio.example.com
+INSTALL_DIR=/var/www/htdocs/radio.example.com
+HTTPD_USER=httpd
+HTTPD_GROUP=httpd
CFLAGS = -g -std=c99 -Wall -Wextra -Wpedantic -Wfatal-errors\
-Wstrict-prototypes -Wold-style-definition\
- -D_DEFAULT_SOURCE -DSTATUS_JSON=\"$(STATUS_JSON)\"
+ -D_DEFAULT_SOURCE -DICECAST_URL=\"$(ICECAST_URL)\" -DNAME=\""$(NAME)"\"\
+ -DSTREAM_URL_REMOVE_PORT
LDFLAGS =
diff --git a/header.html b/header.html
@@ -6,6 +6,6 @@
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>IRCForever Radio | Free Culture Media Streaming</title>
+ <title>TITLE</title>
<link rel="stylesheet" type="text/css" href="/style.css"/>
</head>
diff --git a/main.c b/main.c
@@ -6,13 +6,22 @@
#include <stdlib.h>
#include <string.h>
-#include "utils.h"
-#include "comment.h"
+#include "util.h"
+#include "thread.h"
#define HTTP_IMPLEMENTATION
#include "http.h"
#include "pdjson/pdjson.h"
+#define THREAD_FILE "threads/thread.db"
+
+enum scope {
+ SCOPE_NONE,
+ SCOPE_ROOT,
+ SCOPE_ICESTATES,
+ SCOPE_SOURCE
+};
+
struct icestats {
char *admin;
char *host;
@@ -50,8 +59,7 @@ struct source {
struct icestats icestats;
struct source *sources = NULL;
-int sources_length = 0;
-int source_active = FALSE;
+int source_len = 0;
int header_sent = FALSE;
void
@@ -96,11 +104,10 @@ icestats_set(struct icestats *s, json_stream *json, const char *key)
else if (strcmp(key, "server_start_iso8601" ) == 0) var = &s->server_start_iso8601;
else if (strcmp(key, "dummy" ) == 0) return;
- if (var == NULL) {
+ if (var == NULL)
fatal("failed to handle icestats key: %s", key);
- } else {
+ else
*var = json_get_value(json);
- }
}
void
@@ -157,6 +164,25 @@ source_set(struct source *s, json_stream *json, const char *key)
if (var == NULL) {
fatal("failed to handle source key: %s", key);
+#ifdef STREAM_URL_REMOVE_PORT
+ /* } else if (var == &s->listenurl) {
+ char *tmp = json_get_value(json);
+ char *port_start, *port_end;
+ char *new_url;
+
+ port_start = strchr(tmp, ':'); *//* ignore the colon after http */
+ /* if ((port_start = strchr(++port_start, ':')) == NULL)
+ *var = tmp;
+ else {
+ if ((port_end = strchr(port_start, '/')) == NULL)
+ fatal("No mount point");
+ new_url = emalloc(port_start - tmp + strlen(port_end) + 1);
+ strncpy(new_url, tmp, port_start - tmp);
+ strcat(new_url, port_end);
+ free(tmp);
+ *var = new_url;
+ } */
+#endif
} else {
*var = json_get_value(json);
if (var == &s->server_name) {
@@ -237,7 +263,7 @@ print_navigation_bar(int index)
char *scripts[] = { "/stream/", "/music/", "/contact/", "/about/", "/login/" };
puts("<ul>");
- puts("<li><a href='/'>IRCForever Radio</a></li>");
+ puts("<li><a href='/'>"NAME"</a></li>");
for (int i = 0; i < 5; i++) {
printf("<li><a ");
@@ -250,103 +276,134 @@ print_navigation_bar(int index)
}
void
-source_increment(void)
-{
- sources_length++;
- sources = erealloc(sources, sources_length * sizeof(struct source));
- memset(&sources[sources_length-1], 0, sizeof(struct source));
-}
-
-void json_handle_object(json_stream *json, enum json_type jtype);
-
-void
-json_handle_source(json_stream *json, enum json_type jtype)
+json_process(json_stream *json)
{
- if (jtype == JSON_ARRAY) {
- while (json_peek(json) != JSON_ARRAY_END
- && !json_get_error(json)) {
- source_increment();
- source_active = TRUE;
- json_handle_object(json, json_next(json));
- source_active = FALSE;
+ enum scope scope = SCOPE_NONE;
+ char *str;
+ while(1) {
+ enum json_type type = json_next(json);
+ switch (type) {
+ case JSON_DONE:
+ if (scope != SCOPE_NONE)
+ fatal("incomplete scope");
+ return;
+ case JSON_STRING:
+ str = json_get_string(json, NULL);
+ switch (scope) {
+ case SCOPE_NONE:
+ fatal("%s: string in none scope");
+ break;
+ case SCOPE_ROOT:
+ if (strcmp(str, "icestats") == 0) {
+ if (json_next(json) != JSON_OBJECT)
+ fatal("icestats doesn't have any scope");
+ scope = SCOPE_ICESTATES;
+ } else {
+ fatal("%s: undefined string in icestats scope");
+ }
+ break;
+ case SCOPE_ICESTATES:
+ if (strcmp(str, "source") == 0) {
+ if (json_next(json) != JSON_OBJECT)
+ fatal("source doesn't have any scope");
+ scope = SCOPE_SOURCE;
+ source_len++;
+ sources = erealloc(sources, (source_len) * sizeof(struct source));
+ memset(&sources[source_len-1], 0, sizeof(struct source));
+ } else {
+ icestats_set(&icestats, json, str);
+ }
+ break;
+ case SCOPE_SOURCE:
+ source_set(&sources[source_len-1], json, str);
+ break;
+ }
+ break;
+ case JSON_ARRAY:
+ break;
+ case JSON_ARRAY_END:
+ break;
+ case JSON_OBJECT:
+ if (scope == SCOPE_NONE)
+ scope = SCOPE_ROOT;
+ else
+ fatal("unknown scope");
+ break;
+ case JSON_OBJECT_END:
+ switch (scope) {
+ case SCOPE_NONE:
+ fatal("extra scope");
+ break;
+ case SCOPE_ROOT:
+ scope = SCOPE_NONE;
+ break;
+ case SCOPE_ICESTATES:
+ scope = SCOPE_ROOT;
+ break;
+ case SCOPE_SOURCE:
+ scope = SCOPE_ICESTATES;
+ break;
+ }
+ break;
+ case JSON_ERROR:
+ fatal("%zu: %s", json_get_lineno(json),
+ json_get_error(json));
+ break;
+ default:
+ fatal("cannot handle json type: %d", type);
+ break;
}
- json_next(json);
- } else if (jtype == JSON_OBJECT) {
- source_increment();
- source_active = TRUE;
- json_handle_object(json, JSON_OBJECT);
- source_active = FALSE;
- } else {
- fatal("json_handle_source: invalid json_type");
}
}
void
-json_handle_object(json_stream *json, enum json_type jtype)
+redirect(char *url)
{
- if (jtype != JSON_OBJECT)
- fatal("json_handle_object: invalid json_type");
-
- while (json_peek(json) != JSON_OBJECT_END && !json_get_error(json)) {
- json_next(json);
- const char *key = json_get_string(json, NULL);
- if (source_active == TRUE) {
- source_set(&sources[sources_length-1], json, key);
- } else if (strcmp(key, "source") == 0) {
- json_handle_source(json, json_next(json));
- } else if (strcmp(key, "icestats") == 0) {
- json_handle_object(json, json_next(json));
- } else {
- icestats_set(&icestats, json, key);
- }
- }
- json_next(json);
+ printf("Content-Type: text/html\r\n");
+ printf("Status: 302 Found\r\n");
+ printf("Location: %s\r\n", url);
+ printf("\r\n");
+ printf("<html><body>Redirecting to <a href='%s'>%s</a></body></html>\n", url, url);
+ exit(0);
}
int
main(int argc, char *argv[])
{
json_stream json;
- char *buffer;
+ char *jsonbuf;
char *request_url;
+ char comment[4096];
+ FILE *f;
+ char c;
+ int id;
+
+ struct thread thread;
- struct input_list *inputs;
- struct comment_list *comments;
+ UNUSED(argc);
+ UNUSED(argv);
/* body */
request_url = getenv("REQUEST_URI");
if (request_url == NULL)
fatal("environment variable 'REQUEST_URI' is not set");
- inputs = input_list_new(request_url);
- comments = comment_list_new("comment.db");
- if (inputs != NULL) {
- comment_write(inputs, "comment.db");
- printf("Content-Type: text/html\r\n");
- printf("Status: 302 Found\r\n");
- printf("Location: %s\r\n", request_url);
- printf("\r\n");
- printf("<html><body>Redirecting to <a href='%s'>%s</a></body></html>\n", request_url, request_url);
- exit(0);
- }
-
- if (!header_sent) {
+ if (strchr(request_url, '?') == NULL) {
puts("Content-Type: text/html\r");
puts("Status: 200 OK\r");
puts("\r");
header_sent = TRUE;
- }
- /* print header */
- if ((buffer = read_file("header.html")) == NULL)
- fatal("read_file: header.html:");
- printf("%s", buffer);
- free(buffer);
+ /* print header */
+ f = file_open("header.html");
+ file_print(f, stdout);
+ file_close(f);
+ }
- /* get json file to the buffer */
+ /* get json file */
http_t* request;
http_status_t status = HTTP_STATUS_PENDING;
- request = http_get(STATUS_JSON, NULL);
+ request = http_get(ICECAST_URL"/status-json.xsl", NULL);
if (request == NULL)
fatal("invalid request");
@@ -354,34 +411,77 @@ main(int argc, char *argv[])
status = http_process(request);
if(status == HTTP_STATUS_FAILED)
fatal("HTTP request failed (%d): %s.\n", request->status_code, request->reason_phrase);
- buffer = (char*)request->response_data;
+ jsonbuf = (char*)request->response_data;
/* parse json buffer */
- json_open_string(&json, buffer);
+ json_open_string(&json, jsonbuf);
json_set_streaming(&json, false);
- json_handle_object(&json, json_next(&json));
- if (json_get_error(&json))
- fatal("%lu: %s", json_get_lineno(&json), json_get_error(&json));
+ json_process(&json);
+ json_close(&json);
+
+ http_release(request);
if (request_url[strlen(request_url)-1] == '/')
request_url[strlen(request_url)-1] = '\0';
- if ((strlen(request_url) == 0) || (strcmp(request_url, "/stream") == 0)) {
+ if (strcmp(request_url, "/stream") == 0
+ || *request_url == '\0') {
print_navigation_bar(0);
- for (int i = 0; i < sources_length; i++)
+ for (int i = 0; i < source_len; i++)
source_print(&sources[i]);
- if ((buffer = read_file("comment.html")) == NULL)
- fatal("read_file: comment.html:");
- printf("%s", buffer);
- free(buffer);
+ puts("<h2> Comments </h2>");
+ /* comment box */
+ f = file_open("thread.html");
+ file_print(f, stdout);
+ file_close(f);
+ /* comment threads */
+ thread_open(&thread, THREAD_FILE);
+ while (thread_next(&thread) != EOF)
+ thread_write_html(&thread, stdout, thread.depth);
+ thread_close(&thread);
+ } else if (sscanf(request_url, "/stream/%9d%c", &id, &c) == 1) {
+ int parent_depth = -1;
+ thread_open(&thread, THREAD_FILE);
+ while (thread_next(&thread) != EOF) {
+ if (thread.id == id) {
+ parent_depth = thread.depth;
+ break;
+ }
+ }
+ if (parent_depth == -1)
+ not_found();
- comment_list_print(comments);
+ print_navigation_bar(0);
+ thread_write_html(&thread, stdout, 0);
+ f = file_open("thread.html");
+ file_print(f, stdout);
+ file_close(f);
+ while (thread_next(&thread) != EOF && thread.depth > parent_depth)
+ thread_write_html(&thread, stdout, thread.depth - parent_depth);
+ thread_close(&thread);
+ } else if (sscanf(request_url, "/stream/?comment=%4095[^&]%c", comment, &c) == 1) {
+ char *msg = request_url + strlen("/stream/?comment=");
+ parse_html_str(msg);
+ thread_open(&thread, THREAD_FILE);
+ thread_add(&thread, 0, msg);
+ thread_close(&thread);
+ *strchr(request_url, '?') = '\0';
+ redirect(request_url);
+ } else if (sscanf(request_url, "/stream/%9d?comment=%4095[^&]%c", &id, comment, &c) == 2) {
+ char *msg = strchr(request_url, '=');
+ msg++;
+ parse_html_str(msg);
+ thread_open(&thread, THREAD_FILE);
+ thread_add(&thread, id, msg);
+ thread_close(&thread);
+ *strchr(request_url, '?') = '\0';
+ redirect(request_url);
} else if (strcmp(request_url, "/about") == 0) {
print_navigation_bar(3);
icestats_print(&icestats);
} else {
- for (int i = 0; i < sources_length; i++) {
+ /* for (int i = 0; i <= source_index; i++) {
if (sources[i].url == NULL)
continue;
if (strcmp(request_url, sources[i].url) == 0) {
@@ -389,29 +489,20 @@ main(int argc, char *argv[])
source_print(&sources[i]);
goto out;
}
- }
+ } */
not_found();
}
out:
- /* comment */
- /* comment_print("comment.db"); */
/* footer */
- if ((buffer = read_file("footer.html")) == NULL)
- fatal("read_file: footer.html:");
- printf("%s", buffer);
- free(buffer);
+ f = file_open("footer.html");
+ file_print(f, stdout);
+ file_close(f);
/* cleanup */
- comment_list_free(comments);
- input_list_free(inputs);
- json_close(&json);
- http_release(request);
- if (sources != NULL) {
- for (int i = 0; i < sources_length; i++)
- source_free(&sources[i]);
- free(sources);
- }
+ for (int i = 0; i < source_len; i++)
+ source_free(&sources[i]);
+ free(sources);
icestats_free(&icestats);
return 0;
diff --git a/style.css b/style.css
@@ -90,12 +90,6 @@ tr:nth-child(odd) {
height: 40px;
}
-.comment {
- margin: 10px 0px;
- padding: 0px 10px;
- border: 1px solid darkgrey;
-}
-
hr {
margin: 20px 0px;
border: none;
@@ -128,5 +122,15 @@ p.footer {
}
form textarea {
- width: 100%;
+ font-size: 16px;
+}
+
+.thread_header {
+ color: dimgrey;
+}
+
+.thread {
+ margin: 10px 0px;
+ padding: 0px 10px;
+ border: 1px solid darkgrey;
}
diff --git a/thread.c b/thread.c
@@ -0,0 +1,266 @@
+#include "thread.h"
+
+#include "util.h"
+
+#include <fcntl.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#define MAX_COLUMN_LEN 78
+
+enum read_state
+{
+ HEADER,
+ MESSAGE
+};
+
+/*
+static void
+fprint_tabs(FILE *stream, int n)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ fputc('\t', stream);
+}
+*/
+
+static int
+msg_push(struct thread *t, char c)
+{
+ int newcap;
+ char *newmsg;
+
+ if (t->msglen == t->msgcap) {
+ newcap = t->msgcap * 2;
+ if ((newmsg = realloc(t->msg, newcap)) == NULL)
+ return -1;
+ t->msgcap = newcap;
+ t->msg = newmsg;
+ }
+ t->msg[t->msglen++] = c;
+ return 0;
+}
+
+int
+thread_open(struct thread *t, char *path)
+{
+ if ((t->stream = fopen(path, "r")) == NULL)
+ fatal("%s:", path);
+ t->msgcap = 1024;
+ t->msg = emalloc(t->msgcap);
+ thread_reset(t);
+ return 0;
+}
+
+void
+thread_reset(struct thread *t)
+{
+ t->msglen = 0;
+ t->depth = -1;
+ if (fseek(t->stream, 0, SEEK_SET) == -1)
+ fatal("fseek:");
+ fscanf(t->stream, "%9d\n", &t->lastid);
+ t->lineno = 1;
+}
+
+int
+thread_close(struct thread *t)
+{
+ fclose(t->stream);
+ free(t->msg);
+ return 0;
+}
+
+int
+thread_next(struct thread *t)
+{
+ char c, lc; /* current char, last char */
+ enum read_state state;
+ char *str;
+ int depth;
+
+ state = HEADER;
+ t->msglen = 0;
+ lc = '\0';
+ depth = -1;
+
+ while ((c = fgetc(t->stream))) {
+ if (c == EOF && lc == '\0')
+ return EOF;
+ if (c == '\n') {
+ t->lineno++;
+ }
+ if (c == '\n' && state == HEADER) {
+ t->msg[t->msglen] = '\0';
+ for(str = t->msg; *str == '#'; str++)
+ depth++;
+ if (depth == -1 || depth > t->depth+1)
+ goto err;
+ if (depth == 0)
+ t->pid = 0;
+ else
+ t->pid = t->depth_array[depth-1];
+ t->depth = depth;
+ if (sscanf(str,
+ "%9d %31s %4d-%2d-%2d %2d:%2d:%2d %c",
+ &t->id, t->user,
+ &t->time[0], &t->time[1], &t->time[2],
+ &t->time[3], &t->time[4], &t->time[5],
+ &c) != 8) /* make sure c don't get assigned */
+ goto err;
+ t->depth_array[t->depth] = t->id;
+ t->msglen = 0;
+ state = MESSAGE;
+ lc = c;
+ } else if (c == EOF || (lc == '\n' && c == '#')) {
+ if (state != MESSAGE || t->msglen == 0)
+ goto err;
+ if (c == '#')
+ ungetc(c, t->stream);
+ t->msg[t->msglen-1] = '\0';
+ return 0;
+ } else {
+ msg_push(t, c);
+ lc = c;
+ }
+ }
+err:
+ fatal("%d: syntax error", t->lineno);
+ return EOF;
+}
+
+static void
+thread_print(FILE *stream,
+ int depth, int id, char *user,
+ int year, int mon, int day,
+ int hour, int min, int sec,
+ char *msg)
+{
+ int i;
+ for (i = 0; i <= depth; i++)
+ fputc('#', stream);
+ fprintf(stream, "%d %s %04d-%02d-%02d %02d:%02d:%02d\n",
+ id, user,
+ year, mon, day,
+ hour, min, sec);
+ fprintf(stream, "%s\n", msg);
+}
+
+int
+thread_add(struct thread *t, int pid, char *msg)
+{
+ FILE *tmp;
+ time_t clock;
+ struct tm *tm;
+ int pdepth = -1; /* parent depth */
+ int res, added = 0;
+
+ if ((tmp = fopen("tmp/tmp.db", "w")) == NULL)
+ fatal("%s:", "tmp.db");
+ fprintf(tmp, "%d\n", t->lastid+1);
+
+ time(&clock);
+ tm = gmtime(&clock);
+ while (1) {
+ res = thread_next(t);
+ if ((pdepth != -1 && !added &&
+ (res == EOF || t->depth <= pdepth)) ||
+ (res == EOF && pid == 0)) {
+ thread_print(tmp, pdepth+1, t->lastid+1, "Anonymous",
+ tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
+ msg);
+ added = 1;
+ }
+ if (res == EOF)
+ break;
+ if (pdepth == -1 && t->id == pid)
+ pdepth = t->depth;
+
+ thread_print(tmp, t->depth, t->id, t->user,
+ t->time[0], t->time[1], t->time[2],
+ t->time[3], t->time[4], t->time[5],
+ t->msg);
+ }
+ fclose(tmp);
+ if (pid != 0 && pdepth == -1)
+ fatal("failed to find parent id: %d", pid);
+
+ if (renameat(AT_FDCWD, "./tmp/tmp.db", AT_FDCWD, "./threads/thread.db") == -1)
+ fatal("renameat:");
+ return 0;
+}
+
+/* int
+thread_write_plain(struct thread *t, FILE *stream)
+{
+ char *s, *last_word;
+ int column;
+
+ fprint_tabs(stream, t->depth);
+ fprintf(stream, "* %s %d-%02d-%02d %02d:%02d:%02d\n",
+ t->user,
+ t->time[0], t->time[1], t->time[2],
+ t->time[3], t->time[4], t->time[5]);
+
+ fprint_tabs(stream, t->depth);
+ fputs("| ", stream);
+ column = 0;
+ last_word = t->msg;
+ for (s = t->msg;; s++) {
+ if (*s == ' ' || *s == '\n' || *s == '\0') {
+ if ((column + s - last_word) > MAX_COLUMN_LEN
+ || (*last_word == '\n')) {
+ last_word++;
+ fputc('\n', stream);
+ fprint_tabs(stream, t->depth);
+ fputs("| ", stream);
+ column = 0;
+ }
+ fprintf(stream, "%.*s", (int)(s - last_word), last_word);
+ column += s - last_word;
+ last_word = s;
+
+ if (*s == '\0')
+ break;
+ }
+ }
+ fputs("\n\n", stream);
+ return 0;
+} */
+
+int
+thread_write_html(struct thread *t, FILE *stream, int depth)
+{
+ char *s;
+
+ fprintf(stream, "<div id=\"%d\" class=\"thread\" style=\"margin-left:%d%%;\">\n",
+ t->id, depth * 2);
+
+ fprintf(stream, "<p class=\"thread_header\">");
+ fprintf(stream, "%s %d-%02d-%02d %02d:%02d:%02d | ", t->user,
+ t->time[0], t->time[1], t->time[2],
+ t->time[3], t->time[4], t->time[5]);
+ if (t->pid != 0) {
+ if (depth == 0)
+ fprintf(stream, "<a href=\"%d\">parent</a> | ", t->pid);
+ else
+ fprintf(stream, "<a href=\"#%d\">parent</a> | ", t->pid);
+ }
+ fprintf(stream, "<a href=\"#%d\">link</a> | ", t->id);
+ fprintf(stream, "<a href=\"%d\">reply</a>", t->id);
+ fprintf(stream, "</p>\n");
+
+ fputs("<p>", stream);
+ for (s = t->msg; *s != '\0'; s++) {
+ if (*s == '\n') {
+ fputs("<br>", stream);
+ }
+ fputc(*s, stream);
+ }
+ fputs("</p>\n", stream);
+ fputs("</div>\n", stream);
+ return 0;
+}
diff --git a/thread.h b/thread.h
@@ -0,0 +1,31 @@
+#ifndef THREAD_H
+#define THREAD_H
+
+#define MAX_DEPTH 10
+
+#include <stdio.h>
+
+struct thread {
+ int id; /* unique id */
+ int pid; /* parent id */
+ int lastid;
+ int time[6];
+ char user[32];
+ char *msg;
+ int msgcap;
+ int msglen;
+ FILE *stream;
+ int lineno;
+ int depth;
+ int depth_array[MAX_DEPTH];
+};
+
+int thread_open(struct thread *, char *);
+void thread_reset(struct thread *);
+int thread_close(struct thread *);
+int thread_next(struct thread *);
+int thread_add(struct thread *, int, char *);
+int thread_write_plain(struct thread *, FILE *);
+int thread_write_html(struct thread *, FILE *, int);
+
+#endif /* THREAD_H */
diff --git a/thread.html b/thread.html
@@ -0,0 +1,12 @@
+<!-- This work is in the public domain. See COPYING file for details. -->
+
+<form>
+ <fieldset>
+ <p><b>Add a comment</b></p>
+ <textarea name="comment" id="comment" cols="80" rows="4" maxlength="1024" required></textarea>
+ <p><b>NOTE: By commenting, you agree to release this comment into the
+ <a href="https://git.ircforever.org/radio/file/COPYING.html">Public Domain</a>.
+ </b></p>
+ <input type="submit" value="add comment">
+ </fieldset>
+</form>
diff --git a/util.c b/util.c
@@ -0,0 +1,118 @@
+/* This work is in the public domain. See COPYING file for details. */
+
+extern int header_sent;
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+void
+fatal(const char *fmt, ...)
+{
+ va_list ap;
+
+ if (!header_sent) {
+ puts("Content-Type: text/html\r");
+ puts("Status: 200 OK\r");
+ puts("\r");
+ header_sent = TRUE;
+ }
+
+ fputs("ERROR: ", stdout);
+ va_start(ap, fmt);
+ vfprintf(stdout, fmt, ap);
+ va_end(ap);
+
+ if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+ fprintf(stdout, " %s\n", strerror(errno));
+ } else {
+ fprintf(stdout, "\n");
+ }
+
+ exit(1);
+}
+
+void *
+emalloc(size_t size)
+{
+ void *p;
+ if ((p = malloc(size)) == NULL)
+ fatal("malloc:");
+ return p;
+}
+
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+ void *p;
+ if ((p = calloc(nmemb, size)) == NULL)
+ fatal("calloc:");
+ return p;
+}
+
+void *
+erealloc(void *ptr, size_t size)
+{
+ void *p;
+ if ((p = realloc(ptr, size)) == NULL)
+ fatal("realloc:");
+ return p;
+}
+
+FILE *
+file_open(char *path)
+{
+ FILE *f;
+ if ((f = fopen(path, "r")) == NULL)
+ fatal("%s:", path);
+ return f;
+}
+
+void
+file_print(FILE *f, FILE *stream)
+{
+ char c;
+
+ if (fseek(f, 0, SEEK_SET) == -1)
+ fatal("fseek:");
+
+ while ((c = fgetc(f)) != EOF) {
+ fputc(c, stream);
+ }
+}
+
+void
+file_close(FILE *f)
+{
+ fclose(f);
+}
+
+void
+parse_html_str(char *str)
+{
+ char *x, *y, tmp;
+
+ x = y = str;
+ while (*y != '\0') {
+ if (*y == '%') {
+ if (*(y+1) == '\0' || *(y+2) == '\0')
+ fatal("parse_html_str: hex char missing");
+ tmp = *(y+3);
+ *(y+3) = '\0';
+ *x = strtol(y+1, NULL, 16);
+ if (*x != 13) /* ignore carriage return */
+ x++;
+ *(y+3) = tmp;
+ y+=3;
+ } else {
+ if (*y == '+')
+ *y = ' ';
+ *x++ = *y++;
+ }
+ }
+ *x = '\0';
+}
diff --git a/util.h b/util.h
@@ -0,0 +1,22 @@
+/* This work is in the public domain. See COPYING file for details. */
+
+#ifndef UTILS_H
+#define UTILS_H
+
+#define TRUE 1
+#define FALSE 0
+
+#define UNUSED(x) (void)(x)
+
+#include <stddef.h>
+
+void fatal(const char *, ...);
+void *emalloc(size_t);
+void *ecalloc(size_t, size_t);
+void *erealloc(void *, size_t);
+FILE *file_open(char *);
+void file_print(FILE *, FILE *);
+void file_close(FILE *);
+void parse_html_str(char *);
+
+#endif /* UTILS_H */
diff --git a/utils.c b/utils.c
@@ -1,179 +0,0 @@
-/* This work is in the public domain. See COPYING file for details. */
-
-extern int header_sent;
-
-#include <errno.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "utils.h"
-
-void
-fatal(const char *fmt, ...)
-{
- va_list ap;
-
- if (!header_sent) {
- puts("Content-Type: text/html\r");
- puts("Status: 200 OK\r");
- puts("\r");
- header_sent = TRUE;
- }
-
- fputs("ERROR: ", stdout);
- va_start(ap, fmt);
- vfprintf(stdout, fmt, ap);
- va_end(ap);
-
- if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
- fprintf(stdout, " %s\n", strerror(errno));
- } else {
- fprintf(stdout, "\n");
- }
-
- exit(1);
-}
-
-void *
-emalloc(size_t size)
-{
- void *p;
- if ((p = malloc(size)) == NULL)
- fatal("malloc:");
- return p;
-}
-
-void *
-ecalloc(size_t nmemb, size_t size)
-{
- void *p;
- if ((p = calloc(nmemb, size)) == NULL)
- fatal("calloc:");
- return p;
-}
-
-void *
-erealloc(void *ptr, size_t size)
-{
- void *p;
- if ((p = realloc(ptr, size)) == NULL)
- fatal("realloc:");
- return p;
-}
-
-char *
-read_file(const char *name)
-{
- char *content;
- long size;
- FILE *file;
-
- file = fopen(name, "rb");
- if (file == NULL)
- goto err;
- if (fseek(file, 0, SEEK_END) != 0)
- goto err;
- if ((size = ftell(file)) == -1)
- goto err;
- if (fseek(file, 0, SEEK_SET) != 0)
- goto err;
- if ((content = malloc((size + 1) * sizeof(char))) == NULL)
- goto err;
- if (fread(content, sizeof(char), size, file) != (size_t)size)
- goto err;
- fclose(file);
- (content)[size] = '\0';
- return content;
-err:
- fatal("%s:", name);
- return NULL;
-}
-
-void
-parse_html_str(char *str)
-{
- char *x, *y, tmp;
-
- x = y = str;
- while (*y != '\0') {
- if (*y == '%') {
- if (*(y+1) == '\0' || *(y+2) == '\0')
- fatal("parse_html_str: hex char missing");
- tmp = *(y+3);
- *(y+3) = '\0';
- *x = strtol(y+1, NULL, 16);
- if (*x != 13) /* ignore carriage return */
- x++;
- *(y+3) = tmp;
- y+=3;
- } else {
- if (*y == '+')
- *y = ' ';
- *x++ = *y++;
- }
- }
- *x = '\0';
-}
-
-struct input_list *
-input_list_new(char *url)
-{
- struct input_list *head, *tail;
- char *name, *value;
- int done;
-
- head = tail = NULL;
- done = FALSE;
-
- if ((url = strchr(url, '?')) == NULL)
- return NULL;
- *url++ = '\0';
-
- while (!done) {
- name = url;
- if ((url = strchr(url, '&')) == NULL)
- done = TRUE;
- else
- *url++ = '\0';
-
- if ((value = strchr(name, '=')) == NULL)
- fatal("parse_url: %s: no value", name);
- *value++ = '\0';
-
- if (head == NULL) {
- head = tail = ecalloc(1, sizeof(*tail));
- } else {
- tail->next = ecalloc(1, sizeof(*tail));
- tail = tail->next;
- }
- parse_html_str(name);
- parse_html_str(value);
- tail->name = strlen(name) == 0 ? NULL : name;
- tail->value = strlen(value) == 0 ? NULL : value;
- }
- return head;
-}
-
-void
-input_list_free(struct input_list *list)
-{
- struct input_list *it, *itn;
- for (it = list; it != NULL;) {
- itn = it->next;
- free(it);
- it = itn;
- }
-}
-
-char *
-input_list_find(struct input_list *list, char *name)
-{
- struct input_list *it;
- for (it = list; it != NULL; it = it->next) {
- if (strcmp(name, it->name) == 0)
- return it->value;
- }
- return NULL;
-}
diff --git a/utils.h b/utils.h
@@ -1,29 +0,0 @@
-/* This work is in the public domain. See COPYING file for details. */
-
-#ifndef UTILS_H
-#define UTILS_H
-
-#define TRUE 1
-#define FALSE 0
-
-#include <stddef.h>
-
-struct input_list {
- char *name;
- char *value;
- struct input_list *next;
-};
-
-void fatal(const char *, ...);
-void *emalloc(size_t);
-void *ecalloc(size_t, size_t);
-void *erealloc(void *, size_t);
-char *read_file(const char *);
-void parse_html_str(char *);
-
-struct input_list *
- input_list_new(char *);
-void input_list_free(struct input_list *);
-char *input_list_find(struct input_list *, char *);
-
-#endif /* UTILS_H */