violet/favicons/favicons.go

115 lines
2.8 KiB
Go
Raw Permalink Normal View History

2023-04-22 18:11:21 +01:00
package favicons
2023-04-22 22:18:39 +01:00
import (
"context"
_ "embed"
2023-04-22 22:18:39 +01:00
"errors"
"fmt"
"github.com/1f349/violet/database"
2024-05-13 19:33:33 +01:00
"github.com/1f349/violet/logger"
2024-04-20 16:17:32 +01:00
"github.com/mrmelon54/rescheduler"
2023-04-22 22:18:39 +01:00
"golang.org/x/sync/errgroup"
"sync"
)
2024-05-13 19:33:33 +01:00
var Logger = logger.Logger.WithPrefix("Violet Favicons")
2023-04-22 22:18:39 +01:00
var ErrFaviconNotFound = errors.New("favicon not found")
2023-04-24 01:35:23 +01:00
// Favicons is a dynamic favicon generator which supports overwriting favicons
type Favicons struct {
db *database.Queries
2023-04-24 01:35:23 +01:00
cmd string
cLock *sync.RWMutex
faviconMap map[string]*FaviconList
r *rescheduler.Rescheduler
2023-04-24 01:35:23 +01:00
}
// New creates a new dynamic favicon generator
func New(db *database.Queries, inkscapeCmd string) *Favicons {
2023-04-22 22:18:39 +01:00
f := &Favicons{
db: db,
cmd: inkscapeCmd,
cLock: &sync.RWMutex{},
faviconMap: make(map[string]*FaviconList),
}
f.r = rescheduler.NewRescheduler(f.threadCompile)
2023-04-22 22:18:39 +01:00
// run compile to get the initial data
f.Compile()
return f
}
2023-04-24 01:35:23 +01:00
// GetIcons returns the favicon list for the provided host or nil if no
// icon is found or generated
func (f *Favicons) GetIcons(host string) *FaviconList {
// read lock for safety
2023-04-22 22:18:39 +01:00
f.cLock.RLock()
defer f.cLock.RUnlock()
2023-04-24 01:35:23 +01:00
// return value from map
return f.faviconMap[host]
2023-04-22 22:18:39 +01:00
}
// Compile downloads the list of favicon mappings from the database and loads
// them and the target favicons into memory for faster lookups
//
// This method makes use of the rescheduler instead of just ignoring multiple
// calls.
func (f *Favicons) Compile() {
f.r.Run()
}
func (f *Favicons) threadCompile() {
// new map
favicons := make(map[string]*FaviconList)
// compile map and check errors
err := f.internalCompile(favicons)
if err != nil {
// log compile errors
2024-05-13 19:33:33 +01:00
Logger.Info("Compile failed", "err", err)
return
}
// lock while replacing the map
f.cLock.Lock()
f.faviconMap = favicons
f.cLock.Unlock()
}
2023-04-24 01:35:23 +01:00
// internalCompile is a hidden internal method for loading and generating all
// favicons.
func (f *Favicons) internalCompile(m map[string]*FaviconList) error {
2023-04-22 22:18:39 +01:00
// query all rows in database
rows, err := f.db.GetFavicons(context.Background())
2023-04-22 22:18:39 +01:00
if err != nil {
return fmt.Errorf("failed to prepare rows: %w", err)
2023-04-22 22:18:39 +01:00
}
2023-04-24 01:35:23 +01:00
// loop over rows and scan in data using error group to catch errors
2023-04-22 22:18:39 +01:00
var g errgroup.Group
for _, row := range rows {
2023-04-24 01:35:23 +01:00
// create favicon list for this row
2023-04-22 22:18:39 +01:00
l := &FaviconList{
Ico: CreateFaviconImage(row.Ico),
Png: CreateFaviconImage(row.Png),
Svg: CreateFaviconImage(row.Svg),
2023-04-22 22:18:39 +01:00
}
2023-04-24 01:35:23 +01:00
// save the favicon list to the map
m[row.Host] = l
2023-04-24 01:35:23 +01:00
// run the pre-process in a separate goroutine
2023-04-22 22:18:39 +01:00
g.Go(func() error {
return l.PreProcess(f.convertSvgToPng)
})
}
return g.Wait()
}
2023-04-24 01:35:23 +01:00
// convertSvgToPng calls svg2png which runs inkscape in a subprocess
2023-04-22 22:18:39 +01:00
func (f *Favicons) convertSvgToPng(in []byte) ([]byte, error) {
return svg2png(f.cmd, in)
}