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  }