]> arthur.barton.de Git - bup.git/blob - lib/bup/hlinkdb.py
get: adjust for python 3 and test there
[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
7 if compat.py_maj > 2:
8     import pickle
9 else:
10     import cPickle as pickle
11
12
13 class Error(Exception):
14     pass
15
16 class HLinkDB:
17     def __init__(self, filename):
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         if not self._save_prepared:
72             raise Error('cannot commit save of %r; no save prepared'
73                         % self._filename)
74         if self._tmpname:
75             os.rename(self._tmpname, self._filename)
76             self._tmpname = None
77         else: # No data -- delete _filename if it exists.
78             try:
79                 os.unlink(self._filename)
80             except OSError as e:
81                 if e.errno == errno.ENOENT:
82                     pass
83                 else:
84                     raise
85         self._save_prepared = None
86
87     def abort_save(self):
88         if self._tmpname:
89             os.unlink(self._tmpname)
90             self._tmpname = None
91
92     def __del__(self):
93         self.abort_save()
94
95     def add_path(self, path, dev, ino):
96         # Assume path is new.
97         node = '%s:%s' % (dev, ino)
98         self._path_node[path] = node
99         link_paths = self._node_paths.get(node)
100         if link_paths and path not in link_paths:
101             link_paths.append(path)
102         else:
103             self._node_paths[node] = [path]
104
105     def _del_node_path(self, node, path):
106         link_paths = self._node_paths[node]
107         link_paths.remove(path)
108         if not link_paths:
109             del self._node_paths[node]
110
111     def change_path(self, path, new_dev, new_ino):
112         prev_node = self._path_node.get(path)
113         if prev_node:
114             self._del_node_path(prev_node, path)
115         self.add_path(new_dev, new_ino, path)
116
117     def del_path(self, path):
118         # Path may not be in db (if updating a pre-hardlink support index).
119         node = self._path_node.get(path)
120         if node:
121             self._del_node_path(node, path)
122             del self._path_node[path]
123
124     def node_paths(self, dev, ino):
125         node = '%s:%s' % (dev, ino)
126         return self._node_paths[node]