forked from External/grumble
Add support for importing a server from a Murmur SQLite database. Add support for seralizing a server to disk 'freezing' (currently zlib-JSON based).
This commit is contained in:
parent
2eb5516d31
commit
3ae9881d91
11 changed files with 130730 additions and 55 deletions
20
Makefile
20
Makefile
|
|
@ -9,10 +9,20 @@ TARG = grumble
|
||||||
PACKAGES = \
|
PACKAGES = \
|
||||||
pkg/packetdatastream \
|
pkg/packetdatastream \
|
||||||
pkg/cryptstate \
|
pkg/cryptstate \
|
||||||
pkg/mumbleproto
|
pkg/mumbleproto \
|
||||||
|
pkg/sqlite
|
||||||
|
|
||||||
GCFLAGS = -Ipkg/cryptstate/_obj -Ipkg/packetdatastream/_obj -Ipkg/mumbleproto/_obj
|
GCFLAGS = \
|
||||||
LDFLAGS = -Lpkg/cryptstate/_obj -Lpkg/packetdatastream/_obj -Lpkg/mumbleproto/_obj
|
-Ipkg/cryptstate/_obj \
|
||||||
|
-Ipkg/packetdatastream/_obj \
|
||||||
|
-Ipkg/mumbleproto/_obj \
|
||||||
|
-Ipkg/sqlite/_obj
|
||||||
|
|
||||||
|
LDFLAGS = \
|
||||||
|
-Lpkg/cryptstate/_obj \
|
||||||
|
-Lpkg/packetdatastream/_obj \
|
||||||
|
-Lpkg/mumbleproto/_obj \
|
||||||
|
-Lpkg/sqlite/_obj
|
||||||
|
|
||||||
GOFILES = \
|
GOFILES = \
|
||||||
grumble.go \
|
grumble.go \
|
||||||
|
|
@ -22,7 +32,9 @@ GOFILES = \
|
||||||
client.go \
|
client.go \
|
||||||
channel.go \
|
channel.go \
|
||||||
acl.go \
|
acl.go \
|
||||||
group.go
|
group.go \
|
||||||
|
murmurdb.go \
|
||||||
|
json.go
|
||||||
|
|
||||||
.PHONY: grumble
|
.PHONY: grumble
|
||||||
grumble: pkg
|
grumble: pkg
|
||||||
|
|
|
||||||
18
group.go
18
group.go
|
|
@ -51,12 +51,30 @@ func (group *Group) AddContains(id int) (ok bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the list of user ids in the Add set.
|
||||||
|
func (group *Group) AddUsers() []int {
|
||||||
|
users := []int{}
|
||||||
|
for uid, _ := range group.Add {
|
||||||
|
users = append(users, uid)
|
||||||
|
}
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
|
||||||
// Check whether the Remove set contains id.
|
// Check whether the Remove set contains id.
|
||||||
func (group *Group) RemoveContains(id int) (ok bool) {
|
func (group *Group) RemoveContains(id int) (ok bool) {
|
||||||
_, ok = group.Remove[id]
|
_, ok = group.Remove[id]
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the list of user ids in the Remove set.
|
||||||
|
func (group *Group) RemoveUsers() []int {
|
||||||
|
users := []int{}
|
||||||
|
for uid, _ := range group.Remove {
|
||||||
|
users = append(users, uid)
|
||||||
|
}
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
|
||||||
// Check whether the Temporary set contains id.
|
// Check whether the Temporary set contains id.
|
||||||
func (group *Group) TemporaryContains(id int) (ok bool) {
|
func (group *Group) TemporaryContains(id int) (ok bool) {
|
||||||
_, ok = group.Temporary[id]
|
_, ok = group.Temporary[id]
|
||||||
|
|
|
||||||
155
grumble.go
155
grumble.go
|
|
@ -9,53 +9,160 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"log"
|
"log"
|
||||||
"mumbleproto"
|
"json"
|
||||||
"goprotobuf.googlecode.com/hg/proto"
|
"sqlite"
|
||||||
|
"compress/zlib"
|
||||||
|
"path/filepath"
|
||||||
)
|
)
|
||||||
|
|
||||||
var help *bool = flag.Bool("help", false, "Show this help")
|
var help *bool = flag.Bool("help", false, "Show this help")
|
||||||
var port *int = flag.Int("port", 64738, "Default port to listen on")
|
var port *int = flag.Int("port", 64738, "Default port to listen on")
|
||||||
var host *string = flag.String("host", "0.0.0.0", "Default host to listen on")
|
var host *string = flag.String("host", "0.0.0.0", "Default host to listen on")
|
||||||
|
var datadir *string = flag.String("datadir", "", "Directory to use for server storage")
|
||||||
|
var blobdir *string = flag.String("blobdir", "", "Directory to use for blob storage")
|
||||||
|
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")
|
||||||
|
|
||||||
func usage() {
|
func Usage() {
|
||||||
fmt.Fprintf(os.Stderr, "usage: grumble [options]\n")
|
fmt.Fprintf(os.Stderr, "usage: grumble [options]\n")
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that we're using a version of goprotobuf that is able to
|
func MurmurImport(filename string) (err os.Error) {
|
||||||
// correctly encode empty byte slices.
|
db, err := sqlite.Open(filename)
|
||||||
func checkProtoLib() {
|
if err != nil {
|
||||||
us := &mumbleproto.UserState{}
|
panic(err.String())
|
||||||
us.Texture = []byte{}
|
|
||||||
d, _ := proto.Marshal(us)
|
|
||||||
nus := &mumbleproto.UserState{}
|
|
||||||
proto.Unmarshal(d, nus)
|
|
||||||
if nus.Texture == nil {
|
|
||||||
log.Fatal("Unpatched version of goprotobuf. Grumble is refusing to run.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stmt, err := db.Prepare("SELECT server_id FROM servers")
|
||||||
|
if err != nil {
|
||||||
|
panic(err.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var servers []int64
|
||||||
|
var sid int64
|
||||||
|
for stmt.Next() {
|
||||||
|
stmt.Scan(&sid)
|
||||||
|
servers = append(servers, sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Found servers: %v (%v servers)", servers, len(servers))
|
||||||
|
|
||||||
|
for _, sid := range servers {
|
||||||
|
m, err := NewServerFromSQLite(sid, db)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Unable to create server: %s", err.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(filepath.Join(*datadir, fmt.Sprintf("%v", sid)))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s", err.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zf, err := zlib.NewWriterLevel(f, zlib.BestCompression)
|
||||||
|
|
||||||
|
enc := json.NewEncoder(zf)
|
||||||
|
err = enc.Encode(m)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s", err.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
zf.Close()
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
log.Printf("Successfully imported server %v", sid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *help == true {
|
if *help == true {
|
||||||
usage()
|
Usage()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
checkProtoLib()
|
log.Printf("Grumble - Mumble server written in Go")
|
||||||
|
|
||||||
// Create our default server
|
if len(*datadir) == 0 {
|
||||||
m, err := NewServer(*host, *port)
|
*datadir = filepath.Join(os.Getenv("HOME"), ".grumble", "data")
|
||||||
|
}
|
||||||
|
log.Printf("Using data directory: %s", *datadir)
|
||||||
|
|
||||||
|
if len(*blobdir) == 0 {
|
||||||
|
*blobdir = filepath.Join(os.Getenv("HOME"), ".grumble", "blob")
|
||||||
|
}
|
||||||
|
log.Printf("Using blob directory: %s", *blobdir)
|
||||||
|
|
||||||
|
|
||||||
|
// Should we import data from a Murmur SQLite file?
|
||||||
|
if len(*sqlitedb) > 0 {
|
||||||
|
f, err := os.Open(*datadir)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Murmur import failed: %s", err.String())
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
names, err := f.Readdirnames(-1)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Murmur import failed: %s", err.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !*cleanup && len(names) > 0 {
|
||||||
|
log.Fatalf("Non-empty datadir. Refusing to import Murmur data.")
|
||||||
|
}
|
||||||
|
if *cleanup {
|
||||||
|
log.Printf("Cleaning up existing data directory")
|
||||||
|
for _, name := range names {
|
||||||
|
if err := os.Remove(filepath.Join(*datadir, name)); err != nil {
|
||||||
|
log.Fatalf("Unable to cleanup file: %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Importing Murmur data from '%s'", *sqlitedb)
|
||||||
|
if err = MurmurImport(*sqlitedb); err != nil {
|
||||||
|
log.Fatalf("Murmur import failed: %s", err.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Import from Murmur SQLite database succeeded.")
|
||||||
|
log.Printf("Please restart Grumble to make use of the imported data.")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(*datadir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
log.Fatalf("Murmur import failed: %s", err.String())
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
names, err := f.Readdirnames(-1)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Murmur import failed: %s", err.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// And launch it.
|
servers := make(map[int64]*Server)
|
||||||
go m.ListenAndMurmur()
|
for _, name := range names {
|
||||||
|
log.Printf("Loading server %v", name)
|
||||||
|
s, err := NewServerFromGrumbleDesc(filepath.Join(*datadir, name))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to load server: %s", err.String())
|
||||||
|
}
|
||||||
|
|
||||||
// Listen forever
|
servers[s.Id] = s
|
||||||
sleeper := make(chan int)
|
go s.ListenAndMurmur()
|
||||||
zzz := <-sleeper
|
}
|
||||||
if zzz > 0 {
|
|
||||||
|
if len(servers) > 0 {
|
||||||
|
// Sleep.
|
||||||
|
sleeper := make(chan int)
|
||||||
|
zzz := <-sleeper
|
||||||
|
if zzz > 0 {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
190
json.go
Normal file
190
json.go
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"json"
|
||||||
|
"os"
|
||||||
|
"compress/zlib"
|
||||||
|
)
|
||||||
|
|
||||||
|
type jsonServer struct {
|
||||||
|
Id int "id"
|
||||||
|
MaxUsers int "max_user"
|
||||||
|
Channels []jsonChannel "channels"
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonChannel struct {
|
||||||
|
Id int "id"
|
||||||
|
Name string "name"
|
||||||
|
ParentId int "parent_id"
|
||||||
|
Position int64 "position"
|
||||||
|
InheritACL bool "inherit_acl"
|
||||||
|
ACL []jsonACL "acl"
|
||||||
|
Groups []jsonGroup "groups"
|
||||||
|
Description string "description"
|
||||||
|
DescriptionHash []byte "description_hash"
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonACL struct {
|
||||||
|
UserId int "user_id"
|
||||||
|
Group string "group"
|
||||||
|
ApplyHere bool "apply_here"
|
||||||
|
ApplySubs bool "apply_subs"
|
||||||
|
Allow uint32 "allow"
|
||||||
|
Deny uint32 "deny"
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonGroup struct {
|
||||||
|
Name string "name"
|
||||||
|
Inherit bool "inherit"
|
||||||
|
Inheritable bool "inheritable"
|
||||||
|
Add []int "add"
|
||||||
|
Remove []int "remove"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal a server into a JSON object
|
||||||
|
func (server *Server) MarshalJSON() (buf []byte, err os.Error) {
|
||||||
|
obj := make(map[string]interface{})
|
||||||
|
obj["id"] = server.Id
|
||||||
|
obj["max_user"] = server.MaxUsers
|
||||||
|
|
||||||
|
channels := []interface{}{}
|
||||||
|
for _, c := range server.Channels {
|
||||||
|
channels = append(channels, c)
|
||||||
|
}
|
||||||
|
obj["channels"] = channels
|
||||||
|
|
||||||
|
return json.Marshal(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal a Channel into a JSON object
|
||||||
|
func (channel *Channel) MarshalJSON() (buf []byte, err os.Error) {
|
||||||
|
obj := make(map[string]interface{})
|
||||||
|
|
||||||
|
obj["id"] = channel.Id
|
||||||
|
obj["name"] = channel.Name
|
||||||
|
if channel.parent != nil {
|
||||||
|
obj["parent_id"] = channel.parent.Id
|
||||||
|
} else {
|
||||||
|
obj["parent_id"] = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
obj["position"] = channel.Position
|
||||||
|
obj["inherit_acl"] = channel.InheritACL
|
||||||
|
obj["description"] = channel.Description
|
||||||
|
obj["description_hash"] = channel.DescriptionHash
|
||||||
|
|
||||||
|
obj["acl"] = channel.ACL
|
||||||
|
|
||||||
|
groups := []*Group{}
|
||||||
|
for _, grp := range channel.Groups {
|
||||||
|
groups = append(groups, grp)
|
||||||
|
}
|
||||||
|
obj["groups"] = groups
|
||||||
|
links := []int{}
|
||||||
|
for cid, _ := range channel.Links {
|
||||||
|
links = append(links, cid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (acl *ChannelACL) MarshalJSON() (buf []byte, err os.Error) {
|
||||||
|
obj := make(map[string]interface{})
|
||||||
|
obj["user_id"] = acl.UserId
|
||||||
|
obj["group"] = acl.Group
|
||||||
|
obj["apply_here"] = acl.ApplyHere
|
||||||
|
obj["apply_subs"] = acl.ApplySubs
|
||||||
|
obj["allow"] = acl.Allow
|
||||||
|
obj["deny"] = acl.Deny
|
||||||
|
|
||||||
|
return json.Marshal(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (group *Group) MarshalJSON() (buf []byte, err os.Error) {
|
||||||
|
obj := make(map[string]interface{})
|
||||||
|
obj["name"] = group.Name
|
||||||
|
obj["inherit"] = group.Inherit
|
||||||
|
obj["inheritable"] = group.Inheritable
|
||||||
|
obj["add"] = group.AddUsers()
|
||||||
|
obj["remove"] = group.RemoveUsers()
|
||||||
|
|
||||||
|
return json.Marshal(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new Server from a Grumble zlib-compressed JSON description
|
||||||
|
func NewServerFromGrumbleDesc(filename string) (s *Server, err os.Error) {
|
||||||
|
descFile, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer descFile.Close()
|
||||||
|
|
||||||
|
zr, err := zlib.NewReader(descFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := new(jsonServer)
|
||||||
|
decoder := json.NewDecoder(zr)
|
||||||
|
decoder.Decode(&srv)
|
||||||
|
|
||||||
|
s, err = NewServer(int64(srv.Id), "", int(DefaultPort+srv.Id-1))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all channels, but don't hook up parent/child relationships
|
||||||
|
// until all of them are loaded.
|
||||||
|
for _, jc := range srv.Channels {
|
||||||
|
c := NewChannel(jc.Id, jc.Name)
|
||||||
|
c.Position = int(jc.Position)
|
||||||
|
c.InheritACL = jc.InheritACL
|
||||||
|
c.Description = jc.Description
|
||||||
|
c.DescriptionHash = jc.DescriptionHash
|
||||||
|
|
||||||
|
for _, jacl := range jc.ACL {
|
||||||
|
acl := NewChannelACL(c)
|
||||||
|
acl.ApplyHere = jacl.ApplyHere
|
||||||
|
acl.ApplySubs = jacl.ApplySubs
|
||||||
|
acl.UserId = jacl.UserId
|
||||||
|
acl.Group = jacl.Group
|
||||||
|
acl.Deny = Permission(jacl.Deny)
|
||||||
|
acl.Allow = Permission(jacl.Allow)
|
||||||
|
c.ACL = append(c.ACL, acl)
|
||||||
|
}
|
||||||
|
for _, jgrp := range jc.Groups {
|
||||||
|
g := NewGroup(c, jgrp.Name)
|
||||||
|
g.Inherit = jgrp.Inherit
|
||||||
|
g.Inheritable = jgrp.Inheritable
|
||||||
|
for _, uid := range jgrp.Add {
|
||||||
|
g.Add[uid] = true
|
||||||
|
}
|
||||||
|
for _, uid := range jgrp.Remove {
|
||||||
|
g.Remove[uid] = true
|
||||||
|
}
|
||||||
|
c.Groups[g.Name] = g
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Channels[c.Id] = c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook up children with their parents.
|
||||||
|
for _, jc := range srv.Channels {
|
||||||
|
if jc.Id == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
childChan, exists := s.Channels[jc.Id]
|
||||||
|
if !exists {
|
||||||
|
return nil, os.NewError("Non-existant child channel")
|
||||||
|
}
|
||||||
|
parentChan, exists := s.Channels[jc.ParentId]
|
||||||
|
if !exists {
|
||||||
|
return nil, os.NewError("Non-existant parent channel")
|
||||||
|
}
|
||||||
|
parentChan.AddChild(childChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.root = s.Channels[0]
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
24
message.go
24
message.go
|
|
@ -146,7 +146,7 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) {
|
||||||
|
|
||||||
// Lookup channel for channel ID
|
// Lookup channel for channel ID
|
||||||
if chanstate.ChannelId != nil {
|
if chanstate.ChannelId != nil {
|
||||||
channel, ok = server.channels[int(*chanstate.ChannelId)]
|
channel, ok = server.Channels[int(*chanstate.ChannelId)]
|
||||||
if !ok {
|
if !ok {
|
||||||
client.Panic("Invalid channel specified in ChannelState message")
|
client.Panic("Invalid channel specified in ChannelState message")
|
||||||
return
|
return
|
||||||
|
|
@ -155,7 +155,7 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) {
|
||||||
|
|
||||||
// Lookup parent
|
// Lookup parent
|
||||||
if chanstate.Parent != nil {
|
if chanstate.Parent != nil {
|
||||||
parent, ok = server.channels[int(*chanstate.Parent)]
|
parent, ok = server.Channels[int(*chanstate.Parent)]
|
||||||
if !ok {
|
if !ok {
|
||||||
client.Panic("Invalid parent channel specified in ChannelState message")
|
client.Panic("Invalid parent channel specified in ChannelState message")
|
||||||
return
|
return
|
||||||
|
|
@ -235,7 +235,7 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the new channel
|
// Add the new channel
|
||||||
channel = server.NewChannel(name)
|
channel = server.AddChannel(name)
|
||||||
channel.Description = description
|
channel.Description = description
|
||||||
channel.Temporary = *chanstate.Temporary
|
channel.Temporary = *chanstate.Temporary
|
||||||
channel.Position = int(*chanstate.Position)
|
channel.Position = int(*chanstate.Position)
|
||||||
|
|
@ -384,13 +384,13 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) {
|
||||||
}
|
}
|
||||||
// Add any valid channels to linkremove slice
|
// Add any valid channels to linkremove slice
|
||||||
for _, cid := range chanstate.LinksRemove {
|
for _, cid := range chanstate.LinksRemove {
|
||||||
if iter, ok := server.channels[int(cid)]; ok {
|
if iter, ok := server.Channels[int(cid)]; ok {
|
||||||
linkremove = append(linkremove, iter)
|
linkremove = append(linkremove, iter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Add any valid channels to linkadd slice
|
// Add any valid channels to linkadd slice
|
||||||
for _, cid := range chanstate.LinksAdd {
|
for _, cid := range chanstate.LinksAdd {
|
||||||
if iter, ok := server.channels[int(cid)]; ok {
|
if iter, ok := server.Channels[int(cid)]; ok {
|
||||||
if !server.HasPermission(client, iter, LinkChannelPermission) {
|
if !server.HasPermission(client, iter, LinkChannelPermission) {
|
||||||
client.sendPermissionDenied(client, iter, LinkChannelPermission)
|
client.sendPermissionDenied(client, iter, LinkChannelPermission)
|
||||||
return
|
return
|
||||||
|
|
@ -528,7 +528,7 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) {
|
||||||
// Does it have a channel ID?
|
// Does it have a channel ID?
|
||||||
if userstate.ChannelId != nil {
|
if userstate.ChannelId != nil {
|
||||||
// Destination channel
|
// Destination channel
|
||||||
dstChan, ok := server.channels[int(*userstate.ChannelId)]
|
dstChan, ok := server.Channels[int(*userstate.ChannelId)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -741,7 +741,7 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if userstate.ChannelId != nil {
|
if userstate.ChannelId != nil {
|
||||||
channel, ok := server.channels[int(*userstate.ChannelId)]
|
channel, ok := server.Channels[int(*userstate.ChannelId)]
|
||||||
if ok {
|
if ok {
|
||||||
server.userEnterChannel(user, channel, userstate)
|
server.userEnterChannel(user, channel, userstate)
|
||||||
broadcast = true
|
broadcast = true
|
||||||
|
|
@ -820,7 +820,7 @@ func (server *Server) handleTextMessage(client *Client, msg *Message) {
|
||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
for _, chanid := range txtmsg.TreeId {
|
for _, chanid := range txtmsg.TreeId {
|
||||||
if channel, ok := server.channels[int(chanid)]; ok {
|
if channel, ok := server.Channels[int(chanid)]; ok {
|
||||||
if !server.HasPermission(client, channel, TextMessagePermission) {
|
if !server.HasPermission(client, channel, TextMessagePermission) {
|
||||||
client.sendPermissionDenied(client, channel, TextMessagePermission)
|
client.sendPermissionDenied(client, channel, TextMessagePermission)
|
||||||
}
|
}
|
||||||
|
|
@ -832,7 +832,7 @@ func (server *Server) handleTextMessage(client *Client, msg *Message) {
|
||||||
|
|
||||||
// Direct-to-channel
|
// Direct-to-channel
|
||||||
for _, chanid := range txtmsg.ChannelId {
|
for _, chanid := range txtmsg.ChannelId {
|
||||||
if channel, ok := server.channels[int(chanid)]; ok {
|
if channel, ok := server.Channels[int(chanid)]; ok {
|
||||||
if !server.HasPermission(client, channel, TextMessagePermission) {
|
if !server.HasPermission(client, channel, TextMessagePermission) {
|
||||||
client.sendPermissionDenied(client, channel, TextMessagePermission)
|
client.sendPermissionDenied(client, channel, TextMessagePermission)
|
||||||
return
|
return
|
||||||
|
|
@ -874,7 +874,7 @@ func (server *Server) handleAclMessage(client *Client, msg *Message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the channel this ACL message operates on.
|
// Look up the channel this ACL message operates on.
|
||||||
channel, ok := server.channels[int(*acl.ChannelId)]
|
channel, ok := server.Channels[int(*acl.ChannelId)]
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -1103,7 +1103,7 @@ func (server *Server) handlePermissionQuery(client *Client, msg *Message) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
channel := server.channels[int(*query.ChannelId)]
|
channel := server.Channels[int(*query.ChannelId)]
|
||||||
server.sendClientPermissions(client, channel)
|
server.sendClientPermissions(client, channel)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1157,7 +1157,7 @@ func (server *Server) handleRequestBlob(client *Client, msg *Message) {
|
||||||
// Request for channel descriptions
|
// Request for channel descriptions
|
||||||
if len(blobreq.ChannelDescription) > 0 {
|
if len(blobreq.ChannelDescription) > 0 {
|
||||||
for _, cid := range blobreq.ChannelDescription {
|
for _, cid := range blobreq.ChannelDescription {
|
||||||
if channel, ok := server.channels[int(cid)]; ok {
|
if channel, ok := server.Channels[int(cid)]; ok {
|
||||||
if len(channel.Description) > 0 {
|
if len(channel.Description) > 0 {
|
||||||
chanstate.Reset()
|
chanstate.Reset()
|
||||||
chanstate.ChannelId = proto.Uint32(uint32(channel.Id))
|
chanstate.ChannelId = proto.Uint32(uint32(channel.Id))
|
||||||
|
|
|
||||||
270
murmurdb.go
Normal file
270
murmurdb.go
Normal file
|
|
@ -0,0 +1,270 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// This file implements a Server that can be created from a Murmur SQLite file.
|
||||||
|
// This is read-only, so it's not generally useful. It's meant as a convenient
|
||||||
|
// way to import a Murmur server into Grumble, to be able to dump the structure of the
|
||||||
|
// SQLite datbase into a format that Grumble can understand.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sqlite"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ChannelInfoDescription int = iota
|
||||||
|
ChannelInfoPosition
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UserInfoName int = iota
|
||||||
|
UserInfoEmail
|
||||||
|
UserInfoComment
|
||||||
|
UserInfoHash
|
||||||
|
UserInfoPassword
|
||||||
|
UserInfoLastActive
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a new Server from a Murmur SQLite database
|
||||||
|
func NewServerFromSQLite(id int64, db *sqlite.Conn) (s *Server, err os.Error) {
|
||||||
|
s, err = NewServer(id, "", int(DefaultPort + id - 1))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = populateChannelsFromDatabase(s, db, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = populateChannelLinkInfo(s, db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the Server with Channels from the database.
|
||||||
|
func populateChannelsFromDatabase(server *Server, db *sqlite.Conn, parentId int) os.Error {
|
||||||
|
parent, exists := server.Channels[parentId]
|
||||||
|
if !exists {
|
||||||
|
return os.NewError("Non-existant parent")
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := db.Prepare("SELECT channel_id, name, inheritacl FROM channels WHERE server_id=? AND parent_id=?")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = stmt.Exec(server.Id, parentId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for stmt.Next() {
|
||||||
|
var (
|
||||||
|
name string
|
||||||
|
chanid int
|
||||||
|
inherit bool
|
||||||
|
)
|
||||||
|
err = stmt.Scan(&chanid, &name, &inherit)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := server.NewChannel(chanid, name)
|
||||||
|
c.InheritACL = inherit
|
||||||
|
parent.AddChild(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add channel_info
|
||||||
|
for _, c := range parent.children {
|
||||||
|
stmt, err = db.Prepare("SELECT value FROM channel_info WHERE server_id=? AND channel_id=? AND key=?")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch description
|
||||||
|
if err := stmt.Exec(server.Id, c.Id, ChannelInfoDescription); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for stmt.Next() {
|
||||||
|
var description string
|
||||||
|
err = stmt.Scan(&description)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Description = description
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stmt.Reset(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch position
|
||||||
|
if err := stmt.Exec(server.Id, c.Id, ChannelInfoPosition); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for stmt.Next() {
|
||||||
|
var pos int
|
||||||
|
if err := stmt.Scan(&pos); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Position = pos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add ACLs
|
||||||
|
for _, c := range parent.children {
|
||||||
|
stmt, err = db.Prepare("SELECT user_id, group_name, apply_here, apply_sub, grantpriv, revokepriv FROM acl WHERE server_id=? AND channel_id=? ORDER BY priority")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stmt.Exec(server.Id, c.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for stmt.Next() {
|
||||||
|
var (
|
||||||
|
UserId string
|
||||||
|
Group string
|
||||||
|
ApplyHere bool
|
||||||
|
ApplySub bool
|
||||||
|
Allow int64
|
||||||
|
Deny int64
|
||||||
|
)
|
||||||
|
if err := stmt.Scan(&UserId, &Group, &ApplyHere, &ApplySub, &Allow, &Deny); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
acl := NewChannelACL(c)
|
||||||
|
acl.ApplyHere = ApplyHere
|
||||||
|
acl.ApplySubs = ApplySub
|
||||||
|
if len(UserId) > 0 {
|
||||||
|
acl.UserId, err = strconv.Atoi(UserId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if len(Group) > 0 {
|
||||||
|
acl.Group = Group
|
||||||
|
} else {
|
||||||
|
return os.NewError("Invalid ACL: Neither Group or UserId specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
acl.Deny = Permission(Deny)
|
||||||
|
acl.Allow = Permission(Allow)
|
||||||
|
c.ACL = append(c.ACL, acl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add groups
|
||||||
|
groups := make(map[int64]*Group)
|
||||||
|
for _, c := range parent.children {
|
||||||
|
stmt, err = db.Prepare("SELECT group_id, name, inherit, inheritable FROM groups WHERE server_id=? AND channel_id=?")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stmt.Exec(server.Id, c.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for stmt.Next() {
|
||||||
|
var (
|
||||||
|
GroupId int64
|
||||||
|
Name string
|
||||||
|
Inherit bool
|
||||||
|
Inheritable bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := stmt.Scan(&GroupId, &Name, &Inherit, &Inheritable); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
g := NewGroup(c, Name)
|
||||||
|
g.Inherit = Inherit
|
||||||
|
g.Inheritable = Inheritable
|
||||||
|
c.Groups[g.Name] = g
|
||||||
|
groups[GroupId] = g
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add group members
|
||||||
|
for gid, grp := range groups {
|
||||||
|
stmt, err = db.Prepare("SELECT user_id, addit FROM group_members WHERE server_id=? AND group_id=?")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stmt.Exec(server.Id, gid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for stmt.Next() {
|
||||||
|
var (
|
||||||
|
UserId int64
|
||||||
|
Add bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := stmt.Scan(&UserId, &Add); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if Add {
|
||||||
|
grp.Add[int(UserId)] = true
|
||||||
|
} else {
|
||||||
|
grp.Remove[int(UserId)] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add subchannels
|
||||||
|
for id, _ := range parent.children {
|
||||||
|
err = populateChannelsFromDatabase(server, db, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link a Server's channels together
|
||||||
|
func populateChannelLinkInfo(server *Server, db *sqlite.Conn) (err os.Error) {
|
||||||
|
stmt, err := db.Prepare("SELECT channel_id, link_id FROM channel_links WHERE server_id=?")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stmt.Exec(server.Id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for stmt.Next() {
|
||||||
|
var (
|
||||||
|
ChannelId int
|
||||||
|
LinkId int
|
||||||
|
)
|
||||||
|
if err := stmt.Scan(&ChannelId, &LinkId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, exists := server.Channels[ChannelId]
|
||||||
|
if !exists {
|
||||||
|
return os.NewError("Attempt to perform link operation on non-existant channel.")
|
||||||
|
}
|
||||||
|
|
||||||
|
other, exists := server.Channels[LinkId]
|
||||||
|
if !exists {
|
||||||
|
return os.NewError("Attempt to perform link operation on non-existant channel.")
|
||||||
|
}
|
||||||
|
|
||||||
|
server.LinkChannels(channel, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
16
pkg/sqlite/Makefile
Normal file
16
pkg/sqlite/Makefile
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
# Use of this source code is governed by a BSD-style
|
||||||
|
# license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
include $(GOROOT)/src/Make.inc
|
||||||
|
|
||||||
|
TARG=sqlite
|
||||||
|
|
||||||
|
CGOFILES=sqlite.go
|
||||||
|
CGO_OFILES=sqlite3.o
|
||||||
|
|
||||||
|
ifeq ($(GOOS),linux)
|
||||||
|
CGO_LDFLAGS=-lpthread -ldl
|
||||||
|
endif
|
||||||
|
|
||||||
|
include $(GOROOT)/src/Make.pkg
|
||||||
402
pkg/sqlite/sqlite.go
Normal file
402
pkg/sqlite/sqlite.go
Normal file
|
|
@ -0,0 +1,402 @@
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package sqlite provides access to the SQLite library, version 3.
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
/*
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// These wrappers are necessary because SQLITE_TRANSIENT
|
||||||
|
// is a pointer constant, and cgo doesn't translate them correctly.
|
||||||
|
// The definition in sqlite3.h is:
|
||||||
|
//
|
||||||
|
// typedef void (*sqlite3_destructor_type)(void*);
|
||||||
|
// #define SQLITE_STATIC ((sqlite3_destructor_type)0)
|
||||||
|
// #define SQLITE_TRANSIENT ((sqlite3_destructor_type)-1)
|
||||||
|
|
||||||
|
static int my_bind_text(sqlite3_stmt *stmt, int n, char *p, int np) {
|
||||||
|
return sqlite3_bind_text(stmt, n, p, np, SQLITE_TRANSIENT);
|
||||||
|
}
|
||||||
|
static int my_bind_blob(sqlite3_stmt *stmt, int n, void *p, int np) {
|
||||||
|
return sqlite3_bind_blob(stmt, n, p, np, SQLITE_TRANSIENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"unsafe"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Errno int
|
||||||
|
|
||||||
|
func (e Errno) String() string {
|
||||||
|
s := errText[e]
|
||||||
|
if s == "" {
|
||||||
|
return fmt.Sprintf("errno %d", int(e))
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrError os.Error = Errno(1) // /* SQL error or missing database */
|
||||||
|
ErrInternal os.Error = Errno(2) // /* Internal logic error in SQLite */
|
||||||
|
ErrPerm os.Error = Errno(3) // /* Access permission denied */
|
||||||
|
ErrAbort os.Error = Errno(4) // /* Callback routine requested an abort */
|
||||||
|
ErrBusy os.Error = Errno(5) // /* The database file is locked */
|
||||||
|
ErrLocked os.Error = Errno(6) // /* A table in the database is locked */
|
||||||
|
ErrNoMem os.Error = Errno(7) // /* A malloc() failed */
|
||||||
|
ErrReadOnly os.Error = Errno(8) // /* Attempt to write a readonly database */
|
||||||
|
ErrInterrupt os.Error = Errno(9) // /* Operation terminated by sqlite3_interrupt()*/
|
||||||
|
ErrIOErr os.Error = Errno(10) // /* Some kind of disk I/O error occurred */
|
||||||
|
ErrCorrupt os.Error = Errno(11) // /* The database disk image is malformed */
|
||||||
|
ErrFull os.Error = Errno(13) // /* Insertion failed because database is full */
|
||||||
|
ErrCantOpen os.Error = Errno(14) // /* Unable to open the database file */
|
||||||
|
ErrEmpty os.Error = Errno(16) // /* Database is empty */
|
||||||
|
ErrSchema os.Error = Errno(17) // /* The database schema changed */
|
||||||
|
ErrTooBig os.Error = Errno(18) // /* String or BLOB exceeds size limit */
|
||||||
|
ErrConstraint os.Error = Errno(19) // /* Abort due to constraint violation */
|
||||||
|
ErrMismatch os.Error = Errno(20) // /* Data type mismatch */
|
||||||
|
ErrMisuse os.Error = Errno(21) // /* Library used incorrectly */
|
||||||
|
ErrNolfs os.Error = Errno(22) // /* Uses OS features not supported on host */
|
||||||
|
ErrAuth os.Error = Errno(23) // /* Authorization denied */
|
||||||
|
ErrFormat os.Error = Errno(24) // /* Auxiliary database format error */
|
||||||
|
ErrRange os.Error = Errno(25) // /* 2nd parameter to sqlite3_bind out of range */
|
||||||
|
ErrNotDB os.Error = Errno(26) // /* File opened that is not a database file */
|
||||||
|
Row = Errno(100) // /* sqlite3_step() has another row ready */
|
||||||
|
Done = Errno(101) // /* sqlite3_step() has finished executing */
|
||||||
|
)
|
||||||
|
|
||||||
|
var errText = map[Errno]string {
|
||||||
|
1: "SQL error or missing database",
|
||||||
|
2: "Internal logic error in SQLite",
|
||||||
|
3: "Access permission denied",
|
||||||
|
4: "Callback routine requested an abort",
|
||||||
|
5: "The database file is locked",
|
||||||
|
6: "A table in the database is locked",
|
||||||
|
7: "A malloc() failed",
|
||||||
|
8: "Attempt to write a readonly database",
|
||||||
|
9: "Operation terminated by sqlite3_interrupt()*/",
|
||||||
|
10: "Some kind of disk I/O error occurred",
|
||||||
|
11: "The database disk image is malformed",
|
||||||
|
12: "NOT USED. Table or record not found",
|
||||||
|
13: "Insertion failed because database is full",
|
||||||
|
14: "Unable to open the database file",
|
||||||
|
15: "NOT USED. Database lock protocol error",
|
||||||
|
16: "Database is empty",
|
||||||
|
17: "The database schema changed",
|
||||||
|
18: "String or BLOB exceeds size limit",
|
||||||
|
19: "Abort due to constraint violation",
|
||||||
|
20: "Data type mismatch",
|
||||||
|
21: "Library used incorrectly",
|
||||||
|
22: "Uses OS features not supported on host",
|
||||||
|
23: "Authorization denied",
|
||||||
|
24: "Auxiliary database format error",
|
||||||
|
25: "2nd parameter to sqlite3_bind out of range",
|
||||||
|
26: "File opened that is not a database file",
|
||||||
|
100: "sqlite3_step() has another row ready",
|
||||||
|
101: "sqlite3_step() has finished executing",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) error(rv C.int) os.Error {
|
||||||
|
if c == nil || c.db == nil {
|
||||||
|
return os.NewError("nil sqlite database")
|
||||||
|
}
|
||||||
|
if rv == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if rv == 21 { // misuse
|
||||||
|
return Errno(rv)
|
||||||
|
}
|
||||||
|
return os.NewError(Errno(rv).String() + ": " + C.GoString(C.sqlite3_errmsg(c.db)))
|
||||||
|
}
|
||||||
|
|
||||||
|
type Conn struct {
|
||||||
|
db *C.sqlite3
|
||||||
|
}
|
||||||
|
|
||||||
|
func Version() string {
|
||||||
|
p := C.sqlite3_libversion();
|
||||||
|
return C.GoString(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
func Open(filename string) (*Conn, os.Error) {
|
||||||
|
if C.sqlite3_threadsafe() == 0 {
|
||||||
|
return nil, os.NewError("sqlite library was not compiled for thread-safe operation")
|
||||||
|
}
|
||||||
|
|
||||||
|
var db *C.sqlite3
|
||||||
|
name := C.CString(filename)
|
||||||
|
defer C.free(unsafe.Pointer(name))
|
||||||
|
rv := C.sqlite3_open_v2(name, &db,
|
||||||
|
C.SQLITE_OPEN_FULLMUTEX |
|
||||||
|
C.SQLITE_OPEN_READWRITE |
|
||||||
|
C.SQLITE_OPEN_CREATE,
|
||||||
|
nil)
|
||||||
|
if rv != 0 {
|
||||||
|
return nil, Errno(rv)
|
||||||
|
}
|
||||||
|
if db == nil {
|
||||||
|
return nil, os.NewError("sqlite succeeded without returning a database")
|
||||||
|
}
|
||||||
|
return &Conn{db}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBackup(dst *Conn, dstTable string, src *Conn, srcTable string) (*Backup, os.Error) {
|
||||||
|
dname := C.CString(dstTable)
|
||||||
|
sname := C.CString(srcTable)
|
||||||
|
defer C.free(unsafe.Pointer(dname))
|
||||||
|
defer C.free(unsafe.Pointer(sname))
|
||||||
|
|
||||||
|
sb := C.sqlite3_backup_init(dst.db, dname, src.db, sname)
|
||||||
|
if sb == nil {
|
||||||
|
return nil, dst.error(C.sqlite3_errcode(dst.db))
|
||||||
|
}
|
||||||
|
return &Backup{sb, dst, src}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Backup struct {
|
||||||
|
sb *C.sqlite3_backup
|
||||||
|
dst, src *Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backup) Step(npage int) os.Error {
|
||||||
|
rv := C.sqlite3_backup_step(b.sb, C.int(npage))
|
||||||
|
if rv == 0 || Errno(rv) == ErrBusy || Errno(rv) == ErrLocked {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return Errno(rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BackupStatus struct {
|
||||||
|
Remaining int
|
||||||
|
PageCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backup) Status() BackupStatus {
|
||||||
|
return BackupStatus{int(C.sqlite3_backup_remaining(b.sb)), int(C.sqlite3_backup_pagecount(b.sb))}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backup) Run(npage int, sleepNs int64, c chan<- BackupStatus) os.Error {
|
||||||
|
var err os.Error
|
||||||
|
for {
|
||||||
|
err = b.Step(npage)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
c <- b.Status()
|
||||||
|
}
|
||||||
|
time.Sleep(sleepNs)
|
||||||
|
}
|
||||||
|
return b.dst.error(C.sqlite3_errcode(b.dst.db))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Backup) Close() os.Error {
|
||||||
|
if b.sb == nil {
|
||||||
|
return os.EINVAL
|
||||||
|
}
|
||||||
|
C.sqlite3_backup_finish(b.sb)
|
||||||
|
b.sb = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) BusyTimeout(ms int) os.Error {
|
||||||
|
rv := C.sqlite3_busy_timeout(c.db, C.int(ms))
|
||||||
|
if rv == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return Errno(rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Exec(cmd string, args ...interface{}) os.Error {
|
||||||
|
s, err := c.Prepare(cmd)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer s.Finalize()
|
||||||
|
err = s.Exec(args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rv := C.sqlite3_step(s.stmt)
|
||||||
|
if Errno(rv) != Done {
|
||||||
|
return c.error(rv)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stmt struct {
|
||||||
|
c *Conn
|
||||||
|
stmt *C.sqlite3_stmt
|
||||||
|
err os.Error
|
||||||
|
t0 int64
|
||||||
|
sql string
|
||||||
|
args string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Prepare(cmd string) (*Stmt, os.Error) {
|
||||||
|
if c == nil || c.db == nil {
|
||||||
|
return nil, os.NewError("nil sqlite database")
|
||||||
|
}
|
||||||
|
cmdstr := C.CString(cmd)
|
||||||
|
defer C.free(unsafe.Pointer(cmdstr))
|
||||||
|
var stmt *C.sqlite3_stmt
|
||||||
|
var tail *C.char
|
||||||
|
rv := C.sqlite3_prepare_v2(c.db, cmdstr, C.int(len(cmd)+1), &stmt, &tail)
|
||||||
|
if rv != 0 {
|
||||||
|
return nil, c.error(rv)
|
||||||
|
}
|
||||||
|
return &Stmt{c: c, stmt: stmt, sql: cmd, t0: time.Nanoseconds()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stmt) Exec(args ...interface{}) os.Error {
|
||||||
|
s.args = fmt.Sprintf(" %v", []interface{}(args))
|
||||||
|
rv := C.sqlite3_reset(s.stmt)
|
||||||
|
if rv != 0 {
|
||||||
|
return s.c.error(rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
n := int(C.sqlite3_bind_parameter_count(s.stmt))
|
||||||
|
if n != len(args) {
|
||||||
|
return os.NewError(fmt.Sprintf("incorrect argument count for Stmt.Exec: have %d want %d", len(args), n))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range args {
|
||||||
|
var str string
|
||||||
|
switch v := v.(type) {
|
||||||
|
case []byte:
|
||||||
|
var p *byte
|
||||||
|
if len(v) > 0 {
|
||||||
|
p = &v[0]
|
||||||
|
}
|
||||||
|
if rv := C.my_bind_blob(s.stmt, C.int(i+1), unsafe.Pointer(p), C.int(len(v))); rv != 0 {
|
||||||
|
return s.c.error(rv)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
str = "1"
|
||||||
|
} else {
|
||||||
|
str = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
str = fmt.Sprint(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
cstr := C.CString(str)
|
||||||
|
rv := C.my_bind_text(s.stmt, C.int(i+1), cstr, C.int(len(str)))
|
||||||
|
C.free(unsafe.Pointer(cstr))
|
||||||
|
if rv != 0 {
|
||||||
|
return s.c.error(rv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stmt) Error() os.Error {
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stmt) Next() bool {
|
||||||
|
rv := C.sqlite3_step(s.stmt)
|
||||||
|
err := Errno(rv)
|
||||||
|
if err == Row {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if err != Done {
|
||||||
|
s.err = s.c.error(rv)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stmt) Reset() os.Error {
|
||||||
|
C.sqlite3_reset(s.stmt)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stmt) Scan(args ...interface{}) os.Error {
|
||||||
|
n := int(C.sqlite3_column_count(s.stmt))
|
||||||
|
if n != len(args) {
|
||||||
|
return os.NewError(fmt.Sprintf("incorrect argument count for Stmt.Scan: have %d want %d", len(args), n))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, v := range args {
|
||||||
|
n := C.sqlite3_column_bytes(s.stmt, C.int(i))
|
||||||
|
p := C.sqlite3_column_blob(s.stmt, C.int(i))
|
||||||
|
if p == nil && n > 0 {
|
||||||
|
return os.NewError("got nil blob")
|
||||||
|
}
|
||||||
|
var data []byte
|
||||||
|
if n > 0 {
|
||||||
|
data = (*[1<<30]byte)(unsafe.Pointer(p))[0:n]
|
||||||
|
}
|
||||||
|
switch v := v.(type) {
|
||||||
|
case *[]byte:
|
||||||
|
*v = data
|
||||||
|
case *string:
|
||||||
|
*v = string(data)
|
||||||
|
case *bool:
|
||||||
|
*v = string(data) == "1"
|
||||||
|
case *int:
|
||||||
|
x, err := strconv.Atoi(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return os.NewError("arg " + strconv.Itoa(i) + " as int: " + err.String())
|
||||||
|
}
|
||||||
|
*v = x
|
||||||
|
case *int64:
|
||||||
|
x, err := strconv.Atoi64(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return os.NewError("arg " + strconv.Itoa(i) + " as int64: " + err.String())
|
||||||
|
}
|
||||||
|
*v = x
|
||||||
|
case *float64:
|
||||||
|
x, err := strconv.Atof64(string(data))
|
||||||
|
if err != nil {
|
||||||
|
return os.NewError("arg " + strconv.Itoa(i) + " as float64: " + err.String())
|
||||||
|
}
|
||||||
|
*v = x
|
||||||
|
default:
|
||||||
|
return os.NewError("unsupported type in Scan: " + reflect.Typeof(v).String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stmt) SQL() string {
|
||||||
|
return s.sql + s.args
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stmt) Nanoseconds() int64 {
|
||||||
|
return time.Nanoseconds() - s.t0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stmt) Finalize() os.Error {
|
||||||
|
rv := C.sqlite3_finalize(s.stmt)
|
||||||
|
if rv != 0 {
|
||||||
|
return s.c.error(rv)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) Close() os.Error {
|
||||||
|
if c == nil || c.db == nil {
|
||||||
|
return os.NewError("nil sqlite database")
|
||||||
|
}
|
||||||
|
rv := C.sqlite3_close(c.db)
|
||||||
|
if rv != 0 {
|
||||||
|
return c.error(rv)
|
||||||
|
}
|
||||||
|
c.db = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
123309
pkg/sqlite/sqlite3.c
Normal file
123309
pkg/sqlite/sqlite3.c
Normal file
File diff suppressed because it is too large
Load diff
6329
pkg/sqlite/sqlite3.h
Normal file
6329
pkg/sqlite/sqlite3.h
Normal file
File diff suppressed because it is too large
Load diff
52
server.go
52
server.go
|
|
@ -25,8 +25,6 @@ const DefaultPort = 64738
|
||||||
const UDPPacketSize = 1024
|
const UDPPacketSize = 1024
|
||||||
|
|
||||||
const CeltCompatBitstream = -2147483637
|
const CeltCompatBitstream = -2147483637
|
||||||
|
|
||||||
// Client connection states
|
|
||||||
const (
|
const (
|
||||||
StateClientConnected = iota
|
StateClientConnected = iota
|
||||||
StateServerSentVersion
|
StateServerSentVersion
|
||||||
|
|
@ -37,6 +35,7 @@ const (
|
||||||
|
|
||||||
// A Murmur server instance
|
// A Murmur server instance
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
Id int64
|
||||||
listener tls.Listener
|
listener tls.Listener
|
||||||
address string
|
address string
|
||||||
port int
|
port int
|
||||||
|
|
@ -67,16 +66,17 @@ type Server struct {
|
||||||
// Channels
|
// Channels
|
||||||
chanid int
|
chanid int
|
||||||
root *Channel
|
root *Channel
|
||||||
channels map[int]*Channel
|
Channels map[int]*Channel
|
||||||
|
|
||||||
// ACL cache
|
// ACL cache
|
||||||
aclcache ACLCache
|
aclcache ACLCache
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate a new Murmur instance
|
// Allocate a new Murmur instance
|
||||||
func NewServer(addr string, port int) (s *Server, err os.Error) {
|
func NewServer(id int64, addr string, port int) (s *Server, err os.Error) {
|
||||||
s = new(Server)
|
s = new(Server)
|
||||||
|
|
||||||
|
s.Id = id
|
||||||
s.address = addr
|
s.address = addr
|
||||||
s.port = port
|
s.port = port
|
||||||
|
|
||||||
|
|
@ -92,16 +92,19 @@ func NewServer(addr string, port int) (s *Server, err os.Error) {
|
||||||
s.MaxBandwidth = 300000
|
s.MaxBandwidth = 300000
|
||||||
s.MaxUsers = 10
|
s.MaxUsers = 10
|
||||||
|
|
||||||
s.channels = make(map[int]*Channel)
|
s.Channels = make(map[int]*Channel)
|
||||||
|
|
||||||
s.root = s.NewChannel("Root")
|
s.root = s.NewChannel(0, "Root")
|
||||||
subChan := s.NewChannel("SubChannel")
|
|
||||||
s.root.AddChild(subChan)
|
/*
|
||||||
|
err = s.addChannelsFromDB(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
s.aclcache = NewACLCache()
|
s.aclcache = NewACLCache()
|
||||||
|
|
||||||
go s.handler()
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,11 +175,28 @@ func (server *Server) RemoveClient(client *Client, kicked bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new channel to the server.
|
// Add an existing channel to the Server. (Do not arbitrarily pick an ID)
|
||||||
func (server *Server) NewChannel(name string) (channel *Channel) {
|
func (server *Server) NewChannel(id int, name string) (channel *Channel) {
|
||||||
|
_, exists := server.Channels[id]
|
||||||
|
if exists {
|
||||||
|
// fime(mkrautz): Handle duplicates
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
channel = NewChannel(id, name)
|
||||||
|
server.Channels[id] = channel
|
||||||
|
|
||||||
|
if id > server.chanid {
|
||||||
|
server.chanid = id + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a new channel to the server. Automatically assign it a channel ID.
|
||||||
|
func (server *Server) AddChannel(name string) (channel *Channel) {
|
||||||
channel = NewChannel(server.chanid, name)
|
channel = NewChannel(server.chanid, name)
|
||||||
server.channels[channel.Id] = channel
|
server.Channels[channel.Id] = channel
|
||||||
server.chanid += 1
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,7 +206,7 @@ func (server *Server) RemoveChanel(channel *Channel) {
|
||||||
log.Printf("Attempted to remove root channel.")
|
log.Printf("Attempted to remove root channel.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
server.channels[channel.Id] = nil, false
|
server.Channels[channel.Id] = nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link two channels
|
// Link two channels
|
||||||
|
|
@ -687,6 +707,8 @@ func (server *Server) userEnterChannel(client *Client, channel *Channel, usersta
|
||||||
|
|
||||||
// The accept loop of the server.
|
// The accept loop of the server.
|
||||||
func (s *Server) ListenAndMurmur() {
|
func (s *Server) ListenAndMurmur() {
|
||||||
|
// Launch the event handler goroutine
|
||||||
|
go s.handler()
|
||||||
|
|
||||||
// Setup our UDP listener and spawn our reader and writer goroutines
|
// Setup our UDP listener and spawn our reader and writer goroutines
|
||||||
s.SetupUDP()
|
s.SetupUDP()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue