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, 2007, 2008 Guillaume Chazarain <guichaz@gmail.com>
22 from pysize
.core
.browsing
import browsedir
23 from pysize
.core
import compute_size
24 from pysize
.core
.pysize_global_fs_cache
import get_dev_ino
, cache_add_dir
25 from pysize
.core
.deletion
import get_deletion_info
, DELETED
27 def _extract_prefix_suffixes(fullpaths
):
28 """['/prefix/suffix1', '/prefix/suffix2', '/prefix/suffix3'] =>
29 ('/prefix', ['suffix1', 'suffix2', 'suffix3'])"""
30 prefix_len
= os
.path
.commonprefix(fullpaths
).rfind('/') + 1
31 prefix
= fullpaths
[0][:prefix_len
- 1] or '/'
32 suffixes
= [p
[prefix_len
:] for p
in fullpaths
]
33 return prefix
, suffixes
35 def _join_prefix_suffixes(prefix
, suffixes
):
36 if len(suffixes
) == 1:
39 suffix
= '{' + ','.join(suffixes
) + '}'
40 return os
.path
.join(prefix
, suffix
)
42 def pretty_paths(fullpaths
):
43 prefix
, suffixes
= _extract_prefix_suffixes(fullpaths
)
44 return _join_prefix_suffixes(prefix
, suffixes
)
46 class _pysize_node(object):
47 """The parent class of all displayed nodes, as these nodes are displayed on
48 screen, there should be few instances."""
49 def __init__(self
, parent
, basename
, options
):
53 self
.basename
= basename
55 self
.size
= compute_size
.slow(self
.get_fullname(),
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])
88 """Does the node correspond to a path that actually exists?"""
91 def get_fullname(self
):
92 return pretty_paths(self
.get_fullpaths())
94 def get_fullpaths(self
):
96 fullpath
= self
.basename
98 fullpath
= os
.path
.join(parent
.basename
, fullpath
)
99 parent
= parent
.parent
103 """Return the name that should be displayed for this node."""
107 """The iterator is a depth first traversal."""
109 for child
in self
.children
:
113 def contains_point(self
, p
):
114 """Is the point p in the graphical representation of this node?"""
115 if not self
.rectangle
:
117 x0
, x1
, y0
, y1
= self
.rectangle
118 return x0
< p
.x
and p
.x
< x1
and y0
< p
.y
and p
.y
< y1
120 class _pysize_node_collection(_pysize_node
):
122 def __init__(self
, parent
, prefix_basename
, children_fullpaths
, max_depth
,
124 _pysize_node
.__init
__(self
, parent
, None, options
)
125 self
.basename
= prefix_basename
127 self
.size
= compute_size
.slow_sum(children_fullpaths
,
128 options
.cross_device
)
131 remaining_fullpaths
= []
133 for child_fullpath
in children_fullpaths
:
134 child_basename
= os
.path
.basename(child_fullpath
)
136 node
= create_node(self
, [child_basename
], [child_fullpath
],
137 max_depth
- 1, min_size
, options
)
139 print child_fullpath
, 'disappeared'
142 self
.children
.append(node
)
143 children_size
+= node
.size
145 remaining_fullpaths
.append(child_fullpath
)
146 remaining_size
+= node
.size
148 self
.children
.sort(cmp=lambda n1
, n2
: cmp(n2
.size
, n1
.size
) or
149 cmp(n1
.get_name(), n2
.get_name()))
150 if remaining_size
> min_size
:
151 rem
= _pysize_node_remaining(self
, remaining_fullpaths
, options
)
152 self
.children
.append(rem
)
153 self
.size
= children_size
+ remaining_size
156 """This node does not actually exists, it is an aggregate."""
159 class _pysize_node_forest(_pysize_node_collection
):
160 def __init__(self
, parent
, children_fullpaths
, max_depth
, min_size
,
162 assert parent
is None
163 prefix
, suffixes
= _extract_prefix_suffixes(children_fullpaths
)
164 self
.trees
= suffixes
165 _pysize_node_collection
.__init
__(self
, parent
, prefix
,
166 children_fullpaths
, max_depth
,
170 return _join_prefix_suffixes(self
.basename
, self
.trees
)
172 def get_fullpaths(self
):
173 return [self
.basename
+ '/' + t
for t
in self
.trees
]
175 def get_dirname(self
):
178 class _pysize_node_dir(_pysize_node_collection
):
180 def __init__(self
, parent
, basename
, st
, max_depth
, min_size
, options
):
181 # Needed for get_fullname() before _pysize_node_collection.__init__()
183 self
.basename
= basename
184 fullpath
= self
.get_fullname()
185 listing
= browsedir(fullpath
, options
.cross_device
)
186 _pysize_node_collection
.__init
__(self
, parent
, basename
, listing
,
187 max_depth
, min_size
, options
)
188 self
.basename
= basename
189 self
.size
+= st
.st_blocks
* 512
190 # update size in cache, in case files were added/removed
191 deletion_info
= get_deletion_info(fullpath
)
192 if deletion_info
is not DELETED
:
193 dev_ino
= (st
.st_dev
, st
.st_ino
)
194 cache_add_dir(dev_ino
, self
.size
+ deletion_info
)
208 class _pysize_node_remaining(_pysize_node_collection
):
209 """The sum of a directory's children that are too small to be drawn."""
210 def __init__(self
, parent
, fullpaths
, options
):
211 _pysize_node
.__init
__(self
, parent
, None, options
)
212 # The parent constructor would visit the files
213 self
.size
= compute_size
.slow_sum(fullpaths
, options
.cross_device
)
214 size_basename
= [(compute_size
.slow(fp
), os
.path
.basename(fp
)) for
216 size_basename
.sort(key
=lambda s_b
: s_b
[0], reverse
=True)
217 self
.remaining_elements
= [b
for s
, b
in size_basename
]
220 return '{' + ','.join(self
.remaining_elements
) + '}'
222 def get_fullname(self
):
223 if self
.remaining_elements
:
224 return _pysize_node_collection
.get_fullname(self
)
225 return '' # This is the initial node
227 def get_fullpaths(self
):
228 fullpaths
= self
.remaining_elements
231 fullpaths
= [os
.path
.join(parent
.basename
, fp
) for fp
in fullpaths
]
232 parent
= parent
.parent
235 class _pysize_node_file(_pysize_node
):
237 def __init__(self
, parent
, basename
, options
):
238 _pysize_node
.__init
__(self
, parent
, basename
, options
)
240 class _pysize_node_hardlink(_pysize_node_file
):
241 """A hardlink, the canonical one, or a link"""
242 def __init__(self
, parent
, basename
, options
):
243 _pysize_node_file
.__init
__(self
, parent
, basename
, options
)
245 def create_node(parent
, basenames
, fullpaths
, max_depth
, min_size
, options
):
246 """Return a pysize_node for parent/basename traversing up to max_depth
247 levels and only taking into account elements bigger than min_size."""
249 return _pysize_node_remaining(parent
, [], options
)
251 assert len(basenames
) == len(fullpaths
)
252 size
= compute_size
.slow_sum(fullpaths
, options
.cross_device
)
254 node
= _pysize_node_remaining(parent
, fullpaths
, options
)
255 elif len(basenames
) > 1:
256 node
= _pysize_node_forest(parent
, basenames
, max_depth
, min_size
,
260 st
= os
.lstat(fullpaths
[0])
263 return _pysize_node_remaining(parent
, [], options
)
264 if stat
.S_ISDIR(st
.st_mode
):
265 node
= _pysize_node_dir(parent
, basenames
[0], st
, max_depth
,
267 elif st
.st_nlink
> 1:
268 node
= _pysize_node_hardlink(parent
, basenames
[0], options
)
270 node
= _pysize_node_file(parent
, basenames
[0], options
)