4 from pysize_global_fs_cache
import get_dev_ino
, cache_add_dir
5 from pysize_global_fs_cache
import cache_add_hardlink
, cache_get_dir_size
7 def _browse_directory(path
):
8 previous_cwd
= os
.open('.', os
.O_RDONLY
)
10 for child
in os
.listdir('.'):
12 os
.fchdir(previous_cwd
)
13 os
.close(previous_cwd
)
15 def _size_fast(path
, dir_ino
):
16 """Return the size of the file or directory at path, using or updating the
17 cache. dir_ino is the inode of the directory containing the path, it is
18 used only with hardlinks.
22 if stat
.S_ISDIR(st
.st_mode
): # Directory
23 dev_ino
= (st
.st_dev
, st
.st_ino
)
24 cached_size
= cache_get_dir_size(dev_ino
)
25 if cached_size
is None:
26 size
= st
.st_blocks
* 512
28 for p
in _browse_directory(path
):
29 size
+= _size_fast(p
, dir_ino
)
30 cache_add_dir(dev_ino
, size
)
33 elif st
.st_nlink
> 1: # Hardlink
34 if cache_add_hardlink(st
.st_dev
, st
.st_ino
, dir_ino
, path
):
35 size
= st
.st_blocks
* 512
39 size
= st
.st_blocks
* 512
46 """Same as _size_fast(path, dir_ino), except that dir_ino is computed."""
48 """Return the parent directory path, taking into account '/'
50 elements
= path
.split('/')
51 length
= len(elements
)
53 for i
in xrange(length
- 1, -1, -1):
54 if elements
[i
] not in ('', '.'):
57 return '/'.join(elements
)
61 if os
.path
.samefile(path
, '.'):
62 return os
.path
.dirname(os
.getcwd())
64 parent
= parent_path()
65 return _size_fast(path
, get_dev_ino(parent
)[1])
68 """The parent class of all displayed nodes, as these nodes are displayed on
69 screen, there should be few instances."""
70 def __init__(self
, parent
, basename
):
71 self
.rectangle
= (0, 0, 0, 0)
74 self
.basename
= basename
75 self
.size
= _size(basename
)
77 def _compute_height(self
):
80 def _compute_depth(self
):
92 """Does the node correspond to a path that actually exists?"""
95 def get_children(self
):
96 """Return the children for a directory."""
99 def get_fullpath(self
):
100 """Return the fullpath of the node, using its parent node."""
101 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
.get_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_remaining(_pysize_node
):
125 """The sum of a directory's children that are too small to be drawn."""
126 def __init__(self
, parent
, size
):
127 _pysize_node
.__init
__(self
, parent
, None)
128 self
.basename
= '..///..'
132 """This node does not actually exists, it is an aggregate."""
135 class _pysize_node_dir(_pysize_node
):
137 def __init__(self
, parent
, basename
, max_depth
, min_size
):
138 _pysize_node
.__init
__(self
, parent
, basename
)
143 for child
in _browse_directory(basename
):
144 node
= _create_node(self
, child
, max_depth
- 1, min_size
)
146 self
.children
.append(node
)
147 children_size
+= node
.size
149 remaining_size
+= node
.size
150 self
.children
.sort(key
=lambda t
: t
.size
, reverse
=True)
151 self
.size
= max(children_size
, self
.size
)
152 if remaining_size
> min_size
:
153 rem
= _pysize_node_remaining(self
, remaining_size
)
154 self
.children
.append(rem
)
156 def _compute_height(self
):
158 children_height
= max([c
._compute
_height
() for c
in self
.children
])
159 return children_height
+ 1
165 def get_children(self
):
169 return self
.basename
+ '/'
171 class _pysize_node_file(_pysize_node
):
173 def __init__(self
, parent
, basename
):
174 _pysize_node
.__init
__(self
, parent
, basename
)
176 class _pysize_node_hardlink(_pysize_node_file
):
177 """A hardlink, the canonical one, or a link"""
178 def __init__(self
, parent
, basename
):
179 _pysize_node_file
.__init
__(self
, parent
, basename
)
181 def _create_node(parent
, basename
, max_depth
, min_size
):
182 """Return a pysize_node for parent/basename traversing up to max_depth
183 levels and only taking into account elements bigger than min_size."""
184 size
= _size(basename
)
186 node
= _pysize_node_remaining(parent
, size
)
188 st
= os
.lstat(basename
)
189 if stat
.S_ISDIR(st
.st_mode
):
190 node
= _pysize_node_dir(parent
, basename
, max_depth
, min_size
)
191 elif st
.st_nlink
> 1:
192 node
= _pysize_node_hardlink(parent
, basename
)
194 node
= _pysize_node_file(parent
, basename
)
197 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/231503
199 def _breadth_first(tree
,children
=iter):
200 """Traverse the nodes of a tree in breadth-first order.
201 The first argument should be the tree root; children
202 should be a function taking as argument a tree node and
203 returning an iterator of the node's children.
207 for node
in _breadth_first(tree
,children
):
208 for child
in children(node
):
215 """The entry point to a tree of pysize_node."""
216 def __init__(self
, path
, max_depth
, min_size_ratio
):
218 min_size
= int(min_size_ratio
* _size(path
))
219 self
.root
= _create_node(None, path
, max_depth
, min_size
)
221 self
.height
= self
.root
._compute
_height
()
225 def get_next_sibling(self
, node
):
226 """Return the next pysize_node in node's level."""
228 for child
in _breadth_first(self
.root
, lambda c
: c
.get_children()):
230 if child
._compute
_depth
() == node
._compute
_depth
():
233 is_next
= child
is node
235 def get_previous_sibling(self
, node
):
236 """Return the previous pysize_node in node's level."""
238 for child
in _breadth_first(self
.root
, lambda c
: c
.get_children()):
240 if prev
._compute
_depth
() == node
._compute
_depth
():
245 def get_first_child(self
, node
):
246 """Return the first pysize_node in node's children."""
247 children
= node
.get_children()
251 def get_parent(self
, node
):
252 """Return the saved parent of node."""
256 tree
= pysize_tree('/usr/share/doc', 4, 0.1)
257 print 'height', tree
.height
259 if __name__
== '__main__':