1
 
#   This library is free software; you can redistribute it and/or
 
2
 
#   modify it under the terms of the GNU Lesser General Public
 
3
 
#   License as published by the Free Software Foundation; either
 
4
 
#   version 2.1 of the License, or (at your option) any later version.
 
6
 
#   This library is distributed in the hope that it will be useful,
 
7
 
#   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
8
 
#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
9
 
#   Lesser General Public License for more details.
 
11
 
#   You should have received a copy of the GNU Lesser General Public
 
12
 
#   License along with this library; if not, write to the 
 
13
 
#      Free Software Foundation, Inc., 
 
14
 
#      59 Temple Place, Suite 330, 
 
15
 
#      Boston, MA  02111-1307  USA
 
17
 
# This file is part of urlgrabber, a high-level cross-protocol url-grabber
 
18
 
# Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko
 
20
 
"""A high-level cross-protocol url-grabber.
 
22
 
GENERAL ARGUMENTS (kwargs)
 
24
 
  Where possible, the module-level default is indicated, and legal
 
29
 
    ignored except for file:// urls, in which case it specifies
 
30
 
    whether urlgrab should still make a copy of the file, or simply
 
31
 
    point to the existing copy. The module level default for this
 
34
 
  close_connection = 0   [0|1]
 
36
 
    tells URLGrabber to close the connection after a file has been
 
37
 
    transfered. This is ignored unless the download happens with the
 
38
 
    http keepalive handler (keepalive=1).  Otherwise, the connection
 
39
 
    is left open for further use. The module level default for this
 
40
 
    option is 0 (keepalive connections will not be closed).
 
44
 
    specifies whether keepalive should be used for HTTP/1.1 servers
 
45
 
    that support it. The module level default for this option is 1
 
46
 
    (keepalive is enabled).
 
50
 
    a class instance that supports the following methods:
 
51
 
      po.start(filename, url, basename, length, text)
 
52
 
      # length will be None if unknown
 
53
 
      po.update(read) # read == bytes read so far
 
58
 
    specifies an alternativ text item in the beginning of the progress
 
59
 
    bar line. If not given, the basename of the file is used.
 
63
 
    a number - if it's an int, it's the bytes/second throttle limit.
 
64
 
    If it's a float, it is first multiplied by bandwidth.  If throttle
 
65
 
    == 0, throttling is disabled.  If None, the module-level default
 
66
 
    (which can be set on default_grabber.throttle) is used. See
 
67
 
    BANDWIDTH THROTTLING for more information.
 
71
 
    a positive float expressing the number of seconds to wait for socket
 
72
 
    operations. If the value is None or 0.0, socket operations will block
 
73
 
    forever. Setting this option causes urlgrabber to call the settimeout
 
74
 
    method on the Socket object used for the request. See the Python
 
75
 
    documentation on settimeout for more information.
 
76
 
    http://www.python.org/doc/current/lib/socket-objects.html
 
80
 
    the nominal max bandwidth in bytes/second.  If throttle is a float
 
81
 
    and bandwidth == 0, throttling is disabled.  If None, the
 
82
 
    module-level default (which can be set on
 
83
 
    default_grabber.bandwidth) is used. See BANDWIDTH THROTTLING for
 
88
 
    a tuple of the form (first_byte, last_byte) describing a byte
 
89
 
    range to retrieve. Either or both of the values may set to
 
90
 
    None. If first_byte is None, byte offset 0 is assumed. If
 
91
 
    last_byte is None, the last byte available is assumed. Note that
 
92
 
    the range specification is python-like in that (0,10) will yeild
 
93
 
    the first 10 bytes of the file.
 
95
 
    If set to None, no range will be used.
 
97
 
  reget = None   [None|'simple'|'check_timestamp']
 
99
 
    whether to attempt to reget a partially-downloaded file.  Reget
 
100
 
    only applies to .urlgrab and (obviously) only if there is a
 
101
 
    partially downloaded file.  Reget has two modes:
 
103
 
      'simple' -- the local file will always be trusted.  If there
 
104
 
        are 100 bytes in the local file, then the download will always
 
105
 
        begin 100 bytes into the requested file.
 
107
 
      'check_timestamp' -- the timestamp of the server file will be
 
108
 
        compared to the timestamp of the local file.  ONLY if the
 
109
 
        local file is newer than or the same age as the server file
 
110
 
        will reget be used.  If the server file is newer, or the
 
111
 
        timestamp is not returned, the entire file will be fetched.
 
113
 
    NOTE: urlgrabber can do very little to verify that the partial
 
114
 
    file on disk is identical to the beginning of the remote file.
 
115
 
    You may want to either employ a custom "checkfunc" or simply avoid
 
116
 
    using reget in situations where corruption is a concern.
 
118
 
  user_agent = 'urlgrabber/VERSION'
 
120
 
    a string, usually of the form 'AGENT/VERSION' that is provided to
 
121
 
    HTTP servers in the User-agent header. The module level default
 
122
 
    for this option is "urlgrabber/VERSION".
 
126
 
    a tuple of 2-tuples, each containing a header and value.  These
 
127
 
    will be used for http and https requests only.  For example, you
 
129
 
      http_headers = (('Pragma', 'no-cache'),)
 
133
 
    this is just like http_headers, but will be used for ftp requests.
 
137
 
    a dictionary that maps protocol schemes to proxy hosts. For
 
138
 
    example, to use a proxy server on host "foo" port 3128 for http
 
140
 
      proxies={ 'http' : 'http://foo:3128', 'https' : 'http://foo:3128' }
 
141
 
    note that proxy authentication information may be provided using
 
142
 
    normal URL constructs:
 
143
 
      proxies={ 'http' : 'http://user:host@foo:3128' }
 
144
 
    Lastly, if proxies is None, the default environment settings will
 
149
 
    a url prefix that will be prepended to all requested urls.  For
 
151
 
      g = URLGrabber(prefix='http://foo.com/mirror/')
 
152
 
      g.urlgrab('some/file.txt')
 
153
 
      ## this will fetch 'http://foo.com/mirror/some/file.txt'
 
154
 
    This option exists primarily to allow identical behavior to
 
155
 
    MirrorGroup (and derived) instances.  Note: a '/' will be inserted
 
156
 
    if necessary, so you cannot specify a prefix that ends with a
 
157
 
    partial file or directory name.
 
161
 
    Overrides the default urllib2.OpenerDirector provided to urllib2
 
162
 
    when making requests.  This option exists so that the urllib2
 
163
 
    handler chain may be customized.  Note that the range, reget,
 
164
 
    proxy, and keepalive features require that custom handlers be
 
165
 
    provided to urllib2 in order to function properly.  If an opener
 
166
 
    option is provided, no attempt is made by urlgrabber to ensure
 
167
 
    chain integrity.  You are responsible for ensuring that any
 
168
 
    extension handlers are present if said features are required.
 
170
 
RETRY RELATED ARGUMENTS
 
174
 
    the number of times to retry the grab before bailing.  If this is
 
175
 
    zero, it will retry forever. This was intentional... really, it
 
176
 
    was :). If this value is not supplied or is supplied but is None
 
177
 
    retrying does not occur.
 
179
 
  retrycodes = [-1,2,4,5,6,7]
 
181
 
    a sequence of errorcodes (values of e.errno) for which it should
 
182
 
    retry. See the doc on URLGrabError for more details on
 
183
 
    this. retrycodes defaults to [-1,2,4,5,6,7] if not specified
 
188
 
    a function to do additional checks. This defaults to None, which
 
189
 
    means no additional checking.  The function should simply return
 
190
 
    on a successful check.  It should raise URLGrabError on an
 
191
 
    unsuccessful check.  Raising of any other exception will be
 
192
 
    considered immediate failure and no retries will occur.
 
194
 
    If it raises URLGrabError, the error code will determine the retry
 
195
 
    behavior.  Negative error numbers are reserved for use by these
 
196
 
    passed in functions, so you can use many negative numbers for
 
197
 
    different types of failure.  By default, -1 results in a retry,
 
198
 
    but this can be customized with retrycodes.
 
200
 
    If you simply pass in a function, it will be given exactly one
 
201
 
    argument: a CallbackObject instance with the .url attribute
 
202
 
    defined and either .filename (for urlgrab) or .data (for urlread).
 
203
 
    For urlgrab, .filename is the name of the local file.  For
 
204
 
    urlread, .data is the actual string data.  If you need other
 
205
 
    arguments passed to the callback (program state of some sort), you
 
208
 
      checkfunc=(function, ('arg1', 2), {'kwarg': 3})
 
210
 
    if the downloaded file has filename /tmp/stuff, then this will
 
211
 
    result in this call (for urlgrab):
 
213
 
      function(obj, 'arg1', 2, kwarg=3)
 
214
 
      # obj.filename = '/tmp/stuff'
 
215
 
      # obj.url = 'http://foo.com/stuff'
 
217
 
    NOTE: both the "args" tuple and "kwargs" dict must be present if
 
218
 
    you use this syntax, but either (or both) can be empty.
 
220
 
  failure_callback = None
 
222
 
    The callback that gets called during retries when an attempt to
 
223
 
    fetch a file fails.  The syntax for specifying the callback is
 
224
 
    identical to checkfunc, except for the attributes defined in the
 
225
 
    CallbackObject instance.  In this case, it will have .exception
 
226
 
    and .url defined.  As you might suspect, .exception is the
 
227
 
    exception that was raised.
 
229
 
    The callback is present primarily to inform the calling program of
 
230
 
    the failure, but if it raises an exception (including the one it's
 
231
 
    passed) that exception will NOT be caught and will therefore cause
 
232
 
    future retries to be aborted.
 
236
 
  urlgrabber supports throttling via two values: throttle and
 
237
 
  bandwidth Between the two, you can either specify and absolute
 
238
 
  throttle threshold or specify a theshold as a fraction of maximum
 
241
 
  throttle is a number - if it's an int, it's the bytes/second
 
242
 
  throttle limit.  If it's a float, it is first multiplied by
 
243
 
  bandwidth.  If throttle == 0, throttling is disabled.  If None, the
 
244
 
  module-level default (which can be set with set_throttle) is used.
 
246
 
  bandwidth is the nominal max bandwidth in bytes/second.  If throttle
 
247
 
  is a float and bandwidth == 0, throttling is disabled.  If None, the
 
248
 
  module-level default (which can be set with set_bandwidth) is used.
 
252
 
  Lets say you have a 100 Mbps connection.  This is (about) 10^8 bits
 
253
 
  per second, or 12,500,000 Bytes per second.  You have a number of
 
256
 
  *) set_bandwidth(12500000); set_throttle(0.5) # throttle is a float
 
258
 
     This will limit urlgrab to use half of your available bandwidth.
 
260
 
  *) set_throttle(6250000) # throttle is an int
 
262
 
     This will also limit urlgrab to use half of your available
 
263
 
     bandwidth, regardless of what bandwidth is set to.
 
265
 
  *) set_throttle(6250000); set_throttle(1.0) # float
 
267
 
     Use half your bandwidth
 
269
 
  *) set_throttle(6250000); set_throttle(2.0) # float
 
271
 
    Use up to 12,500,000 Bytes per second (your nominal max bandwidth)
 
273
 
  *) set_throttle(6250000); set_throttle(0) # throttle = 0
 
275
 
     Disable throttling - this is more efficient than a very large
 
278
 
  *) set_throttle(0); set_throttle(1.0) # throttle is float, bandwidth = 0
 
280
 
     Disable throttling - this is the default when the module is loaded.
 
282
 
  SUGGESTED AUTHOR IMPLEMENTATION (THROTTLING)
 
284
 
  While this is flexible, it's not extremely obvious to the user.  I
 
285
 
  suggest you implement a float throttle as a percent to make the
 
286
 
  distinction between absolute and relative throttling very explicit.
 
288
 
  Also, you may want to convert the units to something more convenient
 
289
 
  than bytes/second, such as kbps or kB/s, etc.
 
293
 
# $Id: grabber.py,v 1.39 2005/03/03 00:54:23 mstenner Exp $
 
303
 
from stat import *  # S_* and ST_*
 
306
 
    exec('from ' + (__name__.split('.'))[0] + ' import __version__')
 
310
 
auth_handler = urllib2.HTTPBasicAuthHandler( \
 
311
 
     urllib2.HTTPPasswordMgrWithDefaultRealm())
 
317
 
except ImportError, msg:
 
321
 
    from httplib import HTTPException
 
322
 
except ImportError, msg:
 
326
 
    # This is a convenient way to make keepalive optional.
 
327
 
    # Just rename the module so it can't be imported.
 
328
 
    from keepalive import HTTPHandler
 
329
 
except ImportError, msg:
 
330
 
    keepalive_handler = None
 
332
 
    keepalive_handler = HTTPHandler()
 
335
 
    # add in range support conditionally too
 
336
 
    from urlgrabber.byterange import HTTPRangeHandler, FileRangeHandler, \
 
337
 
         FTPRangeHandler, range_tuple_normalize, range_tuple_to_header, \
 
339
 
except ImportError, msg:
 
344
 
    range_handlers = (HTTPRangeHandler(), FileRangeHandler(), FTPRangeHandler())
 
348
 
# check whether socket timeout support is available (Python >= 2.3)
 
351
 
    TimeoutError = socket.timeout
 
352
 
    have_socket_timeout = True
 
353
 
except AttributeError:
 
355
 
    have_socket_timeout = False
 
357
 
class URLGrabError(IOError):
 
359
 
    URLGrabError error codes:
 
361
 
      URLGrabber error codes (0 -- 255)
 
362
 
        0    - everything looks good (you should never see this)
 
364
 
        2    - local file doesn't exist
 
365
 
        3    - request for non-file local file (dir, etc)
 
368
 
        6    - no content length header when we expected one
 
370
 
        8    - Exceeded read limit (for urlread)
 
371
 
        9    - Requested byte range not satisfiable.
 
372
 
        10   - Byte range requested, but range support unavailable
 
373
 
        11   - Illegal reget mode
 
376
 
      MirrorGroup error codes (256 -- 511)
 
377
 
        256  - No more mirrors left to try
 
379
 
      Custom (non-builtin) classes derived from MirrorGroup (512 -- 767)
 
380
 
        [ this range reserved for application-specific error codes ]
 
383
 
        -1   - retry the download, unknown reason
 
385
 
    Note: to test which group a code is in, you can simply do integer
 
386
 
    division by 256: e.errno / 256
 
388
 
    Negative codes are reserved for use by functions passed in to
 
389
 
    retrygrab with checkfunc.  The value -1 is built in as a generic
 
390
 
    retry code and is already included in the retrycodes list.
 
391
 
    Therefore, you can create a custom check function that simply
 
392
 
    returns -1 and the fetch will be re-tried.  For more customized
 
393
 
    retries, you can use other negative number and include them in
 
394
 
    retry-codes.  This is nice for outputting useful messages about
 
397
 
    You can use these error codes like so:
 
399
 
      except URLGrabError, e:
 
404
 
         print e  #### print '[Errno %i] %s' % (e.errno, e.strerror)
 
408
 
class CallbackObject:
 
409
 
    """Container for returned callback data.
 
411
 
    This is currently a dummy class into which urlgrabber can stuff
 
412
 
    information for passing to callbacks.  This way, the prototype for
 
413
 
    all callbacks is the same, regardless of the data that will be
 
414
 
    passed back.  Any function that accepts a callback function as an
 
415
 
    argument SHOULD document what it will define in this object.
 
417
 
    It is possible that this class will have some greater
 
418
 
    functionality in the future.
 
423
 
    """close any open keepalive connections"""
 
424
 
    if keepalive_handler: keepalive_handler.close_all()
 
426
 
def urlgrab(url, filename=None, **kwargs):
 
427
 
    """grab the file at <url> and make a local copy at <filename>
 
428
 
    If filename is none, the basename of the url is used.
 
429
 
    urlgrab returns the filename of the local file, which may be different
 
430
 
    from the passed-in filename if the copy_local kwarg == 0.
 
432
 
    See module documentation for a description of possible kwargs.
 
434
 
    return default_grabber.urlgrab(url, filename, **kwargs)
 
436
 
def urlopen(url, **kwargs):
 
437
 
    """open the url and return a file object
 
438
 
    If a progress object or throttle specifications exist, then
 
439
 
    a special file object will be returned that supports them.
 
440
 
    The file object can be treated like any other file object.
 
442
 
    See module documentation for a description of possible kwargs.
 
444
 
    return default_grabber.urlopen(url, **kwargs)
 
446
 
def urlread(url, limit=None, **kwargs):
 
447
 
    """read the url into a string, up to 'limit' bytes
 
448
 
    If the limit is exceeded, an exception will be thrown.  Note that urlread
 
449
 
    is NOT intended to be used as a way of saying "I want the first N bytes"
 
450
 
    but rather 'read the whole file into memory, but don't use too much'
 
452
 
    See module documentation for a description of possible kwargs.
 
454
 
    return default_grabber.urlread(url, limit, **kwargs)
 
457
 
class URLGrabberOptions:
 
458
 
    """Class to ease kwargs handling."""
 
460
 
    def __init__(self, delegate=None, **kwargs):
 
461
 
        """Initialize URLGrabberOptions object.
 
462
 
        Set default values for all options and then update options specified
 
465
 
        self.delegate = delegate
 
468
 
        self._set_attributes(**kwargs)
 
470
 
    def __getattr__(self, name):
 
471
 
        if self.delegate and hasattr(self.delegate, name):
 
472
 
            return getattr(self.delegate, name)
 
473
 
        raise AttributeError, name
 
475
 
    def raw_throttle(self):
 
476
 
        """Calculate raw throttle value from throttle and bandwidth 
 
479
 
        if self.throttle <= 0:  
 
481
 
        elif type(self.throttle) == type(0): 
 
482
 
            return float(self.throttle)
 
483
 
        else: # throttle is a float
 
484
 
            return self.bandwidth * self.throttle
 
486
 
    def derive(self, **kwargs):
 
487
 
        """Create a derived URLGrabberOptions instance.
 
488
 
        This method creates a new instance and overrides the
 
489
 
        options specified in kwargs.
 
491
 
        return URLGrabberOptions(delegate=self, **kwargs)
 
493
 
    def _set_attributes(self, **kwargs):
 
494
 
        """Update object attributes with those provided in kwargs."""
 
495
 
        self.__dict__.update(kwargs)
 
496
 
        if have_range and kwargs.has_key('range'):
 
497
 
            # normalize the supplied range value
 
498
 
            self.range = range_tuple_normalize(self.range)
 
499
 
        if not self.reget in [None, 'simple', 'check_timestamp']:
 
500
 
            raise URLGrabError(11, _('Illegal reget mode: %s') \
 
503
 
    def _set_defaults(self):
 
504
 
        """Set all options to their default values. 
 
505
 
        When adding new options, make sure a default is
 
508
 
        self.progress_obj = None
 
512
 
        self.retrycodes = [-1,2,4,5,6,7]
 
513
 
        self.checkfunc = None
 
515
 
        self.close_connection = 0
 
517
 
        self.user_agent = 'urlgrabber/%s' % __version__
 
521
 
        self.failure_callback = None
 
524
 
        self.cache_openers = True
 
527
 
        self.http_headers = None
 
528
 
        self.ftp_headers = None
 
531
 
    """Provides easy opening of URLs with a variety of options.
 
533
 
    All options are specified as kwargs. Options may be specified when
 
534
 
    the class is created and may be overridden on a per request basis.
 
536
 
    New objects inherit default values from default_grabber.
 
539
 
    def __init__(self, **kwargs):
 
540
 
        self.opts = URLGrabberOptions(**kwargs)
 
542
 
    def _retry(self, opts, func, *args):
 
547
 
                return apply(func, (opts,) + args, {})
 
548
 
            except URLGrabError, e:
 
549
 
                if DEBUG: print 'EXCEPTION: %s' % e
 
550
 
                if (opts.retry is None) \
 
551
 
                    or (tries == opts.retry) \
 
552
 
                    or (e.errno not in opts.retrycodes): raise
 
553
 
                if opts.failure_callback:
 
554
 
                    cb_func, cb_args, cb_kwargs = \
 
555
 
                          self._make_callback(opts.failure_callback)
 
556
 
                    # this is a little icky - for now, the first element
 
557
 
                    # of args is the url.  we might consider a way to tidy
 
559
 
                    obj = CallbackObject()
 
562
 
                    cb_func(obj, *cb_args, **cb_kwargs)
 
564
 
    def urlopen(self, url, **kwargs):
 
565
 
        """open the url and return a file object
 
566
 
        If a progress object or throttle value specified when this 
 
567
 
        object was created, then  a special file object will be 
 
568
 
        returned that supports them. The file object can be treated 
 
569
 
        like any other file object.
 
571
 
        opts = self.opts.derive(**kwargs)
 
572
 
        (url,parts) = self._parse_url(url) 
 
573
 
        def retryfunc(opts, url):
 
574
 
            return URLGrabberFileObject(url, filename=None, opts=opts)
 
575
 
        return self._retry(opts, retryfunc, url)
 
577
 
    def urlgrab(self, url, filename=None, **kwargs):
 
578
 
        """grab the file at <url> and make a local copy at <filename>
 
579
 
        If filename is none, the basename of the url is used.
 
580
 
        urlgrab returns the filename of the local file, which may be 
 
581
 
        different from the passed-in filename if copy_local == 0.
 
583
 
        opts = self.opts.derive(**kwargs)
 
584
 
        (url, parts) = self._parse_url(url)
 
585
 
        (scheme, host, path, parm, query, frag) = parts
 
587
 
            if scheme in [ 'http', 'https' ]:
 
588
 
                filename = os.path.basename( urllib.unquote(path) )
 
590
 
                filename = os.path.basename( path )
 
591
 
        if scheme == 'file' and not opts.copy_local:
 
592
 
            # just return the name of the local file - don't make a 
 
594
 
            if not os.path.exists(path):
 
595
 
                raise URLGrabError(2, 
 
596
 
                      _('Local file does not exist: %s') % (path, ))
 
597
 
            elif not os.path.isfile(path):
 
598
 
                raise URLGrabError(3, 
 
599
 
                              _('Not a normal file: %s') % (path, ))
 
603
 
        def retryfunc(opts, url, filename):
 
604
 
            fo = URLGrabberFileObject(url, filename, opts)
 
607
 
                if not opts.checkfunc is None:
 
608
 
                    cb_func, cb_args, cb_kwargs = \
 
609
 
                             self._make_callback(opts.checkfunc)
 
610
 
                    obj = CallbackObject()
 
611
 
                    obj.filename = filename
 
613
 
                    apply(cb_func, (obj, )+cb_args, cb_kwargs)
 
618
 
        return self._retry(opts, retryfunc, url, filename)
 
620
 
    def urlread(self, url, limit=None, **kwargs):
 
621
 
        """read the url into a string, up to 'limit' bytes
 
622
 
        If the limit is exceeded, an exception will be thrown.  Note
 
623
 
        that urlread is NOT intended to be used as a way of saying 
 
624
 
        "I want the first N bytes" but rather 'read the whole file 
 
625
 
        into memory, but don't use too much'
 
627
 
        opts = self.opts.derive(**kwargs)
 
628
 
        (url, parts) = self._parse_url(url)
 
629
 
        if limit is not None:
 
632
 
        def retryfunc(opts, url, limit):
 
633
 
            fo = URLGrabberFileObject(url, filename=None, opts=opts)
 
636
 
                # this is an unfortunate thing.  Some file-like objects
 
637
 
                # have a default "limit" of None, while the built-in (real)
 
638
 
                # file objects have -1.  They each break the other, so for
 
639
 
                # now, we just force the default if necessary.
 
640
 
                if limit is None: s = fo.read()
 
641
 
                else: s = fo.read(limit)
 
643
 
                if not opts.checkfunc is None:
 
644
 
                    cb_func, cb_args, cb_kwargs = \
 
645
 
                             self._make_callback(opts.checkfunc)
 
646
 
                    obj = CallbackObject()
 
649
 
                    apply(cb_func, (obj, )+cb_args, cb_kwargs)
 
654
 
        s = self._retry(opts, retryfunc, url, limit)
 
655
 
        if limit and len(s) > limit:
 
656
 
            raise URLGrabError(8, 
 
657
 
                        _('Exceeded limit (%i): %s') % (limit, url))
 
660
 
    def _parse_url(self,url):
 
661
 
        """break up the url into its component parts
 
663
 
        This function disassembles a url and
 
664
 
        1) "normalizes" it, tidying it up a bit
 
665
 
        2) does any authentication stuff it needs to do
 
667
 
        it returns the (cleaned) url and a tuple of component parts
 
671
 
            if p[-1] == '/' or url[0] == '/': url = p + url
 
672
 
            else: url = p + '/' + url
 
674
 
        (scheme, host, path, parm, query, frag) = \
 
675
 
                                             urlparse.urlparse(url)
 
677
 
            if not url[0] == '/': url = os.path.abspath(url)
 
679
 
            (scheme, host, path, parm, query, frag) = \
 
680
 
                                             urlparse.urlparse(url)
 
681
 
        path = os.path.normpath(path)
 
682
 
        if scheme in ['http', 'https']: path = urllib.quote(path)
 
683
 
        if '@' in host and auth_handler and scheme in ['http', 'https']:
 
685
 
                user_pass, host = host.split('@', 1)
 
686
 
                if ':' in user_pass: user, password = user_pass.split(':', 1)
 
687
 
            except ValueError, e:
 
688
 
                raise URLGrabError(1, _('Bad URL: %s') % url)
 
689
 
            if DEBUG: print 'adding HTTP auth: %s, %s' % (user, password)
 
690
 
            auth_handler.add_password(None, host, user, password)
 
691
 
        parts = (scheme, host, path, parm, query, frag)
 
692
 
        url = urlparse.urlunparse(parts)
 
695
 
    def _make_callback(self, callback_obj):
 
696
 
        if callable(callback_obj):
 
697
 
            return callback_obj, (), {}
 
701
 
# create the default URLGrabber used by urlXXX functions.
 
702
 
# NOTE: actual defaults are set in URLGrabberOptions
 
703
 
default_grabber = URLGrabber()
 
705
 
class URLGrabberFileObject:
 
706
 
    """This is a file-object wrapper that supports progress objects 
 
709
 
    This exists to solve the following problem: lets say you want to
 
710
 
    drop-in replace a normal open with urlopen.  You want to use a
 
711
 
    progress meter and/or throttling, but how do you do that without
 
712
 
    rewriting your code?  Answer: urlopen will return a wrapped file
 
713
 
    object that does the progress meter and-or throttling internally.
 
716
 
    def __init__(self, url, filename, opts):
 
718
 
        self.filename = filename
 
722
 
        self._rbufsize = 1024*8
 
723
 
        self._ttime = time.time()
 
725
 
        self._amount_read = 0
 
729
 
    def __getattr__(self, name):
 
730
 
        """This effectively allows us to wrap at the instance level.
 
731
 
        Any attribute not found in _this_ object will be searched for
 
732
 
        in self.fo.  This includes methods."""
 
733
 
        if hasattr(self.fo, name):
 
734
 
            return getattr(self.fo, name)
 
735
 
        raise AttributeError, name
 
737
 
    def _get_opener(self):
 
738
 
        """Build a urllib2 OpenerDirector based on request options."""
 
740
 
            return self.opts.opener
 
741
 
        elif self._opener is None:
 
743
 
            need_keepalive_handler = (keepalive_handler and self.opts.keepalive)
 
744
 
            need_range_handler = (range_handlers and \
 
745
 
                                  (self.opts.range or self.opts.reget))
 
746
 
            # if you specify a ProxyHandler when creating the opener
 
747
 
            # it _must_ come before all other handlers in the list or urllib2
 
749
 
            if self.opts.proxies:
 
750
 
                handlers.append( CachedProxyHandler(self.opts.proxies) )
 
752
 
                # -------------------------------------------------------
 
753
 
                # OK, these next few lines are a serious kludge to get
 
754
 
                # around what I think is a bug in python 2.2's
 
755
 
                # urllib2.  The basic idea is that default handlers
 
756
 
                # get applied first.  If you override one (like a
 
757
 
                # proxy handler), then the default gets pulled, but
 
758
 
                # the replacement goes on the end.  In the case of
 
759
 
                # proxies, this means the normal handler picks it up
 
760
 
                # first and the proxy isn't used.  Now, this probably
 
761
 
                # only happened with ftp or non-keepalive http, so not
 
762
 
                # many folks saw it.  The simple approach to fixing it
 
763
 
                # is just to make sure you override the other
 
764
 
                # conflicting defaults as well.  I would LOVE to see
 
765
 
                # these go way or be dealt with more elegantly.  The
 
766
 
                # problem isn't there after 2.2.  -MDS 2005/02/24
 
767
 
                if not need_keepalive_handler:
 
768
 
                    handlers.append( urllib2.HTTPHandler() )
 
769
 
                if not need_range_handler:
 
770
 
                    handlers.append( urllib2.FTPHandler() )
 
771
 
                # -------------------------------------------------------
 
773
 
            if need_keepalive_handler:
 
774
 
                handlers.append( keepalive_handler )
 
775
 
            if need_range_handler:
 
776
 
                handlers.extend( range_handlers )
 
777
 
            handlers.append( auth_handler )
 
778
 
            if self.opts.cache_openers:
 
779
 
              self._opener = CachedOpenerDirector(*handlers)
 
781
 
              self._opener = urllib2.build_opener(*handlers)
 
782
 
            # OK, I don't like to do this, but otherwise, we end up with
 
783
 
            # TWO user-agent headers.
 
784
 
            self._opener.addheaders = []
 
788
 
        opener = self._get_opener()
 
790
 
        req = urllib2.Request(self.url) # build request object
 
791
 
        self._add_headers(req) # add misc headers that we need
 
792
 
        self._build_range(req) # take care of reget and byterange stuff
 
794
 
        fo, hdr = self._make_request(req, opener)
 
795
 
        if self.reget_time and self.opts.reget == 'check_timestamp':
 
796
 
            # do this if we have a local file with known timestamp AND
 
797
 
            # we're in check_timestamp reget mode.
 
800
 
                modified_tuple  = hdr.getdate_tz('last-modified')
 
801
 
                modified_stamp  = rfc822.mktime_tz(modified_tuple)
 
802
 
                if modified_stamp > self.reget_time: fetch_again = 1
 
807
 
                # the server version is newer than the (incomplete) local
 
808
 
                # version, so we should abandon the version we're getting
 
809
 
                # and fetch the whole thing again.
 
811
 
                self.opts.reget = None
 
812
 
                del req.headers['Range']
 
813
 
                self._build_range(req)
 
814
 
                fo, hdr = self._make_request(req, opener)
 
816
 
        (scheme, host, path, parm, query, frag) = urlparse.urlparse(self.url)
 
817
 
        if not (self.opts.progress_obj or self.opts.raw_throttle() \
 
818
 
                or self.opts.timeout):
 
819
 
            # if we're not using the progress_obj, throttling, or timeout
 
820
 
            # we can get a performance boost by going directly to
 
821
 
            # the underlying fileobject for reads.
 
823
 
            if hasattr(fo, 'readline'):
 
824
 
                self.readline = fo.readline
 
825
 
        elif self.opts.progress_obj:
 
826
 
            try:    length = int(hdr['Content-Length'])
 
827
 
            except: length = None
 
828
 
            self.opts.progress_obj.start(str(self.filename), self.url, 
 
829
 
                                         os.path.basename(path), 
 
832
 
            self.opts.progress_obj.update(0)
 
833
 
        (self.fo, self.hdr) = (fo, hdr)
 
835
 
    def _add_headers(self, req):
 
836
 
        if self.opts.user_agent:
 
837
 
            req.add_header('User-agent', self.opts.user_agent)
 
838
 
        try: req_type = req.get_type()
 
839
 
        except ValueError: req_type = None
 
840
 
        if self.opts.http_headers and req_type in ('http', 'https'):
 
841
 
            for h, v in self.opts.http_headers:
 
843
 
        if self.opts.ftp_headers and req_type == 'ftp':
 
844
 
            for h, v in self.opts.ftp_headers:
 
847
 
    def _build_range(self, req):
 
848
 
        self.reget_time = None
 
852
 
        if have_range and self.opts.reget and type(self.filename) == type(''):
 
853
 
            # we have reget turned on and we're dumping to a file
 
855
 
                s = os.stat(self.filename)
 
859
 
                self.reget_time = s[ST_MTIME]
 
860
 
                reget_length = s[ST_SIZE]
 
861
 
                rt = reget_length, ''
 
866
 
                raise URLGrabError(10, _('Byte range requested but range '\
 
867
 
                                         'support unavailable'))
 
869
 
            if rt[0]: rt = (rt[0] + reget_length, rt[1])
 
872
 
            header = range_tuple_to_header(rt)
 
873
 
            if header: req.add_header('Range', header)
 
875
 
    def _make_request(self, req, opener):
 
877
 
            if have_socket_timeout and self.opts.timeout:
 
878
 
                old_to = socket.getdefaulttimeout()
 
879
 
                socket.setdefaulttimeout(self.opts.timeout)
 
881
 
                    fo = opener.open(req)
 
883
 
                    socket.setdefaulttimeout(old_to)
 
885
 
                fo = opener.open(req)
 
887
 
        except ValueError, e:
 
888
 
            raise URLGrabError(1, _('Bad URL: %s') % (e, ))
 
889
 
        except RangeError, e:
 
890
 
            raise URLGrabError(9, _('%s') % (e, ))
 
892
 
            if hasattr(e, 'reason') and have_socket_timeout and \
 
893
 
                   isinstance(e.reason, TimeoutError):
 
894
 
                raise URLGrabError(12, _('Timeout: %s') % (e, ))
 
896
 
                raise URLGrabError(4, _('IOError: %s') % (e, ))
 
898
 
            raise URLGrabError(5, _('OSError: %s') % (e, ))
 
899
 
        except HTTPException, e:
 
900
 
            raise URLGrabError(7, _('HTTP Error (%s): %s') % \
 
901
 
                            (e.__class__.__name__, e))
 
906
 
        """dump the file to self.filename."""
 
907
 
        if self.append: new_fo = open(self.filename, 'ab')
 
908
 
        else: new_fo = open(self.filename, 'wb')
 
912
 
        block = self.read(bs)
 
913
 
        size = size + len(block)
 
916
 
            block = self.read(bs)
 
917
 
            size = size + len(block)
 
921
 
            modified_tuple  = self.hdr.getdate_tz('last-modified')
 
922
 
            modified_stamp  = rfc822.mktime_tz(modified_tuple)
 
923
 
            os.utime(self.filename, (modified_stamp, modified_stamp))
 
924
 
        except (TypeError,), e: pass
 
928
 
    def _fill_buffer(self, amt=None):
 
929
 
        """fill the buffer to contain at least 'amt' bytes by reading
 
930
 
        from the underlying file object.  If amt is None, then it will
 
931
 
        read until it gets nothing more.  It updates the progress meter
 
932
 
        and throttles after every self._rbufsize bytes."""
 
933
 
        # the _rbuf test is only in this first 'if' for speed.  It's not
 
934
 
        # logically necessary
 
935
 
        if self._rbuf and not amt is None:
 
942
 
        # if we've made it here, then we don't have enough in the buffer
 
943
 
        # and we need to read more.
 
946
 
        bufsize = len(self._rbuf)
 
947
 
        while amt is None or amt:
 
948
 
            # first, delay if necessary for throttling reasons
 
949
 
            if self.opts.raw_throttle():
 
950
 
                diff = self._tsize/self.opts.raw_throttle() - \
 
951
 
                       (time.time() - self._ttime)
 
952
 
                if diff > 0: time.sleep(diff)
 
953
 
                self._ttime = time.time()
 
955
 
            # now read some data, up to self._rbufsize
 
956
 
            if amt is None: readamount = self._rbufsize
 
957
 
            else:           readamount = min(amt, self._rbufsize)
 
959
 
                new = self.fo.read(readamount)
 
960
 
            except socket.error, e:
 
961
 
                raise URLGrabError(4, _('Socket Error: %s') % (e, ))
 
962
 
            except TimeoutError, e:
 
963
 
                raise URLGrabError(12, _('Timeout: %s') % (e, ))
 
965
 
            if not newsize: break # no more to read
 
967
 
            if amt: amt = amt - newsize
 
969
 
            bufsize = bufsize + newsize
 
970
 
            self._tsize = newsize
 
971
 
            self._amount_read = self._amount_read + newsize
 
972
 
            if self.opts.progress_obj:
 
973
 
                self.opts.progress_obj.update(self._amount_read)
 
975
 
        self._rbuf = string.join(buf, '')
 
978
 
    def read(self, amt=None):
 
979
 
        self._fill_buffer(amt)
 
981
 
            s, self._rbuf = self._rbuf, ''
 
983
 
            s, self._rbuf = self._rbuf[:amt], self._rbuf[amt:]
 
986
 
    def readline(self, limit=-1):
 
987
 
        i = string.find(self._rbuf, '\n')
 
988
 
        while i < 0 and not (0 < limit <= len(self._rbuf)):
 
990
 
            self._fill_buffer(L + self._rbufsize)
 
991
 
            if not len(self._rbuf) > L: break
 
992
 
            i = string.find(self._rbuf, '\n', L)
 
994
 
        if i < 0: i = len(self._rbuf)
 
996
 
        if 0 <= limit < len(self._rbuf): i = limit
 
998
 
        s, self._rbuf = self._rbuf[:i], self._rbuf[i:]
 
1002
 
        if self.opts.progress_obj:
 
1003
 
            self.opts.progress_obj.end(self._amount_read)
 
1005
 
        if self.opts.close_connection:
 
1006
 
            try: self.fo.close_connection()
 
1010
 
def CachedOpenerDirector(*handlers):
 
1011
 
    for (cached_handlers, opener) in _handler_cache:
 
1012
 
        if cached_handlers == handlers:
 
1013
 
            for handler in opener.handlers:
 
1014
 
                handler.add_parent(opener)
 
1016
 
    opener = urllib2.build_opener(*handlers)
 
1017
 
    _handler_cache.append( (handlers, opener) )
 
1021
 
def CachedProxyHandler(proxies):
 
1022
 
    for (pdict, handler) in _proxy_cache:
 
1023
 
        if pdict == proxies:
 
1026
 
        handler = urllib2.ProxyHandler(proxies)
 
1027
 
        _proxy_cache.append( (proxies, handler) )
 
1030
 
#####################################################################
 
1031
 
# DEPRECATED FUNCTIONS
 
1032
 
def set_throttle(new_throttle):
 
1033
 
    """Deprecated. Use: default_grabber.throttle = new_throttle"""
 
1034
 
    default_grabber.throttle = new_throttle
 
1036
 
def set_bandwidth(new_bandwidth):
 
1037
 
    """Deprecated. Use: default_grabber.bandwidth = new_bandwidth"""
 
1038
 
    default_grabber.bandwidth = new_bandwidth
 
1040
 
def set_progress_obj(new_progress_obj):
 
1041
 
    """Deprecated. Use: default_grabber.progress_obj = new_progress_obj"""
 
1042
 
    default_grabber.progress_obj = new_progress_obj
 
1044
 
def set_user_agent(new_user_agent):
 
1045
 
    """Deprecated. Use: default_grabber.user_agent = new_user_agent"""
 
1046
 
    default_grabber.user_agent = new_user_agent
 
1048
 
def retrygrab(url, filename=None, copy_local=0, close_connection=0,
 
1049
 
              progress_obj=None, throttle=None, bandwidth=None,
 
1050
 
              numtries=3, retrycodes=[-1,2,4,5,6,7], checkfunc=None):
 
1051
 
    """Deprecated. Use: urlgrab() with the retry arg instead"""
 
1052
 
    kwargs = {'copy_local' :  copy_local, 
 
1053
 
              'close_connection' : close_connection,
 
1054
 
              'progress_obj' : progress_obj, 
 
1055
 
              'throttle' : throttle, 
 
1056
 
              'bandwidth' : bandwidth,
 
1058
 
              'retrycodes' : retrycodes,
 
1059
 
              'checkfunc' : checkfunc 
 
1061
 
    return urlgrab(url, filename, **kwargs)
 
1064
 
#####################################################################
 
1068
 
    try: url, filename = sys.argv[1:3]
 
1070
 
        print 'usage:', sys.argv[0], \
 
1071
 
              '<url> <filename> [copy_local=0|1] [close_connection=0|1]'
 
1075
 
    for a in sys.argv[3:]:
 
1076
 
        k, v = string.split(a, '=', 1)
 
1080
 
    set_bandwidth(32 * 1024)
 
1081
 
    print "throttle: %s,  throttle bandwidth: %s B/s" % (default_grabber.throttle, 
 
1082
 
                                                        default_grabber.bandwidth)
 
1084
 
    try: from progress import text_progress_meter
 
1085
 
    except ImportError, e: pass
 
1086
 
    else: kwargs['progress_obj'] = text_progress_meter()
 
1088
 
    try: name = apply(urlgrab, (url, filename), kwargs)
 
1089
 
    except URLGrabError, e: print e
 
1090
 
    else: print 'LOCAL FILE:', name
 
1095
 
    try: url, filename = sys.argv[1:3]
 
1097
 
        print 'usage:', sys.argv[0], \
 
1098
 
              '<url> <filename> [copy_local=0|1] [close_connection=0|1]'
 
1102
 
    for a in sys.argv[3:]:
 
1103
 
        k, v = string.split(a, '=', 1)
 
1106
 
    try: from progress import text_progress_meter
 
1107
 
    except ImportError, e: pass
 
1108
 
    else: kwargs['progress_obj'] = text_progress_meter()
 
1112
 
    def cfunc(filename, hello, there='foo'):
 
1115
 
        rnum = random.random()
 
1117
 
            print 'forcing retry'
 
1118
 
            raise URLGrabError(-1, 'forcing retry')
 
1120
 
            print 'forcing failure'
 
1121
 
            raise URLGrabError(-2, 'forcing immediate failure')
 
1126
 
    kwargs['checkfunc'] = (cfunc, ('hello',), {'there':'there'})
 
1127
 
    try: name = apply(retrygrab, (url, filename), kwargs)
 
1128
 
    except URLGrabError, e: print e
 
1129
 
    else: print 'LOCAL FILE:', name
 
1131
 
def _file_object_test(filename=None):
 
1132
 
    import random, cStringIO, sys
 
1133
 
    if filename is None:
 
1135
 
    print 'using file "%s" for comparisons' % filename
 
1140
 
    for testfunc in [_test_file_object_smallread,
 
1141
 
                     _test_file_object_readall,
 
1142
 
                     _test_file_object_readline,
 
1143
 
                     _test_file_object_readlines]:
 
1144
 
        fo_input = cStringIO.StringIO(s_input)
 
1145
 
        fo_output = cStringIO.StringIO()
 
1146
 
        wrapper = URLGrabberFileObject(fo_input, None, 0)
 
1147
 
        print 'testing %-30s ' % testfunc.__name__,
 
1148
 
        testfunc(wrapper, fo_output)
 
1149
 
        s_output = fo_output.getvalue()
 
1150
 
        if s_output == s_input: print 'passed'
 
1151
 
        else: print 'FAILED'
 
1153
 
def _test_file_object_smallread(wrapper, fo_output):
 
1155
 
        s = wrapper.read(23)
 
1159
 
def _test_file_object_readall(wrapper, fo_output):
 
1163
 
def _test_file_object_readline(wrapper, fo_output):
 
1165
 
        s = wrapper.readline()
 
1169
 
def _test_file_object_readlines(wrapper, fo_output):
 
1170
 
    li = wrapper.readlines()
 
1171
 
    fo_output.write(string.join(li, ''))
 
1173
 
if __name__ == '__main__':
 
1176
 
    _file_object_test('test')