mirror of
https://codeberg.org/gigirassy/pinata
synced 2026-08-30 15:27:39 +00:00
add chunk mode (actual main.go)
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
// main.go
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -16,6 +15,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -56,6 +56,9 @@ type BookmarkEntry struct {
|
|||||||
var bookmarkKey []byte
|
var bookmarkKey []byte
|
||||||
var bookmarkingEnabled bool
|
var bookmarkingEnabled bool
|
||||||
var disableReverse bool
|
var disableReverse bool
|
||||||
|
var chunkedMode bool
|
||||||
|
var chunkSize = 8
|
||||||
|
var chunkWorkers = 4
|
||||||
|
|
||||||
const maxBookmarks = 30
|
const maxBookmarks = 30
|
||||||
const maxItemLen = 256
|
const maxItemLen = 256
|
||||||
@@ -85,6 +88,40 @@ func init() {
|
|||||||
default:
|
default:
|
||||||
disableReverse = false
|
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) ----------
|
// ---------- encryption helpers (AES-GCM) ----------
|
||||||
@@ -344,7 +381,6 @@ button[type="submit"],.btn-save{background:linear-gradient(90deg,var(--accent),#
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
|
||||||
// ---------- handlers ----------
|
// ---------- handlers ----------
|
||||||
|
|
||||||
func styleHandler(w http.ResponseWriter, r *http.Request) {
|
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)
|
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(`<div class="card">`)
|
||||||
|
b.WriteString(`<a href="/image_proxy?url=`)
|
||||||
|
b.WriteString(esc)
|
||||||
|
b.WriteString(`" style="display:block;"><img loading="lazy" src="/image_proxy?url=`)
|
||||||
|
b.WriteString(esc)
|
||||||
|
b.WriteString(`" alt="image"></a>`)
|
||||||
|
b.WriteString(`<div class="card-controls">`)
|
||||||
|
if !disableReverse {
|
||||||
|
b.WriteString(`<a class="magnifier" href="/revsearch?b64=`)
|
||||||
|
b.WriteString(b64)
|
||||||
|
b.WriteString(`" title="Search Tineye" target="_blank">🔍</a>`)
|
||||||
|
}
|
||||||
|
if bookmarkingEnabled {
|
||||||
|
b.WriteString(`<form method="post" action="/bookmark_image" style="display:inline;margin:0;">`)
|
||||||
|
b.WriteString(`<input type="hidden" name="url" value="`)
|
||||||
|
b.WriteString(html.EscapeString(u))
|
||||||
|
b.WriteString(`"><input type="hidden" name="next" value="`)
|
||||||
|
b.WriteString(html.EscapeString(next))
|
||||||
|
b.WriteString(`"><button class="btn-save-mini" type="submit" title="Save image">❤</button></form>`)
|
||||||
|
}
|
||||||
|
b.WriteString(`</div></div>`)
|
||||||
|
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)
|
// Index (front) - server-rendered bookmarks and settings form (no JS)
|
||||||
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
accent, imgScale := getThemeVars(r)
|
accent, imgScale := getThemeVars(r)
|
||||||
@@ -521,6 +650,8 @@ func searchHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
dec := json.NewDecoder(resp.Body)
|
dec := json.NewDecoder(resp.Body)
|
||||||
var nextBookmark string
|
var nextBookmark string
|
||||||
|
nextSearch := "/search?q=" + url.QueryEscape(q)
|
||||||
|
chunk := make([]string, 0, chunkSize)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
tk, err := dec.Token()
|
tk, err := dec.Token()
|
||||||
@@ -561,25 +692,17 @@ func searchHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
if u == "" {
|
if u == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
esc := url.QueryEscape(u)
|
if chunkedMode {
|
||||||
b64 := base64.StdEncoding.EncodeToString([]byte(u))
|
chunk = append(chunk, u)
|
||||||
|
if len(chunk) >= chunkSize {
|
||||||
// card
|
writeChunkedCards(w, q, nextSearch, chunk)
|
||||||
_, _ = io.WriteString(w, `<div class="card">`)
|
chunk = chunk[:0]
|
||||||
_, _ = io.WriteString(w, `<a href="/image_proxy?url=`+esc+`" style="display:block;"><img loading="lazy" src="/image_proxy?url=`+esc+`" alt="image"></a>`)
|
}
|
||||||
_, _ = io.WriteString(w, `<div class="card-controls">`)
|
} else {
|
||||||
if !disableReverse {
|
_, _ = io.WriteString(w, renderCardHTML(q, nextSearch, u))
|
||||||
_, _ = io.WriteString(w, `<a class="magnifier" href="/revsearch?b64=`+b64+`" title="Search Tineye" target="_blank">🔍</a>`)
|
if f, ok := w.(http.Flusher); ok {
|
||||||
}
|
f.Flush()
|
||||||
if bookmarkingEnabled {
|
}
|
||||||
next := "/search?q=" + url.QueryEscape(q)
|
|
||||||
_, _ = io.WriteString(w, `<form method="post" action="/bookmark_image" style="display:inline;margin:0;"><input type="hidden" name="url" value="`+html.EscapeString(u)+`"><input type="hidden" name="next" value="`+html.EscapeString(next)+`"><button class="btn-save-mini" type="submit" title="Save image">❤</button></form>`)
|
|
||||||
}
|
|
||||||
_, _ = io.WriteString(w, `</div>`) // card-controls
|
|
||||||
_, _ = io.WriteString(w, `</div>`) // card
|
|
||||||
|
|
||||||
if f, ok := w.(http.Flusher); ok {
|
|
||||||
f.Flush()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, _ = dec.Token()
|
_, _ = 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, `</div>`)
|
_, _ = io.WriteString(w, `</div>`)
|
||||||
if nextBookmark != "" {
|
if nextBookmark != "" {
|
||||||
qenc := url.QueryEscape(q)
|
qenc := url.QueryEscape(q)
|
||||||
@@ -688,7 +815,7 @@ func revsearchHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Redirect(w, r, tineye, http.StatusSeeOther)
|
http.Redirect(w, r, tineye, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- bookmark handlers (unchanged from previous) ----------
|
// ---------- bookmark handlers ----------
|
||||||
|
|
||||||
func bookmarkPostHandler(w http.ResponseWriter, r *http.Request) {
|
func bookmarkPostHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if !bookmarkingEnabled {
|
if !bookmarkingEnabled {
|
||||||
@@ -909,4 +1036,4 @@ func main() {
|
|||||||
|
|
||||||
log.Println("Pinata listening on :8080 (no-JS mode). Bookmarking enabled:", bookmarkingEnabled, " Reverse disabled:", disableReverse)
|
log.Println("Pinata listening on :8080 (no-JS mode). Bookmarking enabled:", bookmarkingEnabled, " Reverse disabled:", disableReverse)
|
||||||
log.Fatal(server.ListenAndServe())
|
log.Fatal(server.ListenAndServe())
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user