From f219ddcf4ef13b9d5de129da4eb2b56dbc899d61 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 21 Mar 2014 12:13:13 -0400 Subject: [PATCH 1/6] Add log config panel in admin --- .gopmfile | 3 +-- modules/base/conf.go | 26 ++++++++++++++------------ routers/admin/admin.go | 3 +++ templates/admin/config.tmpl | 16 +++++++++++++++- 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/.gopmfile b/.gopmfile index 9e2440f3ef..5b690a06a7 100644 --- a/.gopmfile +++ b/.gopmfile @@ -9,13 +9,12 @@ github.com/Unknwon/com= github.com/Unknwon/cae= github.com/Unknwon/goconfig= github.com/dchest/scrypt= -github.com/go-sql-driver/mysql= -github.com/lib/pq= github.com/lunny/xorm= github.com/gogits/logs= github.com/gogits/binding= github.com/gogits/git= github.com/gogits/gfm= +github.com/gogits/cache= [res] include=templates|public|conf diff --git a/modules/base/conf.go b/modules/base/conf.go index 2c3ecd72d8..863daca644 100644 --- a/modules/base/conf.go +++ b/modules/base/conf.go @@ -43,6 +43,9 @@ var ( Cache cache.Cache CacheAdapter string CacheConfig string + + LogMode string + LogConfig string ) var Service struct { @@ -83,15 +86,15 @@ func newService() { func newLogService() { // Get and check log mode. - mode := Cfg.MustValue("log", "MODE", "console") - modeSec := "log." + mode + LogMode = Cfg.MustValue("log", "MODE", "console") + modeSec := "log." + LogMode if _, err := Cfg.GetSection(modeSec); err != nil { - fmt.Printf("Unknown log mode: %s\n", mode) + fmt.Printf("Unknown log mode: %s\n", LogMode) os.Exit(2) } // Log level. - levelName := Cfg.MustValue("log."+mode, "LEVEL", "Trace") + levelName := Cfg.MustValue("log."+LogMode, "LEVEL", "Trace") level, ok := logLevels[levelName] if !ok { fmt.Printf("Unknown log level: %s\n", levelName) @@ -99,14 +102,13 @@ func newLogService() { } // Generate log configuration. - var config string - switch mode { + switch LogMode { case "console": - config = fmt.Sprintf(`{"level":%s}`, level) + LogConfig = fmt.Sprintf(`{"level":%s}`, level) case "file": logPath := Cfg.MustValue(modeSec, "FILE_NAME", "log/gogs.log") os.MkdirAll(path.Dir(logPath), os.ModePerm) - config = fmt.Sprintf( + LogConfig = fmt.Sprintf( `{"level":%s,"filename":%s,"rotate":%v,"maxlines":%d,"maxsize",%d,"daily":%v,"maxdays":%d}`, level, logPath, Cfg.MustBool(modeSec, "LOG_ROTATE", true), @@ -115,13 +117,13 @@ func newLogService() { Cfg.MustBool(modeSec, "DAILY_ROTATE", true), Cfg.MustInt(modeSec, "MAX_DAYS", 7)) case "conn": - config = fmt.Sprintf(`{"level":%s,"reconnectOnMsg":%v,"reconnect":%v,"net":%s,"addr":%s}`, level, + LogConfig = fmt.Sprintf(`{"level":%s,"reconnectOnMsg":%v,"reconnect":%v,"net":%s,"addr":%s}`, level, Cfg.MustBool(modeSec, "RECONNECT_ON_MSG", false), Cfg.MustBool(modeSec, "RECONNECT", false), Cfg.MustValue(modeSec, "PROTOCOL", "tcp"), Cfg.MustValue(modeSec, "ADDR", ":7020")) case "smtp": - config = fmt.Sprintf(`{"level":%s,"username":%s,"password":%s,"host":%s,"sendTos":%s,"subject":%s}`, level, + LogConfig = fmt.Sprintf(`{"level":%s,"username":%s,"password":%s,"host":%s,"sendTos":%s,"subject":%s}`, level, Cfg.MustValue(modeSec, "USER", "example@example.com"), Cfg.MustValue(modeSec, "PASSWD", "******"), Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"), @@ -129,8 +131,8 @@ func newLogService() { Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve")) } - log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), mode, config) - log.Info("Log Mode: %s(%s)", strings.Title(mode), levelName) + log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig) + log.Info("Log Mode: %s(%s)", strings.Title(LogMode), levelName) } func newCacheService() { diff --git a/routers/admin/admin.go b/routers/admin/admin.go index d70af3c50c..2e19b99c10 100644 --- a/routers/admin/admin.go +++ b/routers/admin/admin.go @@ -70,5 +70,8 @@ func Config(ctx *middleware.Context) { ctx.Data["CacheAdapter"] = base.CacheAdapter ctx.Data["CacheConfig"] = base.CacheConfig + ctx.Data["LogMode"] = base.LogMode + ctx.Data["LogConfig"] = base.LogConfig + ctx.HTML(200, "admin/config") } diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index ad32ec3fb1..6906f2409d 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -71,7 +71,21 @@
Cache Adapter: {{.CacheAdapter}}
-
Cache Config: {{.CacheConfig}}
+
Cache Config:
+
{{.CacheConfig}}
+
+ + +
+
+ Log Configuration +
+ +
+
Log Mode: {{.LogMode}}
+
Log Config:
+
{{.LogConfig}}
+
From efdaf6ee1536f043d9e242dc16a096c99ec1bfda Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sat, 22 Mar 2014 00:48:26 +0800 Subject: [PATCH 2/6] add http protocol clone support --- models/repo.go | 11 +++++++++++ models/user.go | 6 ++---- routers/repo/single.go | 25 +++++++++++++++++++++++++ web.go | 2 ++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/models/repo.go b/models/repo.go index 4b6dedaf90..cf1e1df5c4 100644 --- a/models/repo.go +++ b/models/repo.go @@ -257,6 +257,17 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep return err } + // hook/post-update + pu2, err := os.OpenFile(filepath.Join(repoPath, "hooks", "post-receive"), os.O_CREATE|os.O_WRONLY, 0777) + if err != nil { + return err + } + defer pu2.Close() + // TODO: Windows .bat + if _, err = pu2.WriteString("#!/usr/bin/env bash\ngit update-server-info\n"); err != nil { + return err + } + // Initialize repository according to user's choice. fileName := map[string]string{} if initReadme { diff --git a/models/user.go b/models/user.go index 76cf2d20ce..69608ec277 100644 --- a/models/user.go +++ b/models/user.go @@ -231,10 +231,8 @@ func UserPath(userName string) string { func GetUserByKeyId(keyId int64) (*User, error) { user := new(User) - rawSql := "SELECT a.* FROM user AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?" - if base.Cfg.MustValue("database", "DB_TYPE") == "postgres" { - rawSql = "SELECT a.* FROM \"user\" AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?" - } + rawSql := "SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?" + has, err := orm.Sql(rawSql, keyId).Get(user) if err != nil { return nil, err diff --git a/routers/repo/single.go b/routers/repo/single.go index c10d30a7d6..064150a234 100644 --- a/routers/repo/single.go +++ b/routers/repo/single.go @@ -5,11 +5,13 @@ package repo import ( + "path" "strings" "github.com/codegangsta/martini" "github.com/gogits/git" + "github.com/gogits/webdav" "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/base" @@ -181,6 +183,29 @@ func Single(ctx *middleware.Context, params martini.Params) { ctx.HTML(200, "repo/single", ctx.Data) } +func Http(ctx *middleware.Context, params martini.Params) { + /*if !ctx.Repo.IsValid { + return + }*/ + + // TODO: access check + + username := params["username"] + reponame := params["reponame"] + if strings.HasSuffix(reponame, ".git") { + reponame = reponame[:len(reponame)-4] + } + + prefix := path.Join("/", username, params["reponame"]) + server := &webdav.Server{ + Fs: webdav.Dir(models.RepoPath(username, reponame)), + TrimPrefix: prefix, + Listings: true, + } + + server.ServeHTTP(ctx.ResponseWriter, ctx.Req) +} + func Setting(ctx *middleware.Context, params martini.Params) { if !ctx.Repo.IsOwner { ctx.Error(404) diff --git a/web.go b/web.go index ceb193e6fd..f083b5508c 100644 --- a/web.go +++ b/web.go @@ -116,6 +116,8 @@ func runWeb(*cli.Context) { m.Get("/:username/:reponame", ignSignIn, middleware.RepoAssignment(true), repo.Single) + m.Any("/:username/:reponame/**", ignSignIn, repo.Http) + if martini.Env == martini.Dev { m.Get("/template/**", dev.TemplatePreview) } From 8e47ae21024bc35a82215e16f1e586f94ae622c9 Mon Sep 17 00:00:00 2001 From: skyblue Date: Sun, 23 Mar 2014 12:24:09 +0800 Subject: [PATCH 3/6] add avatar inorder to view code on github --- modules/avatar/avatar.go | 136 ++++++++++++++++++++++++++++++++++ modules/avatar/avatar_test.go | 35 +++++++++ 2 files changed, 171 insertions(+) create mode 100644 modules/avatar/avatar.go create mode 100644 modules/avatar/avatar_test.go diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go new file mode 100644 index 0000000000..93f842eaef --- /dev/null +++ b/modules/avatar/avatar.go @@ -0,0 +1,136 @@ +package avatar + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + "sync" + "time" +) + +var gravatar = "http://www.gravatar.com/avatar" + +// hash email to md5 string +func HashEmail(email string) string { + h := md5.New() + h.Write([]byte(strings.ToLower(email))) + return hex.EncodeToString(h.Sum(nil)) +} + +type Avatar struct { + Hash string + cacheDir string // image save dir + reqParams string + imagePath string +} + +func New(hash string, cacheDir string) *Avatar { + return &Avatar{ + Hash: hash, + cacheDir: cacheDir, + reqParams: url.Values{ + "d": {"retro"}, + "size": {"200"}, + "r": {"pg"}}.Encode(), + imagePath: filepath.Join(cacheDir, hash+".jpg"), + } +} + +// get image from gravatar.com +func (this *Avatar) Update() { + thunder.Fetch(gravatar+"/"+this.Hash+"?"+this.reqParams, + this.Hash+".jpg") +} + +func (this *Avatar) UpdateTimeout(timeout time.Duration) { + select { + case <-time.After(timeout): + log.Println("timeout") + case <-thunder.GoFetch(gravatar+"/"+this.Hash+"?"+this.reqParams, + this.Hash+".jpg"): + } +} + +var thunder = &Thunder{QueueSize: 10} + +type Thunder struct { + QueueSize int // download queue size + q chan *thunderTask + once sync.Once +} + +func (t *Thunder) init() { + if t.QueueSize < 1 { + t.QueueSize = 1 + } + t.q = make(chan *thunderTask, t.QueueSize) + for i := 0; i < t.QueueSize; i++ { + go func() { + for { + task := <-t.q + task.Fetch() + } + }() + } +} + +func (t *Thunder) Fetch(url string, saveFile string) error { + t.once.Do(t.init) + task := &thunderTask{ + Url: url, + SaveFile: saveFile, + } + task.Add(1) + t.q <- task + task.Wait() + return task.err +} + +func (t *Thunder) GoFetch(url, saveFile string) chan error { + c := make(chan error) + go func() { + c <- t.Fetch(url, saveFile) + }() + return c +} + +// thunder download +type thunderTask struct { + Url string + SaveFile string + sync.WaitGroup + err error +} + +func (this *thunderTask) Fetch() { + this.err = this.fetch() + this.Done() +} + +func (this *thunderTask) fetch() error { + resp, err := http.Get(this.Url) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return fmt.Errorf("status code: %d", resp.StatusCode) + } + fd, err := os.Create(this.SaveFile) + if err != nil { + return err + } + defer fd.Close() + _, err = io.Copy(fd, resp.Body) + if err != nil { + return err + } + return nil +} diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go new file mode 100644 index 0000000000..49f8f91f35 --- /dev/null +++ b/modules/avatar/avatar_test.go @@ -0,0 +1,35 @@ +package avatar + +import ( + "log" + "strconv" + "testing" + "time" +) + +func TestFetch(t *testing.T) { + hash := HashEmail("ssx205@gmail.com") + avatar := New(hash, "./") + //avatar.Update() + avatar.UpdateTimeout(time.Millisecond * 200) + time.Sleep(5 * time.Second) +} + +func TestFetchMany(t *testing.T) { + log.Println("start") + var n = 50 + ch := make(chan bool, n) + for i := 0; i < n; i++ { + go func(i int) { + hash := HashEmail(strconv.Itoa(i) + "ssx205@gmail.com") + avatar := New(hash, "./") + avatar.Update() + log.Println("finish", hash) + ch <- true + }(i) + } + for i := 0; i < n; i++ { + <-ch + } + log.Println("end") +} From 79604f553f45af658a884544187b00fb9fa3169c Mon Sep 17 00:00:00 2001 From: skyblue Date: Sun, 23 Mar 2014 15:55:27 +0800 Subject: [PATCH 4/6] fix download part problem, add png support --- modules/avatar/avatar.go | 179 +++++++++++++++++++++++++++++++++++---- 1 file changed, 162 insertions(+), 17 deletions(-) diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go index 93f842eaef..55d1e13d94 100644 --- a/modules/avatar/avatar.go +++ b/modules/avatar/avatar.go @@ -3,7 +3,11 @@ package avatar import ( "crypto/md5" "encoding/hex" + "errors" "fmt" + "image" + "image/jpeg" + "image/png" "io" "log" "net/http" @@ -13,9 +17,14 @@ import ( "strings" "sync" "time" + + "github.com/nfnt/resize" ) -var gravatar = "http://www.gravatar.com/avatar" +var ( + gravatar = "http://www.gravatar.com/avatar" + defaultImagePath = "./default.jpg" +) // hash email to md5 string func HashEmail(email string) string { @@ -25,37 +34,145 @@ func HashEmail(email string) string { } type Avatar struct { - Hash string - cacheDir string // image save dir - reqParams string - imagePath string + Hash string + cacheDir string // image save dir + reqParams string + imagePath string + expireDuration time.Duration } func New(hash string, cacheDir string) *Avatar { return &Avatar{ - Hash: hash, - cacheDir: cacheDir, + Hash: hash, + cacheDir: cacheDir, + expireDuration: time.Minute * 10, reqParams: url.Values{ "d": {"retro"}, "size": {"200"}, "r": {"pg"}}.Encode(), - imagePath: filepath.Join(cacheDir, hash+".jpg"), + imagePath: filepath.Join(cacheDir, hash+".image"), //maybe png or jpeg } } +func (this *Avatar) InCache() bool { + fileInfo, err := os.Stat(this.imagePath) + return err == nil && fileInfo.Mode().IsRegular() +} + +func (this *Avatar) Modtime() (modtime time.Time, err error) { + fileInfo, err := os.Stat(this.imagePath) + if err != nil { + return + } + return fileInfo.ModTime(), nil +} + +func (this *Avatar) Expired() bool { + if !this.InCache() { + return true + } + fileInfo, err := os.Stat(this.imagePath) + return err != nil || time.Since(fileInfo.ModTime()) > this.expireDuration +} + +// default image format: jpeg +func (this *Avatar) Encode(wr io.Writer, size int) (err error) { + var img image.Image + decodeImageFile := func(file string) (img image.Image, err error) { + fd, err := os.Open(file) + if err != nil { + return + } + defer fd.Close() + img, err = jpeg.Decode(fd) + if err != nil { + fd.Seek(0, os.SEEK_SET) + img, err = png.Decode(fd) + } + return + } + imgPath := this.imagePath + if !this.InCache() { + imgPath = defaultImagePath + } + img, err = decodeImageFile(imgPath) + if err != nil { + return + } + m := resize.Resize(uint(size), 0, img, resize.Lanczos3) + return jpeg.Encode(wr, m, nil) +} + // get image from gravatar.com func (this *Avatar) Update() { thunder.Fetch(gravatar+"/"+this.Hash+"?"+this.reqParams, - this.Hash+".jpg") + this.imagePath) } -func (this *Avatar) UpdateTimeout(timeout time.Duration) { +func (this *Avatar) UpdateTimeout(timeout time.Duration) error { + var err error select { case <-time.After(timeout): - log.Println("timeout") - case <-thunder.GoFetch(gravatar+"/"+this.Hash+"?"+this.reqParams, - this.Hash+".jpg"): + err = errors.New("get gravatar image timeout") + case err = <-thunder.GoFetch(gravatar+"/"+this.Hash+"?"+this.reqParams, + this.imagePath): } + return err +} + +func init() { + log.SetFlags(log.Lshortfile | log.LstdFlags) +} + +// http.Handle("/avatar/", avatar.HttpHandler("./cache")) +func HttpHandler(cacheDir string) func(w http.ResponseWriter, r *http.Request) { + MustInt := func(r *http.Request, defaultValue int, keys ...string) int { + var v int + for _, k := range keys { + if _, err := fmt.Sscanf(r.FormValue(k), "%d", &v); err == nil { + defaultValue = v + } + } + return defaultValue + } + + return func(w http.ResponseWriter, r *http.Request) { + urlPath := r.URL.Path + hash := urlPath[strings.LastIndex(urlPath, "/")+1:] + hash = HashEmail(hash) + size := MustInt(r, 80, "s", "size") // size = 80*80 + + avatar := New(hash, cacheDir) + if avatar.Expired() { + err := avatar.UpdateTimeout(time.Millisecond * 500) + if err != nil { + log.Println(err) + } + } + if modtime, err := avatar.Modtime(); err == nil { + etag := fmt.Sprintf("size(%d)", size) + if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) && etag == r.Header.Get("If-None-Match") { + h := w.Header() + delete(h, "Content-Type") + delete(h, "Content-Length") + w.WriteHeader(http.StatusNotModified) + return + } + w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat)) + w.Header().Set("ETag", etag) + } + w.Header().Set("Content-Type", "image/jpeg") + err := avatar.Encode(w, size) + if err != nil { + log.Println(err) + w.WriteHeader(500) + } + } +} + +func init() { + http.HandleFunc("/", HttpHandler("./")) + log.Fatal(http.ListenAndServe(":8001", nil)) } var thunder = &Thunder{QueueSize: 10} @@ -114,8 +231,17 @@ func (this *thunderTask) Fetch() { this.Done() } +var client = &http.Client{} + func (this *thunderTask) fetch() error { - resp, err := http.Get(this.Url) + log.Println("thunder, fetch", this.Url) + req, _ := http.NewRequest("GET", this.Url, nil) + req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8") + req.Header.Set("Accept-Encoding", "gzip,deflate,sdch") + req.Header.Set("Accept-Language", "zh-CN,zh;q=0.8") + req.Header.Set("Cache-Control", "no-cache") + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36") + resp, err := client.Do(req) if err != nil { return err } @@ -123,14 +249,33 @@ func (this *thunderTask) fetch() error { if resp.StatusCode != 200 { return fmt.Errorf("status code: %d", resp.StatusCode) } - fd, err := os.Create(this.SaveFile) + + /* + log.Println("headers:", resp.Header) + switch resp.Header.Get("Content-Type") { + case "image/jpeg": + this.SaveFile += ".jpeg" + case "image/png": + this.SaveFile += ".png" + } + */ + /* + imgType := resp.Header.Get("Content-Type") + if imgType != "image/jpeg" && imgType != "image/png" { + return errors.New("not png or jpeg") + } + */ + + tmpFile := this.SaveFile + ".part" // mv to destination when finished + fd, err := os.Create(tmpFile) if err != nil { return err } - defer fd.Close() _, err = io.Copy(fd, resp.Body) + fd.Close() if err != nil { + os.Remove(tmpFile) return err } - return nil + return os.Rename(tmpFile, this.SaveFile) } From 964e537479c497a5ba42799a1c1a7c430720e990 Mon Sep 17 00:00:00 2001 From: Gogs Date: Sun, 23 Mar 2014 18:13:23 +0800 Subject: [PATCH 5/6] append route to web --- conf/app.ini | 8 +- models/user.go | 2 +- modules/avatar/avatar.go | 137 +++++++++++++++++++--------------- modules/avatar/avatar_test.go | 41 +++++++--- modules/base/tool.go | 2 +- public/img/avatar/default.jpg | Bin 0 -> 17379 bytes web.go | 5 +- 7 files changed, 118 insertions(+), 77 deletions(-) create mode 100644 public/img/avatar/default.jpg diff --git a/conf/app.ini b/conf/app.ini index ecb0d2511f..160aef0ffe 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -7,7 +7,7 @@ RUN_USER = lunny RUN_MODE = dev [repository] -ROOT = /Users/%(RUN_USER)s/git/gogs-repositories +ROOT = /home/work/%(RUN_USER)s/git/gogs-repositories LANG_IGNS = Google Go|C|C++|Python|Ruby|C Sharp LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|BSD (3-Clause) License @@ -15,7 +15,7 @@ LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0| DOMAIN = localhost ROOT_URL = http://%(DOMAIN)s:%(HTTP_PORT)s/ HTTP_ADDR = -HTTP_PORT = 3000 +HTTP_PORT = 8002 [database] ; Either "mysql", "postgres" or "sqlite3"(binary release only), it's your choice @@ -23,7 +23,7 @@ DB_TYPE = mysql HOST = NAME = gogs USER = root -PASSWD = +PASSWD = toor ; For "postgres" only, either "disable", "require" or "verify-full" SSL_MODE = disable ; For "sqlite3" only @@ -120,4 +120,4 @@ HOST = USER = PASSWD = ; Receivers, can be one or more, e.g. ["1@example.com","2@example.com"] -RECEIVERS = \ No newline at end of file +RECEIVERS = diff --git a/models/user.go b/models/user.go index 3c11091285..cedf342496 100644 --- a/models/user.go +++ b/models/user.go @@ -72,7 +72,7 @@ func (user *User) HomeLink() string { // AvatarLink returns the user gravatar link. func (user *User) AvatarLink() string { - return "http://1.gravatar.com/avatar/" + user.Avatar + return "/avatar/" + user.Avatar } // NewGitSig generates and returns the signature of given user. diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go index 55d1e13d94..1a18d8a7ec 100644 --- a/modules/avatar/avatar.go +++ b/modules/avatar/avatar.go @@ -1,3 +1,8 @@ +// Copyright 2014 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +// for www.gravatar.com image cache package avatar import ( @@ -22,11 +27,17 @@ import ( ) var ( - gravatar = "http://www.gravatar.com/avatar" - defaultImagePath = "./default.jpg" + gravatar = "http://www.gravatar.com/avatar" ) +func debug(a ...interface{}) { + if true { + log.Println(a...) + } +} + // hash email to md5 string +// keep this func in order to make this package indenpent func HashEmail(email string) string { h := md5.New() h.Write([]byte(strings.ToLower(email))) @@ -35,6 +46,7 @@ func HashEmail(email string) string { type Avatar struct { Hash string + AlterImage string // image path cacheDir string // image save dir reqParams string imagePath string @@ -54,7 +66,7 @@ func New(hash string, cacheDir string) *Avatar { } } -func (this *Avatar) InCache() bool { +func (this *Avatar) HasCache() bool { fileInfo, err := os.Stat(this.imagePath) return err == nil && fileInfo.Mode().IsRegular() } @@ -68,11 +80,8 @@ func (this *Avatar) Modtime() (modtime time.Time, err error) { } func (this *Avatar) Expired() bool { - if !this.InCache() { - return true - } - fileInfo, err := os.Stat(this.imagePath) - return err != nil || time.Since(fileInfo.ModTime()) > this.expireDuration + modtime, err := this.Modtime() + return err != nil || time.Since(modtime) > this.expireDuration } // default image format: jpeg @@ -92,8 +101,11 @@ func (this *Avatar) Encode(wr io.Writer, size int) (err error) { return } imgPath := this.imagePath - if !this.InCache() { - imgPath = defaultImagePath + if !this.HasCache() { + if this.AlterImage == "" { + return errors.New("request image failed, and no alt image offered") + } + imgPath = this.AlterImage } img, err = decodeImageFile(imgPath) if err != nil { @@ -120,61 +132,66 @@ func (this *Avatar) UpdateTimeout(timeout time.Duration) error { return err } -func init() { - log.SetFlags(log.Lshortfile | log.LstdFlags) +type avatarHandler struct { + cacheDir string + altImage string +} + +func (this *avatarHandler) mustInt(r *http.Request, defaultValue int, keys ...string) int { + var v int + for _, k := range keys { + if _, err := fmt.Sscanf(r.FormValue(k), "%d", &v); err == nil { + defaultValue = v + } + } + return defaultValue +} + +func (this *avatarHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + urlPath := r.URL.Path + hash := urlPath[strings.LastIndex(urlPath, "/")+1:] + //hash = HashEmail(hash) + size := this.mustInt(r, 80, "s", "size") // size = 80*80 + + avatar := New(hash, this.cacheDir) + avatar.AlterImage = this.altImage + if avatar.Expired() { + err := avatar.UpdateTimeout(time.Millisecond * 500) + if err != nil { + debug(err) + //log.Trace("avatar update error: %v", err) + } + } + if modtime, err := avatar.Modtime(); err == nil { + etag := fmt.Sprintf("size(%d)", size) + if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) && etag == r.Header.Get("If-None-Match") { + h := w.Header() + delete(h, "Content-Type") + delete(h, "Content-Length") + w.WriteHeader(http.StatusNotModified) + return + } + w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat)) + w.Header().Set("ETag", etag) + } + w.Header().Set("Content-Type", "image/jpeg") + err := avatar.Encode(w, size) + if err != nil { + //log.Warn("avatar encode error: %v", err) // will panic when err != nil + debug(err) + w.WriteHeader(500) + } } // http.Handle("/avatar/", avatar.HttpHandler("./cache")) -func HttpHandler(cacheDir string) func(w http.ResponseWriter, r *http.Request) { - MustInt := func(r *http.Request, defaultValue int, keys ...string) int { - var v int - for _, k := range keys { - if _, err := fmt.Sscanf(r.FormValue(k), "%d", &v); err == nil { - defaultValue = v - } - } - return defaultValue - } - - return func(w http.ResponseWriter, r *http.Request) { - urlPath := r.URL.Path - hash := urlPath[strings.LastIndex(urlPath, "/")+1:] - hash = HashEmail(hash) - size := MustInt(r, 80, "s", "size") // size = 80*80 - - avatar := New(hash, cacheDir) - if avatar.Expired() { - err := avatar.UpdateTimeout(time.Millisecond * 500) - if err != nil { - log.Println(err) - } - } - if modtime, err := avatar.Modtime(); err == nil { - etag := fmt.Sprintf("size(%d)", size) - if t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since")); err == nil && modtime.Before(t.Add(1*time.Second)) && etag == r.Header.Get("If-None-Match") { - h := w.Header() - delete(h, "Content-Type") - delete(h, "Content-Length") - w.WriteHeader(http.StatusNotModified) - return - } - w.Header().Set("Last-Modified", modtime.UTC().Format(http.TimeFormat)) - w.Header().Set("ETag", etag) - } - w.Header().Set("Content-Type", "image/jpeg") - err := avatar.Encode(w, size) - if err != nil { - log.Println(err) - w.WriteHeader(500) - } +func HttpHandler(cacheDir string, defaultImgPath string) http.Handler { + return &avatarHandler{ + cacheDir: cacheDir, + altImage: defaultImgPath, } } -func init() { - http.HandleFunc("/", HttpHandler("./")) - log.Fatal(http.ListenAndServe(":8001", nil)) -} - +// thunder downloader var thunder = &Thunder{QueueSize: 10} type Thunder struct { @@ -234,7 +251,7 @@ func (this *thunderTask) Fetch() { var client = &http.Client{} func (this *thunderTask) fetch() error { - log.Println("thunder, fetch", this.Url) + //log.Println("thunder, fetch", this.Url) req, _ := http.NewRequest("GET", this.Url, nil) req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8") req.Header.Set("Accept-Encoding", "gzip,deflate,sdch") diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go index 49f8f91f35..a337959c6f 100644 --- a/modules/avatar/avatar_test.go +++ b/modules/avatar/avatar_test.go @@ -1,29 +1,41 @@ -package avatar +// Copyright 2014 The Gogs 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 avatar_test import ( "log" + "os" "strconv" "testing" "time" + + "github.com/gogits/gogs/modules/avatar" ) +const TMPDIR = "test-avatar" + func TestFetch(t *testing.T) { - hash := HashEmail("ssx205@gmail.com") - avatar := New(hash, "./") - //avatar.Update() - avatar.UpdateTimeout(time.Millisecond * 200) - time.Sleep(5 * time.Second) + os.Mkdir(TMPDIR, 0755) + defer os.RemoveAll(TMPDIR) + + hash := avatar.HashEmail("ssx205@gmail.com") + a := avatar.New(hash, TMPDIR) + a.UpdateTimeout(time.Millisecond * 200) } func TestFetchMany(t *testing.T) { + os.Mkdir(TMPDIR, 0755) + defer os.RemoveAll(TMPDIR) + log.Println("start") - var n = 50 + var n = 5 ch := make(chan bool, n) for i := 0; i < n; i++ { go func(i int) { - hash := HashEmail(strconv.Itoa(i) + "ssx205@gmail.com") - avatar := New(hash, "./") - avatar.Update() + hash := avatar.HashEmail(strconv.Itoa(i) + "ssx205@gmail.com") + a := avatar.New(hash, TMPDIR) + a.Update() log.Println("finish", hash) ch <- true }(i) @@ -33,3 +45,12 @@ func TestFetchMany(t *testing.T) { } log.Println("end") } + +// cat +// wget http://www.artsjournal.com/artfulmanager/wp/wp-content/uploads/2013/12/200x200xmirror_cat.jpg.pagespeed.ic.GOZSv6v1_H.jpg -O default.jpg +/* +func TestHttp(t *testing.T) { + http.Handle("/", avatar.HttpHandler("./", "default.jpg")) + http.ListenAndServe(":8001", nil) +} +*/ diff --git a/modules/base/tool.go b/modules/base/tool.go index 8fabb8c531..8d0d38216d 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -98,7 +98,7 @@ func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string // AvatarLink returns avatar link by given e-mail. func AvatarLink(email string) string { - return "http://1.gravatar.com/avatar/" + EncodeMd5(email) + return "/avatar/" + EncodeMd5(email) } // Seconds-based time units diff --git a/public/img/avatar/default.jpg b/public/img/avatar/default.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c5a698da91f00405ca92b87252def7a5425ecd4b GIT binary patch literal 17379 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 DbPBAW literal 0 HcmV?d00001 diff --git a/web.go b/web.go index bb316a6724..637ee7ce95 100644 --- a/web.go +++ b/web.go @@ -18,6 +18,7 @@ import ( "github.com/gogits/gogs/models" "github.com/gogits/gogs/modules/auth" + "github.com/gogits/gogs/modules/avatar" "github.com/gogits/gogs/modules/base" "github.com/gogits/gogs/modules/log" "github.com/gogits/gogs/modules/mailer" @@ -114,6 +115,9 @@ func runWeb(*cli.Context) { m.Get("/help", routers.Help) + avatarHandler := avatar.HttpHandler("public/img/avatar", "public/img/avatar/default.jpg") + m.Get("/avatar/:hash", avatarHandler.ServeHTTP) + adminReq := middleware.AdminRequire() m.Get("/admin", reqSignIn, adminReq, admin.Dashboard) m.Get("/admin/users", reqSignIn, adminReq, admin.Users) @@ -136,7 +140,6 @@ func runWeb(*cli.Context) { ignSignIn, middleware.RepoAssignment(true), repo.Single) m.Get("/:username/:reponame/commit/:commitid/**", ignSignIn, middleware.RepoAssignment(true), repo.Single) m.Get("/:username/:reponame/commit/:commitid", ignSignIn, middleware.RepoAssignment(true), repo.Single) - m.Get("/:username/:reponame", ignSignIn, middleware.RepoAssignment(true), repo.Single) m.Any("/:username/:reponame/**", ignSignIn, repo.Http) From 0d39c18b6ad715a68144d3d9e7f9ea3893f07d4f Mon Sep 17 00:00:00 2001 From: Gogs Date: Sun, 23 Mar 2014 18:19:24 +0800 Subject: [PATCH 6/6] rollback conf/app.ini --- conf/app.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/conf/app.ini b/conf/app.ini index 160aef0ffe..ecb0d2511f 100644 --- a/conf/app.ini +++ b/conf/app.ini @@ -7,7 +7,7 @@ RUN_USER = lunny RUN_MODE = dev [repository] -ROOT = /home/work/%(RUN_USER)s/git/gogs-repositories +ROOT = /Users/%(RUN_USER)s/git/gogs-repositories LANG_IGNS = Google Go|C|C++|Python|Ruby|C Sharp LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|BSD (3-Clause) License @@ -15,7 +15,7 @@ LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0| DOMAIN = localhost ROOT_URL = http://%(DOMAIN)s:%(HTTP_PORT)s/ HTTP_ADDR = -HTTP_PORT = 8002 +HTTP_PORT = 3000 [database] ; Either "mysql", "postgres" or "sqlite3"(binary release only), it's your choice @@ -23,7 +23,7 @@ DB_TYPE = mysql HOST = NAME = gogs USER = root -PASSWD = toor +PASSWD = ; For "postgres" only, either "disable", "require" or "verify-full" SSL_MODE = disable ; For "sqlite3" only @@ -120,4 +120,4 @@ HOST = USER = PASSWD = ; Receivers, can be one or more, e.g. ["1@example.com","2@example.com"] -RECEIVERS = +RECEIVERS = \ No newline at end of file