mirror of
https://github.com/mumble-voip/grumble.git
synced 2025-12-19 21:59:59 -08:00
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 = \
|
||||
pkg/packetdatastream \
|
||||
pkg/cryptstate \
|
||||
pkg/mumbleproto
|
||||
pkg/mumbleproto \
|
||||
pkg/sqlite
|
||||
|
||||
GCFLAGS = -Ipkg/cryptstate/_obj -Ipkg/packetdatastream/_obj -Ipkg/mumbleproto/_obj
|
||||
LDFLAGS = -Lpkg/cryptstate/_obj -Lpkg/packetdatastream/_obj -Lpkg/mumbleproto/_obj
|
||||
GCFLAGS = \
|
||||
-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 = \
|
||||
grumble.go \
|
||||
|
|
@ -22,7 +32,9 @@ GOFILES = \
|
|||
client.go \
|
||||
channel.go \
|
||||
acl.go \
|
||||
group.go
|
||||
group.go \
|
||||
murmurdb.go \
|
||||
json.go
|
||||
|
||||
.PHONY: grumble
|
||||
grumble: pkg
|
||||
|
|
|
|||
18
group.go
18
group.go
|
|
@ -51,12 +51,30 @@ func (group *Group) AddContains(id int) (ok bool) {
|
|||
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.
|
||||
func (group *Group) RemoveContains(id int) (ok bool) {
|
||||
_, ok = group.Remove[id]
|
||||
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.
|
||||
func (group *Group) TemporaryContains(id int) (ok bool) {
|
||||
_, ok = group.Temporary[id]
|
||||
|
|
|
|||
155
grumble.go
155
grumble.go
|
|
@ -9,53 +9,160 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"log"
|
||||
"mumbleproto"
|
||||
"goprotobuf.googlecode.com/hg/proto"
|
||||
"json"
|
||||
"sqlite"
|
||||
"compress/zlib"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var help *bool = flag.Bool("help", false, "Show this help")
|
||||
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 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")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
// Check that we're using a version of goprotobuf that is able to
|
||||
// correctly encode empty byte slices.
|
||||
func checkProtoLib() {
|
||||
us := &mumbleproto.UserState{}
|
||||
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.")
|
||||
func MurmurImport(filename string) (err os.Error) {
|
||||
db, err := sqlite.Open(filename)
|
||||
if err != nil {
|
||||
panic(err.String())
|
||||
}
|
||||
|
||||
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() {
|
||||
flag.Parse()
|
||||
if *help == true {
|
||||
usage()
|
||||
Usage()
|
||||
return
|
||||
}
|
||||
|
||||
checkProtoLib()
|
||||
log.Printf("Grumble - Mumble server written in Go")
|
||||
|
||||
// Create our default server
|
||||
m, err := NewServer(*host, *port)
|
||||
if len(*datadir) == 0 {
|
||||
*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 {
|
||||
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.
|
||||
go m.ListenAndMurmur()
|
||||
servers := make(map[int64]*Server)
|
||||
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
|
||||
sleeper := make(chan int)
|
||||
zzz := <-sleeper
|
||||
if zzz > 0 {
|
||||
servers[s.Id] = s
|
||||
go s.ListenAndMurmur()
|
||||
}
|
||||
|
||||
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
|
||||
if chanstate.ChannelId != nil {
|
||||
channel, ok = server.channels[int(*chanstate.ChannelId)]
|
||||
channel, ok = server.Channels[int(*chanstate.ChannelId)]
|
||||
if !ok {
|
||||
client.Panic("Invalid channel specified in ChannelState message")
|
||||
return
|
||||
|
|
@ -155,7 +155,7 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) {
|
|||
|
||||
// Lookup parent
|
||||
if chanstate.Parent != nil {
|
||||
parent, ok = server.channels[int(*chanstate.Parent)]
|
||||
parent, ok = server.Channels[int(*chanstate.Parent)]
|
||||
if !ok {
|
||||
client.Panic("Invalid parent channel specified in ChannelState message")
|
||||
return
|
||||
|
|
@ -235,7 +235,7 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) {
|
|||
}
|
||||
|
||||
// Add the new channel
|
||||
channel = server.NewChannel(name)
|
||||
channel = server.AddChannel(name)
|
||||
channel.Description = description
|
||||
channel.Temporary = *chanstate.Temporary
|
||||
channel.Position = int(*chanstate.Position)
|
||||
|
|
@ -384,13 +384,13 @@ func (server *Server) handleChannelStateMessage(client *Client, msg *Message) {
|
|||
}
|
||||
// Add any valid channels to linkremove slice
|
||||
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)
|
||||
}
|
||||
}
|
||||
// Add any valid channels to linkadd slice
|
||||
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) {
|
||||
client.sendPermissionDenied(client, iter, LinkChannelPermission)
|
||||
return
|
||||
|
|
@ -528,7 +528,7 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) {
|
|||
// Does it have a channel ID?
|
||||
if userstate.ChannelId != nil {
|
||||
// Destination channel
|
||||
dstChan, ok := server.channels[int(*userstate.ChannelId)]
|
||||
dstChan, ok := server.Channels[int(*userstate.ChannelId)]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
|
@ -741,7 +741,7 @@ func (server *Server) handleUserStateMessage(client *Client, msg *Message) {
|
|||
}
|
||||
|
||||
if userstate.ChannelId != nil {
|
||||
channel, ok := server.channels[int(*userstate.ChannelId)]
|
||||
channel, ok := server.Channels[int(*userstate.ChannelId)]
|
||||
if ok {
|
||||
server.userEnterChannel(user, channel, userstate)
|
||||
broadcast = true
|
||||
|
|
@ -820,7 +820,7 @@ func (server *Server) handleTextMessage(client *Client, msg *Message) {
|
|||
|
||||
// Tree
|
||||
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) {
|
||||
client.sendPermissionDenied(client, channel, TextMessagePermission)
|
||||
}
|
||||
|
|
@ -832,7 +832,7 @@ func (server *Server) handleTextMessage(client *Client, msg *Message) {
|
|||
|
||||
// Direct-to-channel
|
||||
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) {
|
||||
client.sendPermissionDenied(client, channel, TextMessagePermission)
|
||||
return
|
||||
|
|
@ -874,7 +874,7 @@ func (server *Server) handleAclMessage(client *Client, msg *Message) {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
|
|
@ -1103,7 +1103,7 @@ func (server *Server) handlePermissionQuery(client *Client, msg *Message) {
|
|||
return
|
||||
}
|
||||
|
||||
channel := server.channels[int(*query.ChannelId)]
|
||||
channel := server.Channels[int(*query.ChannelId)]
|
||||
server.sendClientPermissions(client, channel)
|
||||
}
|
||||
|
||||
|
|
@ -1157,7 +1157,7 @@ func (server *Server) handleRequestBlob(client *Client, msg *Message) {
|
|||
// Request for channel descriptions
|
||||
if len(blobreq.ChannelDescription) > 0 {
|
||||
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 {
|
||||
chanstate.Reset()
|
||||
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 CeltCompatBitstream = -2147483637
|
||||
|
||||
// Client connection states
|
||||
const (
|
||||
StateClientConnected = iota
|
||||
StateServerSentVersion
|
||||
|
|
@ -37,6 +35,7 @@ const (
|
|||
|
||||
// A Murmur server instance
|
||||
type Server struct {
|
||||
Id int64
|
||||
listener tls.Listener
|
||||
address string
|
||||
port int
|
||||
|
|
@ -67,16 +66,17 @@ type Server struct {
|
|||
// Channels
|
||||
chanid int
|
||||
root *Channel
|
||||
channels map[int]*Channel
|
||||
Channels map[int]*Channel
|
||||
|
||||
// ACL cache
|
||||
aclcache ACLCache
|
||||
}
|
||||
|
||||
// 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.Id = id
|
||||
s.address = addr
|
||||
s.port = port
|
||||
|
||||
|
|
@ -92,16 +92,19 @@ func NewServer(addr string, port int) (s *Server, err os.Error) {
|
|||
s.MaxBandwidth = 300000
|
||||
s.MaxUsers = 10
|
||||
|
||||
s.channels = make(map[int]*Channel)
|
||||
s.Channels = make(map[int]*Channel)
|
||||
|
||||
s.root = s.NewChannel("Root")
|
||||
subChan := s.NewChannel("SubChannel")
|
||||
s.root.AddChild(subChan)
|
||||
s.root = s.NewChannel(0, "Root")
|
||||
|
||||
/*
|
||||
err = s.addChannelsFromDB(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
*/
|
||||
|
||||
s.aclcache = NewACLCache()
|
||||
|
||||
go s.handler()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -172,11 +175,28 @@ func (server *Server) RemoveClient(client *Client, kicked bool) {
|
|||
}
|
||||
}
|
||||
|
||||
// Add a new channel to the server.
|
||||
func (server *Server) NewChannel(name string) (channel *Channel) {
|
||||
// Add an existing channel to the Server. (Do not arbitrarily pick an ID)
|
||||
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)
|
||||
server.channels[channel.Id] = channel
|
||||
server.chanid += 1
|
||||
server.Channels[channel.Id] = channel
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +206,7 @@ func (server *Server) RemoveChanel(channel *Channel) {
|
|||
log.Printf("Attempted to remove root channel.")
|
||||
return
|
||||
}
|
||||
server.channels[channel.Id] = nil, false
|
||||
server.Channels[channel.Id] = nil, false
|
||||
}
|
||||
|
||||
// Link two channels
|
||||
|
|
@ -687,6 +707,8 @@ func (server *Server) userEnterChannel(client *Client, channel *Channel, usersta
|
|||
|
||||
// The accept loop of the server.
|
||||
func (s *Server) ListenAndMurmur() {
|
||||
// Launch the event handler goroutine
|
||||
go s.handler()
|
||||
|
||||
// Setup our UDP listener and spawn our reader and writer goroutines
|
||||
s.SetupUDP()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue