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  }