Skip to content

Commit 1538a9e

Browse files
Add bucket storage for repo avatars
1 parent 33fc3c9 commit 1538a9e

File tree

2 files changed

+104
-43
lines changed

2 files changed

+104
-43
lines changed

models/repo.go

Lines changed: 104 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ package models
77

88
import (
99
"bytes"
10+
"context"
1011
"crypto/md5"
1112
"errors"
1213
"fmt"
1314
"html/template"
15+
"image"
1416

1517
// Needed for jpeg support
1618
_ "image/jpeg"
@@ -38,6 +40,7 @@ import (
3840

3941
"github.com/Unknwon/com"
4042
"github.com/go-xorm/xorm"
43+
"gocloud.dev/blob"
4144
ini "gopkg.in/ini.v1"
4245
"xorm.io/builder"
4346
)
@@ -1934,11 +1937,8 @@ func DeleteRepository(doer *User, uid, repoID int64) error {
19341937
}
19351938

19361939
if len(repo.Avatar) > 0 {
1937-
avatarPath := repo.CustomAvatarPath()
1938-
if com.IsExist(avatarPath) {
1939-
if err := os.Remove(avatarPath); err != nil {
1940-
return fmt.Errorf("Failed to remove %s: %v", avatarPath, err)
1941-
}
1940+
if err := repo.deleteAvatarFromBucket(); err != nil {
1941+
return err
19421942
}
19431943
}
19441944

@@ -2544,18 +2544,11 @@ func (repo *Repository) generateRandomAvatar(e Engine) error {
25442544
}
25452545

25462546
repo.Avatar = idToString
2547-
if err = os.MkdirAll(filepath.Dir(repo.CustomAvatarPath()), os.ModePerm); err != nil {
2548-
return fmt.Errorf("MkdirAll: %v", err)
2549-
}
2550-
fw, err := os.Create(repo.CustomAvatarPath())
2551-
if err != nil {
2552-
return fmt.Errorf("Create: %v", err)
2553-
}
2554-
defer fw.Close()
25552547

2556-
if err = png.Encode(fw, img); err != nil {
2557-
return fmt.Errorf("Encode: %v", err)
2548+
if err := repo.uploadAvatarToBucket(img); err != nil {
2549+
return err
25582550
}
2551+
25592552
log.Info("New random avatar created for repository: %d", repo.ID)
25602553

25612554
if _, err := e.ID(repo.ID).Cols("avatar").NoAutoTime().Update(repo); err != nil {
@@ -2585,23 +2578,55 @@ func (repo *Repository) RelAvatarLink() string {
25852578
return repo.relAvatarLink(x)
25862579
}
25872580

2581+
// getAvatarLinkFromBucket returns repo avatar link from bucket
2582+
func (repo *Repository) getAvatarLinkFromBucket() (string, error) {
2583+
ctx := context.Background()
2584+
2585+
bucket, err := blob.OpenBucket(ctx, setting.FileStorage.Bucket)
2586+
if err != nil {
2587+
return "", fmt.Errorf("Failed to setup bucket: %v", err)
2588+
}
2589+
bucket = blob.PrefixedBucket(bucket, setting.RepositoryAvatarUploadPath+"/")
2590+
2591+
exist, err := bucket.Exists(ctx, repo.Avatar)
2592+
if exist {
2593+
opts := &blob.SignedURLOptions{
2594+
Expiry: blob.DefaultSignedURLExpiry,
2595+
}
2596+
signedUrl, err := bucket.SignedURL(ctx, repo.Avatar, opts)
2597+
if err != nil {
2598+
return "", err
2599+
}
2600+
return signedUrl, nil
2601+
}
2602+
return "", fmt.Errorf("File doesn't exist, error %v", err)
2603+
}
2604+
25882605
func (repo *Repository) relAvatarLink(e Engine) string {
25892606
// If no avatar - path is empty
25902607
avatarPath := repo.CustomAvatarPath()
2591-
if len(avatarPath) == 0 || !com.IsFile(avatarPath) {
2608+
2609+
var avatarLink string
2610+
var err error
2611+
2612+
if len(avatarPath) > 0 {
2613+
avatarLink, err = repo.getAvatarLinkFromBucket()
2614+
}
2615+
if len(avatarPath) == 0 || err != nil {
25922616
switch mode := setting.RepositoryAvatarFallback; mode {
25932617
case "image":
25942618
return setting.RepositoryAvatarFallbackImage
25952619
case "random":
25962620
if err := repo.generateRandomAvatar(e); err != nil {
25972621
log.Error("generateRandomAvatar: %v", err)
25982622
}
2623+
avatarLink, _ = repo.getAvatarLinkFromBucket()
25992624
default:
26002625
// default behaviour: do not display avatar
26012626
return ""
26022627
}
26032628
}
2604-
return setting.AppSubURL + "/repo-avatars/" + repo.Avatar
2629+
return avatarLink
26052630
}
26062631

26072632
// avatarLink returns user avatar absolute link.
@@ -2616,6 +2641,36 @@ func (repo *Repository) avatarLink(e Engine) string {
26162641
return link
26172642
}
26182643

2644+
// uploadAvatarToBucket uploads repo avatar to bucket
2645+
func (repo *Repository) uploadAvatarToBucket(img image.Image) error {
2646+
ctx := context.Background()
2647+
bucket, err := blob.OpenBucket(ctx, setting.FileStorage.Bucket)
2648+
if err != nil {
2649+
return fmt.Errorf("failed to setup bucket: %v", err)
2650+
}
2651+
bucket = blob.PrefixedBucket(bucket, setting.RepositoryAvatarUploadPath+"/")
2652+
2653+
buf := new(bytes.Buffer)
2654+
if err = png.Encode(buf, img); err != nil {
2655+
return fmt.Errorf("failed to encode: %v", err)
2656+
}
2657+
imgData := buf.Bytes()
2658+
2659+
bucketWriter, err := bucket.NewWriter(ctx, repo.Avatar, nil)
2660+
if err != nil {
2661+
return fmt.Errorf("failed to obtain writer: %v", err)
2662+
}
2663+
2664+
if _, err = bucketWriter.Write(imgData); err != nil {
2665+
return fmt.Errorf("error occurred: %v", err)
2666+
}
2667+
if err = bucketWriter.Close(); err != nil {
2668+
return fmt.Errorf("failed to close: %v", err)
2669+
}
2670+
2671+
return nil
2672+
}
2673+
26192674
// UploadAvatar saves custom avatar for repository.
26202675
// FIXME: split uploads to different subdirs in case we have massive number of repos.
26212676
func (repo *Repository) UploadAvatar(data []byte) error {
@@ -2630,36 +2685,49 @@ func (repo *Repository) UploadAvatar(data []byte) error {
26302685
return err
26312686
}
26322687

2688+
oldAvatar := repo.Avatar
26332689
oldAvatarPath := repo.CustomAvatarPath()
26342690

26352691
// Users can upload the same image to other repo - prefix it with ID
26362692
// Then repo will be removed - only it avatar file will be removed
2637-
repo.Avatar = fmt.Sprintf("%d-%x", repo.ID, md5.Sum(data))
2693+
newAvatar := fmt.Sprintf("%d-%x", repo.ID, md5.Sum(data))
2694+
repo.Avatar = newAvatar
2695+
if len(oldAvatarPath) > 0 && oldAvatarPath != repo.CustomAvatarPath() {
2696+
repo.Avatar = oldAvatar
2697+
if err := repo.deleteAvatarFromBucket(); err != nil {
2698+
log.Trace("DeleteOldAvatar: ", err)
2699+
}
2700+
}
2701+
repo.Avatar = newAvatar
2702+
26382703
if _, err := sess.ID(repo.ID).Cols("avatar").Update(repo); err != nil {
26392704
return fmt.Errorf("UploadAvatar: Update repository avatar: %v", err)
26402705
}
26412706

2642-
if err := os.MkdirAll(setting.RepositoryAvatarUploadPath, os.ModePerm); err != nil {
2643-
return fmt.Errorf("UploadAvatar: Failed to create dir %s: %v", setting.RepositoryAvatarUploadPath, err)
2707+
if err := repo.uploadAvatarToBucket(*m); err != nil {
2708+
return err
26442709
}
26452710

2646-
fw, err := os.Create(repo.CustomAvatarPath())
2647-
if err != nil {
2648-
return fmt.Errorf("UploadAvatar: Create file: %v", err)
2649-
}
2650-
defer fw.Close()
2711+
return sess.Commit()
2712+
}
26512713

2652-
if err = png.Encode(fw, *m); err != nil {
2653-
return fmt.Errorf("UploadAvatar: Encode png: %v", err)
2714+
// deleteAvatarFromBucket deletes repo avatar from bucket
2715+
func (repo *Repository) deleteAvatarFromBucket() error {
2716+
ctx := context.Background()
2717+
bucket, err := blob.OpenBucket(ctx, setting.FileStorage.Bucket)
2718+
if err != nil {
2719+
return fmt.Errorf("Failed to setup bucket: %v", err)
26542720
}
2721+
bucket = blob.PrefixedBucket(bucket, setting.RepositoryAvatarUploadPath+"/")
26552722

2656-
if len(oldAvatarPath) > 0 && oldAvatarPath != repo.CustomAvatarPath() {
2657-
if err := os.Remove(oldAvatarPath); err != nil {
2658-
return fmt.Errorf("UploadAvatar: Failed to remove old repo avatar %s: %v", oldAvatarPath, err)
2659-
}
2723+
exist, err := bucket.Exists(ctx, repo.Avatar)
2724+
if err != nil {
2725+
return err
2726+
} else if !exist {
2727+
return errors.New("Repo avatar not found")
26602728
}
26612729

2662-
return sess.Commit()
2730+
return bucket.Delete(ctx, repo.Avatar)
26632731
}
26642732

26652733
// DeleteAvatar deletes the repos's custom avatar.
@@ -2679,19 +2747,15 @@ func (repo *Repository) DeleteAvatar() error {
26792747
return err
26802748
}
26812749

2750+
if err := repo.deleteAvatarFromBucket(); err != nil {
2751+
return err
2752+
}
2753+
26822754
repo.Avatar = ""
26832755
if _, err := sess.ID(repo.ID).Cols("avatar").Update(repo); err != nil {
26842756
return fmt.Errorf("DeleteAvatar: Update repository avatar: %v", err)
26852757
}
26862758

2687-
if _, err := os.Stat(avatarPath); err == nil {
2688-
if err := os.Remove(avatarPath); err != nil {
2689-
return fmt.Errorf("DeleteAvatar: Failed to remove %s: %v", avatarPath, err)
2690-
}
2691-
} else {
2692-
// // Schrodinger: file may or may not exist. See err for details.
2693-
log.Trace("DeleteAvatar[%d]: %v", err)
2694-
}
26952759
return sess.Commit()
26962760
}
26972761

modules/setting/setting.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -851,9 +851,6 @@ func NewContext() {
851851
forcePathSeparator(AvatarUploadPath)
852852
RepositoryAvatarUploadPath = sec.Key("REPOSITORY_AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "repo-avatars"))
853853
forcePathSeparator(RepositoryAvatarUploadPath)
854-
if !filepath.IsAbs(RepositoryAvatarUploadPath) {
855-
RepositoryAvatarUploadPath = path.Join(AppWorkPath, RepositoryAvatarUploadPath)
856-
}
857854
RepositoryAvatarFallback = sec.Key("REPOSITORY_AVATAR_FALLBACK").MustString("none")
858855
RepositoryAvatarFallbackImage = sec.Key("REPOSITORY_AVATAR_FALLBACK_IMAGE").MustString("/img/repo_default.png")
859856
AvatarMaxWidth = sec.Key("AVATAR_MAX_WIDTH").MustInt(4096)

0 commit comments

Comments
 (0)