When sending via Bluetooth, always rename/copy the file
[gpodder.git] / src / gpodder / download.py
blobaf08bb44f3696dd9af53cb45acf471d5cbabe7f6
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
32 import gpodder
34 import threading
35 import urllib
36 import shutil
37 import os.path
38 import time
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:
51 proxies = None
52 else:
53 proxies = {}
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 )
67 return ( None, None )
70 class DownloadThread(threading.Thread):
71 MAX_UPDATES_PER_SEC = 1
73 def __init__( self, channel, episode, notification = None):
74 threading.Thread.__init__( self)
75 self.setDaemon( True)
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
91 self.start_time = 0.0
92 self.speed = _('Queued')
93 self.progress = 0.0
94 self.downloader = DownloadURLOpener( self.channel)
95 self.last_update = 0.0
97 def cancel( self):
98 self.cancelled = True
100 def status_updated( self, count, blockSize, totalSize):
101 if totalSize:
102 self.progress = 100.0*float(count*blockSize)/float(totalSize)
103 else:
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()
111 if self.cancelled:
112 util.delete_file( self.tempname)
113 raise DownloadCancelledException()
115 def calculate_speed( self, count, blockSize):
116 if count % 5 == 0:
117 now = time.time()
118 if self.start_time > 0:
119 passed = now - self.start_time
120 if passed > 0:
121 speed = (count*blockSize)/passed
122 else:
123 speed = 0
124 else:
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))
137 time.sleep( delay)
139 def run( self):
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()
147 try:
148 try:
149 if self.cancelled:
150 return
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)
157 finally:
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)
162 except IOError, ioe:
163 if self.notification != None:
164 title = ioe.strerror
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)
168 except:
169 log( 'Error while downloading "%s".', self.episode.title, sender = self, traceback = True)