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 (
"fmt" "fmt"
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
"log" "log"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -74,6 +75,7 @@ type Hub struct {
buffers BufferCache buffers BufferCache
broadcastChatMessages uint64 broadcastChatMessages uint64
unicastChatMessages uint64 unicastChatMessages uint64
buddyImages ImageCache
} }
func NewHub(version string, config *Config, sessionSecret string, turnSecret string) *Hub { 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
h.tickets = securecookie.New(h.sessionSecret, nil) h.tickets = securecookie.New(h.sessionSecret, nil)
h.buffers = NewBufferCache(1024, bytes.MinRead) h.buffers = NewBufferCache(1024, bytes.MinRead)
h.buddyImages = NewImageCache()
return h return h
} }
@ -318,6 +321,18 @@ func (h *Hub) userupdateHandler(u *UserUpdate) uint64 {
var rev uint64 var rev uint64
if ok { if ok {
rev = user.Update(u) 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 { } else {
log.Printf("Update data for unknown user %s\n", u.Id) log.Printf("Update data for unknown user %s\n", u.Id)
} }

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

@ -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 (
"os" "os"
"path" "path"
goruntime "runtime" goruntime "runtime"
"strconv"
"strings" "strings"
"syscall" "syscall"
"time" "time"
@ -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) { func handleRoomView(room string, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=UTF-8") w.Header().Set("Content-Type", "text/html; charset=UTF-8")
@ -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("/favicon.ico", http.StripPrefix(basePath, http.FileServer(http.Dir(path.Join(rootFolder, "static", "img")))))
r.Handle("/ws", makeWsHubHandler(hub)) r.Handle("/ws", makeWsHubHandler(hub))
r.HandleFunc("/{room}", httputils.MakeGzipHandler(roomHandler)) r.HandleFunc("/{room}", httputils.MakeGzipHandler(roomHandler))
r.Handle("/img/buddy/{imageid}/{idx:.*}", http.StripPrefix(basePath, makeImageHandler(hub, time.Hour)))
// Add API end points. // Add API end points.
api := sleepy.NewAPI(r.PathPrefix("/api/v1/").Subrouter()) api := sleepy.NewAPI(r.PathPrefix("/api/v1/").Subrouter())

25
static/js/services/buddylist.js

@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * 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() { var BuddyTree = function() {
@ -122,6 +122,9 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text!
var doc = $window.document; var doc = $window.document;
var buddyCount = 0; var buddyCount = 0;
var globalcontext = $("#globalcontext").text();
var context = JSON.parse(globalcontext);
var Buddylist = function($element, $scope, opts) { var Buddylist = function($element, $scope, opts) {
this.$scope = $scope; this.$scope = $scope;
@ -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) { Buddylist.prototype.onStatus = function(status) {
//console.log("onStatus", status); //console.log("onStatus", status);
@ -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); console.warn("Received old status update in status", status.Rev, scope.status.Rev);
} else { } else {
scope.status = status.Status; scope.status = status.Status;
if (scope.status.buddyPicture) {
scope.status.buddyPicture = this.updateBuddyPicture(scope.status.buddyPicture);
}
var displayName = scope.displayName; var displayName = scope.displayName;
if (scope.status.displayName) { if (scope.status.displayName) {
scope.displayName = scope.status.displayName; scope.displayName = scope.status.displayName;
@ -354,6 +374,9 @@ define(['underscore', 'modernizr', 'avltree', 'text!partials/buddy.html', 'text!
} else { } else {
scope.status = user.Status; scope.status = user.Status;
scope.displayName = scope.status.displayName; scope.displayName = scope.status.displayName;
if (scope.status.buddyPicture) {
scope.status.buddyPicture = this.updateBuddyPicture(scope.status.buddyPicture);
}
} }
} }
//console.log("Joined scope", scope, scope.element); //console.log("Joined scope", scope, scope.element);

Loading…
Cancel
Save