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