# Copyright 2013 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import logging import os import posixpath import traceback from branch_utility import BranchUtility from file_system import FileNotFoundError from third_party.json_schema_compiler.model import UnixName import svn_constants def _SimplifyFileName(file_name): return (posixpath.splitext(file_name)[0] .lower() .replace('.', '') .replace('-', '') .replace('_', '')) class PathCanonicalizer(object): '''Transforms paths into their canonical forms. Since the dev server has had many incarnations - e.g. there didn't use to be apps/ - there may be old paths lying around the webs. We try to redirect those to where they are now. ''' def __init__(self, compiled_fs_factory): # Map of simplified API names (for typo detection) to their real paths. def make_public_apis(_, file_names): return dict((_SimplifyFileName(name), name) for name in file_names) self._public_apis = compiled_fs_factory.Create(make_public_apis, PathCanonicalizer) def Canonicalize(self, path): '''Returns the canonical path for |path|, and whether that path is a permanent canonicalisation (e.g. when we redirect from a channel to a channel-less URL) or temporary (e.g. when we redirect from an apps-only API to an extensions one - we may at some point enable it for extensions). ''' class ReturnType(object): def __init__(self, path, permanent): self.path = path self.permanent = permanent # Catch incorrect comparisons by disabling ==/!=. def __eq__(self, _): raise NotImplementedError() def __ne__(self, _): raise NotImplementedError() # Strip any channel info off it. There are no channels anymore. for channel_name in BranchUtility.GetAllChannelNames(): channel_prefix = channel_name + '/' if path.startswith(channel_prefix): # Redirect now so that we can set the permanent-redirect bit. Channel # redirects are the only things that should be permanent redirects; # anything else *could* change, so is temporary. return ReturnType(path[len(channel_prefix):], True) # No further work needed for static. if path.startswith('static/'): return ReturnType(path, False) # People go to just "extensions" or "apps". Redirect to the directory. if path in ('extensions', 'apps'): return ReturnType(path + '/', False) # The rest of this function deals with trying to figure out what API page # for extensions/apps to redirect to, if any. We see a few different cases # here: # - Unqualified names ("browserAction.html"). These are easy to resolve; # figure out whether it's an extension or app API and redirect. # - but what if it's both? Well, assume extensions. Maybe later we can # check analytics and see which is more popular. # - Wrong names ("apps/browserAction.html"). This really does happen, # damn it, so do the above logic but record which is the default. if path.startswith(('extensions/', 'apps/')): default_platform, reference_path = path.split('/', 1) else: default_platform, reference_path = ('extensions', path) try: apps_public = self._public_apis.GetFromFileListing( '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'apps'))) extensions_public = self._public_apis.GetFromFileListing( '/'.join((svn_constants.PUBLIC_TEMPLATE_PATH, 'extensions'))) except FileNotFoundError: # Probably offline. logging.warning(traceback.format_exc()) return ReturnType(path, False) simple_reference_path = _SimplifyFileName(reference_path) apps_path = apps_public.get(simple_reference_path) extensions_path = extensions_public.get(simple_reference_path) if apps_path is None: if extensions_path is None: # No idea. Just return the original path. It'll probably 404. pass else: path = 'extensions/%s' % extensions_path else: if extensions_path is None: path = 'apps/%s' % apps_path else: assert apps_path == extensions_path path = '%s/%s' % (default_platform, apps_path) return ReturnType(path, False)