1 # This program is free software; you can redistribute it and/or modify
2 # it under the terms of the GNU General Public License as published by
3 # the Free Software Foundation; either version 2 of the License, or
4 # (at your option) any later version.
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # GNU Library General Public License for more details.
11 # You should have received a copy of the GNU General Public License
12 # along with this program; if not, write to the Free Software
13 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15 # See the COPYING file for license information.
17 # Copyright (c) 2007 Guillaume Chazarain <guichaz@gmail.com>
28 from polysh
import callbacks
29 from polysh
import pity
30 from polysh
.console
import console_output
31 from polysh
import remote_dispatcher
32 from polysh
import dispatchers
34 def pity_dot_py_source():
36 if not os
.path
.exists(path
):
38 zip_importer
= zipimport
.zipimporter(os
.path
.dirname(path
))
41 return zip_importer
.get_source('pity')
42 if not path
.endswith('.py'):
43 # Read from the .py source file
44 dot_py_start
= path
.find('.py')
46 path
= path
[:dot_py_start
+3]
48 return file(path
).read()
52 for line
in pity_dot_py_source().splitlines():
53 hash_pos
= line
.find('#')
55 line
= line
[:hash_pos
]
58 python_lines
.append(line
)
59 python_source
= '\n'.join(python_lines
)
60 encoded
= base64
.encodestring(python_source
).rstrip('\n').replace('\n', ',')
65 path
= path
.rstrip('/') or '/'
68 dirname
= pipes
.quote(os
.path
.dirname(path
) or '.')
69 basename
= pipes
.quote(os
.path
.basename(path
) or '/')
70 return 'tar c -C %s %s' % (dirname
, basename
)
72 BASE64_PITY_PY
= base64version()
74 CMD_PREFIX
= 'python -c "`echo "%s"|tr , \\\\\\n|openssl base64 -d`" ' % \
77 CMD_UPLOAD_EMIT
= ('STTY_MODE="$(stty --save)";' +
78 'stty raw &> /dev/null;' +
80 CMD_PREFIX
+ ' %s upload %s;' +
81 'stty "$STTY_MODE"\n')
82 CMD_REPLICATE_EMIT
= '%s | ' + CMD_PREFIX
+ ' %s replicate %s\n'
83 CMD_FORWARD
= CMD_PREFIX
+ ' %s forward %s %s %s\n'
85 def tree_max_children(depth
):
88 class file_transfer_tree_node(object):
99 self
.remote_dispatcher
= dispatcher
103 self
.is_upload
= is_upload
104 num_children
= min(len(children_dispatchers
), tree_max_children(depth
))
106 child_length
= int(math
.ceil(float(len(children_dispatchers
)) /
109 for i
in xrange(num_children
):
110 begin
= i
* child_length
111 if begin
>= len(children_dispatchers
):
113 child_dispatcher
= children_dispatchers
[begin
]
114 end
= begin
+ child_length
116 child
= file_transfer_tree_node(self
,
118 children_dispatchers
[begin
:end
],
121 self
.children
.append(child
)
122 self
.should_print_bw
= should_print_bw(self
)
123 self
.try_start_pity()
125 def host_port_cb(self
, host_port
):
126 self
.host_port
= host_port
127 self
.parent
.try_start_pity()
129 def try_start_pity(self
):
130 host_ports
= [child
.host_port
for child
in self
.children
]
131 if len(filter(bool, host_ports
)) != len(host_ports
):
133 host_ports
= ' '.join(map(pipes
.quote
, host_ports
))
134 if self
.should_print_bw
:
139 cb
= lambda host_port
: self
.host_port_cb(host_port
)
140 t1
, t2
= callbacks
.add('file_transfer', cb
, False)
141 cmd
= CMD_FORWARD
% (opt
, t1
, t2
, host_ports
)
143 def start_upload(unused
):
144 local_uploader(self
.path
, self
.remote_dispatcher
)
145 t1
, t2
= callbacks
.add('upload_start', start_upload
, False)
146 cmd
= CMD_UPLOAD_EMIT
% (t1
, t2
, opt
, host_ports
)
148 cmd
= CMD_REPLICATE_EMIT
% (tarCreate(self
.path
), opt
, host_ports
)
149 self
.remote_dispatcher
.dispatch_command(cmd
)
153 for child
in self
.children
:
154 child_str
= str(child
)
155 for line
in child_str
.splitlines():
156 children_str
+= '+--%s\n' % line
157 return '%s\n%s' % (self
.remote_dispatcher
.display_name
, children_str
)
160 def replicate(shell
, path
):
161 peers
= [i
for i
in dispatchers
.all_instances() if i
.enabled
]
163 console_output('No other remote shell to replicate files to\n')
166 def should_print_bw(node
, already_chosen
=[False]):
167 if not node
.children
and not already_chosen
[0] and not node
.is_upload
:
168 already_chosen
[0] = True
172 sender_index
= peers
.index(shell
)
173 destinations
= peers
[:sender_index
] + peers
[sender_index
+1:]
174 tree
= file_transfer_tree_node(None,
182 class local_uploader(remote_dispatcher
.remote_dispatcher
):
183 def __init__(self
, path_to_upload
, first_destination
):
184 self
.path_to_upload
= path_to_upload
185 self
.trigger1
, self
.trigger2
= callbacks
.add('upload_done',
188 self
.first_destination
= first_destination
189 self
.first_destination
.drain_and_block_writing()
190 remote_dispatcher
.remote_dispatcher
.__init
__(self
, '.')
191 self
.temporary
= True
193 def launch_ssh(self
, name
):
194 cmd
= '%s | (openssl base64; echo %s) >&%d' % (
195 tarCreate(self
.path_to_upload
),
196 pity
.BASE64_TERMINATOR
,
197 self
.first_destination
.fd
)
198 subprocess
.call(cmd
, shell
=True)
200 os
.write(1, self
.trigger1
+ self
.trigger2
+ '\n')
201 os
._exit
(0) # The atexit handler would kill all remote shells
203 def upload_done(self
, unused
):
204 self
.first_destination
.allow_writing()
207 def upload(local_path
):
208 peers
= [i
for i
in dispatchers
.all_instances() if i
.enabled
]
210 console_output('No other remote shell to replicate files to\n')
214 # We wouldn't be able to show the progress indicator with only one
215 # destination. We need one remote connection in blocking mode to send
216 # the base64 data to. We also need one remote connection in non blocking
217 # mode for polysh to display the progress indicator via the main select
219 console_output('Uploading to only one remote shell is not supported, '
223 def should_print_bw(node
, already_chosen
=[False]):
224 if not node
.children
and not already_chosen
[0]:
225 already_chosen
[0] = True
229 tree
= file_transfer_tree_node(None,