3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-12-22 18:52:41 +01:00

get rid of user abstraction, services

This commit is contained in:
Jeremy Latt 2014-02-04 19:28:24 -08:00
parent f133f3691d
commit f04dd7c5d5
11 changed files with 145 additions and 983 deletions

View File

@ -1,5 +1,5 @@
#!/bin/bash
export GOPATH="$PWD"
go get "code.google.com/p/go.crypto/bcrypt"
go get "github.com/mattn/go-sqlite3"
go install ergonomadic genpasswd ergonomadicdb
#go get "code.google.com/p/go.crypto/bcrypt"
#go get "github.com/mattn/go-sqlite3"
go install ergonomadic genpasswd

View File

@ -9,82 +9,41 @@ const (
)
type Channel struct {
id *RowId
server *Server
commands chan<- ChannelCommand
replies chan<- Reply
name string
key string
topic string
members UserSet
members ClientSet
name string
noOutside bool
password string
replies chan<- Reply
server *Server
topic string
}
type ChannelSet map[*Channel]bool
func (set ChannelSet) Add(channel *Channel) {
set[channel] = true
}
func (set ChannelSet) Remove(channel *Channel) {
delete(set, channel)
}
func (set ChannelSet) Ids() (ids []RowId) {
ids = make([]RowId, len(set))
var i = 0
for channel := range set {
ids[i] = *(channel.id)
i++
}
return ids
}
type ChannelCommand interface {
Command
HandleChannel(channel *Channel)
}
// NewChannel creates a new channel from a `Server` and a `name` string, which
// must be unique on the server.
// NewChannel creates a new channel from a `Server` and a `name`
// string, which must be unique on the server.
func NewChannel(s *Server, name string) *Channel {
commands := make(chan ChannelCommand)
replies := make(chan Reply)
channel := &Channel{
name: name,
members: make(UserSet),
members: make(ClientSet),
server: s,
commands: commands,
replies: replies,
}
go channel.receiveCommands(commands)
go channel.receiveReplies(replies)
Save(s.db, channel)
return channel
}
func (channel *Channel) Save(q Queryable) bool {
if channel.id == nil {
if err := InsertChannel(q, channel); err != nil {
log.Println(err)
return false
}
channelId, err := FindChannelIdByName(q, channel.name)
if err != nil {
log.Println(err)
return false
}
channel.id = &channelId
} else {
if err := UpdateChannel(q, channel); err != nil {
log.Println(err)
return false
}
}
return true
}
func (channel *Channel) receiveCommands(commands <-chan ChannelCommand) {
for command := range commands {
if DEBUG_CHANNEL {
@ -99,16 +58,13 @@ func (channel *Channel) receiveReplies(replies <-chan Reply) {
if DEBUG_CHANNEL {
log.Printf("%s ← %s : %s", channel, reply.Source(), reply)
}
for user := range channel.members {
if user != reply.Source() {
user.Replies() <- reply
for client := range channel.members {
if client != reply.Source() {
client.replies <- reply
}
}
}
}
func (channel *Channel) Nicks() []string {
return channel.members.Nicks()
}
func (channel *Channel) IsEmpty() bool {
return len(channel.members) == 0
@ -127,6 +83,16 @@ func (channel *Channel) GetUsers(replier Replier) {
replier.Replies() <- NewNamesReply(channel)
}
func (channel *Channel) Nicks() []string {
nicks := make([]string, len(channel.members))
i := 0
for client := range channel.members {
nicks[i] = client.Nick()
i += 1
}
return nicks
}
func (channel *Channel) Replies() chan<- Reply {
return channel.replies
}
@ -143,20 +109,22 @@ func (channel *Channel) PublicId() string {
return channel.name
}
func (channel *Channel) Commands() chan<- ChannelCommand {
return channel.commands
}
func (channel *Channel) String() string {
return channel.Id()
}
func (channel *Channel) Join(user *User) {
channel.members.Add(user)
user.channels.Add(channel)
channel.Replies() <- RplJoin(channel, user)
channel.GetTopic(user)
channel.GetUsers(user)
func (channel *Channel) Join(client *Client) {
channel.members[client] = true
client.channels[channel] = true
reply := RplJoin(channel, client)
client.replies <- reply
channel.replies <- reply
channel.GetTopic(client)
channel.GetUsers(client)
}
func (channel *Channel) HasMember(client *Client) bool {
return channel.members[client]
}
//
@ -166,58 +134,54 @@ func (channel *Channel) Join(user *User) {
func (m *JoinCommand) HandleChannel(channel *Channel) {
client := m.Client()
if channel.key != m.channels[channel.name] {
client.Replies() <- ErrBadChannelKey(channel)
client.replies <- ErrBadChannelKey(channel)
return
}
channel.Join(client.user)
channel.Join(client)
}
func (m *PartCommand) HandleChannel(channel *Channel) {
user := m.Client().user
c := m.Client()
if !channel.members[user] {
user.Replies() <- ErrNotOnChannel(channel)
if !channel.HasMember(c) {
c.replies <- ErrNotOnChannel(channel)
return
}
msg := m.message
if msg == "" {
msg = user.Nick()
msg = c.Nick()
}
channel.Replies() <- RplPart(channel, user, msg)
channel.replies <- RplPart(channel, c, msg)
channel.members.Remove(user)
user.channels.Remove(channel)
delete(channel.members, c)
delete(c.channels, channel)
if channel.IsEmpty() {
if channel.IsEmpty() { // TODO persistent channels
channel.server.DeleteChannel(channel)
}
}
func (m *TopicCommand) HandleChannel(channel *Channel) {
user := m.User()
client := m.Client()
if !channel.members[user] {
user.Replies() <- ErrNotOnChannel(channel)
if !channel.HasMember(client) {
client.replies <- ErrNotOnChannel(channel)
return
}
if m.topic == "" {
channel.GetTopic(user)
channel.GetTopic(client)
return
}
channel.topic = m.topic
if channel.topic == "" {
channel.Replies() <- RplNoTopic(channel)
return
}
channel.Replies() <- RplTopic(channel)
channel.GetTopic(channel)
}
func (m *PrivMsgCommand) HandleChannel(channel *Channel) {
channel.Replies() <- RplPrivMsgChannel(channel, m.User(), m.message)
channel.replies <- RplPrivMsgChannel(channel, m.Client(), m.message)
}

View File

@ -8,22 +8,28 @@ import (
)
const (
DEBUG_CLIENT = true
DEBUG_CLIENT = false
)
type ClientCommand interface {
Command
HandleClient(*Client)
}
type Client struct {
atime time.Time
away bool
channels ChannelSet
commands chan<- ClientCommand
conn net.Conn
username string
realname string
hostname string
nick string
serverPass bool
realname string
registered bool
away bool
server *Server
atime time.Time
user *User
replies chan<- Reply
server *Server
serverPass bool
username string
}
type ClientSet map[*Client]bool
@ -31,17 +37,21 @@ type ClientSet map[*Client]bool
func NewClient(server *Server, conn net.Conn) *Client {
read := StringReadChan(conn)
write := StringWriteChan(conn)
commands := make(chan ClientCommand)
replies := make(chan Reply)
client := &Client{
channels: make(ChannelSet),
commands: commands,
conn: conn,
hostname: conn.RemoteAddr().String(),
server: server,
hostname: LookupHostname(conn.RemoteAddr()),
replies: replies,
server: server,
}
go client.readConn(read)
go client.writeConn(write, replies)
go client.receiveCommands(commands)
return client
}
@ -51,9 +61,9 @@ func (c *Client) readConn(recv <-chan string) {
m, err := ParseCommand(str)
if err != nil {
if err == NotEnoughArgsError {
c.Replies() <- ErrNeedMoreParams(c.server, str)
c.replies <- ErrNeedMoreParams(c.server, str)
} else {
c.Replies() <- ErrUnknownCommand(c.server, str)
c.replies <- ErrUnknownCommand(c.server, str)
}
continue
}
@ -72,6 +82,15 @@ func (c *Client) writeConn(write chan<- string, replies <-chan Reply) {
}
}
func (client *Client) receiveCommands(commands <-chan ClientCommand) {
for command := range commands {
if DEBUG_CLIENT {
log.Printf("%s → %s : %s", command.Client(), client, command)
}
command.HandleClient(client)
}
}
func (c *Client) Replies() chan<- Reply {
return c.replies
}
@ -81,11 +100,7 @@ func (c *Client) Server() *Server {
}
func (c *Client) Nick() string {
if c.user != nil {
return c.user.Nick()
}
if c.nick != "" {
if c.HasNick() {
return c.nick
}
@ -97,7 +112,7 @@ func (c *Client) UModeString() string {
}
func (c *Client) HasNick() bool {
return c.Nick() != ""
return c.nick != ""
}
func (c *Client) HasUsername() bool {
@ -126,3 +141,11 @@ func (c *Client) String() string {
func (c *Client) PublicId() string {
return fmt.Sprintf("%s!%s@%s", c.Nick(), c.Nick(), c.server.Id())
}
//
// commands
//
func (m *PrivMsgCommand) HandleClient(client *Client) {
client.replies <- RplPrivMsg(m.Client(), client, m.message)
}

View File

@ -9,7 +9,6 @@ import (
type Command interface {
Client() *Client
User() *User
Source() Identifier
Reply(Reply)
HandleServer(*Server)
@ -46,10 +45,6 @@ func (command *BaseCommand) Client() *Client {
return command.client
}
func (command *BaseCommand) User() *User {
return command.Client().user
}
func (command *BaseCommand) SetBase(c *Client) {
*command = BaseCommand{c}
}

View File

@ -8,7 +8,7 @@ import (
)
const (
DEBUG_NET = true
DEBUG_NET = false
)
func readTrimmedLine(reader *bufio.Reader) (string, error) {
@ -64,7 +64,7 @@ func LookupHostname(addr net.Addr) string {
if err != nil {
return addrStr
}
names, err := net.LookupAddr(ipaddr)
names, err := net.LookupHost(ipaddr)
if err != nil {
return ipaddr
}

View File

@ -1,158 +0,0 @@
package irc
import (
"fmt"
"log"
)
const (
DEBUG_NICKSERV = true
)
type NickServCommand interface {
HandleNickServ(*NickServ)
Client() *Client
SetBase(*Client)
}
type NickServ struct {
BaseService
}
func NewNickServ(s *Server) Service {
return NewService(new(NickServ), s, "NickServ")
}
func (ns *NickServ) SetBase(base *BaseService) {
ns.BaseService = *base
}
func (ns *NickServ) Debug() bool {
return DEBUG_NICKSERV
}
var (
parseNickServCommandFuncs = map[string]func([]string) (NickServCommand, error){
"REGISTER": NewRegisterCommand,
"IDENTIFY": NewIdentifyCommand,
}
)
//
// commands
//
func (ns *NickServ) HandlePrivMsg(m *PrivMsgCommand) {
command, args := parseLine(m.message)
constructor := parseNickServCommandFuncs[command]
if constructor == nil {
ns.Reply(m.Client(), "Unknown command.")
return
}
cmd, err := constructor(args)
if err != nil {
ns.Reply(m.Client(), "Not enough parameters.")
return
}
cmd.SetBase(m.Client())
if ns.Debug() {
log.Printf("%s ← %s %s", ns, cmd.Client(), cmd)
}
cmd.HandleNickServ(ns)
}
//
// sub-commands
//
type RegisterCommand struct {
BaseCommand
password string
email string
}
func (m *RegisterCommand) String() string {
return fmt.Sprintf("REGISTER(email=%s, password=%s)", m.email, m.password)
}
func NewRegisterCommand(args []string) (NickServCommand, error) {
if len(args) == 0 {
return nil, NotEnoughArgsError
}
cmd := &RegisterCommand{
BaseCommand: BaseCommand{},
password: args[0],
}
if len(args) > 1 {
cmd.email = args[1]
}
return cmd, nil
}
func (m *RegisterCommand) HandleNickServ(ns *NickServ) {
client := m.Client()
if client.user != nil {
ns.Reply(client, "You are already registered.")
return
}
if ns.server.users[client.nick] != nil {
ns.Reply(client, "That nick is already registered.")
return
}
user := NewUser(client.nick, ns.server)
user.SetPassword(m.password)
Save(ns.server.db, user)
ns.Reply(client, "You have registered.")
if !user.Login(client, client.nick, m.password) {
ns.Reply(client, "Login failed.")
return
}
ns.Reply(client, "Logged in.")
}
type IdentifyCommand struct {
BaseCommand
password string
}
func (m *IdentifyCommand) String() string {
return fmt.Sprintf("IDENTIFY(password=%s)", m.password)
}
func NewIdentifyCommand(args []string) (NickServCommand, error) {
if len(args) == 0 {
return nil, NotEnoughArgsError
}
return &IdentifyCommand{
BaseCommand: BaseCommand{},
password: args[0],
}, nil
}
func (m *IdentifyCommand) HandleNickServ(ns *NickServ) {
client := m.Client()
if client.user != nil {
ns.Reply(client, "That nick is already registered.")
return
}
user := ns.server.users[client.nick]
if user == nil {
ns.Reply(client, "No such nick.")
return
}
if !user.Login(client, client.nick, m.password) {
ns.Reply(client, "Login failed.")
}
ns.Reply(client, "Logged in.")
}

View File

@ -1,270 +0,0 @@
package irc
import (
"database/sql"
//"fmt"
"bufio"
_ "github.com/mattn/go-sqlite3"
"log"
"os"
"path/filepath"
"strings"
)
type RowId uint64
type Queryable interface {
Exec(string, ...interface{}) (sql.Result, error)
Query(string, ...interface{}) (*sql.Rows, error)
QueryRow(string, ...interface{}) *sql.Row
}
type Savable interface {
Save(q Queryable) bool
}
type Loadable interface {
Load(q Queryable) bool
}
//
// general
//
func NewDatabase() (db *sql.DB) {
db, err := sql.Open("sqlite3", "ergonomadic.db")
if err != nil {
log.Fatalln("cannot open database")
}
return
}
func readLines(filename string) <-chan string {
file, err := os.Open(filename)
if err != nil {
log.Fatalln(err)
}
reader := bufio.NewReader(file)
lines := make(chan string)
go func(lines chan<- string) {
defer file.Close()
defer close(lines)
for {
line, err := reader.ReadString(';')
if err != nil {
break
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
lines <- line
}
}(lines)
return lines
}
func ExecSqlFile(db *sql.DB, filename string) {
Transact(db, func(q Queryable) bool {
for line := range readLines(filepath.Join("sql", filename)) {
log.Println(line)
_, err := q.Exec(line)
if err != nil {
log.Fatalln(err)
}
}
return true
})
}
func Transact(db *sql.DB, txf func(Queryable) bool) {
tx, err := db.Begin()
if err != nil {
log.Panicln(err)
}
if txf(tx) {
tx.Commit()
} else {
tx.Rollback()
}
}
func Save(db *sql.DB, s Savable) {
Transact(db, s.Save)
}
func Load(db *sql.DB, l Loadable) {
Transact(db, l.Load)
}
//
// general purpose sql
//
func findId(q Queryable, sql string, args ...interface{}) (rowId RowId, err error) {
row := q.QueryRow(sql, args...)
err = row.Scan(&rowId)
return
}
func countRows(q Queryable, sql string, args ...interface{}) (count uint, err error) {
row := q.QueryRow(sql, args...)
err = row.Scan(&count)
return
}
//
// data
//
type UserRow struct {
id RowId
nick string
hash []byte
}
type ChannelRow struct {
id RowId
name string
}
// user
func FindAllUsers(q Queryable) (urs []*UserRow, err error) {
var rows *sql.Rows
rows, err = q.Query("SELECT id, nick, hash FROM user")
if err != nil {
return
}
urs = make([]*UserRow, 0)
for rows.Next() {
ur := &UserRow{}
err = rows.Scan(&(ur.id), &(ur.nick), &(ur.hash))
if err != nil {
return
}
urs = append(urs, ur)
}
return
}
func FindUserByNick(q Queryable, nick string) (ur *UserRow, err error) {
ur = &UserRow{}
row := q.QueryRow("SELECT id, nick, hash FROM user LIMIT 1 WHERE nick = ?",
nick)
err = row.Scan(&(ur.id), &(ur.nick), &(ur.hash))
return
}
func FindUserIdByNick(q Queryable, nick string) (RowId, error) {
return findId(q, "SELECT id FROM user WHERE nick = ?", nick)
}
func FindChannelByName(q Queryable, name string) (cr *ChannelRow) {
cr = new(ChannelRow)
row := q.QueryRow("SELECT id, name FROM channel LIMIT 1 WHERE name = ?", name)
err := row.Scan(&(cr.id), &(cr.name))
if err != nil {
cr = nil
}
return
}
func InsertUser(q Queryable, row *UserRow) (err error) {
_, err = q.Exec("INSERT INTO user (nick, hash) VALUES (?, ?)",
row.nick, row.hash)
return
}
func UpdateUser(q Queryable, row *UserRow) (err error) {
_, err = q.Exec("UPDATE user SET nick = ?, hash = ? WHERE id = ?",
row.nick, row.hash, row.id)
return
}
func DeleteUser(q Queryable, id RowId) (err error) {
_, err = q.Exec("DELETE FROM user WHERE id = ?", id)
return
}
// user-channel
func DeleteAllUserChannels(q Queryable, rowId RowId) (err error) {
_, err = q.Exec("DELETE FROM user_channel WHERE user_id = ?", rowId)
return
}
func DeleteOtherUserChannels(q Queryable, userId RowId, channelIds []RowId) (err error) {
_, err = q.Exec(`DELETE FROM user_channel WHERE
user_id = ? AND channel_id NOT IN ?`, userId, channelIds)
return
}
func InsertUserChannels(q Queryable, userId RowId, channelIds []RowId) (err error) {
ins := "INSERT OR IGNORE INTO user_channel (user_id, channel_id) VALUES "
vals := strings.Repeat("(?, ?), ", len(channelIds))
vals = vals[0 : len(vals)-2]
args := make([]RowId, 2*len(channelIds))
for i, channelId := range channelIds {
args[i] = userId
args[i+1] = channelId
}
_, err = q.Exec(ins+vals, args)
return
}
// channel
func FindChannelIdByName(q Queryable, name string) (RowId, error) {
return findId(q, "SELECT id FROM channel WHERE name = ?", name)
}
func findChannels(q Queryable, where string, args ...interface{}) (crs []*ChannelRow, err error) {
count, err := countRows(q, "SELECT COUNT(id) FROM channel "+where, args...)
if err != nil {
return
}
rows, err := q.Query("SELECT id, name FROM channel "+where, args...)
if err != nil {
return
}
crs = make([]*ChannelRow, count)
var i = 0
for rows.Next() {
cr := &ChannelRow{}
err = rows.Scan(&(cr.id), &(cr.name))
if err != nil {
return
}
crs[i] = cr
i++
}
return
}
func FindChannelsForUser(q Queryable, userId RowId) (crs []*ChannelRow, err error) {
crs, err = findChannels(q,
"WHERE id IN (SELECT channel_id from user_channel WHERE user_id = ?)", userId)
return
}
func FindAllChannels(q Queryable) (crs []*ChannelRow, err error) {
crs, err = findChannels(q, "")
return
}
func InsertChannel(q Queryable, channel *Channel) (err error) {
_, err = q.Exec("INSERT INTO channel (name) VALUES (?)", channel.name)
return
}
func UpdateChannel(q Queryable, channel *Channel) (err error) {
_, err = q.Exec("UPDATE channel SET name = ? WHERE id = ?",
channel.name, *(channel.id))
return
}
func DeleteChannel(q Queryable, channel *Channel) (err error) {
_, err = q.Exec("DELETE FROM channel WHERE id = ?", *(channel.id))
return
}

View File

@ -93,6 +93,7 @@ func NewNamesReply(channel *Channel) Reply {
BaseReply: &BaseReply{
source: channel,
},
channel: channel,
}
}
@ -142,12 +143,12 @@ func RplPrivMsgChannel(channel *Channel, source Identifier, message string) Repl
return NewStringReply(source, RPL_PRIVMSG, "%s :%s", channel.name, message)
}
func RplJoin(channel *Channel, user *User) Reply {
return NewStringReply(user, RPL_JOIN, channel.name)
func RplJoin(channel *Channel, client *Client) Reply {
return NewStringReply(client, RPL_JOIN, channel.name)
}
func RplPart(channel *Channel, user *User, message string) Reply {
return NewStringReply(user, RPL_PART, "%s :%s", channel.name, message)
func RplPart(channel *Channel, client *Client, message string) Reply {
return NewStringReply(client, RPL_PART, "%s :%s", channel.name, message)
}
func RplPong(server *Server) Reply {

View File

@ -2,7 +2,6 @@ package irc
import (
"code.google.com/p/go.crypto/bcrypt"
"database/sql"
"log"
"net"
"time"
@ -13,19 +12,16 @@ const (
)
type ChannelNameMap map[string]*Channel
type UserNameMap map[string]*User
type ServiceNameMap map[string]Service
type ClientNameMap map[string]*Client
type Server struct {
hostname string
channels ChannelNameMap
commands chan<- Command
ctime time.Time
hostname string
name string
password []byte
users UserNameMap
channels ChannelNameMap
services ServiceNameMap
commands chan<- Command
db *sql.DB
clients ClientNameMap
}
func NewServer(name string) *Server {
@ -34,43 +30,13 @@ func NewServer(name string) *Server {
ctime: time.Now(),
name: name,
commands: commands,
users: make(UserNameMap),
clients: make(ClientNameMap),
channels: make(ChannelNameMap),
services: make(ServiceNameMap),
db: NewDatabase(),
}
go server.receiveCommands(commands)
NewNickServ(server)
Load(server.db, server)
return server
}
func (server *Server) Load(q Queryable) bool {
crs, err := FindAllChannels(q)
if err != nil {
log.Println(err)
return false
}
for _, cr := range crs {
channel := server.GetOrMakeChannel(cr.name)
channel.id = &(cr.id)
}
urs, err := FindAllUsers(q)
if err != nil {
log.Println(err)
return false
}
for _, ur := range urs {
user := NewUser(ur.nick, server)
user.SetHash(ur.hash)
if !user.Load(q) {
return false
}
}
return true
}
func (server *Server) receiveCommands(commands <-chan Command) {
for command := range commands {
if DEBUG_SERVER {
@ -113,16 +79,16 @@ func (s *Server) GetOrMakeChannel(name string) *Channel {
}
// Send a message to clients of channels fromClient is a member.
func (s *Server) InterestedUsers(fromUser *User) UserSet {
users := make(UserSet)
users.Add(fromUser)
for channel := range fromUser.channels {
for user := range channel.members {
users.Add(user)
func (s *Server) interestedClients(fromClient *Client) ClientSet {
clients := make(ClientSet)
clients[fromClient] = true
for channel := range fromClient.channels {
for client := range channel.members {
clients[client] = true
}
}
return users
return clients
}
// server functionality
@ -164,9 +130,6 @@ func (s *Server) Nick() string {
func (s *Server) DeleteChannel(channel *Channel) {
delete(s.channels, channel.name)
if err := DeleteChannel(s.db, channel); err != nil {
log.Println(err)
}
}
//
@ -198,32 +161,30 @@ func (m *PassCommand) HandleServer(s *Server) {
func (m *NickCommand) HandleServer(s *Server) {
c := m.Client()
if c.user == nil {
c.Replies() <- RplNick(c, m.nickname)
c.nick = m.nickname
s.tryRegister(c)
if s.clients[m.nickname] != nil {
c.replies <- ErrNickNameInUse(s, m.nickname)
return
}
user := c.user
if s.users[m.nickname] != nil {
user.Replies() <- ErrNickNameInUse(s, m.nickname)
return
reply := RplNick(c, m.nickname)
for iclient := range s.interestedClients(c) {
iclient.replies <- reply
}
delete(s.users, user.nick)
s.users[m.nickname] = user
reply := RplNick(user, m.nickname)
for iuser := range s.InterestedUsers(user) {
iuser.Replies() <- reply
if c.HasNick() {
delete(s.clients, c.nick)
}
user.nick = m.nickname
s.clients[m.nickname] = c
c.nick = m.nickname
s.tryRegister(c)
}
func (m *UserMsgCommand) HandleServer(s *Server) {
c := m.Client()
if c.username != "" {
c.Replies() <- ErrAlreadyRegistered(s)
if c.registered {
c.replies <- ErrAlreadyRegistered(s)
return
}
@ -234,108 +195,65 @@ func (m *UserMsgCommand) HandleServer(s *Server) {
func (m *QuitCommand) HandleServer(s *Server) {
c := m.Client()
user := c.user
if user != nil {
reply := RplQuit(c, m.message)
for user := range s.InterestedUsers(c.user) {
user.Replies() <- reply
}
reply := RplQuit(c, m.message)
for client := range s.interestedClients(c) {
client.replies <- reply
}
c.conn.Close()
if user == nil {
return
cmd := &PartCommand{
BaseCommand: BaseCommand{c},
}
user.LogoutClient(c)
if !user.HasClients() {
cmd := &PartCommand{
BaseCommand: BaseCommand{c},
}
for channel := range user.channels {
channel.Commands() <- cmd
}
for channel := range c.channels {
channel.commands <- cmd
}
}
func (m *JoinCommand) HandleServer(s *Server) {
c := m.Client()
if c.user == nil {
c.Replies() <- ErrNoPrivileges(s)
return
}
if m.zero {
cmd := &PartCommand{
BaseCommand: BaseCommand{c},
}
for channel := range c.user.channels {
channel.Commands() <- cmd
for channel := range c.channels {
channel.commands <- cmd
}
return
}
for name := range m.channels {
s.GetOrMakeChannel(name).Commands() <- m
s.GetOrMakeChannel(name).commands <- m
}
}
func (m *PartCommand) HandleServer(s *Server) {
user := m.User()
if user == nil {
m.Client().Replies() <- ErrNoPrivileges(s)
return
}
for _, chname := range m.channels {
channel := s.channels[chname]
if channel == nil {
user.Replies() <- ErrNoSuchChannel(s, channel.name)
m.Client().replies <- ErrNoSuchChannel(s, channel.name)
continue
}
channel.Commands() <- m
channel.commands <- m
}
}
func (m *TopicCommand) HandleServer(s *Server) {
user := m.User()
// Hide all channels from logged-out clients.
if user == nil {
m.Client().Replies() <- ErrNoPrivileges(s)
return
}
channel := s.channels[m.channel]
if channel == nil {
m.Client().Replies() <- ErrNoSuchChannel(s, m.channel)
m.Client().replies <- ErrNoSuchChannel(s, m.channel)
return
}
channel.Commands() <- m
channel.commands <- m
}
func (m *PrivMsgCommand) HandleServer(s *Server) {
service := s.services[m.target]
if service != nil {
service.Commands() <- m
return
}
user := m.User()
// Hide all users from logged-out clients.
if user == nil {
m.Client().Replies() <- ErrNoPrivileges(s)
return
}
if m.TargetIsChannel() {
channel := s.channels[m.target]
if channel == nil {
m.Client().Replies() <- ErrNoSuchChannel(s, m.target)
m.Client().replies <- ErrNoSuchChannel(s, m.target)
return
}
@ -343,9 +261,9 @@ func (m *PrivMsgCommand) HandleServer(s *Server) {
return
}
target := s.users[m.target]
target := s.clients[m.target]
if target == nil {
m.Client().Replies() <- ErrNoSuchNick(s, m.target)
m.Client().replies <- ErrNoSuchNick(s, m.target)
return
}
@ -353,5 +271,5 @@ func (m *PrivMsgCommand) HandleServer(s *Server) {
}
func (m *ModeCommand) HandleServer(s *Server) {
m.Client().Replies() <- RplUModeIs(s, m.Client())
m.Client().replies <- RplUModeIs(s, m.Client())
}

View File

@ -1,83 +0,0 @@
package irc
import (
"fmt"
"log"
)
type ServiceCommand interface {
Command
HandleService(Service)
}
type Service interface {
Identifier
Commands() chan<- ServiceCommand
HandlePrivMsg(*PrivMsgCommand)
Debug() bool
}
type EditableService interface {
Service
SetBase(*BaseService)
}
type BaseService struct {
server *Server
name string
commands chan<- ServiceCommand
}
func NewService(service EditableService, s *Server, name string) Service {
commands := make(chan ServiceCommand)
base := &BaseService{
server: s,
name: name,
commands: commands,
}
go receiveCommands(service, commands)
service.SetBase(base)
s.services[service.Nick()] = service
return service
}
func receiveCommands(service Service, commands <-chan ServiceCommand) {
for command := range commands {
if service.Debug() {
log.Printf("%s ← %s %s", service.Id(), command.Client(), command)
}
command.HandleService(service)
}
}
func (service *BaseService) Id() string {
return fmt.Sprintf("%s!%s@%s", service.name, service.name, service.server.name)
}
func (service *BaseService) String() string {
return service.Id()
}
func (service *BaseService) PublicId() string {
return service.Id()
}
func (service *BaseService) Nick() string {
return service.name
}
func (service *BaseService) Reply(client *Client, message string) {
client.Replies() <- RplPrivMsg(service, client, message)
}
func (service *BaseService) Commands() chan<- ServiceCommand {
return service.commands
}
//
// commands
//
func (m *PrivMsgCommand) HandleService(service Service) {
service.HandlePrivMsg(m)
}

View File

@ -1,228 +0,0 @@
package irc
import (
"code.google.com/p/go.crypto/bcrypt"
"fmt"
"log"
)
const (
DEBUG_USER = true
)
type UserCommand interface {
Command
HandleUser(*User)
}
type User struct {
id RowId
nick string
hash []byte
server *Server
clients ClientSet
channels ChannelSet
commands chan<- UserCommand
replies chan<- Reply
}
type UserSet map[*User]bool
func (set UserSet) Add(user *User) {
set[user] = true
}
func (set UserSet) Remove(user *User) {
delete(set, user)
}
func (set UserSet) Nicks() []string {
nicks := make([]string, len(set))
i := 0
for member := range set {
nicks[i] = member.Nick()
i++
}
return nicks
}
func NewUser(nick string, server *Server) *User {
commands := make(chan UserCommand)
replies := make(chan Reply)
user := &User{
nick: nick,
server: server,
clients: make(ClientSet),
channels: make(ChannelSet),
replies: replies,
}
go user.receiveCommands(commands)
go user.receiveReplies(replies)
server.users[nick] = user
return user
}
func (user *User) Row() *UserRow {
return &UserRow{user.id, user.nick, user.hash}
}
func (user *User) Create(q Queryable) bool {
var err error
if err := InsertUser(q, user.Row()); err != nil {
log.Println(err)
return false
}
user.id, err = FindUserIdByNick(q, user.nick)
if err != nil {
log.Println(err)
return false
}
return true
}
func (user *User) Save(q Queryable) bool {
if err := UpdateUser(q, user.Row()); err != nil {
log.Println(err)
return false
}
channelIds := user.channels.Ids()
if len(channelIds) == 0 {
if err := DeleteAllUserChannels(q, user.id); err != nil {
log.Println(err)
return false
}
} else {
if err := DeleteOtherUserChannels(q, user.id, channelIds); err != nil {
log.Println(err)
return false
}
if err := InsertUserChannels(q, user.id, channelIds); err != nil {
log.Println(err)
return false
}
}
return true
}
func (user *User) Delete(q Queryable) bool {
err := DeleteUser(q, user.id)
if err != nil {
log.Println(err)
return false
}
return true
}
func (user *User) Load(q Queryable) bool {
crs, err := FindChannelsForUser(q, user.id)
if err != nil {
log.Println(err)
return false
}
for _, cr := range crs {
user.server.GetOrMakeChannel(cr.name).Join(user)
}
return true
}
func (user *User) SetPassword(password string) {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
log.Panicln(err)
}
user.SetHash(hash)
}
func (user *User) SetHash(hash []byte) {
user.hash = hash
}
func (user *User) receiveCommands(commands <-chan UserCommand) {
for command := range commands {
if DEBUG_USER {
log.Printf("%s → %s : %s", command.Client(), user, command)
}
command.HandleUser(user)
}
}
// Distribute replies to clients.
func (user *User) receiveReplies(replies <-chan Reply) {
for reply := range replies {
if DEBUG_USER {
log.Printf("%s ← %s : %s", user, reply.Source(), reply)
}
for client := range user.clients {
client.Replies() <- reply
}
}
}
// Identifier
func (user *User) Id() string {
return fmt.Sprintf("%s!%s@%s", user.nick, user.nick, user.server.Id())
}
func (user *User) PublicId() string {
return user.Id()
}
func (user *User) Nick() string {
return user.nick
}
func (user *User) String() string {
return user.Id()
}
func (user *User) Login(c *Client, nick string, password string) bool {
if nick != c.nick {
return false
}
if user.hash == nil {
return false
}
err := bcrypt.CompareHashAndPassword(user.hash, []byte(password))
if err != nil {
c.Replies() <- ErrNoPrivileges(user.server)
return false
}
user.clients[c] = true
c.user = user
for channel := range user.channels {
channel.GetTopic(c)
channel.GetUsers(c)
}
return true
}
func (user *User) LogoutClient(c *Client) bool {
if user.clients[c] {
delete(user.clients, c)
return true
}
return false
}
func (user *User) HasClients() bool {
return len(user.clients) > 0
}
func (user *User) Replies() chan<- Reply {
return user.replies
}
//
// commands
//
func (m *PrivMsgCommand) HandleUser(user *User) {
user.Replies() <- RplPrivMsg(m.Client(), user, m.message)
}