blob: 8b6314dc07971f96df47525b134692d25d58a793 [file] [log] [blame]
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from future import Gettable, Future
class FileNotFoundError(Exception):
'''Raised when a file isn't found for read or stat.
'''
def __init__(self, filename):
Exception.__init__(self, filename)
class FileSystemError(Exception):
'''Raised on when there are errors reading or statting files, such as a
network timeout.
'''
def __init__(self, filename):
Exception.__init__(self, filename)
class StatInfo(object):
'''The result of calling Stat on a FileSystem.
'''
def __init__(self, version, child_versions=None):
self.version = version
self.child_versions = child_versions
def __eq__(self, other):
return (isinstance(other, StatInfo) and
self.version == other.version and
self.child_versions == other.child_versions)
def __ne__(self, other):
return not (self == other)
def __str__(self):
return '{version: %s, child_versions: %s}' % (self.version,
self.child_versions)
def __repr__(self):
return str(self)
def ToUnicode(data):
'''Returns the str |data| as a unicode object. It's expected to be utf8, but
there are also latin-1 encodings in there for some reason. Fall back to that.
'''
try:
return unicode(data, 'utf-8')
except:
return unicode(data, 'latin-1')
class FileSystem(object):
'''A FileSystem interface that can read files and directories.
'''
def Read(self, paths, binary=False):
'''Reads each file in paths and returns a dictionary mapping the path to the
contents. If a path in paths ends with a '/', it is assumed to be a
directory, and a list of files in the directory is mapped to the path.
If binary=False, the contents of each file will be unicode parsed as utf-8,
and failing that as latin-1 (some extension docs use latin-1). If
binary=True then the contents will be a str.
If any path cannot be found, raises a FileNotFoundError. This is guaranteed
to only happen once the Future has been resolved (Get() called).
For any other failure, raises a FileSystemError.
'''
raise NotImplementedError(self.__class__)
def ReadSingle(self, path, binary=False):
'''Reads a single file from the FileSystem. Returns a Future with the same
rules as Read().
'''
read_single = self.Read([path], binary=binary)
return Future(delegate=Gettable(lambda: read_single.Get()[path]))
def Refresh(self):
raise NotImplementedError(self.__class__)
# TODO(cduvall): Allow Stat to take a list of paths like Read.
def Stat(self, path):
'''Returns a |StatInfo| object containing the version of |path|. If |path|
is a directory, |StatInfo| will have the versions of all the children of
the directory in |StatInfo.child_versions|.
If the path cannot be found, raises a FileNotFoundError.
For any other failure, raises a FileSystemError.
'''
raise NotImplementedError(self.__class__)
def GetIdentity(self):
'''The identity of the file system, exposed for caching classes to
namespace their caches. this will usually depend on the configuration of
that file system - e.g. a LocalFileSystem with a base path of /var is
different to that of a SubversionFileSystem with a base path of /bar, is
different to a LocalFileSystem with a base path of /usr.
'''
raise NotImplementedError(self.__class__)
def Walk(self, root):
'''Recursively walk the directories in a file system, starting with root.
Emulates os.walk from the standard os module.
If the root cannot be found, raises a FileNotFoundError.
For any other failure, raises a FileSystemError.
'''
basepath = root.rstrip('/') + '/'
def walk(root):
if not root.endswith('/'):
root += '/'
dirs, files = [], []
for f in self.ReadSingle(root).Get():
if f.endswith('/'):
dirs.append(f)
else:
files.append(f)
yield root[len(basepath):].rstrip('/'), dirs, files
for d in dirs:
for walkinfo in walk(root + d):
yield walkinfo
for walkinfo in walk(root):
yield walkinfo
def __repr__(self):
return '<%s>' % type(self).__name__
def __str__(self):
return repr(self)