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