[MerlinMusicPlayer] fixed google cover display
[enigma2-plugins.git] / mediadownloader / src / MediaDownloader.py
1 from __future__ import print_function
2
3 # for localized messages
4 from . import _
5
6 # GUI (Screens)
7 from Screens.Screen import Screen
8 from Screens.MessageBox import MessageBox
9
10 # GUI (Components)
11 from Components.ActionMap import ActionMap
12 from Components.Sources.StaticText import StaticText
13
14 # Download
15 from VariableProgressSource import VariableProgressSource
16
17 from Components.config import config
18 try:
19         from urlparse import urlparse, urlunparse
20 except ImportError as ie:
21         from urllib.parse import urlparse, urlunparse
22
23 import time
24
25 def _parse(url, defaultPort = None):
26         url = url.strip()
27         parsed = urlparse(url)
28         scheme = parsed[0]
29         path = urlunparse(('','')+parsed[2:])
30
31         if defaultPort is None:
32                 if scheme == 'https':
33                         defaultPort = 443
34                 elif scheme == 'ftp':
35                         defaultPort = 21
36                 else:
37                         defaultPort = 80
38
39         host, port = parsed[1], defaultPort
40
41         if '@' in host:
42                 username, host = host.split('@')
43                 if ':' in username:
44                         username, password = username.split(':')
45                 else:
46                         password = ""
47         else:
48                 username = ""
49                 password = ""
50
51         if ':' in host:
52                 host, port = host.split(':')
53                 port = int(port)
54
55         if path == "":
56                 path = "/"
57
58         return scheme, host, port, path, username, password
59
60 def download(url, file, writeProgress = None, contextFactory = None, \
61         *args, **kwargs):
62
63         """Download a remote file and provide current-/total-length.
64
65         @param file: path to file on filesystem, or file-like object.
66         @param writeProgress: function or list of functions taking two parameters (pos, length)
67
68         See HTTPDownloader to see what extra args can be passed if remote file
69         is accessible via http or https. Both Backends should offer supportPartial.
70         """
71
72         scheme, host, port, path, username, password = _parse(url)
73
74         if scheme == 'ftp':
75                 from FTPProgressDownloader import FTPProgressDownloader
76
77                 if not (username and password):
78                         username = 'anonymous'
79                         password = 'my@email.com'
80
81                 client = FTPProgressDownloader(
82                         host,
83                         port,
84                         path,
85                         file,
86                         username,
87                         password,
88                         writeProgress,
89                         *args,
90                         **kwargs
91                 )
92                 return client.deferred
93
94         # We force username and password here as we lack a satisfying input method
95         if username and password:
96                 from base64 import encodestring
97
98                 # twisted will crash if we don't rewrite this ;-)
99                 url = scheme + '://' + host + ':' + str(port) + path
100
101                 basicAuth = encodestring("%s:%s" % (username, password))
102                 authHeader = "Basic " + basicAuth.strip()
103                 AuthHeaders = {"Authorization": authHeader}
104
105                 if "headers" in kwargs:
106                         kwargs["headers"].update(AuthHeaders)
107                 else:
108                         kwargs["headers"] = AuthHeaders
109
110         from HTTPProgressDownloader import HTTPProgressDownloader
111         from twisted.internet import reactor
112
113         factory = HTTPProgressDownloader(url, file, writeProgress, *args, **kwargs)
114         if scheme == 'https':
115                 from twisted.internet import ssl
116                 if contextFactory is None:
117                         contextFactory = ssl.ClientContextFactory()
118                 reactor.connectSSL(host, port, factory, contextFactory)
119         else:
120                 reactor.connectTCP(host, port, factory)
121
122         return factory.deferred
123
124 class MediaDownloader(Screen):
125         """Simple Plugin which downloads a given file. If not targetfile is specified the user will be asked
126         for a location (see LocationBox). If doOpen is True the Plugin will try to open it after downloading."""
127
128         skin = """<screen name="MediaDownloader" position="center,center" size="540,95" >
129                         <widget source="wait" render="Label" position="2,10" size="500,30" valign="center" font="Regular;23" />
130                         <widget source="progress" render="Progress" position="2,40" size="536,20" />
131                         <widget source="eta" render="Label" position="2,65" size="200,30" font="Regular;23" />
132                         <widget source="speed" render="Label" position="338,65" size="200,30" halign="right" font="Regular;23" />
133                 </screen>"""
134
135         def __init__(self, session, file, askOpen = False, downloadTo = None, callback = None):
136                 Screen.__init__(self, session)
137
138                 # Save arguments local
139                 self.file = file
140                 self.askOpen = askOpen
141                 self.filename = downloadTo
142                 self.callback = callback
143
144                 # Init what we need for progress callback
145                 self.lastLength = 0
146                 self.lastTime = 0
147                 self.lastApprox = 0
148
149                 # Inform user about whats currently done
150                 self["wait"] = StaticText(_("Downloading..."))
151                 self["progress"] = VariableProgressSource()
152                 self["eta"] = StaticText(_("ETA ??:?? h")) # XXX: we could just leave eta and speed empty
153                 self["speed"] = StaticText(_("?? kb/s"))
154
155                 # Set Limit if we know it already (Server might not tell it)
156                 if self.file.size:
157                         self["progress"].writeValues(0, self.file.size*1048576)
158
159                 # Call getFilename as soon as we are able to open a new screen
160                 self.onExecBegin.append(self.getFilename)
161
162         def getFilename(self):
163                 self.onExecBegin.remove(self.getFilename)
164
165                 # If we have a filename (downloadTo provided) start fetching
166                 if self.filename is not None:
167                         self.fetchFile()
168                 # Else open LocationBox to determine where to save
169                 else:
170                         # TODO: determine basename without os.path?
171                         from os import path
172                         from Screens.LocationBox import LocationBox
173
174                         self.session.openWithCallback(
175                                 self.gotFilename,
176                                 LocationBox,
177                                 _("Where to save?"),
178                                 path.basename(self.file.path),
179                                 minFree = self.file.size,
180                                 bookmarks = config.plugins.mediadownloader.bookmarks
181                         )
182
183         def gotFilename(self, res):
184                 # If we got a filename try to fetch file
185                 if res is not None:
186                         self.filename = res
187                         self.fetchFile()
188                 # Else close
189                 else:
190                         self.close()
191
192         def fetchFile(self):
193                 # Fetch file
194                 d = download(
195                         self.file.path,
196                         self.filename,
197                         [
198                                 self["progress"].writeValues,
199                                 self.gotProgress
200                         ]
201                 )
202
203                 d.addCallback(self.gotFile).addErrback(self.error)
204
205         def gotProgress(self, pos, max):
206                 newTime = time.time()
207                 # Check if we're called the first time (got total)
208                 lastTime = self.lastTime
209                 if lastTime == 0:
210                         self.lastTime = newTime
211
212                 # 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)
213                 elif int(newTime - lastTime) >= 2:
214                         newLength = pos
215
216                         lastApprox = round(((newLength - self.lastLength) / (newTime - lastTime) / 1024), 2)
217
218                         secLen = int(round(((max-pos) / 1024) / lastApprox))
219                         self["eta"].text = _("ETA %d:%02d min") % (secLen / 60, secLen % 60)
220                         self["speed"].text = _("%d kb/s") % (lastApprox)
221
222                         self.lastApprox = lastApprox
223                         self.lastLength = newLength
224                         self.lastTime = newTime
225
226         def openCallback(self, res):
227                 from Components.Scanner import openFile
228
229                 # Try to open file if res was True
230                 if res and not openFile(self.session, None, self.filename):
231                         self.session.open(
232                                 MessageBox,
233                                 _("No suitable Viewer found!"),
234                                 type = MessageBox.TYPE_ERROR,
235                                 timeout = 5
236                         )
237
238                 # Calback with Filename on success
239                 if self.callback is not None:
240                         self.callback(self.filename)
241
242                 self.close()
243
244         def gotFile(self, data = ""):
245                 # Ask if file should be opened unless told not to
246                 if self.askOpen:
247                         self.session.openWithCallback(
248                                 self.openCallback,
249                                 MessageBox,
250                                 _("Do you want to try to open the downloaded file?"),
251                                 type = MessageBox.TYPE_YESNO
252                         )
253                 # Otherwise callback and close
254                 else:
255                         # Calback with Filename on success
256                         if self.callback is not None:
257                                 self.callback(self.filename)
258
259                         self.close()
260
261         def error(self, msg = ""):
262                 if msg != "":
263                         print("[MediaDownloader] Error downloading:", msg)
264
265                 self.session.open(
266                         MessageBox,
267                         _("Error while downloading file %s") % (self.file.path),
268                         type = MessageBox.TYPE_ERROR,
269                         timeout = 3
270                 )
271
272                 # Calback with None on failure
273                 if self.callback is not None:
274                         self.callback(None)
275
276                 self.close()