1# Copyright (C) 2010 Google Inc. All rights reserved. 2# 3# Redistribution and use in source and binary forms, with or without 4# modification, are permitted provided that the following conditions are 5# met: 6# 7# * Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# * Redistributions in binary form must reproduce the above 10# copyright notice, this list of conditions and the following disclaimer 11# in the documentation and/or other materials provided with the 12# distribution. 13# * Neither the name of Google Inc. nor the names of its 14# contributors may be used to endorse or promote products derived from 15# this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29"""generic routines to convert platform-specific paths to URIs.""" 30from __future__ import with_statement 31 32import atexit 33import subprocess 34import sys 35import threading 36import urllib 37 38 39def abspath_to_uri(path, platform=None): 40 """Converts a platform-specific absolute path to a file: URL.""" 41 if platform is None: 42 platform = sys.platform 43 return "file:" + _escape(_convert_path(path, platform)) 44 45 46def cygpath(path): 47 """Converts an absolute cygwin path to an absolute Windows path.""" 48 return _CygPath.convert_using_singleton(path) 49 50 51# Note that this object is not threadsafe and must only be called 52# from multiple threads under protection of a lock (as is done in cygpath()) 53class _CygPath(object): 54 """Manages a long-running 'cygpath' process for file conversion.""" 55 _lock = None 56 _singleton = None 57 58 @staticmethod 59 def stop_cygpath_subprocess(): 60 if not _CygPath._lock: 61 return 62 63 with _CygPath._lock: 64 if _CygPath._singleton: 65 _CygPath._singleton.stop() 66 67 @staticmethod 68 def convert_using_singleton(path): 69 if not _CygPath._lock: 70 _CygPath._lock = threading.Lock() 71 72 with _CygPath._lock: 73 if not _CygPath._singleton: 74 _CygPath._singleton = _CygPath() 75 # Make sure the cygpath subprocess always gets shutdown cleanly. 76 atexit.register(_CygPath.stop_cygpath_subprocess) 77 78 return _CygPath._singleton.convert(path) 79 80 def __init__(self): 81 self._child_process = None 82 83 def start(self): 84 assert(self._child_process is None) 85 args = ['cygpath', '-f', '-', '-wa'] 86 self._child_process = subprocess.Popen(args, 87 stdin=subprocess.PIPE, 88 stdout=subprocess.PIPE) 89 90 def is_running(self): 91 if not self._child_process: 92 return False 93 return self._child_process.returncode is None 94 95 def stop(self): 96 if self._child_process: 97 self._child_process.stdin.close() 98 self._child_process.wait() 99 self._child_process = None 100 101 def convert(self, path): 102 if not self.is_running(): 103 self.start() 104 self._child_process.stdin.write("%s\r\n" % path) 105 self._child_process.stdin.flush() 106 windows_path = self._child_process.stdout.readline().rstrip() 107 # Some versions of cygpath use lowercase drive letters while others 108 # use uppercase. We always convert to uppercase for consistency. 109 windows_path = '%s%s' % (windows_path[0].upper(), windows_path[1:]) 110 return windows_path 111 112 113def _escape(path): 114 """Handle any characters in the path that should be escaped.""" 115 # FIXME: web browsers don't appear to blindly quote every character 116 # when converting filenames to files. Instead of using urllib's default 117 # rules, we allow a small list of other characters through un-escaped. 118 # It's unclear if this is the best possible solution. 119 return urllib.quote(path, safe='/+:') 120 121 122def _convert_path(path, platform): 123 """Handles any os-specific path separators, mappings, etc.""" 124 if platform == 'win32': 125 return _winpath_to_uri(path) 126 if platform == 'cygwin': 127 return _winpath_to_uri(cygpath(path)) 128 return _unixypath_to_uri(path) 129 130 131def _winpath_to_uri(path): 132 """Converts a window absolute path to a file: URL.""" 133 return "///" + path.replace("\\", "/") 134 135 136def _unixypath_to_uri(path): 137 """Converts a unix-style path to a file: URL.""" 138 return "//" + path 139