Compare commits
65 Commits
v2.0.20-be
...
v2.0.23-be
Author | SHA1 | Date | |
---|---|---|---|
![]() |
522684b2ab | ||
![]() |
feab16b351 | ||
![]() |
ee041db63d | ||
![]() |
2479533d07 | ||
![]() |
d045fd5834 | ||
![]() |
8407f27fed | ||
![]() |
b505286caf | ||
![]() |
feb762ce8b | ||
![]() |
8acdb5af83 | ||
![]() |
5af1294f71 | ||
![]() |
87d2d273d3 | ||
![]() |
b5c52ac71e | ||
![]() |
efe9a15f72 | ||
![]() |
525f1e4b0b | ||
![]() |
d18820b832 | ||
![]() |
7e024fd736 | ||
![]() |
c9c5989474 | ||
![]() |
ce9f96d3be | ||
![]() |
7362dd0bf4 | ||
![]() |
9905ebc144 | ||
![]() |
8f8010884b | ||
![]() |
37afd141be | ||
![]() |
a3643b4302 | ||
![]() |
02cfd8d9b7 | ||
![]() |
941ce439b4 | ||
![]() |
a08bce2073 | ||
![]() |
4e9c8322c3 | ||
![]() |
89bfe85be3 | ||
![]() |
98d994591c | ||
![]() |
a29bc7f4f9 | ||
![]() |
288f4c5f7f | ||
![]() |
a6bf78ed56 | ||
![]() |
8dbb05931e | ||
![]() |
ac8a712ff0 | ||
![]() |
39406c25c3 | ||
![]() |
48d7c2c54c | ||
![]() |
0217188274 | ||
![]() |
fd762e71de | ||
![]() |
4d5c3b6df0 | ||
![]() |
7df54e4d1b | ||
![]() |
5d085de9d3 | ||
![]() |
a8a4299086 | ||
![]() |
86f0e8425c | ||
![]() |
d2e879be4a | ||
![]() |
544114fffe | ||
![]() |
3b3e207b11 | ||
![]() |
84aad638ac | ||
![]() |
2bb691966e | ||
![]() |
8f5e788270 | ||
![]() |
7c43ea2f46 | ||
![]() |
8146e1e3cf | ||
![]() |
51b1ff6d4a | ||
![]() |
403e8dfbea | ||
![]() |
9d08717c83 | ||
![]() |
66167d5960 | ||
![]() |
624863d826 | ||
![]() |
d4b3810fbc | ||
![]() |
6056e1d3b9 | ||
![]() |
1a293d525f | ||
![]() |
b87eb68bdd | ||
![]() |
8620546d07 | ||
![]() |
a082109045 | ||
![]() |
559a9b393e | ||
![]() |
ae41b22e59 | ||
![]() |
754fd24421 |
4
API.md
4
API.md
@@ -401,6 +401,7 @@ Returns:
|
||||
"quality_profile": "Original",
|
||||
"rating": "7.8",
|
||||
"rating_key": "153037",
|
||||
"relay": 0,
|
||||
"section_id": "2",
|
||||
"session_id": "helf15l3rxgw01xxe0jf3l3d",
|
||||
"session_key": "27",
|
||||
@@ -1673,7 +1674,8 @@ Optional parameters:
|
||||
remote (int): 0 or 1
|
||||
|
||||
Returns:
|
||||
string: The unique PMS identifier
|
||||
json:
|
||||
{'identifier': '08u2phnlkdshf890bhdlksghnljsahgleikjfg9t'}
|
||||
```
|
||||
|
||||
|
||||
|
53
CHANGELOG.md
53
CHANGELOG.md
@@ -1,5 +1,58 @@
|
||||
# Changelog
|
||||
|
||||
## v2.0.23-beta (2018-03-16)
|
||||
|
||||
* Monitoring:
|
||||
* Fix: Certain transcode stream showing incorrectly as direct play in history. Fix is not retroactive.
|
||||
* Notifications:
|
||||
* New: Added season/episode/album/track count to notification parameters.
|
||||
* New: Added "Value 3" setting for IFTTT notifications.
|
||||
* New: Set PLEX_URL, PLEX_TOKEN, TAUTULLI_URL, and TAUTULLI_APIKEY environment variables for scripts.
|
||||
* Fix: Notifications failing to send with invalid custom conditions json.
|
||||
* Fix: Email notifications failing with unicode username/passwords.
|
||||
* Change: Facebook Graph API version updated to v2.12.
|
||||
* UI:
|
||||
* New: Show the Plex Server URL in the settings.
|
||||
* Fix: Incorrect info displayed in the Tautulli login logs.
|
||||
* API:
|
||||
* Fix: API returning empty data if a message was in the original data.
|
||||
* Change: get_server_id command returns json instead of string.
|
||||
* Other:
|
||||
* Fix: Forgot git pull when changing branches in the web UI.
|
||||
|
||||
|
||||
## v2.0.22 (2018-03-10)
|
||||
|
||||
* Tautulli v2 release!
|
||||
|
||||
|
||||
## v2.0.22-beta (2018-03-09)
|
||||
|
||||
* Notifications:
|
||||
* Fix: Pushover notifications failing with priority 2 is set.
|
||||
* Fix: Expanding selectize box for some notification agent settings.
|
||||
* Other:
|
||||
* Fix: Update check failing when an update is available.
|
||||
* Fix: Item count incorrect for photo libraries.
|
||||
|
||||
|
||||
## v2.0.21-beta (2018-03-04)
|
||||
|
||||
* Monitoring:
|
||||
* New: Identify if a stream is using Plex Relay.
|
||||
* Change: Don't ping the Plex server if the websocket is disconnected.
|
||||
* Notifications:
|
||||
* Fix: Pause/resume state not being sent correctly in some instances.
|
||||
* Other:
|
||||
* New: Add Patreon donation method.
|
||||
* Fix: Catch failure to send analytics.
|
||||
* Fix: IP address connection lookup error when the country is missing.
|
||||
* Change: Updated all init scripts to Tautulli.
|
||||
* Change: Move database to tautulli.db.
|
||||
* Change: Move logs to tautulli.log.
|
||||
* Change: Move startup file to Tautulli.py.
|
||||
|
||||
|
||||
## v2.0.20-beta (2018-02-24)
|
||||
|
||||
* Notifications:
|
||||
|
235
PlexPy.py
235
PlexPy.py
@@ -21,239 +21,8 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
from Tautulli import main
|
||||
|
||||
# Ensure lib added to path, before any other imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib/'))
|
||||
|
||||
import argparse
|
||||
import locale
|
||||
import signal
|
||||
import time
|
||||
|
||||
import plexpy
|
||||
from plexpy import config, database, logger, webstart
|
||||
|
||||
|
||||
# Register signals, such as CTRL + C
|
||||
signal.signal(signal.SIGINT, plexpy.sig_handler)
|
||||
signal.signal(signal.SIGTERM, plexpy.sig_handler)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Tautulli application entry point. Parses arguments, setups encoding and
|
||||
initializes the application.
|
||||
"""
|
||||
|
||||
# Fixed paths to Tautulli
|
||||
if hasattr(sys, 'frozen'):
|
||||
plexpy.FULL_PATH = os.path.abspath(sys.executable)
|
||||
else:
|
||||
plexpy.FULL_PATH = os.path.abspath(__file__)
|
||||
|
||||
plexpy.PROG_DIR = os.path.dirname(plexpy.FULL_PATH)
|
||||
plexpy.ARGS = sys.argv[1:]
|
||||
|
||||
# From sickbeard
|
||||
plexpy.SYS_PLATFORM = sys.platform
|
||||
plexpy.SYS_ENCODING = None
|
||||
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
plexpy.SYS_LANGUAGE, plexpy.SYS_ENCODING = locale.getdefaultlocale()
|
||||
except (locale.Error, IOError):
|
||||
pass
|
||||
|
||||
# for OSes that are poorly configured I'll just force UTF-8
|
||||
if not plexpy.SYS_ENCODING or plexpy.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
|
||||
plexpy.SYS_ENCODING = 'UTF-8'
|
||||
|
||||
# Set up and gather command line arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
description='A Python based monitoring and tracking tool for Plex Media Server.')
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbose', action='store_true', help='Increase console logging verbosity')
|
||||
parser.add_argument(
|
||||
'-q', '--quiet', action='store_true', help='Turn off console logging')
|
||||
parser.add_argument(
|
||||
'-d', '--daemon', action='store_true', help='Run as a daemon')
|
||||
parser.add_argument(
|
||||
'-p', '--port', type=int, help='Force Tautulli to run on a specified port')
|
||||
parser.add_argument(
|
||||
'--dev', action='store_true', help='Start Tautulli in the development environment')
|
||||
parser.add_argument(
|
||||
'--datadir', help='Specify a directory where to store your data files')
|
||||
parser.add_argument(
|
||||
'--config', help='Specify a config file to use')
|
||||
parser.add_argument(
|
||||
'--nolaunch', action='store_true', help='Prevent browser from launching on startup')
|
||||
parser.add_argument(
|
||||
'--pidfile', help='Create a pid file (only relevant when running as a daemon)')
|
||||
parser.add_argument(
|
||||
'--nofork', action='store_true', help='Start Tautulli as a service, do not fork when restarting')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
plexpy.VERBOSE = True
|
||||
if args.quiet:
|
||||
plexpy.QUIET = True
|
||||
|
||||
# Do an intial setup of the logger.
|
||||
logger.initLogger(console=not plexpy.QUIET, log_dir=False,
|
||||
verbose=plexpy.VERBOSE)
|
||||
|
||||
if args.dev:
|
||||
plexpy.DEV = True
|
||||
logger.debug(u"Tautulli is running in the dev environment.")
|
||||
|
||||
if args.daemon:
|
||||
if sys.platform == 'win32':
|
||||
sys.stderr.write(
|
||||
"Daemonizing not supported under Windows, starting normally\n")
|
||||
else:
|
||||
plexpy.DAEMON = True
|
||||
plexpy.QUIET = True
|
||||
|
||||
if args.nofork:
|
||||
plexpy.NOFORK = True
|
||||
logger.info("Tautulli is running as a service, it will not fork when restarted.")
|
||||
|
||||
if args.pidfile:
|
||||
plexpy.PIDFILE = str(args.pidfile)
|
||||
|
||||
# If the pidfile already exists, plexpy may still be running, so
|
||||
# exit
|
||||
if os.path.exists(plexpy.PIDFILE):
|
||||
try:
|
||||
with open(plexpy.PIDFILE, 'r') as fp:
|
||||
pid = int(fp.read())
|
||||
os.kill(pid, 0)
|
||||
except IOError as e:
|
||||
raise SystemExit("Unable to read PID file: %s", e)
|
||||
except OSError:
|
||||
logger.warn("PID file '%s' already exists, but PID %d is " \
|
||||
"not running. Ignoring PID file." %
|
||||
(plexpy.PIDFILE, pid))
|
||||
else:
|
||||
# The pidfile exists and points to a live PID. plexpy may
|
||||
# still be running, so exit.
|
||||
raise SystemExit("PID file '%s' already exists. Exiting." %
|
||||
plexpy.PIDFILE)
|
||||
|
||||
# The pidfile is only useful in daemon mode, make sure we can write the
|
||||
# file properly
|
||||
if plexpy.DAEMON:
|
||||
plexpy.CREATEPID = True
|
||||
|
||||
try:
|
||||
with open(plexpy.PIDFILE, 'w') as fp:
|
||||
fp.write("pid\n")
|
||||
except IOError as e:
|
||||
raise SystemExit("Unable to write PID file: %s", e)
|
||||
else:
|
||||
logger.warn("Not running in daemon mode. PID file creation " \
|
||||
"disabled.")
|
||||
|
||||
# Determine which data directory and config file to use
|
||||
if args.datadir:
|
||||
plexpy.DATA_DIR = args.datadir
|
||||
else:
|
||||
plexpy.DATA_DIR = plexpy.PROG_DIR
|
||||
|
||||
if args.config:
|
||||
config_file = args.config
|
||||
else:
|
||||
config_file = os.path.join(plexpy.DATA_DIR, config.FILENAME)
|
||||
|
||||
# Try to create the DATA_DIR if it doesn't exist
|
||||
if not os.path.exists(plexpy.DATA_DIR):
|
||||
try:
|
||||
os.makedirs(plexpy.DATA_DIR)
|
||||
except OSError:
|
||||
raise SystemExit(
|
||||
'Could not create data directory: ' + plexpy.DATA_DIR + '. Exiting....')
|
||||
|
||||
# Make sure the DATA_DIR is writeable
|
||||
if not os.access(plexpy.DATA_DIR, os.W_OK):
|
||||
raise SystemExit(
|
||||
'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...')
|
||||
|
||||
# Put the database in the DATA_DIR
|
||||
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME)
|
||||
|
||||
if plexpy.DAEMON:
|
||||
plexpy.daemonize()
|
||||
|
||||
# Read config and start logging
|
||||
plexpy.initialize(config_file)
|
||||
|
||||
# Start the background threads
|
||||
plexpy.start()
|
||||
|
||||
# Force the http port if neccessary
|
||||
if args.port:
|
||||
http_port = args.port
|
||||
logger.info('Using forced web server port: %i', http_port)
|
||||
else:
|
||||
http_port = int(plexpy.CONFIG.HTTP_PORT)
|
||||
|
||||
# Check if pyOpenSSL is installed. It is required for certificate generation
|
||||
# and for CherryPy.
|
||||
if plexpy.CONFIG.ENABLE_HTTPS:
|
||||
try:
|
||||
import OpenSSL
|
||||
except ImportError:
|
||||
logger.warn("The pyOpenSSL module is missing. Install this " \
|
||||
"module to enable HTTPS. HTTPS will be disabled.")
|
||||
plexpy.CONFIG.ENABLE_HTTPS = False
|
||||
|
||||
# Try to start the server. Will exit here is address is already in use.
|
||||
web_config = {
|
||||
'http_port': http_port,
|
||||
'http_host': plexpy.CONFIG.HTTP_HOST,
|
||||
'http_root': plexpy.CONFIG.HTTP_ROOT,
|
||||
'http_environment': plexpy.CONFIG.HTTP_ENVIRONMENT,
|
||||
'http_proxy': plexpy.CONFIG.HTTP_PROXY,
|
||||
'enable_https': plexpy.CONFIG.ENABLE_HTTPS,
|
||||
'https_cert': plexpy.CONFIG.HTTPS_CERT,
|
||||
'https_cert_chain': plexpy.CONFIG.HTTPS_CERT_CHAIN,
|
||||
'https_key': plexpy.CONFIG.HTTPS_KEY,
|
||||
'http_username': plexpy.CONFIG.HTTP_USERNAME,
|
||||
'http_password': plexpy.CONFIG.HTTP_PASSWORD,
|
||||
'http_basic_auth': plexpy.CONFIG.HTTP_BASIC_AUTH
|
||||
}
|
||||
webstart.initialize(web_config)
|
||||
|
||||
# Open webbrowser
|
||||
if plexpy.CONFIG.LAUNCH_BROWSER and not args.nolaunch and not plexpy.DEV:
|
||||
plexpy.launch_browser(plexpy.CONFIG.HTTP_HOST, http_port,
|
||||
plexpy.CONFIG.HTTP_ROOT)
|
||||
|
||||
# Wait endlessy for a signal to happen
|
||||
while True:
|
||||
if not plexpy.SIGNAL:
|
||||
try:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
plexpy.SIGNAL = 'shutdown'
|
||||
else:
|
||||
logger.info('Received signal: %s', plexpy.SIGNAL)
|
||||
|
||||
if plexpy.SIGNAL == 'shutdown':
|
||||
plexpy.shutdown()
|
||||
elif plexpy.SIGNAL == 'restart':
|
||||
plexpy.shutdown(restart=True)
|
||||
elif plexpy.SIGNAL == 'checkout':
|
||||
plexpy.shutdown(restart=True, checkout=True)
|
||||
else:
|
||||
plexpy.shutdown(restart=True, update=True)
|
||||
|
||||
plexpy.SIGNAL = None
|
||||
|
||||
# Call main()
|
||||
# Call main() from Tautulli.py
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
267
Tautulli.py
Executable file
267
Tautulli.py
Executable file
@@ -0,0 +1,267 @@
|
||||
#!/bin/sh
|
||||
''''which python >/dev/null 2>&1 && exec python "$0" "$@" # '''
|
||||
''''which python2 >/dev/null 2>&1 && exec python2 "$0" "$@" # '''
|
||||
''''which python2.7 >/dev/null 2>&1 && exec python2.7 "$0" "$@" # '''
|
||||
''''exec echo "Error: Python not found!" # '''
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file is part of Tautulli.
|
||||
#
|
||||
# Tautulli is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Tautulli is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Ensure lib added to path, before any other imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib/'))
|
||||
|
||||
import argparse
|
||||
import locale
|
||||
import signal
|
||||
import time
|
||||
|
||||
import plexpy
|
||||
from plexpy import config, database, logger, webstart
|
||||
|
||||
|
||||
# Register signals, such as CTRL + C
|
||||
signal.signal(signal.SIGINT, plexpy.sig_handler)
|
||||
signal.signal(signal.SIGTERM, plexpy.sig_handler)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Tautulli application entry point. Parses arguments, setups encoding and
|
||||
initializes the application.
|
||||
"""
|
||||
|
||||
# Fixed paths to Tautulli
|
||||
if hasattr(sys, 'frozen'):
|
||||
plexpy.FULL_PATH = os.path.abspath(sys.executable)
|
||||
else:
|
||||
plexpy.FULL_PATH = os.path.abspath(__file__)
|
||||
|
||||
plexpy.PROG_DIR = os.path.dirname(plexpy.FULL_PATH)
|
||||
plexpy.ARGS = sys.argv[1:]
|
||||
|
||||
# From sickbeard
|
||||
plexpy.SYS_PLATFORM = sys.platform
|
||||
plexpy.SYS_ENCODING = None
|
||||
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
plexpy.SYS_LANGUAGE, plexpy.SYS_ENCODING = locale.getdefaultlocale()
|
||||
except (locale.Error, IOError):
|
||||
pass
|
||||
|
||||
# for OSes that are poorly configured I'll just force UTF-8
|
||||
if not plexpy.SYS_ENCODING or plexpy.SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
|
||||
plexpy.SYS_ENCODING = 'UTF-8'
|
||||
|
||||
# Set up and gather command line arguments
|
||||
parser = argparse.ArgumentParser(
|
||||
description='A Python based monitoring and tracking tool for Plex Media Server.')
|
||||
|
||||
parser.add_argument(
|
||||
'-v', '--verbose', action='store_true', help='Increase console logging verbosity')
|
||||
parser.add_argument(
|
||||
'-q', '--quiet', action='store_true', help='Turn off console logging')
|
||||
parser.add_argument(
|
||||
'-d', '--daemon', action='store_true', help='Run as a daemon')
|
||||
parser.add_argument(
|
||||
'-p', '--port', type=int, help='Force Tautulli to run on a specified port')
|
||||
parser.add_argument(
|
||||
'--dev', action='store_true', help='Start Tautulli in the development environment')
|
||||
parser.add_argument(
|
||||
'--datadir', help='Specify a directory where to store your data files')
|
||||
parser.add_argument(
|
||||
'--config', help='Specify a config file to use')
|
||||
parser.add_argument(
|
||||
'--nolaunch', action='store_true', help='Prevent browser from launching on startup')
|
||||
parser.add_argument(
|
||||
'--pidfile', help='Create a pid file (only relevant when running as a daemon)')
|
||||
parser.add_argument(
|
||||
'--nofork', action='store_true', help='Start Tautulli as a service, do not fork when restarting')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
plexpy.VERBOSE = True
|
||||
if args.quiet:
|
||||
plexpy.QUIET = True
|
||||
|
||||
# Do an intial setup of the logger.
|
||||
logger.initLogger(console=not plexpy.QUIET, log_dir=False,
|
||||
verbose=plexpy.VERBOSE)
|
||||
|
||||
if args.dev:
|
||||
plexpy.DEV = True
|
||||
logger.debug(u"Tautulli is running in the dev environment.")
|
||||
|
||||
if args.daemon:
|
||||
if sys.platform == 'win32':
|
||||
sys.stderr.write(
|
||||
"Daemonizing not supported under Windows, starting normally\n")
|
||||
else:
|
||||
plexpy.DAEMON = True
|
||||
plexpy.QUIET = True
|
||||
|
||||
if args.nofork:
|
||||
plexpy.NOFORK = True
|
||||
logger.info("Tautulli is running as a service, it will not fork when restarted.")
|
||||
|
||||
if args.pidfile:
|
||||
plexpy.PIDFILE = str(args.pidfile)
|
||||
|
||||
# If the pidfile already exists, plexpy may still be running, so
|
||||
# exit
|
||||
if os.path.exists(plexpy.PIDFILE):
|
||||
try:
|
||||
with open(plexpy.PIDFILE, 'r') as fp:
|
||||
pid = int(fp.read())
|
||||
os.kill(pid, 0)
|
||||
except IOError as e:
|
||||
raise SystemExit("Unable to read PID file: %s", e)
|
||||
except OSError:
|
||||
logger.warn("PID file '%s' already exists, but PID %d is " \
|
||||
"not running. Ignoring PID file." %
|
||||
(plexpy.PIDFILE, pid))
|
||||
else:
|
||||
# The pidfile exists and points to a live PID. plexpy may
|
||||
# still be running, so exit.
|
||||
raise SystemExit("PID file '%s' already exists. Exiting." %
|
||||
plexpy.PIDFILE)
|
||||
|
||||
# The pidfile is only useful in daemon mode, make sure we can write the
|
||||
# file properly
|
||||
if plexpy.DAEMON:
|
||||
plexpy.CREATEPID = True
|
||||
|
||||
try:
|
||||
with open(plexpy.PIDFILE, 'w') as fp:
|
||||
fp.write("pid\n")
|
||||
except IOError as e:
|
||||
raise SystemExit("Unable to write PID file: %s", e)
|
||||
else:
|
||||
logger.warn("Not running in daemon mode. PID file creation " \
|
||||
"disabled.")
|
||||
|
||||
# Determine which data directory and config file to use
|
||||
if args.datadir:
|
||||
plexpy.DATA_DIR = args.datadir
|
||||
else:
|
||||
plexpy.DATA_DIR = plexpy.PROG_DIR
|
||||
|
||||
if args.config:
|
||||
config_file = args.config
|
||||
else:
|
||||
config_file = os.path.join(plexpy.DATA_DIR, config.FILENAME)
|
||||
|
||||
# Try to create the DATA_DIR if it doesn't exist
|
||||
if not os.path.exists(plexpy.DATA_DIR):
|
||||
try:
|
||||
os.makedirs(plexpy.DATA_DIR)
|
||||
except OSError:
|
||||
raise SystemExit(
|
||||
'Could not create data directory: ' + plexpy.DATA_DIR + '. Exiting....')
|
||||
|
||||
# Make sure the DATA_DIR is writeable
|
||||
if not os.access(plexpy.DATA_DIR, os.W_OK):
|
||||
raise SystemExit(
|
||||
'Cannot write to the data directory: ' + plexpy.DATA_DIR + '. Exiting...')
|
||||
|
||||
# Put the database in the DATA_DIR
|
||||
plexpy.DB_FILE = os.path.join(plexpy.DATA_DIR, database.FILENAME)
|
||||
|
||||
# Move 'plexpy.db' to 'tautulli.db'
|
||||
if os.path.isfile(os.path.join(plexpy.DATA_DIR, 'plexpy.db')) and \
|
||||
not os.path.isfile(os.path.join(plexpy.DATA_DIR, plexpy.DB_FILE)):
|
||||
try:
|
||||
os.rename(os.path.join(plexpy.DATA_DIR, 'plexpy.db'), plexpy.DB_FILE)
|
||||
except OSError as e:
|
||||
raise SystemExit("Unable to rename plexpy.db to tautulli.db: %s", e)
|
||||
|
||||
if plexpy.DAEMON:
|
||||
plexpy.daemonize()
|
||||
|
||||
# Read config and start logging
|
||||
plexpy.initialize(config_file)
|
||||
|
||||
# Start the background threads
|
||||
plexpy.start()
|
||||
|
||||
# Force the http port if neccessary
|
||||
if args.port:
|
||||
http_port = args.port
|
||||
logger.info('Using forced web server port: %i', http_port)
|
||||
else:
|
||||
http_port = int(plexpy.CONFIG.HTTP_PORT)
|
||||
|
||||
# Check if pyOpenSSL is installed. It is required for certificate generation
|
||||
# and for CherryPy.
|
||||
if plexpy.CONFIG.ENABLE_HTTPS:
|
||||
try:
|
||||
import OpenSSL
|
||||
except ImportError:
|
||||
logger.warn("The pyOpenSSL module is missing. Install this " \
|
||||
"module to enable HTTPS. HTTPS will be disabled.")
|
||||
plexpy.CONFIG.ENABLE_HTTPS = False
|
||||
|
||||
# Try to start the server. Will exit here is address is already in use.
|
||||
web_config = {
|
||||
'http_port': http_port,
|
||||
'http_host': plexpy.CONFIG.HTTP_HOST,
|
||||
'http_root': plexpy.CONFIG.HTTP_ROOT,
|
||||
'http_environment': plexpy.CONFIG.HTTP_ENVIRONMENT,
|
||||
'http_proxy': plexpy.CONFIG.HTTP_PROXY,
|
||||
'enable_https': plexpy.CONFIG.ENABLE_HTTPS,
|
||||
'https_cert': plexpy.CONFIG.HTTPS_CERT,
|
||||
'https_cert_chain': plexpy.CONFIG.HTTPS_CERT_CHAIN,
|
||||
'https_key': plexpy.CONFIG.HTTPS_KEY,
|
||||
'http_username': plexpy.CONFIG.HTTP_USERNAME,
|
||||
'http_password': plexpy.CONFIG.HTTP_PASSWORD,
|
||||
'http_basic_auth': plexpy.CONFIG.HTTP_BASIC_AUTH
|
||||
}
|
||||
webstart.initialize(web_config)
|
||||
|
||||
# Open webbrowser
|
||||
if plexpy.CONFIG.LAUNCH_BROWSER and not args.nolaunch and not plexpy.DEV:
|
||||
plexpy.launch_browser(plexpy.CONFIG.HTTP_HOST, http_port,
|
||||
plexpy.CONFIG.HTTP_ROOT)
|
||||
|
||||
# Wait endlessy for a signal to happen
|
||||
while True:
|
||||
if not plexpy.SIGNAL:
|
||||
try:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
plexpy.SIGNAL = 'shutdown'
|
||||
else:
|
||||
logger.info('Received signal: %s', plexpy.SIGNAL)
|
||||
|
||||
if plexpy.SIGNAL == 'shutdown':
|
||||
plexpy.shutdown()
|
||||
elif plexpy.SIGNAL == 'restart':
|
||||
plexpy.shutdown(restart=True)
|
||||
elif plexpy.SIGNAL == 'checkout':
|
||||
plexpy.shutdown(restart=True, checkout=True)
|
||||
else:
|
||||
plexpy.shutdown(restart=True, update=True)
|
||||
|
||||
plexpy.SIGNAL = None
|
||||
|
||||
# Call main()
|
||||
if __name__ == "__main__":
|
||||
main()
|
@@ -15,7 +15,7 @@
|
||||
<meta name="author" content="">
|
||||
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
|
||||
<link href="${http_root}css/pnotify.custom.min.css" rel="stylesheet" />
|
||||
<link href="${http_root}css/plexpy.css${cache_param}" rel="stylesheet">
|
||||
<link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
|
||||
<link href="${http_root}css/opensans.min.css" rel="stylesheet">
|
||||
<link href="${http_root}css/font-awesome.min.css" rel="stylesheet">
|
||||
${next.headIncludes()}
|
||||
@@ -47,7 +47,7 @@
|
||||
You are running an unknown version of Tautulli.<br />
|
||||
<a href="update">Update</a> or <a href="#" id="updateDismiss">Dismiss</a>
|
||||
</div>
|
||||
% elif plexpy.CONFIG.CHECK_GITHUB and plexpy.COMMITS_BEHIND > 0 and plexpy.common.BRANCH in ('master', 'beta') and plexpy.common.VERSION_NUMBER != plexpy.LATEST_RELEASE:
|
||||
% elif plexpy.CONFIG.CHECK_GITHUB and plexpy.COMMITS_BEHIND > 0 and plexpy.common.BRANCH in ('master', 'beta') and plexpy.common.RELEASE != plexpy.LATEST_RELEASE:
|
||||
<div id="updatebar" style="display: none;">
|
||||
A <a href="${anon_url('https://github.com/%s/%s/releases/tag/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO, plexpy.LATEST_RELEASE))}" target="_blank">
|
||||
new release (${plexpy.LATEST_RELEASE})</a> of Tautulli is available!<br />
|
||||
@@ -227,15 +227,23 @@ ${next.modalIncludes()}
|
||||
</div>
|
||||
</div>
|
||||
<ul id="donation_type" class="nav nav-pills" role="tablist" style="display: flex; justify-content: center; margin: 10px 0;">
|
||||
<li class="active"><a href="#paypal-donation" role="tab" data-toggle="tab">PayPal</a></li>
|
||||
<li><a href="#flattr-donation" role="tab" data-toggle="tab">Flattr</a></li>
|
||||
<li class="active"><a href="#patreon-donation" role="tab" data-toggle="tab">Patreon</a></li>
|
||||
<li><a href="#paypal-donation" role="tab" data-toggle="tab">PayPal</a></li>
|
||||
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="bitcoin" data-name="Bitcoin" data-address="3FdfJAyNWU15Sf11U9FTgPHuP1hPz32eEN">Bitcoin</a></li>
|
||||
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="bitcoincash" data-name="Bitcoin Cash" data-address="1H2atabxAQGaFAWYQEiLkXKSnK9CZZvt2n">Bitcoin Cash</a></li>
|
||||
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="ethereum" data-name="Ethereum" data-address="0x77ae4c2b8de1a1ccfa93553db39971da58c873d3">Ethereum</a></li>
|
||||
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="litecoin" data-name="Litecoin" data-address="LWpPmUqQYHBhMV83XSCsHzPmKLhJt6r57J">Litecoin</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="paypal-donation" style="text-align: center">
|
||||
<div role="tabpanel" class="tab-pane active" id="patreon-donation" style="text-align: center">
|
||||
<p>
|
||||
Click the button below to continue to Patreon.
|
||||
</p>
|
||||
<a href="${anon_url('https://www.patreon.com/bePatron?u=10078609')}" target="_blank">
|
||||
<img src="images/become_a_patron_button.png" alt="Become a Patron" height="40">
|
||||
</a>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="paypal-donation" style="text-align: center">
|
||||
<p>
|
||||
Click the button below to continue to PayPal.
|
||||
</p>
|
||||
@@ -243,14 +251,6 @@ ${next.modalIncludes()}
|
||||
<img src="images/gold-rect-paypal-34px.png" alt="PayPal">
|
||||
</a>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="flattr-donation" style="text-align: center">
|
||||
<p>
|
||||
Click the button below to continue to Flattr.
|
||||
</p>
|
||||
<a href="${anon_url('https://flattr.com/submit/auto?user_id=JonnyWong16&url=https://github.com/%s/%s&title=Tautulli&language=en_GB&tags=github&category=software' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO))}" target="_blank">
|
||||
<img src="images/flattr-badge-large.png" alt="Flattr">
|
||||
</a>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="crypto-donation">
|
||||
<label>QR Code</label>
|
||||
<pre id="crypto_qr_code" style="text-align: center"></pre>
|
||||
|
@@ -66,7 +66,6 @@ div.form-control .selectize-input {
|
||||
color: #fff;
|
||||
border: 0px solid #444;
|
||||
background: #555;
|
||||
height: 32px;
|
||||
padding: 6px 12px;
|
||||
background-color: #555;
|
||||
border-radius: 3px;
|
||||
@@ -92,6 +91,7 @@ div.form-control .selectize-input {
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
min-height: 32px !important;
|
||||
height: 32px !important;
|
||||
}
|
||||
.input-group .selectize-control.form-control.selectize-pms-ip .selectize-input > div {
|
||||
max-width: 450px;
|
||||
@@ -1419,7 +1419,7 @@ a .dashboard-activity-metadata-user-thumb:hover {
|
||||
}
|
||||
.dashboard-stats-info-item .sub-count {
|
||||
height: 100%;
|
||||
margin-left: 10px;
|
||||
margin-left: 5px;
|
||||
color: #f9be03;
|
||||
font-size: 12px;
|
||||
text-align: right;
|
||||
@@ -1430,7 +1430,7 @@ a .dashboard-activity-metadata-user-thumb:hover {
|
||||
}
|
||||
.dashboard-stats-info-item .sub-divider {
|
||||
height: 100%;
|
||||
margin-left: 10px;
|
||||
margin-left: 5px;
|
||||
color: #aaa;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
@@ -2372,21 +2372,6 @@ a .library-user-instance-box:hover {
|
||||
#watched-stats-days-selection label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
#watched-stats-days {
|
||||
margin: 0;
|
||||
width: 75px;
|
||||
height: 34px;
|
||||
}
|
||||
#watched-stats-count {
|
||||
margin: 0;
|
||||
width: 75px;
|
||||
height: 34px;
|
||||
}
|
||||
#recently-added-count {
|
||||
margin: 0;
|
||||
width: 75px;
|
||||
height: 34px;
|
||||
}
|
||||
.home-padded-header {
|
||||
margin: 25px 0;
|
||||
height: 34px;
|
||||
@@ -3435,22 +3420,10 @@ pre::-webkit-scrollbar-thumb {
|
||||
.notification-params tr:nth-child(even) td {
|
||||
background-color: rgba(255,255,255,0.010);
|
||||
}
|
||||
#days-selection label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
#graph-days {
|
||||
margin: 0;
|
||||
width: 75px;
|
||||
height: 34px;
|
||||
}
|
||||
#days-selection label,
|
||||
#months-selection label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
#graph-months {
|
||||
margin: 0;
|
||||
width: 75px;
|
||||
height: 34px;
|
||||
}
|
||||
.card-sortable {
|
||||
height: 36px;
|
||||
padding: 0 20px 0 0;
|
||||
@@ -3721,6 +3694,7 @@ a:hover .overlay-refresh-image:hover {
|
||||
}
|
||||
.git-group select.form-control {
|
||||
width: 50%;
|
||||
height: 32px;
|
||||
}
|
||||
#changelog-modal .modal-body > h2 {
|
||||
margin-bottom: 10px;
|
||||
@@ -3967,3 +3941,14 @@ a:hover .overlay-refresh-image:hover {
|
||||
.stream-info tr:nth-child(even) td {
|
||||
background-color: rgba(255,255,255,0.010);
|
||||
}
|
||||
.number-input {
|
||||
margin: 0 !important;
|
||||
width: 55px !important;
|
||||
height: 34px !important;
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
.number-input::-webkit-inner-spin-button,
|
||||
.number-input::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
@@ -279,16 +279,20 @@ DOCUMENTATION :: END
|
||||
<span id="location-${sk}">${data['location'].upper()}</span>:
|
||||
% if data['ip_address'] != 'N/A':
|
||||
<span class="ip-container"><span class="ip-address">${data['ip_address']}</span></span>
|
||||
<a href="#" class="external_ip-modal" data-toggle="modal" data-target="#ip-info-modal" data-ip="${data['ip_address']}">
|
||||
<span id="external_ip-${sk}" class="external-ip-tooltip" data-toggle="tooltip" title="Lookup External IP" style="display: none;"><i class="fa fa-map-marker"></i></span>
|
||||
</a>
|
||||
<script>
|
||||
isPrivateIP("${data['ip_address']}").then(function () {
|
||||
$("#external_ip-${sk}").hide();
|
||||
}, function () {
|
||||
$("#external_ip-${sk}").show();
|
||||
});
|
||||
</script>
|
||||
% if data['relay']:
|
||||
<span data-toggle="tooltip" title="Plex Relay"><i class="fa fa-exclamation-circle"></i></span>
|
||||
% else:
|
||||
<a href="#" class="external_ip-modal" data-toggle="modal" data-target="#ip-info-modal" data-ip="${data['ip_address']}">
|
||||
<span id="external_ip-${sk}" class="external-ip-tooltip" data-toggle="tooltip" title="Lookup External IP" style="display: none;"><i class="fa fa-map-marker"></i></span>
|
||||
</a>
|
||||
<script>
|
||||
isPrivateIP("${data['ip_address']}").then(function () {
|
||||
$("#external_ip-${sk}").hide();
|
||||
}, function () {
|
||||
$("#external_ip-${sk}").show();
|
||||
});
|
||||
</script>
|
||||
% endif
|
||||
% else:
|
||||
N/A
|
||||
% endif
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
@@ -39,12 +39,12 @@
|
||||
</div>
|
||||
<div class="input-group pull-right" style="width: 1px;" id="days-selection">
|
||||
<span class="input-group-addon btn-dark inactive">Last</span>
|
||||
<input type="number" class="form-control" name="graph-days" id="graph-days" value="${config['graph_days']}" min="1" data-default="7" data-toggle="tooltip" title="Min: 1 day" />
|
||||
<input type="number" class="form-control number-input" name="graph-days" id="graph-days" value="${config['graph_days']}" min="1" data-default="7" data-toggle="tooltip" title="Min: 1 day" />
|
||||
<span class="input-group-addon btn-dark inactive">days</span>
|
||||
</div>
|
||||
<div class="input-group pull-right" style="width: 1px;" id="months-selection">
|
||||
<span class="input-group-addon btn-dark inactive">Last</span>
|
||||
<input type="number" class="form-control" name="graph-months" id="graph-months" value="${config['graph_months']}" min="1" data-default="12" data-toggle="tooltip" title="Min: 1 month" />
|
||||
<input type="number" class="form-control number-input" name="graph-months" id="graph-months" value="${config['graph_months']}" min="1" data-default="12" data-toggle="tooltip" title="Min: 1 month" />
|
||||
<span class="input-group-addon btn-dark inactive">months</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
@@ -163,7 +163,7 @@
|
||||
}
|
||||
|
||||
var media_type = null;
|
||||
var selected_user_id = "${_session['user_id']}" == "None" ? null : "${_session['user_id']}";
|
||||
var selected_user_id = "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}";
|
||||
loadHistoryTable(media_type, selected_user_id);
|
||||
|
||||
% if _session['user_group'] == 'admin':
|
||||
|
BIN
data/interfaces/default/images/become_a_patron_button.png
Normal file
BIN
data/interfaces/default/images/become_a_patron_button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.2 KiB |
@@ -22,7 +22,16 @@
|
||||
</h3>
|
||||
</div>
|
||||
<div id="currentActivity">
|
||||
<% from plexpy import PLEX_SERVER_UP %>
|
||||
% if PLEX_SERVER_UP:
|
||||
<div class="text-muted" id="dashboard-checking-activity"><i class="fa fa-refresh fa-spin"></i> Checking for activity...</div>
|
||||
% else:
|
||||
<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.
|
||||
% if _session['user_group'] == 'admin':
|
||||
Check the <a href="logs">logs</a> and verify your server connection in the <a href="settings#tab_tabs-plex_media_server">settings</a>.
|
||||
% endif
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,7 +60,7 @@
|
||||
</div>
|
||||
<div class="input-group pull-left" style="width: 1px; margin-right: 3px" id="watched-stats-days-selection">
|
||||
<span class="input-group-addon btn-dark inactive">Last</span>
|
||||
<input type="number" class="form-control" name="watched-stats-days" id="watched-stats-days" value="${config['home_stats_length']}" min="1" data-default="30" data-toggle="tooltip" title="Min: 1 day" />
|
||||
<input type="number" class="form-control number-input" name="watched-stats-days" id="watched-stats-days" value="${config['home_stats_length']}" min="1" data-default="30" data-toggle="tooltip" title="Min: 1 day" />
|
||||
<span class="input-group-addon btn-dark inactive">days</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,7 +123,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group pull-left" style="width: 1px;" id="recently-added-count-selection">
|
||||
<input type="number" class="form-control" name="recently-added-count" id="recently-added-count" value="${config['home_stats_recently_added_count']}" min="1" max="100" data-default="50" data-toggle="tooltip" title="Min: 1 item<br>Max: 100 items" />
|
||||
<input type="number" class="form-control number-input" name="recently-added-count" id="recently-added-count" value="${config['home_stats_recently_added_count']}" min="1" max="100" data-default="50" data-toggle="tooltip" title="Min: 1 item<br>Max: 100 items" />
|
||||
<span class="input-group-addon btn-dark inactive">items</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -137,13 +146,13 @@
|
||||
<%def name="modalIncludes()">
|
||||
|
||||
% if _session['user_group'] == 'admin' and config['update_show_changelog']:
|
||||
<% from plexpy.common import VERSION_NUMBER %>
|
||||
<% from plexpy.common import RELEASE %>
|
||||
<div id="changelog-modal" class="modal fade wide" tabindex="-1" role="dialog" aria-labelledby="changelog-modal">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||
<h4 class="modal-title">Tautulli Updated to <strong>${VERSION_NUMBER}</strong></h4>
|
||||
<h4 class="modal-title">Tautulli Updated to <strong>${RELEASE}</strong></h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
</div>
|
||||
@@ -241,9 +250,10 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
</script>
|
||||
% if 'current_activity' in config['home_sections']:
|
||||
<% from plexpy import PLEX_SERVER_UP %>
|
||||
% if 'current_activity' in config['home_sections'] and PLEX_SERVER_UP:
|
||||
<script>
|
||||
var defaultHandler = {
|
||||
get: function(target, name) {
|
||||
@@ -266,6 +276,7 @@
|
||||
async: true,
|
||||
error: function (xhr, status, error) {
|
||||
console.log(status + ': ' + error);
|
||||
activity_ready = true;
|
||||
},
|
||||
complete: function (xhr, status) {
|
||||
$('#dashboard-checking-activity').remove();
|
||||
@@ -280,9 +291,9 @@
|
||||
|
||||
if (!(current_activity)) {
|
||||
% if _session['user_group'] == 'admin':
|
||||
var msg_settings = ' Verify your server connection in the <a href="settings#tab_tabs-plex_media_server">settings</a>.';
|
||||
var msg_settings = ' Check the <a href="logs">logs</a> and verify your server connection in the <a href="settings#tab_tabs-plex_media_server">settings</a>.';
|
||||
% else:
|
||||
var msg_settings = ''
|
||||
var msg_settings = '';
|
||||
% endif
|
||||
$('#currentActivityHeader').hide();
|
||||
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.' + msg_settings + '</div>');
|
||||
|
@@ -64,7 +64,7 @@ DOCUMENTATION :: END
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
@@ -552,7 +552,7 @@ DOCUMENTATION :: END
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
grandparent_rating_key: "${data['rating_key']}",
|
||||
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
||||
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -568,7 +568,7 @@ DOCUMENTATION :: END
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
parent_rating_key: "${data['rating_key']}",
|
||||
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
||||
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -584,7 +584,7 @@ DOCUMENTATION :: END
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
rating_key: "${data['rating_key']}",
|
||||
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
||||
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -292,7 +292,11 @@ function millisecondsToMinutes(ms, roundToMinute) {
|
||||
if (ms > 0) {
|
||||
var minutes = Math.floor(ms / 60000);
|
||||
var seconds = ((ms % 60000) / 1000).toFixed(0);
|
||||
return (seconds == 60 ? (minutes+1) + ":00" : minutes + ":" + (seconds < 10 ? "0" : "") + seconds);
|
||||
if (roundToMinute) {
|
||||
return (seconds >= 30 ? (minutes + 1) : minutes);
|
||||
} else {
|
||||
return (seconds == 60 ? (minutes + 1) + ":00" : minutes + ":" + (seconds < 10 ? "0" : "") + seconds);
|
||||
}
|
||||
} else {
|
||||
if (roundToMinute) {
|
||||
return '0';
|
||||
|
@@ -54,7 +54,7 @@ media_info_table_options = {
|
||||
} else if (rowData['media_type'] === 'album') {
|
||||
expand_details = '<span class="expand-media-info-tooltip" data-toggle="tooltip" title="Show Tracks"><i class="fa fa-plus-circle fa-fw"></i></span>';
|
||||
$(td).html('<div><a href="#"><div style="float: left;">' + expand_details + ' ' + date + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'photo' && rowData['parent_rating_key'] == '') {
|
||||
} else if (rowData['media_type'] === 'photo_album') {
|
||||
expand_details = '<span class="expand-media-info-tooltip" data-toggle="tooltip" title="Show Photos"><i class="fa fa-plus-circle fa-fw"></i></span>';
|
||||
$(td).html('<div><a href="#"><div style="float: left;">' + expand_details + ' ' + date + '</div></a></div>');
|
||||
} else {
|
||||
@@ -77,32 +77,44 @@ media_info_table_options = {
|
||||
if (rowData['media_type'] === 'movie') {
|
||||
if (rowData['year']) { parent_info = ' (' + rowData['year'] + ')'; }
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Movie"><i class="fa fa-film fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + parent_info + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + parent_info + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'show') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="TV Show"><i class="fa fa-television fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'season') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Season"><i class="fa fa-television fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 15px;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'episode') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=art" data-height="80" data-width="140">E' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=art" data-height="80" data-width="140">E' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 30px;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'artist') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Artist"><i class="fa fa-music fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + rowData['title'] + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'album') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Album"><i class="fa fa-music fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + rowData['title'] + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 15px;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'track') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Track"><i class="fa fa-music fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">T' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>'
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=300&fallback=cover" data-height="80" data-width="80">T' + rowData['media_index'] + ' - ' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><a href="info?rating_key=' + rowData['rating_key'] + '"><div style="float: left; padding-left: 30px;">' + media_type + ' ' + thumb_popover + '</div></a></div>');
|
||||
} else if (rowData['media_type'] === 'photo_album') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Photo Album"><i class="fa fa-camera fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><div style="float: left; padding-left: 15px;">' + media_type + ' ' + thumb_popover + '</div></div>');
|
||||
} else if (rowData['media_type'] === 'photo') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Photo"><i class="fa fa-picture-o fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><div style="float: left; padding-left: 15px;">' + media_type + ' ' + thumb_popover + '</div></div>');
|
||||
} else if (rowData['media_type'] === 'clip') {
|
||||
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Video"><i class="fa fa-video-camera fa-fw"></i></span>';
|
||||
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=art" data-height="80" data-width="140">' + rowData['title'] + '</span>';
|
||||
$(td).html('<div class="history-title"><div style="float: left; padding-left: 15px;">' + media_type + ' ' + thumb_popover + '</div></div>');
|
||||
} else {
|
||||
$(td).html(cellData);
|
||||
}
|
||||
@@ -335,7 +347,7 @@ function childTableOptionsMedia(rowData) {
|
||||
case 'album':
|
||||
section_type = 'track';
|
||||
break;
|
||||
case 'photo':
|
||||
case 'photo_album':
|
||||
section_type = 'picture';
|
||||
break;
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
|
@@ -30,7 +30,7 @@ DOCUMENTATION :: END
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
@@ -379,7 +379,7 @@ DOCUMENTATION :: END
|
||||
return {
|
||||
json_data: JSON.stringify( d ),
|
||||
section_id: section_id,
|
||||
user_id: "${_session['user_id']}" == "None" ? null : "${_session['user_id']}"
|
||||
user_id: "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}"
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@@ -29,7 +29,7 @@ DOCUMENTATION :: END
|
||||
headers = {'movie': ('Movie Libraries', ('Movies', '', '')),
|
||||
'show': ('TV Show Libraries', ('Shows', 'Seasons', 'Episodes')),
|
||||
'artist': ('Music Libraries', ('Artists', 'Albums', 'Tracks')),
|
||||
'photo': ('Photo Libraries', ('Albums', '', 'Photos'))}
|
||||
'photo': ('Photo Libraries', ('Albums', 'Photos', 'Videos'))}
|
||||
%>
|
||||
% for section_type in types:
|
||||
% if section_type in data:
|
||||
|
@@ -9,7 +9,7 @@
|
||||
<meta name="author" content="">
|
||||
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
|
||||
<link href="${http_root}css/pnotify.custom.min.css" rel="stylesheet" />
|
||||
<link href="${http_root}css/plexpy.css${cache_param}" rel="stylesheet">
|
||||
<link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
|
||||
<link href="${http_root}css/opensans.min.css" rel="stylesheet">
|
||||
<link href="${http_root}css/font-awesome.min.css" rel="stylesheet">
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
<style>
|
||||
td {word-break: break-all;}
|
||||
</style>
|
||||
@@ -21,9 +21,9 @@
|
||||
<span><i class="fa fa-list-alt"></i> Logs</span>
|
||||
</div>
|
||||
<div class="button-bar">
|
||||
<div class="btn-group" id="plexpy-log-levels">
|
||||
<div class="btn-group" id="tautulli-log-levels">
|
||||
<label>
|
||||
<select name="plexpy-log-level-filter" id="plexpy-log-level-filter" class="btn" style="color: inherit;">
|
||||
<select name="tautulli-log-level-filter" id="tautulli-log-level-filter" class="btn" style="color: inherit;">
|
||||
<option value="">All log levels</option>
|
||||
<option disabled>────────────</option>
|
||||
<option value="DEBUG">Debug</option>
|
||||
@@ -45,7 +45,7 @@
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn btn-dark" id="download-plexpylog"><i class="fa fa-download"></i> Download logs</button>
|
||||
<button class="btn btn-dark" id="download-tautullilog"><i class="fa fa-download"></i> Download logs</button>
|
||||
<button class="btn btn-dark" id="download-plexserverlog" style="display: none;"><i class="fa fa-download"></i> Download logs</button>
|
||||
<button class="btn btn-dark" id="download-plexscannerlog" style="display: none;"><i class="fa fa-download"></i> Download logs</button>
|
||||
<button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear logs</button>
|
||||
@@ -56,17 +56,17 @@
|
||||
<div class='table-card-back'>
|
||||
<div>
|
||||
<ul id="log_tabs" class="nav nav-pills" role="tablist">
|
||||
<li role="presentation" class="active"><a id="plexpy-logs-btn" href="#tabs-plexpy_log" aria-controls="tabs-plexpy_log" role="tab" data-toggle="tab">Tautulli Logs</a></li>
|
||||
<li role="presentation"><a id="plexpy-api-logs-btn" href="#tabs-plexpy_api_log" aria-controls="tabs-plexpy_api_log" role="tab" data-toggle="tab">Tautulli API Logs</a></li>
|
||||
<li role="presentation" class="active"><a id="tautulli-logs-btn" href="#tabs-tautulli_log" aria-controls="tabs-tautulli_log" role="tab" data-toggle="tab">Tautulli Logs</a></li>
|
||||
<li role="presentation"><a id="tautulli-api-logs-btn" href="#tabs-tautulli_api_log" aria-controls="tabs-tautulli_api_log" role="tab" data-toggle="tab">Tautulli API Logs</a></li>
|
||||
<li role="presentation"><a id="plex-logs-btn" href="#tabs-plex_log" aria-controls="tabs-plex_log" role="tab" data-toggle="tab">Plex Media Server Logs</a></li>
|
||||
<li role="presentation"><a id="plex-scanner-logs-btn" href="#tabs-plex_scanner_log" aria-controls="tabs-plex_scanner_log" role="tab" data-toggle="tab">Plex Media Scanner Logs</a></li>
|
||||
<li role="presentation"><a id="plexpy-websocket-logs-btn" href="#tabs-plex_websocket_log" aria-controls="tabs-plex_websocket_log" role="tab" data-toggle="tab">Plex Websocket Logs</a></li>
|
||||
<li role="presentation"><a id="plex-websocket-logs-btn" href="#tabs-plex_websocket_log" aria-controls="tabs-plex_websocket_log" role="tab" data-toggle="tab">Plex Websocket Logs</a></li>
|
||||
<li role="presentation"><a id="notification-logs-btn" href="#tabs-notification_log" aria-controls="tabs-notification_log" role="tab" data-toggle="tab">Notification Logs</a></li>
|
||||
<li role="presentation"><a id="login-logs-btn" href="#tabs-login_log" aria-controls="tabs-login_log" role="tab" data-toggle="tab">Login Logs</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="tabs-plexpy_log" data-logfile="plexpy">
|
||||
<table class="display" id="plexpy_log_table" width="100%">
|
||||
<div role="tabpanel" class="tab-pane active" id="tabs-tautulli_log" data-logfile="tautulli">
|
||||
<table class="display" id="tautulli_log_table" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="min-tablet" align="left" id="timestamp">Timestamp</th>
|
||||
@@ -77,8 +77,8 @@
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-plexpy_api_log" data-logfile="plexpy_api">
|
||||
<table class="display" id="plexpy_api_log_table" width="100%">
|
||||
<div role="tabpanel" class="tab-pane" id="tabs-tautulli_api_log" data-logfile="tautulli_api">
|
||||
<table class="display" id="tautulli_api_log_table" width="100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="min-tablet" align="left" id="timestamp">Timestamp</th>
|
||||
@@ -195,8 +195,8 @@
|
||||
<script>
|
||||
|
||||
$(document).ready(function() {
|
||||
loadPlexPyLogs('plexpy', selected_log_level);
|
||||
clearSearchButton('plexpy_log_table', log_table);
|
||||
loadtautullilogs('tautulli', selected_log_level);
|
||||
clearSearchButton('tautulli_log_table', log_table);
|
||||
});
|
||||
|
||||
var log_levels = ['DEBUG', 'INFO', 'WARN', 'ERROR'];
|
||||
@@ -227,7 +227,7 @@
|
||||
}
|
||||
|
||||
var selected_log_level = null;
|
||||
function loadPlexPyLogs(logfile, selected_log_level) {
|
||||
function loadtautullilogs(logfile, selected_log_level) {
|
||||
log_table_options.ajax = {
|
||||
url: "get_log",
|
||||
type: 'post',
|
||||
@@ -238,10 +238,10 @@
|
||||
log_level: selected_log_level
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
log_table = $('#' + logfile + '_log_table').DataTable(log_table_options);
|
||||
|
||||
$('#plexpy-log-level-filter').on('change', function () {
|
||||
$('#tautulli-log-level-filter').on('change', function () {
|
||||
selected_log_level = $(this).val() || null;
|
||||
log_table.draw();
|
||||
});
|
||||
@@ -250,7 +250,7 @@
|
||||
function loadPlexLogs() {
|
||||
plex_log_table_options.ajax = {
|
||||
url: "get_plex_log?log_type=server"
|
||||
}
|
||||
};
|
||||
plex_log_table_options.initComplete = bindLogLevelFilter;
|
||||
plex_log_table = $('#plex_log_table').DataTable(plex_log_table_options);
|
||||
}
|
||||
@@ -258,7 +258,7 @@
|
||||
function loadPlexScannerLogs() {
|
||||
plex_log_table_options.ajax = {
|
||||
url: "get_plex_log?log_type=scanner"
|
||||
}
|
||||
};
|
||||
plex_log_table_options.initComplete = bindLogLevelFilter;
|
||||
plex_scanner_log_table = $('#plex_scanner_log_table').DataTable(plex_log_table_options);
|
||||
}
|
||||
@@ -271,7 +271,7 @@
|
||||
json_data: JSON.stringify(d)
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
notification_log_table = $('#notification_log_table').DataTable(notification_log_table_options);
|
||||
}
|
||||
|
||||
@@ -284,56 +284,56 @@
|
||||
json_data: JSON.stringify(d)
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
login_log_table = $('#login_log_table').DataTable(login_log_table_options);
|
||||
}
|
||||
|
||||
$("#plexpy-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").show();
|
||||
$("#tautulli-logs-btn").click(function () {
|
||||
$("#tautulli-log-levels").show();
|
||||
$("#plex-log-levels").hide();
|
||||
$("#clear-logs").show();
|
||||
$("#download-plexpylog").show()
|
||||
$("#download-plexserverlog").hide()
|
||||
$("#download-plexscannerlog").hide()
|
||||
$("#download-tautullilog").show();
|
||||
$("#download-plexserverlog").hide();
|
||||
$("#download-plexscannerlog").hide();
|
||||
$("#clear-notify-logs").hide();
|
||||
$("#clear-login-logs").hide();
|
||||
loadPlexPyLogs('plexpy', selected_log_level);
|
||||
clearSearchButton('plexpy_log_table', log_table);
|
||||
loadtautullilogs('tautulli', selected_log_level);
|
||||
clearSearchButton('tautulli_log_table', log_table);
|
||||
});
|
||||
|
||||
$("#plexpy-api-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").show();
|
||||
$("#tautulli-api-logs-btn").click(function () {
|
||||
$("#tautulli-log-levels").show();
|
||||
$("#plex-log-levels").hide();
|
||||
$("#clear-logs").show();
|
||||
$("#download-plexpylog").show()
|
||||
$("#download-plexserverlog").hide()
|
||||
$("#download-plexscannerlog").hide()
|
||||
$("#download-tautullilog").show();
|
||||
$("#download-plexserverlog").hide();
|
||||
$("#download-plexscannerlog").hide();
|
||||
$("#clear-notify-logs").hide();
|
||||
$("#clear-login-logs").hide();
|
||||
loadPlexPyLogs('plexpy_api', selected_log_level);
|
||||
clearSearchButton('plexpy_api_log_table', log_table);
|
||||
loadtautullilogs('tautulli_api', selected_log_level);
|
||||
clearSearchButton('tautulli_api_log_table', log_table);
|
||||
});
|
||||
|
||||
$("#plexpy-websocket-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").show();
|
||||
$("#plex-websocket-logs-btn").click(function () {
|
||||
$("#tautulli-log-levels").show();
|
||||
$("#plex-log-levels").hide();
|
||||
$("#clear-logs").show();
|
||||
$("#download-plexpylog").show()
|
||||
$("#download-plexserverlog").hide()
|
||||
$("#download-plexscannerlog").hide()
|
||||
$("#download-tautullilog").show();
|
||||
$("#download-plexserverlog").hide();
|
||||
$("#download-plexscannerlog").hide();
|
||||
$("#clear-notify-logs").hide();
|
||||
$("#clear-login-logs").hide();
|
||||
loadPlexPyLogs('plex_websocket', selected_log_level);
|
||||
loadtautullilogs('plex_websocket', selected_log_level);
|
||||
clearSearchButton('plex_websocket_log_table', log_table);
|
||||
});
|
||||
|
||||
$("#plex-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").hide();
|
||||
$("#tautulli-log-levels").hide();
|
||||
$("#plex-log-levels").show();
|
||||
$("#clear-logs").hide();
|
||||
$("#download-plexpylog").hide()
|
||||
$("#download-plexserverlog").show()
|
||||
$("#download-plexscannerlog").hide()
|
||||
$("#download-tautullilog").hide();
|
||||
$("#download-plexserverlog").show();
|
||||
$("#download-plexscannerlog").hide();
|
||||
$("#clear-notify-logs").hide();
|
||||
$("#clear-login-logs").hide();
|
||||
loadPlexLogs();
|
||||
@@ -341,12 +341,12 @@
|
||||
});
|
||||
|
||||
$("#plex-scanner-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").hide();
|
||||
$("#tautulli-log-levels").hide();
|
||||
$("#plex-log-levels").show();
|
||||
$("#clear-logs").hide();
|
||||
$("#download-plexpylog").hide()
|
||||
$("#download-plexserverlog").hide()
|
||||
$("#download-plexscannerlog").show()
|
||||
$("#download-tautullilog").hide();
|
||||
$("#download-plexserverlog").hide();
|
||||
$("#download-plexscannerlog").show();
|
||||
$("#clear-notify-logs").hide();
|
||||
$("#clear-login-logs").hide();
|
||||
loadPlexScannerLogs();
|
||||
@@ -354,12 +354,12 @@
|
||||
});
|
||||
|
||||
$("#notification-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").hide();
|
||||
$("#tautulli-log-levels").hide();
|
||||
$("#plex-log-levels").hide();
|
||||
$("#clear-logs").hide();
|
||||
$("#download-plexpylog").hide()
|
||||
$("#download-plexserverlog").hide()
|
||||
$("#download-plexscannerlog").hide()
|
||||
$("#download-tautullilog").hide();
|
||||
$("#download-plexserverlog").hide();
|
||||
$("#download-plexscannerlog").hide();
|
||||
$("#clear-notify-logs").show();
|
||||
$("#clear-login-logs").hide();
|
||||
loadNotificationLogs();
|
||||
@@ -367,12 +367,12 @@
|
||||
});
|
||||
|
||||
$("#login-logs-btn").click(function () {
|
||||
$("#plexpy-log-levels").hide();
|
||||
$("#tautulli-log-levels").hide();
|
||||
$("#plex-log-levels").hide();
|
||||
$("#clear-logs").hide();
|
||||
$("#download-plexpylog").hide()
|
||||
$("#download-plexserverlog").hide()
|
||||
$("#download-plexscannerlog").hide()
|
||||
$("#download-tautullilog").hide();
|
||||
$("#download-plexserverlog").hide();
|
||||
$("#download-plexscannerlog").hide();
|
||||
$("#clear-notify-logs").hide();
|
||||
$("#clear-login-logs").show();
|
||||
loadLoginLogs();
|
||||
@@ -384,8 +384,8 @@
|
||||
});
|
||||
|
||||
$("#clear-logs").click(function () {
|
||||
var logfile = $(".tab-pane.active").data('logfile')
|
||||
var title = $("#log_tabs li.active a").text()
|
||||
var logfile = $(".tab-pane.active").data('logfile');
|
||||
var title = $("#log_tabs li.active a").text();
|
||||
|
||||
$("#confirm-message").text("Are you sure you want to clear the " + title + "?");
|
||||
$('#confirm-modal').modal();
|
||||
@@ -397,7 +397,7 @@
|
||||
complete: function (xhr, status) {
|
||||
result = $.parseJSON(xhr.responseText);
|
||||
msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
if (result.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
|
||||
} else {
|
||||
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
|
||||
@@ -408,7 +408,7 @@
|
||||
});
|
||||
});
|
||||
|
||||
$("#download-plexpylog").click(function () {
|
||||
$("#download-tautullilog").click(function () {
|
||||
var logfile = $(".tab-pane.active").data('logfile');
|
||||
window.location.href = "download_log?logfile=" + logfile;
|
||||
});
|
||||
@@ -431,7 +431,7 @@
|
||||
complete: function (xhr, status) {
|
||||
result = $.parseJSON(xhr.responseText);
|
||||
msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
if (result.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
|
||||
} else {
|
||||
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
|
||||
@@ -452,7 +452,7 @@
|
||||
complete: function (xhr, status) {
|
||||
result = $.parseJSON(xhr.responseText);
|
||||
msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
if (result.result === 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
|
||||
} else {
|
||||
showMsg('<i class="fa fa-times"></i> ' + msg, false, true, 5000, true)
|
||||
@@ -473,10 +473,10 @@
|
||||
{
|
||||
clearInterval(timer);
|
||||
}
|
||||
if(refreshrate.value != 0)
|
||||
if(refreshrate.value !== 0)
|
||||
{
|
||||
timer = setInterval(function() {
|
||||
if ($("#tabs-plexpy_log").hasClass("active") || $("#tabs-plexpy_api_log").hasClass("active") || $("#tabs-plex_websocket_log").hasClass("active")) {
|
||||
if ($("#tabs-tautulli_log").hasClass("active") || $("#tabs-tautulli_api_log").hasClass("active") || $("#tabs-plex_websocket_log").hasClass("active")) {
|
||||
log_table.ajax.reload();
|
||||
} else if ($("#tabs-plex_log").hasClass("active")) {
|
||||
plex_log_table.ajax.reload();
|
||||
|
@@ -171,7 +171,7 @@
|
||||
|
||||
<div class="form-group">
|
||||
<label for="custom_conditions_logic">Condition Logic</label>
|
||||
<input type="text" class="form-control" name="custom_conditions_logic" id="custom_conditions_logic" value="${notifier['custom_conditions_logic']}" required />
|
||||
<input type="text" class="form-control" name="custom_conditions_logic" id="custom_conditions_logic" value="${notifier['custom_conditions_logic']}" />
|
||||
<div id="custom_conditions_logic_error" class="alert alert-danger" role="alert" style="padding-top: 5px; padding-bottom: 5px; margin: 0; display: none;"><i class="fa fa-exclamation-triangle" style="color: #a94442;"></i> <span></span></div>
|
||||
<p class="help-block">
|
||||
Optional: Enter custom logic to use when evaluating the conditions (e.g. <span class="inline-pre">{1} and ({2} or {3})</span>).
|
||||
@@ -333,11 +333,11 @@
|
||||
$('#notifier-config-modal').unbind('hidden.bs.modal');
|
||||
|
||||
// Need this for setting conditions since conditions contain the character "
|
||||
$('#custom_conditions').val(${json.dumps(notifier["custom_conditions"]) | n});
|
||||
$('#custom_conditions').val(JSON.stringify(${json.dumps(notifier["custom_conditions"]) | n}));
|
||||
|
||||
$('#condition-widget').filterer({
|
||||
parameters: ${parameters | n},
|
||||
conditions: ${notifier["custom_conditions"] | n},
|
||||
parameters: ${json.dumps(parameters) | n},
|
||||
conditions: ${json.dumps(notifier["custom_conditions"]) | n},
|
||||
updateConditions: function(newConditions){
|
||||
$('#custom_conditions').val(JSON.stringify(newConditions));
|
||||
}
|
||||
@@ -606,6 +606,22 @@
|
||||
});
|
||||
});
|
||||
|
||||
% elif notifier['agent_name'] == 'pushover':
|
||||
function pushoverPriority() {
|
||||
if ($('#pushover_priority').val() == '2') {
|
||||
$('#pushover_retry').closest('.form-group').show();
|
||||
$('#pushover_expire').closest('.form-group').show();
|
||||
} else {
|
||||
$('#pushover_retry').closest('.form-group').hide();
|
||||
$('#pushover_expire').closest('.form-group').hide();
|
||||
}
|
||||
}
|
||||
|
||||
pushoverPriority();
|
||||
$('#pushover_priority').change( function () {
|
||||
pushoverPriority();
|
||||
});
|
||||
|
||||
% endif
|
||||
|
||||
function validateLogic() {
|
||||
|
@@ -60,9 +60,9 @@
|
||||
<input type="hidden" id="show_advanced_settings" name="show_advanced_settings" value="${config['show_advanced_settings']}" required>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="tabs-help_info">
|
||||
% if common.VERSION_NUMBER:
|
||||
% if common.RELEASE:
|
||||
<div class="padded-header">
|
||||
<h3>Version ${common.VERSION_NUMBER} <small><a id="changelog-modal-link" href="#"><i class="fa fa-info-circle"></i> Changelog</a></small></h3>
|
||||
<h3>Version ${common.RELEASE} <small><a id="changelog-modal-link" href="#"><i class="fa fa-info-circle"></i> Changelog</a></small></h3>
|
||||
</div>
|
||||
% endif
|
||||
<div class="padded-header">
|
||||
@@ -642,7 +642,7 @@
|
||||
<label for="pms_port">Plex Port</label>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<input data-parsley-type="integer" class="pms-settings form-control" type="text" id="pms_port" name="pms_port" value="${config['pms_port']}" size="30" data-parsley-trigger="change" data-parsley-errors-container="#pms_port_error" required>
|
||||
<input data-parsley-type="integer" class="form-control pms-settings" type="text" id="pms_port" name="pms_port" value="${config['pms_port']}" size="30" data-parsley-trigger="change" data-parsley-errors-container="#pms_port_error" required>
|
||||
</div>
|
||||
<div id="pms_port_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
@@ -650,29 +650,45 @@
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="pms_is_remote_checkbox" class="checkbox-toggle" data-id="pms_is_remote" value="1" ${checked(config['pms_is_remote'])}> Remote Server
|
||||
<input type="checkbox" id="pms_is_remote_checkbox" class="checkbox-toggle pms-settings" data-id="pms_is_remote" value="1" ${checked(config['pms_is_remote'])}> Remote Server
|
||||
<input type="hidden" id="pms_is_remote" name="pms_is_remote" value="${config['pms_is_remote']}">
|
||||
</label>
|
||||
<p class="help-block">Check this if your Plex Server is not on the same local network as Tautulli.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="pms_ssl_checkbox" class="checkbox-toggle" data-id="pms_ssl" value="1" ${checked(config['pms_ssl'])}> Use SSL
|
||||
<input type="checkbox" id="pms_ssl_checkbox" class="checkbox-toggle pms-settings" data-id="pms_ssl" value="1" ${checked(config['pms_ssl'])}> Use SSL
|
||||
<input type="hidden" id="pms_ssl" name="pms_ssl" value="${config['pms_ssl']}">
|
||||
</label>
|
||||
<p class="help-block">If you have secure connections enabled on your Plex Server, communicate with it securely.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="pms_url">Plex Server URL</label>
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="pms_url" name="pms_url" value="${config['pms_url']}" size="30" readonly>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="test_pms_url_button">Test URL</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
The server URL that Tautulli will use to connect to your Plex server. Retrieved automatically.
|
||||
</p>
|
||||
</div>
|
||||
<div class="checkbox advanced-setting">
|
||||
<label>
|
||||
<input type="checkbox" id="pms_url_manual" name="pms_url_manual" value="1" ${config['pms_url_manual']}> Manual Connection
|
||||
<input type="checkbox" class="pms-settings" id="pms_url_manual" name="pms_url_manual" value="1" ${config['pms_url_manual']}> Manual Connection
|
||||
</label>
|
||||
<span id="cloudManualConnection" style="display: none; color: #eb8600; padding-left: 10px;"> Not available for Plex Cloud servers.</span>
|
||||
<p class="help-block">Use the user defined connection details. Do not retrieve the server connection URL automatically.</p>
|
||||
</div>
|
||||
<div class="form-group advanced-setting">
|
||||
<label for="pms_logs_folder">Plex Web URL</label>
|
||||
<label for="pms_web_url">Plex Web URL</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-9">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="pms_web_url" name="pms_web_url" value="${config['pms_web_url']}" size="30" data-parsley-trigger="change" data-parsley-pattern="^https?:\/\/\S+$|^https:\/\/app.plex.tv\/desktop$" data-parsley-errors-container="#pms_web_url_error" data-parsley-error-message="Invalid Plex Web URL.">
|
||||
<span class="input-group-btn">
|
||||
@@ -1062,8 +1078,8 @@
|
||||
</div>
|
||||
<p class="form-group">
|
||||
<label>Registered Devices</label>
|
||||
<p class="help-block">Register a new device, or configure an existing device by clicking the settings icon on the right.</p>
|
||||
<p id="app_api_msg" style="color: #eb8600;">The API must be enabled under <a data-tab-destination="tabs-access_control" style="cursor: pointer;">Access Control</a> to use the app.</p>
|
||||
<p class="help-block">Register a new device using a QR code, or configure an existing device by clicking the settings icon on the right.</p>
|
||||
<p id="app_api_msg" style="color: #eb8600;">The API must be enabled under <a data-tab-destination="tabs-web_interface" style="cursor: pointer;">Web Interface</a> to use the app.</p>
|
||||
<div class="row">
|
||||
<div id="plexpy-mobile-devices-table" class="col-md-12">
|
||||
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading registered devices...</div>
|
||||
@@ -1573,7 +1589,7 @@ $(document).ready(function() {
|
||||
}
|
||||
|
||||
function preSaveChecks(_callback) {
|
||||
if ($("#pms_identifier").val() == "") {
|
||||
if (serverChanged) {
|
||||
verifyServer();
|
||||
}
|
||||
verifyPMSWebURL();
|
||||
@@ -1585,7 +1601,7 @@ $(document).ready(function() {
|
||||
|
||||
// Alert the user that their changes require a restart.
|
||||
function postSaveChecks() {
|
||||
if (serverChanged || authChanged || httpChanged || directoryChanged) {
|
||||
if (authChanged || httpChanged || directoryChanged) {
|
||||
$('#restart-modal').modal('show');
|
||||
}
|
||||
$("#http_hashed_password").val($("#http_hash_password").is(":checked") ? 1 : 0);
|
||||
@@ -1769,9 +1785,8 @@ $(document).ready(function() {
|
||||
|
||||
$( ".pms-settings" ).change(function() {
|
||||
serverChanged = true;
|
||||
$("#pms_identifier").val("");
|
||||
$("#server_changed").prop('checked', true);
|
||||
verifyServer();
|
||||
$("#pms_verify").hide();
|
||||
});
|
||||
|
||||
$('.checkbox-toggle').click(function () {
|
||||
@@ -1841,6 +1856,7 @@ $(document).ready(function() {
|
||||
$('#pms_ssl').val(ssl !== 'undefined' && ssl === 1 ? 1 : 0);
|
||||
$('#pms_is_cloud').val(is_cloud !== 'undefined' && is_cloud === true ? 1 : 0);
|
||||
$('#pms_url_manual').prop('checked', false);
|
||||
$('#pms_url').val('Please verify your server above to retrieve the URL');
|
||||
PMSCloudCheck();
|
||||
}
|
||||
});
|
||||
@@ -1906,6 +1922,7 @@ $(document).ready(function() {
|
||||
var pms_identifier = $("#pms_identifier").val();
|
||||
var pms_ssl = $("#pms_ssl").val();
|
||||
var pms_is_remote = $("#pms_is_remote").val();
|
||||
var pms_url_manual = $("#pms_url_manual").is(':checked') ? 1 : 0;
|
||||
|
||||
if (($("#pms_ip").val() !== '') || ($("#pms_port").val() !== '')) {
|
||||
$("#pms_verify").html('<i class="fa fa-refresh fa-spin"></i>').fadeIn('fast');
|
||||
@@ -1916,7 +1933,9 @@ $(document).ready(function() {
|
||||
port: pms_port,
|
||||
identifier: pms_identifier,
|
||||
ssl: pms_ssl,
|
||||
remote: pms_is_remote
|
||||
remote: pms_is_remote,
|
||||
manual: pms_url_manual,
|
||||
get_url: serverChanged
|
||||
},
|
||||
cache: true,
|
||||
async: true,
|
||||
@@ -1925,13 +1944,20 @@ $(document).ready(function() {
|
||||
$("#pms_verify").html('<i class="fa fa-close"></i>').fadeIn('fast');
|
||||
$("#pms_ip_group").addClass("has-error");
|
||||
},
|
||||
success: function (json) {
|
||||
var machine_identifier = json;
|
||||
if (machine_identifier) {
|
||||
$("#pms_identifier").val(machine_identifier);
|
||||
success: function(xhr, status) {
|
||||
var result = xhr;
|
||||
var identifier = result.identifier;
|
||||
var url = result.url;
|
||||
if (identifier) {
|
||||
$("#pms_identifier").val(identifier);
|
||||
if (url) {
|
||||
$("#pms_url").val(url);
|
||||
}
|
||||
$("#pms_verify").html('<i class="fa fa-check"></i>').fadeIn('fast');
|
||||
$("#pms_ip_group").removeClass("has-error");
|
||||
|
||||
serverChanged = false;
|
||||
|
||||
if (_callback) {
|
||||
_callback();
|
||||
}
|
||||
@@ -1950,7 +1976,6 @@ $(document).ready(function() {
|
||||
}
|
||||
|
||||
$('#verify_server_button').on('click', function(){
|
||||
$("#pms_identifier").val("");
|
||||
verifyServer();
|
||||
});
|
||||
|
||||
@@ -1959,6 +1984,13 @@ $(document).ready(function() {
|
||||
$("#pms_web_url").val(pms_web_url || 'https://app.plex.tv/desktop');
|
||||
}
|
||||
|
||||
$('#test_pms_url_button').on('click', function(){
|
||||
var pms_url = $.trim($("#pms_url").val());
|
||||
if (pms_url.startsWith('http')) {
|
||||
window.open(pms_url + '/web', '_blank');
|
||||
}
|
||||
});
|
||||
|
||||
$('#test_pms_web_button').on('click', function(){
|
||||
var pms_web_url = $.trim($("#pms_web_url").val());
|
||||
window.open(pms_web_url, '_blank');
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
|
||||
<style>
|
||||
td {word-wrap: break-word}
|
||||
@@ -134,7 +134,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
var selected_user_id = "${_session['user_id']}" == "None" ? null : "${_session['user_id']}";
|
||||
var selected_user_id = "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}";
|
||||
loadSyncTable(selected_user_id);
|
||||
|
||||
% if _session['user_group'] == 'admin':
|
||||
|
@@ -32,7 +32,7 @@ DOCUMENTATION :: END
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<%def name="headIncludes()">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.bootstrap.css">
|
||||
<link rel="stylesheet" href="${http_root}css/dataTables.colVis.css">
|
||||
<link rel="stylesheet" href="${http_root}css/plexpy-dataTables.css">
|
||||
<link rel="stylesheet" href="${http_root}css/tautulli-dataTables.css">
|
||||
</%def>
|
||||
|
||||
<%def name="body()">
|
||||
|
@@ -14,7 +14,7 @@
|
||||
<meta name="author" content="">
|
||||
<link href="${http_root}css/bootstrap3/bootstrap.css" rel="stylesheet">
|
||||
<link href="${http_root}css/bootstrap-wizard.css" rel="stylesheet">
|
||||
<link href="${http_root}css/plexpy.css${cache_param}" rel="stylesheet">
|
||||
<link href="${http_root}css/tautulli.css${cache_param}" rel="stylesheet">
|
||||
<link href="${http_root}css/selectize.bootstrap3.css" rel="stylesheet">
|
||||
<link href="${http_root}css/opensans.min.css" rel="stylesheet">
|
||||
<link href="${http_root}css/font-awesome.min.css" rel="stylesheet">
|
||||
@@ -94,7 +94,7 @@
|
||||
<label for="pms_ip">Plex IP or Hostname</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<select class="form-control selectize-pms-ip" id="pms_ip" name="pms_ip">
|
||||
<select class="form-control pms-settings selectize-pms-ip" id="pms_ip" name="pms_ip">
|
||||
<option value="${config['pms_ip']}" selected>${config['pms_ip']}</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -104,12 +104,12 @@
|
||||
<label for="pms_port">Plex Port</label>
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<input type="text" class="form-control pms_settings" name="pms_port" id="pms_port" placeholder="32400" value="${config['pms_port']}" required>
|
||||
<input type="text" class="form-control pms-settings" name="pms_port" id="pms_port" placeholder="32400" value="${config['pms_port']}" required>
|
||||
</div>
|
||||
<div class="col-xs-4">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="pms_ssl_checkbox" class="checkbox-toggle" data-id="pms_ssl" value="1" ${helpers.checked(config['pms_ssl'])}> Use SSL
|
||||
<input type="checkbox" id="pms_ssl_checkbox" class="checkbox-toggle pms-settings" data-id="pms_ssl" value="1" ${helpers.checked(config['pms_ssl'])}> Use SSL
|
||||
<input type="hidden" id="pms_ssl" name="pms_ssl" value="${config['pms_ssl']}">
|
||||
</label>
|
||||
</div>
|
||||
@@ -117,16 +117,16 @@
|
||||
<div class="col-xs-4">
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="pms_is_remote_checkbox" class="checkbox-toggle" data-id="pms_is_remote" value="1" ${helpers.checked(config['pms_is_remote'])}> Remote Server
|
||||
<input type="checkbox" id="pms_is_remote_checkbox" class="checkbox-toggle pms-settings" data-id="pms_is_remote" value="1" ${helpers.checked(config['pms_is_remote'])}> Remote Server
|
||||
<input type="hidden" id="pms_is_remote" name="pms_is_remote" value="${config['pms_is_remote']}">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" class="form-control pms-settings" id="pms_valid" data-validate="validatePMSip" value="">
|
||||
<input type="hidden" id="pms_valid" data-validate="validatePMSip" value="">
|
||||
<input type="hidden" id="pms_is_cloud" name="pms_is_cloud" value="${config['pms_is_cloud']}">
|
||||
<input type="hidden" class="form-control pms-settings" id="pms_identifier" name="pms_identifier" value="${config['pms_identifier']}">
|
||||
<input type="hidden" id="pms_identifier" name="pms_identifier" value="${config['pms_identifier']}">
|
||||
<a class="btn btn-dark" id="verify-plex-server" href="#" role="button">Verify</a><span style="margin-left: 10px; display: none;" id="pms-verify-status"></span>
|
||||
</div>
|
||||
|
||||
@@ -419,7 +419,8 @@ $(document).ready(function() {
|
||||
port: pms_port,
|
||||
identifier: pms_identifier,
|
||||
ssl: pms_ssl,
|
||||
remote: pms_is_remote },
|
||||
remote: pms_is_remote
|
||||
},
|
||||
cache: true,
|
||||
async: true,
|
||||
timeout: 5000,
|
||||
@@ -427,10 +428,11 @@ $(document).ready(function() {
|
||||
$("#pms-verify-status").html('<i class="fa fa-exclamation-circle"></i> This is not a Plex Server!');
|
||||
$('#pms-verify-status').fadeIn('fast');
|
||||
},
|
||||
success: function (json) {
|
||||
var machine_identifier = json;
|
||||
if (machine_identifier) {
|
||||
$("#pms_identifier").val(machine_identifier);
|
||||
success: function(xhr, status) {
|
||||
var result = xhr;
|
||||
var identifier = result.identifier;
|
||||
if (identifier) {
|
||||
$("#pms_identifier").val(identifier);
|
||||
$("#pms-verify-status").html('<i class="fa fa-check"></i> Server found!');
|
||||
$('#pms-verify-status').fadeIn('fast');
|
||||
pms_verified = true;
|
||||
|
@@ -1,54 +1,54 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# PROVIDE: plexpy
|
||||
# REQUIRE: plexpy
|
||||
# PROVIDE: tautulli
|
||||
# REQUIRE: tautulli
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
|
||||
# to enable this service:
|
||||
#
|
||||
# plexpy_enable (bool): Set to NO by default.
|
||||
# tautulli_enable (bool): Set to NO by default.
|
||||
# Set it to YES to enable it.
|
||||
# plexpy_user: The user account PlexPy daemon runs as what
|
||||
# you want it to be. It uses 'plexpy' user by
|
||||
# tautulli_user: The user account Tautulli daemon runs as what
|
||||
# you want it to be. It uses 'tautulli' user by
|
||||
# default. Do not sets it as empty or it will run
|
||||
# as root.
|
||||
# plexpy_dir: Directory where PlexPy lives.
|
||||
# Default: /usr/local/plexpy
|
||||
# plexpy_chdir: Change to this directory before running PlexPy.
|
||||
# Default is same as plexpy_dir.
|
||||
# plexpy_pid: The name of the pidfile to create.
|
||||
# Default is plexpy.pid in plexpy_dir.
|
||||
# tautulli_dir: Directory where Tautulli lives.
|
||||
# Default: /usr/local/share/Tautulli
|
||||
# tautulli_chdir: Change to this directory before running Tautulli.
|
||||
# Default is same as tautulli_dir.
|
||||
# tautulli_pid: The name of the pidfile to create.
|
||||
# Default is tautulli.pid in tautulli_dir.
|
||||
|
||||
. /etc/rc.subr
|
||||
|
||||
name="plexpy"
|
||||
name="tautulli"
|
||||
rcvar=${name}_enable
|
||||
|
||||
load_rc_config ${name}
|
||||
|
||||
: ${plexpy_enable:="NO"}
|
||||
: ${plexpy_user:="plexpy"}
|
||||
: ${plexpy_dir:="/usr/local/plexpy"}
|
||||
: ${plexpy_chdir:="${plexpy_dir}"}
|
||||
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
|
||||
: ${plexpy_conf:="${plexpy_dir}/config.ini"}
|
||||
: ${tautulli_enable:="NO"}
|
||||
: ${tautulli_user:="tautulli"}
|
||||
: ${tautulli_dir:="/usr/local/share/Tautulli"}
|
||||
: ${tautulli_chdir:="${tautulli_dir}"}
|
||||
: ${tautulli_pid:="${tautulli_dir}/tautulli.pid"}
|
||||
: ${tautulli_conf:="${tautulli_dir}/config.ini"}
|
||||
|
||||
WGET="/usr/local/bin/wget" # You need wget for this script to safely shutdown PlexPy.
|
||||
if [ -e "${plexpy_conf}" ]; then
|
||||
HOST=`grep -A64 "\[General\]" "${plexpy_conf}"|egrep "^http_host"|perl -wple 's/^http_host = (.*)$/$1/'`
|
||||
PORT=`grep -A64 "\[General\]" "${plexpy_conf}"|egrep "^http_port"|perl -wple 's/^http_port = (.*)$/$1/'`
|
||||
WGET="/usr/local/bin/wget" # You need wget for this script to safely shutdown Tautulli.
|
||||
if [ -e "${tautulli_conf}" ]; then
|
||||
HOST=`grep -A64 "\[General\]" "${tautulli_conf}"|egrep "^http_host"|perl -wple 's/^http_host = (.*)$/$1/'`
|
||||
PORT=`grep -A64 "\[General\]" "${tautulli_conf}"|egrep "^http_port"|perl -wple 's/^http_port = (.*)$/$1/'`
|
||||
fi
|
||||
|
||||
status_cmd="${name}_status"
|
||||
stop_cmd="${name}_stop"
|
||||
|
||||
command="${plexpy_dir}/PlexPy.py"
|
||||
command_args="--daemon --quiet --nolaunch --port ${PORT} --pidfile ${plexpy_pid} --config ${plexpy_conf}"
|
||||
command="${tautulli_dir}/Tautulli.py"
|
||||
command_args="--daemon --quiet --nolaunch --port ${PORT} --pidfile ${tautulli_pid} --config ${tautulli_conf}"
|
||||
|
||||
# Check for wget and refuse to start without it.
|
||||
if [ ! -x "${WGET}" ]; then
|
||||
warn "PlexPy not started: You need wget to safely shut down PlexPy."
|
||||
warn "Tautulli not started: You need wget to safely shut down Tautulli."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -58,21 +58,21 @@ if [ `id -u` != "0" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_plexpy_pid() {
|
||||
# Make sure the pid corresponds to the PlexPy process.
|
||||
pid=`cat ${plexpy_pid} 2>/dev/null`
|
||||
ps -p ${pid} | grep -q "python ${plexpy_dir}/PlexPy.py"
|
||||
verify_tautulli_pid() {
|
||||
# Make sure the pid corresponds to the Tautulli process.
|
||||
pid=`cat ${tautulli_pid} 2>/dev/null`
|
||||
ps -p ${pid} | grep -q "python ${tautulli_dir}/Tautulli.py"
|
||||
return $?
|
||||
}
|
||||
|
||||
# Try to stop PlexPy cleanly by calling shutdown over http.
|
||||
plexpy_stop() {
|
||||
if [ ! -e "${plexpy_conf}" ]; then
|
||||
echo "PlexPy' settings file does not exist. Try starting PlexPy, as this should create the file."
|
||||
# Try to stop Tautulli cleanly by calling shutdown over http.
|
||||
tautulli_stop() {
|
||||
if [ ! -e "${tautulli_conf}" ]; then
|
||||
echo "Tautulli' settings file does not exist. Try starting Tautulli, as this should create the file."
|
||||
exit 1
|
||||
fi
|
||||
echo "Stopping $name"
|
||||
verify_plexpy_pid
|
||||
verify_tautulli_pid
|
||||
${WGET} -O - -q --user=${SBUSR} --password=${SBPWD} "http://${HOST}:${PORT}/shutdown/" >/dev/null
|
||||
|
||||
if [ -n "${pid}" ]; then
|
||||
@@ -81,8 +81,8 @@ plexpy_stop() {
|
||||
fi
|
||||
}
|
||||
|
||||
plexpy_status() {
|
||||
verify_plexpy_pid && echo "$name is running as ${pid}" || echo "$name is not running"
|
||||
tautulli_status() {
|
||||
verify_tautulli_pid && echo "$name is running as ${pid}" || echo "$name is not running"
|
||||
}
|
||||
|
||||
run_rc_command "$1"
|
||||
|
@@ -1,25 +1,25 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
### BEGIN INIT INFO
|
||||
# Provides: PlexPy
|
||||
# Provides: Tautulli
|
||||
# Required-Start: $all
|
||||
# Required-Stop: $all
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: starts PlexPy
|
||||
# Description: starts PlexPy
|
||||
# Short-Description: starts Tautulli
|
||||
# Description: starts Tautulli
|
||||
### END INIT INFO
|
||||
|
||||
# Source function library.
|
||||
. /etc/init.d/functions
|
||||
|
||||
## Variables
|
||||
prog=plexpy
|
||||
prog=tautulli
|
||||
lockfile=/var/lock/subsys/$prog
|
||||
homedir=/opt/plexpy
|
||||
datadir=/opt/plexpy
|
||||
configfile=/opt/plexpy/config.ini
|
||||
pidfile=/var/run/plexpy.pid
|
||||
homedir=/opt/Tautulli
|
||||
datadir=/opt/Tautulli
|
||||
configfile=/opt/Tautulli/config.ini
|
||||
pidfile=/var/run/tautulli.pid
|
||||
nice=
|
||||
# The following line must point to your Python 2.7 install
|
||||
python27=/usr/src/Python-2.7.11/python
|
||||
@@ -30,7 +30,7 @@ options=" --daemon --config $configfile --pidfile $pidfile --datadir $datadir --
|
||||
start() {
|
||||
# Start daemon.
|
||||
echo -n $"Starting $prog: "
|
||||
daemon --pidfile=$pidfile $nice $python27 $homedir/PlexPy.py $options
|
||||
daemon --pidfile=$pidfile $nice $python27 $homedir/Tautulli.py $options
|
||||
RETVAL=$?
|
||||
echo
|
||||
[ $RETVAL -eq 0 ] && touch $lockfile
|
||||
|
@@ -1,45 +1,45 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# PROVIDE: plexpy
|
||||
# REQUIRE: DAEMON plexpy
|
||||
# PROVIDE: tautulli
|
||||
# REQUIRE: DAEMON tautulli
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
|
||||
# to enable this service:
|
||||
#
|
||||
# plexpy_enable (bool): Set to NO by default.
|
||||
# tautulli_enable (bool): Set to NO by default.
|
||||
# Set it to YES to enable it.
|
||||
# plexpy_user: The user account PlexPy daemon runs as what
|
||||
# you want it to be. It uses 'plexpy' user by
|
||||
# tautulli_user: The user account Tautulli daemon runs as what
|
||||
# you want it to be. It uses 'tautulli' user by
|
||||
# default. Do not sets it as empty or it will run
|
||||
# as root.
|
||||
# plexpy_dir: Directory where PlexPy lives.
|
||||
# Default: /usr/local/share/plexpy
|
||||
# plexpy_chdir: Change to this directory before running PlexPy.
|
||||
# Default is same as plexpy_dir.
|
||||
# plexpy_pid: The name of the pidfile to create.
|
||||
# Default is plexpy.pid in plexpy_dir.
|
||||
# tautulli_dir: Directory where Tautulli lives.
|
||||
# Default: /usr/local/share/Tautulli
|
||||
# tautulli_chdir: Change to this directory before running Tautulli.
|
||||
# Default is same as tautulli_dir.
|
||||
# tautulli_pid: The name of the pidfile to create.
|
||||
# Default is tautulli.pid in tautulli_dir.
|
||||
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
|
||||
|
||||
. /etc/rc.subr
|
||||
|
||||
name="plexpy"
|
||||
name="tautulli"
|
||||
rcvar=${name}_enable
|
||||
|
||||
load_rc_config ${name}
|
||||
|
||||
: ${plexpy_enable:="NO"}
|
||||
: ${plexpy_user:="plexpy"}
|
||||
: ${plexpy_dir:="/usr/local/share/plexpy"}
|
||||
: ${plexpy_chdir:="${plexpy_dir}"}
|
||||
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
|
||||
: ${plexpy_flags:=""}
|
||||
: ${tautulli_enable:="NO"}
|
||||
: ${tautulli_user:="tautulli"}
|
||||
: ${tautulli_dir:="/usr/local/share/Tautulli"}
|
||||
: ${tautulli_chdir:="${tautulli_dir}"}
|
||||
: ${tautulli_pid:="${tautulli_dir}/tautulli.pid"}
|
||||
: ${tautulli_flags:=""}
|
||||
|
||||
status_cmd="${name}_status"
|
||||
stop_cmd="${name}_stop"
|
||||
|
||||
command="${plexpy_dir}/PlexPy.py"
|
||||
command_args="--daemon --pidfile ${plexpy_pid} --quiet --nolaunch ${plexpy_flags}"
|
||||
command="${tautulli_dir}/Tautulli.py"
|
||||
command_args="--daemon --pidfile ${tautulli_pid} --quiet --nolaunch ${tautulli_flags}"
|
||||
|
||||
# Ensure user is root when running this script.
|
||||
if [ `id -u` != "0" ]; then
|
||||
@@ -47,21 +47,21 @@ if [ `id -u` != "0" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_plexpy_pid() {
|
||||
# Make sure the pid corresponds to the PlexPy process.
|
||||
if [ -f ${plexpy_pid} ]; then
|
||||
pid=`cat ${plexpy_pid} 2>/dev/null`
|
||||
ps -p ${pid} | grep -q "python2 ${plexpy_dir}/PlexPy.py"
|
||||
verify_tautulli_pid() {
|
||||
# Make sure the pid corresponds to the Tautulli process.
|
||||
if [ -f ${tautulli_pid} ]; then
|
||||
pid=`cat ${tautulli_pid} 2>/dev/null`
|
||||
ps -p ${pid} | grep -q "python2 ${tautulli_dir}/Tautulli.py"
|
||||
return $?
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Try to stop PlexPy cleanly by sending SIGTERM
|
||||
plexpy_stop() {
|
||||
# Try to stop Tautulli cleanly by sending SIGTERM
|
||||
tautulli_stop() {
|
||||
echo "Stopping $name"
|
||||
verify_plexpy_pid
|
||||
verify_tautulli_pid
|
||||
if [ -n "${pid}" ]; then
|
||||
kill ${pid}
|
||||
wait_for_pids ${pid}
|
||||
@@ -69,8 +69,8 @@ plexpy_stop() {
|
||||
fi
|
||||
}
|
||||
|
||||
plexpy_status() {
|
||||
verify_plexpy_pid
|
||||
tautulli_status() {
|
||||
verify_tautulli_pid
|
||||
if [ -n "${pid}" ]; then
|
||||
echo "$name is running as ${pid}."
|
||||
else
|
||||
|
@@ -1,45 +1,45 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# PROVIDE: plexpy
|
||||
# REQUIRE: DAEMON plexpy
|
||||
# PROVIDE: tautulli
|
||||
# REQUIRE: DAEMON tautulli
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
|
||||
# to enable this service:
|
||||
#
|
||||
# plexpy_enable (bool): Set to NO by default.
|
||||
# tautulli_enable (bool): Set to NO by default.
|
||||
# Set it to YES to enable it.
|
||||
# plexpy_user: The user account PlexPy daemon runs as what
|
||||
# you want it to be. It uses 'plexpy' user by
|
||||
# tautulli_user: The user account Tautulli daemon runs as what
|
||||
# you want it to be. It uses 'tautulli' user by
|
||||
# default. Do not sets it as empty or it will run
|
||||
# as root.
|
||||
# plexpy_dir: Directory where PlexPy lives.
|
||||
# Default: /usr/local/share/plexpy
|
||||
# plexpy_chdir: Change to this directory before running PlexPy.
|
||||
# Default is same as plexpy_dir.
|
||||
# plexpy_pid: The name of the pidfile to create.
|
||||
# Default is plexpy.pid in plexpy_dir.
|
||||
# tautulli_dir: Directory where Tautulli lives.
|
||||
# Default: /usr/local/share/Tautulli
|
||||
# tautulli_chdir: Change to this directory before running Tautulli.
|
||||
# Default is same as tautulli_dir.
|
||||
# tautulli_pid: The name of the pidfile to create.
|
||||
# Default is tautulli.pid in tautulli_dir.
|
||||
PATH="/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin"
|
||||
|
||||
. /etc/rc.subr
|
||||
|
||||
name="plexpy"
|
||||
name="tautulli"
|
||||
rcvar=${name}_enable
|
||||
|
||||
load_rc_config ${name}
|
||||
|
||||
: ${plexpy_enable:="NO"}
|
||||
: ${plexpy_user:="plexpy"}
|
||||
: ${plexpy_dir:="/usr/local/share/plexpy"}
|
||||
: ${plexpy_chdir:="${plexpy_dir}"}
|
||||
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
|
||||
: ${plexpy_flags:=""}
|
||||
: ${tautulli_enable:="NO"}
|
||||
: ${tautulli_user:="tautulli"}
|
||||
: ${tautulli_dir:="/usr/local/share/Tautulli"}
|
||||
: ${tautulli_chdir:="${tautulli_dir}"}
|
||||
: ${tautulli_pid:="${tautulli_dir}/tautulli.pid"}
|
||||
: ${tautulli_flags:=""}
|
||||
|
||||
status_cmd="${name}_status"
|
||||
stop_cmd="${name}_stop"
|
||||
|
||||
command="${plexpy_dir}/PlexPy.py"
|
||||
command_args="--daemon --pidfile ${plexpy_pid} --quiet --nolaunch ${plexpy_flags}"
|
||||
command="${tautulli_dir}/Tautulli.py"
|
||||
command_args="--daemon --pidfile ${tautulli_pid} --quiet --nolaunch ${tautulli_flags}"
|
||||
|
||||
# Ensure user is root when running this script.
|
||||
if [ `id -u` != "0" ]; then
|
||||
@@ -47,21 +47,21 @@ if [ `id -u` != "0" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
verify_plexpy_pid() {
|
||||
# Make sure the pid corresponds to the PlexPy process.
|
||||
if [ -f ${plexpy_pid} ]; then
|
||||
pid=`cat ${plexpy_pid} 2>/dev/null`
|
||||
ps -p ${pid} | grep -q "python2 ${plexpy_dir}/PlexPy.py"
|
||||
verify_tautulli_pid() {
|
||||
# Make sure the pid corresponds to the Tautulli process.
|
||||
if [ -f ${tautulli_pid} ]; then
|
||||
pid=`cat ${tautulli_pid} 2>/dev/null`
|
||||
ps -p ${pid} | grep -q "python2 ${tautulli_dir}/Tautulli.py"
|
||||
return $?
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Try to stop PlexPy cleanly by sending SIGTERM
|
||||
plexpy_stop() {
|
||||
# Try to stop Tautulli cleanly by sending SIGTERM
|
||||
tautulli_stop() {
|
||||
echo "Stopping $name."
|
||||
verify_plexpy_pid
|
||||
verify_tautulli_pid
|
||||
if [ -n "${pid}" ]; then
|
||||
kill ${pid}
|
||||
wait_for_pids ${pid}
|
||||
@@ -69,8 +69,8 @@ plexpy_stop() {
|
||||
fi
|
||||
}
|
||||
|
||||
plexpy_status() {
|
||||
verify_plexpy_pid
|
||||
tautulli_status() {
|
||||
verify_tautulli_pid
|
||||
if [ -n "${pid}" ]; then
|
||||
echo "$name is running as ${pid}."
|
||||
else
|
||||
|
@@ -3,12 +3,12 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>plexpy</string>
|
||||
<string>tautulli</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<!-- Modify these two lines if you need to to reflect your python location and PlexPy install location -->
|
||||
<!-- Modify these two lines if you need to to reflect your python location and Tautulli install location -->
|
||||
<string>/usr/bin/python</string>
|
||||
<string>/Applications/PlexPy/PlexPy.py</string>
|
||||
<string>/Applications/Tautulli/Tautulli.py</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
@@ -2,9 +2,9 @@
|
||||
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
|
||||
<!--
|
||||
Created by Manifold
|
||||
--><service_bundle type="manifest" name="plexpy">
|
||||
--><service_bundle type="manifest" name="tautulli">
|
||||
|
||||
<service name="application/plexpy" type="service" version="1">
|
||||
<service name="application/tautulli" type="service" version="1">
|
||||
|
||||
<create_default_instance enabled="true"/>
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
</dependency>
|
||||
|
||||
<method_context>
|
||||
<method_credential user="plexpy" group="nogroup"/>
|
||||
<method_credential user="tautulli" group="nogroup"/>
|
||||
</method_context>
|
||||
|
||||
<exec_method type="method" name="start" exec="python /opt/plexpy/PlexPy.py --daemon --quiet --nolaunch" timeout_seconds="60"/>
|
||||
<exec_method type="method" name="start" exec="python /opt/Tautulli/Tautulli.py --daemon --quiet --nolaunch" timeout_seconds="60"/>
|
||||
|
||||
<exec_method type="method" name="stop" exec=":kill" timeout_seconds="60"/>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<template>
|
||||
<common_name>
|
||||
<loctext xml:lang="C">
|
||||
PlexPy
|
||||
Tautulli
|
||||
</loctext>
|
||||
</common_name>
|
||||
</template>
|
||||
|
@@ -1,11 +1,11 @@
|
||||
# PlexPy - Stats for Plex Media Server usage
|
||||
# Tautulli - Stats for Plex Media Server usage
|
||||
#
|
||||
# Service Unit file for systemd system manager
|
||||
#
|
||||
# INSTALLATION NOTES
|
||||
#
|
||||
# 1. Rename this file as you want, ensuring that it ends in .service
|
||||
# e.g. 'plexpy.service'
|
||||
# e.g. 'tautulli.service'
|
||||
#
|
||||
# 2. Adjust configuration settings as required. More details in the
|
||||
# "CONFIGURATION NOTES" section shown below.
|
||||
@@ -15,39 +15,39 @@
|
||||
#
|
||||
# 4. Enable boot-time autostart with the following commands:
|
||||
# systemctl daemon-reload
|
||||
# systemctl enable plexpy.service
|
||||
# systemctl enable tautulli.service
|
||||
#
|
||||
# 5. Start now with the following command:
|
||||
# systemctl start plexpy.service
|
||||
# systemctl start tautulli.service
|
||||
#
|
||||
# CONFIGURATION NOTES
|
||||
#
|
||||
# - The example settings in this file assume that you will run PlexPy as user: plexpy
|
||||
# - To create this user and give it ownership of the plexpy directory:
|
||||
# sudo adduser --system --no-create-home plexpy
|
||||
# sudo chown plexpy:nogroup -R /opt/plexpy
|
||||
# - The example settings in this file assume that you will run Tautulli as user: tautulli
|
||||
# - To create this user and give it ownership of the tautulli directory:
|
||||
# sudo adduser --system --no-create-home tautulli
|
||||
# sudo chown tautulli:nogroup -R /opt/Tautulli
|
||||
#
|
||||
# - Option names (e.g. ExecStart=, Type=) appear to be case-sensitive)
|
||||
#
|
||||
# - Adjust ExecStart= to point to:
|
||||
# 1. Your PlexPy executable,
|
||||
# 1. Your Tautulli executable,
|
||||
# 2. Your config file (recommended is to put it somewhere in /etc)
|
||||
# 3. Your datadir (recommended is to NOT put it in your PlexPy exec dir)
|
||||
# 3. Your datadir (recommended is to NOT put it in your Tautulli exec dir)
|
||||
#
|
||||
# - Adjust User= and Group= to the user/group you want PlexPy to run as.
|
||||
# - Adjust User= and Group= to the user/group you want Tautulli to run as.
|
||||
#
|
||||
# - WantedBy= specifies which target (i.e. runlevel) to start PlexPy for.
|
||||
# - WantedBy= specifies which target (i.e. runlevel) to start Tautulli for.
|
||||
# multi-user.target equates to runlevel 3 (multi-user text mode)
|
||||
# graphical.target equates to runlevel 5 (multi-user X11 graphical mode)
|
||||
|
||||
[Unit]
|
||||
Description=PlexPy - Stats for Plex Media Server usage
|
||||
Description=Tautulli - Stats for Plex Media Server usage
|
||||
|
||||
[Service]
|
||||
ExecStart=/opt/plexpy/PlexPy.py --quiet --daemon --nolaunch --config /opt/plexpy/config.ini --datadir /opt/plexpy
|
||||
ExecStart=/opt/Tautulli/Tautulli.py --quiet --daemon --nolaunch --config /opt/Tautulli/config.ini --datadir /opt/Tautulli
|
||||
GuessMainPID=no
|
||||
Type=forking
|
||||
User=plexpy
|
||||
User=tautulli
|
||||
Group=nogroup
|
||||
|
||||
[Install]
|
||||
|
@@ -1,71 +1,71 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
## Don't edit this file
|
||||
## Edit user configuation in /etc/default/plexpy to change
|
||||
## Edit user configuation in /etc/default/tautulli to change
|
||||
##
|
||||
## Make sure init script is executable
|
||||
## sudo chmod +x /path/to/init.ubuntu
|
||||
##
|
||||
## Install the init script
|
||||
## sudo ln -s /path/to/init.ubuntu /etc/init.d/plexpy
|
||||
## sudo ln -s /path/to/init.ubuntu /etc/init.d/tautulli
|
||||
##
|
||||
## Create the plexpy daemon user:
|
||||
## sudo adduser --system --no-create-home plexpy
|
||||
## Create the tautulli daemon user:
|
||||
## sudo adduser --system --no-create-home tautulli
|
||||
##
|
||||
## Make sure /opt/plexpy is owned by the plexpy user
|
||||
## sudo chown plexpy:nogroup -R /opt/plexpy
|
||||
## Make sure /opt/Tautulli is owned by the tautulli user
|
||||
## sudo chown tautulli:nogroup -R /opt/Tautulli
|
||||
##
|
||||
## Touch the default file to stop the warning message when starting
|
||||
## sudo touch /etc/default/plexpy
|
||||
## sudo touch /etc/default/tautulli
|
||||
##
|
||||
## To start PlexPy automatically
|
||||
## sudo update-rc.d plexpy defaults
|
||||
## To start Tautulli automatically
|
||||
## sudo update-rc.d tautulli defaults
|
||||
##
|
||||
## To start/stop/restart PlexPy
|
||||
## sudo service plexpy start
|
||||
## sudo service plexpy stop
|
||||
## sudo service plexpy restart
|
||||
## To start/stop/restart Tautulli
|
||||
## sudo service tautulli start
|
||||
## sudo service tautulli stop
|
||||
## sudo service tautulli restart
|
||||
##
|
||||
## HP_USER= #$RUN_AS, username to run plexpy under, the default is plexpy
|
||||
## HP_HOME= #$APP_PATH, the location of PlexPy.py, the default is /opt/plexpy
|
||||
## HP_DATA= #$DATA_DIR, the location of plexpy.db, cache, logs, the default is /opt/plexpy
|
||||
## HP_PIDFILE= #$PID_FILE, the location of plexpy.pid, the default is /var/run/plexpy/plexpy.pid
|
||||
## TAUTULLI_USER= #$RUN_AS, username to run Tautulli under, the default is tautulli
|
||||
## TAUTULLI_HOME= #$APP_PATH, the location of Tautulli.py, the default is /opt/Tautulli
|
||||
## TAUTULLI_DATA= #$DATA_DIR, the location of plexpy.db, cache, logs, the default is /opt/Tautulli
|
||||
## TAUTULLI_PIDFILE= #$PID_FILE, the location of tautulli.pid, the default is /var/run/tautulli/tautulli.pid
|
||||
## PYTHON_BIN= #$DAEMON, the location of the python binary, the default is /usr/bin/python
|
||||
## HP_OPTS= #$EXTRA_DAEMON_OPTS, extra cli option for plexpy, i.e. " --config=/home/plexpy/config.ini"
|
||||
## TAUTULLI_OPTS= #$EXTRA_DAEMON_OPTS, extra cli option for Tautulli, i.e. " --config=/home/Tautulli/config.ini"
|
||||
## SSD_OPTS= #$EXTRA_SSD_OPTS, extra start-stop-daemon option like " --group=users"
|
||||
## HP_PORT= #$PORT_OPTS, hardcoded port for the webserver, overrides value in config.ini
|
||||
## TAUTULLI_PORT= #$PORT_OPTS, hardcoded port for the webserver, overrides value in config.ini
|
||||
##
|
||||
## EXAMPLE if want to run as different user
|
||||
## add HP_USER=username to /etc/default/plexpy
|
||||
## otherwise default plexpy is used
|
||||
## add TAUTULLI_USER=username to /etc/default/tautulli
|
||||
## otherwise default tautulli is used
|
||||
#
|
||||
### BEGIN INIT INFO
|
||||
# Provides: plexpy
|
||||
# Provides: tautulli
|
||||
# Required-Start: $local_fs $network $remote_fs
|
||||
# Required-Stop: $local_fs $network $remote_fs
|
||||
# Should-Start: $NetworkManager
|
||||
# Should-Stop: $NetworkManager
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: starts instance of PlexPy
|
||||
# Description: starts instance of PlexPy using start-stop-daemon
|
||||
# Short-Description: starts instance of Tautulli
|
||||
# Description: starts instance of Tautulli using start-stop-daemon
|
||||
### END INIT INFO
|
||||
|
||||
# Script name
|
||||
NAME=plexpy
|
||||
NAME=tautulli
|
||||
|
||||
# App name
|
||||
DESC=PlexPy
|
||||
DESC=Tautulli
|
||||
|
||||
SETTINGS_LOADED=FALSE
|
||||
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
# Source PlexPy configuration
|
||||
if [ -f /etc/default/plexpy ]; then
|
||||
SETTINGS=/etc/default/plexpy
|
||||
# Source Tautulli configuration
|
||||
if [ -f /etc/default/tautulli ]; then
|
||||
SETTINGS=/etc/default/tautulli
|
||||
else
|
||||
log_warning_msg "/etc/default/plexpy not found using default settings.";
|
||||
log_warning_msg "/etc/default/tautulli not found using default settings.";
|
||||
fi
|
||||
|
||||
check_retval() {
|
||||
@@ -84,32 +84,32 @@ load_settings() {
|
||||
|
||||
## The defaults
|
||||
# Run as username
|
||||
RUN_AS=${HP_USER-plexpy}
|
||||
RUN_AS=${TAUTULLI_USER-tautulli}
|
||||
|
||||
# Path to app HP_HOME=path_to_app_PlexPy.py
|
||||
APP_PATH=${HP_HOME-/opt/plexpy}
|
||||
# Path to app TAUTULLI_HOME=path_to_app_Tautulli.py
|
||||
APP_PATH=${TAUTULLI_HOME-/opt/Tautulli}
|
||||
|
||||
# Data directory where plexpy.db, cache and logs are stored
|
||||
DATA_DIR=${HP_DATA-/opt/plexpy}
|
||||
DATA_DIR=${TAUTULLI_DATA-/opt/Tautulli}
|
||||
|
||||
# Path to store PID file
|
||||
PID_FILE=${HP_PIDFILE-/var/run/plexpy/plexpy.pid}
|
||||
PID_FILE=${TAUTULLI_PIDFILE-/var/run/tautulli/tautulli.pid}
|
||||
|
||||
# Path to python bin
|
||||
DAEMON=${PYTHON_BIN-/usr/bin/python}
|
||||
|
||||
# Extra daemon option like: HP_OPTS=" --config=/home/plexpy/config.ini"
|
||||
EXTRA_DAEMON_OPTS=${HP_OPTS-}
|
||||
# Extra daemon option like: TAUTULLI_OPTS=" --config=/home/Tautulli/config.ini"
|
||||
EXTRA_DAEMON_OPTS=${TAUTULLI_OPTS-}
|
||||
|
||||
# Extra start-stop-daemon option like START_OPTS=" --group=users"
|
||||
EXTRA_SSD_OPTS=${SSD_OPTS-}
|
||||
|
||||
# Hardcoded port to run on, overrides config.ini settings
|
||||
[ -n "$HP_PORT" ] && {
|
||||
PORT_OPTS=" --port=${HP_PORT} "
|
||||
[ -n "$TAUTULLI_PORT" ] && {
|
||||
PORT_OPTS=" --port=${TAUTULLI_PORT} "
|
||||
}
|
||||
|
||||
DAEMON_OPTS=" PlexPy.py --quiet --daemon --nolaunch --pidfile=${PID_FILE} --datadir=${DATA_DIR} ${PORT_OPTS}${EXTRA_DAEMON_OPTS}"
|
||||
DAEMON_OPTS=" Tautulli.py --quiet --daemon --nolaunch --pidfile=${PID_FILE} --datadir=${DATA_DIR} ${PORT_OPTS}${EXTRA_DAEMON_OPTS}"
|
||||
|
||||
SETTINGS_LOADED=TRUE
|
||||
fi
|
||||
@@ -162,7 +162,7 @@ handle_updates () {
|
||||
return 0; }
|
||||
}
|
||||
|
||||
start_plexpy () {
|
||||
start_tautulli () {
|
||||
handle_pid
|
||||
handle_datadir
|
||||
handle_updates
|
||||
@@ -175,7 +175,7 @@ start_plexpy () {
|
||||
fi
|
||||
}
|
||||
|
||||
stop_plexpy () {
|
||||
stop_tautulli () {
|
||||
if is_running; then
|
||||
log_daemon_msg "Stopping $DESC"
|
||||
start-stop-daemon -o --stop --pidfile $PID_FILE --retry 15
|
||||
@@ -187,14 +187,14 @@ stop_plexpy () {
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start_plexpy
|
||||
start_tautulli
|
||||
;;
|
||||
stop)
|
||||
stop_plexpy
|
||||
stop_tautulli
|
||||
;;
|
||||
restart|force-reload)
|
||||
stop_plexpy
|
||||
start_plexpy
|
||||
stop_tautulli
|
||||
start_tautulli
|
||||
;;
|
||||
status)
|
||||
status_of_proc -p "$PID_FILE" "$DAEMON" "$DESC"
|
||||
|
@@ -1,18 +1,18 @@
|
||||
# plexpy
|
||||
# tautulli
|
||||
#
|
||||
# This is a session/user job. Install this file into /usr/share/upstart/sessions
|
||||
# if plexpy is installed system wide, and into $XDG_CONFIG_HOME/upstart if
|
||||
# plexpy is installed per user. Change the executable path appropiately.
|
||||
# if Tautulli is installed system wide, and into $XDG_CONFIG_HOME/upstart if
|
||||
# Tautulli is installed per user. Change the executable path appropiately.
|
||||
|
||||
start on desktop-start
|
||||
stop on desktop-end
|
||||
|
||||
env CONFIG=""$XDG_CONFIG_HOME"/plexpy"
|
||||
env DATA=""$XDG_DATA_HOME"/plexpy"
|
||||
env CONFIG=""$XDG_CONFIG_HOME"/Tautulli"
|
||||
env DATA=""$XDG_DATA_HOME"/Tautulli"
|
||||
|
||||
pre-start script
|
||||
[ -d "$CONFIG" ] || mkdir -p "$CONFIG"
|
||||
[ -d "$DATA" ] || mkdir -p "$DATA"
|
||||
end script
|
||||
|
||||
exec PlexPy.py --nolaunch --config "$CONFIG"/config.ini --datadir "$DATA"
|
||||
exec Tautulli.py --nolaunch --config "$CONFIG"/config.ini --datadir "$DATA"
|
||||
|
@@ -45,7 +45,8 @@ __version__ = version.__version__
|
||||
|
||||
FACEBOOK_GRAPH_URL = "https://graph.facebook.com/"
|
||||
FACEBOOK_OAUTH_DIALOG_URL = "https://www.facebook.com/dialog/oauth?"
|
||||
VALID_API_VERSIONS = ["2.3", "2.4", "2.5", "2.6", "2.7", "2.8", "2.9"]
|
||||
VALID_API_VERSIONS = [
|
||||
"2.5", "2.6", "2.7", "2.8", "2.9", "2.10", "2.11", "2.12"]
|
||||
VALID_SEARCH_TYPES = ["page", "event", "group", "place", "placetopic", "user"]
|
||||
|
||||
|
||||
@@ -89,7 +90,7 @@ class GraphAPI(object):
|
||||
self.session = session or requests.Session()
|
||||
|
||||
if version:
|
||||
version_regex = re.compile("^\d\.\d$")
|
||||
version_regex = re.compile("^\d\.\d{1,2}$")
|
||||
match = version_regex.search(str(version))
|
||||
if match is not None:
|
||||
if str(version) not in VALID_API_VERSIONS:
|
||||
@@ -229,7 +230,7 @@ class GraphAPI(object):
|
||||
try:
|
||||
headers = response.headers
|
||||
version = headers["facebook-api-version"].replace("v", "")
|
||||
return float(version)
|
||||
return str(version)
|
||||
except Exception:
|
||||
raise GraphAPIError("API version number not available")
|
||||
|
||||
@@ -369,24 +370,24 @@ class GraphAPIError(Exception):
|
||||
self.code = None
|
||||
try:
|
||||
self.type = result["error_code"]
|
||||
except:
|
||||
except (KeyError, TypeError):
|
||||
self.type = ""
|
||||
|
||||
# OAuth 2.0 Draft 10
|
||||
try:
|
||||
self.message = result["error_description"]
|
||||
except:
|
||||
except (KeyError, TypeError):
|
||||
# OAuth 2.0 Draft 00
|
||||
try:
|
||||
self.message = result["error"]["message"]
|
||||
self.code = result["error"].get("code")
|
||||
if not self.type:
|
||||
self.type = result["error"].get("type", "")
|
||||
except:
|
||||
except (KeyError, TypeError):
|
||||
# REST server style
|
||||
try:
|
||||
self.message = result["error_msg"]
|
||||
except:
|
||||
except (KeyError, TypeError):
|
||||
self.message = result
|
||||
|
||||
Exception.__init__(self, self.message)
|
||||
|
@@ -46,6 +46,7 @@ import notifiers
|
||||
import plextv
|
||||
import users
|
||||
import versioncheck
|
||||
import web_socket
|
||||
import plexpy.config
|
||||
|
||||
PROG_DIR = None
|
||||
@@ -95,6 +96,7 @@ HTTP_ROOT = None
|
||||
|
||||
DEV = False
|
||||
|
||||
WEBSOCKET = None
|
||||
WS_CONNECTED = False
|
||||
PLEX_SERVER_UP = None
|
||||
|
||||
@@ -240,7 +242,7 @@ def initialize(config_file):
|
||||
|
||||
# Get the previous release from the file
|
||||
release_file = os.path.join(DATA_DIR, "release.lock")
|
||||
PREV_RELEASE = common.VERSION_NUMBER
|
||||
PREV_RELEASE = common.RELEASE
|
||||
if os.path.isfile(release_file):
|
||||
try:
|
||||
with open(release_file, "r") as fp:
|
||||
@@ -252,7 +254,7 @@ def initialize(config_file):
|
||||
PREV_RELEASE = 'v1.4.25'
|
||||
|
||||
# Check if the release was updated
|
||||
if common.VERSION_NUMBER != PREV_RELEASE:
|
||||
if common.RELEASE != PREV_RELEASE:
|
||||
CONFIG.UPDATE_SHOW_CHANGELOG = 1
|
||||
CONFIG.write()
|
||||
_UPDATE = True
|
||||
@@ -260,7 +262,7 @@ def initialize(config_file):
|
||||
# Write current release version to file for update checking
|
||||
try:
|
||||
with open(release_file, "w") as fp:
|
||||
fp.write(common.VERSION_NUMBER)
|
||||
fp.write(common.RELEASE)
|
||||
except IOError as e:
|
||||
logger.error(u"Unable to write current release to file '%s': %s" %
|
||||
(release_file, e))
|
||||
@@ -1621,8 +1623,15 @@ def upgrade():
|
||||
|
||||
def shutdown(restart=False, update=False, checkout=False):
|
||||
cherrypy.engine.exit()
|
||||
SCHED.shutdown(wait=False)
|
||||
activity_handler.ACTIVITY_SCHED.shutdown(wait=False)
|
||||
|
||||
# Shutdown the websocket connection
|
||||
if WEBSOCKET:
|
||||
web_socket.shutdown()
|
||||
|
||||
if SCHED.running:
|
||||
SCHED.shutdown(wait=False)
|
||||
if activity_handler.ACTIVITY_SCHED.running:
|
||||
activity_handler.ACTIVITY_SCHED.shutdown(wait=False)
|
||||
|
||||
# Stop the notification threads
|
||||
for i in range(CONFIG.NOTIFICATION_THREADS):
|
||||
@@ -1693,7 +1702,7 @@ def initialize_tracker():
|
||||
data = {
|
||||
'dataSource': 'server',
|
||||
'appName': 'Tautulli',
|
||||
'appVersion': common.VERSION_NUMBER,
|
||||
'appVersion': common.RELEASE,
|
||||
'appId': plexpy.INSTALL_TYPE,
|
||||
'appInstallerId': plexpy.CONFIG.GIT_BRANCH,
|
||||
'dimension1': '{} {}'.format(common.PLATFORM, common.PLATFORM_VERSION), # App Platform
|
||||
@@ -1702,7 +1711,8 @@ def initialize_tracker():
|
||||
'noninteractive': True
|
||||
}
|
||||
|
||||
tracker = Tracker.create('UA-111522699-2', client_id=CONFIG.PMS_UUID, hash_client_id=True)
|
||||
tracker = Tracker.create('UA-111522699-2', client_id=CONFIG.PMS_UUID, hash_client_id=True,
|
||||
user_agent=common.USER_AGENT)
|
||||
tracker.set(data)
|
||||
|
||||
return tracker
|
||||
@@ -1721,4 +1731,7 @@ def analytics_event(category, action, label=None, value=None, **kwargs):
|
||||
data.update(kwargs)
|
||||
|
||||
if TRACKER:
|
||||
TRACKER.send('event', data)
|
||||
try:
|
||||
TRACKER.send('event', data)
|
||||
except Exception as e:
|
||||
logger.warn(u"Failed to send analytics event for category '%s', action '%s': %s" % (category, action, e))
|
||||
|
@@ -74,9 +74,22 @@ class ActivityHandler(object):
|
||||
return None
|
||||
|
||||
def update_db_session(self, session=None):
|
||||
# Update our session temp table values
|
||||
monitor_proc = activity_processor.ActivityProcessor()
|
||||
monitor_proc.write_session(session=session, notify=False)
|
||||
if session is None:
|
||||
session = self.get_live_session()
|
||||
|
||||
if session:
|
||||
# Update our session temp table values
|
||||
ap = activity_processor.ActivityProcessor()
|
||||
ap.write_session(session=session, notify=False)
|
||||
|
||||
self.set_session_state()
|
||||
|
||||
def set_session_state(self):
|
||||
ap = activity_processor.ActivityProcessor()
|
||||
ap.set_session_state(session_key=self.get_session_key(),
|
||||
state=self.timeline['state'],
|
||||
view_offset=self.timeline['viewOffset'],
|
||||
stopped=int(time.time()))
|
||||
|
||||
def on_start(self):
|
||||
if self.is_valid_session():
|
||||
@@ -114,10 +127,7 @@ class ActivityHandler(object):
|
||||
# Update the session state and viewOffset
|
||||
# Set force_stop to true to disable the state set
|
||||
if not force_stop:
|
||||
ap.set_session_state(session_key=self.get_session_key(),
|
||||
state=self.timeline['state'],
|
||||
view_offset=self.timeline['viewOffset'],
|
||||
stopped=int(time.time()))
|
||||
self.set_session_state()
|
||||
|
||||
# Retrieve the session data from our temp table
|
||||
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
||||
@@ -150,10 +160,7 @@ class ActivityHandler(object):
|
||||
ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=int(time.time()))
|
||||
|
||||
# Update the session state and viewOffset
|
||||
ap.set_session_state(session_key=self.get_session_key(),
|
||||
state=self.timeline['state'],
|
||||
view_offset=self.timeline['viewOffset'],
|
||||
stopped=int(time.time()))
|
||||
self.update_db_session()
|
||||
|
||||
# Retrieve the session data from our temp table
|
||||
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
||||
@@ -170,10 +177,7 @@ class ActivityHandler(object):
|
||||
ap.set_session_last_paused(session_key=self.get_session_key(), timestamp=None)
|
||||
|
||||
# Update the session state and viewOffset
|
||||
ap.set_session_state(session_key=self.get_session_key(),
|
||||
state=self.timeline['state'],
|
||||
view_offset=self.timeline['viewOffset'],
|
||||
stopped=int(time.time()))
|
||||
self.update_db_session()
|
||||
|
||||
# Retrieve the session data from our temp table
|
||||
db_session = ap.get_session_by_key(session_key=self.get_session_key())
|
||||
@@ -198,10 +202,7 @@ class ActivityHandler(object):
|
||||
buffer_last_triggered = ap.get_session_buffer_trigger_time(self.get_session_key())
|
||||
|
||||
# Update the session state and viewOffset
|
||||
ap.set_session_state(session_key=self.get_session_key(),
|
||||
state=self.timeline['state'],
|
||||
view_offset=self.timeline['viewOffset'],
|
||||
stopped=int(time.time()))
|
||||
self.update_db_session()
|
||||
|
||||
time_since_last_trigger = 0
|
||||
if buffer_last_triggered:
|
||||
@@ -243,9 +244,7 @@ class ActivityHandler(object):
|
||||
# Update the session in our temp session table
|
||||
# if the last set temporary stopped time exceeds 15 seconds
|
||||
if int(time.time()) - db_session['stopped'] > 60:
|
||||
session = self.get_live_session()
|
||||
if session:
|
||||
self.update_db_session(session=session)
|
||||
self.update_db_session()
|
||||
|
||||
# Start our state checks
|
||||
if this_state != last_state:
|
||||
|
@@ -167,8 +167,8 @@ class API2:
|
||||
"""
|
||||
logfile = os.path.join(plexpy.CONFIG.LOG_DIR, logger.FILENAME)
|
||||
templog = []
|
||||
start = int(kwargs.get('start', 0))
|
||||
end = int(kwargs.get('end', 0))
|
||||
start = int(start)
|
||||
end = int(end)
|
||||
|
||||
if regex:
|
||||
logger.api_debug(u'Tautulli APIv2 :: Filtering log using regex %s' % regex)
|
||||
@@ -652,10 +652,9 @@ General optional parameters:
|
||||
# {result: error, message: 'Some shit happend'}
|
||||
if isinstance(ret, dict):
|
||||
if ret.get('message'):
|
||||
self._api_msg = ret.get('message', {})
|
||||
ret = {}
|
||||
self._api_msg = ret.pop('message', None)
|
||||
|
||||
if ret.get('result'):
|
||||
self._api_result_type = ret.get('result')
|
||||
self._api_result_type = ret.pop('result', None)
|
||||
|
||||
return self._api_out_as(self._api_responds(result_type=self._api_result_type, msg=self._api_msg, data=ret))
|
||||
|
@@ -19,13 +19,12 @@ from collections import OrderedDict
|
||||
import version
|
||||
|
||||
# Identify Our Application
|
||||
USER_AGENT = 'Tautulli/-' + version.PLEXPY_BRANCH + ' v' + version.PLEXPY_RELEASE_VERSION + ' (' + platform.system() + \
|
||||
' ' + platform.release() + ')'
|
||||
|
||||
PLATFORM = platform.system()
|
||||
PLATFORM_VERSION = platform.release()
|
||||
BRANCH = version.PLEXPY_BRANCH
|
||||
VERSION_NUMBER = version.PLEXPY_RELEASE_VERSION
|
||||
RELEASE = version.PLEXPY_RELEASE_VERSION
|
||||
|
||||
USER_AGENT = 'Tautulli/{} ({} {})'.format(RELEASE, PLATFORM, PLATFORM_VERSION)
|
||||
|
||||
DEFAULT_USER_THUMB = "interfaces/default/images/gravatar-default-80x80.png"
|
||||
DEFAULT_POSTER_THUMB = "interfaces/default/images/poster.png"
|
||||
@@ -392,6 +391,10 @@ NOTIFICATION_PARAMETERS = [
|
||||
{'name': 'Episode Number 00', 'type': 'int', 'value': 'episode_num00', 'description': 'The two digit episode number.', 'example': 'e.g. 06, or 06-10'},
|
||||
{'name': 'Track Number', 'type': 'int', 'value': 'track_num', 'description': 'The track number.', 'example': 'e.g. 4, or 4-10'},
|
||||
{'name': 'Track Number 00', 'type': 'int', 'value': 'track_num00', 'description': 'The two digit track number.', 'example': 'e.g. 04, or 04-10'},
|
||||
{'name': 'Season Count', 'type': 'int', 'value': 'season_count', 'description': 'The number of seasons.'},
|
||||
{'name': 'Episode Count', 'type': 'int', 'value': 'episode_count', 'description': 'The number of episodes.'},
|
||||
{'name': 'Album Count', 'type': 'int', 'value': 'album_count', 'description': 'The number of albums.'},
|
||||
{'name': 'Track Count', 'type': 'int', 'value': 'track_count', 'description': 'The number of tracks.'},
|
||||
{'name': 'Year', 'type': 'int', 'value': 'year', 'description': 'The release year for the item.'},
|
||||
{'name': 'Release Date', 'type': 'int', 'value': 'release_date', 'description': 'The release date (in date format) for the item.'},
|
||||
{'name': 'Air Date', 'type': 'str', 'value': 'air_date', 'description': 'The air date (in date format) for the item.'},
|
||||
|
@@ -23,7 +23,7 @@ import time
|
||||
import plexpy
|
||||
import logger
|
||||
|
||||
FILENAME = "plexpy.db"
|
||||
FILENAME = "tautulli.db"
|
||||
db_lock = threading.Lock()
|
||||
|
||||
|
||||
@@ -63,9 +63,9 @@ def make_backup(cleanup=False, scheduler=False):
|
||||
""" Makes a backup of db, removes all but the last 5 backups """
|
||||
|
||||
if scheduler:
|
||||
backup_file = 'plexpy.backup-%s.sched.db' % arrow.now().format('YYYYMMDDHHmmss')
|
||||
backup_file = 'tautulli.backup-%s.sched.db' % arrow.now().format('YYYYMMDDHHmmss')
|
||||
else:
|
||||
backup_file = 'plexpy.backup-%s.db' % arrow.now().format('YYYYMMDDHHmmss')
|
||||
backup_file = 'tautulli.backup-%s.db' % arrow.now().format('YYYYMMDDHHmmss')
|
||||
backup_folder = plexpy.CONFIG.BACKUP_DIR
|
||||
backup_file_fp = os.path.join(backup_folder, backup_file)
|
||||
|
||||
|
@@ -646,7 +646,7 @@ def whois_lookup(ip_address):
|
||||
countries = ipwhois.utils.get_countries()
|
||||
nets = whois['nets']
|
||||
for net in nets:
|
||||
net['country'] = countries[net['country']]
|
||||
net['country'] = countries.get(net['country'])
|
||||
if net['postal_code']:
|
||||
net['postal_code'] = net['postal_code'].replace('-', ' ')
|
||||
except ValueError as e:
|
||||
@@ -933,3 +933,36 @@ def eval_logic_groups_to_bool(logic_groups, eval_conds):
|
||||
result = result or eval_cond
|
||||
|
||||
return result
|
||||
|
||||
def get_plexpy_url(hostname=None):
|
||||
if plexpy.CONFIG.ENABLE_HTTPS:
|
||||
scheme = 'https'
|
||||
else:
|
||||
scheme = 'http'
|
||||
|
||||
if hostname is None and plexpy.CONFIG.HTTP_HOST == '0.0.0.0':
|
||||
import socket
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
s.connect(('<broadcast>', 0))
|
||||
hostname = s.getsockname()[0]
|
||||
except socket.error:
|
||||
hostname = socket.gethostbyname(socket.gethostname())
|
||||
|
||||
if not hostname:
|
||||
hostname = 'localhost'
|
||||
else:
|
||||
hostname = hostname or plexpy.CONFIG.HTTP_HOST
|
||||
|
||||
if plexpy.CONFIG.HTTP_PORT not in (80, 443):
|
||||
port = ':' + str(plexpy.CONFIG.HTTP_PORT)
|
||||
else:
|
||||
port = ''
|
||||
|
||||
if plexpy.CONFIG.HTTP_ROOT.strip('/'):
|
||||
root = '/' + plexpy.CONFIG.HTTP_ROOT.strip('/')
|
||||
else:
|
||||
root = ''
|
||||
|
||||
return scheme + '://' + hostname + port + root
|
@@ -41,7 +41,7 @@ class HTTPHandler(object):
|
||||
|
||||
self.headers = {'X-Plex-Device-Name': 'Tautulli',
|
||||
'X-Plex-Product': 'Tautulli',
|
||||
'X-Plex-Version': plexpy.common.VERSION_NUMBER,
|
||||
'X-Plex-Version': plexpy.common.RELEASE,
|
||||
'X-Plex-Platform': plexpy.common.PLATFORM,
|
||||
'X-Plex-Platform-Version': plexpy.common.PLATFORM_VERSION,
|
||||
'X-Plex-Client-Identifier': plexpy.CONFIG.PMS_UUID,
|
||||
|
@@ -125,8 +125,8 @@ def update_section_ids():
|
||||
library_children = pms_connect.get_library_children_details(section_id=section_id,
|
||||
section_type=section_type)
|
||||
if library_children:
|
||||
children_list = library_children['childern_list']
|
||||
key_mappings.update({child['rating_key']:child['section_id'] for child in children_list})
|
||||
children_list = library_children['children_list']
|
||||
key_mappings.update({child['rating_key']: child['section_id'] for child in children_list})
|
||||
else:
|
||||
logger.warn(u"Tautulli Libraries :: Unable to get a list of library items for section_id %s." % section_id)
|
||||
|
||||
@@ -198,7 +198,7 @@ def update_labels():
|
||||
label_key=label['label_key'])
|
||||
|
||||
if library_children:
|
||||
children_list = library_children['childern_list']
|
||||
children_list = library_children['children_list']
|
||||
# rating_key_list = [child['rating_key'] for child in children_list]
|
||||
|
||||
for rating_key in [child['rating_key'] for child in children_list]:
|
||||
@@ -456,7 +456,7 @@ class Libraries(object):
|
||||
get_media_info=True)
|
||||
if library_children:
|
||||
library_count = library_children['library_count']
|
||||
children_list = library_children['childern_list']
|
||||
children_list = library_children['children_list']
|
||||
else:
|
||||
logger.warn(u"Tautulli Libraries :: Unable to get a list of library items.")
|
||||
return default_return
|
||||
@@ -744,7 +744,7 @@ class Libraries(object):
|
||||
logger.warn(u"Tautulli Libraries :: Unable to retrieve library %s from database. Requesting library list refresh."
|
||||
% section_id)
|
||||
# Let's first refresh the libraries list to make sure the library isn't newly added and not in the db yet
|
||||
pmsconnect.refresh_libraries()
|
||||
refresh_libraries()
|
||||
|
||||
library_details = get_library_details(section_id=section_id)
|
||||
|
||||
|
@@ -30,8 +30,8 @@ import plexpy
|
||||
import helpers
|
||||
|
||||
# These settings are for file logging only
|
||||
FILENAME = "plexpy.log"
|
||||
FILENAME_API = "plexpy_api.log"
|
||||
FILENAME = "tautulli.log"
|
||||
FILENAME_API = "tautulli_api.log"
|
||||
FILENAME_PLEX_WEBSOCKET = "plex_websocket.log"
|
||||
MAX_SIZE = 5000000 # 5 MB
|
||||
MAX_FILES = 5
|
||||
@@ -39,9 +39,9 @@ MAX_FILES = 5
|
||||
_BLACKLIST_WORDS = set()
|
||||
|
||||
# Tautulli logger
|
||||
logger = logging.getLogger("plexpy")
|
||||
logger = logging.getLogger("tautulli")
|
||||
# Tautulli API logger
|
||||
logger_api = logging.getLogger("plexpy_api")
|
||||
logger_api = logging.getLogger("tautulli_api")
|
||||
# Tautulli websocket logger
|
||||
logger_plex_websocket = logging.getLogger("plex_websocket")
|
||||
|
||||
@@ -178,9 +178,9 @@ def initMultiprocessing():
|
||||
def initLogger(console=False, log_dir=False, verbose=False):
|
||||
"""
|
||||
Setup logging for Tautulli. It uses the logger instance with the name
|
||||
'plexpy'. Three log handlers are added:
|
||||
'tautulli'. Three log handlers are added:
|
||||
|
||||
* RotatingFileHandler: for the file plexpy.log
|
||||
* RotatingFileHandler: for the file tautulli.log
|
||||
* LogListHandler: for Web UI
|
||||
* StreamHandler: for console (if console)
|
||||
|
||||
|
@@ -208,7 +208,7 @@ def notify_custom_conditions(notifier_id=None, parameters=None):
|
||||
notifier_config = notifiers.get_notifier_config(notifier_id=notifier_id)
|
||||
|
||||
custom_conditions_logic = notifier_config['custom_conditions_logic']
|
||||
custom_conditions = json.loads(notifier_config['custom_conditions']) or []
|
||||
custom_conditions = notifier_config['custom_conditions']
|
||||
|
||||
if custom_conditions_logic or any(c for c in custom_conditions if c['value']):
|
||||
logger.debug(u"Tautulli NotificationHandler :: Checking custom notification conditions for notifier_id %s."
|
||||
@@ -507,9 +507,9 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
||||
user_stream_count = len(user_sessions)
|
||||
|
||||
# Generate a combined transcode decision value
|
||||
if session.get('video_decision','') == 'transcode' or session.get('audio_decision','') == 'transcode':
|
||||
if session.get('stream_video_decision', '') == 'transcode' or session.get('stream_audio_decision', '') == 'transcode':
|
||||
transcode_decision = 'Transcode'
|
||||
elif session.get('video_decision','') == 'copy' or session.get('audio_decision','') == 'copy':
|
||||
elif session.get('stream_video_decision', '') == 'copy' or session.get('stream_audio_decision', '') == 'copy':
|
||||
transcode_decision = 'Direct Stream'
|
||||
else:
|
||||
transcode_decision = 'Direct Play'
|
||||
@@ -640,13 +640,17 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
||||
album_name = ''
|
||||
track_name = ''
|
||||
|
||||
num, num00 = format_group_index([helpers.cast_to_int(d['media_index'])
|
||||
for d in child_metadata if d['parent_rating_key'] == rating_key])
|
||||
child_num = [helpers.cast_to_int(
|
||||
d['media_index']) for d in child_metadata if d['parent_rating_key'] == rating_key]
|
||||
num, num00 = format_group_index(child_num)
|
||||
season_num, season_num00 = num, num00
|
||||
|
||||
episode_num, episode_num00 = '', ''
|
||||
track_num, track_num00 = '', ''
|
||||
|
||||
child_count = len(child_num)
|
||||
grandchild_count = ''
|
||||
|
||||
elif ((manual_trigger or plexpy.CONFIG.NOTIFY_GROUP_RECENTLY_ADDED_PARENT)
|
||||
and notify_params['media_type'] in ('season', 'album')):
|
||||
show_name = notify_params['parent_title']
|
||||
@@ -654,14 +658,19 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
||||
artist_name = notify_params['parent_title']
|
||||
album_name = notify_params['title']
|
||||
track_name = ''
|
||||
|
||||
season_num = str(notify_params['media_index']).zfill(1)
|
||||
season_num00 = str(notify_params['media_index']).zfill(2)
|
||||
|
||||
num, num00 = format_group_index([helpers.cast_to_int(d['media_index'])
|
||||
for d in child_metadata if d['parent_rating_key'] == rating_key])
|
||||
grandchild_num = [helpers.cast_to_int(
|
||||
d['media_index']) for d in child_metadata if d['parent_rating_key'] == rating_key]
|
||||
num, num00 = format_group_index(grandchild_num)
|
||||
episode_num, episode_num00 = num, num00
|
||||
track_num, track_num00 = num, num00
|
||||
|
||||
child_count = 1
|
||||
grandchild_count = len(grandchild_num)
|
||||
|
||||
else:
|
||||
show_name = notify_params['grandparent_title']
|
||||
episode_name = notify_params['title']
|
||||
@@ -674,10 +683,12 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
||||
episode_num00 = str(notify_params['media_index']).zfill(2)
|
||||
track_num = str(notify_params['media_index']).zfill(1)
|
||||
track_num00 = str(notify_params['media_index']).zfill(2)
|
||||
child_count = 1
|
||||
grandchild_count = 1
|
||||
|
||||
available_params = {
|
||||
# Global paramaters
|
||||
'tautulli_version': common.VERSION_NUMBER,
|
||||
'tautulli_version': common.RELEASE,
|
||||
'tautulli_remote': plexpy.CONFIG.GIT_REMOTE,
|
||||
'tautulli_branch': plexpy.CONFIG.GIT_BRANCH,
|
||||
'tautulli_commit': plexpy.CURRENT_VERSION,
|
||||
@@ -783,6 +794,10 @@ def build_media_notify_params(notify_action=None, session=None, timeline=None, m
|
||||
'episode_num00': episode_num00,
|
||||
'track_num': track_num,
|
||||
'track_num00': track_num00,
|
||||
'season_count': child_count,
|
||||
'episode_count': grandchild_count,
|
||||
'album_count': child_count,
|
||||
'track_count': grandchild_count,
|
||||
'year': notify_params['year'],
|
||||
'release_date': arrow.get(notify_params['originally_available_at']).format(date_format)
|
||||
if notify_params['originally_available_at'] else '',
|
||||
@@ -877,7 +892,7 @@ def build_server_notify_params(notify_action=None, **kwargs):
|
||||
|
||||
available_params = {
|
||||
# Global paramaters
|
||||
'tautulli_version': common.VERSION_NUMBER,
|
||||
'tautulli_version': common.RELEASE,
|
||||
'tautulli_remote': plexpy.CONFIG.GIT_REMOTE,
|
||||
'tautulli_branch': plexpy.CONFIG.GIT_BRANCH,
|
||||
'tautulli_commit': plexpy.CURRENT_VERSION,
|
||||
|
@@ -54,6 +54,7 @@ import twitter
|
||||
import pynma
|
||||
|
||||
import plexpy
|
||||
import common
|
||||
import database
|
||||
import helpers
|
||||
import logger
|
||||
@@ -94,6 +95,8 @@ AGENT_IDS = {'growl': 0,
|
||||
'zapier': 24
|
||||
}
|
||||
|
||||
DEFAULT_CUSTOM_CONDITIONS = [{'parameter': '', 'operator': '', 'value': ''}]
|
||||
|
||||
|
||||
def available_notification_agents():
|
||||
agents = [{'label': 'Tautulli Remote Android App',
|
||||
@@ -446,7 +449,6 @@ def get_notifier_config(notifier_id=None):
|
||||
db = database.MonitorDatabase()
|
||||
result = db.select_single('SELECT * FROM notifiers WHERE id = ?',
|
||||
args=[notifier_id])
|
||||
|
||||
if not result:
|
||||
return None
|
||||
|
||||
@@ -468,6 +470,14 @@ def get_notifier_config(notifier_id=None):
|
||||
notifier_text[k] = {'subject': result.pop(k + '_subject'),
|
||||
'body': result.pop(k + '_body')}
|
||||
|
||||
try:
|
||||
result['custom_conditions'] = json.loads(result['custom_conditions'])
|
||||
except (ValueError, TypeError):
|
||||
result['custom_conditions'] = DEFAULT_CUSTOM_CONDITIONS
|
||||
|
||||
if not result['custom_conditions_logic']:
|
||||
result['custom_conditions_logic'] = ''
|
||||
|
||||
result['config'] = config
|
||||
result['config_options'] = notifier_config
|
||||
result['actions'] = notifier_actions
|
||||
@@ -494,7 +504,9 @@ def add_notifier_config(agent_id=None, **kwargs):
|
||||
'agent_name': agent['name'],
|
||||
'agent_label': agent['label'],
|
||||
'friendly_name': '',
|
||||
'notifier_config': json.dumps(get_agent_class(agent_id=agent['id']).config)
|
||||
'notifier_config': json.dumps(get_agent_class(agent_id=agent['id']).config),
|
||||
'custom_conditions': json.dumps(DEFAULT_CUSTOM_CONDITIONS),
|
||||
'custom_conditions_logic': ''
|
||||
}
|
||||
if agent['name'] == 'scripts':
|
||||
for a in available_notification_actions():
|
||||
@@ -549,7 +561,7 @@ def set_notifier_config(notifier_id=None, agent_id=None, **kwargs):
|
||||
'agent_label': agent['label'],
|
||||
'friendly_name': kwargs.get('friendly_name', ''),
|
||||
'notifier_config': json.dumps(notifier_config),
|
||||
'custom_conditions': kwargs.get('custom_conditions', ''),
|
||||
'custom_conditions': kwargs.get('custom_conditions', json.dumps(DEFAULT_CUSTOM_CONDITIONS)),
|
||||
'custom_conditions_logic': kwargs.get('custom_conditions_logic', ''),
|
||||
}
|
||||
values.update(actions)
|
||||
@@ -718,6 +730,13 @@ class PrettyMetadata(object):
|
||||
def get_plex_url(self):
|
||||
return self.parameters['plex_url']
|
||||
|
||||
@staticmethod
|
||||
def get_parameters():
|
||||
parameters = {param['value']: param['name']
|
||||
for category in common.NOTIFICATION_PARAMETERS for param in category['parameters']}
|
||||
parameters[''] = ''
|
||||
return parameters
|
||||
|
||||
|
||||
class Notifier(object):
|
||||
NAME = ''
|
||||
@@ -1290,7 +1309,7 @@ class EMAIL(Notifier):
|
||||
mailserver.ehlo()
|
||||
|
||||
if self.config['smtp_user']:
|
||||
mailserver.login(self.config['smtp_user'], self.config['smtp_password'])
|
||||
mailserver.login(str(self.config['smtp_user']), str(self.config['smtp_password']))
|
||||
|
||||
mailserver.sendmail(self.config['from'], recipients, msg.as_string())
|
||||
mailserver.quit()
|
||||
@@ -1434,7 +1453,7 @@ class FACEBOOK(Notifier):
|
||||
|
||||
try:
|
||||
# Request user access token
|
||||
api = facebook.GraphAPI(version='2.5')
|
||||
api = facebook.GraphAPI(version='2.12')
|
||||
response = api.get_access_token_from_code(code=code,
|
||||
redirect_uri=redirect_uri + '/facebookStep2',
|
||||
app_id=app_id,
|
||||
@@ -1442,7 +1461,7 @@ class FACEBOOK(Notifier):
|
||||
access_token = response['access_token']
|
||||
|
||||
# Request extended user access token
|
||||
api = facebook.GraphAPI(access_token=access_token, version='2.5')
|
||||
api = facebook.GraphAPI(access_token=access_token, version='2.12')
|
||||
response = api.extend_access_token(app_id=app_id,
|
||||
app_secret=app_secret)
|
||||
|
||||
@@ -1460,7 +1479,7 @@ class FACEBOOK(Notifier):
|
||||
|
||||
def _post_facebook(self, **data):
|
||||
if self.config['group_id']:
|
||||
api = facebook.GraphAPI(access_token=self.config['access_token'], version='2.5')
|
||||
api = facebook.GraphAPI(access_token=self.config['access_token'], version='2.12')
|
||||
|
||||
try:
|
||||
api.put_object(parent_object=self.config['group_id'], connection_name='feed', **data)
|
||||
@@ -1932,7 +1951,8 @@ class IFTTT(Notifier):
|
||||
"""
|
||||
NAME = 'IFTTT'
|
||||
_DEFAULT_CONFIG = {'key': '',
|
||||
'event': 'plexpy'
|
||||
'event': 'tautulli',
|
||||
'value3': '',
|
||||
}
|
||||
|
||||
def agent_notify(self, subject='', body='', action='', **kwargs):
|
||||
@@ -1941,6 +1961,10 @@ class IFTTT(Notifier):
|
||||
data = {'value1': subject.encode("utf-8"),
|
||||
'value2': body.encode("utf-8")}
|
||||
|
||||
if self.config['value3']:
|
||||
pretty_metadata = PrettyMetadata(kwargs['parameters'])
|
||||
data['value3'] = pretty_metadata.parameters.get(self.config['value3'], '')
|
||||
|
||||
headers = {'Content-type': 'application/json'}
|
||||
|
||||
return self.make_request('https://maker.ifttt.com/trigger/{}/with/key/{}'.format(event, self.config['key']),
|
||||
@@ -1964,6 +1988,13 @@ class IFTTT(Notifier):
|
||||
' as <span class="inline-pre">value1</span>'
|
||||
' and <span class="inline-pre">value2</span> respectively.',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'Value 3',
|
||||
'value': self.config['value3'],
|
||||
'name': 'ifttt_value3',
|
||||
'description': 'Optional: Select a parameter to send as <span class="inline-pre">value3</span>.',
|
||||
'input_type': 'select',
|
||||
'select_options': PrettyMetadata().get_parameters()
|
||||
}
|
||||
]
|
||||
|
||||
@@ -2135,7 +2166,7 @@ class MQTT(Notifier):
|
||||
'protocol': 'MQTTv311',
|
||||
'username': '',
|
||||
'password': '',
|
||||
'clientid': 'plexpy',
|
||||
'clientid': 'tautulli',
|
||||
'topic': '',
|
||||
'qos': 1,
|
||||
'retain': 0,
|
||||
@@ -2320,7 +2351,7 @@ class OSX(Notifier):
|
||||
self.objc.classAddMethod(cls, SEL, new_IMP)
|
||||
|
||||
def _swizzled_bundleIdentifier(self, original, swizzled):
|
||||
return 'ade.plexpy.osxnotify'
|
||||
return 'ade.tautulli.osxnotify'
|
||||
|
||||
def agent_notify(self, subject='', body='', action='', **kwargs):
|
||||
|
||||
@@ -2691,8 +2722,10 @@ class PUSHOVER(Notifier):
|
||||
_DEFAULT_CONFIG = {'api_token': '',
|
||||
'key': '',
|
||||
'html_support': 1,
|
||||
'priority': 0,
|
||||
'sound': '',
|
||||
'priority': 0,
|
||||
'retry': 30,
|
||||
'expire': 3600,
|
||||
'incl_url': 1,
|
||||
'incl_subject': 1,
|
||||
'incl_poster': 0,
|
||||
@@ -2713,6 +2746,10 @@ class PUSHOVER(Notifier):
|
||||
if self.config['incl_subject']:
|
||||
data['title'] = subject.encode("utf-8")
|
||||
|
||||
if self.config['priority'] == 2:
|
||||
data['retry'] = max(30, self.config['retry'])
|
||||
data['expire'] = max(30, self.config['expire'])
|
||||
|
||||
headers = {'Content-type': 'application/x-www-form-urlencoded'}
|
||||
|
||||
files = {}
|
||||
@@ -2789,6 +2826,13 @@ class PUSHOVER(Notifier):
|
||||
'description': 'Your Pushover user or group key.',
|
||||
'input_type': 'text'
|
||||
},
|
||||
{'label': 'Sound',
|
||||
'value': self.config['sound'],
|
||||
'name': 'pushover_sound',
|
||||
'description': 'Set the notification sound. Leave blank for the default sound.',
|
||||
'input_type': 'select',
|
||||
'select_options': self.get_sounds()
|
||||
},
|
||||
{'label': 'Priority',
|
||||
'value': self.config['priority'],
|
||||
'name': 'pushover_priority',
|
||||
@@ -2796,12 +2840,19 @@ class PUSHOVER(Notifier):
|
||||
'input_type': 'select',
|
||||
'select_options': {-2: -2, -1: -1, 0: 0, 1: 1, 2: 2}
|
||||
},
|
||||
{'label': 'Sound',
|
||||
'value': self.config['sound'],
|
||||
'name': 'pushover_sound',
|
||||
'description': 'Set the notification sound. Leave blank for the default sound.',
|
||||
'input_type': 'select',
|
||||
'select_options': self.get_sounds()
|
||||
{'label': 'Retry Interval',
|
||||
'value': self.config['retry'],
|
||||
'name': 'pushover_retry',
|
||||
'description': 'Set the interval in seconds to keep retrying the notification.<br>'
|
||||
'Note: For priority 2 only. Minimum 30 seconds.',
|
||||
'input_type': 'number'
|
||||
},
|
||||
{'label': 'Expire Duration',
|
||||
'value': self.config['expire'],
|
||||
'name': 'pushover_expire',
|
||||
'description': 'Set the duration in seconds when the notification will stop retrying.<br>'
|
||||
'Note: For priority 2 only. Minimum 30 seconds.',
|
||||
'input_type': 'number'
|
||||
},
|
||||
{'label': 'Enable HTML Support',
|
||||
'value': self.config['html_support'],
|
||||
@@ -2903,6 +2954,13 @@ class SCRIPTS(Notifier):
|
||||
process.kill()
|
||||
self.script_killed = True
|
||||
|
||||
# Common environment variables
|
||||
env = {'PLEX_URL': plexpy.CONFIG.PMS_URL,
|
||||
'PLEX_TOKEN': plexpy.CONFIG.PMS_TOKEN,
|
||||
'TAUTULLI_URL': helpers.get_plexpy_url(hostname='localhost'),
|
||||
'TAUTULLI_APIKEY': plexpy.CONFIG.API_KEY
|
||||
}
|
||||
|
||||
self.script_killed = False
|
||||
output = error = ''
|
||||
try:
|
||||
@@ -2910,7 +2968,8 @@ class SCRIPTS(Notifier):
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
cwd=self.config['script_folder'])
|
||||
cwd=self.config['script_folder'],
|
||||
env=env)
|
||||
|
||||
if self.config['timeout'] > 0:
|
||||
timer = threading.Timer(self.config['timeout'], kill_script, (process,))
|
||||
@@ -2918,11 +2977,13 @@ class SCRIPTS(Notifier):
|
||||
timer = None
|
||||
|
||||
try:
|
||||
if timer: timer.start()
|
||||
if timer:
|
||||
timer.start()
|
||||
output, error = process.communicate()
|
||||
status = process.returncode
|
||||
finally:
|
||||
if timer: timer.cancel()
|
||||
if timer:
|
||||
timer.cancel()
|
||||
|
||||
except OSError as e:
|
||||
logger.error(u"Tautulli Notifiers :: Failed to run script: %s" % e)
|
||||
|
@@ -29,7 +29,7 @@ import pmsconnect
|
||||
import session
|
||||
|
||||
|
||||
def get_server_resources(return_presence=False):
|
||||
def get_server_resources(return_presence=False, return_server=False, **kwargs):
|
||||
if not return_presence:
|
||||
logger.info(u"Tautulli PlexTV :: Requesting resources for server...")
|
||||
|
||||
@@ -42,9 +42,15 @@ def get_server_resources(return_presence=False):
|
||||
'pms_is_remote': plexpy.CONFIG.PMS_IS_REMOTE,
|
||||
'pms_is_cloud': plexpy.CONFIG.PMS_IS_CLOUD,
|
||||
'pms_url': plexpy.CONFIG.PMS_URL,
|
||||
'pms_url_manual': plexpy.CONFIG.PMS_URL_MANUAL
|
||||
'pms_url_manual': plexpy.CONFIG.PMS_URL_MANUAL,
|
||||
'pms_identifier': plexpy.CONFIG.PMS_IDENTIFIER
|
||||
}
|
||||
|
||||
if kwargs:
|
||||
server.update(kwargs)
|
||||
for k in ['pms_ssl', 'pms_is_remote', 'pms_is_cloud', 'pms_url_manual']:
|
||||
server[k] = int(server[k])
|
||||
|
||||
if server['pms_url_manual'] and server['pms_ssl'] or server['pms_is_cloud']:
|
||||
scheme = 'https'
|
||||
else:
|
||||
@@ -55,7 +61,7 @@ def get_server_resources(return_presence=False):
|
||||
port=server['pms_port'])
|
||||
|
||||
plex_tv = PlexTV()
|
||||
result = plex_tv.get_server_connections(pms_identifier=plexpy.CONFIG.PMS_IDENTIFIER,
|
||||
result = plex_tv.get_server_connections(pms_identifier=server['pms_identifier'],
|
||||
pms_ip=server['pms_ip'],
|
||||
pms_port=server['pms_port'],
|
||||
include_https=server['pms_ssl'])
|
||||
@@ -103,6 +109,9 @@ def get_server_resources(return_presence=False):
|
||||
server['pms_url'] = fallback_url
|
||||
logger.info(u"Tautulli PlexTV :: Using user-defined URL.")
|
||||
|
||||
if return_server:
|
||||
return server
|
||||
|
||||
plexpy.CONFIG.process_kwargs(server)
|
||||
plexpy.CONFIG.write()
|
||||
|
||||
@@ -645,6 +654,7 @@ class PlexTV(object):
|
||||
'label': helpers.get_xml_attr(d, 'name'),
|
||||
'ip': helpers.get_xml_attr(c, 'address'),
|
||||
'port': helpers.get_xml_attr(c, 'port'),
|
||||
'uri': helpers.get_xml_attr(c, 'uri'),
|
||||
'local': helpers.get_xml_attr(c, 'local'),
|
||||
'value': helpers.get_xml_attr(c, 'address'),
|
||||
'is_cloud': is_cloud
|
||||
|
@@ -666,6 +666,11 @@ class PmsConnect(object):
|
||||
}
|
||||
|
||||
elif metadata_type == 'show':
|
||||
# Workaround for for duration sometimes reported in minutes for a show
|
||||
duration = helpers.get_xml_attr(metadata_main, 'duration')
|
||||
if duration.isdigit() and int(duration) < 1000:
|
||||
duration = unicode(int(duration) * 60 * 1000)
|
||||
|
||||
metadata = {'media_type': metadata_type,
|
||||
'section_id': section_id,
|
||||
'library_name': library_name,
|
||||
@@ -685,7 +690,7 @@ class PmsConnect(object):
|
||||
'rating': helpers.get_xml_attr(metadata_main, 'rating'),
|
||||
'audience_rating': helpers.get_xml_attr(metadata_main, 'audienceRating'),
|
||||
'user_rating': helpers.get_xml_attr(metadata_main, 'userRating'),
|
||||
'duration': helpers.get_xml_attr(metadata_main, 'duration'),
|
||||
'duration': duration,
|
||||
'year': helpers.get_xml_attr(metadata_main, 'year'),
|
||||
'thumb': helpers.get_xml_attr(metadata_main, 'thumb'),
|
||||
'parent_thumb': helpers.get_xml_attr(metadata_main, 'parentThumb'),
|
||||
@@ -1404,6 +1409,10 @@ class PmsConnect(object):
|
||||
'location': 'wan' if player_details['local'] == '0' else 'lan'
|
||||
}
|
||||
|
||||
# Check if using Plex Relay
|
||||
session_details['relay'] = int(session_details['location'] != 'lan'
|
||||
and player_details['ip_address_public'] == '127.0.0.1')
|
||||
|
||||
# Get the transcode details
|
||||
if session.getElementsByTagName('TranscodeSession'):
|
||||
transcode_info = session.getElementsByTagName('TranscodeSession')[0]
|
||||
@@ -1461,18 +1470,12 @@ class PmsConnect(object):
|
||||
transcode_details['transcode_hw_decoding'] = int(transcode_details['transcode_hw_decode'].lower() in common.HW_DECODERS)
|
||||
transcode_details['transcode_hw_encoding'] = int(transcode_details['transcode_hw_encode'].lower() in common.HW_ENCODERS)
|
||||
|
||||
# Generate a combined transcode decision value
|
||||
if transcode_details['video_decision'] == 'transcode' or transcode_details['audio_decision'] == 'transcode':
|
||||
transcode_decision = 'transcode'
|
||||
elif transcode_details['video_decision'] == 'copy' or transcode_details['audio_decision'] == 'copy':
|
||||
transcode_decision = 'copy'
|
||||
else:
|
||||
transcode_decision = 'direct play'
|
||||
|
||||
# Determine if a synced version is being played
|
||||
sync_id = None
|
||||
if media_type not in ('photo', 'clip') and not session.getElementsByTagName('Session') \
|
||||
and helpers.get_xml_attr(session, 'ratingKey').isdigit() and transcode_decision == 'direct play':
|
||||
if media_type not in ('photo', 'clip') \
|
||||
and not session.getElementsByTagName('Session') \
|
||||
and not session.getElementsByTagName('TranscodeSession') \
|
||||
and helpers.get_xml_attr(session, 'ratingKey').isdigit():
|
||||
plex_tv = plextv.PlexTV()
|
||||
parent_rating_key = helpers.get_xml_attr(session, 'parentRatingKey')
|
||||
grandparent_rating_key = helpers.get_xml_attr(session, 'grandparentRatingKey')
|
||||
@@ -1578,6 +1581,14 @@ class PmsConnect(object):
|
||||
'stream_subtitle_decision': ''
|
||||
}
|
||||
|
||||
# Generate a combined transcode decision value
|
||||
if video_details['stream_video_decision'] == 'transcode' or audio_details['stream_audio_decision'] == 'transcode':
|
||||
transcode_decision = 'transcode'
|
||||
elif video_details['stream_video_decision'] == 'copy' or audio_details['stream_audio_decision'] == 'copy':
|
||||
transcode_decision = 'copy'
|
||||
else:
|
||||
transcode_decision = 'direct play'
|
||||
|
||||
# Get the bif thumbnail
|
||||
indexes = helpers.get_xml_attr(stream_media_parts_info, 'indexes')
|
||||
view_offset = helpers.get_xml_attr(session, 'viewOffset')
|
||||
@@ -2134,10 +2145,12 @@ class PmsConnect(object):
|
||||
sort_type = '&type=10'
|
||||
elif section_type == 'photo':
|
||||
sort_type = ''
|
||||
elif section_type == 'photoAlbum':
|
||||
elif section_type == 'photo_album':
|
||||
sort_type = '&type=14'
|
||||
elif section_type == 'picture':
|
||||
sort_type = '&type=13'
|
||||
sort_type = '&type=13&clusterZoomLevel=1'
|
||||
elif section_type == 'clip':
|
||||
sort_type = '&type=12&clusterZoomLevel=1'
|
||||
else:
|
||||
sort_type = ''
|
||||
|
||||
@@ -2155,16 +2168,16 @@ class PmsConnect(object):
|
||||
logger.warn(u"Tautulli Pmsconnect :: Unable to parse XML for get_library_children_details: %s." % e)
|
||||
return []
|
||||
|
||||
childern_list = []
|
||||
children_list = []
|
||||
|
||||
for a in xml_head:
|
||||
if a.getAttribute('size'):
|
||||
if a.getAttribute('size') == '0':
|
||||
logger.debug(u"Tautulli Pmsconnect :: No library data.")
|
||||
childern_list = {'library_count': '0',
|
||||
'childern_list': []
|
||||
children_list = {'library_count': '0',
|
||||
'children_list': []
|
||||
}
|
||||
return childern_list
|
||||
return children_list
|
||||
|
||||
if rating_key:
|
||||
library_count = helpers.get_xml_attr(xml_head[0], 'size')
|
||||
@@ -2222,10 +2235,10 @@ class PmsConnect(object):
|
||||
}
|
||||
item_info.update(media_info)
|
||||
|
||||
childern_list.append(item_info)
|
||||
children_list.append(item_info)
|
||||
|
||||
output = {'library_count': library_count,
|
||||
'childern_list': childern_list
|
||||
'children_list': children_list
|
||||
}
|
||||
|
||||
return output
|
||||
@@ -2280,12 +2293,12 @@ class PmsConnect(object):
|
||||
library_stats.update(child_stats)
|
||||
|
||||
if section_type == 'photo':
|
||||
parent_list = self.get_library_children_details(section_id=section_id, section_type='photoAlbum', count='1')
|
||||
parent_list = self.get_library_children_details(section_id=section_id, section_type='picture', count='1')
|
||||
if parent_list:
|
||||
parent_stats = {'parent_count': parent_list['library_count']}
|
||||
library_stats.update(parent_stats)
|
||||
|
||||
child_list = self.get_library_children_details(section_id=section_id, section_type='picture', count='1')
|
||||
child_list = self.get_library_children_details(section_id=section_id, section_type='clip', count='1')
|
||||
if child_list:
|
||||
child_stats = {'child_count': child_list['library_count']}
|
||||
library_stats.update(child_stats)
|
||||
|
@@ -38,14 +38,14 @@ def get_session_user():
|
||||
Returns the user_id for the current logged in session
|
||||
"""
|
||||
_session = get_session_info()
|
||||
return _session['user'] if _session and _session['user'] else None
|
||||
return _session['user'] if _session['user_group'] == 'guest' and _session['user'] else None
|
||||
|
||||
def get_session_user_id():
|
||||
"""
|
||||
Returns the user_id for the current logged in session
|
||||
"""
|
||||
_session = get_session_info()
|
||||
return str(_session['user_id']) if _session and _session['user_id'] else None
|
||||
return str(_session['user_id']) if _session['user_group'] == 'guest' and _session['user_id'] else None
|
||||
|
||||
def get_session_shared_libraries():
|
||||
"""
|
||||
@@ -79,7 +79,7 @@ def get_session_library_filters_type(filters, media_type=None):
|
||||
filters = filters.get('filter_tv', ())
|
||||
elif media_type == 'artist' or media_type == 'album' or media_type == 'track':
|
||||
filters = filters.get('filter_music', ())
|
||||
elif media_type == 'photo' or media_type == 'photoAlbum' or media_type == 'picture':
|
||||
elif media_type == 'photo' or media_type == 'photo_album' or media_type == 'picture' or media_type == 'clip':
|
||||
filters = filters.get('filter_photos', ())
|
||||
else:
|
||||
filters = filters.get('filter_all', ())
|
||||
|
@@ -1,2 +1,2 @@
|
||||
PLEXPY_BRANCH = "beta"
|
||||
PLEXPY_RELEASE_VERSION = "v2.0.20-beta"
|
||||
PLEXPY_RELEASE_VERSION = "v2.0.23-beta"
|
||||
|
@@ -20,9 +20,9 @@ import subprocess
|
||||
import tarfile
|
||||
|
||||
import plexpy
|
||||
import common
|
||||
import logger
|
||||
import request
|
||||
import version
|
||||
|
||||
|
||||
def runGit(args):
|
||||
@@ -65,7 +65,7 @@ def runGit(args):
|
||||
|
||||
def getVersion():
|
||||
|
||||
if version.PLEXPY_BRANCH.startswith('win32build'):
|
||||
if common.BRANCH.startswith('win32build'):
|
||||
plexpy.INSTALL_TYPE = 'win'
|
||||
|
||||
# Don't have a way to update exe yet, but don't want to set VERSION to None
|
||||
@@ -120,15 +120,15 @@ def getVersion():
|
||||
version_file = os.path.join(plexpy.PROG_DIR, 'version.txt')
|
||||
|
||||
if not os.path.isfile(version_file):
|
||||
return None, 'origin', 'master'
|
||||
return None, 'origin', common.BRANCH
|
||||
|
||||
with open(version_file, 'r') as f:
|
||||
current_version = f.read().strip(' \n\r')
|
||||
|
||||
if current_version:
|
||||
return current_version, plexpy.CONFIG.GIT_REMOTE, plexpy.CONFIG.GIT_BRANCH
|
||||
return current_version, 'origin', common.BRANCH
|
||||
else:
|
||||
return None, 'origin', 'master'
|
||||
return None, 'origin', common.BRANCH
|
||||
|
||||
|
||||
def checkGithub(auto_update=False):
|
||||
@@ -190,9 +190,9 @@ def checkGithub(auto_update=False):
|
||||
if plexpy.CONFIG.GIT_BRANCH == 'master':
|
||||
release = next((r for r in releases if not r['prerelease']), releases[0])
|
||||
elif plexpy.CONFIG.GIT_BRANCH == 'beta':
|
||||
release = next((r for r in releases if r['prerelease'] and '-beta' in r['tag_name']), releases[0])
|
||||
release = next((r for r in releases if not r['tag_name'].endswith('-nightly')), releases[0])
|
||||
elif plexpy.CONFIG.GIT_BRANCH == 'nightly':
|
||||
release = next((r for r in releases if r['prerelease'] and '-nightly' in r['tag_name']), releases[0])
|
||||
release = next((r for r in releases), releases[0])
|
||||
else:
|
||||
release = releases[0]
|
||||
|
||||
@@ -292,8 +292,8 @@ def update():
|
||||
|
||||
def checkout_git_branch():
|
||||
if plexpy.INSTALL_TYPE == 'git':
|
||||
output, err = runGit('fetch ' + plexpy.CONFIG.GIT_REMOTE)
|
||||
output, err = runGit('checkout ' + plexpy.CONFIG.GIT_BRANCH)
|
||||
output, err = runGit('fetch %s' % plexpy.CONFIG.GIT_REMOTE)
|
||||
output, err = runGit('checkout %s' % plexpy.CONFIG.GIT_BRANCH)
|
||||
|
||||
if not output:
|
||||
logger.error('Unable to change git branch.')
|
||||
@@ -304,6 +304,8 @@ def checkout_git_branch():
|
||||
logger.error('Unable to checkout from git: ' + line)
|
||||
logger.info('Output: ' + str(output))
|
||||
|
||||
output, err = runGit('pull %s %s' % (plexpy.CONFIG.GIT_REMOTE, plexpy.CONFIG.GIT_BRANCH))
|
||||
|
||||
|
||||
def read_changelog(latest_only=False, since_prev_release=False):
|
||||
changelog_file = os.path.join(plexpy.PROG_DIR, 'CHANGELOG.md')
|
||||
|
@@ -29,14 +29,16 @@ import logger
|
||||
|
||||
name = 'websocket'
|
||||
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
|
||||
ws_reconnect = False
|
||||
ws_shutdown = False
|
||||
|
||||
|
||||
def start_thread():
|
||||
# Check for any existing sessions on start up
|
||||
activity_pinger.check_active_sessions(ws_request=True)
|
||||
# Start the websocket listener on it's own thread
|
||||
threading.Thread(target=run).start()
|
||||
thread = threading.Thread(target=run)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
|
||||
def on_connect():
|
||||
@@ -65,8 +67,18 @@ def on_disconnect():
|
||||
|
||||
|
||||
def reconnect():
|
||||
global ws_reconnect
|
||||
ws_reconnect = True
|
||||
shutdown()
|
||||
logger.info(u"Tautulli WebSocket :: Reconnecting websocket...")
|
||||
start_thread()
|
||||
|
||||
|
||||
def shutdown():
|
||||
global ws_shutdown
|
||||
ws_shutdown = True
|
||||
|
||||
logger.info(u"Tautulli WebSocket :: Disconnecting websocket...")
|
||||
plexpy.WEBSOCKET.close()
|
||||
plexpy.WS_CONNECTED = False
|
||||
|
||||
|
||||
def run():
|
||||
@@ -88,8 +100,8 @@ def run():
|
||||
else:
|
||||
header = []
|
||||
|
||||
global ws_reconnect
|
||||
ws_reconnect = False
|
||||
global ws_shutdown
|
||||
ws_shutdown = False
|
||||
reconnects = 0
|
||||
|
||||
# Try an open the websocket connection
|
||||
@@ -106,7 +118,7 @@ def run():
|
||||
logger.info(u"Tautulli WebSocket :: Connection attempt %s." % str(reconnects))
|
||||
|
||||
try:
|
||||
ws = create_connection(uri, header=header)
|
||||
plexpy.WEBSOCKET = create_connection(uri, header=header)
|
||||
logger.info(u"Tautulli WebSocket :: Ready")
|
||||
plexpy.WS_CONNECTED = True
|
||||
except (websocket.WebSocketException, IOError, Exception) as e:
|
||||
@@ -117,12 +129,15 @@ def run():
|
||||
|
||||
while plexpy.WS_CONNECTED:
|
||||
try:
|
||||
process(*receive(ws))
|
||||
process(*receive(plexpy.WEBSOCKET))
|
||||
|
||||
# successfully received data, reset reconnects counter
|
||||
reconnects = 0
|
||||
|
||||
except websocket.WebSocketConnectionClosedException:
|
||||
if ws_shutdown:
|
||||
break
|
||||
|
||||
if reconnects == 0:
|
||||
logger.warn(u"Tautulli WebSocket :: Connection has closed.")
|
||||
|
||||
@@ -136,31 +151,25 @@ def run():
|
||||
logger.warn(u"Tautulli WebSocket :: Reconnection attempt %s." % str(reconnects))
|
||||
|
||||
try:
|
||||
ws = create_connection(uri, header=header)
|
||||
plexpy.WEBSOCKET = create_connection(uri, header=header)
|
||||
logger.info(u"Tautulli WebSocket :: Ready")
|
||||
plexpy.WS_CONNECTED = True
|
||||
except (websocket.WebSocketException, IOError, Exception) as e:
|
||||
logger.error(u"Tautulli WebSocket :: %s." % e)
|
||||
|
||||
else:
|
||||
ws.shutdown()
|
||||
plexpy.WS_CONNECTED = False
|
||||
shutdown()
|
||||
break
|
||||
|
||||
except (websocket.WebSocketException, Exception) as e:
|
||||
if ws_shutdown:
|
||||
break
|
||||
|
||||
logger.error(u"Tautulli WebSocket :: %s." % e)
|
||||
ws.shutdown()
|
||||
plexpy.WS_CONNECTED = False
|
||||
shutdown()
|
||||
break
|
||||
|
||||
# Check if we recieved a restart notification and close websocket connection cleanly
|
||||
if ws_reconnect:
|
||||
logger.info(u"Tautulli WebSocket :: Reconnecting websocket...")
|
||||
ws.shutdown()
|
||||
plexpy.WS_CONNECTED = False
|
||||
start_thread()
|
||||
|
||||
if not plexpy.WS_CONNECTED and not ws_reconnect:
|
||||
if not plexpy.WS_CONNECTED and not ws_shutdown:
|
||||
on_disconnect()
|
||||
|
||||
logger.debug(u"Tautulli WebSocket :: Leaving thread.")
|
||||
|
@@ -106,10 +106,10 @@ def check_credentials(username, password, admin_login='0'):
|
||||
if plexpy.CONFIG.HTTP_PASSWORD:
|
||||
if plexpy.CONFIG.HTTP_HASHED_PASSWORD and \
|
||||
username == plexpy.CONFIG.HTTP_USERNAME and check_hash(password, plexpy.CONFIG.HTTP_PASSWORD):
|
||||
return True, 'admin'
|
||||
return True, 'tautulli admin'
|
||||
elif not plexpy.CONFIG.HTTP_HASHED_PASSWORD and \
|
||||
username == plexpy.CONFIG.HTTP_USERNAME and password == plexpy.CONFIG.HTTP_PASSWORD:
|
||||
return True, 'admin'
|
||||
return True, 'tautulli admin'
|
||||
|
||||
if plexpy.CONFIG.HTTP_PLEX_ADMIN or (not admin_login == '1' and plexpy.CONFIG.ALLOW_GUEST_ACCESS):
|
||||
plex_login = user_login(username, password)
|
||||
@@ -215,12 +215,12 @@ class AuthController(object):
|
||||
return
|
||||
raise cherrypy.HTTPRedirect(plexpy.HTTP_ROOT)
|
||||
|
||||
def on_login(self, user_id, username, user_group):
|
||||
def on_login(self, username, user_id=None, user_group=None, success=0):
|
||||
"""Called on successful login"""
|
||||
|
||||
# Save login to the database
|
||||
ip_address = cherrypy.request.headers.get('X-Forwarded-For', cherrypy.request.headers.get('Remote-Addr'))
|
||||
host = cherrypy.request.headers.get('Host', cherrypy.request.headers.get('Origin'))
|
||||
ip_address = cherrypy.request.remote.ip
|
||||
host = cherrypy.request.base
|
||||
user_agent = cherrypy.request.headers.get('User-Agent')
|
||||
|
||||
Users().set_user_login(user_id=user_id,
|
||||
@@ -229,28 +229,15 @@ class AuthController(object):
|
||||
ip_address=ip_address,
|
||||
host=host,
|
||||
user_agent=user_agent,
|
||||
success=1)
|
||||
success=success)
|
||||
|
||||
logger.debug(u"Tautulli WebAuth :: %s user '%s' logged into Tautulli." % (user_group.capitalize(), username))
|
||||
if success == 1:
|
||||
logger.debug(u"Tautulli WebAuth :: %s user '%s' logged into Tautulli." % (user_group.capitalize(), username))
|
||||
|
||||
def on_logout(self, username, user_group):
|
||||
"""Called on logout"""
|
||||
logger.debug(u"Tautulli WebAuth :: %s user '%s' logged out of Tautulli." % (user_group.capitalize(), username))
|
||||
|
||||
def on_login_failed(self, username):
|
||||
"""Called on failed login"""
|
||||
|
||||
# Save login attempt to the database
|
||||
ip_address = cherrypy.request.headers.get('X-Forwarded-For', cherrypy.request.headers.get('Remote-Addr'))
|
||||
host = cherrypy.request.headers.get('Origin')
|
||||
user_agent = cherrypy.request.headers.get('User-Agent')
|
||||
|
||||
Users().set_user_login(user=username,
|
||||
ip_address=ip_address,
|
||||
host=host,
|
||||
user_agent=user_agent,
|
||||
success=0)
|
||||
|
||||
def get_loginform(self):
|
||||
from plexpy.webserve import serve_template
|
||||
return serve_template(templatename="login.html", title="Login")
|
||||
@@ -293,15 +280,16 @@ class AuthController(object):
|
||||
valid_login, user_group = check_credentials(username, password, admin_login)
|
||||
|
||||
if valid_login:
|
||||
if user_group == 'guest':
|
||||
if user_group == 'tautulli admin':
|
||||
user_group = 'admin'
|
||||
user_id = None
|
||||
else:
|
||||
if re.match(r"[^@]+@[^@]+\.[^@]+", username):
|
||||
user_details = Users().get_details(email=username)
|
||||
else:
|
||||
user_details = Users().get_details(user=username)
|
||||
|
||||
user_id = user_details['user_id']
|
||||
else:
|
||||
user_id = None
|
||||
|
||||
time_delta = timedelta(days=30) if remember_me == '1' else timedelta(minutes=60)
|
||||
expiry = datetime.utcnow() + time_delta
|
||||
@@ -315,7 +303,10 @@ class AuthController(object):
|
||||
|
||||
jwt_token = jwt.encode(payload, plexpy.CONFIG.JWT_SECRET, algorithm=JWT_ALGORITHM)
|
||||
|
||||
self.on_login(user_id, username, user_group)
|
||||
self.on_login(username=username,
|
||||
user_id=user_id,
|
||||
user_group=user_group,
|
||||
success=1)
|
||||
|
||||
jwt_cookie = JWT_COOKIE_NAME + plexpy.CONFIG.PMS_UUID
|
||||
cherrypy.response.cookie[jwt_cookie] = jwt_token
|
||||
@@ -327,13 +318,13 @@ class AuthController(object):
|
||||
return {'status': 'success', 'token': jwt_token.decode('utf-8'), 'uuid': plexpy.CONFIG.PMS_UUID}
|
||||
|
||||
elif admin_login == '1':
|
||||
self.on_login_failed(username)
|
||||
self.on_login(username=username)
|
||||
logger.debug(u"Tautulli WebAuth :: Invalid admin login attempt from '%s'." % username)
|
||||
cherrypy.response.status = 401
|
||||
return error_message
|
||||
|
||||
else:
|
||||
self.on_login_failed(username)
|
||||
self.on_login(username=username)
|
||||
logger.debug(u"Tautulli WebAuth :: Invalid login attempt from '%s'." % username)
|
||||
cherrypy.response.status = 401
|
||||
return error_message
|
||||
|
@@ -63,7 +63,7 @@ def serve_template(templatename, **kwargs):
|
||||
|
||||
http_root = plexpy.HTTP_ROOT
|
||||
server_name = plexpy.CONFIG.PMS_NAME
|
||||
cache_param = '?' + (plexpy.CURRENT_VERSION or common.VERSION_NUMBER)
|
||||
cache_param = '?' + (plexpy.CURRENT_VERSION or common.RELEASE)
|
||||
|
||||
_session = get_session_info()
|
||||
|
||||
@@ -2293,7 +2293,7 @@ class WebInterface(object):
|
||||
filtered = []
|
||||
fa = filt.append
|
||||
|
||||
if logfile == "plexpy_api":
|
||||
if logfile == "tautulli_api":
|
||||
filename = logger.FILENAME_API
|
||||
elif logfile == "plex_websocket":
|
||||
filename = logger.FILENAME_PLEX_WEBSOCKET
|
||||
@@ -2496,7 +2496,7 @@ class WebInterface(object):
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
def delete_logs(self, logfile='', **kwargs):
|
||||
if logfile == "plexpy_api":
|
||||
if logfile == "tautulli_api":
|
||||
filename = logger.FILENAME_API
|
||||
elif logfile == "plex_websocket":
|
||||
filename = logger.FILENAME_PLEX_WEBSOCKET
|
||||
@@ -2538,7 +2538,7 @@ class WebInterface(object):
|
||||
@cherrypy.expose
|
||||
@requireAuth(member_of("admin"))
|
||||
def logFile(self, logfile='', **kwargs):
|
||||
if logfile == "plexpy_api":
|
||||
if logfile == "tautulli_api":
|
||||
filename = logger.FILENAME_API
|
||||
elif logfile == "plex_websocket":
|
||||
filename = logger.FILENAME_PLEX_WEBSOCKET
|
||||
@@ -2611,6 +2611,7 @@ class WebInterface(object):
|
||||
"pms_ssl": plexpy.CONFIG.PMS_SSL,
|
||||
"pms_is_remote": plexpy.CONFIG.PMS_IS_REMOTE,
|
||||
"pms_is_cloud": plexpy.CONFIG.PMS_IS_CLOUD,
|
||||
"pms_url": plexpy.CONFIG.PMS_URL,
|
||||
"pms_url_manual": checked(plexpy.CONFIG.PMS_URL_MANUAL),
|
||||
"pms_uuid": plexpy.CONFIG.PMS_UUID,
|
||||
"pms_web_url": plexpy.CONFIG.PMS_WEB_URL,
|
||||
@@ -2812,6 +2813,12 @@ class WebInterface(object):
|
||||
|
||||
return {'result': 'success', 'message': 'Settings saved.'}
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
def get_server_resources(self, **kwargs):
|
||||
return plextv.get_server_resources(return_server=True, **kwargs)
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
@@ -3004,18 +3011,12 @@ class WebInterface(object):
|
||||
def get_notifier_config_modal(self, notifier_id=None, **kwargs):
|
||||
result = notifiers.get_notifier_config(notifier_id=notifier_id)
|
||||
|
||||
if not result['custom_conditions']:
|
||||
result['custom_conditions'] = json.dumps([{'parameter': '', 'operator': '', 'value': ''}])
|
||||
|
||||
if not result['custom_conditions_logic']:
|
||||
result['custom_conditions_logic'] = ''
|
||||
|
||||
parameters = [
|
||||
{'name': param['name'], 'type': param['type'], 'value': param['value']}
|
||||
for category in common.NOTIFICATION_PARAMETERS for param in category['parameters']
|
||||
]
|
||||
|
||||
return serve_template(templatename="notifier_config.html", notifier=result, parameters=json.dumps(parameters))
|
||||
return serve_template(templatename="notifier_config.html", notifier=result, parameters=parameters)
|
||||
|
||||
@cherrypy.expose
|
||||
@cherrypy.tools.json_out()
|
||||
@@ -3461,7 +3462,8 @@ class WebInterface(object):
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth(member_of("admin"))
|
||||
@addtoapi()
|
||||
def get_server_id(self, hostname=None, port=None, identifier=None, ssl=0, remote=0, **kwargs):
|
||||
def get_server_id(self, hostname=None, port=None, identifier=None, ssl=0, remote=0, manual=0,
|
||||
get_url=False, **kwargs):
|
||||
""" Get the PMS server identifier.
|
||||
|
||||
```
|
||||
@@ -3474,7 +3476,8 @@ class WebInterface(object):
|
||||
remote (int): 0 or 1
|
||||
|
||||
Returns:
|
||||
string: The unique PMS identifier
|
||||
json:
|
||||
{'identifier': '08u2phnlkdshf890bhdlksghnljsahgleikjfg9t'}
|
||||
```
|
||||
"""
|
||||
# Attempt to get the pms_identifier from plex.tv if the server is published
|
||||
@@ -3505,11 +3508,21 @@ class WebInterface(object):
|
||||
xml_head = request.getElementsByTagName('MediaContainer')[0]
|
||||
identifier = xml_head.getAttribute('machineIdentifier')
|
||||
|
||||
result = {'identifier': identifier}
|
||||
|
||||
if identifier:
|
||||
return identifier
|
||||
if get_url == 'true':
|
||||
server = self.get_server_resources(pms_ip=hostname,
|
||||
pms_port=port,
|
||||
pms_ssl=ssl,
|
||||
pms_is_remote=remote,
|
||||
pms_url_manual=manual,
|
||||
pms_identifier=identifier)
|
||||
result['url'] = server['pms_url']
|
||||
return result
|
||||
else:
|
||||
logger.warn('Unable to retrieve the PMS identifier.')
|
||||
return None
|
||||
return result
|
||||
|
||||
@cherrypy.expose
|
||||
@requireAuth(member_of("admin"))
|
||||
@@ -3581,11 +3594,11 @@ class WebInterface(object):
|
||||
}
|
||||
|
||||
elif plexpy.COMMITS_BEHIND > 0 and plexpy.common.BRANCH in ('master', 'beta') and \
|
||||
plexpy.common.VERSION_NUMBER != plexpy.LATEST_RELEASE:
|
||||
plexpy.common.RELEASE != plexpy.LATEST_RELEASE:
|
||||
return {'result': 'success',
|
||||
'update': True,
|
||||
'release': True,
|
||||
'message': 'A new release (%) of Tautulli is available.' % plexpy.LATEST_RELEASE,
|
||||
'message': 'A new release (%s) of Tautulli is available.' % plexpy.LATEST_RELEASE,
|
||||
'latest_release': plexpy.LATEST_RELEASE,
|
||||
'release_url': helpers.anon_url(
|
||||
'https://github.com/%s/%s/releases/tag/%s'
|
||||
@@ -3668,7 +3681,7 @@ class WebInterface(object):
|
||||
latest_only = (latest_only == 'true')
|
||||
since_prev_release = (since_prev_release == 'true')
|
||||
|
||||
if since_prev_release and plexpy.PREV_RELEASE == common.VERSION_NUMBER:
|
||||
if since_prev_release and plexpy.PREV_RELEASE == common.RELEASE:
|
||||
latest_only = True
|
||||
since_prev_release = False
|
||||
|
||||
@@ -3912,7 +3925,7 @@ class WebInterface(object):
|
||||
@addtoapi()
|
||||
def download_log(self, logfile='', **kwargs):
|
||||
""" Download the Tautulli log file. """
|
||||
if logfile == "plexpy_api":
|
||||
if logfile == "tautulli_api":
|
||||
filename = logger.FILENAME_API
|
||||
log = logger.logger_api
|
||||
elif logfile == "plex_websocket":
|
||||
@@ -4706,6 +4719,7 @@ class WebInterface(object):
|
||||
"quality_profile": "Original",
|
||||
"rating": "7.8",
|
||||
"rating_key": "153037",
|
||||
"relay": 0,
|
||||
"section_id": "2",
|
||||
"session_id": "helf15l3rxgw01xxe0jf3l3d",
|
||||
"session_key": "27",
|
||||
@@ -5270,34 +5284,4 @@ class WebInterface(object):
|
||||
@cherrypy.tools.json_out()
|
||||
@requireAuth()
|
||||
def get_plexpy_url(self, **kwargs):
|
||||
if plexpy.CONFIG.ENABLE_HTTPS:
|
||||
scheme = 'https'
|
||||
else:
|
||||
scheme = 'http'
|
||||
|
||||
# Have to return some hostname if socket fails even if 127.0.0.1 won't work
|
||||
hostname = '127.0.0.1'
|
||||
|
||||
if plexpy.CONFIG.HTTP_HOST == '0.0.0.0':
|
||||
import socket
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
s.connect(('<broadcast>', 0))
|
||||
hostname = s.getsockname()[0]
|
||||
except socket.error:
|
||||
hostname = socket.gethostbyname(socket.gethostname())
|
||||
else:
|
||||
hostname = plexpy.CONFIG.HTTP_HOST
|
||||
|
||||
if plexpy.CONFIG.HTTP_PORT not in (80, 443):
|
||||
port = ':' + str(plexpy.CONFIG.HTTP_PORT)
|
||||
else:
|
||||
port = ''
|
||||
|
||||
if plexpy.CONFIG.HTTP_ROOT.strip('/'):
|
||||
root = '/' + plexpy.CONFIG.HTTP_ROOT.strip('/')
|
||||
else:
|
||||
root = ''
|
||||
|
||||
return scheme + '://' + hostname + port + root
|
||||
return helpers.get_plexpy_url()
|
||||
|
@@ -72,7 +72,7 @@ def initialize(options):
|
||||
if plexpy.CONFIG.HTTP_PLEX_ADMIN:
|
||||
login_allowed.append("Plex admin")
|
||||
|
||||
logger.info(u"Tautulli WebStart :: Web server authentication is enabled: %s allowed", ' and '.join(login_allowed))
|
||||
logger.info(u"Tautulli WebStart :: Web server authentication is enabled: %s.", ' and '.join(login_allowed))
|
||||
|
||||
if options['http_basic_auth']:
|
||||
auth_enabled = False
|
||||
@@ -122,6 +122,7 @@ def initialize(options):
|
||||
'/images': {
|
||||
'tools.staticdir.on': True,
|
||||
'tools.staticdir.dir': "interfaces/default/images",
|
||||
'tools.staticdir.content_types': {'svg': 'image/svg+xml'},
|
||||
'tools.caching.on': True,
|
||||
'tools.caching.force': True,
|
||||
'tools.caching.delay': 0,
|
||||
|
Reference in New Issue
Block a user