Remove tabs and trailing space
[pysize.git] / pysize / core / pysize_fs_node.py
blob3931713377930dcb0319fe9292bd92a55370bcb5
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 Guillaume Chazarain <guichaz@yahoo.fr>
19 import os
20 import stat
22 from pysize.core import chdir_browsing
23 from pysize.core import compute_size
24 from pysize.core.pysize_global_fs_cache import get_dev_ino, cache_add_dir
25 from pysize.core.deletion import get_subtracted
27 def _extract_prefix_suffixes(paths):
28 """['/prefix/suffix1', '/prefix/suffix2', '/prefix/suffix3'] =>
29 ('/prefix', ['suffix1', 'suffix2', 'suffix3'])"""
30 prefix_len = os.path.commonprefix(paths).rfind('/') + 1
31 prefix = paths[0][:prefix_len - 1]
32 suffixes = [p[prefix_len:] for p in paths]
33 return prefix, suffixes
35 def _join_prefix_suffixes(prefix, suffixes):
36 if len(suffixes) == 1:
37 suffix = suffixes[0]
38 else:
39 suffix = '{' + ','.join(suffixes) + '}'
40 return os.path.join(prefix, suffix)
43 def _sort_nodes(nodes):
44 def cmp_fn(n1, n2):
45 return cmp(n2.size, n1.size) or cmp(n1.get_name(), n2.get_name())
46 nodes.sort(cmp=cmp_fn)
48 class _pysize_node(object):
49 """The parent class of all displayed nodes, as these nodes are displayed on
50 screen, there should be few instances."""
51 def __init__(self, parent, basename, options):
52 self.rectangle = 0, 0, 0, 0
53 self.parent = parent
54 self.children = []
55 self.basename = basename
56 if basename:
57 self.size = compute_size.slow(basename, options.cross_device)
58 else:
59 self.size = 0
61 def compute_height(self):
62 if self.children:
63 children_height = max([c.compute_height() for c in self.children])
64 return children_height + 1
65 return 0
67 def compute_depth(self):
68 depth = 0
69 node = self
70 while node:
71 node = node.parent
72 depth += 1
73 return depth
75 def minimum_node_size(self):
76 res = self.size
77 for child in self.children:
78 child_min_node_size = child.minimum_node_size()
79 res = min(res, child_min_node_size)
80 return res
82 def get_dirname(self):
83 return os.path.dirname(self.get_fullpaths()[0])
85 def is_dir(self):
86 return False
88 def is_real(self):
89 """Does the node correspond to a path that actually exists?"""
90 return True
92 def get_fullname(self):
93 fullpaths = self.get_fullpaths()
94 prefix, suffixes = _extract_prefix_suffixes(fullpaths)
95 fullname = _join_prefix_suffixes(prefix, suffixes)
96 return fullname
98 def get_fullpaths(self):
99 parent = self.parent
100 fullpath = self.basename
101 while parent:
102 fullpath = os.path.join(parent.basename, fullpath)
103 parent = parent.parent
104 return [fullpath]
106 def get_name(self):
107 """Return the name that should be displayed for this node."""
108 return self.basename
110 def __iter__(self):
111 """The iterator is a depth first traversal."""
112 yield self
113 for child in self.children:
114 for node in child:
115 yield node
117 def contains_point(self, p):
118 """Is the point p in the graphical representation of this node?"""
119 x0, x1, y0, y1 = self.rectangle
120 return x0 < p.x and p.x < x1 and y0 < p.y and p.y < y1
122 class _pysize_node_collection(_pysize_node):
123 """A directory"""
124 def __init__(self, parent, prefix, children, max_depth, min_size, options):
125 super(_pysize_node_collection, self).__init__(parent, None, options)
126 self.basename = prefix
127 fullpaths = [os.path.join(prefix, p) for p in children]
128 if max_depth == 0:
129 self.size = compute_size.slow_sum(fullpaths, options.cross_device)
130 else:
131 children_size = 0
132 remaining_size = 0
133 remaining_nodes = []
134 cookie = chdir_browsing.init(prefix)
135 try:
136 for child in children:
137 node = create_node(self, child, max_depth - 1, min_size,
138 options)
139 if node.is_real():
140 self.children.append(node)
141 children_size += node.size
142 else:
143 node.__name = child
144 remaining_nodes.append(node)
145 remaining_size += node.size
147 _sort_nodes(self.children)
148 if remaining_size > min_size:
149 _sort_nodes(remaining_nodes)
150 names = [n.__name for n in remaining_nodes]
151 rem = _pysize_node_remaining(self, names, options)
152 self.children.append(rem)
153 finally:
154 chdir_browsing.finalize(cookie)
155 self.size = children_size + remaining_size
157 def is_real(self):
158 """This node does not actually exists, it is an aggregate."""
159 return False
161 class _pysize_node_forest(_pysize_node_collection):
162 def __init__(self, parent, children, max_depth, min_size, options):
163 prefix, suffixes = _extract_prefix_suffixes(children)
164 super(_pysize_node_forest, self).__init__(parent, prefix, suffixes,
165 max_depth, min_size, options)
166 self.basename = prefix
167 self.forest_paths = suffixes
168 self.forest_name = _join_prefix_suffixes(prefix, suffixes)
170 def get_name(self):
171 return self.forest_name
173 def get_dirname(self):
174 return self.basename
176 def get_fullpaths(self):
177 fullpaths = self.forest_paths
178 parent = self
179 while parent:
180 fullpaths = [os.path.join(parent.basename, fp) for fp in fullpaths]
181 parent = parent.parent
182 return fullpaths
184 class _pysize_node_dir(_pysize_node_collection):
185 """A directory"""
186 def __init__(self, parent, basename, max_depth, min_size, options):
187 listing = chdir_browsing.listdir(basename, options.cross_device)
188 super(_pysize_node_dir, self).__init__(parent, basename,
189 listing, max_depth,
190 min_size, options)
191 self.basename = basename
192 self.size += os.lstat(basename).st_blocks * 512
193 # update size in cache, in case files were added/removed
194 dev_ino = get_dev_ino(basename)
195 cache_add_dir(dev_ino, self.size + get_subtracted(basename))
197 def is_dir(self):
198 return True
200 def is_real(self):
201 return True
203 def get_name(self):
204 name = self.basename
205 if name != '/':
206 name += '/'
207 return name
209 class _pysize_node_remaining(_pysize_node_collection):
210 """The sum of a directory's children that are too small to be drawn."""
211 def __init__(self, parent, elements, options):
212 _pysize_node.__init__(self, parent, None, options)
213 # The parent constructor would visit the files
214 self.size = compute_size.slow_sum(elements, options.cross_device)
215 self.remaining_elements = elements
217 def get_name(self):
218 return '{' + ','.join(self.remaining_elements) + '}'
220 def get_fullname(self):
221 if self.remaining_elements:
222 return _pysize_node_collection.get_fullname(self)
223 return '' # This is the initial node
225 def get_fullpaths(self):
226 fullpaths = self.remaining_elements
227 parent = self.parent
228 while parent:
229 fullpaths = [os.path.join(parent.basename, fp) for fp in fullpaths]
230 parent = parent.parent
231 return fullpaths
233 class _pysize_node_file(_pysize_node):
234 """A file"""
235 def __init__(self, parent, basename, options):
236 super(_pysize_node_file, self).__init__(parent, basename, options)
238 class _pysize_node_hardlink(_pysize_node_file):
239 """A hardlink, the canonical one, or a link"""
240 def __init__(self, parent, basename, options):
241 super(_pysize_node_hardlink, self).__init__(parent, basename, options)
243 def create_node(parent, what, max_depth, min_size, options):
244 """Return a pysize_node for parent/basename traversing up to max_depth
245 levels and only taking into account elements bigger than min_size."""
246 if not what:
247 return _pysize_node_remaining(parent, [], options)
249 if isinstance(what, list):
250 size = compute_size.slow_sum(what, options.cross_device)
251 if len(what) == 1:
252 what = what[0]
253 else:
254 size = compute_size.slow(what, options.cross_device)
256 if size < min_size:
257 if isinstance(what, str):
258 what = [what]
259 node = _pysize_node_remaining(parent, what, options)
260 elif isinstance(what, list):
261 node = _pysize_node_forest(parent, what, max_depth, min_size, options)
262 else:
263 st = os.lstat(what)
264 if stat.S_ISDIR(st.st_mode):
265 node = _pysize_node_dir(parent, what, max_depth, min_size, options)
266 elif st.st_nlink > 1:
267 node = _pysize_node_hardlink(parent, what, options)
268 else:
269 node = _pysize_node_file(parent, what, options)
270 return node