From ed602e9d8c4cea5e8684fa3d61bbcc2360f400eb Mon Sep 17 00:00:00 2001 From: Mikkel Krautz Date: Mon, 11 Apr 2011 17:55:11 +0200 Subject: [PATCH] Hook blobstore into Grumble. --- Makefile | 2 +- channel.go | 22 ++++++++- client.go | 21 +++++---- freeze.go | 2 +- grumble.go | 9 ++++ message.go | 126 ++++++++++++++++++++++++++++------------------------ murmurdb.go | 18 +++++++- user.go | 31 +++++++++++++ 8 files changed, 159 insertions(+), 72 deletions(-) diff --git a/Makefile b/Makefile index 56ad839..acba41f 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ LDFLAGS = \ -Lpkg/cryptstate/_obj \ -Lpkg/packetdatastream/_obj \ -Lpkg/mumbleproto/_obj \ - -Ipkg/blobstore/_obj \ + -Lpkg/blobstore/_obj \ -Lpkg/sqlite/_obj GOFILES = \ diff --git a/channel.go b/channel.go index c65a3be..fef2e7c 100644 --- a/channel.go +++ b/channel.go @@ -4,6 +4,10 @@ package main +import ( + "encoding/hex" +) + // A Mumble channel type Channel struct { Id int @@ -27,8 +31,7 @@ type Channel struct { Links map[int]*Channel // Blobs - Description string - DescriptionHash []byte + DescriptionBlob string } func NewChannel(id int, name string) (channel *Channel) { @@ -65,3 +68,18 @@ func (channel *Channel) RemoveClient(client *Client) { channel.clients[client.Session] = nil, false client.Channel = nil } + +// Does the channel have a description? +func (channel *Channel) HasDescription() bool { + return len(channel.DescriptionBlob) > 0 +} + +// Get the channel's blob hash as a byte slice for sending via a protobuf message. +// Returns nil if there is no blob. +func (channel *Channel) DescriptionBlobHashBytes() (buf []byte) { + buf, err := hex.DecodeString(channel.DescriptionBlob) + if err != nil { + return nil + } + return buf +} diff --git a/client.go b/client.go index a5ab3d5..34b25da 100644 --- a/client.go +++ b/client.go @@ -60,12 +60,6 @@ type Client struct { OSName string OSVersion string - // Blobs - Comment string - CommentHash []byte - Texture []byte - TextureHash []byte - // Personal Username string Session uint32 @@ -527,17 +521,26 @@ func (client *Client) sendChannelList() { } func (client *Client) sendChannelTree(channel *Channel) { - // Start at the root channel. - log.Printf("sending channel ID=%i, NAME=%s", channel.Id, channel.Name) chanstate := &mumbleproto.ChannelState{ ChannelId: proto.Uint32(uint32(channel.Id)), Name: proto.String(channel.Name), - Description: proto.String(channel.Description), } if channel.parent != nil { chanstate.Parent = proto.Uint32(uint32(channel.parent.Id)) } + if channel.HasDescription() { + if client.Version >= 0x10202 { + chanstate.DescriptionHash = channel.DescriptionBlobHashBytes() + } else { + buf, err := globalBlobstore.Get(channel.DescriptionBlob) + if err != nil { + panic("Blobstore error.") + } + chanstate.Description = proto.String(string(buf)) + } + } + err := client.sendProtoMessage(MessageChannelState, chanstate) if err != nil { client.Panic(err.String()) diff --git a/freeze.go b/freeze.go index c44aef1..ed89310 100644 --- a/freeze.go +++ b/freeze.go @@ -195,7 +195,7 @@ func NewServerFromFrozen(filename string) (s *Server, err os.Error) { c := NewChannel(fc.Id, fc.Name) c.Position = int(fc.Position) c.InheritACL = fc.InheritACL - c.DescriptionHash = []byte{} // fixme + c.DescriptionBlob = fc.DescriptionBlob for _, facl := range fc.ACL { acl := NewChannelACL(c) diff --git a/grumble.go b/grumble.go index f29a06d..1bd5b65 100644 --- a/grumble.go +++ b/grumble.go @@ -5,6 +5,7 @@ package main import ( + "blobstore" "compress/gzip" "flag" "fmt" @@ -23,6 +24,8 @@ var blobdir *string = flag.String("blobdir", "", "Directory to use for blob stor var sqlitedb *string = flag.String("murmurdb", "", "Path to murmur.sqlite to import server structure from") var cleanup *bool = flag.Bool("clean", false, "Clean up existing data dir content before importing Murmur data") +var globalBlobstore *blobstore.BlobStore + func Usage() { fmt.Fprintf(os.Stderr, "usage: grumble [options]\n") flag.PrintDefaults() @@ -82,6 +85,7 @@ func MurmurImport(filename string) (err os.Error) { } func main() { + var err os.Error flag.Parse() if *help == true { Usage() @@ -98,7 +102,12 @@ func main() { if len(*blobdir) == 0 { *blobdir = filepath.Join(os.Getenv("HOME"), ".grumble", "blob") } + log.Printf("Using blob directory: %s", *blobdir) + globalBlobstore, err = blobstore.NewBlobStore(*blobdir, true) + if err != nil { + log.Fatalf("Unable to initialize blobstore: %v", err.String()) + } // Should we import data from a Murmur SQLite file? if len(*sqlitedb) > 0 { diff --git a/message.go b/message.go index f92582d..4898262 100644 --- a/message.go +++ b/message.go @@ -9,7 +9,6 @@ import ( "mumbleproto" "goprotobuf.googlecode.com/hg/proto" "net" - "crypto/sha1" "cryptstate" "fmt" ) @@ -234,22 +233,21 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) { return } + key := "" + if len(description) > 0 { + key, err = globalBlobstore.Put([]byte(description)) + if err != nil { + log.Panicf("Blobstore error: %v", err.String()) + } + } + // Add the new channel channel = server.AddChannel(name) - channel.Description = description + channel.DescriptionBlob = key channel.Temporary = *chanstate.Temporary channel.Position = int(*chanstate.Position) parent.AddChild(channel) - // Generate description hash. - if len(channel.Description) >= 128 { - hash := sha1.New() - hash.Write([]byte(channel.Description)) - channel.DescriptionHash = hash.Sum() - } else { - channel.DescriptionHash = []byte{} - } - // Add the creator to the channel's admin group if client.IsRegistered() { grp := NewGroup(channel, "admin") @@ -282,10 +280,11 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) { server.broadcastProtoMessageWithPredicate(MessageChannelState, chanstate, func(client *Client) bool { return client.Version < 0x10202 }) + // Remove description if client knows how to handle blobs. - if len(channel.DescriptionHash) > 0 { + if chanstate.Description != nil && channel.HasDescription() { chanstate.Description = nil - chanstate.DescriptionHash = channel.DescriptionHash + chanstate.DescriptionHash = channel.DescriptionBlobHashBytes() } server.broadcastProtoMessageWithPredicate(MessageChannelState, chanstate, func(client *Client) bool { return client.Version >= 0x10202 @@ -415,14 +414,11 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) { // Description change if chanstate.Description != nil { - // Generate description hash. - if len(channel.Description) >= 128 { - hash := sha1.New() - hash.Write([]byte(channel.Description)) - channel.DescriptionHash = hash.Sum() - } else { - channel.DescriptionHash = []byte{} + key, err := globalBlobstore.Put([]byte(*chanstate.Description)) + if err != nil { + log.Panicf("Blobstore error: %v", err.String()) } + channel.DescriptionBlob = key } // Position change @@ -444,11 +440,13 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) { server.broadcastProtoMessageWithPredicate(MessageChannelState, chanstate, func(client *Client) bool { return client.Version < 0x10202 }) + // Remove description blob when sending to 1.2.2 >= users. Only send the blob hash. - if chanstate.Description != nil && len(channel.DescriptionHash) > 0 { + if channel.HasDescription() { chanstate.Description = nil - chanstate.DescriptionHash = channel.DescriptionHash + chanstate.DescriptionHash = channel.DescriptionBlobHashBytes() } + chanstate.DescriptionHash = channel.DescriptionBlobHashBytes() server.broadcastProtoMessageWithPredicate(MessageChannelState, chanstate, func(client *Client) bool { return client.Version >= 0x10202 }) @@ -591,13 +589,7 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { } } - // Check if the text is allowed. - - // Only set the comment if it is different from the current - // user comment. - if comment == target.Comment { - userstate.Comment = nil - } + // todo(mkrautz): Check if the text is allowed. } // Texture change @@ -646,14 +638,17 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { broadcast := false if userstate.Texture != nil { - target.Texture = userstate.Texture - if len(target.Texture) >= 128 { - hash := sha1.New() - hash.Write(target.Texture) - target.TextureHash = hash.Sum() - } else { - target.TextureHash = []byte{} + key, err := globalBlobstore.Put(userstate.Texture) + if err != nil { + log.Panicf("Blobstore error: %v", err.String()) } + + if target.user.TextureBlob != key { + target.user.TextureBlob = key + } else { + userstate.Texture = nil + } + broadcast = true } @@ -683,14 +678,17 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { } if userstate.Comment != nil { - target.Comment = *userstate.Comment - if len(target.Comment) >= 128 { - hash := sha1.New() - hash.Write([]byte(target.Comment)) - target.CommentHash = hash.Sum() - } else { - target.CommentHash = []byte{} + key, err := globalBlobstore.Put([]byte(*userstate.Comment)) + if err != nil { + log.Panicf("Blobstore error: %v", err.String()) } + + if target.user.CommentBlob != key { + target.user.CommentBlob = key + } else { + userstate.Comment = nil + } + broadcast = true } @@ -754,11 +752,12 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { // Mumble and Murmur used qCompress and qUncompress from Qt to compress // textures that were sent over the wire. We can use this to determine // whether a texture is a "new style" or an "old style" texture. + texture := userstate.Texture texlen := uint32(0) - if target.Texture != nil && len(target.Texture) > 4 { - texlen = uint32(target.Texture[0])<<24 | uint32(target.Texture[1])<<16 | uint32(target.Texture[2])<<8 | uint32(target.Texture[3]) + if texture != nil && len(texture) > 4 { + texlen = uint32(texture[0])<<24 | uint32(texture[1])<<16 | uint32(texture[2])<<8 | uint32(texture[3]) } - if userstate.Texture != nil && len(target.Texture) > 4 && texlen != 600*60*4 { + if texture != nil && len(texture) > 4 && texlen != 600*60*4 { // The sent texture is a new-style texture. Strip it from the message // we send to pre-1.2.2 clients. userstate.Texture = nil @@ -769,7 +768,7 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { log.Panic("Unable to broadcast UserState") } // Re-add it to the message, so that 1.2.2+ clients *do* get the new-style texture. - userstate.Texture = target.Texture + userstate.Texture = texture } else { // Old style texture. We can send the message as-is. err := server.broadcastProtoMessageWithPredicate(MessageUserState, userstate, func(client *Client) bool { @@ -783,14 +782,15 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) { // If a texture hash is set on user, we transmit that instead of // the texture itself. This allows the client to intelligently fetch // the blobs that it does not already have in its local storage. - if userstate != nil && len(target.TextureHash) > 0 { + if userstate.Texture != nil && target.user.HasTexture() { userstate.Texture = nil - userstate.TextureHash = target.TextureHash + userstate.TextureHash = target.user.TextureBlobHashBytes() } + // Ditto for comments. - if userstate.Comment != nil && len(target.CommentHash) > 0 { + if userstate.Comment != nil && target.user.HasComment() { userstate.Comment = nil - userstate.CommentHash = target.CommentHash + userstate.CommentHash = target.user.CommentBlobHashBytes() } err := server.broadcastProtoMessageWithPredicate(MessageUserState, userstate, func(client *Client) bool { @@ -1122,10 +1122,14 @@ func (server *Server) handleRequestBlob(client *Client, msg *Message) { if len(blobreq.SessionTexture) > 0 { for _, sid := range blobreq.SessionTexture { if target, ok := server.clients[sid]; ok { - if len(target.Texture) > 0 { + if target.user.HasTexture() { + buf, err := globalBlobstore.Get(target.user.TextureBlob) + if err != nil { + log.Panicf("Blobstore error: %v", err.String()) + } userstate.Reset() userstate.Session = proto.Uint32(uint32(target.Session)) - userstate.Texture = target.Texture + userstate.Texture = buf if err := client.sendProtoMessage(MessageUserState, userstate); err != nil { client.Panic(err.String()) return @@ -1139,10 +1143,14 @@ func (server *Server) handleRequestBlob(client *Client, msg *Message) { if len(blobreq.SessionComment) > 0 { for _, sid := range blobreq.SessionComment { if target, ok := server.clients[sid]; ok { - if len(target.Comment) > 0 { + if target.user.HasComment() { + buf, err := globalBlobstore.Get(target.user.CommentBlob) + if err != nil { + log.Panicf("Blobstore error: %v", err.String()) + } userstate.Reset() userstate.Session = proto.Uint32(uint32(target.Session)) - userstate.Comment = proto.String(target.Comment) + userstate.Comment = proto.String(string(buf)) if err := client.sendProtoMessage(MessageUserState, userstate); err != nil { client.Panic(err.String()) return @@ -1158,10 +1166,14 @@ func (server *Server) handleRequestBlob(client *Client, msg *Message) { if len(blobreq.ChannelDescription) > 0 { for _, cid := range blobreq.ChannelDescription { if channel, ok := server.Channels[int(cid)]; ok { - if len(channel.Description) > 0 { + if channel.HasDescription() { chanstate.Reset() + buf, err := globalBlobstore.Get(channel.DescriptionBlob) + if err != nil { + log.Panicf("Blobstore error: %v", err.String()) + } chanstate.ChannelId = proto.Uint32(uint32(channel.Id)) - chanstate.Description = proto.String(channel.Description) + chanstate.Description = proto.String(string(buf)) if err := client.sendProtoMessage(MessageChannelState, chanstate); err != nil { client.Panic(err.String()) return diff --git a/murmurdb.go b/murmurdb.go index f9c224e..eb1ac5e 100644 --- a/murmurdb.go +++ b/murmurdb.go @@ -105,7 +105,11 @@ func populateChannelsFromDatabase(server *Server, db *sqlite.Conn, parentId int) return err } - c.Description = description + key, err := globalBlobstore.Put([]byte(description)) + if err != nil { + return err + } + c.DescriptionBlob = key } if err := stmt.Reset(); err != nil { @@ -315,6 +319,12 @@ func populateUsers(server *Server, db *sqlite.Conn) (err os.Error) { return err } + key, err := globalBlobstore.Put(Texture) + if err != nil { + return err + } + user.TextureBlob = key + user.LastActive = uint64(LastActive) user.LastChannelId = LastChannel @@ -353,7 +363,11 @@ func populateUsers(server *Server, db *sqlite.Conn) (err os.Error) { case UserInfoEmail: user.Email = Value case UserInfoComment: - // unhandled + key, err := globalBlobstore.Put([]byte(Value)) + if err != nil { + return err + } + user.CommentBlob = key case UserInfoHash: user.CertHash = Value case UserInfoLastActive: diff --git a/user.go b/user.go index 4bded11..277b7b4 100644 --- a/user.go +++ b/user.go @@ -5,6 +5,7 @@ package main import ( + "encoding/hex" "os" ) @@ -37,3 +38,33 @@ func NewUser(id uint32, name string) (user *User, err os.Error) { Name: name, },nil } + +// Does the channel have comment? +func (user *User) HasComment() bool { + return len(user.CommentBlob) > 0 +} + +// Get the hash of the user's comment blob as a byte slice for transmitting via a protobuf message. +// Returns nil if there is no such blob. +func (user *User) CommentBlobHashBytes() (buf []byte) { + buf, err := hex.DecodeString(user.CommentBlob) + if err != nil { + return nil + } + return buf +} + +// Does the user have a texture? +func (user *User) HasTexture() bool { + return len(user.TextureBlob) > 0 +} + +// Get the hash of the user's texture blob as a byte slice for transmitting via a protobuf message. +// Returns nil if there is no such blob. +func (user *User) TextureBlobHashBytes() (buf []byte) { + buf, err := hex.DecodeString(user.TextureBlob) + if err != nil { + return nil + } + return buf +}