From 9a00b8d27e5efafb76e188aa16b819d86f077e50 Mon Sep 17 00:00:00 2001 From: gigirassy Date: Thu, 26 Mar 2026 23:59:28 +0100 Subject: [PATCH] add chunk mode (actual main.go) --- main.go | 173 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 150 insertions(+), 23 deletions(-) diff --git a/main.go b/main.go index 836b1ab..6c76658 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,3 @@ -// main.go package main import ( @@ -16,6 +15,7 @@ import ( "net/http" "net/url" "os" + "runtime" "strconv" "strings" "sync" @@ -56,6 +56,9 @@ type BookmarkEntry struct { var bookmarkKey []byte var bookmarkingEnabled bool var disableReverse bool +var chunkedMode bool +var chunkSize = 8 +var chunkWorkers = 4 const maxBookmarks = 30 const maxItemLen = 256 @@ -85,6 +88,40 @@ func init() { default: disableReverse = false } + + // CHUNK enables chunked/threaded rendering of result cards. + // Examples: + // CHUNK=0/false/no/off -> disabled + // CHUNK=1/true/yes/on -> enabled with default chunk size + // CHUNK=12 -> enabled with 12-item batches + if raw := strings.TrimSpace(os.Getenv("CHUNK")); raw != "" { + switch strings.ToLower(raw) { + case "0", "false", "no", "off": + chunkedMode = false + default: + chunkedMode = true + if n, err := strconv.Atoi(raw); err == nil && n > 0 { + chunkSize = n + } + } + } + if chunkSize < 4 { + chunkSize = 4 + } + if chunkSize > 16 { + chunkSize = 16 + } + cpus := runtime.GOMAXPROCS(0) + if cpus < 1 { + cpus = 1 + } + if cpus > 4 { + cpus = 4 + } + chunkWorkers = cpus + if chunkedMode { + log.Printf("Chunked mode enabled: chunkSize=%d workers=%d", chunkSize, chunkWorkers) + } } // ---------- encryption helpers (AES-GCM) ---------- @@ -344,7 +381,6 @@ button[type="submit"],.btn-save{background:linear-gradient(90deg,var(--accent),# } ` - // ---------- handlers ---------- func styleHandler(w http.ResponseWriter, r *http.Request) { @@ -395,6 +431,99 @@ func settingsPostHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, next, http.StatusSeeOther) } +func renderCardHTML(q, next, u string) string { + esc := url.QueryEscape(u) + b64 := base64.StdEncoding.EncodeToString([]byte(u)) + + var b strings.Builder + b.Grow(len(u)*2 + 512) + b.WriteString(`
`) + b.WriteString(`image`) + b.WriteString(`
`) + if !disableReverse { + b.WriteString(`🔍`) + } + if bookmarkingEnabled { + b.WriteString(`
`) + b.WriteString(`
`) + } + b.WriteString(`
`) + return b.String() +} + +func writeChunkedCards(w http.ResponseWriter, q, next string, urls []string) { + if len(urls) == 0 { + return + } + if !chunkedMode || len(urls) == 1 { + for _, u := range urls { + _, _ = io.WriteString(w, renderCardHTML(q, next, u)) + } + if f, ok := w.(http.Flusher); ok { + f.Flush() + } + return + } + + type job struct { + idx int + u string + } + type result struct { + idx int + html string + } + + jobs := make(chan job, len(urls)) + results := make(chan result, len(urls)) + + workers := chunkWorkers + if workers > len(urls) { + workers = len(urls) + } + var wg sync.WaitGroup + wg.Add(workers) + for i := 0; i < workers; i++ { + go func() { + defer wg.Done() + for j := range jobs { + results <- result{idx: j.idx, html: renderCardHTML(q, next, j.u)} + } + }() + } + + for i, u := range urls { + jobs <- job{idx: i, u: u} + } + close(jobs) + + go func() { + wg.Wait() + close(results) + }() + + out := make([]string, len(urls)) + for r := range results { + out[r.idx] = r.html + } + for _, s := range out { + _, _ = io.WriteString(w, s) + } + if f, ok := w.(http.Flusher); ok { + f.Flush() + } +} + // Index (front) - server-rendered bookmarks and settings form (no JS) func indexHandler(w http.ResponseWriter, r *http.Request) { accent, imgScale := getThemeVars(r) @@ -521,6 +650,8 @@ func searchHandler(w http.ResponseWriter, r *http.Request) { dec := json.NewDecoder(resp.Body) var nextBookmark string + nextSearch := "/search?q=" + url.QueryEscape(q) + chunk := make([]string, 0, chunkSize) for { tk, err := dec.Token() @@ -561,25 +692,17 @@ func searchHandler(w http.ResponseWriter, r *http.Request) { if u == "" { continue } - esc := url.QueryEscape(u) - b64 := base64.StdEncoding.EncodeToString([]byte(u)) - - // card - _, _ = io.WriteString(w, `
`) - _, _ = io.WriteString(w, `image`) - _, _ = io.WriteString(w, `
`) - if !disableReverse { - _, _ = io.WriteString(w, `🔍`) - } - if bookmarkingEnabled { - next := "/search?q=" + url.QueryEscape(q) - _, _ = io.WriteString(w, `
`) - } - _, _ = io.WriteString(w, `
`) // card-controls - _, _ = io.WriteString(w, `
`) // card - - if f, ok := w.(http.Flusher); ok { - f.Flush() + if chunkedMode { + chunk = append(chunk, u) + if len(chunk) >= chunkSize { + writeChunkedCards(w, q, nextSearch, chunk) + chunk = chunk[:0] + } + } else { + _, _ = io.WriteString(w, renderCardHTML(q, nextSearch, u)) + if f, ok := w.(http.Flusher); ok { + f.Flush() + } } } _, _ = dec.Token() @@ -595,6 +718,10 @@ func searchHandler(w http.ResponseWriter, r *http.Request) { } } + if chunkedMode && len(chunk) > 0 { + writeChunkedCards(w, q, nextSearch, chunk) + } + _, _ = io.WriteString(w, ``) if nextBookmark != "" { qenc := url.QueryEscape(q) @@ -688,7 +815,7 @@ func revsearchHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, tineye, http.StatusSeeOther) } -// ---------- bookmark handlers (unchanged from previous) ---------- +// ---------- bookmark handlers ---------- func bookmarkPostHandler(w http.ResponseWriter, r *http.Request) { if !bookmarkingEnabled { @@ -909,4 +1036,4 @@ func main() { log.Println("Pinata listening on :8080 (no-JS mode). Bookmarking enabled:", bookmarkingEnabled, " Reverse disabled:", disableReverse) log.Fatal(server.ListenAndServe()) -} +} \ No newline at end of file