diff --git a/.dockerignore b/.dockerignore index 742c4925..d525a859 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,7 @@ .gitignore contrib init-scripts +package pylintrc *.md !CHANGELOG*.md diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 8834af3f..5b04c4bd 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -2,27 +2,189 @@ name: Publish Release on: push: tags: [v*] + jobs: - build: - runs-on: ubuntu-latest + build-windows: + if: ${{ !endsWith(github.ref, '-beta') }} + runs-on: windows-latest steps: - name: Checkout Code - uses: actions/checkout@master - - name: Get Release Version - run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF#refs/tags/} + uses: actions/checkout@v2.1.0 + + - name: Set Release Version + id: get_version + shell: bash + run: | + echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v} + echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/} + echo $GITHUB_SHA > version.txt + + - name: Set Up Python + uses: actions/setup-python@v1.2.0 + with: + python-version: 3.8 + + - name: Cache Dependencies + id: cache_dependencies + uses: actions/cache@v1 + with: + path: ~\AppData\Local\pip\Cache + key: ${{ runner.os }}-pip-${{ steps.get_version.outputs.VERSION }} + restore-keys: ${{ runner.os }}-pip- + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install pyopenssl pycryptodomex pywin32 pyinstaller + + - name: Build Package + run: | + pyinstaller -y ./package/Tautulli-windows.spec + + - name: Create Installer + uses: joncloud/makensis-action@v1 + with: + script-file: ./package/Tautulli.nsi + arguments: /DVERSION=${{ steps.get_version.outputs.VERSION }}.0 /DINSTALLER_NAME=..\Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}.exe + includeMorePlugins: package/nsis-plugins + + - name: Upload Installer + uses: actions/upload-artifact@v1 + with: + name: Tautulli-windows-installer + path: Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}.exe + + - name: Post Status to Discord + uses: sarisia/actions-status-discord@v1 + if: always() + with: + webhook: ${{ secrets.DISCORD_WEBHOOK }} + status: ${{ job.status }} + job: Build Windows Installer + nofail: true + + build-macos: + if: ${{ !endsWith(github.ref, '-beta') }} + runs-on: macos-latest + steps: + - name: Checkout Code + uses: actions/checkout@v2.1.0 + + - name: Set Release Version + id: get_version + shell: bash + run: | + echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v} + echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/} + echo $GITHUB_SHA > version.txt + echo ::set-env name=VERSION::${GITHUB_REF#refs/tags/v} + + - name: Set Up Python + uses: actions/setup-python@v1.2.0 + with: + python-version: 3.8 + + - name: Cache Dependencies + id: cache_dependencies + uses: actions/cache@v1 + with: + path: ~/Library/Caches/pip + key: ${{ runner.os }}-pip-${{ steps.get_version.outputs.VERSION }} + restore-keys: ${{ runner.os }}-pip- + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install pyopenssl pycryptodomex pyinstaller + + - name: Build Package + run: | + pyinstaller -y ./package/Tautulli-macos.spec + + - name: Create Installer + run: | + sudo pkgbuild --install-location /Applications --version ${{ steps.get_version.outputs.VERSION }} --component ./dist/Tautulli.app --scripts ./package/macos-scripts Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}.pkg + + - name: Upload Installer + uses: actions/upload-artifact@v1 + with: + name: Tautulli-macos-package + path: Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}.pkg + + - name: Post Status to Discord + uses: sarisia/actions-status-discord@v1 + if: always() + with: + webhook: ${{ secrets.DISCORD_WEBHOOK }} + status: ${{ job.status }} + job: Build MacOS Package + nofail: true + + release: + needs: [build-windows, build-macos] + if: always() + runs-on: ubuntu-latest + steps: + - name: Get Build Job Status + uses: technote-space/workflow-conclusion-action@v1 + + - name: Checkout Code + uses: actions/checkout@v2.1.0 + + - name: Set Release Version + id: get_version + run: | + echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/} + + - name: Download Windows Installer + if: env.WORKFLOW_CONCLUSION == 'success' + uses: actions/download-artifact@v1 + with: + name: Tautulli-windows-installer + + - name: Download MacOS Package + if: env.WORKFLOW_CONCLUSION == 'success' + uses: actions/download-artifact@v1 + with: + name: Tautulli-macos-package + - name: Get Changelog - run: echo ::set-env name=CHANGELOG::"$( sed -n '/^## /{p; :loop n; p; /^## /q; b loop}' CHANGELOG.md | sed '$d' | sed '$d' | sed '$d' | sed ':a;N;$!ba;s/\n/%0A/g' )" + id: get_changelog + run: echo ::set-output name=CHANGELOG::"$( sed -n '/^## /{p; :loop n; p; /^## /q; b loop}' CHANGELOG.md | sed '$d' | sed '$d' | sed '$d' | sed ':a;N;$!ba;s/\n/%0A/g' )" + - name: Create Release id: create_release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: ${{ env.RELEASE_VERSION }} - release_name: Tautulli ${{ env.RELEASE_VERSION }} + tag_name: ${{ steps.get_version.outputs.RELEASE_VERSION }} + release_name: Tautulli ${{ steps.get_version.outputs.RELEASE_VERSION }} body: | ## Changelog - ##${{ env.CHANGELOG }} + ##${{ steps.get_changelog.outputs.CHANGELOG }} draft: false - prerelease: ${{ endsWith(env.RELEASE_VERSION, '-beta') }} + prerelease: ${{ endsWith(steps.get_version.outputs.RELEASE_VERSION, '-beta') }} + + - name: Upload Windows Installer + if: env.WORKFLOW_CONCLUSION == 'success' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: Tautulli-windows-installer/Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}.exe + asset_name: Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}.exe + asset_content_type: application/vnd.microsoft.portable-executable + + - name: Upload MacOS Package + if: env.WORKFLOW_CONCLUSION == 'success' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: Tautulli-macos-package/Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}.pkg + asset_name: Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}.pkg + asset_content_type: application/vnd.apple.installer+xml diff --git a/.gitignore b/.gitignore index ded1ace0..6bb5c9b5 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ backups/* cache/* newsletters/* *.mmdb +version.txt +branch.txt # HTTPS Cert/Key # ################## @@ -74,3 +76,7 @@ _ReSharper*/ /logs .project .pydevproject + +#Ignore files generated by pyinstaller +/build +/dist diff --git a/data/interfaces/default/images/logo-circle.icns b/data/interfaces/default/images/logo-circle.icns new file mode 100644 index 00000000..3ced71f4 Binary files /dev/null and b/data/interfaces/default/images/logo-circle.icns differ diff --git a/data/interfaces/default/images/logo-circle.ico b/data/interfaces/default/images/logo-circle.ico new file mode 100644 index 00000000..45c5d3ff Binary files /dev/null and b/data/interfaces/default/images/logo-circle.ico differ diff --git a/package/Tautulli-macos.spec b/package/Tautulli-macos.spec new file mode 100644 index 00000000..c1936f4e --- /dev/null +++ b/package/Tautulli-macos.spec @@ -0,0 +1,49 @@ +# -*- mode: python ; coding: utf-8 -*- + +import sys +sys.modules['FixTk'] = None + +import os +VERSION = os.getenv('VERSION', '0.0.0') + +block_cipher = None + +analysis = Analysis( + ['../Tautulli.py'], + pathex=['lib'], + datas=[ + ('../data', 'data'), + ('../CHANGELOG.md', '.'), + ('../LICENSE', '.'), + ('../version.txt', '.') + ], + excludes=['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter'], + cipher=block_cipher +) +pyz = PYZ( + analysis.pure, + analysis.zipped_data, + cipher=block_cipher +) +exe = EXE( + pyz, + analysis.scripts, + exclude_binaries=True, + name='Tautulli', + console=False, + icon='../data/interfaces/default/images/logo-circle.icns' +) +coll = COLLECT( + exe, + analysis.binaries, + analysis.zipfiles, + analysis.datas, + name='Tautulli' +) +app = BUNDLE( + coll, + name='Tautulli.app', + icon='../data/interfaces/default/images/logo-circle.icns', + bundle_identifier='com.Tautulli.Tautulli', + version=VERSION +) diff --git a/package/Tautulli-windows.spec b/package/Tautulli-windows.spec new file mode 100644 index 00000000..84e78cda --- /dev/null +++ b/package/Tautulli-windows.spec @@ -0,0 +1,38 @@ +# -*- mode: python ; coding: utf-8 -*- + +import sys +sys.modules['FixTk'] = None + +block_cipher = None + +analysis = Analysis( + ['..\\Tautulli.py'], + pathex=['lib'], + datas=[('..\\data', 'data'), + ('..\\CHANGELOG.md', '.'), + ('..\\LICENSE', '.'), + ('..\\version.txt', '.') + ], + excludes=['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter'], + cipher=block_cipher, +) +pyz = PYZ( + analysis.pure, + analysis.zipped_data, + cipher=block_cipher +) +exe = EXE( + pyz, + analysis.scripts, + exclude_binaries=True, + name='Tautulli', + console=False, + icon='..\\data\\interfaces\\default\\images\\logo-circle.ico' +) +coll = COLLECT( + exe, + analysis.binaries, + analysis.zipfiles, + analysis.datas, + name='Tautulli' +) diff --git a/package/Tautulli.nsi b/package/Tautulli.nsi new file mode 100644 index 00000000..ff6e59f7 --- /dev/null +++ b/package/Tautulli.nsi @@ -0,0 +1,244 @@ +############################################################################################ +# NSIS Installation Script created by NSIS Quick Setup Script Generator v1.09.18 +# Entirely Edited with NullSoft Scriptable Installation System +# by Vlasis K. Barkas aka Red Wine red_wine@freemail.gr Sep 2006 +############################################################################################ + +!define APP_NAME "Tautulli" +!define COMP_NAME "Tautulli" +!define WEB_SITE "https://tautulli.com" +!define COPYRIGHT "Tautulli © 2020" +!define DESCRIPTION "Monitor your Plex Media Server" +!define APP_ICON "..\dist\Tautulli\data\interfaces\default\images\logo-circle.ico" +!define LICENSE_TXT "..\dist\Tautulli\LICENSE" +!define MAIN_APP_EXE "Tautulli.exe" +!define INSTALL_TYPE "SetShellVarContext current" +!define REG_ROOT "HKCU" +!define REG_APP_PATH "Software\Microsoft\Windows\CurrentVersion\App Paths\${MAIN_APP_EXE}" +!define UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}" + +!define REG_START_MENU "Start Menu Folder" + +var SM_Folder + +###################################################################### + +VIProductVersion "${VERSION}" +VIAddVersionKey "ProductName" "${APP_NAME}" +VIAddVersionKey "CompanyName" "${COMP_NAME}" +VIAddVersionKey "LegalCopyright" "${COPYRIGHT}" +VIAddVersionKey "FileDescription" "${DESCRIPTION}" +VIAddVersionKey "FileVersion" "${VERSION}" + +###################################################################### + +SetCompressor ZLIB +Name "${APP_NAME}" +Caption "${APP_NAME}" +OutFile "${INSTALLER_NAME}" +BrandingText "${APP_NAME}" +XPStyle on +InstallDirRegKey "${REG_ROOT}" "${REG_APP_PATH}" "" +InstallDir "$PROGRAMFILES\${APP_NAME}" + +###################################################################### + +!define nsProcess::FindProcess `!insertmacro nsProcess::FindProcess` + +!macro nsProcess::FindProcess _FILE _ERR + nsProcess::_FindProcess /NOUNLOAD `${_FILE}` + Pop ${_ERR} +!macroend + + +!define nsProcess::KillProcess `!insertmacro nsProcess::KillProcess` + +!macro nsProcess::KillProcess _FILE _ERR + nsProcess::_KillProcess /NOUNLOAD `${_FILE}` + Pop ${_ERR} +!macroend + +!define nsProcess::CloseProcess `!insertmacro nsProcess::CloseProcess` + +!macro nsProcess::CloseProcess _FILE _ERR + nsProcess::_CloseProcess /NOUNLOAD `${_FILE}` + Pop ${_ERR} +!macroend + + +!define nsProcess::Unload `!insertmacro nsProcess::Unload` + +!macro nsProcess::Unload + nsProcess::_Unload +!macroend + +###################################################################### + +!include Sections.nsh + +Var /GLOBAL nolaunch + +!include "MUI.nsh" + +!define MUI_ABORTWARNING +!define MUI_UNABORTWARNING + +!define MUI_ICON "${APP_ICON}" + +!insertmacro MUI_PAGE_WELCOME + +!ifdef LICENSE_TXT +!insertmacro MUI_PAGE_LICENSE "${LICENSE_TXT}" +!endif + +!ifdef REG_START_MENU +!define MUI_STARTMENUPAGE_DEFAULTFOLDER "${APP_NAME}" +!define MUI_STARTMENUPAGE_REGISTRY_ROOT "${REG_ROOT}" +!define MUI_STARTMENUPAGE_REGISTRY_KEY "${UNINSTALL_PATH}" +!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "${REG_START_MENU}" +!insertmacro MUI_PAGE_STARTMENU Application $SM_Folder +!endif + +!insertmacro MUI_PAGE_INSTFILES + +!define MUI_FINISHPAGE_RUN "$INSTDIR\${MAIN_APP_EXE}" +!define MUI_FINISHPAGE_RUN_PARAMETERS $nolaunch +!insertmacro MUI_PAGE_FINISH + +!insertmacro MUI_UNPAGE_CONFIRM + +!insertmacro MUI_UNPAGE_INSTFILES + +!insertmacro MUI_UNPAGE_FINISH + +!insertmacro MUI_LANGUAGE "English" + +###################################################################### + +Section -MainProgram +${INSTALL_TYPE} +SetOverwrite ifnewer +SetOutPath "$INSTDIR" +File /nonfatal /a /r "..\dist\${APP_NAME}\" + +IfSilent 0 +2 +ExecShell "" "$INSTDIR\${MAIN_APP_EXE}" $nolaunch +SectionEnd + +###################################################################### + +Section -Icons_Reg +SetOutPath "$INSTDIR" +WriteUninstaller "$INSTDIR\uninstall.exe" + +!ifdef REG_START_MENU +!insertmacro MUI_STARTMENU_WRITE_BEGIN Application +CreateDirectory "$SMPROGRAMS\$SM_Folder" +CreateShortCut "$SMPROGRAMS\$SM_Folder\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" +CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" +CreateShortCut "$SMPROGRAMS\$SM_Folder\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe" + +!ifdef WEB_SITE +WriteIniStr "$INSTDIR\${APP_NAME} website.url" "InternetShortcut" "URL" "${WEB_SITE}" +CreateShortCut "$SMPROGRAMS\$SM_Folder\${APP_NAME} Website.lnk" "$INSTDIR\${APP_NAME} website.url" +!endif +!insertmacro MUI_STARTMENU_WRITE_END +!endif + +!ifndef REG_START_MENU +CreateDirectory "$SMPROGRAMS\${APP_NAME}" +CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" +CreateShortCut "$DESKTOP\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" +CreateShortCut "$SMPROGRAMS\${APP_NAME}\Uninstall ${APP_NAME}.lnk" "$INSTDIR\uninstall.exe" + +!ifdef WEB_SITE +WriteIniStr "$INSTDIR\${APP_NAME} website.url" "InternetShortcut" "URL" "${WEB_SITE}" +CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME} Website.lnk" "$INSTDIR\${APP_NAME} website.url" +!endif +!endif + +WriteRegStr ${REG_ROOT} "${REG_APP_PATH}" "" "$INSTDIR\${MAIN_APP_EXE}" +WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayName" "${APP_NAME}" +WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "UninstallString" "$INSTDIR\uninstall.exe" +WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayIcon" "$INSTDIR\${MAIN_APP_EXE}" +WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "DisplayVersion" "${VERSION}" +WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "Publisher" "${COMP_NAME}" + +!ifdef WEB_SITE +WriteRegStr ${REG_ROOT} "${UNINSTALL_PATH}" "URLInfoAbout" "${WEB_SITE}" +!endif +SectionEnd + +###################################################################### + +Section Uninstall +${INSTALL_TYPE} +Delete "$INSTDIR\uninstall.exe" +!ifdef WEB_SITE +Delete "$INSTDIR\${APP_NAME} website.url" +!endif + +RmDir /r "$INSTDIR" +RmDir "$INSTDIR" + +!ifdef REG_START_MENU +!insertmacro MUI_STARTMENU_GETFOLDER "Application" $SM_Folder +Delete "$SMPROGRAMS\$SM_Folder\${APP_NAME}.lnk" +Delete "$SMPROGRAMS\$SM_Folder\Uninstall ${APP_NAME}.lnk" +!ifdef WEB_SITE +Delete "$SMPROGRAMS\$SM_Folder\${APP_NAME} Website.lnk" +!endif +Delete "$DESKTOP\${APP_NAME}.lnk" + +RmDir "$SMPROGRAMS\$SM_Folder" +!endif + +!ifndef REG_START_MENU +Delete "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" +Delete "$SMPROGRAMS\${APP_NAME}\Uninstall ${APP_NAME}.lnk" +!ifdef WEB_SITE +Delete "$SMPROGRAMS\${APP_NAME}\${APP_NAME} Website.lnk" +!endif +Delete "$DESKTOP\${APP_NAME}.lnk" + +RmDir "$SMPROGRAMS\${APP_NAME}" +!endif + +DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}" +DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}" +SectionEnd + +###################################################################### + +Function .onInit + IfSilent 0 +2 + StrCpy $nolaunch "--nolaunch" + + IfSilent +5 0 + Loop: + ${nsProcess::FindProcess} ${MAIN_APP_EXE} $R0 + StrCmp $R0 0 0 NoAbort + MessageBox MB_ABORTRETRYIGNORE|MB_ICONEXCLAMATION "${APP_NAME} is still running. Please shutdown ${APP_NAME} before continuing.$\r$\n$\r$\nIgnore will attempt to shutdown ${APP_NAME} for you." IDABORT End IDRETRY Loop + ${nsProcess::CloseProcess} ${MAIN_APP_EXE} $R0 + StrCmp $R0 0 0 NoAbort + Goto Loop + End: + Abort + NoAbort: +FunctionEnd + +Function un.onInit + IfSilent +5 0 + Loop: + ${nsProcess::FindProcess} ${MAIN_APP_EXE} $R0 + StrCmp $R0 0 0 NoAbort + MessageBox MB_ABORTRETRYIGNORE|MB_ICONEXCLAMATION "${APP_NAME} is still running. Please shutdown ${APP_NAME} before continuing.$\r$\n$\r$\nIgnore will attempt to shutdown ${APP_NAME} for you." IDABORT End IDRETRY Loop + ${nsProcess::CloseProcess} ${MAIN_APP_EXE} $R0 + StrCmp $R0 0 0 NoAbort + Goto Loop + End: + Abort + NoAbort: +FunctionEnd + +###################################################################### diff --git a/package/macos-scripts/preinstall b/package/macos-scripts/preinstall new file mode 100755 index 00000000..75ad6a71 --- /dev/null +++ b/package/macos-scripts/preinstall @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +function check_tautulli_running () { + if pgrep -x "Tautulli" > /dev/null + then + dialogText=`osascript -e 'set dialogText to button returned of (display dialog "Tautulli is still running. Please shutdown Tautulli before continuing." buttons {"Retry", "Shutdown Tautulli", "Abort"})'`; + + if [[ $dialogText == 'Retry' ]] + then + check_tautulli_running; + elif [[ $dialogText == 'Shutdown Tautulli' ]] + then + pkill Tautulli + while pgrep Tautulli > /dev/null; do sleep 1; done + check_tautulli_running; + elif [[ $dialogText == 'Abort' ]] + then + exit 1; + fi + else + exit 0; + fi +} + +check_tautulli_running; diff --git a/package/nsis-plugins/nsProcess.dll b/package/nsis-plugins/nsProcess.dll new file mode 100644 index 00000000..4ce01210 Binary files /dev/null and b/package/nsis-plugins/nsProcess.dll differ