X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=lib%2Fbup%2Fvfs.py;h=00e1b8dbe97e1d2ce9b87a8ee8937423e3ac1d62;hb=7e1f05fe8b8d580d61c6a233ec1440adc248c97c;hp=e1de1fe6bb19d12caab951de3884b668cc164cbd;hpb=ee777400cf624b4f7a24b15614c2b6c745560dcb;p=bup.git diff --git a/lib/bup/vfs.py b/lib/bup/vfs.py index e1de1fe..00e1b8d 100644 --- a/lib/bup/vfs.py +++ b/lib/bup/vfs.py @@ -3,21 +3,16 @@ 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, metadata -from helpers import * -from bup.git import BUP_NORMAL, BUP_CHUNKED +from helpers import debug1, debug2 +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.""" @@ -40,44 +35,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)): @@ -92,20 +87,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): @@ -128,12 +123,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: @@ -150,7 +146,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: @@ -163,14 +160,15 @@ 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 @@ -288,8 +286,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): @@ -300,21 +296,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): + 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 self.parent: - self.parent._populate_metadata() + 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 @@ -327,7 +328,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 @@ -336,9 +338,11 @@ class File(Node): if self._cached_size == None: debug1('<<<