Increased version number
[galtack.git] / galcon / net_client_housekeeping.py
blob98673d9d7709e5aeb86c007af95856cce7fad695
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 """
4 Galcon 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 galcon.net_client_base import *
35 class GalconClientPrependSeqNrMixin(GalconClientSendCmdBaseMixin):
36 """
37 GalconClient that adds the sequence number to each command before
38 sending.
39 """
41 def __init__(self, *a, **kw):
42 super(GalconClientPrependSeqNrMixin, 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 = GalconClientPrependSeqNrMixin
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 GalconClientAckExpectingMixin(GalconClientBackCallerMixin,
60 GalconClientPrependSeqNrMixin):
61 """
62 GalconClient 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(GalconClientAckExpectingMixin, self).__init__(*a, **kw)
68 self.__awaiting_ack = {}
69 self.register_command_callback("[ACK]", self.__cb_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 = GalconClientAckExpectingMixin
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 __cb_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 GalconClientAckSendingMixin(GalconClientBackCallerMixin,
97 GalconClientPrependSeqNrMixin):
98 """
99 GalconClient that sends out [ACK] commands when needed.
102 def __init__(self, *a, **kw):
103 super(GalconClientAckSendingMixin, self).__init__(*a, **kw)
104 self.register_command_callback(False, self.__cb_commands)
106 def __cb_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 GalconClientHousekeeperMixin(
118 GalconClientAckExpectingMixin,
119 GalconClientAckSendingMixin,
120 GalconClientSendCmdBaseMixin,
123 GalconClient 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 registered:
130 def callback() # *no* argument
131 It gets called whenever a logout could be completed successfully.
133 Alternatively, you may connect to the deferred returned by logout().
136 def __init__(self, *a, **kw):
137 super(GalconClientHousekeeperMixin, self).__init__(*a, **kw)
138 self.register_command_callback("version", self.__cb_version)
139 self.__logout_deferred = None
141 def startProtocol(self):
142 super(GalconClientHousekeeperMixin, self).startProtocol()
143 self.send_commands(
144 (1, "[RESET]"),
145 (1, "[RESET]"),
146 (1, "version", "0.18.0"),
149 def __cb_version(self, command):
150 self.send_commands((1, "login"))
152 def logout(self):
153 self.send_commands(
154 (1, "logout"),
155 (1, "[PING]"),
157 self.register_command_callback("[CLOSE]", self.__cb_close)
158 self.__logout_deferred = defer.Deferred()
159 return self.__logout_deferred
161 def __cb_close(self, command):
162 self.unregister_command_callback("[CLOSE]", self.__cb_close)
163 commands = self.send_commands(
164 (1, "[CLOSE]"),
166 self.__awaiting_ack_for_seq_nr = commands[0][0]
167 self.register_command_callback("[ACK]", self.__cb_close_ack)
169 def __cb_close_ack(self, command):
170 if int(command[3]) == self.__awaiting_ack_for_seq_nr:
171 self.unregister_command_callback("[ACK]", self.__cb_close_ack)
172 del self.__awaiting_ack_for_seq_nr
173 self.__logout_deferred.callback(None)
174 self.call_command_callbacks("[CLOSE]-[ACK]")