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.
8 from future import Future
9 from path_util import (
10 AssertIsDirectory, AssertIsValid, IsDirectory, IsValid, SplitParent,
14 class _BaseFileSystemException(Exception):
15 def __init__(self, message):
16 Exception.__init__(self, message)
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)
25 class FileNotFoundError(_BaseFileSystemException):
26 '''Raised when a file isn't found for read or stat.
28 def __init__(self, filename):
29 _BaseFileSystemException.__init__(self, filename)
32 class FileSystemError(_BaseFileSystemException):
33 '''Raised on when there are errors reading or statting files, such as a
36 def __init__(self, filename):
37 _BaseFileSystemException.__init__(self, filename)
40 class StatInfo(object):
41 '''The result of calling Stat on a FileSystem.
43 def __init__(self, version, child_versions=None):
45 assert all(IsValid(path) for path in child_versions.iterkeys()), \
47 self.version = version
48 self.child_versions = child_versions
50 def __eq__(self, other):
51 return (isinstance(other, StatInfo) and
52 self.version == other.version and
53 self.child_versions == other.child_versions)
55 def __ne__(self, other):
56 return not (self == other)
59 return '{version: %s, child_versions: %s}' % (self.version,
66 class FileSystem(object):
67 '''A FileSystem interface that can read files and directories.
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.
74 The contents will be a str.
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()
83 For any other failure, raises a FileSystemError.
85 raise NotImplementedError(self.__class__)
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().
92 read_single = self.Read([path])
93 return Future(callback=lambda: read_single.Get()[path])
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.
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.
105 # There is always a root directory.
106 return Future(value=True)
108 parent, base = SplitParent(path)
110 if isinstance(error, FileNotFoundError):
113 return self.ReadSingle(ToDirectory(parent)).Then(lambda l: base in l,
117 '''Asynchronously refreshes the content of the FileSystem, returning a
118 future to its completion.
120 raise NotImplementedError(self.__class__)
122 # TODO(cduvall): Allow Stat to take a list of paths like Read.
123 def Stat(self, path):
124 '''Returns a |StatInfo| object containing the version of |path|. If |path|
125 is a directory, |StatInfo| will have the versions of all the children of
126 the directory in |StatInfo.child_versions|.
128 If the path cannot be found, raises a FileNotFoundError.
129 For any other failure, raises a FileSystemError.
131 raise NotImplementedError(self.__class__)
133 def StatAsync(self, path):
134 '''Bandaid for a lack of an async Stat function. Stat() should be async
135 by default but for now just let implementations override this if they like.
137 return Future(callback=lambda: self.Stat(path))
139 def GetIdentity(self):
140 '''The identity of the file system, exposed for caching classes to
141 namespace their caches. this will usually depend on the configuration of
142 that file system - e.g. a LocalFileSystem with a base path of /var is
143 different to that of a SubversionFileSystem with a base path of /bar, is
144 different to a LocalFileSystem with a base path of /usr.
146 raise NotImplementedError(self.__class__)
148 def Walk(self, root):
149 '''Recursively walk the directories in a file system, starting with root.
151 Behaviour is very similar to os.walk from the standard os module, yielding
152 (base, dirs, files) recursively, where |base| is the base path of |files|,
153 |dirs| relative to |root|, and |files| and |dirs| the list of files/dirs in
156 Note that directories will always end with a '/', files never will.
158 If |root| cannot be found, raises a FileNotFoundError.
159 For any other failure, raises a FileSystemError.
161 AssertIsDirectory(root)
165 AssertIsDirectory(root)
168 for f in self.ReadSingle(root).Get():
174 yield root[len(basepath):].rstrip('/'), dirs, files
177 for walkinfo in walk(root + d):
180 for walkinfo in walk(root):
183 def __eq__(self, other):
184 return (isinstance(other, FileSystem) and
185 self.GetIdentity() == other.GetIdentity())
187 def __ne__(self, other):
188 return not (self == other)
191 return '<%s>' % type(self).__name__