new configuration: mark files under 1MB as failed (yes/no)
[enigma2-plugins.git] / rsdownloader / src / plugin.py
1 ##\r
2 ## RS Downloader\r
3 ## by AliAbdul\r
4 ##\r
5 ##\r
6 from base64 import encodestring\r
7 from Components.ActionMap import ActionMap\r
8 from Components.config import config, ConfigInteger, ConfigText, ConfigYesNo, ConfigClock, ConfigSubsection, getConfigListEntry\r
9 from Components.ConfigList import ConfigListScreen\r
10 from Components.Label import Label\r
11 from Components.Language import language\r
12 from Components.MenuList import MenuList\r
13 from Components.MultiContent import MultiContentEntryText, MultiContentEntryPixmapAlphaTest\r
14 from Components.ScrollLabel import ScrollLabel\r
15 from container.decrypt import decrypt\r
16 from enigma import eListboxPythonMultiContent, eTimer, gFont, RT_HALIGN_CENTER, RT_HALIGN_RIGHT\r
17 from os import environ, listdir, remove\r
18 from Plugins.Plugin import PluginDescriptor\r
19 from Screens.ChoiceBox import ChoiceBox\r
20 from Screens.MessageBox import MessageBox\r
21 from Screens.Screen import Screen\r
22 from Screens.VirtualKeyBoard import VirtualKeyBoard\r
23 from time import localtime, sleep, strftime, time\r
24 from Tools.Directories import resolveFilename, SCOPE_SKIN_IMAGE, SCOPE_LANGUAGE, SCOPE_PLUGINS\r
25 from Tools.Downloader import HTTPProgressDownloader\r
26 from Tools.LoadPixmap import LoadPixmap\r
27 from twisted.internet import reactor\r
28 from twisted.python import failure\r
29 from twisted.web.client import getPage\r
30 from urlparse import urlparse, urlunparse\r
31 import gettext, re, socket, urllib2\r
32 \r
33 ##############################################################################\r
34 \r
35 config.plugins.RSDownloader = ConfigSubsection()\r
36 config.plugins.RSDownloader.onoff = ConfigYesNo(default=True)\r
37 config.plugins.RSDownloader.username = ConfigText(default="", fixed_size=False)\r
38 config.plugins.RSDownloader.password = ConfigText(default="", fixed_size=False)\r
39 config.plugins.RSDownloader.lists_directory = ConfigText(default="/media/hdd/rs/lists/", fixed_size=False)\r
40 config.plugins.RSDownloader.downloads_directory = ConfigText(default="/media/hdd/rs/downloads", fixed_size=False)\r
41 config.plugins.RSDownloader.ignore_time = ConfigYesNo(default=False)\r
42 config.plugins.RSDownloader.start_time = ConfigClock(default=time())\r
43 config.plugins.RSDownloader.end_time = ConfigClock(default=time())\r
44 config.plugins.RSDownloader.download_monday = ConfigYesNo(default=True)\r
45 config.plugins.RSDownloader.download_tuesday = ConfigYesNo(default=True)\r
46 config.plugins.RSDownloader.download_wednesday = ConfigYesNo(default=True)\r
47 config.plugins.RSDownloader.download_thursday = ConfigYesNo(default=True)\r
48 config.plugins.RSDownloader.download_friday = ConfigYesNo(default=True)\r
49 config.plugins.RSDownloader.download_saturday = ConfigYesNo(default=True)\r
50 config.plugins.RSDownloader.download_sunday = ConfigYesNo(default=True)\r
51 config.plugins.RSDownloader.count_downloads = ConfigInteger(default=3, limits=(1, 6))\r
52 config.plugins.RSDownloader.count_maximal_downloads = ConfigInteger(default=40, limits=(1, 400))\r
53 config.plugins.RSDownloader.write_log = ConfigYesNo(default=True)\r
54 config.plugins.RSDownloader.reconnect_fritz = ConfigYesNo(default=False)\r
55 config.plugins.RSDownloader.autorestart_failed = ConfigYesNo(default=False)\r
56 config.plugins.RSDownloader.mark_small_as_failed = ConfigYesNo(default=True)\r
57 \r
58 ##############################################################################\r
59 \r
60 def localeInit():\r
61         lang = language.getLanguage()\r
62         environ["LANGUAGE"] = lang[:2]\r
63         gettext.bindtextdomain("enigma2", resolveFilename(SCOPE_LANGUAGE))\r
64         gettext.textdomain("enigma2")\r
65         gettext.bindtextdomain("RSDownloader", "%s%s"%(resolveFilename(SCOPE_PLUGINS), "Extensions/RSDownloader/locale/"))\r
66 \r
67 def _(txt):\r
68         t = gettext.dgettext("RSDownloader", txt)\r
69         if t == txt:\r
70                 t = gettext.gettext(txt)\r
71         return t\r
72 \r
73 localeInit()\r
74 language.addCallback(localeInit)\r
75 \r
76 ##############################################################################\r
77 \r
78 def writeLog(message):\r
79         if config.plugins.RSDownloader.write_log.value:\r
80                 try:\r
81                         f = open("/tmp/rapidshare.log", "a")\r
82                         f.write(strftime("%c", localtime(time())) + " - " + message + "\n")\r
83                         f.close()\r
84                 except:\r
85                         pass\r
86 \r
87 ##############################################################################\r
88 \r
89 def _parse(url):\r
90         url = url.strip()\r
91         parsed = urlparse(url)\r
92         scheme = parsed[0]\r
93         path = urlunparse(('','') + parsed[2:])\r
94         host, port = parsed[1], 80\r
95         if '@' in host:\r
96                 username, host = host.split('@')\r
97                 if ':' in username:\r
98                         username, password = username.split(':')\r
99                 else:\r
100                         password = ""\r
101         else:\r
102                 username = ""\r
103                 password = ""\r
104         if ':' in host:\r
105                 host, port = host.split(':')\r
106                 port = int(port)\r
107         if path == "":\r
108                 path = "/"\r
109         return scheme, host, port, path, username, password\r
110 \r
111 class ProgressDownload:\r
112         def __init__(self, url, outputfile, contextFactory=None, *args, **kwargs):\r
113                 scheme, host, port, path, username, password = _parse(url)\r
114                 if username and password:\r
115                         url = scheme + '://' + host + ':' + str(port) + path\r
116                         basicAuth = encodestring("%s:%s"%(username, password))\r
117                         authHeader = "Basic " + basicAuth.strip()\r
118                         AuthHeaders = {"Authorization": authHeader}\r
119                         if kwargs.has_key("headers"):\r
120                                 kwargs["headers"].update(AuthHeaders)\r
121                         else:\r
122                                 kwargs["headers"] = AuthHeaders\r
123                 self.factory = HTTPProgressDownloader(url, outputfile, *args, **kwargs)\r
124                 self.connection = reactor.connectTCP(host, port, self.factory)\r
125 \r
126         def start(self):\r
127                 return self.factory.deferred\r
128 \r
129         def stop(self):\r
130                 self.connection.disconnect()\r
131 \r
132         def addProgress(self, progress_callback):\r
133                 self.factory.progress_callback = progress_callback\r
134 \r
135 ##############################################################################\r
136 \r
137 def get(url):\r
138         try:\r
139                 data = urllib2.urlopen(url)\r
140                 return data.read()\r
141         except:\r
142                 return ""\r
143    \r
144 def post(url, data):\r
145         try:\r
146                 return urllib2.urlopen(url, data).read()\r
147         except:\r
148                 return ""\r
149 \r
150 def matchGet(rex, string):\r
151         match = re.search(rex, string)\r
152         if match:\r
153                 if len(match.groups()) == 0:\r
154                         return string[match.span()[0]:match.span()[1]]\r
155                 if len(match.groups()) == 1:\r
156                         return match.groups()[0]\r
157         else:\r
158                 return False\r
159 \r
160 ##############################################################################\r
161 \r
162 def reconnect(host='fritz.box', port=49000):\r
163         http_body = '\r\n'.join((\r
164                 '<?xml version="1.0" encoding="utf-8"?>',\r
165                 '<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">',\r
166                 '  <s:Body>',\r
167                 '    <u:ForceTermination xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"/>',\r
168                 '  </s:Body>',\r
169                 '</s:Envelope>'))\r
170         http_data = '\r\n'.join((\r
171                 'POST /upnp/control/WANIPConn1 HTTP/1.1',\r
172                 'Host: %s:%d'%(host, port),\r
173                 'SoapAction: urn:schemas-upnp-org:service:WANIPConnection:1#ForceTermination',\r
174                 'Content-Type: text/xml; charset="utf-8"',\r
175                 'Content-Length: %d'%len(http_body),\r
176                 '',\r
177                 http_body))\r
178         try:\r
179                 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\r
180                 s.connect((host, port))\r
181                 s.send(http_data)\r
182                 s.close()\r
183         except:\r
184                 pass\r
185 \r
186 ##############################################################################\r
187 \r
188 class RSDownload:\r
189         def __init__(self, url):\r
190                 writeLog("Adding: %s"%url)\r
191                 self.url = url\r
192                 self.download = None\r
193                 self.downloading = False\r
194                 self.progress = 0\r
195                 self.size = 0\r
196                 self.status = _("Waiting")\r
197                 self.name = self.url.split("/")[-1]\r
198                 self.finishCallbacks = []\r
199 \r
200         def start(self):\r
201                 writeLog("Downloading: %s"%self.url)\r
202                 self.downloading = True\r
203                 self.progress = 0\r
204                 self.size = 0\r
205                 username = config.plugins.RSDownloader.username.value\r
206                 password = config.plugins.RSDownloader.password.value\r
207                 if self.url.__contains__("rapidshare.com") and username == "" and password == "":\r
208                         writeLog("Free RS-Download: %s"%self.url)\r
209                         self.status = _("Checking")\r
210                         if config.plugins.RSDownloader.reconnect_fritz.value:\r
211                                 reconnect()\r
212                                 sleep(3)\r
213                         data = get(self.url)\r
214                         url = matchGet('<form[^>]+action="([^"]+)', data)\r
215                         if not url:\r
216                                 writeLog("Failed: %s"%self.url)\r
217                                 self.httpFailed(True, "Failed to get download page url: %s"%self.url)\r
218                         else:\r
219                                 data = post(url, "dl.start=Free")\r
220                                 seconds = matchGet('var c=([0-9]+)', data)\r
221                                 if not seconds:\r
222                                         self.httpFailed(True, "Failed to get download page url: %s"%self.url)\r
223                                 else:\r
224                                         writeLog("Free RS-Download... must wait %s seconds: %s"%(seconds, self.url))\r
225                                         self.status = "%s %s"%(_("Waiting"), seconds)\r
226                                         url = matchGet('"dlf" action="([^"]+)', data)\r
227                                         if not url:\r
228                                                 self.httpFailed(True, "Failed to get download page url: %s"%self.url)\r
229                                         else:\r
230                                                 self.freeDownloadUrl = url\r
231                                                 self.freeDownloadTimer = eTimer()\r
232                                                 self.freeDownloadTimer.callback.append(self.freeDownloadStart)\r
233                                                 self.freeDownloadTimer.start((int(seconds) + 2) * 1000, 1)
234                 elif self.url.__contains__("uploaded.to") or self.url.__contains__("ul.to"):
235                         writeLog("Free Uploaded.to-Download: %s"%self.url)\r
236                         self.status = _("Checking")\r
237                         if config.plugins.RSDownloader.reconnect_fritz.value:\r
238                                 reconnect()\r
239                                 sleep(3)\r
240                         data = get(self.url)
241                         tmp = re.search(r"Or wait (\d+) minutes", data)
242                         if tmp:
243                                 minutes = tmp.group(1)
244                                 writeLog("Free Uploaded.to-Download... must wait %s minutes: %s"%(minutes, self.url))
245                                 self.status = "%s %s"%(_("Waiting"), minutes)\r
246                                 self.freeDownloadTimer = eTimer()\r
247                                 self.freeDownloadTimer.callback.append(self.start)\r
248                                 self.freeDownloadTimer.start((int(minutes) + 1) * 60000, 1)
249                         else:
250                                 url = re.search(r".*<form name=\"download_form\" method=\"post\" action=\"(.*)\">", data).group(1)
251                                 self.name = re.search(r"<td><b>\s+(.+)\s", data).group(1) + re.search(r"</td><td>(\..+)</td></tr>", data).group(1)\r
252                                 self.status = _("Downloading")\r
253                                 self.download = ProgressDownload(url, ("%s/%s"%(config.plugins.RSDownloader.downloads_directory.value, self.name)).replace("//", "/"))\r
254                                 self.download.addProgress(self.httpProgress)\r
255                                 self.download.start().addCallback(self.httpFinished).addErrback(self.httpFailed)\r
256                 elif self.url.__contains__("youtube.com"):\r
257                         writeLog("Getting youtube video link: %s"%self.url)\r
258                         self.status = _("Checking")\r
259                         downloadLink = self.getYoutubeDownloadLink()\r
260                         if downloadLink:\r
261                                 self.status = _("Downloading")\r
262                                 writeLog("Downloading video: %s"%downloadLink)\r
263                                 req = urllib2.Request(downloadLink)\r
264                                 url_handle = urllib2.urlopen(req)\r
265                                 headers = url_handle.info()\r
266                                 if headers.getheader("content-type") == "video/mp4":\r
267                                         ext = "mp4"\r
268                                 else:\r
269                                         ext = "flv"\r
270                                 self.download = ProgressDownload(downloadLink, ("%s/%s.%s"%(config.plugins.RSDownloader.downloads_directory.value, self.name, ext)).replace("//", "/"))\r
271                                 self.download.addProgress(self.httpProgress)\r
272                                 self.download.start().addCallback(self.httpFinished).addErrback(self.httpFailed)\r
273                         else:\r
274                                 self.httpFailed(True, "Failed to get video url: %s"%self.url)\r
275                 else:\r
276                         if self.url.__contains__("rapidshare.com"):\r
277                                 url = self.url.replace("http://", "http://" + username + ":" + password + "@")\r
278                         else:\r
279                                 url = self.url\r
280                         self.status = _("Downloading")\r
281                         self.download = ProgressDownload(url, ("%s/%s"%(config.plugins.RSDownloader.downloads_directory.value, self.name)).replace("//", "/").replace(".html", ""))\r
282                         self.download.addProgress(self.httpProgress)\r
283                         self.download.start().addCallback(self.httpFinished).addErrback(self.httpFailed)\r
284 \r
285         def freeDownloadStart(self):\r
286                 self.status = _("Downloading")\r
287                 self.download = ProgressDownload(self.freeDownloadUrl, ("%s/%s"%(config.plugins.RSDownloader.downloads_directory.value, self.name)).replace("//", "/").replace(".html", ""))\r
288                 self.download.addProgress(self.httpProgress)\r
289                 self.download.start().addCallback(self.httpFinished).addErrback(self.httpFailed)\r
290 \r
291         def stop(self):\r
292                 self.progress = 0\r
293                 self.downloading = False\r
294                 self.status = _("Waiting")\r
295                 if self.download:\r
296                         writeLog("Stopping download: %s"%self.url)\r
297                         self.download.stop()\r
298 \r
299         def httpProgress(self, recvbytes, totalbytes):\r
300                 if self.size == 0:\r
301                         self.size = int((totalbytes / 1024) / 1024)\r
302                 self.progress = int(100.0 * float(recvbytes) / float(totalbytes))\r
303 \r
304         def httpFinished(self, string=None):\r
305                 if string is not None:\r
306                         writeLog("Failed: %s"%self.url)\r
307                         writeLog("Error: %s"%string)\r
308                 self.status = _("Checking")\r
309                 self.checkTimer = eTimer()\r
310                 self.checkTimer.callback.append(self.doCheckTimer)\r
311                 self.checkTimer.start(10000, 1)\r
312 \r
313         def doCheckTimer(self):\r
314                 if (self.size == 0) or (self.progress < 100) or ((config.plugins.RSDownloader.mark_small_as_failed.value == True) and (self.size < 1)):\r
315                         self.status = _("Failed")\r
316                         if config.plugins.RSDownloader.autorestart_failed.value:\r
317                                 self.restartFailedTimer = eTimer()\r
318                                 self.restartFailedTimer.callback.append(self.restartFailedCheck)\r
319                                 self.restartFailedTimer.start(10000*60, 1)\r
320                 elif self.progress == 100:\r
321                         self.status = _("Finished")
322                         writeLog("Finished: %s"%self.url)\r
323                 self.downloading = False\r
324                 self.execFinishCallbacks()\r
325 \r
326         def restartFailedCheck(self):\r
327                 if self.status == _("Failed"): # check if user didn't restart already\r
328                         self.download = None\r
329                         self.status = _("Waiting")\r
330 \r
331         def execFinishCallbacks(self):\r
332                 for x in self.finishCallbacks:\r
333                         x()\r
334 \r
335         def httpFailed(self, failure=None, error=""):\r
336                 if failure:\r
337                         if error == "":\r
338                                 error = failure.getErrorMessage()\r
339                         if error != "" and not error.startswith("[Errno 2]"):\r
340                                 writeLog("Failed: %s"%self.url)\r
341                                 writeLog("Error: %s"%error)\r
342                                 self.status = _("Checking")\r
343                 self.checkTimer = eTimer()\r
344                 self.checkTimer.callback.append(self.doCheckTimer)\r
345                 self.checkTimer.start(10000, 1)\r
346 \r
347         def getYoutubeDownloadLink(self):\r
348                 mrl = None\r
349                 html = get(self.url)\r
350                 if html != "":\r
351                         isHDAvailable = False\r
352                         video_id = None\r
353                         t = None\r
354                         reonecat = re.compile(r'<title>(.+?)</title>', re.DOTALL)\r
355                         titles = reonecat.findall(html)\r
356                         if titles:\r
357                                 self.name = titles[0]\r
358                                 if self.name.startswith("YouTube - "):\r
359                                         self.name = (self.name[10:]).replace("&amp;", "&")\r
360                         if html.__contains__("isHDAvailable = true"):\r
361                                 isHDAvailable = True\r
362                         for line in html.split('\n'):\r
363                                 if 'swfArgs' in line:\r
364                                         line = line.strip().split()\r
365                                         x = 0\r
366                                         for thing in line:\r
367                                                 if 'video_id' in thing:\r
368                                                         video_id = line[x+1][1:-2]\r
369                                                 elif '"t":' == thing:\r
370                                                         t = line[x+1][1:-2]\r
371                                                 x += 1\r
372                         if video_id and t:\r
373                                 if isHDAvailable == True:\r
374                                         mrl = "http://www.youtube.com/get_video?video_id=%s&t=%s&fmt=22" % (video_id, t)\r
375                                 else:\r
376                                         mrl = "http://www.youtube.com/get_video?video_id=%s&t=%s&fmt=18" % (video_id, t)\r
377                 return mrl\r
378 \r
379 ##############################################################################\r
380 \r
381 class RS:\r
382         def __init__(self):\r
383                 self.downloads = []\r
384                 self.checkTimer = eTimer()\r
385                 self.checkTimer.callback.append(self.startDownloading)\r
386                 self.checkTimer.start(5000*60, False)\r
387 \r
388         def mayDownload(self):\r
389                 if config.plugins.RSDownloader.onoff.value == False:\r
390                         writeLog("RS Downloader is turned off...")\r
391                         return False\r
392                 elif config.plugins.RSDownloader.ignore_time.value:\r
393                         return True\r
394                 else:\r
395                         start = config.plugins.RSDownloader.start_time.value\r
396                         end = config.plugins.RSDownloader.end_time.value\r
397                         t = localtime()\r
398                         weekday = t[6]\r
399                         if weekday == 0 and config.plugins.RSDownloader.download_monday.value == False:\r
400                                 return False\r
401                         elif weekday == 1 and config.plugins.RSDownloader.download_tuesday.value == False:\r
402                                 return False\r
403                         elif weekday == 2 and config.plugins.RSDownloader.download_wednesday.value == False:\r
404                                 return False\r
405                         elif weekday == 3 and config.plugins.RSDownloader.download_thursday.value == False:\r
406                                 return False\r
407                         elif weekday == 4 and config.plugins.RSDownloader.download_friday.value == False:\r
408                                 return False\r
409                         elif weekday == 5 and config.plugins.RSDownloader.download_saturday.value == False:\r
410                                 return False\r
411                         elif weekday == 6 and config.plugins.RSDownloader.download_sunday.value == False:\r
412                                 return False\r
413                         else:\r
414                                 hour_now = t[3]\r
415                                 minute_now = t[4]\r
416                                 hour_start = start[0]\r
417                                 minute_start = start[1]\r
418                                 hour_end = end[0]\r
419                                 minute_end = end[1]\r
420                                 if start == end: # Same start and end-time\r
421                                         return True\r
422                                 elif hour_end < hour_start: # Different days!!!\r
423                                         if hour_now > hour_start or hour_now < hour_end:\r
424                                                 return True\r
425                                         elif hour_now == hour_start and minute_now > minute_start:\r
426                                                 return True\r
427                                         elif hour_now == hour_end and minute_now < minute_end:\r
428                                                 return True\r
429                                         else:\r
430                                                 return False\r
431                                 elif hour_now > hour_start and hour_now < hour_end: # Same day...\r
432                                         return True\r
433                                 elif hour_now == hour_start and minute_now > minute_start: # Same day, same start-hour...\r
434                                         return True\r
435                                 elif hour_now == hour_end and minute_now < minute_end: # Same day, same end-hour...\r
436                                         return True\r
437                                 else:\r
438                                         return False\r
439 \r
440         def allDownloadsFinished(self):\r
441                 allDone = True\r
442                 for download in self.downloads:\r
443                         if (download.status != _("Failed")) and (download.status != _("Finished")):\r
444                                 allDone = False\r
445                 return allDone\r
446 \r
447         def startDownloading(self):\r
448                 if self.mayDownload() == True:\r
449                         if self.allDownloadsFinished() == True:\r
450                                 self.readLists()\r
451                         downloadCount = 0\r
452                         for download in self.downloads:\r
453                                 if download.downloading == True:\r
454                                         downloadCount += 1 # Count the downloaded files\r
455                         if config.plugins.RSDownloader.username.value == "" and config.plugins.RSDownloader.password.value == "":\r
456                                 if downloadCount < 1: # Allow one download if without account\r
457                                         for download in self.downloads:\r
458                                                 if download.downloading == False and download.status.startswith(_("Waiting")):\r
459                                                         download.start() # Start first download in the list\r
460                                                         break\r
461                         else:\r
462                                 mayDownloadCount = config.plugins.RSDownloader.count_downloads.value - downloadCount\r
463                                 for download in self.downloads:\r
464                                         if download.downloading == False:\r
465                                                 if mayDownloadCount > 0 and download.status == _("Waiting"):\r
466                                                         download.start()\r
467                                                         mayDownloadCount -= 1\r
468 \r
469         def addDownload(self, url):\r
470                 error = False\r
471                 for download in self.downloads:\r
472                         if download.url == url:\r
473                                 error = True\r
474                 if error:\r
475                         return False\r
476                 else:\r
477                         download = RSDownload(url)\r
478                         download.finishCallbacks.append(self.cleanLists)\r
479                         self.downloads.append(download)\r
480                         return True\r
481 \r
482         def readLists(self):\r
483                 writeLog("Reading all lists...")\r
484                 path = config.plugins.RSDownloader.lists_directory.value\r
485                 if not path.endswith("/"):\r
486                         path = path + "/"\r
487                 writeLog("Directory: " + path)\r
488                 try:\r
489                         file_list = listdir(path)
490                         file_list.sort()\r
491                         writeLog("Count of lists: " + str(len(file_list)))\r
492                 except:\r
493                         file_list = []\r
494                         writeLog("Could not find any list!")
495                 added_downloads = 0\r
496                 for x in file_list:\r
497                         list = path + x\r
498                         if list.endswith(".txt"):\r
499                                 try:\r
500                                         writeLog("Reading list %s..."%list)\r
501                                         f = open(list, "r")\r
502                                         count = 0\r
503                                         for l in f:\r
504                                                 if l.startswith("http://"):
505                                                         if added_downloads < config.plugins.RSDownloader.count_maximal_downloads.value:\r
506                                                                 if (self.addDownload(l.replace("\n", "").replace("\r", ""))) == True:\r
507                                                                         count += 1
508                                                                         added_downloads += 1
509                                                         else:
510                                                                 break\r
511                                         f.close()\r
512                                         if count == 0:\r
513                                                 writeLog("Empty list or downloads already in download list: %s"%list)\r
514                                         else:\r
515                                                 writeLog("Added %d files from list %s..."%(count, list))\r
516                                 except:\r
517                                         writeLog("Error while reading list %s!"%list)\r
518                         else:\r
519                                 writeLog("No *.txt file: %s!"%list)\r
520 \r
521         def cleanLists(self):\r
522                 writeLog("Cleaning lists...")\r
523                 path = config.plugins.RSDownloader.lists_directory.value\r
524                 if not path.endswith("/"):\r
525                         path = path + "/"\r
526                 try:\r
527                         file_list = listdir(path)\r
528                 except:\r
529                         file_list = []
530                 
531                 finished_downloads = []
532                 for download in self.downloads:\r
533                         if download.status == _("Finished"):
534                                 finished_downloads.append(download)\r
535                 for x in file_list:\r
536                         list = path + x
537                         if list.endswith(".txt"):\r
538                                 try:\r
539                                         f = open(list, "r")\r
540                                         content = f.read()\r
541                                         f.close()
542                                         for finished in finished_downloads:\r
543                                                 if content.__contains__(finished.url):\r
544                                                         content = content.replace(finished.url, "")\r
545                                                         content = content.replace("\n\n", "\n").replace("\r\r", "\r")\r
546                                         f = open(list, "w")\r
547                                         f.write(content)\r
548                                         f.close()\r
549                                 except:\r
550                                         writeLog("Error while cleaning list %s!"%list)\r
551                 self.startDownloading()\r
552 \r
553         def removeDownload(self, url):\r
554                 tmp = []\r
555                 for download in self.downloads:\r
556                         if download.url == url:\r
557                                 download.stop()\r
558                         else:\r
559                                 tmp.append(download)\r
560                 del self.downloads\r
561                 self.downloads = tmp\r
562                 self.removeFromLists(url)\r
563 \r
564         def removeFromLists(self, url):\r
565                 path = config.plugins.RSDownloader.lists_directory.value\r
566                 if not path.endswith("/"):\r
567                         path = path + "/"\r
568                 try:\r
569                         file_list = listdir(path)\r
570                 except:\r
571                         file_list = []\r
572                 for x in file_list:\r
573                         list = path + x\r
574                         try:\r
575                                 f = open(list, "r")\r
576                                 content = f.read()\r
577                                 f.close()\r
578                                 if content.__contains__(url):\r
579                                         content = content.replace(url, "")\r
580                                         content = content.replace("\n\n", "\n").replace("\r\r", "\r")\r
581                                 f = open(list, "w")\r
582                                 f.write(content)\r
583                                 f.close()\r
584                         except:\r
585                                 pass\r
586 \r
587         def clearFinishedDownload(self, url):\r
588                 idx = 0\r
589                 for x in self.downloads:\r
590                         if x.url == url:\r
591                                 del self.downloads[idx]\r
592                                 break\r
593                         else:\r
594                                 idx += 1\r
595 \r
596         def clearFinishedDownloads(self):\r
597                 tmp = []\r
598                 for download in self.downloads:\r
599                         if download.status != _("Finished"):\r
600                                 tmp.append(download)\r
601                 del self.downloads\r
602                 self.downloads = tmp\r
603 \r
604         def deleteFailedDownloads(self):\r
605                 tmp = []\r
606                 for download in self.downloads:\r
607                         if download.status == _("Failed"):\r
608                                 self.removeFromLists(download.url)\r
609                         else:\r
610                                 tmp.append(download)\r
611                 del self.downloads\r
612                 self.downloads = tmp\r
613 \r
614         def restartFailedDownloads(self):\r
615                 tmp = []\r
616                 for download in self.downloads:\r
617                         if download.status == _("Failed"):\r
618                                 download.download = None\r
619                                 download.downloading = False\r
620                                 download.progress = 0\r
621                                 download.size = 0\r
622                                 download.status = _("Waiting")\r
623                         tmp.append(download)\r
624                 del self.downloads\r
625                 self.downloads = tmp\r
626                 self.startDownloading()\r
627 \r
628 rapidshare = RS()\r
629 \r
630 ##############################################################################\r
631 \r
632 class ChangedScreen(Screen):\r
633         def __init__(self, session, parent=None):\r
634                 Screen.__init__(self, session, parent)\r
635                 self.onLayoutFinish.append(self.setScreenTitle)\r
636 \r
637         def setScreenTitle(self):\r
638                 self.setTitle(_("RS Downloader"))\r
639 \r
640 ##############################################################################\r
641 \r
642 class RSConfig(ConfigListScreen, ChangedScreen):\r
643         skin = """\r
644                 <screen position="center,center" size="560,450" title="RS Downloader">\r
645                         <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" size="140,40" transparent="1" alphatest="on" />\r
646                         <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" size="140,40" transparent="1" alphatest="on" />\r
647                         <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" size="140,40" transparent="1" alphatest="on" />\r
648                         <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" size="140,40" transparent="1" alphatest="on" />\r
649                         <widget name="key_green" position="140,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />\r
650                         <widget name="config" position="0,45" size="560,400" scrollbarMode="showOnDemand" />\r
651                 </screen>"""\r
652 \r
653         def __init__(self, session):\r
654                 ChangedScreen.__init__(self, session)\r
655                 \r
656                 self["key_green"] = Label(_("Save"))\r
657                 \r
658                 ConfigListScreen.__init__(self, [\r
659                         getConfigListEntry(_("Download in the background:"), config.plugins.RSDownloader.onoff),\r
660                         getConfigListEntry(_("Username:"), config.plugins.RSDownloader.username),\r
661                         getConfigListEntry(_("Password:"), config.plugins.RSDownloader.password),\r
662                         getConfigListEntry(_("Lists directory:"), config.plugins.RSDownloader.lists_directory),\r
663                         getConfigListEntry(_("Downloads directory:"), config.plugins.RSDownloader.downloads_directory),\r
664                         getConfigListEntry(_("Ignore download times:"), config.plugins.RSDownloader.ignore_time),\r
665                         getConfigListEntry(_("Allow downloading on monday:"), config.plugins.RSDownloader.download_monday),\r
666                         getConfigListEntry(_("Allow downloading on tuesday:"), config.plugins.RSDownloader.download_tuesday),\r
667                         getConfigListEntry(_("Allow downloading on wednesday:"), config.plugins.RSDownloader.download_wednesday),\r
668                         getConfigListEntry(_("Allow downloading on thursday:"), config.plugins.RSDownloader.download_thursday),\r
669                         getConfigListEntry(_("Allow downloading on friday:"), config.plugins.RSDownloader.download_friday),\r
670                         getConfigListEntry(_("Allow downloading on saturday:"), config.plugins.RSDownloader.download_saturday),\r
671                         getConfigListEntry(_("Allow downloading on sunday:"), config.plugins.RSDownloader.download_sunday),\r
672                         getConfigListEntry(_("Don't download before:"), config.plugins.RSDownloader.start_time),\r
673                         getConfigListEntry(_("Don't download after:"), config.plugins.RSDownloader.end_time),\r
674                         getConfigListEntry(_("Maximal downloads:"), config.plugins.RSDownloader.count_downloads),\r
675                         getConfigListEntry(_("Take x downloads to list:"), config.plugins.RSDownloader.count_maximal_downloads),\r
676                         getConfigListEntry(_("Write log:"), config.plugins.RSDownloader.write_log),\r
677                         getConfigListEntry(_("Reconnect fritz.Box before downloading:"), config.plugins.RSDownloader.reconnect_fritz),\r
678                         getConfigListEntry(_("Restart failed after 10 minutes:"), config.plugins.RSDownloader.autorestart_failed),
679                         getConfigListEntry(_("Mark files < 1 MB as failed:"), config.plugins.RSDownloader.mark_small_as_failed)])\r
680                 \r
681                 self["actions"] = ActionMap(["OkCancelActions", "ColorActions"], {"green": self.save, "cancel": self.exit}, -1)\r
682 \r
683         def save(self):\r
684                 for x in self["config"].list:\r
685                         x[1].save()\r
686                 self.close()\r
687 \r
688         def exit(self):\r
689                 for x in self["config"].list:\r
690                         x[1].cancel()\r
691                 self.close()\r
692 \r
693 ##############################################################################\r
694 \r
695 class RSSearch(Screen):\r
696         skin = """\r
697                 <screen position="center,center" size="560,450" title="Searching... please wait!">\r
698                         <widget name="list" position="0,0" size="570,450" scrollbarMode="showOnDemand" />\r
699                 </screen>"""\r
700 \r
701         def __init__(self, session, searchFor):\r
702                 Screen.__init__(self, session)\r
703                 self.session = session\r
704                 \r
705                 self.searchFor = searchFor.replace(" ", "%2B")\r
706                 self.maxPage = 1\r
707                 self.curPage = 1\r
708                 self.files = []\r
709                 \r
710                 self["list"] = MenuList([])\r
711                 \r
712                 self["actions"] = ActionMap(["OkCancelActions", "InfobarChannelSelection"],\r
713                         {\r
714                                 "historyBack": self.previousPage,\r
715                                 "historyNext": self.nextPage,\r
716                                 "ok": self.okClicked,\r
717                                 "cancel": self.close\r
718                         }, -1)\r
719                 \r
720                 self.onLayoutFinish.append(self.search)\r
721 \r
722         def okClicked(self):\r
723                 if len(self.files) > 0:\r
724                         idx = self["list"].getSelectedIndex()\r
725                         url = self.files[idx]\r
726                         try:\r
727                                 f = open(("%s/search.txt" % config.plugins.RSDownloader.lists_directory.value).replace("//", "/"), "a")\r
728                                 f.write("%s\n"%url)\r
729                                 f.close()\r
730                                 self.session.open(MessageBox, (_("Added %s to the download-list.") % url), MessageBox.TYPE_INFO)\r
731                         except:\r
732                                 self.session.open(MessageBox, (_("Error while adding %s to the download-list!") % url), MessageBox.TYPE_ERROR)\r
733 \r
734         def search(self):\r
735                 getPage("http://rapidshare-search-engine.com/index-s_submit=Search&sformval=1&s_type=0&what=1&s=%s&start=%d.html"%(self.searchFor, self.curPage)).addCallback(self.searchCallback).addErrback(self.searchError)\r
736 \r
737         def searchCallback(self, html=""):\r
738                 list = []\r
739                 files = []\r
740                 \r
741                 if html.__contains__("Nothing found, sorry."):\r
742                         self.session.open(MessageBox, (_("Error while searching http://rapidshare-search-engine.com!\n\nError: Nothing found, sorry.")), MessageBox.TYPE_ERROR)\r
743                         self.instance.setTitle(_("Nothing found, sorry."))\r
744                 else:\r
745                         tmp = html\r
746                         while tmp.__contains__("goPg('"):\r
747                                 idx = tmp.index("goPg('")\r
748                                 tmp = tmp[idx+6:]\r
749                                 idx = tmp.index("'")\r
750                                 pageNumber = tmp[:idx]\r
751                                 \r
752                                 try:\r
753                                         pageNumber = int(pageNumber)\r
754                                         if pageNumber > self.maxPage:\r
755                                                 self.maxPage = pageNumber\r
756                                 except:\r
757                                         pass\r
758                                 \r
759                                 self.instance.setTitle(_("Page %d / %d. Push < > to switch the page...")%(self.curPage, self.maxPage))\r
760                         \r
761                         while html.__contains__('title="Download"'):\r
762                                 idx = html.index('title="Download"')\r
763                                 html = html[idx:]\r
764                                 idx = html.index('value="')\r
765                                 html = html[idx+7:]\r
766                                 idx = html.index('"')\r
767                                 size = html[:idx]\r
768                                 idx = html.index('http://rapidshare.com/')\r
769                                 html = html[idx:]\r
770                                 idx = html.index('"')\r
771                                 url = html[:idx]\r
772                                 \r
773                                 files.append(url) \r
774                                 try:\r
775                                         urllist = url.split("/")\r
776                                         idx = len(urllist) - 1\r
777                                         name = urllist[idx]\r
778                                         list.append("%s - %s"%(size, name))\r
779                                 except:\r
780                                         list.append("%s - %s"%(size, url))\r
781                 \r
782                 self.files = files\r
783                 self["list"].setList(list)\r
784 \r
785         def searchError(self, error=""):\r
786                 self.session.open(MessageBox, (_("Error while searching http://rapidshare-search-engine.com!\n\nError: %s")%str(error)), MessageBox.TYPE_ERROR)\r
787 \r
788         def previousPage(self):\r
789                 if self.curPage > 1:\r
790                         self.curPage -= 1\r
791                         self.instance.setTitle(_("Loading previous page... please wait!"))\r
792                         self.search()\r
793 \r
794         def nextPage(self):\r
795                 if self.curPage < self.maxPage:\r
796                         self.curPage += 1\r
797                         self.instance.setTitle(_("Loading next page... please wait!"))\r
798                         self.search()\r
799 \r
800 ##############################################################################\r
801 \r
802 class RSLogScreen(ChangedScreen):\r
803         skin = """\r
804                 <screen position="center,center" size="560,450" title="RS Downloader">\r
805                         <widget name="label" position="0,0" size="560,450" font="Regular;20" />\r
806                 </screen>"""\r
807 \r
808         def __init__(self, session):\r
809                 ChangedScreen.__init__(self, session)\r
810                 \r
811                 try:\r
812                         f = open("/tmp/rapidshare.log")\r
813                         log = f.read()\r
814                         f.close()\r
815                 except:\r
816                         log = ""\r
817                 self["label"] = ScrollLabel(log)\r
818                 \r
819                 self["actions"] = ActionMap(["WizardActions"],\r
820                         {\r
821                                 "ok": self.close,\r
822                                 "back": self.close,\r
823                                 "up": self["label"].pageUp,\r
824                                 "down": self["label"].pageDown,\r
825                                 "left": self["label"].pageUp,\r
826                                 "right": self["label"].pageDown\r
827                         }, -1)\r
828 \r
829 ##############################################################################\r
830 \r
831 class RSContainerSelector(ChangedScreen):\r
832         skin = """\r
833                 <screen position="center,center" size="560,450" title="RS Downloader">\r
834                         <widget name="list" position="0,0" size="560,450" />\r
835                 </screen>"""\r
836 \r
837         def __init__(self, session, list):\r
838                 ChangedScreen.__init__(self, session)\r
839                 self["list"] = MenuList(list)\r
840                 self["actions"] = ActionMap(["OkCancelActions"], {"ok": self.okClicked, "cancel": self.close}, -1)\r
841 \r
842         def okClicked(self):\r
843                 cur = self["list"].getCurrent()\r
844                 self.close(cur)\r
845 \r
846 ##############################################################################\r
847 \r
848 class RSList(MenuList):\r
849         def __init__(self, list):\r
850                 MenuList.__init__(self, list, False, eListboxPythonMultiContent)\r
851                 self.l.setItemHeight(25)\r
852                 self.l.setFont(0, gFont("Regular", 20))\r
853 \r
854 ##############################################################################\r
855 \r
856 def RSListEntry(download):\r
857         res = [(download)]\r
858         res.append(MultiContentEntryText(pos=(0, 0), size=(170, 25), font=0, text=download.name))\r
859         res.append(MultiContentEntryText(pos=(175, 0), size=(75, 25), font=0, text="%d%s"%(download.size, "MB"), flags=RT_HALIGN_CENTER))\r
860         res.append(MultiContentEntryPixmapAlphaTest(pos=(260, 9), size=(84, 7), png=LoadPixmap(cached=True, path=resolveFilename(SCOPE_SKIN_IMAGE, "skin_default/progress_bg.png"))))\r
861         res.append(MultiContentEntryPixmapAlphaTest(pos=(260, 10), size=(int(0.84 * download.progress), 5), png=LoadPixmap(cached=True, path=resolveFilename(SCOPE_SKIN_IMAGE, "skin_default/progress_small.png"))))\r
862         res.append(MultiContentEntryText(pos=(360, 0), size=(60, 25), font=0, text="%d%s"%(download.progress, "%"), flags=RT_HALIGN_CENTER))\r
863         res.append(MultiContentEntryText(pos=(420, 0), size=(140, 25), font=0, text=download.status, flags=RT_HALIGN_RIGHT))\r
864         return res\r
865 \r
866 ##############################################################################\r
867 \r
868 class RSMain(ChangedScreen):\r
869         skin = """\r
870                 <screen position="center,center" size="560,450" title="RS Downloader">\r
871                         <ePixmap pixmap="skin_default/buttons/red.png" position="0,0" size="140,40" transparent="1" alphatest="on" />\r
872                         <ePixmap pixmap="skin_default/buttons/green.png" position="140,0" size="140,40" transparent="1" alphatest="on" />\r
873                         <ePixmap pixmap="skin_default/buttons/yellow.png" position="280,0" size="140,40" transparent="1" alphatest="on" />\r
874                         <ePixmap pixmap="skin_default/buttons/blue.png" position="420,0" size="140,40" transparent="1" alphatest="on" />\r
875                         <ePixmap pixmap="skin_default/buttons/key_menu.png" position="10,420" size="35,25" transparent="1" alphatest="on" />\r
876                         <widget name="key_red" position="0,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />\r
877                         <widget name="key_green" position="140,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />\r
878                         <widget name="key_yellow" position="280,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />\r
879                         <widget name="key_blue" position="420,0" zPosition="1" size="140,40" font="Regular;20" valign="center" halign="center" backgroundColor="#1f771f" transparent="1" />\r
880                         <widget name="key_menu" position="50,422" size="300,25" font="Regular;20" transparent="1" />\r
881                         <widget name="list" position="0,40" size="560,375" scrollbarMode="showNever" />\r
882                 </screen>"""\r
883 \r
884         def __init__(self, session):\r
885                 ChangedScreen.__init__(self, session)\r
886                 self.session = session\r
887                 \r
888                 self["key_red"] = Label(_("Delete"))\r
889                 self["key_green"] = Label(_("Search"))\r
890                 self["key_yellow"] = Label(_("Add"))\r
891                 self["key_blue"] = Label(_("Config"))\r
892                 self["key_menu"] = Label(_("Menu"))\r
893                 self["list"] = RSList([])\r
894                 \r
895                 self.refreshTimer = eTimer()\r
896                 self.refreshTimer.callback.append(self.updateList)\r
897                 \r
898                 self["actions"] = ActionMap(["OkCancelActions", "ColorActions", "InfobarMenuActions"],\r
899                         {\r
900                                 "mainMenu": self.menu,\r
901                                 "cancel": self.close,\r
902                                 "red": self.delete,\r
903                                 "green": self.search,\r
904                                 "yellow": self.add,\r
905                                 "blue": self.config\r
906                         }, prio=-1)\r
907                 \r
908                 self.onLayoutFinish.append(self.updateList)\r
909 \r
910         def menu(self):\r
911                 list = []\r
912                 #TODO: Add sort list functions\r
913                 list.append((_("Delete download"), self.delete))\r
914                 list.append((_("Use search engine"), self.search))\r
915                 list.append((_("Add downloads from txt files"), self.add))\r
916                 list.append((_("Add files from container"), self.addContainer))\r
917                 list.append((_("Delete failed downloads"), self.deleteFailed))\r
918                 list.append((_("Restart failed downloads"), self.restartFailed))\r
919                 list.append((_("Clear finished downloads"), self.clearFinished))\r
920                 list.append((_("Show log"), self.showLog))\r
921                 list.append((_("Delete log"), self.deleteLog))\r
922                 list.append((_("Close plugin"), self.close))\r
923                 self.session.openWithCallback(self.menuCallback, ChoiceBox, title=_("Please choose a function..."), list=list)\r
924 \r
925         def menuCallback(self, callback=None):\r
926                 if callback is not None:\r
927                         callback[1]()\r
928 \r
929         def deleteFailed(self):\r
930                 rapidshare.deleteFailedDownloads()\r
931 \r
932         def restartFailed(self):\r
933                 rapidshare.restartFailedDownloads()\r
934 \r
935         def clearFinished(self):\r
936                 rapidshare.clearFinishedDownloads()\r
937 \r
938         def showLog(self):\r
939                 self.session.open(RSLogScreen)\r
940 \r
941         def deleteLog(self):\r
942                 try:\r
943                         remove("/tmp/rapidshare.log")\r
944                 except:\r
945                         pass\r
946 \r
947         def updateList(self):\r
948                 list = []\r
949                 for download in rapidshare.downloads:\r
950                         list.append(RSListEntry(download))\r
951                 self["list"].setList(list)\r
952                 self.refreshTimer.start(2000, 1)\r
953 \r
954         def delete(self):\r
955                 cur = self["list"].getCurrent()\r
956                 if cur:\r
957                         cur = cur[0]\r
958                         if cur.status == _("Finished"):\r
959                                 rapidshare.clearFinishedDownload(cur.url)\r
960                         else:\r
961                                 self.session.openWithCallback(self.deleteCallback, MessageBox, (_("Delete %s?")%cur.name))\r
962 \r
963         def deleteCallback(self, callback):\r
964                 if callback:\r
965                         rapidshare.removeDownload(self["list"].getCurrent()[0].url)\r
966                         self.refreshTimer.stop()\r
967                         self.updateList()\r
968 \r
969         def search(self):\r
970                 self.session.openWithCallback(self.searchCallback, VirtualKeyBoard, title=_("Search http://rapidshare-search-engine.com for:"))\r
971 \r
972         def searchCallback(self, callback):\r
973                 if callback is not None and callback != "":\r
974                         self.session.openWithCallback(self.searchScreenCallback, RSSearch, callback)\r
975 \r
976 \r
977         def searchScreenCallback(self):\r
978                 self.refreshTimer.stop()\r
979                 rapidshare.startDownloading()\r
980                 self.updateList()\r
981 \r
982         def add(self):\r
983                 self.refreshTimer.stop()\r
984                 rapidshare.startDownloading()\r
985                 self.updateList()\r
986 \r
987         def config(self):\r
988                 self.session.openWithCallback(self.configCallback, RSConfig)\r
989 \r
990         def configCallback(self):\r
991                 if config.plugins.RSDownloader.onoff.value:\r
992                         rapidshare.startDownloading()\r
993                 else:\r
994                         for download in rapidshare.downloads:\r
995                                 if download.downloading:\r
996                                         download.stop()\r
997                 self.updateList()\r
998 \r
999         def addContainer(self):\r
1000                 try:\r
1001                         file_list = listdir(config.plugins.RSDownloader.lists_directory.value)\r
1002                 except:\r
1003                         file_list = []\r
1004                 list = []\r
1005                 for file in file_list:\r
1006                         if file.lower().endswith(".ccf") or file.lower().endswith(".dlc") or file.lower().endswith(".rsdf"):\r
1007                                 list.append(file)\r
1008                 list.sort()\r
1009                 self.session.openWithCallback(self.addContainerCallback, RSContainerSelector, list)\r
1010 \r
1011         def addContainerCallback(self, callback=None):\r
1012                 if callback:\r
1013                         file = "%s/%s"%(config.plugins.RSDownloader.lists_directory.value, callback)\r
1014                         file = file.replace("//", "/")\r
1015                         links = decrypt(file)\r
1016                         try:\r
1017                                 f = open(("%s/%s.txt" % (config.plugins.RSDownloader.lists_directory.value, callback)).replace("//", "/"), "w")\r
1018                                 for link in links:\r
1019                                         if link.endswith(".html"):\r
1020                                                 link = link[:-5]\r
1021                                         elif link.endswith(".htm"):\r
1022                                                 link = link[:-4]\r
1023                                         f.write("%s\n"%link)\r
1024                                 f.close()
1025                                 remove(file)\r
1026                         except:\r
1027                                 pass\r
1028                         self.refreshTimer.stop()\r
1029                         rapidshare.startDownloading()\r
1030                         self.updateList()\r
1031 \r
1032 ##############################################################################\r
1033 \r
1034 def autostart(reason, **kwargs):\r
1035         if reason == 0:\r
1036                 rapidshare.startDownloading()\r
1037 \r
1038 ##############################################################################\r
1039 \r
1040 def main(session, **kwargs):\r
1041         session.open(RSMain)\r
1042 \r
1043 ##############################################################################\r
1044 \r
1045 def Plugins(**kwargs):\r
1046         return [\r
1047                 PluginDescriptor(where=PluginDescriptor.WHERE_AUTOSTART, fnc=autostart),\r
1048                 PluginDescriptor(name=_("RS Downloader"), description=_("Download files from rapidshare"), where=[PluginDescriptor.WHERE_EXTENSIONSMENU, PluginDescriptor.WHERE_PLUGINMENU], icon="rs.png", fnc=main)]\r
1049 \r