From 964e537479c497a5ba42799a1c1a7c430720e990 Mon Sep 17 00:00:00 2001
From: Gogs <gogitservice@gmail.com>
Date: Sun, 23 Mar 2014 18:13:23 +0800
Subject: [PATCH] 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 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
+}
+
+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 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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

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)