349 lines
10 KiB
C
349 lines
10 KiB
C
/* lemon_board Copyright (C) 2022 Wael Karram.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, version 3 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/* Function implementations. */
|
|
/* This function is used to generate one post. */
|
|
/* Returns NULL on allocation error, caller should free returned buffer. */
|
|
static inline gemtext_post_t *generate_post_gemtext(post_t *post) {
|
|
/* Sanity check for debug mode. */
|
|
assert(post);
|
|
|
|
/* String constants. */
|
|
const char append_op[3] = ":\n";
|
|
const char append_id[3] = ", ";
|
|
const char append_row[2] = "\n"
|
|
|
|
/* Calculate and allocate output buffer. */
|
|
size_t buffer_size, i;
|
|
char *buffer, *id_buffer;
|
|
gemtext_post_t *post_result;
|
|
|
|
/* Buffer size = size of each referenced post (including current) + sizeof char for each extra (\n) + sizeof text + sizeof char for null-terminator. */
|
|
buffer_size = ((GEMTEXT_GENERATOR_H_ID_BUFFER_SIZE + 3L)* sizeof(char)) * (post->referenced_posts_length + 1L)
|
|
+ post->text_length * sizeof(char) + sizeof(char);
|
|
/* Attempt to allocate. */
|
|
buffer = malloc(buffer_size);
|
|
id_buffer = malloc(GEMTEXT_GENERATOR_H_ID_BUFFER_SIZE);
|
|
post_result = malloc(sizeof(gemtext_post_t));
|
|
if (!buffer || !id_buffer || !post_result) {
|
|
free(buffer);
|
|
free(id_buffer);
|
|
free(post_result);
|
|
return NULL;
|
|
}
|
|
|
|
/* Set the total allocated text length. */
|
|
post_result->allocated_length = (buffer_size / sizeof(char));
|
|
|
|
/* Allocation successfull, fill. */
|
|
/* Start with the ID.*/
|
|
sprintf(id_buffer, "%lld", post->id);
|
|
strncat(buffer, id_buffer, buffer_size);
|
|
buffer_size -= strlen(buffer);
|
|
/* Append the end of the row. */
|
|
strncat(buffer, append_op, buffer_size);
|
|
buffer_size -= (strlen(append_op) - 1L);
|
|
/* Append the post IDs. */
|
|
for(i = 0L; i < post->referenced_posts_length - 1; i++) {
|
|
/* Copy into a string. */
|
|
sprintf(id_buffer, "%lld", (post->referenced_posts)[i]);
|
|
/* Append. */
|
|
strncat(buffer, id_buffer, buffer_size);
|
|
buffer_size -= (strlen(id_buffer) - 1L);
|
|
/* Append the end of the row. */
|
|
strncat(buffer, append_id, buffer_size);
|
|
buffer_size -= (strlen(append_id) - 1L);
|
|
}
|
|
/* Append the last referenced post. */
|
|
if (post->referenced_posts_length) {
|
|
sprintf(id_buffer, "%lld", (post->referenced_posts)[i+1]);
|
|
strncat(buffer, append_row, buffer_size);
|
|
buffer_size -= (strlen(id_buffer) - 1L);
|
|
/* Append the end of the row. */
|
|
strncat(buffer, append_row, buffer_size);
|
|
buffer_size -= (strlen(append_row) - 1L);
|
|
}
|
|
|
|
/* Append the post's text. */
|
|
strncat(buffer, text, buffer_size);
|
|
|
|
/* Free the id buffer. */
|
|
free(id_buffer);
|
|
|
|
/* Set the text. */
|
|
post_result->length = strlen(buffer);
|
|
post_result->text = buffer;
|
|
|
|
/* Return the resultant post. */
|
|
return post_result;
|
|
}
|
|
|
|
/* This function is used to generate a thread. */
|
|
/* Returns NULL on allocation error, caller should free returned buffer. */
|
|
static inline char *generate_thread_gemtext(thread_t *thread) {
|
|
/* Sanity check for debug mode. */
|
|
assert(thread);
|
|
|
|
/* String constants. */
|
|
const char[6] post_separator = "----\n";
|
|
|
|
/* Generate posts and store them in an array. */
|
|
gemtext_post_t **posts;
|
|
char *buffer;
|
|
size_t length = 1L; /* To account for the terminating null char. */
|
|
size_t i;
|
|
|
|
/* Allocate the post array. */
|
|
posts = (gemtext_post_t**) calloc((thread->replies + 1L), sizeof(gemtext_post_t*));
|
|
/* Check if the allocation failed. */
|
|
if (!posts)
|
|
return NULL;
|
|
|
|
/* Generate and store the OP in the array. */
|
|
posts[0] = generate_post_gemtext(thread->original_post);
|
|
/* Check for failure. */
|
|
if (!posts[0]) {
|
|
free(posts);
|
|
return NULL;
|
|
}
|
|
/* Increase the length. */
|
|
length += ((posts[0])->length + strlen(post_separator) - 2L);
|
|
|
|
/* Generate and store the posts in the array. */
|
|
for (i = 0L; i < thread->replies; i++) {
|
|
posts[i + 1L] = generate_post_gemtext((thread->posts)[i]);
|
|
/* Check for failure. */
|
|
if (!posts[i + 1L]) {
|
|
free_gemtext_post_t_array(posts, i + 1L);
|
|
return NULL;
|
|
}
|
|
/* Increase the length, less by two to remove overlapping null chars. */
|
|
length += ((posts[i])->length + strlen(post_separator) - 2L);
|
|
}
|
|
|
|
/* Allocate the buffer. */
|
|
buffer = (char*) calloc(length, sizeof(char));
|
|
if (!buffer) {
|
|
free_gemtext_post_t_array(posts, i);
|
|
return NULL;
|
|
}
|
|
|
|
/* Loop and append. */
|
|
for (i = 0L; i < thread->replies; i++) {
|
|
/* Append the current post. */
|
|
strncat(buffer, (posts[i])->text, (posts[i])->length);
|
|
length -= (((posts[i])->length) - 1L);
|
|
/* Append the current separator. */
|
|
strncat(buffer, post_separator, strlen(post_separator) - 1L);
|
|
length -= ((i != (thread->replies - 1L)) ? (strlen(post_separator) - 1L) : (strlen(post_separator)));
|
|
}
|
|
|
|
/* Free the posts. */
|
|
free_gemtext_post_array(posts, i);
|
|
|
|
/* Return the buffer. */
|
|
return buffer;
|
|
}
|
|
|
|
/* This function is used to generate a board. */
|
|
static inline char *generate_board_gemtext(board_t *board, size_t max_posts) {
|
|
/* Sanity check. */
|
|
assert(board && max_posts && board->threads);
|
|
|
|
/* Local varibles. */
|
|
char **threads;
|
|
char *buffer;
|
|
size_t i, length, *lengths;
|
|
|
|
/* Allocate an array for the thread text. */
|
|
threads = (char**) calloc(board->threads, sizeof(char*));
|
|
/* Check if the allocation failed. */
|
|
if (!threads)
|
|
return NULL;
|
|
|
|
/* Allocate the lengths array. */
|
|
lengths = (size_t*) calloc(board->threads, sizeof(size_t));
|
|
if (!lengths) {
|
|
free(threads);
|
|
return NULL;
|
|
}
|
|
|
|
/* Generate all the thread previews. */
|
|
for (length = 0L ,i = 0L; i < board->threads; i++) {
|
|
threads[i] = generate_thread_preview_gemtext(board->threads_array[i], max_posts);
|
|
/* Check if generation failed. */
|
|
if (!threads[i]) {
|
|
/* Cleanup. */
|
|
free_strings_array(threads, i);
|
|
free(threads);
|
|
return NULL;
|
|
}
|
|
/* Increment the total length. */
|
|
lengths[i] = strlen(threads[i]);
|
|
length += lengths[i];
|
|
}
|
|
|
|
/* Add extra place for null terminator. */
|
|
length++;
|
|
|
|
/* Attempt to allocate the buffer. */
|
|
buffer = (char*) calloc(length, sizeof(char));
|
|
/* Check for allocation failure. */
|
|
if (!buffer) {
|
|
/* Cleanup. */
|
|
free_strings_array(threads, i);
|
|
free(threads);
|
|
free(lengths);
|
|
return NULL;
|
|
}
|
|
|
|
/* Append the strings. */
|
|
buffer = strcatarr(threads, lengths, length);
|
|
|
|
/* Free all intermediate buffers. */
|
|
free_strings_array(threads, i);
|
|
free(threads);
|
|
free(lengths);
|
|
|
|
/* Return the result. */
|
|
return buffer;
|
|
}
|
|
|
|
/* TODO: handle linking (clicking on a board name should move to open said board). */
|
|
/* This function is used to generate an index. */
|
|
/* Returns NULL on allocation error. */
|
|
/* Caller should free resultant buffer. */
|
|
static inline char *generate_index_gemtext(index_t *index) {
|
|
/* Sanity check. */
|
|
assert(index);
|
|
|
|
/* Format constant for name. */
|
|
const char[5] header = "*%s\n";
|
|
/* Attempt to calculate the total buffer size. */
|
|
char *buffer;
|
|
size_t buffer_length, i;
|
|
board_t *current;
|
|
|
|
/* Start adding up. */
|
|
buffer_length = strlen(header) + strlen(index->index_name) + 1L; /* Extra space for newline. */
|
|
/* Loop and count how much for the boards. */
|
|
for (i = 0L, current = index->board_array[0]; i < index->boards; i++, current++)
|
|
buffer_length += strlen(current->board_name) + 1L; /* Extra space for newline. */
|
|
/* Extra place for null terminator. */
|
|
buffer_length++;
|
|
|
|
/* Attempt to allocate the buffer. */
|
|
buffer = (char*) calloc(buffer_length, sizeof(char));
|
|
if (!buffer)
|
|
return NULL;
|
|
|
|
/* TODO: Check this code, might be wrong. */
|
|
/* Fill the buffer. */
|
|
snprintf(buffer, strlen(header) + strlen(index->index_name) + 1L, header, index->index_name);
|
|
/* Loop and concatenate. */
|
|
for (i = 0L, current = index->board_array[0]; i < index->boards; i++, current++)
|
|
strcat(buffer, current->board_name);
|
|
|
|
/* Return the resultant buffer. */
|
|
return buffer;
|
|
}
|
|
|
|
|
|
/* Helper functions. */
|
|
/* This function frees a gemtext_post_t struct. */
|
|
static inline void free_gemtext_post_t(gemtext_post_t *post) {
|
|
free(post->text);
|
|
free(post);
|
|
}
|
|
|
|
/* This function frees an array of gemtext_post_t structs, up to length-1 (inclusive). */
|
|
static inline void free_gemtext_post_t_array(gemtext_post_t **array, size_t length) {
|
|
/* Loop and free. */
|
|
for (size_t i = 0L; i < length; i++)
|
|
free_gemtext_post_t(array[i]);
|
|
/* Free the array. */
|
|
free(array);
|
|
}
|
|
|
|
/* TODO: handle thread links (clicking on the OP ID should open the thread). */
|
|
/* This function is used to generate a thread's preview, up to max_posts included. */
|
|
/* Returns NULL on allocation error, caller should free returned buffer. */
|
|
static inline char *generate_thread_preview_gemtext(thread_t *thread, size_t max_posts) {
|
|
/* Sanity check. */
|
|
assert(thread && max_posts);
|
|
|
|
/* String constants. */
|
|
const char[6] post_separator = "----\n";
|
|
|
|
/* Generate posts and store them in an array. */
|
|
gemtext_post_t **posts;
|
|
char *buffer;
|
|
size_t length = 1L; /* To account for the terminating null char. */
|
|
size_t i;
|
|
|
|
/* Set the number of posts. */
|
|
if (max_posts > (thread->replies + 1L))
|
|
max_posts = thrad->replies + 1L;
|
|
|
|
/* Allocate the post array. */
|
|
posts = (gemtext_post_t**) calloc(max_posts, sizeof(gemtext_post_t*));
|
|
/* Check if the allocation failed. */
|
|
if (!posts)
|
|
return NULL;
|
|
|
|
/* Generate and store the OP in the array. */
|
|
posts[0] = generate_post_gemtext(thread->original_post);
|
|
/* Check for failure. */
|
|
if (!posts[0]) {
|
|
free(posts);
|
|
return NULL;
|
|
}
|
|
/* Increase the length. */
|
|
length += ((posts[0])->length + strlen(post_separator) - 2L);
|
|
|
|
/* Generate and store the posts in the array. */
|
|
for (i = 0L; i < (max_posts - 1L); i++) {
|
|
posts[i + 1L] = generate_post_gemtext((thread->posts)[i]);
|
|
/* Check for failure. */
|
|
if (!posts[i + 1L]) {
|
|
free_gemtext_post_t_array(posts, i + 1L);
|
|
return NULL;
|
|
}
|
|
/* Increase the length, less by two to remove overlapping null chars. */
|
|
length += ((posts[i])->length + strlen(post_separator) - 2L);
|
|
}
|
|
|
|
/* Allocate the buffer. */
|
|
buffer = (char*) calloc(length, sizeof(char));
|
|
if (!buffer) {
|
|
free_gemtext_post_t_array(posts, i);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Free the posts. */
|
|
free_gemtext_post_array(posts, i);
|
|
|
|
/* Return the buffer. */
|
|
return buffer;
|
|
}
|
|
|
|
/* This function frees strings in an array. */
|
|
static inline void free_strings_array(char **array, size_t length) {
|
|
/* Loop and free. */
|
|
for (size_t i = 0L; i < length; i++)
|
|
free(array[i]);
|
|
}
|