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