Home | History | Annotate | Download | only in server2
      1 # Copyright 2013 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 from compiled_file_system import CompiledFileSystem
      6 from docs_server_utils import StringIdentity
      7 from file_system import FileNotFoundError
      8 from future import Future
      9 
     10 
     11 class ChainedCompiledFileSystem(object):
     12   '''A CompiledFileSystem implementation that fetches data from a chain of
     13   possible FileSystems. The chain consists of some number of FileSystems which
     14   may have cached data for their CompiledFileSystem instances (injected on
     15   Factory construction) + the main FileSystem (injected at Creation time).
     16 
     17   The expected configuration is that the main FileSystem is a PatchedFileSystem
     18   and the chain the FileSystem which it patches, but with file systems
     19   constructed via the HostFileSystemIterator the main FileSystems could be
     20   anything.
     21 
     22   This slightly unusual configuration is primarily needed to avoid re-compiling
     23   data for PatchedFileSystems, which are very similar to the FileSystem which
     24   they patch. Re-compiling data is expensive and a waste of memory resources.
     25   ChainedCompiledFileSystem shares the data.
     26   '''
     27   class Factory(CompiledFileSystem.Factory):
     28     def __init__(self, file_system_chain, object_store):
     29       self._file_system_chain = file_system_chain
     30       self._object_store = object_store
     31 
     32     def Create(self, file_system, populate_function, cls, category=None):
     33       return ChainedCompiledFileSystem(
     34           # Chain of CompiledFileSystem instances.
     35           tuple(CompiledFileSystem.Factory(self._object_store).Create(
     36                     fs, populate_function, cls, category=category)
     37                 for fs in [file_system] + self._file_system_chain),
     38           # Identity, as computed by all file systems.
     39           StringIdentity(*(fs.GetIdentity() for fs in self._file_system_chain)))
     40 
     41   def __init__(self, compiled_fs_chain, identity):
     42     '''|compiled_fs_chain| is a list of tuples (compiled_fs, file_system).
     43     '''
     44     assert len(compiled_fs_chain) > 0
     45     self._compiled_fs_chain = compiled_fs_chain
     46     self._identity = identity
     47 
     48   def GetFromFile(self, path):
     49     return self._GetImpl(
     50         path,
     51         lambda compiled_fs: compiled_fs.GetFromFile(path),
     52         lambda compiled_fs: compiled_fs.GetFileVersion(path))
     53 
     54   def GetFromFileListing(self, path):
     55     if not path.endswith('/'):
     56       path += '/'
     57     return self._GetImpl(
     58         path,
     59         lambda compiled_fs: compiled_fs.GetFromFileListing(path),
     60         lambda compiled_fs: compiled_fs.GetFileListingVersion(path))
     61 
     62   def _GetImpl(self, path, reader, version_getter):
     63     # Strategy: Get the current version of |path| in main FileSystem, then run
     64     # through |_compiled_fs_chain| in *reverse* to find the "oldest" FileSystem
     65     # with an up-to-date version of that file.
     66     #
     67     # Obviously, if files have been added in the main FileSystem then none of
     68     # the older FileSystems will be able to find it.
     69     read_futures = [(reader(compiled_fs), compiled_fs)
     70                     for compiled_fs in self._compiled_fs_chain]
     71 
     72     def resolve():
     73       try:
     74         first_compiled_fs = self._compiled_fs_chain[0]
     75         # The first file system contains both files of a newer version and
     76         # files shared with other compiled file systems. We are going to try
     77         # each compiled file system in the reverse order and return the data
     78         # when version matches. Data cached in other compiled file system will
     79         # be reused whenever possible so that we don't need to recompile things
     80         # that are not changed across these file systems.
     81         first_version = version_getter(first_compiled_fs)
     82         for read_future, compiled_fs in reversed(read_futures):
     83           if version_getter(compiled_fs) == first_version:
     84             return read_future.Get()
     85       except FileNotFoundError:
     86         pass
     87       # Try an arbitrary operation again to generate a realistic stack trace.
     88       return read_futures[0][0].Get()
     89 
     90     return Future(callback=resolve)
     91 
     92   def GetIdentity(self):
     93     return self._identity
     94