diff --git a/.gitmodules b/.gitmodules index 45c4fa0..79d85de 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,12 @@ [submodule "incbin"] path = incbin url = https://github.com/graphitemaster/incbin.git +[submodule "build-id"] + path = build-id + url = https://github.com/mattst88/build-id.git +[submodule "cJSON"] + path = cJSON + url = https://github.com/DaveGamble/cJSON.git +[submodule "md5-c"] + path = md5-c + url = https://github.com/Zunawe/md5-c.git diff --git a/baked.c b/baked.c index b902c12..4badb0b 100644 --- a/baked.c +++ b/baked.c @@ -13,6 +13,7 @@ INCBIN(page_missing_t, "./tmpls/page-missing.t"); INCBIN(simple_min_css, "./etc/simple.min.css"); INCBIN(favicon_ico, "./etc/favicon.ico"); +INCBIN(hotreload_js, "./etc/hotreload.js"); Baked_Resource *baked_resources = NULL; @@ -34,6 +35,7 @@ void init_baked_resources(void) add_baked_resource("gpp1", gpp1_data, gpp1_size); add_baked_resource("simple.min.css", simple_min_css_data, simple_min_css_size); add_baked_resource("favicon.ico", favicon_ico_data, favicon_ico_size); + add_baked_resource("hotreload.js", hotreload_js_data, hotreload_js_size); } void free_baked_resources(void) diff --git a/baked.h b/baked.h index 4153c2a..4073572 100644 --- a/baked.h +++ b/baked.h @@ -10,6 +10,7 @@ INCBIN_EXTERN(page_missing_t); INCBIN_EXTERN(simple_min_css); INCBIN_EXTERN(favicon_ico); +INCBIN_EXTERN(hotreload_js); typedef struct { char *key; // path diff --git a/build.c b/build.c index e342ac5..7d4dc9b 100644 --- a/build.c +++ b/build.c @@ -20,7 +20,8 @@ int main(int argc, char ** argv) "./mongoose.o", "./gpp1", "./tmpls/home.t", - "./tmpls/page-missing.t" + "./tmpls/page-missing.t", + "./etc/hotreload.js" ) { RULE("./mongoose.o", "./mongoose/mongoose.c") { @@ -36,13 +37,15 @@ int main(int argc, char ** argv) } #if DEBUG - CMD("cc", "-fPIC", "-ggdb", "-I.", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", + CMD("cc", "-fPIC", "-ggdb", "-I.", "-DDEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-o", "./aboba", - "./main.c", "./routes.c", "./baked.c", "./mongoose.o"); + "./main.c", "./routes.c", "./baked.c", "./mongoose.o", "./cJSON/cJSON.c", + "./md5-c/md5.c"); #else CMD("cc", "-fPIC", "-I.", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-o", "./aboba", - "./main.c", "./routes.c", "./baked.c", "./mongoose.o"); + "./main.c", "./routes.c", "./baked.c", "./mongoose.o", "./cJSON/cJSON.c", + "./md5-c/md5.c"); #endif } } else if (strcmp(cmd, "clean") == 0) { diff --git a/cJSON b/cJSON new file mode 160000 index 0000000..8f2beb5 --- /dev/null +++ b/cJSON @@ -0,0 +1 @@ +Subproject commit 8f2beb57ddad1f94bed899790b00f46df893ccac diff --git a/etc/hotreload.js b/etc/hotreload.js new file mode 100644 index 0000000..c0b42bc --- /dev/null +++ b/etc/hotreload.js @@ -0,0 +1,47 @@ +let build_id = ""; + +async function fetch_build_id() +{ + const response = await fetch("/build-id"); + const json = await response.json(); + return json.build_id; +} + +(async function() { + build_id = await fetch_build_id(); +})(); + + +const asyncIntervals = []; + +const runAsyncInterval = async (cb, interval, intervalIndex) => { + await cb(); + if (asyncIntervals[intervalIndex]) { + setTimeout(() => runAsyncInterval(cb, interval, intervalIndex), interval); + } +}; + +const setAsyncInterval = (cb, interval) => { + if (cb && typeof cb === "function") { + const intervalIndex = asyncIntervals.length; + asyncIntervals.push(true); + runAsyncInterval(cb, interval, intervalIndex); + return intervalIndex; + } else { + throw new Error('Callback must be a function'); + } +}; + +const clearAsyncInterval = (intervalIndex) => { + if (asyncIntervals[intervalIndex]) { + asyncIntervals[intervalIndex] = false; + } +}; + +setAsyncInterval(async function () { + let new_build_id = await fetch_build_id(); + if (build_id !== new_build_id) { + location.reload(true); + } +}, 1000); + diff --git a/main.c b/main.c index 59523af..0c6b77a 100644 --- a/main.c +++ b/main.c @@ -2,9 +2,7 @@ #define GEBS_IMPLEMENTATION #include "gebs/gebs.h" - #include "mongoose/mongoose.h" - #define STB_DS_IMPLEMENTATION #include "stb/stb_ds.h" @@ -32,13 +30,15 @@ void event_handler(struct mg_connection *conn, int ev, void *ev_data) } } -static void init_route_hashtable(void) +void init_route_hashtable(void) { shdefault(route_hashtable, &route_page_not_found); shput(route_hashtable, "/page-missing", &route_page_not_found); shput(route_hashtable, "/simple.min.css", &route_simple_css); shput(route_hashtable, "/favicon.ico", &route_favicon); + shput(route_hashtable, "/hotreload.js", &route_hotreload_js); shput(route_hashtable, "/", &route_home); + shput(route_hashtable, "/build-id", &route_build_id); } int main(int argc, char ** argv) diff --git a/md5-c b/md5-c new file mode 160000 index 0000000..f3529b6 --- /dev/null +++ b/md5-c @@ -0,0 +1 @@ +Subproject commit f3529b666b7ae8b80b0a9fa88ac2a91b389909c7 diff --git a/routes.c b/routes.c index 5934386..2c90a55 100644 --- a/routes.c +++ b/routes.c @@ -1,5 +1,7 @@ #include "gebs/gebs.h" #include "mongoose/mongoose.h" +#include "cJSON/cJSON.h" +#include "md5-c/md5.h" #include "routes.h" #include "baked.h" @@ -29,6 +31,31 @@ bool gpp_run(char *path, NString_List *env, String_Builder *out) return cmd_run_collect(&cmd, out) == 0; } +void route_build_id(struct mg_connection *conn, struct mg_http_message *msg) +{ + cJSON *root = cJSON_CreateObject(); + defer { cJSON_Delete(root); } + + char *time = __TIME__; + uchar md5_buf[16]; + md5String(time, md5_buf); + String_Builder sb = {0}; + defer { sb_free(&sb); } + for (size_t i = 0; i < 16; i++) { + char tmp[3]; + snprintf(tmp, sizeof(tmp), "%02x", md5_buf[i]); + sb_append_nstr(&sb, tmp); + } + sb_finish(&sb); + + cJSON_AddItemToObject(root, "build_id", cJSON_CreateString(sb.items)); + + char *root_text = cJSON_PrintUnformatted(root); + defer { free(root_text); } + + mg_http_reply(conn, 200, "Content-Type: application/json\r\n", root_text); +} + void route_page_not_found(struct mg_connection *conn, struct mg_http_message *msg) { NString_List env = {0}; @@ -44,6 +71,12 @@ void route_page_not_found(struct mg_connection *conn, struct mg_http_message *ms mg_http_reply(conn, 500, "Content-Type: text/plain\r\n", INTERNAL_SERVER_ERROR_MSG); return; } + + #if DEBUG + list_append(&env, "-DHOTRELOAD=1"); + #else + list_append(&env, "-DHOTRELOAD=0"); + #endif bool ok = gpp_run(path, &env, &out); sb_finish(&out); @@ -93,6 +126,25 @@ void route_favicon(struct mg_connection *conn, struct mg_http_message *msg) mg_http_reply(conn, 200, "Content-Type: image/x-icon\r\n", "%s", sb.items); } +void route_hotreload_js(struct mg_connection *conn, struct mg_http_message *msg) +{ + char path[PATH_MAX] = {0}; + if (!get_baked_resource_path("hotreload.js", path, sizeof(path))) { + mg_http_reply(conn, 500, "Content-Type: text/plain\r\n", INTERNAL_SERVER_ERROR_MSG); + return; + } + + String_Builder sb = {0}; + defer { sb_free(&sb); } + if (!sb_read_file(&sb, path)) { + mg_http_reply(conn, 500, "Content-Type: text/plain\r\n", INTERNAL_SERVER_ERROR_MSG); + return; + } + sb_finish(&sb); + + mg_http_reply(conn, 200, "Content-Type: text/javascript\r\n", "%s", sb.items); +} + void route_home(struct mg_connection *conn, struct mg_http_message *msg) { NString_List env = {0}; @@ -106,7 +158,13 @@ void route_home(struct mg_connection *conn, struct mg_http_message *msg) mg_http_reply(conn, 500, "Content-Type: text/plain\r\n", INTERNAL_SERVER_ERROR_MSG); return; } - + + #if DEBUG + list_append(&env, "-DHOTRELOAD=1"); + #else + list_append(&env, "-DHOTRELOAD=0"); + #endif + bool ok = gpp_run(path, &env, &out); sb_finish(&out); diff --git a/routes.h b/routes.h index 137c8ea..41bda17 100644 --- a/routes.h +++ b/routes.h @@ -6,6 +6,8 @@ void route_page_not_found(struct mg_connection *conn, struct mg_http_message *msg); void route_simple_css(struct mg_connection *conn, struct mg_http_message *msg); void route_favicon(struct mg_connection *conn, struct mg_http_message *msg); +void route_hotreload_js(struct mg_connection *conn, struct mg_http_message *msg); void route_home(struct mg_connection *conn, struct mg_http_message *msg); +void route_build_id(struct mg_connection *conn, struct mg_http_message *msg); #endif // ROUTES_H_ diff --git a/tmpls/home.t b/tmpls/home.t index 2a4cdcf..2c47b63 100644 --- a/tmpls/home.t +++ b/tmpls/home.t @@ -41,5 +41,9 @@ + + <#if HOTRELOAD> + + <#endif> diff --git a/tmpls/page-missing.t b/tmpls/page-missing.t index 7ac93da..52f7070 100644 --- a/tmpls/page-missing.t +++ b/tmpls/page-missing.t @@ -12,5 +12,9 @@ + + <#if HOTRELOAD> + + <#endif>