Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1019fecc9e | ||
![]() |
1855f93c1c | ||
![]() |
52e6a44aa4 | ||
![]() |
0b77808af6 | ||
![]() |
9233ed5c53 | ||
![]() |
ee68c0f622 | ||
![]() |
366823cee9 | ||
![]() |
40e1eb9a49 | ||
![]() |
1af419a860 | ||
![]() |
397f18c435 | ||
![]() |
2e5dd05a6c | ||
![]() |
a9fb8ddfb8 | ||
![]() |
562c726787 | ||
![]() |
5f82c1dc17 | ||
![]() |
222800bdb6 | ||
![]() |
5dd3636571 | ||
![]() |
2296a9fbb3 | ||
![]() |
63b5a7c036 | ||
![]() |
b74ca2670e | ||
![]() |
393f4e0e58 | ||
![]() |
3a9ca29e99 | ||
![]() |
32995fef24 | ||
![]() |
a73c99fc64 | ||
![]() |
a5834470ba | ||
![]() |
da3bc127dc | ||
![]() |
0dddc4d58f | ||
![]() |
a4d5d9157b | ||
![]() |
c70d5d4398 | ||
![]() |
7c08b07ef5 | ||
![]() |
e426b5dd35 | ||
![]() |
2fdf619582 | ||
![]() |
d9eed14b7a | ||
![]() |
8230ffb8a4 | ||
![]() |
7098930b19 | ||
![]() |
56244245a4 | ||
![]() |
dd2f12fa8e | ||
![]() |
9598247a0d | ||
![]() |
230ee90b1c | ||
![]() |
e705bedc91 | ||
![]() |
b5ebe7590c | ||
![]() |
6d0831ceaa | ||
![]() |
19e00ee2f2 | ||
![]() |
80723d224e | ||
![]() |
ff1bd0a4b8 |
@@ -5,6 +5,7 @@ contrib
|
|||||||
init-scripts
|
init-scripts
|
||||||
package
|
package
|
||||||
pylintrc
|
pylintrc
|
||||||
|
snap
|
||||||
*.md
|
*.md
|
||||||
!CHANGELOG*.md
|
!CHANGELOG*.md
|
||||||
start.bat
|
start.bat
|
||||||
|
20
.github/pull_request_template.md
vendored
Normal file
20
.github/pull_request_template.md
vendored
Normal 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
|
46
.github/workflows/publish-docker.yml
vendored
46
.github/workflows/publish-docker.yml
vendored
@@ -1,10 +1,14 @@
|
|||||||
name: Publish Docker
|
name: Publish Docker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master, beta, nightly, python3]
|
branches: [master, beta, nightly]
|
||||||
tags: [v*]
|
tags: [v*]
|
||||||
|
pull_request: ~
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build-docker:
|
||||||
|
name: Build Docker Image
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -20,7 +24,9 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo ::set-output name=tag::${GITHUB_REF#refs/heads/}
|
echo ::set-output name=tag::${GITHUB_REF#refs/heads/}
|
||||||
fi
|
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
|
echo ::set-output name=branch::master
|
||||||
else
|
else
|
||||||
echo ::set-output name=branch::${GITHUB_REF#refs/heads/}
|
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_platforms::linux/amd64,linux/arm64/v8,linux/arm/v7,linux/arm/v6
|
||||||
echo ::set-output name=docker_image::${{ secrets.DOCKER_REPO }}/tautulli
|
echo ::set-output name=docker_image::${{ secrets.DOCKER_REPO }}/tautulli
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set Up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v1
|
||||||
with:
|
|
||||||
platforms: all
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
id: buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v1
|
||||||
|
id: buildx
|
||||||
with:
|
with:
|
||||||
version: latest
|
version: latest
|
||||||
|
|
||||||
@@ -51,14 +55,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v1
|
||||||
if: success()
|
if: success() && github.event_name != 'pull_request'
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v1
|
||||||
if: success()
|
if: success() && github.event_name != 'pull_request'
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
@@ -70,7 +74,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
push: true
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
platforms: ${{ steps.prepare.outputs.docker_platforms }}
|
platforms: ${{ steps.prepare.outputs.docker_platforms }}
|
||||||
build-args: |
|
build-args: |
|
||||||
TAG=${{ steps.prepare.outputs.tag }}
|
TAG=${{ steps.prepare.outputs.tag }}
|
||||||
@@ -83,11 +87,29 @@ jobs:
|
|||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
cache-to: type=local,dest=/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
|
- name: Post Status to Discord
|
||||||
uses: sarisia/actions-status-discord@v1
|
uses: sarisia/actions-status-discord@v1
|
||||||
if: always()
|
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK }}
|
webhook: ${{ secrets.DISCORD_WEBHOOK }}
|
||||||
status: ${{ job.status }}
|
status: ${{ steps.status.outputs.status }}
|
||||||
title: ${{ github.workflow }}
|
title: ${{ github.workflow }}
|
||||||
nofail: true
|
nofail: true
|
||||||
|
@@ -1,12 +1,26 @@
|
|||||||
name: Publish Release
|
name: Publish Installers
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master, beta, nightly, python3]
|
branches: [master, beta, nightly]
|
||||||
tags: [v*]
|
tags: [v*]
|
||||||
|
pull_request: ~
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-windows:
|
build-installer:
|
||||||
runs-on: windows-latest
|
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'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@@ -16,11 +30,13 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||||
|
echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||||
VERSION_NSIS=${GITHUB_REF#refs/tags/v}.1
|
VERSION_NSIS=${GITHUB_REF#refs/tags/v}.1
|
||||||
echo ::set-output name=VERSION_NSIS::${VERSION_NSIS/%-beta.1/.0}
|
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=VERSION::${GITHUB_REF#refs/tags/v}
|
||||||
echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/}
|
echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/}
|
||||||
else
|
else
|
||||||
|
echo "VERSION=0.0.0" >> $GITHUB_ENV
|
||||||
echo ::set-output name=VERSION_NSIS::0.0.0.0
|
echo ::set-output name=VERSION_NSIS::0.0.0.0
|
||||||
echo ::set-output name=VERSION::0.0.0
|
echo ::set-output name=VERSION::0.0.0
|
||||||
echo ::set-output name=RELEASE_VERSION::${GITHUB_SHA::7}
|
echo ::set-output name=RELEASE_VERSION::${GITHUB_SHA::7}
|
||||||
@@ -33,110 +49,52 @@ jobs:
|
|||||||
python-version: 3.8
|
python-version: 3.8
|
||||||
|
|
||||||
- name: Cache Dependencies
|
- name: Cache Dependencies
|
||||||
id: cache_dependencies
|
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
with:
|
with:
|
||||||
path: ~\AppData\Local\pip\Cache
|
path: ~\AppData\Local\pip\Cache
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('package/requirements-windows.txt') }}
|
key: ${{ runner.os }}-pip-${{ hashFiles(format('package/requirements-{0}.txt', matrix.os)) }}
|
||||||
restore-keys: ${{ runner.os }}-pip-
|
restore-keys: ${{ runner.os }}-pip-
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -r package/requirements-windows.txt
|
pip install -r package/requirements-${{ matrix.os }}.txt
|
||||||
|
|
||||||
- name: Build Package
|
- name: Build Package
|
||||||
run: |
|
run: |
|
||||||
pyinstaller -y ./package/Tautulli-windows.spec
|
pyinstaller -y ./package/Tautulli-${{ matrix.os }}.spec
|
||||||
|
|
||||||
- name: Create Installer
|
- name: Create Windows Installer
|
||||||
uses: joncloud/makensis-action@v1.2
|
uses: joncloud/makensis-action@v1.2
|
||||||
|
if: matrix.os == 'windows'
|
||||||
with:
|
with:
|
||||||
script-file: ./package/Tautulli.nsi
|
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
|
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-more-plugins: true
|
||||||
include-custom-plugins-path: package/nsis-plugins
|
include-custom-plugins-path: package/nsis-plugins
|
||||||
|
|
||||||
- name: Upload Installer
|
- name: Create MacOS Installer
|
||||||
uses: actions/upload-artifact@v2
|
if: matrix.os == 'macos'
|
||||||
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
|
|
||||||
|
|
||||||
- name: Set Release Version
|
|
||||||
id: get_version
|
|
||||||
shell: bash
|
|
||||||
run: |
|
run: |
|
||||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
sudo pkgbuild \
|
||||||
echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
--install-location /Applications \
|
||||||
echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/v}
|
--version ${{ steps.get_version.outputs.VERSION }} \
|
||||||
echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/}
|
--component ./dist/Tautulli.app \
|
||||||
else
|
--scripts ./package/macos-scripts \
|
||||||
echo "VERSION=0.0.0" >> $GITHUB_ENV
|
Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg
|
||||||
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: ~/Library/Caches/pip
|
|
||||||
key: ${{ runner.os }}-pip-${{ hashFiles('package/requirements-macos.txt') }}
|
|
||||||
restore-keys: ${{ runner.os }}-pip-
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install -r package/requirements-macos.txt
|
|
||||||
|
|
||||||
- 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 }}-x64.pkg
|
|
||||||
|
|
||||||
- name: Upload Installer
|
- name: Upload Installer
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: Tautulli-macos-installer
|
name: Tautulli-${{ matrix.os }}-installer
|
||||||
path: Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg
|
path: Tautulli-${{ matrix.os }}-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.${{ matrix.ext }}
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
needs: [build-windows, build-macos]
|
name: Release Installers
|
||||||
if: startsWith(github.ref, 'refs/tags/') && always()
|
needs: build-installer
|
||||||
|
if: always() && startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Get Build Job Status
|
- name: Get Build Job Status
|
||||||
@@ -150,25 +108,19 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/}
|
echo ::set-output name=RELEASE_VERSION::${GITHUB_REF#refs/tags/}
|
||||||
|
|
||||||
- name: Download Windows Installer
|
- name: Download Installers
|
||||||
if: env.WORKFLOW_CONCLUSION == 'success'
|
if: env.WORKFLOW_CONCLUSION == 'success'
|
||||||
uses: actions/download-artifact@v2
|
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
|
- name: Get Changelog
|
||||||
id: 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
|
- name: Create Release
|
||||||
id: create_release
|
|
||||||
uses: actions/create-release@v1
|
uses: actions/create-release@v1
|
||||||
|
id: create_release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
@@ -182,23 +134,50 @@ jobs:
|
|||||||
prerelease: ${{ endsWith(steps.get_version.outputs.RELEASE_VERSION, '-beta') }}
|
prerelease: ${{ endsWith(steps.get_version.outputs.RELEASE_VERSION, '-beta') }}
|
||||||
|
|
||||||
- name: Upload Windows Installer
|
- name: Upload Windows Installer
|
||||||
if: env.WORKFLOW_CONCLUSION == 'success'
|
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
|
if: env.WORKFLOW_CONCLUSION == 'success'
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
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_name: Tautulli-windows-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.exe
|
||||||
asset_content_type: application/vnd.microsoft.portable-executable
|
asset_content_type: application/vnd.microsoft.portable-executable
|
||||||
|
|
||||||
- name: Upload MacOS Installer
|
- name: Upload MacOS Installer
|
||||||
if: env.WORKFLOW_CONCLUSION == 'success'
|
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
|
if: env.WORKFLOW_CONCLUSION == 'success'
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
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_name: Tautulli-macos-${{ steps.get_version.outputs.RELEASE_VERSION }}-x64.pkg
|
||||||
asset_content_type: application/vnd.apple.installer+xml
|
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
94
.github/workflows/publish-snap.yml
vendored
Normal 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
|
13
.gitignore
vendored
13
.gitignore
vendored
@@ -81,3 +81,16 @@ _ReSharper*/
|
|||||||
#Ignore files generated by pyinstaller
|
#Ignore files generated by pyinstaller
|
||||||
/build
|
/build
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
|
#snapcraft specifics
|
||||||
|
/parts/
|
||||||
|
/stage/
|
||||||
|
/prime/
|
||||||
|
|
||||||
|
*.snap
|
||||||
|
|
||||||
|
.snapcraft
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*_source.tar.bz2
|
||||||
|
snap/.snapcraft
|
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,5 +1,24 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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)
|
## v2.6.2 (2020-12-05)
|
||||||
|
|
||||||
* Notifications:
|
* Notifications:
|
||||||
|
@@ -38,7 +38,8 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
|
|||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| Release | [](https://github.com/Tautulli/Tautulli/releases/latest) <br> [](https://github.com/Tautulli/Tautulli/releases/latest) | [](https://github.com/Tautulli/Tautulli/releases) <br> [](https://github.com/Tautulli/Tautulli/commits/beta) | [](https://github.com/Tautulli/Tautulli/commits/nightly) <br> [](https://github.com/Tautulli/Tautulli/commits/nightly) |
|
| Release | [](https://github.com/Tautulli/Tautulli/releases/latest) <br> [](https://github.com/Tautulli/Tautulli/releases/latest) | [](https://github.com/Tautulli/Tautulli/releases) <br> [](https://github.com/Tautulli/Tautulli/commits/beta) | [](https://github.com/Tautulli/Tautulli/commits/nightly) <br> [](https://github.com/Tautulli/Tautulli/commits/nightly) |
|
||||||
| Docker | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Amaster) | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Abeta) | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Anightly) |
|
| Docker | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Amaster) | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Abeta) | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Docker"+branch%3Anightly) |
|
||||||
| Installer | [](https://github.com/Tautulli/Tautulli/releases/latest) <br> [](https://github.com/Tautulli/Tautulli/releases/latest) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Release"+branch%3Amaster) | [](https://github.com/Tautulli/Tautulli/releases) <br> [](https://github.com/Tautulli/Tautulli/releases) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Release"+branch%3Abeta) | [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Release"+branch%3Anightly) |
|
| Snap | [](https://snapcraft.io/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Snap"+branch%3Amaster) | [](https://snapcraft.io/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Snap"+branch%3Abeta) | [](https://snapcraft.io/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Snap"+branch%3Anightly) |
|
||||||
|
| Installer | [](https://github.com/Tautulli/Tautulli/releases/latest) <br> [](https://github.com/Tautulli/Tautulli/releases/latest) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Installers"+branch%3Amaster) | [](https://github.com/Tautulli/Tautulli/releases) <br> [](https://github.com/Tautulli/Tautulli/releases) <br> [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Installers"+branch%3Abeta) | [](https://github.com/Tautulli/Tautulli/actions?query=workflow%3A"Publish+Installers"+branch%3Anightly) |
|
||||||
|
|
||||||
[](https://github.com/Tautulli/Tautulli-Wiki/wiki)
|
[](https://github.com/Tautulli/Tautulli-Wiki/wiki)
|
||||||
[](https://tautulli.com/discord)
|
[](https://tautulli.com/discord)
|
||||||
|
@@ -124,6 +124,8 @@ def main():
|
|||||||
|
|
||||||
if helpers.bool_true(os.getenv('TAUTULLI_DOCKER', False)):
|
if helpers.bool_true(os.getenv('TAUTULLI_DOCKER', False)):
|
||||||
plexpy.DOCKER = True
|
plexpy.DOCKER = True
|
||||||
|
if helpers.bool_true(os.getenv('TAUTULLI_SNAP', False)):
|
||||||
|
plexpy.SNAP = True
|
||||||
|
|
||||||
if args.dev:
|
if args.dev:
|
||||||
plexpy.DEV = True
|
plexpy.DEV = True
|
||||||
|
@@ -59,6 +59,8 @@
|
|||||||
% endif
|
% endif
|
||||||
% if plexpy.INSTALL_TYPE == 'docker':
|
% if plexpy.INSTALL_TYPE == 'docker':
|
||||||
Update your Docker container or <a href="#" id="updateDismiss">Dismiss</a>
|
Update your Docker container or <a href="#" id="updateDismiss">Dismiss</a>
|
||||||
|
% elif plexpy.INSTALL_TYPE == 'snap':
|
||||||
|
Update your Snap package or <a href="#" id="updateDismiss">Dismiss</a>
|
||||||
% elif plexpy.INSTALL_TYPE in ('windows', 'macos'):
|
% elif plexpy.INSTALL_TYPE in ('windows', '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>
|
<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:
|
% else:
|
||||||
@@ -337,6 +339,8 @@ ${next.modalIncludes()}
|
|||||||
}
|
}
|
||||||
if (result.install_type === 'docker') {
|
if (result.install_type === 'docker') {
|
||||||
msg += 'Update your Docker container or <a href="#" id="updateDismiss">Dismiss</a>';
|
msg += 'Update your Docker container or <a href="#" id="updateDismiss">Dismiss</a>';
|
||||||
|
} else if (result.install_type === 'snap') {
|
||||||
|
msg += 'Update your Snap package or <a href="#" id="updateDismiss">Dismiss</a>';
|
||||||
} else if (result.install_type === 'windows' || result.install_type === 'macos') {
|
} else if (result.install_type === 'windows' || 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>'
|
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 {
|
} else {
|
||||||
|
@@ -212,6 +212,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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
|
% endif
|
||||||
|
|
||||||
<div class="modal fade" id="ip-info-modal" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal">
|
<div class="modal fade" id="ip-info-modal" tabindex="-1" role="dialog" aria-labelledby="ip-info-modal">
|
||||||
@@ -1010,4 +1032,16 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
% endif
|
% 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>
|
</%def>
|
@@ -220,7 +220,7 @@
|
|||||||
<p class="help-block">Check for Tautulli updates periodically.</p>
|
<p class="help-block">Check for Tautulli updates periodically.</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="git_update_options">
|
<div id="git_update_options">
|
||||||
% if not plexpy.FROZEN:
|
% if not plexpy.SNAP and not plexpy.FROZEN:
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label>
|
<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}
|
<input type="checkbox" id="plexpy_auto_update" name="plexpy_auto_update" value="1" ${config['plexpy_auto_update']} ${docker_setting}> Update Automatically ${docker_msg | n}
|
||||||
|
@@ -98,6 +98,7 @@ CREATEPID = False
|
|||||||
PIDFILE = None
|
PIDFILE = None
|
||||||
NOFORK = False
|
NOFORK = False
|
||||||
DOCKER = False
|
DOCKER = False
|
||||||
|
SNAP = False
|
||||||
FROZEN = False
|
FROZEN = False
|
||||||
|
|
||||||
SCHED = None
|
SCHED = None
|
||||||
@@ -194,6 +195,8 @@ def initialize(config_file):
|
|||||||
|
|
||||||
if DOCKER:
|
if DOCKER:
|
||||||
build = '[Docker] '
|
build = '[Docker] '
|
||||||
|
elif SNAP:
|
||||||
|
build = '[Snap] '
|
||||||
elif FROZEN:
|
elif FROZEN:
|
||||||
build = '[Bundle] '
|
build = '[Bundle] '
|
||||||
else:
|
else:
|
||||||
|
@@ -257,7 +257,7 @@ def import_tautulli_config(config=None, backup=False):
|
|||||||
# Remove keys that should not be imported
|
# Remove keys that should not be imported
|
||||||
for key in _DO_NOT_IMPORT_KEYS:
|
for key in _DO_NOT_IMPORT_KEYS:
|
||||||
delattr(imported_config, key)
|
delattr(imported_config, key)
|
||||||
if plexpy.DOCKER:
|
if plexpy.DOCKER or plexpy.SNAP:
|
||||||
for key in _DO_NOT_IMPORT_KEYS_DOCKER:
|
for key in _DO_NOT_IMPORT_KEYS_DOCKER:
|
||||||
delattr(imported_config, key)
|
delattr(imported_config, key)
|
||||||
|
|
||||||
@@ -540,3 +540,9 @@ class Config(object):
|
|||||||
self.JWT_UPDATE_SECRET = True
|
self.JWT_UPDATE_SECRET = True
|
||||||
|
|
||||||
self.CONFIG_VERSION = 16
|
self.CONFIG_VERSION = 16
|
||||||
|
|
||||||
|
if self.CONFIG_VERSION == 16:
|
||||||
|
if plexpy.SNAP:
|
||||||
|
self.PLEXPY_AUTO_UPDATE = 0
|
||||||
|
|
||||||
|
self.CONFIG_VERSION = 17
|
||||||
|
@@ -1885,7 +1885,8 @@ class Export(object):
|
|||||||
elif self.media_type == 'playlist' and 'item' in self._custom_fields:
|
elif self.media_type == 'playlist' and 'item' in self._custom_fields:
|
||||||
export_attrs_set.update(self._custom_fields['item'])
|
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
|
self._reload_check_files = True
|
||||||
|
|
||||||
for attr in export_attrs_set:
|
for attr in export_attrs_set:
|
||||||
|
@@ -19,11 +19,11 @@ from __future__ import unicode_literals
|
|||||||
from future.builtins import object
|
from future.builtins import object
|
||||||
from future.builtins import str
|
from future.builtins import str
|
||||||
|
|
||||||
from functools import partial
|
|
||||||
from multiprocessing.dummy import Pool as ThreadPool
|
from multiprocessing.dummy import Pool as ThreadPool
|
||||||
from future.moves.urllib.parse import urljoin
|
from future.moves.urllib.parse import urljoin
|
||||||
|
|
||||||
import certifi
|
import certifi
|
||||||
|
import requests
|
||||||
import urllib3
|
import urllib3
|
||||||
|
|
||||||
import plexpy
|
import plexpy
|
||||||
@@ -41,6 +41,7 @@ class HTTPHandler(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, urls, headers=None, token=None, timeout=10, ssl_verify=True, silent=False):
|
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
|
self._silent = silent
|
||||||
|
|
||||||
if isinstance(urls, str):
|
if isinstance(urls, str):
|
||||||
@@ -51,24 +52,34 @@ class HTTPHandler(object):
|
|||||||
if headers:
|
if headers:
|
||||||
self.headers = headers
|
self.headers = headers
|
||||||
else:
|
else:
|
||||||
self.headers = {'X-Plex-Product': plexpy.common.PRODUCT,
|
self.headers = {
|
||||||
'X-Plex-Version': plexpy.common.RELEASE,
|
'X-Plex-Product': plexpy.common.PRODUCT,
|
||||||
'X-Plex-Client-Identifier': plexpy.CONFIG.PMS_UUID,
|
'X-Plex-Version': plexpy.common.RELEASE,
|
||||||
'X-Plex-Platform': plexpy.common.PLATFORM,
|
'X-Plex-Client-Identifier': plexpy.CONFIG.PMS_UUID,
|
||||||
'X-Plex-Platform-Version': plexpy.common.PLATFORM_RELEASE,
|
'X-Plex-Platform': plexpy.common.PLATFORM,
|
||||||
'X-Plex-Device': '{} {}'.format(plexpy.common.PLATFORM,
|
'X-Plex-Platform-Version': plexpy.common.PLATFORM_RELEASE,
|
||||||
plexpy.common.PLATFORM_RELEASE),
|
'X-Plex-Device': '{} {}'.format(plexpy.common.PLATFORM,
|
||||||
'X-Plex-Device-Name': plexpy.common.PLATFORM_DEVICE_NAME
|
plexpy.common.PLATFORM_RELEASE),
|
||||||
}
|
'X-Plex-Device-Name': plexpy.common.PLATFORM_DEVICE_NAME
|
||||||
|
}
|
||||||
|
|
||||||
self.token = token
|
self.token = token
|
||||||
if self.token:
|
if self.token:
|
||||||
self.headers['X-Plex-Token'] = self.token
|
self.headers['X-Plex-Token'] = self.token
|
||||||
|
|
||||||
|
self._session = requests.Session()
|
||||||
self.timeout = timeout
|
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,
|
def make_request(self,
|
||||||
uri=None,
|
uri=None,
|
||||||
@@ -96,7 +107,7 @@ class HTTPHandler(object):
|
|||||||
self.timeout = timeout or self.timeout
|
self.timeout = timeout or self.timeout
|
||||||
self.request_kwargs = request_kwargs
|
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.")
|
logger.debug("HTTP request made but unsupported request type given.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -115,7 +126,7 @@ class HTTPHandler(object):
|
|||||||
return responses[0]
|
return responses[0]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.debug("HTTP request made but no enpoint given.")
|
logger.debug("HTTP request made but no uri endpoint provided.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _http_requests_pool(self, urls, workers=10, chunk=None):
|
def _http_requests_pool(self, urls, workers=10, chunk=None):
|
||||||
@@ -128,20 +139,13 @@ class HTTPHandler(object):
|
|||||||
if len(urls) == 0:
|
if len(urls) == 0:
|
||||||
chunk = 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:
|
if len(urls) == 1:
|
||||||
yield part(urls[0])
|
yield self._http_requests_single(urls[0])
|
||||||
else:
|
else:
|
||||||
pool = ThreadPool(workers)
|
pool = ThreadPool(workers)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for work in pool.imap_unordered(part, urls, chunk):
|
for work in pool.imap_unordered(self._http_requests_single, urls, chunk):
|
||||||
yield work
|
yield work
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if not self._silent:
|
if not self._silent:
|
||||||
@@ -150,34 +154,40 @@ class HTTPHandler(object):
|
|||||||
pool.close()
|
pool.close()
|
||||||
pool.join()
|
pool.join()
|
||||||
|
|
||||||
def _http_requests_urllib3(self, url, session):
|
def _http_requests_single(self, url):
|
||||||
"""Request the data from the url"""
|
"""Request the data from the url"""
|
||||||
|
error_msg = "Failed to access uri endpoint %s. " % self.uri
|
||||||
try:
|
try:
|
||||||
r = session.request(self.request_type, url, headers=self.headers, fields=self.data,
|
r = self._session.request(self.request_type, url, headers=self.headers, data=self.data,
|
||||||
timeout=self.timeout, **self.request_kwargs)
|
timeout=self.timeout, verify=self.ssl_verify, **self.request_kwargs)
|
||||||
except IOError as e:
|
r.raise_for_status()
|
||||||
|
except requests.exceptions.Timeout as e:
|
||||||
if not self._silent:
|
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
|
return None
|
||||||
except Exception as e:
|
except requests.exceptions.SSLError as e:
|
||||||
if not self._silent:
|
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
|
return None
|
||||||
except:
|
except requests.exceptions.HTTPError as e:
|
||||||
if not self._silent:
|
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
|
return None
|
||||||
|
|
||||||
response_status = r.status
|
response_status = r.status_code
|
||||||
response_content = r.data
|
response_content = r.content
|
||||||
response_headers = r.headers
|
response_headers = r.headers
|
||||||
|
|
||||||
if response_status in (200, 201):
|
if response_status in (200, 201):
|
||||||
return self._http_format_output(response_content, response_headers)
|
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):
|
def _http_format_output(self, response_content, response_headers):
|
||||||
"""Formats the request response to the desired type"""
|
"""Formats the request response to the desired type"""
|
||||||
|
@@ -66,7 +66,8 @@ class MacOSSystemTray(object):
|
|||||||
self.menu[2].state = plexpy.CONFIG.LAUNCH_STARTUP
|
self.menu[2].state = plexpy.CONFIG.LAUNCH_STARTUP
|
||||||
self.menu[3].state = plexpy.CONFIG.LAUNCH_BROWSER
|
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):
|
def start(self):
|
||||||
logger.info("Launching MacOS menu bar icon.")
|
logger.info("Launching MacOS menu bar icon.")
|
||||||
|
@@ -854,8 +854,8 @@ class Notifier(object):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
verify_msg = ""
|
verify_msg = ""
|
||||||
if response is not None and response.status_code >= 400 and response.status_code < 500:
|
if response is not None and 400 <= response.status_code < 500:
|
||||||
verify_msg = " Verify you notification agent settings are correct."
|
verify_msg = " Verify your notification agent settings are correct."
|
||||||
|
|
||||||
logger.error("Tautulli Notifiers :: {name} notification failed.{msg}".format(msg=verify_msg, name=self.NAME))
|
logger.error("Tautulli Notifiers :: {name} notification failed.{msg}".format(msg=verify_msg, name=self.NAME))
|
||||||
|
|
||||||
|
@@ -213,6 +213,7 @@ def mask_session_info(list_of_dicts, mask_metadata=True):
|
|||||||
|
|
||||||
keys_to_mask = {'user_id': '',
|
keys_to_mask = {'user_id': '',
|
||||||
'user': 'Plex User',
|
'user': 'Plex User',
|
||||||
|
'username': 'Plex User',
|
||||||
'friendly_name': 'Plex User',
|
'friendly_name': 'Plex User',
|
||||||
'user_thumb': common.DEFAULT_USER_THUMB,
|
'user_thumb': common.DEFAULT_USER_THUMB,
|
||||||
'ip_address': 'N/A',
|
'ip_address': 'N/A',
|
||||||
|
@@ -18,4 +18,4 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
PLEXPY_BRANCH = "master"
|
PLEXPY_BRANCH = "master"
|
||||||
PLEXPY_RELEASE_VERSION = "v2.6.2"
|
PLEXPY_RELEASE_VERSION = "v2.6.4"
|
@@ -133,7 +133,13 @@ def get_version():
|
|||||||
return cur_commit_hash, remote_name, branch_name
|
return cur_commit_hash, remote_name, branch_name
|
||||||
|
|
||||||
else:
|
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()
|
current_version, current_branch = get_version_from_file()
|
||||||
return current_version, 'origin', current_branch
|
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:
|
if not plexpy.CURRENT_VERSION:
|
||||||
plexpy.UPDATE_AVAILABLE = None
|
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.common.RELEASE != plexpy.LATEST_RELEASE:
|
||||||
plexpy.UPDATE_AVAILABLE = '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'
|
plexpy.UPDATE_AVAILABLE = 'commit'
|
||||||
else:
|
else:
|
||||||
plexpy.UPDATE_AVAILABLE = False
|
plexpy.UPDATE_AVAILABLE = False
|
||||||
@@ -265,7 +274,11 @@ def check_github(scheduler=False, notify=False, use_cache=False):
|
|||||||
'plexpy_update_commit': plexpy.LATEST_VERSION,
|
'plexpy_update_commit': plexpy.LATEST_VERSION,
|
||||||
'plexpy_update_behind': plexpy.COMMITS_BEHIND})
|
'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:
|
||||||
logger.info('Running automatic update.')
|
logger.info('Running automatic update.')
|
||||||
plexpy.shutdown(restart=True, update=True)
|
plexpy.shutdown(restart=True, update=True)
|
||||||
|
|
||||||
@@ -276,10 +289,14 @@ def check_github(scheduler=False, notify=False, use_cache=False):
|
|||||||
|
|
||||||
|
|
||||||
def update():
|
def update():
|
||||||
|
if plexpy.PYTHON2:
|
||||||
|
logger.warn('Tautulli is running using Python 2. Unable to update.')
|
||||||
|
return
|
||||||
|
|
||||||
if not plexpy.UPDATE_AVAILABLE:
|
if not plexpy.UPDATE_AVAILABLE:
|
||||||
return
|
return
|
||||||
|
|
||||||
if plexpy.INSTALL_TYPE in ('docker', 'windows', 'macos'):
|
if plexpy.INSTALL_TYPE in ('docker', 'snap', 'windows', 'macos'):
|
||||||
return
|
return
|
||||||
|
|
||||||
elif plexpy.INSTALL_TYPE == 'git':
|
elif plexpy.INSTALL_TYPE == 'git':
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
|
# 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
|
# Form based authentication for CherryPy. Requires the
|
||||||
# Session tool to be loaded.
|
# Session tool to be loaded.
|
||||||
|
|
||||||
|
@@ -4309,7 +4309,7 @@ class WebInterface(object):
|
|||||||
plexpy.CONFIG.GIT_REPO,
|
plexpy.CONFIG.GIT_REPO,
|
||||||
plexpy.CURRENT_VERSION,
|
plexpy.CURRENT_VERSION,
|
||||||
plexpy.LATEST_VERSION))
|
plexpy.LATEST_VERSION))
|
||||||
}
|
}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
update = {'result': 'success',
|
update = {'result': 'success',
|
||||||
@@ -4317,7 +4317,7 @@ class WebInterface(object):
|
|||||||
'message': 'Tautulli is up to date.'
|
'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
|
update['install_type'] = plexpy.INSTALL_TYPE
|
||||||
|
|
||||||
return update
|
return update
|
||||||
@@ -4351,7 +4351,9 @@ class WebInterface(object):
|
|||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
def update(self, **kwargs):
|
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")
|
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT + "home")
|
||||||
|
|
||||||
# Show changelog after updating
|
# Show changelog after updating
|
||||||
|
65
snap/snapcraft.yaml
Normal file
65
snap/snapcraft.yaml
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
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_COMMON/Tautulli
|
||||||
|
--config $SNAP_COMMON/Tautulli/config.ini
|
||||||
|
--quiet
|
||||||
|
--nolaunch
|
||||||
|
daemon: simple
|
||||||
|
restart-condition: on-abnormal
|
||||||
|
restart-delay: 5s
|
||||||
|
plugs:
|
||||||
|
- network
|
||||||
|
- network-bind
|
||||||
|
environment:
|
||||||
|
TAUTULLI_SNAP: "True"
|
Reference in New Issue
Block a user