code.gitea.io/gitea@v1.21.7/services/repository/avatar.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package repository 5 6 import ( 7 "context" 8 "fmt" 9 "io" 10 "strconv" 11 "strings" 12 13 "code.gitea.io/gitea/models/db" 14 repo_model "code.gitea.io/gitea/models/repo" 15 "code.gitea.io/gitea/modules/avatar" 16 "code.gitea.io/gitea/modules/log" 17 "code.gitea.io/gitea/modules/storage" 18 ) 19 20 // UploadAvatar saves custom avatar for repository. 21 // FIXME: split uploads to different subdirs in case we have massive number of repos. 22 func UploadAvatar(ctx context.Context, repo *repo_model.Repository, data []byte) error { 23 avatarData, err := avatar.ProcessAvatarImage(data) 24 if err != nil { 25 return err 26 } 27 28 newAvatar := avatar.HashAvatar(repo.ID, data) 29 if repo.Avatar == newAvatar { // upload the same picture 30 return nil 31 } 32 33 ctx, committer, err := db.TxContext(ctx) 34 if err != nil { 35 return err 36 } 37 defer committer.Close() 38 39 oldAvatarPath := repo.CustomAvatarRelativePath() 40 41 // Users can upload the same image to other repo - prefix it with ID 42 // Then repo will be removed - only it avatar file will be removed 43 repo.Avatar = newAvatar 44 if err := repo_model.UpdateRepositoryCols(ctx, repo, "avatar"); err != nil { 45 return fmt.Errorf("UploadAvatar: Update repository avatar: %w", err) 46 } 47 48 if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error { 49 _, err := w.Write(avatarData) 50 return err 51 }); err != nil { 52 return fmt.Errorf("UploadAvatar %s failed: Failed to remove old repo avatar %s: %w", repo.RepoPath(), newAvatar, err) 53 } 54 55 if len(oldAvatarPath) > 0 { 56 if err := storage.RepoAvatars.Delete(oldAvatarPath); err != nil { 57 return fmt.Errorf("UploadAvatar: Failed to remove old repo avatar %s: %w", oldAvatarPath, err) 58 } 59 } 60 61 return committer.Commit() 62 } 63 64 // DeleteAvatar deletes the repos's custom avatar. 65 func DeleteAvatar(ctx context.Context, repo *repo_model.Repository) error { 66 // Avatar not exists 67 if len(repo.Avatar) == 0 { 68 return nil 69 } 70 71 avatarPath := repo.CustomAvatarRelativePath() 72 log.Trace("DeleteAvatar[%d]: %s", repo.ID, avatarPath) 73 74 ctx, committer, err := db.TxContext(ctx) 75 if err != nil { 76 return err 77 } 78 defer committer.Close() 79 80 repo.Avatar = "" 81 if err := repo_model.UpdateRepositoryCols(ctx, repo, "avatar"); err != nil { 82 return fmt.Errorf("DeleteAvatar: Update repository avatar: %w", err) 83 } 84 85 if err := storage.RepoAvatars.Delete(avatarPath); err != nil { 86 return fmt.Errorf("DeleteAvatar: Failed to remove %s: %w", avatarPath, err) 87 } 88 89 return committer.Commit() 90 } 91 92 // RemoveRandomAvatars removes the randomly generated avatars that were created for repositories 93 func RemoveRandomAvatars(ctx context.Context) error { 94 return db.Iterate(ctx, nil, func(ctx context.Context, repository *repo_model.Repository) error { 95 select { 96 case <-ctx.Done(): 97 return db.ErrCancelledf("before random avatars removed for %s", repository.FullName()) 98 default: 99 } 100 stringifiedID := strconv.FormatInt(repository.ID, 10) 101 if repository.Avatar == stringifiedID { 102 return DeleteAvatar(ctx, repository) 103 } 104 return nil 105 }) 106 } 107 108 // generateAvatar generates the avatar from a template repository 109 func generateAvatar(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { 110 generateRepo.Avatar = strings.Replace(templateRepo.Avatar, strconv.FormatInt(templateRepo.ID, 10), strconv.FormatInt(generateRepo.ID, 10), 1) 111 if _, err := storage.Copy(storage.RepoAvatars, generateRepo.CustomAvatarRelativePath(), storage.RepoAvatars, templateRepo.CustomAvatarRelativePath()); err != nil { 112 return err 113 } 114 115 return repo_model.UpdateRepositoryCols(ctx, generateRepo, "avatar") 116 }