summaryrefslogtreecommitdiffstats
path: root/chrome/common/extensions/docs/server2/file_system.py
blob: 99e33c5bbf7afbd166b6df577bbb2584be2e8600 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import posixpath
import traceback

from future import Future
from path_util import (
    AssertIsDirectory, AssertIsValid, IsDirectory, IsValid, SplitParent,
    ToDirectory)


class _BaseFileSystemException(Exception):
  def __init__(self, message):
    Exception.__init__(self, message)

  @classmethod
  def RaiseInFuture(cls, message):
    stack = traceback.format_stack()
    def boom(): raise cls('%s. Creation stack:\n%s' % (message, ''.join(stack)))
    return Future(callback=boom)


class FileNotFoundError(_BaseFileSystemException):
  '''Raised when a file isn't found for read or stat.
  '''
  def __init__(self, filename):
    _BaseFileSystemException.__init__(self, filename)


class FileSystemError(_BaseFileSystemException):
  '''Raised on when there are errors reading or statting files, such as a
  network timeout.
  '''
  def __init__(self, filename):
    _BaseFileSystemException.__init__(self, filename)


class StatInfo(object):
  '''The result of calling Stat on a FileSystem.
  '''
  def __init__(self, version, child_versions=None):
    if child_versions:
      assert all(IsValid(path) for path in child_versions.iterkeys()), \
             child_versions
    self.version = version
    self.child_versions = child_versions

  def __eq__(self, other):
    return (isinstance(other, StatInfo) and
            self.version == other.version and
            self.child_versions == other.child_versions)

  def __ne__(self, other):
    return not (self == other)

  def __str__(self):
    return '{version: %s, child_versions: %s}' % (self.version,
                                                  self.child_versions)

  def __repr__(self):
    return str(self)


class FileSystem(object):
  '''A FileSystem interface that can read files and directories.
  '''
  def Read(self, paths, skip_not_found=False):
    '''Reads each file in paths and returns a dictionary mapping the path to the
    contents. If a path in paths ends with a '/', it is assumed to be a
    directory, and a list of files in the directory is mapped to the path.

    The contents will be a str.

    If any path cannot be found:
      - If |skip_not_found| is True, the resulting object will not contain any
        mapping for that path.
      - Otherwise, and by default, a FileNotFoundError is raised. This is
        guaranteed to only happen once the Future has been resolved (Get()
        called).

    For any other failure, raises a FileSystemError.
    '''
    raise NotImplementedError(self.__class__)

  def ReadSingle(self, path):
    '''Reads a single file from the FileSystem. Returns a Future with the same
    rules as Read(). If |path| is not found raise a FileNotFoundError on Get().
    '''
    AssertIsValid(path)
    read_single = self.Read([path])
    return Future(callback=lambda: read_single.Get()[path])

  def Exists(self, path):
    '''Returns a Future to the existence of |path|; True if |path| exists,
    False if not. This method will not throw a FileNotFoundError unlike
    the Read* methods, however it may still throw a FileSystemError.

    There are several ways to implement this method via the interface but this
    method exists to do so in a canonical and most efficient way for caching.
    '''
    AssertIsValid(path)
    if path == '':
      # There is always a root directory.
      return Future(value=True)

    parent, base = SplitParent(path)
    list_future = self.ReadSingle(ToDirectory(parent))
    def resolve():
      try:
        return base in list_future.Get()
      except FileNotFoundError:
        return False
    return Future(callback=resolve)

  def Refresh(self):
    '''Asynchronously refreshes the content of the FileSystem, returning a
    future to its completion.
    '''
    raise NotImplementedError(self.__class__)

  # TODO(cduvall): Allow Stat to take a list of paths like Read.
  def Stat(self, path):
    '''Returns a |StatInfo| object containing the version of |path|. If |path|
    is a directory, |StatInfo| will have the versions of all the children of
    the directory in |StatInfo.child_versions|.

    If the path cannot be found, raises a FileNotFoundError.
    For any other failure, raises a FileSystemError.
    '''
    raise NotImplementedError(self.__class__)

  def StatAsync(self, path):
    '''Bandaid for a lack of an async Stat function. Stat() should be async
    by default but for now just let implementations override this if they like.
    '''
    return Future(callback=lambda: self.Stat(path))

  def GetIdentity(self):
    '''The identity of the file system, exposed for caching classes to
    namespace their caches. this will usually depend on the configuration of
    that file system - e.g. a LocalFileSystem with a base path of /var is
    different to that of a SubversionFileSystem with a base path of /bar, is
    different to a LocalFileSystem with a base path of /usr.
    '''
    raise NotImplementedError(self.__class__)

  def Walk(self, root):
    '''Recursively walk the directories in a file system, starting with root.

    Behaviour is very similar to os.walk from the standard os module, yielding
    (base, dirs, files) recursively, where |base| is the base path of |files|,
    |dirs| relative to |root|, and |files| and |dirs| the list of files/dirs in
    |base| respectively.

    Note that directories will always end with a '/', files never will.

    If |root| cannot be found, raises a FileNotFoundError.
    For any other failure, raises a FileSystemError.
    '''
    AssertIsDirectory(root)
    basepath = root

    def walk(root):
      AssertIsDirectory(root)
      dirs, files = [], []

      for f in self.ReadSingle(root).Get():
        if IsDirectory(f):
          dirs.append(f)
        else:
          files.append(f)

      yield root[len(basepath):].rstrip('/'), dirs, files

      for d in dirs:
        for walkinfo in walk(root + d):
          yield walkinfo

    for walkinfo in walk(root):
      yield walkinfo

  def __eq__(self, other):
    return (isinstance(other, FileSystem) and
            self.GetIdentity() == other.GetIdentity())

  def __ne__(self, other):
    return not (self == other)

  def __repr__(self):
    return '<%s>' % type(self).__name__

  def __str__(self):
    return repr(self)