Fixed GaltackClientIgnoreResentCmdsMixin
[galtack.git] / galtack / net_client_housekeeping.py
blob8b5e107ec965b4bda97d21e40d6e54e6068498bb
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 from twisted.internet import defer, reactor
32 from galtack.net_client_base import *
35 class GaltackClientPrependSeqNrMixin(GaltackClientSendCmdBaseMixin):
36 """
37 GaltackClient that adds the sequence number to each command before
38 sending.
39 """
41 def __init__(self, *a, **kw):
42 super(GaltackClientPrependSeqNrMixin, self).__init__(*a, **kw)
43 self.__seq_nr = 0
45 def _pre_send_command_hook(self, command, **kw):
46 """
47 To disable this hook, call:
48 send_command(..., prepend_seq_nr = False)
49 """
50 Cls = GaltackClientPrependSeqNrMixin
51 prepend_seq_nr = kw.pop("prepend_seq_nr", True)
52 command = super(Cls, self)._pre_send_command_hook(command, **kw)
53 if prepend_seq_nr:
54 self.__seq_nr += 1
55 command = (self.__seq_nr,) + command
56 return command
59 class GaltackClientAckExpectingMixin(GaltackClientBackCallerMixin,
60 GaltackClientPrependSeqNrMixin):
61 """
62 GaltackClient that expects an [ACK] from the server for each to-be-sent
63 command asking for one.
64 """
66 def __init__(self, *a, **kw):
67 super(GaltackClientAckExpectingMixin, self).__init__(*a, **kw)
68 self.__awaiting_ack = {}
69 self.register_command_callback("[ACK]", self.__cmd_ack)
71 def __resend(self, seq_nr):
72 command, deferred = self.__awaiting_ack[int(seq_nr)]
73 self.send_commands(command, prepend_seq_nr = False)
75 def _pre_send_command_hook(self, command, **kw):
76 Cls = GaltackClientAckExpectingMixin
77 command = super(Cls, self)._pre_send_command_hook(command, **kw)
78 seq_nr, want_ack = map(int, command[:2])
80 if seq_nr in self.__awaiting_ack: # we are resending
81 del self.__awaiting_ack[seq_nr]
83 if want_ack:
84 deferred = reactor.callLater(1, self.__resend, seq_nr)
85 self.__awaiting_ack[seq_nr] = (command, deferred)
87 return command
89 def __cmd_ack(self, command):
90 seq_nr = int(command[3])
91 command, deferred = self.__awaiting_ack.pop(seq_nr, (None, None))
92 if deferred is not None:
93 deferred.cancel()
96 class GaltackClientAckSendingMixin(GaltackClientBackCallerMixin,
97 GaltackClientPrependSeqNrMixin):
98 """
99 GaltackClient that sends out [ACK] commands when needed.
102 def __init__(self, *a, **kw):
103 super(GaltackClientAckSendingMixin, self).__init__(*a, **kw)
104 self.register_command_callback(False, self.__cmd_commands)
106 def __cmd_commands(self, commands):
107 seq_nr_list = []
108 for command in commands:
109 try:
110 seq_nr, want_ack = map(int, command[0:2])
111 except: continue # in case the server would send [HEADER]
112 if not want_ack: continue
113 seq_nr_list.append(seq_nr)
114 self.send_commands(*((0, "[ACK]", sn) for sn in seq_nr_list))
117 class GaltackClientHousekeeperMixin(
118 GaltackClientAckExpectingMixin,
119 GaltackClientAckSendingMixin,
120 GaltackClientSendCmdBaseMixin,
123 GaltackClient that interacts properly with a Galcon server.
125 It takes the responsibility for setting up and shutting down the
126 connection.
128 It (implicitly) defines an imaginary server-side command name
129 "[CLOSE]-[ACK]" to which callbacks with the following signature may be
130 registered:
131 def callback() # *no* argument
132 It gets called whenever a logout could be completed successfully.
134 Alternatively, you may connect to the deferred returned by logout(),
135 which receives None as an argument.
138 def __init__(self, *a, **kw):
139 super(GaltackClientHousekeeperMixin, self).__init__(*a, **kw)
140 self.register_command_callback("version", self.__cmd_version)
141 self.__logout_deferred = None
143 def startProtocol(self):
144 super(GaltackClientHousekeeperMixin, self).startProtocol()
145 self.send_commands(
146 (1, "[RESET]"),
147 (1, "[RESET]"),
148 (1, "version", "0.18.0"),
151 def __cmd_version(self, command):
152 self.send_commands((1, "login"))
154 def logout(self):
155 self.send_commands(
156 (1, "logout"),
157 (1, "[PING]"),
159 self.register_command_callback("[CLOSE]", self.__cmd_close)
160 self.__logout_deferred = defer.Deferred()
161 return self.__logout_deferred
163 def __cmd_close(self, command):
164 self.unregister_command_callback("[CLOSE]", self.__cmd_close)
165 commands = self.send_commands(
166 (1, "[CLOSE]"),
168 self.__awaiting_ack_for_seq_nr = commands[0][0]
169 self.register_command_callback("[ACK]", self.__cmd_close_ack)
171 def __cmd_close_ack(self, command):
172 if int(command[3]) == self.__awaiting_ack_for_seq_nr:
173 self.unregister_command_callback("[ACK]", self.__cmd_close_ack)
174 del self.__awaiting_ack_for_seq_nr
175 self.__logout_deferred.callback(None)
176 self.call_command_callbacks("[CLOSE]-[ACK]")