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 Future
11 class PatchedFileSystem(FileSystem
):
12 ''' Class to fetch resources with a patch applied.
14 def __init__(self
, base_file_system
, patcher
):
15 self
._base
_file
_system
= base_file_system
16 self
._patcher
= patcher
18 def Read(self
, paths
, skip_not_found
=False):
20 added
, deleted
, modified
= self
._patcher
.GetPatchedFiles()
21 if set(paths
) & set(deleted
):
22 def raise_file_not_found():
23 raise FileNotFoundError('Files are removed from the patch.')
24 return Future(callback
=raise_file_not_found
)
26 patched_files |
= (set(added
) |
set(modified
))
27 dir_paths
= set(path
for path
in paths
if path
.endswith('/'))
28 file_paths
= set(paths
) - dir_paths
29 patched_paths
= file_paths
& patched_files
30 unpatched_paths
= file_paths
- patched_files
32 def patch_directory_listing(path
, original_listing
):
33 added
, deleted
, modified
= (
34 self
._GetDirectoryListingFromPatch
(path
))
35 if original_listing
is None:
37 raise FileNotFoundError('Directory %s not found in the patch.' % path
)
39 return list((set(original_listing
) |
set(added
)) - set(deleted
))
42 dirs_value
= self
._TryReadDirectory
(dir_paths
)
43 files
.update(self
._patcher
.Apply(patched_paths
,
44 self
._base
_file
_system
).Get())
45 files
.update(dict((path
, patch_directory_listing(path
, dirs_value
[path
]))
46 for path
in dirs_value
))
48 return self
._base
_file
_system
.Read(unpatched_paths
,
49 skip_not_found
=skip_not_found
).Then(next
)
52 return self
._base
_file
_system
.Refresh()
54 ''' Given the list of patched files, it's not possible to determine whether
55 a directory to read exists in self._base_file_system. So try reading each one
56 and handle FileNotFoundError.
58 def _TryReadDirectory(self
, paths
):
61 assert path
.endswith('/')
63 value
[path
] = self
._base
_file
_system
.ReadSingle(path
).Get()
64 except FileNotFoundError
:
68 def _GetDirectoryListingFromPatch(self
, path
):
69 assert path
.endswith('/')
70 def _FindChildrenInPath(files
, path
):
73 if f
.startswith(path
):
74 child_path
= f
[len(path
):]
76 child_name
= child_path
[0:child_path
.find('/') + 1]
78 child_name
= child_path
79 result
.append(child_name
)
82 added
, deleted
, modified
= (tuple(
83 _FindChildrenInPath(files
, path
)
84 for files
in self
._patcher
.GetPatchedFiles()))
86 # A patch applies to files only. It cannot delete directories.
87 deleted_files
= [child
for child
in deleted
if not child
.endswith('/')]
88 # However, these directories are actually modified because their children
90 modified
+= [child
for child
in deleted
if child
.endswith('/')]
92 return (added
, deleted_files
, modified
)
94 def _PatchStat(self
, stat_info
, version
, added
, deleted
, modified
):
95 assert len(added
) + len(deleted
) + len(modified
) > 0
96 assert stat_info
.child_versions
is not None
98 # Deep copy before patching to make sure it doesn't interfere with values
100 stat_info
= deepcopy(stat_info
)
102 stat_info
.version
= version
103 for child
in added
+ modified
:
104 stat_info
.child_versions
[child
] = version
105 for child
in deleted
:
106 if stat_info
.child_versions
.get(child
):
107 del stat_info
.child_versions
[child
]
111 def Stat(self
, path
):
112 version
= self
._patcher
.GetVersion()
113 assert version
is not None
114 version
= 'patched_%s' % version
116 directory
, filename
= path
.rsplit('/', 1)
117 added
, deleted
, modified
= self
._GetDirectoryListingFromPatch
(
121 # There are new files added. It's possible (if |directory| is new) that
122 # self._base_file_system.Stat will throw an exception.
124 stat_info
= self
._PatchStat
(
125 self
._base
_file
_system
.Stat(directory
+ '/'),
130 except FileNotFoundError
:
131 stat_info
= StatInfo(
133 dict((child
, version
) for child
in added
+ modified
))
134 elif len(deleted
) + len(modified
) > 0:
135 # No files were added.
136 stat_info
= self
._PatchStat
(self
._base
_file
_system
.Stat(directory
+ '/'),
142 # No changes are made in this directory.
143 return self
._base
_file
_system
.Stat(path
)
145 if stat_info
.child_versions
is not None:
147 if filename
in stat_info
.child_versions
:
148 stat_info
= StatInfo(stat_info
.child_versions
[filename
])
150 raise FileNotFoundError('%s was not in child versions' % filename
)
153 def GetIdentity(self
):
154 return '%s(%s,%s)' % (self
.__class
__.__name
__,
155 self
._base
_file
_system
.GetIdentity(),
156 self
._patcher
.GetIdentity())