2 # -*- coding: utf-8 -*-
4 Galcon 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 galcon
.net_client_base
import *
35 class GalconClientPrependSeqNrMixin(GalconClientSendCmdBaseMixin
):
37 GalconClient that adds the sequence number to each command before
41 def __init__(self
, *a
, **kw
):
42 super(GalconClientPrependSeqNrMixin
, 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
= GalconClientPrependSeqNrMixin
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 GalconClientAckExpectingMixin(GalconClientBackCallerMixin
,
60 GalconClientPrependSeqNrMixin
):
62 GalconClient 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(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
]
84 deferred
= reactor
.callLater(1, self
.__resend
, seq_nr
)
85 self
.__awaiting
_ack
[seq_nr
] = (command
, deferred
)
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:
96 class GalconClientAckSendingMixin(GalconClientBackCallerMixin
,
97 GalconClientPrependSeqNrMixin
):
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
):
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 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
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()
146 (1, "version", "0.18.0"),
149 def __cb_version(self
, command
):
150 self
.send_commands((1, "login"))
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(
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]")