Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Repository avatar fallback configuration #7087

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions custom/conf/app.ini.sample
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,10 @@ SESSION_LIFE_TIME = 86400
[picture]
AVATAR_UPLOAD_PATH = data/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.
; This is to limit the amount of RAM used when resizing the image.
AVATAR_MAX_WIDTH = 4096
Expand Down
5 changes: 5 additions & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,11 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
[http://www.libravatar.org](http://www.libravatar.org)).
- `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_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_HEIGHT`: **3072**: Maximum avatar image height in pixels.
- `AVATAR_MAX_FILE_SIZE`: **1048576** (1Mb): Maximum avatar image file size in bytes.
Expand Down
77 changes: 69 additions & 8 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -2528,17 +2528,78 @@ func (repo *Repository) CustomAvatarPath() string {
return filepath.Join(setting.RepositoryAvatarUploadPath, repo.Avatar)
}

// RelAvatarLink returns a relative link to the user's avatar.
// The link a sub-URL to this site
// Since Gravatar support not needed here - just check for image path.
// GenerateRandomAvatar generates a random avatar for repository.
func (repo *Repository) GenerateRandomAvatar() error {
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 {
saitho marked this conversation as resolved.
Show resolved Hide resolved
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why we need to remove the random avatars?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Random avatars are placed on the file system and set in the database.

Let's say an owner used generated repository avatars and now wants to display a default avatar instead. The generated avatars are stored just like uploaded avatars on the file system (the only difference is the file name) and also update the repository in the database.
The default avatar will be used when no image was added to the repository, which is why the randomly generated images will still be used instead of the default avatar.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a repository has a random avatar, delete it from UI will make it as default image and upload a new avatar will delete the previous image. So I think the cron task is unnecessary?

Copy link
Contributor Author

@saitho saitho Jun 1, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True. But still, looking at an organization with 100 repositories an admin would have to through all projects and delete the avatars. That's why I wanted to provide an easy way to do so, which is why I placed it in the administration panel.
It's not supposed to be a cron task that is executed regularly. Maybe it would be better to put that into a cmd task like gitea cleanup repository-avatars...

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 {

// If no avatar - path is empty
avatarPath := repo.CustomAvatarPath()
if len(avatarPath) <= 0 {
return ""
}
if !com.IsFile(avatarPath) {
return ""
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 setting.AppSubURL + "/repo-avatars/" + repo.Avatar
}
Expand Down
24 changes: 14 additions & 10 deletions modules/setting/setting.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,16 +250,18 @@ var (
}

// Picture settings
AvatarUploadPath string
AvatarMaxWidth int
AvatarMaxHeight int
GravatarSource string
GravatarSourceURL *url.URL
DisableGravatar bool
EnableFederatedAvatar bool
LibravatarService *libravatar.Libravatar
AvatarMaxFileSize int64
RepositoryAvatarUploadPath string
AvatarUploadPath string
AvatarMaxWidth int
AvatarMaxHeight int
GravatarSource string
GravatarSourceURL *url.URL
DisableGravatar bool
EnableFederatedAvatar bool
LibravatarService *libravatar.Libravatar
AvatarMaxFileSize int64
RepositoryAvatarUploadPath string
RepositoryAvatarFallback string
RepositoryAvatarFallbackImage string

// Log settings
LogLevel string
Expand Down Expand Up @@ -842,6 +844,8 @@ func NewContext() {
if !filepath.IsAbs(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)
AvatarMaxHeight = sec.Key("AVATAR_MAX_HEIGHT").MustInt(3072)
AvatarMaxFileSize = sec.Key("AVATAR_MAX_FILE_SIZE").MustInt64(1048576)
Expand Down
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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_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_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_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.)
Expand Down
Binary file added public/img/repo_default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions routers/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ const (
reinitMissingRepository
syncExternalUsers
gitFsck
deleteGeneratedRepositoryAvatars
)

// Dashboard show admin panel dashboard
Expand Down Expand Up @@ -167,6 +168,9 @@ func Dashboard(ctx *context.Context) {
case gitFsck:
success = ctx.Tr("admin.dashboard.git_fsck_started")
go models.GitFsck()
case deleteGeneratedRepositoryAvatars:
success = ctx.Tr("admin.dashboard.delete_generated_repository_avatars_success")
err = models.RemoveRandomAvatars()
}

if err != nil {
Expand Down
4 changes: 4 additions & 0 deletions templates/admin/dashboard.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@
<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>
</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>
</table>
</div>
Expand Down
4 changes: 3 additions & 1 deletion templates/explore/repo_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
{{range .Repos}}
<div class="item">
<div class="ui header">
<img class="ui avatar image" src="{{.RelAvatarLink}}">
{{if .RelAvatarLink}}
<img class="ui avatar image" src="{{.RelAvatarLink}}">
{{end}}
<a class="name" href="{{.Link}}">
{{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}}
{{if .IsArchived}}<i class="archive icon archived-icon"></i>{{end}}
Expand Down