import os import xbmc import xbmcaddon import xbmcvfs import xbmcgui import requests import zipfile import gzip import resources.lib.utils as utils from io import BytesIO try: # Python 3 from urllib.parse import urlparse except ImportError: # Python 2 from urlparse import urlparse class Downloader(object): def __init__(self, download_url=None, extract_to=None): self.addon = xbmcaddon.Addon() self.download_url = download_url self.extract_to = xbmc.translatePath(extract_to) self.msg_cleardir = self.addon.getLocalizedString(32054) def recursive_delete_dir(self, fullpath): '''helper to recursively delete a directory''' success = True if not isinstance(fullpath, unicode): fullpath = fullpath.decode("utf-8") dirs, files = xbmcvfs.listdir(fullpath) for file in files: file = file.decode("utf-8") success = xbmcvfs.delete(os.path.join(fullpath, file)) for directory in dirs: directory = directory.decode("utf-8") success = self.recursive_delete_dir(os.path.join(fullpath, directory)) success = xbmcvfs.rmdir(fullpath) return success def is_url(self, url): try: result = urlparse(url) return all([result.scheme, result.netloc]) except ValueError: return False def check_url(self, url, cred): if not self.is_url(url): utils.kodi_log("URL is not of a valid schema: {0}".format(url), 1) return False try: response = requests.head(url, allow_redirects=True, auth=cred) if response.status_code < 300: utils.kodi_log("URL check passed for {0}: Status code [{1}]".format(url, response.status_code), 1) return True elif response.status_code < 400: utils.kodi_log("URL check redirected from {0} to {1}: Status code [{2}]".format(url, response.headers['Location'], response.status_code), 1) return self.check_url(response.headers['Location']) elif response.status_code == 401: utils.kodi_log("URL requires authentication for {0}: Status code [{1}]".format(url, response.status_code), 1) return 'auth' else: utils.kodi_log("URL check failed for {0}: Status code [{1}]".format(url, response.status_code), 1) return False except Exception as e: utils.kodi_log("URL check error for {0}: [{1}]".format(url, e), 1) return False def open_url(self, url, stream=False, check=False, cred=None, count=0): if not url: return False valid = self.check_url(url, cred) if not valid: return False if check: return True if valid == 'auth' and not cred: cred = (xbmcgui.Dialog().input(heading=xbmc.getLocalizedString(1014)) or '', xbmcgui.Dialog().input(heading=xbmc.getLocalizedString(733), option=xbmcgui.ALPHANUM_HIDE_INPUT) or '') response = requests.get(url, timeout=10.000, stream=stream, auth=cred) if response.status_code == 401: if count > 2 or not xbmcgui.Dialog().yesno(self.addon.getAddonInfo('name'), self.addon.getLocalizedString(32056), yeslabel=self.addon.getLocalizedString(32057), nolabel=xbmc.getLocalizedString(222)): xbmcgui.Dialog().ok(self.addon.getAddonInfo('name'), self.addon.getLocalizedString(32055)) return False count += 1 cred = (xbmcgui.Dialog().input(heading=xbmc.getLocalizedString(1014)) or '', xbmcgui.Dialog().input(heading=xbmc.getLocalizedString(733), option=xbmcgui.ALPHANUM_HIDE_INPUT) or '') response = self.open_url(url, stream, check, cred, count) return response def clear_dir(self, folder): for filename in os.listdir(folder): file_path = os.path.join(folder, filename) try: if os.path.isfile(file_path): os.unlink(file_path) elif os.path.isdir(file_path): self.recursive_delete_dir(file_path) except Exception as e: utils.kodi_log(u'Could not delete file {0}: {1}'.format(file_path, str(e))) def get_gzip_text(self): if not self.download_url: return with utils.busy_dialog(): response = self.open_url(self.download_url) if not response: xbmcgui.Dialog().ok(self.addon.getAddonInfo('name'), self.addon.getLocalizedString(32058)) return with gzip.GzipFile(fileobj=BytesIO(response.content)) as downloaded_gzip: content = downloaded_gzip.read() return content def get_extracted_zip(self): if not self.download_url or not self.extract_to: return with utils.busy_dialog(): response = self.open_url(self.download_url) if not response: xbmcgui.Dialog().ok(self.addon.getAddonInfo('name'), self.addon.getLocalizedString(32058)) return if not os.path.exists(self.extract_to): os.makedirs(self.extract_to) if xbmcgui.Dialog().yesno(self.addon.getAddonInfo('name'), self.msg_cleardir): with utils.busy_dialog(): self.clear_dir(self.extract_to) with utils.busy_dialog(): num_files = 0 with zipfile.ZipFile(BytesIO(response.content)) as downloaded_zip: for item in [x for x in downloaded_zip.namelist() if x.endswith('.json')]: filename = os.path.basename(item) if not filename: continue _file = downloaded_zip.open(item) with open(os.path.join(self.extract_to, filename), 'w') as target: target.write(_file.read()) num_files += 1 try: _tempzip = os.path.join(self.extract_to, 'temp.zip') os.remove(_tempzip) except Exception as e: utils.kodi_log(u'Could not delete package {0}: {1}'.format(_tempzip, str(e))) if num_files: xbmcgui.Dialog().ok(self.addon.getAddonInfo('name'), '{0}\n\n{1} {2}.'.format(self.addon.getLocalizedString(32059), num_files, self.addon.getLocalizedString(32060)))