package gui import ( "fmt" "log" "os" "path/filepath" "strconv" "sync" "github.com/diamondburned/gotk4/pkg/core/gerror" "github.com/diamondburned/gotk4/pkg/gdkpixbuf/v2" "github.com/diamondburned/gotk4/pkg/glib/v2" "suruatoel.xyz/mcg/internal/client" "suruatoel.xyz/mcg/internal/common" ) type CoverLoader struct { hostDir string size int mutex sync.Mutex } func NewCoverLoader(host string) *CoverLoader { loader := CoverLoader{} loader.hostDir = loader.getHostDir(host) loader.size = loader.readCacheSize() return &loader } func (l *CoverLoader) SetSize(size int) { if l.size != size { log.Println("cache size has changed from", l.size, "to", size) l.clearCache() l.writeCacheSize(size) l.size = size } } func (l *CoverLoader) LoadThumbnail(album *common.Album) *gdkpixbuf.Pixbuf { // Load cover from cache pixbuf := l.getCached(album) if pixbuf != nil { return pixbuf } // Load cover from server albumart := client.Instance().LoadAlbumartNow(album) if albumart != nil { pixbuf = l.loadImage(albumart) } if pixbuf != nil { pixbuf = pixbuf.ScaleSimple(l.size, l.size, gdkpixbuf.InterpHyper) if pixbuf != nil { l.setCached(album, pixbuf) } } return pixbuf } func (l *CoverLoader) getHostDir(host string) string { cacheDir, cacheDirErr := os.UserCacheDir() if cacheDirErr != nil { log.Println("failed to detect user cache directory:", cacheDirErr) return "" } hostDir := filepath.Join(cacheDir, "mcg", host) return hostDir } func (l *CoverLoader) ensureHostDir() { hostDirErr := os.MkdirAll(l.hostDir, 0o750) if hostDirErr != nil { log.Println("failed to create cache folder for host", l.hostDir, ":", hostDirErr) } } func (l *CoverLoader) readCacheSize() int { sizeFile := l.getCacheSizeFile() if sizeFile == "" { return 100 } l.mutex.Lock() defer l.mutex.Unlock() content, readErr := os.ReadFile(sizeFile) if readErr != nil { log.Println("failed to read cache size file for host", l.hostDir, ":", readErr) return 100 } size, sizeErr := strconv.Atoi(string(content[:])) if sizeErr != nil { log.Println("invalid content of cache size file for host", l.hostDir, ":", readErr) } return size } func (l *CoverLoader) writeCacheSize(size int) { l.ensureHostDir() sizeFile := l.getCacheSizeFile() if sizeFile == "" { return } writeErr := os.WriteFile(sizeFile, []byte(fmt.Sprintf("%d", size)), 0o644) if writeErr != nil { log.Println("failed to write cache size file for host", l.hostDir, ":", writeErr) } } func (l *CoverLoader) getCacheFile(album *common.Album) string { if l.hostDir == "" { return "" } return filepath.Join(l.hostDir, album.ID()) } func (l *CoverLoader) getCacheSizeFile() string { if l.hostDir == "" { return "" } return filepath.Join(l.hostDir, "size") } func (l *CoverLoader) clearCache() { if l.hostDir == "" { return } removeErr := os.RemoveAll(l.hostDir) if removeErr != nil { log.Println("failed to clear cache folder for host", l.hostDir, ":", removeErr) } } func (l *CoverLoader) getCached(album *common.Album) *gdkpixbuf.Pixbuf { cacheFile := l.getCacheFile(album) if cacheFile == "" { return nil } pixbuf, pixbufErr := gdkpixbuf.NewPixbufFromFile(cacheFile) if pixbufErr != nil { if !isFileNotFoundError(pixbufErr) { log.Println("failed to read cached thumbnail for host", l.hostDir, ":", pixbufErr) } return nil } return pixbuf } func isFileNotFoundError(err error) bool { if gerr, isGerror := err.(*gerror.GError); isGerror { return gerr.Quark() == uint32(glib.FileErrorQuark()) && gerr.ErrorCode() == int(glib.FileErrorNoent) } return false } func (l *CoverLoader) setCached(album *common.Album, pixbuf *gdkpixbuf.Pixbuf) { cacheFile := l.getCacheFile(album) if cacheFile == "" { return } l.ensureHostDir() pixbuf.Savev(cacheFile, "jpeg", []string{}, []string{}) } func (l *CoverLoader) loadImage(file []byte) *gdkpixbuf.Pixbuf { pixbufLoader, loaderErr := loadPixbuf(file) if loaderErr != nil { log.Println("failed to load image:", loaderErr) return nil } return pixbufLoader.Pixbuf() }