Compare commits
52 Commits
v2.2.1
...
v2.2.2-bet
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6d5c320701 | ||
![]() |
400a189455 | ||
![]() |
b7d03a4f31 | ||
![]() |
523e6421be | ||
![]() |
e0cd6f7071 | ||
![]() |
38db0b7a70 | ||
![]() |
f39ecd89a7 | ||
![]() |
f7f76d82b6 | ||
![]() |
9097e79e4f | ||
![]() |
88711e7601 | ||
![]() |
d0fa83bb8c | ||
![]() |
1271458f83 | ||
![]() |
2ae09a07e6 | ||
![]() |
33d860384c | ||
![]() |
a4eda99a4a | ||
![]() |
752c7badd2 | ||
![]() |
6399c90642 | ||
![]() |
97089846e9 | ||
![]() |
4de7884e39 | ||
![]() |
440adfb914 | ||
![]() |
5f26d0085d | ||
![]() |
f484604c69 | ||
![]() |
899d2fbf9d | ||
![]() |
6a87dc9c40 | ||
![]() |
104e2929df | ||
![]() |
faac6b11c2 | ||
![]() |
377a23478e | ||
![]() |
c979e78802 | ||
![]() |
38f64c7d85 | ||
![]() |
1091a64863 | ||
![]() |
23de9616f1 | ||
![]() |
198e7767dc | ||
![]() |
aa31bf1a19 | ||
![]() |
f366304c50 | ||
![]() |
ce289995ff | ||
![]() |
ca2b4085c9 | ||
![]() |
1d08069162 | ||
![]() |
bcbfaae630 | ||
![]() |
ae9df92d28 | ||
![]() |
47610323b0 | ||
![]() |
d1f1763919 | ||
![]() |
1326ad8708 | ||
![]() |
6e09e509bd | ||
![]() |
e8d0557852 | ||
![]() |
aac705f465 | ||
![]() |
009971901b | ||
![]() |
1ffd6c0ea1 | ||
![]() |
50ce29cc64 | ||
![]() |
e4ec24be26 | ||
![]() |
04765288d7 | ||
![]() |
8fdd0ba0d9 | ||
![]() |
aa5affe366 |
@@ -1,5 +1,8 @@
|
|||||||
.git
|
.git
|
||||||
.github
|
.github
|
||||||
.gitignore
|
.gitignore
|
||||||
|
contrib
|
||||||
|
init-scripts
|
||||||
|
pylintrc
|
||||||
*.md
|
*.md
|
||||||
!CHANGELOG*.md
|
!CHANGELOG*.md
|
||||||
|
83
.github/workflows/publish-docker.yml
vendored
Normal file
83
.github/workflows/publish-docker.yml
vendored
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
name: Publish Docker
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master, beta, nightly]
|
||||||
|
tags: [v*]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Prepare
|
||||||
|
id: prepare
|
||||||
|
run: |
|
||||||
|
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||||
|
echo ::set-output name=tag::${GITHUB_REF#refs/tags/}
|
||||||
|
elif [[ $GITHUB_REF == refs/heads/master ]]; then
|
||||||
|
echo ::set-output name=tag::latest
|
||||||
|
else
|
||||||
|
echo ::set-output name=tag::${GITHUB_REF#refs/heads/}
|
||||||
|
fi
|
||||||
|
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||||
|
echo ::set-output name=branch::master
|
||||||
|
else
|
||||||
|
echo ::set-output name=branch::${GITHUB_REF#refs/heads/}
|
||||||
|
fi
|
||||||
|
echo ::set-output name=commit::${GITHUB_SHA}
|
||||||
|
echo ::set-output name=build_date::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||||
|
echo ::set-output name=docker_platforms::linux/amd64,linux/arm64,linux/arm
|
||||||
|
echo ::set-output name=docker_image::tautulli/tautulli
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: crazy-max/ghaction-docker-buildx@v1
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Docker Buildx (no push)
|
||||||
|
run: |
|
||||||
|
docker buildx build \
|
||||||
|
--platform ${{ steps.prepare.outputs.docker_platforms }} \
|
||||||
|
--output "type=image,push=false" \
|
||||||
|
--build-arg "TAG=${{ steps.prepare.outputs.tag }}" \
|
||||||
|
--build-arg "BRANCH=${{ steps.prepare.outputs.branch }}" \
|
||||||
|
--build-arg "COMMIT=${{ steps.prepare.outputs.commit }}" \
|
||||||
|
--build-arg "BUILD_DATE=${{ steps.prepare.outputs.build_date }}" \
|
||||||
|
--tag "${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.tag }}" \
|
||||||
|
--file Dockerfile .
|
||||||
|
|
||||||
|
- name: Docker Login
|
||||||
|
if: success()
|
||||||
|
env:
|
||||||
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
echo "${DOCKER_PASSWORD}" | docker login --username "${{ secrets.DOCKER_USERNAME }}" --password-stdin
|
||||||
|
|
||||||
|
- name: Docker Buildx (push)
|
||||||
|
if: success()
|
||||||
|
run: |
|
||||||
|
docker buildx build \
|
||||||
|
--platform ${{ steps.prepare.outputs.docker_platforms }} \
|
||||||
|
--output "type=image,push=true" \
|
||||||
|
--build-arg "TAG=${{ steps.prepare.outputs.tag }}" \
|
||||||
|
--build-arg "BRANCH=${{ steps.prepare.outputs.branch }}" \
|
||||||
|
--build-arg "COMMIT=${{ steps.prepare.outputs.commit }}" \
|
||||||
|
--build-arg "BUILD_DATE=${{ steps.prepare.outputs.build_date }}" \
|
||||||
|
--tag "${{ steps.prepare.outputs.docker_image }}:${{ steps.prepare.outputs.tag }}" \
|
||||||
|
--file Dockerfile .
|
||||||
|
|
||||||
|
- name: Clear
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
rm -f ${HOME}/.docker/config.json
|
||||||
|
|
||||||
|
- name: Post Status to Discord
|
||||||
|
uses: sarisia/actions-status-discord@v1
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
webhook: ${{ secrets.DISCORD_WEBHOOK }}
|
||||||
|
status: ${{ job.status }}
|
||||||
|
job: ${{ github.workflow }}
|
||||||
|
nofail: true
|
@@ -1,8 +1,7 @@
|
|||||||
name: Create Release
|
name: Publish Release
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags: [v*]
|
||||||
- 'v*'
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -10,7 +9,7 @@ jobs:
|
|||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@master
|
uses: actions/checkout@master
|
||||||
- name: Get Release Version
|
- name: Get Release Version
|
||||||
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF/refs\/tags\//}
|
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF#refs/tags/}
|
||||||
- name: Get Changelog
|
- name: Get Changelog
|
||||||
run: echo ::set-env name=CHANGELOG::"$( sed -n '/^## /{p; :loop n; p; /^## /q; b loop}' CHANGELOG.md | sed '$d' | sed '$d' | sed '$d' | sed ':a;N;$!ba;s/\n/%0A/g' )"
|
run: echo ::set-env name=CHANGELOG::"$( sed -n '/^## /{p; :loop n; p; /^## /q; b loop}' CHANGELOG.md | sed '$d' | sed '$d' | sed '$d' | sed ':a;N;$!ba;s/\n/%0A/g' )"
|
||||||
- name: Create Release
|
- name: Create Release
|
30
.github/workflows/publishdocker-branch.yml
vendored
30
.github/workflows/publishdocker-branch.yml
vendored
@@ -1,30 +0,0 @@
|
|||||||
name: Publish Docker Branch
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master, beta, nightly]
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout Code
|
|
||||||
uses: actions/checkout@master
|
|
||||||
- name: Get Branch
|
|
||||||
run: echo ::set-env name=BRANCH::${GITHUB_REF#refs/heads/}
|
|
||||||
- name: Publish to Registry
|
|
||||||
uses: elgohr/Publish-Docker-Github-Action@master
|
|
||||||
env:
|
|
||||||
VERSION: ${{ github.sha }}
|
|
||||||
with:
|
|
||||||
name: tautulli/tautulli
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
buildargs: VERSION, BRANCH
|
|
||||||
- name: Post Status to Discord
|
|
||||||
uses: sarisia/actions-status-discord@v1
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK }}
|
|
||||||
status: ${{ job.status }}
|
|
||||||
job: ${{ github.workflow }}
|
|
||||||
nofail: true
|
|
32
.github/workflows/publishdocker-release.yml
vendored
32
.github/workflows/publishdocker-release.yml
vendored
@@ -1,32 +0,0 @@
|
|||||||
name: Publish Docker Release
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout Code
|
|
||||||
uses: actions/checkout@master
|
|
||||||
- name: Get Branch
|
|
||||||
run: echo ::set-env name=BRANCH::${GITHUB_REF/refs\/tags\//}
|
|
||||||
- name: Publish to Registry
|
|
||||||
uses: elgohr/Publish-Docker-Github-Action@master
|
|
||||||
env:
|
|
||||||
VERSION: ${{ github.sha }}
|
|
||||||
with:
|
|
||||||
name: tautulli/tautulli
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
buildargs: VERSION, BRANCH
|
|
||||||
tags: ${{ env.BRANCH }}
|
|
||||||
- name: Post Status to Discord
|
|
||||||
uses: sarisia/actions-status-discord@v1
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
webhook: ${{ secrets.DISCORD_WEBHOOK }}
|
|
||||||
status: ${{ job.status }}
|
|
||||||
job: ${{ github.workflow }}
|
|
||||||
nofail: true
|
|
51
API.md
51
API.md
@@ -88,7 +88,8 @@ Required parameters:
|
|||||||
section_id (str): The id of the Plex library section
|
section_id (str): The id of the Plex library section
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
None
|
server_id (str): The Plex server identifier of the library section
|
||||||
|
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
@@ -103,7 +104,7 @@ Required parameters:
|
|||||||
user_id (str): The id of the Plex user
|
user_id (str): The id of the Plex user
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
None
|
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
@@ -114,6 +115,21 @@ Returns:
|
|||||||
Delete and recreate the cache directory.
|
Delete and recreate the cache directory.
|
||||||
|
|
||||||
|
|
||||||
|
### delete_history
|
||||||
|
Delete history rows from Tautulli.
|
||||||
|
|
||||||
|
```
|
||||||
|
Required parameters:
|
||||||
|
row_ids (str): Comma separated row ids to delete, e.g. "65,110,2,3645"
|
||||||
|
|
||||||
|
Optional parameters:
|
||||||
|
None
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
### delete_hosted_images
|
### delete_hosted_images
|
||||||
Delete the images uploaded to image hosting services.
|
Delete the images uploaded to image hosting services.
|
||||||
|
|
||||||
@@ -146,7 +162,8 @@ Required parameters:
|
|||||||
section_id (str): The id of the Plex library section
|
section_id (str): The id of the Plex library section
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
None
|
server_id (str): The Plex server identifier of the library section
|
||||||
|
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
@@ -294,7 +311,7 @@ Required parameters:
|
|||||||
user_id (str): The id of the Plex user
|
user_id (str): The id of the Plex user
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
None
|
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
@@ -726,7 +743,6 @@ Returns:
|
|||||||
"group_count": 1,
|
"group_count": 1,
|
||||||
"group_ids": "1124",
|
"group_ids": "1124",
|
||||||
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
||||||
"id": 1124,
|
|
||||||
"ip_address": "xxx.xxx.xxx.xxx",
|
"ip_address": "xxx.xxx.xxx.xxx",
|
||||||
"live": 0,
|
"live": 0,
|
||||||
"media_index": 17,
|
"media_index": 17,
|
||||||
@@ -742,6 +758,7 @@ Returns:
|
|||||||
"player": "Castle-PC",
|
"player": "Castle-PC",
|
||||||
"rating_key": 4348,
|
"rating_key": 4348,
|
||||||
"reference_id": 1123,
|
"reference_id": 1123,
|
||||||
|
"row_id": 1124,
|
||||||
"session_key": null,
|
"session_key": null,
|
||||||
"started": 1462688107,
|
"started": 1462688107,
|
||||||
"state": null,
|
"state": null,
|
||||||
@@ -853,6 +870,7 @@ Returns:
|
|||||||
[{"art": "/:/resources/show-fanart.jpg",
|
[{"art": "/:/resources/show-fanart.jpg",
|
||||||
"child_count": "3745",
|
"child_count": "3745",
|
||||||
"count": "62",
|
"count": "62",
|
||||||
|
"is_active": 1,
|
||||||
"parent_count": "240",
|
"parent_count": "240",
|
||||||
"section_id": "2",
|
"section_id": "2",
|
||||||
"section_name": "TV Shows",
|
"section_name": "TV Shows",
|
||||||
@@ -894,7 +912,8 @@ Returns:
|
|||||||
"do_notify_created": "Checked",
|
"do_notify_created": "Checked",
|
||||||
"duration": 1578037,
|
"duration": 1578037,
|
||||||
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
||||||
"id": 1128,
|
"histroy_row_id": 1128,
|
||||||
|
"is_active": 1,
|
||||||
"keep_history": "Checked",
|
"keep_history": "Checked",
|
||||||
"labels": [],
|
"labels": [],
|
||||||
"last_accessed": 1462693216,
|
"last_accessed": 1462693216,
|
||||||
@@ -910,9 +929,11 @@ Returns:
|
|||||||
"parent_title": "",
|
"parent_title": "",
|
||||||
"plays": 772,
|
"plays": 772,
|
||||||
"rating_key": 153037,
|
"rating_key": 153037,
|
||||||
|
"row_id": 1,
|
||||||
"section_id": 2,
|
"section_id": 2,
|
||||||
"section_name": "TV Shows",
|
"section_name": "TV Shows",
|
||||||
"section_type": "Show",
|
"section_type": "Show",
|
||||||
|
"server_id": "ds48g4r354a8v9byrrtr697g3g79w",
|
||||||
"thumb": "/library/metadata/153036/thumb/1462175062",
|
"thumb": "/library/metadata/153036/thumb/1462175062",
|
||||||
"year": 2016
|
"year": 2016
|
||||||
},
|
},
|
||||||
@@ -940,13 +961,16 @@ Returns:
|
|||||||
"deleted_section": 0,
|
"deleted_section": 0,
|
||||||
"do_notify": 1,
|
"do_notify": 1,
|
||||||
"do_notify_created": 1,
|
"do_notify_created": 1,
|
||||||
|
"is_active": 1,
|
||||||
"keep_history": 1,
|
"keep_history": 1,
|
||||||
"library_art": "/:/resources/movie-fanart.jpg",
|
"library_art": "/:/resources/movie-fanart.jpg",
|
||||||
"library_thumb": "/:/resources/movie.png",
|
"library_thumb": "/:/resources/movie.png",
|
||||||
"parent_count": null,
|
"parent_count": null,
|
||||||
|
"row_id": 1,
|
||||||
"section_id": 1,
|
"section_id": 1,
|
||||||
"section_name": "Movies",
|
"section_name": "Movies",
|
||||||
"section_type": "movie"
|
"section_type": "movie",
|
||||||
|
"server_id": "ds48g4r354a8v9byrrtr697g3g79w"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1066,6 +1090,7 @@ Required parameters:
|
|||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
grouping (int): 0 or 1
|
grouping (int): 0 or 1
|
||||||
|
query_days (str): Comma separated days, e.g. "1,7,30,0"
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
json:
|
json:
|
||||||
@@ -2222,10 +2247,13 @@ Returns:
|
|||||||
"do_notify": 1,
|
"do_notify": 1,
|
||||||
"email": "Jon.Snow.1337@CastleBlack.com",
|
"email": "Jon.Snow.1337@CastleBlack.com",
|
||||||
"friendly_name": "Jon Snow",
|
"friendly_name": "Jon Snow",
|
||||||
|
"is_active": 1,
|
||||||
|
"is_admin": 0,
|
||||||
"is_allow_sync": 1,
|
"is_allow_sync": 1,
|
||||||
"is_home_user": 1,
|
"is_home_user": 1,
|
||||||
"is_restricted": 0,
|
"is_restricted": 0,
|
||||||
"keep_history": 1,
|
"keep_history": 1,
|
||||||
|
"row_id": 1,
|
||||||
"shared_libraries": ["10", "1", "4", "5", "15", "20", "2"],
|
"shared_libraries": ["10", "1", "4", "5", "15", "20", "2"],
|
||||||
"user_id": 133788,
|
"user_id": 133788,
|
||||||
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
||||||
@@ -2378,6 +2406,7 @@ Required parameters:
|
|||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
grouping (int): 0 or 1
|
grouping (int): 0 or 1
|
||||||
|
query_days (str): Comma separated days, e.g. "1,7,30,0"
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
json:
|
json:
|
||||||
@@ -2421,11 +2450,13 @@ Returns:
|
|||||||
"filter_music": "",
|
"filter_music": "",
|
||||||
"filter_photos": "",
|
"filter_photos": "",
|
||||||
"filter_tv": "",
|
"filter_tv": "",
|
||||||
|
"is_active": 1,
|
||||||
"is_admin": 0,
|
"is_admin": 0,
|
||||||
"is_allow_sync": 1,
|
"is_allow_sync": 1,
|
||||||
"is_home_user": 1,
|
"is_home_user": 1,
|
||||||
"is_restricted": 0,
|
"is_restricted": 0,
|
||||||
"keep_history": 1,
|
"keep_history": 1,
|
||||||
|
"row_id": 1,
|
||||||
"server_token": "PU9cMuQZxJKFBtGqHk68",
|
"server_token": "PU9cMuQZxJKFBtGqHk68",
|
||||||
"shared_libraries": "1;2;3",
|
"shared_libraries": "1;2;3",
|
||||||
"thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
"thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
||||||
@@ -2465,8 +2496,9 @@ Returns:
|
|||||||
"duration": 2998290,
|
"duration": 2998290,
|
||||||
"friendly_name": "Jon Snow",
|
"friendly_name": "Jon Snow",
|
||||||
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
||||||
"id": 1121,
|
"history_row_id": 1121,
|
||||||
"ip_address": "xxx.xxx.xxx.xxx",
|
"ip_address": "xxx.xxx.xxx.xxx",
|
||||||
|
"is_active": 1,
|
||||||
"keep_history": "Checked",
|
"keep_history": "Checked",
|
||||||
"last_played": "Game of Thrones - The Red Woman",
|
"last_played": "Game of Thrones - The Red Woman",
|
||||||
"last_seen": 1462591869,
|
"last_seen": 1462591869,
|
||||||
@@ -2480,6 +2512,7 @@ Returns:
|
|||||||
"player": "Plex Web (Chrome)",
|
"player": "Plex Web (Chrome)",
|
||||||
"plays": 487,
|
"plays": 487,
|
||||||
"rating_key": 153037,
|
"rating_key": 153037,
|
||||||
|
"row_id": 1,
|
||||||
"thumb": "/library/metadata/153036/thumb/1462175062",
|
"thumb": "/library/metadata/153036/thumb/1462175062",
|
||||||
"transcode_decision": "transcode",
|
"transcode_decision": "transcode",
|
||||||
"user_id": 133788,
|
"user_id": 133788,
|
||||||
@@ -2741,7 +2774,7 @@ Returns:
|
|||||||
### sql
|
### sql
|
||||||
Query the Tautulli database with raw SQL. Automatically makes a backup of
|
Query the Tautulli database with raw SQL. Automatically makes a backup of
|
||||||
the database if the latest backup is older then 24h. `api_sql` must be
|
the database if the latest backup is older then 24h. `api_sql` must be
|
||||||
manually enabled in the config file.
|
manually enabled in the config file while Tautulli is shut down.
|
||||||
|
|
||||||
```
|
```
|
||||||
Required parameters:
|
Required parameters:
|
||||||
|
28
CHANGELOG.md
28
CHANGELOG.md
@@ -1,5 +1,33 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v2.2.2-beta (2020-04-12)
|
||||||
|
|
||||||
|
* Notifications:
|
||||||
|
* New: Added notification trigger for Tautulli database corruption.
|
||||||
|
* New: Added TAUTULLI_PYTHON_VERSION to script notification environment variables.
|
||||||
|
* Fix: Notification grouping by season/album and show/artist not enabled by default.
|
||||||
|
* Change: The file size notification parameter is now reported in SI units. (Thanks @aaronldunlap)
|
||||||
|
* UI:
|
||||||
|
* Fix: Delete lookup info from the media info page failing.
|
||||||
|
* New: Added icon on the users table to indicate if the user is not on the Plex server.
|
||||||
|
* New: Added icon on the libraries table to indicate if the library is not on the Plex server.
|
||||||
|
* Fix: XBMC platform icon not being redirected to the Kodi platform icon.
|
||||||
|
* Change: Improved deleting libraries so libraries with the same section ID are not also deleted.
|
||||||
|
* API:
|
||||||
|
* Fix: Returning XML for the API failing due to unicode characters.
|
||||||
|
* Fix: Grouping parameter for various API commands not falling back to default setting.
|
||||||
|
* New: Added time_queries parameter to get_library_watch_time_stats and get_user_watch_time_stats API command. (Thanks @KaasKop97)
|
||||||
|
* New: Added an "is_active" return value to the get_user, get_users, get_library, and get_libraries API commands which indicates if the user or library is on the Plex server.
|
||||||
|
* New: Added delete_history API command.
|
||||||
|
* Change: Added optional parameter for row_ids for delete_library, delete_user, delete_all_library_history, and delete_all_user_history API commands.
|
||||||
|
* Mobile App:
|
||||||
|
* Fix: Temporary device token not being invalidated after cancelling device registration.
|
||||||
|
* Other:
|
||||||
|
* Fix: Update failing on CentOS due to an older git version.
|
||||||
|
* Fix: Manifest file for creating a web app had incorrect info.
|
||||||
|
* New: Docker images updated to support ARM platforms.
|
||||||
|
|
||||||
|
|
||||||
## v2.2.1 (2020-03-28)
|
## v2.2.1 (2020-03-28)
|
||||||
|
|
||||||
* Notifications:
|
* Notifications:
|
||||||
|
18
Dockerfile
18
Dockerfile
@@ -1,9 +1,9 @@
|
|||||||
FROM python:2.7.17-slim
|
FROM tautulli/tautulli-baseimage:latest
|
||||||
|
|
||||||
LABEL maintainer="TheMeanCanEHdian"
|
LABEL maintainer="Tautulli"
|
||||||
|
|
||||||
ARG VERSION
|
|
||||||
ARG BRANCH
|
ARG BRANCH
|
||||||
|
ARG COMMIT
|
||||||
|
|
||||||
ENV TAUTULLI_DOCKER=True
|
ENV TAUTULLI_DOCKER=True
|
||||||
ENV TZ=UTC
|
ENV TZ=UTC
|
||||||
@@ -11,16 +11,8 @@ ENV TZ=UTC
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
RUN \
|
RUN \
|
||||||
apt-get -q -y update --no-install-recommends && \
|
echo ${BRANCH} > /app/branch.txt && \
|
||||||
apt-get install -q -y --no-install-recommends \
|
echo ${COMMIT} > /app/version.txt
|
||||||
curl && \
|
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
|
||||||
pip install --no-cache-dir --upgrade pip && \
|
|
||||||
pip install --no-cache-dir --upgrade \
|
|
||||||
pycryptodomex \
|
|
||||||
pyopenssl && \
|
|
||||||
echo ${VERSION} > /app/version.txt && \
|
|
||||||
echo ${BRANCH} > /app/branch.txt
|
|
||||||
|
|
||||||
COPY . /app
|
COPY . /app
|
||||||
|
|
||||||
|
@@ -36,7 +36,7 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
|
|||||||
| Status | Branch: `master` | Branch: `beta` | Branch: `nightly` |
|
| Status | Branch: `master` | Branch: `beta` | Branch: `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) |
|
| 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=branch%3Amaster) | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=branch%3Abeta) | [](https://hub.docker.com/r/tautulli/tautulli) <br> [](https://github.com/Tautulli/Tautulli/actions?query=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) |
|
||||||
|
|
||||||
[](https://github.com/Tautulli/Tautulli-Wiki/wiki)
|
[](https://github.com/Tautulli/Tautulli-Wiki/wiki)
|
||||||
[](https://tautulli.com/discord)
|
[](https://tautulli.com/discord)
|
||||||
|
@@ -711,7 +711,6 @@ fieldset[disabled] .form-control {
|
|||||||
box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1);
|
box-shadow: 0 0 4px rgba(0,0,0,.3),inset 0 0 0 1px rgba(255,255,255,.1);
|
||||||
}
|
}
|
||||||
.users-poster-face {
|
.users-poster-face {
|
||||||
overflow: hidden;
|
|
||||||
float: left;
|
float: left;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
@@ -857,7 +856,6 @@ a .users-poster-face:hover {
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
.dashboard-activity-info-platform {
|
.dashboard-activity-info-platform {
|
||||||
padding: 6px !important;
|
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
@@ -1036,13 +1034,13 @@ a .users-poster-face:hover {
|
|||||||
}
|
}
|
||||||
.dashboard-activity-container:hover .progress-bar {
|
.dashboard-activity-container:hover .progress-bar {
|
||||||
color: rgba(255, 255, 255, 1);
|
color: rgba(255, 255, 255, 1);
|
||||||
background-image: -webkit-linear-gradient(left,rgba(0,0,0,0.25),0%,rgba(0,0,0,0),50px);
|
background-image: -webkit-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
||||||
background-image: -moz-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
background-image: -moz-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
||||||
background-image: linear-gradient(to left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
background-image: linear-gradient(to left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
||||||
}
|
}
|
||||||
.dashboard-activity-container:hover .buffer-bar {
|
.dashboard-activity-container:hover .buffer-bar {
|
||||||
color: rgba(255, 255, 255, 1);
|
color: rgba(255, 255, 255, 1);
|
||||||
background-image: -webkit-linear-gradient(left,rgba(0,0,0,0.25),0%,rgba(0,0,0,0),50px);
|
background-image: -webkit-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
||||||
background-image: -moz-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
background-image: -moz-linear-gradient(left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
||||||
background-image: linear-gradient(to left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
background-image: linear-gradient(to left,rgba(0,0,0,0.25) 0%,rgba(0,0,0,0) 50px);
|
||||||
}
|
}
|
||||||
@@ -1742,7 +1740,7 @@ a:hover .dashboard-recent-media-cover {
|
|||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-image: -webkit-gradient(linear,left 0,left 100%,from(rgba(0,0,0,.7)),to(rgba(0,0,0,.9)));
|
background-image: -webkit-gradient(linear,left 0,left 100%,from(rgba(0,0,0,.7)),to(rgba(0,0,0,.9)));
|
||||||
background-image: -webkit-linear-gradient(top,rgba(0,0,0,.7),0,rgba(0,0,0,.9),100%);
|
background-image: -webkit-linear-gradient(top,rgba(0,0,0,.7) 0,rgba(0,0,0,.9) 100%);
|
||||||
background-image: -moz-linear-gradient(top,rgba(0,0,0,.7) 0,rgba(0,0,0,.9) 100%);
|
background-image: -moz-linear-gradient(top,rgba(0,0,0,.7) 0,rgba(0,0,0,.9) 100%);
|
||||||
background-image: linear-gradient(to bottom,rgba(0,0,0,.7) 0,rgba(0,0,0,.9) 100%);
|
background-image: linear-gradient(to bottom,rgba(0,0,0,.7) 0,rgba(0,0,0,.9) 100%);
|
||||||
background-repeat: repeat-x;
|
background-repeat: repeat-x;
|
||||||
@@ -3119,6 +3117,21 @@ div.dataTables_info {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
.inactive-library-tooltip,
|
||||||
|
.inactive-user-tooltip {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.inactive-library-tooltip i.fa,
|
||||||
|
.inactive-user-tooltip i.fa {
|
||||||
|
color: #E5A00D;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
text-shadow: 0 0 2px rgba(0,0,0,.5);
|
||||||
|
}
|
||||||
.history-thumbnail-popover {
|
.history-thumbnail-popover {
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -3808,9 +3821,8 @@ a:hover .overlay-refresh-image:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
padding: 10px;
|
background-size: calc(100% - 20px) calc(100% - 20px) !important;
|
||||||
background-origin: content-box !important;
|
background-origin: content-box !important;
|
||||||
background-size: contain !important;
|
|
||||||
background-repeat: no-repeat !important;
|
background-repeat: no-repeat !important;
|
||||||
background-position: center !important;
|
background-position: center !important;
|
||||||
}
|
}
|
||||||
@@ -3920,7 +3932,7 @@ a:hover .overlay-refresh-image:hover {
|
|||||||
}
|
}
|
||||||
.platform-xbmc {
|
.platform-xbmc {
|
||||||
background-color: #3b4872;
|
background-color: #3b4872;
|
||||||
background-image: url(../images/platforms/xbmc.svg);
|
background-image: url(../images/platforms/kodi.svg);
|
||||||
}
|
}
|
||||||
.platform-xbox {
|
.platform-xbox {
|
||||||
background-color: #107c10;
|
background-color: #107c10;
|
||||||
|
@@ -143,7 +143,7 @@ DOCUMENTATION :: END
|
|||||||
<div id="platform-${sk}" class="dashboard-activity-info-platform${no_terminate} svg-icon platform-${data['platform_name']}" title="${data['platform']}"></div>
|
<div id="platform-${sk}" class="dashboard-activity-info-platform${no_terminate} svg-icon platform-${data['platform_name']}" title="${data['platform']}"></div>
|
||||||
% if _session['user_group'] == 'admin' and plexpy.CONFIG.PMS_PLEXPASS and data['session_id']:
|
% if _session['user_group'] == 'admin' and plexpy.CONFIG.PMS_PLEXPASS and data['session_id']:
|
||||||
<div class="dashboard-activity-terminate-session" id="terminate-button-${sk}" data-key="${sk}" data-id="${data['session_id']}" data-toggle="tooltip" title="Terminate Stream">
|
<div class="dashboard-activity-terminate-session" id="terminate-button-${sk}" data-key="${sk}" data-id="${data['session_id']}" data-toggle="tooltip" title="Terminate Stream">
|
||||||
<i class="fa fa-times" style="padding-top: 8px;"></i>
|
<i class="fa fa-times" style="padding-top: 10px;"></i>
|
||||||
</div>
|
</div>
|
||||||
% endif
|
% endif
|
||||||
</div>
|
</div>
|
||||||
|
@@ -185,18 +185,16 @@
|
|||||||
$('#deleteCount').text(history_to_delete.length);
|
$('#deleteCount').text(history_to_delete.length);
|
||||||
$('#confirm-modal-delete').modal();
|
$('#confirm-modal-delete').modal();
|
||||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||||
history_to_delete.forEach(function(row, idx) {
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'delete_history_rows',
|
url: 'delete_history_rows',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: { row_id: row },
|
data: { row_ids: history_to_delete.join(',') },
|
||||||
async: true,
|
async: true,
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
var msg = "History deleted";
|
var msg = "History deleted";
|
||||||
showMsg(msg, false, true, 2000);
|
showMsg(msg, false, true, 2000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
history_table.draw();
|
history_table.draw();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<browserconfig>
|
<browserconfig>
|
||||||
<msapplication>
|
<msapplication>
|
||||||
<tile>
|
<tile>
|
||||||
<square150x150logo src="${http_root}images/favicon/mstile-150x150.png?v=2.0.5"/>
|
<square150x150logo src="mstile-150x150.png?v=2.0.5"/>
|
||||||
<TileColor>#282a2d</TileColor>
|
<TileColor>#282a2d</TileColor>
|
||||||
</tile>
|
</tile>
|
||||||
</msapplication>
|
</msapplication>
|
||||||
|
@@ -1,18 +1,23 @@
|
|||||||
{
|
{
|
||||||
"name": "Tautulli",
|
"name": "Tautulli: Monitor your Plex Media Server",
|
||||||
|
"short_name": "Tautulli",
|
||||||
|
"Description": "A Python based monitoring and tracking tool for Plex Media Server.",
|
||||||
|
"start_url": "../../",
|
||||||
|
"scope": "../../",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "${http_root}images/favicon/android-chrome-192x192.png?v=2.0.5",
|
"src": "android-chrome-192x192.png?v=2.0.5",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "${http_root}images/favicon/android-chrome-256x256.png?v=2.0.5",
|
"src": "android-chrome-256x256.png?v=2.0.5",
|
||||||
"sizes": "256x256",
|
"sizes": "256x256",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"theme_color": "#282a2d",
|
"theme_color": "#282a2d",
|
||||||
"background_color": "#282a2d",
|
"background_color": "#282a2d",
|
||||||
"display": "standalone"
|
"display": "standalone",
|
||||||
|
"orientation": "any"
|
||||||
}
|
}
|
@@ -721,18 +721,16 @@ DOCUMENTATION :: END
|
|||||||
$('#deleteCount').text(history_to_delete.length);
|
$('#deleteCount').text(history_to_delete.length);
|
||||||
$('#confirm-modal-delete').modal();
|
$('#confirm-modal-delete').modal();
|
||||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||||
history_to_delete.forEach(function (row, idx) {
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'delete_history_rows',
|
url: 'delete_history_rows',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: { row_id: row },
|
data: { row_ids: history_to_delete.join(',') },
|
||||||
async: true,
|
async: true,
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
var msg = "History deleted";
|
var msg = "History deleted";
|
||||||
showMsg(msg, false, true, 2000);
|
showMsg(msg, false, true, 2000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
history_table.draw();
|
history_table.draw();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -461,8 +461,9 @@ $('*').on('click', '.refresh_pms_image', function (e) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Taken from http://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable#answer-14919494
|
// Taken from http://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable#answer-14919494
|
||||||
function humanFileSize(bytes, si) {
|
function humanFileSize(bytes, si = true) {
|
||||||
var thresh = si ? 1000 : 1024;
|
//var thresh = si ? 1000 : 1024;
|
||||||
|
var thresh = 1024; // Always divide by 2^10 but display SI units
|
||||||
if (Math.abs(bytes) < thresh) {
|
if (Math.abs(bytes) < thresh) {
|
||||||
return bytes + ' B';
|
return bytes + ' B';
|
||||||
}
|
}
|
||||||
|
@@ -36,10 +36,10 @@ history_table_options = {
|
|||||||
"targets": [0],
|
"targets": [0],
|
||||||
"data": null,
|
"data": null,
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (rowData['id'] === null) {
|
if (rowData['row_id'] === null) {
|
||||||
$(td).html('');
|
$(td).html('');
|
||||||
} else {
|
} else {
|
||||||
$(td).html('<button class="btn btn-xs btn-warning" data-id="' + rowData['id'] + '"><i class="fa fa-trash-o fa-fw"></i> Delete</button>');
|
$(td).html('<button class="btn btn-xs btn-warning" data-id="' + rowData['row_id'] + '"><i class="fa fa-trash-o fa-fw"></i> Delete</button>');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"width": "5%",
|
"width": "5%",
|
||||||
@@ -317,19 +317,19 @@ history_table_options = {
|
|||||||
"rowCallback": function (row, rowData, rowIndex) {
|
"rowCallback": function (row, rowData, rowIndex) {
|
||||||
if (rowData['group_count'] == 1) {
|
if (rowData['group_count'] == 1) {
|
||||||
// if no grouped rows simply toggle the delete button
|
// if no grouped rows simply toggle the delete button
|
||||||
if ($.inArray(rowData['id'], history_to_delete) !== -1) {
|
if ($.inArray(rowData['row_id'], history_to_delete) !== -1) {
|
||||||
$(row).find('button[data-id="' + rowData['id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
$(row).find('button[data-id="' + rowData['row_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||||
}
|
}
|
||||||
} else if (rowData['id'] !== null) {
|
} else if (rowData['row_id'] !== null) {
|
||||||
// if grouped rows
|
// if grouped rows
|
||||||
// toggle the parent button to danger
|
// toggle the parent button to danger
|
||||||
$(row).find('button[data-id="' + rowData['id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
$(row).find('button[data-id="' + rowData['row_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||||
// check if any child rows are not selected
|
// check if any child rows are not selected
|
||||||
var group_ids = rowData['group_ids'].split(',').map(Number);
|
var group_ids = rowData['group_ids'].split(',').map(Number);
|
||||||
group_ids.forEach(function (id) {
|
group_ids.forEach(function (id) {
|
||||||
var index = $.inArray(id, history_to_delete);
|
var index = $.inArray(id, history_to_delete);
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
$(row).find('button[data-id="' + rowData['id'] + '"]').addClass('btn-warning').removeClass('btn-danger');
|
$(row).find('button[data-id="' + rowData['row_id'] + '"]').addClass('btn-warning').removeClass('btn-danger');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -353,7 +353,7 @@ $('.history_table').on('click', '> tbody > tr > td.modal-control', function () {
|
|||||||
var rowData = row.data();
|
var rowData = row.data();
|
||||||
|
|
||||||
$.get('get_stream_data', {
|
$.get('get_stream_data', {
|
||||||
row_id: rowData['id'],
|
row_id: rowData['row_id'],
|
||||||
session_key: rowData['session_key'],
|
session_key: rowData['session_key'],
|
||||||
user: rowData['friendly_name']
|
user: rowData['friendly_name']
|
||||||
}).then(function (jqXHR) {
|
}).then(function (jqXHR) {
|
||||||
@@ -382,9 +382,9 @@ $('.history_table').on('click', '> tbody > tr > td.delete-control > button', fun
|
|||||||
|
|
||||||
if (rowData['group_count'] == 1) {
|
if (rowData['group_count'] == 1) {
|
||||||
// if no grouped rows simply add or remove row from history_to_delete
|
// if no grouped rows simply add or remove row from history_to_delete
|
||||||
var index = $.inArray(rowData['id'], history_to_delete);
|
var index = $.inArray(rowData['row_id'], history_to_delete);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
history_to_delete.push(rowData['id']);
|
history_to_delete.push(rowData['row_id']);
|
||||||
} else {
|
} else {
|
||||||
history_to_delete.splice(index, 1);
|
history_to_delete.splice(index, 1);
|
||||||
}
|
}
|
||||||
@@ -549,7 +549,7 @@ function createChildTable(row, rowData) {
|
|||||||
var childRowData = childRow.data();
|
var childRowData = childRow.data();
|
||||||
|
|
||||||
$.get('get_stream_data', {
|
$.get('get_stream_data', {
|
||||||
row_id: childRowData['id'],
|
row_id: childRowData['row_id'],
|
||||||
user: childRowData['friendly_name']
|
user: childRowData['friendly_name']
|
||||||
}).then(function (jqXHR) {
|
}).then(function (jqXHR) {
|
||||||
$("#info-modal").html(jqXHR);
|
$("#info-modal").html(jqXHR);
|
||||||
@@ -576,9 +576,9 @@ function createChildTable(row, rowData) {
|
|||||||
var childRowData = childRow.data();
|
var childRowData = childRow.data();
|
||||||
|
|
||||||
// add or remove row from history_to_delete
|
// add or remove row from history_to_delete
|
||||||
var index = $.inArray(childRowData['id'], history_to_delete);
|
var index = $.inArray(childRowData['row_id'], history_to_delete);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
history_to_delete.push(childRowData['id']);
|
history_to_delete.push(childRowData['row_id']);
|
||||||
} else {
|
} else {
|
||||||
history_to_delete.splice(index, 1);
|
history_to_delete.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
@@ -169,7 +169,7 @@ $('.history_table').on('click', 'td.modal-control', function () {
|
|||||||
function showStreamDetails() {
|
function showStreamDetails() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'get_stream_data',
|
url: 'get_stream_data',
|
||||||
data: { row_id: rowData['id'], user: rowData['friendly_name'] },
|
data: { row_id: rowData['row_id'], user: rowData['friendly_name'] },
|
||||||
cache: false,
|
cache: false,
|
||||||
async: true,
|
async: true,
|
||||||
complete: function (xhr, status) {
|
complete: function (xhr, status) {
|
||||||
|
@@ -27,8 +27,8 @@ libraries_list_table_options = {
|
|||||||
"data": null,
|
"data": null,
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
$(td).html('<div class="edit-library-toggles">' +
|
$(td).html('<div class="edit-library-toggles">' +
|
||||||
'<button class="btn btn-xs btn-warning delete-library" data-id="' + rowData['section_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button> ' +
|
'<button class="btn btn-xs btn-warning delete-library" data-id="' + rowData['row_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button> ' +
|
||||||
'<button class="btn btn-xs btn-warning purge-library" data-id="' + rowData['section_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>   ' +
|
'<button class="btn btn-xs btn-warning purge-library" data-id="' + rowData['row_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>   ' +
|
||||||
'<input type="checkbox" id="keep_history-' + rowData['section_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['section_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label> ' +
|
'<input type="checkbox" id="keep_history-' + rowData['section_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['section_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label> ' +
|
||||||
'</div>');
|
'</div>');
|
||||||
},
|
},
|
||||||
@@ -41,14 +41,16 @@ libraries_list_table_options = {
|
|||||||
"targets": [1],
|
"targets": [1],
|
||||||
"data": "library_thumb",
|
"data": "library_thumb",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
|
var inactive = '';
|
||||||
|
if (!rowData['is_active']) { inactive = '<span class="inactive-library-tooltip" data-toggle="tooltip" title="Library not on Plex server"><i class="fa fa-exclamation-triangle"></i></span>'; }
|
||||||
if (cellData !== null && cellData !== '') {
|
if (cellData !== null && cellData !== '') {
|
||||||
if (rowData['library_thumb'].substring(0, 4) == "http") {
|
if (rowData['library_thumb'].substring(0, 4) == "http") {
|
||||||
$(td).html('<a href="library?section_id=' + rowData['section_id'] + '"><div class="libraries-poster-face" style="background-image: url(' + rowData['library_thumb'] + ');"></div></a>');
|
$(td).html('<a href="' + page('library', rowData['section_id']) + '"><div class="libraries-poster-face" style="background-image: url(' + rowData['library_thumb'] + ');">' + inactive + '</div></a>');
|
||||||
} else {
|
} else {
|
||||||
$(td).html('<a href="library?section_id=' + rowData['section_id'] + '"><div class="libraries-poster-face svg-icon library-' + rowData['section_type'] + '"></div></a>');
|
$(td).html('<a href="' + page('library', rowData['section_id']) + '"><div class="libraries-poster-face svg-icon library-' + rowData['section_type'] + '">' + inactive + '</div></a>');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$(td).html('<a href="library?section_id=' + rowData['section_id'] + '"><div class="libraries-poster-face" style="background-image: url(../../images/cover.png);"></div></a>');
|
$(td).html('<a href="' + page('library', rowData['section_id']) + '"><div class="libraries-poster-face" style="background-image: url(../../images/cover.png);">' + inactive + '</div></a>');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"orderable": false,
|
"orderable": false,
|
||||||
@@ -61,8 +63,8 @@ libraries_list_table_options = {
|
|||||||
"data": "section_name",
|
"data": "section_name",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (cellData !== null && cellData !== '') {
|
if (cellData !== null && cellData !== '') {
|
||||||
$(td).html('<div data-id="' + rowData['section_id'] + '">' +
|
$(td).html('<div data-id="' + rowData['row_id'] + '">' +
|
||||||
'<a href="library?section_id=' + rowData['section_id'] + '">' + cellData + '</a>' +
|
'<a href="' + page('library', rowData['section_id']) + '">' + cellData + '</a>' +
|
||||||
'</div>');
|
'</div>');
|
||||||
} else {
|
} else {
|
||||||
$(td).html('n/a');
|
$(td).html('n/a');
|
||||||
@@ -232,11 +234,11 @@ libraries_list_table_options = {
|
|||||||
showMsg(msg, false, false, 0)
|
showMsg(msg, false, false, 0)
|
||||||
},
|
},
|
||||||
"rowCallback": function (row, rowData) {
|
"rowCallback": function (row, rowData) {
|
||||||
if ($.inArray(rowData['section_id'], libraries_to_delete) !== -1) {
|
if ($.inArray(rowData['row_id'], libraries_to_delete) !== -1) {
|
||||||
$(row).find('button.delete-library[data-id="' + rowData['section_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
$(row).find('button.delete-library[data-id="' + rowData['row_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||||
}
|
}
|
||||||
if ($.inArray(rowData['section_id'], libraries_to_purge) !== -1) {
|
if ($.inArray(rowData['row_id'], libraries_to_purge) !== -1) {
|
||||||
$(row).find('button.purge-library[data-id="' + rowData['section_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
$(row).find('button.purge-library[data-id="' + rowData['row_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,11 +279,11 @@ $('#libraries_list_table').on('click', 'td.edit-control > .edit-library-toggles
|
|||||||
var row = libraries_list_table.row(tr);
|
var row = libraries_list_table.row(tr);
|
||||||
var rowData = row.data();
|
var rowData = row.data();
|
||||||
|
|
||||||
var index_delete = $.inArray(rowData['section_id'], libraries_to_delete);
|
var index_delete = $.inArray(rowData['row_id'], libraries_to_delete);
|
||||||
var index_purge = $.inArray(rowData['section_id'], libraries_to_purge);
|
var index_purge = $.inArray(rowData['row_id'], libraries_to_purge);
|
||||||
|
|
||||||
if (index_delete === -1) {
|
if (index_delete === -1) {
|
||||||
libraries_to_delete.push(rowData['section_id']);
|
libraries_to_delete.push(rowData['row_id']);
|
||||||
if (index_purge === -1) {
|
if (index_purge === -1) {
|
||||||
tr.find('button.purge-library').click();
|
tr.find('button.purge-library').click();
|
||||||
}
|
}
|
||||||
@@ -300,11 +302,11 @@ $('#libraries_list_table').on('click', 'td.edit-control > .edit-library-toggles
|
|||||||
var row = libraries_list_table.row(tr);
|
var row = libraries_list_table.row(tr);
|
||||||
var rowData = row.data();
|
var rowData = row.data();
|
||||||
|
|
||||||
var index_delete = $.inArray(rowData['section_id'], libraries_to_delete);
|
var index_delete = $.inArray(rowData['row_id'], libraries_to_delete);
|
||||||
var index_purge = $.inArray(rowData['section_id'], libraries_to_purge);
|
var index_purge = $.inArray(rowData['row_id'], libraries_to_purge);
|
||||||
|
|
||||||
if (index_purge === -1) {
|
if (index_purge === -1) {
|
||||||
libraries_to_purge.push(rowData['section_id']);
|
libraries_to_purge.push(rowData['row_id']);
|
||||||
} else {
|
} else {
|
||||||
libraries_to_purge.splice(index_purge, 1);
|
libraries_to_purge.splice(index_purge, 1);
|
||||||
if (index_delete != -1) {
|
if (index_delete != -1) {
|
||||||
|
@@ -167,7 +167,7 @@ $('.user_ip_table').on('click', 'td.modal-control', function () {
|
|||||||
function showStreamDetails() {
|
function showStreamDetails() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'get_stream_data',
|
url: 'get_stream_data',
|
||||||
data: { row_id: rowData['id'], user: rowData['friendly_name'] },
|
data: { row_id: rowData['history_row_id'], user: rowData['friendly_name'] },
|
||||||
cache: false,
|
cache: false,
|
||||||
async: true,
|
async: true,
|
||||||
complete: function (xhr, status) {
|
complete: function (xhr, status) {
|
||||||
|
@@ -44,8 +44,8 @@ users_list_table_options = {
|
|||||||
"data": null,
|
"data": null,
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
$(td).html('<div class="edit-user-toggles">' +
|
$(td).html('<div class="edit-user-toggles">' +
|
||||||
'<button class="btn btn-xs btn-warning delete-user" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button> ' +
|
'<button class="btn btn-xs btn-warning delete-user" data-id="' + rowData['row_id'] + '" data-toggle="button"><i class="fa fa-trash-o fa-fw"></i> Delete</button> ' +
|
||||||
'<button class="btn btn-xs btn-warning purge-user" data-id="' + rowData['user_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>   ' +
|
'<button class="btn btn-xs btn-warning purge-user" data-id="' + rowData['row_id'] + '" data-toggle="button"><i class="fa fa-eraser fa-fw"></i> Purge</button>   ' +
|
||||||
'<input type="checkbox" id="keep_history-' + rowData['user_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label> ' +
|
'<input type="checkbox" id="keep_history-' + rowData['user_id'] + '" name="keep_history" value="1" ' + rowData['keep_history'] + '><label class="edit-tooltip" for="keep_history-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle History"><i class="fa fa-history fa-lg fa-fw"></i></label> ' +
|
||||||
'<input type="checkbox" id="allow_guest-' + rowData['user_id'] + '" name="allow_guest" value="1" ' + rowData['allow_guest'] + '><label class="edit-tooltip" for="allow_guest-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle Guest Access"><i class="fa fa-unlock-alt fa-lg fa-fw"></i></label> ' +
|
'<input type="checkbox" id="allow_guest-' + rowData['user_id'] + '" name="allow_guest" value="1" ' + rowData['allow_guest'] + '><label class="edit-tooltip" for="allow_guest-' + rowData['user_id'] + '" data-toggle="tooltip" title="Toggle Guest Access"><i class="fa fa-unlock-alt fa-lg fa-fw"></i></label> ' +
|
||||||
'</div>');
|
'</div>');
|
||||||
@@ -59,10 +59,12 @@ users_list_table_options = {
|
|||||||
"targets": [1],
|
"targets": [1],
|
||||||
"data": "user_thumb",
|
"data": "user_thumb",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
|
var inactive = '';
|
||||||
|
if (!rowData['is_active']) { inactive = '<span class="inactive-user-tooltip" data-toggle="tooltip" title="User not on Plex server"><i class="fa fa-exclamation-triangle"></i></span>'; }
|
||||||
if (cellData === '') {
|
if (cellData === '') {
|
||||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(../../images/gravatar-default-80x80.png);"></div></a>');
|
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(../../images/gravatar-default-80x80.png);">' + inactive + '</div></a>');
|
||||||
} else {
|
} else {
|
||||||
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(' + rowData['user_thumb'] + ');"></div></a>');
|
$(td).html('<a href="' + page('user', rowData['user_id']) + '"><div class="users-poster-face" style="background-image: url(' + rowData['user_thumb'] + ');">' + inactive + '</div></a>');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"orderable": false,
|
"orderable": false,
|
||||||
@@ -75,7 +77,7 @@ users_list_table_options = {
|
|||||||
"data": "friendly_name",
|
"data": "friendly_name",
|
||||||
"createdCell": function (td, cellData, rowData, row, col) {
|
"createdCell": function (td, cellData, rowData, row, col) {
|
||||||
if (cellData !== null && cellData !== '') {
|
if (cellData !== null && cellData !== '') {
|
||||||
$(td).html('<div class="edit-user-name" data-id="' + rowData['user_id'] + '">' +
|
$(td).html('<div class="edit-user-name" data-id="' + rowData['row_id'] + '">' +
|
||||||
'<a href="' + page('user', rowData['user_id']) + '">' + cellData + '</a>' +
|
'<a href="' + page('user', rowData['user_id']) + '">' + cellData + '</a>' +
|
||||||
'<input type="text" class="hidden" value="' + cellData + '">' +
|
'<input type="text" class="hidden" value="' + cellData + '">' +
|
||||||
'</div>');
|
'</div>');
|
||||||
@@ -254,10 +256,10 @@ users_list_table_options = {
|
|||||||
},
|
},
|
||||||
"rowCallback": function (row, rowData) {
|
"rowCallback": function (row, rowData) {
|
||||||
if ($.inArray(rowData['user_id'], users_to_delete) !== -1) {
|
if ($.inArray(rowData['user_id'], users_to_delete) !== -1) {
|
||||||
$(row).find('button.delete-user[data-id="' + rowData['user_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
$(row).find('button.delete-user[data-id="' + rowData['row_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||||
}
|
}
|
||||||
if ($.inArray(rowData['user_id'], users_to_purge) !== -1) {
|
if ($.inArray(rowData['user_id'], users_to_purge) !== -1) {
|
||||||
$(row).find('button.purge-user[data-id="' + rowData['user_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
$(row).find('button.purge-user[data-id="' + rowData['row_id'] + '"]').toggleClass('btn-warning').toggleClass('btn-danger');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,7 +270,7 @@ $('#users_list_table').on('click', 'td.modal-control', function () {
|
|||||||
var rowData = row.data();
|
var rowData = row.data();
|
||||||
|
|
||||||
$.get('get_stream_data', {
|
$.get('get_stream_data', {
|
||||||
row_id: rowData['id'],
|
row_id: rowData['history_row_id'],
|
||||||
user: rowData['friendly_name']
|
user: rowData['friendly_name']
|
||||||
}).then(function (jqXHR) {
|
}).then(function (jqXHR) {
|
||||||
$("#info-modal").html(jqXHR);
|
$("#info-modal").html(jqXHR);
|
||||||
@@ -326,11 +328,11 @@ $('#users_list_table').on('click', 'td.edit-control > .edit-user-toggles > butto
|
|||||||
var row = users_list_table.row(tr);
|
var row = users_list_table.row(tr);
|
||||||
var rowData = row.data();
|
var rowData = row.data();
|
||||||
|
|
||||||
var index_delete = $.inArray(rowData['user_id'], users_to_delete);
|
var index_delete = $.inArray(rowData['row_id'], users_to_delete);
|
||||||
var index_purge = $.inArray(rowData['user_id'], users_to_purge);
|
var index_purge = $.inArray(rowData['row_id'], users_to_purge);
|
||||||
|
|
||||||
if (index_delete === -1) {
|
if (index_delete === -1) {
|
||||||
users_to_delete.push(rowData['user_id']);
|
users_to_delete.push(rowData['row_id']);
|
||||||
if (index_purge === -1) {
|
if (index_purge === -1) {
|
||||||
tr.find('button.purge-user').click();
|
tr.find('button.purge-user').click();
|
||||||
}
|
}
|
||||||
@@ -349,11 +351,11 @@ $('#users_list_table').on('click', 'td.edit-control > .edit-user-toggles > butto
|
|||||||
var row = users_list_table.row(tr);
|
var row = users_list_table.row(tr);
|
||||||
var rowData = row.data();
|
var rowData = row.data();
|
||||||
|
|
||||||
var index_delete = $.inArray(rowData['user_id'], users_to_delete);
|
var index_delete = $.inArray(rowData['row_id'], users_to_delete);
|
||||||
var index_purge = $.inArray(rowData['user_id'], users_to_purge);
|
var index_purge = $.inArray(rowData['row_id'], users_to_purge);
|
||||||
|
|
||||||
if (index_purge === -1) {
|
if (index_purge === -1) {
|
||||||
users_to_purge.push(rowData['user_id']);
|
users_to_purge.push(rowData['row_id']);
|
||||||
} else {
|
} else {
|
||||||
users_to_purge.splice(index_purge, 1);
|
users_to_purge.splice(index_purge, 1);
|
||||||
if (index_delete != -1) {
|
if (index_delete != -1) {
|
||||||
|
@@ -116,14 +116,14 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (libraries_to_delete.length > 0) {
|
if (libraries_to_delete.length > 0) {
|
||||||
$('#libraries-to-delete').prepend('<p>Are you REALLY sure you want to delete the following libraries:</p>')
|
$('#libraries-to-delete').prepend('<p>Are you REALLY sure you want to delete the following libraries:</p>');
|
||||||
for (var i = 0; i < libraries_to_delete.length; i++) {
|
for (var i = 0; i < libraries_to_delete.length; i++) {
|
||||||
$('#libraries-to-delete').append('<li>' + $('div[data-id=' + libraries_to_delete[i] + ']').text() + '</li>');
|
$('#libraries-to-delete').append('<li>' + $('div[data-id=' + libraries_to_delete[i] + ']').text() + '</li>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (libraries_to_purge.length > 0) {
|
if (libraries_to_purge.length > 0) {
|
||||||
$('#libraries-to-purge').prepend('<p>Are you REALLY sure you want to purge all history for the following libraries:</p>')
|
$('#libraries-to-purge').prepend('<p>Are you REALLY sure you want to purge all history for the following libraries:</p>');
|
||||||
for (var i = 0; i < libraries_to_purge.length; i++) {
|
for (var i = 0; i < libraries_to_purge.length; i++) {
|
||||||
$('#libraries-to-purge').append('<li>' + $('div[data-id=' + libraries_to_purge[i] + ']').text() + '</li>');
|
$('#libraries-to-purge').append('<li>' + $('div[data-id=' + libraries_to_purge[i] + ']').text() + '</li>');
|
||||||
}
|
}
|
||||||
@@ -131,33 +131,30 @@
|
|||||||
|
|
||||||
$('#confirm-modal-delete').modal();
|
$('#confirm-modal-delete').modal();
|
||||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||||
libraries_to_delete.forEach(function(row, idx) {
|
|
||||||
$.ajax({
|
|
||||||
url: 'delete_library',
|
|
||||||
type: 'POST',
|
|
||||||
data: { section_id: row },
|
|
||||||
cache: false,
|
|
||||||
async: true,
|
|
||||||
success: function (data) {
|
|
||||||
var msg = "Library deleted";
|
|
||||||
showMsg(msg, false, true, 2000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
libraries_to_purge.forEach(function(row, idx) {
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'delete_all_library_history',
|
url: 'delete_all_library_history',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: { section_id: row },
|
data: { row_ids: libraries_to_purge.join(',') },
|
||||||
cache: false,
|
cache: false,
|
||||||
async: true,
|
async: true,
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
var msg = "Library history purged";
|
var msg = "Library history purged";
|
||||||
showMsg(msg, false, true, 2000);
|
showMsg(msg, false, true, 2000);
|
||||||
|
libraries_list_table.draw();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
$.ajax({
|
||||||
|
url: 'delete_library',
|
||||||
|
type: 'POST',
|
||||||
|
data: { row_ids: libraries_to_delete.join(',') },
|
||||||
|
cache: false,
|
||||||
|
async: true,
|
||||||
|
success: function (data) {
|
||||||
|
var msg = "Library deleted";
|
||||||
|
showMsg(msg, false, true, 2000);
|
||||||
libraries_list_table.draw();
|
libraries_list_table.draw();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +185,7 @@
|
|||||||
complete: function (xhr, status) {
|
complete: function (xhr, status) {
|
||||||
var result = $.parseJSON(xhr.responseText);
|
var result = $.parseJSON(xhr.responseText);
|
||||||
var msg = result.message;
|
var msg = result.message;
|
||||||
if (result.result == 'success') {
|
if (result.result === 'success') {
|
||||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 2000, false);
|
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 2000, false);
|
||||||
libraries_list_table.draw();
|
libraries_list_table.draw();
|
||||||
} else {
|
} else {
|
||||||
|
@@ -62,9 +62,21 @@ DOCUMENTATION :: END
|
|||||||
<div class="table-card-back">
|
<div class="table-card-back">
|
||||||
<div class="user-info-wrapper">
|
<div class="user-info-wrapper">
|
||||||
% if data['library_thumb'].startswith('http'):
|
% if data['library_thumb'].startswith('http'):
|
||||||
<div class="library-info-poster-face" style="background-image: url(${page('pms_image_proxy', data['library_thumb'], None, 80, 80)});"></div>
|
<div class="library-info-poster-face" style="background-image: url(${page('pms_image_proxy', data['library_thumb'], None, 80, 80)});">
|
||||||
|
% if not data['is_active']:
|
||||||
|
<span class="inactive-library-tooltip" data-toggle="tooltip" title="Library not on Plex server">
|
||||||
|
<i class="fa fa-2x fa-exclamation-triangle"></i>
|
||||||
|
</span>
|
||||||
|
% endif
|
||||||
|
</div>
|
||||||
% else:
|
% else:
|
||||||
<div class="library-info-poster-face svg-icon library-${data['section_type']}"></div>
|
<div class="library-info-poster-face svg-icon library-${data['section_type']}">
|
||||||
|
% if not data['is_active']:
|
||||||
|
<span class="inactive-library-tooltip" data-toggle="tooltip" title="Library not on Plex server">
|
||||||
|
<i class="fa fa-2x fa-exclamation-triangle"></i>
|
||||||
|
</span>
|
||||||
|
% endif
|
||||||
|
</div>
|
||||||
% endif
|
% endif
|
||||||
<div class="user-info-username">
|
<div class="user-info-username">
|
||||||
<span class="set-username">${data['section_name']}</span>
|
<span class="set-username">${data['section_name']}</span>
|
||||||
@@ -411,6 +423,8 @@ DOCUMENTATION :: END
|
|||||||
history_table.draw();
|
history_table.draw();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(".inactive-library-tooltip").tooltip();
|
||||||
|
|
||||||
% if _session['user_group'] == 'admin':
|
% if _session['user_group'] == 'admin':
|
||||||
function loadMediaInfoTable() {
|
function loadMediaInfoTable() {
|
||||||
// Build media info table
|
// Build media info table
|
||||||
@@ -471,18 +485,16 @@ DOCUMENTATION :: END
|
|||||||
$('#deleteCount').text(history_to_delete.length);
|
$('#deleteCount').text(history_to_delete.length);
|
||||||
$('#confirm-modal-delete').modal();
|
$('#confirm-modal-delete').modal();
|
||||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||||
history_to_delete.forEach(function(row, idx) {
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'delete_history_rows',
|
url: 'delete_history_rows',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: { row_id: row },
|
data: { row_ids: history_to_delete.join(',') },
|
||||||
async: true,
|
async: true,
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
var msg = "History deleted";
|
var msg = "History deleted";
|
||||||
showMsg(msg, false, true, 2000);
|
showMsg(msg, false, true, 2000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
history_table.draw();
|
history_table.draw();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -51,7 +51,13 @@ DOCUMENTATION :: END
|
|||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="table-card-back">
|
<div class="table-card-back">
|
||||||
<div class="user-info-wrapper">
|
<div class="user-info-wrapper">
|
||||||
<div class="user-info-poster-face" style="background-image: url(${data['user_thumb']});"></div>
|
<div class="user-info-poster-face" style="background-image: url(${data['user_thumb']});">
|
||||||
|
% if not data['is_active']:
|
||||||
|
<span class="inactive-user-tooltip" data-toggle="tooltip" title="User not on Plex server">
|
||||||
|
<i class="fa fa-2x fa-exclamation-triangle"></i>
|
||||||
|
</span>
|
||||||
|
% endif
|
||||||
|
</div>
|
||||||
<div class="user-info-username">
|
<div class="user-info-username">
|
||||||
<span class="set-username">${data['friendly_name']}</span>
|
<span class="set-username">${data['friendly_name']}</span>
|
||||||
% if _session['user_group'] == 'admin':
|
% if _session['user_group'] == 'admin':
|
||||||
@@ -540,6 +546,8 @@ DOCUMENTATION :: END
|
|||||||
login_log_table.draw();
|
login_log_table.draw();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(".inactive-user-tooltip").tooltip();
|
||||||
|
|
||||||
% if _session['user_group'] == 'admin':
|
% if _session['user_group'] == 'admin':
|
||||||
$("#edit-user-tooltip").tooltip();
|
$("#edit-user-tooltip").tooltip();
|
||||||
|
|
||||||
@@ -566,18 +574,16 @@ DOCUMENTATION :: END
|
|||||||
$('#deleteType').text('history');
|
$('#deleteType').text('history');
|
||||||
$('#confirm-modal-delete').modal();
|
$('#confirm-modal-delete').modal();
|
||||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||||
history_to_delete.forEach(function(row, idx) {
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'delete_history_rows',
|
url: 'delete_history_rows',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: { row_id: row },
|
data: { row_ids: history_to_delete.join(',') },
|
||||||
async: true,
|
async: true,
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
var msg = "History deleted";
|
var msg = "History deleted";
|
||||||
showMsg(msg, false, true, 2000);
|
showMsg(msg, false, true, 2000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
history_table.draw();
|
history_table.draw();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -119,14 +119,14 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (users_to_delete.length > 0) {
|
if (users_to_delete.length > 0) {
|
||||||
$('#users-to-delete').prepend('<p>Are you REALLY sure you want to delete and purge all history for the following users:</p>')
|
$('#users-to-delete').prepend('<p>Are you REALLY sure you want to delete and purge all history for the following users:</p>');
|
||||||
for (var i = 0; i < users_to_delete.length; i++) {
|
for (var i = 0; i < users_to_delete.length; i++) {
|
||||||
$('#users-to-delete').append('<li>' + $('div[data-id=' + users_to_delete[i] + '] > input').val() + '</li>');
|
$('#users-to-delete').append('<li>' + $('div[data-id=' + users_to_delete[i] + '] > input').val() + '</li>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (users_to_purge.length > 0) {
|
if (users_to_purge.length > 0) {
|
||||||
$('#users-to-purge').prepend('<p>Are you REALLY sure you want to purge all history for the following users:</p>')
|
$('#users-to-purge').prepend('<p>Are you REALLY sure you want to purge all history for the following users:</p>');
|
||||||
for (var i = 0; i < users_to_purge.length; i++) {
|
for (var i = 0; i < users_to_purge.length; i++) {
|
||||||
$('#users-to-purge').append('<li>' + $('div[data-id=' + users_to_purge[i] + '] > input').val() + '</li>');
|
$('#users-to-purge').append('<li>' + $('div[data-id=' + users_to_purge[i] + '] > input').val() + '</li>');
|
||||||
}
|
}
|
||||||
@@ -134,33 +134,30 @@
|
|||||||
|
|
||||||
$('#confirm-modal-delete').modal();
|
$('#confirm-modal-delete').modal();
|
||||||
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
$('#confirm-modal-delete').one('click', '#confirm-delete', function () {
|
||||||
users_to_delete.forEach(function(row, idx) {
|
|
||||||
$.ajax({
|
|
||||||
url: 'delete_user',
|
|
||||||
type: 'POST',
|
|
||||||
data: { user_id: row },
|
|
||||||
cache: false,
|
|
||||||
async: true,
|
|
||||||
success: function (data) {
|
|
||||||
var msg = "User deleted";
|
|
||||||
showMsg(msg, false, true, 2000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
users_to_purge.forEach(function(row, idx) {
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'delete_all_user_history',
|
url: 'delete_all_user_history',
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: { user_id: row },
|
data: { row_ids: users_to_purge.join(',') },
|
||||||
cache: false,
|
cache: false,
|
||||||
async: true,
|
async: true,
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
var msg = "User history purged";
|
var msg = "User history purged";
|
||||||
showMsg(msg, false, true, 2000);
|
showMsg(msg, false, true, 2000);
|
||||||
|
users_list_table.draw();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
$.ajax({
|
||||||
|
url: 'delete_user',
|
||||||
|
type: 'POST',
|
||||||
|
data: { row_ids: users_to_delete.join(',') },
|
||||||
|
cache: false,
|
||||||
|
async: true,
|
||||||
|
success: function (data) {
|
||||||
|
var msg = "User deleted";
|
||||||
|
showMsg(msg, false, true, 2000);
|
||||||
users_list_table.draw();
|
users_list_table.draw();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +189,7 @@
|
|||||||
complete: function (xhr, status) {
|
complete: function (xhr, status) {
|
||||||
var result = $.parseJSON(xhr.responseText);
|
var result = $.parseJSON(xhr.responseText);
|
||||||
var msg = result.message;
|
var msg = result.message;
|
||||||
if (result.result == 'success') {
|
if (result.result === 'success') {
|
||||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 2000, false);
|
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 2000, false);
|
||||||
users_list_table.draw();
|
users_list_table.draw();
|
||||||
} else {
|
} else {
|
||||||
|
@@ -224,6 +224,8 @@
|
|||||||
<input type="checkbox" name="check_github" id="check_github" value="1" checked>
|
<input type="checkbox" name="check_github" id="check_github" value="1" checked>
|
||||||
<input type="checkbox" name="log_blacklist" id="log_blacklist" value="1" checked>
|
<input type="checkbox" name="log_blacklist" id="log_blacklist" value="1" checked>
|
||||||
<input type="checkbox" name="cache_images" id="cache_images" value="1" checked>
|
<input type="checkbox" name="cache_images" id="cache_images" value="1" checked>
|
||||||
|
<input type="checkbox" name="notify_group_recently_added_grandparent" id="notify_group_recently_added_grandparent" value="1" checked>
|
||||||
|
<input type="checkbox" name="notify_group_recently_added_parent" id="notify_group_recently_added_parent" value="1" checked>
|
||||||
<input type="checkbox" name="server_changed" id="server_changed" value="1" checked>
|
<input type="checkbox" name="server_changed" id="server_changed" value="1" checked>
|
||||||
<input type="checkbox" name="first_run_complete" id="first_run_complete" value="1" checked>
|
<input type="checkbox" name="first_run_complete" id="first_run_complete" value="1" checked>
|
||||||
<input type="text" name="home_stats_cards" id="home_stats_cards" value="first_run_wizard">
|
<input type="text" name="home_stats_cards" id="home_stats_cards" value="first_run_wizard">
|
||||||
|
@@ -34,6 +34,8 @@ from apscheduler.triggers.interval import IntervalTrigger
|
|||||||
from UniversalAnalytics import Tracker
|
from UniversalAnalytics import Tracker
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
|
PYTHON_VERSION = sys.version_info[:3]
|
||||||
|
|
||||||
import activity_handler
|
import activity_handler
|
||||||
import activity_pinger
|
import activity_pinger
|
||||||
import common
|
import common
|
||||||
@@ -661,11 +663,11 @@ def dbcheck():
|
|||||||
c_db.execute(
|
c_db.execute(
|
||||||
'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||||
'user_id INTEGER DEFAULT NULL UNIQUE, username TEXT NOT NULL, friendly_name TEXT, '
|
'user_id INTEGER DEFAULT NULL UNIQUE, username TEXT NOT NULL, friendly_name TEXT, '
|
||||||
'thumb TEXT, custom_avatar_url TEXT, email TEXT, is_admin INTEGER DEFAULT 0, is_home_user INTEGER DEFAULT NULL, '
|
'thumb TEXT, custom_avatar_url TEXT, email TEXT, is_active INTEGER DEFAULT 1, is_admin INTEGER DEFAULT 0, '
|
||||||
'is_allow_sync INTEGER DEFAULT NULL, is_restricted INTEGER DEFAULT NULL, do_notify INTEGER DEFAULT 1, '
|
'is_home_user INTEGER DEFAULT NULL, is_allow_sync INTEGER DEFAULT NULL, is_restricted INTEGER DEFAULT NULL, '
|
||||||
'keep_history INTEGER DEFAULT 1, deleted_user INTEGER DEFAULT 0, allow_guest INTEGER DEFAULT 0, '
|
'do_notify INTEGER DEFAULT 1, keep_history INTEGER DEFAULT 1, deleted_user INTEGER DEFAULT 0, '
|
||||||
'user_token TEXT, server_token TEXT, shared_libraries TEXT, filter_all TEXT, filter_movies TEXT, filter_tv TEXT, '
|
'allow_guest INTEGER DEFAULT 0, user_token TEXT, server_token TEXT, shared_libraries TEXT, '
|
||||||
'filter_music TEXT, filter_photos TEXT)'
|
'filter_all TEXT, filter_movies TEXT, filter_tv TEXT, filter_music TEXT, filter_photos TEXT)'
|
||||||
)
|
)
|
||||||
|
|
||||||
# library_sections table :: This table keeps record of the servers library sections
|
# library_sections table :: This table keeps record of the servers library sections
|
||||||
@@ -673,7 +675,7 @@ def dbcheck():
|
|||||||
'CREATE TABLE IF NOT EXISTS library_sections (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
'CREATE TABLE IF NOT EXISTS library_sections (id INTEGER PRIMARY KEY AUTOINCREMENT, '
|
||||||
'server_id TEXT, section_id INTEGER, section_name TEXT, section_type TEXT, agent TEXT, '
|
'server_id TEXT, section_id INTEGER, section_name TEXT, section_type TEXT, agent TEXT, '
|
||||||
'thumb TEXT, custom_thumb_url TEXT, art TEXT, custom_art_url TEXT, '
|
'thumb TEXT, custom_thumb_url TEXT, art TEXT, custom_art_url TEXT, '
|
||||||
'count INTEGER, parent_count INTEGER, child_count INTEGER, '
|
'count INTEGER, parent_count INTEGER, child_count INTEGER, is_active INTEGER DEFAULT 1, '
|
||||||
'do_notify INTEGER DEFAULT 1, do_notify_created INTEGER DEFAULT 1, keep_history INTEGER DEFAULT 1, '
|
'do_notify INTEGER DEFAULT 1, do_notify_created INTEGER DEFAULT 1, keep_history INTEGER DEFAULT 1, '
|
||||||
'deleted_section INTEGER DEFAULT 0, UNIQUE(server_id, section_id))'
|
'deleted_section INTEGER DEFAULT 0, UNIQUE(server_id, section_id))'
|
||||||
)
|
)
|
||||||
@@ -694,16 +696,19 @@ def dbcheck():
|
|||||||
'on_created INTEGER DEFAULT 0, on_extdown INTEGER DEFAULT 0, on_intdown INTEGER DEFAULT 0, '
|
'on_created INTEGER DEFAULT 0, on_extdown INTEGER DEFAULT 0, on_intdown INTEGER DEFAULT 0, '
|
||||||
'on_extup INTEGER DEFAULT 0, on_intup INTEGER DEFAULT 0, on_pmsupdate INTEGER DEFAULT 0, '
|
'on_extup INTEGER DEFAULT 0, on_intup INTEGER DEFAULT 0, on_pmsupdate INTEGER DEFAULT 0, '
|
||||||
'on_concurrent INTEGER DEFAULT 0, on_newdevice INTEGER DEFAULT 0, on_plexpyupdate INTEGER DEFAULT 0, '
|
'on_concurrent INTEGER DEFAULT 0, on_newdevice INTEGER DEFAULT 0, on_plexpyupdate INTEGER DEFAULT 0, '
|
||||||
|
'on_plexpydbcorrupt INTEGER DEFAULT 0, '
|
||||||
'on_play_subject TEXT, on_stop_subject TEXT, on_pause_subject TEXT, '
|
'on_play_subject TEXT, on_stop_subject TEXT, on_pause_subject TEXT, '
|
||||||
'on_resume_subject TEXT, on_change_subject TEXT, on_buffer_subject TEXT, on_watched_subject TEXT, '
|
'on_resume_subject TEXT, on_change_subject TEXT, on_buffer_subject TEXT, on_watched_subject TEXT, '
|
||||||
'on_created_subject TEXT, on_extdown_subject TEXT, on_intdown_subject TEXT, '
|
'on_created_subject TEXT, on_extdown_subject TEXT, on_intdown_subject TEXT, '
|
||||||
'on_extup_subject TEXT, on_intup_subject TEXT, on_pmsupdate_subject TEXT, '
|
'on_extup_subject TEXT, on_intup_subject TEXT, on_pmsupdate_subject TEXT, '
|
||||||
'on_concurrent_subject TEXT, on_newdevice_subject TEXT, on_plexpyupdate_subject TEXT, '
|
'on_concurrent_subject TEXT, on_newdevice_subject TEXT, on_plexpyupdate_subject TEXT, '
|
||||||
|
'on_plexpydbcorrupt_subject TEXT, '
|
||||||
'on_play_body TEXT, on_stop_body TEXT, on_pause_body TEXT, '
|
'on_play_body TEXT, on_stop_body TEXT, on_pause_body TEXT, '
|
||||||
'on_resume_body TEXT, on_change_body TEXT, on_buffer_body TEXT, on_watched_body TEXT, '
|
'on_resume_body TEXT, on_change_body TEXT, on_buffer_body TEXT, on_watched_body TEXT, '
|
||||||
'on_created_body TEXT, on_extdown_body TEXT, on_intdown_body TEXT, '
|
'on_created_body TEXT, on_extdown_body TEXT, on_intdown_body TEXT, '
|
||||||
'on_extup_body TEXT, on_intup_body TEXT, on_pmsupdate_body TEXT, '
|
'on_extup_body TEXT, on_intup_body TEXT, on_pmsupdate_body TEXT, '
|
||||||
'on_concurrent_body TEXT, on_newdevice_body TEXT, on_plexpyupdate_body TEXT, '
|
'on_concurrent_body TEXT, on_newdevice_body TEXT, on_plexpyupdate_body TEXT, '
|
||||||
|
'on_plexpydbcorrupt_body TEXT, '
|
||||||
'custom_conditions TEXT, custom_conditions_logic TEXT)'
|
'custom_conditions TEXT, custom_conditions_logic TEXT)'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1731,6 +1736,15 @@ def dbcheck():
|
|||||||
'ALTER TABLE users ADD COLUMN is_admin INTEGER DEFAULT 0'
|
'ALTER TABLE users ADD COLUMN is_admin INTEGER DEFAULT 0'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Upgrade users table from earlier versions
|
||||||
|
try:
|
||||||
|
c_db.execute('SELECT is_active FROM users')
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
logger.debug(u"Altering database. Updating database table users.")
|
||||||
|
c_db.execute(
|
||||||
|
'ALTER TABLE users ADD COLUMN is_active INTEGER DEFAULT 1'
|
||||||
|
)
|
||||||
|
|
||||||
# Upgrade notify_log table from earlier versions
|
# Upgrade notify_log table from earlier versions
|
||||||
try:
|
try:
|
||||||
c_db.execute('SELECT poster_url FROM notify_log')
|
c_db.execute('SELECT poster_url FROM notify_log')
|
||||||
@@ -1903,6 +1917,15 @@ def dbcheck():
|
|||||||
'ALTER TABLE library_sections ADD COLUMN custom_art_url TEXT'
|
'ALTER TABLE library_sections ADD COLUMN custom_art_url TEXT'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Upgrade library_sections table from earlier versions
|
||||||
|
try:
|
||||||
|
c_db.execute('SELECT is_active FROM library_sections')
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
logger.debug(u"Altering database. Updating database table library_sections.")
|
||||||
|
c_db.execute(
|
||||||
|
'ALTER TABLE library_sections ADD COLUMN is_active INTEGER DEFAULT 1'
|
||||||
|
)
|
||||||
|
|
||||||
# Upgrade users table from earlier versions (remove UNIQUE constraint on username)
|
# Upgrade users table from earlier versions (remove UNIQUE constraint on username)
|
||||||
try:
|
try:
|
||||||
result = c_db.execute('SELECT SQL FROM sqlite_master WHERE type="table" AND name="users"').fetchone()
|
result = c_db.execute('SELECT SQL FROM sqlite_master WHERE type="table" AND name="users"').fetchone()
|
||||||
@@ -1989,6 +2012,21 @@ def dbcheck():
|
|||||||
'ALTER TABLE notifiers ADD COLUMN on_change_body TEXT'
|
'ALTER TABLE notifiers ADD COLUMN on_change_body TEXT'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Upgrade notifiers table from earlier versions
|
||||||
|
try:
|
||||||
|
c_db.execute('SELECT on_plexpydbcorrupt FROM notifiers')
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
logger.debug(u"Altering database. Updating database table notifiers.")
|
||||||
|
c_db.execute(
|
||||||
|
'ALTER TABLE notifiers ADD COLUMN on_plexpydbcorrupt INTEGER DEFAULT 0'
|
||||||
|
)
|
||||||
|
c_db.execute(
|
||||||
|
'ALTER TABLE notifiers ADD COLUMN on_plexpydbcorrupt_subject TEXT'
|
||||||
|
)
|
||||||
|
c_db.execute(
|
||||||
|
'ALTER TABLE notifiers ADD COLUMN on_plexpydbcorrupt_body TEXT'
|
||||||
|
)
|
||||||
|
|
||||||
# Upgrade tvmaze_lookup table from earlier versions
|
# Upgrade tvmaze_lookup table from earlier versions
|
||||||
try:
|
try:
|
||||||
c_db.execute('SELECT rating_key FROM tvmaze_lookup')
|
c_db.execute('SELECT rating_key FROM tvmaze_lookup')
|
||||||
|
@@ -120,7 +120,7 @@ class API2:
|
|||||||
self._api_app = True
|
self._api_app = True
|
||||||
|
|
||||||
if plexpy.CONFIG.API_ENABLED and not self._api_msg or self._api_cmd in ('get_apikey', 'docs', 'docs_md'):
|
if plexpy.CONFIG.API_ENABLED and not self._api_msg or self._api_cmd in ('get_apikey', 'docs', 'docs_md'):
|
||||||
if self._api_apikey == plexpy.CONFIG.API_KEY or (self._api_app and self._api_apikey == mobile_app.TEMP_DEVICE_TOKEN):
|
if self._api_apikey == plexpy.CONFIG.API_KEY or (self._api_app and self._api_apikey == mobile_app.get_temp_device_token()):
|
||||||
self._api_authenticated = True
|
self._api_authenticated = True
|
||||||
|
|
||||||
elif self._api_app and mobile_app.get_mobile_device_by_token(self._api_apikey):
|
elif self._api_app and mobile_app.get_mobile_device_by_token(self._api_apikey):
|
||||||
@@ -292,7 +292,7 @@ class API2:
|
|||||||
def sql(self, query=''):
|
def sql(self, query=''):
|
||||||
""" Query the Tautulli database with raw SQL. Automatically makes a backup of
|
""" Query the Tautulli database with raw SQL. Automatically makes a backup of
|
||||||
the database if the latest backup is older then 24h. `api_sql` must be
|
the database if the latest backup is older then 24h. `api_sql` must be
|
||||||
manually enabled in the config file.
|
manually enabled in the config file while Tautulli is shut down.
|
||||||
|
|
||||||
```
|
```
|
||||||
Required parameters:
|
Required parameters:
|
||||||
@@ -404,7 +404,7 @@ class API2:
|
|||||||
if result:
|
if result:
|
||||||
self._api_msg = 'Device registration successful.'
|
self._api_msg = 'Device registration successful.'
|
||||||
self._api_result_type = 'success'
|
self._api_result_type = 'success'
|
||||||
mobile_app.TEMP_DEVICE_TOKEN = None
|
mobile_app.set_temp_device_token(None)
|
||||||
else:
|
else:
|
||||||
self._api_msg = 'Device registartion failed: database error.'
|
self._api_msg = 'Device registartion failed: database error.'
|
||||||
self._api_result_type = 'error'
|
self._api_result_type = 'error'
|
||||||
@@ -619,9 +619,9 @@ General optional parameters:
|
|||||||
cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'
|
cherrypy.response.headers['Content-Type'] = 'application/json;charset=UTF-8'
|
||||||
try:
|
try:
|
||||||
if self._api_debug:
|
if self._api_debug:
|
||||||
out = json.dumps(out, indent=4, sort_keys=True, ensure_ascii=False).encode('utf-8')
|
out = json.dumps(out, indent=4, sort_keys=True, ensure_ascii=False)
|
||||||
else:
|
else:
|
||||||
out = json.dumps(out, ensure_ascii=False).encode('utf-8')
|
out = json.dumps(out, ensure_ascii=False)
|
||||||
if self._api_callback is not None:
|
if self._api_callback is not None:
|
||||||
cherrypy.response.headers['Content-Type'] = 'application/javascript'
|
cherrypy.response.headers['Content-Type'] = 'application/javascript'
|
||||||
# wrap with JSONP call if requested
|
# wrap with JSONP call if requested
|
||||||
@@ -634,7 +634,7 @@ General optional parameters:
|
|||||||
out['result'] = 'error'
|
out['result'] = 'error'
|
||||||
|
|
||||||
elif self._api_out_type == 'xml':
|
elif self._api_out_type == 'xml':
|
||||||
cherrypy.response.headers['Content-Type'] = 'application/xml'
|
cherrypy.response.headers['Content-Type'] = 'application/xml;charset=UTF-8'
|
||||||
try:
|
try:
|
||||||
out = xmltodict.unparse(out, pretty=True)
|
out = xmltodict.unparse(out, pretty=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -655,7 +655,7 @@ General optional parameters:
|
|||||||
</response>
|
</response>
|
||||||
''' % e
|
''' % e
|
||||||
|
|
||||||
return out
|
return out.encode('utf-8')
|
||||||
|
|
||||||
def _api_run(self, *args, **kwargs):
|
def _api_run(self, *args, **kwargs):
|
||||||
""" handles the stuff from the handler """
|
""" handles the stuff from the handler """
|
||||||
|
@@ -338,7 +338,7 @@ _CONFIG_DEFINITIONS = {
|
|||||||
'NMA_ON_NEWDEVICE': (int, 'NMA', 0),
|
'NMA_ON_NEWDEVICE': (int, 'NMA', 0),
|
||||||
'NOTIFICATION_THREADS': (int, 'Advanced', 2),
|
'NOTIFICATION_THREADS': (int, 'Advanced', 2),
|
||||||
'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1),
|
'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1),
|
||||||
'NOTIFY_GROUP_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 0),
|
'NOTIFY_GROUP_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 1),
|
||||||
'NOTIFY_GROUP_RECENTLY_ADDED_PARENT': (int, 'Monitoring', 1),
|
'NOTIFY_GROUP_RECENTLY_ADDED_PARENT': (int, 'Monitoring', 1),
|
||||||
'NOTIFY_GROUP_RECENTLY_ADDED': (int, 'Monitoring', 1),
|
'NOTIFY_GROUP_RECENTLY_ADDED': (int, 'Monitoring', 1),
|
||||||
'NOTIFY_UPLOAD_POSTERS': (int, 'Monitoring', 0),
|
'NOTIFY_UPLOAD_POSTERS': (int, 'Monitoring', 0),
|
||||||
|
@@ -21,6 +21,7 @@ import threading
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import plexpy
|
import plexpy
|
||||||
|
import helpers
|
||||||
import logger
|
import logger
|
||||||
|
|
||||||
FILENAME = "tautulli.db"
|
FILENAME = "tautulli.db"
|
||||||
@@ -57,6 +58,60 @@ def delete_recently_added():
|
|||||||
return clear_table('recently_added')
|
return clear_table('recently_added')
|
||||||
|
|
||||||
|
|
||||||
|
def delete_rows_from_table(table, row_ids):
|
||||||
|
if row_ids and isinstance(row_ids, basestring):
|
||||||
|
row_ids = map(helpers.cast_to_int, row_ids.split(','))
|
||||||
|
|
||||||
|
if row_ids:
|
||||||
|
logger.info(u"Tautulli Database :: Deleting row ids %s from %s database table", row_ids, table)
|
||||||
|
query = "DELETE FROM " + table + " WHERE id IN (%s) " % ','.join(['?'] * len(row_ids))
|
||||||
|
monitor_db = MonitorDatabase()
|
||||||
|
|
||||||
|
try:
|
||||||
|
monitor_db.action(query, row_ids)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(u"Tautulli Database :: Failed to delete rows from %s database table: %s" % (table, row_ids))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def delete_session_history_rows(row_ids=None):
|
||||||
|
success = []
|
||||||
|
for table in ('session_history', 'session_history_media_info', 'session_history_metadata'):
|
||||||
|
success.append(delete_rows_from_table(table=table, row_ids=row_ids))
|
||||||
|
return all(success)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_user_history(user_id=None):
|
||||||
|
if str(user_id).isdigit():
|
||||||
|
monitor_db = MonitorDatabase()
|
||||||
|
|
||||||
|
# Get all history associated with the user_id
|
||||||
|
result = monitor_db.select('SELECT id FROM session_history WHERE user_id = ?',
|
||||||
|
[user_id])
|
||||||
|
row_ids = [row['id'] for row in result]
|
||||||
|
|
||||||
|
logger.info(u"Tautulli Database :: Deleting all history for user_id %s from database." % user_id)
|
||||||
|
return delete_session_history_rows(row_ids=row_ids)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_library_history(section_id=None):
|
||||||
|
if str(section_id).isdigit():
|
||||||
|
monitor_db = MonitorDatabase()
|
||||||
|
|
||||||
|
# Get all history associated with the section_id
|
||||||
|
result = monitor_db.select('SELECT session_history.id FROM session_history '
|
||||||
|
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id '
|
||||||
|
'WHERE session_history_metadata.section_id = ?',
|
||||||
|
[section_id])
|
||||||
|
row_ids = [row['id'] for row in result]
|
||||||
|
|
||||||
|
logger.info(u"Tautulli Database :: Deleting all history for library section_id %s from database." % section_id)
|
||||||
|
return delete_session_history_rows(row_ids=row_ids)
|
||||||
|
|
||||||
|
|
||||||
def db_filename(filename=FILENAME):
|
def db_filename(filename=FILENAME):
|
||||||
""" Returns the filepath to the db """
|
""" Returns the filepath to the db """
|
||||||
|
|
||||||
@@ -72,6 +127,7 @@ def make_backup(cleanup=False, scheduler=False):
|
|||||||
corrupt = ''
|
corrupt = ''
|
||||||
if not integrity:
|
if not integrity:
|
||||||
corrupt = '.corrupt'
|
corrupt = '.corrupt'
|
||||||
|
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_plexpydbcorrupt'})
|
||||||
|
|
||||||
if scheduler:
|
if scheduler:
|
||||||
backup_file = 'tautulli.backup-{}{}.sched.db'.format(arrow.now().format('YYYYMMDDHHmmss'), corrupt)
|
backup_file = 'tautulli.backup-{}{}.sched.db'.format(arrow.now().format('YYYYMMDDHHmmss'), corrupt)
|
||||||
|
@@ -64,7 +64,7 @@ class DataFactory(object):
|
|||||||
|
|
||||||
columns = [
|
columns = [
|
||||||
'session_history.reference_id',
|
'session_history.reference_id',
|
||||||
'session_history.id',
|
'session_history.id AS row_id',
|
||||||
'MAX(started) AS date',
|
'MAX(started) AS date',
|
||||||
'MIN(started) AS started',
|
'MIN(started) AS started',
|
||||||
'MAX(stopped) AS stopped',
|
'MAX(stopped) AS stopped',
|
||||||
@@ -116,7 +116,7 @@ class DataFactory(object):
|
|||||||
|
|
||||||
columns_union = [
|
columns_union = [
|
||||||
'NULL AS reference_id',
|
'NULL AS reference_id',
|
||||||
'NULL AS id',
|
'NULL AS row_id',
|
||||||
'started AS date',
|
'started AS date',
|
||||||
'started',
|
'started',
|
||||||
'stopped',
|
'stopped',
|
||||||
@@ -228,7 +228,7 @@ class DataFactory(object):
|
|||||||
platform = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform'])
|
platform = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform'])
|
||||||
|
|
||||||
row = {'reference_id': item['reference_id'],
|
row = {'reference_id': item['reference_id'],
|
||||||
'id': item['id'],
|
'row_id': item['row_id'],
|
||||||
'date': item['date'],
|
'date': item['date'],
|
||||||
'started': item['started'],
|
'started': item['started'],
|
||||||
'stopped': item['stopped'],
|
'stopped': item['stopped'],
|
||||||
@@ -336,7 +336,6 @@ class DataFactory(object):
|
|||||||
'user': '',
|
'user': '',
|
||||||
'friendly_name': '',
|
'friendly_name': '',
|
||||||
'platform': '',
|
'platform': '',
|
||||||
'platform': '',
|
|
||||||
'live': item['live'],
|
'live': item['live'],
|
||||||
'guid': item['guid'],
|
'guid': item['guid'],
|
||||||
'row_id': item['id']
|
'row_id': item['id']
|
||||||
@@ -1415,7 +1414,7 @@ class DataFactory(object):
|
|||||||
|
|
||||||
if rating_key:
|
if rating_key:
|
||||||
logger.info(u"Tautulli DataFactory :: Deleting lookup info for rating_key %s from the database."
|
logger.info(u"Tautulli DataFactory :: Deleting lookup info for rating_key %s from the database."
|
||||||
% (title, rating_key))
|
% rating_key)
|
||||||
result_themoviedb = monitor_db.action('DELETE FROM themoviedb_lookup WHERE rating_key = ?', [rating_key])
|
result_themoviedb = monitor_db.action('DELETE FROM themoviedb_lookup WHERE rating_key = ?', [rating_key])
|
||||||
result_tvmaze = monitor_db.action('DELETE FROM tvmaze_lookup WHERE rating_key = ?', [rating_key])
|
result_tvmaze = monitor_db.action('DELETE FROM tvmaze_lookup WHERE rating_key = ?', [rating_key])
|
||||||
result_musicbrainz = monitor_db.action('DELETE FROM musicbrainz_lookup WHERE rating_key = ?', [rating_key])
|
result_musicbrainz = monitor_db.action('DELETE FROM musicbrainz_lookup WHERE rating_key = ?', [rating_key])
|
||||||
@@ -1562,22 +1561,6 @@ class DataFactory(object):
|
|||||||
|
|
||||||
return key_list
|
return key_list
|
||||||
|
|
||||||
def delete_session_history_rows(self, row_id=None):
|
|
||||||
monitor_db = database.MonitorDatabase()
|
|
||||||
|
|
||||||
if row_id.isdigit():
|
|
||||||
logger.info(u"Tautulli DataFactory :: Deleting row id %s from the session history database." % row_id)
|
|
||||||
session_history_del = \
|
|
||||||
monitor_db.action('DELETE FROM session_history WHERE id = ?', [row_id])
|
|
||||||
session_history_media_info_del = \
|
|
||||||
monitor_db.action('DELETE FROM session_history_media_info WHERE id = ?', [row_id])
|
|
||||||
session_history_metadata_del = \
|
|
||||||
monitor_db.action('DELETE FROM session_history_metadata WHERE id = ?', [row_id])
|
|
||||||
|
|
||||||
return 'Deleted rows %s.' % row_id
|
|
||||||
else:
|
|
||||||
return 'Unable to delete rows. Input row not valid.'
|
|
||||||
|
|
||||||
def update_metadata(self, old_key_list='', new_key_list='', media_type=''):
|
def update_metadata(self, old_key_list='', new_key_list='', media_type=''):
|
||||||
pms_connect = pmsconnect.PmsConnect()
|
pms_connect = pmsconnect.PmsConnect()
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
@@ -440,7 +440,11 @@ def create_https_certificates(ssl_cert, ssl_key):
|
|||||||
|
|
||||||
This code is stolen from SickBeard (http://github.com/midgetspy/Sick-Beard).
|
This code is stolen from SickBeard (http://github.com/midgetspy/Sick-Beard).
|
||||||
"""
|
"""
|
||||||
|
try:
|
||||||
from OpenSSL import crypto
|
from OpenSSL import crypto
|
||||||
|
except ImportError:
|
||||||
|
logger.error("Unable to generate self-signed certificates: Missing OpenSSL module.")
|
||||||
|
return False
|
||||||
from certgen import createKeyPair, createSelfSignedCertificate, TYPE_RSA
|
from certgen import createKeyPair, createSelfSignedCertificate, TYPE_RSA
|
||||||
|
|
||||||
serial = int(time.time())
|
serial = int(time.time())
|
||||||
@@ -1024,13 +1028,14 @@ def build_datatables_json(kwargs, dt_columns, default_sort_col=None):
|
|||||||
return json.dumps(json_data)
|
return json.dumps(json_data)
|
||||||
|
|
||||||
|
|
||||||
def humanFileSize(bytes, si=False):
|
def humanFileSize(bytes, si=True):
|
||||||
if str(bytes).isdigit():
|
if str(bytes).isdigit():
|
||||||
bytes = cast_to_float(bytes)
|
bytes = cast_to_float(bytes)
|
||||||
else:
|
else:
|
||||||
return bytes
|
return bytes
|
||||||
|
|
||||||
thresh = 1000 if si else 1024
|
#thresh = 1000 if si else 1024
|
||||||
|
thresh = 1024 # Always divide by 2^10 but display SI units
|
||||||
if bytes < thresh:
|
if bytes < thresh:
|
||||||
return str(bytes) + ' B'
|
return str(bytes) + ' B'
|
||||||
|
|
||||||
@@ -1260,8 +1265,10 @@ def mask_config_passwords(config):
|
|||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
def bool_true(value):
|
def bool_true(value, return_none=False):
|
||||||
if value is True or value == 1:
|
if value is None and return_none:
|
||||||
|
return None
|
||||||
|
elif value is True or value == 1:
|
||||||
return True
|
return True
|
||||||
elif isinstance(value, basestring) and value.lower() in ('1', 'true', 't', 'yes', 'y', 'on'):
|
elif isinstance(value, basestring) and value.lower() in ('1', 'true', 't', 'yes', 'y', 'on'):
|
||||||
return True
|
return True
|
||||||
|
@@ -43,7 +43,12 @@ def refresh_libraries():
|
|||||||
library_keys = []
|
library_keys = []
|
||||||
new_keys = []
|
new_keys = []
|
||||||
|
|
||||||
|
# Keep track of section_id to update is_active status
|
||||||
|
section_ids = [common.LIVE_TV_SECTION_ID] # Live TV library always considered active
|
||||||
|
|
||||||
for section in library_sections:
|
for section in library_sections:
|
||||||
|
section_ids.append(helpers.cast_to_int(section['section_id']))
|
||||||
|
|
||||||
section_keys = {'server_id': server_id,
|
section_keys = {'server_id': server_id,
|
||||||
'section_id': section['section_id']}
|
'section_id': section['section_id']}
|
||||||
section_values = {'server_id': server_id,
|
section_values = {'server_id': server_id,
|
||||||
@@ -65,6 +70,10 @@ def refresh_libraries():
|
|||||||
if result == 'insert':
|
if result == 'insert':
|
||||||
new_keys.append(section['section_id'])
|
new_keys.append(section['section_id'])
|
||||||
|
|
||||||
|
query = 'UPDATE library_sections SET is_active = 0 WHERE server_id != ? OR ' \
|
||||||
|
'section_id NOT IN ({})'.format(', '.join(['?'] * len(section_ids)))
|
||||||
|
monitor_db.action(query=query, args=[plexpy.CONFIG.PMS_IDENTIFIER] + section_ids)
|
||||||
|
|
||||||
if plexpy.CONFIG.HOME_LIBRARY_CARDS == ['first_run_wizard']:
|
if plexpy.CONFIG.HOME_LIBRARY_CARDS == ['first_run_wizard']:
|
||||||
plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', library_keys)
|
plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', library_keys)
|
||||||
plexpy.CONFIG.write()
|
plexpy.CONFIG.write()
|
||||||
@@ -289,7 +298,9 @@ class Libraries(object):
|
|||||||
|
|
||||||
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
|
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
|
||||||
|
|
||||||
columns = ['library_sections.section_id',
|
columns = ['library_sections.id AS row_id',
|
||||||
|
'library_sections.server_id',
|
||||||
|
'library_sections.section_id',
|
||||||
'library_sections.section_name',
|
'library_sections.section_name',
|
||||||
'library_sections.section_type',
|
'library_sections.section_type',
|
||||||
'library_sections.count',
|
'library_sections.count',
|
||||||
@@ -303,7 +314,7 @@ class Libraries(object):
|
|||||||
ELSE 0 END) - SUM(CASE WHEN session_history.paused_counter IS NULL THEN 0 ELSE \
|
ELSE 0 END) - SUM(CASE WHEN session_history.paused_counter IS NULL THEN 0 ELSE \
|
||||||
session_history.paused_counter END) AS duration',
|
session_history.paused_counter END) AS duration',
|
||||||
'MAX(session_history.started) AS last_accessed',
|
'MAX(session_history.started) AS last_accessed',
|
||||||
'MAX(session_history.id) AS id',
|
'MAX(session_history.id) AS history_row_id',
|
||||||
'session_history_metadata.full_title AS last_played',
|
'session_history_metadata.full_title AS last_played',
|
||||||
'session_history.rating_key',
|
'session_history.rating_key',
|
||||||
'session_history_metadata.media_type',
|
'session_history_metadata.media_type',
|
||||||
@@ -322,7 +333,8 @@ class Libraries(object):
|
|||||||
'session_history_metadata.guid',
|
'session_history_metadata.guid',
|
||||||
'library_sections.do_notify',
|
'library_sections.do_notify',
|
||||||
'library_sections.do_notify_created',
|
'library_sections.do_notify_created',
|
||||||
'library_sections.keep_history'
|
'library_sections.keep_history',
|
||||||
|
'library_sections.is_active'
|
||||||
]
|
]
|
||||||
try:
|
try:
|
||||||
query = data_tables.ssp_query(table_name='library_sections',
|
query = data_tables.ssp_query(table_name='library_sections',
|
||||||
@@ -361,7 +373,9 @@ class Libraries(object):
|
|||||||
else:
|
else:
|
||||||
library_thumb = common.DEFAULT_COVER_THUMB
|
library_thumb = common.DEFAULT_COVER_THUMB
|
||||||
|
|
||||||
row = {'section_id': item['section_id'],
|
row = {'row_id': item['row_id'],
|
||||||
|
'server_id': item['server_id'],
|
||||||
|
'section_id': item['section_id'],
|
||||||
'section_name': item['section_name'],
|
'section_name': item['section_name'],
|
||||||
'section_type': item['section_type'],
|
'section_type': item['section_type'],
|
||||||
'count': item['count'],
|
'count': item['count'],
|
||||||
@@ -372,7 +386,7 @@ class Libraries(object):
|
|||||||
'plays': item['plays'],
|
'plays': item['plays'],
|
||||||
'duration': item['duration'],
|
'duration': item['duration'],
|
||||||
'last_accessed': item['last_accessed'],
|
'last_accessed': item['last_accessed'],
|
||||||
'id': item['id'],
|
'history_row_id': item['history_row_id'],
|
||||||
'last_played': item['last_played'],
|
'last_played': item['last_played'],
|
||||||
'rating_key': item['rating_key'],
|
'rating_key': item['rating_key'],
|
||||||
'media_type': item['media_type'],
|
'media_type': item['media_type'],
|
||||||
@@ -388,7 +402,8 @@ class Libraries(object):
|
|||||||
'guid': item['guid'],
|
'guid': item['guid'],
|
||||||
'do_notify': helpers.checked(item['do_notify']),
|
'do_notify': helpers.checked(item['do_notify']),
|
||||||
'do_notify_created': helpers.checked(item['do_notify_created']),
|
'do_notify_created': helpers.checked(item['do_notify_created']),
|
||||||
'keep_history': helpers.checked(item['keep_history'])
|
'keep_history': helpers.checked(item['keep_history']),
|
||||||
|
'is_active': item['is_active']
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
@@ -725,7 +740,9 @@ class Libraries(object):
|
|||||||
logger.warn(u"Tautulli Libraries :: Unable to execute database query for set_config: %s." % e)
|
logger.warn(u"Tautulli Libraries :: Unable to execute database query for set_config: %s." % e)
|
||||||
|
|
||||||
def get_details(self, section_id=None):
|
def get_details(self, section_id=None):
|
||||||
default_return = {'section_id': 0,
|
default_return = {'row_id': 0,
|
||||||
|
'server_id': '',
|
||||||
|
'section_id': 0,
|
||||||
'section_name': 'Local',
|
'section_name': 'Local',
|
||||||
'section_type': '',
|
'section_type': '',
|
||||||
'library_thumb': common.DEFAULT_COVER_THUMB,
|
'library_thumb': common.DEFAULT_COVER_THUMB,
|
||||||
@@ -733,6 +750,7 @@ class Libraries(object):
|
|||||||
'count': 0,
|
'count': 0,
|
||||||
'parent_count': 0,
|
'parent_count': 0,
|
||||||
'child_count': 0,
|
'child_count': 0,
|
||||||
|
'is_active': 1,
|
||||||
'do_notify': 0,
|
'do_notify': 0,
|
||||||
'do_notify_created': 0,
|
'do_notify_created': 0,
|
||||||
'keep_history': 1,
|
'keep_history': 1,
|
||||||
@@ -747,9 +765,10 @@ class Libraries(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if str(section_id).isdigit():
|
if str(section_id).isdigit():
|
||||||
query = 'SELECT section_id, section_name, section_type, count, parent_count, child_count, ' \
|
query = 'SELECT id AS row_id, server_id, section_id, section_name, section_type, ' \
|
||||||
|
'count, parent_count, child_count, ' \
|
||||||
'thumb AS library_thumb, custom_thumb_url AS custom_thumb, art AS library_art, ' \
|
'thumb AS library_thumb, custom_thumb_url AS custom_thumb, art AS library_art, ' \
|
||||||
'custom_art_url AS custom_art, ' \
|
'custom_art_url AS custom_art, is_active, ' \
|
||||||
'do_notify, do_notify_created, keep_history, deleted_section ' \
|
'do_notify, do_notify_created, keep_history, deleted_section ' \
|
||||||
'FROM library_sections ' \
|
'FROM library_sections ' \
|
||||||
'WHERE section_id = ? '
|
'WHERE section_id = ? '
|
||||||
@@ -775,7 +794,9 @@ class Libraries(object):
|
|||||||
else:
|
else:
|
||||||
library_art = item['library_art']
|
library_art = item['library_art']
|
||||||
|
|
||||||
library_details = {'section_id': item['section_id'],
|
library_details = {'row_id': item['row_id'],
|
||||||
|
'server_id': item['server_id'],
|
||||||
|
'section_id': item['section_id'],
|
||||||
'section_name': item['section_name'],
|
'section_name': item['section_name'],
|
||||||
'section_type': item['section_type'],
|
'section_type': item['section_type'],
|
||||||
'library_thumb': library_thumb,
|
'library_thumb': library_thumb,
|
||||||
@@ -783,6 +804,7 @@ class Libraries(object):
|
|||||||
'count': item['count'],
|
'count': item['count'],
|
||||||
'parent_count': item['parent_count'],
|
'parent_count': item['parent_count'],
|
||||||
'child_count': item['child_count'],
|
'child_count': item['child_count'],
|
||||||
|
'is_active': item['is_active'],
|
||||||
'do_notify': item['do_notify'],
|
'do_notify': item['do_notify'],
|
||||||
'do_notify_created': item['do_notify_created'],
|
'do_notify_created': item['do_notify_created'],
|
||||||
'keep_history': item['keep_history'],
|
'keep_history': item['keep_history'],
|
||||||
@@ -812,21 +834,25 @@ class Libraries(object):
|
|||||||
# If there is no library data we must return something
|
# If there is no library data we must return something
|
||||||
return default_return
|
return default_return
|
||||||
|
|
||||||
def get_watch_time_stats(self, section_id=None, grouping=None):
|
def get_watch_time_stats(self, section_id=None, grouping=None, query_days=None):
|
||||||
if not session.allow_session_library(section_id):
|
if not session.allow_session_library(section_id):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if grouping is None:
|
if grouping is None:
|
||||||
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
|
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
|
||||||
|
|
||||||
|
if query_days and query_days is not None:
|
||||||
|
query_days = map(helpers.cast_to_int, query_days.split(','))
|
||||||
|
else:
|
||||||
|
query_days = [1, 7, 30, 0]
|
||||||
|
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
time_queries = [1, 7, 30, 0]
|
|
||||||
library_watch_time_stats = []
|
library_watch_time_stats = []
|
||||||
|
|
||||||
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
|
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
|
||||||
|
|
||||||
for days in time_queries:
|
for days in query_days:
|
||||||
try:
|
try:
|
||||||
if days > 0:
|
if days > 0:
|
||||||
if str(section_id).isdigit():
|
if str(section_id).isdigit():
|
||||||
@@ -998,61 +1024,48 @@ class Libraries(object):
|
|||||||
|
|
||||||
return libraries
|
return libraries
|
||||||
|
|
||||||
def delete_all_history(self, section_id=None):
|
def delete(self, server_id=None, section_id=None, row_ids=None, purge_only=False):
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
try:
|
if row_ids and row_ids is not None:
|
||||||
if section_id.isdigit():
|
row_ids = map(helpers.cast_to_int, row_ids.split(','))
|
||||||
logger.info(u"Tautulli Libraries :: Deleting all history for library id %s from database." % section_id)
|
|
||||||
session_history_media_info_del = \
|
|
||||||
monitor_db.action('DELETE FROM '
|
|
||||||
'session_history_media_info '
|
|
||||||
'WHERE session_history_media_info.id IN (SELECT session_history_media_info.id '
|
|
||||||
'FROM session_history_media_info '
|
|
||||||
'JOIN session_history_metadata ON session_history_media_info.id = session_history_metadata.id '
|
|
||||||
'WHERE session_history_metadata.section_id = ?)', [section_id])
|
|
||||||
session_history_del = \
|
|
||||||
monitor_db.action('DELETE FROM '
|
|
||||||
'session_history '
|
|
||||||
'WHERE session_history.id IN (SELECT session_history.id '
|
|
||||||
'FROM session_history '
|
|
||||||
'JOIN session_history_metadata ON session_history.id = session_history_metadata.id '
|
|
||||||
'WHERE session_history_metadata.section_id = ?)', [section_id])
|
|
||||||
session_history_metadata_del = \
|
|
||||||
monitor_db.action('DELETE FROM '
|
|
||||||
'session_history_metadata '
|
|
||||||
'WHERE session_history_metadata.section_id = ?', [section_id])
|
|
||||||
|
|
||||||
return 'Deleted all items for section_id %s.' % section_id
|
# Get the user_ids corresponding to the row_ids
|
||||||
|
result = monitor_db.select('SELECT server_id, section_id FROM library_sections '
|
||||||
|
'WHERE id IN ({})'.format(','.join(['?'] * len(row_ids))), row_ids)
|
||||||
|
|
||||||
|
success = []
|
||||||
|
for library in result:
|
||||||
|
success.append(self.delete(server_id=library['server_id'], section_id=library['section_id'],
|
||||||
|
purge_only=purge_only))
|
||||||
|
return all(success)
|
||||||
|
|
||||||
|
elif str(section_id).isdigit():
|
||||||
|
server_id = server_id or plexpy.CONFIG.PMS_IDENTIFIER
|
||||||
|
if server_id == plexpy.CONFIG.PMS_IDENTIFIER:
|
||||||
|
delete_success = database.delete_library_history(section_id=section_id)
|
||||||
else:
|
else:
|
||||||
return 'Unable to delete items, section_id not valid.'
|
logger.warn(u"Tautulli Libraries :: Library history not deleted for library section_id %s "
|
||||||
except Exception as e:
|
u"because library server_id %s does not match Plex server identifier %s."
|
||||||
logger.warn(u"Tautulli Libraries :: Unable to execute database query for delete_all_history: %s." % e)
|
% (section_id, server_id, plexpy.CONFIG.PMS_IDENTIFIER))
|
||||||
|
delete_success = True
|
||||||
|
|
||||||
def delete(self, section_id=None):
|
if purge_only:
|
||||||
monitor_db = database.MonitorDatabase()
|
return delete_success
|
||||||
|
|
||||||
try:
|
|
||||||
if section_id.isdigit():
|
|
||||||
self.delete_all_history(section_id)
|
|
||||||
logger.info(u"Tautulli Libraries :: Deleting library with id %s from database." % section_id)
|
|
||||||
monitor_db.action('UPDATE library_sections SET deleted_section = 1 WHERE section_id = ?', [section_id])
|
|
||||||
monitor_db.action('UPDATE library_sections SET keep_history = 0 WHERE section_id = ?', [section_id])
|
|
||||||
monitor_db.action('UPDATE library_sections SET do_notify = 0 WHERE section_id = ?', [section_id])
|
|
||||||
monitor_db.action('UPDATE library_sections SET do_notify_created = 0 WHERE section_id = ?', [section_id])
|
|
||||||
|
|
||||||
library_cards = plexpy.CONFIG.HOME_LIBRARY_CARDS
|
|
||||||
if section_id in library_cards:
|
|
||||||
library_cards.remove(section_id)
|
|
||||||
plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', library_cards)
|
|
||||||
plexpy.CONFIG.write()
|
|
||||||
|
|
||||||
return 'Deleted library with id %s.' % section_id
|
|
||||||
else:
|
else:
|
||||||
return 'Unable to delete library, section_id not valid.'
|
logger.info(u"Tautulli Libraries :: Deleting library with server_id %s and section_id %s from database."
|
||||||
|
% (server_id, section_id))
|
||||||
|
try:
|
||||||
|
monitor_db.action('UPDATE library_sections '
|
||||||
|
'SET deleted_section = 1, keep_history = 0, do_notify = 0, do_notify_created = 0 '
|
||||||
|
'WHERE server_id = ? AND section_id = ?', [server_id, section_id])
|
||||||
|
return delete_success
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn(u"Tautulli Libraries :: Unable to execute database query for delete: %s." % e)
|
logger.warn(u"Tautulli Libraries :: Unable to execute database query for delete: %s." % e)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def undelete(self, section_id=None, section_name=None):
|
def undelete(self, section_id=None, section_name=None):
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
@@ -1062,10 +1075,10 @@ class Libraries(object):
|
|||||||
result = monitor_db.select(query=query, args=[section_id])
|
result = monitor_db.select(query=query, args=[section_id])
|
||||||
if result:
|
if result:
|
||||||
logger.info(u"Tautulli Libraries :: Re-adding library with id %s to database." % section_id)
|
logger.info(u"Tautulli Libraries :: Re-adding library with id %s to database." % section_id)
|
||||||
monitor_db.action('UPDATE library_sections SET deleted_section = 0 WHERE section_id = ?', [section_id])
|
monitor_db.action('UPDATE library_sections '
|
||||||
monitor_db.action('UPDATE library_sections SET keep_history = 1 WHERE section_id = ?', [section_id])
|
'SET deleted_section = 0, keep_history = 1, do_notify = 1, do_notify_created = 1 '
|
||||||
monitor_db.action('UPDATE library_sections SET do_notify = 1 WHERE section_id = ?', [section_id])
|
'WHERE section_id = ?',
|
||||||
monitor_db.action('UPDATE library_sections SET do_notify_created = 1 WHERE section_id = ?', [section_id])
|
[section_id])
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@@ -1075,10 +1088,10 @@ class Libraries(object):
|
|||||||
result = monitor_db.select(query=query, args=[section_name])
|
result = monitor_db.select(query=query, args=[section_name])
|
||||||
if result:
|
if result:
|
||||||
logger.info(u"Tautulli Libraries :: Re-adding library with name %s to database." % section_name)
|
logger.info(u"Tautulli Libraries :: Re-adding library with name %s to database." % section_name)
|
||||||
monitor_db.action('UPDATE library_sections SET deleted_section = 0 WHERE section_name = ?', [section_name])
|
monitor_db.action('UPDATE library_sections '
|
||||||
monitor_db.action('UPDATE library_sections SET keep_history = 1 WHERE section_name = ?', [section_name])
|
'SET deleted_section = 0, keep_history = 1, do_notify = 1, do_notify_created = 1 '
|
||||||
monitor_db.action('UPDATE library_sections SET do_notify = 1 WHERE section_name = ?', [section_name])
|
'WHERE section_name = ?',
|
||||||
monitor_db.action('UPDATE library_sections SET do_notify_created = 1 WHERE section_name = ?', [section_name])
|
[section_name])
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
|
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
import threading
|
||||||
|
|
||||||
import plexpy
|
import plexpy
|
||||||
import database
|
import database
|
||||||
@@ -22,6 +23,24 @@ import logger
|
|||||||
|
|
||||||
|
|
||||||
TEMP_DEVICE_TOKEN = None
|
TEMP_DEVICE_TOKEN = None
|
||||||
|
INVALIDATE_TIMER = None
|
||||||
|
|
||||||
|
|
||||||
|
def set_temp_device_token(token=None):
|
||||||
|
global TEMP_DEVICE_TOKEN
|
||||||
|
TEMP_DEVICE_TOKEN = token
|
||||||
|
|
||||||
|
if TEMP_DEVICE_TOKEN is not None:
|
||||||
|
global INVALIDATE_TIMER
|
||||||
|
if INVALIDATE_TIMER:
|
||||||
|
INVALIDATE_TIMER.cancel()
|
||||||
|
invalidate_time = 5 * 60 # 5 minutes
|
||||||
|
INVALIDATE_TIMER = threading.Timer(invalidate_time, set_temp_device_token, args=[None])
|
||||||
|
INVALIDATE_TIMER.start()
|
||||||
|
|
||||||
|
|
||||||
|
def get_temp_device_token():
|
||||||
|
return TEMP_DEVICE_TOKEN
|
||||||
|
|
||||||
|
|
||||||
def get_mobile_devices(device_id=None, device_token=None):
|
def get_mobile_devices(device_id=None, device_token=None):
|
||||||
|
@@ -284,7 +284,7 @@ def send_newsletter(newsletter_id=None, subject=None, body=None, message=None, n
|
|||||||
email_config=newsletter_config['email_config'],
|
email_config=newsletter_config['email_config'],
|
||||||
subject=subject,
|
subject=subject,
|
||||||
body=body,
|
body=body,
|
||||||
messsage=message)
|
message=message)
|
||||||
return agent.send()
|
return agent.send()
|
||||||
else:
|
else:
|
||||||
logger.debug(u"Tautulli Newsletters :: Notification requested but no newsletter_id received.")
|
logger.debug(u"Tautulli Newsletters :: Notification requested but no newsletter_id received.")
|
||||||
|
@@ -339,6 +339,14 @@ def available_notification_actions():
|
|||||||
'body': 'An update is available for Tautulli (version {tautulli_update_version}).',
|
'body': 'An update is available for Tautulli (version {tautulli_update_version}).',
|
||||||
'icon': 'fa-refresh',
|
'icon': 'fa-refresh',
|
||||||
'media_types': ('server',)
|
'media_types': ('server',)
|
||||||
|
},
|
||||||
|
{'label': 'Tautulli Database Corruption',
|
||||||
|
'name': 'on_plexpydbcorrupt',
|
||||||
|
'description': 'Trigger a notification if Tautulli database corruption is detected when backing up the database.',
|
||||||
|
'subject': 'Tautulli ({server_name})',
|
||||||
|
'body': 'Tautulli database corruption detected. Automatic cleanup of database backups is suspended.',
|
||||||
|
'icon': 'fa-database',
|
||||||
|
'media_types': ('server',)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3060,7 +3068,8 @@ class SCRIPTS(Notifier):
|
|||||||
'TAUTULLI_URL': helpers.get_plexpy_url(hostname='localhost'),
|
'TAUTULLI_URL': helpers.get_plexpy_url(hostname='localhost'),
|
||||||
'TAUTULLI_PUBLIC_URL': plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT,
|
'TAUTULLI_PUBLIC_URL': plexpy.CONFIG.HTTP_BASE_URL + plexpy.HTTP_ROOT,
|
||||||
'TAUTULLI_APIKEY': plexpy.CONFIG.API_KEY,
|
'TAUTULLI_APIKEY': plexpy.CONFIG.API_KEY,
|
||||||
'TAUTULLI_ENCODING': plexpy.SYS_ENCODING
|
'TAUTULLI_ENCODING': plexpy.SYS_ENCODING,
|
||||||
|
'TAUTULLI_PYTHON_VERSION': '.'.join(map(str, plexpy.PYTHON_VERSION))
|
||||||
})
|
})
|
||||||
|
|
||||||
if user_id:
|
if user_id:
|
||||||
|
@@ -396,6 +396,7 @@ class PlexTV(object):
|
|||||||
"username": helpers.get_xml_attr(a, 'username'),
|
"username": helpers.get_xml_attr(a, 'username'),
|
||||||
"thumb": helpers.get_xml_attr(a, 'thumb'),
|
"thumb": helpers.get_xml_attr(a, 'thumb'),
|
||||||
"email": helpers.get_xml_attr(a, 'email'),
|
"email": helpers.get_xml_attr(a, 'email'),
|
||||||
|
"is_active": 1,
|
||||||
"is_admin": 1,
|
"is_admin": 1,
|
||||||
"is_home_user": helpers.get_xml_attr(a, 'home'),
|
"is_home_user": helpers.get_xml_attr(a, 'home'),
|
||||||
"is_allow_sync": 1,
|
"is_allow_sync": 1,
|
||||||
@@ -423,6 +424,7 @@ class PlexTV(object):
|
|||||||
"username": helpers.get_xml_attr(a, 'title'),
|
"username": helpers.get_xml_attr(a, 'title'),
|
||||||
"thumb": helpers.get_xml_attr(a, 'thumb'),
|
"thumb": helpers.get_xml_attr(a, 'thumb'),
|
||||||
"email": helpers.get_xml_attr(a, 'email'),
|
"email": helpers.get_xml_attr(a, 'email'),
|
||||||
|
"is_active": 1,
|
||||||
"is_admin": 0,
|
"is_admin": 0,
|
||||||
"is_home_user": helpers.get_xml_attr(a, 'home'),
|
"is_home_user": helpers.get_xml_attr(a, 'home'),
|
||||||
"is_allow_sync": helpers.get_xml_attr(a, 'allowSync'),
|
"is_allow_sync": helpers.get_xml_attr(a, 'allowSync'),
|
||||||
|
@@ -2643,7 +2643,8 @@ class PmsConnect(object):
|
|||||||
'agent': library['agent'],
|
'agent': library['agent'],
|
||||||
'thumb': library['thumb'],
|
'thumb': library['thumb'],
|
||||||
'art': library['art'],
|
'art': library['art'],
|
||||||
'count': children_list['library_count']
|
'count': children_list['library_count'],
|
||||||
|
'is_active': 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if section_type == 'show':
|
if section_type == 'show':
|
||||||
|
154
plexpy/users.py
154
plexpy/users.py
@@ -34,7 +34,11 @@ def refresh_users():
|
|||||||
if result:
|
if result:
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
|
# Keep track of user_id to update is_active status
|
||||||
|
user_ids = [0] # Local user always considered active
|
||||||
|
|
||||||
for item in result:
|
for item in result:
|
||||||
|
user_ids.append(helpers.cast_to_int(item['user_id']))
|
||||||
|
|
||||||
if item.get('shared_libraries'):
|
if item.get('shared_libraries'):
|
||||||
item['shared_libraries'] = ';'.join(item['shared_libraries'])
|
item['shared_libraries'] = ';'.join(item['shared_libraries'])
|
||||||
@@ -58,6 +62,9 @@ def refresh_users():
|
|||||||
|
|
||||||
monitor_db.upsert('users', item, keys_dict)
|
monitor_db.upsert('users', item, keys_dict)
|
||||||
|
|
||||||
|
query = 'UPDATE users SET is_active = 0 WHERE user_id NOT IN ({})'.format(', '.join(['?'] * len(user_ids)))
|
||||||
|
monitor_db.action(query=query, args=user_ids)
|
||||||
|
|
||||||
logger.info(u"Tautulli Users :: Users list refreshed.")
|
logger.info(u"Tautulli Users :: Users list refreshed.")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@@ -92,7 +99,8 @@ class Users(object):
|
|||||||
|
|
||||||
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
|
group_by = 'session_history.reference_id' if grouping else 'session_history.id'
|
||||||
|
|
||||||
columns = ['users.user_id',
|
columns = ['users.id AS row_id',
|
||||||
|
'users.user_id',
|
||||||
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \
|
'(CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = "" \
|
||||||
THEN users.username ELSE users.friendly_name END) AS friendly_name',
|
THEN users.username ELSE users.friendly_name END) AS friendly_name',
|
||||||
'users.thumb AS user_thumb',
|
'users.thumb AS user_thumb',
|
||||||
@@ -102,7 +110,7 @@ class Users(object):
|
|||||||
ELSE 0 END) - SUM(CASE WHEN session_history.paused_counter IS NULL THEN 0 ELSE \
|
ELSE 0 END) - SUM(CASE WHEN session_history.paused_counter IS NULL THEN 0 ELSE \
|
||||||
session_history.paused_counter END) AS duration',
|
session_history.paused_counter END) AS duration',
|
||||||
'MAX(session_history.started) AS last_seen',
|
'MAX(session_history.started) AS last_seen',
|
||||||
'MAX(session_history.id) AS id',
|
'MAX(session_history.id) AS history_row_id',
|
||||||
'session_history_metadata.full_title AS last_played',
|
'session_history_metadata.full_title AS last_played',
|
||||||
'session_history.ip_address',
|
'session_history.ip_address',
|
||||||
'session_history.platform',
|
'session_history.platform',
|
||||||
@@ -121,9 +129,10 @@ class Users(object):
|
|||||||
'session_history_metadata.originally_available_at',
|
'session_history_metadata.originally_available_at',
|
||||||
'session_history_metadata.guid',
|
'session_history_metadata.guid',
|
||||||
'session_history_media_info.transcode_decision',
|
'session_history_media_info.transcode_decision',
|
||||||
'users.do_notify as do_notify',
|
'users.do_notify AS do_notify',
|
||||||
'users.keep_history as keep_history',
|
'users.keep_history AS keep_history',
|
||||||
'users.allow_guest as allow_guest'
|
'users.allow_guest AS allow_guest',
|
||||||
|
'users.is_active AS is_active'
|
||||||
]
|
]
|
||||||
try:
|
try:
|
||||||
query = data_tables.ssp_query(table_name='users',
|
query = data_tables.ssp_query(table_name='users',
|
||||||
@@ -165,14 +174,15 @@ class Users(object):
|
|||||||
# Rename Mystery platform names
|
# Rename Mystery platform names
|
||||||
platform = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform'])
|
platform = common.PLATFORM_NAME_OVERRIDES.get(item['platform'], item['platform'])
|
||||||
|
|
||||||
row = {'user_id': item['user_id'],
|
row = {'row_id': item['row_id'],
|
||||||
|
'user_id': item['user_id'],
|
||||||
'friendly_name': item['friendly_name'],
|
'friendly_name': item['friendly_name'],
|
||||||
'user_thumb': user_thumb,
|
'user_thumb': user_thumb,
|
||||||
'plays': item['plays'],
|
'plays': item['plays'],
|
||||||
'duration': item['duration'],
|
'duration': item['duration'],
|
||||||
'last_seen': item['last_seen'],
|
'last_seen': item['last_seen'],
|
||||||
'last_played': item['last_played'],
|
'last_played': item['last_played'],
|
||||||
'id': item['id'],
|
'history_row_id': item['history_row_id'],
|
||||||
'ip_address': item['ip_address'],
|
'ip_address': item['ip_address'],
|
||||||
'platform': platform,
|
'platform': platform,
|
||||||
'player': item['player'],
|
'player': item['player'],
|
||||||
@@ -189,7 +199,8 @@ class Users(object):
|
|||||||
'transcode_decision': item['transcode_decision'],
|
'transcode_decision': item['transcode_decision'],
|
||||||
'do_notify': helpers.checked(item['do_notify']),
|
'do_notify': helpers.checked(item['do_notify']),
|
||||||
'keep_history': helpers.checked(item['keep_history']),
|
'keep_history': helpers.checked(item['keep_history']),
|
||||||
'allow_guest': helpers.checked(item['allow_guest'])
|
'allow_guest': helpers.checked(item['allow_guest']),
|
||||||
|
'is_active': item['is_active']
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.append(row)
|
rows.append(row)
|
||||||
@@ -216,7 +227,7 @@ class Users(object):
|
|||||||
|
|
||||||
custom_where = ['users.user_id', user_id]
|
custom_where = ['users.user_id', user_id]
|
||||||
|
|
||||||
columns = ['session_history.id',
|
columns = ['session_history.id AS history_row_id',
|
||||||
'MAX(session_history.started) AS last_seen',
|
'MAX(session_history.started) AS last_seen',
|
||||||
'session_history.ip_address',
|
'session_history.ip_address',
|
||||||
'COUNT(session_history.id) AS play_count',
|
'COUNT(session_history.id) AS play_count',
|
||||||
@@ -276,7 +287,7 @@ class Users(object):
|
|||||||
# Rename Mystery platform names
|
# Rename Mystery platform names
|
||||||
platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"])
|
platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"])
|
||||||
|
|
||||||
row = {'id': item['id'],
|
row = {'history_row_id': item['history_row_id'],
|
||||||
'last_seen': item['last_seen'],
|
'last_seen': item['last_seen'],
|
||||||
'ip_address': item['ip_address'],
|
'ip_address': item['ip_address'],
|
||||||
'play_count': item['play_count'],
|
'play_count': item['play_count'],
|
||||||
@@ -325,11 +336,13 @@ class Users(object):
|
|||||||
logger.warn(u"Tautulli Users :: Unable to execute database query for set_config: %s." % e)
|
logger.warn(u"Tautulli Users :: Unable to execute database query for set_config: %s." % e)
|
||||||
|
|
||||||
def get_details(self, user_id=None, user=None, email=None):
|
def get_details(self, user_id=None, user=None, email=None):
|
||||||
default_return = {'user_id': 0,
|
default_return = {'row_id': 0,
|
||||||
|
'user_id': 0,
|
||||||
'username': 'Local',
|
'username': 'Local',
|
||||||
'friendly_name': 'Local',
|
'friendly_name': 'Local',
|
||||||
'user_thumb': common.DEFAULT_USER_THUMB,
|
'user_thumb': common.DEFAULT_USER_THUMB,
|
||||||
'email': '',
|
'email': '',
|
||||||
|
'is_active': 1,
|
||||||
'is_admin': '',
|
'is_admin': '',
|
||||||
'is_home_user': 0,
|
'is_home_user': 0,
|
||||||
'is_allow_sync': 0,
|
'is_allow_sync': 0,
|
||||||
@@ -349,22 +362,28 @@ class Users(object):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if str(user_id).isdigit():
|
if str(user_id).isdigit():
|
||||||
query = 'SELECT user_id, username, friendly_name, thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \
|
query = 'SELECT id AS row_id, user_id, username, friendly_name, ' \
|
||||||
'email, is_admin, is_home_user, is_allow_sync, is_restricted, do_notify, keep_history, deleted_user, ' \
|
'thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \
|
||||||
|
'email, is_active, is_admin, is_home_user, is_allow_sync, is_restricted, ' \
|
||||||
|
'do_notify, keep_history, deleted_user, ' \
|
||||||
'allow_guest, shared_libraries ' \
|
'allow_guest, shared_libraries ' \
|
||||||
'FROM users ' \
|
'FROM users ' \
|
||||||
'WHERE user_id = ? '
|
'WHERE user_id = ? '
|
||||||
result = monitor_db.select(query, args=[user_id])
|
result = monitor_db.select(query, args=[user_id])
|
||||||
elif user:
|
elif user:
|
||||||
query = 'SELECT user_id, username, friendly_name, thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \
|
query = 'SELECT id AS row_id, user_id, username, friendly_name, ' \
|
||||||
'email, is_admin, is_home_user, is_allow_sync, is_restricted, do_notify, keep_history, deleted_user, ' \
|
'thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \
|
||||||
|
'email, is_active, is_admin, is_home_user, is_allow_sync, is_restricted, ' \
|
||||||
|
'do_notify, keep_history, deleted_user, ' \
|
||||||
'allow_guest, shared_libraries ' \
|
'allow_guest, shared_libraries ' \
|
||||||
'FROM users ' \
|
'FROM users ' \
|
||||||
'WHERE username = ? COLLATE NOCASE '
|
'WHERE username = ? COLLATE NOCASE '
|
||||||
result = monitor_db.select(query, args=[user])
|
result = monitor_db.select(query, args=[user])
|
||||||
elif email:
|
elif email:
|
||||||
query = 'SELECT user_id, username, friendly_name, thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \
|
query = 'SELECT id AS row_id, user_id, username, friendly_name, ' \
|
||||||
'email, is_admin, is_home_user, is_allow_sync, is_restricted, do_notify, keep_history, deleted_user, ' \
|
'thumb AS user_thumb, custom_avatar_url AS custom_thumb, ' \
|
||||||
|
'email, is_active, is_admin, is_home_user, is_allow_sync, is_restricted, ' \
|
||||||
|
'do_notify, keep_history, deleted_user, ' \
|
||||||
'allow_guest, shared_libraries ' \
|
'allow_guest, shared_libraries ' \
|
||||||
'FROM users ' \
|
'FROM users ' \
|
||||||
'WHERE email = ? COLLATE NOCASE '
|
'WHERE email = ? COLLATE NOCASE '
|
||||||
@@ -394,11 +413,13 @@ class Users(object):
|
|||||||
|
|
||||||
shared_libraries = tuple(item['shared_libraries'].split(';')) if item['shared_libraries'] else ()
|
shared_libraries = tuple(item['shared_libraries'].split(';')) if item['shared_libraries'] else ()
|
||||||
|
|
||||||
user_details = {'user_id': item['user_id'],
|
user_details = {'row_id': item['row_id'],
|
||||||
|
'user_id': item['user_id'],
|
||||||
'username': item['username'],
|
'username': item['username'],
|
||||||
'friendly_name': friendly_name,
|
'friendly_name': friendly_name,
|
||||||
'user_thumb': user_thumb,
|
'user_thumb': user_thumb,
|
||||||
'email': item['email'],
|
'email': item['email'],
|
||||||
|
'is_active': item['is_active'],
|
||||||
'is_admin': item['is_admin'],
|
'is_admin': item['is_admin'],
|
||||||
'is_home_user': item['is_home_user'],
|
'is_home_user': item['is_home_user'],
|
||||||
'is_allow_sync': item['is_allow_sync'],
|
'is_allow_sync': item['is_allow_sync'],
|
||||||
@@ -434,21 +455,25 @@ class Users(object):
|
|||||||
# Use "Local" user to retain compatibility with PlexWatch database value
|
# Use "Local" user to retain compatibility with PlexWatch database value
|
||||||
return default_return
|
return default_return
|
||||||
|
|
||||||
def get_watch_time_stats(self, user_id=None, grouping=None):
|
def get_watch_time_stats(self, user_id=None, grouping=None, query_days=None):
|
||||||
if not session.allow_session_user(user_id):
|
if not session.allow_session_user(user_id):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if grouping is None:
|
if grouping is None:
|
||||||
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
|
grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES
|
||||||
|
|
||||||
|
if query_days and query_days is not None:
|
||||||
|
query_days = map(helpers.cast_to_int, query_days.split(','))
|
||||||
|
else:
|
||||||
|
query_days = [1, 7, 30, 0]
|
||||||
|
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
time_queries = [1, 7, 30, 0]
|
|
||||||
user_watch_time_stats = []
|
user_watch_time_stats = []
|
||||||
|
|
||||||
group_by = 'reference_id' if grouping else 'id'
|
group_by = 'reference_id' if grouping else 'id'
|
||||||
|
|
||||||
for days in time_queries:
|
for days in query_days:
|
||||||
try:
|
try:
|
||||||
if days > 0:
|
if days > 0:
|
||||||
if str(user_id).isdigit():
|
if str(user_id).isdigit():
|
||||||
@@ -601,8 +626,8 @@ class Users(object):
|
|||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
query = 'SELECT user_id, username, friendly_name, thumb, custom_avatar_url, email, ' \
|
query = 'SELECT id AS row_id, user_id, username, friendly_name, thumb, custom_avatar_url, email, ' \
|
||||||
'is_admin, is_home_user, is_allow_sync, is_restricted, ' \
|
'is_active, is_admin, is_home_user, is_allow_sync, is_restricted, ' \
|
||||||
'do_notify, keep_history, allow_guest, server_token, shared_libraries, ' \
|
'do_notify, keep_history, allow_guest, server_token, shared_libraries, ' \
|
||||||
'filter_all, filter_movies, filter_tv, filter_music, filter_photos ' \
|
'filter_all, filter_movies, filter_tv, filter_music, filter_photos ' \
|
||||||
'FROM users WHERE deleted_user = 0'
|
'FROM users WHERE deleted_user = 0'
|
||||||
@@ -613,11 +638,13 @@ class Users(object):
|
|||||||
|
|
||||||
users = []
|
users = []
|
||||||
for item in result:
|
for item in result:
|
||||||
user = {'user_id': item['user_id'],
|
user = {'row_id': item['row_id'],
|
||||||
|
'user_id': item['user_id'],
|
||||||
'username': item['username'],
|
'username': item['username'],
|
||||||
'friendly_name': item['friendly_name'] or item['username'],
|
'friendly_name': item['friendly_name'] or item['username'],
|
||||||
'thumb': item['custom_avatar_url'] or item['thumb'],
|
'thumb': item['custom_avatar_url'] or item['thumb'],
|
||||||
'email': item['email'],
|
'email': item['email'],
|
||||||
|
'is_active': item['is_active'],
|
||||||
'is_admin': item['is_admin'],
|
'is_admin': item['is_admin'],
|
||||||
'is_home_user': item['is_home_user'],
|
'is_home_user': item['is_home_user'],
|
||||||
'is_allow_sync': item['is_allow_sync'],
|
'is_allow_sync': item['is_allow_sync'],
|
||||||
@@ -637,54 +664,41 @@ class Users(object):
|
|||||||
|
|
||||||
return users
|
return users
|
||||||
|
|
||||||
def delete_all_history(self, user_id=None):
|
def delete(self, user_id=None, row_ids=None, purge_only=False):
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
try:
|
if row_ids and row_ids is not None:
|
||||||
if str(user_id).isdigit():
|
row_ids = map(helpers.cast_to_int, row_ids.split(','))
|
||||||
logger.info(u"Tautulli Users :: Deleting all history for user id %s from database." % user_id)
|
|
||||||
session_history_media_info_del = \
|
|
||||||
monitor_db.action('DELETE FROM '
|
|
||||||
'session_history_media_info '
|
|
||||||
'WHERE session_history_media_info.id IN (SELECT session_history_media_info.id '
|
|
||||||
'FROM session_history_media_info '
|
|
||||||
'JOIN session_history ON session_history_media_info.id = session_history.id '
|
|
||||||
'WHERE session_history.user_id = ?)', [user_id])
|
|
||||||
session_history_metadata_del = \
|
|
||||||
monitor_db.action('DELETE FROM '
|
|
||||||
'session_history_metadata '
|
|
||||||
'WHERE session_history_metadata.id IN (SELECT session_history_metadata.id '
|
|
||||||
'FROM session_history_metadata '
|
|
||||||
'JOIN session_history ON session_history_metadata.id = session_history.id '
|
|
||||||
'WHERE session_history.user_id = ?)', [user_id])
|
|
||||||
session_history_del = \
|
|
||||||
monitor_db.action('DELETE FROM '
|
|
||||||
'session_history '
|
|
||||||
'WHERE session_history.user_id = ?', [user_id])
|
|
||||||
|
|
||||||
return 'Deleted all items for user_id %s.' % user_id
|
# Get the user_ids corresponding to the row_ids
|
||||||
|
result = monitor_db.select('SELECT user_id FROM users '
|
||||||
|
'WHERE id IN ({})'.format(','.join(['?'] * len(row_ids))), row_ids)
|
||||||
|
|
||||||
|
success = []
|
||||||
|
for user in result:
|
||||||
|
success.append(self.delete(user_id=user['user_id'],
|
||||||
|
purge_only=purge_only))
|
||||||
|
return all(success)
|
||||||
|
|
||||||
|
elif str(user_id).isdigit():
|
||||||
|
delete_success = database.delete_user_history(user_id=user_id)
|
||||||
|
|
||||||
|
if purge_only:
|
||||||
|
return delete_success
|
||||||
else:
|
else:
|
||||||
return 'Unable to delete items. Input user_id not valid.'
|
logger.info(u"Tautulli Users :: Deleting user with user_id %s from database."
|
||||||
except Exception as e:
|
% user_id)
|
||||||
logger.warn(u"Tautulli Users :: Unable to execute database query for delete_all_history: %s." % e)
|
|
||||||
|
|
||||||
def delete(self, user_id=None):
|
|
||||||
monitor_db = database.MonitorDatabase()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if str(user_id).isdigit():
|
monitor_db.action('UPDATE users '
|
||||||
self.delete_all_history(user_id)
|
'SET deleted_user = 1, keep_history = 0, do_notify = 0 '
|
||||||
logger.info(u"Tautulli Users :: Deleting user with id %s from database." % user_id)
|
'WHERE user_id = ?', [user_id])
|
||||||
monitor_db.action('UPDATE users SET deleted_user = 1 WHERE user_id = ?', [user_id])
|
return delete_success
|
||||||
monitor_db.action('UPDATE users SET keep_history = 0 WHERE user_id = ?', [user_id])
|
|
||||||
monitor_db.action('UPDATE users SET do_notify = 0 WHERE user_id = ?', [user_id])
|
|
||||||
|
|
||||||
return 'Deleted user with id %s.' % user_id
|
|
||||||
else:
|
|
||||||
return 'Unable to delete user, user_id not valid.'
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn(u"Tautulli Users :: Unable to execute database query for delete: %s." % e)
|
logger.warn(u"Tautulli Users :: Unable to execute database query for delete: %s." % e)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def undelete(self, user_id=None, username=None):
|
def undelete(self, user_id=None, username=None):
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
@@ -694,9 +708,9 @@ class Users(object):
|
|||||||
result = monitor_db.select(query=query, args=[user_id])
|
result = monitor_db.select(query=query, args=[user_id])
|
||||||
if result:
|
if result:
|
||||||
logger.info(u"Tautulli Users :: Re-adding user with id %s to database." % user_id)
|
logger.info(u"Tautulli Users :: Re-adding user with id %s to database." % user_id)
|
||||||
monitor_db.action('UPDATE users SET deleted_user = 0 WHERE user_id = ?', [user_id])
|
monitor_db.action('UPDATE users '
|
||||||
monitor_db.action('UPDATE users SET keep_history = 1 WHERE user_id = ?', [user_id])
|
'SET deleted_user = 0, keep_history = 1, do_notify = 1 '
|
||||||
monitor_db.action('UPDATE users SET do_notify = 1 WHERE user_id = ?', [user_id])
|
'WHERE user_id = ?', [user_id])
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
@@ -706,9 +720,9 @@ class Users(object):
|
|||||||
result = monitor_db.select(query=query, args=[username])
|
result = monitor_db.select(query=query, args=[username])
|
||||||
if result:
|
if result:
|
||||||
logger.info(u"Tautulli Users :: Re-adding user with username %s to database." % username)
|
logger.info(u"Tautulli Users :: Re-adding user with username %s to database." % username)
|
||||||
monitor_db.action('UPDATE users SET deleted_user = 0 WHERE username = ?', [username])
|
monitor_db.action('UPDATE users '
|
||||||
monitor_db.action('UPDATE users SET keep_history = 1 WHERE username = ?', [username])
|
'SET deleted_user = 0, keep_history = 1, do_notify = 1 '
|
||||||
monitor_db.action('UPDATE users SET do_notify = 1 WHERE username = ?', [username])
|
'WHERE username = ?', [username])
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@@ -1,2 +1,2 @@
|
|||||||
PLEXPY_BRANCH = "master"
|
PLEXPY_BRANCH = "beta"
|
||||||
PLEXPY_RELEASE_VERSION = "v2.2.1"
|
PLEXPY_RELEASE_VERSION = "v2.2.2-beta"
|
||||||
|
@@ -251,7 +251,7 @@ def update():
|
|||||||
logger.info('Windows .exe updating not supported yet.')
|
logger.info('Windows .exe updating not supported yet.')
|
||||||
|
|
||||||
elif plexpy.INSTALL_TYPE == 'git':
|
elif plexpy.INSTALL_TYPE == 'git':
|
||||||
output, err = runGit('pull {} {} --ff-only'.format(plexpy.CONFIG.GIT_REMOTE,
|
output, err = runGit('pull --ff-only {} {}'.format(plexpy.CONFIG.GIT_REMOTE,
|
||||||
plexpy.CONFIG.GIT_BRANCH))
|
plexpy.CONFIG.GIT_BRANCH))
|
||||||
|
|
||||||
if not output:
|
if not output:
|
||||||
|
@@ -397,7 +397,8 @@ class WebInterface(object):
|
|||||||
"do_notify_created": "Checked",
|
"do_notify_created": "Checked",
|
||||||
"duration": 1578037,
|
"duration": 1578037,
|
||||||
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
||||||
"id": 1128,
|
"histroy_row_id": 1128,
|
||||||
|
"is_active": 1,
|
||||||
"keep_history": "Checked",
|
"keep_history": "Checked",
|
||||||
"labels": [],
|
"labels": [],
|
||||||
"last_accessed": 1462693216,
|
"last_accessed": 1462693216,
|
||||||
@@ -413,9 +414,11 @@ class WebInterface(object):
|
|||||||
"parent_title": "",
|
"parent_title": "",
|
||||||
"plays": 772,
|
"plays": 772,
|
||||||
"rating_key": 153037,
|
"rating_key": 153037,
|
||||||
|
"row_id": 1,
|
||||||
"section_id": 2,
|
"section_id": 2,
|
||||||
"section_name": "TV Shows",
|
"section_name": "TV Shows",
|
||||||
"section_type": "Show",
|
"section_type": "Show",
|
||||||
|
"server_id": "ds48g4r354a8v9byrrtr697g3g79w",
|
||||||
"thumb": "/library/metadata/153036/thumb/1462175062",
|
"thumb": "/library/metadata/153036/thumb/1462175062",
|
||||||
"year": 2016
|
"year": 2016
|
||||||
},
|
},
|
||||||
@@ -441,6 +444,8 @@ class WebInterface(object):
|
|||||||
("duration", True, False)]
|
("duration", True, False)]
|
||||||
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "section_name")
|
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "section_name")
|
||||||
|
|
||||||
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
library_data = libraries.Libraries()
|
library_data = libraries.Libraries()
|
||||||
library_list = library_data.get_datatables_list(kwargs=kwargs, grouping=grouping)
|
library_list = library_data.get_datatables_list(kwargs=kwargs, grouping=grouping)
|
||||||
|
|
||||||
@@ -786,13 +791,16 @@ class WebInterface(object):
|
|||||||
"deleted_section": 0,
|
"deleted_section": 0,
|
||||||
"do_notify": 1,
|
"do_notify": 1,
|
||||||
"do_notify_created": 1,
|
"do_notify_created": 1,
|
||||||
|
"is_active": 1,
|
||||||
"keep_history": 1,
|
"keep_history": 1,
|
||||||
"library_art": "/:/resources/movie-fanart.jpg",
|
"library_art": "/:/resources/movie-fanart.jpg",
|
||||||
"library_thumb": "/:/resources/movie.png",
|
"library_thumb": "/:/resources/movie.png",
|
||||||
"parent_count": null,
|
"parent_count": null,
|
||||||
|
"row_id": 1,
|
||||||
"section_id": 1,
|
"section_id": 1,
|
||||||
"section_name": "Movies",
|
"section_name": "Movies",
|
||||||
"section_type": "movie"
|
"section_type": "movie",
|
||||||
|
"server_id": "ds48g4r354a8v9byrrtr697g3g79w"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
@@ -810,7 +818,7 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def get_library_watch_time_stats(self, section_id=None, grouping=None, **kwargs):
|
def get_library_watch_time_stats(self, section_id=None, grouping=None, query_days=None, **kwargs):
|
||||||
""" Get a library's watch time statistics.
|
""" Get a library's watch time statistics.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -819,6 +827,7 @@ class WebInterface(object):
|
|||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
grouping (int): 0 or 1
|
grouping (int): 0 or 1
|
||||||
|
query_days (str): Comma separated days, e.g. "1,7,30,0"
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
json:
|
json:
|
||||||
@@ -841,11 +850,12 @@ class WebInterface(object):
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
if section_id:
|
if section_id:
|
||||||
library_data = libraries.Libraries()
|
library_data = libraries.Libraries()
|
||||||
result = library_data.get_watch_time_stats(section_id=section_id, grouping=grouping)
|
result = library_data.get_watch_time_stats(section_id=section_id, grouping=grouping,
|
||||||
|
query_days=query_days)
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
@@ -884,7 +894,7 @@ class WebInterface(object):
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
if section_id:
|
if section_id:
|
||||||
library_data = libraries.Libraries()
|
library_data = libraries.Libraries()
|
||||||
@@ -900,7 +910,7 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def delete_all_library_history(self, section_id, **kwargs):
|
def delete_all_library_history(self, server_id=None, section_id=None, row_ids=None, **kwargs):
|
||||||
""" Delete all Tautulli history for a specific library.
|
""" Delete all Tautulli history for a specific library.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -908,27 +918,28 @@ class WebInterface(object):
|
|||||||
section_id (str): The id of the Plex library section
|
section_id (str): The id of the Plex library section
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
None
|
server_id (str): The Plex server identifier of the library section
|
||||||
|
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
if (server_id and section_id) or row_ids:
|
||||||
library_data = libraries.Libraries()
|
library_data = libraries.Libraries()
|
||||||
|
success = library_data.delete(server_id=server_id, section_id=section_id, row_ids=row_ids, purge_only=True)
|
||||||
if section_id:
|
if success:
|
||||||
delete_row = library_data.delete_all_history(section_id=section_id)
|
return {'result': 'success', 'message': 'Deleted library history.'}
|
||||||
|
|
||||||
if delete_row:
|
|
||||||
return {'message': delete_row}
|
|
||||||
else:
|
else:
|
||||||
return {'message': 'no data received'}
|
return {'result': 'error', 'message': 'Failed to delete library(s) history.'}
|
||||||
|
else:
|
||||||
|
return {'result': 'error', 'message': 'No server id and section id or row ids received.'}
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def delete_library(self, section_id, **kwargs):
|
def delete_library(self, server_id=None, section_id=None, row_ids=None, **kwargs):
|
||||||
""" Delete a library section from Tautulli. Also erases all history for the library.
|
""" Delete a library section from Tautulli. Also erases all history for the library.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -936,21 +947,22 @@ class WebInterface(object):
|
|||||||
section_id (str): The id of the Plex library section
|
section_id (str): The id of the Plex library section
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
None
|
server_id (str): The Plex server identifier of the library section
|
||||||
|
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
|
if (server_id and section_id) or row_ids:
|
||||||
library_data = libraries.Libraries()
|
library_data = libraries.Libraries()
|
||||||
|
success = library_data.delete(server_id=server_id, section_id=section_id, row_ids=row_ids)
|
||||||
if section_id:
|
if success:
|
||||||
delete_row = library_data.delete(section_id=section_id)
|
return {'result': 'success', 'message': 'Deleted library.'}
|
||||||
|
|
||||||
if delete_row:
|
|
||||||
return {'message': delete_row}
|
|
||||||
else:
|
else:
|
||||||
return {'message': 'no data received'}
|
return {'result': 'error', 'message': 'Failed to delete library(s).'}
|
||||||
|
else:
|
||||||
|
return {'result': 'error', 'message': 'No server id and section id or row ids received.'}
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@@ -1066,8 +1078,9 @@ class WebInterface(object):
|
|||||||
"duration": 2998290,
|
"duration": 2998290,
|
||||||
"friendly_name": "Jon Snow",
|
"friendly_name": "Jon Snow",
|
||||||
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
||||||
"id": 1121,
|
"history_row_id": 1121,
|
||||||
"ip_address": "xxx.xxx.xxx.xxx",
|
"ip_address": "xxx.xxx.xxx.xxx",
|
||||||
|
"is_active": 1,
|
||||||
"keep_history": "Checked",
|
"keep_history": "Checked",
|
||||||
"last_played": "Game of Thrones - The Red Woman",
|
"last_played": "Game of Thrones - The Red Woman",
|
||||||
"last_seen": 1462591869,
|
"last_seen": 1462591869,
|
||||||
@@ -1081,6 +1094,7 @@ class WebInterface(object):
|
|||||||
"player": "Plex Web (Chrome)",
|
"player": "Plex Web (Chrome)",
|
||||||
"plays": 487,
|
"plays": 487,
|
||||||
"rating_key": 153037,
|
"rating_key": 153037,
|
||||||
|
"row_id": 1,
|
||||||
"thumb": "/library/metadata/153036/thumb/1462175062",
|
"thumb": "/library/metadata/153036/thumb/1462175062",
|
||||||
"transcode_decision": "transcode",
|
"transcode_decision": "transcode",
|
||||||
"user_id": 133788,
|
"user_id": 133788,
|
||||||
@@ -1108,6 +1122,8 @@ class WebInterface(object):
|
|||||||
("duration", True, False)]
|
("duration", True, False)]
|
||||||
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "friendly_name")
|
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "friendly_name")
|
||||||
|
|
||||||
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
user_data = users.Users()
|
user_data = users.Users()
|
||||||
user_list = user_data.get_datatables_list(kwargs=kwargs, grouping=grouping)
|
user_list = user_data.get_datatables_list(kwargs=kwargs, grouping=grouping)
|
||||||
|
|
||||||
@@ -1402,10 +1418,13 @@ class WebInterface(object):
|
|||||||
"do_notify": 1,
|
"do_notify": 1,
|
||||||
"email": "Jon.Snow.1337@CastleBlack.com",
|
"email": "Jon.Snow.1337@CastleBlack.com",
|
||||||
"friendly_name": "Jon Snow",
|
"friendly_name": "Jon Snow",
|
||||||
|
"is_active": 1,
|
||||||
|
"is_admin": 0,
|
||||||
"is_allow_sync": 1,
|
"is_allow_sync": 1,
|
||||||
"is_home_user": 1,
|
"is_home_user": 1,
|
||||||
"is_restricted": 0,
|
"is_restricted": 0,
|
||||||
"keep_history": 1,
|
"keep_history": 1,
|
||||||
|
"row_id": 1,
|
||||||
"shared_libraries": ["10", "1", "4", "5", "15", "20", "2"],
|
"shared_libraries": ["10", "1", "4", "5", "15", "20", "2"],
|
||||||
"user_id": 133788,
|
"user_id": 133788,
|
||||||
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
"user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
||||||
@@ -1427,7 +1446,7 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def get_user_watch_time_stats(self, user_id=None, grouping=None, **kwargs):
|
def get_user_watch_time_stats(self, user_id=None, grouping=None, query_days=None, **kwargs):
|
||||||
""" Get a user's watch time statistics.
|
""" Get a user's watch time statistics.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -1436,6 +1455,7 @@ class WebInterface(object):
|
|||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
grouping (int): 0 or 1
|
grouping (int): 0 or 1
|
||||||
|
query_days (str): Comma separated days, e.g. "1,7,30,0"
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
json:
|
json:
|
||||||
@@ -1458,11 +1478,11 @@ class WebInterface(object):
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
if user_id:
|
if user_id:
|
||||||
user_data = users.Users()
|
user_data = users.Users()
|
||||||
result = user_data.get_watch_time_stats(user_id=user_id, grouping=grouping)
|
result = user_data.get_watch_time_stats(user_id=user_id, grouping=grouping, query_days=query_days)
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
@@ -1501,7 +1521,7 @@ class WebInterface(object):
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
if user_id:
|
if user_id:
|
||||||
user_data = users.Users()
|
user_data = users.Users()
|
||||||
@@ -1517,7 +1537,7 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def delete_all_user_history(self, user_id, **kwargs):
|
def delete_all_user_history(self, user_id=None, row_ids=None, **kwargs):
|
||||||
""" Delete all Tautulli history for a specific user.
|
""" Delete all Tautulli history for a specific user.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -1525,25 +1545,27 @@ class WebInterface(object):
|
|||||||
user_id (str): The id of the Plex user
|
user_id (str): The id of the Plex user
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
None
|
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
if user_id:
|
if user_id or row_ids:
|
||||||
user_data = users.Users()
|
user_data = users.Users()
|
||||||
delete_row = user_data.delete_all_history(user_id=user_id)
|
success = user_data.delete(user_id=user_id, row_ids=row_ids, purge_only=True)
|
||||||
if delete_row:
|
if success:
|
||||||
return {'message': delete_row}
|
return {'result': 'success', 'message': 'Deleted user history.'}
|
||||||
else:
|
else:
|
||||||
return {'message': 'no data received'}
|
return {'result': 'error', 'message': 'Failed to delete user(s) history.'}
|
||||||
|
else:
|
||||||
|
return {'result': 'error', 'message': 'No user id or row ids received.'}
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def delete_user(self, user_id, **kwargs):
|
def delete_user(self, user_id=None, row_ids=None, **kwargs):
|
||||||
""" Delete a user from Tautulli. Also erases all history for the user.
|
""" Delete a user from Tautulli. Also erases all history for the user.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -1551,19 +1573,21 @@ class WebInterface(object):
|
|||||||
user_id (str): The id of the Plex user
|
user_id (str): The id of the Plex user
|
||||||
|
|
||||||
Optional parameters:
|
Optional parameters:
|
||||||
None
|
row_ids (str): Comma separated row ids to delete, e.g. "2,3,8"
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
if user_id:
|
if user_id or row_ids:
|
||||||
user_data = users.Users()
|
user_data = users.Users()
|
||||||
delete_row = user_data.delete(user_id=user_id)
|
success = user_data.delete(user_id=user_id, row_ids=row_ids)
|
||||||
if delete_row:
|
if success:
|
||||||
return {'message': delete_row}
|
return {'result': 'success', 'message': 'Deleted user.'}
|
||||||
else:
|
else:
|
||||||
return {'message': 'no data received'}
|
return {'result': 'error', 'message': 'Failed to delete user(s).'}
|
||||||
|
else:
|
||||||
|
return {'result': 'error', 'message': 'No user id or row ids received.'}
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@@ -1651,7 +1675,6 @@ class WebInterface(object):
|
|||||||
"group_count": 1,
|
"group_count": 1,
|
||||||
"group_ids": "1124",
|
"group_ids": "1124",
|
||||||
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
"guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
|
||||||
"id": 1124,
|
|
||||||
"ip_address": "xxx.xxx.xxx.xxx",
|
"ip_address": "xxx.xxx.xxx.xxx",
|
||||||
"live": 0,
|
"live": 0,
|
||||||
"media_index": 17,
|
"media_index": 17,
|
||||||
@@ -1667,6 +1690,7 @@ class WebInterface(object):
|
|||||||
"player": "Castle-PC",
|
"player": "Castle-PC",
|
||||||
"rating_key": 4348,
|
"rating_key": 4348,
|
||||||
"reference_id": 1123,
|
"reference_id": 1123,
|
||||||
|
"row_id": 1124,
|
||||||
"session_key": null,
|
"session_key": null,
|
||||||
"started": 1462688107,
|
"started": 1462688107,
|
||||||
"state": null,
|
"state": null,
|
||||||
@@ -1703,10 +1727,7 @@ class WebInterface(object):
|
|||||||
("watched_status", False, False)]
|
("watched_status", False, False)]
|
||||||
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "date")
|
kwargs['json_data'] = build_datatables_json(kwargs, dt_columns, "date")
|
||||||
|
|
||||||
if grouping and str(grouping).isdigit():
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
grouping = int(grouping)
|
|
||||||
elif grouping == 'false':
|
|
||||||
grouping = 0
|
|
||||||
|
|
||||||
custom_where = []
|
custom_where = []
|
||||||
if user_id:
|
if user_id:
|
||||||
@@ -1844,16 +1865,32 @@ class WebInterface(object):
|
|||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
def delete_history_rows(self, row_id, **kwargs):
|
@addtoapi("delete_history")
|
||||||
|
def delete_history_rows(self, row_ids=None, **kwargs):
|
||||||
|
""" Delete history rows from Tautulli.
|
||||||
|
|
||||||
|
```
|
||||||
|
Required parameters:
|
||||||
|
row_ids (str): Comma separated row ids to delete, e.g. "65,110,2,3645"
|
||||||
|
|
||||||
|
Optional parameters:
|
||||||
|
None
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
```
|
||||||
|
"""
|
||||||
data_factory = datafactory.DataFactory()
|
data_factory = datafactory.DataFactory()
|
||||||
|
|
||||||
if row_id:
|
if row_ids:
|
||||||
delete_row = data_factory.delete_session_history_rows(row_id=row_id)
|
success = database.delete_session_history_rows(row_ids=row_ids)
|
||||||
|
|
||||||
if delete_row:
|
if success:
|
||||||
return {'message': delete_row}
|
return {'result': 'success', 'message': 'Deleted history.'}
|
||||||
else:
|
else:
|
||||||
return {'message': 'no data received'}
|
return {'result': 'error', 'message': 'Failed to delete history.'}
|
||||||
|
else:
|
||||||
|
return {'result': 'error', 'message': 'No row ids received.'}
|
||||||
|
|
||||||
|
|
||||||
##### Graphs #####
|
##### Graphs #####
|
||||||
@@ -1922,10 +1959,13 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_per_day(time_range=time_range, user_id=user_id, y_axis=y_axis, grouping=grouping)
|
result = graph.get_total_plays_per_day(time_range=time_range,
|
||||||
|
y_axis=y_axis,
|
||||||
|
user_id=user_id,
|
||||||
|
grouping=grouping)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
@@ -1962,10 +2002,13 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_per_dayofweek(time_range=time_range, user_id=user_id, y_axis=y_axis)
|
result = graph.get_total_plays_per_dayofweek(time_range=time_range,
|
||||||
|
y_axis=y_axis,
|
||||||
|
user_id=user_id,
|
||||||
|
grouping=grouping)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
@@ -2002,10 +2045,13 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_per_hourofday(time_range=time_range, user_id=user_id, y_axis=y_axis)
|
result = graph.get_total_plays_per_hourofday(time_range=time_range,
|
||||||
|
y_axis=y_axis,
|
||||||
|
user_id=user_id,
|
||||||
|
grouping=grouping)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
@@ -2042,10 +2088,13 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_per_month(time_range=time_range, y_axis=y_axis, user_id=user_id)
|
result = graph.get_total_plays_per_month(time_range=time_range,
|
||||||
|
y_axis=y_axis,
|
||||||
|
user_id=user_id,
|
||||||
|
grouping=grouping)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
@@ -2056,7 +2105,7 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth()
|
@requireAuth()
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def get_plays_by_top_10_platforms(self, time_range='30', y_axis='plays', grouping=None, user_id=None, **kwargs):
|
def get_plays_by_top_10_platforms(self, time_range='30', y_axis='plays', user_id=None, grouping=None, **kwargs):
|
||||||
""" Get graph data by top 10 platforms.
|
""" Get graph data by top 10 platforms.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -2082,10 +2131,13 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_by_top_10_platforms(time_range=time_range, y_axis=y_axis, user_id=user_id)
|
result = graph.get_total_plays_by_top_10_platforms(time_range=time_range,
|
||||||
|
y_axis=y_axis,
|
||||||
|
user_id=user_id,
|
||||||
|
grouping=grouping)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
@@ -2096,7 +2148,7 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth()
|
@requireAuth()
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def get_plays_by_top_10_users(self, time_range='30', y_axis='plays', grouping=None, user_id=None, **kwargs):
|
def get_plays_by_top_10_users(self, time_range='30', y_axis='plays', user_id=None, grouping=None, **kwargs):
|
||||||
""" Get graph data by top 10 users.
|
""" Get graph data by top 10 users.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -2122,10 +2174,13 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_by_top_10_users(time_range=time_range, y_axis=y_axis, user_id=user_id)
|
result = graph.get_total_plays_by_top_10_users(time_range=time_range,
|
||||||
|
y_axis=y_axis,
|
||||||
|
user_id=user_id,
|
||||||
|
grouping=grouping)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
@@ -2136,7 +2191,7 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth()
|
@requireAuth()
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def get_plays_by_stream_type(self, time_range='30', y_axis='plays', grouping=None, user_id=None, **kwargs):
|
def get_plays_by_stream_type(self, time_range='30', y_axis='plays', user_id=None, grouping=None, **kwargs):
|
||||||
""" Get graph data by stream type by date.
|
""" Get graph data by stream type by date.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -2161,10 +2216,13 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_per_stream_type(time_range=time_range, y_axis=y_axis, user_id=user_id)
|
result = graph.get_total_plays_per_stream_type(time_range=time_range,
|
||||||
|
y_axis=y_axis,
|
||||||
|
user_id=user_id,
|
||||||
|
grouping=grouping)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
@@ -2175,7 +2233,7 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth()
|
@requireAuth()
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def get_plays_by_source_resolution(self, time_range='30', y_axis='plays', grouping=None, user_id=None, **kwargs):
|
def get_plays_by_source_resolution(self, time_range='30', y_axis='plays', user_id=None, grouping=None, **kwargs):
|
||||||
""" Get graph data by source resolution.
|
""" Get graph data by source resolution.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -2200,10 +2258,13 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_by_source_resolution(time_range=time_range, y_axis=y_axis, user_id=user_id)
|
result = graph.get_total_plays_by_source_resolution(time_range=time_range,
|
||||||
|
y_axis=y_axis,
|
||||||
|
user_id=user_id,
|
||||||
|
grouping=grouping)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
@@ -2214,7 +2275,7 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth()
|
@requireAuth()
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def get_plays_by_stream_resolution(self, time_range='30', y_axis='plays', grouping=None, user_id=None, **kwargs):
|
def get_plays_by_stream_resolution(self, time_range='30', y_axis='plays', user_id=None, grouping=None, **kwargs):
|
||||||
""" Get graph data by stream resolution.
|
""" Get graph data by stream resolution.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -2239,10 +2300,13 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_total_plays_by_stream_resolution(time_range=time_range, y_axis=y_axis, user_id=user_id)
|
result = graph.get_total_plays_by_stream_resolution(time_range=time_range,
|
||||||
|
y_axis=y_axis,
|
||||||
|
user_id=user_id,
|
||||||
|
grouping=grouping)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
@@ -2253,7 +2317,7 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth()
|
@requireAuth()
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def get_stream_type_by_top_10_users(self, time_range='30', y_axis='plays', grouping=None, user_id=None, **kwargs):
|
def get_stream_type_by_top_10_users(self, time_range='30', y_axis='plays', user_id=None, grouping=None, **kwargs):
|
||||||
""" Get graph data by stream type by top 10 users.
|
""" Get graph data by stream type by top 10 users.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -2278,10 +2342,13 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_stream_type_by_top_10_users(time_range=time_range, y_axis=y_axis, user_id=user_id)
|
result = graph.get_stream_type_by_top_10_users(time_range=time_range,
|
||||||
|
y_axis=y_axis,
|
||||||
|
user_id=user_id,
|
||||||
|
grouping=grouping)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
@@ -2292,7 +2359,7 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth()
|
@requireAuth()
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def get_stream_type_by_top_10_platforms(self, time_range='30', y_axis='plays', grouping=None, user_id=None, **kwargs):
|
def get_stream_type_by_top_10_platforms(self, time_range='30', y_axis='plays', user_id=None, grouping=None, **kwargs):
|
||||||
""" Get graph data by stream type by top 10 platforms.
|
""" Get graph data by stream type by top 10 platforms.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -2317,10 +2384,13 @@ class WebInterface(object):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
"""
|
"""
|
||||||
grouping = int(grouping) if str(grouping).isdigit() else grouping
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
graph = graphs.Graphs()
|
graph = graphs.Graphs()
|
||||||
result = graph.get_stream_type_by_top_10_platforms(time_range=time_range, y_axis=y_axis, user_id=user_id)
|
result = graph.get_stream_type_by_top_10_platforms(time_range=time_range,
|
||||||
|
y_axis=y_axis,
|
||||||
|
user_id=user_id,
|
||||||
|
grouping=grouping)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return result
|
return result
|
||||||
@@ -3517,12 +3587,12 @@ class WebInterface(object):
|
|||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
def verify_mobile_device(self, device_token='', cancel=False, **kwargs):
|
def verify_mobile_device(self, device_token='', cancel=False, **kwargs):
|
||||||
if helpers.bool_true(cancel):
|
if helpers.bool_true(cancel):
|
||||||
mobile_app.TEMP_DEVICE_TOKEN = None
|
mobile_app.set_temp_device_token(None)
|
||||||
return {'result': 'error', 'message': 'Device registration cancelled.'}
|
return {'result': 'error', 'message': 'Device registration cancelled.'}
|
||||||
|
|
||||||
result = mobile_app.get_mobile_device_by_token(device_token)
|
result = mobile_app.get_mobile_device_by_token(device_token)
|
||||||
if result:
|
if result:
|
||||||
mobile_app.TEMP_DEVICE_TOKEN = None
|
mobile_app.set_temp_device_token(None)
|
||||||
return {'result': 'success', 'message': 'Device registered successfully.', 'data': result}
|
return {'result': 'success', 'message': 'Device registered successfully.', 'data': result}
|
||||||
else:
|
else:
|
||||||
return {'result': 'error', 'message': 'Device not registered.'}
|
return {'result': 'error', 'message': 'Device not registered.'}
|
||||||
@@ -3809,7 +3879,7 @@ class WebInterface(object):
|
|||||||
logger._BLACKLIST_WORDS.add(apikey)
|
logger._BLACKLIST_WORDS.add(apikey)
|
||||||
|
|
||||||
if helpers.bool_true(device):
|
if helpers.bool_true(device):
|
||||||
mobile_app.TEMP_DEVICE_TOKEN = apikey
|
mobile_app.set_temp_device_token(apikey)
|
||||||
|
|
||||||
return apikey
|
return apikey
|
||||||
|
|
||||||
@@ -5319,6 +5389,7 @@ class WebInterface(object):
|
|||||||
[{"art": "/:/resources/show-fanart.jpg",
|
[{"art": "/:/resources/show-fanart.jpg",
|
||||||
"child_count": "3745",
|
"child_count": "3745",
|
||||||
"count": "62",
|
"count": "62",
|
||||||
|
"is_active": 1,
|
||||||
"parent_count": "240",
|
"parent_count": "240",
|
||||||
"section_id": "2",
|
"section_id": "2",
|
||||||
"section_name": "TV Shows",
|
"section_name": "TV Shows",
|
||||||
@@ -5362,11 +5433,13 @@ class WebInterface(object):
|
|||||||
"filter_music": "",
|
"filter_music": "",
|
||||||
"filter_photos": "",
|
"filter_photos": "",
|
||||||
"filter_tv": "",
|
"filter_tv": "",
|
||||||
|
"is_active": 1,
|
||||||
"is_admin": 0,
|
"is_admin": 0,
|
||||||
"is_allow_sync": 1,
|
"is_allow_sync": 1,
|
||||||
"is_home_user": 1,
|
"is_home_user": 1,
|
||||||
"is_restricted": 0,
|
"is_restricted": 0,
|
||||||
"keep_history": 1,
|
"keep_history": 1,
|
||||||
|
"row_id": 1,
|
||||||
"server_token": "PU9cMuQZxJKFBtGqHk68",
|
"server_token": "PU9cMuQZxJKFBtGqHk68",
|
||||||
"shared_libraries": "1;2;3",
|
"shared_libraries": "1;2;3",
|
||||||
"thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
"thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
|
||||||
@@ -5457,7 +5530,7 @@ class WebInterface(object):
|
|||||||
@cherrypy.tools.json_out()
|
@cherrypy.tools.json_out()
|
||||||
@requireAuth(member_of("admin"))
|
@requireAuth(member_of("admin"))
|
||||||
@addtoapi()
|
@addtoapi()
|
||||||
def get_home_stats(self, grouping=0, time_range=30, stats_type='plays', stats_count=10, **kwargs):
|
def get_home_stats(self, time_range=30, stats_type='plays', stats_count=10, grouping=None, **kwargs):
|
||||||
""" Get the homepage watch statistics.
|
""" Get the homepage watch statistics.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -5539,6 +5612,8 @@ class WebInterface(object):
|
|||||||
elif stats_type in (1, '1'):
|
elif stats_type in (1, '1'):
|
||||||
stats_type = 'duration'
|
stats_type = 'duration'
|
||||||
|
|
||||||
|
grouping = helpers.bool_true(grouping, return_none=True)
|
||||||
|
|
||||||
data_factory = datafactory.DataFactory()
|
data_factory = datafactory.DataFactory()
|
||||||
result = data_factory.get_home_stats(grouping=grouping,
|
result = data_factory.get_home_stats(grouping=grouping,
|
||||||
time_range=time_range,
|
time_range=time_range,
|
||||||
|
Reference in New Issue
Block a user