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 class _BaseFileSystemException(Exception):
     15   def __init__(self, message):
     16     Exception.__init__(self, message)
     17 
     18   @classmethod
     19   def RaiseInFuture(cls, message):
     20     stack = traceback.format_stack()
     21     def boom(): raise cls('%s. Creation stack:\n%s' % (message, ''.join(stack)))
     22     return Future(callback=boom)
     23 
     24 
     25 class FileNotFoundError(_BaseFileSystemException):
     26   '''Raised when a file isn't found for read or stat.
     27   '''
     28   def __init__(self, filename):
     29     _BaseFileSystemException.__init__(self, filename)
     30 
     31 
     32 class FileSystemError(_BaseFileSystemException):
     33   '''Raised on when there are errors reading or statting files, such as a
     34   network timeout.
     35   '''
     36   def __init__(self, filename):
     37     _BaseFileSystemException.__init__(self, filename)
     38 
     39 
     40 class StatInfo(object):
     41   '''The result of calling Stat on a FileSystem.
     42   '''
     43   def __init__(self, version, child_versions=None):
     44     if child_versions:
     45       assert all(IsValid(path) for path in child_versions.iterkeys()), \
     46              child_versions
     47     self.version = version
     48     self.child_versions = child_versions
     49 
     50   def __eq__(self, other):
     51     return (isinstance(other, StatInfo) and
     52             self.version == other.version and
     53             self.child_versions == other.child_versions)
     54 
     55   def __ne__(self, other):
     56     return not (self == other)
     57 
     58   def __str__(self):
     59     return '{version: %s, child_versions: %s}' % (self.version,
     60                                                   self.child_versions)
     61 
     62   def __repr__(self):
     63     return str(self)
     64 
     65 
     66 class FileSystem(object):
     67   '''A FileSystem interface that can read files and directories.
     68   '''
     69   def Read(self, paths, skip_not_found=False):
     70     '''Reads each file in paths and returns a dictionary mapping the path to the
     71     contents. If a path in paths ends with a '/', it is assumed to be a
     72     directory, and a list of files in the directory is mapped to the path.
     73 
     74     The contents will be a str.
     75 
     76     If any path cannot be found:
     77       - If |skip_not_found| is True, the resulting object will not contain any
     78         mapping for that path.
     79       - Otherwise, and by default, a FileNotFoundError is raised. This is
     80         guaranteed to only happen once the Future has been resolved (Get()
     81         called).
     82 
     83     For any other failure, raises a FileSystemError.
     84     '''
     85     raise NotImplementedError(self.__class__)
     86 
     87   def ReadSingle(self, path):
     88     '''Reads a single file from the FileSystem. Returns a Future with the same
     89     rules as Read(). If |path| is not found raise a FileNotFoundError on Get().
     90     '''
     91     AssertIsValid(path)
     92     read_single = self.Read([path])
     93     return Future(callback=lambda: read_single.Get()[path])
     94 
     95   def Exists(self, path):
     96     '''Returns a Future to the existence of |path|; True if |path| exists,
     97     False if not. This method will not throw a FileNotFoundError unlike
     98     the Read* methods, however it may still throw a FileSystemError.
     99 
    100     There are several ways to implement this method via the interface but this
    101     method exists to do so in a canonical and most efficient way for caching.
    102     '''
    103     AssertIsValid(path)
    104     if path == '':
    105       # There is always a root directory.
    106       return Future(value=True)
    107 
    108     parent, base = SplitParent(path)
    109     list_future = self.ReadSingle(ToDirectory(parent))
    110     def resolve():
    111       try:
    112         return base in list_future.Get()
    113       except FileNotFoundError:
    114         return False
    115     return Future(callback=resolve)
    116 
    117   def Refresh(self):
    118     '''Asynchronously refreshes the content of the FileSystem, returning a
    119     future to its completion.
    120     '''
    121     raise NotImplementedError(self.__class__)
    122 
    123   # TODO(cduvall): Allow Stat to take a list of paths like Read.
    124   def Stat(self, path):
    125     '''Returns a |StatInfo| object containing the version of |path|. If |path|
    126     is a directory, |StatInfo| will have the versions of all the children of
    127     the directory in |StatInfo.child_versions|.
    128 
    129     If the path cannot be found, raises a FileNotFoundError.
    130     For any other failure, raises a FileSystemError.
    131     '''
    132     raise NotImplementedError(self.__class__)
    133 
    134   def StatAsync(self, path):
    135     '''Bandaid for a lack of an async Stat function. Stat() should be async
    136     by default but for now just let implementations override this if they like.
    137     '''
    138     return Future(callback=lambda: self.Stat(path))
    139 
    140   def GetIdentity(self):
    141     '''The identity of the file system, exposed for caching classes to
    142     namespace their caches. this will usually depend on the configuration of
    143     that file system - e.g. a LocalFileSystem with a base path of /var is
    144     different to that of a SubversionFileSystem with a base path of /bar, is
    145     different to a LocalFileSystem with a base path of /usr.
    146     '''
    147     raise NotImplementedError(self.__class__)
    148 
    149   def Walk(self, root):
    150     '''Recursively walk the directories in a file system, starting with root.
    151 
    152     Behaviour is very similar to os.walk from the standard os module, yielding
    153     (base, dirs, files) recursively, where |base| is the base path of |files|,
    154     |dirs| relative to |root|, and |files| and |dirs| the list of files/dirs in
    155     |base| respectively.
    156 
    157     Note that directories will always end with a '/', files never will.
    158 
    159     If |root| cannot be found, raises a FileNotFoundError.
    160     For any other failure, raises a FileSystemError.
    161     '''
    162     AssertIsDirectory(root)
    163     basepath = root
    164 
    165     def walk(root):
    166       AssertIsDirectory(root)
    167       dirs, files = [], []
    168 
    169       for f in self.ReadSingle(root).Get():
    170         if IsDirectory(f):
    171           dirs.append(f)
    172         else:
    173           files.append(f)
    174 
    175       yield root[len(basepath):].rstrip('/'), dirs, files
    176 
    177       for d in dirs:
    178         for walkinfo in walk(root + d):
    179           yield walkinfo
    180 
    181     for walkinfo in walk(root):
    182       yield walkinfo
    183 
    184   def __eq__(self, other):
    185     return (isinstance(other, FileSystem) and
    186             self.GetIdentity() == other.GetIdentity())
    187 
    188   def __ne__(self, other):
    189     return not (self == other)
    190 
    191   def __repr__(self):
    192     return '<%s>' % type(self).__name__
    193 
    194   def __str__(self):
    195     return repr(self)
    196