mercurial/byterange.py
changeset 575 7f5ce4bbdd7b
parent 0 9117c6561b0b
child 667 31a9aa890016
--- a/mercurial/byterange.py	Sat Jul 02 13:39:40 2005 -0800
+++ b/mercurial/byterange.py	Sat Jul 02 13:40:15 2005 -0800
@@ -9,9 +9,9 @@
 #   Lesser General Public License for more details.
 #
 #   You should have received a copy of the GNU Lesser General Public
-#   License along with this library; if not, write to the 
-#      Free Software Foundation, Inc., 
-#      59 Temple Place, Suite 330, 
+#   License along with this library; if not, write to the
+#      Free Software Foundation, Inc.,
+#      59 Temple Place, Suite 330,
 #      Boston, MA  02111-1307  USA
 
 # This file is part of urlgrabber, a high-level cross-protocol url-grabber
@@ -25,59 +25,59 @@
 import urllib2
 import rfc822
 
-try:    
+try:
     from cStringIO import StringIO
-except ImportError, msg: 
+except ImportError, msg:
     from StringIO import StringIO
 
 class RangeError(IOError):
     """Error raised when an unsatisfiable range is requested."""
     pass
-    
+
 class HTTPRangeHandler(urllib2.BaseHandler):
     """Handler that enables HTTP Range headers.
-    
+
     This was extremely simple. The Range header is a HTTP feature to
-    begin with so all this class does is tell urllib2 that the 
-    "206 Partial Content" reponse from the HTTP server is what we 
+    begin with so all this class does is tell urllib2 that the
+    "206 Partial Content" reponse from the HTTP server is what we
     expected.
-    
+
     Example:
         import urllib2
         import byterange
-        
+
         range_handler = range.HTTPRangeHandler()
         opener = urllib2.build_opener(range_handler)
-        
+
         # install it
         urllib2.install_opener(opener)
-        
+
         # create Request and set Range header
         req = urllib2.Request('http://www.python.org/')
         req.header['Range'] = 'bytes=30-50'
         f = urllib2.urlopen(req)
     """
-    
+
     def http_error_206(self, req, fp, code, msg, hdrs):
         # 206 Partial Content Response
         r = urllib.addinfourl(fp, hdrs, req.get_full_url())
         r.code = code
         r.msg = msg
         return r
-    
+
     def http_error_416(self, req, fp, code, msg, hdrs):
         # HTTP's Range Not Satisfiable error
         raise RangeError('Requested Range Not Satisfiable')
 
 class RangeableFileObject:
     """File object wrapper to enable raw range handling.
-    This was implemented primarilary for handling range 
-    specifications for file:// urls. This object effectively makes 
-    a file object look like it consists only of a range of bytes in 
+    This was implemented primarilary for handling range
+    specifications for file:// urls. This object effectively makes
+    a file object look like it consists only of a range of bytes in
     the stream.
-    
+
     Examples:
-        # expose 10 bytes, starting at byte position 20, from 
+        # expose 10 bytes, starting at byte position 20, from
         # /etc/aliases.
         >>> fo = RangeableFileObject(file('/etc/passwd', 'r'), (20,30))
         # seek seeks within the range (to position 23 in this case)
@@ -89,11 +89,11 @@
         # byte in the range. the following will return only 7 bytes.
         >>> fo.read(30)
     """
-    
+
     def __init__(self, fo, rangetup):
         """Create a RangeableFileObject.
-        fo       -- a file like object. only the read() method need be 
-                    supported but supporting an optimized seek() is 
+        fo       -- a file like object. only the read() method need be
+                    supported but supporting an optimized seek() is
                     preferable.
         rangetup -- a (firstbyte,lastbyte) tuple specifying the range
                     to work over.
@@ -103,7 +103,7 @@
         (self.firstbyte, self.lastbyte) = range_tuple_normalize(rangetup)
         self.realpos = 0
         self._do_seek(self.firstbyte)
-        
+
     def __getattr__(self, name):
         """This effectively allows us to wrap at the instance level.
         Any attribute not found in _this_ object will be searched for
@@ -111,16 +111,16 @@
         if hasattr(self.fo, name):
             return getattr(self.fo, name)
         raise AttributeError, name
-    
+
     def tell(self):
         """Return the position within the range.
-        This is different from fo.seek in that position 0 is the 
+        This is different from fo.seek in that position 0 is the
         first byte position of the range tuple. For example, if
         this object was created with a range tuple of (500,899),
         tell() will return 0 when at byte position 500 of the file.
         """
         return (self.realpos - self.firstbyte)
-    
+
     def seek(self,offset,whence=0):
         """Seek within the byte range.
         Positioning is identical to that described under tell().
@@ -133,13 +133,13 @@
         elif whence == 2: # absolute from end of file
             # XXX: are we raising the right Error here?
             raise IOError('seek from end of file not supported.')
-        
+
         # do not allow seek past lastbyte in range
         if self.lastbyte and (realoffset >= self.lastbyte):
             realoffset = self.lastbyte
-        
+
         self._do_seek(realoffset - self.realpos)
-        
+
     def read(self, size=-1):
         """Read within the range.
         This method will limit the size read based on the range.
@@ -148,7 +148,7 @@
         rslt = self.fo.read(size)
         self.realpos += len(rslt)
         return rslt
-    
+
     def readline(self, size=-1):
         """Read lines within the range.
         This method will limit the size read based on the range.
@@ -157,7 +157,7 @@
         rslt = self.fo.readline(size)
         self.realpos += len(rslt)
         return rslt
-    
+
     def _calc_read_size(self, size):
         """Handles calculating the amount of data to read based on
         the range.
@@ -169,7 +169,7 @@
             else:
                 size = (self.lastbyte - self.realpos)
         return size
-        
+
     def _do_seek(self,offset):
         """Seek based on whether wrapped object supports seek().
         offset is relative to the current position (self.realpos).
@@ -180,7 +180,7 @@
         else:
             self.fo.seek(self.realpos + offset)
         self.realpos+= offset
-        
+
     def _poor_mans_seek(self,offset):
         """Seek by calling the wrapped file objects read() method.
         This is used for file like objects that do not have native
@@ -188,7 +188,7 @@
         to manually seek to the desired position.
         offset -- read this number of bytes from the wrapped
                   file object.
-        raise RangeError if we encounter EOF before reaching the 
+        raise RangeError if we encounter EOF before reaching the
         specified offset.
         """
         pos = 0
@@ -237,10 +237,10 @@
         return urllib.addinfourl(fo, headers, 'file:'+file)
 
 
-# FTP Range Support 
+# FTP Range Support
 # Unfortunately, a large amount of base FTP code had to be copied
 # from urllib and urllib2 in order to insert the FTP REST command.
-# Code modifications for range support have been commented as 
+# Code modifications for range support have been commented as
 # follows:
 # -- range support modifications start/end here
 
@@ -271,7 +271,7 @@
         host = unquote(host)
         user = unquote(user or '')
         passwd = unquote(passwd or '')
-        
+
         try:
             host = socket.gethostbyname(host)
         except socket.error, msg:
@@ -290,22 +290,22 @@
                 if attr.lower() == 'type' and \
                    value in ('a', 'A', 'i', 'I', 'd', 'D'):
                     type = value.upper()
-            
+
             # -- range support modifications start here
             rest = None
-            range_tup = range_header_to_tuple(req.headers.get('Range',None))    
+            range_tup = range_header_to_tuple(req.headers.get('Range',None))
             assert range_tup != ()
             if range_tup:
                 (fb,lb) = range_tup
                 if fb > 0: rest = fb
             # -- range support modifications end here
-            
+
             fp, retrlen = fw.retrfile(file, type, rest)
-            
+
             # -- range support modifications start here
             if range_tup:
                 (fb,lb) = range_tup
-                if lb == '': 
+                if lb == '':
                     if retrlen is None or retrlen == 0:
                         raise RangeError('Requested Range Not Satisfiable due to unobtainable file length.')
                     lb = retrlen
@@ -317,7 +317,7 @@
                     retrlen = lb - fb
                     fp = RangeableFileObject(fp, (0,retrlen))
             # -- range support modifications end here
-            
+
             headers = ""
             mtype = mimetypes.guess_type(req.get_full_url())[0]
             if mtype:
@@ -389,17 +389,17 @@
 _rangere = None
 def range_header_to_tuple(range_header):
     """Get a (firstbyte,lastbyte) tuple from a Range header value.
-    
+
     Range headers have the form "bytes=<firstbyte>-<lastbyte>". This
     function pulls the firstbyte and lastbyte values and returns
     a (firstbyte,lastbyte) tuple. If lastbyte is not specified in
     the header value, it is returned as an empty string in the
     tuple.
-    
+
     Return None if range_header is None
-    Return () if range_header does not conform to the range spec 
+    Return () if range_header does not conform to the range spec
     pattern.
-    
+
     """
     global _rangere
     if range_header is None: return None
@@ -407,9 +407,9 @@
         import re
         _rangere = re.compile(r'^bytes=(\d{1,})-(\d*)')
     match = _rangere.match(range_header)
-    if match: 
+    if match:
         tup = range_tuple_normalize(match.group(1,2))
-        if tup and tup[1]: 
+        if tup and tup[1]:
             tup = (tup[0],tup[1]+1)
         return tup
     return ()
@@ -422,14 +422,14 @@
     if range_tup is None: return None
     range_tup = range_tuple_normalize(range_tup)
     if range_tup:
-        if range_tup[1]: 
+        if range_tup[1]:
             range_tup = (range_tup[0],range_tup[1] - 1)
         return 'bytes=%s-%s' % range_tup
-    
+
 def range_tuple_normalize(range_tup):
     """Normalize a (first_byte,last_byte) range tuple.
     Return a tuple whose first element is guaranteed to be an int
-    and whose second element will be '' (meaning: the last byte) or 
+    and whose second element will be '' (meaning: the last byte) or
     an int. Finally, return None if the normalized tuple == (0,'')
     as that is equivelant to retrieving the entire file.
     """
@@ -441,7 +441,7 @@
     # handle last byte
     try: lb = range_tup[1]
     except IndexError: lb = ''
-    else:  
+    else:
         if lb is None: lb = ''
         elif lb != '': lb = int(lb)
     # check if range is over the entire file
@@ -449,4 +449,3 @@
     # check that the range is valid
     if lb < fb: raise RangeError('Invalid byte range: %s-%s' % (fb,lb))
     return (fb,lb)
-