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