Chg example; reload/run_loadable() returns a value
[galtack.git] / galtack / net_client_housekeeping.py
blobac6b167635eab5ba4a5ddea7e6c9822402b9dd0e
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 self.send_commands((1, "[PING]"))
100 self.__create_ping_delayed()
103 class GaltackClientHousekeeperMixin(
104 GaltackClientAckExpectingMixin,
105 GaltackClientPingSenderMixin,
106 GaltackClientAckSenderMixin,
107 GaltackClientSendCmdBaseMixin,
110 GaltackClient that interacts properly with a Galcon server.
112 It takes the responsibility for setting up and shutting down the
113 connection.
115 It (implicitly) defines an imaginary server-side command name
116 "[CLOSE]-[ACK]" to which callbacks with the following signature may be
117 registered:
118 def callback() # *no* argument
119 It gets called whenever a logout could be completed successfully.
121 Alternatively, you may connect to the deferred returned by logout(),
122 which receives None as an argument.
125 class LogoutAborted(Exception): pass
127 def __init__(self, *a, **kw):
128 super(GaltackClientHousekeeperMixin, self).__init__(*a, **kw)
129 self.register_command_callback("version", self.__cmd_version)
130 self.__logout_deferred = None
132 def startProtocol(self):
133 super(GaltackClientHousekeeperMixin, self).startProtocol()
134 self.send_commands(
135 (1, "[RESET]"),
136 (1, "[RESET]"),
137 (1, "version", "0.18.0"),
140 def __cmd_version(self, command):
141 self.send_commands((1, "login"))
143 def logout(self):
144 self.send_commands(
145 (1, "logout"),
146 (1, "[PING]"),
148 self.register_command_callback("[CLOSE]", self.__cmd_close)
149 self.__logout_deferred = defer.Deferred()
150 self.__logout_abort = reactor.callLater(10, self.abort_logout)
151 return self.__logout_deferred
153 def abort_logout(self):
154 self.__logout_deferred.errback(self.LogoutAborted())
156 def __cmd_close(self, command):
157 self.__logout_abort.cancel()
158 self.unregister_command_callback("[CLOSE]", self.__cmd_close)
159 commands = self.send_commands(
160 (1, "[CLOSE]"),
162 self.__awaiting_ack_for_seq_nr = commands[0][0]
163 self.register_command_callback("[ACK]", self.__cmd_close_ack)
165 def __cmd_close_ack(self, command):
166 if int(command[3]) == self.__awaiting_ack_for_seq_nr:
167 self.unregister_command_callback("[ACK]", self.__cmd_close_ack)
168 del self.__awaiting_ack_for_seq_nr
169 self.__logout_deferred.callback(None)
170 self.call_command_callbacks("[CLOSE]-[ACK]")