diff --git a/pkg/modules/avatar/upload/upload.go b/pkg/modules/avatar/upload/upload.go index 97f53d5bd..79996a346 100644 --- a/pkg/modules/avatar/upload/upload.go +++ b/pkg/modules/avatar/upload/upload.go @@ -19,6 +19,7 @@ package upload import ( "bytes" "encoding/base64" + "encoding/gob" "fmt" "image" "image/png" @@ -34,6 +35,10 @@ import ( "xorm.io/xorm" ) +func init() { + gob.Register(CachedAvatar{}) +} + // Provider represents the upload avatar provider type Provider struct { } @@ -53,76 +58,48 @@ type CachedAvatar struct { // GetAvatar returns an uploaded user avatar func (p *Provider) GetAvatar(u *user.User, size int64) (avatar []byte, mimeType string, err error) { - return p.getAvatarWithDepth(u, size, 0) -} - -func (p *Provider) getAvatarWithDepth(u *user.User, size int64, recursionDepth int) (avatar []byte, mimeType string, err error) { - // Prevent infinite recursion - max 3 attempts - if recursionDepth >= 3 { - return nil, "", fmt.Errorf("maximum recursion depth reached while generating avatar for user %d, size %d", u.ID, size) - } - cacheKey := CacheKeyPrefix + strconv.Itoa(int(u.ID)) + "_" + strconv.FormatInt(size, 10) - result, err := keyvalue.Remember(cacheKey, func() (any, error) { + cachedAvatar, err := keyvalue.RememberValue(cacheKey, func() (CachedAvatar, error) { log.Debugf("Uploaded avatar for user %d and size %d not cached, resizing and caching.", u.ID, size) - // Check if user has an avatar file ID if u.AvatarFileID == 0 { - return nil, fmt.Errorf("user %d has no avatar file", u.ID) + return CachedAvatar{}, fmt.Errorf("user %d has no avatar file", u.ID) } - // If we get this far, the avatar is either not cached at all or not in this size f := &files.File{ID: u.AvatarFileID} if err := f.LoadFileByID(); err != nil { - return nil, err + return CachedAvatar{}, err } if err := f.LoadFileMetaByID(); err != nil { - return nil, err + return CachedAvatar{}, err } img, _, err := image.Decode(f.File) if err != nil { - return nil, err + return CachedAvatar{}, err } resizedImg := imaging.Resize(img, 0, int(size), imaging.Lanczos) buf := &bytes.Buffer{} if err := png.Encode(buf, resizedImg); err != nil { - return nil, err + return CachedAvatar{}, err } - avatar, err = io.ReadAll(buf) + avatarBytes, err := io.ReadAll(buf) if err != nil { - return nil, err + return CachedAvatar{}, err } - // Always use image/png for resized avatars since we're encoding with png - mimeType = "image/png" return CachedAvatar{ - Content: avatar, - MimeType: mimeType, + Content: avatarBytes, + MimeType: "image/png", }, nil }) if err != nil { return nil, "", err } - // Safe type assertion to handle cases where cached data might be corrupted or in legacy format - cachedAvatar, ok := result.(CachedAvatar) - if !ok { - // Log the type mismatch with the actual stored value for debugging - log.Errorf("Invalid cached avatar type for user %d, size %d. Expected CachedAvatar, got %T with value: %+v. Clearing cache and regenerating.", u.ID, size, result, result) - - // Clear the invalid cache entry - if err := keyvalue.Del(cacheKey); err != nil { - log.Errorf("Failed to clear invalid cache entry for key %s: %v", cacheKey, err) - } - - // Regenerate the avatar by calling the function again (without the corrupted cache) - return p.getAvatarWithDepth(u, size, recursionDepth+1) - } - return cachedAvatar.Content, cachedAvatar.MimeType, nil } diff --git a/pkg/modules/background/unsplash/unsplash.go b/pkg/modules/background/unsplash/unsplash.go index b9b5482c3..afeed3496 100644 --- a/pkg/modules/background/unsplash/unsplash.go +++ b/pkg/modules/background/unsplash/unsplash.go @@ -19,6 +19,7 @@ package unsplash import ( "bytes" "context" + "encoding/gob" "encoding/json" "fmt" "io" @@ -41,6 +42,10 @@ import ( "code.vikunja.io/api/pkg/web" ) +func init() { + gob.Register(Photo{}) +} + const ( unsplashAPIURL = `https://api.unsplash.com/` cachePrefix = `unsplash_photo_` @@ -126,7 +131,7 @@ func getImageID(fullURL string) string { // Gets an unsplash photo either from cache or directly from the unsplash api func getUnsplashPhotoInfoByID(photoID string) (photo *Photo, err error) { - result, err := keyvalue.Remember(cachePrefix+photoID, func() (any, error) { + p, err := keyvalue.RememberValue(cachePrefix+photoID, func() (Photo, error) { log.Debugf("Image information for unsplash photo %s not cached, requesting from unsplash...", photoID) photo := &Photo{} err := doGet("photos/"+photoID, photo) @@ -136,8 +141,6 @@ func getUnsplashPhotoInfoByID(photoID string) (photo *Photo, err error) { return nil, err } - p := result.(Photo) - return &p, nil }