Browse Source

Load buddy images from URL.

This allows the frontend proxy to cache images and avoids hitting the backend server too often. This also is required in preparation for further changes to move user data away from memory.
pull/15/head
Joachim Bauch 12 years ago
parent
commit
8794894302
  1. 15
      src/app/spreed-speakfreely-server/hub.go
  2. 99
      src/app/spreed-speakfreely-server/images.go
  3. 25
      src/app/spreed-speakfreely-server/main.go
  4. 25
      static/js/services/buddylist.js

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

@ -29,6 +29,7 @@ import ( @@ -29,6 +29,7 @@ import (
"fmt"
"github.com/gorilla/securecookie"
"log"
"strings"
"sync"
"sync/atomic"
"time"
@ -74,6 +75,7 @@ type Hub struct { @@ -74,6 +75,7 @@ type Hub struct {
buffers BufferCache
broadcastChatMessages uint64
unicastChatMessages uint64
buddyImages ImageCache
}
func NewHub(version string, config *Config, sessionSecret string, turnSecret string) *Hub {
@ -90,6 +92,7 @@ func NewHub(version string, config *Config, sessionSecret string, turnSecret str @@ -90,6 +92,7 @@ func NewHub(version string, config *Config, sessionSecret string, turnSecret str
h.tickets = securecookie.New(h.sessionSecret, nil)
h.buffers = NewBufferCache(1024, bytes.MinRead)
h.buddyImages = NewImageCache()
return h
}
@ -318,6 +321,18 @@ func (h *Hub) userupdateHandler(u *UserUpdate) uint64 { @@ -318,6 +321,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)
}

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

@ -0,0 +1,99 @@ @@ -0,0 +1,99 @@
package main
import (
"crypto/rand"
"encoding/base64"
"log"
"strconv"
"strings"
"sync"
)
type Image struct {
updateIdx int
userid string
mimetype string
data []byte
}
type ImageCache interface {
Update(userId string, image string) string
Get(imageId string) *Image
}
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)
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, 16, 16)
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()
}
img.updateIdx++
img.mimetype = mimetype
img.data = decoded
return result + "/" + strconv.Itoa(img.updateIdx)
}
func (self *imageCache) Get(imageId string) *Image {
self.mutex.RLock()
image := self.images[imageId]
self.mutex.RUnlock()
return image
}

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

@ -34,6 +34,7 @@ import ( @@ -34,6 +34,7 @@ import (
"os"
"path"
goruntime "runtime"
"strconv"
"strings"
"syscall"
"time"
@ -85,6 +86,29 @@ func roomHandler(w http.ResponseWriter, r *http.Request) { @@ -85,6 +86,29 @@ 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("Content-Length", strconv.Itoa(len(image.data)))
w.Header().Set("Date", time.Now().Format(time.RFC822))
if expires >= time.Second {
w.Header().Set("Expires", time.Now().Add(expires).Format(time.RFC1123))
w.Header().Set("Cache-Control", "max-age="+strconv.Itoa(int(expires.Seconds())))
}
w.Write(image.data)
}
}
func handleRoomView(room string, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=UTF-8")
@ -300,6 +324,7 @@ func runner(runtime phoenix.Runtime) error { @@ -300,6 +324,7 @@ func runner(runtime phoenix.Runtime) error {
r.Handle("/favicon.ico", http.StripPrefix(basePath, http.FileServer(http.Dir(path.Join(rootFolder, "static", "img")))))
r.Handle("/ws", makeWsHubHandler(hub))
r.HandleFunc("/{room}", httputils.MakeGzipHandler(roomHandler))
r.Handle("/img/buddy/{imageid}/{idx:.*}", http.StripPrefix(basePath, makeImageHandler(hub, time.Hour)))
// Add API end points.
api := sleepy.NewAPI(r.PathPrefix("/api/v1/").Subrouter())

25
static/js/services/buddylist.js

@ -18,7 +18,7 @@ @@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text!partials/buddyactions.html', 'text!partials/buddyactionsforaudiomixer.html', 'rAF'], function(_, Modernizr, AvlTree, templateBuddy, templateBuddyActions, templateBuddyActionsForAudioMixer) {
define(['underscore', 'modernizr', 'jquery', 'avltree', 'text!partials/buddy.html', 'text!partials/buddyactions.html', 'text!partials/buddyactionsforaudiomixer.html', 'rAF'], function(_, Modernizr, $, AvlTree, templateBuddy, templateBuddyActions, templateBuddyActionsForAudioMixer) {
var BuddyTree = function() {
@ -122,6 +122,9 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! @@ -122,6 +122,9 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text!
var doc = $window.document;
var buddyCount = 0;
var globalcontext = $("#globalcontext").text();
var context = JSON.parse(globalcontext);
var Buddylist = function($element, $scope, opts) {
this.$scope = $scope;
@ -317,6 +320,20 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! @@ -317,6 +320,20 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text!
};
Buddylist.prototype.updateBuddyPicture = function(url) {
if (url.indexOf("data:") === 0) {
// can use data: urls directly
return url;
} else if (url.indexOf("img:") === 0) {
return context.Cfg.B + "img/buddy/"+url.substr(4);
}
console.log("Unknown buddy picture url", url);
return url;
};
Buddylist.prototype.onStatus = function(status) {
//console.log("onStatus", status);
@ -326,6 +343,9 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! @@ -326,6 +343,9 @@ 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;
if (scope.status.buddyPicture) {
scope.status.buddyPicture = this.updateBuddyPicture(scope.status.buddyPicture);
}
var displayName = scope.displayName;
if (scope.status.displayName) {
scope.displayName = scope.status.displayName;
@ -354,6 +374,9 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text! @@ -354,6 +374,9 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text!
} else {
scope.status = user.Status;
scope.displayName = scope.status.displayName;
if (scope.status.buddyPicture) {
scope.status.buddyPicture = this.updateBuddyPicture(scope.status.buddyPicture);
}
}
}
//console.log("Joined scope", scope, scope.element);

Loading…
Cancel
Save