]> arthur.barton.de Git - bup.git/blob - lib/bup/vfs.py
Move python library files to lib/bup/
[bup.git] / lib / bup / vfs.py
1 import os, re, stat, time
2 from bup import git
3 from helpers import *
4
5 EMPTY_SHA='\0'*20
6
7 _cp = None
8 def cp():
9     global _cp
10     if not _cp:
11         _cp = git.CatPipe()
12     return _cp
13
14 class NodeError(Exception):
15     pass
16 class NoSuchFile(NodeError):
17     pass
18 class NotDir(NodeError):
19     pass
20 class NotFile(NodeError):
21     pass
22 class TooManySymlinks(NodeError):
23     pass
24
25
26 class FileReader:
27     def __init__(self, node):
28         self.n = node
29         self.ofs = 0
30         self.size = self.n.size()
31
32     def seek(self, ofs):
33         if ofs > self.size:
34             self.ofs = self.size
35         elif ofs < 0:
36             self.ofs = 0
37         else:
38             self.ofs = ofs
39
40     def tell(self):
41         return self.ofs
42
43     def read(self, count = -1):
44         if count < 0:
45             count = self.size - self.ofs
46         buf = self.n.readbytes(self.ofs, count)
47         self.ofs += len(buf)
48         return buf
49
50
51 class Node:
52     def __init__(self, parent, name, mode, hash):
53         self.parent = parent
54         self.name = name
55         self.mode = mode
56         self.hash = hash
57         self._subs = None
58         
59     def __cmp__(a, b):
60         return cmp(a.name or None, b.name or None)
61     
62     def __iter__(self):
63         return iter(self.subs())
64     
65     def fullname(self):
66         if self.parent:
67             return os.path.join(self.parent.fullname(), self.name)
68         else:
69             return self.name
70     
71     def _mksubs(self):
72         self._subs = {}
73         
74     def subs(self):
75         if self._subs == None:
76             self._mksubs()
77         return sorted(self._subs.values())
78         
79     def sub(self, name):
80         if self._subs == None:
81             self._mksubs()
82         ret = self._subs.get(name)
83         if not ret:
84             raise NoSuchFile("no file %r in %r" % (name, self.name))
85         return ret
86
87     def top(self):
88         if self.parent:
89             return self.parent.top()
90         else:
91             return self
92
93     def _lresolve(self, parts):
94         #log('_lresolve %r in %r\n' % (parts, self.name))
95         if not parts:
96             return self
97         (first, rest) = (parts[0], parts[1:])
98         if first == '.':
99             return self._lresolve(rest)
100         elif first == '..':
101             if not self.parent:
102                 raise NoSuchFile("no parent dir for %r" % self.name)
103             return self.parent._lresolve(rest)
104         elif rest:
105             return self.sub(first)._lresolve(rest)
106         else:
107             return self.sub(first)
108
109     def lresolve(self, path):
110         start = self
111         if path.startswith('/'):
112             start = self.top()
113             path = path[1:]
114         parts = re.split(r'/+', path or '.')
115         if not parts[-1]:
116             parts[-1] = '.'
117         #log('parts: %r %r\n' % (path, parts))
118         return start._lresolve(parts)
119
120     def resolve(self, path):
121         return self.lresolve(path).lresolve('')
122     
123     def nlinks(self):
124         if self._subs == None:
125             self._mksubs()
126         return 1
127
128     def size(self):
129         return 0
130
131     def open(self):
132         raise NotFile('%s is not a regular file' % self.name)
133     
134     def readbytes(self, ofs, count):
135         raise NotFile('%s is not a regular file' % self.name)
136     
137     def read(self, num = -1):
138         if num < 0:
139             num = self.size()
140         return self.readbytes(0, num)
141     
142     
143 class File(Node):
144     def _content(self):
145         return cp().join(self.hash.encode('hex'))
146
147     def open(self):
148         return FileReader(self)
149     
150     def size(self):
151         # FIXME inefficient
152         return sum(len(blob) for blob in self._content())
153     
154     def readbytes(self, ofs, count):
155         # FIXME inefficient
156         buf = ''.join(self._content())
157         return buf[ofs:ofs+count]
158     
159
160 _symrefs = 0
161 class Symlink(File):
162     def __init__(self, parent, name, hash):
163         File.__init__(self, parent, name, 0120000, hash)
164
165     def readlink(self):
166         return self.read(1024)
167
168     def dereference(self):
169         global _symrefs
170         if _symrefs > 100:
171             raise TooManySymlinks('too many levels of symlinks: %r'
172                                   % self.fullname())
173         _symrefs += 1
174         try:
175             return self.parent.lresolve(self.readlink())
176         finally:
177             _symrefs -= 1
178
179     def _lresolve(self, parts):
180         return self.dereference()._lresolve(parts)
181     
182
183 class FakeSymlink(Symlink):
184     def __init__(self, parent, name, toname):
185         Symlink.__init__(self, parent, name, EMPTY_SHA)
186         self.toname = toname
187         
188     def _content(self):
189         return self.toname
190     
191
192 class Dir(Node):
193     def _mksubs(self):
194         self._subs = {}
195         it = cp().get(self.hash.encode('hex'))
196         type = it.next()
197         if type == 'commit':
198             del it
199             it = cp().get(self.hash.encode('hex') + ':')
200             type = it.next()
201         assert(type == 'tree')
202         for (mode,name,sha) in git._treeparse(''.join(it)):
203             mode = int(mode, 8)
204             if stat.S_ISDIR(mode):
205                 self._subs[name] = Dir(self, name, mode, sha)
206             elif stat.S_ISLNK(mode):
207                 self._subs[name] = Symlink(self, name, sha)
208             else:
209                 self._subs[name] = File(self, name, mode, sha)
210                 
211
212 class CommitList(Node):
213     def __init__(self, parent, name, hash):
214         Node.__init__(self, parent, name, 040000, hash)
215         
216     def _mksubs(self):
217         self._subs = {}
218         revs = list(git.rev_list(self.hash.encode('hex')))
219         for (date, commit) in revs:
220             l = time.localtime(date)
221             ls = time.strftime('%Y-%m-%d-%H%M%S', l)
222             commithex = commit.encode('hex')
223             self._subs[commithex] = Dir(self, commithex, 040000, commit)
224             self._subs[ls] = FakeSymlink(self, ls, commit.encode('hex'))
225             latest = max(revs)
226         if latest:
227             (date, commit) = latest
228             self._subs['latest'] = FakeSymlink(self, 'latest',
229                                                commit.encode('hex'))
230
231     
232 class RefList(Node):
233     def __init__(self, parent):
234         Node.__init__(self, parent, '/', 040000, EMPTY_SHA)
235         
236     def _mksubs(self):
237         self._subs = {}
238         for (name,sha) in git.list_refs():
239             if name.startswith('refs/heads/'):
240                 name = name[11:]
241                 self._subs[name] = CommitList(self, name, sha)
242         
243