X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=lib%2Fbup%2Fvfs.py;h=fbe5bd6cc6fca5ed2eb0407c7c6792c3a9032122;hb=b283da69ee1a65502f9ddbb7e4603af34e7bde6b;hp=20627ab852532e5d129ac229a045bd5f2d6dc691;hpb=383111ee06546d90f3762f2147a89c46f0784dff;p=bup.git diff --git a/lib/bup/vfs.py b/lib/bup/vfs.py index 20627ab..fbe5bd6 100644 --- a/lib/bup/vfs.py +++ b/lib/bup/vfs.py @@ -4,19 +4,13 @@ The vfs.py library makes it possible to expose contents from bup's repository and abstracts internal name mangling and storage from the exposition layer. """ import os, re, stat, time -from bup import git +from bup import git, metadata from helpers import * +from bup.git import BUP_NORMAL, BUP_CHUNKED, cp from bup.hashsplit import GIT_MODE_TREE, GIT_MODE_FILE EMPTY_SHA='\0'*20 -_cp = None -def cp(): - """Create a git.CatPipe object or reuse the already existing one.""" - global _cp - if not _cp: - _cp = git.CatPipe() - return _cp class NodeError(Exception): """VFS base exception.""" @@ -39,44 +33,44 @@ class TooManySymlinks(NodeError): pass -def _treeget(hash): - it = cp().get(hash.encode('hex')) +def _treeget(hash, repo_dir=None): + it = cp(repo_dir).get(hash.encode('hex')) type = it.next() assert(type == 'tree') return git.tree_decode(''.join(it)) -def _tree_decode(hash): +def _tree_decode(hash, repo_dir=None): tree = [(int(name,16),stat.S_ISDIR(mode),sha) for (mode,name,sha) - in _treeget(hash)] + in _treeget(hash, repo_dir)] assert(tree == list(sorted(tree))) return tree -def _chunk_len(hash): - return sum(len(b) for b in cp().join(hash.encode('hex'))) +def _chunk_len(hash, repo_dir=None): + return sum(len(b) for b in cp(repo_dir).join(hash.encode('hex'))) -def _last_chunk_info(hash): - tree = _tree_decode(hash) +def _last_chunk_info(hash, repo_dir=None): + tree = _tree_decode(hash, repo_dir) assert(tree) (ofs,isdir,sha) = tree[-1] if isdir: - (subofs, sublen) = _last_chunk_info(sha) + (subofs, sublen) = _last_chunk_info(sha, repo_dir) return (ofs+subofs, sublen) else: return (ofs, _chunk_len(sha)) -def _total_size(hash): - (lastofs, lastsize) = _last_chunk_info(hash) +def _total_size(hash, repo_dir=None): + (lastofs, lastsize) = _last_chunk_info(hash, repo_dir) return lastofs + lastsize -def _chunkiter(hash, startofs): +def _chunkiter(hash, startofs, repo_dir=None): assert(startofs >= 0) - tree = _tree_decode(hash) + tree = _tree_decode(hash, repo_dir) # skip elements before startofs for i in xrange(len(tree)): @@ -91,20 +85,20 @@ def _chunkiter(hash, startofs): if skipmore < 0: skipmore = 0 if isdir: - for b in _chunkiter(sha, skipmore): + for b in _chunkiter(sha, skipmore, repo_dir): yield b else: - yield ''.join(cp().join(sha.encode('hex')))[skipmore:] + yield ''.join(cp(repo_dir).join(sha.encode('hex')))[skipmore:] class _ChunkReader: - def __init__(self, hash, isdir, startofs): + def __init__(self, hash, isdir, startofs, repo_dir=None): if isdir: - self.it = _chunkiter(hash, startofs) + self.it = _chunkiter(hash, startofs, repo_dir) self.blob = None else: self.it = None - self.blob = ''.join(cp().join(hash.encode('hex')))[startofs:] + self.blob = ''.join(cp(repo_dir).join(hash.encode('hex')))[startofs:] self.ofs = startofs def next(self, size): @@ -127,12 +121,13 @@ class _ChunkReader: class _FileReader(object): - def __init__(self, hash, size, isdir): + def __init__(self, hash, size, isdir, repo_dir=None): self.hash = hash self.ofs = 0 self.size = size self.isdir = isdir self.reader = None + self._repo_dir = repo_dir def seek(self, ofs): if ofs > self.size: @@ -149,7 +144,8 @@ class _FileReader(object): if count < 0: count = self.size - self.ofs if not self.reader or self.reader.ofs != self.ofs: - self.reader = _ChunkReader(self.hash, self.isdir, self.ofs) + self.reader = _ChunkReader(self.hash, self.isdir, self.ofs, + self._repo_dir) try: buf = self.reader.next(count) except: @@ -162,15 +158,23 @@ class _FileReader(object): pass -class Node: +class Node(object): """Base class for file representation.""" - def __init__(self, parent, name, mode, hash): + def __init__(self, parent, name, mode, hash, repo_dir=None): self.parent = parent self.name = name self.mode = mode self.hash = hash self.ctime = self.mtime = self.atime = 0 + self._repo_dir = repo_dir self._subs = None + self._metadata = None + + def __repr__(self): + return "<%s object at %s - name:%r hash:%s parent:%r>" \ + % (self.__class__, hex(id(self)), + self.name, self.hash.encode('hex'), + self.parent.name if self.parent else None) def __cmp__(a, b): if a is b: @@ -280,8 +284,6 @@ class Node: def nlinks(self): """Get the number of hard links to the current node.""" - if self._subs == None: - self._mksubs() return 1 def size(self): @@ -292,11 +294,26 @@ class Node: """Open the current node. It is an error to open a non-file node.""" raise NotFile('%s is not a regular file' % self.name) + def _populate_metadata(self, force=False): + # Only Dirs contain .bupm files, so by default, do nothing. + pass + + def metadata(self): + """Return this Node's Metadata() object, if any.""" + if not self._metadata and self.parent: + self.parent._populate_metadata(force=True) + return self._metadata + + def release(self): + """Release resources that can be automatically restored (at a cost).""" + self._metadata = None + self._subs = None + class File(Node): """A normal file from bup's repository.""" - def __init__(self, parent, name, mode, hash, bupmode): - Node.__init__(self, parent, name, mode, hash) + def __init__(self, parent, name, mode, hash, bupmode, repo_dir=None): + Node.__init__(self, parent, name, mode, hash, repo_dir) self.bupmode = bupmode self._cached_size = None self._filereader = None @@ -309,7 +326,8 @@ class File(Node): # object here so we're not constantly re-seeking. if not self._filereader: self._filereader = _FileReader(self.hash, self.size(), - self.bupmode == git.BUP_CHUNKED) + self.bupmode == git.BUP_CHUNKED, + repo_dir = self._repo_dir) self._filereader.seek(0) return self._filereader @@ -318,9 +336,11 @@ class File(Node): if self._cached_size == None: debug1('<<<