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