Compare commits

...

77 Commits

Author SHA1 Message Date
JonnyWong16
97f80adf0b v2.6.5 2021-01-09 14:24:27 -08:00
JonnyWong16
2fc7b08909 Add misfire grace time to scheduled tasks 2021-01-09 14:15:32 -08:00
JonnyWong16
defceed696 Fix homepage server status when sections are disabled 2021-01-06 17:24:53 -08:00
JonnyWong16
249533ac51 Migrate snap user data 2021-01-05 17:56:53 -08:00
JonnyWong16
12aee8762e Allow snap package to access home directory 2021-01-05 10:19:41 -08:00
JonnyWong16
d9325b7adf Add Crypto link to funding 2020-12-28 14:16:16 -08:00
JonnyWong16
4975cad4fa Keep news open for longer 2020-12-27 14:53:33 -08:00
JonnyWong16
33fc079318 More logger blacklist improvements 2020-12-27 14:52:18 -08:00
JonnyWong16
b3b2752554 Fix regex masking in logger args 2020-12-27 13:15:06 -08:00
JonnyWong16
505cf25ca3 Add back crypto donation 2020-12-25 14:01:04 -08:00
JonnyWong16
9747e3ba98 Add pull request workflow 2020-12-24 18:23:06 -08:00
JonnyWong16
729191722a Fix typo 2020-12-24 17:00:39 -08:00
JonnyWong16
ff2cf73f23 Update .gitignore 2020-12-24 16:43:00 -08:00
JonnyWong16
9c4d97c0f8 Update installer workflow 2020-12-24 16:43:00 -08:00
JonnyWong16
be911e7700 Move nsis plugin folder 2020-12-24 16:42:37 -08:00
JonnyWong16
00629c0983 Use updater to modify Task Scheduler XML 2020-12-24 16:41:28 -08:00
JonnyWong16
52ebc9a908 Move Tautulli to Program Files x64 2020-12-24 16:41:21 -08:00
JonnyWong16
a029d6a931 Restart Tautulli process if Windows installer failed 2020-12-22 19:26:53 -08:00
JonnyWong16
7641e3b081 Add logger function to Windows updater 2020-12-22 19:18:45 -08:00
JonnyWong16
b54210480f Don't shutdown for Windows installer update 2020-12-22 19:09:27 -08:00
JonnyWong16
0d9c1c640e Update Windows updater 2020-12-22 19:09:06 -08:00
JonnyWong16
7f84353c69 Disable console window when running task scheduler 2020-12-22 16:24:21 -08:00
JonnyWong16
c319a4a5cc Don't auto restart when updating Windows install 2020-12-22 16:22:45 -08:00
JonnyWong16
60f13df992 Add auto update for Windows exe install 2020-12-22 15:59:19 -08:00
JonnyWong16
dea51e32a5 Strip branch and version when reading text file in Windows updater 2020-12-22 15:46:11 -08:00
JonnyWong16
7019f5618b Update Windows powershell command to move updater 2020-12-22 15:36:21 -08:00
JonnyWong16
9106c068ac Update Windows installer workflow 2020-12-22 15:26:37 -08:00
JonnyWong16
0b845294fb Add Tautulli Windows exe updater 2020-12-22 15:21:06 -08:00
JonnyWong16
7e850dd88d Fix typo in apscheduler in package requirements file 2020-12-22 12:33:52 -08:00
JonnyWong16
877bf7060e Missing = sign in package requirements file 2020-12-22 12:31:22 -08:00
JonnyWong16
9326d03a57 Update PyInstaller to 4.1
* APScheduler needs to be installed from PyPI due to the way it retrieves it's own version number using pkg_resources.get_distribution
2020-12-22 12:29:43 -08:00
JonnyWong16
4787f42d2e Add args to Windows and MacOS auto startup 2020-12-21 13:39:53 -08:00
JonnyWong16
56a9ccd818 Make Windows registry key unique to allow more than one instance 2020-12-21 13:39:29 -08:00
JonnyWong16
1019fecc9e v2.6.4 2020-12-20 08:59:41 -08:00
JonnyWong16
1855f93c1c Revert Snap data folder
* Fixes Tautulli/Tautulli-Issues#294
2020-12-20 08:58:02 -08:00
JonnyWong16
52e6a44aa4 Put Discord notification after release 2020-12-19 16:18:29 -08:00
JonnyWong16
0b77808af6 Fix release changelog 2020-12-19 16:18:13 -08:00
JonnyWong16
9233ed5c53 Revert "Also publish snap to beta for full release"
This reverts commit 366823cee9.
2020-12-19 15:56:09 -08:00
JonnyWong16
ee68c0f622 Fix release installer path 2020-12-19 15:42:23 -08:00
JonnyWong16
366823cee9 Also publish snap to beta for full release 2020-12-19 15:30:02 -08:00
JonnyWong16
40e1eb9a49 Revert "Build snap based on branch instead of tag"
This reverts commit 2e5dd05a6c.
2020-12-19 15:22:30 -08:00
JonnyWong16
1af419a860 v2.6.3 2020-12-19 15:09:56 -08:00
JonnyWong16
397f18c435 Disable updater if using Python 2 2020-12-19 14:59:05 -08:00
JonnyWong16
2e5dd05a6c Build snap based on branch instead of tag 2020-12-19 12:11:03 -08:00
JonnyWong16
a9fb8ddfb8 Separate Discord notification jobs 2020-12-18 14:31:15 -08:00
JonnyWong16
562c726787 Merge pull request #1385 from elpollodiablo/master
Change http_handler to use requests instead of urllib3
2020-12-18 14:11:53 -08:00
JonnyWong16
5f82c1dc17 Add python3-pycryptodome to snap package 2020-12-17 20:48:23 -08:00
JonnyWong16
222800bdb6 Update README with Snap badges 2020-12-17 20:27:41 -08:00
JonnyWong16
5dd3636571 Fix typo in Snap architecture 2020-12-17 19:02:00 -08:00
JonnyWong16
2296a9fbb3 Merge pull request #1375 from capruro/master
Snap Package integration
2020-12-17 18:53:25 -08:00
JonnyWong16
63b5a7c036 Update workflows 2020-12-17 18:46:02 -08:00
JonnyWong16
b74ca2670e Update Publish Snap workflow 2020-12-17 18:44:29 -08:00
JonnyWong16
393f4e0e58 Add multi-architecture build to Publish Snap workflow 2020-12-17 18:38:04 -08:00
JonnyWong16
3a9ca29e99 Add pull request to workflows 2020-12-17 18:28:50 -08:00
JonnyWong16
32995fef24 Update snapcraft.yml 2020-12-17 17:47:28 -08:00
JonnyWong16
a73c99fc64 Disable updated for Snap package 2020-12-17 17:47:28 -08:00
JonnyWong16
a5834470ba Update Publish Docker workflow 2020-12-17 17:41:12 -08:00
JonnyWong16
da3bc127dc Add TAUTULLI_SNAP environment variable 2020-12-17 12:48:12 -08:00
JonnyWong16
0dddc4d58f Fix typo in Publish Snap workflow 2020-12-16 17:56:39 -08:00
JonnyWong16
a4d5d9157b Add snap to .dockerignore 2020-12-16 17:49:17 -08:00
JonnyWong16
c70d5d4398 Add Publish Snap GitHub workflow 2020-12-16 17:49:17 -08:00
JonnyWong16
7c08b07ef5 Update snapcraft.yaml 2020-12-16 17:49:17 -08:00
capruro
e426b5dd35 add PyOpenSSL package 2020-12-16 17:46:50 -08:00
capruro
2fdf619582 Delete .editorconfig
NOT required to be implemented :)
2020-12-16 17:46:50 -08:00
Marcello Franco
d9eed14b7a Initial commit 2020-12-16 17:46:40 -08:00
JonnyWong16
8230ffb8a4 Update readme installer build badges 2020-12-16 00:01:00 -08:00
JonnyWong16
7098930b19 Update build installer workflow with matrix 2020-12-15 23:54:30 -08:00
JonnyWong16
56244245a4 Fix export accessible and exists for media info level 9 2020-12-15 22:49:51 -08:00
JonnyWong16
dd2f12fa8e Fix typo in notifier error log message 2020-12-15 22:49:51 -08:00
JonnyWong16
9598247a0d Merge pull request #1384 from krowvin/patch-1
Update reference link to CherryPy auth tool
2020-12-15 22:48:46 -08:00
JonnyWong16
230ee90b1c Remove link to web.archive.org for CherryPy auth tool 2020-12-15 22:47:11 -08:00
JonnyWong16
e705bedc91 Clean up http_handler using requests
* Restore ThreadPool for multiple requests
* Use a requests.Session()
* Update requests.exceptions
2020-12-15 22:38:43 -08:00
JonnyWong16
b5ebe7590c Create pull_request_template.md 2020-12-12 12:16:46 -08:00
Philip Poten
6d0831ceaa replace urllib3 with requests, remove unused session logic 2020-12-12 16:03:18 +01:00
JonnyWong16
19e00ee2f2 Set template icon for macOS menu bar 2020-12-06 11:13:21 -08:00
JonnyWong16
80723d224e Add username to masked session info for guests 2020-12-05 18:44:06 -08:00
krowvin
ff1bd0a4b8 Update webauth.py
Link is broken. Added two alternate links to the same information.
2020-11-12 17:03:04 -06:00
40 changed files with 973 additions and 222 deletions

View File

@@ -5,6 +5,7 @@ contrib
init-scripts
package
pylintrc
snap
*.md
!CHANGELOG*.md
start.bat

2
.github/FUNDING.yml vendored
View File

@@ -1,3 +1,3 @@
github: JonnyWong16
patreon: Tautulli
custom: ["https://bit.ly/2InPp15"]
custom: ["https://bit.ly/2InPp15", "https://bit.ly/2WTq83m"]

20
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,20 @@
## Description
Please include a summary of the change and which issue is fixed.
Fixes Tautulli/Tautulli-Issues#(issue)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have added or updated the docstring for new or existing methods

View File

@@ -1,10 +1,14 @@
name: Publish Docker
on:
push:
branches: [master, beta, nightly, python3]
branches: [master, beta, nightly]
tags: [v*]
pull_request: ~
jobs:
build:
build-docker:
name: Build Docker Image
runs-on: ubuntu-latest
steps:
- name: Checkout Code
@@ -20,7 +24,9 @@ jobs:
else
echo ::set-output name=tag::${GITHUB_REF#refs/heads/}
fi
if [[ $GITHUB_REF == refs/tags/* ]]; then
if [[ $GITHUB_REF == refs/tags/*-beta ]]; then
echo ::set-output name=branch::beta
elif [[ $GITHUB_REF == refs/tags/* ]]; then
echo ::set-output name=branch::master
else
echo ::set-output name=branch::${GITHUB_REF#refs/heads/}
@@ -30,14 +36,12 @@ jobs:
echo ::set-output name=docker_platforms::linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6
echo ::set-output name=docker_image::${{ secrets.DOCKER_REPO }}/tautulli
- name: Set up QEMU
- name: Set Up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
id: buildx
with:
version: latest
@@ -51,14 +55,14 @@ jobs:
- name: Login to DockerHub
uses: docker/login-action@v1
if: success()
if: success() && github.event_name != 'pull_request'
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
if: success()
if: success() && github.event_name != 'pull_request'
with:
registry: ghcr.io
username: ${{ secrets.DOCKER_USERNAME }}
@@ -70,7 +74,7 @@ jobs:
with:
context: .
file: ./Dockerfile
push: true
push: ${{ github.event_name != 'pull_request' }}
platforms: ${{ steps.prepare.outputs.docker_platforms }}
build-args: |
TAG=${{ steps.prepare.outputs.tag }}
@@ -83,11 +87,29 @@ jobs:
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache
discord:
name: Discord Notification
needs: build-docker
if: always() && github.event_name != 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Get Build Job Status
uses: technote-space/workflow-conclusion-action@v1
- name: Combine Job Status
id: status
run: |
failures=(neutral, skipped, timed_out, action_required)
if [[ ${array[@]} =~ $WORKFLOW_CONCLUSION ]]; then
echo ::set-output name=status::failure
else
echo ::set-output name=status::$WORKFLOW_CONCLUSION
fi
- name: Post Status to Discord
uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ job.status }}
status: ${{ steps.status.outputs.status }}
title: ${{ github.workflow }}
nofail: true

View File

@@ -1,79 +1,26 @@
name: Publish Release
name: Publish Installers
on:
push:
branches: [master, beta, nightly, python3]
branches: [master, beta, nightly]
tags: [v*]
pull_request: ~
jobs:
build-windows:
runs-on: windows-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
build-installer:
name: Build ${{ matrix.os_upper }} Installer
runs-on: ${{ matrix.os }}-latest
strategy:
fail-fast: false
matrix:
include:
- os: 'windows'
os_upper: 'Windows'
ext: 'exe'
- os: 'macos'
os_upper: 'MacOS'
ext: 'pkg'
- name: Set Release Version
id: get_version
shell: bash
run: |
if [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION_NSIS=${GITHUB_REF#refs/tags/v}.1
echo ::set-output name=VERSION_NSIS::${VERSION_NSIS/%-beta.1/.0}
echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v}
echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/}
else
echo ::set-output name=VERSION_NSIS::0.0.0.0
echo ::set-output name=VERSION::0.0.0
echo ::set-output name=RELEASE_VERSION::${GITHUB_SHA::7}
fi
echo $GITHUB_SHA > version.txt
- name: Set Up Python
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Cache Dependencies
id: cache_dependencies
uses: actions/cache@v2
with:
path: ~\AppData\Local\pip\Cache
key: ${{ runner.os }}-pip-${{ hashFiles('package/requirements-windows.txt') }}
restore-keys: ${{ runner.os }}-pip-
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r package/requirements-windows.txt
- name: Build Package
run: |
pyinstaller -y ./package/Tautulli-windows.spec
- name: Create Installer
uses: joncloud/makensis-action@v1.2
with:
script-file: ./package/Tautulli.nsi
arguments: /DVERSION=${{ steps.get_version.outputs.VERSION_NSIS }} /DINSTALLER_NAME=..\Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
include-more-plugins: true
include-custom-plugins-path: package/nsis-plugins
- name: Upload Installer
uses: actions/upload-artifact@v2
with:
name: Tautulli-windows-installer
path: Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
- name: Post Status to Discord
uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ job.status }}
title: Build Windows Installer
nofail: true
build-macos:
runs-on: macos-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
@@ -84,13 +31,23 @@ jobs:
run: |
if [[ $GITHUB_REF == refs/tags/* ]]; then
echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
VERSION_NSIS=${GITHUB_REF#refs/tags/v}.1
echo ::set-output name=VERSION_NSIS::${VERSION_NSIS/%-beta.1/.0}
echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v}
echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/}
else
echo "VERSION=0.0.0" >> $GITHUB_ENV
echo ::set-output name=VERSION_NSIS::0.0.0.0
echo ::set-output name=VERSION::0.0.0
echo ::set-output name=RELEASE_VERSION::${GITHUB_SHA::7}
fi
if [[ $GITHUB_REF == refs/tags/*-beta ]]; then
echo "beta" > branch.txt
elif [[ $GITHUB_REF == refs/tags/* ]]; then
echo "master" > branch.txt
else
echo ${GITHUB_REF#refs/heads/} > branch.txt
fi
echo $GITHUB_SHA > version.txt
- name: Set Up Python
@@ -99,44 +56,56 @@ jobs:
python-version: 3.8
- name: Cache Dependencies
id: cache_dependencies
uses: actions/cache@v2
with:
path: ~/Library/Caches/pip
key: ${{ runner.os }}-pip-${{ hashFiles('package/requirements-macos.txt') }}
path: ~\AppData\Local\pip\Cache
key: ${{ runner.os }}-pip-${{ hashFiles(format('package/requirements-{0}.txt', matrix.os)) }}
restore-keys: ${{ runner.os }}-pip-
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r package/requirements-macos.txt
pip install -r package/requirements-${{ matrix.os }}.txt
- name: Build Package
run: |
pyinstaller -y ./package/Tautulli-macos.spec
pyinstaller -y ./package/Tautulli-${{ matrix.os }}.spec
- name: Create Installer
- name: Move Windows Updater Files
if: matrix.os == 'windows'
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 }}-x64.pkg
Move-Item dist\updater\* dist\Tautulli\ -Force
- name: Create Windows Installer
uses: joncloud/makensis-action@v3.4
if: matrix.os == 'windows'
with:
script-file: ./package/Tautulli.nsi
arguments: >
/DVERSION=${{ steps.get_version.outputs.VERSION_NSIS }}
/DINSTALLER_NAME=..\Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
additional-plugin-paths: package/nsis-plugins
- name: Create MacOS Installer
if: matrix.os == 'macos'
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 }}-x64.pkg
- name: Upload Installer
uses: actions/upload-artifact@v2
with:
name: Tautulli-macos-installer
path: Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg
- name: Post Status to Discord
uses: sarisia/actions-status-discord@v1
if: always()
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ job.status }}
title: Build MacOS Installer
nofail: true
name: Tautulli-${{ matrix.os }}-installer
path: Tautulli-${{ matrix.os }}-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.${{ matrix.ext }}
release:
needs: [build-windows, build-macos]
if: startsWith(github.ref, 'refs/tags/') && always()
name: Release Installers
needs: build-installer
if: always() && startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Get Build Job Status
@@ -150,25 +119,19 @@ jobs:
run: |
echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/}
- name: Download Windows Installer
- name: Download Installers
if: env.WORKFLOW_CONCLUSION == 'success'
uses: actions/download-artifact@v2
with:
name: Tautulli-windows-installer
- name: Download MacOS Installer
if: env.WORKFLOW_CONCLUSION == 'success'
uses: actions/download-artifact@v2
with:
name: Tautulli-macos-installer
- name: Get Changelog
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' )"
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
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -182,23 +145,50 @@ jobs:
prerelease: ${{ endsWith(steps.get_version.outputs.RELEASE_VERSION, '-beta') }}
- name: Upload Windows Installer
if: env.WORKFLOW_CONCLUSION == 'success'
uses: actions/upload-release-asset@v1
if: env.WORKFLOW_CONCLUSION == 'success'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
asset_path: Tautulli-windows-installer/Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
asset_name: Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
asset_content_type: application/vnd.microsoft.portable-executable
- name: Upload MacOS Installer
if: env.WORKFLOW_CONCLUSION == 'success'
uses: actions/upload-release-asset@v1
if: env.WORKFLOW_CONCLUSION == 'success'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg
asset_path: Tautulli-macos-installer/Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg
asset_name: Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg
asset_content_type: application/vnd.apple.installer+xml
discord:
name: Discord Notification
needs: [build-installer, release]
if: always() && github.event_name != 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Get Build Job Status
uses: technote-space/workflow-conclusion-action@v1
- name: Combine Job Status
id: status
run: |
failures=(neutral, skipped, timed_out, action_required)
if [[ ${array[@]} =~ $WORKFLOW_CONCLUSION ]]; then
echo ::set-output name=status::failure
else
echo ::set-output name=status::$WORKFLOW_CONCLUSION
fi
- name: Post Status to Discord
uses: sarisia/actions-status-discord@v1
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ steps.status.outputs.status }}
title: ${{ github.workflow }}
nofail: true

94
.github/workflows/publish-snap.yml vendored Normal file
View File

@@ -0,0 +1,94 @@
name: Publish Snap
on:
push:
branches: [master, beta, nightly]
tags: [v*]
pull_request: ~
jobs:
build-snap:
name: Build Snap Package (${{ matrix.architecture }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
architecture:
- i386
- amd64
- arm64
- armhf
- ppc64el
#- s390x # broken at the moment
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Prepare
id: prepare
run: |
git fetch --prune --unshallow --tags
if [[ $GITHUB_REF == refs/tags/*-beta || $GITHUB_REF == refs/heads/beta ]]; then
echo ::set-output name=RELEASE::beta
elif [[ $GITHUB_REF == refs/tags/* || $GITHUB_REF == refs/heads/master ]]; then
echo ::set-output name=RELEASE::stable
else
echo ::set-output name=RELEASE::edge
fi
- name: Set Up QEMU
uses: docker/setup-qemu-action@v1
- name: Build Snap Package
uses: diddlesnaps/snapcraft-multiarch-action@v1
id: build
with:
architecture: ${{ matrix.architecture }}
- name: Upload Snap Package
uses: actions/upload-artifact@v2
with:
name: Tautulli-snap-package-${{ matrix.architecture }}
path: ${{ steps.build.outputs.snap }}
- name: Review Snap Package
uses: diddlesnaps/snapcraft-review-tools-action@v1
with:
snap: ${{ steps.build.outputs.snap }}
- name: Publish Snap Package
uses: snapcore/action-publish@v1
if: >
github.event_name != 'pull_request' &&
(startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/nightly')
with:
store_login: ${{ secrets.SNAP_LOGIN }}
snap: ${{ steps.build.outputs.snap }}
release: ${{ steps.prepare.outputs.RELEASE }}
discord:
name: Discord Notification
needs: build-snap
if: always() && github.event_name != 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Get Build Job Status
uses: technote-space/workflow-conclusion-action@v1
- name: Combine Job Status
id: status
run: |
failures=(neutral, skipped, timed_out, action_required)
if [[ ${array[@]} =~ $WORKFLOW_CONCLUSION ]]; then
echo ::set-output name=status::failure
else
echo ::set-output name=status::$WORKFLOW_CONCLUSION
fi
- name: Post Status to Discord
uses: sarisia/actions-status-discord@v1
with:
webhook: ${{ secrets.DISCORD_WEBHOOK }}
status: ${{ steps.status.outputs.status }}
title: ${{ github.workflow }}
nofail: true

28
.github/workflows/pull-requests.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Pull Requests
on:
pull_request_target:
types: [opened, synchronize, edited, reopened]
jobs:
check-branch:
name: Check Pull Request
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Comment on Pull Request
uses: mshick/add-pr-comment@v1
if: github.base_ref != 'nightly'
with:
message: Pull requests must be made to the `nightly` branch. Thanks.
repo-token: ${{ secrets.GITHUB_TOKEN }}
repo-token-user-login: 'github-actions[bot]'
- name: Fail Workflow
if: github.base_ref != 'nightly'
run: |
echo Base: ${{ github.base_ref }}
echo Head: ${{ github.head_ref }}
exit 1

11
.gitignore vendored
View File

@@ -1,6 +1,7 @@
# Compiled source #
###################
__pycache__
*.pyc
*.py~
*.pyproj
@@ -64,7 +65,6 @@ Thumbs.db
*.bak
*.cache
*.ilk
*.log
[Bb]in
[Dd]ebug*/
*.lib
@@ -81,3 +81,12 @@ _ReSharper*/
#Ignore files generated by pyinstaller
/build
/dist
#snapcraft specifics
/parts/
/stage/
/prime/
*.snap
.snapcraft
*_source.tar.bz2
snap/.snapcraft

View File

@@ -1,5 +1,33 @@
# Changelog
## v2.6.5 (2021-01-09)
* Other:
* Fix: Some IP addresses not being masked in the logs.
* New: Auto-updater for Windows exe installer.
* Change: Allow Snap package to access the user home directory.
* Change: Migrate Snap user data to a persistent location that is retained if Tautulli is reinstalled.
## v2.6.4 (2020-12-20)
* Other:
* Fix: Restore Snap data folder from previous installs.
## v2.6.3 (2020-12-19)
* Announcements:
* This is the last Tautulli version to support Python 2. Python 3 will be required to continue receiving updates. You can check your Python version on the settings page.
* Exporter:
* Fix: Accessible and exists attributes were blank for media info export level 9.
* UI:
* Fix: Guest usernames were not masked on mouse hover.
* Other:
* Fix: macOS menu bar icon for light and dark mode.
* New: Tautulli can officially be installed on Linux using a Snap package. See the installation wiki for details.
## v2.6.2 (2020-12-05)
* Notifications:

View File

@@ -38,7 +38,8 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
| --- | --- | --- | --- |
| Release | [![Release@master](https://img.shields.io/github/v/release/Tautulli/Tautulli?style=flat-square)](https://github.com/Tautulli/Tautulli/releases/latest) <br> [![Release Date@master](https://img.shields.io/github/release-date/Tautulli/Tautulli?style=flat-square&color=blue)](https://github.com/Tautulli/Tautulli/releases/latest) | [![Release@beta](https://img.shields.io/github/v/release/Tautulli/Tautulli?include_prereleases&style=flat-square)](https://github.com/Tautulli/Tautulli/releases) <br> [![Commits@beta](https://img.shields.io/github/commits-since/Tautulli/Tautulli/latest/beta?style=flat-square&color=blue)](https://github.com/Tautulli/Tautulli/commits/beta) | [![Last Commits@nightly](https://img.shields.io/github/last-commit/Tautulli/Tautulli/nightly?style=flat-square&color=blue)](https://github.com/Tautulli/Tautulli/commits/nightly) <br> [![Commits@nightly](https://img.shields.io/github/commits-since/Tautulli/Tautulli/latest/nightly?style=flat-square&color=blue)](https://github.com/Tautulli/Tautulli/commits/nightly) |
| Docker | [![Docker@master](https://img.shields.io/badge/docker-latest-blue?style=flat-square)](https://hub.docker.com/r/tautulli/tautulli) <br> [![Docker Build@master](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker/master?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Amaster) | [![Docker@beta](https://img.shields.io/badge/docker-beta-blue?style=flat-square)](https://hub.docker.com/r/tautulli/tautulli) <br> [![Docker Build@beta](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker/beta?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Abeta) | [![Docker@nightly](https://img.shields.io/badge/docker-nightly-blue?style=flat-square)](https://hub.docker.com/r/tautulli/tautulli) <br> [![Docker Build@nightly](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Docker/nightly?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Anightly) |
| Installer | [![Windows@master](https://img.shields.io/github/v/release/Tautulli/Tautulli?label=windows&style=flat-square)](https://github.com/Tautulli/Tautulli/releases/latest) <br> [![MacOS@master](https://img.shields.io/github/v/release/Tautulli/Tautulli?label=macos&style=flat-square)](https://github.com/Tautulli/Tautulli/releases/latest) <br> [![Installer Build@master](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Release/master?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Release"+branch%3Amaster) | [![Windows@beta](https://img.shields.io/github/v/release/Tautulli/Tautulli?label=windows&include_prereleases&style=flat-square)](https://github.com/Tautulli/Tautulli/releases) <br> [![MacOS@beta](https://img.shields.io/github/v/release/Tautulli/Tautulli?label=macos&include_prereleases&style=flat-square)](https://github.com/Tautulli/Tautulli/releases) <br> [![Installer Build@beta](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Release/beta?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Release"+branch%3Abeta) | [![Installer Build@nightly](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Release/nightly?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Release"+branch%3Anightly) |
| Snap | [![Snap@master](https://img.shields.io/badge/snap-stable-blue?style=flat-square)](https://snapcraft.io/tautulli) <br> [![Snap Build@master](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Snap/master?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Snap"+branch%3Amaster) | [![Snap@beta](https://img.shields.io/badge/snap-beta-blue?style=flat-square)](https://snapcraft.io/tautulli) <br> [![Snap Build@beta](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Snap/beta?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Snap"+branch%3Abeta) | [![Snap@nightly](https://img.shields.io/badge/snap-edge-blue?style=flat-square)](https://snapcraft.io/tautulli) <br> [![Snap Build@nightly](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Snap/nightly?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Snap"+branch%3Anightly) |
| Installer | [![Windows@master](https://img.shields.io/github/v/release/Tautulli/Tautulli?label=windows&style=flat-square)](https://github.com/Tautulli/Tautulli/releases/latest) <br> [![MacOS@master](https://img.shields.io/github/v/release/Tautulli/Tautulli?label=macos&style=flat-square)](https://github.com/Tautulli/Tautulli/releases/latest) <br> [![Installer Build@master](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Installers/master?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Installers"+branch%3Amaster) | [![Windows@beta](https://img.shields.io/github/v/release/Tautulli/Tautulli?label=windows&include_prereleases&style=flat-square)](https://github.com/Tautulli/Tautulli/releases) <br> [![MacOS@beta](https://img.shields.io/github/v/release/Tautulli/Tautulli?label=macos&include_prereleases&style=flat-square)](https://github.com/Tautulli/Tautulli/releases) <br> [![Installer Build@beta](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Installers/beta?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Installers"+branch%3Abeta) | [![Installer Build@nightly](https://img.shields.io/github/workflow/status/Tautulli/Tautulli/Publish%20Installers/nightly?style=flat-square)](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Installers"+branch%3Anightly) |
[![Wiki](https://img.shields.io/badge/github-wiki-black?style=flat-square)](https://github.com/Tautulli/Tautulli-Wiki/wiki)
[![Discord](https://img.shields.io/discord/183396325142822912?label=discord&style=flat-square&color=7289DA)](https://tautulli.com/discord)

View File

@@ -31,6 +31,7 @@ import datetime
import locale
import pytz
import signal
import shutil
import time
import threading
import tzlocal
@@ -124,6 +125,8 @@ def main():
if helpers.bool_true(os.getenv('TAUTULLI_DOCKER', False)):
plexpy.DOCKER = True
if helpers.bool_true(os.getenv('TAUTULLI_SNAP', False)):
plexpy.SNAP = True
if args.dev:
plexpy.DEV = True
@@ -186,6 +189,15 @@ def main():
else:
plexpy.DATA_DIR = plexpy.PROG_DIR
# Migrate Snap data dir
if plexpy.SNAP:
snap_common = os.environ['SNAP_COMMON']
old_data_dir = os.path.join(snap_common, 'Tautulli')
if os.path.exists(old_data_dir) and os.listdir(old_data_dir):
plexpy.SNAP_MIGRATE = True
logger.info("Migrating Snap user data.")
shutil.move(old_data_dir, plexpy.DATA_DIR)
if args.config:
config_file = args.config
else:

View File

@@ -59,7 +59,9 @@
% endif
% if plexpy.INSTALL_TYPE == 'docker':
Update your Docker container or <a href="#" id="updateDismiss">Dismiss</a>
% elif plexpy.INSTALL_TYPE in ('windows', 'macos'):
% elif plexpy.INSTALL_TYPE == 'snap':
Update your Snap package or <a href="#" id="updateDismiss">Dismiss</a>
% elif plexpy.INSTALL_TYPE == 'macos':
<a href="${anon_url('https://github.com/%s/%s/releases/tag/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.LATEST_RELEASE))}" target="_blank" rel="noreferrer">Download</a> and install the latest version or <a href="#" id="updateDismiss">Dismiss</a>
% else:
<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>
@@ -235,6 +237,7 @@ ${next.modalIncludes()}
<li class="active"><a href="#github-donation" role="tab" data-toggle="tab">GitHub</a></li>
<li><a href="#patreon-donation" role="tab" data-toggle="tab">Patreon</a></li>
<li><a href="#paypal-donation" role="tab" data-toggle="tab">PayPal</a></li>
<li><a href="#crypto-donation" role="tab" data-toggle="tab">Crypto</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="github-donation" style="text-align: center">
@@ -261,6 +264,14 @@ ${next.modalIncludes()}
<img src="images/gold-rect-paypal-34px.png" alt="PayPal">
</a>
</div>
<div role="tabpanel" class="tab-pane" id="crypto-donation" style="text-align: center">
<p>
Click the button below to continue to Coinbase.
</p>
<a href="https://blankrefer.com/?https://commerce.coinbase.com/checkout/8a9fa08c-8a38-409e-9220-868124c4ba0c" target="_blank" rel="noreferrer" class="donate-with-crypto">
<span>Donate with Crypto</span>
</a>
</div>
</div>
</div>
<div class="modal-footer">
@@ -337,7 +348,9 @@ ${next.modalIncludes()}
}
if (result.install_type === 'docker') {
msg += 'Update your Docker container or <a href="#" id="updateDismiss">Dismiss</a>';
} else if (result.install_type === 'windows' || result.install_type === 'macos') {
} else if (result.install_type === 'snap') {
msg += 'Update your Snap package or <a href="#" id="updateDismiss">Dismiss</a>';
} else if (result.install_type === 'macos') {
msg += '<a href="' + result.release_url + '" target="_blank" rel="noreferrer">Download</a> and install the latest version or <a href="#" id="updateDismiss">Dismiss</a>'
} else {
msg += '<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>';

View File

@@ -4368,3 +4368,66 @@ a[data-tab-destination] {
.news-body a:hover {
color: #f9be03;
}
a.donate-with-crypto,
a.donate-with-crypto > span {
background: none;
border: none;
border-radius: 0;
box-sizing: border-box;
clear: none;
clip: auto;
cursor: default;
display: block;
float: none;
height: auto;
margin: 0;
max-height: none;
min-height: none;
padding: 0;
opacity: 1;
text-shadow: none;
vertical-align: baseline;
visibility: visible;
width: auto;
}
a.donate-with-crypto {
user-select: none;
user-drag: none;
-webkit-user-drag: none;
text-decoration: none;
background: #1652f0 linear-gradient(#1652f0, #0655ab);
cursor: pointer;
transition: background 0.2s ease-in-out, padding 0.2s;
border-radius: 6px;
display: inline-block;
height: 40px;
padding: 9px 15px 11px 15px;
position: relative;
min-width: 160px;
}
a.donate-with-crypto:hover {
background: #1652f0;
}
a.donate-with-crypto > span {
color: white;
font: normal 500 14px/20px -apple-system, BlinkMacSystemFont, '.SFNSText-Regular', 'San Francisco', 'Roboto', 'Segoe UI', 'Helvetica Neue', 'Lucida Grande', sans-serif;
letter-spacing: 0;
overflow: hidden;
text-align: center;
text-overflow: ellipsis;
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.15);
white-space: nowrap;
}
a.donate-with-crypto::after {
border-radius: 6px;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.2);
content: '';
display: block;
width: 100%;
height: 100%;
position: absolute;
opacity: 1;
top: 0;
left: 0;
}

View File

@@ -212,6 +212,28 @@
</div>
</div>
</div>
<% from plexpy.helpers import anon_url %>
<div id="python2-modal" class="modal fade wide" tabindex="-1" role="dialog" aria-labelledby="python2-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h4 class="modal-title">Unable to Update</h4>
</div>
<div class="modal-body" style="text-align: center;">
<p>Tautulli is still running using Python 2 and cannot be updated past v2.6.3.</p>
<p>Python 3 is required to continue receiving updates.</p>
<p>
<strong>Please see the <a href="${anon_url('https://github.com/Tautulli/Tautulli-Wiki/wiki/Upgrading-to-Python-3-%28Tautulli-v2.5%29')}" target="_blank" rel="noreferrer">wiki</a>
for instructions on how to upgrade to Python 3.</strong>
</p>
</div>
<div class="modal-footer">
<input type="button" class="btn btn-bright" data-dismiss="modal" value="Close">
</div>
</div>
</div>
</div>
% endif
<div class="modal fade" id="ip-info-modal" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal">
@@ -258,22 +280,32 @@
var error_msg = 'There was an error communicating with your Plex Server.' + msg_settings;
% if 'current_activity' in config['home_sections'] or 'recently_added' in config['home_sections']:
var server_status;
server_status = setInterval(function() {
$.getJSON('server_status', function (data) {
if (data.connected === true) {
clearInterval(server_status);
% if 'current_activity' in config['home_sections']:
$('#currentActivity').html('<div id="dashboard-checking-activity" class="text-muted"><i class="fa fa-refresh fa-spin"></i>&nbsp; Checking for activity...</div>');
$('#recentlyAdded').html('<div id="dashboard-checking-recently-added" class="text-muted"><i class="fa fa-refresh fa-spin"></i>&nbsp; Looking for new items...</div>');
activityConnected();
% endif
% if 'recently_added' in config['home_sections']:
$('#recentlyAdded').html('<div id="dashboard-checking-recently-added" class="text-muted"><i class="fa fa-refresh fa-spin"></i>&nbsp; Looking for new items...</div>');
recentlyAddedConnected();
% endif
} else if (data.connected === false) {
clearInterval(server_status);
% if 'current_activity' in config['home_sections']:
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">' + error_msg + '</div>');
% endif
% if 'recently_added' in config['home_sections']:
$('#recentlyAdded').html('<div id="dashboard-no-recently-added" class="text-muted">' + error_msg + '</div>');
% endif
}
});
}, 1000);
% endif
</script>
% if 'current_activity' in config['home_sections']:
<script>
@@ -1010,4 +1042,16 @@
});
</script>
% endif
% if _session['user_group'] == 'admin':
<script>
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
if (urlParams.get('update') === 'python2') {
$("#python2-modal").modal({
backdrop: 'static',
keyboard: false
});
}
</script>
% endif
</%def>

View File

@@ -220,7 +220,7 @@
<p class="help-block">Check for Tautulli updates periodically.</p>
</div>
<div id="git_update_options">
% if not plexpy.FROZEN:
% if not plexpy.SNAP and not (plexpy.FROZEN and common.PLATFORM == 'Darwin'):
<div class="checkbox">
<label>
<input type="checkbox" id="plexpy_auto_update" name="plexpy_auto_update" value="1" ${config['plexpy_auto_update']} ${docker_setting}> Update Automatically ${docker_msg | n}
@@ -3105,7 +3105,7 @@ $(document).ready(function() {
if (news_item.subtitle) { content.append(subtitle); }
content.append(body);
var li = $('<li/>').append(header).append(content)
if (index === 0 && Math.abs(now.diff(date, 'days')) < 7) {
if (index === 0 && Math.abs(now.diff(date, 'days')) <= 30) {
li.addClass('open');
content.css('display', 'block');
}

View File

@@ -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'
)

View File

@@ -32,6 +32,7 @@ VIAddVersionKey "FileVersion" "${VERSION}"
######################################################################
Unicode True
SetCompressor ZLIB
Name "${APP_NAME}"
Caption "${APP_NAME}"
@@ -39,7 +40,7 @@ OutFile "${INSTALLER_NAME}"
BrandingText "${APP_NAME}"
XPStyle on
InstallDirRegKey "${REG_ROOT}" "${REG_APP_PATH}" ""
InstallDir "$PROGRAMFILES\${APP_NAME}"
InstallDir "$PROGRAMFILES64\${APP_NAME}"
######################################################################
@@ -76,9 +77,13 @@ InstallDir "$PROGRAMFILES\${APP_NAME}"
!include Sections.nsh
Var /GLOBAL norun
Var /GLOBAL nolaunch
!include "MUI.nsh"
!include "FileFunc.nsh"
!insertmacro GetParameters
!insertmacro GetOptions
!define MUI_ABORTWARNING
!define MUI_UNABORTWARNING
@@ -99,6 +104,7 @@ Var /GLOBAL nolaunch
!insertmacro MUI_PAGE_STARTMENU Application $SM_Folder
!endif
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!define MUI_FINISHPAGE_RUN "$INSTDIR\${MAIN_APP_EXE}"
@@ -119,10 +125,14 @@ Section -MainProgram
Call UninstallPrevious
${INSTALL_TYPE}
SetOverwrite ifnewer
SetOverwrite on
SetOutPath "$INSTDIR"
File /nonfatal /a /r "..\dist\${APP_NAME}\"
nsExec::Exec "$INSTDIR\updater.exe --xml"
nsExec::Exec '$SYSDIR\SCHTASKS /Create /TN TautulliUpdateTask /XML "$INSTDIR\TautulliUpdateTask.xml" /F'
StrCmp $norun 1 +3 0
IfSilent 0 +2
ExecShell "" "$INSTDIR\${MAIN_APP_EXE}" $nolaunch
SectionEnd
@@ -208,11 +218,20 @@ RmDir "$SMPROGRAMS\${APP_NAME}"
DeleteRegKey ${REG_ROOT} "${REG_APP_PATH}"
DeleteRegKey ${REG_ROOT} "${UNINSTALL_PATH}"
nsExec::Exec "$SYSDIR\SCHTASKS /Delete /TN TautulliUpdateTask /F"
SectionEnd
######################################################################
Function .onInit
StrCpy $norun 0
${GetParameters} $CMDLINE
${GetOptions} "$CMDLINE" "/NORUN" $R0
IfErrors +2 0
StrCpy $norun 1
IfSilent 0 +2
StrCpy $nolaunch "--nolaunch"

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,4 +1,5 @@
pyinstaller==3.6
apscheduler==3.6.3
pyinstaller==4.1
pyopenssl==20.0.0
pycryptodomex==3.9.9
pyobjc-framework-Cocoa==6.2.2

View File

@@ -1,4 +1,6 @@
pyinstaller==3.6
apscheduler==3.6.3
psutil==5.8.0
pyinstaller==4.1
pyopenssl==20.0.0
pycryptodomex==3.9.9
pywin32==300

194
package/updater-windows.py Normal file
View File

@@ -0,0 +1,194 @@
# -*- 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 <http://www.gnu.org/licenses/>.
from logging import handlers
import argparse
import logging
import os
import psutil
import re
import requests
import shutil
import subprocess
import sys
import tempfile
import xml.etree.ElementTree as ET
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 = 1000000 # 1MB
MAX_FILES = 1
def init_logger():
log = logging.getLogger('updater')
log.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)
log.addHandler(file_handler)
return log
def read_file(file_path):
try:
with open(file_path, 'r') as f:
return f.read().strip(' \n\r')
except Exception as e:
logger.error('Read file error: %s', e)
raise Exception(1)
def request_json(url):
try:
response = requests.get(url)
response.raise_for_status()
return response.json()
except Exception as e:
logger.error('Request error: %s', e)
raise Exception(2)
def kill_and_get_processes(process_name):
processes = []
for process in psutil.process_iter():
if process.name() == process_name:
processes.append(process.cmdline())
logger.info('Sending SIGTERM to %s (PID=%d)', process.name(), process.pid)
process.terminate()
return processes
def update_tautulli():
logger.info('Starting Tautulli update check')
branch = read_file(os.path.join(SCRIPT_PATH, 'branch.txt'))
logger.info('Branch: %s', branch)
current_version = read_file(os.path.join(SCRIPT_PATH, 'version.txt'))
logger.info('Current version: %s', current_version)
logger.info('Retrieving latest version from GitHub')
commits = request_json('{}/commits/{}'.format(REPO_URL, branch))
latest_version = commits['sha']
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')
compare = request_json('{}/compare/{}...{}'.format(REPO_URL, latest_version, current_version))
commits_behind = compare['behind_by']
logger.info('Commits behind: %s', commits_behind)
if commits_behind <= 0:
logger.info('Tautulli is already up to date')
return 0
logger.info('Retrieving releases on GitHub')
releases = request_json('{}/releases'.format(REPO_URL))
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']
logger.info('Release: %s', version)
win_exe = 'application/vnd.microsoft.portable-executable'
asset = next((a for a in release['assets'] if a['content_type'] == win_exe), None)
download_url = asset['browser_download_url']
download_file = asset['name']
file_path = os.path.join(tempfile.gettempdir(), download_file)
logger.info('Downloading installer to temporary directory: %s', file_path)
try:
with requests.get(download_url, stream=True) as r:
with open(file_path, 'wb') as f:
shutil.copyfileobj(r.raw, f)
except Exception as e:
logger.error('Failed to download %s: %s', download_file, e)
return 2
logger.info('Stopping Tautulli processes')
try:
processes = kill_and_get_processes('Tautulli.exe')
except Exception as e:
logger.error('Failed to stop Tautulli: %s', e)
return 1
logger.info('Running %s', download_file)
try:
subprocess.call([file_path, '/S', '/NORUN', '/D=' + SCRIPT_PATH], creationflags=CREATE_NO_WINDOW)
status = 0
except Exception as e:
logger.exception('Failed to install Tautulli: %s', e)
status = -1
if status == 0:
logger.info('Tautulli updated to %s', version)
logger.info('Restarting Tautulli processes')
for process in processes:
logger.info('Starting process: %s', process)
subprocess.Popen(process, creationflags=CREATE_NO_WINDOW)
return status
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--xml', action='store_true')
opts = parser.parse_args()
if opts.xml:
xml_path = os.path.join(SCRIPT_PATH, 'TautulliUpdateTask.xml')
tree = ET.parse(xml_path)
task = tree.getroot()
match = re.match(r'{(.*)}', task.tag)
namespace = match.group(1)
namespaces = {'': namespace}
ET.register_namespace('', namespace)
for elem in task.iterfind('./Actions/Exec/Command', namespaces=namespaces):
elem.text = os.path.join(SCRIPT_PATH, 'updater.exe')
for elem in task.iterfind('./Actions/Exec/WorkingDirectory', namespaces=namespaces):
elem.text = SCRIPT_PATH
tree.write(xml_path, encoding='UTF-16')
else:
logger = init_logger()
try:
status_code = update_tautulli()
except Exception as exc:
status_code = exc
logger.debug('Update function returned status code %s', status_code)
sys.exit(status_code)

View File

@@ -98,6 +98,8 @@ CREATEPID = False
PIDFILE = None
NOFORK = False
DOCKER = False
SNAP = False
SNAP_MIGRATE = False
FROZEN = False
SCHED = None
@@ -172,6 +174,18 @@ def initialize(config_file):
if _INITIALIZED:
return False
if SNAP_MIGRATE:
snap_common = os.environ['SNAP_COMMON']
old_data_dir = os.path.join(snap_common, 'Tautulli')
CONFIG.HTTPS_CERT = CONFIG.HTTPS_CERT.replace(old_data_dir, DATA_DIR)
CONFIG.HTTPS_CERT_CHAIN = CONFIG.HTTPS_CERT_CHAIN.replace(old_data_dir, DATA_DIR)
CONFIG.HTTPS_KEY = CONFIG.HTTPS_KEY.replace(old_data_dir, DATA_DIR)
CONFIG.LOG_DIR = CONFIG.LOG_DIR.replace(old_data_dir, DATA_DIR)
CONFIG.BACKUP_DIR = CONFIG.BACKUP_DIR.replace(old_data_dir, DATA_DIR)
CONFIG.CACHE_DIR = CONFIG.CACHE_DIR.replace(old_data_dir, DATA_DIR)
CONFIG.EXPORT_DIR = CONFIG.EXPORT_DIR.replace(old_data_dir, DATA_DIR)
CONFIG.NEWSLETTER_DIR = CONFIG.NEWSLETTER_DIR.replace(old_data_dir, DATA_DIR)
if CONFIG.HTTP_PORT < 21 or CONFIG.HTTP_PORT > 65535:
logger.warn("HTTP_PORT out of bounds: 21 < %s < 65535", CONFIG.HTTP_PORT)
CONFIG.HTTP_PORT = 8181
@@ -194,6 +208,8 @@ def initialize(config_file):
if DOCKER:
build = '[Docker] '
elif SNAP:
build = '[Snap] '
elif FROZEN:
build = '[Bundle] '
else:
@@ -498,12 +514,16 @@ def schedule_job(func, name, hours=0, minutes=0, seconds=0, args=None):
SCHED.remove_job(name)
logger.info("Removed background task: %s", name)
elif job.trigger.interval != datetime.timedelta(hours=hours, minutes=minutes):
SCHED.reschedule_job(name, trigger=IntervalTrigger(
hours=hours, minutes=minutes, seconds=seconds, timezone=pytz.UTC), args=args)
SCHED.reschedule_job(
name, trigger=IntervalTrigger(
hours=hours, minutes=minutes, seconds=seconds, timezone=pytz.UTC),
args=args)
logger.info("Re-scheduled background task: %s", name)
elif hours > 0 or minutes > 0 or seconds > 0:
SCHED.add_job(func, id=name, trigger=IntervalTrigger(
hours=hours, minutes=minutes, seconds=seconds, timezone=pytz.UTC), args=args)
SCHED.add_job(
func, id=name, trigger=IntervalTrigger(
hours=hours, minutes=minutes, seconds=seconds, timezone=pytz.UTC),
args=args, misfire_grace_time=None)
logger.info("Scheduled background task: %s", name)
@@ -2275,7 +2295,12 @@ def upgrade():
return
def shutdown(restart=False, update=False, checkout=False, reset=False):
def shutdown(restart=False, update=False, checkout=False, reset=False,
_shutdown=True):
if FROZEN and common.PLATFORM == 'Windows' and update:
restart = False
_shutdown = False
webstart.stop()
# Shutdown the websocket connection
@@ -2348,14 +2373,15 @@ def shutdown(restart=False, update=False, checkout=False, reset=False):
else:
logger.info("Tautulli is shutting down...")
logger.shutdown()
if _shutdown:
logger.shutdown()
if WIN_SYS_TRAY_ICON:
WIN_SYS_TRAY_ICON.shutdown()
elif MAC_SYS_TRAY_ICON:
MAC_SYS_TRAY_ICON.shutdown()
if WIN_SYS_TRAY_ICON:
WIN_SYS_TRAY_ICON.shutdown()
elif MAC_SYS_TRAY_ICON:
MAC_SYS_TRAY_ICON.shutdown()
os._exit(0)
os._exit(0)
def generate_uuid():

View File

@@ -606,7 +606,8 @@ def schedule_callback(id, func=None, remove_job=False, args=None, **kwargs):
ACTIVITY_SCHED.add_job(
func, args=args, id=id, trigger=DateTrigger(
run_date=datetime.datetime.now(pytz.UTC) + datetime.timedelta(**kwargs),
timezone=pytz.UTC))
timezone=pytz.UTC),
misfire_grace_time=None)
def force_stop_stream(session_key, title, user):

View File

@@ -257,7 +257,7 @@ def import_tautulli_config(config=None, backup=False):
# Remove keys that should not be imported
for key in _DO_NOT_IMPORT_KEYS:
delattr(imported_config, key)
if plexpy.DOCKER:
if plexpy.DOCKER or plexpy.SNAP:
for key in _DO_NOT_IMPORT_KEYS_DOCKER:
delattr(imported_config, key)
@@ -540,3 +540,9 @@ class Config(object):
self.JWT_UPDATE_SECRET = True
self.CONFIG_VERSION = 16
if self.CONFIG_VERSION == 16:
if plexpy.SNAP:
self.PLEXPY_AUTO_UPDATE = 0
self.CONFIG_VERSION = 17

View File

@@ -228,7 +228,7 @@ def delete_rows_from_table(table, row_ids):
if row_ids:
logger.info("Tautulli Database :: Deleting row ids %s from %s database table", row_ids, table)
# SQlite verions prior to 3.32.0 (2020-05-22) have maximum variable limit of 999
# SQlite versions prior to 3.32.0 (2020-05-22) have maximum variable limit of 999
# https://sqlite.org/limits.html
sqlite_max_variable_number = 999

View File

@@ -1885,7 +1885,8 @@ class Export(object):
elif self.media_type == 'playlist' and 'item' in self._custom_fields:
export_attrs_set.update(self._custom_fields['item'])
if 'media.parts.accessible' in export_attrs_set or 'media.parts.exists' in export_attrs_set:
if 'media.parts.accessible' in export_attrs_set or 'media.parts.exists' in export_attrs_set or \
self.media_info_level == 9:
self._reload_check_files = True
for attr in export_attrs_set:

View File

@@ -19,11 +19,11 @@ from __future__ import unicode_literals
from future.builtins import object
from future.builtins import str
from functools import partial
from multiprocessing.dummy import Pool as ThreadPool
from future.moves.urllib.parse import urljoin
import certifi
import requests
import urllib3
import plexpy
@@ -41,6 +41,7 @@ class HTTPHandler(object):
"""
def __init__(self, urls, headers=None, token=None, timeout=10, ssl_verify=True, silent=False):
self._valid_request_types = {'GET', 'POST', 'PUT', 'DELETE'}
self._silent = silent
if isinstance(urls, str):
@@ -51,24 +52,34 @@ class HTTPHandler(object):
if headers:
self.headers = headers
else:
self.headers = {'X-Plex-Product': plexpy.common.PRODUCT,
'X-Plex-Version': plexpy.common.RELEASE,
'X-Plex-Client-Identifier': plexpy.CONFIG.PMS_UUID,
'X-Plex-Platform': plexpy.common.PLATFORM,
'X-Plex-Platform-Version': plexpy.common.PLATFORM_RELEASE,
'X-Plex-Device': '{} {}'.format(plexpy.common.PLATFORM,
plexpy.common.PLATFORM_RELEASE),
'X-Plex-Device-Name': plexpy.common.PLATFORM_DEVICE_NAME
}
self.headers = {
'X-Plex-Product': plexpy.common.PRODUCT,
'X-Plex-Version': plexpy.common.RELEASE,
'X-Plex-Client-Identifier': plexpy.CONFIG.PMS_UUID,
'X-Plex-Platform': plexpy.common.PLATFORM,
'X-Plex-Platform-Version': plexpy.common.PLATFORM_RELEASE,
'X-Plex-Device': '{} {}'.format(plexpy.common.PLATFORM,
plexpy.common.PLATFORM_RELEASE),
'X-Plex-Device-Name': plexpy.common.PLATFORM_DEVICE_NAME
}
self.token = token
if self.token:
self.headers['X-Plex-Token'] = self.token
self._session = requests.Session()
self.timeout = timeout
self.ssl_verify = ssl_verify
self.ssl_verify = certifi.where() if ssl_verify else False
if not self.ssl_verify:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
self.valid_request_types = ('GET', 'POST', 'PUT', 'DELETE')
self.uri = None
self.data = None
self.request_type = 'GET'
self.output_format = 'raw'
self.return_type = False
self.callback = None
self.request_kwargs = {}
def make_request(self,
uri=None,
@@ -96,7 +107,7 @@ class HTTPHandler(object):
self.timeout = timeout or self.timeout
self.request_kwargs = request_kwargs
if self.request_type not in self.valid_request_types:
if self.request_type not in self._valid_request_types:
logger.debug("HTTP request made but unsupported request type given.")
return None
@@ -115,7 +126,7 @@ class HTTPHandler(object):
return responses[0]
else:
logger.debug("HTTP request made but no enpoint given.")
logger.debug("HTTP request made but no uri endpoint provided.")
return None
def _http_requests_pool(self, urls, workers=10, chunk=None):
@@ -128,20 +139,13 @@ class HTTPHandler(object):
if len(urls) == 0:
chunk = 0
if self.ssl_verify:
session = urllib3.PoolManager(cert_reqs=2, ca_certs=certifi.where()) # ssl.CERT_REQUIRED = 2
else:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
session = urllib3.PoolManager()
part = partial(self._http_requests_urllib3, session=session)
if len(urls) == 1:
yield part(urls[0])
yield self._http_requests_single(urls[0])
else:
pool = ThreadPool(workers)
try:
for work in pool.imap_unordered(part, urls, chunk):
for work in pool.imap_unordered(self._http_requests_single, urls, chunk):
yield work
except Exception as e:
if not self._silent:
@@ -150,34 +154,40 @@ class HTTPHandler(object):
pool.close()
pool.join()
def _http_requests_urllib3(self, url, session):
def _http_requests_single(self, url):
"""Request the data from the url"""
error_msg = "Failed to access uri endpoint %s. " % self.uri
try:
r = session.request(self.request_type, url, headers=self.headers, fields=self.data,
timeout=self.timeout, **self.request_kwargs)
except IOError as e:
r = self._session.request(self.request_type, url, headers=self.headers, data=self.data,
timeout=self.timeout, verify=self.ssl_verify, **self.request_kwargs)
r.raise_for_status()
except requests.exceptions.Timeout as e:
if not self._silent:
logger.warn("Failed to access uri endpoint %s with error %s" % (self.uri, e))
logger.error(error_msg + "Request timed out: %s", e)
return None
except Exception as e:
except requests.exceptions.SSLError as e:
if not self._silent:
logger.warn("Failed to access uri endpoint %s. Is your server maybe accepting SSL connections only? %s" % (self.uri, e))
logger.error(error_msg + "Is your server maybe accepting SSL connections only? %s", e)
return None
except:
except requests.exceptions.HTTPError as e:
if not self._silent:
logger.warn("Failed to access uri endpoint %s with Uncaught exception." % self.uri)
logger.error(error_msg + "Status code %s", e)
return None
except requests.exceptions.ConnectionError as e:
if not self._silent:
logger.error(error_msg + "Connection error: %s", e)
return None
except requests.exceptions.RequestException as e:
if not self._silent:
logger.error(error_msg + "Uncaught exception: %s", e)
return None
response_status = r.status
response_content = r.data
response_status = r.status_code
response_content = r.content
response_headers = r.headers
if response_status in (200, 201):
return self._http_format_output(response_content, response_headers)
else:
if not self._silent:
logger.warn("Failed to access uri endpoint %s. Status code %r" % (self.uri, response_status))
return None
def _http_format_output(self, response_content, response_headers):
"""Formats the request response to the desired type"""

View File

@@ -103,11 +103,21 @@ class BlacklistFilter(logging.Filter):
try:
if item in record.msg:
record.msg = record.msg.replace(item, 16 * '*')
if any(item in str(arg) for arg in record.args):
record.args = tuple(arg.replace(item, 16 * '*') if isinstance(arg, str) else arg
for arg in record.args)
args = []
for arg in record.args:
try:
arg_str = str(arg)
if item in arg_str:
arg_str = arg_str.replace(item, 16 * '*')
arg = arg_str
except:
pass
args.append(arg)
record.args = tuple(args)
except:
pass
return True
@@ -131,9 +141,15 @@ class RegexFilter(logging.Filter):
args = []
for arg in record.args:
matches = self.regex.findall(arg) if isinstance(arg, str) else []
for match in matches:
arg = self.replace(arg, match)
try:
arg_str = str(arg)
matches = self.regex.findall(arg_str)
if matches:
for match in matches:
arg_str = self.replace(arg_str, match)
arg = arg_str
except:
pass
args.append(arg)
record.args = tuple(args)
except:

View File

@@ -66,7 +66,8 @@ class MacOSSystemTray(object):
self.menu[2].state = plexpy.CONFIG.LAUNCH_STARTUP
self.menu[3].state = plexpy.CONFIG.LAUNCH_BROWSER
self.tray_icon = rumps.App(common.PRODUCT, icon=self.icon, menu=self.menu, quit_button=None)
self.tray_icon = rumps.App(common.PRODUCT, icon=self.icon, template=True,
menu=self.menu, quit_button=None)
def start(self):
logger.info("Launching MacOS menu bar icon.")
@@ -161,10 +162,11 @@ def set_startup():
plist_file_path = os.path.join(launch_agents, plist_file)
exe = sys.executable
run_args = [arg for arg in plexpy.ARGS if arg != '--nolaunch']
if plexpy.FROZEN:
args = [exe]
args = [exe] + run_args
else:
args = [exe, plexpy.FULL_PATH]
args = [exe, plexpy.FULL_PATH] + run_args
plist_dict = {
'Label': common.PRODUCT,

View File

@@ -82,7 +82,8 @@ def schedule_newsletter_job(newsletter_job_id, name='', func=None, remove_job=Fa
logger.info("Tautulli NewsletterHandler :: Re-scheduled newsletter: %s" % name)
elif not remove_job:
NEWSLETTER_SCHED.add_job(
func, args=args, id=newsletter_job_id, trigger=CronTrigger.from_crontab(cron))
func, args=args, id=newsletter_job_id, trigger=CronTrigger.from_crontab(cron),
misfire_grace_time=None)
logger.info("Tautulli NewsletterHandler :: Scheduled newsletter: %s" % name)

View File

@@ -854,8 +854,8 @@ class Notifier(object):
else:
verify_msg = ""
if response is not None and response.status_code >= 400 and response.status_code < 500:
verify_msg = " Verify you notification agent settings are correct."
if response is not None and 400 <= response.status_code < 500:
verify_msg = " Verify your notification agent settings are correct."
logger.error("Tautulli Notifiers :: {name} notification failed.{msg}".format(msg=verify_msg, name=self.NAME))

View File

@@ -213,6 +213,7 @@ def mask_session_info(list_of_dicts, mask_metadata=True):
keys_to_mask = {'user_id': '',
'user': 'Plex User',
'username': 'Plex User',
'friendly_name': 'Plex User',
'user_thumb': common.DEFAULT_USER_THUMB,
'ip_address': 'N/A',

View File

@@ -18,4 +18,4 @@
from __future__ import unicode_literals
PLEXPY_BRANCH = "master"
PLEXPY_RELEASE_VERSION = "v2.6.2"
PLEXPY_RELEASE_VERSION = "v2.6.5"

View File

@@ -133,7 +133,13 @@ def get_version():
return cur_commit_hash, remote_name, branch_name
else:
plexpy.INSTALL_TYPE = 'docker' if plexpy.DOCKER else 'source'
if plexpy.DOCKER:
plexpy.INSTALL_TYPE = 'docker'
elif plexpy.SNAP:
plexpy.INSTALL_TYPE = 'snap'
else:
plexpy.INSTALL_TYPE = 'source'
current_version, current_branch = get_version_from_file()
return current_version, 'origin', current_branch
@@ -162,10 +168,13 @@ def check_update(scheduler=False, notify=False, use_cache=False):
if not plexpy.CURRENT_VERSION:
plexpy.UPDATE_AVAILABLE = None
elif plexpy.COMMITS_BEHIND > 0 and (plexpy.common.BRANCH in ('master', 'beta') or plexpy.FROZEN) and \
elif plexpy.COMMITS_BEHIND > 0 and \
(plexpy.common.BRANCH in ('master', 'beta') or plexpy.SNAP or plexpy.FROZEN) and \
plexpy.common.RELEASE != plexpy.LATEST_RELEASE:
plexpy.UPDATE_AVAILABLE = 'release'
elif plexpy.COMMITS_BEHIND > 0 and plexpy.CURRENT_VERSION != plexpy.LATEST_VERSION and not plexpy.FROZEN:
elif plexpy.COMMITS_BEHIND > 0 and \
not plexpy.SNAP and not plexpy.FROZEN and \
plexpy.CURRENT_VERSION != plexpy.LATEST_VERSION:
plexpy.UPDATE_AVAILABLE = 'commit'
else:
plexpy.UPDATE_AVAILABLE = False
@@ -265,7 +274,12 @@ def check_github(scheduler=False, notify=False, use_cache=False):
'plexpy_update_commit': plexpy.LATEST_VERSION,
'plexpy_update_behind': plexpy.COMMITS_BEHIND})
if scheduler and plexpy.CONFIG.PLEXPY_AUTO_UPDATE and not plexpy.DOCKER and not plexpy.FROZEN:
if plexpy.PYTHON2:
logger.warn('Tautulli is running using Python 2. Unable to run automatic update.')
elif scheduler and plexpy.CONFIG.PLEXPY_AUTO_UPDATE and \
not plexpy.DOCKER and not plexpy.SNAP and \
not (plexpy.FROZEN and common.PLATFORM == 'Darwin'):
logger.info('Running automatic update.')
plexpy.shutdown(restart=True, update=True)
@@ -276,12 +290,22 @@ def check_github(scheduler=False, notify=False, use_cache=False):
def update():
if plexpy.PYTHON2:
logger.warn('Tautulli is running using Python 2. Unable to update.')
return
if not plexpy.UPDATE_AVAILABLE:
return
if plexpy.INSTALL_TYPE in ('docker', 'windows', 'macos'):
if plexpy.INSTALL_TYPE in ('docker', 'snap', 'macos'):
return
elif plexpy.INSTALL_TYPE == 'windows':
logger.info('Calling Windows scheduled task to update Tautulli')
CREATE_NO_WINDOW = 0x08000000
subprocess.Popen(['SCHTASKS', '/Run', '/TN', 'TautulliUpdateTask'],
creationflags=CREATE_NO_WINDOW)
elif plexpy.INSTALL_TYPE == 'git':
output, err = runGit('pull --ff-only {} {}'.format(plexpy.CONFIG.GIT_REMOTE,
plexpy.CONFIG.GIT_BRANCH))

View File

@@ -16,7 +16,7 @@
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
# http://tools.cherrypy.org/wiki/AuthenticationAndAccessRestrictions
# https://github.com/cherrypy/tools/blob/master/AuthenticationAndAccessRestrictions
# Form based authentication for CherryPy. Requires the
# Session tool to be loaded.

View File

@@ -4309,7 +4309,7 @@ class WebInterface(object):
plexpy.CONFIG.GIT_REPO,
plexpy.CURRENT_VERSION,
plexpy.LATEST_VERSION))
}
}
else:
update = {'result': 'success',
@@ -4317,7 +4317,7 @@ class WebInterface(object):
'message': 'Tautulli is up to date.'
}
if plexpy.DOCKER or plexpy.FROZEN:
if plexpy.DOCKER or plexpy.SNAP or plexpy.FROZEN:
update['install_type'] = plexpy.INSTALL_TYPE
return update
@@ -4351,7 +4351,9 @@ class WebInterface(object):
@cherrypy.expose
@requireAuth(member_of("admin"))
def update(self, **kwargs):
if plexpy.DOCKER:
if plexpy.PYTHON2:
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home?update=python2")
if plexpy.DOCKER or plexpy.SNAP:
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
# Show changelog after updating

View File

@@ -148,27 +148,18 @@ def set_startup():
startup_reg_path = "Software\\Microsoft\\Windows\\CurrentVersion\\Run"
exe = sys.executable
run_args = [arg for arg in plexpy.ARGS if arg != '--nolaunch']
if plexpy.FROZEN:
args = [exe]
args = [exe] + run_args
else:
args = [exe, plexpy.FULL_PATH]
args = [exe, plexpy.FULL_PATH] + run_args
registry_key_name = '{}_{}'.format(common.PRODUCT, plexpy.CONFIG.PMS_UUID)
cmd = ' '.join(cmd_quote(arg) for arg in args).replace('python.exe', 'pythonw.exe').replace("'", '"')
if plexpy.CONFIG.LAUNCH_STARTUP:
try:
winreg.CreateKey(winreg.HKEY_CURRENT_USER, startup_reg_path)
registry_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, startup_reg_path, 0, winreg.KEY_WRITE)
winreg.SetValueEx(registry_key, common.PRODUCT, 0, winreg.REG_SZ, cmd)
winreg.CloseKey(registry_key)
logger.info("Added Tautulli to Windows system startup registry key.")
return True
except WindowsError as e:
logger.error("Failed to create Windows system startup registry key: %s", e)
return False
else:
# Check if registry value exists
# Rename old Tautulli registry key
try:
registry_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, startup_reg_path, 0, winreg.KEY_ALL_ACCESS)
winreg.QueryValueEx(registry_key, common.PRODUCT)
@@ -180,6 +171,33 @@ def set_startup():
try:
winreg.DeleteValue(registry_key, common.PRODUCT)
winreg.CloseKey(registry_key)
except WindowsError:
pass
try:
winreg.CreateKey(winreg.HKEY_CURRENT_USER, startup_reg_path)
registry_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, startup_reg_path, 0, winreg.KEY_WRITE)
winreg.SetValueEx(registry_key, registry_key_name, 0, winreg.REG_SZ, cmd)
winreg.CloseKey(registry_key)
logger.info("Added Tautulli to Windows system startup registry key.")
return True
except WindowsError as e:
logger.error("Failed to create Windows system startup registry key: %s", e)
return False
else:
# Check if registry value exists
try:
registry_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, startup_reg_path, 0, winreg.KEY_ALL_ACCESS)
winreg.QueryValueEx(registry_key, registry_key_name)
reg_value_exists = True
except WindowsError:
reg_value_exists = False
if reg_value_exists:
try:
winreg.DeleteValue(registry_key, registry_key_name)
winreg.CloseKey(registry_key)
logger.info("Removed Tautulli from Windows system startup registry key.")
return True
except WindowsError as e:

66
snap/snapcraft.yaml Normal file
View File

@@ -0,0 +1,66 @@
name: tautulli
adopt-info: tautulli
summary: A Python based monitoring and tracking tool for Plex Media Server.
description: >
Tautulli is a 3rd party application that you can run alongside your Plex Media Server to monitor activity and track various statistics.
Most importantly, these statistics include what has been watched, who watched it, when and where they watched it, and how it was watched.
The only thing missing is "why they watched it", but who am I to question your 42 plays of Frozen.
All statistics are presented in a nice and clean interface with many tables and graphs, which makes it easy to brag about your server to everyone else.
base: core18
confinement: strict
parts:
tautulli:
plugin: dump
source: .
stage-packages:
- python3
- python3-openssl
- python3-pycryptodome
- python3-setuptools
build-packages:
- git
override-pull: |
snapcraftctl pull
TAG_FULL=$(git describe --tag)
TAG=$(echo $TAG_FULL | grep -oP '(v\d+\.\d+\.\d+(?>-beta)?)')
BRANCH=$(git rev-parse --abbrev-ref HEAD)
COMMIT=$(git rev-parse HEAD)
if [ "$TAG" = "$TAG_FULL" ]; then
VERSION=$TAG
else
VERSION=$(echo $COMMIT | head -c 7)
fi
if [ ! "$VERSION" = "$TAG" ] || [ ! "$VERSION" = "${VERSION%-beta}" ]; then
GRADE=devel
else
GRADE=stable
fi
if [ "$VERSION" = "$TAG" ] && [ ! "$VERSION" = "${VERSION%-beta}" ]; then
BRANCH=beta
elif [ "$VERSION" = "$TAG" ]; then
BRANCH=master
fi
echo $BRANCH > branch.txt
echo $COMMIT > version.txt
snapcraftctl set-version "$VERSION"
snapcraftctl set-grade "$GRADE"
apps:
tautulli:
command: >
usr/bin/python3 $SNAP/Tautulli.py
--datadir $SNAP_USER_COMMON/Tautulli
--config $SNAP_USER_COMMON/Tautulli/config.ini
--quiet
--nolaunch
daemon: simple
restart-condition: on-abnormal
restart-delay: 5s
plugs:
- home
- network
- network-bind
environment:
TAUTULLI_SNAP: "True"