Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / common / extensions / docs / server2 / patched_file_system.py
blobed335cd455918ca38d8e0b6404e0f25a5b27ef25
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.
5 from copy import deepcopy
7 from file_system import FileSystem, StatInfo, FileNotFoundError
8 from future import Gettable, Future
10 class _AsyncFetchFuture(object):
11 def __init__(self,
12 unpatched_files_future,
13 patched_files_future,
14 dirs_value,
15 patched_file_system):
16 self._unpatched_files_future = unpatched_files_future
17 self._patched_files_future = patched_files_future
18 self._dirs_value = dirs_value
19 self._patched_file_system = patched_file_system
21 def Get(self):
22 files = self._unpatched_files_future.Get()
23 files.update(self._patched_files_future.Get())
24 files.update(
25 dict((path, self._PatchDirectoryListing(path, self._dirs_value[path]))
26 for path in self._dirs_value))
27 return files
29 def _PatchDirectoryListing(self, path, original_listing):
30 added, deleted, modified = (
31 self._patched_file_system._GetDirectoryListingFromPatch(path))
32 if original_listing is None:
33 if len(added) == 0:
34 raise FileNotFoundError('Directory %s not found in the patch.' % path)
35 return added
36 return list((set(original_listing) | set(added)) - set(deleted))
38 class PatchedFileSystem(FileSystem):
39 ''' Class to fetch resources with a patch applied.
40 '''
41 def __init__(self, base_file_system, patcher):
42 self._base_file_system = base_file_system
43 self._patcher = patcher
45 def Read(self, paths):
46 patched_files = set()
47 added, deleted, modified = self._patcher.GetPatchedFiles()
48 if set(paths) & set(deleted):
49 def raise_file_not_found():
50 raise FileNotFoundError('Files are removed from the patch.')
51 return Future(delegate=Gettable(raise_file_not_found))
52 patched_files |= (set(added) | set(modified))
53 dir_paths = set(path for path in paths if path.endswith('/'))
54 file_paths = set(paths) - dir_paths
55 patched_paths = file_paths & patched_files
56 unpatched_paths = file_paths - patched_files
57 return Future(delegate=_AsyncFetchFuture(
58 self._base_file_system.Read(unpatched_paths),
59 self._patcher.Apply(patched_paths, self._base_file_system),
60 self._TryReadDirectory(dir_paths),
61 self))
63 def Refresh(self):
64 return self._base_file_system.Refresh()
66 ''' Given the list of patched files, it's not possible to determine whether
67 a directory to read exists in self._base_file_system. So try reading each one
68 and handle FileNotFoundError.
69 '''
70 def _TryReadDirectory(self, paths):
71 value = {}
72 for path in paths:
73 assert path.endswith('/')
74 try:
75 value[path] = self._base_file_system.ReadSingle(path).Get()
76 except FileNotFoundError:
77 value[path] = None
78 return value
80 def _GetDirectoryListingFromPatch(self, path):
81 assert path.endswith('/')
82 def _FindChildrenInPath(files, path):
83 result = []
84 for f in files:
85 if f.startswith(path):
86 child_path = f[len(path):]
87 if '/' in child_path:
88 child_name = child_path[0:child_path.find('/') + 1]
89 else:
90 child_name = child_path
91 result.append(child_name)
92 return result
94 added, deleted, modified = (tuple(
95 _FindChildrenInPath(files, path)
96 for files in self._patcher.GetPatchedFiles()))
98 # A patch applies to files only. It cannot delete directories.
99 deleted_files = [child for child in deleted if not child.endswith('/')]
100 # However, these directories are actually modified because their children
101 # are patched.
102 modified += [child for child in deleted if child.endswith('/')]
104 return (added, deleted_files, modified)
106 def _PatchStat(self, stat_info, version, added, deleted, modified):
107 assert len(added) + len(deleted) + len(modified) > 0
108 assert stat_info.child_versions is not None
110 # Deep copy before patching to make sure it doesn't interfere with values
111 # cached in memory.
112 stat_info = deepcopy(stat_info)
114 stat_info.version = version
115 for child in added + modified:
116 stat_info.child_versions[child] = version
117 for child in deleted:
118 if stat_info.child_versions.get(child):
119 del stat_info.child_versions[child]
121 return stat_info
123 def Stat(self, path):
124 version = self._patcher.GetVersion()
125 assert version is not None
126 version = 'patched_%s' % version
128 directory, filename = path.rsplit('/', 1)
129 added, deleted, modified = self._GetDirectoryListingFromPatch(
130 directory + '/')
132 if len(added) > 0:
133 # There are new files added. It's possible (if |directory| is new) that
134 # self._base_file_system.Stat will throw an exception.
135 try:
136 stat_info = self._PatchStat(
137 self._base_file_system.Stat(directory + '/'),
138 version,
139 added,
140 deleted,
141 modified)
142 except FileNotFoundError:
143 stat_info = StatInfo(
144 version,
145 dict((child, version) for child in added + modified))
146 elif len(deleted) + len(modified) > 0:
147 # No files were added.
148 stat_info = self._PatchStat(self._base_file_system.Stat(directory + '/'),
149 version,
150 added,
151 deleted,
152 modified)
153 else:
154 # No changes are made in this directory.
155 return self._base_file_system.Stat(path)
157 if stat_info.child_versions is not None:
158 if filename:
159 if filename in stat_info.child_versions:
160 stat_info = StatInfo(stat_info.child_versions[filename])
161 else:
162 raise FileNotFoundError('%s was not in child versions' % filename)
163 return stat_info
165 def GetIdentity(self):
166 return '%s(%s,%s)' % (self.__class__.__name__,
167 self._base_file_system.GetIdentity(),
168 self._patcher.GetIdentity())