2 from __future__ import absolute_import
3 import errno, os, tempfile
6 from bup.compat import pending_raise
11 return pickle.load(f, encoding='bytes')
13 import cPickle as pickle
14 pickle_load = pickle.load
17 class Error(Exception):
21 def __init__(self, filename):
23 # Map a "dev:ino" node to a list of paths associated with that node.
25 # Map a path to a "dev:ino" node.
27 self._filename = filename
28 self._save_prepared = None
32 f = open(filename, 'rb')
34 if e.errno == errno.ENOENT:
40 self._node_paths = pickle_load(f)
44 # Set up the reverse hard link index.
45 for node, paths in compat.items(self._node_paths):
47 self._path_node[path] = node
49 def prepare_save(self):
50 """ Commit all of the relevant data to disk. Do as much work
51 as possible without actually making the changes visible."""
52 if self._save_prepared:
53 raise Error('save of %r already in progress' % self._filename)
55 (dir, name) = os.path.split(self._filename)
56 (ffd, self._tmpname) = tempfile.mkstemp(b'.tmp', name, dir)
59 f = os.fdopen(ffd, 'wb', 65536)
64 pickle.dump(self._node_paths, f, 2)
69 tmpname = self._tmpname
73 self._save_prepared = True
75 def commit_save(self):
77 if not self._save_prepared:
78 raise Error('cannot commit save of %r; no save prepared'
81 os.rename(self._tmpname, self._filename)
83 else: # No data -- delete _filename if it exists.
85 os.unlink(self._filename)
87 if e.errno == errno.ENOENT:
91 self._save_prepared = None
96 os.unlink(self._tmpname)
102 def __exit__(self, type, value, traceback):
103 with pending_raise(value, rethrow=True):
109 def add_path(self, path, dev, ino):
110 # Assume path is new.
111 node = b'%d:%d' % (dev, ino)
112 self._path_node[path] = node
113 link_paths = self._node_paths.get(node)
114 if link_paths and path not in link_paths:
115 link_paths.append(path)
117 self._node_paths[node] = [path]
119 def _del_node_path(self, node, path):
120 link_paths = self._node_paths[node]
121 link_paths.remove(path)
123 del self._node_paths[node]
125 def change_path(self, path, new_dev, new_ino):
126 prev_node = self._path_node.get(path)
128 self._del_node_path(prev_node, path)
129 self.add_path(new_dev, new_ino, path)
131 def del_path(self, path):
132 # Path may not be in db (if updating a pre-hardlink support index).
133 node = self._path_node.get(path)
135 self._del_node_path(node, path)
136 del self._path_node[path]
138 def node_paths(self, dev, ino):
139 node = b'%d:%d' % (dev, ino)
140 return self._node_paths[node]