]> arthur.barton.de Git - bup.git/blob - lib/bup/hlinkdb.py
3a53b4059952af61828198ed5e783cf1da818ad8
[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                 f = os.fdopen(ffd, 'wb', 65536)
44             except:
45                 os.close(ffd)
46                 raise
47             try:
48                 cPickle.dump(self._node_paths, f, 2)
49             except:
50                 f.close()
51                 os.unlink(self._tmpname)
52                 self._tmpname = None
53                 raise
54             else:
55                 f.close()
56                 f = None
57         self._save_prepared = True
58
59     def commit_save(self):
60         if not self._save_prepared:
61             raise Error('cannot commit save of %r; no save prepared'
62                         % self._filename)
63         if self._tmpname:
64             os.rename(self._tmpname, self._filename)
65             self._tmpname = None
66         else: # No data -- delete _filename if it exists.
67             try:
68                 os.unlink(self._filename)
69             except OSError, e:
70                 if e.errno == errno.ENOENT:
71                     pass
72                 else:
73                     raise
74         self._save_prepared = None
75
76     def abort_save(self):
77         if self._tmpname:
78             os.unlink(self._tmpname)
79             self._tmpname = None
80
81     def __del__(self):
82         self.abort_save()
83
84     def add_path(self, path, dev, ino):
85         # Assume path is new.
86         node = '%s:%s' % (dev, ino)
87         self._path_node[path] = node
88         link_paths = self._node_paths.get(node)
89         if link_paths and path not in link_paths:
90             link_paths.append(path)
91         else:
92             self._node_paths[node] = [path]
93
94     def _del_node_path(self, node, path):
95         link_paths = self._node_paths[node]
96         link_paths.remove(path)
97         if not link_paths:
98             del self._node_paths[node]
99
100     def change_path(self, path, new_dev, new_ino):
101         prev_node = self._path_node.get(path)
102         if prev_node:
103             self._del_node_path(prev_node, path)
104         self.add_path(new_dev, new_ino, path)
105
106     def del_path(self, path):
107         # Path may not be in db (if updating a pre-hardlink support index).
108         node = self._path_node.get(path)
109         if node:
110             self._del_node_path(node, path)
111             del self._path_node[path]
112
113     def node_paths(self, dev, ino):
114         node = '%s:%s' % (dev, ino)
115         return self._node_paths[node]