Fix blogstats

This commit is contained in:
Jan-Lukas Else 2021-07-01 21:30:41 +02:00
parent f04e731efc
commit 87f574991e
2 changed files with 57 additions and 45 deletions

View File

@ -1,10 +1,12 @@
package main package main
import ( import (
"bytes"
"database/sql" "database/sql"
"encoding/json" "encoding/gob"
"log"
"net/http" "net/http"
servertiming "github.com/mitchellh/go-server-timing"
) )
func (a *goBlog) initBlogStats() { func (a *goBlog) initBlogStats() {
@ -31,9 +33,11 @@ func (a *goBlog) serveBlogStats(w http.ResponseWriter, r *http.Request) {
func (a *goBlog) serveBlogStatsTable(w http.ResponseWriter, r *http.Request) { func (a *goBlog) serveBlogStatsTable(w http.ResponseWriter, r *http.Request) {
blog := r.Context().Value(blogContextKey).(string) blog := r.Context().Value(blogContextKey).(string)
t := servertiming.FromContext(r.Context()).NewMetric("bs").Start()
data, err, _ := a.blogStatsCacheGroup.Do(blog, func() (interface{}, error) { data, err, _ := a.blogStatsCacheGroup.Do(blog, func() (interface{}, error) {
return a.db.getBlogStats(blog) return a.db.getBlogStats(blog)
}) })
t.Stop()
if err != nil { if err != nil {
a.serveError(w, r, err.Error(), http.StatusInternalServerError) a.serveError(w, r, err.Error(), http.StatusInternalServerError)
return return
@ -49,13 +53,19 @@ const blogStatsSql = `
with filtered as ( with filtered as (
select select
path, path,
coalesce(published, '') as pub, pub,
substr(published, 1, 4) as year, substr(pub, 1, 4) as year,
substr(published, 6, 2) as month, substr(pub, 6, 2) as month,
wordcount(coalesce(content, '')) as words, wordcount(content) as words,
charcount(coalesce(content, '')) as chars charcount(content) as chars
from posts from (
where status = @status and blog = @blog select
path,
coalesce(published, '') as pub,
mdtext(coalesce(content, '')) as content
from posts
where status = @status and blog = @blog
)
) )
select * select *
from ( from (
@ -116,7 +126,18 @@ from (
); );
` `
func (db *database) getBlogStats(blog string) (data map[string]interface{}, err error) { type blogStatsRow struct {
Name, Posts, Chars, Words, WordsPerPost string
}
type blogStatsData struct {
Total blogStatsRow
NoDate blogStatsRow
Years []blogStatsRow
Months map[string][]blogStatsRow
}
func (db *database) getBlogStats(blog string) (data *blogStatsData, err error) {
// Check cache // Check cache
if stats := db.loadBlogStatsCache(blog); stats != nil { if stats := db.loadBlogStatsCache(blog); stats != nil {
return stats, nil return stats, nil
@ -124,18 +145,13 @@ func (db *database) getBlogStats(blog string) (data map[string]interface{}, err
// Prevent creating posts while getting stats // Prevent creating posts while getting stats
db.pcm.Lock() db.pcm.Lock()
defer db.pcm.Unlock() defer db.pcm.Unlock()
// Stats type to hold the stats data for a single row
type statsTableType struct {
Name, Posts, Chars, Words, WordsPerPost string
}
// Scan objects // Scan objects
currentStats := statsTableType{} currentStats := blogStatsRow{}
var currentMonth, currentYear string var currentMonth, currentYear string
// Data to later return // Data to later return
var total statsTableType data = &blogStatsData{
var noDate statsTableType Months: map[string][]blogStatsRow{},
var years []statsTableType }
months := map[string][]statsTableType{}
// Query and scan // Query and scan
rows, err := db.query(blogStatsSql, sql.Named("status", statusPublished), sql.Named("blog", blog)) rows, err := db.query(blogStatsSql, sql.Named("status", statusPublished), sql.Named("blog", blog))
if err != nil { if err != nil {
@ -144,21 +160,21 @@ func (db *database) getBlogStats(blog string) (data map[string]interface{}, err
for rows.Next() { for rows.Next() {
err = rows.Scan(&currentYear, &currentMonth, &currentStats.Posts, &currentStats.Words, &currentStats.Chars, &currentStats.WordsPerPost) err = rows.Scan(&currentYear, &currentMonth, &currentStats.Posts, &currentStats.Words, &currentStats.Chars, &currentStats.WordsPerPost)
if currentYear == "A" && currentMonth == "A" { if currentYear == "A" && currentMonth == "A" {
total = statsTableType{ data.Total = blogStatsRow{
Posts: currentStats.Posts, Posts: currentStats.Posts,
Words: currentStats.Words, Words: currentStats.Words,
Chars: currentStats.Chars, Chars: currentStats.Chars,
WordsPerPost: currentStats.WordsPerPost, WordsPerPost: currentStats.WordsPerPost,
} }
} else if currentYear == "N" && currentMonth == "N" { } else if currentYear == "N" && currentMonth == "N" {
noDate = statsTableType{ data.NoDate = blogStatsRow{
Posts: currentStats.Posts, Posts: currentStats.Posts,
Words: currentStats.Words, Words: currentStats.Words,
Chars: currentStats.Chars, Chars: currentStats.Chars,
WordsPerPost: currentStats.WordsPerPost, WordsPerPost: currentStats.WordsPerPost,
} }
} else if currentMonth == "A" { } else if currentMonth == "A" {
years = append(years, statsTableType{ data.Years = append(data.Years, blogStatsRow{
Name: currentYear, Name: currentYear,
Posts: currentStats.Posts, Posts: currentStats.Posts,
Words: currentStats.Words, Words: currentStats.Words,
@ -166,7 +182,7 @@ func (db *database) getBlogStats(blog string) (data map[string]interface{}, err
WordsPerPost: currentStats.WordsPerPost, WordsPerPost: currentStats.WordsPerPost,
}) })
} else { } else {
months[currentYear] = append(months[currentYear], statsTableType{ data.Months[currentYear] = append(data.Months[currentYear], blogStatsRow{
Name: currentMonth, Name: currentMonth,
Posts: currentStats.Posts, Posts: currentStats.Posts,
Words: currentStats.Words, Words: currentStats.Words,
@ -175,29 +191,25 @@ func (db *database) getBlogStats(blog string) (data map[string]interface{}, err
}) })
} }
} }
data = map[string]interface{}{
"total": total,
"years": years,
"withoutdate": noDate,
"months": months,
}
db.cacheBlogStats(blog, data) db.cacheBlogStats(blog, data)
return data, nil return data, nil
} }
func (db *database) cacheBlogStats(blog string, stats map[string]interface{}) { func (db *database) cacheBlogStats(blog string, stats *blogStatsData) {
jb, _ := json.Marshal(stats) var buf bytes.Buffer
_ = db.cachePersistently("blogstats_"+blog, jb) _ = gob.NewEncoder(&buf).Encode(stats)
_ = db.cachePersistently("blogstats_"+blog, buf.Bytes())
} }
func (db *database) loadBlogStatsCache(blog string) (stats map[string]interface{}) { func (db *database) loadBlogStatsCache(blog string) (stats *blogStatsData) {
data, err := db.retrievePersistentCache("blogstats_" + blog) data, err := db.retrievePersistentCache("blogstats_" + blog)
if err != nil || data == nil { if err != nil || data == nil {
return nil return nil
} }
err = json.Unmarshal(data, &stats) stats = &blogStatsData{}
err = gob.NewDecoder(bytes.NewReader(data)).Decode(stats)
if err != nil { if err != nil {
log.Println(err) return nil
} }
return stats return stats
} }

View File

@ -10,8 +10,8 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{ $months := .Data.months }} {{ $months := .Data.Months }}
{{ range $year := .Data.years }} {{ range $year := .Data.Years }}
<tr class="statsyear" data-year="{{ $year.Name }}"> <tr class="statsyear" data-year="{{ $year.Name }}">
<td class="tal">{{ $year.Name }}</td> <td class="tal">{{ $year.Name }}</td>
<td class="tar">{{ $year.Posts }}</td> <td class="tar">{{ $year.Posts }}</td>
@ -31,17 +31,17 @@
{{ end }} {{ end }}
<tr> <tr>
<td class="tal">{{ string .Blog.Lang "withoutdate" }}</td> <td class="tal">{{ string .Blog.Lang "withoutdate" }}</td>
<td class="tar">{{ .Data.withoutdate.Posts }}</td> <td class="tar">{{ .Data.NoDate.Posts }}</td>
<td class="tar">{{ .Data.withoutdate.Chars }}</td> <td class="tar">{{ .Data.NoDate.Chars }}</td>
<td class="tar">{{ .Data.withoutdate.Words }}</td> <td class="tar">{{ .Data.NoDate.Words }}</td>
<td class="tar">{{ .Data.withoutdate.WordsPerPost }}</td> <td class="tar">{{ .Data.NoDate.WordsPerPost }}</td>
</tr> </tr>
<tr> <tr>
<td class="tal"><b>{{ string .Blog.Lang "total" }}</b></td> <td class="tal"><b>{{ string .Blog.Lang "total" }}</b></td>
<td class="tar">{{ .Data.total.Posts }}</td> <td class="tar">{{ .Data.Total.Posts }}</td>
<td class="tar">{{ .Data.total.Chars }}</td> <td class="tar">{{ .Data.Total.Chars }}</td>
<td class="tar">{{ .Data.total.Words }}</td> <td class="tar">{{ .Data.Total.Words }}</td>
<td class="tar">{{ .Data.total.WordsPerPost }}</td> <td class="tar">{{ .Data.Total.WordsPerPost }}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>