Browse Source

Merge pull request #15 from fancycode/buddy_image_urls

Load buddy images separately
pull/13/merge
Simon Eisenmann 11 years ago
parent
commit
1122860f84
  1. 19
      src/app/spreed-speakfreely-server/hub.go
  2. 135
      src/app/spreed-speakfreely-server/images.go
  3. 29
      src/app/spreed-speakfreely-server/main.go
  4. 17
      static/js/services/buddylist.js
  5. 3
      static/js/services/mediastream.js
  6. 4
      static/partials/buddy.html

19
src/app/spreed-speakfreely-server/hub.go

@ -30,6 +30,7 @@ import ( @@ -30,6 +30,7 @@ import (
"fmt"
"github.com/gorilla/securecookie"
"log"
"strings"
"sync"
"sync/atomic"
"time"
@ -75,6 +76,7 @@ type Hub struct { @@ -75,6 +76,7 @@ type Hub struct {
buffers BufferCache
broadcastChatMessages uint64
unicastChatMessages uint64
buddyImages ImageCache
}
func NewHub(version string, config *Config, sessionSecret, turnSecret string) *Hub {
@ -91,6 +93,7 @@ func NewHub(version string, config *Config, sessionSecret, turnSecret string) *H @@ -91,6 +93,7 @@ func NewHub(version string, config *Config, sessionSecret, turnSecret string) *H
h.tickets = securecookie.New(h.sessionSecret, nil)
h.buffers = NewBufferCache(1024, bytes.MinRead)
h.buddyImages = NewImageCache()
return h
}
@ -277,10 +280,14 @@ func (h *Hub) unregisterHandler(c *Connection) { @@ -277,10 +280,14 @@ func (h *Hub) unregisterHandler(c *Connection) {
h.mutex.Unlock()
return
}
user := c.User
c.close()
delete(h.connectionTable, c.Id)
delete(h.userTable, c.Id)
h.mutex.Unlock()
if user != nil {
h.buddyImages.DeleteUserImage(user.Id)
}
//log.Printf("Unregister (%d) from %s: %s\n", c.Idx, c.RemoteAddr, c.Id)
h.server.OnUnregister(c)
@ -323,6 +330,18 @@ func (h *Hub) userupdateHandler(u *UserUpdate) uint64 { @@ -323,6 +330,18 @@ func (h *Hub) userupdateHandler(u *UserUpdate) uint64 {
var rev uint64
if ok {
rev = user.Update(u)
if u.Status != nil {
status, ok := u.Status.(map[string]interface{})
if ok && status["buddyPicture"] != nil {
pic := status["buddyPicture"].(string)
if strings.HasPrefix(pic, "data:") {
imageId := h.buddyImages.Update(u.Id, pic[5:])
if imageId != "" {
status["buddyPicture"] = "img:" + imageId
}
}
}
}
} else {
log.Printf("Update data for unknown user %s\n", u.Id)
}

135
src/app/spreed-speakfreely-server/images.go

@ -0,0 +1,135 @@ @@ -0,0 +1,135 @@
package main
import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/binary"
"log"
"strings"
"sync"
"time"
)
var imageFilenames map[string]string
type Image struct {
updateIdx int
lastChange time.Time
lastChangeId string
userid string
mimetype string
data []byte
}
type ImageCache interface {
Update(userId string, image string) string
Get(imageId string) *Image
DeleteUserImage(userId string)
}
type imageCache struct {
images map[string]*Image
userImages map[string]string
mutex sync.RWMutex
}
func NewImageCache() ImageCache {
result := &imageCache{}
result.images = make(map[string]*Image)
result.userImages = make(map[string]string)
if imageFilenames == nil {
imageFilenames = map[string]string{
"image/png": "picture.png",
"image/jpeg": "picture.jpg",
"image/gif": "picture.gif",
}
}
return result
}
func (self *imageCache) Update(userId string, image string) string {
var mimetype string = "image/x-unknown"
pos := strings.Index(image, ";")
if pos != -1 {
mimetype = image[:pos]
image = image[pos+1:]
}
pos = strings.Index(image, ",")
var decoded []byte
var err error
if pos != -1 {
encoding := image[:pos]
switch encoding {
case "base64":
decoded, err = base64.StdEncoding.DecodeString(image[pos+1:])
if err != nil {
return ""
}
default:
log.Println("Unknown encoding", encoding)
return ""
}
} else {
decoded = []byte(image[pos+1:])
}
var img *Image
self.mutex.RLock()
result, ok := self.userImages[userId]
if !ok {
self.mutex.RUnlock()
imageId := make([]byte, 15, 15)
if _, err = rand.Read(imageId); err != nil {
return ""
}
result = base64.URLEncoding.EncodeToString(imageId)
img = &Image{userid: userId}
self.mutex.Lock()
resultTmp, ok := self.userImages[userId]
if !ok {
self.userImages[userId] = result
self.images[result] = img
} else {
result = resultTmp
img = self.images[result]
}
self.mutex.Unlock()
} else {
img = self.images[result]
self.mutex.RUnlock()
}
if mimetype != img.mimetype || !bytes.Equal(img.data, decoded) {
img.updateIdx++
img.lastChange = time.Now()
tmp := make([]byte, binary.MaxVarintLen64)
count := binary.PutUvarint(tmp, uint64(img.lastChange.UnixNano()))
img.lastChangeId = base64.URLEncoding.EncodeToString(tmp[:count])
img.mimetype = mimetype
img.data = decoded
}
result += "/" + img.lastChangeId
filename, ok := imageFilenames[mimetype]
if ok {
result += "/" + filename
}
return result
}
func (self *imageCache) Get(imageId string) *Image {
self.mutex.RLock()
image := self.images[imageId]
self.mutex.RUnlock()
return image
}
func (self *imageCache) DeleteUserImage(userId string) {
self.mutex.Lock()
imageId, ok := self.userImages[userId]
if ok {
delete(self.userImages, userId)
delete(self.images, imageId)
}
self.mutex.Unlock()
}

29
src/app/spreed-speakfreely-server/main.go

@ -22,6 +22,7 @@ package main @@ -22,6 +22,7 @@ package main
import (
"app/spreed-speakfreely-server/sleepy"
"bytes"
"flag"
"fmt"
"github.com/gorilla/mux"
@ -35,6 +36,7 @@ import ( @@ -35,6 +36,7 @@ import (
"os"
"path"
goruntime "runtime"
"strconv"
"strings"
"syscall"
"time"
@ -86,6 +88,32 @@ func roomHandler(w http.ResponseWriter, r *http.Request) { @@ -86,6 +88,32 @@ func roomHandler(w http.ResponseWriter, r *http.Request) {
}
func makeImageHandler(hub *Hub, expires time.Duration) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
image := hub.buddyImages.Get(vars["imageid"])
if image == nil {
http.Error(w, "Unknown image", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", image.mimetype)
w.Header().Set("ETag", image.lastChangeId)
age := time.Now().Sub(image.lastChange)
if age >= time.Second {
w.Header().Set("Age", strconv.Itoa(int(age.Seconds())))
}
if expires >= time.Second {
w.Header().Set("Expires", time.Now().Add(expires).Format(time.RFC1123))
w.Header().Set("Cache-Control", "public, no-transform, max-age="+strconv.Itoa(int(expires.Seconds())))
}
http.ServeContent(w, r, "", image.lastChange, bytes.NewReader(image.data))
}
}
func handleRoomView(room string, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
@ -303,6 +331,7 @@ func runner(runtime phoenix.Runtime) error { @@ -303,6 +331,7 @@ func runner(runtime phoenix.Runtime) error {
router := mux.NewRouter()
r := router.PathPrefix(basePath).Subrouter().StrictSlash(true)
r.HandleFunc("/", httputils.MakeGzipHandler(mainHandler))
r.Handle("/static/img/buddy/{flags}/{imageid}/{idx:.*}", http.StripPrefix(basePath, makeImageHandler(hub, time.Duration(24)*time.Hour)))
r.Handle("/static/{path:.*}", http.StripPrefix(basePath, httputils.FileStaticServer(http.Dir(rootFolder))))
r.Handle("/robots.txt", http.StripPrefix(basePath, http.FileServer(http.Dir(path.Join(rootFolder, "static")))))
r.Handle("/favicon.ico", http.StripPrefix(basePath, http.FileServer(http.Dir(path.Join(rootFolder, "static", "img")))))

17
static/js/services/buddylist.js

@ -110,7 +110,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! @@ -110,7 +110,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text!
};
// buddyList
return ["$window", "$compile", "playSound", "buddyData", "fastScroll", function ($window, $compile, playSound, buddyData, fastScroll) {
return ["$window", "$compile", "playSound", "buddyData", "fastScroll", "mediaStream", function ($window, $compile, playSound, buddyData, fastScroll, mediaStream) {
var requestAnimationFrame = $window.requestAnimationFrame;
@ -317,6 +317,19 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! @@ -317,6 +317,19 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text!
};
Buddylist.prototype.updateBuddyPicture = function(status) {
url = status.buddyPicture
if (!url) {
return;
}
if (url.indexOf("img:") === 0) {
status.buddyPicture = status.buddyPictureLocalUrl = mediaStream.url.buddy(url.substr(4));
}
};
Buddylist.prototype.onStatus = function(status) {
//console.log("onStatus", status);
@ -326,6 +339,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! @@ -326,6 +339,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text!
console.warn("Received old status update in status", status.Rev, scope.status.Rev);
} else {
scope.status = status.Status;
this.updateBuddyPicture(scope.status);
var displayName = scope.displayName;
if (scope.status.displayName) {
scope.displayName = scope.status.displayName;
@ -354,6 +368,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! @@ -354,6 +368,7 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text!
} else {
scope.status = user.Status;
scope.displayName = scope.status.displayName;
this.updateBuddyPicture(scope.status);
}
}
//console.log("Joined scope", scope, scope.element);

3
static/js/services/mediastream.js

@ -68,6 +68,9 @@ define([ @@ -68,6 +68,9 @@ define([
id = $window.encodeURIComponent(id);
return $window.location.protocol+'//'+$window.location.host+context.Cfg.B+id;
},
buddy: function(id) {
return $window.location.protocol+'//'+$window.location.host+context.Cfg.B+"static/img/buddy/s46/"+id;
},
api: function(path) {
return (context.Cfg.B || "/") + "api/v1/" + path;
}

4
static/partials/buddy.html

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
<div class="buddy withSubline" ng-click="doDefault(user.Id)">
<div class="buddyimage"><i class="fa fa-user fa-3x"/><img ng-show="status.buddyPicture" alt="" ng-src="{{status.buddyPicture}}"/></div>
<div class="buddyimage"><i class="fa fa-user fa-3x"/><img ng-show="status.buddyPicture" alt ng-src="{{status.buddyPicture}}" width="46" height="46"/></div>
<div class="buddy1">{{user.Id|displayName}}</div>
<div class="buddy2">{{user.Ua}}</div>
</div>
</div>

Loading…
Cancel
Save