Beginning of a color code
[pysize.git] / pysize / core / pysize_fs_node.py
blob14f52e9374e85089f6e4d27d5e5fdbd3a1fe9137
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
26 def _extract_prefix_suffixes(paths):
27 """['/prefix/suffix1', '/prefix/suffix2', '/prefix/suffix3'] =>
28 ('/prefix', ['suffix1', 'suffix2', 'suffix3'])"""
29 prefix_len = os.path.commonprefix(paths).rfind('/') + 1
30 prefix = paths[0][:prefix_len - 1]
31 suffixes = [p[prefix_len:] for p in paths]
32 return prefix, suffixes
34 def _join_prefix_suffixes(prefix, suffixes):
35 if len(suffixes) == 1:
36 suffix = suffixes[0]
37 else:
38 suffix = '{' + ','.join(suffixes) + '}'
39 return os.path.join(prefix, suffix)
42 def _sort_nodes(nodes):
43 def cmp_fn(n1, n2):
44 return cmp(n2.size, n1.size) or cmp(n1.get_name(), n2.get_name())
45 nodes.sort(cmp=cmp_fn)
47 class _pysize_node(object):
48 """The parent class of all displayed nodes, as these nodes are displayed on
49 screen, there should be few instances."""
50 def __init__(self, parent, basename, options):
51 self.rectangle = 0, 0, 0, 0
52 self.parent = parent
53 self.children = []
54 self.basename = basename
55 if basename:
56 self.size = compute_size.slow(basename, options.cross_device)
57 else:
58 self.size = 0
60 def compute_height(self):
61 if self.children:
62 children_height = max([c.compute_height() for c in self.children])
63 return children_height + 1
64 return 0
66 def compute_depth(self):
67 depth = 0
68 node = self
69 while node:
70 node = node.parent
71 depth += 1
72 return depth
74 def minimum_node_size(self):
75 res = self.size
76 for child in self.children:
77 child_min_node_size = child.minimum_node_size()
78 res = min(res, child_min_node_size)
79 return res
81 def get_dirname(self):
82 return os.path.dirname(self.get_fullpaths()[0])
84 def get_useful_size(self):
85 return self.size
87 def is_dir(self):
88 return False
90 def is_real(self):
91 """Does the node correspond to a path that actually exists?"""
92 return True
94 def get_fullname(self):
95 fullpaths = self.get_fullpaths()
96 prefix, suffixes = _extract_prefix_suffixes(fullpaths)
97 fullname = _join_prefix_suffixes(prefix, suffixes)
98 return fullname
100 def get_fullpaths(self):
101 parent = self.parent
102 fullpath = self.basename
103 while parent:
104 fullpath = os.path.join(parent.basename, fullpath)
105 parent = parent.parent
106 return [fullpath]
108 def get_name(self):
109 """Return the name that should be displayed for this node."""
110 return self.basename
112 def __iter__(self):
113 """The iterator is a depth first traversal."""
114 yield self
115 for child in self.children:
116 for node in child:
117 yield node
119 def contains_point(self, p):
120 """Is the point p in the graphical representation of this node?"""
121 x0, x1, y0, y1 = self.rectangle
122 return x0 < p.x and p.x < x1 and y0 < p.y and p.y < y1
124 class _pysize_node_collection(_pysize_node):
125 """A directory"""
126 def __init__(self, parent, prefix, children, max_depth, min_size, options):
127 super(_pysize_node_collection, self).__init__(parent, None, options)
128 self.basename = prefix
129 fullpaths = [os.path.join(prefix, p) for p in children]
130 if max_depth == 0:
131 self.size = compute_size.slow_sum(fullpaths, options.cross_device)
132 else:
133 children_size = 0
134 remaining_size = 0
135 remaining_nodes = []
136 cookie = chdir_browsing.init(prefix)
137 try:
138 for child in children:
139 node = create_node(self, child, max_depth - 1, min_size,
140 options)
141 if node.is_real():
142 self.children.append(node)
143 children_size += node.size
144 else:
145 node.__name = child
146 remaining_nodes.append(node)
147 remaining_size += node.size
149 _sort_nodes(self.children)
150 if remaining_size > min_size:
151 _sort_nodes(remaining_nodes)
152 names = [n.__name for n in remaining_nodes]
153 rem = _pysize_node_remaining(self, names, options)
154 self.children.append(rem)
155 finally:
156 chdir_browsing.finalize(cookie)
157 self.size = children_size + remaining_size
159 def is_real(self):
160 """This node does not actually exists, it is an aggregate."""
161 return False
163 class _pysize_node_forest(_pysize_node_collection):
164 def __init__(self, parent, children, max_depth, min_size, options):
165 prefix, suffixes = _extract_prefix_suffixes(children)
166 super(_pysize_node_forest, self).__init__(parent, prefix, suffixes,
167 max_depth, min_size, options)
168 self.basename = prefix
169 self.forest_paths = suffixes
170 self.forest_name = _join_prefix_suffixes(prefix, suffixes)
172 def get_name(self):
173 return self.forest_name
175 def get_dirname(self):
176 return self.basename
178 def get_fullpaths(self):
179 fullpaths = self.forest_paths
180 parent = self
181 while parent:
182 fullpaths = [os.path.join(parent.basename, fp) for fp in fullpaths]
183 parent = parent.parent
184 return fullpaths
186 class _pysize_node_dir(_pysize_node_collection):
187 """A directory"""
188 def __init__(self, parent, basename, max_depth, min_size, options):
189 listing = chdir_browsing.listdir(basename, options.cross_device)
190 super(_pysize_node_dir, self).__init__(parent, basename,
191 listing, max_depth,
192 min_size, options)
193 self.basename = basename
194 self.size += os.lstat(basename).st_blocks * 512
195 # update size in cache, in case files were added/removed
196 cache_add_dir(get_dev_ino(basename), self.size)
198 def is_dir(self):
199 return True
201 def is_real(self):
202 return True
204 def get_name(self):
205 name = self.basename
206 if name != '/':
207 name += '/'
208 return name
210 class _pysize_node_remaining(_pysize_node_collection):
211 """The sum of a directory's children that are too small to be drawn."""
212 def __init__(self, parent, elements, options):
213 _pysize_node.__init__(self, parent, None, options)
214 # The parent constructor would visit the files
215 self.size = compute_size.slow_sum(elements, options.cross_device)
216 self.remaining_elements = elements
217 if elements:
218 median = elements[len(elements) / 2]
219 self.useful_size = compute_size.slow(median)
220 else:
221 self.useful_size = 0
223 def get_name(self):
224 return '{' + ','.join(self.remaining_elements) + '}'
226 def get_fullname(self):
227 if self.remaining_elements:
228 return _pysize_node_collection.get_fullname(self)
229 return '' # This is the initial node
231 def get_fullpaths(self):
232 fullpaths = self.remaining_elements
233 parent = self.parent
234 while parent:
235 fullpaths = [os.path.join(parent.basename, fp) for fp in fullpaths]
236 parent = parent.parent
237 return fullpaths
239 def get_useful_size(self):
240 return self.useful_size
242 class _pysize_node_file(_pysize_node):
243 """A file"""
244 def __init__(self, parent, basename, options):
245 super(_pysize_node_file, self).__init__(parent, basename, options)
247 class _pysize_node_hardlink(_pysize_node_file):
248 """A hardlink, the canonical one, or a link"""
249 def __init__(self, parent, basename, options):
250 super(_pysize_node_hardlink, self).__init__(parent, basename, options)
252 def create_node(parent, what, max_depth, min_size, options):
253 """Return a pysize_node for parent/basename traversing up to max_depth
254 levels and only taking into account elements bigger than min_size."""
255 if not what:
256 return _pysize_node_remaining(parent, [], options)
258 if isinstance(what, list):
259 size = compute_size.slow_sum(what, options.cross_device)
260 if len(what) == 1:
261 what = what[0]
262 else:
263 size = compute_size.slow(what, options.cross_device)
265 if size < min_size:
266 if isinstance(what, str):
267 what = [what]
268 node = _pysize_node_remaining(parent, what, options)
269 elif isinstance(what, list):
270 node = _pysize_node_forest(parent, what, max_depth, min_size, options)
271 else:
272 st = os.lstat(what)
273 if stat.S_ISDIR(st.st_mode):
274 node = _pysize_node_dir(parent, what, max_depth, min_size, options)
275 elif st.st_nlink > 1:
276 node = _pysize_node_hardlink(parent, what, options)
277 else:
278 node = _pysize_node_file(parent, what, options)
279 return node