]> arthur.barton.de Git - bup.git/blob - lib/bup/hlinkdb.py
hlinkdb.py: clean up temp file more carefully
[bup.git] / lib / bup / hlinkdb.py
1 import cPickle, errno, os, tempfile
2
3 class Error(Exception):
4     pass
5
6 class HLinkDB:
7     def __init__(self, filename):
8         # Map a "dev:ino" node to a list of paths associated with that node.
9         self._node_paths = {}
10         # Map a path to a "dev:ino" node.
11         self._path_node = {}
12         self._filename = filename
13         self._save_prepared = None
14         self._tmpname = None
15         f = None
16         try:
17             f = open(filename, 'r')
18         except IOError, e:
19             if e.errno == errno.ENOENT:
20                 pass
21             else:
22                 raise
23         if f:
24             try:
25                 self._node_paths = cPickle.load(f)
26             finally:
27                 f.close()
28                 f = None
29         # Set up the reverse hard link index.
30         for node, paths in self._node_paths.iteritems():
31             for path in paths:
32                 self._path_node[path] = node
33
34     def prepare_save(self):
35         """ Commit all of the relevant data to disk.  Do as much work
36         as possible without actually making the changes visible."""
37         if self._save_prepared:
38             raise Error('save of %r already in progress' % self._filename)
39         if self._node_paths:
40             (dir, name) = os.path.split(self._filename)
41             (ffd, self._tmpname) = tempfile.mkstemp('.tmp', name, dir)
42             try:
43                 try:
44                     f = os.fdopen(ffd, 'wb', 65536)
45                 except:
46                     os.close(ffd)
47                     raise
48                 try:
49                     cPickle.dump(self._node_paths, f, 2)
50                 finally:
51                     f.close()
52                     f = None
53             except:
54                 tmpname = self._tmpname
55                 self._tmpname = None
56                 os.unlink(tmpname)
57                 raise
58         self._save_prepared = True
59
60     def commit_save(self):
61         if not self._save_prepared:
62             raise Error('cannot commit save of %r; no save prepared'
63                         % self._filename)
64         if self._tmpname:
65             os.rename(self._tmpname, self._filename)
66             self._tmpname = None
67         else: # No data -- delete _filename if it exists.
68             try:
69                 os.unlink(self._filename)
70             except OSError, e:
71                 if e.errno == errno.ENOENT:
72                     pass
73                 else:
74                     raise
75         self._save_prepared = None
76
77     def abort_save(self):
78         if self._tmpname:
79             os.unlink(self._tmpname)
80             self._tmpname = None
81
82     def __del__(self):
83         self.abort_save()
84
85     def add_path(self, path, dev, ino):
86         # Assume path is new.
87         node = '%s:%s' % (dev, ino)
88         self._path_node[path] = node
89         link_paths = self._node_paths.get(node)
90         if link_paths and path not in link_paths:
91             link_paths.append(path)
92         else:
93             self._node_paths[node] = [path]
94
95     def _del_node_path(self, node, path):
96         link_paths = self._node_paths[node]
97         link_paths.remove(path)
98         if not link_paths:
99             del self._node_paths[node]
100
101     def change_path(self, path, new_dev, new_ino):
102         prev_node = self._path_node.get(path)
103         if prev_node:
104             self._del_node_path(prev_node, path)
105         self.add_path(new_dev, new_ino, path)
106
107     def del_path(self, path):
108         # Path may not be in db (if updating a pre-hardlink support index).
109         node = self._path_node.get(path)
110         if node:
111             self._del_node_path(node, path)
112             del self._path_node[path]
113
114     def node_paths(self, dev, ino):
115         node = '%s:%s' % (dev, ino)
116         return self._node_paths[node]