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 import logging |
5 import os | 6 import os |
| 7 import posixpath |
| 8 import traceback |
| 9 |
| 10 from branch_utility import BranchUtility |
| 11 from file_system import FileNotFoundError |
6 from third_party.json_schema_compiler.model import UnixName | 12 from third_party.json_schema_compiler.model import UnixName |
7 import svn_constants | 13 import svn_constants |
8 | 14 |
| 15 def _SimplifyFileName(file_name): |
| 16 return (posixpath.splitext(file_name)[0] |
| 17 .lower() |
| 18 .replace('.', '') |
| 19 .replace('-', '') |
| 20 .replace('_', '')) |
| 21 |
9 class PathCanonicalizer(object): | 22 class PathCanonicalizer(object): |
10 '''Transforms paths into their canonical forms. Since the dev server has had | 23 '''Transforms paths into their canonical forms. Since the dev server has had |
11 many incarnations - e.g. there didn't use to be apps/ - there may be old | 24 many incarnations - e.g. there didn't use to be apps/ - there may be old |
12 paths lying around the webs. We try to redirect those to where they are now. | 25 paths lying around the webs. We try to redirect those to where they are now. |
13 ''' | 26 ''' |
14 def __init__(self, channel, compiled_fs_factory): | 27 def __init__(self, compiled_fs_factory): |
15 self._channel = channel | 28 # Map of simplified API names (for typo detection) to their real paths. |
16 self._identity_fs = compiled_fs_factory.CreateIdentity(PathCanonicalizer) | 29 def make_public_apis(_, file_names): |
| 30 return dict((_SimplifyFileName(name), name) for name in file_names) |
| 31 self._public_apis = compiled_fs_factory.Create(make_public_apis, |
| 32 PathCanonicalizer) |
17 | 33 |
18 def Canonicalize(self, path): | 34 def Canonicalize(self, path): |
19 starts_with_channel, path_without_channel = (False, path) | 35 '''Returns the canonical path for |path|, and whether that path is a |
20 if path.startswith('%s/' % self._channel): | 36 permanent canonicalisation (e.g. when we redirect from a channel to a |
21 starts_with_channel, path_without_channel = ( | 37 channel-less URL) or temporary (e.g. when we redirect from an apps-only API |
22 True, path[len(self._channel) + 1:]) | 38 to an extensions one - we may at some point enable it for extensions). |
| 39 ''' |
| 40 class ReturnType(object): |
| 41 def __init__(self, path, permanent): |
| 42 self.path = path |
| 43 self.permanent = permanent |
23 | 44 |
24 if any(path.startswith(prefix) | 45 # Catch incorrect comparisons by disabling ==/!=. |
25 for prefix in ('extensions/', 'apps/', 'static/')): | 46 def __eq__(self, _): raise NotImplementedError() |
26 return path | 47 def __ne__(self, _): raise NotImplementedError() |
27 | 48 |
28 if '/' in path_without_channel or path_without_channel == '404.html': | 49 # Strip any channel info off it. There are no channels anymore. |
29 return path | 50 for channel_name in BranchUtility.GetAllChannelNames(): |
| 51 channel_prefix = channel_name + '/' |
| 52 if path.startswith(channel_prefix): |
| 53 # Redirect now so that we can set the permanent-redirect bit. Channel |
| 54 # redirects are the only things that should be permanent redirects; |
| 55 # anything else *could* change, so is temporary. |
| 56 return ReturnType(path[len(channel_prefix):], True) |
30 | 57 |
31 apps_templates = self._identity_fs.GetFromFileListing( | 58 # No further work needed for static. |
32 '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'apps'))) | 59 if path.startswith('static/'): |
33 extensions_templates = self._identity_fs.GetFromFileListing( | 60 return ReturnType(path, False) |
34 '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'extensions'))) | |
35 | 61 |
36 if self._channel is None or not starts_with_channel: | 62 # People go to just "extensions" or "apps". Redirect to the directory. |
37 apps_path = 'apps/%s' % path_without_channel | 63 if path in ('extensions', 'apps'): |
38 extensions_path = 'extensions/%s' % path_without_channel | 64 return ReturnType(path + '/', False) |
| 65 |
| 66 # The rest of this function deals with trying to figure out what API page |
| 67 # for extensions/apps to redirect to, if any. We see a few different cases |
| 68 # here: |
| 69 # - Unqualified names ("browserAction.html"). These are easy to resolve; |
| 70 # figure out whether it's an extension or app API and redirect. |
| 71 # - but what if it's both? Well, assume extensions. Maybe later we can |
| 72 # check analytics and see which is more popular. |
| 73 # - Wrong names ("apps/browserAction.html"). This really does happen, |
| 74 # damn it, so do the above logic but record which is the default. |
| 75 if path.startswith(('extensions/', 'apps/')): |
| 76 default_platform, reference_path = path.split('/', 1) |
39 else: | 77 else: |
40 apps_path = '%s/apps/%s' % (self._channel, path_without_channel) | 78 default_platform, reference_path = ('extensions', path) |
41 extensions_path = '%s/extensions/%s' % (self._channel, | |
42 path_without_channel) | |
43 | 79 |
44 unix_path = UnixName(os.path.splitext(path_without_channel)[0]) | 80 try: |
45 if unix_path in extensions_templates: | 81 apps_public = self._public_apis.GetFromFileListing( |
46 return extensions_path | 82 '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'apps'))) |
47 if unix_path in apps_templates: | 83 extensions_public = self._public_apis.GetFromFileListing( |
48 return apps_path | 84 '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'extensions'))) |
49 return extensions_path | 85 except FileNotFoundError: |
| 86 # Probably offline. |
| 87 logging.warning(traceback.format_exc()) |
| 88 return ReturnType(path, False) |
| 89 |
| 90 simple_reference_path = _SimplifyFileName(reference_path) |
| 91 apps_path = apps_public.get(simple_reference_path) |
| 92 extensions_path = extensions_public.get(simple_reference_path) |
| 93 |
| 94 if apps_path is None: |
| 95 if extensions_path is None: |
| 96 # No idea. Just return the original path. It'll probably 404. |
| 97 pass |
| 98 else: |
| 99 path = 'extensions/%s' % extensions_path |
| 100 else: |
| 101 if extensions_path is None: |
| 102 path = 'apps/%s' % apps_path |
| 103 else: |
| 104 assert apps_path == extensions_path |
| 105 path = '%s/%s' % (default_platform, apps_path) |
| 106 |
| 107 return ReturnType(path, False) |
OLD | NEW |