Fix get_fullname() with collections.
[pysize.git] / pysize / core / pysize_fs_node.py
blobff26a6bce588dd427b60f5e9eb521507b14b6579
1 import os
2 import stat
4 from pysize.core import chdir_browsing
5 from pysize.core import compute_size
7 class _pysize_node:
8 """The parent class of all displayed nodes, as these nodes are displayed on
9 screen, there should be few instances."""
10 def __init__(self, parent, basename):
11 self.rectangle = (0, 0, 0, 0)
12 self.parent = parent
13 self.children = []
14 self.basename = basename
15 if basename:
16 self.size = compute_size.slow(basename)
17 else:
18 self.size = 0
20 def compute_height(self):
21 if self.children:
22 children_height = max([c.compute_height() for c in self.children])
23 return children_height + 1
24 return 0
26 def compute_depth(self):
27 depth = 0
28 node = self
29 while node:
30 node = node.parent
31 depth += 1
32 return depth
34 def minimum_real_size(self):
35 res = self.size
36 for child in self.children:
37 child_min_real_size = child.minimum_real_size()
38 if child_min_real_size >= 0 and child_min_real_size < res:
39 res = child_min_real_size
40 return res
42 def get_dirname(self):
43 return os.path.dirname(self.get_fullpaths()[0])
45 def is_dir(self):
46 return False
48 def is_real(self):
49 """Does the node correspond to a path that actually exists?"""
50 return True
52 def get_fullname(self):
53 return self.get_dirname() + '/' + self.get_name()
55 def get_fullpaths(self):
56 parent = self.parent
57 fullpath = self.basename
58 while parent:
59 fullpath = parent.basename + '/' + fullpath
60 parent = parent.parent
61 return [fullpath]
63 def get_name(self):
64 """Return the name that should be displayed for this node."""
65 return self.basename
67 def __iter__(self):
68 """The iterator is a depth first traversal."""
69 yield self
70 for child in self.children:
71 for node in child:
72 yield node
74 def contains_point(self, p):
75 """Is the point p in the graphical representation of this node?"""
76 (x0, x1, y0, y1) = self.rectangle
77 return x0 < p.x and p.x < x1 and y0 < p.y and p.y < y1
79 class _pysize_node_collection(_pysize_node):
80 """A directory"""
81 def __init__(self, parent, prefix, children, max_depth, min_size):
82 _pysize_node.__init__(self, parent, None)
83 self.basename = prefix
84 self.size = sum([compute_size.slow(prefix + '/' + p) for p in children])
85 if max_depth != 0:
86 children_size = 0
87 remaining_size = 0
88 remaining_items = []
89 cookie = chdir_browsing.init(prefix)
90 try:
91 for child in children:
92 node = create_node(self, child, max_depth - 1, min_size)
93 if node.is_real():
94 self.children.append(node)
95 children_size += node.size
96 else:
97 remaining_items.append(child)
98 remaining_size += node.size
100 self.children.sort(key=lambda t: t.size, reverse=True)
101 if remaining_size > min_size:
102 remaining_items.sort(key=compute_size.slow, reverse=True)
103 rem = _pysize_node_remaining(self, remaining_items)
104 self.children.append(rem)
105 finally:
106 chdir_browsing.finalize(cookie)
107 self.size = max(children_size, self.size)
109 def is_real(self):
110 """This node does not actually exists, it is an aggregate."""
111 return False
113 class _pysize_node_forest(_pysize_node_collection):
114 def __init__(self, parent, children, max_depth, min_size):
115 (prefix, suffixes) = self._extract_prefix_suffixes(children)
116 _pysize_node_collection.__init__(self, parent, prefix, suffixes,
117 max_depth, min_size)
118 self.basename = prefix
119 self.forest_name = prefix + '/{' + ','.join(suffixes) + '}'
121 def get_name(self):
122 return self.forest_name
124 def get_dirname(self):
125 return self.basename
127 @staticmethod
128 def _extract_prefix_suffixes(paths):
129 """['/prefix/suffix1', '/prefix/suffix2', '/prefix/suffix3'] =>
130 ('/prefix', ['suffix1', 'suffix2', 'suffix3'])"""
131 prefix_len = os.path.commonprefix(paths).rfind('/') + 1
132 prefix = paths[0][:prefix_len - 1]
133 suffixes = [p[prefix_len:] for p in paths]
134 return (prefix, suffixes)
136 class _pysize_node_dir(_pysize_node_collection):
137 """A directory"""
138 def __init__(self, parent, basename, max_depth, min_size):
139 zuper = _pysize_node_collection.__init__
140 zuper(self, parent, basename, os.listdir(basename), max_depth, min_size)
141 self.basename = basename
142 self.size = max(self.size, compute_size.slow(basename))
144 def is_dir(self):
145 return True
147 def is_real(self):
148 return True
150 def get_name(self):
151 return self.basename + '/'
153 class _pysize_node_remaining(_pysize_node_collection):
154 """The sum of a directory's children that are too small to be drawn."""
155 def __init__(self, parent, elements):
156 _pysize_node.__init__(self, parent, None)
157 # The parent constructor would visit the files
158 self.size = sum(map(compute_size.slow, elements))
159 self.remaining_elements = elements
161 def minimum_real_size(self):
162 return -1
164 def get_name(self):
165 return '{' + ','.join(self.remaining_elements) + '}'
167 def get_fullname(self):
168 if self.remaining_elements:
169 return _pysize_node_collection.get_fullname(self)
170 return '' # This is the initial node
172 def get_fullpaths(self):
173 fullpaths = self.remaining_elements
174 parent = self.parent
175 while parent:
176 fullpaths = [parent.basename + '/' + fp for fp in fullpaths]
177 parent = parent.parent
178 return fullpaths
180 class _pysize_node_file(_pysize_node):
181 """A file"""
182 def __init__(self, parent, basename):
183 _pysize_node.__init__(self, parent, basename)
185 class _pysize_node_hardlink(_pysize_node_file):
186 """A hardlink, the canonical one, or a link"""
187 def __init__(self, parent, basename):
188 _pysize_node_file.__init__(self, parent, basename)
190 def create_node(parent, what, max_depth, min_size):
191 """Return a pysize_node for parent/basename traversing up to max_depth
192 levels and only taking into account elements bigger than min_size."""
193 if not what:
194 return _pysize_node_remaining(parent, [])
196 if isinstance(what, list):
197 size = sum(map(compute_size.slow, what))
198 if len(what) == 1:
199 what = what[0]
200 else:
201 size = compute_size.slow(what)
203 if size < min_size:
204 if isinstance(what, str):
205 what = [what]
206 node = _pysize_node_remaining(parent, what)
207 elif isinstance(what, list):
208 node = _pysize_node_forest(parent, what, max_depth, min_size)
209 else:
210 st = os.lstat(what)
211 if stat.S_ISDIR(st.st_mode):
212 node = _pysize_node_dir(parent, what, max_depth, min_size)
213 elif st.st_nlink > 1:
214 node = _pysize_node_hardlink(parent, what)
215 else:
216 node = _pysize_node_file(parent, what)
217 return node