X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=lib%2Fbup%2Fvfs.py;h=b9e90f3415c18569a2c72fc2e4f3121066652063;hb=d5cb741fd70c5cb8d206a5fdc70fa8e640250e99;hp=9baa6b5f48f200affb3d80fbf23dd31688964979;hpb=bab7b16b760ba1fd686f6f5b274fc8ba2ad360e2;p=bup.git diff --git a/lib/bup/vfs.py b/lib/bup/vfs.py index 9baa6b5..b9e90f3 100644 --- a/lib/bup/vfs.py +++ b/lib/bup/vfs.py @@ -3,19 +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 -from helpers import * + +from bup import git, metadata +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.""" @@ -38,44 +35,44 @@ class TooManySymlinks(NodeError): pass -def _treeget(hash): - it = cp().get(hash.encode('hex')) - type = it.next() +def _treeget(hash, repo_dir=None): + it = cp(repo_dir).get(hash.encode('hex')) + _, type, _ = next(it) assert(type == 'tree') - return git.treeparse(''.join(it)) + return git.tree_decode(''.join(it)) -def _tree_decode(hash): - tree = [(int(name,16),stat.S_ISDIR(int(mode,8)),sha) +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)): @@ -90,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(object): - def __init__(self, hash, isdir, startofs): +class _ChunkReader: + 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): @@ -111,7 +108,7 @@ class _ChunkReader(object): while len(out) < size: if self.it and not self.blob: try: - self.blob = self.it.next() + self.blob = next(self.it) except StopIteration: self.it = None if self.blob: @@ -126,12 +123,13 @@ class _ChunkReader(object): 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: @@ -148,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,16 +162,27 @@ class _FileReader(object): 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): - return cmp(a and a.name or None, b and b.name or None) + if a is b: + return 0 + return (cmp(a and a.parent, b and b.parent) or + cmp(a and a.name, b and b.name)) def __iter__(self): return iter(self.subs()) @@ -276,8 +286,6 @@ class Node(object): def nlinks(self): """Get the number of hard links to the current node.""" - if self._subs == None: - self._mksubs() return 1 def size(self): @@ -288,11 +296,26 @@ class Node(object): """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 @@ -305,18 +328,21 @@ 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 def size(self): """Get this file's size.""" if self._cached_size == None: - debug1('<<<