2 # -*- coding: utf-8 -*-
4 GalTacK networking - client-side protocol housekeeping.
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.
31 from twisted
.internet
import defer
, reactor
32 from galtack
.net_client_base
import *
35 class GaltackClientPrependSeqNrMixin(GaltackClientSendCmdBaseMixin
):
37 GaltackClient that adds the sequence number to each command before
41 def __init__(self
, *a
, **kw
):
42 super(GaltackClientPrependSeqNrMixin
, self
).__init
__(*a
, **kw
)
45 def _pre_send_command_hook(self
, command
, **kw
):
47 To disable this hook, call:
48 send_command(..., prepend_seq_nr = False)
50 Cls
= GaltackClientPrependSeqNrMixin
51 prepend_seq_nr
= kw
.pop("prepend_seq_nr", True)
52 command
= super(Cls
, self
)._pre
_send
_command
_hook
(command
, **kw
)
55 command
= (self
.__seq
_nr
,) + command
59 class GaltackClientAckExpectingMixin(GaltackClientBackCallerMixin
,
60 GaltackClientPrependSeqNrMixin
):
62 GaltackClient that expects an [ACK] from the server for each to-be-sent
63 command asking for one.
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
]
84 deferred
= reactor
.callLater(1, self
.__resend
, seq_nr
)
85 self
.__awaiting
_ack
[seq_nr
] = (command
, deferred
)
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:
96 class GaltackClientAckSendingMixin(GaltackClientBackCallerMixin
,
97 GaltackClientPrependSeqNrMixin
):
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
):
108 for command
in commands
:
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
128 It (implicitly) defines an imaginary server-side command name
129 "[CLOSE]-[ACK]" to which callbacks with the following signature may be
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()
148 (1, "version", "0.18.0"),
151 def __cmd_version(self
, command
):
152 self
.send_commands((1, "login"))
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(
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]")