1
1
Fork 0
lemon_board/frontend/gemtext/gemtext_generator.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]);
}