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>
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:
38 suffix
= '{' + ','.join(suffixes
) + '}'
39 return os
.path
.join(prefix
, suffix
)
42 def _sort_nodes(nodes
):
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
54 self
.basename
= basename
56 self
.size
= compute_size
.slow(basename
, options
.cross_device
)
60 def compute_height(self
):
62 children_height
= max([c
.compute_height() for c
in self
.children
])
63 return children_height
+ 1
66 def compute_depth(self
):
74 def minimum_node_size(self
):
76 for child
in self
.children
:
77 child_min_node_size
= child
.minimum_node_size()
78 res
= min(res
, child_min_node_size
)
81 def get_dirname(self
):
82 return os
.path
.dirname(self
.get_fullpaths()[0])
84 def get_useful_size(self
):
91 """Does the node correspond to a path that actually exists?"""
94 def get_fullname(self
):
95 fullpaths
= self
.get_fullpaths()
96 prefix
, suffixes
= _extract_prefix_suffixes(fullpaths
)
97 fullname
= _join_prefix_suffixes(prefix
, suffixes
)
100 def get_fullpaths(self
):
102 fullpath
= self
.basename
104 fullpath
= os
.path
.join(parent
.basename
, fullpath
)
105 parent
= parent
.parent
109 """Return the name that should be displayed for this node."""
113 """The iterator is a depth first traversal."""
115 for child
in self
.children
:
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
):
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
]
131 self
.size
= compute_size
.slow_sum(fullpaths
, options
.cross_device
)
136 cookie
= chdir_browsing
.init(prefix
)
138 for child
in children
:
139 node
= create_node(self
, child
, max_depth
- 1, min_size
,
142 self
.children
.append(node
)
143 children_size
+= node
.size
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
)
156 chdir_browsing
.finalize(cookie
)
157 self
.size
= children_size
+ remaining_size
160 """This node does not actually exists, it is an aggregate."""
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
)
173 return self
.forest_name
175 def get_dirname(self
):
178 def get_fullpaths(self
):
179 fullpaths
= self
.forest_paths
182 fullpaths
= [os
.path
.join(parent
.basename
, fp
) for fp
in fullpaths
]
183 parent
= parent
.parent
186 class _pysize_node_dir(_pysize_node_collection
):
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
,
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
)
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
218 median
= elements
[len(elements
) / 2]
219 self
.useful_size
= compute_size
.slow(median
)
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
235 fullpaths
= [os
.path
.join(parent
.basename
, fp
) for fp
in fullpaths
]
236 parent
= parent
.parent
239 def get_useful_size(self
):
240 return self
.useful_size
242 class _pysize_node_file(_pysize_node
):
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."""
256 return _pysize_node_remaining(parent
, [], options
)
258 if isinstance(what
, list):
259 size
= compute_size
.slow_sum(what
, options
.cross_device
)
263 size
= compute_size
.slow(what
, options
.cross_device
)
266 if isinstance(what
, str):
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
)
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
)
278 node
= _pysize_node_file(parent
, what
, options
)