Don't use the name 'item'. Nodes are nodes, fullpaths are fullpaths.
[pysize.git] / pysize / core / pysize_fs_node.py
blob6e5a582111f099d24401d0cbfa3236eea0150054
1 import os
2 import stat
4 from pysize.core import chdir_browsing
5 from pysize.core import compute_size
7 def _extract_prefix_suffixes(paths):
8 """['/prefix/suffix1', '/prefix/suffix2', '/prefix/suffix3'] =>
9 ('/prefix', ['suffix1', 'suffix2', 'suffix3'])"""
10 prefix_len = os.path.commonprefix(paths).rfind('/') + 1
11 prefix = paths[0][:prefix_len - 1]
12 suffixes = [p[prefix_len:] for p in paths]
13 return (prefix, suffixes)
15 def _join_prefix_suffixes(prefix, suffixes):
16 if len(suffixes) == 1:
17 return prefix + '/' + suffixes[0]
18 return prefix + '/{' + ','.join(suffixes) + '}'
20 def _sort_nodes(nodes):
21 def cmp_fn(n1, n2):
22 return cmp(n2.size, n1.size) or cmp(n1.get_name(), n2.get_name())
23 nodes.sort(cmp=cmp_fn)
25 class _pysize_node(object):
26 """The parent class of all displayed nodes, as these nodes are displayed on
27 screen, there should be few instances."""
28 def __init__(self, parent, basename):
29 self.rectangle = (0, 0, 0, 0)
30 self.parent = parent
31 self.children = []
32 self.basename = basename
33 if basename:
34 self.size = compute_size.slow(basename)
35 else:
36 self.size = 0
38 def compute_height(self):
39 if self.children:
40 children_height = max([c.compute_height() for c in self.children])
41 return children_height + 1
42 return 0
44 def compute_depth(self):
45 depth = 0
46 node = self
47 while node:
48 node = node.parent
49 depth += 1
50 return depth
52 def minimum_real_size(self):
53 res = self.size
54 for child in self.children:
55 child_min_real_size = child.minimum_real_size()
56 if child_min_real_size >= 0 and child_min_real_size < res:
57 res = child_min_real_size
58 return res
60 def get_dirname(self):
61 return os.path.dirname(self.get_fullpaths()[0])
63 def is_dir(self):
64 return False
66 def is_real(self):
67 """Does the node correspond to a path that actually exists?"""
68 return True
70 def get_fullname(self):
71 fullpaths = self.get_fullpaths()
72 (prefix, suffixes) = _extract_prefix_suffixes(fullpaths)
73 fullname = _join_prefix_suffixes(prefix, suffixes)
74 return fullname
76 def get_fullpaths(self):
77 parent = self.parent
78 fullpath = self.basename
79 while parent:
80 fullpath = parent.basename + '/' + fullpath
81 parent = parent.parent
82 return [fullpath]
84 def get_name(self):
85 """Return the name that should be displayed for this node."""
86 return self.basename
88 def __iter__(self):
89 """The iterator is a depth first traversal."""
90 yield self
91 for child in self.children:
92 for node in child:
93 yield node
95 def contains_point(self, p):
96 """Is the point p in the graphical representation of this node?"""
97 (x0, x1, y0, y1) = self.rectangle
98 return x0 < p.x and p.x < x1 and y0 < p.y and p.y < y1
100 class _pysize_node_collection(_pysize_node):
101 """A directory"""
102 def __init__(self, parent, prefix, children, max_depth, min_size):
103 super(_pysize_node_collection, self).__init__(parent, None)
104 self.basename = prefix
105 self.size = sum([compute_size.slow(prefix + '/' + p) for p in children])
106 if max_depth != 0:
107 children_size = 0
108 remaining_size = 0
109 remaining_nodes = []
110 cookie = chdir_browsing.init(prefix)
111 try:
112 for child in children:
113 node = create_node(self, child, max_depth - 1, min_size)
114 if node.is_real():
115 self.children.append(node)
116 children_size += node.size
117 else:
118 node.__name = child
119 remaining_nodes.append(node)
120 remaining_size += node.size
122 _sort_nodes(self.children)
123 if remaining_size > min_size:
124 _sort_nodes(remaining_nodes)
125 names = [n.__name for n in remaining_nodes]
126 rem = _pysize_node_remaining(self, names)
127 self.children.append(rem)
128 finally:
129 chdir_browsing.finalize(cookie)
130 self.size = max(children_size + remaining_size, self.size)
132 def is_real(self):
133 """This node does not actually exists, it is an aggregate."""
134 return False
136 class _pysize_node_forest(_pysize_node_collection):
137 def __init__(self, parent, children, max_depth, min_size):
138 (prefix, suffixes) = _extract_prefix_suffixes(children)
139 super(_pysize_node_forest, self).__init__(parent, prefix, suffixes,
140 max_depth, min_size)
141 self.basename = prefix
142 self.forest_paths = suffixes
143 self.forest_name = _join_prefix_suffixes(prefix, suffixes)
145 def get_name(self):
146 return self.forest_name
148 def get_dirname(self):
149 return self.basename
151 def get_fullpaths(self):
152 fullpaths = self.forest_paths
153 parent = self
154 while parent:
155 fullpaths = [parent.basename + '/' + fp for fp in fullpaths]
156 parent = parent.parent
157 return fullpaths
159 class _pysize_node_dir(_pysize_node_collection):
160 """A directory"""
161 def __init__(self, parent, basename, max_depth, min_size):
162 super(_pysize_node_dir, self).__init__(parent, basename,
163 os.listdir(basename), max_depth,
164 min_size)
165 self.basename = basename
166 self.size = max(self.size, compute_size.slow(basename))
168 def is_dir(self):
169 return True
171 def is_real(self):
172 return True
174 def get_name(self):
175 return self.basename + '/'
177 class _pysize_node_remaining(_pysize_node_collection):
178 """The sum of a directory's children that are too small to be drawn."""
179 def __init__(self, parent, elements):
180 _pysize_node.__init__(self, parent, None)
181 # The parent constructor would visit the files
182 self.size = sum(map(compute_size.slow, elements))
183 self.remaining_elements = elements
185 def minimum_real_size(self):
186 return -1
188 def get_name(self):
189 return '{' + ','.join(self.remaining_elements) + '}'
191 def get_fullname(self):
192 if self.remaining_elements:
193 return _pysize_node_collection.get_fullname(self)
194 return '' # This is the initial node
196 def get_fullpaths(self):
197 fullpaths = self.remaining_elements
198 parent = self.parent
199 while parent:
200 fullpaths = [parent.basename + '/' + fp for fp in fullpaths]
201 parent = parent.parent
202 return fullpaths
204 class _pysize_node_file(_pysize_node):
205 """A file"""
206 def __init__(self, parent, basename):
207 super(_pysize_node_file, self).__init__(parent, basename)
209 class _pysize_node_hardlink(_pysize_node_file):
210 """A hardlink, the canonical one, or a link"""
211 def __init__(self, parent, basename):
212 super(_pysize_node_hardlink, self).__init__(parent, basename)
214 def create_node(parent, what, max_depth, min_size):
215 """Return a pysize_node for parent/basename traversing up to max_depth
216 levels and only taking into account elements bigger than min_size."""
217 if not what:
218 return _pysize_node_remaining(parent, [])
220 if isinstance(what, list):
221 size = sum(map(compute_size.slow, what))
222 if len(what) == 1:
223 what = what[0]
224 else:
225 size = compute_size.slow(what)
227 if size < min_size:
228 if isinstance(what, str):
229 what = [what]
230 node = _pysize_node_remaining(parent, what)
231 elif isinstance(what, list):
232 node = _pysize_node_forest(parent, what, max_depth, min_size)
233 else:
234 st = os.lstat(what)
235 if stat.S_ISDIR(st.st_mode):
236 node = _pysize_node_dir(parent, what, max_depth, min_size)
237 elif st.st_nlink > 1:
238 node = _pysize_node_hardlink(parent, what)
239 else:
240 node = _pysize_node_file(parent, what)
241 return node