mercurial/byterange.py
changeset 28883 032c4c2f802a
parent 27504 ce3ae9ccd800
child 34131 0fa781320203
equal deleted inserted replaced
28882:800ec7c048b0 28883:032c4c2f802a
    24 import mimetypes
    24 import mimetypes
    25 import os
    25 import os
    26 import re
    26 import re
    27 import socket
    27 import socket
    28 import stat
    28 import stat
    29 import urllib
    29 
    30 import urllib2
    30 from . import (
    31 
    31     util,
    32 addclosehook = urllib.addclosehook
    32 )
    33 addinfourl = urllib.addinfourl
    33 
    34 splitattr = urllib.splitattr
    34 urlerr = util.urlerr
    35 splitpasswd = urllib.splitpasswd
    35 urlreq = util.urlreq
    36 splitport = urllib.splitport
    36 
    37 splituser = urllib.splituser
    37 addclosehook = urlreq.addclosehook
    38 unquote = urllib.unquote
    38 addinfourl = urlreq.addinfourl
       
    39 splitattr = urlreq.splitattr
       
    40 splitpasswd = urlreq.splitpasswd
       
    41 splitport = urlreq.splitport
       
    42 splituser = urlreq.splituser
       
    43 unquote = urlreq.unquote
    39 
    44 
    40 class RangeError(IOError):
    45 class RangeError(IOError):
    41     """Error raised when an unsatisfiable range is requested."""
    46     """Error raised when an unsatisfiable range is requested."""
    42     pass
    47     pass
    43 
    48 
    44 class HTTPRangeHandler(urllib2.BaseHandler):
    49 class HTTPRangeHandler(urlreq.basehandler):
    45     """Handler that enables HTTP Range headers.
    50     """Handler that enables HTTP Range headers.
    46 
    51 
    47     This was extremely simple. The Range header is a HTTP feature to
    52     This was extremely simple. The Range header is a HTTP feature to
    48     begin with so all this class does is tell urllib2 that the
    53     begin with so all this class does is tell urllib2 that the
    49     "206 Partial Content" response from the HTTP server is what we
    54     "206 Partial Content" response from the HTTP server is what we
    52     Example:
    57     Example:
    53         import urllib2
    58         import urllib2
    54         import byterange
    59         import byterange
    55 
    60 
    56         range_handler = range.HTTPRangeHandler()
    61         range_handler = range.HTTPRangeHandler()
    57         opener = urllib2.build_opener(range_handler)
    62         opener = urlreq.buildopener(range_handler)
    58 
    63 
    59         # install it
    64         # install it
    60         urllib2.install_opener(opener)
    65         urlreq.installopener(opener)
    61 
    66 
    62         # create Request and set Range header
    67         # create Request and set Range header
    63         req = urllib2.Request('http://www.python.org/')
    68         req = urlreq.request('http://www.python.org/')
    64         req.header['Range'] = 'bytes=30-50'
    69         req.header['Range'] = 'bytes=30-50'
    65         f = urllib2.urlopen(req)
    70         f = urlreq.urlopen(req)
    66     """
    71     """
    67 
    72 
    68     def http_error_206(self, req, fp, code, msg, hdrs):
    73     def http_error_206(self, req, fp, code, msg, hdrs):
    69         # 206 Partial Content Response
    74         # 206 Partial Content Response
    70         r = urllib.addinfourl(fp, hdrs, req.get_full_url())
    75         r = urlreq.addinfourl(fp, hdrs, req.get_full_url())
    71         r.code = code
    76         r.code = code
    72         r.msg = msg
    77         r.msg = msg
    73         return r
    78         return r
    74 
    79 
    75     def http_error_416(self, req, fp, code, msg, hdrs):
    80     def http_error_416(self, req, fp, code, msg, hdrs):
   202             buf = self.fo.read(bufsize)
   207             buf = self.fo.read(bufsize)
   203             if len(buf) != bufsize:
   208             if len(buf) != bufsize:
   204                 raise RangeError('Requested Range Not Satisfiable')
   209                 raise RangeError('Requested Range Not Satisfiable')
   205             pos += bufsize
   210             pos += bufsize
   206 
   211 
   207 class FileRangeHandler(urllib2.FileHandler):
   212 class FileRangeHandler(urlreq.filehandler):
   208     """FileHandler subclass that adds Range support.
   213     """FileHandler subclass that adds Range support.
   209     This class handles Range headers exactly like an HTTP
   214     This class handles Range headers exactly like an HTTP
   210     server would.
   215     server would.
   211     """
   216     """
   212     def open_local_file(self, req):
   217     def open_local_file(self, req):
   213         host = req.get_host()
   218         host = req.get_host()
   214         file = req.get_selector()
   219         file = req.get_selector()
   215         localfile = urllib.url2pathname(file)
   220         localfile = urlreq.url2pathname(file)
   216         stats = os.stat(localfile)
   221         stats = os.stat(localfile)
   217         size = stats[stat.ST_SIZE]
   222         size = stats[stat.ST_SIZE]
   218         modified = email.Utils.formatdate(stats[stat.ST_MTIME])
   223         modified = email.Utils.formatdate(stats[stat.ST_MTIME])
   219         mtype = mimetypes.guess_type(file)[0]
   224         mtype = mimetypes.guess_type(file)[0]
   220         if host:
   225         if host:
   221             host, port = urllib.splitport(host)
   226             host, port = urlreq.splitport(host)
   222             if port or socket.gethostbyname(host) not in self.get_names():
   227             if port or socket.gethostbyname(host) not in self.get_names():
   223                 raise urllib2.URLError('file not on local host')
   228                 raise urlerr.urlerror('file not on local host')
   224         fo = open(localfile,'rb')
   229         fo = open(localfile,'rb')
   225         brange = req.headers.get('Range', None)
   230         brange = req.headers.get('Range', None)
   226         brange = range_header_to_tuple(brange)
   231         brange = range_header_to_tuple(brange)
   227         assert brange != ()
   232         assert brange != ()
   228         if brange:
   233         if brange:
   234             size = (lb - fb)
   239             size = (lb - fb)
   235             fo = RangeableFileObject(fo, (fb, lb))
   240             fo = RangeableFileObject(fo, (fb, lb))
   236         headers = email.message_from_string(
   241         headers = email.message_from_string(
   237             'Content-Type: %s\nContent-Length: %d\nLast-Modified: %s\n' %
   242             'Content-Type: %s\nContent-Length: %d\nLast-Modified: %s\n' %
   238             (mtype or 'text/plain', size, modified))
   243             (mtype or 'text/plain', size, modified))
   239         return urllib.addinfourl(fo, headers, 'file:'+file)
   244         return urlreq.addinfourl(fo, headers, 'file:'+file)
   240 
   245 
   241 
   246 
   242 # FTP Range Support
   247 # FTP Range Support
   243 # Unfortunately, a large amount of base FTP code had to be copied
   248 # Unfortunately, a large amount of base FTP code had to be copied
   244 # from urllib and urllib2 in order to insert the FTP REST command.
   249 # from urllib and urllib2 in order to insert the FTP REST command.
   245 # Code modifications for range support have been commented as
   250 # Code modifications for range support have been commented as
   246 # follows:
   251 # follows:
   247 # -- range support modifications start/end here
   252 # -- range support modifications start/end here
   248 
   253 
   249 class FTPRangeHandler(urllib2.FTPHandler):
   254 class FTPRangeHandler(urlreq.ftphandler):
   250     def ftp_open(self, req):
   255     def ftp_open(self, req):
   251         host = req.get_host()
   256         host = req.get_host()
   252         if not host:
   257         if not host:
   253             raise IOError('ftp error', 'no host given')
   258             raise IOError('ftp error', 'no host given')
   254         host, port = splitport(host)
   259         host, port = splitport(host)
   268         passwd = unquote(passwd or '')
   273         passwd = unquote(passwd or '')
   269 
   274 
   270         try:
   275         try:
   271             host = socket.gethostbyname(host)
   276             host = socket.gethostbyname(host)
   272         except socket.error as msg:
   277         except socket.error as msg:
   273             raise urllib2.URLError(msg)
   278             raise urlerr.urlerror(msg)
   274         path, attrs = splitattr(req.get_selector())
   279         path, attrs = splitattr(req.get_selector())
   275         dirs = path.split('/')
   280         dirs = path.split('/')
   276         dirs = map(unquote, dirs)
   281         dirs = map(unquote, dirs)
   277         dirs, file = dirs[:-1], dirs[-1]
   282         dirs, file = dirs[:-1], dirs[-1]
   278         if dirs and not dirs[0]:
   283         if dirs and not dirs[0]:
   332 
   337 
   333     def connect_ftp(self, user, passwd, host, port, dirs):
   338     def connect_ftp(self, user, passwd, host, port, dirs):
   334         fw = ftpwrapper(user, passwd, host, port, dirs)
   339         fw = ftpwrapper(user, passwd, host, port, dirs)
   335         return fw
   340         return fw
   336 
   341 
   337 class ftpwrapper(urllib.ftpwrapper):
   342 class ftpwrapper(urlreq.ftpwrapper):
   338     # range support note:
   343     # range support note:
   339     # this ftpwrapper code is copied directly from
   344     # this ftpwrapper code is copied directly from
   340     # urllib. The only enhancement is to add the rest
   345     # urllib. The only enhancement is to add the rest
   341     # argument and pass it on to ftp.ntransfercmd
   346     # argument and pass it on to ftp.ntransfercmd
   342     def retrfile(self, file, type, rest=None):
   347     def retrfile(self, file, type, rest=None):