Skip to content

Commit 33fc3c9

Browse files
Add bucket storage for avatars
1 parent 08c6319 commit 33fc3c9

File tree

645 files changed

+196751
-110
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

645 files changed

+196751
-110
lines changed

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ require (
2525
github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d // indirect
2626
github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect
2727
github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect
28+
github.com/davecgh/go-spew v1.1.1
2829
github.com/denisenkom/go-mssqldb v0.0.0-20190724012636-11b2859924c1
2930
github.com/dgrijalva/jwt-go v3.2.0+incompatible
3031
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 // indirect
@@ -62,7 +63,6 @@ require (
6263
github.com/issue9/identicon v0.0.0-20160320065130-d36b54562f4c
6364
github.com/jaytaylor/html2text v0.0.0-20160923191438-8fb95d837f7d
6465
github.com/jmhodges/levigo v1.0.0 // indirect
65-
github.com/joho/godotenv v1.3.0 // indirect
6666
github.com/kballard/go-shellquote v0.0.0-20170619183022-cd60e84ee657
6767
github.com/keybase/go-crypto v0.0.0-20170605145657-00ac4db533f6
6868
github.com/klauspost/compress v0.0.0-20161025140425-8df558b6cb6f
@@ -106,9 +106,10 @@ require (
106106
github.com/willf/bitset v0.0.0-20180426185212-8ce1146b8621 // indirect
107107
github.com/yohcop/openid-go v0.0.0-20160914080427-2c050d2dae53
108108
go.etcd.io/bbolt v1.3.2 // indirect
109+
gocloud.dev v0.15.0
109110
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
110111
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80
111-
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421
112+
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a
112113
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3
113114
golang.org/x/text v0.3.2
114115
golang.org/x/tools v0.0.0-20190731214159-1e85ed8060aa // indirect

go.sum

Lines changed: 98 additions & 5 deletions
Large diffs are not rendered by default.

main.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ import (
2424
// for embed
2525
_ "github.com/shurcooL/vfsgen"
2626

27+
// Google, Azure and S3 packages for bucket storage
28+
_ "gocloud.dev/blob/azureblob"
29+
_ "gocloud.dev/blob/fileblob"
30+
_ "gocloud.dev/blob/gcsblob"
31+
_ "gocloud.dev/blob/s3blob"
32+
2733
"github.com/urfave/cli"
2834
)
2935

models/user.go

Lines changed: 88 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
package models
77

88
import (
9+
"bytes"
910
"container/list"
11+
"context"
1012
"crypto/md5"
1113
"crypto/sha256"
1214
"crypto/subtle"
1315
"encoding/hex"
1416
"errors"
1517
"fmt"
18+
"image"
1619
_ "image/jpeg" // Needed for jpeg support
1720
"image/png"
1821
"os"
@@ -34,6 +37,7 @@ import (
3437

3538
"github.com/Unknwon/com"
3639
"github.com/go-xorm/xorm"
40+
"gocloud.dev/blob"
3741
"golang.org/x/crypto/argon2"
3842
"golang.org/x/crypto/bcrypt"
3943
"golang.org/x/crypto/pbkdf2"
@@ -345,27 +349,44 @@ func (u *User) generateRandomAvatar(e Engine) error {
345349
if u.Avatar == "" {
346350
u.Avatar = fmt.Sprintf("%d", u.ID)
347351
}
348-
if err = os.MkdirAll(filepath.Dir(u.CustomAvatarPath()), os.ModePerm); err != nil {
349-
return fmt.Errorf("MkdirAll: %v", err)
350-
}
351-
fw, err := os.Create(u.CustomAvatarPath())
352-
if err != nil {
353-
return fmt.Errorf("Create: %v", err)
354-
}
355-
defer fw.Close()
356352

357-
if _, err := e.ID(u.ID).Cols("avatar").Update(u); err != nil {
353+
if err := u.uploadAvatarToBucket(img); err != nil {
358354
return err
359355
}
360356

361-
if err = png.Encode(fw, img); err != nil {
362-
return fmt.Errorf("Encode: %v", err)
357+
if _, err := e.ID(u.ID).Cols("avatar").Update(u); err != nil {
358+
return err
363359
}
364360

365361
log.Info("New random avatar created: %d", u.ID)
366362
return nil
367363
}
368364

365+
func (u *User) getAvatarLink() (string, error) {
366+
ctx := context.Background()
367+
368+
bucket, err := blob.OpenBucket(ctx, setting.FileStorage.Bucket)
369+
if err != nil {
370+
return "", fmt.Errorf("Failed to setup bucket: %v", err)
371+
}
372+
bucket = blob.PrefixedBucket(bucket, setting.AvatarUploadPath+"/")
373+
374+
exist, err := bucket.Exists(ctx, u.Avatar)
375+
if exist {
376+
opts := &blob.SignedURLOptions{
377+
Expiry: blob.DefaultSignedURLExpiry,
378+
}
379+
signedUrl, err := bucket.SignedURL(ctx, u.Avatar, opts)
380+
if err != nil {
381+
return "", err
382+
}
383+
384+
return signedUrl, nil
385+
}
386+
387+
return "", fmt.Errorf("file doesn't exist, error: %v", err)
388+
}
389+
369390
// SizedRelAvatarLink returns a relative link to the user's avatar. When
370391
// applicable, the link is for an avatar of the indicated size (in pixels).
371392
func (u *User) SizedRelAvatarLink(size int) string {
@@ -375,10 +396,10 @@ func (u *User) SizedRelAvatarLink(size int) string {
375396

376397
switch {
377398
case u.UseCustomAvatar:
378-
if !com.IsFile(u.CustomAvatarPath()) {
379-
return base.DefaultAvatarLink()
399+
if link, err := u.getAvatarLink(); err == nil {
400+
return link
380401
}
381-
return setting.AppSubURL + "/avatars/" + u.Avatar
402+
return base.DefaultAvatarLink()
382403
case setting.DisableGravatar, setting.OfflineMode:
383404
if !com.IsFile(u.CustomAvatarPath()) {
384405
if err := u.GenerateRandomAvatar(); err != nil {
@@ -485,6 +506,35 @@ func (u *User) IsPasswordSet() bool {
485506
return len(u.Passwd) > 0
486507
}
487508

509+
// uploadAvatarToBucket uploads avatar to bucket
510+
func (u *User) uploadAvatarToBucket(img image.Image) error {
511+
ctx := context.Background()
512+
bucket, err := blob.OpenBucket(ctx, setting.FileStorage.Bucket)
513+
if err != nil {
514+
return fmt.Errorf("Failed to setup bucket: %v", err)
515+
}
516+
bucket = blob.PrefixedBucket(bucket, setting.AvatarUploadPath+"/")
517+
518+
buf := new(bytes.Buffer)
519+
if err = png.Encode(buf, img); err != nil {
520+
return fmt.Errorf("Failed to encode: %v", err)
521+
}
522+
imgData := buf.Bytes()
523+
524+
bucketWriter, err := bucket.NewWriter(ctx, u.Avatar, nil)
525+
if err != nil {
526+
return fmt.Errorf("Failed to obtain writer: %v", err)
527+
}
528+
529+
if _, err = bucketWriter.Write(imgData); err != nil {
530+
return fmt.Errorf("error occurred: %v", err)
531+
}
532+
if err = bucketWriter.Close(); err != nil {
533+
return fmt.Errorf("Failed to close: %v", err)
534+
}
535+
return nil
536+
}
537+
488538
// UploadAvatar saves custom avatar for user.
489539
// FIXME: split uploads to different subdirs in case we have massive users.
490540
func (u *User) UploadAvatar(data []byte) error {
@@ -500,34 +550,45 @@ func (u *User) UploadAvatar(data []byte) error {
500550
}
501551

502552
u.UseCustomAvatar = true
503-
u.Avatar = fmt.Sprintf("%x", md5.Sum(data))
553+
u.Avatar = fmt.Sprintf("%v-%x", u.ID, md5.Sum(data))
504554
if err = updateUser(sess, u); err != nil {
505555
return fmt.Errorf("updateUser: %v", err)
506556
}
507557

508-
if err := os.MkdirAll(setting.AvatarUploadPath, os.ModePerm); err != nil {
509-
return fmt.Errorf("Failed to create dir %s: %v", setting.AvatarUploadPath, err)
558+
if err := u.uploadAvatarToBucket(*m); err != nil {
559+
return err
510560
}
561+
return sess.Commit()
562+
}
511563

512-
fw, err := os.Create(u.CustomAvatarPath())
564+
// deleteAvatarFromBucket deletes user avatar from bucket
565+
func (u *User) deleteAvatarFromBucket() error {
566+
ctx := context.Background()
567+
bucket, err := blob.OpenBucket(ctx, setting.FileStorage.Bucket)
513568
if err != nil {
514-
return fmt.Errorf("Create: %v", err)
569+
return fmt.Errorf("Failed to setup bucket: %v", err)
515570
}
516-
defer fw.Close()
571+
bucket = blob.PrefixedBucket(bucket, setting.AvatarUploadPath+"/")
517572

518-
if err = png.Encode(fw, *m); err != nil {
519-
return fmt.Errorf("Encode: %v", err)
573+
exist, err := bucket.Exists(ctx, u.Avatar)
574+
if err != nil {
575+
return err
576+
} else if !exist {
577+
return fmt.Errorf("Avatar %s not found", u.Avatar)
520578
}
521579

522-
return sess.Commit()
580+
if err := bucket.Delete(ctx, u.Avatar); err != nil {
581+
return fmt.Errorf("Failed to remove %s: %v", u.Avatar, err)
582+
}
583+
return nil
523584
}
524585

525586
// DeleteAvatar deletes the user's custom avatar.
526587
func (u *User) DeleteAvatar() error {
527588
log.Trace("DeleteAvatar[%d]: %s", u.ID, u.CustomAvatarPath())
528589
if len(u.Avatar) > 0 {
529-
if err := os.Remove(u.CustomAvatarPath()); err != nil {
530-
return fmt.Errorf("Failed to remove %s: %v", u.CustomAvatarPath(), err)
590+
if err := u.deleteAvatarFromBucket(); err != nil {
591+
return err
531592
}
532593
}
533594

@@ -1146,11 +1207,8 @@ func deleteUser(e *xorm.Session, u *User) error {
11461207
}
11471208

11481209
if len(u.Avatar) > 0 {
1149-
avatarPath := u.CustomAvatarPath()
1150-
if com.IsExist(avatarPath) {
1151-
if err := os.Remove(avatarPath); err != nil {
1152-
return fmt.Errorf("Failed to remove %s: %v", avatarPath, err)
1153-
}
1210+
if err := u.deleteAvatarFromBucket(); err != nil {
1211+
return err
11541212
}
11551213
}
11561214

modules/setting/file_storage.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package setting
2+
3+
import (
4+
"os"
5+
6+
"github.com/davecgh/go-spew/spew"
7+
)
8+
9+
// FileStorage represents where to save avatars
10+
var FileStorage struct {
11+
Bucket string
12+
}
13+
14+
func newFileStorage() {
15+
sec := Cfg.Section("storage")
16+
cwd, _ := os.Getwd()
17+
FileStorage.Bucket = sec.Key("BUCKET").MustString("file://" + cwd) // Preferred: "gs://<bucket-name>?required_key1=required_value1&rq_k2=rq_v2"
18+
// Default Credential path for GoogleStorage => $HOME/.config/gcloud/application_default_credentials.json
19+
spew.Dump(FileStorage.Bucket)
20+
}

modules/setting/setting.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,7 @@ func NewContext() {
611611
OfflineMode = sec.Key("OFFLINE_MODE").MustBool()
612612
DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool()
613613
StaticRootPath = sec.Key("STATIC_ROOT_PATH").MustString(AppWorkPath)
614-
AppDataPath = sec.Key("APP_DATA_PATH").MustString(path.Join(AppWorkPath, "data"))
614+
AppDataPath = sec.Key("APP_DATA_PATH").MustString("data")
615615
EnableGzip = sec.Key("ENABLE_GZIP").MustBool()
616616
EnablePprof = sec.Key("ENABLE_PPROF").MustBool(false)
617617
PprofDataPath = sec.Key("PPROF_DATA_PATH").MustString(path.Join(AppWorkPath, "data/tmp/pprof"))
@@ -849,9 +849,6 @@ func NewContext() {
849849
sec = Cfg.Section("picture")
850850
AvatarUploadPath = sec.Key("AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "avatars"))
851851
forcePathSeparator(AvatarUploadPath)
852-
if !filepath.IsAbs(AvatarUploadPath) {
853-
AvatarUploadPath = path.Join(AppWorkPath, AvatarUploadPath)
854-
}
855852
RepositoryAvatarUploadPath = sec.Key("REPOSITORY_AVATAR_UPLOAD_PATH").MustString(path.Join(AppDataPath, "repo-avatars"))
856853
forcePathSeparator(RepositoryAvatarUploadPath)
857854
if !filepath.IsAbs(RepositoryAvatarUploadPath) {
@@ -1047,4 +1044,5 @@ func NewServices() {
10471044
newNotifyMailService()
10481045
newWebhookService()
10491046
newIndexerService()
1047+
newFileStorage()
10501048
}

vendor/cloud.google.com/go/AUTHORS

Lines changed: 0 additions & 15 deletions
This file was deleted.

vendor/cloud.google.com/go/CONTRIBUTORS

Lines changed: 0 additions & 40 deletions
This file was deleted.

0 commit comments

Comments
 (0)