From 529d8b616b7ec1417c15fa7f55669d9012297377 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Mon, 12 Jul 2021 19:29:32 +0200 Subject: [PATCH] Further improve counting of media uses --- database.go | 8 +++++++ editorFiles.go | 16 +++++++++++++ postsDb.go | 45 +++++++++++++++++++++++++++++------- postsDb_test.go | 7 ++++-- render.go | 31 ++++++++++++------------- templates/editorfiles.gohtml | 3 ++- 6 files changed, 83 insertions(+), 27 deletions(-) diff --git a/database.go b/database.go index 4bf9b37..39de4a3 100644 --- a/database.go +++ b/database.go @@ -185,6 +185,10 @@ func (db *database) exec(query string, args ...interface{}) (sql.Result, error) } func (db *database) query(query string, args ...interface{}) (*sql.Rows, error) { + // Check if prepared cache should be skipped + if len(args) > 0 && args[0] == dbNoCache { + return db.db.Query(query, args[1:]...) + } // Use prepared statement st, _ := db.prepare(query) if st != nil { @@ -195,6 +199,10 @@ func (db *database) query(query string, args ...interface{}) (*sql.Rows, error) } func (db *database) queryRow(query string, args ...interface{}) (*sql.Row, error) { + // Check if prepared cache should be skipped + if len(args) > 0 && args[0] == dbNoCache { + return db.db.QueryRow(query, args[1:]...), nil + } // Use prepared statement st, _ := db.prepare(query) if st != nil { diff --git a/editorFiles.go b/editorFiles.go index 5b6d245..86a87ab 100644 --- a/editorFiles.go +++ b/editorFiles.go @@ -3,6 +3,8 @@ package main import ( "net/http" "sort" + + "github.com/thoas/go-funk" ) func (a *goBlog) serveEditorFiles(w http.ResponseWriter, r *http.Request) { @@ -15,12 +17,26 @@ func (a *goBlog) serveEditorFiles(w http.ResponseWriter, r *http.Request) { sort.Slice(files, func(i, j int) bool { return files[i].Time.After(files[j].Time) }) + // Find uses + fileNames, ok := funk.Map(files, func(f *mediaFile) string { + return f.Name + }).([]string) + if !ok { + a.serveError(w, r, "Failed to get file names", http.StatusInternalServerError) + return + } + uses, err := a.db.usesOfMediaFile(fileNames...) + if err != nil { + a.serveError(w, r, err.Error(), http.StatusInternalServerError) + return + } // Serve HTML blog := r.Context().Value(blogContextKey).(string) a.render(w, r, templateEditorFiles, &renderData{ BlogString: blog, Data: map[string]interface{}{ "Files": files, + "Uses": uses, }, }) } diff --git a/postsDb.go b/postsDb.go index 0092641..30fdf1d 100644 --- a/postsDb.go +++ b/postsDb.go @@ -471,15 +471,44 @@ func (d *database) allPublishedDates(blog string) (dates []publishedDate, err er return } -func (db *database) usesOfMediaFile(name string) (count int, err error) { - query := "select count(distinct path) from (select path from posts_fts where content match @fts_name union all select path from post_parameters where instr(value, @name) > 0)" - row, err := db.queryRow(query, sql.Named("fts_name", fmt.Sprintf("\"%s\"", name)), sql.Named("name", name)) - if err != nil { - return 0, err +const mediaUseSql = ` +with mediafiles (name) as (values %s) +select name, count(path) as count from ( + select distinct m.name, p.path + from mediafiles m, post_parameters p + where instr(p.value, m.name) > 0 + union + select distinct m.name, p.path + from mediafiles m, posts_fts p + where p.content match '"' || m.name || '"' +) +group by name; +` + +func (db *database) usesOfMediaFile(names ...string) (counts map[string]int, err error) { + sqlArgs := []interface{}{dbNoCache} + nameValues := "" + for i, n := range names { + if i > 0 { + nameValues += ", " + } + named := fmt.Sprintf("name%v", i) + nameValues += fmt.Sprintf("(@%s)", named) + sqlArgs = append(sqlArgs, sql.Named(named, n)) } - err = row.Scan(&count) + rows, err := db.query(fmt.Sprintf(mediaUseSql, nameValues), sqlArgs...) if err != nil { - return 0, err + return nil, err } - return count, nil + counts = map[string]int{} + var name string + var count int + for rows.Next() { + err = rows.Scan(&name, &count) + if err != nil { + return nil, err + } + counts[name] = count + } + return counts, nil } diff --git a/postsDb_test.go b/postsDb_test.go index 5eb8023..561827b 100644 --- a/postsDb_test.go +++ b/postsDb_test.go @@ -338,7 +338,10 @@ func Test_usesOfMediaFile(t *testing.T) { }, &postCreationOptions{new: true}) require.NoError(t, err) - count, err := app.db.usesOfMediaFile("test.jpg") + counts, err := app.db.usesOfMediaFile("test.jpg") require.NoError(t, err) - assert.Equal(t, 2, count) + assert.Len(t, counts, 1) + if assert.NotNil(t, counts["test.jpg"]) { + assert.Equal(t, 2, counts["test.jpg"]) + } } diff --git a/render.go b/render.go index 73925d3..9d5fd5b 100644 --- a/render.go +++ b/render.go @@ -56,22 +56,21 @@ func (a *goBlog) initRendering() error { "translations": a.postTranslations, "shorturl": a.shortPostURL, // Others - "dateformat": dateFormat, - "isodate": isoDateFormat, - "unixtodate": unixToLocalDateString, - "now": localNowString, - "asset": a.assetFileName, - "string": a.ts.GetTemplateStringVariantFunc(), - "include": a.includeRenderedTemplate, - "urlize": urlize, - "sort": sortedStrings, - "absolute": a.getFullAddress, - "mentions": a.db.getWebmentionsByAddress, - "geotitle": a.geoTitle, - "geolink": geoOSMLink, - "opensearch": openSearchUrl, - "mbytes": mBytesString, - "mediafileuses": a.db.usesOfMediaFile, + "dateformat": dateFormat, + "isodate": isoDateFormat, + "unixtodate": unixToLocalDateString, + "now": localNowString, + "asset": a.assetFileName, + "string": a.ts.GetTemplateStringVariantFunc(), + "include": a.includeRenderedTemplate, + "urlize": urlize, + "sort": sortedStrings, + "absolute": a.getFullAddress, + "mentions": a.db.getWebmentionsByAddress, + "geotitle": a.geoTitle, + "geolink": geoOSMLink, + "opensearch": openSearchUrl, + "mbytes": mBytesString, } baseTemplate, err := template.New("base").Funcs(templateFunctions).ParseFiles(path.Join(templatesDir, templateBase+templatesExt)) if err != nil { diff --git a/templates/editorfiles.gohtml b/templates/editorfiles.gohtml index 3d864ab..9974acb 100644 --- a/templates/editorfiles.gohtml +++ b/templates/editorfiles.gohtml @@ -9,8 +9,9 @@ {{ if .Data.Files }}