Home | History | Annotate | Download | only in server2
      1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import posixpath
      6 import traceback
      7 
      8 from future import Future
      9 from path_util import (
     10     AssertIsDirectory, AssertIsValid, IsDirectory, IsValid, SplitParent,
     11     ToDirectory)
     12 
     13 
     14 def IsFileSystemThrottledError(error):
     15   return type(error).__name__ == 'FileSystemThrottledError'
     16 
     17 
     18 class _BaseFileSystemException(Exception):
     19   def __init__(self, message):
     20     Exception.__init__(self, message)
     21 
     22   @classmethod
     23   def RaiseInFuture(cls, message):
     24     stack = traceback.format_stack()
     25     def boom(): raise cls('%s. Creation stack:\n%s' % (message, ''.join(stack)))
     26     return Future(callback=boom)
     27 
     28 
     29 class FileNotFoundError(_BaseFileSystemException):
     30   '''Raised when a file isn't found for read or stat.
     31   '''
     32   def __init__(self, filename):
     33     _BaseFileSystemException.__init__(self, filename)
     34 
     35 
     36 class FileSystemThrottledError(_BaseFileSystemException):
     37   '''Raised when access to a file system resource is temporarily unavailable
     38   due to service throttling.
     39   '''
     40   def __init__(self, filename):
     41     _BaseFileSystemException.__init__(self, filename)
     42 
     43 
     44 class FileSystemError(_BaseFileSystemException):
     45   '''Raised on when there are errors reading or statting files, such as a
     46   network timeout.
     47   '''
     48   def __init__(self, filename):
     49     _BaseFileSystemException.__init__(self, filename)
     50 
     51 
     52 class StatInfo(object):
     53   '''The result of calling Stat on a FileSystem.
     54   '''
     55   def __init__(self, version, child_versions=None):
     56     if child_versions:
     57       assert all(IsValid(path) for path in child_versions.iterkeys()), \
     58              child_versions
     59     self.version = version
     60     self.child_versions = child_versions
     61 
     62   def __eq__(self, other):
     63     return (isinstance(other, StatInfo) and
     64             self.version == other.version and
     65             self.child_versions == other.child_versions)
     66 
     67   def __ne__(self, other):
     68     return not (self == other)
     69 
     70   def __str__(self):
     71     return '{version: %s, child_versions: %s}' % (self.version,
     72                                                   self.child_versions)
     73 
     74   def __repr__(self):
     75     return str(self)
     76 
     77 
     78 class FileSystem(object):
     79   '''A FileSystem interface that can read files and directories.
     80   '''
     81   def Read(self, paths, skip_not_found=False):
     82     '''Reads each file in paths and returns a dictionary mapping the path to the
     83     contents. If a path in paths ends with a '/', it is assumed to be a
     84     directory, and a list of files in the directory is mapped to the path.
     85 
     86     The contents will be a str.
     87 
     88     If any path cannot be found:
     89       - If |skip_not_found| is True, the resulting object will not contain any
     90         mapping for that path.
     91       - Otherwise, and by default, a FileNotFoundError is raised. This is
     92         guaranteed to only happen once the Future has been resolved (Get()
     93         called).
     94 
     95     For any other failure, raises a FileSystemError.
     96     '''
     97     raise NotImplementedError(self.__class__)
     98 
     99   def ReadSingle(self, path, skip_not_found=False):
    100     '''Reads a single file from the FileSystem. Returns a Future with the same
    101     rules as Read(). If |path| is not found raise a FileNotFoundError on Get(),
    102     or if |skip_not_found| is True then return None.
    103     '''
    104     AssertIsValid(path)
    105     read_single = self.Read([path], skip_not_found=skip_not_found)
    106     return Future(callback=lambda: read_single.Get().get(path, None))
    107 
    108   def Exists(self, path):
    109     '''Returns a Future to the existence of |path|; True if |path| exists,
    110     False if not. This method will not throw a FileNotFoundError unlike
    111     the Read* methods, however it may still throw a FileSystemError.
    112 
    113     There are several ways to implement this method via the interface but this
    114     method exists to do so in a canonical and most efficient way for caching.
    115     '''
    116     AssertIsValid(path)
    117     if path == '':
    118       # There is always a root directory.
    119       return Future(value=True)
    120 
    121     parent, base = SplitParent(path)
    122     def handle(error):
    123       if isinstance(error, FileNotFoundError):
    124         return False
    125       raise error
    126     return self.ReadSingle(ToDirectory(parent)).Then(lambda l: base in l,
    127                                                      handle)
    128 
    129   def Refresh(self):
    130     '''Asynchronously refreshes the content of the FileSystem, returning a
    131     future to its completion.
    132     '''
    133     raise NotImplementedError(self.__class__)
    134 
    135   # TODO(cduvall): Allow Stat to take a list of paths like Read.
    136   def Stat(self, path):
    137     '''DEPRECATED: Please try to use StatAsync instead.
    138 
    139     Returns a |StatInfo| object containing the version of |path|. If |path|
    140     is a directory, |StatInfo| will have the versions of all the children of
    141     the directory in |StatInfo.child_versions|.
    142 
    143     If the path cannot be found, raises a FileNotFoundError.
    144     For any other failure, raises a FileSystemError.
    145     '''
    146     # Delegate to this implementation's StatAsync if it has been implemented.
    147     if type(self).StatAsync != FileSystem.StatAsync:
    148       return self.StatAsync(path).Get()
    149     raise NotImplementedError(self.__class__)
    150 
    151   def StatAsync(self, path):
    152     '''An async version of Stat. Returns a Future to a StatInfo rather than a
    153     raw StatInfo.
    154 
    155     This is a bandaid for a lack of an async Stat function. Stat() should be
    156     async by default but for now just let implementations override this if they
    157     like.
    158     '''
    159     return Future(callback=lambda: self.Stat(path))
    160 
    161   def GetIdentity(self):
    162     '''The identity of the file system, exposed for caching classes to
    163     namespace their caches. this will usually depend on the configuration of
    164     that file system - e.g. a LocalFileSystem with a base path of /var is
    165     different to that of a SubversionFileSystem with a base path of /bar, is
    166     different to a LocalFileSystem with a base path of /usr.
    167     '''
    168     raise NotImplementedError(self.__class__)
    169 
    170   def Walk(self, root, depth=-1, file_lister=None):
    171     '''Recursively walk the directories in a file system, starting with root.
    172 
    173     Behaviour is very similar to os.walk from the standard os module, yielding
    174     (base, dirs, files) recursively, where |base| is the base path of |files|,
    175     |dirs| relative to |root|, and |files| and |dirs| the list of files/dirs in
    176     |base| respectively. If |depth| is specified and greater than 0, Walk will
    177     only recurse |depth| times.
    178 
    179     |file_lister|, if specified, should be a callback of signature
    180 
    181       def my_file_lister(root):,
    182 
    183     which returns a tuple (dirs, files), where |dirs| is a list of directory
    184     names under |root|, and |files| is a list of file names under |root|. Note
    185     that the listing of files and directories should be for a *single* level
    186     only, i.e. it should not recursively list anything.
    187 
    188     Note that directories will always end with a '/', files never will.
    189 
    190     If |root| cannot be found, raises a FileNotFoundError.
    191     For any other failure, raises a FileSystemError.
    192     '''
    193     AssertIsDirectory(root)
    194     basepath = root
    195 
    196     def walk(root, depth):
    197       if depth == 0:
    198         return
    199       AssertIsDirectory(root)
    200 
    201       if file_lister:
    202         dirs, files = file_lister(root)
    203       else:
    204         dirs, files = [], []
    205         for f in self.ReadSingle(root).Get():
    206           if IsDirectory(f):
    207             dirs.append(f)
    208           else:
    209             files.append(f)
    210 
    211       yield root[len(basepath):].rstrip('/'), dirs, files
    212 
    213       for d in dirs:
    214         for walkinfo in walk(root + d, depth - 1):
    215           yield walkinfo
    216 
    217     for walkinfo in walk(root, depth):
    218       yield walkinfo
    219 
    220   def __eq__(self, other):
    221     return (isinstance(other, FileSystem) and
    222             self.GetIdentity() == other.GetIdentity())
    223 
    224   def __ne__(self, other):
    225     return not (self == other)
    226 
    227   def __repr__(self):
    228     return '<%s>' % type(self).__name__
    229 
    230   def __str__(self):
    231     return repr(self)
    232