home · contact · privacy
Add basic flood protection to server.
[plomrogue2] / plomrogue / io.py
1 import queue
2 import threading
3 import inspect
4
5
6
7 class GameIO():
8
9     def __init__(self, game, save_file='savefile'):
10         from plomrogue.parser import Parser
11         self.parser = Parser(game)
12         self.game = game
13         self.save_file = save_file
14         self.servers = []
15
16     def loop(self, q):
17         """Handle commands coming through queue q, run game, send results back.
18
19         As basic flood protection, Only accepts one command per connection per
20         1/100 of a second.
21
22         """
23         import time
24         potential_flooders = {}
25         while True:
26             try:
27                 command, connection_id = q.get(timeout=0.001)
28                 if connection_id in potential_flooders:
29                     if int(time.time() * 100) == potential_flooders[connection_id]:
30                         continue
31                 potential_flooders[connection_id] = int(time.time() * 100)
32                 self.handle_input(connection_id, command)
33             except queue.Empty:
34                 self.game.run_tick()
35
36     def start_loop(self):
37         """Start game loop, set up self.queue to communicate with it.
38
39         The game loop works sequentially through game commands received
40         via self.queue from connected servers' clients."""
41         self.queue = queue.Queue()
42         c = threading.Thread(target=self.loop, args=(self.queue,))
43         c.start()
44
45     def start_server(self, port, server_class, certfile=None, keyfile=None):
46         """Start server of server_class in talk with game loop.
47
48         The server communicates with the game loop via self.queue.
49         """
50         if 'certfile' in list(inspect.signature(server_class.__init__).parameters):
51             server = server_class(self.queue, port, certfile=certfile, keyfile=keyfile)
52         else:
53             server = server_class(self.queue, port)
54         self.servers += [server]
55         c = threading.Thread(target=server.serve_forever)
56         c.start()
57
58     def handle_input(self, input_, connection_id=None, god_mode=False):
59         """Process input_ to command grammar, call command handler if found.
60
61         Command handlers that have no connectin_i argument in their
62         signature will only be called if god_mode is set.
63
64         """
65         from plomrogue.errors import GameError, ArgError, PlayError
66         from plomrogue.misc import quote
67
68         def answer(connection_id, msg):
69             if connection_id:
70                 self.send(msg, connection_id)
71             else:
72                 print(msg)
73
74         try:
75             command, args = self.parser.parse(input_)
76             if command is None:
77                 answer(connection_id, 'UNHANDLED_INPUT')
78             else:
79                 if 'connection_id' in list(inspect.signature(command).parameters):
80                     command(*args, connection_id=connection_id)
81                 elif god_mode:
82                     command(*args)
83                     #if store and not hasattr(command, 'dont_save'):
84                     #    with open(self.game_file_name, 'a') as f:
85                     #        f.write(input_ + '\n')
86         except ArgError as e:
87             answer(connection_id, 'ARGUMENT_ERROR ' + quote(str(e)))
88         except PlayError as e:
89             answer(connection_id, 'PLAY_ERROR ' + quote(str(e)))
90         except GameError as e:
91             answer(connection_id, 'GAME_ERROR ' + quote(str(e)))
92
93     def send(self, msg, connection_id=None):
94         """Send message msg to servers' client(s).
95
96         If a specific client is identified by connection_id, only
97         sends msg to that one. Else, sends it to all client sessions.
98
99         """
100         if connection_id:
101             for server in self.servers:
102                  if connection_id in server.clients:
103                     client = server.clients[connection_id]
104                     client.put(msg)
105         else:
106             for c_id in self.game.sessions:
107                 for server in self.servers:
108                     if c_id in server.clients:
109                         client = server.clients[c_id]
110                         client.put(msg)
111                         break