| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 from collections import defaultdict | 5 from collections import defaultdict |
| 6 import posixpath | 6 import posixpath |
| 7 | 7 |
| 8 from path_util import SplitParent | 8 from path_util import SplitParent |
| 9 from special_paths import SITE_VERIFICATION_FILE |
| 9 | 10 |
| 10 | 11 |
| 11 def _SimplifyFileName(file_name): | 12 def _SimplifyFileName(file_name): |
| 12 return (posixpath.splitext(file_name)[0] | 13 return (posixpath.splitext(file_name)[0] |
| 13 .lower() | 14 .lower() |
| 14 .replace('.', '') | 15 .replace('.', '') |
| 15 .replace('-', '') | 16 .replace('-', '') |
| 16 .replace('_', '')) | 17 .replace('_', '')) |
| 17 | 18 |
| 18 | 19 |
| 19 class PathCanonicalizer(object): | 20 class PathCanonicalizer(object): |
| 20 '''Transforms paths into their canonical forms. Since the dev server has had | 21 '''Transforms paths into their canonical forms. Since the docserver has had |
| 21 many incarnations - e.g. there didn't use to be apps/ - there may be old | 22 many incarnations - e.g. there didn't use to be apps/ - there may be old |
| 22 paths lying around the webs. We try to redirect those to where they are now. | 23 paths lying around the webs. We try to redirect those to where they are now. |
| 23 ''' | 24 ''' |
| 24 def __init__(self, compiled_fs_factory, file_system): | 25 def __init__(self, |
| 26 file_system, |
| 27 object_store_creator, |
| 28 strip_extensions): |
| 29 # |strip_extensions| is a list of file extensions (e.g. .html) that should |
| 30 # be stripped for a path's canonical form. |
| 31 self._cache = object_store_creator.Create( |
| 32 PathCanonicalizer, category=file_system.GetIdentity()) |
| 25 self._file_system = file_system | 33 self._file_system = file_system |
| 26 # A lazily populated mapping of file names to a list of full paths that | 34 self._strip_extensions = strip_extensions |
| 27 # contain them. For example, | |
| 28 # - browserAction.html: [extensions/browserAction.html] | |
| 29 # - storage.html: [apps/storage.html, extensions/storage.html] | |
| 30 self._files_to_paths = None | |
| 31 | 35 |
| 32 def _GetPotentialPaths(self, filename): | 36 def _LoadCache(self): |
| 33 '''Returns the paths to any file called |filename|. | 37 cached = self._cache.GetMulti(('canonical_paths', |
| 34 ''' | 38 'simplified_paths_map')).Get() |
| 35 if self._files_to_paths is None: | 39 |
| 36 self._files_to_paths = defaultdict(list) | 40 # |canonical_paths| is the pre-calculated set of canonical paths. |
| 41 # |simplified_paths_map| is a lazily populated mapping of simplified file |
| 42 # names to a list of full paths that contain them. For example, |
| 43 # - browseraction: [extensions/browserAction.html] |
| 44 # - storage: [apps/storage.html, extensions/storage.html] |
| 45 canonical_paths, simplified_paths_map = ( |
| 46 cached.get('canonical_paths'), cached.get('simplified_paths_map')) |
| 47 |
| 48 if canonical_paths is None: |
| 49 assert simplified_paths_map is None |
| 50 canonical_paths = set() |
| 51 simplified_paths_map = defaultdict(list) |
| 37 for base, dirs, files in self._file_system.Walk(''): | 52 for base, dirs, files in self._file_system.Walk(''): |
| 38 for f in dirs + files: | 53 for path in dirs + files: |
| 39 self._files_to_paths[_SimplifyFileName(f)].append( | 54 path_without_ext, ext = posixpath.splitext(path) |
| 40 posixpath.join(base, f)) | 55 canonical_path = posixpath.join(base, path_without_ext) |
| 41 return self._files_to_paths.get(_SimplifyFileName(filename)) | 56 if (ext not in self._strip_extensions or |
| 57 path == SITE_VERIFICATION_FILE): |
| 58 canonical_path += ext |
| 59 canonical_paths.add(canonical_path) |
| 60 simplified_paths_map[_SimplifyFileName(path)].append(canonical_path) |
| 61 # Store |simplified_paths_map| sorted. Ties in length are broken by taking |
| 62 # the shortest, lexicographically smallest path. |
| 63 for path_list in simplified_paths_map.itervalues(): |
| 64 path_list.sort(key=lambda p: (len(p), p)) |
| 65 self._cache.SetMulti({ |
| 66 'canonical_paths': canonical_paths, |
| 67 'simplified_paths_map': simplified_paths_map, |
| 68 }) |
| 69 else: |
| 70 assert simplified_paths_map is not None |
| 71 |
| 72 return canonical_paths, simplified_paths_map |
| 42 | 73 |
| 43 def Canonicalize(self, path): | 74 def Canonicalize(self, path): |
| 44 '''Returns the canonical path for |path|. | 75 '''Returns the canonical path for |path|. |
| 45 ''' | 76 ''' |
| 77 canonical_paths, simplified_paths_map = self._LoadCache() |
| 78 |
| 46 # Path may already be the canonical path. | 79 # Path may already be the canonical path. |
| 47 if self._file_system.Exists(path).Get(): | 80 if path in canonical_paths: |
| 48 return path | 81 return path |
| 49 | 82 |
| 50 # Path not found. Our single heuristic: find |basename| in the directory | 83 # Path not found. Our single heuristic: find |base| in the directory |
| 51 # structure with the longest common prefix of |path|. | 84 # structure with the longest common prefix of |path|. |
| 52 _, base = SplitParent(path) | 85 _, base = SplitParent(path) |
| 53 potential_paths = self._GetPotentialPaths(base) | 86 potential_paths = simplified_paths_map.get(_SimplifyFileName(base)) |
| 54 if not potential_paths: | 87 if not potential_paths: |
| 55 # There is no file with that name. | 88 # There is no file with anything close to that name. |
| 56 return path | 89 return path |
| 57 | 90 |
| 58 # The most likely canonical file is the one with the longest common prefix. | 91 # The most likely canonical file is the one with the longest common prefix |
| 59 # This is slightly weaker than it could be; |path| is compared, not the | 92 # with |path|. This is slightly weaker than it could be; |path| is |
| 60 # simplified form of |path|, which may matter. Ties in length are broken by | 93 # compared, not the simplified form of |path|, which may matter. |
| 61 # taking the shortest, lexicographically smallest path. | |
| 62 potential_paths.sort(key=lambda p: (len(p), p)) | |
| 63 max_prefix = potential_paths[0] | 94 max_prefix = potential_paths[0] |
| 64 max_prefix_length = len(posixpath.commonprefix((max_prefix, path))) | 95 max_prefix_length = len(posixpath.commonprefix((max_prefix, path))) |
| 65 for path_for_file in potential_paths[1:]: | 96 for path_for_file in potential_paths[1:]: |
| 66 prefix_length = len(posixpath.commonprefix((path_for_file, path))) | 97 prefix_length = len(posixpath.commonprefix((path_for_file, path))) |
| 67 if prefix_length > max_prefix_length: | 98 if prefix_length > max_prefix_length: |
| 68 max_prefix, max_prefix_length = path_for_file, prefix_length | 99 max_prefix, max_prefix_length = path_for_file, prefix_length |
| 69 | 100 |
| 70 return max_prefix | 101 return max_prefix |
| OLD | NEW |