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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 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