From 4ad218b5710659899ba842da5a597db5052f2d4d Mon Sep 17 00:00:00 2001 From: "Rafael G. Martins" Date: Sun, 12 Feb 2017 23:44:52 +0100 Subject: blogc-make: implemented automatic reloader for runserver rule this is not the best implementation possible, but it works for most use cases --- src/blogc-make/ctx.c | 110 +++++++++++++++++++++++++++++++++++++++++++++++-- src/blogc-make/ctx.h | 6 ++- src/blogc-make/main.c | 2 +- src/blogc-make/rules.c | 68 +++++++++++++++++++++++++++++- 4 files changed, 180 insertions(+), 6 deletions(-) diff --git a/src/blogc-make/ctx.c b/src/blogc-make/ctx.c index 39f1bf4..2dd4f97 100644 --- a/src/blogc-make/ctx.c +++ b/src/blogc-make/ctx.c @@ -50,6 +50,49 @@ bm_filectx_new(bm_ctx_t *ctx, const char *filename) } +bool +bm_filectx_changed(bm_filectx_t *ctx, struct timespec *ts) +{ + if (ctx == NULL) + return false; + + struct stat buf; + + if (0 == stat(ctx->path, &buf)) { + if (buf.st_mtim.tv_sec == ctx->timestamp.tv_sec) { + if (buf.st_mtim.tv_nsec > ctx->timestamp.tv_nsec) { + if (ts != NULL) + *ts = buf.st_mtim; + return true; + } + } + else if (buf.st_mtim.tv_sec > ctx->timestamp.tv_sec) { + if (ts != NULL) + *ts = buf.st_mtim; + return true; + } + } + + return false; +} + + +void +bm_filectx_reload(bm_filectx_t *ctx) +{ + if (ctx == NULL) + return; + + struct timespec ts; + + if (!bm_filectx_changed(ctx, &ts)) + return; + + ctx->timestamp = ts; + ctx->readable = true; +} + + void bm_filectx_free(bm_filectx_t *fctx) { @@ -62,7 +105,7 @@ bm_filectx_free(bm_filectx_t *fctx) bm_ctx_t* -bm_ctx_new(const char *settings_file, bc_error_t **err) +bm_ctx_new(bm_ctx_t *base, const char *settings_file, bc_error_t **err) { if (settings_file == NULL || err == NULL || *err != NULL) return NULL; @@ -92,7 +135,14 @@ bm_ctx_new(const char *settings_file, bc_error_t **err) return NULL; } - bm_ctx_t *rv = bc_malloc(sizeof(bm_ctx_t)); + bm_ctx_t *rv = NULL; + if (base == NULL) { + rv = bc_malloc(sizeof(bm_ctx_t)); + } + else { + bm_ctx_free_internal(base); + rv = base; + } rv->settings = settings; char *real_filename = realpath(settings_file, NULL); @@ -108,6 +158,8 @@ bm_ctx_new(const char *settings_file, bc_error_t **err) rv->output_dir = bc_strdup_printf("%s/%s", rv->root_dir, output_dir); } + // can't return null and set error after this! + const char *template_dir = bc_trie_lookup(settings->settings, "template_dir"); @@ -158,25 +210,77 @@ bm_ctx_new(const char *settings_file, bc_error_t **err) void -bm_ctx_free(bm_ctx_t *ctx) +bm_ctx_reload(bm_ctx_t *ctx) +{ + if (ctx == NULL || ctx->settings_fctx == NULL) + return; + + if (bm_filectx_changed(ctx->settings_fctx, NULL)) { + // reload everything! we could just reload settings_fctx, as this + // would force rebuilding everything, but we need to know new/deleted + // files + + // needs to dup path, because it may be freed when reloading. + char *tmp = bc_strdup(ctx->settings_fctx->path); + bc_error_t *err = NULL; + ctx = bm_ctx_new(ctx, tmp, &err); + free(tmp); + if (err != NULL) { // failed to reload, keep old ctx + bc_error_print(err, "blogc-make"); + bc_error_free(err); + } + return; + } + + bm_filectx_reload(ctx->main_template_fctx); + bm_filectx_reload(ctx->atom_template_fctx); + + for (bc_slist_t *tmp = ctx->posts_fctx; tmp != NULL; tmp = tmp->next) + bm_filectx_reload((bm_filectx_t*) tmp->data); + + for (bc_slist_t *tmp = ctx->pages_fctx; tmp != NULL; tmp = tmp->next) + bm_filectx_reload((bm_filectx_t*) tmp->data); + + for (bc_slist_t *tmp = ctx->copy_files_fctx; tmp != NULL; tmp = tmp->next) + bm_filectx_reload((bm_filectx_t*) tmp->data); +} + + +void +bm_ctx_free_internal(bm_ctx_t *ctx) { if (ctx == NULL) return; bm_settings_free(ctx->settings); + ctx->settings = NULL; free(ctx->root_dir); + ctx->root_dir = NULL; free(ctx->output_dir); + ctx->output_dir = NULL; bm_atom_destroy(ctx->atom_template_fctx->path); bm_filectx_free(ctx->main_template_fctx); + ctx->main_template_fctx = NULL; bm_filectx_free(ctx->atom_template_fctx); + ctx->atom_template_fctx = NULL; bm_filectx_free(ctx->settings_fctx); + ctx->settings_fctx = NULL; bc_slist_free_full(ctx->posts_fctx, (bc_free_func_t) bm_filectx_free); + ctx->posts_fctx = NULL; bc_slist_free_full(ctx->pages_fctx, (bc_free_func_t) bm_filectx_free); + ctx->pages_fctx = NULL; bc_slist_free_full(ctx->copy_files_fctx, (bc_free_func_t) bm_filectx_free); + ctx->copy_files_fctx = NULL; +} + +void +bm_ctx_free(bm_ctx_t *ctx) +{ + bm_ctx_free_internal(ctx); free(ctx); } diff --git a/src/blogc-make/ctx.h b/src/blogc-make/ctx.h index 05e85fc..7b68965 100644 --- a/src/blogc-make/ctx.h +++ b/src/blogc-make/ctx.h @@ -38,8 +38,12 @@ typedef struct { } bm_ctx_t; bm_filectx_t* bm_filectx_new(bm_ctx_t *ctx, const char *filename); +bool bm_filectx_changed(bm_filectx_t *ctx, struct timespec *ts); +void bm_filectx_reload(bm_filectx_t *ctx); void bm_filectx_free(bm_filectx_t *fctx); -bm_ctx_t* bm_ctx_new(const char *filename, bc_error_t **err); +bm_ctx_t* bm_ctx_new(bm_ctx_t *base, const char *settings_file, bc_error_t **err); +void bm_ctx_reload(bm_ctx_t *ctx); +void bm_ctx_free_internal(bm_ctx_t *ctx); void bm_ctx_free(bm_ctx_t *ctx); #endif /* _MAKE_CTX_H */ diff --git a/src/blogc-make/main.c b/src/blogc-make/main.c index 1e43580..98d4cc7 100644 --- a/src/blogc-make/main.c +++ b/src/blogc-make/main.c @@ -104,7 +104,7 @@ main(int argc, char **argv) rules = bc_slist_append(rules, bc_strdup("all")); } - ctx = bm_ctx_new(blogcfile ? blogcfile : "blogcfile", &err); + ctx = bm_ctx_new(NULL, blogcfile ? blogcfile : "blogcfile", &err); if (err != NULL) { bc_error_print(err, "blogc-make"); rv = 3; diff --git a/src/blogc-make/rules.c b/src/blogc-make/rules.c index 711809a..1f18d5e 100644 --- a/src/blogc-make/rules.c +++ b/src/blogc-make/rules.c @@ -12,6 +12,9 @@ #include #include #include +#include +#include +#include #include "../common/utils.h" #include "ctx.h" #include "exec.h" @@ -552,14 +555,77 @@ static int all_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bool verbose); // RUNSERVER RULE + +typedef struct { + bm_ctx_t *ctx; + bool verbose; +} runserver_args_t; + +static runserver_args_t *args = NULL; + +static void +runserver_sig_handler(int sig) +{ + free(args); + args = NULL; +} + +static void* +runserver_thread(void *arg) +{ + signal(SIGINT, runserver_sig_handler); + signal(SIGTERM, runserver_sig_handler); + args = arg; + while (true) { + bm_ctx_reload(args->ctx); + if (args->ctx == NULL) { + fprintf(stderr, "blogc-make: error: failed to reload context. " + "reloader disabled!\n"); + goto runserver_cleanup; + } + if (0 != all_exec(args->ctx, NULL, args->verbose)) { + fprintf(stderr, "blogc-make: error: failed to rebuild website. " + "reloader disabled!\n"); + goto runserver_cleanup; + } + sleep(1); + } + +runserver_cleanup: + free(args); + args = NULL; + + return NULL; +} + static int runserver_exec(bm_ctx_t *ctx, bc_slist_t *outputs, bool verbose) { + // first 'all' call is syncronous, to do a 'sanity check' int rv = all_exec(ctx, NULL, verbose); if (rv != 0) return rv; - return bm_exec_blogc_runserver(ctx->settings, verbose); + runserver_args_t *args = bc_malloc(sizeof(runserver_args_t)); + args->ctx = ctx; + args->verbose = verbose; + + pthread_t thread; + + if (0 != pthread_create(&thread, NULL, runserver_thread, args)) { + fprintf(stderr, "blogc-make: error: failed to create blogc-runserver " + "thread!\n"); + return 3; + } + + if (0 != pthread_detach(thread)) { + fprintf(stderr, "blogc-make: error: failed to detach blogc-runserver " + "thread!\n"); + return 3; + } + + bm_exec_blogc_runserver(ctx->settings, verbose); + return 0; } -- cgit v1.2.3-18-g5258