summaryrefslogtreecommitdiffstats
path: root/models
diff options
context:
space:
mode:
authorsilverwind <me@silverwind.io>2020-12-03 19:46:11 +0100
committerGitHub <noreply@github.com>2020-12-03 19:46:11 +0100
commit9269a038a4e904bdeaf5470e76e3a4f8a2a4685b (patch)
tree59730c0dbe7b011a8cb751d135e44152e7feeec0 /models
parent0d35ef5b439623774d1adddf7071d744b1116809 (diff)
downloadgitea-9269a038a4e904bdeaf5470e76e3a4f8a2a4685b.tar.gz
gitea-9269a038a4e904bdeaf5470e76e3a4f8a2a4685b.zip
Direct avatar rendering (#13649)
* Direct avatar rendering This adds new template helpers for avatar rendering which output image elements with direct links to avatars which makes them cacheable by the browsers. This should be a major performance improvment for pages with many avatars. * fix avatars of other user's profile pages * fix top border on user avatar name * uncircle avatars * remove old incomplete avatar selector * use title attribute for name and add it back on blame * minor refactor * tweak comments * fix url path join and adjust test to new result * dedupe functions
Diffstat (limited to 'models')
-rw-r--r--models/action.go6
-rw-r--r--models/avatar.go77
-rw-r--r--models/avatar_test.go52
-rw-r--r--models/user_avatar.go11
4 files changed, 132 insertions, 14 deletions
diff --git a/models/action.go b/models/action.go
index f4d163afa3..ca186033a6 100644
--- a/models/action.go
+++ b/models/action.go
@@ -140,12 +140,6 @@ func (a *Action) GetDisplayNameTitle() string {
return a.GetActFullName()
}
-// GetActAvatar the action's user's avatar link
-func (a *Action) GetActAvatar() string {
- a.loadActUser()
- return a.ActUser.RelAvatarLink()
-}
-
// GetRepoUserName returns the name of the action repository owner.
func (a *Action) GetRepoUserName() string {
a.loadRepo()
diff --git a/models/avatar.go b/models/avatar.go
index c9ba2961ef..ac260fbd93 100644
--- a/models/avatar.go
+++ b/models/avatar.go
@@ -8,9 +8,13 @@ import (
"crypto/md5"
"fmt"
"net/url"
+ "path"
+ "strconv"
"strings"
+ "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/cache"
+ "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
@@ -20,6 +24,28 @@ type EmailHash struct {
Email string `xorm:"UNIQUE NOT NULL"`
}
+// DefaultAvatarLink the default avatar link
+func DefaultAvatarLink() string {
+ u, err := url.Parse(setting.AppSubURL)
+ if err != nil {
+ log.Error("GetUserByEmail: %v", err)
+ return ""
+ }
+
+ u.Path = path.Join(u.Path, "/img/avatar_default.png")
+ return u.String()
+}
+
+// DefaultAvatarSize is a sentinel value for the default avatar size, as
+// determined by the avatar-hosting service.
+const DefaultAvatarSize = -1
+
+// HashEmail hashes email address to MD5 string.
+// https://en.gravatar.com/site/implement/hash/
+func HashEmail(email string) string {
+ return base.EncodeMD5(strings.ToLower(strings.TrimSpace(email)))
+}
+
// GetEmailForHash converts a provided md5sum to the email
func GetEmailForHash(md5Sum string) (string, error) {
return cache.GetString("Avatar:"+md5Sum, func() (string, error) {
@@ -32,8 +58,24 @@ func GetEmailForHash(md5Sum string) (string, error) {
})
}
-// AvatarLink returns an avatar link for a provided email
-func AvatarLink(email string) string {
+// LibravatarURL returns the URL for the given email. This function should only
+// be called if a federated avatar service is enabled.
+func LibravatarURL(email string) (*url.URL, error) {
+ urlStr, err := setting.LibravatarService.FromEmail(email)
+ if err != nil {
+ log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err)
+ return nil, err
+ }
+ u, err := url.Parse(urlStr)
+ if err != nil {
+ log.Error("Failed to parse libravatar url(%s): error %v", urlStr, err)
+ return nil, err
+ }
+ return u, nil
+}
+
+// HashedAvatarLink returns an avatar link for a provided email
+func HashedAvatarLink(email string) string {
lowerEmail := strings.ToLower(strings.TrimSpace(email))
sum := fmt.Sprintf("%x", md5.Sum([]byte(lowerEmail)))
_, _ = cache.GetString("Avatar:"+sum, func() (string, error) {
@@ -57,3 +99,34 @@ func AvatarLink(email string) string {
})
return setting.AppSubURL + "/avatar/" + url.PathEscape(sum)
}
+
+// MakeFinalAvatarURL constructs the final avatar URL string
+func MakeFinalAvatarURL(u *url.URL, size int) string {
+ vals := u.Query()
+ vals.Set("d", "identicon")
+ if size != DefaultAvatarSize {
+ vals.Set("s", strconv.Itoa(size))
+ }
+ u.RawQuery = vals.Encode()
+ return u.String()
+}
+
+// SizedAvatarLink returns a sized link to the avatar for the given email address.
+func SizedAvatarLink(email string, size int) string {
+ var avatarURL *url.URL
+ if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
+ // This is the slow path that would need to call LibravatarURL() which
+ // does DNS lookups. Avoid it by issuing a redirect so we don't block
+ // the template render with network requests.
+ return HashedAvatarLink(email)
+ } else if !setting.DisableGravatar {
+ // copy GravatarSourceURL, because we will modify its Path.
+ copyOfGravatarSourceURL := *setting.GravatarSourceURL
+ avatarURL = &copyOfGravatarSourceURL
+ avatarURL.Path = path.Join(avatarURL.Path, HashEmail(email))
+ } else {
+ return DefaultAvatarLink()
+ }
+
+ return MakeFinalAvatarURL(avatarURL, size)
+}
diff --git a/models/avatar_test.go b/models/avatar_test.go
new file mode 100644
index 0000000000..89540705a0
--- /dev/null
+++ b/models/avatar_test.go
@@ -0,0 +1,52 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package models
+
+import (
+ "net/url"
+ "testing"
+
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/stretchr/testify/assert"
+)
+
+const gravatarSource = "https://secure.gravatar.com/avatar/"
+
+func disableGravatar() {
+ setting.EnableFederatedAvatar = false
+ setting.LibravatarService = nil
+ setting.DisableGravatar = true
+}
+
+func enableGravatar(t *testing.T) {
+ setting.DisableGravatar = false
+ var err error
+ setting.GravatarSourceURL, err = url.Parse(gravatarSource)
+ assert.NoError(t, err)
+}
+
+func TestHashEmail(t *testing.T) {
+ assert.Equal(t,
+ "d41d8cd98f00b204e9800998ecf8427e",
+ HashEmail(""),
+ )
+ assert.Equal(t,
+ "353cbad9b58e69c96154ad99f92bedc7",
+ HashEmail("gitea@example.com"),
+ )
+}
+
+func TestSizedAvatarLink(t *testing.T) {
+ disableGravatar()
+ assert.Equal(t, "/suburl/img/avatar_default.png",
+ SizedAvatarLink("gitea@example.com", 100))
+
+ enableGravatar(t)
+ assert.Equal(t,
+ "https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100",
+ SizedAvatarLink("gitea@example.com", 100),
+ )
+}
diff --git a/models/user_avatar.go b/models/user_avatar.go
index 2f9db5c2e2..50c1c99c57 100644
--- a/models/user_avatar.go
+++ b/models/user_avatar.go
@@ -13,7 +13,6 @@ import (
"strings"
"code.gitea.io/gitea/modules/avatar"
- "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
@@ -41,7 +40,7 @@ func (u *User) generateRandomAvatar(e Engine) error {
}
if u.Avatar == "" {
- u.Avatar = base.HashEmail(u.AvatarEmail)
+ u.Avatar = HashEmail(u.AvatarEmail)
}
if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error {
@@ -76,13 +75,13 @@ func (u *User) SizedRelAvatarLink(size int) string {
//
func (u *User) RealSizedAvatarLink(size int) string {
if u.ID == -1 {
- return base.DefaultAvatarLink()
+ return DefaultAvatarLink()
}
switch {
case u.UseCustomAvatar:
if u.Avatar == "" {
- return base.DefaultAvatarLink()
+ return DefaultAvatarLink()
}
return setting.AppSubURL + "/avatars/" + u.Avatar
case setting.DisableGravatar, setting.OfflineMode:
@@ -94,14 +93,14 @@ func (u *User) RealSizedAvatarLink(size int) string {
return setting.AppSubURL + "/avatars/" + u.Avatar
}
- return base.SizedAvatarLink(u.AvatarEmail, size)
+ return SizedAvatarLink(u.AvatarEmail, size)
}
// RelAvatarLink returns a relative link to the user's avatar. The link
// may either be a sub-URL to this site, or a full URL to an external avatar
// service.
func (u *User) RelAvatarLink() string {
- return u.SizedRelAvatarLink(base.DefaultAvatarSize)
+ return u.SizedRelAvatarLink(DefaultAvatarSize)
}
// AvatarLink returns user avatar absolute link.