Update e-mail address
[pysize.git] / pysize / core / deletion.py
blob6c97fbe72ef83fef6016898b2f377ef424383248
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>
19 import os
21 from pysize.core.pysize_global_fs_cache import get_dev_ino
22 from pysize.core.exception_propagation import catches, unexpect
24 # ['/path/to/file1', '/path/to/file2']
25 TO_DELETE = []
27 # {device: {inode: (size_to_subtract|DELETED)}}
28 DEV_INO_TO_SUBTRACT = {}
30 # Deleted inodes are marked as such, instead of subtracting their whole size
31 # as the latter may change
32 class deleted(object):
33 pass
34 DELETED = deleted()
36 @unexpect(OSError)
37 def _iter_from_the_root(fullpath):
38 """If path is /a/b/c, iterates over /, /a, /a/b, /a/b/c and returns also
39 for each path the inodes dictionary in DEV_INO_TO_SUBTRACT as well as the
40 inode number"""
41 rebuilt = '/'
42 for element in [''] + fullpath.strip('/').split('/'):
43 rebuilt = os.path.join(rebuilt, element)
44 dev, ino = get_dev_ino(rebuilt)
45 yield DEV_INO_TO_SUBTRACT.setdefault(dev, {}), ino, rebuilt
47 @catches(OSError)
48 def get_deletion_info(fullpath):
49 """Get the size to subtract to the size of the given path, the returned
50 value can be:
51 o The size to subtract
52 o DELETED if the given path has been deleted"""
53 subtracted = 0
54 if DEV_INO_TO_SUBTRACT:
55 try:
56 for inodes, ino, prefix in _iter_from_the_root(fullpath):
57 subtracted = inodes.get(ino, 0)
58 if subtracted == 0:
59 # Nothing to delete
60 break
61 if subtracted is DELETED:
62 # The prefix is deleted, so the complete path too
63 break
64 # Otherwise the prefix contains some deleted items, so we must
65 # continue
66 except OSError, e:
67 print e
68 return DELETED
69 return subtracted
71 def _add_to_hash(fullpath):
72 """Add the path to the hash table, including its parents"""
73 from pysize.core import compute_size
74 size = compute_size.slow(fullpath)
75 dev, ino = get_dev_ino(fullpath)
76 DEV_INO_TO_SUBTRACT.setdefault(dev, {})[ino] = DELETED
77 for inodes, ino, prefix in _iter_from_the_root(os.path.dirname(fullpath)):
78 inodes[ino] = inodes.get(ino, 0) + size
80 # TODO: Deletion of hard links
81 @catches(OSError)
82 def schedule(nodes):
83 """Simulate the deletion of some files"""
84 for node in nodes:
85 for fullpath in node.get_fullpaths():
86 to_delete = False
87 try:
88 subtract = get_deletion_info(fullpath)
89 if subtract is not DELETED:
90 _add_to_hash(fullpath)
91 to_delete = True
92 except OSError, e:
93 print e
94 if to_delete:
95 TO_DELETE.append(fullpath)
97 _prune_list()
98 _rebuild_hash()
100 def _prune_list():
101 """Remove children if we have the parent"""
102 while True:
103 for i in xrange(len(TO_DELETE)):
104 parent = os.path.dirname(TO_DELETE[i])
105 if get_deletion_info(parent) is DELETED:
106 del TO_DELETE[i]
107 break
108 else:
109 break
111 def _rebuild_hash():
112 """Rebuild the hash table from the list of deleted files"""
113 DEV_INO_TO_SUBTRACT.clear()
114 for fullpath in TO_DELETE:
115 _add_to_hash(fullpath)
117 def filter_deleted(dirname, basenames):
118 """Basenames are files in the current directory"""
119 if not DEV_INO_TO_SUBTRACT:
120 return basenames
121 subtracted_prefix = get_deletion_info(dirname)
122 if subtracted_prefix is DELETED:
123 return []
124 if not subtracted_prefix:
125 return basenames
126 @catches(OSError)
127 def is_not_deleted(basename):
128 try:
129 dev, ino = get_dev_ino(dirname + '/' + basename)
130 return DEV_INO_TO_SUBTRACT.get(dev, {}).get(ino, 0) is not DELETED
131 except OSError, e:
132 print e
133 return False
134 return filter(is_not_deleted, basenames)
136 def get_deleted():
137 """Return a copy to permit modifications while iterating"""
138 return TO_DELETE[:]
140 @catches(OSError)
141 def restore(fullpath):
142 """Cancel the deletion of the given path"""
143 from pysize.core import compute_size
144 try:
145 dev, ino = get_dev_ino(fullpath)
146 except OSError, e:
147 print e
148 return
149 TO_DELETE.remove(fullpath)
150 del DEV_INO_TO_SUBTRACT[dev][ino]
151 size = compute_size.slow(fullpath, account_deletion=False)
152 for inodes, ino, prefix in _iter_from_the_root(os.path.dirname(fullpath)):
153 if inodes[ino] == size:
154 del inodes[ino]
155 else:
156 inodes[ino] -= size