def deactivate(self): """ Remove override for all plugins. """ self.log_info(_("Reverting back to default hosters")) try: get_hook_manager().removeEvent("plugin_updated", self.plugins_updated) except ValueError: pass self.periodical.stop() self.fail_count = 0 if self.supported: self.log_debug("Unload: %s" % ", ".join(self.supported)) for plugin in self.supported: self.unload_plugin(plugin) #: Reset pattern hdict = get_plugin_manager().plugins[self.plugintype][self.classname] hdict['pattern'] = getattr(self.pluginclass, "__pattern__", r'^unmatchable$') hdict['re'] = re.compile(hdict['pattern'])
def deletePackage(self, id): """delete package and all contained links""" p = self.getPackage(id) if not p: if id in self.packageCache: del self.packageCache[id] return oldorder = p.order queue = p.queue e = RemoveEvent("pack", id, "collector" if not p.queue else "queue") pyfiles = self.cache.values() for pyfile in pyfiles: if pyfile.packageid == id: pyfile.abortDownload() pyfile.release() self.db.deletePackage(p) get_pull_manager().addEvent(e) get_hook_manager().dispatchEvent("packageDeleted", id) if id in self.packageCache: del self.packageCache[id] packs = self.packageCache.values() for pack in packs: if pack.queue == queue and pack.order > oldorder: pack.order -= 1 pack.notifyChange()
def shutdown(self): self.log.info(_("shutting down...")) try: if self.config['webinterface']['activated'] and hasattr(self, "webserver"): self.webserver.quit() for thread in get_thread_manager().threads: thread.put("quit") pyfiles = self.files.cache.values() for pyfile in pyfiles: pyfile.abortDownload() get_hook_manager().coreExiting() except: if self.debug: print_exc() self.log.info(_("error while shutting down")) finally: self.files.syncSave() self.shuttedDown = True self.deletePidFile()
def checkAllLinksFinished(self): """checks if all files are finished and dispatch event""" if not self.getQueueCount(True): get_hook_manager().dispatchEvent("allDownloadsFinished") self.core.log.debug("All downloads finished") return True return False
def checkPackageFinished(self, pyfile): """ checks if package is finished and calls hookmanager """ ids = self.db.getUnfinished(pyfile.packageid) if not ids or (pyfile.id in ids and len(ids) == 1): if not pyfile.package().setFinished: self.core.log.info(_("Package finished: %s") % pyfile.package().name) get_hook_manager().packageFinished(pyfile.package()) pyfile.package().setFinished = True
def addLinks(self, urls, package): """adds links""" get_hook_manager().dispatchEvent("linksAdded", urls, package) data = get_plugin_manager().parseUrls(urls) self.db.addLinks(data, package) get_thread_manager().createInfoThread(data, package) #@TODO change from reloadAll event to package update event get_pull_manager().addEvent(ReloadAllEvent("collector"))
def checkAllLinksProcessed(self, fid): """checks if all files was processed and pyload would idle now, needs fid which will be ignored when counting""" # reset count so statistic will update (this is called when dl was processed) self.resetCount() if not self.db.processcount(1, fid): get_hook_manager().dispatchEvent("allDownloadsProcessed") self.core.log.debug("All downloads processed") return True return False
def do_download(self, ip, port, file_name, file_size): if self.dl_started: return self.dl_started = True try: self.pyfile.name = file_name self.req.filesize = file_size self.pyfile.setStatus("downloading") dl_folder = fsjoin(self.pyload.config.get('general', 'download_folder'), self.pyfile.package().folder if self.pyload.config.get("general", "folder_per_package") else "") if not exists(dl_folder): try: os.makedirs(dl_folder) except Exception as e: self.fail(e.message) self.set_permissions(dl_folder) self.check_duplicates() dl_file = fsjoin(dl_folder, self.pyfile.name) self.log_debug(_("DOWNLOAD XDCC '%s' from %s:%d") % (file_name, ip, port)) get_hook_manager().dispatchEvent("download_start", self.pyfile, "%s:%s" % (ip, port), dl_file) newname = self.req.download(ip, port, dl_file, progressNotify=self.pyfile.setProgress, resume=self.xdcc_send_resume) if newname and newname != dl_file: self.log_info(_("%(name)s saved as %(newname)s") % {'name': self.pyfile.name, 'newname': newname}) dl_file = newname self.last_download = dl_file except Abort: pass except Exception as e: bot = self.info['pattern']['BOT'] self.irc_client.xdcc_cancel_pack(bot) if not self.exc_info: self.exc_info = sys.exc_info() #: pass the exception to the main thread self.dl_finished = True
def getInfoByPlugin(self, plugin): """Returns information stored by a specific plugin. :param plugin: pluginname :return: dict of attr names mapped to value {"name": value} """ return get_hook_manager().getInfo(plugin)
def _finalize(self): pypack = self.pyfile.package() hook_manager = get_hook_manager() hook_manager.dispatchEvent("download_processed", self.pyfile) try: unfinished = any( fdata.get('status') in (3, 7) for fid, fdata in pypack.getChildren().items() if fid != self.pyfile.id) if unfinished: return hook_manager.dispatchEvent("package_processed", pypack) failed = any( fdata.get('status') in (1, 6, 8, 9, 14) for fid, fdata in pypack.getChildren().items()) if not failed: return hook_manager.dispatchEvent("package_failed", pypack) finally: self.check_status()
def hasService(self, plugin, func): """Checks wether a service is available. :param plugin: :param func: :return: bool """ cont = get_hook_manager().methods return plugin in cont and func in cont[plugin]
def getServices(self): """ A dict of available services, these can be defined by hook plugins. :return: dict with this style: {"plugin": {"method": "description"}} """ data = {} for plugin, funcs in six.iteritems(get_hook_manager().methods): data[plugin] = funcs return data
def setConfigValue(self, category, option, value, section="core"): """Set new config value. :param category: :param option: :param value: new config value :param section: 'plugin' or 'core """ get_hook_manager().dispatchEvent("configChanged", category, option, value, section) if section == "core": self.core.config[category][option] = value if option in ("limit_speed", "max_speed"): # not so nice to update the limit get_request_factory().updateBucket() elif section == "plugin": self.core.config.setPlugin(category, option, value)
def init_plugin(self): plugin_manager = get_plugin_manager() plugin, self.plugintype = plugin_manager.findPlugin(self.classname) if plugin: self.pluginmodule = plugin_manager.loadModule( self.plugintype, self.classname) self.pluginclass = plugin_manager.loadClass( self.plugintype, self.classname) get_hook_manager().addEvent("plugin_updated", self.plugins_updated) interval = self.config.get('mh_interval', 12) * 60 * 60 self.periodical.start(interval, threaded=True, delay=2) else: self.log_warning( _("Multi-hoster feature will be deactivated due missing plugin reference" ))
def _check_download(self): self.log_info(_("Checking download...")) self.pyfile.setCustomStatus(_("checking")) if not self.last_download: if self.captcha.task: self.retry_captcha() else: self.error(_("No file downloaded")) elif self.scan_download( {'Empty file': re.compile(r'\A((.|)(\2|\s)*)\Z')}): if self.remove(self.last_download): self.last_download = "" self.error(_("Empty file")) else: get_hook_manager().dispatchEvent("download_check", self.pyfile) self.check_status() self.log_info(_("File is OK"))
def _process(self, thread): """ Handles important things to do before starting """ self.thread = thread self._initialize() self._setup() #@TODO: Enable in 0.4.10 # get_hook_manager().downloadPreparing(self.pyfile) # self.check_status() #@TODO: Remove in 0.4.10 if self.__type__ == "crypter": get_hook_manager().downloadPreparing(self.pyfile) self.check_status() self.pyfile.setStatus("starting") self.log_info(_("Processing url: ") + self.pyfile.url) self.process(self.pyfile) self.check_status()
def handleCaptcha(self, task): cli = self.core.isClientConnected() if cli: #client connected -> should solve the captcha task.setWaiting(50) #wait 50 sec for response for plugin in get_hook_manager().activePlugins(): try: plugin.newCaptchaTask(task) except: if self.core.debug: print_exc() if task.handler or cli: #the captcha was handled self.tasks.append(task) return True task.error = _("No Client connected for captcha decrypting") return False
def call(self, info): """Calls a service (a method in hook plugin). :param info: `ServiceCall` :return: result :raises: ServiceDoesNotExists, when its not available :raises: ServiceException, when a exception was raised """ plugin = info.plugin func = info.func args = info.arguments parse = info.parseArguments if not self.hasService(plugin, func): raise ServiceDoesNotExists(plugin, func) try: ret = get_hook_manager().callRPC(plugin, func, args, parse) return str(ret) except Exception as e: raise ServiceException(e.message)
def start(self, rpc=True, web=True): """ starts the fun :D """ self.version = CURRENT_VERSION if not exists("pyload.conf"): from module.setup import Setup print("This is your first start, running configuration assistent now.") self.config = ConfigParser() s = Setup(pypath, self.config) res = False try: res = s.start() except SystemExit: pass except KeyboardInterrupt: print("\nSetup interrupted") except: res = False print_exc() print("Setup failed") if not res: remove("pyload.conf") exit() try: signal.signal(signal.SIGQUIT, self.quit) except: pass self.config = ConfigParser() gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) translation = gettext.translation("pyLoad", self.path("locale"), languages=[self.config['general']['language'],"en"],fallback=True) install_translation(translation) self.debug = self.doDebug or self.config['general']['debug_mode'] self.remote &= self.config['remote']['activated'] pid = self.isAlreadyRunning() if pid: print(_("pyLoad already running with pid %s") % pid) exit() if not IS_WINDOWS and self.config["general"]["renice"]: os.system("renice %d %d" % (self.config["general"]["renice"], os.getpid())) if self.config["permission"]["change_group"]: if not IS_WINDOWS: try: from grp import getgrnam group = getgrnam(self.config["permission"]["group"]) os.setgid(group[2]) except Exception as e: print(_("Failed changing group: %s") % e) if self.config["permission"]["change_user"]: if not IS_WINDOWS: try: from pwd import getpwnam user = getpwnam(self.config["permission"]["user"]) os.setuid(user[2]) except Exception as e: print(_("Failed changing user: %s") % e) self.check_file( self.config['log']['log_folder'], _("folder for logs"), is_folder=True, ) if self.debug: self.init_logger(logging.DEBUG) # logging level else: self.init_logger(logging.INFO) # logging level sys.excepthook = exceptHook self.do_kill = False self.do_restart = False self.shuttedDown = False self.log.info(_("Starting") + " pyLoad %s" % CURRENT_VERSION) self.log.info(_("Using home directory: %s") % getcwd()) self.writePidFile() #@TODO refractor remote.activated = self.remote self.log.debug("Remote activated: %s" % self.remote) self.check_install("Crypto", _("pycrypto to decode container files")) #img = self.check_install("Image", _("Python Image Libary (PIL) for captcha reading")) #self.check_install("pycurl", _("pycurl to download any files"), True, True) self.check_file("tmp", _("folder for temporary files"), is_folder=True) #tesser = self.check_install("tesseract", _("tesseract for captcha reading"), False) if not IS_WINDOWS else True self.captcha = True # checks seems to fail, althoug tesseract is available self.check_file( self.config['general']['download_folder'], _("folder for downloads"), is_folder=True, ) if self.config['ssl']['activated']: self.check_install("OpenSSL", _("OpenSSL for secure connection")) self.setupDB() if self.deleteLinks: self.log.info(_("All links removed")) self.db.purgeLinks() set_request_factory(RequestFactory(self)) self.lastClientConnected = 0 # later imported because they would trigger api import, and remote value not set correctly from module import Api from module.HookManager import HookManager from module.ThreadManager import ThreadManager if Api.activated != self.remote: self.log.warning("Import error: API remote status not correct.") self.api = Api.Api(self) self.scheduler = Scheduler(self) #hell yeah, so many important managers :D set_plugin_manager(PluginManager(self)) set_pull_manager(PullManager(self)) set_thread_manager(ThreadManager(self)) set_account_manager(AccountManager(self)) set_captcha_manager(CaptchaManager(self)) # HookManager sets itself as a singleton HookManager(self) set_remote_manager(RemoteManager(self)) thread_manager = get_thread_manager() self.js = JsEngine() self.log.info(_("Downloadtime: %s") % self.api.isTimeDownload()) if rpc: get_remote_manager().startBackends() if web: self.init_webserver() spaceLeft = freeSpace(self.config["general"]["download_folder"]) self.log.info(_("Free space: %s") % formatSize(spaceLeft)) self.config.save() #save so config files gets filled link_file = join(pypath, "links.txt") if exists(link_file): f = open(link_file, "rb") if f.read().strip(): self.api.addPackage("links.txt", [link_file], 1) f.close() link_file = "links.txt" if exists(link_file): f = open(link_file, "rb") if f.read().strip(): self.api.addPackage("links.txt", [link_file], 1) f.close() #self.scheduler.addJob(0, get_account_manager().getAccountInfos) self.log.info(_("Activating Accounts...")) get_account_manager().getAccountInfos() thread_manager.pause = False self.running = True self.log.info(_("Activating Plugins...")) get_hook_manager().coreReady() self.log.info(_("pyLoad is up and running")) #test api # from module.common.APIExerciser import startApiExerciser # startApiExerciser(self, 3) #some memory stats # from guppy import hpy # hp=hpy() # import objgraph # objgraph.show_most_common_types(limit=20) # import memdebug # memdebug.start(8002) locals().clear() while True: try: sleep(2) except IOError as e: if e.errno != 4: # errno.EINTR raise if self.do_restart: self.log.info(_("restarting pyLoad")) self.restart() if self.do_kill: self.shutdown() self.log.info(_("pyLoad quits")) self.removeLogger() _exit(0) #@TODO thrift blocks shutdown thread_manager.work() self.scheduler.work()
def reactivate(self, refresh=False): reloading = self.info['data'].get('hosters') is not None if not self.info['login']['valid']: self.fail_count += 1 if self.fail_count < 3: if reloading: self.log_error( _("Could not reload hoster list - invalid account, retry in 5 minutes" )) else: self.log_error( _("Could not load hoster list - invalid account, retry in 5 minutes" )) self.periodical.set_interval(5 * 60) else: if reloading: self.log_error( _("Could not reload hoster list - invalid account, deactivating" )) else: self.log_error( _("Could not load hoster list - invalid account, deactivating" )) self.deactivate() return if not self.logged: if not self.relogin(): self.fail_count += 1 if self.fail_count < 3: if reloading: self.log_error( _("Could not reload hoster list - login failed, retry in 5 minutes" )) else: self.log_error( _("Could not load hoster list - login failed, retry in 5 minutes" )) self.periodical.set_interval(5 * 60) else: if reloading: self.log_error( _("Could not reload hoster list - login failed, deactivating" )) else: self.log_error( _("Could not load hoster list - login failed, deactivating" )) self.deactivate() return #: Make sure we have one active hook hook_manager = get_hook_manager() try: hook_manager.removeEvent("plugin_updated", self.plugins_updated) except ValueError: pass hook_manager.addEvent("plugin_updated", self.plugins_updated) if refresh or not reloading: if not self.get_plugins(cached=False): self.fail_count += 1 if self.fail_count < 3: self.log_error( _("Failed to load hoster list for user `%s`, retry in 5 minutes" ) % self.user) self.periodical.set_interval(5 * 60) else: self.log_error( _("Failed to load hoster list for user `%s`, deactivating" ) % self.user) self.deactivate() return if self.fail_count: self.fail_count = 0 interval = self.config.get('mh_interval', 12) * 60 * 60 self.periodical.set_interval(interval) self._override()
def download(self, url, get={}, post={}, ref=True, cookies=True, disposition=False): """Downloads the content at url to download folder :param url: :param get: :param post: :param ref: :param cookies: :param disposition: if True and server provides content-disposition header\ the filename will be changed if needed :return: The location where the file was saved """ self.checkForSameFiles() self.pyfile.setStatus("downloading") download_folder = self.config['general']['download_folder'] location = save_join(download_folder, self.pyfile.package().folder) if not exists(location): makedirs(location, int(self.core.config["permission"]["folder"], 8)) if self.core.config["permission"]["change_dl"] and not IS_WINDOWS: try: uid = getpwnam(self.config["permission"]["user"])[2] gid = getgrnam(self.config["permission"]["group"])[2] chown(location, uid, gid) except Exception as e: self.log.warning( _("Setting User and Group failed: %s") % str(e)) # convert back to unicode location = fs_decode(location) name = save_path(self.pyfile.name) filename = join(location, name) get_hook_manager().dispatchEvent("downloadStarts", self.pyfile, url, filename) try: newname = self.req.httpDownload( url, filename, get=get, post=post, ref=ref, cookies=cookies, chunks=self.getChunkCount(), resume=self.resumeDownload, progressNotify=self.pyfile.setProgress, disposition=disposition) finally: self.pyfile.size = self.req.size if disposition and newname and newname != name: #triple check, just to be sure self.log.info("%(name)s saved as %(newname)s" % { "name": name, "newname": newname }) self.pyfile.name = newname filename = join(location, newname) fs_filename = fs_encode(filename) if self.core.config["permission"]["change_file"]: chmod(fs_filename, int(self.core.config["permission"]["file"], 8)) if self.core.config["permission"]["change_dl"] and not IS_WINDOWS: try: uid = getpwnam(self.config["permission"]["user"])[2] gid = getgrnam(self.config["permission"]["group"])[2] chown(fs_filename, uid, gid) except Exception as e: self.log.warning( _("Setting User and Group failed: %s") % str(e)) self.lastDownload = filename return self.lastDownload
def __new__(cls, fn, *args, **kwargs): get_hook_manager().addRPC(fn.__module__, fn.__name__, fn.__doc__) return fn
def run(*args, **kwargs): get_hook_manager().startThread(fn, *args, **kwargs)
def getAllInfo(self): """Returns all information stored by hook plugins. Values are always strings :return: {"plugin": {"name": value } } """ return get_hook_manager().getAllInfo()
def run(self): """run method""" pyfile = None while True: del pyfile self.active = self.queue.get() pyfile = self.active if self.active == "quit": self.active = False self.m.threads.remove(self) return True hook_manager = get_hook_manager() try: if not pyfile.hasPlugin(): continue #this pyfile was deleted while queueing pyfile.plugin.checkForSameFiles(starting=True) self.m.log.info(_("Download starts: %s" % pyfile.name)) # start download hook_manager.downloadPreparing(pyfile) pyfile.plugin.preprocessing(self) self.m.log.info(_("Download finished: %s") % pyfile.name) hook_manager.downloadFinished(pyfile) self.m.core.files.checkPackageFinished(pyfile) except NotImplementedError: self.m.log.error( _("Plugin %s is missing a function.") % pyfile.pluginname) pyfile.setStatus("failed") pyfile.error = "Plugin does not work" self.clean(pyfile) continue except Abort: try: self.m.log.info(_("Download aborted: %s") % pyfile.name) except: pass pyfile.setStatus("aborted") self.clean(pyfile) continue except Reconnect: self.queue.put(pyfile) #pyfile.req.clearCookies() while self.m.reconnecting.isSet(): sleep(0.5) continue except Retry as e: reason = e.args[0] self.m.log.info( _("Download restarted: %(name)s | %(msg)s") % { "name": pyfile.name, "msg": reason }) self.queue.put(pyfile) continue except Fail as e: msg = e.args[0] if msg == "offline": pyfile.setStatus("offline") self.m.log.warning( _("Download is offline: %s") % pyfile.name) elif msg == "temp. offline": pyfile.setStatus("temp. offline") self.m.log.warning( _("Download is temporary offline: %s") % pyfile.name) else: pyfile.setStatus("failed") self.m.log.warning( _("Download failed: %(name)s | %(msg)s") % { "name": pyfile.name, "msg": msg }) pyfile.error = msg hook_manager.downloadFailed(pyfile) self.clean(pyfile) continue except error as e: if len(e.args) == 2: code, msg = e.args else: code = 0 msg = e.args self.m.log.debug("pycurl exception %s: %s" % (code, msg)) if code in (7, 18, 28, 52, 56): self.m.log.warning( _("Couldn't connect to host or connection reset, waiting 1 minute and retry." )) wait = time() + 60 pyfile.waitUntil = wait pyfile.setStatus("waiting") while time() < wait: sleep(1) if pyfile.abort: break if pyfile.abort: self.m.log.info( _("Download aborted: %s") % pyfile.name) pyfile.setStatus("aborted") self.clean(pyfile) else: self.queue.put(pyfile) continue else: pyfile.setStatus("failed") self.m.log.error("pycurl error %s: %s" % (code, msg)) if self.m.core.debug: print_exc() self.writeDebugReport(pyfile) hook_manager.downloadFailed(pyfile) self.clean(pyfile) continue except SkipDownload as e: pyfile.setStatus("skipped") self.m.log.info( _("Download skipped: %(name)s due to %(plugin)s") % { "name": pyfile.name, "plugin": e.message }) self.clean(pyfile) self.m.core.files.checkPackageFinished(pyfile) self.active = False self.m.core.files.save() continue except Exception as e: pyfile.setStatus("failed") self.m.log.warning( _("Download failed: %(name)s | %(msg)s") % { "name": pyfile.name, "msg": str(e) }) pyfile.error = str(e) if self.m.core.debug: print_exc() self.writeDebugReport(pyfile) hook_manager.downloadFailed(pyfile) self.clean(pyfile) continue finally: self.m.core.files.save() pyfile.checkIfProcessed() try: sys.exc_clear() except Exception: pass #pyfile.plugin.req.clean() self.active = False pyfile.finishIfDone() self.m.core.files.save()
def tryReconnect(self): """checks if reconnect needed""" if not (self.core.config["reconnect"]["activated"] and self.core.api.isTimeReconnect()): return False active = [ x.active.plugin.wantReconnect and x.active.plugin.waiting for x in self.threads if x.active ] if not (0 < active.count(True) == len(active)): return False if not exists(self.core.config['reconnect']['method']): if exists(join(pypath, self.core.config['reconnect']['method'])): self.core.config['reconnect']['method'] = join( pypath, self.core.config['reconnect']['method']) else: self.core.config["reconnect"]["activated"] = False self.log.warning(_("Reconnect script not found!")) return self.reconnecting.set() # Do reconnect self.log.info(_("Starting reconnect")) while [x.active.plugin.waiting for x in self.threads if x.active].count(True) != 0: sleep(0.25) ip = self.getIP() hook_manager = get_hook_manager() hook_manager.beforeReconnecting(ip) self.log.debug("Old IP: %s" % ip) try: reconn = Popen(self.core.config['reconnect']['method'], bufsize=-1, shell=True) #, stdout=subprocess.PIPE) except: self.log.warning(_("Failed executing reconnect script!")) self.core.config["reconnect"]["activated"] = False self.reconnecting.clear() if self.core.debug: print_exc() return reconn.wait() sleep(1) ip = self.getIP() hook_manager.afterReconnecting(ip) self.log.info(_("Reconnected, new IP: %s") % ip) self.reconnecting.clear()
def download(self, url, get={}, post={}, ref=True, cookies=True, disposition=True, resume=None, chunks=None, fixurl=True): """ Downloads the content at url to download folder :param url: :param get: :param post: :param ref: :param cookies: :param disposition: if True and server provides content-disposition header\ the filename will be changed if needed :return: The location where the file was saved """ self.check_status() if self.pyload.debug: self.log_debug( "DOWNLOAD URL " + url, *[ "%s=%s" % (key, value) for key, value in locals().items() if key not in ("self", "url", "_[1]") ]) dl_url = self.fixurl(url) if fixurl else url dl_basename = parse_name(self.pyfile.name) self.pyfile.name = dl_basename self.check_duplicates() self.pyfile.setStatus("downloading") dl_folder = self.pyload.config.get('general', 'download_folder') dl_dirname = safejoin(dl_folder, self.pyfile.package().folder) dl_filename = safejoin(dl_dirname, dl_basename) dl_dir = encode(dl_dirname) dl_file = encode(dl_filename) if not exists(dl_dir): try: os.makedirs(dl_dir) except Exception as e: self.fail(e.message) self.set_permissions(dl_dir) get_hook_manager().dispatchEvent("download_start", self.pyfile, dl_url, dl_filename) self.check_status() newname = self._download(dl_url, dl_filename, get, post, ref, cookies, disposition, resume, chunks) #@TODO: Recheck in 0.4.10 if disposition and newname: safename = parse_name(newname.split(' filename*=')[0]) if safename != newname: try: old_file = fsjoin(dl_dirname, newname) new_file = fsjoin(dl_dirname, safename) os.rename(old_file, new_file) except OSError as e: self.log_warning( _("Error renaming `%s` to `%s`") % (newname, safename), e) safename = newname self.log_info( _("`%s` saved as `%s`") % (self.pyfile.name, safename)) self.pyfile.name = safename dl_filename = os.path.join(dl_dirname, safename) dl_file = encode(dl_filename) self.set_permissions(dl_file) self.last_download = dl_filename return dl_filename