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