Add GTK application implementation

This commit is contained in:
Olli 2026-02-26 10:41:33 +01:00
commit b7c0307f6b
29 changed files with 2564 additions and 266 deletions

188
internal/gui/coverloader.go Normal file
View file

@ -0,0 +1,188 @@
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()
}