[PING] only if possible; log for universe
[galtack.git] / galtack / net_client_housekeeping.py
blob9d0c1c8776a9a398a924306a93d7e109a21b2946
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """
4 GalTacK networking - client-side protocol housekeeping.
5 """
6 # Copyright (C) 2007 Felix Rabe <public@felixrabe.textdriven.com>
7 # Copyright (C) 2007 Michael Carter
9 # Permission is hereby granted, free of charge, to any person obtaining a
10 # copy of this software and associated documentation files (the
11 # "Software"), to deal in the Software without restriction, including
12 # without limitation the rights to use, copy, modify, merge, publish,
13 # distribute, sublicense, and/or sell copies of the Software, and to permit
14 # persons to whom the Software is furnished to do so, subject to the
15 # following conditions:
17 # The above copyright notice and this permission notice shall be included
18 # in all copies or substantial portions of the Software.
20 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23 # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24 # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
25 # OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
26 # THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 # Recommended line length or text width: 75 characters.
30 import sys
31 import time
32 from twisted.internet import defer, reactor
33 from galtack.net_client_base import *
36 class GaltackClientAckExpectingMixin(GaltackClientPrependSeqNrMixin,
37 GaltackClientBackCallerMixin):
38 """
39 GaltackClient that expects an [ACK] from the server for each to-be-sent
40 command asking for one.
41 """
43 def __init__(self, *a, **kw):
44 super(GaltackClientAckExpectingMixin, self).__init__(*a, **kw)
45 self.__awaiting_ack = {}
46 self.register_command_callback("[ACK]", self.__cmd_ack)
48 def __resend(self, seq_nr):
49 command, deferred = self.__awaiting_ack[int(seq_nr)]
50 self.send_commands(command, prepend_seq_nr = False)
52 def _pre_send_command_hook(self, command, **kw):
53 Cls = GaltackClientAckExpectingMixin
54 command = super(Cls, self)._pre_send_command_hook(command, **kw)
55 seq_nr, want_ack = map(int, command[:2])
57 if seq_nr in self.__awaiting_ack: # we are resending
58 del self.__awaiting_ack[seq_nr]
60 if want_ack:
61 deferred = reactor.callLater(1, self.__resend, seq_nr)
62 self.__awaiting_ack[seq_nr] = (command, deferred)
64 return command
66 def __cmd_ack(self, command):
67 seq_nr = int(command[3])
68 command, deferred = self.__awaiting_ack.pop(seq_nr, (None, None))
69 if deferred is not None:
70 deferred.cancel()
73 class GaltackClientPingSenderMixin(GaltackClientAckSenderMixin):
74 """
75 GaltackClient that sends out [PING] commands after a while.
77 This is to make sure the server doesn't timeout the connection because
78 we never sent or [ACK]'ed something.
79 """
81 def __init__(self, *a, **kw):
82 super(GaltackClientPingSenderMixin, self).__init__(*a, **kw)
83 self.__create_ping_delayed()
85 def __create_ping_delayed(self):
86 if hasattr(self, "_GaltackClientPingSenderMixin__ping_delayed"):
87 if self.__ping_delayed.active():
88 self.__ping_delayed.reset(5)
89 return
90 self.__ping_delayed = reactor.callLater(5, self.__send_ping)
92 def _pre_send_commands_hook(self, *commands, **kw):
93 sup = super(GaltackClientPingSenderMixin, self)
94 commands = sup._pre_send_commands_hook(*commands, **kw)
95 self.__create_ping_delayed()
96 return commands
98 def __send_ping(self):
99 if self._GaltackClientSendCmdBaseMixin__can_send_commands:
100 self.send_commands((1, "[PING]"))
101 self.__create_ping_delayed()
104 class GaltackClientHousekeeperMixin(
105 GaltackClientAckExpectingMixin,
106 GaltackClientPingSenderMixin,
107 GaltackClientAckSenderMixin,
108 GaltackClientSendCmdBaseMixin,
111 GaltackClient that interacts properly with a Galcon server.
113 It takes the responsibility for setting up and shutting down the
114 connection.
116 It (implicitly) defines an imaginary server-side command name
117 "[CLOSE]-[ACK]" to which callbacks with the following signature may be
118 registered:
119 def callback() # *no* argument
120 It gets called whenever a logout could be completed successfully.
122 Alternatively, you may connect to the deferred returned by logout(),
123 which receives None as an argument.
126 class LogoutAborted(Exception): pass
128 def __init__(self, *a, **kw):
129 super(GaltackClientHousekeeperMixin, self).__init__(*a, **kw)
130 self.register_command_callback("version", self.__cmd_version)
131 self.__logout_deferred = None
133 def startProtocol(self):
134 super(GaltackClientHousekeeperMixin, self).startProtocol()
135 self.send_commands(
136 (1, "[RESET]"),
137 (1, "[RESET]"),
138 (1, "version", "0.18.0"),
141 def __cmd_version(self, command):
142 self.send_commands((1, "login"))
144 def logout(self):
145 self.send_commands(
146 (1, "logout"),
147 (1, "[PING]"),
149 self.register_command_callback("[CLOSE]", self.__cmd_close)
150 self.__logout_deferred = defer.Deferred()
151 self.__logout_abort = reactor.callLater(10, self.abort_logout)
152 return self.__logout_deferred
154 def abort_logout(self):
155 self.__logout_deferred.errback(self.LogoutAborted())
157 def __cmd_close(self, command):
158 self.__logout_abort.cancel()
159 self.unregister_command_callback("[CLOSE]", self.__cmd_close)
160 commands = self.send_commands(
161 (1, "[CLOSE]"),
163 self.__awaiting_ack_for_seq_nr = commands[0][0]
164 self.register_command_callback("[ACK]", self.__cmd_close_ack)
166 def __cmd_close_ack(self, command):
167 if int(command[3]) == self.__awaiting_ack_for_seq_nr:
168 self.unregister_command_callback("[ACK]", self.__cmd_close_ack)
169 del self.__awaiting_ack_for_seq_nr
170 self.__logout_deferred.callback(None)
171 self.call_command_callbacks("[CLOSE]-[ACK]")