file_system.py revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
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 5from future import Gettable, Future 6 7 8class FileNotFoundError(Exception): 9 '''Raised when a file isn't found for read or stat. 10 ''' 11 def __init__(self, filename): 12 Exception.__init__(self, filename) 13 14class FileSystemError(Exception): 15 '''Raised on when there are errors reading or statting files, such as a 16 network timeout. 17 ''' 18 def __init__(self, filename): 19 Exception.__init__(self, filename) 20 21class StatInfo(object): 22 '''The result of calling Stat on a FileSystem. 23 ''' 24 def __init__(self, version, child_versions=None): 25 self.version = version 26 self.child_versions = child_versions 27 28 def __eq__(self, other): 29 return (isinstance(other, StatInfo) and 30 self.version == other.version and 31 self.child_versions == other.child_versions) 32 33 def __ne__(self, other): 34 return not (self == other) 35 36 def __str__(self): 37 return '{version: %s, child_versions: %s}' % (self.version, 38 self.child_versions) 39 40 def __repr__(self): 41 return str(self) 42 43def ToUnicode(data): 44 '''Returns the str |data| as a unicode object. It's expected to be utf8, but 45 there are also latin-1 encodings in there for some reason. Fall back to that. 46 ''' 47 try: 48 return unicode(data, 'utf-8') 49 except: 50 return unicode(data, 'latin-1') 51 52class FileSystem(object): 53 '''A FileSystem interface that can read files and directories. 54 ''' 55 def Read(self, paths, binary=False): 56 '''Reads each file in paths and returns a dictionary mapping the path to the 57 contents. If a path in paths ends with a '/', it is assumed to be a 58 directory, and a list of files in the directory is mapped to the path. 59 60 If binary=False, the contents of each file will be unicode parsed as utf-8, 61 and failing that as latin-1 (some extension docs use latin-1). If 62 binary=True then the contents will be a str. 63 64 If any path cannot be found, raises a FileNotFoundError. This is guaranteed 65 to only happen once the Future has been resolved (Get() called). 66 67 For any other failure, raises a FileSystemError. 68 ''' 69 raise NotImplementedError(self.__class__) 70 71 def ReadSingle(self, path, binary=False): 72 '''Reads a single file from the FileSystem. Returns a Future with the same 73 rules as Read(). 74 ''' 75 read_single = self.Read([path], binary=binary) 76 return Future(delegate=Gettable(lambda: read_single.Get()[path])) 77 78 def Refresh(self): 79 raise NotImplementedError(self.__class__) 80 81 # TODO(cduvall): Allow Stat to take a list of paths like Read. 82 def Stat(self, path): 83 '''Returns a |StatInfo| object containing the version of |path|. If |path| 84 is a directory, |StatInfo| will have the versions of all the children of 85 the directory in |StatInfo.child_versions|. 86 87 If the path cannot be found, raises a FileNotFoundError. 88 For any other failure, raises a FileSystemError. 89 ''' 90 raise NotImplementedError(self.__class__) 91 92 def GetIdentity(self): 93 '''The identity of the file system, exposed for caching classes to 94 namespace their caches. this will usually depend on the configuration of 95 that file system - e.g. a LocalFileSystem with a base path of /var is 96 different to that of a SubversionFileSystem with a base path of /bar, is 97 different to a LocalFileSystem with a base path of /usr. 98 ''' 99 raise NotImplementedError(self.__class__) 100 101 def Walk(self, root): 102 '''Recursively walk the directories in a file system, starting with root. 103 Emulates os.walk from the standard os module. 104 105 If the root cannot be found, raises a FileNotFoundError. 106 For any other failure, raises a FileSystemError. 107 ''' 108 basepath = root.rstrip('/') + '/' 109 110 def walk(root): 111 if not root.endswith('/'): 112 root += '/' 113 114 dirs, files = [], [] 115 116 for f in self.ReadSingle(root).Get(): 117 if f.endswith('/'): 118 dirs.append(f) 119 else: 120 files.append(f) 121 122 yield root[len(basepath):].rstrip('/'), dirs, files 123 124 for d in dirs: 125 for walkinfo in walk(root + d): 126 yield walkinfo 127 128 for walkinfo in walk(root): 129 yield walkinfo 130 131 def __repr__(self): 132 return '<%s>' % type(self).__name__ 133 134 def __str__(self): 135 return repr(self) 136