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>