Added updater at the start of the program

This commit is contained in:
Manuel Cortez 2018-02-26 09:52:33 -06:00
parent eab1627925
commit 0a2307d56f
9 changed files with 204 additions and 2 deletions

View File

@ -6,7 +6,7 @@ authorEmail = "manuel@manuelcortez.net"
copyright = "Copyright (C) 2018, Manuel Cortez" 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." 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" 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. # The short name will be used for detecting translation files. See languageHandler for more details.
short_name = "musicdl" short_name = "musicdl"
translators = [] translators = []

View File

@ -7,6 +7,7 @@ import utils
from pubsub import pub from pubsub import pub
from wxUI import mainWindow, menus from wxUI import mainWindow, menus
from extractors import zaycev, youtube from extractors import zaycev, youtube
from update import updater
from . import player from . import player
log = logging.getLogger("controller.main") log = logging.getLogger("controller.main")
@ -18,7 +19,6 @@ class Controller(object):
log.debug("Starting main controller...") log.debug("Starting main controller...")
# Setting up the player object # Setting up the player object
player.setup() player.setup()
# Instantiate the only available extractor for now.
# Get main window # Get main window
self.window = mainWindow.mainWindow() self.window = mainWindow.mainWindow()
log.debug("Main window created") log.debug("Main window created")
@ -31,6 +31,7 @@ class Controller(object):
self.timer.Start(75) self.timer.Start(75)
self.window.vol_slider.SetValue(player.player.volume) self.window.vol_slider.SetValue(player.player.volume)
# Shows window. # Shows window.
utils.call_threaded(updater.do_update)
self.window.Show() self.window.Show()
def get_status_info(self): def get_status_info(self):

0
src/update/__init__.py Normal file
View File

116
src/update/update.py Normal file
View 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
View 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
View 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
View 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.