--- 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)
-