2 from Screens.Screen import Screen
3 from Screens.MessageBox import MessageBox
6 from Components.ActionMap import ActionMap
7 from Components.Label import Label
10 from VariableProgressSource import VariableProgressSource
12 from Components.config import config
13 from urlparse import urlparse, urlunparse
17 def _parse(url, defaultPort = None):
19 parsed = urlparse(url)
21 path = urlunparse(('','')+parsed[2:])
23 if defaultPort is None:
31 host, port = parsed[1], defaultPort
34 username, host = host.split('@')
36 username, password = username.split(':')
44 host, port = host.split(':')
50 return scheme, host, port, path, username, password
52 def download(url, file, writeProgress = None, contextFactory = None, \
55 """Download a remote file and provide current-/total-length.
57 @param file: path to file on filesystem, or file-like object.
58 @param writeProgress: function or list of functions taking two parameters (pos, length)
60 See HTTPDownloader to see what extra args can be passed if remote file
61 is accessible via http or https. Both Backends should offer supportPartial.
64 scheme, host, port, path, username, password = _parse(url)
67 from FTPProgressDownloader import FTPProgressDownloader
69 if not (username and password):
70 username = 'anonymous'
71 password = 'my@email.com'
73 client = FTPProgressDownloader(
84 return client.deferred
86 # We force username and password here as we lack a satisfying input method
87 if username and password:
88 from base64 import encodestring
90 # twisted will crash if we don't rewrite this ;-)
91 url = scheme + '://' + host + ':' + str(port) + path
93 basicAuth = encodestring("%s:%s" % (username, password))
94 authHeader = "Basic " + basicAuth.strip()
95 AuthHeaders = {"Authorization": authHeader}
97 if kwargs.has_key("headers"):
98 kwargs["headers"].update(AuthHeaders)
100 kwargs["headers"] = AuthHeaders
102 from HTTPProgressDownloader import HTTPProgressDownloader
103 from twisted.internet import reactor
105 factory = HTTPProgressDownloader(url, file, writeProgress, *args, **kwargs)
106 if scheme == 'https':
107 from twisted.internet import ssl
108 if contextFactory is None:
109 contextFactory = ssl.ClientContextFactory()
110 reactor.connectSSL(host, port, factory, contextFactory)
112 reactor.connectTCP(host, port, factory)
114 return factory.deferred
116 class MediaDownloader(Screen):
117 """Simple Plugin which downloads a given file. If not targetfile is specified the user will be asked
118 for a location (see LocationBox). If doOpen is True the Plugin will try to open it after downloading."""
120 skin = """<screen name="MediaDownloader" position="100,150" size="540,95" >
121 <widget name="wait" position="2,10" size="500,30" valign="center" font="Regular;23" />
122 <widget source="progress" render="Progress" position="2,40" size="536,20" />
123 <widget name="eta" position="2,65" size="200,30" font="Regular;23" />
124 <widget name="speed" position="338,65" size="200,30" halign="right" font="Regular;23" />
127 def __init__(self, session, file, askOpen = False, downloadTo = None, callback = None):
128 Screen.__init__(self, session)
130 # Save arguments local
132 self.askOpen = askOpen
133 self.filename = downloadTo
134 self.callback = callback
136 # Init what we need for progress callback
141 # Inform user about whats currently done
142 self["wait"] = Label(_("Downloading..."))
143 self["progress"] = VariableProgressSource()
144 self["eta"] = Label(_("ETA ??:?? h")) # XXX: we could just leave eta and speed empty
145 self["speed"] = Label(_("?? kb/s"))
147 # Set Limit if we know it already (Server might not tell it)
149 self["progress"].writeValues(0, self.file.size*1048576)
151 # Call getFilename as soon as we are able to open a new screen
152 self.onExecBegin.append(self.getFilename)
154 def getFilename(self):
155 self.onExecBegin.remove(self.getFilename)
157 # If we have a filename (downloadTo provided) start fetching
158 if self.filename is not None:
160 # Else open LocationBox to determine where to save
162 # TODO: determine basename without os.path?
164 from Screens.LocationBox import LocationBox
166 self.session.openWithCallback(
170 path.basename(self.file.path),
171 minFree = self.file.size,
172 bookmarks = config.plugins.mediadownloader.bookmarks
175 def gotFilename(self, res):
176 # If we got a filename try to fetch file
190 self["progress"].writeValues,
195 d.addCallback(self.gotFile).addErrback(self.error)
197 def gotProgress(self, pos, max):
198 newTime = time.time()
199 # Check if we're called the first time (got total)
200 lastTime = self.lastTime
202 self.lastTime = newTime
204 # We dont want to update more often than every two sec (could be done by a timer, but this should give a more accurate result though it might lag)
205 elif int(newTime - lastTime) >= 2:
208 lastApprox = round(((newLength - self.lastLength) / (newTime - lastTime) / 1024), 2)
210 secLen = int(round(((max-pos) / 1024) / lastApprox))
211 self["eta"].setText(_("ETA %d:%02d min") % (secLen / 60, secLen % 60))
212 self["speed"].setText(_("%d kb/s") % (lastApprox))
214 self.lastApprox = lastApprox
215 self.lastLength = newLength
216 self.lastTime = newTime
218 def openCallback(self, res):
219 from Components.Scanner import openFile
221 # Try to open file if res was True
222 if res and not openFile(self.session, None, self.filename):
225 _("No suitable Viewer found!"),
226 type = MessageBox.TYPE_ERROR,
230 # Calback with Filename on success
231 if self.callback is not None:
232 self.callback(self.filename)
236 def gotFile(self, data = ""):
237 # Ask if file should be opened unless told not to
239 self.session.openWithCallback(
242 _("Do you want to try to open the downloaded file?"),
243 type = MessageBox.TYPE_YESNO
245 # Otherwise callback and close
247 # Calback with Filename on success
248 if self.callback is not None:
249 self.callback(self.filename)
253 def error(self, msg = ""):
255 print "[MediaDownloader] Error downloading:", msg
259 _("Error while downloading file %s") % (self.file.path),
260 type = MessageBox.TYPE_ERROR,
264 # Calback with None on failure
265 if self.callback is not None: