~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to urlgrabber/grabber.py

  • Committer: Martin Pool
  • Date: 2005-06-10 06:34:26 UTC
  • Revision ID: mbp@sourcefrog.net-20050610063426-cfcf5c0f96c271ec
- split out updated progress indicator

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.
 
5
#
 
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.
 
10
#
 
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
 
16
 
 
17
# This file is part of urlgrabber, a high-level cross-protocol url-grabber
 
18
# Copyright 2002-2004 Michael D. Stenner, Ryan Tomayko
 
19
 
 
20
"""A high-level cross-protocol url-grabber.
 
21
 
 
22
GENERAL ARGUMENTS (kwargs)
 
23
 
 
24
  Where possible, the module-level default is indicated, and legal
 
25
  values are provided.
 
26
 
 
27
  copy_local = 0   [0|1]
 
28
 
 
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
 
32
    option is 0.
 
33
 
 
34
  close_connection = 0   [0|1]
 
35
 
 
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).
 
41
 
 
42
  keepalive = 1   [0|1]
 
43
 
 
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).
 
47
 
 
48
  progress_obj = None
 
49
 
 
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
 
54
      po.end()
 
55
 
 
56
  text = None
 
57
  
 
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.
 
60
 
 
61
  throttle = 1.0
 
62
 
 
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.
 
68
 
 
69
  timeout = None
 
70
 
 
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
 
77
 
 
78
  bandwidth = 0
 
79
 
 
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
 
84
    more information.
 
85
 
 
86
  range = None
 
87
 
 
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.
 
94
 
 
95
    If set to None, no range will be used.
 
96
    
 
97
  reget = None   [None|'simple'|'check_timestamp']
 
98
 
 
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:
 
102
 
 
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.
 
106
 
 
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.
 
112
 
 
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.
 
117
 
 
118
  user_agent = 'urlgrabber/VERSION'
 
119
 
 
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".
 
123
 
 
124
  http_headers = None
 
125
 
 
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
 
128
    can do
 
129
      http_headers = (('Pragma', 'no-cache'),)
 
130
 
 
131
  ftp_headers = None
 
132
 
 
133
    this is just like http_headers, but will be used for ftp requests.
 
134
 
 
135
  proxies = None
 
136
 
 
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
 
139
    and https URLs:
 
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
 
145
    be used.
 
146
 
 
147
  prefix = None
 
148
 
 
149
    a url prefix that will be prepended to all requested urls.  For
 
150
    example:
 
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.
 
158
 
 
159
  opener = None
 
160
  
 
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.
 
169
    
 
170
RETRY RELATED ARGUMENTS
 
171
 
 
172
  retry = None
 
173
 
 
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.
 
178
 
 
179
  retrycodes = [-1,2,4,5,6,7]
 
180
 
 
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
 
184
    explicitly.
 
185
 
 
186
  checkfunc = None
 
187
 
 
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.
 
193
 
 
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.
 
199
 
 
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
 
206
    can do so like this:
 
207
 
 
208
      checkfunc=(function, ('arg1', 2), {'kwarg': 3})
 
209
 
 
210
    if the downloaded file has filename /tmp/stuff, then this will
 
211
    result in this call (for urlgrab):
 
212
 
 
213
      function(obj, 'arg1', 2, kwarg=3)
 
214
      # obj.filename = '/tmp/stuff'
 
215
      # obj.url = 'http://foo.com/stuff'
 
216
      
 
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.
 
219
 
 
220
  failure_callback = None
 
221
 
 
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.
 
228
 
 
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.
 
233
 
 
234
BANDWIDTH THROTTLING
 
235
 
 
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
 
239
  available bandwidth.
 
240
 
 
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.
 
245
 
 
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.
 
249
 
 
250
  THROTTLING EXAMPLES:
 
251
 
 
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
 
254
  throttling options:
 
255
 
 
256
  *) set_bandwidth(12500000); set_throttle(0.5) # throttle is a float
 
257
 
 
258
     This will limit urlgrab to use half of your available bandwidth.
 
259
 
 
260
  *) set_throttle(6250000) # throttle is an int
 
261
 
 
262
     This will also limit urlgrab to use half of your available
 
263
     bandwidth, regardless of what bandwidth is set to.
 
264
 
 
265
  *) set_throttle(6250000); set_throttle(1.0) # float
 
266
 
 
267
     Use half your bandwidth
 
268
 
 
269
  *) set_throttle(6250000); set_throttle(2.0) # float
 
270
 
 
271
    Use up to 12,500,000 Bytes per second (your nominal max bandwidth)
 
272
 
 
273
  *) set_throttle(6250000); set_throttle(0) # throttle = 0
 
274
 
 
275
     Disable throttling - this is more efficient than a very large
 
276
     throttle setting.
 
277
 
 
278
  *) set_throttle(0); set_throttle(1.0) # throttle is float, bandwidth = 0
 
279
 
 
280
     Disable throttling - this is the default when the module is loaded.
 
281
 
 
282
  SUGGESTED AUTHOR IMPLEMENTATION (THROTTLING)
 
283
 
 
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.
 
287
 
 
288
  Also, you may want to convert the units to something more convenient
 
289
  than bytes/second, such as kbps or kB/s, etc.
 
290
 
 
291
"""
 
292
 
 
293
# $Id: grabber.py,v 1.39 2005/03/03 00:54:23 mstenner Exp $
 
294
 
 
295
import os
 
296
import os.path
 
297
import urlparse
 
298
import rfc822
 
299
import time
 
300
import string
 
301
import urllib
 
302
import urllib2
 
303
from stat import *  # S_* and ST_*
 
304
 
 
305
try:
 
306
    exec('from ' + (__name__.split('.'))[0] + ' import __version__')
 
307
except:
 
308
    __version__ = '???'
 
309
 
 
310
auth_handler = urllib2.HTTPBasicAuthHandler( \
 
311
     urllib2.HTTPPasswordMgrWithDefaultRealm())
 
312
 
 
313
DEBUG=0
 
314
 
 
315
try:
 
316
    from i18n import _
 
317
except ImportError, msg:
 
318
    def _(st): return st
 
319
 
 
320
try:
 
321
    from httplib import HTTPException
 
322
except ImportError, msg:
 
323
    HTTPException = None
 
324
 
 
325
try:
 
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
 
331
else:
 
332
    keepalive_handler = HTTPHandler()
 
333
 
 
334
try:
 
335
    # add in range support conditionally too
 
336
    from urlgrabber.byterange import HTTPRangeHandler, FileRangeHandler, \
 
337
         FTPRangeHandler, range_tuple_normalize, range_tuple_to_header, \
 
338
         RangeError
 
339
except ImportError, msg:
 
340
    range_handlers = ()
 
341
    RangeError = None
 
342
    have_range = 0
 
343
else:
 
344
    range_handlers = (HTTPRangeHandler(), FileRangeHandler(), FTPRangeHandler())
 
345
    have_range = 1
 
346
 
 
347
 
 
348
# check whether socket timeout support is available (Python >= 2.3)
 
349
import socket
 
350
try:
 
351
    TimeoutError = socket.timeout
 
352
    have_socket_timeout = True
 
353
except AttributeError:
 
354
    TimeoutError = None
 
355
    have_socket_timeout = False
 
356
 
 
357
class URLGrabError(IOError):
 
358
    """
 
359
    URLGrabError error codes:
 
360
 
 
361
      URLGrabber error codes (0 -- 255)
 
362
        0    - everything looks good (you should never see this)
 
363
        1    - malformed url
 
364
        2    - local file doesn't exist
 
365
        3    - request for non-file local file (dir, etc)
 
366
        4    - IOError on fetch
 
367
        5    - OSError on fetch
 
368
        6    - no content length header when we expected one
 
369
        7    - HTTPException
 
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
 
374
        12   - Socket timeout.
 
375
 
 
376
      MirrorGroup error codes (256 -- 511)
 
377
        256  - No more mirrors left to try
 
378
 
 
379
      Custom (non-builtin) classes derived from MirrorGroup (512 -- 767)
 
380
        [ this range reserved for application-specific error codes ]
 
381
 
 
382
      Retry codes (< 0)
 
383
        -1   - retry the download, unknown reason
 
384
 
 
385
    Note: to test which group a code is in, you can simply do integer
 
386
    division by 256: e.errno / 256
 
387
 
 
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
 
395
    what failed.
 
396
 
 
397
    You can use these error codes like so:
 
398
      try: urlgrab(url)
 
399
      except URLGrabError, e:
 
400
         if e.errno == 3: ...
 
401
           # or
 
402
         print e.strerror
 
403
           # or simply
 
404
         print e  #### print '[Errno %i] %s' % (e.errno, e.strerror)
 
405
    """
 
406
    pass
 
407
 
 
408
class CallbackObject:
 
409
    """Container for returned callback data.
 
410
 
 
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.
 
416
 
 
417
    It is possible that this class will have some greater
 
418
    functionality in the future.
 
419
    """
 
420
    pass
 
421
 
 
422
def close_all():
 
423
    """close any open keepalive connections"""
 
424
    if keepalive_handler: keepalive_handler.close_all()
 
425
 
 
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.
 
431
    
 
432
    See module documentation for a description of possible kwargs.
 
433
    """
 
434
    return default_grabber.urlgrab(url, filename, **kwargs)
 
435
 
 
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.
 
441
    
 
442
    See module documentation for a description of possible kwargs.
 
443
    """
 
444
    return default_grabber.urlopen(url, **kwargs)
 
445
 
 
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'
 
451
    
 
452
    See module documentation for a description of possible kwargs.
 
453
    """
 
454
    return default_grabber.urlread(url, limit, **kwargs)
 
455
 
 
456
 
 
457
class URLGrabberOptions:
 
458
    """Class to ease kwargs handling."""
 
459
 
 
460
    def __init__(self, delegate=None, **kwargs):
 
461
        """Initialize URLGrabberOptions object.
 
462
        Set default values for all options and then update options specified
 
463
        in kwargs.
 
464
        """
 
465
        self.delegate = delegate
 
466
        if delegate is None:
 
467
            self._set_defaults()
 
468
        self._set_attributes(**kwargs)
 
469
    
 
470
    def __getattr__(self, name):
 
471
        if self.delegate and hasattr(self.delegate, name):
 
472
            return getattr(self.delegate, name)
 
473
        raise AttributeError, name
 
474
    
 
475
    def raw_throttle(self):
 
476
        """Calculate raw throttle value from throttle and bandwidth 
 
477
        values.
 
478
        """
 
479
        if self.throttle <= 0:  
 
480
            return 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
 
485
        
 
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.
 
490
        """
 
491
        return URLGrabberOptions(delegate=self, **kwargs)
 
492
        
 
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') \
 
501
                               % (self.reget, ))
 
502
 
 
503
    def _set_defaults(self):
 
504
        """Set all options to their default values. 
 
505
        When adding new options, make sure a default is
 
506
        provided here.
 
507
        """
 
508
        self.progress_obj = None
 
509
        self.throttle = 1.0
 
510
        self.bandwidth = 0
 
511
        self.retry = None
 
512
        self.retrycodes = [-1,2,4,5,6,7]
 
513
        self.checkfunc = None
 
514
        self.copy_local = 0
 
515
        self.close_connection = 0
 
516
        self.range = None
 
517
        self.user_agent = 'urlgrabber/%s' % __version__
 
518
        self.keepalive = 1
 
519
        self.proxies = None
 
520
        self.reget = None
 
521
        self.failure_callback = None
 
522
        self.prefix = None
 
523
        self.opener = None
 
524
        self.cache_openers = True
 
525
        self.timeout = None
 
526
        self.text = None
 
527
        self.http_headers = None
 
528
        self.ftp_headers = None
 
529
 
 
530
class URLGrabber:
 
531
    """Provides easy opening of URLs with a variety of options.
 
532
    
 
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.
 
535
    
 
536
    New objects inherit default values from default_grabber.
 
537
    """
 
538
    
 
539
    def __init__(self, **kwargs):
 
540
        self.opts = URLGrabberOptions(**kwargs)
 
541
    
 
542
    def _retry(self, opts, func, *args):
 
543
        tries = 0
 
544
        while 1:
 
545
            tries = tries + 1
 
546
            try:
 
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
 
558
                    # that up, though
 
559
                    obj = CallbackObject()
 
560
                    obj.exception = e
 
561
                    obj.url = args[0]
 
562
                    cb_func(obj, *cb_args, **cb_kwargs)
 
563
    
 
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.
 
570
        """
 
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)
 
576
    
 
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.
 
582
        """
 
583
        opts = self.opts.derive(**kwargs)
 
584
        (url, parts) = self._parse_url(url)
 
585
        (scheme, host, path, parm, query, frag) = parts
 
586
        if filename is None:
 
587
            if scheme in [ 'http', 'https' ]:
 
588
                filename = os.path.basename( urllib.unquote(path) )
 
589
            else:
 
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 
 
593
            # copy currently
 
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, ))
 
600
            elif not opts.range:
 
601
                return path
 
602
        
 
603
        def retryfunc(opts, url, filename):
 
604
            fo = URLGrabberFileObject(url, filename, opts)
 
605
            try:
 
606
                fo._do_grab()
 
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
 
612
                    obj.url = url
 
613
                    apply(cb_func, (obj, )+cb_args, cb_kwargs)
 
614
            finally:
 
615
                fo.close()
 
616
            return filename
 
617
        
 
618
        return self._retry(opts, retryfunc, url, filename)
 
619
    
 
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'
 
626
        """
 
627
        opts = self.opts.derive(**kwargs)
 
628
        (url, parts) = self._parse_url(url)
 
629
        if limit is not None:
 
630
            limit = limit + 1
 
631
            
 
632
        def retryfunc(opts, url, limit):
 
633
            fo = URLGrabberFileObject(url, filename=None, opts=opts)
 
634
            s = ''
 
635
            try:
 
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)
 
642
 
 
643
                if not opts.checkfunc is None:
 
644
                    cb_func, cb_args, cb_kwargs = \
 
645
                             self._make_callback(opts.checkfunc)
 
646
                    obj = CallbackObject()
 
647
                    obj.data = s
 
648
                    obj.url = url
 
649
                    apply(cb_func, (obj, )+cb_args, cb_kwargs)
 
650
            finally:
 
651
                fo.close()
 
652
            return s
 
653
            
 
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))
 
658
        return s
 
659
        
 
660
    def _parse_url(self,url):
 
661
        """break up the url into its component parts
 
662
 
 
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
 
666
 
 
667
        it returns the (cleaned) url and a tuple of component parts
 
668
        """
 
669
        if self.opts.prefix:
 
670
            p = self.opts.prefix
 
671
            if p[-1] == '/' or url[0] == '/': url = p + url
 
672
            else: url = p + '/' + url
 
673
            
 
674
        (scheme, host, path, parm, query, frag) = \
 
675
                                             urlparse.urlparse(url)
 
676
        if not scheme:
 
677
            if not url[0] == '/': url = os.path.abspath(url)
 
678
            url = 'file:' + 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']:
 
684
            try:
 
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)
 
693
        return url, parts
 
694
        
 
695
    def _make_callback(self, callback_obj):
 
696
        if callable(callback_obj):
 
697
            return callback_obj, (), {}
 
698
        else:
 
699
            return callback_obj
 
700
 
 
701
# create the default URLGrabber used by urlXXX functions.
 
702
# NOTE: actual defaults are set in URLGrabberOptions
 
703
default_grabber = URLGrabber()
 
704
 
 
705
class URLGrabberFileObject:
 
706
    """This is a file-object wrapper that supports progress objects 
 
707
    and throttling.
 
708
 
 
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.
 
714
    """
 
715
 
 
716
    def __init__(self, url, filename, opts):
 
717
        self.url = url
 
718
        self.filename = filename
 
719
        self.opts = opts
 
720
        self.fo = None
 
721
        self._rbuf = ''
 
722
        self._rbufsize = 1024*8
 
723
        self._ttime = time.time()
 
724
        self._tsize = 0
 
725
        self._amount_read = 0
 
726
        self._opener = None
 
727
        self._do_open()
 
728
        
 
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
 
736
    
 
737
    def _get_opener(self):
 
738
        """Build a urllib2 OpenerDirector based on request options."""
 
739
        if self.opts.opener:
 
740
            return self.opts.opener
 
741
        elif self._opener is None:
 
742
            handlers = []
 
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
 
748
            # chokes.
 
749
            if self.opts.proxies:
 
750
                handlers.append( CachedProxyHandler(self.opts.proxies) )
 
751
 
 
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
                # -------------------------------------------------------
 
772
                    
 
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)
 
780
            else:
 
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 = []
 
785
        return self._opener
 
786
        
 
787
    def _do_open(self):
 
788
        opener = self._get_opener()
 
789
 
 
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
 
793
 
 
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.
 
798
            fetch_again = 0
 
799
            try:
 
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
 
803
            except (TypeError,):
 
804
                fetch_again = 1
 
805
            
 
806
            if fetch_again:
 
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.
 
810
                fo.close()
 
811
                self.opts.reget = None
 
812
                del req.headers['Range']
 
813
                self._build_range(req)
 
814
                fo, hdr = self._make_request(req, opener)
 
815
 
 
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.
 
822
            self.read = fo.read
 
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), 
 
830
                                         length,
 
831
                                         text=self.opts.text)
 
832
            self.opts.progress_obj.update(0)
 
833
        (self.fo, self.hdr) = (fo, hdr)
 
834
    
 
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:
 
842
                req.add_header(h, v)
 
843
        if self.opts.ftp_headers and req_type == 'ftp':
 
844
            for h, v in self.opts.ftp_headers:
 
845
                req.add_header(h, v)
 
846
 
 
847
    def _build_range(self, req):
 
848
        self.reget_time = None
 
849
        self.append = 0
 
850
        reget_length = 0
 
851
        rt = 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
 
854
            try:
 
855
                s = os.stat(self.filename)
 
856
            except OSError:
 
857
                pass
 
858
            else:
 
859
                self.reget_time = s[ST_MTIME]
 
860
                reget_length = s[ST_SIZE]
 
861
                rt = reget_length, ''
 
862
                self.append = 1
 
863
                
 
864
        if self.opts.range:
 
865
            if not have_range:
 
866
                raise URLGrabError(10, _('Byte range requested but range '\
 
867
                                         'support unavailable'))
 
868
            rt = self.opts.range
 
869
            if rt[0]: rt = (rt[0] + reget_length, rt[1])
 
870
 
 
871
        if rt:
 
872
            header = range_tuple_to_header(rt)
 
873
            if header: req.add_header('Range', header)
 
874
 
 
875
    def _make_request(self, req, opener):
 
876
        try:
 
877
            if have_socket_timeout and self.opts.timeout:
 
878
                old_to = socket.getdefaulttimeout()
 
879
                socket.setdefaulttimeout(self.opts.timeout)
 
880
                try:
 
881
                    fo = opener.open(req)
 
882
                finally:
 
883
                    socket.setdefaulttimeout(old_to)
 
884
            else:
 
885
                fo = opener.open(req)
 
886
            hdr = fo.info()
 
887
        except ValueError, e:
 
888
            raise URLGrabError(1, _('Bad URL: %s') % (e, ))
 
889
        except RangeError, e:
 
890
            raise URLGrabError(9, _('%s') % (e, ))
 
891
        except IOError, e:
 
892
            if hasattr(e, 'reason') and have_socket_timeout and \
 
893
                   isinstance(e.reason, TimeoutError):
 
894
                raise URLGrabError(12, _('Timeout: %s') % (e, ))
 
895
            else:
 
896
                raise URLGrabError(4, _('IOError: %s') % (e, ))
 
897
        except OSError, 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))
 
902
        else:
 
903
            return (fo, hdr)
 
904
        
 
905
    def _do_grab(self):
 
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')
 
909
        bs = 1024*8
 
910
        size = 0
 
911
 
 
912
        block = self.read(bs)
 
913
        size = size + len(block)
 
914
        while block:
 
915
            new_fo.write(block)
 
916
            block = self.read(bs)
 
917
            size = size + len(block)
 
918
 
 
919
        new_fo.close()
 
920
        try:
 
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
 
925
 
 
926
        return size
 
927
    
 
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:
 
936
            L = len(self._rbuf)
 
937
            if amt > L:
 
938
                amt = amt - L
 
939
            else:
 
940
                return
 
941
 
 
942
        # if we've made it here, then we don't have enough in the buffer
 
943
        # and we need to read more.
 
944
 
 
945
        buf = [self._rbuf]
 
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()
 
954
                
 
955
            # now read some data, up to self._rbufsize
 
956
            if amt is None: readamount = self._rbufsize
 
957
            else:           readamount = min(amt, self._rbufsize)
 
958
            try:
 
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, ))
 
964
            newsize = len(new)
 
965
            if not newsize: break # no more to read
 
966
 
 
967
            if amt: amt = amt - newsize
 
968
            buf.append(new)
 
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)
 
974
 
 
975
        self._rbuf = string.join(buf, '')
 
976
        return
 
977
 
 
978
    def read(self, amt=None):
 
979
        self._fill_buffer(amt)
 
980
        if amt is None:
 
981
            s, self._rbuf = self._rbuf, ''
 
982
        else:
 
983
            s, self._rbuf = self._rbuf[:amt], self._rbuf[amt:]
 
984
        return s
 
985
 
 
986
    def readline(self, limit=-1):
 
987
        i = string.find(self._rbuf, '\n')
 
988
        while i < 0 and not (0 < limit <= len(self._rbuf)):
 
989
            L = 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)
 
993
 
 
994
        if i < 0: i = len(self._rbuf)
 
995
        else: i = i+1
 
996
        if 0 <= limit < len(self._rbuf): i = limit
 
997
 
 
998
        s, self._rbuf = self._rbuf[:i], self._rbuf[i:]
 
999
        return s
 
1000
 
 
1001
    def close(self):
 
1002
        if self.opts.progress_obj:
 
1003
            self.opts.progress_obj.end(self._amount_read)
 
1004
        self.fo.close()
 
1005
        if self.opts.close_connection:
 
1006
            try: self.fo.close_connection()
 
1007
            except: pass
 
1008
 
 
1009
_handler_cache = []
 
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)
 
1015
            return opener
 
1016
    opener = urllib2.build_opener(*handlers)
 
1017
    _handler_cache.append( (handlers, opener) )
 
1018
    return opener
 
1019
 
 
1020
_proxy_cache = []
 
1021
def CachedProxyHandler(proxies):
 
1022
    for (pdict, handler) in _proxy_cache:
 
1023
        if pdict == proxies:
 
1024
            break
 
1025
    else:
 
1026
        handler = urllib2.ProxyHandler(proxies)
 
1027
        _proxy_cache.append( (proxies, handler) )
 
1028
    return handler
 
1029
 
 
1030
#####################################################################
 
1031
# DEPRECATED FUNCTIONS
 
1032
def set_throttle(new_throttle):
 
1033
    """Deprecated. Use: default_grabber.throttle = new_throttle"""
 
1034
    default_grabber.throttle = new_throttle
 
1035
 
 
1036
def set_bandwidth(new_bandwidth):
 
1037
    """Deprecated. Use: default_grabber.bandwidth = new_bandwidth"""
 
1038
    default_grabber.bandwidth = new_bandwidth
 
1039
 
 
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
 
1043
 
 
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
 
1047
    
 
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,
 
1057
              'retry' : numtries,
 
1058
              'retrycodes' : retrycodes,
 
1059
              'checkfunc' : checkfunc 
 
1060
              }
 
1061
    return urlgrab(url, filename, **kwargs)
 
1062
 
 
1063
        
 
1064
#####################################################################
 
1065
#  TESTING
 
1066
def _main_test():
 
1067
    import sys
 
1068
    try: url, filename = sys.argv[1:3]
 
1069
    except ValueError:
 
1070
        print 'usage:', sys.argv[0], \
 
1071
              '<url> <filename> [copy_local=0|1] [close_connection=0|1]'
 
1072
        sys.exit()
 
1073
 
 
1074
    kwargs = {}
 
1075
    for a in sys.argv[3:]:
 
1076
        k, v = string.split(a, '=', 1)
 
1077
        kwargs[k] = int(v)
 
1078
 
 
1079
    set_throttle(1.0)
 
1080
    set_bandwidth(32 * 1024)
 
1081
    print "throttle: %s,  throttle bandwidth: %s B/s" % (default_grabber.throttle, 
 
1082
                                                        default_grabber.bandwidth)
 
1083
 
 
1084
    try: from progress import text_progress_meter
 
1085
    except ImportError, e: pass
 
1086
    else: kwargs['progress_obj'] = text_progress_meter()
 
1087
 
 
1088
    try: name = apply(urlgrab, (url, filename), kwargs)
 
1089
    except URLGrabError, e: print e
 
1090
    else: print 'LOCAL FILE:', name
 
1091
 
 
1092
 
 
1093
def _retry_test():
 
1094
    import sys
 
1095
    try: url, filename = sys.argv[1:3]
 
1096
    except ValueError:
 
1097
        print 'usage:', sys.argv[0], \
 
1098
              '<url> <filename> [copy_local=0|1] [close_connection=0|1]'
 
1099
        sys.exit()
 
1100
 
 
1101
    kwargs = {}
 
1102
    for a in sys.argv[3:]:
 
1103
        k, v = string.split(a, '=', 1)
 
1104
        kwargs[k] = int(v)
 
1105
 
 
1106
    try: from progress import text_progress_meter
 
1107
    except ImportError, e: pass
 
1108
    else: kwargs['progress_obj'] = text_progress_meter()
 
1109
 
 
1110
    global DEBUG
 
1111
    #DEBUG = 1
 
1112
    def cfunc(filename, hello, there='foo'):
 
1113
        print hello, there
 
1114
        import random
 
1115
        rnum = random.random()
 
1116
        if rnum < .5:
 
1117
            print 'forcing retry'
 
1118
            raise URLGrabError(-1, 'forcing retry')
 
1119
        if rnum < .75:
 
1120
            print 'forcing failure'
 
1121
            raise URLGrabError(-2, 'forcing immediate failure')
 
1122
        print 'success'
 
1123
        return
 
1124
        
 
1125
    close_all()
 
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
 
1130
 
 
1131
def _file_object_test(filename=None):
 
1132
    import random, cStringIO, sys
 
1133
    if filename is None:
 
1134
        filename = __file__
 
1135
    print 'using file "%s" for comparisons' % filename
 
1136
    fo = open(filename)
 
1137
    s_input = fo.read()
 
1138
    fo.close()
 
1139
 
 
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'
 
1152
            
 
1153
def _test_file_object_smallread(wrapper, fo_output):
 
1154
    while 1:
 
1155
        s = wrapper.read(23)
 
1156
        fo_output.write(s)
 
1157
        if not s: return
 
1158
 
 
1159
def _test_file_object_readall(wrapper, fo_output):
 
1160
    s = wrapper.read()
 
1161
    fo_output.write(s)
 
1162
 
 
1163
def _test_file_object_readline(wrapper, fo_output):
 
1164
    while 1:
 
1165
        s = wrapper.readline()
 
1166
        fo_output.write(s)
 
1167
        if not s: return
 
1168
 
 
1169
def _test_file_object_readlines(wrapper, fo_output):
 
1170
    li = wrapper.readlines()
 
1171
    fo_output.write(string.join(li, ''))
 
1172
 
 
1173
if __name__ == '__main__':
 
1174
    _main_test()
 
1175
    _retry_test()
 
1176
    _file_object_test('test')
 
1177