Add NEWS file
[pysize.git] / pysize / core / pysize_fs_node.py
blob534ae9ea03a6c9311814445dd44e4fd7541c8741
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):
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)
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):
120 super(_pysize_node_collection, self).__init__(parent, None)
121 self.basename = prefix
122 self.size = sum([compute_size.slow(prefix + '/' + p) for p in children])
123 if max_depth != 0:
124 children_size = 0
125 remaining_size = 0
126 remaining_nodes = []
127 cookie = chdir_browsing.init(prefix)
128 try:
129 for child in children:
130 node = create_node(self, child, max_depth - 1, min_size)
131 if node.is_real():
132 self.children.append(node)
133 children_size += node.size
134 else:
135 node.__name = child
136 remaining_nodes.append(node)
137 remaining_size += node.size
139 _sort_nodes(self.children)
140 if remaining_size > min_size:
141 _sort_nodes(remaining_nodes)
142 names = [n.__name for n in remaining_nodes]
143 rem = _pysize_node_remaining(self, names)
144 self.children.append(rem)
145 finally:
146 chdir_browsing.finalize(cookie)
147 self.size = max(children_size + remaining_size, self.size)
149 def is_real(self):
150 """This node does not actually exists, it is an aggregate."""
151 return False
153 class _pysize_node_forest(_pysize_node_collection):
154 def __init__(self, parent, children, max_depth, min_size):
155 prefix, suffixes = _extract_prefix_suffixes(children)
156 super(_pysize_node_forest, self).__init__(parent, prefix, suffixes,
157 max_depth, min_size)
158 self.basename = prefix
159 self.forest_paths = suffixes
160 self.forest_name = _join_prefix_suffixes(prefix, suffixes)
162 def get_name(self):
163 return self.forest_name
165 def get_dirname(self):
166 return self.basename
168 def get_fullpaths(self):
169 fullpaths = self.forest_paths
170 parent = self
171 while parent:
172 fullpaths = [parent.basename + '/' + fp for fp in fullpaths]
173 parent = parent.parent
174 return fullpaths
176 class _pysize_node_dir(_pysize_node_collection):
177 """A directory"""
178 def __init__(self, parent, basename, max_depth, min_size):
179 super(_pysize_node_dir, self).__init__(parent, basename,
180 os.listdir(basename), max_depth,
181 min_size)
182 self.basename = basename
183 self.size = max(self.size, compute_size.slow(basename))
185 def is_dir(self):
186 return True
188 def is_real(self):
189 return True
191 def get_name(self):
192 return self.basename + '/'
194 class _pysize_node_remaining(_pysize_node_collection):
195 """The sum of a directory's children that are too small to be drawn."""
196 def __init__(self, parent, elements):
197 _pysize_node.__init__(self, parent, None)
198 # The parent constructor would visit the files
199 self.size = sum(map(compute_size.slow, elements))
200 self.remaining_elements = elements
202 def get_name(self):
203 return '{' + ','.join(self.remaining_elements) + '}'
205 def get_fullname(self):
206 if self.remaining_elements:
207 return _pysize_node_collection.get_fullname(self)
208 return '' # This is the initial node
210 def get_fullpaths(self):
211 fullpaths = self.remaining_elements
212 parent = self.parent
213 while parent:
214 fullpaths = [parent.basename + '/' + fp for fp in fullpaths]
215 parent = parent.parent
216 return fullpaths
218 class _pysize_node_file(_pysize_node):
219 """A file"""
220 def __init__(self, parent, basename):
221 super(_pysize_node_file, self).__init__(parent, basename)
223 class _pysize_node_hardlink(_pysize_node_file):
224 """A hardlink, the canonical one, or a link"""
225 def __init__(self, parent, basename):
226 super(_pysize_node_hardlink, self).__init__(parent, basename)
228 def create_node(parent, what, max_depth, min_size):
229 """Return a pysize_node for parent/basename traversing up to max_depth
230 levels and only taking into account elements bigger than min_size."""
231 if not what:
232 return _pysize_node_remaining(parent, [])
234 if isinstance(what, list):
235 size = sum(map(compute_size.slow, what))
236 if len(what) == 1:
237 what = what[0]
238 else:
239 size = compute_size.slow(what)
241 if size < min_size:
242 if isinstance(what, str):
243 what = [what]
244 node = _pysize_node_remaining(parent, what)
245 elif isinstance(what, list):
246 node = _pysize_node_forest(parent, what, max_depth, min_size)
247 else:
248 st = os.lstat(what)
249 if stat.S_ISDIR(st.st_mode):
250 node = _pysize_node_dir(parent, what, max_depth, min_size)
251 elif st.st_nlink > 1:
252 node = _pysize_node_hardlink(parent, what)
253 else:
254 node = _pysize_node_file(parent, what)
255 return node