Added updater at the start of the program
This commit is contained in:
		| @@ -6,7 +6,7 @@ authorEmail = "manuel@manuelcortez.net" | ||||
| copyright = "Copyright (C) 2018, Manuel Cortez" | ||||
| description = name+" Is an application that will allow you to download music from popular sites such as youtube, zaycev.net." | ||||
| url = "https://manuelcortez.net/music_dl" | ||||
| #update_url = "https://raw.githubusercontent.com/manuelcortez/socializer/master/update-files/socializer.json" | ||||
| update_url = "https://manuelcortez.net/music_dl/update" | ||||
| # The short name will be used for detecting translation files. See languageHandler for more details. | ||||
| short_name = "musicdl" | ||||
| translators = [] | ||||
| @@ -7,6 +7,7 @@ import utils | ||||
| from pubsub import pub | ||||
| from wxUI import mainWindow, menus | ||||
| from extractors import zaycev, youtube | ||||
| from update import updater | ||||
| from . import player | ||||
|  | ||||
| log = logging.getLogger("controller.main") | ||||
| @@ -18,7 +19,6 @@ class Controller(object): | ||||
| 		log.debug("Starting main controller...") | ||||
| 		# Setting up the player object | ||||
| 		player.setup() | ||||
| 		# Instantiate the only available extractor for now. | ||||
| 		# Get main window | ||||
| 		self.window = mainWindow.mainWindow() | ||||
| 		log.debug("Main window created") | ||||
| @@ -31,6 +31,7 @@ class Controller(object): | ||||
| 		self.timer.Start(75) | ||||
| 		self.window.vol_slider.SetValue(player.player.volume) | ||||
| 		# Shows window. | ||||
| 		utils.call_threaded(updater.do_update) | ||||
| 		self.window.Show() | ||||
|  | ||||
| 	def get_status_info(self): | ||||
|   | ||||
							
								
								
									
										0
									
								
								src/update/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/update/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										116
									
								
								src/update/update.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/update/update.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
| from logging import getLogger | ||||
| logger = getLogger('update') | ||||
|  | ||||
| import contextlib | ||||
| import io | ||||
| import os | ||||
| import platform | ||||
| import requests | ||||
| import tempfile | ||||
| try: | ||||
| 	import czipfile as zipfile | ||||
| except ImportError: | ||||
| 	import zipfile | ||||
|  | ||||
| from platform_utils import paths | ||||
|  | ||||
| def perform_update(endpoint, current_version, app_name='', password=None, update_available_callback=None, progress_callback=None, update_complete_callback=None): | ||||
| 	requests_session = create_requests_session(app_name=app_name, version=current_version) | ||||
| 	available_update = find_update(endpoint, requests_session=requests_session) | ||||
| 	if not available_update: | ||||
| 		logger.debug("No update available") | ||||
| 		return False | ||||
| 	available_version = float(available_update['current_version']) | ||||
| 	if not float(available_version) > float(current_version) or platform.system()+platform.architecture()[0][:2] not in available_update['downloads']: | ||||
| 		logger.debug("No update for this architecture") | ||||
| 		return False | ||||
| 	available_description = available_update.get('description', None) | ||||
| 	update_url = available_update ['downloads'][platform.system()+platform.architecture()[0][:2]] | ||||
| 	logger.info("A new update is available. Version %s" % available_version) | ||||
| 	if callable(update_available_callback) and not update_available_callback(version=available_version, description=available_description): #update_available_callback should return a falsy value to stop the process | ||||
| 		logger.info("User canceled update.") | ||||
| 		return | ||||
| 	base_path = tempfile.mkdtemp() | ||||
| 	download_path = os.path.join(base_path, 'update.zip') | ||||
| 	update_path = os.path.join(base_path, 'update') | ||||
| 	downloaded = download_update(update_url, download_path, requests_session=requests_session, progress_callback=progress_callback) | ||||
| 	extracted = extract_update(downloaded, update_path, password=password) | ||||
| 	bootstrap_path = move_bootstrap(extracted) | ||||
| 	execute_bootstrap(bootstrap_path, extracted) | ||||
| 	logger.info("Update prepared for installation.") | ||||
| 	if callable(update_complete_callback): | ||||
| 		update_complete_callback() | ||||
|  | ||||
| def create_requests_session(app_name=None, version=None): | ||||
| 	user_agent = '' | ||||
| 	session = requests.session() | ||||
| 	if app_name: | ||||
| 		user_agent = ' %s/%r' % (app_name, version) | ||||
| 	session.headers['User-Agent'] = session.headers['User-Agent'] + user_agent | ||||
| 	return session | ||||
|  | ||||
| def find_update(endpoint, requests_session): | ||||
| 	response = requests_session.get(endpoint) | ||||
| 	response.raise_for_status() | ||||
| 	content = response.json() | ||||
| 	return content | ||||
|  | ||||
| def download_update(update_url, update_destination, requests_session, progress_callback=None, chunk_size=io.DEFAULT_BUFFER_SIZE): | ||||
| 	total_downloaded = total_size = 0 | ||||
| 	with io.open(update_destination, 'w+b') as outfile: | ||||
| 		download = requests_session.get(update_url, stream=True) | ||||
| 		total_size = int(download.headers.get('content-length', 0)) | ||||
| 		logger.debug("Total update size: %d" % total_size) | ||||
| 		download.raise_for_status() | ||||
| 		for chunk in download.iter_content(chunk_size): | ||||
| 			outfile.write(chunk) | ||||
| 			total_downloaded += len(chunk) | ||||
| 			if callable(progress_callback): | ||||
| 				call_callback(progress_callback, total_downloaded, total_size) | ||||
| 	logger.debug("Update downloaded") | ||||
| 	return update_destination | ||||
|  | ||||
| def extract_update(update_archive, destination, password=None): | ||||
| 	"""Given an update archive, extracts it. Returns the directory to which it has been extracted""" | ||||
| 	with contextlib.closing(zipfile.ZipFile(update_archive)) as archive: | ||||
| 		if password: | ||||
| 			archive.setpassword(password) | ||||
| 		archive.extractall(path=destination) | ||||
| 	logger.debug("Update extracted") | ||||
| 	return destination | ||||
|  | ||||
| def move_bootstrap(extracted_path): | ||||
| 	working_path = os.path.abspath(os.path.join(extracted_path, '..')) | ||||
| 	if platform.system() == 'Darwin': | ||||
| 		extracted_path = os.path.join(extracted_path, 'Contents', 'Resources') | ||||
| 	downloaded_bootstrap = os.path.join(extracted_path, bootstrap_name()) | ||||
| 	new_bootstrap_path = os.path.join(working_path, bootstrap_name()) | ||||
| 	os.rename(downloaded_bootstrap, new_bootstrap_path) | ||||
| 	return new_bootstrap_path | ||||
|  | ||||
| def execute_bootstrap(bootstrap_path, source_path): | ||||
| 	arguments = r'"%s" "%s" "%s" "%s"' % (os.getpid(), source_path, paths.app_path(), paths.get_executable()) | ||||
| 	if platform.system() == 'Windows': | ||||
| 		import win32api | ||||
| 		win32api.ShellExecute(0, 'open', bootstrap_path, arguments, '', 5) | ||||
| 	else:   | ||||
| 		import subprocess | ||||
| 		make_executable(bootstrap_path) | ||||
| 		subprocess.Popen(['%s %s' % (bootstrap_path, arguments)], shell=True) | ||||
| 	logger.info("Bootstrap executed") | ||||
|  | ||||
| def bootstrap_name(): | ||||
| 	if platform.system() == 'Windows': return 'bootstrap.exe' | ||||
| 	if platform.system() == 'Darwin': return 'bootstrap-mac.sh' | ||||
| 	return 'bootstrap-lin.sh' | ||||
|  | ||||
| def make_executable(path): | ||||
| 	import stat | ||||
| 	st = os.stat(path) | ||||
| 	os.chmod(path, st.st_mode | stat.S_IEXEC) | ||||
|  | ||||
| def call_callback(callback, *args, **kwargs): | ||||
| # try: | ||||
| 	callback(*args, **kwargs) | ||||
| # except: | ||||
| #  logger.exception("Failed calling callback %r with args %r and kwargs %r" % (callback, args, kwargs)) | ||||
							
								
								
									
										14
									
								
								src/update/updater.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/update/updater.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| import application | ||||
| import platform | ||||
| import logging | ||||
| from requests.exceptions import ConnectionError | ||||
| from .import update | ||||
| from .wxUpdater import * | ||||
| logger = logging.getLogger("updater") | ||||
|  | ||||
| def do_update(endpoint=application.update_url): | ||||
| 	try: | ||||
| 		return update.perform_update(endpoint=endpoint, current_version=application.version, app_name=application.name, update_available_callback=available_update_dialog, progress_callback=progress_callback, update_complete_callback=update_finished) | ||||
| 	except ConnectionError: | ||||
| 		logger.exception("Update failed.") | ||||
							
								
								
									
										42
									
								
								src/update/utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/update/utils.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| def convert_bytes(n): | ||||
| 	K, M, G, T, P = 1 << 10, 1 << 20, 1 << 30, 1 << 40, 1 << 50 | ||||
| 	if   n >= P: | ||||
| 		return '%.2fPb' % (float(n) / T) | ||||
| 	elif   n >= T: | ||||
| 		return '%.2fTb' % (float(n) / T) | ||||
| 	elif n >= G: | ||||
| 		return '%.2fGb' % (float(n) / G) | ||||
| 	elif n >= M: | ||||
| 		return '%.2fMb' % (float(n) / M) | ||||
| 	elif n >= K: | ||||
| 		return '%.2fKb' % (float(n) / K) | ||||
| 	else: | ||||
| 		return '%d' % n | ||||
|  | ||||
| def seconds_to_string(seconds, precision=0): | ||||
| 	day = seconds // 86400 | ||||
| 	hour = seconds // 3600 | ||||
| 	min = (seconds // 60) % 60 | ||||
| 	sec = seconds - (hour * 3600) - (min * 60) | ||||
| 	sec_spec = "." + str(precision) + "f" | ||||
| 	sec_string = sec.__format__(sec_spec) | ||||
| 	string = "" | ||||
| 	if day == 1: | ||||
| 		string += _(u"%d day, ") % day | ||||
| 	elif day >= 2: | ||||
| 		string += _(u"%d days, ") % day | ||||
| 	if (hour == 1): | ||||
| 		string += _(u"%d hour, ") % hour | ||||
| 	elif (hour >= 2): | ||||
| 		string += _("%d hours, ") % hour | ||||
| 	if (min == 1): | ||||
| 		string += _(u"%d minute, ") % min | ||||
| 	elif (min >= 2): | ||||
| 		string += _(u"%d minutes, ") % min | ||||
| 	if sec >= 0 and sec <= 2: | ||||
| 		string += _(u"%s second") % sec_string | ||||
| 	else: | ||||
| 		string += _(u"%s seconds") % sec_string | ||||
| 	return string | ||||
							
								
								
									
										29
									
								
								src/update/wxUpdater.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/update/wxUpdater.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
| import wx | ||||
| import application | ||||
| import utils | ||||
|  | ||||
| progress_dialog = None | ||||
|  | ||||
| def available_update_dialog(version, description): | ||||
| 	dialog = wx.MessageDialog(None, _(u"There's a new %s version available. Would you like to download it now?\n\n %s version: %s\n\nChanges:\n%s") % (application.name, application.name, version, description), _(u"New version for %s") % application.name, style=wx.YES|wx.NO|wx.ICON_WARNING) | ||||
| 	if dialog.ShowModal() == wx.ID_YES: | ||||
| 		return True | ||||
| 	else: | ||||
| 		return False | ||||
|  | ||||
| def create_progress_dialog(): | ||||
| 	return wx.ProgressDialog(_(u"Download in Progress"), _(u"Downloading the new version..."),  parent=None, maximum=100) | ||||
|  | ||||
| def progress_callback(total_downloaded, total_size): | ||||
| 	global progress_dialog | ||||
| 	if progress_dialog == None: | ||||
| 		progress_dialog = create_progress_dialog() | ||||
| 		progress_dialog.Show() | ||||
| 	if total_downloaded == total_size: | ||||
| 		progress_dialog.Destroy() | ||||
| 	else: | ||||
| 		progress_dialog.Update((total_downloaded*100)/total_size, _(u"Updating... %s of %s") % (str(utils.convert_bytes(total_downloaded)), str(utils.convert_bytes(total_size)))) | ||||
|  | ||||
| def update_finished(): | ||||
| 	ms = wx.MessageDialog(None, _(u"The update has been downloaded and installed successfully. Press OK to continue."), _(u"Done!")).ShowModal() | ||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
		Reference in New Issue
	
	Block a user