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