Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / common / extensions / docs / server2 / file_system.py
blob1cbeb75ab07a22fc5a7b55b4f43a8978d9ee7aa6
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.
5 import posixpath
6 import traceback
8 from future import Future
9 from path_util import (
10 AssertIsDirectory, AssertIsValid, IsDirectory, IsValid, SplitParent,
11 ToDirectory)
14 def IsFileSystemThrottledError(error):
15 return type(error).__name__ == 'FileSystemThrottledError'
18 class _BaseFileSystemException(Exception):
19 def __init__(self, message):
20 Exception.__init__(self, message)
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)
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)
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)
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)
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
62 def __eq__(self, other):
63 return (isinstance(other, StatInfo) and
64 self.version == other.version and
65 self.child_versions == other.child_versions)
67 def __ne__(self, other):
68 return not (self == other)
70 def __str__(self):
71 return '{version: %s, child_versions: %s}' % (self.version,
72 self.child_versions)
74 def __repr__(self):
75 return str(self)
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.
86 The contents will be a str.
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).
95 For any other failure, raises a FileSystemError.
96 '''
97 raise NotImplementedError(self.__class__)
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.
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))
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.
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.
116 AssertIsValid(path)
117 if path == '':
118 # There is always a root directory.
119 return Future(value=True)
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)
129 def Refresh(self):
130 '''Asynchronously refreshes the content of the FileSystem, returning a
131 future to its completion.
133 raise NotImplementedError(self.__class__)
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.
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|.
143 If the path cannot be found, raises a FileNotFoundError.
144 For any other failure, raises a FileSystemError.
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__)
151 def StatAsync(self, path):
152 '''An async version of Stat. Returns a Future to a StatInfo rather than a
153 raw StatInfo.
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.
159 return Future(callback=lambda: self.Stat(path))
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.
168 raise NotImplementedError(self.__class__)
170 def GetVersion(self):
171 '''The version of the file system, exposed for more granular caching.
172 This may be any serializable data, though generally it should be a revision
173 number or hash string. The default implementation returns None, indicating
174 that the FileSystem is not versioned.
176 return None
178 def Walk(self, root, depth=-1, file_lister=None):
179 '''Recursively walk the directories in a file system, starting with root.
181 Behaviour is very similar to os.walk from the standard os module, yielding
182 (base, dirs, files) recursively, where |base| is the base path of |files|,
183 |dirs| relative to |root|, and |files| and |dirs| the list of files/dirs in
184 |base| respectively. If |depth| is specified and greater than 0, Walk will
185 only recurse |depth| times.
187 |file_lister|, if specified, should be a callback of signature
189 def my_file_lister(root):,
191 which returns a tuple (dirs, files), where |dirs| is a list of directory
192 names under |root|, and |files| is a list of file names under |root|. Note
193 that the listing of files and directories should be for a *single* level
194 only, i.e. it should not recursively list anything.
196 Note that directories will always end with a '/', files never will.
198 If |root| cannot be found, raises a FileNotFoundError.
199 For any other failure, raises a FileSystemError.
201 AssertIsDirectory(root)
202 basepath = root
204 def walk(root, depth):
205 if depth == 0:
206 return
207 AssertIsDirectory(root)
209 if file_lister:
210 dirs, files = file_lister(root)
211 else:
212 dirs, files = [], []
213 for f in self.ReadSingle(root).Get():
214 if IsDirectory(f):
215 dirs.append(f)
216 else:
217 files.append(f)
219 yield root[len(basepath):].rstrip('/'), dirs, files
221 for d in dirs:
222 for walkinfo in walk(root + d, depth - 1):
223 yield walkinfo
225 for walkinfo in walk(root, depth):
226 yield walkinfo
228 def __eq__(self, other):
229 return (isinstance(other, FileSystem) and
230 self.GetIdentity() == other.GetIdentity())
232 def __ne__(self, other):
233 return not (self == other)
235 def __repr__(self):
236 return '<%s>' % type(self).__name__
238 def __str__(self):
239 return repr(self)