mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 01:34:27 +00:00 
			
		
		
		
	Repository avatar fallback configuration (#7087)
* Only show repository avatar in list when one was selected Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds fallback configuration option for repository avatar Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Implements repository avatar fallback Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Adds admin task for deleting generated repository avatars Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Solve linting issues Signed-off-by: Mario Lubenka <mario.lubenka@googlemail.com> * Save avatar before updating database * Linting * Update models/repo.go Co-Authored-By: zeripath <art27@cantab.net>
This commit is contained in:
		
				
					committed by
					
						
						Lunny Xiao
					
				
			
			
				
	
			
			
			
						parent
						
							356854fc5f
						
					
				
				
					commit
					8eba27c792
				
			@@ -505,6 +505,10 @@ SESSION_LIFE_TIME = 86400
 | 
				
			|||||||
[picture]
 | 
					[picture]
 | 
				
			||||||
AVATAR_UPLOAD_PATH = data/avatars
 | 
					AVATAR_UPLOAD_PATH = data/avatars
 | 
				
			||||||
REPOSITORY_AVATAR_UPLOAD_PATH = data/repo-avatars
 | 
					REPOSITORY_AVATAR_UPLOAD_PATH = data/repo-avatars
 | 
				
			||||||
 | 
					; How Gitea deals with missing repository avatars
 | 
				
			||||||
 | 
					; none = no avatar will be displayed; random = random avatar will be displayed; image = default image will be used
 | 
				
			||||||
 | 
					REPOSITORY_AVATAR_FALLBACK = none
 | 
				
			||||||
 | 
					REPOSITORY_AVATAR_FALLBACK_IMAGE = /img/repo_default.png
 | 
				
			||||||
; Max Width and Height of uploaded avatars.
 | 
					; Max Width and Height of uploaded avatars.
 | 
				
			||||||
; This is to limit the amount of RAM used when resizing the image.
 | 
					; This is to limit the amount of RAM used when resizing the image.
 | 
				
			||||||
AVATAR_MAX_WIDTH = 4096
 | 
					AVATAR_MAX_WIDTH = 4096
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -292,6 +292,11 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
 | 
				
			|||||||
   [http://www.libravatar.org](http://www.libravatar.org)).
 | 
					   [http://www.libravatar.org](http://www.libravatar.org)).
 | 
				
			||||||
- `AVATAR_UPLOAD_PATH`: **data/avatars**: Path to store user avatar image files.
 | 
					- `AVATAR_UPLOAD_PATH`: **data/avatars**: Path to store user avatar image files.
 | 
				
			||||||
- `REPOSITORY_AVATAR_UPLOAD_PATH`: **data/repo-avatars**: Path to store repository avatar image files.
 | 
					- `REPOSITORY_AVATAR_UPLOAD_PATH`: **data/repo-avatars**: Path to store repository avatar image files.
 | 
				
			||||||
 | 
					- `REPOSITORY_AVATAR_FALLBACK`: **none**: How Gitea deals with missing repository avatars
 | 
				
			||||||
 | 
					  - none = no avatar will be displayed
 | 
				
			||||||
 | 
					  - random = random avatar will be generated
 | 
				
			||||||
 | 
					  - image = default image will be used (which is set in `REPOSITORY_AVATAR_DEFAULT_IMAGE`)
 | 
				
			||||||
 | 
					- `REPOSITORY_AVATAR_FALLBACK_IMAGE`: **/img/repo_default.png**: Image used as default repository avatar (if `REPOSITORY_AVATAR_FALLBACK` is set to image and none was uploaded)
 | 
				
			||||||
- `AVATAR_MAX_WIDTH`: **4096**: Maximum avatar image width in pixels.
 | 
					- `AVATAR_MAX_WIDTH`: **4096**: Maximum avatar image width in pixels.
 | 
				
			||||||
- `AVATAR_MAX_HEIGHT`: **3072**: Maximum avatar image height in pixels.
 | 
					- `AVATAR_MAX_HEIGHT`: **3072**: Maximum avatar image height in pixels.
 | 
				
			||||||
- `AVATAR_MAX_FILE_SIZE`: **1048576** (1Mb): Maximum avatar image file size in bytes.
 | 
					- `AVATAR_MAX_FILE_SIZE`: **1048576** (1Mb): Maximum avatar image file size in bytes.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2528,17 +2528,78 @@ func (repo *Repository) CustomAvatarPath() string {
 | 
				
			|||||||
	return filepath.Join(setting.RepositoryAvatarUploadPath, repo.Avatar)
 | 
						return filepath.Join(setting.RepositoryAvatarUploadPath, repo.Avatar)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RelAvatarLink returns a relative link to the user's avatar.
 | 
					// GenerateRandomAvatar generates a random avatar for repository.
 | 
				
			||||||
// The link a sub-URL to this site
 | 
					func (repo *Repository) GenerateRandomAvatar() error {
 | 
				
			||||||
// Since Gravatar support not needed here - just check for image path.
 | 
						return repo.generateRandomAvatar(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (repo *Repository) generateRandomAvatar(e Engine) error {
 | 
				
			||||||
 | 
						idToString := fmt.Sprintf("%d", repo.ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						seed := idToString
 | 
				
			||||||
 | 
						img, err := avatar.RandomImage([]byte(seed))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("RandomImage: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						repo.Avatar = idToString
 | 
				
			||||||
 | 
						if err = os.MkdirAll(filepath.Dir(repo.CustomAvatarPath()), os.ModePerm); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("MkdirAll: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						fw, err := os.Create(repo.CustomAvatarPath())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Create: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer fw.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = png.Encode(fw, img); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Encode: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						log.Info("New random avatar created for repository: %d", repo.ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := e.ID(repo.ID).Cols("avatar").NoAutoTime().Update(repo); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RemoveRandomAvatars removes the randomly generated avatars that were created for repositories
 | 
				
			||||||
 | 
					func RemoveRandomAvatars() error {
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							err error
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						err = x.
 | 
				
			||||||
 | 
							Where("id > 0").BufferSize(setting.IterateBufferSize).
 | 
				
			||||||
 | 
							Iterate(new(Repository),
 | 
				
			||||||
 | 
								func(idx int, bean interface{}) error {
 | 
				
			||||||
 | 
									repository := bean.(*Repository)
 | 
				
			||||||
 | 
									stringifiedID := strconv.FormatInt(repository.ID, 10)
 | 
				
			||||||
 | 
									if repository.Avatar == stringifiedID {
 | 
				
			||||||
 | 
										return repository.DeleteAvatar()
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RelAvatarLink returns a relative link to the repository's avatar.
 | 
				
			||||||
func (repo *Repository) RelAvatarLink() string {
 | 
					func (repo *Repository) RelAvatarLink() string {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// If no avatar - path is empty
 | 
						// If no avatar - path is empty
 | 
				
			||||||
	avatarPath := repo.CustomAvatarPath()
 | 
						avatarPath := repo.CustomAvatarPath()
 | 
				
			||||||
	if len(avatarPath) <= 0 {
 | 
						if len(avatarPath) <= 0 || !com.IsFile(avatarPath) {
 | 
				
			||||||
 | 
							switch mode := setting.RepositoryAvatarFallback; mode {
 | 
				
			||||||
 | 
							case "image":
 | 
				
			||||||
 | 
								return setting.RepositoryAvatarFallbackImage
 | 
				
			||||||
 | 
							case "random":
 | 
				
			||||||
 | 
								if err := repo.GenerateRandomAvatar(); err != nil {
 | 
				
			||||||
 | 
									log.Error("GenerateRandomAvatar: %v", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								// default behaviour: do not display avatar
 | 
				
			||||||
			return ""
 | 
								return ""
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	if !com.IsFile(avatarPath) {
 | 
					 | 
				
			||||||
		return ""
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return setting.AppSubURL + "/repo-avatars/" + repo.Avatar
 | 
						return setting.AppSubURL + "/repo-avatars/" + repo.Avatar
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -260,6 +260,8 @@ var (
 | 
				
			|||||||
	LibravatarService             *libravatar.Libravatar
 | 
						LibravatarService             *libravatar.Libravatar
 | 
				
			||||||
	AvatarMaxFileSize             int64
 | 
						AvatarMaxFileSize             int64
 | 
				
			||||||
	RepositoryAvatarUploadPath    string
 | 
						RepositoryAvatarUploadPath    string
 | 
				
			||||||
 | 
						RepositoryAvatarFallback      string
 | 
				
			||||||
 | 
						RepositoryAvatarFallbackImage string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Log settings
 | 
						// Log settings
 | 
				
			||||||
	LogLevel           string
 | 
						LogLevel           string
 | 
				
			||||||
@@ -842,6 +844,8 @@ func NewContext() {
 | 
				
			|||||||
	if !filepath.IsAbs(RepositoryAvatarUploadPath) {
 | 
						if !filepath.IsAbs(RepositoryAvatarUploadPath) {
 | 
				
			||||||
		RepositoryAvatarUploadPath = path.Join(AppWorkPath, RepositoryAvatarUploadPath)
 | 
							RepositoryAvatarUploadPath = path.Join(AppWorkPath, RepositoryAvatarUploadPath)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						RepositoryAvatarFallback = sec.Key("REPOSITORY_AVATAR_FALLBACK").MustString("none")
 | 
				
			||||||
 | 
						RepositoryAvatarFallbackImage = sec.Key("REPOSITORY_AVATAR_FALLBACK_IMAGE").MustString("/img/repo_default.png")
 | 
				
			||||||
	AvatarMaxWidth = sec.Key("AVATAR_MAX_WIDTH").MustInt(4096)
 | 
						AvatarMaxWidth = sec.Key("AVATAR_MAX_WIDTH").MustInt(4096)
 | 
				
			||||||
	AvatarMaxHeight = sec.Key("AVATAR_MAX_HEIGHT").MustInt(3072)
 | 
						AvatarMaxHeight = sec.Key("AVATAR_MAX_HEIGHT").MustInt(3072)
 | 
				
			||||||
	AvatarMaxFileSize = sec.Key("AVATAR_MAX_FILE_SIZE").MustInt64(1048576)
 | 
						AvatarMaxFileSize = sec.Key("AVATAR_MAX_FILE_SIZE").MustInt64(1048576)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1522,6 +1522,8 @@ dashboard.delete_repo_archives = Delete all repository archives
 | 
				
			|||||||
dashboard.delete_repo_archives_success = All repository archives have been deleted.
 | 
					dashboard.delete_repo_archives_success = All repository archives have been deleted.
 | 
				
			||||||
dashboard.delete_missing_repos = Delete all repositories missing their Git files
 | 
					dashboard.delete_missing_repos = Delete all repositories missing their Git files
 | 
				
			||||||
dashboard.delete_missing_repos_success = All repositories missing their Git files have been deleted.
 | 
					dashboard.delete_missing_repos_success = All repositories missing their Git files have been deleted.
 | 
				
			||||||
 | 
					dashboard.delete_generated_repository_avatars = Delete generated repository avatars
 | 
				
			||||||
 | 
					dashboard.delete_generated_repository_avatars_success = Generated repository avatars were deleted.
 | 
				
			||||||
dashboard.git_gc_repos = Garbage collect all repositories
 | 
					dashboard.git_gc_repos = Garbage collect all repositories
 | 
				
			||||||
dashboard.git_gc_repos_success = All repositories have finished garbage collection.
 | 
					dashboard.git_gc_repos_success = All repositories have finished garbage collection.
 | 
				
			||||||
dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys. (Not needed for the built-in SSH server.)
 | 
					dashboard.resync_all_sshkeys = Update the '.ssh/authorized_keys' file with Gitea SSH keys. (Not needed for the built-in SSH server.)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								public/img/repo_default.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/img/repo_default.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 2.4 KiB  | 
@@ -125,6 +125,7 @@ const (
 | 
				
			|||||||
	reinitMissingRepository
 | 
						reinitMissingRepository
 | 
				
			||||||
	syncExternalUsers
 | 
						syncExternalUsers
 | 
				
			||||||
	gitFsck
 | 
						gitFsck
 | 
				
			||||||
 | 
						deleteGeneratedRepositoryAvatars
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Dashboard show admin panel dashboard
 | 
					// Dashboard show admin panel dashboard
 | 
				
			||||||
@@ -167,6 +168,9 @@ func Dashboard(ctx *context.Context) {
 | 
				
			|||||||
		case gitFsck:
 | 
							case gitFsck:
 | 
				
			||||||
			success = ctx.Tr("admin.dashboard.git_fsck_started")
 | 
								success = ctx.Tr("admin.dashboard.git_fsck_started")
 | 
				
			||||||
			go models.GitFsck()
 | 
								go models.GitFsck()
 | 
				
			||||||
 | 
							case deleteGeneratedRepositoryAvatars:
 | 
				
			||||||
 | 
								success = ctx.Tr("admin.dashboard.delete_generated_repository_avatars_success")
 | 
				
			||||||
 | 
								err = models.RemoveRandomAvatars()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,6 +53,10 @@
 | 
				
			|||||||
						<td>{{.i18n.Tr "admin.dashboard.git_fsck"}}</td>
 | 
											<td>{{.i18n.Tr "admin.dashboard.git_fsck"}}</td>
 | 
				
			||||||
						<td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=9">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td>
 | 
											<td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=9">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td>
 | 
				
			||||||
					</tr>
 | 
										</tr>
 | 
				
			||||||
 | 
										<tr>
 | 
				
			||||||
 | 
											<td>{{.i18n.Tr "admin.dashboard.delete_generated_repository_avatars"}}</td>
 | 
				
			||||||
 | 
											<td><i class="fa fa-caret-square-o-right"></i> <a href="{{AppSubUrl}}/admin?op=10">{{.i18n.Tr "admin.dashboard.operation_run"}}</a></td>
 | 
				
			||||||
 | 
										</tr>
 | 
				
			||||||
				</tbody>
 | 
									</tbody>
 | 
				
			||||||
			</table>
 | 
								</table>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,9 @@
 | 
				
			|||||||
	{{range .Repos}}
 | 
						{{range .Repos}}
 | 
				
			||||||
		<div class="item">
 | 
							<div class="item">
 | 
				
			||||||
			<div class="ui header">
 | 
								<div class="ui header">
 | 
				
			||||||
 | 
									{{if .RelAvatarLink}}
 | 
				
			||||||
					<img class="ui avatar image" src="{{.RelAvatarLink}}">
 | 
										<img class="ui avatar image" src="{{.RelAvatarLink}}">
 | 
				
			||||||
 | 
									{{end}}
 | 
				
			||||||
				<a class="name" href="{{.Link}}">
 | 
									<a class="name" href="{{.Link}}">
 | 
				
			||||||
					{{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}}
 | 
										{{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}}
 | 
				
			||||||
					{{if .IsArchived}}<i class="archive icon archived-icon"></i>{{end}}
 | 
										{{if .IsArchived}}<i class="archive icon archived-icon"></i>{{end}}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user