Detect leaked gsh processes
[gsh.git] / gsh / dispatchers.py
blobd3700291f4be8c64e2e37b78d6eda98cdc82cbff
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) 2006, 2007, 2008 Guillaume Chazarain <guichaz@gmail.com>
19 import asyncore
20 import fcntl
21 import struct
22 import termios
24 from gsh import remote_dispatcher
25 from gsh.terminal_size import terminal_size
27 def all_instances():
28 """Iterator over all the remote_dispatcher instances"""
29 for i in asyncore.socket_map.itervalues():
30 if isinstance(i, remote_dispatcher.remote_dispatcher):
31 yield i
33 def make_unique_name(name):
34 """Each shell must have a unique display name, so identical hostnames are
35 suffixed by #NUMBER"""
36 display_names = set([i.display_name for i in all_instances()])
37 candidate_name = name
38 i = 0
39 while candidate_name in display_names:
40 i += 1
41 candidate_name = '%s#%d' % (name, i)
42 return candidate_name
44 def count_awaited_processes():
45 """Return a tuple with the number of awaited processes and the total
46 number"""
47 awaited = 0
48 total = 0
49 for i in all_instances():
50 if i.enabled:
51 total += 1
52 if i.state is not remote_dispatcher.STATE_IDLE:
53 awaited += 1
54 return awaited, total
56 def all_terminated():
57 """For each remote shell determine if its terminated"""
58 instances_found = False
59 for i in all_instances():
60 instances_found = True
61 if i.state not in (remote_dispatcher.STATE_TERMINATED,
62 remote_dispatcher.STATE_DEAD):
63 return False
64 return instances_found
66 max_display_name_length = 0
67 def update_max_display_name_length(change):
68 """The max_display_name_length serves to compute the length of the
69 whitespace used to align the output of the remote shells. A positive change
70 argument indicates that a remote shells with such a name length was enabled
71 while a negative change argument indicates a disabled remote shell"""
72 global max_display_name_length
74 if change < 0:
75 if -change < max_display_name_length:
76 # The disabled shell didn't have the longest name
77 return
78 new_max = 0
79 for i in all_instances():
80 if i.enabled:
81 l = len(i.display_name)
82 if l >= -change:
83 # The disabled shell was not alone with the longest name
84 return
85 new_max = max(l, new_max)
86 else:
87 new_max = max(change, max_display_name_length)
89 if new_max != max_display_name_length:
90 max_display_name_length = new_max
91 update_terminal_size()
93 def update_terminal_size():
94 """Propagate the terminal size to the remote shells accounting for the
95 place taken by the longest name"""
96 w, h = terminal_size()
97 w = max(w - max_display_name_length - 2, min(w, 10))
98 # python bug http://python.org/sf/1112949 on amd64
99 # from ajaxterm.py
100 bug = struct.unpack('i', struct.pack('I', termios.TIOCSWINSZ))[0]
101 packed_size = struct.pack('HHHH', h, w, 0, 0)
102 term_size = w, h
103 for i in all_instances():
104 if i.enabled and i.term_size != term_size:
105 i.term_size = term_size
106 fcntl.ioctl(i.fd, bug, packed_size)
108 def format_info(info_list):
109 """Turn a 2-dimension list of strings into a 1-dimension list of strings
110 with correct spacing"""
111 info_list.sort(key=lambda i:i[0])
112 max_lengths = []
113 if info_list:
114 nr_columns = len(info_list[0])
115 else:
116 nr_columns = 0
117 for i in xrange(nr_columns):
118 max_lengths.append(max([len(str(info[i])) for info in info_list]))
119 for info_id in xrange(len(info_list)):
120 info = info_list[info_id]
121 for str_id in xrange(len(info)):
122 orig_str = str(info[str_id])
123 indent = max_lengths[str_id] - len(orig_str)
124 info[str_id] = orig_str + indent * ' '
125 info_list[info_id] = ' '.join(info) + '\n'