From bc037315470431ac1f1ba2c7365ca634895e113f Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch Date: Mon, 25 Dec 2023 02:44:30 +0100 Subject: Shuffle code --- main.go | 412 +++++++++++++++++++++++++++--------------------------- public/gallery.js | 230 +++++++++++++++--------------- 2 files changed, 321 insertions(+), 321 deletions(-) diff --git a/main.go b/main.go index 29b08d3..53c7e22 100644 --- a/main.go +++ b/main.go @@ -453,212 +453,6 @@ func handleAPITags(w http.ResponseWriter, r *http.Request) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -func getImageDimensions(sha1 string) (w int64, h int64, err error) { - err = db.QueryRow(`SELECT width, height FROM image WHERE sha1 = ?`, - sha1).Scan(&w, &h) - return -} - -func getImagePaths(sha1 string) (paths []string, err error) { - rows, err := db.Query(`WITH RECURSIVE paths(parent, path) AS ( - SELECT parent, name AS path FROM node WHERE sha1 = ? - UNION ALL - SELECT n.parent, n.name || '/' || p.path - FROM node AS n JOIN paths AS p ON n.id = p.parent - ) SELECT path FROM paths WHERE parent IS NULL`, sha1) - if err != nil { - return nil, err - } - defer rows.Close() - - paths = []string{} - for rows.Next() { - var path string - if err := rows.Scan(&path); err != nil { - return nil, err - } - paths = append(paths, path) - } - return paths, rows.Err() -} - -func getImageTags(sha1 string) (map[string]map[string]float32, error) { - rows, err := db.Query(` - SELECT ts.name, t.name, ta.weight FROM tag_assignment AS ta - JOIN tag AS t ON t.id = ta.tag - JOIN tag_space AS ts ON ts.id = t.space - WHERE ta.sha1 = ?`, sha1) - if err != nil { - return nil, err - } - defer rows.Close() - - result := make(map[string]map[string]float32) - for rows.Next() { - var ( - space, tag string - weight float32 - ) - if err := rows.Scan(&space, &tag, &weight); err != nil { - return nil, err - } - - tags := result[space] - if tags == nil { - tags = make(map[string]float32) - result[space] = tags - } - tags[tag] = weight - } - return result, rows.Err() -} - -func handleAPIInfo(w http.ResponseWriter, r *http.Request) { - var params struct { - SHA1 string - } - if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - var result struct { - Width int64 `json:"width"` - Height int64 `json:"height"` - Paths []string `json:"paths"` - Tags map[string]map[string]float32 `json:"tags"` - } - - var err error - result.Width, result.Height, err = getImageDimensions(params.SHA1) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - result.Paths, err = getImagePaths(params.SHA1) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - result.Tags, err = getImageTags(params.SHA1) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - if err := json.NewEncoder(w).Encode(result); err != nil { - log.Println(err) - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -type webSimilarImage struct { - SHA1 string `json:"sha1"` - PixelsRatio float32 `json:"pixelsRatio"` - ThumbW int64 `json:"thumbW"` - ThumbH int64 `json:"thumbH"` - Paths []string `json:"paths"` -} - -func getSimilar(sha1 string, dhash int64, pixels int64, distance int) ( - result []webSimilarImage, err error) { - // For distance ∈ {0, 1}, this query is quite inefficient. - // In exchange, it's generic. - // - // If there's a dhash, there should also be thumbnail dimensions, - // so not bothering with IFNULL on them. - rows, err := db.Query(` - SELECT sha1, width * height, IFNULL(thumbw, 0), IFNULL(thumbh, 0) - FROM image WHERE sha1 <> ? AND dhash IS NOT NULL - AND hamming(dhash, ?) = ?`, sha1, dhash, distance) - if err != nil { - return nil, err - } - defer rows.Close() - - result = []webSimilarImage{} - for rows.Next() { - var ( - match webSimilarImage - matchPixels int64 - ) - if err = rows.Scan(&match.SHA1, - &matchPixels, &match.ThumbW, &match.ThumbH); err != nil { - return nil, err - } - if match.Paths, err = getImagePaths(match.SHA1); err != nil { - return nil, err - } - match.PixelsRatio = float32(matchPixels) / float32(pixels) - result = append(result, match) - } - return result, rows.Err() -} - -func getSimilarGroups(sha1 string, dhash int64, pixels int64, - output map[string][]webSimilarImage) error { - var err error - for distance := 0; distance <= 1; distance++ { - output[fmt.Sprintf("Perceptual distance %d", distance)], err = - getSimilar(sha1, dhash, pixels, distance) - if err != nil { - return err - } - } - return nil -} - -func handleAPISimilar(w http.ResponseWriter, r *http.Request) { - var params struct { - SHA1 string - } - if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - var result struct { - Info webSimilarImage `json:"info"` - Groups map[string][]webSimilarImage `json:"groups"` - } - - result.Info = webSimilarImage{SHA1: params.SHA1, PixelsRatio: 1} - if paths, err := getImagePaths(params.SHA1); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } else { - result.Info.Paths = paths - } - - var ( - width, height int64 - dhash sql.NullInt64 - ) - err := db.QueryRow(` - SELECT width, height, dhash, IFNULL(thumbw, 0), IFNULL(thumbh, 0) - FROM image WHERE sha1 = ?`, params.SHA1).Scan(&width, &height, &dhash, - &result.Info.ThumbW, &result.Info.ThumbH) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - result.Groups = make(map[string][]webSimilarImage) - if dhash.Valid { - if err := getSimilarGroups( - params.SHA1, dhash.Int64, width*height, result.Groups); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - } - - if err := json.NewEncoder(w).Encode(result); err != nil { - log.Println(err) - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - type webDuplicateImage struct { SHA1 string `json:"sha1"` ThumbW int64 `json:"thumbW"` @@ -899,6 +693,212 @@ func handleAPIOrphans(w http.ResponseWriter, r *http.Request) { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +func getImageDimensions(sha1 string) (w int64, h int64, err error) { + err = db.QueryRow(`SELECT width, height FROM image WHERE sha1 = ?`, + sha1).Scan(&w, &h) + return +} + +func getImagePaths(sha1 string) (paths []string, err error) { + rows, err := db.Query(`WITH RECURSIVE paths(parent, path) AS ( + SELECT parent, name AS path FROM node WHERE sha1 = ? + UNION ALL + SELECT n.parent, n.name || '/' || p.path + FROM node AS n JOIN paths AS p ON n.id = p.parent + ) SELECT path FROM paths WHERE parent IS NULL`, sha1) + if err != nil { + return nil, err + } + defer rows.Close() + + paths = []string{} + for rows.Next() { + var path string + if err := rows.Scan(&path); err != nil { + return nil, err + } + paths = append(paths, path) + } + return paths, rows.Err() +} + +func getImageTags(sha1 string) (map[string]map[string]float32, error) { + rows, err := db.Query(` + SELECT ts.name, t.name, ta.weight FROM tag_assignment AS ta + JOIN tag AS t ON t.id = ta.tag + JOIN tag_space AS ts ON ts.id = t.space + WHERE ta.sha1 = ?`, sha1) + if err != nil { + return nil, err + } + defer rows.Close() + + result := make(map[string]map[string]float32) + for rows.Next() { + var ( + space, tag string + weight float32 + ) + if err := rows.Scan(&space, &tag, &weight); err != nil { + return nil, err + } + + tags := result[space] + if tags == nil { + tags = make(map[string]float32) + result[space] = tags + } + tags[tag] = weight + } + return result, rows.Err() +} + +func handleAPIInfo(w http.ResponseWriter, r *http.Request) { + var params struct { + SHA1 string + } + if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + var result struct { + Width int64 `json:"width"` + Height int64 `json:"height"` + Paths []string `json:"paths"` + Tags map[string]map[string]float32 `json:"tags"` + } + + var err error + result.Width, result.Height, err = getImageDimensions(params.SHA1) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + result.Paths, err = getImagePaths(params.SHA1) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + result.Tags, err = getImageTags(params.SHA1) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if err := json.NewEncoder(w).Encode(result); err != nil { + log.Println(err) + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +type webSimilarImage struct { + SHA1 string `json:"sha1"` + PixelsRatio float32 `json:"pixelsRatio"` + ThumbW int64 `json:"thumbW"` + ThumbH int64 `json:"thumbH"` + Paths []string `json:"paths"` +} + +func getSimilar(sha1 string, dhash int64, pixels int64, distance int) ( + result []webSimilarImage, err error) { + // For distance ∈ {0, 1}, this query is quite inefficient. + // In exchange, it's generic. + // + // If there's a dhash, there should also be thumbnail dimensions, + // so not bothering with IFNULL on them. + rows, err := db.Query(` + SELECT sha1, width * height, IFNULL(thumbw, 0), IFNULL(thumbh, 0) + FROM image WHERE sha1 <> ? AND dhash IS NOT NULL + AND hamming(dhash, ?) = ?`, sha1, dhash, distance) + if err != nil { + return nil, err + } + defer rows.Close() + + result = []webSimilarImage{} + for rows.Next() { + var ( + match webSimilarImage + matchPixels int64 + ) + if err = rows.Scan(&match.SHA1, + &matchPixels, &match.ThumbW, &match.ThumbH); err != nil { + return nil, err + } + if match.Paths, err = getImagePaths(match.SHA1); err != nil { + return nil, err + } + match.PixelsRatio = float32(matchPixels) / float32(pixels) + result = append(result, match) + } + return result, rows.Err() +} + +func getSimilarGroups(sha1 string, dhash int64, pixels int64, + output map[string][]webSimilarImage) error { + var err error + for distance := 0; distance <= 1; distance++ { + output[fmt.Sprintf("Perceptual distance %d", distance)], err = + getSimilar(sha1, dhash, pixels, distance) + if err != nil { + return err + } + } + return nil +} + +func handleAPISimilar(w http.ResponseWriter, r *http.Request) { + var params struct { + SHA1 string + } + if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + var result struct { + Info webSimilarImage `json:"info"` + Groups map[string][]webSimilarImage `json:"groups"` + } + + result.Info = webSimilarImage{SHA1: params.SHA1, PixelsRatio: 1} + if paths, err := getImagePaths(params.SHA1); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } else { + result.Info.Paths = paths + } + + var ( + width, height int64 + dhash sql.NullInt64 + ) + err := db.QueryRow(` + SELECT width, height, dhash, IFNULL(thumbw, 0), IFNULL(thumbh, 0) + FROM image WHERE sha1 = ?`, params.SHA1).Scan(&width, &height, &dhash, + &result.Info.ThumbW, &result.Info.ThumbH) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + result.Groups = make(map[string][]webSimilarImage) + if dhash.Valid { + if err := getSimilarGroups( + params.SHA1, dhash.Int64, width*height, result.Groups); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + + if err := json.NewEncoder(w).Encode(result); err != nil { + log.Println(err) + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // cmdWeb runs a web UI against GD on ADDRESS. func cmdWeb(args []string) error { if len(args) != 2 { diff --git a/public/gallery.js b/public/gallery.js index 4933064..52a92ab 100644 --- a/public/gallery.js +++ b/public/gallery.js @@ -253,6 +253,120 @@ let Tags = { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +let DuplicatesModel = { + entries: [], + + async reload() { + this.entries = await call('duplicates', {}) + }, +} + +let DuplicatesThumbnail = { + view(vnode) { + const info = vnode.attrs.info + return [ + m(m.route.Link, {href: `/similar/${info.sha1}`}, + m(Thumbnail, {info})), + (info.occurences != 1) ? ` ×${info.occurences}` : [], + ] + }, +} + +let DuplicatesList = { + // See BrowseView. + oncreate(vnode) { vnode.dom.focus() }, + + view(vnode) { + let children = (DuplicatesModel.entries.length == 0) + ? "No duplicates" + : DuplicatesModel.entries.map(group => + m('.row', group.map(entry => + m(DuplicatesThumbnail, {info: entry})))) + return m('.duplicates[tabindex=0]', {}, children) + }, +} + +let Duplicates = { + oninit(vnode) { + DuplicatesModel.reload() + }, + + view(vnode) { + return m('.container', {}, [ + m(Header), + m('.body', {}, m(DuplicatesList)), + ]) + }, +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +let OrphansModel = { + entries: [], + + async reload() { + this.entries = await call('orphans', {}) + }, +} + +let OrphansReplacement = { + view(vnode) { + const info = vnode.attrs.info + if (!info) + return [] + + return [ + ` → `, + m(m.route.Link, {href: `/view/${info.sha1}`}, + m(Thumbnail, {info})), + `${info.tags} tags`, + ] + }, +} + +let OrphansRow = { + view(vnode) { + const info = vnode.attrs.info + return m('.row', [ + // It might not load, but still allow tag viewing. + m(m.route.Link, {href: `/view/${info.sha1}`}, + m(Thumbnail, {info})), + `${info.tags} tags`, + m(OrphansReplacement, {info: info.replacement}), + ]) + }, +} + +let OrphansList = { + // See BrowseView. + oncreate(vnode) { vnode.dom.focus() }, + + view(vnode) { + let children = (OrphansModel.entries.length == 0) + ? "No orphans" + : OrphansModel.entries.map(info => [ + m("h2", info.lastPath), + m(OrphansRow, {info}), + ]) + return m('.orphans[tabindex=0]', {}, children) + }, +} + +let Orphans = { + oninit(vnode) { + OrphansModel.reload() + }, + + view(vnode) { + return m('.container', {}, [ + m(Header), + m('.body', {}, m(OrphansList)), + ]) + }, +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + let ViewModel = { sha1: undefined, width: 0, @@ -427,120 +541,6 @@ let Similar = { // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -let DuplicatesModel = { - entries: [], - - async reload() { - this.entries = await call('duplicates', {}) - }, -} - -let DuplicatesThumbnail = { - view(vnode) { - const info = vnode.attrs.info - return [ - m(m.route.Link, {href: `/similar/${info.sha1}`}, - m(Thumbnail, {info})), - (info.occurences != 1) ? ` ×${info.occurences}` : [], - ] - }, -} - -let DuplicatesList = { - // See BrowseView. - oncreate(vnode) { vnode.dom.focus() }, - - view(vnode) { - let children = (DuplicatesModel.entries.length == 0) - ? "No duplicates" - : DuplicatesModel.entries.map(group => - m('.row', group.map(entry => - m(DuplicatesThumbnail, {info: entry})))) - return m('.duplicates[tabindex=0]', {}, children) - }, -} - -let Duplicates = { - oninit(vnode) { - DuplicatesModel.reload() - }, - - view(vnode) { - return m('.container', {}, [ - m(Header), - m('.body', {}, m(DuplicatesList)), - ]) - }, -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -let OrphansModel = { - entries: [], - - async reload() { - this.entries = await call('orphans', {}) - }, -} - -let OrphansReplacement = { - view(vnode) { - const info = vnode.attrs.info - if (!info) - return [] - - return [ - ` → `, - m(m.route.Link, {href: `/view/${info.sha1}`}, - m(Thumbnail, {info})), - `${info.tags} tags`, - ] - }, -} - -let OrphansRow = { - view(vnode) { - const info = vnode.attrs.info - return m('.row', [ - // It might not load, but still allow tag viewing. - m(m.route.Link, {href: `/view/${info.sha1}`}, - m(Thumbnail, {info})), - `${info.tags} tags`, - m(OrphansReplacement, {info: info.replacement}), - ]) - }, -} - -let OrphansList = { - // See BrowseView. - oncreate(vnode) { vnode.dom.focus() }, - - view(vnode) { - let children = (OrphansModel.entries.length == 0) - ? "No orphans" - : OrphansModel.entries.map(info => [ - m("h2", info.lastPath), - m(OrphansRow, {info}), - ]) - return m('.orphans[tabindex=0]', {}, children) - }, -} - -let Orphans = { - oninit(vnode) { - OrphansModel.reload() - }, - - view(vnode) { - return m('.container', {}, [ - m(Header), - m('.body', {}, m(OrphansList)), - ]) - }, -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - window.addEventListener('load', () => { m.route(document.body, "/browse/", { // The path doesn't need to be escaped, perhaps change that (":key..."). @@ -554,6 +554,6 @@ window.addEventListener('load', () => { "/view/:key": View, "/similar/:key": Similar, - "/search/:space/:tag": undefined, + "/search/:key": undefined, }) }) -- cgit v1.2.3-70-g09d2