Imported Upstream version 2008.1+svn1656
[opeanno-debian-packaging.git] / game / serverlist.py
blob682d8ee430b9b565ab55b15bf24c66268ea8f53c
1 # ###################################################
2 # Copyright (C) 2008 The OpenAnno Team
3 # team@openanno.org
4 # This file is part of OpenAnno.
6 # OpenAnno is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the
18 # Free Software Foundation, Inc.,
19 # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 # ###################################################
22 import urllib
23 import re
24 import game.main
25 from game.network import Socket
26 from game.packets import QueryPacket, InfoPacket
27 import time
29 class Server(object):
30 re_ip_port = re.compile("^((?:[0-1]?[0-9]{1,2}|2(?:[0-4][0-9]|5[0-5]))[.](?:[0-1]?[0-9]{1,2}|2(?:[0-4][0-9]|5[0-5]))[.](?:[0-1]?[0-9]{1,2}|2(?:[0-4][0-9]|5[0-5]))[.](?:[0-1]?[0-9]{1,2}|2(?:[0-4][0-9]|5[0-5])))(?::((?:[0-5]?[0-9]{1,4}|6(?:[0-4][0-9]{3}|5(?:[0-4][0-9]{2}|5(?:[0-2][0-9]|3[0-5]))))))?$")
32 """This represents a server in the serverlist
33 @param address: the address
34 @param port: and the port :)
35 """
36 def __init__(self, address, port = None):
37 if port is None:
38 match = Server.re_ip_port.match(address)
39 if match:
40 address = match.group(1)
41 port = match.group(2) or game.main.settings.network.port
42 else:
43 port = game.main.settings.network.port
44 self.address = address
45 self.port = int(port)
46 self.ping, self.map, self.players, self.bots, self.maxplayers, self.timeLastQuery, self.timeLastResponse = None, None, None, None, None, None, None
48 def __eq__(self, other):
49 """check if the server is the same (only address & port have to match)
50 @param other: the other one
51 """
52 return self.address == other.address and self.port == other.port
54 def __str__(self):
55 """A nice representation we can show in the list
56 """
57 info = ['timeout' if self.ping is None else 'ping: ' + str(self.ping) + 'ms']
58 if self.map is not None:
59 info.append('map: ' + self.map)
60 if self.players is not None or self.maxplayers is not None or self.bots is not None:
61 info.append('players: ' + str(self.players) + '+' + str(self.bots) + '/' + str(self.maxplayers))
62 return str(self.address) + ':' + str(self.port) + ' (' + ', '.join(info) + ')'
64 class ServerList(object):
65 queryIntervall = 1
66 queryTimeout = 2
68 """A basic Serverlist, should be extended to implement more functionality
69 """
70 def __init__(self):
71 self._servers = []
72 self._socket = Socket()
73 self._socket.receive = self._response
74 game.main.ext_scheduler.add_new_object(self._pump, self, self.queryIntervall, -1)
76 def end(self):
77 """
78 """
79 game.main.ext_scheduler.rem_all_classinst_calls(self)
80 self._socket.receive = lambda x : None
81 self._socket.end()
83 def _pump(self):
84 """internal function, regularly called to ping the contained servers etc
85 """
86 for server in self:
87 if server.ping is not None and int(server.timeLastResponse or 0) + self.__class__.queryTimeout <= server.timeLastQuery:
88 server.ping = None
89 self.changed()
90 self._query(server.address, server.port)
91 return
93 def _clear(self):
94 """remove all servers from the list
95 """
96 self._servers = []
97 self.changed()
99 def _add(self, server):
100 """add a server to the list
101 @param server: the server to add
103 if server in self:
104 self._servers.remove(server)
105 self._servers.append(server)
106 self.changed()
108 def _remove(self, server):
109 """remove a server from the list
110 @param server: the server to remove
112 self._servers.remove(server)
114 def _query(self, address, port):
115 """query(ask for an InfoPacket) a address port combination and update the corresponding server
116 @param address: the address to query
117 @param port: and the port
119 tmp_server = Server(address, port)
120 for server in self:
121 if server == tmp_server:
122 server.timeLastQuery = time.time()
123 self._request(address, port)
125 def _request(self, address, port):
126 """query(ask for an InfoPacket) a address port combination
127 @param address: the address to query
128 @param port: and the port
130 self._socket.send(QueryPacket(address, port))
132 def _response(self, packet):
133 """internal function, called when a server responded
134 @param packet: the received packet
136 if not isinstance(packet, InfoPacket):
137 return
138 for server in self:
139 if server.address == packet.address and server.port == packet.port:
140 server.timeLastResponse = time.time()
141 server.ping = int(round(((server.timeLastResponse - server.timeLastQuery) * 1000)))
142 server.map, server.players, server.bots, server.maxplayers = packet.map, packet.players, packet.bots, packet.maxplayers
143 self.changed()
145 def changed(self):
146 """callback, called when the list changed
148 pass
150 def __getitem__(self, *args, **kwargs): return self._servers.__getitem__(*args, **kwargs)
151 def __getslice__(self, *args, **kwargs): return self._servers.__getslice__(*args, **kwargs)
152 def __iter__(self, *args, **kwargs): return self._servers.__iter__(*args, **kwargs)
153 def __len__(self, *args, **kwargs): return self._servers.__len__(*args, **kwargs)
154 def __contains__(self, *args, **kwargs): return self._servers.__contains__(*args, **kwargs)
156 class WANServerList(ServerList):
157 updateIntervall = 60
159 """a specialized serverlist, gets its content from an url
161 def __init__(self):
162 super(WANServerList, self).__init__()
163 self.update()
164 if self.__class__.updateIntervall > 0:
165 game.main.ext_scheduler.add_new_object(self.update, self, self.updateIntervall, -1)
167 def end(self):
170 game.main.ext_scheduler.rem_all_classinst_calls(self)
171 super(WANServerList, self).end()
173 def update(self):
174 """manually update the serverlist from the url
176 self._clear()
178 wan_servers = []
179 try:
180 wan_servers = (urllib.urlopen(game.main.settings.network.url_servers))
181 except IOError,e:
182 game.main.showPopup("Network Error", "Error: "+e.strerror[1])
184 for server in wan_servers:
185 match = Server.re_ip_port.match(server)
186 if match:
187 server = Server(match.group(1), match.group(2) or game.main.settings.network.port)
188 if not server in self:
189 self._add(server)
190 self._query(server.address, server.port)
192 class LANServerList(ServerList):
193 updateIntervall = 3
195 """a serverlist which regularly searches the lan for servers (sends a broadcast)
197 def __init__(self):
198 super(LANServerList, self).__init__()
199 self.update()
200 if self.__class__.updateIntervall > 0:
201 game.main.ext_scheduler.add_new_object(self.update, self, self.updateIntervall, -1)
203 def end(self):
206 game.main.ext_scheduler.rem_all_classinst_calls(self)
207 super(LANServerList, self).end()
209 def update(self):
210 """manually update the list, search for servers (send the broadcast)
212 for server in self:
213 server.timeLastQuery = time.time()
214 self._request('255.255.255.255', game.main.settings.network.port)
215 #self._request('192.168.2.255', game.main.settings.network.port)
217 def _response(self, packet):
218 """overwritten function of the base class, ensures that the server is in the list when a packet is received
219 @param packet: the packet
221 if not isinstance(packet, InfoPacket):
222 return
223 for server in self:
224 if server.address == packet.address and server.port == packet.port:
225 break
226 else:
227 self._add(Server(packet.address, packet.port))
228 self._query(packet.address, packet.port)
229 super(LANServerList, self)._response(packet)
231 class FavoriteServerList(ServerList):
232 """a specialzed serverlist, which holds a static set of servers and just regularly updates them
234 def __init__(self):
235 super(FavoriteServerList, self).__init__()
236 for serverstr in game.main.settings.network.favorites:
237 server = Server(serverstr)
238 self._add(server)
239 self._query(server.address, server.port)
241 def update(self):
242 """manually update
244 for server in self:
245 self._query(server.address, server.port)
247 def add(self, serverstr):
248 """add a server
249 @param serverstr: a string in "address[:port]" notion
251 server = Server(serverstr)
252 self._add(server)
253 self._query(server.address, server.port)
254 game.main.settings.network.favorites = game.main.settings.network.favorites + [serverstr]
256 def remove(self, serverstr):
257 """remove a server
258 @param serverstr: a string in "address[:port]" notion
260 self._remove(Server(serverstr))
261 favorites = game.main.settings.network.favorites
262 favorites.remove(serverstr)
263 game.main.settings.network.favorites = favorites
265 def clear(self):
266 """make the list empty
268 self._clear()
269 game.main.settings.network.favorites = []