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