shayla

a simple yet fast static site builder
Log | Files | Refs | Submodules | README | LICENSE | git clone https://git.ne02ptzero.me/git/shayla

commit 02d774e51479bd0f20a37b27f4ec1fd5de7fb8a9
parent 7816c7870e3bb1674b4460273c0c1697107140ce
Author: Louis Solofrizzo <lsolofrizzo@online.net>
Date:   Tue, 25 Sep 2018 21:46:06 +0200

Parser: Now (concurrently) parsing header of posts file

This commit introduces full reading and header parsing from posts files.
In order to do that, so changes were made to the core code. Mainly, a
new API to execute asynchronous code in pool.c. I've also added a
--debug flag to the binary, and some fixes here and there to the log
functions.

Signed-off-by: Louis Solofrizzo <lsolofrizzo@online.net>

Diffstat:
A.gitmodules | 3+++
MCMakeLists.txt | 25++++++++++++-------------
Mdir.c | 12+++++++++---
Afile.c | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Mfile.h | 34++++++++++++++++++++++++++++++++--
Aheader.c | 163+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aheader.h | 31+++++++++++++++++++++++++++++++
Mlist.h | 1+
Mlog.c | 34++++++++++++++++++++++------------
Mlog.h | 10++++++----
Mmain.c | 61++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Apool.c | 165+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apool.h | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aposts.c | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Aposts.h | 24++++++++++++++++++++++++
Mshayla.h | 6++++++
Asundown | 1+
17 files changed, 728 insertions(+), 39 deletions(-)

diff --git a/.gitmodules b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "sundown"] + path = sundown + url = https://github.com/vmg/sundown.git diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -16,7 +16,6 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-protector") # Compile / Run time sta set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pipe") # Avoid temporary files, speeding up builds set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FORTIFY_SOURCE=2") # Run-time buffer overflow detection set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=format-security") # Reject potentially unsafe format string arguents -set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wshadow") # Don't shadow variables set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wunreachable-code") # Warn if the compiler detects that code will never be executed set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wswitch-enum") # Force a switch on an enum to have all the cases possible @@ -39,19 +38,19 @@ link_libraries(pthread) add_executable(${SDOW_NAME} main.c args.c - #file.c - #parser.c - #parse_header.c + file.c + posts.c + header.c log.c list.c dir.c -# site.c - #./sundown/src/autolink.c - #./sundown/src/buffer.c - #./sundown/src/markdown.c - #./sundown/html/html.c - #./sundown/html/html_smartypants.c - #./sundown/html/houdini_href_e.c - #./sundown/html/houdini_html_e.c - #./sundown/src/stack.c + pool.c + ./sundown/src/autolink.c + ./sundown/src/buffer.c + ./sundown/src/markdown.c + ./sundown/html/html.c + ./sundown/html/html_smartypants.c + ./sundown/html/houdini_href_e.c + ./sundown/html/houdini_html_e.c + ./sundown/src/stack.c ) diff --git a/dir.c b/dir.c @@ -36,7 +36,7 @@ static bool add_dir(list_head_t *head, const char *path) path = realpath(path, real_path); if (path == NULL) - return true; + return false; STATIC_ARRAY_FOREACH(const char **dir, reserved_directories) { @@ -82,7 +82,10 @@ static bool add_file(list_head_t *files, const char *path, const char *ext, size list_add_tail(&tmp->list, files); (*file_count)++; - PROGRESS_UPDATE("Listing %zu files...", *file_count); + if (*file_count > 1) + PROGRESS_UPDATE("Listing %zu files...", *file_count); + else + PROGRESS_UPDATE("Listing %zu file...", *file_count); return true; } @@ -191,10 +194,13 @@ bool list_dir(list_head_t *head, const char *dir_name, const char *ext) INIT_LIST_HEAD(&dir); if (!add_dir(&dir, dir_name)) + { + ERROR("Cannot open %s: %s\n", dir_name, strerror(errno)); return false; + } file_count = 0; - PROGRESS_BEGIN("Listing %zu files...", file_count, dir_name); + PROGRESS_BEGIN(); list_for_each_entry(tmp, &dir, list) { diff --git a/file.c b/file.c @@ -0,0 +1,50 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#include <file.h> +#include <sys/stat.h> +#include <errno.h> +#include <header.h> + +bool file_read(file_t *file) +{ + FILE *fd = fopen(file->path, "r"); + struct stat st; + + if (fd == NULL) + return false; + + fstat(fd->_fileno, &st); + file->ts = st.st_mtime; + + file->ib = bufnew(st.st_size); + bufgrow(file->ib, st.st_size); + + fread(file->ib->data, 1, file->ib->asize, fd); + fclose(fd); + + return true; +} + +bool file_parse(file_t *ptr) +{ + if (!file_read(ptr)) + return false; + + if (!file_parse_header(ptr)) + return false; + + return true; +} diff --git a/file.h b/file.h @@ -15,6 +15,7 @@ #ifndef FILE_H # define FILE_H +# define _XOPEN_SOURCE 1600 # include <list.h> # include <log.h> @@ -22,6 +23,7 @@ # include <stdlib.h> # include <string.h> # include <limits.h> +# include <markdown.h> typedef struct { char path[PATH_MAX]; /*!< Path of the file */ @@ -32,6 +34,10 @@ typedef struct { list_head_t list; /*!< List member */ time_t ts; /*!< Last update time of the file */ + + struct buf *ib; /*!< File buffer */ + struct buf *ob; /*!< Output buffer */ + size_t header_end_pos; /*!< Header end position in the file */ } file_t; /*! @@ -41,7 +47,11 @@ typedef struct { */ static inline void file_dtr(file_t *ptr) { - UNUSED_ARG(ptr); + free(ptr->route); + free(ptr->title); + free(ptr->summary); + + bufrelease(ptr->ib); } /*! @@ -54,7 +64,7 @@ static inline void file_dtr(file_t *ptr) */ static inline bool file_init(file_t *ptr, const char *path) { - if (strlen(path) > sizeof(ptr->path)) + if (strlen(path) >= sizeof(ptr->path)) { DEBUG("The size of the path is greater than " STR(sizeof(ptr->path)) \ ", cannot continue.\n"); @@ -62,6 +72,8 @@ static inline bool file_init(file_t *ptr, const char *path) } strcpy(ptr->path, path); + ptr->is_listed = true; + return true; } @@ -136,4 +148,22 @@ static inline bool file_exist(const char *str) return result; } +/*! + * \brief Parse an entire file + * + * \param[in,out] ptr File to parse + * + * \return true on success, false on failure + */ +bool file_parse(file_t *ptr); + +/*! + * \brief Read an entire file + * + * \param[in,out] ptr File to read + * + * \return true on success + */ +bool file_read(file_t *file); + #endif /* FILE_H */ diff --git a/header.c b/header.c @@ -0,0 +1,163 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#define _DEFAULT_SOURCE +#include <header.h> + +#define HEADER_DELIM "---" +#define IS_HEADER_DELIM_LINE(str) (strncmp(HEADER_DELIM, str, sizeof(HEADER_DELIM) - 1) == 0) +#define HEADER_LINE_DELIM ':' + +typedef struct { + const char *name; + bool (*fn)(file_t *, char *); +} header_options_t; + +static bool header_set_title(file_t *file, char *val) +{ + return (file->title = strdup(val)) != NULL; +} + +static bool header_set_summary(file_t *file, char *val) +{ + return (file->summary = strdup(val)) != NULL; +} + +static bool header_set_route(file_t *file, char *val) +{ + return (file->route = strdup(val)) != NULL; +} + +static bool header_set_date(file_t *file, char *val) +{ + struct tm tm; + time_t ts; + + if (strptime(val, "%Y-%m-%d", &tm) == NULL) + { + PROGRESS_WARNING("'%s' is an incorrect date", val); + return false; + } + + ts = mktime(&tm); + file->ts = ts; + + return true; +} + +static bool header_set_list(file_t *file, char *val) +{ + if (strcmp(val, "true") == 0) + { + file->is_listed = true; + return true; + } + + if (strcmp(val, "false") == 0) + { + file->is_listed = false; + return true; + } + + PROGRESS_WARNING("'%s' is not a boolean value (true/false)", val); + return false; +} + +static const header_options_t options[] = { + { + .name = "title", + .fn = header_set_title + }, + { + .name = "summary", + .fn = header_set_summary, + }, + { + .name = "route", + .fn = header_set_route + }, + { + .name = "date", + .fn = header_set_date + }, + { + .name = "list", + .fn = header_set_list + } +}; + +static bool file_parse_header_line(file_t *file, char *line) +{ + size_t i; + char *value; + + for (i = 0; line[i] != '\0' && line[i] != HEADER_LINE_DELIM; i++) + ; + + if (line[i] == '\0') + return false; + + line[i] = '\0'; + value = line + i + 1; + + while (*value != '\0' && *value == ' ') + value++; + + if (value == NULL) + return false; + + STATIC_ARRAY_FOREACH(const header_options_t *opt, options) + { + if (strcmp(line, opt->name) == 0) + { + return opt->fn(file, value); + } + } + + PROGRESS_WARNING("Unknown token '%s' in file %s", line, file->path); + return false; +} + +bool file_parse_header(file_t *file) +{ + char *str; + char *data = (char *)file->ib->data; + char *line; + + if (file->ib->asize < 3) + return false; + + if (!IS_HEADER_DELIM_LINE(data)) + return false; + + str = strstr(data + sizeof(HEADER_DELIM), HEADER_DELIM); + if (str == NULL || str == data) + return false; + + file->header_end_pos = str - data; + data += sizeof(HEADER_DELIM); + + while ((line = strsep(&data, "\n")) != NULL) + { + if (IS_HEADER_DELIM_LINE(line)) + break ; + + if (!file_parse_header_line(file, line)) + return false; + + } + + return true; +} diff --git a/header.h b/header.h @@ -0,0 +1,31 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef POSTS_H +# define POSTS_H + +# include <file.h> +# include <stdbool.h> + +/*! + * \brief Parse the header of a file + * + * \param[in,out] ptr File to parse + * + * \return true on success, false on failure + */ +bool file_parse_header(file_t *ptr); + +#endif /* POSTS_H */ diff --git a/list.h b/list.h @@ -70,6 +70,7 @@ static inline void list_add_tail(list_head_t *new, list_head_t *head) static inline void __list_del(list_head_t *prev, list_head_t *next) { next->prev = prev; + prev->next = next; } static inline void list_del(list_head_t *entry) diff --git a/log.c b/log.c @@ -15,6 +15,8 @@ #include <log.h> #include <pthread.h> +#include <strings.h> +#include <assert.h> static bool g_debug = false; static pthread_t thread_progress = { 0 }; @@ -22,6 +24,8 @@ static pthread_mutex_t msg_lock = { 0 }; static char *spinners[] = { "⠋", "⠙","⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" }; static int spinner_count = 0; static char msg[2048] = EMPTY_STR(); +static const char *prefix = NULL; +#define PREFIX prefix == NULL ? "" : prefix static bool run = false; void set_debug(bool val) @@ -34,6 +38,11 @@ bool get_debug(void) return g_debug; } +void set_prefix(const char *str) +{ + prefix = str; +} + static void *progress_function(void *unused) { UNUSED_ARG(unused); @@ -41,8 +50,8 @@ static void *progress_function(void *unused) for (size_t i = 0; run; i++) { pthread_mutex_lock(&msg_lock); - fprintf(stdout, "\r" COLOR_INFO "%s" COLOR_RESET " %s", - spinners[spinner_count], msg); + fprintf(stdout, "\r" COLOR_INFO "%s" COLOR_RESET "%s %s", + spinners[spinner_count], PREFIX, msg); pthread_mutex_unlock(&msg_lock); if (i % 80 == 0 && i != 0) @@ -65,16 +74,10 @@ static void update_msg(const char *str, va_list *ap) pthread_mutex_unlock(&msg_lock); } -void progress_begin(const char *str, ...) +void progress_begin(void) { - va_list ap; - pthread_mutex_init(&msg_lock, NULL); - va_start(ap, str); - update_msg(str, &ap); - va_end(ap); - run = true; pthread_create(&thread_progress, NULL, progress_function, NULL); } @@ -104,14 +107,21 @@ void progress_warning(const char *str, ...) void progress_end(bool result) { + assert(run == true); run = false; pthread_join(thread_progress, NULL); - if (result) - fprintf(stdout, "\r" SYMBOL_INFO " %s\n", msg); + if (msg[0] != '\0') + { + if (result) + fprintf(stdout, "\r" SYMBOL_INFO "%s %s\n", PREFIX, msg); + else + fprintf(stderr, "\r" SYMBOL_ERROR "%s %s\n", PREFIX, msg); + } else - fprintf(stderr, "\r" SYMBOL_ERROR " %s\n", msg); + fprintf(stdout, "\r"); pthread_mutex_destroy(&msg_lock); spinner_count = 0; + bzero(msg, sizeof(msg)); } diff --git a/log.h b/log.h @@ -40,6 +40,7 @@ # define WARNING(str, ...) fprintf(stdout, SYMBOL_WARNING " " str, ##__VA_ARGS__) # define ERROR(str, ...) fprintf(stderr, SYMBOL_ERROR " " str, ##__VA_ARGS__) # define FATAL(str, ...) fprintf(stderr, SYMBOL_FATAL " " str, ##__VA_ARGS__) +# define OK(str, ...) fprintf(stdout, SYMBOL_OK " " str, ##__VA_ARGS__); # define DEBUG(str, ...) \ do { \ if (GET_DEBUG()) \ @@ -62,15 +63,16 @@ void set_debug(bool val); # define GET_DEBUG get_debug bool get_debug(void); +#define SET_PREFIX(str) set_prefix(" " str) +void set_prefix(const char *str); + /*! * \brief Begin a spinner progress thread * - * \param[in] str Printf format-like string - * * \note A new thread will be created */ -# define PROGRESS_BEGIN(str, ...) progress_begin(str, ##__VA_ARGS__) -void progress_begin(const char *str, ...); +# define PROGRESS_BEGIN() progress_begin() +void progress_begin(void); /*! * \brief Update the progress message diff --git a/main.c b/main.c @@ -17,18 +17,24 @@ #include <shayla.h> #include <dir.h> #include <args.h> +#include <posts.h> #include <inttypes.h> +#include <pool.h> +#include <errno.h> enum { OPT_VERSION = 0, OPT_TITLE, OPT_SRC, + OPT_STYLE, OPT_LAYOUTS, OPT_DEST, OPT_ROOT, OPT_FAVICON, OPT_URL, - OPT_IMG + OPT_IMG, + OPT_THREADS, + OPT_DEBUG }; static opts_t options[] = { @@ -46,6 +52,11 @@ static opts_t options[] = { SINGLE_OPT('s'), ARG_TYPE(OPT_TYPE_STRING) }, + [OPT_STYLE] = { + LONG_OPT("style"), + SINGLE_OPT('c'), + ARG_TYPE(OPT_TYPE_STRING) + }, [OPT_LAYOUTS] = { LONG_OPT("layouts"), SINGLE_OPT('l'), @@ -75,13 +86,21 @@ static opts_t options[] = { LONG_OPT("img"), SINGLE_OPT('i'), ARG_TYPE(OPT_TYPE_STRING) + }, + [OPT_THREADS] = { + LONG_OPT("threads"), + SINGLE_OPT('t'), + ARG_TYPE(OPT_TYPE_INT) + }, + [OPT_DEBUG] = { + LONG_OPT("debug") } }; #define usage(str, ...) fprintf(stdout, str "\n", ##__VA_ARGS__) static void help(void) { - usage("Usage: " SDOWN_NAME " -[vhtsldrfui]"); + usage("Usage: " SDOWN_NAME " -[vhtsldrfuit]"); usage("Generate an HTML static site for markdown sources."); usage ("If used with no options, " SDOWN_NAME " will look for directory in the current path."); usage(""); @@ -90,12 +109,15 @@ static void help(void) usage(" -h, --help Show this message"); usage(" -t, --title=TITLE Title to be used in the final site"); usage(" -s, --src=DIR Markdown sources directory"); + usage(" -c, --style=DIR Style sources directory"); usage(" -l, --layouts=DIR Layouts directory"); usage(" -d, --dest=DIR Destination directory"); usage(" -r, --root=ROOT Root URL of the website"); usage(" -f, --favicon=FILE Favicon to use"); usage(" -u, --url=URL Url of the website"); usage(" -i, --img=DIR Images directory"); + usage(" -t, --threads=NUM Number of threads to launch"); + usage(" --debug Print more information"); usage(""); usage("Default required tree:"); usage(" ."); @@ -151,8 +173,8 @@ static void version(void) #undef usage typedef struct { - size_t off; - const char *def; + size_t off; /*!< Structure variable offset */ + const char *def; /*!< Default value */ } shayla_opt_t; static void shayla_options(shayla_t *ctx) @@ -161,6 +183,7 @@ static void shayla_options(shayla_t *ctx) #define shayla_offset(member) offsetof(shayla_t, member) [OPT_TITLE] = { shayla_offset(title), SDOWN_NAME }, [OPT_SRC] = { shayla_offset(posts_dir), "markdown" }, + [OPT_STYLE] = { shayla_offset(styles_dir), "styles" }, [OPT_LAYOUTS] = { shayla_offset(layouts_dir), "layouts" }, [OPT_DEST] = { shayla_offset(dest), "build" }, [OPT_ROOT] = { shayla_offset(root), "/"}, @@ -186,6 +209,15 @@ static void shayla_options(shayla_t *ctx) else if (opt->def != NULL) *str = strdup(opt->def); } + + if (IS_ARG_HERE(options, OPT_DEBUG)) + set_debug(true); + + /* Static non-string options */ + if (IS_ARG_HERE(options, OPT_THREADS)) + pool_init(GET_INT_ARG(options, OPT_THREADS)); + else + pool_init(DEFAULT_THREADS); } int main(int ac, const char **av) @@ -207,11 +239,30 @@ int main(int ac, const char **av) shayla_options(&ctx); - list_dir(&ctx.posts, ctx.posts_dir, ".md"); + SET_PREFIX("Posts:"); + if (!list_dir(&ctx.posts, ctx.posts_dir, ".md")) + goto end; + + if (!posts_parse_all(&ctx.posts)) + goto end; + + SET_PREFIX("Layouts:"); + if (!list_dir(&ctx.layouts, ctx.layouts_dir, ".html")) + goto end; + + SET_PREFIX("Styles:"); + if (!list_dir(&ctx.styles, ctx.styles_dir, "css")) + goto end; + + SET_PREFIX("Images:"); + if (!list_dir(&ctx.img, ctx.img_dir, NULL)) + goto end; ret = 0; end: free_args(options, COUNT_OF(options)); + pool_cleanup(); shayla_dtr(&ctx); + OK("All good, ret = %d\n", ret); return ret; } diff --git a/pool.c b/pool.c @@ -0,0 +1,165 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#include <pool.h> +#include <log.h> + +#include <pthread.h> +#include <assert.h> +#include <list.h> +#include <stdlib.h> +#include <string.h> + +typedef struct { + job_callback_t *fn; + void *data; + struct list_head node; + bool do_free; +} pool_job_t; + +static pthread_mutex_t pool_lock = { 0 }; +static pthread_t *pool_threads = NULL; +static uint8_t pool_threads_size = 0; +static bool pool_do_quit = false; +static list_head_t pool_jobs = { 0 }; + +static void *pool_job_worker(void *unused) +{ + pool_job_t *job = NULL; + + UNUSED_ARG(unused); + while (true) + { + pthread_mutex_lock(&pool_lock); + + /* We got a job to do! */ + if (!list_empty(&pool_jobs)) + { + /* Take ownership of the job */ + job = list_first_entry(&pool_jobs, pool_job_t, node); + list_del(&job->node); + + /* Give back the lock before treatment */ + pthread_mutex_unlock(&pool_lock); + + /* Treat the job and cleanup everything */ + job->fn(job->data); + + if (job->do_free) + free(job->data); + free(job); + } + else + { + /* Nothing to do, let's give back the lock and let the CPU sleep */ + pthread_mutex_unlock(&pool_lock); + if (!pool_do_quit) + return NULL; + + usleep(500); + } + } + + return NULL; +} + +bool pool_init(uint8_t threads) +{ + assert(threads >= 1); + + pool_threads = calloc(threads, sizeof(*pool_threads)); + if (pool_threads == NULL) + return false; + + pool_threads_size = threads; + INIT_LIST_HEAD(&pool_jobs); + + pthread_mutex_init(&pool_lock, NULL); + return true; +} + +void pool_run(void) +{ + pool_do_quit = true; + for (uint8_t i = 0; i < pool_threads_size; i++) + { + pthread_create(&pool_threads[i], NULL, &pool_job_worker, NULL); + } + + DEBUG("Created a pool of %d threads\n", pool_threads_size); +} + +void pool_cleanup(void) +{ + pthread_mutex_destroy(&pool_lock); + free(pool_threads); + pool_threads_size = 0; +} + +static bool pool_add_job_to_list(job_callback_t *fn, void *data, size_t data_size, bool do_copy) +{ + pool_job_t *ptr; + + assert(fn != NULL); + + ptr = calloc(1, sizeof(*ptr)); + if (ptr == NULL) + return false; + + if (do_copy) + { + ptr->data = calloc(1, data_size); + if (ptr->data == NULL) + { + free(ptr); + return false; + } + + memcpy(ptr->data, data, data_size); + ptr->do_free = true; + } + else + { + ptr->data = data; + } + + ptr->fn = fn; + + pthread_mutex_lock(&pool_lock); + list_add_tail(&(ptr->node), &pool_jobs); + pthread_mutex_unlock(&pool_lock); + + return true; +} + +bool pool_add_job(job_callback_t *fn, void *data, size_t data_size) +{ + return pool_add_job_to_list(fn, data, data_size, true); +} + +bool pool_add_job_no_copy(job_callback_t *fn, void *data) +{ + return pool_add_job_to_list(fn, data, 0, false); +} + +void pool_wait(void) +{ + pool_do_quit = false; + + for (uint8_t i = 0; i < pool_threads_size; i++) + { + pthread_join(pool_threads[i], NULL); + } +} diff --git a/pool.h b/pool.h @@ -0,0 +1,95 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef POOL_H +# define POOL_H + +# include <stdbool.h> +# include <stdint.h> +# include <stddef.h> + +typedef void (job_callback_t)(void *data); + +/*! + * \brief Init the thread pool + * + * \param[in] threads Number of threads to create + * + * \return true on sucess, false on failure + */ +bool pool_init(uint8_t threads); + +/*! + * \brief Cleanup the thread pool + */ +void pool_cleanup(void); + +/*! + * \brief Run the jobs + */ +void pool_run(void); + +/*! + * \brief Add a job to the thread pool + * + * \param[in] fn Callback of the job + * \param[in] data Private pointer + * \param[in] data_size Private pointer size + * + * \sa pool_add_job + * \note Data is copied of data_size bytes + * + * \return true on success, false on failure + */ +bool pool_add_job(job_callback_t *fn, void *data, size_t data_size); + +/*! + * \brief Similar to pool_add_job, but with no copy of the pointer + */ +bool pool_add_job_no_copy(job_callback_t *fn, void *data); + +/*! + * \brief Wrapper to pool_add_job + * + * This macro is there to ease the use of private structure as private data. + * It is intended to be use the following way: + * + * typedef struct { + * int value; + * char *ptr; + * } job_data_t; + * + * void job_callback(void *data) + * { + * job_data_t *ptr = data; + * [...] + * } + * + * [...] + * + * pool_add_job_struct(&job_callback, job_data_t, .value = 10, .ptr = some_pointer); + * + * \sa __pool_add_job + */ +#define pool_add_job_struct(fn, type, ...) \ + pool_add_job(fn, &(type){__VA_ARGS__}, sizeof(type)) + +/*! + * \brief Wait for all jobs to be done + * \note This function will BLOCK until all jobs are done + */ +void pool_wait(void); + +#endif /* POOL_H */ diff --git a/posts.c b/posts.c @@ -0,0 +1,52 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#include <posts.h> +#include <file.h> +#include <pool.h> + +static bool parse_error = false; + +static void post_parse_callback(void *data) +{ + file_t *file = data; + + PROGRESS_UPDATE("Parsing %s...", file->path); + + if (!file_parse(file)) + { + PROGRESS_WARNING("Could not parse file %s", file->path); + parse_error = true; + } +} + +bool posts_parse_all(list_head_t *head) +{ + file_t *ptr; + + SET_PREFIX("Posts:"); + PROGRESS_BEGIN(); + + pool_run(); + list_for_each_entry(ptr, head, list) + { + pool_add_job_no_copy(&post_parse_callback, ptr); + } + + pool_wait(); + PROGRESS_DONE(); + + return !parse_error; +} diff --git a/posts.h b/posts.h @@ -0,0 +1,24 @@ +/** + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +#ifndef POSTS_H +# define POSTS_H + +# include <stdbool.h> +# include <list.h> + +bool posts_parse_all(list_head_t *head); + +#endif /* POSTS_H */ diff --git a/shayla.h b/shayla.h @@ -19,6 +19,8 @@ # include <list.h> # include <file.h> +# define DEFAULT_THREADS 8 + typedef struct { list_head_t posts; /*!< List of posts (file_t) */ list_head_t styles; /*!< List of styles (file_t) */ @@ -53,7 +55,11 @@ static inline void shayla_init(shayla_t *ptr) static inline void shayla_dtr(shayla_t *ptr) { file_list_free(&ptr->posts); + file_list_free(&ptr->styles); + file_list_free(&ptr->layouts); + file_list_free(&ptr->img); free((char *)ptr->posts_dir); + free((char *)ptr->styles_dir); free((char *)ptr->layouts_dir); free((char *)ptr->img_dir); free((char *)ptr->title); diff --git a/sundown b/sundown @@ -0,0 +1 @@ +Subproject commit 37728fb2d7137ff7c37d0a474cb827a8d6d846d8