mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 22:28:05 +01:00 
			
		
		
		
	
						commit
						8b43bd1c37
					
				| @ -10,4 +10,4 @@ | ||||
| # Compiled Static libraries | ||||
| *.lai | ||||
| *.la | ||||
| *.a | ||||
| *.a | ||||
|  | ||||
| @ -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. | ||||
|  | ||||
							
								
								
									
										298
									
								
								modules/avatar/avatar.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										298
									
								
								modules/avatar/avatar.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,298 @@ | ||||
| // 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 ( | ||||
| 	"crypto/md5" | ||||
| 	"encoding/hex" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"image" | ||||
| 	"image/jpeg" | ||||
| 	"image/png" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/nfnt/resize" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	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))) | ||||
| 	return hex.EncodeToString(h.Sum(nil)) | ||||
| } | ||||
| 
 | ||||
| type Avatar struct { | ||||
| 	Hash           string | ||||
| 	AlterImage     string // image path | ||||
| 	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, | ||||
| 		expireDuration: time.Minute * 10, | ||||
| 		reqParams: url.Values{ | ||||
| 			"d":    {"retro"}, | ||||
| 			"size": {"200"}, | ||||
| 			"r":    {"pg"}}.Encode(), | ||||
| 		imagePath: filepath.Join(cacheDir, hash+".image"), //maybe png or jpeg | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (this *Avatar) HasCache() 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 { | ||||
| 	modtime, err := this.Modtime() | ||||
| 	return err != nil || time.Since(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.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 { | ||||
| 		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.imagePath) | ||||
| } | ||||
| 
 | ||||
| func (this *Avatar) UpdateTimeout(timeout time.Duration) error { | ||||
| 	var err error | ||||
| 	select { | ||||
| 	case <-time.After(timeout): | ||||
| 		err = errors.New("get gravatar image timeout") | ||||
| 	case err = <-thunder.GoFetch(gravatar+"/"+this.Hash+"?"+this.reqParams, | ||||
| 		this.imagePath): | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| 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, defaultImgPath string) http.Handler { | ||||
| 	return &avatarHandler{ | ||||
| 		cacheDir: cacheDir, | ||||
| 		altImage: defaultImgPath, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // thunder downloader | ||||
| 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() | ||||
| } | ||||
| 
 | ||||
| var client = &http.Client{} | ||||
| 
 | ||||
| func (this *thunderTask) fetch() error { | ||||
| 	//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 | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
| 	if resp.StatusCode != 200 { | ||||
| 		return fmt.Errorf("status code: %d", resp.StatusCode) | ||||
| 	} | ||||
| 
 | ||||
| 	/* | ||||
| 		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 | ||||
| 	} | ||||
| 	_, err = io.Copy(fd, resp.Body) | ||||
| 	fd.Close() | ||||
| 	if err != nil { | ||||
| 		os.Remove(tmpFile) | ||||
| 		return err | ||||
| 	} | ||||
| 	return os.Rename(tmpFile, this.SaveFile) | ||||
| } | ||||
							
								
								
									
										56
									
								
								modules/avatar/avatar_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								modules/avatar/avatar_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| // 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) { | ||||
| 	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 = 5 | ||||
| 	ch := make(chan bool, n) | ||||
| 	for i := 0; i < n; i++ { | ||||
| 		go func(i int) { | ||||
| 			hash := avatar.HashEmail(strconv.Itoa(i) + "ssx205@gmail.com") | ||||
| 			a := avatar.New(hash, TMPDIR) | ||||
| 			a.Update() | ||||
| 			log.Println("finish", hash) | ||||
| 			ch <- true | ||||
| 		}(i) | ||||
| 	} | ||||
| 	for i := 0; i < n; i++ { | ||||
| 		<-ch | ||||
| 	} | ||||
| 	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) | ||||
| } | ||||
| */ | ||||
| @ -102,7 +102,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 | ||||
|  | ||||
							
								
								
									
										
											BIN
										
									
								
								public/img/avatar/default.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								public/img/avatar/default.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 6.8 KiB | 
							
								
								
									
										4
									
								
								web.go
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								web.go
									
									
									
									
									
								
							| @ -17,6 +17,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" | ||||
| @ -93,6 +94,9 @@ func runWeb(*cli.Context) { | ||||
| 	m.Get("/stars", reqSignIn, user.Stars) | ||||
| 	m.Get("/help", routers.Help) | ||||
| 
 | ||||
| 	avatarCache := avatar.HttpHandler("public/img/avatar/", "public/img/avatar/default.jpg") | ||||
| 	m.Get("/avatar/:hash", avatarCache.ServeHTTP) | ||||
| 
 | ||||
| 	m.Group("/user", func(r martini.Router) { | ||||
| 		r.Any("/login", binding.BindIgnErr(auth.LogInForm{}), user.SignIn) | ||||
| 		r.Any("/sign_up", binding.BindIgnErr(auth.RegisterForm{}), user.SignUp) | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user