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
1=== added file '.bzrignore'
2--- .bzrignore 1970-01-01 00:00:00 +0000
3+++ .bzrignore 2014-03-17 19:05:27 +0000
4@@ -0,0 +1,1 @@
5+*.exe
6
7=== modified file 'wlms/client.go'
8--- wlms/client.go 2014-02-03 21:14:19 +0000
9+++ wlms/client.go 2014-03-17 19:05:27 +0000
10@@ -277,6 +277,7 @@
11
12 if len(receiver) == 0 {
13 server.BroadcastToConnectedClients("CHAT", client.Name(), message, "public")
14+ server.BroadcastToIrc(client.Name() + ": " + message)
15 } else {
16 recv_client := server.HasClient(receiver)
17 if recv_client != nil {
18
19=== added file 'wlms/ircbridge.go'
20--- wlms/ircbridge.go 1970-01-01 00:00:00 +0000
21+++ wlms/ircbridge.go 2014-03-17 19:05:27 +0000
22@@ -0,0 +1,71 @@
23+package main
24+
25+import (
26+ "log"
27+
28+ "github.com/thoj/go-ircevent"
29+)
30+
31+type IRCBridger interface {
32+ Connect(chan Message, chan Message) bool
33+ Quit()
34+}
35+
36+type IRCBridge struct {
37+ connection *irc.Connection
38+ nick, user, channel, server string
39+ useTLS bool
40+}
41+
42+type Message struct {
43+ message, nick string
44+}
45+
46+func NewIRCBridge(server, realname, nickname, channel string, tls bool) *IRCBridge {
47+ return &IRCBridge{
48+ server: server,
49+ user: realname,
50+ nick: nickname,
51+ channel: channel,
52+ useTLS: tls,
53+ }
54+}
55+
56+func (bridge *IRCBridge) Connect(messagesIn chan Message, messagesOut chan Message) bool {
57+ //Create new connection
58+ bridge.connection = irc.IRC(bridge.nick, bridge.user)
59+ //Set options
60+ bridge.connection.UseTLS = bridge.useTLS
61+ //connection.TLSOptions //set ssl options
62+ //connection.Password = "[server password]"
63+ //Commands
64+ err := bridge.connection.Connect(bridge.server) //Connect to server
65+ if err != nil {
66+ log.Fatal("Can't connect %s", bridge.server)
67+ return false
68+ }
69+ bridge.connection.Join(bridge.channel)
70+ bridge.connection.AddCallback("PRIVMSG", func(event *irc.Event) {
71+ //e.Message contains the message
72+ //e.Nick Contains the sender
73+ //e.Arguments[0] Contains the channel
74+ select {
75+ case messagesOut <- Message{nick: event.Nick,
76+ message: event.Message(),
77+ }:
78+ default:
79+ log.Println("Message Queue full.")
80+ }
81+
82+ })
83+ go func() {
84+ for m := range messagesIn {
85+ bridge.connection.Privmsg(bridge.channel, m.message)
86+ }
87+ }()
88+ return true
89+}
90+
91+func (bridge *IRCBridge) Quit() {
92+ bridge.connection.Quit()
93+}
94
95=== modified file 'wlms/main.go'
96--- wlms/main.go 2013-12-30 12:20:31 +0000
97+++ wlms/main.go 2014-03-17 19:05:27 +0000
98@@ -8,7 +8,8 @@
99 )
100
101 type Config struct {
102- Database, User, Password, Table string
103+ Database, User, Password, Table, Backend, IRCServer, Nickname, Realname, Channel string
104+ UseTLS bool
105 }
106
107 func (l *Config) ConfigFrom(path string) error {
108@@ -25,16 +26,26 @@
109 flag.Parse()
110
111 var db UserDb
112+ var ircbridge IRCBridger
113 if config != "" {
114 var cfg Config
115 if err := cfg.ConfigFrom(config); err != nil {
116 log.Fatalf("Could not parse config file: %v", err)
117 }
118- db = NewMySqlDatabase(cfg.Database, cfg.User, cfg.Password, cfg.Table)
119+ if cfg.Backend == "mysql" {
120+ db = NewMySqlDatabase(cfg.Database, cfg.User, cfg.Password, cfg.Table)
121+ } else {
122+ db = NewInMemoryDb()
123+ }
124+ ircbridge = NewIRCBridge(cfg.IRCServer, cfg.Realname, cfg.Nickname, cfg.Channel, cfg.UseTLS)
125 } else {
126 db = NewInMemoryDb()
127 }
128 defer db.Close()
129
130- RunServer(db)
131+ messagesToIrc := make(chan Message, 50)
132+ messagesToLobby := make(chan Message, 50)
133+ ircbridge.Connect(messagesToIrc, messagesToLobby)
134+ RunServer(db, messagesToLobby, messagesToIrc)
135+
136 }
137
138=== modified file 'wlms/server.go'
139--- wlms/server.go 2014-02-08 20:48:04 +0000
140+++ wlms/server.go 2014-03-17 19:05:27 +0000
141@@ -33,10 +33,11 @@
142 gamePingTimeout time.Duration
143
144 clientForgetTimeout time.Duration
145- gamePingerFactory GampPingerFactory
146+ gamePingerFactory GamePingerFactory
147+ messagesOut chan Message
148 }
149
150-type GampPingerFactory interface {
151+type GamePingerFactory interface {
152 New(client *Client, timeout time.Duration) *GamePinger
153 }
154
155@@ -100,6 +101,7 @@
156 }
157
158 func (s *Server) AddClient(client *Client) {
159+ s.BroadcastToIrc(client.Name() + " has joined the lobby.")
160 s.clients.PushBack(client)
161 }
162
163@@ -121,11 +123,15 @@
164 if e.Value.(*Client) == client {
165 log.Printf("Removing client %s.", client.Name())
166 s.clients.Remove(e)
167+ s.messagesOut <- Message{
168+ message: client.Name() + " has left the lobby.",
169+ nick: client.Name(),
170+ }
171 }
172 }
173 }
174
175-func (s *Server) HasClient(name string) *Client {
176+func (s Server) HasClient(name string) *Client {
177 for e := s.clients.Front(); e != nil; e = e.Next() {
178 client := e.Value.(*Client)
179 if client.Name() == name {
180@@ -135,7 +141,7 @@
181 return nil
182 }
183
184-func (s *Server) NrActiveClients() int {
185+func (s Server) NrActiveClients() int {
186 count := 0
187 for e := s.clients.Front(); e != nil; e = e.Next() {
188 if e.Value.(*Client).State() == CONNECTED {
189@@ -158,6 +164,7 @@
190 func (s *Server) AddGame(game *Game) {
191 s.games.PushBack(game)
192 s.BroadcastToConnectedClients("GAMES_UPDATE")
193+ s.BroadcastToIrc("A new game " + game.Name() + " was opened by " + game.Host())
194 }
195
196 func (s *Server) RemoveGame(game *Game) {
197@@ -170,7 +177,7 @@
198 }
199 }
200
201-func (s *Server) HasGame(name string) *Game {
202+func (s Server) HasGame(name string) *Game {
203 for e := s.games.Front(); e != nil; e = e.Next() {
204 game := e.Value.(*Game)
205 if game.Name() == name {
206@@ -180,7 +187,7 @@
207 return nil
208 }
209
210-func (s *Server) NrGames() int {
211+func (s Server) NrGames() int {
212 return s.games.Len()
213 }
214
215@@ -199,7 +206,18 @@
216 }
217 }
218
219-func RunServer(db UserDb) {
220+func (s Server) BroadcastToIrc(message string) {
221+ select {
222+ case s.messagesOut <- Message{
223+ message: message,
224+ }:
225+ default:
226+ log.Println("Message Queue full.")
227+ }
228+
229+}
230+
231+func RunServer(db UserDb, messagesIn chan Message, messagesOut chan Message) {
232 ln, err := net.Listen("tcp", ":7395")
233 if err != nil {
234 log.Fatal(err)
235@@ -216,8 +234,7 @@
236 C <- conn
237 }
238 }()
239-
240- CreateServerUsing(C, db).WaitTillShutdown()
241+ CreateServerUsing(C, db, messagesIn, messagesOut).WaitTillShutdown()
242 }
243
244 type RealGamePingerFactory struct {
245@@ -255,11 +272,11 @@
246 return pinger
247 }
248
249-func (server *Server) InjectGamePingerFactory(gpf GampPingerFactory) {
250+func (server *Server) InjectGamePingerFactory(gpf GamePingerFactory) {
251 server.gamePingerFactory = gpf
252 }
253
254-func CreateServerUsing(acceptedConnections chan ReadWriteCloserWithIp, db UserDb) *Server {
255+func CreateServerUsing(acceptedConnections chan ReadWriteCloserWithIp, db UserDb, messagesIn chan Message, messagesOut chan Message) *Server {
256 server := &Server{
257 acceptedConnections: acceptedConnections,
258 shutdownServer: make(chan bool),
259@@ -272,8 +289,14 @@
260 pingCycleTime: time.Second * 15,
261 clientSendingTimeout: time.Minute * 2,
262 clientForgetTimeout: time.Minute * 5,
263+ messagesOut: messagesOut,
264 }
265 server.gamePingerFactory = RealGamePingerFactory{server}
266+ go func() {
267+ for m := range messagesIn {
268+ server.BroadcastToConnectedClients("CHAT", m.nick+"(IRC)", m.message, "public")
269+ }
270+ }()
271 go server.mainLoop()
272 return server
273 }
274
275=== modified file 'wlms/server_test.go'
276--- wlms/server_test.go 2014-02-08 20:48:04 +0000
277+++ wlms/server_test.go 2014-03-17 19:05:27 +0000
278@@ -40,7 +40,12 @@
279 cons[i] = NewFakeConn(c)
280 acceptingConnections <- cons[i]
281 }
282- return CreateServerUsing(acceptingConnections, db), cons
283+
284+ //irc := NewIRCBridge("chat.freenode.net:7000", "wltest", "wltest", "#widelands-test", true)
285+ toIrc := make(chan Message, 100)
286+ fromIrc := make(chan Message, 100)
287+ //irc.Connect(toIrc, fromIrc)
288+ return CreateServerUsing(acceptingConnections, db, fromIrc, toIrc), cons
289 }
290
291 type Matching string

Subscribers

People subscribed via source and target branches

to all changes: