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.
32 from twisted
.internet
import defer
, reactor
33 from galtack
.net_client_base
import *
36 class GaltackClientAckExpectingMixin(GaltackClientPrependSeqNrMixin
,
37 GaltackClientBackCallerMixin
):
39 GaltackClient that expects an [ACK] from the server for each to-be-sent
40 command asking for one.
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
]
61 deferred
= reactor
.callLater(1, self
.__resend
, seq_nr
)
62 self
.__awaiting
_ack
[seq_nr
] = (command
, deferred
)
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:
73 class GaltackClientPingSenderMixin(GaltackClientAckSenderMixin
):
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.
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)
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
()
98 def __send_ping(self
):
99 if self
._GaltackClientSendCmdBaseMixin
__can
_send
_commands
:
100 self
.send_commands((1, "[PING]"))
101 self
.__create
_ping
_delayed
()
104 class GaltackClientHousekeeperMixin(
105 GaltackClientAckExpectingMixin
,
106 GaltackClientPingSenderMixin
,
107 GaltackClientAckSenderMixin
,
108 GaltackClientSendCmdBaseMixin
,
111 GaltackClient that interacts properly with a Galcon server.
113 It takes the responsibility for setting up and shutting down the
116 It (implicitly) defines an imaginary server-side command name
117 "[CLOSE]-[ACK]" to which callbacks with the following signature may be
119 def callback() # *no* argument
120 It gets called whenever a logout could be completed successfully.
122 Alternatively, you may connect to the deferred returned by logout(),
123 which receives None as an argument.
126 class LogoutAborted(Exception): pass
128 def __init__(self
, *a
, **kw
):
129 super(GaltackClientHousekeeperMixin
, self
).__init
__(*a
, **kw
)
130 self
.register_command_callback("version", self
.__cmd
_version
)
131 self
.__logout
_deferred
= None
133 def startProtocol(self
):
134 super(GaltackClientHousekeeperMixin
, self
).startProtocol()
138 (1, "version", "0.18.0"),
141 def __cmd_version(self
, command
):
142 self
.send_commands((1, "login"))
149 self
.register_command_callback("[CLOSE]", self
.__cmd
_close
)
150 self
.__logout
_deferred
= defer
.Deferred()
151 self
.__logout
_abort
= reactor
.callLater(10, self
.abort_logout
)
152 return self
.__logout
_deferred
154 def abort_logout(self
):
155 self
.__logout
_deferred
.errback(self
.LogoutAborted())
157 def __cmd_close(self
, command
):
158 self
.__logout
_abort
.cancel()
159 self
.unregister_command_callback("[CLOSE]", self
.__cmd
_close
)
160 commands
= self
.send_commands(
163 self
.__awaiting
_ack
_for
_seq
_nr
= commands
[0][0]
164 self
.register_command_callback("[ACK]", self
.__cmd
_close
_ack
)
166 def __cmd_close_ack(self
, command
):
167 if int(command
[3]) == self
.__awaiting
_ack
_for
_seq
_nr
:
168 self
.unregister_command_callback("[ACK]", self
.__cmd
_close
_ack
)
169 del self
.__awaiting
_ack
_for
_seq
_nr
170 self
.__logout
_deferred
.callback(None)
171 self
.call_command_callbacks("[CLOSE]-[ACK]")