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 9e2440f3e..5b690a06a 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 2c3ecd72d..863daca64 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 d70af3c50..2e19b99c1 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 ad32ec3fb..6906f2409 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 4b6dedaf9..cf1e1df5c 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 76cf2d20c..69608ec27 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 c10d30a7d..064150a23 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 ceb193e6f..f083b5508 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 000000000..93f842eae --- /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 000000000..49f8f91f3 --- /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 93f842eae..55d1e13d9 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 | 123 +++++++++++++++++++--------------- 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, 111 insertions(+), 70 deletions(-) create mode 100644 public/img/avatar/default.jpg diff --git a/conf/app.ini b/conf/app.ini index ecb0d2511..160aef0ff 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 3c1109128..cedf34249 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 55d1e13d9..1a18d8a7e 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 } -// 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 - } +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 } + 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 +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, 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) + avatar := New(hash, this.cacheDir) + avatar.AlterImage = this.altImage + if avatar.Expired() { + err := avatar.UpdateTimeout(time.Millisecond * 500) if err != nil { - log.Println(err) - w.WriteHeader(500) + 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) } } -func init() { - http.HandleFunc("/", HttpHandler("./")) - log.Fatal(http.ListenAndServe(":8001", nil)) +// http.Handle("/avatar/", avatar.HttpHandler("./cache")) +func HttpHandler(cacheDir string, defaultImgPath string) http.Handler { + return &avatarHandler{ + cacheDir: cacheDir, + altImage: defaultImgPath, + } } +// 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 49f8f91f3..a337959c6 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 8fabb8c53..8d0d38216 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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�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 literal 0 HcmV?d00001 diff --git a/web.go b/web.go index bb316a672..637ee7ce9 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 160aef0ff..ecb0d2511 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