1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (C) 2005-2007 Thomas Perl <thp at perli.net>
6 # gPodder is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # gPodder is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 # download.py -- Download client using DownloadStatusManager
23 # Thomas Perl <thp@perli.net> 2007-09-15
25 # Based on libwget.py (2005-10-29)
28 from gpodder
.liblogger
import log
29 from gpodder
import libgpodder
30 from gpodder
import util
31 from gpodder
import services
40 from xml
.sax
import saxutils
42 class DownloadCancelledException(Exception): pass
45 class DownloadURLOpener(urllib
.FancyURLopener
):
46 version
= gpodder
.user_agent
48 def __init__( self
, channel
):
49 gl
= libgpodder
.gPodderLib()
50 if gl
.config
.proxy_use_environment
:
54 if gl
.config
.http_proxy
:
55 proxies
['http'] = gl
.config
.http_proxy
56 if gl
.config
.ftp_proxy
:
57 proxies
['ftp'] = gl
.config
.ftp_proxy
59 self
.channel
= channel
60 urllib
.FancyURLopener
.__init
__( self
, proxies
)
62 def prompt_user_passwd( self
, host
, realm
):
63 if self
.channel
.username
or self
.channel
.password
:
64 log( 'Authenticating as "%s" to "%s" for realm "%s".', self
.channel
.username
, host
, realm
, sender
= self
)
65 return ( self
.channel
.username
, self
.channel
.password
)
70 class DownloadThread(threading
.Thread
):
71 MAX_UPDATES_PER_SEC
= 1
73 def __init__( self
, channel
, episode
, notification
= None):
74 threading
.Thread
.__init
__( self
)
77 self
.channel
= channel
78 self
.episode
= episode
80 self
.notification
= notification
82 self
.url
= self
.episode
.url
83 self
.filename
= self
.episode
.local_filename()
84 self
.tempname
= os
.path
.join( os
.path
.dirname( self
.filename
), '.tmp-' + os
.path
.basename( self
.filename
))
86 gl
= libgpodder
.gPodderLib()
87 self
.limit_rate
= gl
.config
.limit_rate
88 self
.limit_rate_value
= gl
.config
.limit_rate_value
90 self
.cancelled
= False
92 self
.speed
= _('Queued')
94 self
.downloader
= DownloadURLOpener( self
.channel
)
95 self
.last_update
= 0.0
100 def status_updated( self
, count
, blockSize
, totalSize
):
102 self
.progress
= 100.0*float(count
*blockSize
)/float(totalSize
)
104 self
.progress
= 100.0
106 self
.calculate_speed( count
, blockSize
)
107 if self
.last_update
< time
.time() - (1.0 / self
.MAX_UPDATES_PER_SEC
):
108 services
.download_status_manager
.update_status( self
.download_id
, speed
= self
.speed
, progress
= self
.progress
)
109 self
.last_update
= time
.time()
112 util
.delete_file( self
.tempname
)
113 raise DownloadCancelledException()
115 def calculate_speed( self
, count
, blockSize
):
118 if self
.start_time
> 0:
119 passed
= now
- self
.start_time
121 speed
= (count
*blockSize
)/passed
125 self
.start_time
= now
126 passed
= now
- self
.start_time
127 speed
= count
*blockSize
128 self
.speed
= '%s/s' % libgpodder
.gPodderLib().format_filesize( speed
)
130 if self
.limit_rate
and speed
> self
.limit_rate_value
:
131 # calculate the time that should have passed to reach
132 # the desired download rate and wait if necessary
133 should_have_passed
= float(count
*blockSize
)/(self
.limit_rate_value
*1024.0)
134 if should_have_passed
> passed
:
135 # sleep a maximum of 10 seconds to not cause time-outs
136 delay
= min( 10.0, float(should_have_passed
-passed
))
140 self
.download_id
= services
.download_status_manager
.reserve_download_id()
141 services
.download_status_manager
.register_download_id( self
.download_id
, self
)
143 # Initial status update
144 services
.download_status_manager
.update_status( self
.download_id
, episode
= self
.episode
.title
, url
= self
.episode
.url
, speed
= self
.speed
, progress
= self
.progress
)
146 acquired
= services
.download_status_manager
.s_acquire()
152 util
.delete_file( self
.tempname
)
153 self
.downloader
.retrieve( self
.episode
.url
, self
.tempname
, reporthook
= self
.status_updated
)
154 shutil
.move( self
.tempname
, self
.filename
)
155 self
.channel
.addDownloadedItem( self
.episode
)
156 services
.download_status_manager
.download_completed(self
.download_id
)
158 services
.download_status_manager
.remove_download_id( self
.download_id
)
159 services
.download_status_manager
.s_release( acquired
)
160 except DownloadCancelledException
:
161 log( 'Download has been cancelled: %s', self
.episode
.title
, sender
= self
)
163 if self
.notification
!= None:
165 message
= _('An error happened while trying to download <b>%s</b>.') % ( saxutils
.escape( self
.episode
.title
), )
166 self
.notification( message
, title
)
167 log( 'Error "%s" while downloading "%s": %s', ioe
.strerror
, self
.episode
.title
, ioe
.filename
, sender
= self
)
169 log( 'Error while downloading "%s".', self
.episode
.title
, sender
= self
, traceback
= True)