code.gitea.io/gitea@v1.22.3/models/user/avatar.go (about) 1 // Copyright 2020 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package user 5 6 import ( 7 "context" 8 "crypto/md5" 9 "fmt" 10 "image/png" 11 "io" 12 13 "code.gitea.io/gitea/models/avatars" 14 "code.gitea.io/gitea/models/db" 15 "code.gitea.io/gitea/modules/avatar" 16 "code.gitea.io/gitea/modules/httplib" 17 "code.gitea.io/gitea/modules/log" 18 "code.gitea.io/gitea/modules/setting" 19 "code.gitea.io/gitea/modules/storage" 20 ) 21 22 // CustomAvatarRelativePath returns user custom avatar relative path. 23 func (u *User) CustomAvatarRelativePath() string { 24 return u.Avatar 25 } 26 27 // GenerateRandomAvatar generates a random avatar for user. 28 func GenerateRandomAvatar(ctx context.Context, u *User) error { 29 seed := u.Email 30 if len(seed) == 0 { 31 seed = u.Name 32 } 33 34 img, err := avatar.RandomImage([]byte(seed)) 35 if err != nil { 36 return fmt.Errorf("RandomImage: %w", err) 37 } 38 39 u.Avatar = avatars.HashEmail(seed) 40 41 // Don't share the images so that we can delete them easily 42 if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error { 43 if err := png.Encode(w, img); err != nil { 44 log.Error("Encode: %v", err) 45 } 46 return err 47 }); err != nil { 48 return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err) 49 } 50 51 if _, err := db.GetEngine(ctx).ID(u.ID).Cols("avatar").Update(u); err != nil { 52 return err 53 } 54 55 log.Info("New random avatar created: %d", u.ID) 56 return nil 57 } 58 59 // AvatarLinkWithSize returns a link to the user's avatar with size. size <= 0 means default size 60 func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string { 61 if u.IsGhost() { 62 return avatars.DefaultAvatarLink() 63 } 64 65 useLocalAvatar := false 66 autoGenerateAvatar := false 67 68 disableGravatar := setting.Config().Picture.DisableGravatar.Value(ctx) 69 70 switch { 71 case u.UseCustomAvatar: 72 useLocalAvatar = true 73 case disableGravatar, setting.OfflineMode: 74 useLocalAvatar = true 75 autoGenerateAvatar = true 76 } 77 78 if useLocalAvatar { 79 if u.Avatar == "" && autoGenerateAvatar { 80 if err := GenerateRandomAvatar(ctx, u); err != nil { 81 log.Error("GenerateRandomAvatar: %v", err) 82 } 83 } 84 if u.Avatar == "" { 85 return avatars.DefaultAvatarLink() 86 } 87 return avatars.GenerateUserAvatarImageLink(u.Avatar, size) 88 } 89 return avatars.GenerateEmailAvatarFastLink(ctx, u.AvatarEmail, size) 90 } 91 92 // AvatarLink returns the full avatar url with http host. 93 // TODO: refactor it to a relative URL, but it is still used in API response at the moment 94 func (u *User) AvatarLink(ctx context.Context) string { 95 relLink := u.AvatarLinkWithSize(ctx, 0) // it can't be empty 96 return httplib.MakeAbsoluteURL(ctx, relLink) 97 } 98 99 // IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data 100 func (u *User) IsUploadAvatarChanged(data []byte) bool { 101 if !u.UseCustomAvatar || len(u.Avatar) == 0 { 102 return true 103 } 104 avatarID := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", u.ID, md5.Sum(data))))) 105 return u.Avatar != avatarID 106 } 107 108 // ExistsWithAvatarAtStoragePath returns true if there is a user with this Avatar 109 func ExistsWithAvatarAtStoragePath(ctx context.Context, storagePath string) (bool, error) { 110 // See func (u *User) CustomAvatarRelativePath() 111 // u.Avatar is used directly as the storage path - therefore we can check for existence directly using the path 112 return db.GetEngine(ctx).Where("`avatar`=?", storagePath).Exist(new(User)) 113 }