perf(catalog): precompute search blob and tag count at build (P2)

ober

78aca23d3edd93215836ea3ea5c9c3b4803769a2

diff --git a/src/imagesite/catalog.ss b/src/imagesite/catalog.ss
index 4cb99e1..d2041aa 100644
--- a/src/imagesite/catalog.ss
+++ b/src/imagesite/catalog.ss
@@ -37,6 +37,17 @@
       (reverse out)
       (loop (cdr rest) (- n 1) (cons (car rest) out)))))
 
+;; Downcased search haystack for a media item, computed once at build time so
+;; searching does not rebuild and downcase it per media per query.
+(def (build-search-blob title caption rel-path tags)
+  (string-downcase-safe
+   (string-append
+    title " " caption " " rel-path " "
+    (let loop ((tags tags) (out ""))
+      (if (null? tags)
+        out
+        (loop (cdr tags) (string-append out " " (car tags))))))))
+
 (def (walk-media-root root hidden-media proc)
   (secure-walk-media-root
    root
@@ -97,7 +108,9 @@
                        (cons "width" #f)
                        (cons "height" #f)
                        (cons "taken_at" #f)
-                       (cons "favorite" 0)))))
+                       (cons "favorite" 0)
+                       (cons "search_blob"
+                             (build-search-blob (media-title rel) caption rel tags))))))
          (set! next-id (+ next-id 1))
          (hash-put! album-counts album-path
                     (+ 1 (if (hash-key? album-counts album-path)
@@ -148,10 +161,11 @@
 	        (cons "media_by_id" media-by-id)
 	        (cons "media_by_path" media-by-path)
 	        (cons "media_by_album" media-by-album)
-        (cons "media_count" (length media-list))
-        (cons "album_count" (length albums)))))))
+         (cons "media_count" (length media-list))
+         (cons "tag_count" (count-distinct-tags media-list))
+         (cons "album_count" (length albums)))))))
 
-(def (tag-count catalog)
+(def (count-distinct-tags media-list)
   (let ((seen (make-hash-table)))
     (for-each
       (lambda (media)
@@ -160,9 +174,12 @@
             (when (string? tag)
               (hash-put! seen (string-downcase-safe tag) #t)))
           (h media "tags" '())))
-      (h catalog "media" '()))
+      media-list)
     (hash-length seen)))
 
+(def (tag-count catalog)
+  (h catalog "tag_count" 0))
+
 (def (catalog-counts catalog)
   (object
    (list
@@ -199,16 +216,7 @@
     (and by-path (hash-key? by-path rel-path) (hash-get by-path rel-path))))
 
 (def (catalog-match? media needle)
-  (let ((haystack (string-downcase-safe
-                   (string-append
-                    (h media "title" "") " "
-                    (h media "caption" "") " "
-                    (h media "rel_path" "") " "
-                    (let loop ((tags (h media "tags" '())) (out ""))
-                      (if (null? tags)
-                        out
-                        (loop (cdr tags) (string-append out " " (car tags)))))))))
-    (string-contains? haystack needle)))
+  (string-contains? (h media "search_blob" "") needle))
 
 (def (media-tag-match? media needle)
   (let loop ((tags (h media "tags" '())))