Merge lp:~widelands-dev/widelands-metaserver/ircbridge into lp:widelands-metaserver

Proposed by Tino
Status: Merged
Merged at revision: 47
Proposed branch: lp:~widelands-dev/widelands-metaserver/ircbridge
Merge into: lp:widelands-metaserver
Diff against target: 291 lines (+127/-15)
6 files modified
.bzrignore (+1/-0)
wlms/client.go (+1/-0)
wlms/ircbridge.go (+71/-0)
wlms/main.go (+14/-3)
wlms/server.go (+34/-11)
wlms/server_test.go (+6/-1)
To merge this branch: bzr merge lp:~widelands-dev/widelands-metaserver/ircbridge
Reviewer Review Type Date Requested Status
Tino Needs Resubmitting
SirVer Needs Fixing
Review via email: mp+203277@code.launchpad.net

Description of the change

A first draft of a irce bridge. Lets the metaserver connect to a irc channel an mirrors messages between the channel and the metaserver lobby.

To post a comment you must log in.
Revision history for this message
SirVer (sirver) wrote :

Nice. That is a really good idea and I think the implementation looks reasonable too. It is a bit drafty still, though (see comments below). Was this the first time you did something with go? How did you like it?

Some comments:

- func NewIRCBridge() IRCBridge should read its configuration from the same json file where we read the data from right now too (see main.go).
- I think the design is a little fragile - if something goes wrong with the IRC connection the whole metaserver comes down. How about running the IRC bridge in a go routine that gets a (buffered) channel of stuff to send and also sends stuff into a channel that the metaserver then displays? That way if the bridge is disconnected it can just discard the messages and the whole design is a bit decoupled. Also you can fake some IRC messages in a test and test the whole thing.
- +func (s *Server) Motd() string - I do not agree with this change. When a method is not changing a struct, it should not be a pointer receiver. There are only a few exceptions to this rule as far as I understood go so far (not an expert by any means).
- Please add some tests. Feel free to add end to end tests, there are already a few in the metaserver.

Revision history for this message
SirVer (sirver) :
review: Needs Fixing
Revision history for this message
Tino (tino79) wrote :

Thanks for the comments, of course i only wanted a review, not a merge in the current state.
This are my first steps with go, so i am just glad that it works at all ;).

- i will add tests
- the irc library handles disconnects itself, and because of the use of a callback function for irc->lobby it is already decoupled. I disconnected from the internet to test and it automatically reconnected and the lobby was still usable. But i will look into using a routine and buffers...
- pointer recievers:
Here i am really unsure, but i've found:

   A method is a function with an implicit first argument, called a receiver.

   So when you call a method:

   - if the receiver is a pointer to int, it copies the pointer to int.
   - if the receiver is a struct, it copies the struct.
   - if the receiver is a pointer to struct, it copies the pointer to struct.

So even if i don't want to modify the struct i would think avoiding a copy operation by passing a pointer to the struct should be faster? But here speaks only 1 day go experience... ;)

Revision history for this message
SirVer (sirver) wrote :

> But i will look into using a routine and buffers...

I understand your comment, but I still think it is a good idea. First, because it is more idiomatic go (as far as I see it right now :)) and secondly goroutines and channels are essential building blocks that somehow set the language apart - you'll find them really interesting I think.

> So even if i don't want to modify the struct i would think avoiding a copy operation by passing a pointer to the struct should be faster?

While this is correct, go optimizes these very strongly. Sofar not using a pointer has not been a performance bottleneck in go code for me. And not using a pointer signifies to the programmer that the method will not change the receiver - which is nice documentation. So only use a pointer receiver when you need to (i.e. when you need to modify the receiver) or when you measured that it impacts performance by benchmarking.

49. By Tino

revert to non pointer recievers when the struct does not get manipulated

50. By Tino

read irc configuration from file

51. By Tino

IRCBridge "interfaced", does compile and run again

52. By Tino

pushing messages to irc via channel

53. By Tino

go channels 2way irc<->lobby

54. By Tino

channel refactoring
tests are ok again (no testing of irc functionality right now)

55. By Tino

non blocking sending

Revision history for this message
Tino (tino79) wrote :

Ok, channels are in place.
Next up are tests, but first i need to understand go tests. The current tests starts dozens of server instances in a few seconds, i need to use only one irc bridge instance for all tests or have to implement my own tests with active irc bridge.

Revision history for this message
SirVer (sirver) wrote :

I do not fully understand your question.

If you only want to test the behavior of the server you can just hand in two channels and fake messages on those in your server tests (i.e. no IRC Bridge is created at all). I think this is the most reasonable approach to take since you cannot rely on IRC being up when you want to test your scripts.

You can also choose not to test your new implementations, but you must at least change the setup procedure so that not necessarily an IRC connection is made when a server is created.

Revision history for this message
SirVer (sirver) wrote :

And progress on this? Anything I can help you with? If you are stuck we can also remote pair on the code for a bit, maybe we come up with something together.

56. By Tino

correct irc address

57. By Tino

merge trunk

Revision history for this message
Tino (tino79) wrote :

The current state is:
- it is working, communication between ircbridge and metaserver via nonblocking channels
- tests are working, result ok
- no tests regarding the ircbridge itself or communication implemented

I am a bit busy atm, i think i won't have time to implement further test before next week.

Revision history for this message
SirVer (sirver) wrote :

no pressure - just wondered if you still want to work on this or if it should be merged around now.

58. By Tino

merge trunk

59. By Tino

adapt to changes in irc library

60. By Tino

implement test draft

61. By Tino

join channel only after hello from server

Revision history for this message
Tino (tino79) wrote :

Ok, perhaps we can merge now?
I does work(tm), only the testcase i've drafted seems to do nothing?

review: Needs Resubmitting
Revision history for this message
SirVer (sirver) wrote :

looks good to me. I merged and deployed on the server - thanks for your work on this!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file '.bzrignore'
--- .bzrignore 1970-01-01 00:00:00 +0000
+++ .bzrignore 2014-03-17 19:05:27 +0000
@@ -0,0 +1,1 @@
1*.exe
02
=== modified file 'wlms/client.go'
--- wlms/client.go 2014-02-03 21:14:19 +0000
+++ wlms/client.go 2014-03-17 19:05:27 +0000
@@ -277,6 +277,7 @@
277277
278 if len(receiver) == 0 {278 if len(receiver) == 0 {
279 server.BroadcastToConnectedClients("CHAT", client.Name(), message, "public")279 server.BroadcastToConnectedClients("CHAT", client.Name(), message, "public")
280 server.BroadcastToIrc(client.Name() + ": " + message)
280 } else {281 } else {
281 recv_client := server.HasClient(receiver)282 recv_client := server.HasClient(receiver)
282 if recv_client != nil {283 if recv_client != nil {
283284
=== added file 'wlms/ircbridge.go'
--- wlms/ircbridge.go 1970-01-01 00:00:00 +0000
+++ wlms/ircbridge.go 2014-03-17 19:05:27 +0000
@@ -0,0 +1,71 @@
1package main
2
3import (
4 "log"
5
6 "github.com/thoj/go-ircevent"
7)
8
9type IRCBridger interface {
10 Connect(chan Message, chan Message) bool
11 Quit()
12}
13
14type IRCBridge struct {
15 connection *irc.Connection
16 nick, user, channel, server string
17 useTLS bool
18}
19
20type Message struct {
21 message, nick string
22}
23
24func NewIRCBridge(server, realname, nickname, channel string, tls bool) *IRCBridge {
25 return &IRCBridge{
26 server: server,
27 user: realname,
28 nick: nickname,
29 channel: channel,
30 useTLS: tls,
31 }
32}
33
34func (bridge *IRCBridge) Connect(messagesIn chan Message, messagesOut chan Message) bool {
35 //Create new connection
36 bridge.connection = irc.IRC(bridge.nick, bridge.user)
37 //Set options
38 bridge.connection.UseTLS = bridge.useTLS
39 //connection.TLSOptions //set ssl options
40 //connection.Password = "[server password]"
41 //Commands
42 err := bridge.connection.Connect(bridge.server) //Connect to server
43 if err != nil {
44 log.Fatal("Can't connect %s", bridge.server)
45 return false
46 }
47 bridge.connection.Join(bridge.channel)
48 bridge.connection.AddCallback("PRIVMSG", func(event *irc.Event) {
49 //e.Message contains the message
50 //e.Nick Contains the sender
51 //e.Arguments[0] Contains the channel
52 select {
53 case messagesOut <- Message{nick: event.Nick,
54 message: event.Message(),
55 }:
56 default:
57 log.Println("Message Queue full.")
58 }
59
60 })
61 go func() {
62 for m := range messagesIn {
63 bridge.connection.Privmsg(bridge.channel, m.message)
64 }
65 }()
66 return true
67}
68
69func (bridge *IRCBridge) Quit() {
70 bridge.connection.Quit()
71}
072
=== modified file 'wlms/main.go'
--- wlms/main.go 2013-12-30 12:20:31 +0000
+++ wlms/main.go 2014-03-17 19:05:27 +0000
@@ -8,7 +8,8 @@
8)8)
99
10type Config struct {10type Config struct {
11 Database, User, Password, Table string11 Database, User, Password, Table, Backend, IRCServer, Nickname, Realname, Channel string
12 UseTLS bool
12}13}
1314
14func (l *Config) ConfigFrom(path string) error {15func (l *Config) ConfigFrom(path string) error {
@@ -25,16 +26,26 @@
25 flag.Parse()26 flag.Parse()
2627
27 var db UserDb28 var db UserDb
29 var ircbridge IRCBridger
28 if config != "" {30 if config != "" {
29 var cfg Config31 var cfg Config
30 if err := cfg.ConfigFrom(config); err != nil {32 if err := cfg.ConfigFrom(config); err != nil {
31 log.Fatalf("Could not parse config file: %v", err)33 log.Fatalf("Could not parse config file: %v", err)
32 }34 }
33 db = NewMySqlDatabase(cfg.Database, cfg.User, cfg.Password, cfg.Table)35 if cfg.Backend == "mysql" {
36 db = NewMySqlDatabase(cfg.Database, cfg.User, cfg.Password, cfg.Table)
37 } else {
38 db = NewInMemoryDb()
39 }
40 ircbridge = NewIRCBridge(cfg.IRCServer, cfg.Realname, cfg.Nickname, cfg.Channel, cfg.UseTLS)
34 } else {41 } else {
35 db = NewInMemoryDb()42 db = NewInMemoryDb()
36 }43 }
37 defer db.Close()44 defer db.Close()
3845
39 RunServer(db)46 messagesToIrc := make(chan Message, 50)
47 messagesToLobby := make(chan Message, 50)
48 ircbridge.Connect(messagesToIrc, messagesToLobby)
49 RunServer(db, messagesToLobby, messagesToIrc)
50
40}51}
4152
=== modified file 'wlms/server.go'
--- wlms/server.go 2014-02-08 20:48:04 +0000
+++ wlms/server.go 2014-03-17 19:05:27 +0000
@@ -33,10 +33,11 @@
33 gamePingTimeout time.Duration33 gamePingTimeout time.Duration
3434
35 clientForgetTimeout time.Duration35 clientForgetTimeout time.Duration
36 gamePingerFactory GampPingerFactory36 gamePingerFactory GamePingerFactory
37 messagesOut chan Message
37}38}
3839
39type GampPingerFactory interface {40type GamePingerFactory interface {
40 New(client *Client, timeout time.Duration) *GamePinger41 New(client *Client, timeout time.Duration) *GamePinger
41}42}
4243
@@ -100,6 +101,7 @@
100}101}
101102
102func (s *Server) AddClient(client *Client) {103func (s *Server) AddClient(client *Client) {
104 s.BroadcastToIrc(client.Name() + " has joined the lobby.")
103 s.clients.PushBack(client)105 s.clients.PushBack(client)
104}106}
105107
@@ -121,11 +123,15 @@
121 if e.Value.(*Client) == client {123 if e.Value.(*Client) == client {
122 log.Printf("Removing client %s.", client.Name())124 log.Printf("Removing client %s.", client.Name())
123 s.clients.Remove(e)125 s.clients.Remove(e)
126 s.messagesOut <- Message{
127 message: client.Name() + " has left the lobby.",
128 nick: client.Name(),
129 }
124 }130 }
125 }131 }
126}132}
127133
128func (s *Server) HasClient(name string) *Client {134func (s Server) HasClient(name string) *Client {
129 for e := s.clients.Front(); e != nil; e = e.Next() {135 for e := s.clients.Front(); e != nil; e = e.Next() {
130 client := e.Value.(*Client)136 client := e.Value.(*Client)
131 if client.Name() == name {137 if client.Name() == name {
@@ -135,7 +141,7 @@
135 return nil141 return nil
136}142}
137143
138func (s *Server) NrActiveClients() int {144func (s Server) NrActiveClients() int {
139 count := 0145 count := 0
140 for e := s.clients.Front(); e != nil; e = e.Next() {146 for e := s.clients.Front(); e != nil; e = e.Next() {
141 if e.Value.(*Client).State() == CONNECTED {147 if e.Value.(*Client).State() == CONNECTED {
@@ -158,6 +164,7 @@
158func (s *Server) AddGame(game *Game) {164func (s *Server) AddGame(game *Game) {
159 s.games.PushBack(game)165 s.games.PushBack(game)
160 s.BroadcastToConnectedClients("GAMES_UPDATE")166 s.BroadcastToConnectedClients("GAMES_UPDATE")
167 s.BroadcastToIrc("A new game " + game.Name() + " was opened by " + game.Host())
161}168}
162169
163func (s *Server) RemoveGame(game *Game) {170func (s *Server) RemoveGame(game *Game) {
@@ -170,7 +177,7 @@
170 }177 }
171}178}
172179
173func (s *Server) HasGame(name string) *Game {180func (s Server) HasGame(name string) *Game {
174 for e := s.games.Front(); e != nil; e = e.Next() {181 for e := s.games.Front(); e != nil; e = e.Next() {
175 game := e.Value.(*Game)182 game := e.Value.(*Game)
176 if game.Name() == name {183 if game.Name() == name {
@@ -180,7 +187,7 @@
180 return nil187 return nil
181}188}
182189
183func (s *Server) NrGames() int {190func (s Server) NrGames() int {
184 return s.games.Len()191 return s.games.Len()
185}192}
186193
@@ -199,7 +206,18 @@
199 }206 }
200}207}
201208
202func RunServer(db UserDb) {209func (s Server) BroadcastToIrc(message string) {
210 select {
211 case s.messagesOut <- Message{
212 message: message,
213 }:
214 default:
215 log.Println("Message Queue full.")
216 }
217
218}
219
220func RunServer(db UserDb, messagesIn chan Message, messagesOut chan Message) {
203 ln, err := net.Listen("tcp", ":7395")221 ln, err := net.Listen("tcp", ":7395")
204 if err != nil {222 if err != nil {
205 log.Fatal(err)223 log.Fatal(err)
@@ -216,8 +234,7 @@
216 C <- conn234 C <- conn
217 }235 }
218 }()236 }()
219237 CreateServerUsing(C, db, messagesIn, messagesOut).WaitTillShutdown()
220 CreateServerUsing(C, db).WaitTillShutdown()
221}238}
222239
223type RealGamePingerFactory struct {240type RealGamePingerFactory struct {
@@ -255,11 +272,11 @@
255 return pinger272 return pinger
256}273}
257274
258func (server *Server) InjectGamePingerFactory(gpf GampPingerFactory) {275func (server *Server) InjectGamePingerFactory(gpf GamePingerFactory) {
259 server.gamePingerFactory = gpf276 server.gamePingerFactory = gpf
260}277}
261278
262func CreateServerUsing(acceptedConnections chan ReadWriteCloserWithIp, db UserDb) *Server {279func CreateServerUsing(acceptedConnections chan ReadWriteCloserWithIp, db UserDb, messagesIn chan Message, messagesOut chan Message) *Server {
263 server := &Server{280 server := &Server{
264 acceptedConnections: acceptedConnections,281 acceptedConnections: acceptedConnections,
265 shutdownServer: make(chan bool),282 shutdownServer: make(chan bool),
@@ -272,8 +289,14 @@
272 pingCycleTime: time.Second * 15,289 pingCycleTime: time.Second * 15,
273 clientSendingTimeout: time.Minute * 2,290 clientSendingTimeout: time.Minute * 2,
274 clientForgetTimeout: time.Minute * 5,291 clientForgetTimeout: time.Minute * 5,
292 messagesOut: messagesOut,
275 }293 }
276 server.gamePingerFactory = RealGamePingerFactory{server}294 server.gamePingerFactory = RealGamePingerFactory{server}
295 go func() {
296 for m := range messagesIn {
297 server.BroadcastToConnectedClients("CHAT", m.nick+"(IRC)", m.message, "public")
298 }
299 }()
277 go server.mainLoop()300 go server.mainLoop()
278 return server301 return server
279}302}
280303
=== modified file 'wlms/server_test.go'
--- wlms/server_test.go 2014-02-08 20:48:04 +0000
+++ wlms/server_test.go 2014-03-17 19:05:27 +0000
@@ -40,7 +40,12 @@
40 cons[i] = NewFakeConn(c)40 cons[i] = NewFakeConn(c)
41 acceptingConnections <- cons[i]41 acceptingConnections <- cons[i]
42 }42 }
43 return CreateServerUsing(acceptingConnections, db), cons43
44 //irc := NewIRCBridge("chat.freenode.net:7000", "wltest", "wltest", "#widelands-test", true)
45 toIrc := make(chan Message, 100)
46 fromIrc := make(chan Message, 100)
47 //irc.Connect(toIrc, fromIrc)
48 return CreateServerUsing(acceptingConnections, db, fromIrc, toIrc), cons
44}49}
4550
46type Matching string51type Matching string

Subscribers

People subscribed via source and target branches

to all changes: