Compare commits

...

18 Commits

Author SHA1 Message Date
JonnyWong16
9d01335395 v2.0.5-beta 2017-12-31 17:52:53 -08:00
JonnyWong16
63814584e9 Add sortcut to /status/sessions 2017-12-31 17:37:07 -08:00
JonnyWong16
bf7e432c0c Add total bandwidth to activity header 2017-12-31 17:13:38 -08:00
JonnyWong16
5a14c229a1 Fix IPv6 overflow on current activity cards 2017-12-31 17:12:49 -08:00
JonnyWong16
79581fb83e Sanitize script output in logger 2017-12-31 14:41:32 -08:00
JonnyWong16
90c587e6e1 Unique session id name to not conflict with multiple installs 2017-12-31 14:33:21 -08:00
JonnyWong16
b2b728a3cc Save cherrypy sessions to disk instead of ram 2017-12-31 14:29:57 -08:00
JonnyWong16
645ef86c75 Fix Join notifications not sending 2017-12-31 14:29:57 -08:00
JonnyWong16
450fda18f1 Merge pull request #1181 from felixbuenemann/fix-cherrypy-ssl-connection-type-deprecation
Fix OpenSSL SSL.ConnectionType deprecation warning
2017-12-31 12:37:22 -07:00
Felix Bünemann
f6162a8df8 Fix OpenSSL SSL.ConnectionType deprecation warning
This patches the vendored cherrypy lib to work around a deprecation
warning in pyOpenSSL 17.1.0 and later by checking and using the
SSL.Connection class if available and falling back to ConnectionType.
2017-12-31 14:30:15 +01:00
JonnyWong16
a8c5f48b60 Merge branch 'nightly' of https://github.com/JonnyWong16/plexpy into nightly 2017-12-30 23:22:02 -08:00
JonnyWong16
edc0b96167 Some sync data updates 2017-12-30 23:21:31 -08:00
JonnyWong16
9b4b3e0ecb Add stopped state to history table activity 2017-12-30 17:27:59 -08:00
JonnyWong16
7bd2c00636 Fix presence in get_server_resources 2017-12-30 17:27:50 -08:00
JonnyWong16
15faccfa2f Update server connection code 2017-12-30 17:27:39 -08:00
JonnyWong16
11e10e7d0e Update resources links 2017-12-30 16:13:55 -08:00
JonnyWong16
8ef48fc548 Update svg logo with shadows 2017-12-30 14:14:47 -08:00
JonnyWong16
29632b0805 Validate git releases as a list 2017-12-29 16:49:55 -08:00
55 changed files with 815 additions and 736 deletions

View File

@@ -1,5 +1,20 @@
# Changelog # Changelog
## v2.0.5-beta (2017-12-31)
* Monitoring:
* Fix: IPv6 addresses overflowing on the activity cards.
* Notifications:
* Fix: Error sending Join notifications.
* UI:
* New: Added total required bandwidth in the activity header.
* Fix: Failing to retrieve releases from GitHub.
* Other:
* Fix: CherryPy SSL connection warning. (Thanks @felixbuenemann)
* Fix: Sanitize script output in logs.
* Change: Login sessions persists across server restarts.
## v2.0.4-beta (2017-12-29) ## v2.0.4-beta (2017-12-29)
* Monitoring: * Monitoring:

View File

@@ -20,26 +20,21 @@
${next.headIncludes()} ${next.headIncludes()}
<!-- Favicons --> <!-- Favicons -->
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.1"> <link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.5">
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.1"> <link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.5">
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.1"> <link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.5">
<!-- ICONS --> <!-- ICONS -->
<!-- Android --> <!-- Android -->
<link rel="icon" type="image/png" sizes="192x192" href="${http_root}images/favicon/android-chrome-192x192.png?v=2.0.0"> <link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5">
<link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.1">
<meta name="theme-color" content="#282a2d"> <meta name="theme-color" content="#282a2d">
<!-- Apple --> <!-- Apple -->
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.1"> <link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.5">
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.0.1" color="#282a2d"> <link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.0.5" color="#282a2d">
<meta name="apple-mobile-web-app-title" content="Tautulli"> <meta name="apple-mobile-web-app-title" content="Tautulli">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="viewport" content="initial-scale=1">
<meta name="format-detection" content="telephone=no">
<!-- Microsoft --> <!-- Microsoft -->
<meta name="application-name" content="Tautulli"> <meta name="application-name" content="Tautulli">
<meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.0.1"> <meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.0.5">
</head> </head>
<body class="content"> <body class="content">
@@ -69,8 +64,8 @@
<span class="icon-bar"></span> <span class="icon-bar"></span>
<span class="icon-bar"></span> <span class="icon-bar"></span>
</button> </button>
<a class="navbar-brand" href="home"> <a class="navbar-brand svg" href="home">
<img alt="Tautulli" src="${http_root}images/logo-tautulli.svg" height="45"> <object data="${http_root}images/logo-tautulli.svg" type="image/svg+xml" style="height: 45px;"></object>
</a> </a>
</div> </div>
<div class="collapse navbar-collapse navbar-right" id="navbar-collapse-1"> <div class="collapse navbar-collapse navbar-right" id="navbar-collapse-1">

View File

@@ -74,11 +74,11 @@ DOCUMENTATION :: END
<tr> <tr>
<td class="top-line">Resources:</td> <td class="top-line">Resources:</td>
<td class="top-line"> <td class="top-line">
<a id="source-link" class="no-highlight" href="${anon_url('https://github.com/%s/plexpy' % plexpy.CONFIG.GIT_USER)}" target="_blank">GitHub Source</a> | <a class="no-highlight" href="${anon_url('http://tautulli.com')}" target="_blank">Tautulli Website</a> |
<a class="no-highlight" href="${anon_url('https://github.com/%s/plexpy' % plexpy.CONFIG.GIT_USER)}" target="_blank">GitHub Source</a> |
<a class="no-highlight guidelines-modal-link" href="${anon_url('https://github.com/%s/plexpy/issues' % plexpy.CONFIG.GIT_USER)}" data-id="issue">GitHub Issues</a> | <a class="no-highlight guidelines-modal-link" href="${anon_url('https://github.com/%s/plexpy/issues' % plexpy.CONFIG.GIT_USER)}" data-id="issue">GitHub Issues</a> |
<a class="no-highlight" href="${anon_url('https://github.com/%s/plexpy/wiki' % plexpy.CONFIG.GIT_USER)}" target="_blank">GitHub Wiki &amp; FAQ</a> |
<a class="no-highlight guidelines-modal-link" href="${anon_url('http://feathub.com/%s/plexpy' % plexpy.CONFIG.GIT_USER)}" data-id="feature request">FeatHub Feature Requests</a> | <a class="no-highlight guidelines-modal-link" href="${anon_url('http://feathub.com/%s/plexpy' % plexpy.CONFIG.GIT_USER)}" data-id="feature request">FeatHub Feature Requests</a> |
<a class="no-highlight" href="${anon_url('https://github.com/%s/plexpy/wiki' % plexpy.CONFIG.GIT_USER)}" target="_blank">Tautulli Wiki</a> |
<a id="faq-source-link" class="no-highlight" href="${anon_url('https://github.com/%s/plexpy/wiki/Frequently-Asked-Questions-(FAQ)' % plexpy.CONFIG.GIT_USER)}" target="_blank">Tautulli FAQ</a>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -112,7 +112,6 @@ DOCUMENTATION :: END
$('.guidelines-modal-link').on('click', function (e) { $('.guidelines-modal-link').on('click', function (e) {
e.preventDefault(); e.preventDefault();
$('#guidelines-link').attr('href', $('#source-link').attr('href'));
$('#guidelines-type').text($(this).data('id')) $('#guidelines-type').text($(this).data('id'))
$('#guidelines-modal').modal(); $('#guidelines-modal').modal();
$('#guidelines-continue').attr('href', $(this).attr('href')).on('click', function () { $('#guidelines-continue').attr('href', $(this).attr('href')).on('click', function () {
@@ -121,7 +120,6 @@ DOCUMENTATION :: END
}); });
$('.support-modal-link').on('click', function (e) { $('.support-modal-link').on('click', function (e) {
e.preventDefault(); e.preventDefault();
$('#faq-link').attr('href', $('#faq-source-link').attr('href'));
$('#support-modal').modal(); $('#support-modal').modal();
$('#support-continue').attr('href', $(this).attr('href')).on('click', function () { $('#support-continue').attr('href', $(this).attr('href')).on('click', function () {
$('#support-modal').modal('hide'); $('#support-modal').modal('hide');

View File

@@ -13,6 +13,18 @@ a:focus {
text-decoration: none; text-decoration: none;
outline: none; outline: none;
} }
a.svg {
position: relative;
display: inline-block;
}
a.svg:after {
content: "";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
select, .react-selectize.bootstrap3.root-node .react-selectize-control { select, .react-selectize.bootstrap3.root-node .react-selectize-control {
margin: 5px 0 5px 0; margin: 5px 0 5px 0;
border: 2px solid #444; border: 2px solid #444;
@@ -841,6 +853,15 @@ a .users-poster-face:hover {
-webkit-flex-grow: 1; -webkit-flex-grow: 1;
flex-grow: 1; flex-grow: 1;
} }
.dashboard-activity-info-item .sub-value .ip-container {
display: inline-flex;
}
.dashboard-activity-info-item .sub-value .ip-address {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 140px;
}
.dashboard-activity-info-time { .dashboard-activity-info-time {
position: absolute; position: absolute;
top: 200px; top: 200px;

View File

@@ -178,7 +178,7 @@ DOCUMENTATION :: END
<ul class="list-unstyled dashboard-activity-info-list"> <ul class="list-unstyled dashboard-activity-info-list">
<li class="dashboard-activity-info-item"> <li class="dashboard-activity-info-item">
% if _session['user_group'] == 'admin': % if _session['user_group'] == 'admin':
<div class="sub-heading"><span class="raw-stream-info-modal" data-toggle="modal" data-target="#raw-stream-info-modal" data-key="${sk}">Stream</span></div>\ <div class="sub-heading"><span class="raw-stream-info-modal" data-toggle="modal" data-target="#raw-stream-info-modal" data-key="${sk}">Stream</span></div>
% else: % else:
<div class="sub-heading">Stream</div> <div class="sub-heading">Stream</div>
% endif % endif
@@ -272,9 +272,9 @@ DOCUMENTATION :: END
<div class="sub-heading">Location</div> <div class="sub-heading">Location</div>
<div class="sub-value"> <div class="sub-value">
% if data['ip_address'] != 'N/A': % if data['ip_address'] != 'N/A':
${'LAN' if data['local'] == 1 else 'WAN'}: ${data['ip_address']} ${'LAN' if data['local'] == 1 else 'WAN'}: <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']}"> <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 IP" style="display: none;"><i class="fa fa-map-marker"></i></span> <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> </a>
<script> <script>
isPrivateIP("${data['ip_address']}").then(function () { isPrivateIP("${data['ip_address']}").then(function () {
@@ -301,7 +301,7 @@ DOCUMENTATION :: END
bw = str(bw) + ' kbps' bw = str(bw) + ' kbps'
%> %>
<span id="stream-bandwidth-${sk}">${bw}</span> <span id="stream-bandwidth-${sk}">${bw}</span>
<span id="streaming-brain-${sk}" data-toggle="tooltip" title="Streaming Brain Estimate"><i class="fa fa-info-circle"></i></span> <span id="streaming-brain-${sk}" data-toggle="tooltip" title="Streaming Brain Estimate (Required Bandwidth)"><i class="fa fa-info-circle"></i></span>
% elif data['synced_version'] == 1 or data['channel_stream'] == 1: % elif data['synced_version'] == 1 or data['channel_stream'] == 1:
<span id="stream-bandwidth-${sk}">None</span> <span id="stream-bandwidth-${sk}">None</span>
% else: % else:

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -2,9 +2,7 @@
<browserconfig> <browserconfig>
<msapplication> <msapplication>
<tile> <tile>
<square70x70logo src="${http_root}images/favicon/mstile-70x70.png?v=2.0.1"/> <square150x150logo src="${http_root}images/favicon/mstile-150x150.png?v=2.0.5"/>
<square150x150logo src="${http_root}images/favicon/mstile-150x150.png?v=2.0.1"/>
<square310x310logo src="${http_root}images/favicon/mstile-310x310.png?v=2.0.1"/>
<TileColor>#282a2d</TileColor> <TileColor>#282a2d</TileColor>
</tile> </tile>
</msapplication> </msapplication>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 644 B

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 971 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -2,12 +2,12 @@
"name": "Tautulli", "name": "Tautulli",
"icons": [ "icons": [
{ {
"src": "${http_root}images/favicon/android-chrome-192x192.png?v=2.0.1", "src": "${http_root}images/favicon/android-chrome-192x192.png?v=2.0.5",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png" "type": "image/png"
}, },
{ {
"src": "${http_root}images/favicon/android-chrome-256x256.png?v=2.0.1", "src": "${http_root}images/favicon/android-chrome-256x256.png?v=2.0.5",
"sizes": "256x256", "sizes": "256x256",
"type": "image/png" "type": "image/png"
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -2,69 +2,72 @@
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="900px" <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="900px"
height="300px" viewBox="-41.335 -35.197 900 300" enable-background="new -41.335 -35.197 900 300" xml:space="preserve"> height="300px" viewBox="0 0 900 300" enable-background="new 0 0 900 300" xml:space="preserve">
<g id="Layer_1"> <g id="Layer_1">
<g id="Logo">
<image overflow="visible" opacity="0.15" enable-background="new " width="259" height="193" xlink:href="13AD46AC.png" transform="matrix(0.9547 0 0 0.9547 -17.8804 54.1814)">
</image>
<image overflow="visible" opacity="0.15" enable-background="new " width="259" height="258" xlink:href="13AD46AD.png" transform="matrix(0.9547 0 0 0.9584 -17.8804 -8.8303)">
</image>
</g>
</g>
<g id="Layer_2">
<g> <g>
<path fill="none" d="M349.343,165.396c7.493,0,14.836,0,22.407,0c-3.766-12.354-7.495-24.585-11.333-37.175 <path fill="none" d="M401.343,203.396c7.493,0,14.836,0,22.407,0c-3.766-12.354-7.495-24.586-11.333-37.176
C356.665,140.819,353.017,153.064,349.343,165.396z"/> C408.665,178.818,405.017,191.064,401.343,203.396z"/>
<path fill="#FFFFFF" d="M103.83,174.477c-7.6,0-14.441-3.232-19.236-8.394l-4.595,2.845l1.406,1.593 <path fill="#FFFFFF" d="M436.772,189.627c-4.399-12.521-8.814-25.034-13.224-37.553c-7.552,0-15.105,0-22.657,0.002
c-2.429,3.979-3.83,8.656-3.83,13.66c0,14.5,11.755,26.255,26.255,26.255c10.38,0,19.351-6.024,23.612-14.767l37.675,5.471 c-0.14,0.611-0.221,1.246-0.428,1.836c-8.502,24.335-17.017,48.666-25.526,72.996c-1.45,4.146-2.887,8.298-4.362,12.535
c0.22,14.31,11.881,25.845,26.244,25.845c14.5,0,26.254-11.755,26.254-26.254c0-14.5-11.754-26.254-26.254-26.254 c6.686,0,13.198,0,19.792,0c2.095-6.873,4.172-13.693,6.248-20.514c10.593,0,21.056,0,31.594,0
c-10.438,0-19.453,6.092-23.685,14.915l-37.598-5.46c-0.064-6.86-2.759-13.089-7.125-17.73 c2.102,6.894,4.177,13.707,6.245,20.489c6.66,0,13.146,0,19.772,0c-0.229-0.676-0.408-1.213-0.597-1.747
C118.165,171.293,111.371,174.477,103.83,174.477z"/> C448.014,221.656,442.4,205.639,436.772,189.627z M401.343,203.396c3.674-12.332,7.322-24.578,11.074-37.176
<path fill="#FFFFFF" d="M77.574,148.22c0-0.054,0.004-0.107,0.004-0.161l-41.163-46.637c3.078-4.301,4.895-9.564,4.895-15.257 c3.837,12.59,7.567,24.82,11.333,37.176C416.179,203.396,408.836,203.396,401.343,203.396z"/>
c0-14.5-11.754-26.254-26.254-26.254c-14.5,0-26.254,11.754-26.254,26.254S0.556,112.42,15.056,112.42 <path fill="#FFFFFF" d="M371.072,152.459c-21.916,0-43.668,0-65.421,0c0,5.111,0,10.227,0,15.341c7.836,0,15.672,0,23.691,0
c4.464,0,8.666-1.117,12.347-3.083l44.545,50.47l6.701-4.148C77.953,153.299,77.574,150.804,77.574,148.22z"/> c0,24.046,0,47.786,0,71.621c6.164,0,12.144,0,18.361,0c0-23.901,0-47.698,0-71.682c7.948,0,15.66,0,23.369,0
<path fill="#E5A00D" d="M191.361,49.885c14.5,0,26.254-11.754,26.254-26.254c0-14.499-11.754-26.254-26.254-26.254 C371.072,162.549,371.072,157.549,371.072,152.459z"/>
s-26.254,11.755-26.254,26.254c0,7.145,2.858,13.619,7.488,18.353l-58.186,82.207c-3.237-1.427-6.814-2.226-10.58-2.226 <path fill="#FFFFFF" d="M532.615,152.555c-6.176,0-12.154,0-18.349,0c0,0.842,0,1.592,0,2.34c0,16.42,0.021,32.838-0.021,49.258
c-14.446,0-26.165,11.669-26.252,26.095c0,0.054-0.004,0.107-0.004,0.161c0,2.584,0.379,5.078,1.075,7.438l-6.701,4.148 c-0.006,2.675-0.119,5.373-0.494,8.018c-0.997,7.07-4.634,11.153-11.06,12.587c-3.731,0.83-7.491,0.754-11.238,0
l-37.506,23.223c-4.801-5.255-11.708-8.552-19.386-8.552c-14.5,0-26.254,11.754-26.254,26.254 c-5.745-1.157-9.344-4.668-10.58-10.297c-0.678-3.088-0.958-6.317-0.976-9.487c-0.094-16.738-0.045-33.478-0.045-50.217
c0,14.499,11.754,26.254,26.254,26.254c14.5,0,26.254-11.755,26.254-26.254c0-2.506-0.358-4.926-1.014-7.221l39.703-24.582 c0-0.732,0-1.467,0-2.148c-6.296,0-12.275,0-18.329,0c0,0.591,0,1.035,0,1.48c0,18.209-0.013,36.416,0.018,54.625
l4.595-2.845c4.794,5.161,11.636,8.394,19.236,8.394c7.542,0,14.335-3.185,19.124-8.275c4.419-4.699,7.132-11.021,7.132-17.981 c0.003,1.908,0.127,3.835,0.396,5.727c1.632,11.408,7.683,19.563,18.562,23.516c11.146,4.052,22.489,3.83,33.593-0.393
c0-6.396-2.29-12.257-6.092-16.812l58.736-82.983C185.434,49.367,188.336,49.885,191.361,49.885z"/> c7.937-3.017,13.483-8.627,16.483-16.617c1.474-3.93,2.044-8.024,2.042-12.211c-0.006-18.146-0.002-36.289-0.002-54.436
<path fill="#FFFFFF" d="M384.772,151.627c-4.399-12.521-8.814-25.034-13.223-37.553c-7.552,0-15.106,0-22.657,0.002 C532.615,153.744,532.615,153.188,532.615,152.555z"/>
c-0.14,0.612-0.221,1.246-0.428,1.836c-8.502,24.335-17.017,48.666-25.526,72.996c-1.451,4.146-2.887,8.298-4.363,12.536 <path fill="#FFFFFF" d="M688.532,152.555c-6.16,0-12.14,0-18.349,0c0,0.924,0,1.62,0,2.318c0,16.482,0.021,32.967-0.021,49.449
c6.686,0,13.198,0,19.792,0c2.095-6.874,4.172-13.694,6.248-20.514c10.593,0,21.056,0,31.594,0 c-0.006,2.609-0.125,5.244-0.49,7.824c-1.003,7.127-4.74,11.291-11.226,12.654c-3.674,0.771-7.373,0.702-11.056-0.037
c2.102,6.893,4.177,13.707,6.245,20.489c6.66,0,13.147,0,19.773,0c-0.229-0.676-0.409-1.213-0.597-1.747 c-5.74-1.156-9.354-4.652-10.596-10.281c-0.682-3.086-0.964-6.317-0.979-9.487c-0.094-16.738-0.044-33.478-0.044-50.216
C396.014,183.656,390.4,167.638,384.772,151.627z M349.343,165.396c3.674-12.332,7.322-24.577,11.074-37.175 c0-0.739,0-1.478,0-2.18c-6.278,0-12.26,0-18.332,0c0,0.582,0,1.024,0,1.467c0.003,18.271-0.011,36.543,0.021,54.815
c3.837,12.59,7.567,24.82,11.333,37.175C364.179,165.396,356.836,165.396,349.343,165.396z"/> c0.002,1.847,0.132,3.707,0.391,5.533c1.626,11.411,7.669,19.567,18.546,23.533c11.213,4.09,22.621,3.836,33.771-0.445
<path fill="#FFFFFF" d="M319.072,114.459c-21.916,0-43.668,0-65.421,0c0,5.112,0,10.227,0,15.341c7.836,0,15.672,0,23.691,0 c7.851-3.019,13.345-8.612,16.315-16.541c1.521-4.052,2.06-8.274,2.056-12.592c-0.018-17.953-0.008-35.905-0.008-53.858
c0,24.046,0,47.786,0,71.621c6.164,0,12.144,0,18.361,0c0-23.902,0-47.698,0-71.682c7.948,0,15.66,0,23.369,0 C688.532,153.893,688.532,153.27,688.532,152.555z"/>
C319.072,124.549,319.072,119.549,319.072,114.459z"/> <path fill="#FFFFFF" d="M542.405,167.793c7.813,0,15.522,0,23.479,0c0,23.979,0,47.771,0,71.648c6.172,0,12.102,0,18.425,0
<path fill="#FFFFFF" d="M480.615,114.554c-6.176,0-12.154,0-18.348,0c0,0.843,0,1.593,0,2.341c0,16.42,0.021,32.838-0.021,49.258 c0-23.948,0-47.738,0-71.729c7.947,0,15.655,0,23.378,0c0-5.158,0-10.115,0-15.115c-21.819,0-43.53,0-65.281,0
c-0.006,2.674-0.119,5.373-0.494,8.017c-0.997,7.07-4.634,11.153-11.06,12.587c-3.731,0.83-7.491,0.754-11.238,0 C542.405,157.691,542.405,162.648,542.405,167.793z"/>
c-5.745-1.157-9.344-4.668-10.58-10.297c-0.678-3.088-0.958-6.318-0.976-9.488c-0.093-16.737-0.044-33.477-0.044-50.216 <path fill="#FFFFFF" d="M725.407,152.58c-6.199,0-12.177,0-18.193,0c0,29.021,0,57.92,0,86.857c17.832,0,35.528,0,53.335,0
c0-0.733,0-1.467,0-2.149c-6.296,0-12.276,0-18.33,0c0,0.591,0,1.036,0,1.481c0,18.209-0.012,36.416,0.018,54.625 c0-5.191,0-10.146,0-15.307c-11.72,0-23.322,0-35.142,0C725.407,200.143,725.407,176.361,725.407,152.58z"/>
c0.003,1.908,0.127,3.835,0.396,5.726c1.632,11.409,7.683,19.563,18.562,23.516c11.146,4.052,22.489,3.83,33.593-0.392 <path fill="#FFFFFF" d="M790.668,152.593c-6.197,0-12.172,0-18.216,0c0,28.985,0,57.833,0,86.831c17.905,0,35.6,0,53.371,0
c7.936-3.017,13.483-8.627,16.483-16.618c1.474-3.93,2.044-8.024,2.042-12.21c-0.006-18.146-0.002-36.29-0.002-54.436 c0-5.105,0-10.107,0-15.316c-11.729,0-23.335,0-35.155,0C790.668,200.119,790.668,176.339,790.668,152.593z"/>
C480.615,115.744,480.615,115.187,480.615,114.554z"/> <path fill="#FFFFFF" d="M838.183,239.464c6.083,0,12.021,0,18.096,0c0-29.09,0-57.987,0-86.879c-6.12,0-12.098,0-18.096,0
<path fill="#FFFFFF" d="M636.532,114.554c-6.16,0-12.139,0-18.348,0c0,0.925,0,1.621,0,2.319c0,16.482,0.021,32.967-0.021,49.449 C838.183,181.611,838.183,210.514,838.183,239.464z"/>
c-0.006,2.609-0.125,5.244-0.49,7.825c-1.003,7.126-4.74,11.291-11.226,12.654c-3.674,0.771-7.373,0.702-11.055-0.038 <ellipse fill="#E5A00D" cx="847.229" cy="129.416" rx="12.459" ry="12.44"/>
c-5.741-1.155-9.354-4.651-10.596-10.281c-0.682-3.086-0.964-6.317-0.979-9.487c-0.093-16.738-0.043-33.477-0.043-50.216 </g>
c0-0.739,0-1.477,0-2.18c-6.279,0-12.26,0-18.332,0c0,0.582,0,1.025,0,1.467c0.002,18.272-0.011,36.544,0.02,54.816 </g>
c0.002,1.846,0.132,3.707,0.391,5.533c1.626,11.411,7.669,19.567,18.546,23.533c11.212,4.09,22.621,3.836,33.771-0.445 <g id="Layer_2_1_">
c7.851-3.018,13.345-8.613,16.315-16.541c1.521-4.052,2.06-8.275,2.056-12.592c-0.018-17.953-0.008-35.905-0.008-53.858 <g>
C636.532,115.892,636.532,115.269,636.532,114.554z"/>
<path fill="#FFFFFF" d="M490.405,129.793c7.813,0,15.523,0,23.479,0c0,23.979,0,47.771,0,71.649c6.172,0,12.101,0,18.425,0 <image overflow="visible" opacity="0.2" width="279" height="216" xlink:href="13AD46AC.png" transform="matrix(1 0 0 1 15.5 73.5)">
c0-23.949,0-47.739,0-71.729c7.947,0,15.655,0,23.378,0c0-5.158,0-10.115,0-15.116c-21.82,0-43.531,0-65.282,0 </image>
C490.405,119.691,490.405,124.648,490.405,129.793z"/> <g>
<path fill="#FFFFFF" d="M673.407,114.58c-6.199,0-12.176,0-18.193,0c0,29.021,0,57.92,0,86.857c17.832,0,35.528,0,53.335,0 <path fill="#FFFFFF" d="M243.77,212.601c-10.482,0-19.533,6.118-23.783,14.977l-37.755-5.478
c0-5.191,0-10.146,0-15.306c-11.72,0-23.322,0-35.142,0C673.407,162.143,673.407,138.362,673.407,114.58z"/> c-0.133-14.445-11.881-26.114-26.356-26.114c-5.163,0-9.975,1.489-14.041,4.051l-53.649-60.787
<path fill="#FFFFFF" d="M738.668,114.593c-6.197,0-12.172,0-18.216,0c0,28.985,0,57.833,0,86.831c17.905,0,35.6,0,53.371,0 c3.091-4.319,4.914-9.605,4.914-15.319c0-14.56-11.802-26.362-26.363-26.362c-14.56,0-26.363,11.803-26.363,26.362
c0-5.106,0-10.107,0-15.316c-11.729,0-23.335,0-35.155,0C738.668,162.119,738.668,138.339,738.668,114.593z"/> c0,14.559,11.803,26.361,26.363,26.361c4.484,0,8.704-1.122,12.399-3.096l54.223,61.438c-2.439,3.994-3.846,8.688-3.846,13.712
<path fill="#FFFFFF" d="M786.183,201.464c6.083,0,12.02,0,18.095,0c0-29.09,0-57.987,0-86.879c-6.12,0-12.097,0-18.095,0 c0,14.56,11.803,26.363,26.364,26.363c10.422,0,19.43-6.05,23.708-14.828l37.832,5.491c0.22,14.369,11.929,25.953,26.354,25.953
C786.183,143.612,786.183,172.513,786.183,201.464z"/> c14.56,0,26.363-11.804,26.363-26.362S258.33,212.601,243.77,212.601z"/>
<ellipse fill="#E5A00D" cx="795.229" cy="91.416" rx="12.458" ry="12.44"/> </g>
</g>
<g>
<image overflow="visible" opacity="0.2" width="279" height="279" xlink:href="13AD46AD.png" transform="matrix(1 0 0 1 15.5 10.5)">
</image>
<g>
<path fill="#E5A00D" d="M243.77,34.779c-14.56,0-26.363,11.802-26.363,26.362c0,7.17,2.867,13.667,7.512,18.42l-58.424,82.548
c-3.25-1.431-6.84-2.23-10.619-2.23c-14.56,0-26.363,11.8-26.363,26.36c0,2.592,0.38,5.094,1.078,7.46L86.2,221.181
c-4.82-5.271-11.754-8.58-19.461-8.58c-14.56,0-26.363,11.803-26.363,26.361s11.803,26.362,26.363,26.362
c14.561,0,26.363-11.804,26.363-26.362c0-2.518-0.36-4.951-1.019-7.256l44.477-27.534c4.815,5.183,11.686,8.429,19.318,8.429
c14.561,0,26.364-11.803,26.364-26.361c0-6.424-2.301-12.31-6.119-16.883l58.973-83.322c2.72,0.948,5.637,1.468,8.677,1.468
c14.561,0,26.363-11.802,26.363-26.361C270.135,46.582,258.33,34.779,243.77,34.779z"/>
</g>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 6.0 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -3,20 +3,30 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="298.961px" height="300px" viewBox="0 0 298.961 300" enable-background="new 0 0 298.961 300" xml:space="preserve"> width="298.961px" height="300px" viewBox="0 0 298.961 300" enable-background="new 0 0 298.961 300" xml:space="preserve">
<image overflow="visible" opacity="0.15" enable-background="new " width="259" height="193" xlink:href="93124B54.png" transform="matrix(1 0 0 1 22.6465 89.25)"> <g>
<image overflow="visible" opacity="0.2" width="289" height="224" xlink:href="93124B54.png" transform="matrix(1 0 0 1 4.9805 70.5)">
</image> </image>
<path fill="#FFFFFF" d="M241.811,215.25c-10.935,0-20.376,6.382-24.809,15.623l-39.383-5.715 <g>
c-0.139-15.068-12.392-27.242-27.493-27.242c-5.385,0-10.405,1.554-14.646,4.229l-55.963-63.414 <path fill="#FFFFFF" d="M241.811,215.25c-10.935,0-20.375,6.382-24.809,15.623l-39.383-5.715
c-0.139-15.068-12.393-27.242-27.493-27.242c-5.385,0-10.405,1.554-14.646,4.229l-55.963-63.414
c3.224-4.505,5.127-10.02,5.127-15.981c0-15.188-12.312-27.5-27.5-27.5s-27.5,12.312-27.5,27.5s12.312,27.5,27.5,27.5 c3.224-4.505,5.127-10.02,5.127-15.981c0-15.188-12.312-27.5-27.5-27.5s-27.5,12.312-27.5,27.5s12.312,27.5,27.5,27.5
c4.677,0,9.079-1.171,12.934-3.23l56.56,64.089c-2.544,4.168-4.012,9.065-4.012,14.307c0,15.188,12.312,27.5,27.5,27.5 c4.677,0,9.079-1.171,12.934-3.23l56.56,64.089c-2.544,4.168-4.012,9.064-4.012,14.307c0,15.188,12.312,27.5,27.5,27.5
c10.872,0,20.269-6.311,24.731-15.467l39.464,5.727c0.229,14.99,12.443,27.074,27.489,27.074c15.188,0,27.5-12.313,27.5-27.5 c10.872,0,20.269-6.311,24.731-15.467l39.463,5.727c0.229,14.99,12.443,27.074,27.49,27.074c15.188,0,27.5-12.313,27.5-27.5
S256.998,215.25,241.811,215.25z"/> S256.998,215.25,241.811,215.25z"/>
<image overflow="visible" opacity="0.15" enable-background="new " width="259" height="259" xlink:href="93124B55.png" transform="matrix(1 0 0 1 22.6465 23.25)"> </g>
</g>
<g>
<image overflow="visible" opacity="0.2" width="289" height="289" xlink:href="93124B55.png" transform="matrix(1 0 0 1 4.9805 5.5)">
</image> </image>
<path fill="#E5A00D" d="M241.811,29.75c-15.188,0-27.5,12.312-27.5,27.5c0,7.48,2.99,14.258,7.835,19.216l-60.943,86.113 <g>
c-3.389-1.493-7.134-2.329-11.076-2.329c-15.188,0-27.5,12.313-27.5,27.5c0,2.704,0.397,5.314,1.125,7.783l-46.306,28.668 <path fill="#E5A00D" d="M241.811,29.75c-15.188,0-27.5,12.312-27.5,27.5c0,7.48,2.99,14.258,7.836,19.216l-60.943,86.113
c-3.389-1.493-7.135-2.329-11.076-2.329c-15.188,0-27.5,12.313-27.5,27.5c0,2.704,0.397,5.314,1.125,7.783l-46.306,28.668
c-5.028-5.5-12.261-8.951-20.3-8.951c-15.188,0-27.5,12.313-27.5,27.5s12.312,27.5,27.5,27.5s27.5-12.313,27.5-27.5 c-5.028-5.5-12.261-8.951-20.3-8.951c-15.188,0-27.5,12.313-27.5,27.5s12.312,27.5,27.5,27.5s27.5-12.313,27.5-27.5
c0-2.627-0.376-5.165-1.064-7.571l46.396-28.723c5.022,5.407,12.189,8.794,20.15,8.794c15.188,0,27.5-12.313,27.5-27.5 c0-2.627-0.376-5.165-1.064-7.57l46.396-28.724c5.022,5.407,12.189,8.794,20.15,8.794c15.188,0,27.5-12.313,27.5-27.5
c0-6.701-2.399-12.84-6.382-17.611l61.515-86.92c2.836,0.988,5.88,1.532,9.052,1.532c15.188,0,27.5-12.312,27.5-27.5 c0-6.701-2.399-12.84-6.382-17.611l61.515-86.92c2.837,0.988,5.88,1.532,9.052,1.532c15.188,0,27.5-12.312,27.5-27.5
S256.998,29.75,241.811,29.75z"/> S256.998,29.75,241.811,29.75z"/>
</g>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -2,43 +2,43 @@
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="953.503px" height="211.614px" viewBox="0 0 953.503 211.614" enable-background="new 0 0 953.503 211.614" width="956.251px" height="214.137px" viewBox="0 -12.083 956.251 214.137" enable-background="new 0 -12.083 956.251 214.137"
xml:space="preserve"> xml:space="preserve">
<g> <g>
<path fill="none" d="M-7.087,418.282c12.933,0,25.607,0,38.673,0c-6.5-21.354-12.936-42.492-19.56-64.253 <path fill="none" d="M165.163,137.282c12.933,0,25.607,0,38.673,0c-6.5-21.354-12.936-42.492-19.56-64.253
C5.548,375.804-0.747,396.967-7.087,418.282z"/> C177.798,94.804,171.503,115.967,165.163,137.282z"/>
<path fill="#FFFFFF" d="M54.064,394.482c-7.594-21.64-15.215-43.269-22.824-64.903c-13.035,0-26.072,0-39.106,0.002 <path fill="#FFFFFF" d="M226.314,113.482c-7.594-21.64-15.215-43.269-22.824-64.903c-13.035,0-26.072,0-39.106,0.002
c-0.241,1.06-0.381,2.153-0.737,3.174c-14.675,42.06-29.37,84.111-44.058,126.164c-2.503,7.166-4.984,14.34-7.53,21.666 c-0.241,1.061-0.381,2.153-0.737,3.174c-14.675,42.061-29.37,84.111-44.058,126.164c-2.503,7.166-4.984,14.34-7.53,21.666
c11.54,0,22.78,0,34.162,0c3.614-11.881,7.2-23.67,10.784-35.455c18.283,0,36.34,0,54.529,0 c11.54,0,22.78,0,34.162,0c3.614-11.881,7.2-23.67,10.784-35.455c18.283,0,36.34,0,54.529,0
c3.627,11.914,7.212,23.691,10.781,35.412c11.493,0,22.693,0,34.128,0c-0.397-1.166-0.705-2.094-1.029-3.02 c3.627,11.914,7.212,23.691,10.781,35.412c11.493,0,22.693,0,34.128,0c-0.397-1.166-0.705-2.094-1.029-3.02
C73.466,449.84,63.776,422.157,54.064,394.482z M-7.087,418.282c6.34-21.314,12.635-42.478,19.113-64.253 C245.716,168.84,236.026,141.157,226.314,113.482z M165.163,137.282c6.34-21.314,12.635-42.478,19.113-64.253
c6.624,21.761,13.06,42.899,19.56,64.253C18.52,418.282,5.846,418.282-7.087,418.282z"/> c6.624,21.761,13.06,42.899,19.56,64.253C190.77,137.282,178.096,137.282,165.163,137.282z"/>
<path fill="#FFFFFF" d="M-59.334,330.243c-37.826,0-75.371,0-112.916,0c0,8.836,0,17.674,0.001,26.514c13.525,0,27.05,0,40.889,0 <path fill="#FFFFFF" d="M112.916,49.243c-37.826,0-75.371,0-112.916,0c0,8.836,0,17.674,0.001,26.514c13.525,0,27.05,0,40.889,0
c0,41.561,0,82.594,0,123.787c10.64,0,20.961,0,31.692,0c0-41.311,0-82.439,0-123.892c13.718,0,27.028,0,40.334,0 c0,41.563,0,82.594,0,123.787c10.64,0,20.961,0,31.692,0c0-41.311,0-82.438,0-123.892c13.718,0,27.028,0,40.334,0
C-59.334,347.68-59.334,339.04-59.334,330.243z"/> C112.916,66.68,112.916,58.04,112.916,49.243z"/>
<path fill="#FFFFFF" d="M219.484,330.409c-10.657,0-20.976,0-31.667,0c0,1.457,0,2.752,0,4.045c0,28.379,0.037,56.758-0.037,85.135 <path fill="#FFFFFF" d="M391.734,49.409c-10.657,0-20.976,0-31.667,0c0,1.457,0,2.752,0,4.045c0,28.379,0.037,56.758-0.037,85.135
c-0.012,4.623-0.206,9.287-0.851,13.857c-1.723,12.219-7.998,19.277-19.089,21.752c-6.441,1.436-12.932,1.305-19.396,0.002 c-0.012,4.623-0.206,9.287-0.851,13.857c-1.723,12.219-7.998,19.277-19.089,21.752c-6.441,1.436-12.932,1.305-19.396,0.002
c-9.917-2-16.129-8.066-18.262-17.797c-1.171-5.336-1.655-10.92-1.685-16.398c-0.158-28.928-0.074-57.86-0.074-86.791 c-9.917-2-16.129-8.066-18.262-17.797c-1.171-5.336-1.655-10.92-1.685-16.398c-0.158-28.928-0.074-57.859-0.074-86.791
c0-1.268,0-2.535,0-3.717c-10.869,0-21.188,0-31.639,0c0,1.023,0,1.793,0,2.562c0,31.47-0.018,62.94,0.032,94.411 c0-1.268,0-2.534,0-3.717c-10.869,0-21.188,0-31.639,0c0,1.023,0,1.793,0,2.563c0,31.471-0.018,62.939,0.032,94.41
c0.005,3.299,0.217,6.629,0.683,9.896c2.816,19.719,13.26,33.813,32.036,40.645c19.239,7.002,38.813,6.619,57.978-0.678 c0.005,3.301,0.217,6.629,0.683,9.896c2.816,19.719,13.26,33.813,32.036,40.645c19.239,7.002,38.813,6.619,57.978-0.678
c13.699-5.215,23.273-14.91,28.45-28.723c2.545-6.791,3.528-13.869,3.526-21.104c-0.011-31.363-0.005-62.723-0.005-94.082 c13.699-5.215,23.273-14.91,28.45-28.723c2.545-6.791,3.528-13.869,3.526-21.104c-0.011-31.363-0.005-62.723-0.005-94.082
C219.484,332.463,219.484,331.502,219.484,330.409z"/> C391.734,51.463,391.734,50.502,391.734,49.409z"/>
<path fill="#FFFFFF" d="M488.595,333.793c0-1.073,0-2.149,0-3.385c-10.631,0-20.952,0-31.668,0c0,1.598,0,2.803,0,4.008 <path fill="#FFFFFF" d="M660.845,52.793c0-1.073,0-2.149,0-3.385c-10.631,0-20.953,0-31.668,0c0,1.598,0,2.803,0,4.008
c0,28.488,0.036,56.979-0.037,85.467c-0.011,4.51-0.215,9.064-0.844,13.525c-1.734,12.314-8.183,19.514-19.379,21.869 c0,28.487,0.035,56.979-0.037,85.467c-0.012,4.51-0.215,9.064-0.844,13.525c-1.734,12.313-8.184,19.514-19.379,21.869
c-6.339,1.332-12.722,1.215-19.076-0.064c-9.912-1.996-16.146-8.039-18.291-17.77c-1.174-5.334-1.662-10.92-1.69-16.398 c-6.34,1.332-12.723,1.215-19.076-0.064c-9.912-1.996-16.146-8.039-18.291-17.77c-1.174-5.334-1.662-10.92-1.689-16.398
c-0.159-28.928-0.075-57.86-0.075-86.791c0-1.277,0-2.553,0-3.769c-10.835,0-21.158,0-31.641,0c0,1.009,0,1.772,0,2.537 c-0.16-28.928-0.076-57.86-0.076-86.791c0-1.277,0-2.553,0-3.77c-10.834,0-21.158,0-31.641,0c0,1.01,0,1.771,0,2.537
c0.003,31.581-0.016,63.161,0.034,94.743c0.005,3.189,0.229,6.406,0.676,9.563c2.806,19.723,13.238,33.82,32.007,40.674 c0.004,31.581-0.016,63.16,0.033,94.742c0.006,3.189,0.23,6.406,0.678,9.564c2.805,19.723,13.236,33.82,32.006,40.674
c19.354,7.068,39.046,6.631,58.289-0.77c13.549-5.215,23.032-14.887,28.161-28.588c2.623-7.006,3.555-14.305,3.549-21.764 c19.354,7.068,39.047,6.631,58.289-0.77c13.549-5.217,23.031-14.889,28.16-28.59c2.623-7.006,3.557-14.305,3.551-21.764
C488.578,395.852,488.595,364.823,488.595,333.793z"/> C660.828,114.852,660.845,83.823,660.845,52.793z"/>
<path fill="#FFFFFF" d="M236.383,356.745c13.484,0,26.794,0,40.523,0c0,41.445,0,82.566,0,123.836c10.655,0,20.887,0,31.803,0 <path fill="#FFFFFF" d="M408.633,75.745c13.484,0,26.794,0,40.523,0c0,41.445,0,82.566,0,123.836c10.655,0,20.887,0,31.802,0
c0-41.391,0-82.51-0.001-123.974c13.718,0,27.021,0,40.351,0c0-8.916,0-17.481,0-26.125c-37.661,0-75.134,0-112.676,0 c0-41.391,0-82.51,0-123.974c13.717,0,27.02,0,40.35,0c0-8.916,0-17.481,0-26.125c-37.66,0-75.133,0-112.675,0
C236.383,339.288,236.383,347.853,236.383,356.745z"/> C408.633,58.288,408.633,66.853,408.633,75.745z"/>
<path fill="#FFFFFF" d="M552.24,330.454c-10.699,0-21.014,0-31.4,0c0,50.158,0,100.104,0,150.119c30.779,0,61.322,0,92.055,0 <path fill="#FFFFFF" d="M724.49,49.454c-10.699,0-21.014,0-31.4,0c0,50.158,0,100.104,0,150.119c30.779,0,61.322,0,92.055,0
c0-8.973,0-17.535,0-26.453c-20.227,0-40.254,0-60.654,0C552.24,412.659,552.24,371.556,552.24,330.454z"/> c0-8.973,0-17.535,0-26.453c-20.227,0-40.254,0-60.652,0C724.49,131.659,724.49,90.556,724.49,49.454z"/>
<path fill="#FFFFFF" d="M664.878,330.476c-10.697,0-21.01,0-31.441,0c0,50.094,0,99.954,0,150.073c30.904,0,61.445,0,92.119,0 <path fill="#FFFFFF" d="M837.128,49.476c-10.697,0-21.01,0-31.441,0c0,50.094,0,99.954,0,150.073c30.904,0,61.445,0,92.119,0
c0-8.826,0-17.469,0-26.473c-20.246,0-40.273,0-60.678,0C664.878,412.618,664.878,371.515,664.878,330.476z"/> c0-8.826,0-17.469,0-26.473c-20.246,0-40.273,0-60.678,0C837.128,131.618,837.128,90.515,837.128,49.476z"/>
<path fill="#FFFFFF" d="M746.886,480.62c10.502,0,20.748,0,31.232,0c0-50.277,0-100.226,0-150.158c-10.563,0-20.879,0-31.232,0 <path fill="#FFFFFF" d="M919.136,199.62c10.502,0,20.748,0,31.232,0c0-50.277,0-100.226,0-150.158c-10.564,0-20.879,0-31.232,0
C746.886,380.631,746.886,430.583,746.886,480.62z"/> C919.136,99.631,919.136,149.583,919.136,199.62z"/>
<circle fill="#E5A00D" cx="762.501" cy="290.417" r="21.5"/> <circle fill="#E5A00D" cx="934.751" cy="9.417" r="21.5"/>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -11,7 +11,15 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<div class="padded-header" id="current-activity-header"> <div class="padded-header" id="current-activity-header">
<h3>Activity &nbsp;&nbsp;<small><span id="currentActivityHeader"></span></small></h3> <h3><span id="sessions-shortcut">Activity</span> &nbsp;&nbsp;
<small>
<span id="currentActivityHeader" style="display: none;">
Streams: <span id="currentActivityHeader-streams"></span> |
Bandwidth: <span id="currentActivityHeader-bandwidth"></span>
<span id="currentActivityHeader-bandwidth-tooltip" data-toggle="tooltip" title="Streaming Brain Estimate (Required Bandwidth)"><i class="fa fa-info-circle"></i></span>
</span>
</small>
</h3>
</div> </div>
<div id="currentActivity"> <div id="currentActivity">
<div class="text-muted" id="dashboard-checking-activity"><i class="fa fa-refresh fa-spin"></i> Checking for activity...</div> <div class="text-muted" id="dashboard-checking-activity"><i class="fa fa-refresh fa-spin"></i> Checking for activity...</div>
@@ -199,6 +207,7 @@
<script src="${http_root}js/moment-with-locale.js"></script> <script src="${http_root}js/moment-with-locale.js"></script>
<script src="${http_root}js/jquery.scrollbar.min.js"></script> <script src="${http_root}js/jquery.scrollbar.min.js"></script>
<script src="${http_root}js/jquery.mousewheel.min.js"></script> <script src="${http_root}js/jquery.mousewheel.min.js"></script>
<script src="${http_root}js/jquery.tripleclick.min.js"></script>
<script> <script>
var date_format = 'YYYY-MM-DD'; var date_format = 'YYYY-MM-DD';
var time_format = 'hh:mm a'; var time_format = 'hh:mm a';
@@ -238,6 +247,8 @@
var create_instances = []; var create_instances = [];
var activity_ready = true; var activity_ready = true;
$('#currentActivityHeader-bandwidth-tooltip').tooltip({ container: 'body', placement: 'right', delay: 50 });
function getCurrentActivity() { function getCurrentActivity() {
activity_ready = false; activity_ready = false;
@@ -266,7 +277,7 @@
% else: % else:
var msg_settings = '' var msg_settings = ''
% endif % endif
$('#currentActivityHeader').text(''); $('#currentActivityHeader').hide();
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.' + msg_settings + '</div>'); $('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.' + msg_settings + '</div>');
return return
} }
@@ -280,20 +291,25 @@
// Update the header stream counts // Update the header stream counts
var sc_dp = current_activity.stream_count_direct_play, var sc_dp = current_activity.stream_count_direct_play,
sc_ds = current_activity.stream_count_direct_stream, sc_ds = current_activity.stream_count_direct_stream,
sc_tc = current_activity.stream_count_transcode; sc_tc = current_activity.stream_count_transcode,
var header_count = stream_count + ' stream' + (stream_count > 1 ? 's' : '') + ' ('; total_bw = current_activity.total_bandwidth;
var streams_header = stream_count + ' stream' + (stream_count > 1 ? 's' : '') + ' (';
if (sc_dp) { if (sc_dp) {
header_count += sc_dp + ' direct play' + (sc_dp > 1 ? 's' : '') + ', '; streams_header += sc_dp + ' direct play' + (sc_dp > 1 ? 's' : '') + ', ';
} }
if (sc_ds) { if (sc_ds) {
header_count += sc_ds + ' direct stream' + (sc_ds > 1 ? 's' : '') + ', '; streams_header += sc_ds + ' direct stream' + (sc_ds > 1 ? 's' : '') + ', ';
} }
if (sc_tc) { if (sc_tc) {
header_count += sc_tc + ' transcode' + (sc_tc > 1 ? 's' : '') + ', '; streams_header += sc_tc + ' transcode' + (sc_tc > 1 ? 's' : '') + ', ';
} }
header_count = header_count.replace(/, $/, ''); streams_header = streams_header.replace(/, $/, '') + ')';
header_count += ')'; $('#currentActivityHeader-streams').text(streams_header);
$('#currentActivityHeader').text(header_count);
var bandwidth_header = (total_bw > 1000) ? ((total_bw / 1000).toFixed(1) + ' Mbps') : (total_bw + ' kbps');
$('#currentActivityHeader-bandwidth').text(bandwidth_header);
$('#currentActivityHeader').show();
sessions.forEach(function (session) { sessions.forEach(function (session) {
var s = new Proxy(session, defaultHandler); var s = new Proxy(session, defaultHandler);
@@ -504,7 +520,7 @@
}); });
} else { } else {
$('#currentActivityHeader').text(''); $('#currentActivityHeader').hide();
$('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">Nothing is currently being played.</div>'); $('#currentActivity').html('<div id="dashboard-no-activity" class="text-muted">Nothing is currently being played.</div>');
} }
@@ -632,6 +648,12 @@
}); });
}); });
}); });
$('#sessions-shortcut').on('tripleclick', function () {
$.getJSON('return_sessions_url', function(sessions_url) {
window.open(sessions_url, '_blank');
});
});
% endif % endif
</script> </script>
% endif % endif

View File

@@ -0,0 +1 @@
!function(t){function i(t){var i=jQuery(this);settings=jQuery.extend({},e,t.data);var a=i.data("triclick_clicks")||0,c=i.data("triclick_start")||0;0===a&&(c=t.timeStamp),0!=c&&t.timeStamp>c+settings.threshold&&(a=0,c=t.timeStamp),a+=1,3===a&&(a=0,c=0,t.type="tripleclick",void 0===jQuery.event.handle?jQuery.event.dispatch.apply(this,arguments):jQuery.event.handle.apply(this,arguments)),i.data("triclick_clicks",a),i.data("triclick_start",c)}var e={threshold:1e3};t.event.special.tripleclick={setup:function(e,a){t(this).bind("touchstart click.triple",e,i)},teardown:function(e){t(this).unbind("touchstart click.triple",i)}}}(jQuery);

View File

@@ -59,6 +59,8 @@ history_table_options = {
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Paused"><i class="fa fa-pause fa-fw"></i></span>'; state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Paused"><i class="fa fa-pause fa-fw"></i></span>';
} else if (rowData['state'] === 'buffering') { } else if (rowData['state'] === 'buffering') {
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Buffering"><i class="fa fa-spinner fa-fw"></i></span>'; state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Buffering"><i class="fa fa-spinner fa-fw"></i></span>';
} else if (rowData['state'] === 'stopped') {
state = '<span class="current-activity-tooltip" data-toggle="tooltip" title="Currently Stopped"><i class="fa fa-stop fa-fw"></i></span>';
} }
$(td).html('<div><div style="float: left;">' + state + '&nbsp;' + date + '</div></div>'); $(td).html('<div><div style="float: left;">' + state + '&nbsp;' + date + '</div></div>');
} else if (rowData['group_count'] > 1) { } else if (rowData['group_count'] > 1) {
@@ -203,7 +205,7 @@ history_table_options = {
"targets": [9], "targets": [9],
"data":"stopped", "data":"stopped",
"createdCell": function (td, cellData, rowData, row, col) { "createdCell": function (td, cellData, rowData, row, col) {
if (cellData === null || rowData['state'] != null) { if (cellData === null || rowData['state'] != "stopped") {
$(td).html('n/a'); $(td).html('n/a');
} else { } else {
$(td).html(moment(cellData,"X").format(time_format)); $(td).html(moment(cellData,"X").format(time_format));

View File

@@ -47,7 +47,7 @@ sync_table_options = {
}, },
{ {
"targets": [2], "targets": [2],
"data": "friendly_name", "data": "user",
"createdCell": function (td, cellData, rowData, row, col) { "createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') { if (cellData !== '') {
if (rowData['user_id']) { if (rowData['user_id']) {
@@ -63,7 +63,7 @@ sync_table_options = {
}, },
{ {
"targets": [3], "targets": [3],
"data": "title", "data": "sync_title",
"createdCell": function (td, cellData, rowData, row, col) { "createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') { if (cellData !== '') {
if (rowData['metadata_type'] !== '') { if (rowData['metadata_type'] !== '') {

View File

@@ -3,7 +3,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<title>Tautulli - ${title} | ${server_name}</title> <title>Tautulli - ${title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content=""> <meta name="description" content="">
<meta name="author" content=""> <meta name="author" content="">
@@ -14,28 +14,21 @@
<link href="${http_root}css/font-awesome.min.css" rel="stylesheet"> <link href="${http_root}css/font-awesome.min.css" rel="stylesheet">
<!-- Favicons --> <!-- Favicons -->
<link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.0"> <link rel="icon" type="image/png" sizes="32x32" href="${http_root}images/favicon/favicon-32x32.png?v=2.0.5">
<link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.0"> <link rel="icon" type="image/png" sizes="16x16" href="${http_root}images/favicon/favicon-16x16.png?v=2.0.5">
<link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.0"> <link rel="shortcut icon" href="${http_root}images/favicon/favicon.ico?v=2.0.5">
<!-- ICONS --> <!-- ICONS -->
<!-- Android >M39 icon --> <!-- Android -->
<link rel="icon" type="image/png" sizes="192x192" href="${http_root}images/favicon/android-chrome-192x192.png?v=2.0.0"> <link rel="manifest" href="${http_root}images/favicon/manifest.json?v=2.0.5">
<link rel="manifest" href="${http_root}json/Android-manifest.json?v=2.0.0"> <meta name="theme-color" content="#282a2d">
<meta name="theme-color" content="#1f1f1f">
<!-- Apple --> <!-- Apple -->
<link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.0"> <link rel="apple-touch-icon" sizes="180x180" href="${http_root}images/favicon/apple-touch-icon.png?v=2.0.5">
<link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.0.0" color="#1f1f1f"> <link rel="mask-icon" href="${http_root}images/favicon/safari-pinned-tab.svg?v=2.0.5" color="#282a2d">
<meta name="apple-mobile-web-app-title" content="Tautulli"> <meta name="apple-mobile-web-app-title" content="Tautulli">
<meta name="apple-mobile-web-app-capable" content="yes"> <!-- Microsoft -->
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="viewport" content="initial-scale=1">
<meta name="format-detection" content="telephone=no">
<!-- IE10 icon -->
<meta name="application-name" content="Tautulli"> <meta name="application-name" content="Tautulli">
<meta name="msapplication-TileColor" content="#1f1f1f"> <meta name="msapplication-config" content="${http_root}images/favicon/browserconfig.xml?v=2.0.5">
<meta name="msapplication-TileImage" content="${http_root}images/favicon/mstile-144x144.png?v=2.0.0">
<meta name="msapplication-config" content="${http_root}xml/IEconfig.xml?v=2.0.0" />
</head> </head>
<body> <body>
@@ -44,7 +37,7 @@
<div class="row"> <div class="row">
<div class="login-container"> <div class="login-container">
<div class="login-logo"> <div class="login-logo">
<img alt="Tautulli" src="${http_root}images/logo-tautulli.svg" height="100"> <object data="${http_root}images/logo-tautulli.svg" type="image/svg+xml" style="height: 100px;"></object>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-6 col-sm-offset-3"> <div class="col-sm-6 col-sm-offset-3">

View File

@@ -45,7 +45,7 @@ DOCUMENTATION :: END
% elif job in ('Check for active sessions', 'Check for recently added items') and plexpy.WS_CONNECTED: % elif job in ('Check for active sessions', 'Check for recently added items') and plexpy.WS_CONNECTED:
<tr> <tr>
<td>${job}</td> <td>${job}</td>
<td><i class="fa fa-sm fa-fw fa-check"></i> Using Websocket</td> <td><i class="fa fa-sm fa-fw fa-check"></i> Websocket</td>
<td>N/A</td> <td>N/A</td>
<td>N/A</td> <td>N/A</td>
<td>N/A</td> <td>N/A</td>

View File

@@ -653,7 +653,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="pms_token">PMS Token</label> <label for="pms_token">Plex.tv Account Token</label>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<div class="input-group"> <div class="input-group">
@@ -1115,7 +1115,8 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div style="text-align: center; margin-top: 20px; margin-bottom: 20px;"> <div style="text-align: center; margin-top: 20px; margin-bottom: 20px;">
<strong>Please read the <a href="#" target="_blank" id="guidelines-link">guidelines</a> in the README document <br />before submitting a new <span id="guidelines-type"></span>!</strong> <strong>Please read the <a href="${anon_url('https://github.com/%s/plexpy/blob/master/CONTRIBUTING.md' % plexpy.CONFIG.GIT_USER)}" target="_blank">guidelines</a>
in the CONTRIBUTING document <br />before submitting a new <span id="guidelines-type"></span>!</strong>
<br /><br /> <br /><br />
Your post may be removed for failure to follow the guidelines. Your post may be removed for failure to follow the guidelines.
</div> </div>
@@ -1136,7 +1137,8 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div style="text-align: center; margin-top: 20px; margin-bottom: 20px;"> <div style="text-align: center; margin-top: 20px; margin-bottom: 20px;">
<strong>Please read the <a href="#" target="_blank" id="faq-link">FAQ</a> before asking for help!</strong> <strong>Please read the <a href="${anon_url('https://github.com/%s/plexpy/wiki/Frequently-Asked-Questions-(FAQ)' % plexpy.CONFIG.GIT_USER)}" target="_blank">FAQ</a>
before asking for help!</strong>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">

View File

@@ -40,7 +40,7 @@
<th align="left" id="delete_row">Delete</th> <th align="left" id="delete_row">Delete</th>
<th align="left" id="state">State</th> <th align="left" id="state">State</th>
<th align="left" id="user">User</th> <th align="left" id="user">User</th>
<th align="left" id="title">Title</th> <th align="left" id="sync_title">Title</th>
<th align="left" id="type">Type</th> <th align="left" id="type">Type</th>
<th align="left" id="platform">Platform</th> <th align="left" id="platform">Platform</th>
<th align="left" id="device">Device</th> <th align="left" id="device">Device</th>

View File

@@ -39,6 +39,10 @@ from cherrypy import wsgiserver
try: try:
from OpenSSL import SSL from OpenSSL import SSL
from OpenSSL import crypto from OpenSSL import crypto
if hasattr(SSL, 'Connection'):
SSLConnectionType = SSL.Connection
else:
SSLConnectionType = SSL.ConnectionType
except ImportError: except ImportError:
SSL = None SSL = None
@@ -244,7 +248,7 @@ class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
return ssl_environ return ssl_environ
def makefile(self, sock, mode='r', bufsize=-1): def makefile(self, sock, mode='r', bufsize=-1):
if SSL and isinstance(sock, SSL.ConnectionType): if SSL and isinstance(sock, SSLConnectionType):
timeout = sock.gettimeout() timeout = sock.gettimeout()
f = SSL_fileobject(sock, mode, bufsize) f = SSL_fileobject(sock, mode, bufsize)
f.ssl_timeout = timeout f.ssl_timeout = timeout

View File

@@ -36,12 +36,14 @@ import activity_handler
import activity_pinger import activity_pinger
import config import config
import database import database
import libraries
import logger import logger
import mobile_app import mobile_app
import notification_handler import notification_handler
import notifiers import notifiers
import plextv import plextv
import pmsconnect import pmsconnect
import users
import versioncheck import versioncheck
import plexpy.config import plexpy.config
@@ -213,16 +215,15 @@ def initialize(config_file):
# Get the real PMS urls for SSL and remote access # Get the real PMS urls for SSL and remote access
if CONFIG.PMS_TOKEN and CONFIG.PMS_IP and CONFIG.PMS_PORT: if CONFIG.PMS_TOKEN and CONFIG.PMS_IP and CONFIG.PMS_PORT:
plextv.get_real_pms_url() plextv.get_server_resources()
pmsconnect.get_server_friendly_name()
# Refresh the users list on startup # Refresh the users list on startup
if CONFIG.PMS_TOKEN and CONFIG.REFRESH_USERS_ON_STARTUP: if CONFIG.PMS_TOKEN and CONFIG.REFRESH_USERS_ON_STARTUP:
plextv.refresh_users() users.refresh_users()
# Refresh the libraries list on startup # Refresh the libraries list on startup
if CONFIG.PMS_IP and CONFIG.PMS_TOKEN and CONFIG.REFRESH_LIBRARIES_ON_STARTUP: if CONFIG.PMS_IP and CONFIG.PMS_TOKEN and CONFIG.REFRESH_LIBRARIES_ON_STARTUP:
pmsconnect.refresh_libraries() libraries.refresh_libraries()
# Store the original umask # Store the original umask
UMASK = os.umask(0) UMASK = os.umask(0)
@@ -323,14 +324,8 @@ def initialize_scheduler():
hours=backup_hours, minutes=0, seconds=0, args=(True, True)) hours=backup_hours, minutes=0, seconds=0, args=(True, True))
if WS_CONNECTED and CONFIG.PMS_IP and CONFIG.PMS_TOKEN: if WS_CONNECTED and CONFIG.PMS_IP and CONFIG.PMS_TOKEN:
#schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions', schedule_job(plextv.get_server_resources, 'Refresh Plex server URLs',
# hours=0, minutes=0, seconds=1)
#schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
# hours=0, minutes=0, seconds=monitor_seconds * bool(CONFIG.NOTIFY_RECENTLY_ADDED))
schedule_job(plextv.get_real_pms_url, 'Refresh Plex server URLs',
hours=12 * (not bool(CONFIG.PMS_URL_MANUAL)), minutes=0, seconds=0) hours=12 * (not bool(CONFIG.PMS_URL_MANUAL)), minutes=0, seconds=0)
schedule_job(pmsconnect.get_server_friendly_name, 'Refresh Plex server name',
hours=12, minutes=0, seconds=0)
schedule_job(activity_pinger.check_server_access, 'Check for Plex remote access', schedule_job(activity_pinger.check_server_access, 'Check for Plex remote access',
hours=0, minutes=0, seconds=60 * bool(CONFIG.MONITOR_REMOTE_ACCESS)) hours=0, minutes=0, seconds=60 * bool(CONFIG.MONITOR_REMOTE_ACCESS))
@@ -341,9 +336,9 @@ def initialize_scheduler():
user_hours = CONFIG.REFRESH_USERS_INTERVAL if 1 <= CONFIG.REFRESH_USERS_INTERVAL <= 24 else 12 user_hours = CONFIG.REFRESH_USERS_INTERVAL if 1 <= CONFIG.REFRESH_USERS_INTERVAL <= 24 else 12
library_hours = CONFIG.REFRESH_LIBRARIES_INTERVAL if 1 <= CONFIG.REFRESH_LIBRARIES_INTERVAL <= 24 else 12 library_hours = CONFIG.REFRESH_LIBRARIES_INTERVAL if 1 <= CONFIG.REFRESH_LIBRARIES_INTERVAL <= 24 else 12
schedule_job(plextv.refresh_users, 'Refresh users list', schedule_job(users.refresh_users, 'Refresh users list',
hours=user_hours, minutes=0, seconds=0) hours=user_hours, minutes=0, seconds=0)
schedule_job(pmsconnect.refresh_libraries, 'Refresh libraries list', schedule_job(libraries.refresh_libraries, 'Refresh libraries list',
hours=library_hours, minutes=0, seconds=0) hours=library_hours, minutes=0, seconds=0)
schedule_job(activity_pinger.check_server_response, 'Check server response', schedule_job(activity_pinger.check_server_response, 'Check server response',
@@ -351,9 +346,7 @@ def initialize_scheduler():
else: else:
# Cancel all jobs # Cancel all jobs
schedule_job(plextv.get_real_pms_url, 'Refresh Plex server URLs', schedule_job(plextv.get_server_resources, 'Refresh Plex server URLs',
hours=0, minutes=0, seconds=0)
schedule_job(pmsconnect.get_server_friendly_name, 'Refresh Plex server name',
hours=0, minutes=0, seconds=0) hours=0, minutes=0, seconds=0)
schedule_job(activity_pinger.check_server_access, 'Check for Plex remote access', schedule_job(activity_pinger.check_server_access, 'Check for Plex remote access',
@@ -361,9 +354,9 @@ def initialize_scheduler():
schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates', schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates',
hours=0, minutes=0, seconds=0) hours=0, minutes=0, seconds=0)
schedule_job(plextv.refresh_users, 'Refresh users list', schedule_job(users.refresh_users, 'Refresh users list',
hours=0, minutes=0, seconds=0) hours=0, minutes=0, seconds=0)
schedule_job(pmsconnect.refresh_libraries, 'Refresh libraries list', schedule_job(libraries.refresh_libraries, 'Refresh libraries list',
hours=0, minutes=0, seconds=0) hours=0, minutes=0, seconds=0)
# Schedule job to reconnect websocket # Schedule job to reconnect websocket

View File

@@ -278,7 +278,6 @@ def check_server_response():
def check_server_access(): def check_server_access():
with monitor_lock: with monitor_lock:
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
server_response = pms_connect.get_server_response() server_response = pms_connect.get_server_response()

View File

@@ -32,10 +32,10 @@ import xmltodict
import plexpy import plexpy
import config import config
import database import database
import libraries
import logger import logger
import mobile_app import mobile_app
import plextv import users
import pmsconnect
class API2: class API2:
@@ -345,14 +345,14 @@ class API2:
def refresh_libraries_list(self, **kwargs): def refresh_libraries_list(self, **kwargs):
""" Refresh the Tautulli libraries list.""" """ Refresh the Tautulli libraries list."""
data = pmsconnect.refresh_libraries() data = libraries.refresh_libraries()
self._api_result_type = 'success' if data else 'error' self._api_result_type = 'success' if data else 'error'
return data return data
def refresh_users_list(self, **kwargs): def refresh_users_list(self, **kwargs):
""" Refresh the Tautulli users list.""" """ Refresh the Tautulli users list."""
data = plextv.refresh_users() data = users.refresh_users()
self._api_result_type = 'success' if data else 'error' self._api_result_type = 'success' if data else 'error'
return data return data

View File

@@ -140,10 +140,10 @@ SCHEDULER_LIST = ['Check GitHub for updates',
'Check for recently added items', 'Check for recently added items',
'Check for Plex updates', 'Check for Plex updates',
'Check for Plex remote access', 'Check for Plex remote access',
'Check server response',
'Refresh users list', 'Refresh users list',
'Refresh libraries list', 'Refresh libraries list',
'Refresh Plex server URLs', 'Refresh Plex server URLs',
'Refresh Plex server name',
'Backup Tautulli database', 'Backup Tautulli database',
'Backup Tautulli config' 'Backup Tautulli config'
] ]

View File

@@ -45,6 +45,7 @@ _CONFIG_DEFINITIONS = {
'PLEXWATCH_DATABASE': (str, 'PlexWatch', ''), 'PLEXWATCH_DATABASE': (str, 'PlexWatch', ''),
'PMS_IDENTIFIER': (str, 'PMS', ''), 'PMS_IDENTIFIER': (str, 'PMS', ''),
'PMS_IP': (str, 'PMS', '127.0.0.1'), 'PMS_IP': (str, 'PMS', '127.0.0.1'),
'PMS_IS_CLOUD': (int, 'PMS', 0),
'PMS_IS_REMOTE': (int, 'PMS', 0), 'PMS_IS_REMOTE': (int, 'PMS', 0),
'PMS_LOGS_FOLDER': (str, 'PMS', ''), 'PMS_LOGS_FOLDER': (str, 'PMS', ''),
'PMS_LOGS_LINE_CAP': (int, 'PMS', 1000), 'PMS_LOGS_LINE_CAP': (int, 'PMS', 1000),

View File

@@ -1,24 +1,28 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# This file is part of Tautulli. # This file is part of PlexPy.
# #
# Tautulli is free software: you can redistribute it and/or modify # PlexPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# Tautulli is distributed in the hope that it will be useful, # PlexPy is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>. # along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from httplib import HTTPSConnection from functools import partial
from httplib import HTTPConnection from multiprocessing.dummy import Pool as ThreadPool
import ssl from urlparse import urljoin
import certifi
from requests.packages import urllib3
from requests.packages.urllib3.exceptions import InsecureRequestWarning
import plexpy import plexpy
import helpers import helpers
@@ -30,94 +34,144 @@ class HTTPHandler(object):
Retrieve data from Plex Server Retrieve data from Plex Server
""" """
def __init__(self, host, port, token, ssl_verify=True): def __init__(self, urls, token=None, timeout=10, ssl_verify=True):
self.host = host if isinstance(urls, basestring):
self.port = str(port) self.urls = urls.split() or urls.split(',')
else:
self.urls = urls
self.token = token self.token = token
if self.token:
self.headers = {'X-Plex-Token': self.token}
else:
self.headers = {}
self.timeout = timeout
self.ssl_verify = ssl_verify self.ssl_verify = ssl_verify
""" self.valid_request_types = ('GET', 'POST', 'PUT', 'DELETE')
Handle the HTTP requests.
Output: object
"""
def make_request(self, def make_request(self,
uri=None, proto='HTTP', uri=None,
request_type='GET',
headers=None, headers=None,
request_type='GET',
output_format='raw', output_format='raw',
return_type=False, return_type=False,
no_token=False, no_token=False,
timeout=None): timeout=None,
callback=None):
"""
Handle the HTTP requests.
if timeout is None: Output: list
timeout = plexpy.CONFIG.PMS_TIMEOUT """
valid_request_types = ['GET', 'POST', 'PUT', 'DELETE'] self.uri = uri
self.request_type = request_type.upper()
self.output_format = output_format.lower()
self.return_type = return_type
self.callback = callback
self.timeout = timeout or self.timeout
if request_type.upper() not in valid_request_types: if self.request_type not in self.valid_request_types:
logger.debug(u"HTTP request made but unsupported request type given.") logger.debug(u"HTTP request made but unsupported request type given.")
return None return None
if uri: if uri:
if proto.upper() == 'HTTPS': request_urls = [urljoin(url, self.uri) for url in self.urls]
if not self.ssl_verify and hasattr(ssl, '_create_unverified_context'):
context = ssl._create_unverified_context() if no_token and headers:
handler = HTTPSConnection(host=self.host, port=self.port, timeout=timeout, context=context) self.headers = headers
logger.warn(u"Tautulli HTTP Handler :: Unverified HTTPS request made. This connection is not secure.") elif headers:
else: self.headers.update(headers)
handler = HTTPSConnection(host=self.host, port=self.port, timeout=timeout)
else: responses = []
handler = HTTPConnection(host=self.host, port=self.port, timeout=timeout) for r in self._http_requests_pool(request_urls):
responses.append(r)
return responses[0]
if not no_token:
if headers:
headers.update({'X-Plex-Token': self.token})
else: else:
headers = {'X-Plex-Token': self.token} logger.debug(u"HTTP request made but no enpoint given.")
return None
def _http_requests_pool(self, urls, workers=10, chunk=None):
"""Generator function to request urls in chunks"""
# From cpython
if chunk is None:
chunk, extra = divmod(len(urls), workers * 4)
if extra:
chunk += 1
if len(urls) == 0:
chunk = 0
if self.ssl_verify:
session = urllib3.PoolManager(cert_reqs='CERT_REQUIRED', ca_certs=certifi.where())
else:
urllib3.disable_warnings(InsecureRequestWarning)
session = urllib3.PoolManager()
part = partial(self._http_requests_urllib3, session=session)
if len(urls) == 1:
yield part(urls[0])
else:
pool = ThreadPool(workers)
try: try:
if headers: for work in pool.imap_unordered(part, urls, chunk):
handler.request(request_type, uri, headers=headers) yield work
else: except Exception as e:
handler.request(request_type, uri) logger.error(u"Failed to yield request: %s" % e)
response = handler.getresponse() finally:
request_status = response.status pool.close()
request_content = response.read() pool.join()
content_type = response.getheader('content-type')
def _http_requests_urllib3(self, url, session):
"""Request the data from the url"""
try:
r = session.request(self.request_type, url, headers=self.headers, timeout=self.timeout)
except IOError as e: except IOError as e:
logger.warn(u"Failed to access uri endpoint %s with error %s" % (uri, e)) logger.warn(u"Failed to access uri endpoint %s with error %s" % (self.uri, e))
return None return None
except Exception as e: except Exception as e:
logger.warn(u"Failed to access uri endpoint %s. Is your server maybe accepting SSL connections only? %s" % (uri, e)) logger.warn(u"Failed to access uri endpoint %s. Is your server maybe accepting SSL connections only? %s" % (self.uri, e))
return None return None
except: except:
logger.warn(u"Failed to access uri endpoint %s with Uncaught exception." % uri) logger.warn(u"Failed to access uri endpoint %s with Uncaught exception." % self.uri)
return None return None
if request_status in (200, 201): response_status = r.status
try: response_content = r.data
if output_format == 'dict': response_headers = r.headers
output = helpers.convert_xml_to_dict(request_content)
elif output_format == 'json':
output = helpers.convert_xml_to_json(request_content)
elif output_format == 'xml':
output = helpers.parse_xml(request_content)
else:
output = request_content
if return_type: if response_status in (200, 201):
return output, content_type return self._http_format_output(response_content, response_headers)
else:
logger.warn(u"Failed to access uri endpoint %s. Status code %r" % (self.uri, response_status))
return None
def _http_format_output(self, response_content, response_headers):
"""Formats the request response to the desired type"""
try:
if self.output_format == 'text':
output = response_content.decode('utf-8', 'ignore')
if self.output_format == 'dict':
output = helpers.convert_xml_to_dict(response_content.decode('utf-8', 'ignore'))
elif self.output_format == 'json':
output = helpers.convert_xml_to_json(response_content.decode('utf-8', 'ignore'))
elif self.output_format == 'xml':
output = helpers.parse_xml(response_content.decode('utf-8', 'ignore'))
else:
output = response_content
if self.callback:
return self.callback(output)
if self.return_type:
return output, response_headers['Content-Type']
return output return output
except Exception as e: except Exception as e:
logger.warn(u"Failed format response from uri %s to %s error %s" % (uri, output_format, e)) logger.warn(u"Failed format response from uri %s to %s error %s" % (self.uri, self.response_type, e))
return None
else:
logger.warn(u"Failed to access uri endpoint %s. Status code %r" % (uri, request_status))
return None
else:
logger.debug(u"HTTP request made but no enpoint given.")
return None return None

View File

@@ -27,6 +27,66 @@ import pmsconnect
import session import session
def refresh_libraries():
logger.info(u"Tautulli Libraries :: Requesting libraries list refresh...")
server_id = plexpy.CONFIG.PMS_IDENTIFIER
if not server_id:
logger.error(u"Tautulli Libraries :: No PMS identifier, cannot refresh libraries. Verify server in settings.")
return
library_sections = pmsconnect.PmsConnect().get_library_details()
if library_sections:
monitor_db = database.MonitorDatabase()
library_keys = []
new_keys = []
for section in library_sections:
section_keys = {'server_id': server_id,
'section_id': section['section_id']}
section_values = {'server_id': server_id,
'section_id': section['section_id'],
'section_name': section['section_name'],
'section_type': section['section_type'],
'thumb': section['thumb'],
'art': section['art'],
'count': section['count'],
'parent_count': section.get('parent_count', None),
'child_count': section.get('child_count', None),
}
result = monitor_db.upsert('library_sections', key_dict=section_keys, value_dict=section_values)
library_keys.append(section['section_id'])
if result == 'insert':
new_keys.append(section['section_id'])
if plexpy.CONFIG.HOME_LIBRARY_CARDS == ['first_run_wizard']:
plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', library_keys)
plexpy.CONFIG.write()
else:
new_keys = plexpy.CONFIG.HOME_LIBRARY_CARDS + new_keys
plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', new_keys)
plexpy.CONFIG.write()
#if plexpy.CONFIG.UPDATE_SECTION_IDS == 1 or plexpy.CONFIG.UPDATE_SECTION_IDS == -1:
# # Start library section_id update on it's own thread
# threading.Thread(target=libraries.update_section_ids).start()
#if plexpy.CONFIG.UPDATE_LABELS == 1 or plexpy.CONFIG.UPDATE_LABELS == -1:
# # Start library labels update on it's own thread
# threading.Thread(target=libraries.update_labels).start()
logger.info(u"Tautulli Libraries :: Libraries list refreshed.")
return True
else:
logger.warn(u"Tautulli Libraries :: Unable to refresh libraries list.")
return False
def update_section_ids(): def update_section_ids():
plexpy.CONFIG.UPDATE_SECTION_IDS = -1 plexpy.CONFIG.UPDATE_SECTION_IDS = -1
@@ -965,7 +1025,7 @@ class Libraries(object):
monitor_db = database.MonitorDatabase() monitor_db = database.MonitorDatabase()
# Refresh the PMS_URL to make sure the server_id is updated # Refresh the PMS_URL to make sure the server_id is updated
plextv.get_real_pms_url() plextv.get_server_resources()
server_id = plexpy.CONFIG.PMS_IDENTIFIER server_id = plexpy.CONFIG.PMS_IDENTIFIER

View File

@@ -1963,7 +1963,7 @@ class JOIN(Notifier):
deviceid_key = 'deviceId%s' % ('s' if len(self.config['device_id'].split(',')) > 1 else '') deviceid_key = 'deviceId%s' % ('s' if len(self.config['device_id'].split(',')) > 1 else '')
data = {'api_key': self.config['api_key'], data = {'apikey': self.config['api_key'],
deviceid_key: self.config['device_id'], deviceid_key: self.config['device_id'],
'text': body.encode("utf-8")} 'text': body.encode("utf-8")}
@@ -2783,12 +2783,12 @@ class SCRIPTS(Notifier):
return False return False
if error: if error:
err = '\n '.join([l for l in error.splitlines()]) err = '\n '.join([helpers.sanitize(l) for l in error.splitlines()])
logger.error(u"Tautulli Notifiers :: Script error: \n %s" % err) logger.error(u"Tautulli Notifiers :: Script error: \n %s" % err)
return False return False
if output: if output:
out = '\n '.join([l for l in output.splitlines()]) out = '\n '.join([helpers.sanitize(l) for l in output.splitlines()])
logger.debug(u"Tautulli Notifiers :: Script returned: \n %s" % out) logger.debug(u"Tautulli Notifiers :: Script returned: \n %s" % out)
if not self.script_killed: if not self.script_killed:

View File

@@ -23,7 +23,6 @@ import activity_processor
import database import database
import helpers import helpers
import logger import logger
import plextv
import users import users
@@ -284,7 +283,7 @@ def import_from_plexivity(database=None, table_name=None, import_ignore_interval
# Get the latest friends list so we can pull user id's # Get the latest friends list so we can pull user id's
try: try:
plextv.refresh_users() users.refresh_users()
except: except:
logger.debug(u"Tautulli Importer :: Unable to refresh the users list. Aborting import.") logger.debug(u"Tautulli Importer :: Unable to refresh the users list. Aborting import.")
return None return None

View File

@@ -18,11 +18,9 @@
import base64 import base64
import json import json
from xml.dom import minidom
import plexpy import plexpy
import common import common
import database
import helpers import helpers
import http_handler import http_handler
import logger import logger
@@ -31,129 +29,99 @@ import pmsconnect
import session import session
def refresh_users(): def get_server_resources(return_presence=False):
logger.info(u"Tautulli PlexTV :: Requesting users list refresh...") if not return_presence:
result = PlexTV().get_full_users_list() logger.info(u"Tautulli PlexTV :: Requesting resources for server...")
monitor_db = database.MonitorDatabase() server = {'pms_name': plexpy.CONFIG.PMS_NAME,
user_data = users.Users() 'pms_version': plexpy.CONFIG.PMS_VERSION,
'pms_platform': plexpy.CONFIG.PMS_PLATFORM,
if result: 'pms_ip': plexpy.CONFIG.PMS_IP,
for item in result: 'pms_port': plexpy.CONFIG.PMS_PORT,
'pms_ssl': plexpy.CONFIG.PMS_SSL,
shared_libraries = '' 'pms_is_remote': plexpy.CONFIG.PMS_IS_REMOTE,
user_tokens = user_data.get_tokens(user_id=item['user_id']) 'pms_is_cloud': plexpy.CONFIG.PMS_IS_CLOUD,
if user_tokens and user_tokens['server_token']: 'pms_url': plexpy.CONFIG.PMS_URL,
pms_connect = pmsconnect.PmsConnect(token=user_tokens['server_token']) 'pms_url_manual': plexpy.CONFIG.PMS_URL_MANUAL
library_details = pms_connect.get_server_children()
if library_details:
shared_libraries = ';'.join(d['section_id'] for d in library_details['libraries_list'])
else:
shared_libraries = ''
control_value_dict = {"user_id": item['user_id']}
new_value_dict = {"username": item['username'],
"thumb": item['thumb'],
"email": item['email'],
"is_home_user": item['is_home_user'],
"is_allow_sync": item['is_allow_sync'],
"is_restricted": item['is_restricted'],
"shared_libraries": shared_libraries,
"filter_all": item['filter_all'],
"filter_movies": item['filter_movies'],
"filter_tv": item['filter_tv'],
"filter_music": item['filter_music'],
"filter_photos": item['filter_photos']
} }
# Check if we've set a custom avatar if so don't overwrite it. if server['pms_url_manual'] and server['pms_ssl'] or server['pms_is_cloud']:
if item['user_id']: scheme = 'https'
avatar_urls = monitor_db.select('SELECT thumb, custom_avatar_url '
'FROM users WHERE user_id = ?',
[item['user_id']])
if avatar_urls:
if not avatar_urls[0]['custom_avatar_url'] or \
avatar_urls[0]['custom_avatar_url'] == avatar_urls[0]['thumb']:
new_value_dict['custom_avatar_url'] = item['thumb']
else: else:
new_value_dict['custom_avatar_url'] = item['thumb'] scheme = 'http'
monitor_db.upsert('users', new_value_dict, control_value_dict) fallback_url = '{scheme}://{hostname}:{port}'.format(scheme=scheme,
hostname=server['pms_ip'],
logger.info(u"Tautulli PlexTV :: Users list refreshed.") port=server['pms_port'])
return True
else:
logger.warn(u"Tautulli PlexTV :: Unable to refresh users list.")
return False
def get_real_pms_url():
logger.info(u"Tautulli PlexTV :: Requesting URLs for server...")
# Reset any current PMS_URL value
plexpy.CONFIG.__setattr__('PMS_URL', '')
plexpy.CONFIG.write()
fallback_url = 'http://{}:{}'.format(plexpy.CONFIG.PMS_IP, plexpy.CONFIG.PMS_PORT)
plex_tv = PlexTV() plex_tv = PlexTV()
result = plex_tv.get_server_urls(include_https=plexpy.CONFIG.PMS_SSL) result = plex_tv.get_server_connections(pms_identifier=plexpy.CONFIG.PMS_IDENTIFIER,
plexpass = plex_tv.get_plexpass_status() pms_ip=server['pms_ip'],
pms_port=server['pms_port'],
include_https=server['pms_ssl'])
connections = []
if result: if result:
plexpy.CONFIG.__setattr__('PMS_VERSION', result['version']) connections = result.pop('connections', [])
plexpy.CONFIG.__setattr__('PMS_PLATFORM', result['platform']) server.update(result)
plexpy.CONFIG.__setattr__('PMS_PLEXPASS', plexpass) presence = server.pop('pms_presence', 0)
connections = result['connections'] else:
connections = []
presence = 0
if return_presence:
return presence
plexpass = plex_tv.get_plexpass_status()
server['pms_plexpass'] = int(plexpass)
# Only need to retrieve PMS_URL if using SSL # Only need to retrieve PMS_URL if using SSL
if not plexpy.CONFIG.PMS_URL_MANUAL and plexpy.CONFIG.PMS_SSL: if not server['pms_url_manual'] and server['pms_ssl']:
if connections: if connections:
if plexpy.CONFIG.PMS_IS_REMOTE: if server['pms_is_remote']:
# Get all remote connections # Get all remote connections
conns = [c for c in connections if c['local'] == '0' and 'plex.direct' in c['uri']] conns = [c for c in connections if
c['local'] == '0' and ('plex.direct' in c['uri'] or 'plex.service' in c['uri'])]
else: else:
# Get all local connections # Get all local connections
conns = [c for c in connections if c['local'] == '1' and 'plex.direct' in c['uri']] conns = [c for c in connections if
c['local'] == '1' and ('plex.direct' in c['uri'] or 'plex.service' in c['uri'])]
if conns: if conns:
# Get connection with matching address, otherwise return first connection # Get connection with matching address, otherwise return first connection
conn = next((c for c in conns if c['address'] == plexpy.CONFIG.PMS_IP conn = next((c for c in conns if c['address'] == server['pms_ip']
and c['port'] == str(plexpy.CONFIG.PMS_PORT)), conns[0]) and c['port'] == str(server['pms_port'])), conns[0])
plexpy.CONFIG.__setattr__('PMS_URL', conn['uri']) server['pms_url'] = conn['uri']
plexpy.CONFIG.write()
logger.info(u"Tautulli PlexTV :: Server URL retrieved.") logger.info(u"Tautulli PlexTV :: Server URL retrieved.")
# get_server_urls() failed or PMS_URL not found, fallback url doesn't use SSL # get_server_urls() failed or PMS_URL not found, fallback url doesn't use SSL
if not plexpy.CONFIG.PMS_URL: if not server['pms_url']:
plexpy.CONFIG.__setattr__('PMS_URL', fallback_url) server['pms_url'] = fallback_url
plexpy.CONFIG.write()
logger.warn(u"Tautulli PlexTV :: Unable to retrieve server URLs. Using user-defined value without SSL.") logger.warn(u"Tautulli PlexTV :: Unable to retrieve server URLs. Using user-defined value without SSL.")
# Not using SSL, remote has no effect # Not using SSL, remote has no effect
else: else:
if plexpy.CONFIG.PMS_URL_MANUAL and plexpy.CONFIG.PMS_SSL: server['pms_url'] = fallback_url
fallback_url = fallback_url.replace('http://', 'https://')
plexpy.CONFIG.__setattr__('PMS_URL', fallback_url)
plexpy.CONFIG.write()
logger.info(u"Tautulli PlexTV :: Using user-defined URL.") logger.info(u"Tautulli PlexTV :: Using user-defined URL.")
plexpy.CONFIG.process_kwargs(server)
plexpy.CONFIG.write()
class PlexTV(object): class PlexTV(object):
""" """
Plex.tv authentication Plex.tv authentication
""" """
def __init__(self, username='', password='', token=None): def __init__(self, username=None, password=None, token=None):
self.protocol = 'HTTPS'
self.username = username self.username = username
self.password = password self.password = password
self.token = token
self.urls = 'https://plex.tv'
self.timeout = plexpy.CONFIG.PMS_TIMEOUT
self.ssl_verify = plexpy.CONFIG.VERIFY_SSL_CERT self.ssl_verify = plexpy.CONFIG.VERIFY_SSL_CERT
if not token: if not self.token:
# Check if we should use the admin token, or the guest server token # Check if we should use the admin token, or the guest server token
if session.get_session_user_id(): if session.get_session_user_id():
user_data = users.Users() user_data = users.Users()
@@ -161,12 +129,14 @@ class PlexTV(object):
self.token = user_tokens['server_token'] self.token = user_tokens['server_token']
else: else:
self.token = plexpy.CONFIG.PMS_TOKEN self.token = plexpy.CONFIG.PMS_TOKEN
else:
self.token = token
self.request_handler = http_handler.HTTPHandler(host='plex.tv', if not self.token:
port=443, logger.error(u"Tautulli PlexTV :: PlexTV called, but no token provided.")
return
self.request_handler = http_handler.HTTPHandler(urls=self.urls,
token=self.token, token=self.token,
timeout=self.timeout,
ssl_verify=self.ssl_verify) ssl_verify=self.ssl_verify)
def get_plex_auth(self, output_format='raw'): def get_plex_auth(self, output_format='raw'):
@@ -183,7 +153,6 @@ class PlexTV(object):
} }
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='POST', request_type='POST',
headers=headers, headers=headers,
output_format=output_format, output_format=output_format,
@@ -265,7 +234,6 @@ class PlexTV(object):
def get_plextv_friends(self, output_format=''): def get_plextv_friends(self, output_format=''):
uri = '/api/users' uri = '/api/users'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -274,7 +242,6 @@ class PlexTV(object):
def get_plextv_user_details(self, output_format=''): def get_plextv_user_details(self, output_format=''):
uri = '/users/account' uri = '/users/account'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -283,7 +250,6 @@ class PlexTV(object):
def get_plextv_devices_list(self, output_format=''): def get_plextv_devices_list(self, output_format=''):
uri = '/devices.xml' uri = '/devices.xml'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -292,7 +258,6 @@ class PlexTV(object):
def get_plextv_server_list(self, output_format=''): def get_plextv_server_list(self, output_format=''):
uri = '/pms/servers.xml' uri = '/pms/servers.xml'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -301,7 +266,6 @@ class PlexTV(object):
def get_plextv_sync_lists(self, machine_id='', output_format=''): def get_plextv_sync_lists(self, machine_id='', output_format=''):
uri = '/servers/%s/sync_lists' % machine_id uri = '/servers/%s/sync_lists' % machine_id
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -313,7 +277,6 @@ class PlexTV(object):
else: else:
uri = '/api/resources' uri = '/api/resources'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -325,7 +288,6 @@ class PlexTV(object):
else: else:
uri = '/api/downloads/1.json' uri = '/api/downloads/1.json'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -334,7 +296,6 @@ class PlexTV(object):
def delete_plextv_device(self, device_id='', output_format=''): def delete_plextv_device(self, device_id='', output_format=''):
uri = '/devices/%s.xml' % device_id uri = '/devices/%s.xml' % device_id
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='DELETE', request_type='DELETE',
output_format=output_format) output_format=output_format)
@@ -343,7 +304,6 @@ class PlexTV(object):
def delete_plextv_device_sync_lists(self, client_id='', output_format=''): def delete_plextv_device_sync_lists(self, client_id='', output_format=''):
uri = '/devices/%s/sync_items' % client_id uri = '/devices/%s/sync_items' % client_id
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -352,30 +312,22 @@ class PlexTV(object):
def delete_plextv_sync(self, client_id='', sync_id='', output_format=''): def delete_plextv_sync(self, client_id='', sync_id='', output_format=''):
uri = '/devices/%s/sync_items/%s' % (client_id, sync_id) uri = '/devices/%s/sync_items/%s' % (client_id, sync_id)
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='DELETE', request_type='DELETE',
output_format=output_format) output_format=output_format)
return request return request
def get_full_users_list(self): def get_full_users_list(self):
friends_list = self.get_plextv_friends() friends_list = self.get_plextv_friends(output_format='xml')
own_account = self.get_plextv_user_details() own_account = self.get_plextv_user_details(output_format='xml')
users_list = [] users_list = []
try: try:
xml_parse = minidom.parseString(own_account) xml_head = own_account.getElementsByTagName('user')
except Exception as e: except Exception as e:
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_full_users_list own account: %s" % e) logger.warn(u"Tautulli PlexTV :: Unable to parse own account XML for get_full_users_list: %s." % e)
return [] return {}
except:
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_full_users_list own account.")
return []
xml_head = xml_parse.getElementsByTagName('user')
if not xml_head:
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_full_users_list.")
else:
for a in xml_head: for a in xml_head:
own_details = {"user_id": helpers.get_xml_attr(a, 'id'), own_details = {"user_id": helpers.get_xml_attr(a, 'id'),
"username": helpers.get_xml_attr(a, 'username'), "username": helpers.get_xml_attr(a, 'username'),
@@ -394,18 +346,11 @@ class PlexTV(object):
users_list.append(own_details) users_list.append(own_details)
try: try:
xml_parse = minidom.parseString(friends_list) xml_head = friends_list.getElementsByTagName('User')
except Exception as e: except Exception as e:
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_full_users_list friends list: %s" % e) logger.warn(u"Tautulli PlexTV :: Unable to parse friends list XML for get_full_users_list: %s." % e)
return [] return {}
except:
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_full_users_list friends list.")
return []
xml_head = xml_parse.getElementsByTagName('User')
if not xml_head:
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_full_users_list.")
else:
for a in xml_head: for a in xml_head:
friend = {"user_id": helpers.get_xml_attr(a, 'id'), friend = {"user_id": helpers.get_xml_attr(a, 'id'),
"username": helpers.get_xml_attr(a, 'title'), "username": helpers.get_xml_attr(a, 'title'),
@@ -425,34 +370,31 @@ class PlexTV(object):
return users_list return users_list
def get_synced_items(self, machine_id=None, client_id_filter=None, user_id_filter=None, rating_key_filter=None): def get_synced_items(self, machine_id=None, client_id_filter=None, user_id_filter=None,
sync_list = self.get_plextv_sync_lists(machine_id) rating_key_filter=None, sync_id_filter=None):
if machine_id is None:
machine_id = plexpy.CONFIG.PMS_IDENTIFIER
sync_list = self.get_plextv_sync_lists(machine_id, output_format='xml')
user_data = users.Users() user_data = users.Users()
synced_items = [] synced_items = []
try: try:
xml_parse = minidom.parseString(sync_list) xml_head = sync_list.getElementsByTagName('SyncList')
except Exception as e: except Exception as e:
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_synced_items: %s" % e) logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_synced_items: %s." % e)
return [] return {}
except:
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_synced_items.")
return []
xml_head = xml_parse.getElementsByTagName('SyncList')
if not xml_head:
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_synced_items.")
else:
for a in xml_head: for a in xml_head:
client_id = helpers.get_xml_attr(a, 'clientIdentifier') client_id = helpers.get_xml_attr(a, 'clientIdentifier')
# Filter by client_id # Filter by client_id
if client_id_filter and client_id_filter != client_id: if client_id_filter and str(client_id_filter) != client_id:
continue continue
sync_id = helpers.get_xml_attr(a, 'id') sync_list_id = helpers.get_xml_attr(a, 'id')
sync_device = a.getElementsByTagName('Device') sync_device = a.getElementsByTagName('Device')
for device in sync_device: for device in sync_device:
@@ -473,7 +415,7 @@ class PlexTV(object):
device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt') device_last_seen = helpers.get_xml_attr(device, 'lastSeenAt')
# Filter by user_id # Filter by user_id
if user_id_filter and user_id_filter != device_user_id: if user_id_filter and str(user_id_filter) != device_user_id:
continue continue
for synced in a.getElementsByTagName('SyncItems'): for synced in a.getElementsByTagName('SyncItems'):
@@ -487,10 +429,15 @@ class PlexTV(object):
for idx, item in enumerate(clean_uri) if item == 'metadata'), None) for idx, item in enumerate(clean_uri) if item == 'metadata'), None)
# Filter by rating_key # Filter by rating_key
if rating_key_filter and rating_key_filter != rating_key: if rating_key_filter and str(rating_key_filter) != rating_key:
continue continue
sync_id = helpers.get_xml_attr(item, 'id') sync_id = helpers.get_xml_attr(item, 'id')
# Filter by sync_id
if sync_id_filter and str(sync_id_filter) != sync_id:
continue
sync_version = helpers.get_xml_attr(item, 'version') sync_version = helpers.get_xml_attr(item, 'version')
sync_root_title = helpers.get_xml_attr(item, 'rootTitle') sync_root_title = helpers.get_xml_attr(item, 'rootTitle')
sync_title = helpers.get_xml_attr(item, 'title') sync_title = helpers.get_xml_attr(item, 'title')
@@ -520,11 +467,11 @@ class PlexTV(object):
sync_details = {"device_name": helpers.sanitize(device_name), sync_details = {"device_name": helpers.sanitize(device_name),
"platform": helpers.sanitize(device_platform), "platform": helpers.sanitize(device_platform),
"username": helpers.sanitize(device_username),
"friendly_name": helpers.sanitize(device_friendly_name),
"user_id": device_user_id, "user_id": device_user_id,
"user": helpers.sanitize(device_friendly_name),
"username": helpers.sanitize(device_username),
"root_title": helpers.sanitize(sync_root_title), "root_title": helpers.sanitize(sync_root_title),
"title": helpers.sanitize(sync_title), "sync_title": helpers.sanitize(sync_title),
"metadata_type": sync_metadata_type, "metadata_type": sync_metadata_type,
"content_type": sync_content_type, "content_type": sync_content_type,
"rating_key": rating_key, "rating_key": rating_key,
@@ -550,27 +497,16 @@ class PlexTV(object):
logger.info(u"Tautulli PlexTV :: Deleting sync item '%s'." % sync_id) logger.info(u"Tautulli PlexTV :: Deleting sync item '%s'." % sync_id)
self.delete_plextv_sync(client_id=client_id, sync_id=sync_id) self.delete_plextv_sync(client_id=client_id, sync_id=sync_id)
def get_server_urls(self, include_https=True): def get_server_connections(self, pms_identifier='', pms_ip='', pms_port=32400, include_https=True):
if plexpy.CONFIG.PMS_IDENTIFIER: if not pms_identifier:
server_id = plexpy.CONFIG.PMS_IDENTIFIER logger.error(u"Tautulli PlexTV :: Unable to retrieve server connections: no pms_identifier provided.")
else:
logger.error(u"Tautulli PlexTV :: Unable to retrieve server identity.")
return {} return {}
plextv_resources = self.get_plextv_resources(include_https=include_https) plextv_resources = self.get_plextv_resources(include_https=include_https,
output_format='xml')
try: try:
xml_parse = minidom.parseString(plextv_resources) xml_head = plextv_resources.getElementsByTagName('Device')
except Exception as e:
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_server_urls: %s" % e)
return {}
except:
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_server_urls.")
return {}
try:
xml_head = xml_parse.getElementsByTagName('Device')
except Exception as e: except Exception as e:
logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_server_urls: %s." % e) logger.warn(u"Tautulli PlexTV :: Unable to parse XML for get_server_urls: %s." % e)
return {} return {}
@@ -580,16 +516,20 @@ class PlexTV(object):
conn = [] conn = []
connections = device.getElementsByTagName('Connection') connections = device.getElementsByTagName('Connection')
server = {"platform": helpers.get_xml_attr(device, 'platform'), server = {'pms_identifier': helpers.get_xml_attr(device, 'clientIdentifier'),
"version": helpers.get_xml_attr(device, 'productVersion') 'pms_name': helpers.get_xml_attr(device, 'name'),
'pms_version': helpers.get_xml_attr(device, 'productVersion'),
'pms_platform': helpers.get_xml_attr(device, 'platform'),
'pms_presence': helpers.get_xml_attr(device, 'presence'),
'pms_is_cloud': 1 if helpers.get_xml_attr(device, 'platform') == 'Cloud' else 0
} }
for c in connections: for c in connections:
server_details = {"protocol": helpers.get_xml_attr(c, 'protocol'), server_details = {'protocol': helpers.get_xml_attr(c, 'protocol'),
"address": helpers.get_xml_attr(c, 'address'), 'address': helpers.get_xml_attr(c, 'address'),
"port": helpers.get_xml_attr(c, 'port'), 'port': helpers.get_xml_attr(c, 'port'),
"uri": helpers.get_xml_attr(c, 'uri'), 'uri': helpers.get_xml_attr(c, 'uri'),
"local": helpers.get_xml_attr(c, 'local') 'local': helpers.get_xml_attr(c, 'local')
} }
conn.append(server_details) conn.append(server_details)
@@ -600,7 +540,7 @@ class PlexTV(object):
# Try to match the device # Try to match the device
for a in xml_head: for a in xml_head:
if helpers.get_xml_attr(a, 'clientIdentifier') == server_id: if helpers.get_xml_attr(a, 'clientIdentifier') == pms_identifier:
server = get_connections(a) server = get_connections(a)
break break
@@ -612,15 +552,8 @@ class PlexTV(object):
connections = a.getElementsByTagName('Connection') connections = a.getElementsByTagName('Connection')
for connection in connections: for connection in connections:
if helpers.get_xml_attr(connection, 'address') == plexpy.CONFIG.PMS_IP and \ if helpers.get_xml_attr(connection, 'address') == pms_ip and \
int(helpers.get_xml_attr(connection, 'port')) == plexpy.CONFIG.PMS_PORT: helpers.get_xml_attr(connection, 'port') == str(pms_port):
plexpy.CONFIG.PMS_IDENTIFIER = helpers.get_xml_attr(a, 'clientIdentifier')
plexpy.CONFIG.write()
logger.info(u"Tautulli PlexTV :: PMS identifier changed from %s to %s."
% (server_id, plexpy.CONFIG.PMS_IDENTIFIER))
server = get_connections(a) server = get_connections(a)
break break
@@ -649,7 +582,7 @@ class PlexTV(object):
return server_times return server_times
def discover(self, include_cloud=True): def discover(self, include_cloud=True, all_servers=False):
""" Query plex for all servers online. Returns the ones you own in a selectize format """ """ Query plex for all servers online. Returns the ones you own in a selectize format """
servers = self.get_plextv_resources(include_https=True, output_format='xml') servers = self.get_plextv_resources(include_https=True, output_format='xml')
clean_servers = [] clean_servers = []
@@ -679,6 +612,7 @@ class PlexTV(object):
connections = d.getElementsByTagName('Connection') connections = d.getElementsByTagName('Connection')
for c in connections: for c in connections:
if not all_servers:
# If this is a remote server don't show any local IPs. # If this is a remote server don't show any local IPs.
if helpers.get_xml_attr(d, 'publicAddressMatches') == '0' and \ if helpers.get_xml_attr(d, 'publicAddressMatches') == '0' and \
helpers.get_xml_attr(c, 'local') == '1': helpers.get_xml_attr(c, 'local') == '1':
@@ -770,8 +704,6 @@ class PlexTV(object):
return True return True
else: else:
logger.debug(u"Tautulli PlexTV :: Plex Pass subscription not found.") logger.debug(u"Tautulli PlexTV :: Plex Pass subscription not found.")
plexpy.CONFIG.__setattr__('PMS_PLEXPASS', 0)
plexpy.CONFIG.write()
return False return False
def get_devices_list(self): def get_devices_list(self):

View File

@@ -22,7 +22,6 @@ import activity_processor
import database import database
import helpers import helpers
import logger import logger
import plextv
import users import users
@@ -275,7 +274,7 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval
# Get the latest friends list so we can pull user id's # Get the latest friends list so we can pull user id's
try: try:
plextv.refresh_users() users.refresh_users()
except: except:
logger.debug(u"Tautulli Importer :: Unable to refresh the users list. Aborting import.") logger.debug(u"Tautulli Importer :: Unable to refresh the users list. Aborting import.")
return None return None

View File

@@ -13,16 +13,12 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Tautulli. If not, see <http://www.gnu.org/licenses/>. # along with Tautulli. If not, see <http://www.gnu.org/licenses/>.
import threading
import urllib import urllib
from urlparse import urlparse
import plexpy import plexpy
import common import common
import database
import helpers import helpers
import http_handler import http_handler
import libraries
import logger import logger
import plextv import plextv
import session import session
@@ -49,83 +45,23 @@ def get_server_friendly_name():
return server_name return server_name
def refresh_libraries():
logger.info(u"Tautulli Pmsconnect :: Requesting libraries list refresh...")
server_id = plexpy.CONFIG.PMS_IDENTIFIER
if not server_id:
logger.error(u"Tautulli Pmsconnect :: No PMS identifier, cannot refresh libraries. Verify server in settings.")
return
library_sections = PmsConnect().get_library_details()
if library_sections:
monitor_db = database.MonitorDatabase()
library_keys = []
new_keys = []
for section in library_sections:
section_keys = {'server_id': server_id,
'section_id': section['section_id']}
section_values = {'server_id': server_id,
'section_id': section['section_id'],
'section_name': section['section_name'],
'section_type': section['section_type'],
'thumb': section['thumb'],
'art': section['art'],
'count': section['count'],
'parent_count': section.get('parent_count', None),
'child_count': section.get('child_count', None),
}
result = monitor_db.upsert('library_sections', key_dict=section_keys, value_dict=section_values)
library_keys.append(section['section_id'])
if result == 'insert':
new_keys.append(section['section_id'])
if plexpy.CONFIG.HOME_LIBRARY_CARDS == ['first_run_wizard']:
plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', library_keys)
plexpy.CONFIG.write()
else:
new_keys = plexpy.CONFIG.HOME_LIBRARY_CARDS + new_keys
plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', new_keys)
plexpy.CONFIG.write()
#if plexpy.CONFIG.UPDATE_SECTION_IDS == 1 or plexpy.CONFIG.UPDATE_SECTION_IDS == -1:
# # Start library section_id update on it's own thread
# threading.Thread(target=libraries.update_section_ids).start()
#if plexpy.CONFIG.UPDATE_LABELS == 1 or plexpy.CONFIG.UPDATE_LABELS == -1:
# # Start library labels update on it's own thread
# threading.Thread(target=libraries.update_labels).start()
logger.info(u"Tautulli Pmsconnect :: Libraries list refreshed.")
return True
else:
logger.warn(u"Tautulli Pmsconnect :: Unable to refresh libraries list.")
return False
class PmsConnect(object): class PmsConnect(object):
""" """
Retrieve data from Plex Server Retrieve data from Plex Server
""" """
def __init__(self, token=None): def __init__(self, url=None, token=None):
if plexpy.CONFIG.PMS_URL: self.url = url
url_parsed = urlparse(plexpy.CONFIG.PMS_URL) self.token = token
hostname = url_parsed.hostname
port = url_parsed.port
self.protocol = url_parsed.scheme
else:
hostname = plexpy.CONFIG.PMS_IP
port = plexpy.CONFIG.PMS_PORT
self.protocol = 'http'
if not token: if not self.url and plexpy.CONFIG.PMS_URL:
self.url = plexpy.CONFIG.PMS_URL
elif not self.url:
self.url = 'http://{hostname}:{port}'.format(hostname=plexpy.CONFIG.PMS_IP,
port=plexpy.CONFIG.PMS_PORT)
self.timeout = plexpy.CONFIG.PMS_TIMEOUT
if not self.token:
# Check if we should use the admin token, or the guest server token # Check if we should use the admin token, or the guest server token
if session.get_session_user_id(): if session.get_session_user_id():
user_data = users.Users() user_data = users.Users()
@@ -133,12 +69,10 @@ class PmsConnect(object):
self.token = user_tokens['server_token'] self.token = user_tokens['server_token']
else: else:
self.token = plexpy.CONFIG.PMS_TOKEN self.token = plexpy.CONFIG.PMS_TOKEN
else:
self.token = token
self.request_handler = http_handler.HTTPHandler(host=hostname, self.request_handler = http_handler.HTTPHandler(urls=self.url,
port=port, token=self.token,
token=self.token) timeout=self.timeout)
def get_sessions(self, output_format=''): def get_sessions(self, output_format=''):
""" """
@@ -150,7 +84,6 @@ class PmsConnect(object):
""" """
uri = '/status/sessions' uri = '/status/sessions'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -166,7 +99,6 @@ class PmsConnect(object):
""" """
uri = '/status/sessions/terminate?sessionId=%s&reason=%s' % (session_id, reason) uri = '/status/sessions/terminate?sessionId=%s&reason=%s' % (session_id, reason)
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -183,7 +115,6 @@ class PmsConnect(object):
""" """
uri = '/library/metadata/' + rating_key uri = '/library/metadata/' + rating_key
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -200,7 +131,6 @@ class PmsConnect(object):
""" """
uri = '/library/metadata/' + rating_key + '/children' uri = '/library/metadata/' + rating_key + '/children'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -217,7 +147,6 @@ class PmsConnect(object):
""" """
uri = '/library/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s' % (start, count) uri = '/library/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s' % (start, count)
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -234,7 +163,6 @@ class PmsConnect(object):
""" """
uri = '/library/sections/%s/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s' % (section_id, start, count) uri = '/library/sections/%s/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s' % (section_id, start, count)
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -251,7 +179,6 @@ class PmsConnect(object):
""" """
uri = '/library/metadata/' + rating_key + '/children' uri = '/library/metadata/' + rating_key + '/children'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -285,7 +212,6 @@ class PmsConnect(object):
""" """
uri = '/library/metadata/' + rating_key + '/allLeaves' uri = '/library/metadata/' + rating_key + '/allLeaves'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -301,7 +227,6 @@ class PmsConnect(object):
""" """
uri = '/servers' uri = '/servers'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -317,7 +242,6 @@ class PmsConnect(object):
""" """
uri = '/:/prefs' uri = '/:/prefs'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -333,7 +257,6 @@ class PmsConnect(object):
""" """
uri = '/identity' uri = '/identity'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -349,7 +272,6 @@ class PmsConnect(object):
""" """
uri = '/library/sections' uri = '/library/sections'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -368,7 +290,6 @@ class PmsConnect(object):
uri = '/library/sections/' + section_id + '/' + list_type + '?X-Plex-Container-Start=0' + count + sort_type + label_key uri = '/library/sections/' + section_id + '/' + list_type + '?X-Plex-Container-Start=0' + count + sort_type + label_key
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -384,7 +305,6 @@ class PmsConnect(object):
""" """
uri = '/library/sections/' + section_id + '/label' uri = '/library/sections/' + section_id + '/label'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -401,7 +321,6 @@ class PmsConnect(object):
""" """
uri = '/sync/items/' + sync_id uri = '/sync/items/' + sync_id
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -417,7 +336,6 @@ class PmsConnect(object):
""" """
uri = '/sync/transcodeQueue' uri = '/sync/transcodeQueue'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -433,7 +351,6 @@ class PmsConnect(object):
""" """
uri = '/hubs/search?query=' + urllib.quote(query.encode('utf8')) + '&limit=' + limit + '&includeCollections=1' uri = '/hubs/search?query=' + urllib.quote(query.encode('utf8')) + '&limit=' + limit + '&includeCollections=1'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -449,7 +366,6 @@ class PmsConnect(object):
""" """
uri = '/myplex/account' uri = '/myplex/account'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -465,7 +381,6 @@ class PmsConnect(object):
""" """
uri = '/myplex/refreshReachability' uri = '/myplex/refreshReachability'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='PUT') request_type='PUT')
return request return request
@@ -480,7 +395,6 @@ class PmsConnect(object):
""" """
uri = '/updater/check?download=0' uri = '/updater/check?download=0'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='PUT', request_type='PUT',
output_format=output_format) output_format=output_format)
@@ -496,7 +410,6 @@ class PmsConnect(object):
""" """
uri = '/updater/status' uri = '/updater/status'
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -515,7 +428,6 @@ class PmsConnect(object):
""" """
uri = '/hubs/home/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s&type=%s' % (start, count, type) uri = '/hubs/home/recentlyAdded?X-Plex-Container-Start=%s&X-Plex-Container-Size=%s&type=%s' % (start, count, type)
request = self.request_handler.make_request(uri=uri, request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
output_format=output_format) output_format=output_format)
@@ -1499,8 +1411,7 @@ class PmsConnect(object):
if media_type not in ('photo', 'clip') and not session.getElementsByTagName('Session') \ 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': and helpers.get_xml_attr(session, 'ratingKey').isdigit() and transcode_decision == 'direct play':
plex_tv = plextv.PlexTV() plex_tv = plextv.PlexTV()
synced_items = plex_tv.get_synced_items(machine_id=plexpy.CONFIG.PMS_IDENTIFIER, synced_items = plex_tv.get_synced_items(client_id_filter=player_details['machine_id'],
client_id_filter=player_details['machine_id'],
rating_key_filter=rating_key) rating_key_filter=rating_key)
if synced_items: if synced_items:
sync_id = synced_items[0]['sync_id'] sync_id = synced_items[0]['sync_id']
@@ -2334,7 +2245,6 @@ class PmsConnect(object):
uri = '/photo/:/transcode?%s' % urllib.urlencode(params) uri = '/photo/:/transcode?%s' % urllib.urlencode(params)
result = self.request_handler.make_request(uri=uri, result = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET', request_type='GET',
return_type=True) return_type=True)

View File

@@ -23,9 +23,67 @@ import datatables
import helpers import helpers
import logger import logger
import plextv import plextv
import pmsconnect
import session import session
def refresh_users():
logger.info(u"Tautulli Users :: Requesting users list refresh...")
result = plextv.PlexTV().get_full_users_list()
monitor_db = database.MonitorDatabase()
user_data = Users()
if result:
for item in result:
shared_libraries = ''
user_tokens = user_data.get_tokens(user_id=item['user_id'])
if user_tokens and user_tokens['server_token']:
pms_connect = pmsconnect.PmsConnect(token=user_tokens['server_token'])
library_details = pms_connect.get_server_children()
if library_details:
shared_libraries = ';'.join(d['section_id'] for d in library_details['libraries_list'])
else:
shared_libraries = ''
control_value_dict = {"user_id": item['user_id']}
new_value_dict = {"username": item['username'],
"thumb": item['thumb'],
"email": item['email'],
"is_home_user": item['is_home_user'],
"is_allow_sync": item['is_allow_sync'],
"is_restricted": item['is_restricted'],
"shared_libraries": shared_libraries,
"filter_all": item['filter_all'],
"filter_movies": item['filter_movies'],
"filter_tv": item['filter_tv'],
"filter_music": item['filter_music'],
"filter_photos": item['filter_photos']
}
# Check if we've set a custom avatar if so don't overwrite it.
if item['user_id']:
avatar_urls = monitor_db.select('SELECT thumb, custom_avatar_url '
'FROM users WHERE user_id = ?',
[item['user_id']])
if avatar_urls:
if not avatar_urls[0]['custom_avatar_url'] or \
avatar_urls[0]['custom_avatar_url'] == avatar_urls[0]['thumb']:
new_value_dict['custom_avatar_url'] = item['thumb']
else:
new_value_dict['custom_avatar_url'] = item['thumb']
monitor_db.upsert('users', new_value_dict, control_value_dict)
logger.info(u"Tautulli Users :: Users list refreshed.")
return True
else:
logger.warn(u"Tautulli Users :: Unable to refresh users list.")
return False
class Users(object): class Users(object):
def __init__(self): def __init__(self):
@@ -360,7 +418,7 @@ class Users(object):
logger.warn(u"Tautulli Users :: Unable to retrieve user %s from database. Requesting user list refresh." logger.warn(u"Tautulli Users :: Unable to retrieve user %s from database. Requesting user list refresh."
% user_id if user_id else user) % user_id if user_id else user)
# Let's first refresh the user list to make sure the user isn't newly added and not in the db yet # Let's first refresh the user list to make sure the user isn't newly added and not in the db yet
plextv.refresh_users() refresh_users()
user_details = get_user_details(user_id=user_id, user=user) user_details = get_user_details(user_id=user_id, user=user)

View File

@@ -1,2 +1,2 @@
PLEXPY_BRANCH = "beta" PLEXPY_BRANCH = "beta"
PLEXPY_RELEASE_VERSION = "v2.0.4-beta" PLEXPY_RELEASE_VERSION = "v2.0.5-beta"

View File

@@ -176,7 +176,7 @@ def checkGithub(auto_update=False):
logger.info('New version is available. You are %s commits behind' % plexpy.COMMITS_BEHIND) logger.info('New version is available. You are %s commits behind' % plexpy.COMMITS_BEHIND)
url = 'https://api.github.com/repos/%s/plexpy/releases' % plexpy.CONFIG.GIT_USER url = 'https://api.github.com/repos/%s/plexpy/releases' % plexpy.CONFIG.GIT_USER
releases = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict) releases = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == list)
if plexpy.CONFIG.GIT_BRANCH == 'master': if plexpy.CONFIG.GIT_BRANCH == 'master':
release = next((r for r in releases if not r['prerelease']), releases[0]) release = next((r for r in releases if not r['prerelease']), releases[0])

View File

@@ -42,6 +42,7 @@ def start_thread():
def on_disconnect(): def on_disconnect():
activity_processor.ActivityProcessor().set_temp_stopped() activity_processor.ActivityProcessor().set_temp_stopped()
plexpy.initialize_scheduler()
def reconnect(): def reconnect():
@@ -148,9 +149,8 @@ def run():
logger.info(u"Tautulli WebSocket :: Unable to get an internal response from the server, Plex server is down.") logger.info(u"Tautulli WebSocket :: Unable to get an internal response from the server, Plex server is down.")
plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_intdown'}) plexpy.NOTIFY_QUEUE.put({'notify_action': 'on_intdown'})
plexpy.PLEX_SERVER_UP = False plexpy.PLEX_SERVER_UP = False
on_disconnect()
plexpy.initialize_scheduler() on_disconnect()
logger.debug(u"Tautulli WebSocket :: Leaving thread.") logger.debug(u"Tautulli WebSocket :: Leaving thread.")

View File

@@ -27,9 +27,8 @@ from hashing_passwords import check_hash
import plexpy import plexpy
import logger import logger
import plextv
from plexpy.database import MonitorDatabase from plexpy.database import MonitorDatabase
from plexpy.users import Users from plexpy.users import Users, refresh_users
from plexpy.plextv import PlexTV from plexpy.plextv import PlexTV
@@ -72,7 +71,7 @@ def user_login(username=None, password=None):
if result: if result:
# Refresh the users list to make sure we have all the correct permissions. # Refresh the users list to make sure we have all the correct permissions.
plextv.refresh_users() refresh_users()
# Successful login # Successful login
return True return True
else: else:
@@ -244,7 +243,6 @@ class AuthController(object):
expiry = datetime.now() + (timedelta(days=30) if remember_me == '1' else timedelta(minutes=60)) expiry = datetime.now() + (timedelta(days=30) if remember_me == '1' else timedelta(minutes=60))
cherrypy.session.regenerate()
cherrypy.request.login = username cherrypy.request.login = username
cherrypy.session[SESSION_KEY] = {'user_id': user_id, cherrypy.session[SESSION_KEY] = {'user_id': user_id,
'user': username, 'user': username,

View File

@@ -16,10 +16,8 @@
import hashlib import hashlib
import json import json
import os import os
import random
import shutil import shutil
import threading import threading
import uuid
import cherrypy import cherrypy
from cherrypy.lib.static import serve_file, serve_download from cherrypy.lib.static import serve_file, serve_download
@@ -119,7 +117,7 @@ class WebInterface(object):
@cherrypy.tools.json_out() @cherrypy.tools.json_out()
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
@addtoapi("get_server_list") @addtoapi("get_server_list")
def discover(self, token=None, include_cloud=True, **kwargs): def discover(self, token=None, include_cloud=True, all_servers=False, **kwargs):
""" Get all your servers that are published to Plex.tv. """ Get all your servers that are published to Plex.tv.
``` ```
@@ -150,12 +148,14 @@ class WebInterface(object):
plexpy.CONFIG.write() plexpy.CONFIG.write()
include_cloud = not (include_cloud == 'false') include_cloud = not (include_cloud == 'false')
all_servers = all_servers == 'true'
plex_tv = plextv.PlexTV() plex_tv = plextv.PlexTV()
servers = plex_tv.discover(include_cloud=include_cloud) servers_list = plex_tv.discover(include_cloud=include_cloud,
all_servers=all_servers)
if servers: if servers_list:
return servers return servers_list
##### Home ##### ##### Home #####
@@ -170,6 +170,7 @@ class WebInterface(object):
"home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT, "home_stats_count": plexpy.CONFIG.HOME_STATS_COUNT,
"home_stats_recently_added_count": plexpy.CONFIG.HOME_STATS_RECENTLY_ADDED_COUNT, "home_stats_recently_added_count": plexpy.CONFIG.HOME_STATS_RECENTLY_ADDED_COUNT,
"pms_name": plexpy.CONFIG.PMS_NAME, "pms_name": plexpy.CONFIG.PMS_NAME,
"pms_is_cloud": plexpy.CONFIG.PMS_IS_CLOUD,
"update_show_changelog": plexpy.CONFIG.UPDATE_SHOW_CHANGELOG "update_show_changelog": plexpy.CONFIG.UPDATE_SHOW_CHANGELOG
} }
return serve_template(templatename="index.html", title="Home", config=config) return serve_template(templatename="index.html", title="Home", config=config)
@@ -262,6 +263,12 @@ class WebInterface(object):
else: else:
return {'result': 'error', 'message': 'Failed to terminate session.'} return {'result': 'error', 'message': 'Failed to terminate session.'}
@cherrypy.expose
@cherrypy.tools.json_out()
@requireAuth(member_of("admin"))
def return_sessions_url(self, **kwargs):
return plexpy.CONFIG.PMS_URL + '/status/sessions?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN
@cherrypy.expose @cherrypy.expose
@requireAuth() @requireAuth()
def home_stats(self, time_range=30, stats_type=0, stats_count=10, **kwargs): def home_stats(self, time_range=30, stats_type=0, stats_count=10, **kwargs):
@@ -452,7 +459,7 @@ class WebInterface(object):
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def refresh_libraries_list(self, **kwargs): def refresh_libraries_list(self, **kwargs):
""" Refresh the libraries list on it's own thread. """ """ Refresh the libraries list on it's own thread. """
threading.Thread(target=pmsconnect.refresh_libraries).start() threading.Thread(target=libraries.refresh_libraries).start()
logger.info(u"Manual libraries list refresh requested.") logger.info(u"Manual libraries list refresh requested.")
return True return True
@@ -1074,7 +1081,7 @@ class WebInterface(object):
@requireAuth(member_of("admin")) @requireAuth(member_of("admin"))
def refresh_users_list(self, **kwargs): def refresh_users_list(self, **kwargs):
""" Refresh the users list on it's own thread. """ """ Refresh the users list on it's own thread. """
threading.Thread(target=plextv.refresh_users).start() threading.Thread(target=users.refresh_users).start()
logger.info(u"Manual users list refresh requested.") logger.info(u"Manual users list refresh requested.")
return True return True
@@ -2559,6 +2566,8 @@ class WebInterface(object):
"pms_port": plexpy.CONFIG.PMS_PORT, "pms_port": plexpy.CONFIG.PMS_PORT,
"pms_token": plexpy.CONFIG.PMS_TOKEN, "pms_token": plexpy.CONFIG.PMS_TOKEN,
"pms_ssl": checked(plexpy.CONFIG.PMS_SSL), "pms_ssl": checked(plexpy.CONFIG.PMS_SSL),
"pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE),
"pms_is_cloud": plexpy.CONFIG.PMS_IS_CLOUD,
"pms_url_manual": checked(plexpy.CONFIG.PMS_URL_MANUAL), "pms_url_manual": checked(plexpy.CONFIG.PMS_URL_MANUAL),
"pms_uuid": plexpy.CONFIG.PMS_UUID, "pms_uuid": plexpy.CONFIG.PMS_UUID,
"pms_web_url": plexpy.CONFIG.PMS_WEB_URL, "pms_web_url": plexpy.CONFIG.PMS_WEB_URL,
@@ -2576,7 +2585,6 @@ class WebInterface(object):
"refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL, "refresh_users_interval": plexpy.CONFIG.REFRESH_USERS_INTERVAL,
"refresh_users_on_startup": checked(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP), "refresh_users_on_startup": checked(plexpy.CONFIG.REFRESH_USERS_ON_STARTUP),
"logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL, "logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL,
"pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE),
"notify_consecutive": checked(plexpy.CONFIG.NOTIFY_CONSECUTIVE), "notify_consecutive": checked(plexpy.CONFIG.NOTIFY_CONSECUTIVE),
"notify_upload_posters": checked(plexpy.CONFIG.NOTIFY_UPLOAD_POSTERS), "notify_upload_posters": checked(plexpy.CONFIG.NOTIFY_UPLOAD_POSTERS),
"notify_recently_added_upgrade": checked(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_UPGRADE), "notify_recently_added_upgrade": checked(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_UPGRADE),
@@ -2733,8 +2741,7 @@ class WebInterface(object):
# Get new server URLs for SSL communications and get new server friendly name # Get new server URLs for SSL communications and get new server friendly name
if server_changed: if server_changed:
plextv.get_real_pms_url() plextv.get_server_resources()
pmsconnect.get_server_friendly_name()
web_socket.reconnect() web_socket.reconnect()
# If first run, start websocket # If first run, start websocket
@@ -2751,11 +2758,11 @@ class WebInterface(object):
# Refresh users table if our server IP changes. # Refresh users table if our server IP changes.
if refresh_libraries: if refresh_libraries:
threading.Thread(target=pmsconnect.refresh_libraries).start() threading.Thread(target=libraries.refresh_libraries).start()
# Refresh users table if our server IP changes. # Refresh users table if our server IP changes.
if refresh_users: if refresh_users:
threading.Thread(target=plextv.refresh_users).start() threading.Thread(target=users.refresh_users).start()
return {'result': 'success', 'message': 'Settings saved.'} return {'result': 'success', 'message': 'Settings saved.'}
@@ -3425,16 +3432,15 @@ class WebInterface(object):
# Fallback to checking /identity endpoint is server is unpublished # Fallback to checking /identity endpoint is server is unpublished
# Cannot set SSL settings on the PMS if unpublished so 'http' is okay # Cannot set SSL settings on the PMS if unpublished so 'http' is okay
if not identifier: if not identifier:
request_handler = http_handler.HTTPHandler(host=hostname, scheme = 'https' if ssl else 'http'
port=port, url = '{scheme}://{hostname}:{port}'.format(scheme=scheme, hostname=hostname, port=port)
token=None)
uri = '/identity' uri = '/identity'
request_handler = http_handler.HTTPHandler(urls=url,
ssl_verify=False)
request = request_handler.make_request(uri=uri, request = request_handler.make_request(uri=uri,
proto='http',
request_type='GET', request_type='GET',
output_format='xml', output_format='xml')
no_token=True,
timeout=10)
if request: if request:
xml_head = request.getElementsByTagName('MediaContainer')[0] xml_head = request.getElementsByTagName('MediaContainer')[0]
identifier = xml_head.getAttribute('machineIdentifier') identifier = xml_head.getAttribute('machineIdentifier')
@@ -4434,7 +4440,8 @@ class WebInterface(object):
counts = {'stream_count_direct_play': 0, counts = {'stream_count_direct_play': 0,
'stream_count_direct_stream': 0, 'stream_count_direct_stream': 0,
'stream_count_transcode': 0} 'stream_count_transcode': 0,
'total_bandwidth': 0}
for s in result['sessions']: for s in result['sessions']:
if s['transcode_decision'] == 'transcode': if s['transcode_decision'] == 'transcode':
@@ -4444,6 +4451,8 @@ class WebInterface(object):
else: else:
counts['stream_count_direct_play'] += 1 counts['stream_count_direct_play'] += 1
counts['total_bandwidth'] += helpers.cast_to_int(s['bandwidth'])
result.update(counts) result.update(counts)
return result return result

View File

@@ -69,14 +69,14 @@ def initialize(options):
if options['http_password']: if options['http_password']:
logger.info(u"Tautulli WebStart :: Web server authentication is enabled, username is '%s'", options['http_username']) logger.info(u"Tautulli WebStart :: Web server authentication is enabled, username is '%s'", options['http_username'])
if options['http_basic_auth']: if options['http_basic_auth']:
auth_enabled = session_enabled = False session_enabled = auth_enabled = False
basic_auth_enabled = True basic_auth_enabled = True
else: else:
options_dict['tools.sessions.on'] = auth_enabled = session_enabled = True options_dict['tools.sessions.on'] = session_enabled = auth_enabled = True
basic_auth_enabled = False basic_auth_enabled = False
cherrypy.tools.auth = cherrypy.Tool('before_handler', webauth.check_auth) cherrypy.tools.auth = cherrypy.Tool('before_handler', webauth.check_auth)
else: else:
auth_enabled = session_enabled = basic_auth_enabled = False session_enabled = auth_enabled = basic_auth_enabled = False
if options['http_root'].strip('/'): if options['http_root'].strip('/'):
plexpy.HTTP_ROOT = options['http_root'] = '/' + options['http_root'].strip('/') + '/' plexpy.HTTP_ROOT = options['http_root'] = '/' + options['http_root'].strip('/') + '/'
@@ -93,9 +93,12 @@ def initialize(options):
'tools.gzip.mime_types': ['text/html', 'text/plain', 'text/css', 'tools.gzip.mime_types': ['text/html', 'text/plain', 'text/css',
'text/javascript', 'application/json', 'text/javascript', 'application/json',
'application/javascript'], 'application/javascript'],
'tools.auth.on': auth_enabled,
'tools.sessions.on': session_enabled, 'tools.sessions.on': session_enabled,
'tools.session.name': 'tautulli_session_id-' + plexpy.CONFIG.PMS_UUID,
'tools.sessions.storage_type': 'file',
'tools.sessions.storage_path': plexpy.CONFIG.CACHE_DIR,
'tools.sessions.timeout': 30 * 24 * 60, # 30 days 'tools.sessions.timeout': 30 * 24 * 60, # 30 days
'tools.auth.on': auth_enabled,
'tools.auth_basic.on': basic_auth_enabled, 'tools.auth_basic.on': basic_auth_enabled,
'tools.auth_basic.realm': 'Tautulli web server', 'tools.auth_basic.realm': 'Tautulli web server',
'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict({ 'tools.auth_basic.checkpassword': cherrypy.lib.auth_basic.checkpassword_dict({
@@ -112,8 +115,8 @@ def initialize(options):
'tools.caching.delay': 0, 'tools.caching.delay': 0,
'tools.expires.on': True, 'tools.expires.on': True,
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
'tools.auth.on': False, 'tools.sessions.on': False,
'tools.sessions.on': False 'tools.auth.on': False
}, },
'/images': { '/images': {
'tools.staticdir.on': True, 'tools.staticdir.on': True,
@@ -123,8 +126,8 @@ def initialize(options):
'tools.caching.delay': 0, 'tools.caching.delay': 0,
'tools.expires.on': True, 'tools.expires.on': True,
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
'tools.auth.on': False, 'tools.sessions.on': False,
'tools.sessions.on': False 'tools.auth.on': False
}, },
'/css': { '/css': {
'tools.staticdir.on': True, 'tools.staticdir.on': True,
@@ -134,8 +137,8 @@ def initialize(options):
'tools.caching.delay': 0, 'tools.caching.delay': 0,
'tools.expires.on': True, 'tools.expires.on': True,
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
'tools.auth.on': False, 'tools.sessions.on': False,
'tools.sessions.on': False 'tools.auth.on': False
}, },
'/fonts': { '/fonts': {
'tools.staticdir.on': True, 'tools.staticdir.on': True,
@@ -145,8 +148,8 @@ def initialize(options):
'tools.caching.delay': 0, 'tools.caching.delay': 0,
'tools.expires.on': True, 'tools.expires.on': True,
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
'tools.auth.on': False, 'tools.sessions.on': False,
'tools.sessions.on': False 'tools.auth.on': False
}, },
'/js': { '/js': {
'tools.staticdir.on': True, 'tools.staticdir.on': True,
@@ -156,8 +159,8 @@ def initialize(options):
'tools.caching.delay': 0, 'tools.caching.delay': 0,
'tools.expires.on': True, 'tools.expires.on': True,
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
'tools.auth.on': False, 'tools.sessions.on': False,
'tools.sessions.on': False 'tools.auth.on': False
}, },
'/cache': { '/cache': {
'tools.staticdir.on': True, 'tools.staticdir.on': True,
@@ -167,8 +170,8 @@ def initialize(options):
'tools.caching.delay': 0, 'tools.caching.delay': 0,
'tools.expires.on': True, 'tools.expires.on': True,
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
'tools.auth.on': False, 'tools.sessions.on': False,
'tools.sessions.on': False 'tools.auth.on': False
}, },
#'/pms_image_proxy': { #'/pms_image_proxy': {
# 'tools.staticdir.on': True, # 'tools.staticdir.on': True,
@@ -189,8 +192,8 @@ def initialize(options):
'tools.caching.delay': 0, 'tools.caching.delay': 0,
'tools.expires.on': True, 'tools.expires.on': True,
'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days 'tools.expires.secs': 60 * 60 * 24 * 30, # 30 days
'tools.auth.on': False, 'tools.sessions.on': False,
'tools.sessions.on': False 'tools.auth.on': False
} }
} }