From 0b845294fb10e442e42e3d56ff65b3de8ecf0d98 Mon Sep 17 00:00:00 2001 From: JonnyWong16 <9099342+JonnyWong16@users.noreply.github.com> Date: Tue, 22 Dec 2020 15:21:06 -0800 Subject: [PATCH] Add Tautulli Windows exe updater --- package/Tautulli-windows.spec | 42 ++++++++- package/Tautulli.nsi | 5 + package/TautulliUpdateTask.xml | Bin 0 -> 2988 bytes package/updater-windows.py | 165 +++++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 package/TautulliUpdateTask.xml create mode 100644 package/updater-windows.py diff --git a/package/Tautulli-windows.spec b/package/Tautulli-windows.spec index 9af5f9ba..4235a55c 100644 --- a/package/Tautulli-windows.spec +++ b/package/Tautulli-windows.spec @@ -3,6 +3,7 @@ import sys sys.modules['FixTk'] = None +excludes = ['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter'] block_cipher = None analysis = Analysis( @@ -12,13 +13,27 @@ analysis = Analysis( ('..\\data', 'data'), ('..\\CHANGELOG.md', '.'), ('..\\LICENSE', '.'), + ('..\\branch.txt', '.'), ('..\\version.txt', '.'), - ('..\\lib\\ipwhois\\data', 'data') + ('..\\lib\\ipwhois\\data', 'data'), + ('TautulliUpdateTask.xml', '.') ], - excludes=['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter'], + excludes=excludes, hiddenimports=['pkg_resources.py2_warn', 'cheroot.ssl', 'cheroot.ssl.builtin'], - cipher=block_cipher, + cipher=block_cipher ) +updater_analysis = Analysis( + ['updater-windows.py'], + pathex=['lib'], + excludes=excludes, + cipher=block_cipher +) + +MERGE( + (analysis, 'Tautulli', 'Tautulli'), + (updater_analysis, 'updater', 'updater') +) + pyz = PYZ( analysis.pure, analysis.zipped_data, @@ -39,3 +54,24 @@ coll = COLLECT( analysis.datas, name='Tautulli' ) + +updater_pyz = PYZ( + updater_analysis.pure, + updater_analysis.zipped_data, + cipher=block_cipher +) +updater_exe = EXE( + updater_pyz, + updater_analysis.scripts, + exclude_binaries=True, + name='updater', + console=False, + icon='..\\data\\interfaces\\default\\images\\logo-circle.ico' +) +coll = COLLECT( + updater_exe, + updater_analysis.binaries, + updater_analysis.zipfiles, + updater_analysis.datas, + name='updater' +) diff --git a/package/Tautulli.nsi b/package/Tautulli.nsi index aff53bd3..c49ffb84 100644 --- a/package/Tautulli.nsi +++ b/package/Tautulli.nsi @@ -123,6 +123,8 @@ SetOverwrite ifnewer SetOutPath "$INSTDIR" File /nonfatal /a /r "..\dist\${APP_NAME}\" +nsExec::Exec '$SYSDIR\SCHTASKS /Create /TN TautulliUpdateTask /XML "$INSTDIR\TautulliUpdateTask.xml" /F' + IfSilent 0 +2 ExecShell "" "$INSTDIR\${MAIN_APP_EXE}" $nolaunch SectionEnd @@ -208,6 +210,9 @@ RmDir "$SMPROGRAMS\${APP_NAME}" DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}" DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}" + +nsExec::Exec "$SYSDIR\SCHTASKS /Delete /TN TautulliUpdateTask /F" + SectionEnd ###################################################################### diff --git a/package/TautulliUpdateTask.xml b/package/TautulliUpdateTask.xml new file mode 100644 index 0000000000000000000000000000000000000000..72227220e1deb2d983a386cb842d20277002a35b GIT binary patch literal 2988 zcmbuB-%k@k5Xa})#Q)*KlRnrYF=9+DK|pDOO0a|%UMel6p}*2=DgN{7=R4c;_O7KC zOmp44otf{?P274vPRL+iTq z+?w{uUZM3Cojt4Db9-hrW?P`#K8U#Eh{5X+O?kSpDZjD3w<^CBUpM$1I8ueN$IJx( zDbF>U6^~|s?%X44Ge1XKwH+efwl}mr7vZaxaN$n8Jzb)Nq8HhFW=%n z@rgW|84&Zgb95 z3u3D4k*c;5>~l_QcItA~miTOuRT{|y=gT??+HJ3!eb^IMBXHuC&2v6M*5w%z%TIiC zz}M;qwp)1CnydDkXx%anozYcx84Ef3nXm2%Rzm25eeC&atyNR|zIs}%mi_QG3k<3z zVyhTg8p}DytgVvAyw&VG{K_(d$0khOCc0=Qh=+EY<0Tp0(;U&UG`3qL-Y}3-3O|74=vr@38Lz zr<>Un{e)d0&iYh~m{g5SSyNTa?FL?-f>4a1K}S$eTwnR2`m>c&#F^a@@9h%j>-1vp z0abl5CF@YbF?N~bb(%4*IzblS`n+mzoK;l5f{H;WgU-?A>^8F~#v_)pPF7~CEGt^7 zFShxqzn1wZ%8GW&m60)0Mp>s>#JnH}b$WIDU{%IirBTE<;@;GuJL>!icZMe8fVmSN zd(7AA<4xP;9&+scYBQUnpKuar9|=*tc-OI(=QBH1&BN8AN(pD)h?UTZd;kmKJiNz) zZe#0C)tY5lP-1GAw(_vd?3=SahGz(RJGB^iu9(Q%wF}bc*8NKH|7!vCN)ki)#{?)qHjrtJ@zU CBkc45 literal 0 HcmV?d00001 diff --git a/package/updater-windows.py b/package/updater-windows.py new file mode 100644 index 00000000..145a6fc9 --- /dev/null +++ b/package/updater-windows.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- + +# This file is part of Tautulli. +# +# Tautulli is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Tautulli is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Tautulli. If not, see . + +from logging import handlers +import logging +import os +import requests +import shutil +import subprocess +import tempfile + + +SCRIPT_PATH = os.path.dirname(os.path.abspath(__file__)) +CREATE_NO_WINDOW = 0x08000000 +REPO_URL = 'https://api.github.com/repos/Tautulli/Tautulli' + +LOGFILE = 'updater.log' +LOGPATH = os.path.join(SCRIPT_PATH, LOGFILE) +MAX_SIZE = 5000000 +MAX_FILES = 1 + +logger = logging.getLogger('updater') +logger.setLevel(logging.DEBUG) +file_formatter = logging.Formatter( + '%(asctime)s - %(levelname)-7s :: %(threadName)s : Tautulli Updater :: %(message)s', + '%Y-%m-%d %H:%M:%S') +file_handler = handlers.RotatingFileHandler( + LOGPATH, maxBytes=MAX_SIZE, backupCount=MAX_FILES, encoding='utf-8') +file_handler.setFormatter(file_formatter) +logger.addHandler(file_handler) + + +def kill_if_exists(process_name): + output = subprocess.check_output( + ['TASKLIST', '/FI', 'IMAGENAME eq {}'.format(process_name)], + creationflags=CREATE_NO_WINDOW).decode() + output = output.strip().split('\n')[-1] + if output.lower().startswith(process_name.lower()): + return subprocess.check_call( + ['TASKKILL', '/IM', process_name], + creationflags=CREATE_NO_WINDOW) + return 0 + + +def update_tautulli(): + logger.info('Starting Tautulli update check') + + with open(os.path.join(SCRIPT_PATH, 'branch.txt'), 'r') as f: + branch = f.read() + logger.info('Branch: %s', branch) + + with open(os.path.join(SCRIPT_PATH, 'version.txt'), 'r') as f: + current_version = f.read() + logger.info('Current version: %s', current_version) + + logger.info('Retrieving latest version from GitHub') + try: + response = requests.get('{}/commits/{}'.format(REPO_URL, branch)) + response.raise_for_status() + except Exception as e: + logger.error('Request error: %s', e) + return 2 + + try: + commits = response.json() + latest_version = commits['sha'] + except Exception as e: + logger.error('Failed to retrieve latest version: %s', e) + return 1 + logger.info('Latest version: %s', latest_version) + + if current_version == latest_version: + logger.info('Tautulli is already up to date') + return 0 + + logger.info('Comparing version on GitHub') + try: + response = requests.get('{}/compare/{}...{}'.format(REPO_URL, latest_version, current_version)) + response.raise_for_status() + except Exception as e: + logger.error('Request error: %s', e) + return 2 + + try: + compare = response.json() + commits_behind = compare['behind_by'] + except Exception as e: + logger.error('Failed to compare commits: %s', e) + return 1 + logger.info('Commits behind: %s', commits_behind) + + if commits_behind > 0: + logger.info('Retrieving releases on GitHub') + try: + response = requests.get('{}/releases'.format(REPO_URL)) + response.raise_for_status() + except Exception as e: + logger.error('Request error: %s', e) + return 2 + + try: + releases = response.json() + + if branch == 'master': + release = next((r for r in releases if not r['prerelease']), releases[0]) + else: + release = next((r for r in releases), releases[0]) + + version = release['tag_name'] + asset = next((a for a in release['assets'] if a['content_type'] == 'application/vnd.microsoft.portable-executable'), None) + download_url = asset['browser_download_url'] + download_file = asset['name'] + except Exception as e: + logger.error('Failed to retrieve releases: %s', e) + return 1 + logger.info('Release: %s', version) + + file_path = os.path.join(tempfile.gettempdir(), download_file) + logger.info('Downloading installer to temporary directory: %s', file_path) + with requests.get(download_url, stream=True) as r: + with open(file_path, 'wb') as f: + shutil.copyfileobj(r.raw, f) + + logger.info('Stopping Tautulli') + try: + killed = kill_if_exists('Tautulli.exe') + except Exception as e: + logger.error('Failed to stop Tautulli: %s', e) + return 1 + + if killed != 0: + logger.error('Failed to stop Tautulli') + return 1 + + logger.info('Running %s', download_file) + try: + subprocess.call( + [file_path, '/S'], + creationflags=CREATE_NO_WINDOW) + except Exception as e: + logger.exception('Failed to install Tautulli: %s', e) + return -1 + + logger.info('Tautulli updated to %s', version) + + return 0 + + +if __name__ == '__main__': + status = update_tautulli() + logger.debug('Update function returned %s', status)