feat: live reload

This commit is contained in:
Krzysztof Rudnicki 2025-09-07 22:00:20 +02:00
parent 88432fba4d
commit 3c2d5f27b3
3 changed files with 88 additions and 18 deletions

View File

@ -2,7 +2,7 @@
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Mini Articles</title>
<title>wassup</title>
<style>
/* Tiny, readable defaults */
*{box-sizing:border-box}html,body{height:100%}body{margin:0;font:16px/1.4 system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;color:#222;background:#fff}
@ -33,7 +33,7 @@ article h1{margin:.25rem 0 .5rem;font-size:1.6rem}
</style>
<header>
<h1>Mini Articles</h1>
<h1>Hi</h1>
<nav>
<button id="toList" title="All articles">List</button>
<button id="toNew" title="Create article">New</button>

View File

@ -9,25 +9,76 @@ SITE_DIR="$DIR"
HOST="${HOST:-127.0.0.1}"
PORT="${PORT:-8000}"
make -s -C "$SITE_DIR" server_c
start_server() {
make -s -C "$SITE_DIR" server_c
export HOST PORT ARTICLES_DATA_DIR
"$SITE_DIR/server_c" &
SRV_PID=$!
}
# Start C server in background
export HOST PORT ARTICLES_DATA_DIR
"$SITE_DIR/server_c" &
SRV_PID=$!
trap 'kill $SRV_PID 2>/dev/null || true' EXIT INT TERM
stop_server() {
if [[ -n "${SRV_PID:-}" ]] && kill -0 "$SRV_PID" 2>/dev/null; then
kill "$SRV_PID" 2>/dev/null || true
wait "$SRV_PID" 2>/dev/null || true
fi
}
restart_server() {
echo "[watch] Rebuilding and restarting server_c..."
stop_server
start_server
}
open_browser_once() {
local url="http://$HOST:$PORT/"
if command -v xdg-open >/dev/null 2>&1; then
xdg-open "$url" >/dev/null 2>&1 || true
fi
echo "Mini Articles running at $url"
}
start_server
trap 'stop_server' EXIT INT TERM
# Give it a moment to start
sleep 0.5
URL="http://$HOST:$PORT/"
open_browser_once
# Try to open browser on Linux
if command -v xdg-open >/dev/null 2>&1; then
xdg-open "$URL" >/dev/null 2>&1 || true
echo "Press Ctrl+C to stop. Watching for changes in index.html and server_c.c (auto-reload enabled)..."
watch_poll() {
local idx_mtime srv_mtime new_idx new_srv
idx_mtime=$(stat -c %Y "$SITE_DIR/index.html" 2>/dev/null || echo 0)
srv_mtime=$(stat -c %Y "$SITE_DIR/server_c.c" 2>/dev/null || echo 0)
while true; do
sleep 0.5
new_idx=$(stat -c %Y "$SITE_DIR/index.html" 2>/dev/null || echo 0)
new_srv=$(stat -c %Y "$SITE_DIR/server_c.c" 2>/dev/null || echo 0)
if [[ "$new_srv" != "$srv_mtime" ]]; then
srv_mtime="$new_srv"
restart_server
fi
if [[ "$new_idx" != "$idx_mtime" ]]; then
idx_mtime="$new_idx"
echo "[watch] index.html changed. Browser will auto-reload."
fi
done
}
watch_inotify() {
inotifywait -e close_write,create,move --format '%w%f' -m \
"$SITE_DIR/index.html" "$SITE_DIR/server_c.c" | while read -r file; do
case "$file" in
*server_c.c) restart_server ;;
*index.html) echo "[watch] index.html changed. Browser will auto-reload." ;;
esac
done
}
if command -v inotifywait >/dev/null 2>&1; then
watch_inotify &
WATCH_PID=$!
wait "$WATCH_PID"
else
watch_poll
fi
echo "Mini Articles running at $URL"
echo "Press Ctrl+C to stop."
# Wait on server
wait "$SRV_PID"

View File

@ -20,6 +20,7 @@
static volatile sig_atomic_t g_stop = 0;
static const char *DOC_ROOT = NULL; // current working directory
static long long g_boot_ms = 0; // server start time for live-reload token
static void on_sigint(int sig){ (void)sig; g_stop = 1; }
@ -261,6 +262,13 @@ static void handle_static(int c, const char* path){
fseek(f,0,SEEK_END); long sz=ftell(f); fseek(f,0,SEEK_SET); char* buf=malloc((size_t)sz); if(!buf){ fclose(f); send_response(c,500,"Internal Server Error","text/plain","",0,false); return; }
size_t n=fread(buf,1,(size_t)sz,f); fclose(f);
const char* mime=guess_mime(full);
// Inject a tiny live-reload script into index.html at serve-time (does not affect file size on disk)
if(strstr(mime, "text/html") && (!strcmp(rel, "/index.html"))){
const char* lr = "<script>setInterval(()=>fetch('/__lr').then(r=>r.text()).then(t=>{if(window.__lr!==t){if(window.__lr)location.reload();window.__lr=t}}),500);</script>";
size_t ln = strlen(lr);
char* out = malloc(n + ln);
if(out){ memcpy(out, buf, n); memcpy(out + n, lr, ln); send_response(c,200,"OK",mime,out,n+ln,false); free(out); free(buf); return; }
}
send_response(c,200,"OK",mime,buf,n,false); free(buf);
}
@ -279,6 +287,16 @@ static void handle_client(int c){
char* body = NULL; if(content_length){ body = malloc(content_length+1); if(!body){ close(c); return; } size_t off=0; if(have_body){ size_t cpy = have_body>content_length?content_length:have_body; memcpy(body, buf+header_bytes, cpy); off = cpy; }
size_t remain = content_length - off; while(remain>0){ ssize_t rr = recv(c, body+off, remain, 0); if(rr<=0) break; off+=rr; remain-=rr; } body[content_length]='\0'; }
// Lightweight live-reload timestamp endpoint
if(!strcmp(method, "GET") && !strcmp(path, "/__lr")){
char idx[SMALL_BUF*2]; snprintf(idx, sizeof(idx), "%s/index.html", DOC_ROOT?DOC_ROOT:".");
struct stat st; char out[96]; size_t w=0;
if(stat(idx, &st)==0){ w=(size_t)snprintf(out, sizeof(out), "%ld-%lld", (long)st.st_mtime, g_boot_ms); }
else { out[0]='0'; w=1; }
send_response(c,200,"OK","text/plain",out,w,false);
free(body); close(c); return;
}
if(!strncmp(path, "/api/", 5)){
handle_api(c, method, path, body, content_length);
} else if(!strcmp(method,"GET")){
@ -295,6 +313,7 @@ static void handle_client(int c){
int main(int argc, char** argv){
signal(SIGINT, on_sigint);
srand((unsigned int)time(NULL));
g_boot_ms = now_ms();
char cwd[SMALL_BUF]; if(getcwd(cwd,sizeof(cwd))) DOC_ROOT=strdup(cwd);
const char* host = getenv_default("HOST","127.0.0.1");
int port = atoi(getenv_default("PORT","8000")); if(port<=0) port=8000;