Compare commits
83 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
29c7853380 | ||
![]() |
cd417aaf44 | ||
![]() |
428a5cc0ff | ||
![]() |
d128d7c8e6 | ||
![]() |
8027199bd5 | ||
![]() |
099a887cc7 | ||
![]() |
ea41d06023 | ||
![]() |
5147baab05 | ||
![]() |
1c50e615cf | ||
![]() |
ed2d34e979 | ||
![]() |
c404016700 | ||
![]() |
14b0353ba4 | ||
![]() |
fbe136a350 | ||
![]() |
4fcfea943e | ||
![]() |
279d27d081 | ||
![]() |
651125ef2c | ||
![]() |
a5eb0e7faa | ||
![]() |
e85cdd5609 | ||
![]() |
83f80b9288 | ||
![]() |
76fe771d8c | ||
![]() |
c6ad09fe8f | ||
![]() |
a3014638c8 | ||
![]() |
8b15c63b0d | ||
![]() |
589adf4df9 | ||
![]() |
b07d85f233 | ||
![]() |
f0e5855a8e | ||
![]() |
5997aa5cd9 | ||
![]() |
cb2a38addc | ||
![]() |
873f857c82 | ||
![]() |
b9a22461c1 | ||
![]() |
85a02771a7 | ||
![]() |
6a0b0327c3 | ||
![]() |
3742f33d08 | ||
![]() |
82ac33dd75 | ||
![]() |
8c7c0101cd | ||
![]() |
4d6179dfdd | ||
![]() |
ef85fba2e5 | ||
![]() |
960b601384 | ||
![]() |
21d9091b43 | ||
![]() |
e881c32797 | ||
![]() |
57f1af05f5 | ||
![]() |
254f41a2cc | ||
![]() |
59ce3404c9 | ||
![]() |
11c7342299 | ||
![]() |
8b0959aa69 | ||
![]() |
9cd8ed12b9 | ||
![]() |
78b10d7ab5 | ||
![]() |
a322ec2b23 | ||
![]() |
1f23654735 | ||
![]() |
e1112b95c7 | ||
![]() |
4e043109bf | ||
![]() |
58e670443d | ||
![]() |
86a9230da8 | ||
![]() |
86f84766c1 | ||
![]() |
fdc7078e5c | ||
![]() |
c649ebfcc0 | ||
![]() |
6aa0d4cd0b | ||
![]() |
1a2e205c1f | ||
![]() |
5dd04cb8ab | ||
![]() |
62d05e5e08 | ||
![]() |
1c087ec856 | ||
![]() |
010c12da67 | ||
![]() |
9bdac38561 | ||
![]() |
790ca9c90a | ||
![]() |
58f72d2d9c | ||
![]() |
285e6513ed | ||
![]() |
412bc8cf2d | ||
![]() |
45cd8b8a00 | ||
![]() |
ae2227959e | ||
![]() |
b50c92f919 | ||
![]() |
93a1d9c164 | ||
![]() |
0b10e68c60 | ||
![]() |
73ac4076ac | ||
![]() |
5968b82a0b | ||
![]() |
ce1d2a0fd9 | ||
![]() |
de3f813b46 | ||
![]() |
4797b1a3b7 | ||
![]() |
3e996d284d | ||
![]() |
420c5a0836 | ||
![]() |
c6b953055a | ||
![]() |
1cd0c112a6 | ||
![]() |
492d28ea37 | ||
![]() |
4eb7e03b67 |
50
API.md
@@ -37,11 +37,11 @@ Get to the chopper!
|
||||
|
||||
|
||||
### backup_config
|
||||
Create a manual backup of the `config.ini` file.
|
||||
Create a manual backup of the `config.ini` file.
|
||||
|
||||
|
||||
### backup_db
|
||||
Create a manual backup of the `plexpy.db` file.
|
||||
Create a manual backup of the `plexpy.db` file.
|
||||
|
||||
|
||||
### delete_all_library_history
|
||||
@@ -142,6 +142,10 @@ Returns:
|
||||
```
|
||||
|
||||
|
||||
### delete_temp_sessions
|
||||
Flush out all of the temporary sessions in the database.
|
||||
|
||||
|
||||
### delete_user
|
||||
Delete a user from PlexPy. Also erases all history for the user.
|
||||
|
||||
@@ -158,11 +162,11 @@ Returns:
|
||||
|
||||
|
||||
### docs
|
||||
Return the api docs as a dict where commands are keys, docstring are value.
|
||||
Return the api docs as a dict where commands are keys, docstring are value.
|
||||
|
||||
|
||||
### docs_md
|
||||
Return the api docs formatted with markdown.
|
||||
Return the api docs formatted with markdown.
|
||||
|
||||
|
||||
### download_log
|
||||
@@ -755,11 +759,11 @@ Optional parameters:
|
||||
|
||||
Returns:
|
||||
json:
|
||||
[{"loglevel": "DEBUG",
|
||||
"msg": "Latest version is 2d10b0748c7fa2ee4cf59960c3d3fffc6aa9512b",
|
||||
"thread": "MainThread",
|
||||
[{"loglevel": "DEBUG",
|
||||
"msg": "Latest version is 2d10b0748c7fa2ee4cf59960c3d3fffc6aa9512b",
|
||||
"thread": "MainThread",
|
||||
"time": "2016-05-08 09:36:51 "
|
||||
},
|
||||
},
|
||||
{...},
|
||||
{...}
|
||||
]
|
||||
@@ -772,7 +776,7 @@ Get the metadata for a media item.
|
||||
```
|
||||
Required parameters:
|
||||
rating_key (str): Rating key of the item
|
||||
media_info (bool): True or False wheter to get media info
|
||||
media_info (bool): True or False whether to get media info
|
||||
|
||||
Optional parameters:
|
||||
None
|
||||
@@ -1793,16 +1797,36 @@ Returns:
|
||||
```
|
||||
|
||||
|
||||
### pms_image_proxy
|
||||
Gets an image from the PMS and saves it to the image cache directory.
|
||||
|
||||
```
|
||||
Required parameters:
|
||||
img (str): /library/metadata/153037/thumb/1462175060
|
||||
or
|
||||
rating_key (str): 54321
|
||||
|
||||
Optional parameters:
|
||||
width (str): 150
|
||||
height (str): 255
|
||||
fallback (str): "poster", "cover", "art"
|
||||
refresh (bool): True or False whether to refresh the image cache
|
||||
|
||||
Returns:
|
||||
None
|
||||
```
|
||||
|
||||
|
||||
### refresh_libraries_list
|
||||
Refresh the PlexPy libraries list.
|
||||
Refresh the PlexPy libraries list.
|
||||
|
||||
|
||||
### refresh_users_list
|
||||
Refresh the PlexPy users list.
|
||||
Refresh the PlexPy users list.
|
||||
|
||||
|
||||
### restart
|
||||
Restart PlexPy.
|
||||
Restart PlexPy.
|
||||
|
||||
|
||||
### search
|
||||
@@ -1885,7 +1909,7 @@ Uninstalls the GeoLite2 database
|
||||
|
||||
|
||||
### update
|
||||
Check for PlexPy updates on Github.
|
||||
Check for PlexPy updates on Github.
|
||||
|
||||
|
||||
### update_metadata_details
|
||||
|
82
CHANGELOG.md
@@ -1,5 +1,87 @@
|
||||
# Changelog
|
||||
|
||||
## v1.4.20 (2017-06-24)
|
||||
|
||||
* New: Added platform image for the PlexTogether player.
|
||||
* Fix: Corrected math used to calculate human duration. (Thanks @senepa)
|
||||
* Fix: Sorting of 4k in media info tables.
|
||||
* Fix: Update file sizes when refreshing media info tables.
|
||||
* Fix: Support a custom port for Mattermost (Slack) notifications.
|
||||
|
||||
|
||||
## v1.4.19 (2017-05-31)
|
||||
|
||||
* Fix: Video resolution not showing up for transcoded streams on PMS 1.7.x.
|
||||
|
||||
|
||||
## v1.4.18 (2017-04-22)
|
||||
|
||||
* New: Added some new Arnold quotes. (Thanks @senepa)
|
||||
* Fix: Text wrapping in datatable footers.
|
||||
* Fix: API command get_apikey. (Thanks @Hellowlol)
|
||||
|
||||
|
||||
## v1.4.17 (2017-03-04)
|
||||
|
||||
* New: Configurable month range for the Plays by month graph. (Thanks @Pbaboe)
|
||||
* New: Option to chanage the week to start on Monday for the the Plays by day of week graph. (Thanks @Pbaboe)
|
||||
* Fix: Invalid iOS icon file paths. (Thanks @demonbane)
|
||||
* Fix: Plex Web 3.0 URLs on info pages and notifications.
|
||||
* Fix: Update bitcoin donation link to Coinbase.
|
||||
* Fix: Update init scripts. (Thanks @ampsonic)
|
||||
|
||||
|
||||
## v1.4.16 (2016-11-25)
|
||||
|
||||
* Fix: Websocket for new json response on PMS 1.3.0.
|
||||
* Fix: Update stream and transcoder tooltip percent.
|
||||
* Fix: Typo in the edit user modal.
|
||||
|
||||
|
||||
## v1.4.15 (2016-11-11)
|
||||
|
||||
* New: Add stream and transcoder progress percent to the current activity tooltip.
|
||||
* Fix: Refreshing of images in the cache when authentication is disabled.
|
||||
* Fix: Plex.tv authentication with special characters in the username or password.
|
||||
* Fix: Line breaks in the info page summaries.
|
||||
* Fix: Redirect to the proper http root when restarting.
|
||||
* Fix: API result type and responses showing incorrectly. (Thanks @Hellowlol)
|
||||
* Change: Use https URL for app.plex.tv.
|
||||
* Change: Show API traceback errors in the browser with debugging enabled. (Thanks @Hellowlol)
|
||||
* Change: Increase table width on mobile devices and max width set to 1750px. (Thanks @XusBadia)
|
||||
|
||||
|
||||
## v1.4.14 (2016-10-12)
|
||||
|
||||
* Fix: History logging locking up if media is removed from Plex before PlexPy can save the session.
|
||||
* Fix: Unable to save API key in the settings.
|
||||
* Fix: Some typos in the settings. (Thanks @Leafar3456)
|
||||
* Change: Disable script timeout by setting timeout to 0 seconds.
|
||||
|
||||
|
||||
## v1.4.13 (2016-10-08)
|
||||
|
||||
* New: Option to set the number of days to keep PlexPy backups.
|
||||
* New: Option to add a supplementary url to Pushover notifications.
|
||||
* New: Option to set a timeout duration for script notifications.
|
||||
* New: Added flush temporary sessions button to extra settings for emergency use.
|
||||
* New: Added pms_image_proxy to the API.
|
||||
* Fix: Insanely long play durations being recorded when connection to the Plex server is lost.
|
||||
* Fix: Script notification output not being sent to the logger.
|
||||
* Fix: New libraries not being added to homepage automatically.
|
||||
* Fix: Success message shown incorrectly when sending a test notification.
|
||||
* Fix: PlexPy log level filter not working.
|
||||
* Fix: Admin username not shown in login logs.
|
||||
* Fix: FeatHub link in readme document.
|
||||
* Change: Posters disabled by default for all notification agents.
|
||||
* Change: Disable manual changing of the PlexPy API key.
|
||||
* Change: Force refresh the Plex.tv token when fetching a new token.
|
||||
* Change: Script notifications run in a new thread with the timeout setting.
|
||||
* Change: Watched percent moved to general settings.
|
||||
* Change: Use human readable file sizes to the media info tables. (Thanks @logaritmisk)
|
||||
* Change: Update pytz library.
|
||||
|
||||
|
||||
## v1.4.12 (2016-09-18)
|
||||
|
||||
* Fix: PMS update check not working for MacOSX.
|
||||
|
@@ -35,7 +35,7 @@ In case you read this because you are posting an issue, please take a minute and
|
||||
|
||||
## Feature Requests
|
||||
|
||||
Feature requests are handled on [FeatHub](http://feathub.com/drzoidberg33/plexpy).
|
||||
Feature requests are handled on [FeatHub](http://feathub.com/JonnyWong16/plexpy).
|
||||
|
||||
1. Search the existing requests to see if your suggestion has already been submitted.
|
||||
2. If a similar request exists, give it a thumbs up (+1), or add additional comments to the request.
|
||||
|
@@ -8,7 +8,7 @@ Reporting Issues:
|
||||
Please use [Gist](http://gist.github.com) or [Pastebin](http://pastebin.com/).
|
||||
|
||||
Feature Requests:
|
||||
* Feature requests are handled on FeatHub: http://feathub.com/drzoidberg33/plexpy
|
||||
* Feature requests are handled on FeatHub: http://feathub.com/JonnyWong16/plexpy
|
||||
* Do not post them on the GitHub issues tracker.
|
||||
-->
|
||||
|
||||
|
10
README.md
@@ -1,15 +1,13 @@
|
||||
# PlexPy
|
||||
|
||||
[](https://gitter.im/plexpy/general?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://discord.gg/36ggawe)
|
||||
[](https://gitter.im/plexpy/general)
|
||||
[](https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program)
|
||||
|
||||
A python based web application for monitoring, analytics and notifications for [Plex Media Server](https://plex.tv).
|
||||
|
||||
This project is based on code from [Headphones](https://github.com/rembo10/headphones) and [PlexWatchWeb](https://github.com/ecleese/plexWatchWeb).
|
||||
|
||||
* [Plex forum thread](https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program)
|
||||
* [Gitter chat](https://gitter.im/plexpy/general)
|
||||
* [/r/Plex Discord server](https://discord.gg/011TFFWSuNFI02EKr) | [PlexPy Discord server](https://discord.gg/36ggawe)
|
||||
|
||||
## Features
|
||||
|
||||
* Responsive web design viewable on desktop, tablet and mobile web browsers.
|
||||
@@ -74,7 +72,7 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
|
||||
|
||||
## Feature Requests
|
||||
|
||||
Feature requests are handled on [FeatHub](http://feathub.com/drzoidberg33/plexpy).
|
||||
Feature requests are handled on [FeatHub](http://feathub.com/JonnyWong16/plexpy).
|
||||
|
||||
1. Search the existing requests to see if your suggestion has already been submitted.
|
||||
2. If a similar request exists, give it a thumbs up (+1), or add additional comments to the request.
|
||||
|
@@ -66,67 +66,67 @@
|
||||
|
||||
<!-- STARTUP IMAGES -->
|
||||
<!-- iPad retina portrait startup image -->
|
||||
<link href="${http_root}images/res/screen/ios/Default-Portrait@2x~ipad.png"
|
||||
<link href="${http_root}images/res/ios/Default-Portrait@2x~ipad.png"
|
||||
media="(device-width: 768px) and (device-height: 1024px)
|
||||
and (-webkit-device-pixel-ratio: 2)
|
||||
and (orientation: portrait)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPad retina landscape startup image -->
|
||||
<link href="${http_root}images/res/screen/ios/Default-Landscape@2x~ipad.png"
|
||||
<link href="${http_root}images/res/ios/Default-Landscape@2x~ipad.png"
|
||||
media="(device-width: 768px) and (device-height: 1024px)
|
||||
and (-webkit-device-pixel-ratio: 2)
|
||||
and (orientation: landscape)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPad non-retina portrait startup image -->
|
||||
<link href="${http_root}images/res/screen/ios/Default-Portrait~ipad.png"
|
||||
<link href="${http_root}images/res/ios/Default-Portrait~ipad.png"
|
||||
media="(device-width: 768px) and (device-height: 1024px)
|
||||
and (-webkit-device-pixel-ratio: 1)
|
||||
and (orientation: portrait)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPad non-retina landscape startup image -->
|
||||
<link href="${http_root}images/res/screen/ios/Default-Landscape~ipad.png"
|
||||
<link href="${http_root}images/res/ios/Default-Landscape~ipad.png"
|
||||
media="(device-width: 768px) and (device-height: 1024px)
|
||||
and (-webkit-device-pixel-ratio: 1)
|
||||
and (orientation: landscape)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPhone 6 Plus portrait startup image -->
|
||||
<link href="${http_root}images/res/screen/ios/Default-736h.png"
|
||||
<link href="${http_root}images/res/ios/Default-736h.png"
|
||||
media="(device-width: 414px) and (device-height: 736px)
|
||||
and (-webkit-device-pixel-ratio: 3)
|
||||
and (orientation: portrait)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPhone 6 Plus landscape startup image -->
|
||||
<link href="${http_root}images/res/screen/ios/Default-Landscape-736h.png"
|
||||
<link href="${http_root}images/res/ios/Default-Landscape-736h.png"
|
||||
media="(device-width: 414px) and (device-height: 736px)
|
||||
and (-webkit-device-pixel-ratio: 3)
|
||||
and (orientation: landscape)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPhone 6 startup image -->
|
||||
<link href="${http_root}images/res/screen/ios/Default-667h.png"
|
||||
<link href="${http_root}images/res/ios/Default-667h.png"
|
||||
media="(device-width: 375px) and (device-height: 667px)
|
||||
and (-webkit-device-pixel-ratio: 2)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPhone 5 startup image -->
|
||||
<link href="${http_root}images/res/screen/ios/Default-568h@2x~iphone5.jpg"
|
||||
<link href="${http_root}images/res/ios/Default-568h@2x~iphone5.jpg"
|
||||
media="(device-width: 320px) and (device-height: 568px)
|
||||
and (-webkit-device-pixel-ratio: 2)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPhone < 5 retina startup image -->
|
||||
<link href="${http_root}images/res/screen/ios/Default@2x~iphone.png"
|
||||
<link href="${http_root}images/res/ios/Default@2x~iphone.png"
|
||||
media="(device-width: 320px) and (device-height: 480px)
|
||||
and (-webkit-device-pixel-ratio: 2)"
|
||||
rel="apple-touch-startup-image">
|
||||
|
||||
<!-- iPhone < 5 non-retina startup image -->
|
||||
<link href="${http_root}images/res/screen/ios/Default~iphone.png"
|
||||
<link href="${http_root}images/res/ios/Default~iphone.png"
|
||||
media="(device-width: 320px) and (device-height: 480px)
|
||||
and (-webkit-device-pixel-ratio: 1)"
|
||||
rel="apple-touch-startup-image">
|
||||
@@ -223,8 +223,8 @@
|
||||
<li><a href="${anon_url('https://github.com/%s/plexpy/wiki/Frequently-Asked-Questions-(FAQ)' % plexpy.CONFIG.GIT_USER)}" target="_blank"><i class="fa fa-fw fa-question-circle"></i> FAQ</a></li>
|
||||
<li><a href="settings?support=true"><i class="fa fa-fw fa-comment"></i> Support</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="${anon_url('https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=DG783BMSCU3V4')}" target="_blank"><i class="fa fa-fw fa-paypal"></i> Paypal</a></li>
|
||||
<li><a href="${anon_url('http://swiftpanda16.tip.me/')}" target="_blank"><i class="fa fa-fw fa-btc"></i> Bitcoin</a></li>
|
||||
<li><a href="${anon_url('https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=6XPPKTDSX9QFL&lc=US&item_name=PlexPy¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted')}" target="_blank"><i class="fa fa-fw fa-paypal"></i> Paypal</a></li>
|
||||
<li><a href="${anon_url('https://www.coinbase.com/JonnyWong16')}" target="_blank"><i class="fa fa-fw fa-btc"></i> Bitcoin</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
% if plexpy.CONFIG.CHECK_GITHUB:
|
||||
<li><a href="#" id="nav-update"><i class="fa fa-fw fa-arrow-circle-up"></i> Check for Updates</a></li>
|
||||
|
@@ -2251,7 +2251,8 @@ a .home-platforms-list-cover-face:hover
|
||||
margin-right: auto;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
width: 90%;
|
||||
width: 100%;
|
||||
max-width: 1750px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.table-card-header {
|
||||
@@ -2263,7 +2264,8 @@ a .home-platforms-list-cover-face:hover
|
||||
margin-right: auto;
|
||||
margin-top: 20px;
|
||||
margin-bottom: -20px;
|
||||
width: 90%;
|
||||
width: 100%;
|
||||
max-width: 1750px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.table-card-back td {
|
||||
@@ -2489,6 +2491,9 @@ a .home-platforms-list-cover-face:hover
|
||||
.dataTables_paginate li {
|
||||
margin: 0;
|
||||
}
|
||||
div.dataTables_info {
|
||||
white-space: normal !important;
|
||||
}
|
||||
.tooltip.top .tooltip-arrow {
|
||||
border-top-color: #fff;
|
||||
}
|
||||
@@ -2831,6 +2836,14 @@ div[id^='media_info_child'] div[id^='media_info_child'] div.dataTables_scrollHea
|
||||
width: 75px;
|
||||
height: 34px;
|
||||
}
|
||||
#months-selection label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
#graph-months {
|
||||
margin: 0;
|
||||
width: 75px;
|
||||
height: 34px;
|
||||
}
|
||||
.card-sortable {
|
||||
height: 36px;
|
||||
padding: 0 20px 0 0;
|
||||
@@ -3055,3 +3068,13 @@ a:hover .overlay-refresh-image:hover {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
#plexpy-log-levels label,
|
||||
#plex-log-levels label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
#api_key.form-control[readonly] {
|
||||
background-color: #555;
|
||||
}
|
||||
#api_key.form-control[readonly]:focus {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
@@ -207,8 +207,8 @@ DOCUMENTATION :: END
|
||||
% endif
|
||||
<div class="dashboard-activity-progress">
|
||||
<div class="dashboard-activity-progress-bar">
|
||||
<div id="bufferbar-${data['session_key']}" class="bufferbar" style="width: ${data['transcode_progress']}%" data-toggle="tooltip" title="Transcoder Progress">${data['transcode_progress']}%</div>
|
||||
<div id="bar-${data['session_key']}" class="bar" style="width: ${data['progress_percent']}%" data-toggle="tooltip" title="Stream Progress">${data['progress_percent']}%</div>
|
||||
<div id="bufferbar-${data['session_key']}" class="bufferbar" style="width: ${data['transcode_progress']}%" data-toggle="tooltip" title="Transcoder Progress ${data['transcode_progress']}%">${data['transcode_progress']}%</div>
|
||||
<div id="bar-${data['session_key']}" class="bar" style="width: ${data['progress_percent']}%" data-toggle="tooltip" title="Stream Progress ${data['progress_percent']}%">${data['progress_percent']}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -45,7 +45,7 @@ DOCUMENTATION :: END
|
||||
<input type="text" class="form-control" id="friendly_name" name="friendly_name" value="${data['friendly_name']}" size="30">
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-block">Replace all occurances of the username with this name.</p>
|
||||
<p class="help-block">Replace all occurrences of the username with this name.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="profile_url">Profile Picture URL</label>
|
||||
|
@@ -42,6 +42,11 @@
|
||||
<input type="number" name="graph-days" id="graph-days" value="${config['graph_days']}" min="1" /> days
|
||||
</label>
|
||||
</div>
|
||||
<div class="btn-group" id="months-selection">
|
||||
<label>
|
||||
<input type="number" name="graph-months" id="graph-months" value="${config['graph_months']}" min="1" /> months
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='table-card-back'>
|
||||
@@ -226,7 +231,7 @@
|
||||
% endif
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h4><i class="fa fa-calendar"></i> Plays by Month <small>Last 12 months</small></h4>
|
||||
<h4><i class="fa fa-calendar"></i> Plays by month <small>Last <span class="months">12</span> months</small></h4>
|
||||
<p class="help-block">
|
||||
The combined total of tv, movies, and music by month.
|
||||
</p>
|
||||
@@ -317,10 +322,12 @@
|
||||
|
||||
// Initial values for graph from config
|
||||
var yaxis = "${config['graph_type']}";
|
||||
var current_range = ${config['graph_days']};
|
||||
var current_day_range = ${config['graph_days']};
|
||||
var current_month_range = ${config['graph_months']};
|
||||
var current_tab = "${'#' + config['graph_tab']}";
|
||||
|
||||
$('.days').html(current_range);
|
||||
$('.days').html(current_day_range);
|
||||
$('.months').html(current_month_range);
|
||||
|
||||
// Load user ids and names (for the selector)
|
||||
$.ajax({
|
||||
@@ -352,6 +359,7 @@
|
||||
|
||||
function loadGraphsTab1(time_range, yaxis) {
|
||||
$('#days-selection').show();
|
||||
$('#months-selection').hide();
|
||||
|
||||
setGraphFormat(yaxis);
|
||||
|
||||
@@ -442,6 +450,7 @@
|
||||
|
||||
function loadGraphsTab2(time_range, yaxis) {
|
||||
$('#days-selection').show();
|
||||
$('#months-selection').hide();
|
||||
|
||||
setGraphFormat(yaxis);
|
||||
|
||||
@@ -525,15 +534,16 @@
|
||||
});
|
||||
}
|
||||
|
||||
function loadGraphsTab3(yaxis) {
|
||||
function loadGraphsTab3(time_range, yaxis) {
|
||||
$('#days-selection').hide();
|
||||
$('#months-selection').show();
|
||||
|
||||
setGraphFormat(yaxis);
|
||||
|
||||
$.ajax({
|
||||
url: "get_plays_per_month",
|
||||
type: 'get',
|
||||
data: { y_axis: yaxis, user_id: selected_user_id },
|
||||
data: { time_range: time_range, y_axis: yaxis, user_id: selected_user_id },
|
||||
dataType: "json",
|
||||
success: function(data) {
|
||||
if (yaxis === 'duration') { dataSecondsToHours(data); }
|
||||
@@ -547,15 +557,15 @@
|
||||
}
|
||||
|
||||
// Set initial state
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(yaxis); }
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
|
||||
// Tab1 opened
|
||||
$('#graph-tabs a[href="#tabs-1"]').on('shown.bs.tab', function (e) {
|
||||
e.preventDefault();
|
||||
current_tab = $(this).attr('href');
|
||||
loadGraphsTab1(current_range, yaxis);
|
||||
loadGraphsTab1(current_day_range, yaxis);
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_tab: current_tab.replace('#','') },
|
||||
@@ -567,7 +577,7 @@
|
||||
$('#graph-tabs a[href="#tabs-2"]').on('shown.bs.tab', function (e) {
|
||||
e.preventDefault();
|
||||
current_tab = $(this).attr('href');
|
||||
loadGraphsTab2(current_range, yaxis);
|
||||
loadGraphsTab2(current_day_range, yaxis);
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_tab: current_tab.replace('#','') },
|
||||
@@ -579,7 +589,7 @@
|
||||
$('#graph-tabs a[href="#tabs-3"]').on('shown.bs.tab', function (e) {
|
||||
e.preventDefault();
|
||||
current_tab = $(this).attr('href');
|
||||
loadGraphsTab3(yaxis);
|
||||
loadGraphsTab3(current_month_range, yaxis);
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_tab: current_tab.replace('#','') },
|
||||
@@ -589,17 +599,35 @@
|
||||
|
||||
// Date range changed
|
||||
$('#graph-days').on('change', function() {
|
||||
current_range = $(this).val();
|
||||
if (current_range < 1) {
|
||||
current_day_range = Math.round($(this).val());
|
||||
$(this).val(current_day_range);
|
||||
if (current_day_range < 1) {
|
||||
$(this).val(7);
|
||||
current_range = 7;
|
||||
current_day_range = 7;
|
||||
}
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_range, yaxis); }
|
||||
$('.days').html(current_range);
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
$('.days').html(current_day_range);
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_days: current_range},
|
||||
data: { graph_days: current_day_range},
|
||||
async: true
|
||||
});
|
||||
});
|
||||
|
||||
// Month range changed
|
||||
$('#graph-months').on('change', function() {
|
||||
current_month_range = Math.round($(this).val());
|
||||
$(this).val(current_month_range);
|
||||
if (current_month_range < 1) {
|
||||
$(this).val(12);
|
||||
current_month_range = 12;
|
||||
}
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
$('.months').html(current_month_range);
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_months: current_month_range},
|
||||
async: true
|
||||
});
|
||||
});
|
||||
@@ -607,17 +635,17 @@
|
||||
// User changed
|
||||
$('#graph-user').on('change', function() {
|
||||
selected_user_id = $(this).val() || null;
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(yaxis); }
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
});
|
||||
|
||||
// Y-axis changed
|
||||
$('#yaxis-selection').on('change', function() {
|
||||
yaxis = $('input[name=yaxis-options]:checked', '#yaxis-selection').val();
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_range, yaxis); }
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(yaxis); }
|
||||
if (current_tab == '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
|
||||
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
|
||||
$.ajax({
|
||||
url: 'set_graph_config',
|
||||
data: { graph_type: yaxis},
|
||||
|
BIN
data/interfaces/default/images/platforms/plextogether.png
Normal file
After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 137 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 6.5 KiB |
@@ -204,8 +204,11 @@
|
||||
|
||||
// update the progress bars
|
||||
// percent - 3 because of 3px padding-right
|
||||
$('#bufferbar-' + key).width(parseInt(s.transcode_progress) - 3 + '%').html(s.transcode_progress + '%');
|
||||
$('#bar-' + key).width(parseInt(s.progress_percent) - 3 + '%').html(s.progress_percent + '%');
|
||||
$('#bufferbar-' + key).width(parseInt(s.transcode_progress) - 3 + '%').html(s.transcode_progress + '%')
|
||||
.attr('data-original-title', 'Transcoder Progress ' + s.transcode_progress + '%');
|
||||
$('#bar-' + key).width(parseInt(s.progress_percent) - 3 + '%').html(s.progress_percent + '%')
|
||||
.attr('data-original-title', 'Stream Progress ' + s.progress_percent + '%');
|
||||
|
||||
|
||||
// add temporary class so we know which instances are still active
|
||||
instance.addClass('updated-temp');
|
||||
|
@@ -53,6 +53,9 @@ DOCUMENTATION :: END
|
||||
if re.match(pattern, codec):
|
||||
return file
|
||||
return codec
|
||||
|
||||
def br(text):
|
||||
return text.replace('\n', '<br /><br />')
|
||||
%>
|
||||
|
||||
<%inherit file="base.html"/>
|
||||
@@ -112,9 +115,9 @@ DOCUMENTATION :: END
|
||||
<div class="col-md-9">
|
||||
<div class="summary-content-poster hidden-xs hidden-sm">
|
||||
% if data['media_type'] == 'track':
|
||||
<a href="http://app.plex.tv/web/app#!/server/${config['pms_identifier']}/details/%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}" target="_blank" title="View in Plex Web">
|
||||
<a href="https://app.plex.tv/web/app#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['parent_rating_key']}" target="_blank" title="View in Plex Web">
|
||||
% else:
|
||||
<a href="http://app.plex.tv/web/app#!/server/${config['pms_identifier']}/details/%2Flibrary%2Fmetadata%2F${data['rating_key']}" target="_blank" title="View in Plex Web">
|
||||
<a href="https://app.plex.tv/web/app#!/server/${config['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${data['rating_key']}" target="_blank" title="View in Plex Web">
|
||||
% endif
|
||||
% if data['media_type'] == 'episode':
|
||||
<div class="summary-poster-face-episode" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=500&height=280&fallback=art);">
|
||||
@@ -250,7 +253,7 @@ DOCUMENTATION :: END
|
||||
</div>
|
||||
% endif
|
||||
<div class="summary-content-summary">
|
||||
<p> ${data['summary']} </p>
|
||||
<p> ${data['summary'] | br, n} </p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -65,8 +65,8 @@ function confirmAjaxCall(url, msg, loader_msg, callback) {
|
||||
url: url,
|
||||
type: 'POST',
|
||||
complete: function (xhr, status) {
|
||||
result = $.parseJSON(xhr.responseText);
|
||||
msg = result.message;
|
||||
var result = $.parseJSON(xhr.responseText);
|
||||
var msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
showMsg('<i class="fa fa-check"></i> ' + msg, false, true, 5000)
|
||||
} else {
|
||||
@@ -80,9 +80,9 @@ function confirmAjaxCall(url, msg, loader_msg, callback) {
|
||||
});
|
||||
}
|
||||
|
||||
function doAjaxCall(url, elem, reload, form, callback) {
|
||||
function doAjaxCall(url, elem, reload, form, showMsg, callback) {
|
||||
// Set Message
|
||||
feedback = $("#ajaxMsg");
|
||||
feedback = (showMsg) ? $("#ajaxMsg") : $();
|
||||
update = $("#updatebar");
|
||||
if (update.is(":visible")) {
|
||||
var height = update.height() + 35;
|
||||
@@ -256,6 +256,8 @@ function getPlatformImagePath(platformName) {
|
||||
return 'images/platforms/wp.png';
|
||||
} else if (platformName.indexOf("Plex Media Player") > -1) {
|
||||
return 'images/platforms/pmp.png';
|
||||
} else if (platformName.indexOf("PlexTogether") > -1) {
|
||||
return 'images/platforms/plextogether.png';
|
||||
} else {
|
||||
return 'images/platforms/default.png';
|
||||
}
|
||||
@@ -448,4 +450,21 @@ $('*').on('click', '.refresh_pms_image', function (e) {
|
||||
background_div.css('background-image', 'url(' + pms_proxy_url + '&refresh=true)');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Taken from http://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable#answer-14919494
|
||||
function humanFileSize(bytes, si) {
|
||||
var thresh = si ? 1000 : 1024;
|
||||
if (Math.abs(bytes) < thresh) {
|
||||
return bytes + ' B';
|
||||
}
|
||||
var units = si
|
||||
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||
var u = -1;
|
||||
do {
|
||||
bytes /= thresh;
|
||||
++u;
|
||||
} while (Math.abs(bytes) >= thresh && u < units.length - 1);
|
||||
return bytes.toFixed(1) + ' ' + units[u];
|
||||
}
|
@@ -4,7 +4,7 @@ var time_format = 'hh:mm a';
|
||||
$.ajax({
|
||||
url: 'get_date_formats',
|
||||
type: 'GET',
|
||||
success: function(data) {
|
||||
success: function (data) {
|
||||
date_format = data.date_format;
|
||||
time_format = data.time_format;
|
||||
}
|
||||
@@ -16,10 +16,10 @@ media_info_table_options = {
|
||||
"destroy": true,
|
||||
"language": {
|
||||
"search": "Search: ",
|
||||
"lengthMenu":"Show _MENU_ entries per page",
|
||||
"info":"Showing _START_ to _END_ of _TOTAL_ library items",
|
||||
"infoEmpty":"Showing 0 to 0 of 0 entries",
|
||||
"infoFiltered":"<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
|
||||
"lengthMenu": "Show _MENU_ entries per page",
|
||||
"info": "Showing _START_ to _END_ of _TOTAL_ library items",
|
||||
"infoEmpty": "Showing 0 to 0 of 0 entries",
|
||||
"infoFiltered": "<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
|
||||
"emptyTable": "No data in table",
|
||||
"loadingRecords": '<i class="fa fa-refresh fa-spin"></i> Loading items...</div>'
|
||||
},
|
||||
@@ -28,7 +28,7 @@ media_info_table_options = {
|
||||
"processing": false,
|
||||
"serverSide": true,
|
||||
"pageLength": 25,
|
||||
"order": [ 1, 'asc'],
|
||||
"order": [1, 'asc'],
|
||||
"autoWidth": false,
|
||||
"scrollX": true,
|
||||
"columnDefs": [
|
||||
@@ -110,7 +110,7 @@ media_info_table_options = {
|
||||
},
|
||||
"width": "20%",
|
||||
"className": "no-wrap",
|
||||
},
|
||||
},
|
||||
{
|
||||
"targets": [2],
|
||||
"data": "container",
|
||||
@@ -194,7 +194,7 @@ media_info_table_options = {
|
||||
"data": "file_size",
|
||||
"createdCell": function (td, cellData, rowData, row, col) {
|
||||
if (cellData !== null && cellData !== '') {
|
||||
$(td).html(Math.round(cellData / Math.pow(1024, 2)).toString() + ' MiB');
|
||||
$(td).html(humanFileSize(cellData));
|
||||
} else {
|
||||
if (rowData['section_type'] != 'photo' && get_file_sizes != null) {
|
||||
get_file_sizes = true;
|
||||
@@ -280,10 +280,10 @@ media_info_table_options = {
|
||||
}
|
||||
|
||||
$("#media_info_table-SID-" + section_id + "_info").append('<span class="hidden-md hidden-sm hidden-xs"> with a total file size of ' +
|
||||
Math.round(settings.json.filtered_file_size / Math.pow(1024, 3)).toString() + ' GiB' +
|
||||
' (filtered from ' + Math.round(settings.json.total_file_size / Math.pow(1024, 3)).toString() + ' GiB)</span>');
|
||||
humanFileSize(settings.json.filtered_file_size) +
|
||||
' (filtered from ' + humanFileSize(settings.json.total_file_size) + ')</span>');
|
||||
},
|
||||
"preDrawCallback": function(settings) {
|
||||
"preDrawCallback": function (settings) {
|
||||
var msg = "<i class='fa fa-refresh fa-spin'></i> Fetching rows...";
|
||||
showMsg(msg, false, false, 0)
|
||||
},
|
||||
@@ -425,17 +425,17 @@ function childTableFormatMedia(rowData) {
|
||||
'<table id="media_info_child-' + rowData['rating_key'] + '" data-id="' + rowData['rating_key'] + '" width="100%">' +
|
||||
'<thead>' +
|
||||
'<tr>' +
|
||||
'<th align="left" id="added_at">Added At</th>' +
|
||||
'<th align="left" id="title">Title</th>' +
|
||||
'<th align="left" id="container">Container</th>' +
|
||||
'<th align="left" id="bitrate">Bitrate</th>' +
|
||||
'<th align="left" id="video_codec">Video Codec</th>' +
|
||||
'<th align="left" id="video_resolution">Video Resolution</th>' +
|
||||
'<th align="left" id="video_resolution">Video Framerate</th>' +
|
||||
'<th align="left" id="audio_codec">Audio Codec</th>' +
|
||||
'<th align="left" id="audio_channels">Audio Channels</th>' +
|
||||
'<th align="left" id="file_size">File Size</th>' +
|
||||
'<th align="left" id="last_played">Last Played</th>' +
|
||||
'<th align="left" id="added_at">Added At</th>' +
|
||||
'<th align="left" id="title">Title</th>' +
|
||||
'<th align="left" id="container">Container</th>' +
|
||||
'<th align="left" id="bitrate">Bitrate</th>' +
|
||||
'<th align="left" id="video_codec">Video Codec</th>' +
|
||||
'<th align="left" id="video_resolution">Video Resolution</th>' +
|
||||
'<th align="left" id="video_resolution">Video Framerate</th>' +
|
||||
'<th align="left" id="audio_codec">Audio Codec</th>' +
|
||||
'<th align="left" id="audio_channels">Audio Channels</th>' +
|
||||
'<th align="left" id="file_size">File Size</th>' +
|
||||
'<th align="left" id="last_played">Last Played</th>' +
|
||||
'<th align="left" id="total_plays">Total Plays</th>' +
|
||||
'</tr>' +
|
||||
'</thead>' +
|
||||
|
@@ -28,7 +28,7 @@
|
||||
<option disabled>────────────</option>
|
||||
<option value="DEBUG">Debug</option>
|
||||
<option value="INFO">Info</option>
|
||||
<option value="WARN">Warning</option>
|
||||
<option value="WARNING">Warning</option>
|
||||
<option value="ERROR">Error</option>
|
||||
</select>
|
||||
</label>
|
||||
@@ -198,7 +198,7 @@
|
||||
var selected_log_level = null;
|
||||
function loadPlexPyLogs(selected_log_level) {
|
||||
log_table_options.ajax = {
|
||||
url: "getLog",
|
||||
url: "get_log",
|
||||
type: 'post',
|
||||
data: function (d) {
|
||||
return {
|
||||
|
@@ -148,7 +148,7 @@
|
||||
|
||||
$('#save-notification-item').click(function () {
|
||||
// Reload modal to update certain fields
|
||||
doAjaxCall('set_notification_config', $(this), 'tabs', true, reloadModal);
|
||||
doAjaxCall('set_notification_config', $(this), 'tabs', true, true, reloadModal);
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
})
|
||||
|
||||
$('#test_notifier').click(function () {
|
||||
doAjaxCall('set_notification_config', $(this), 'tabs', true, sendTestNotification);
|
||||
doAjaxCall('set_notification_config', $(this), 'tabs', true, false, sendTestNotification);
|
||||
});
|
||||
|
||||
function sendTestNotification() {
|
||||
@@ -219,7 +219,7 @@
|
||||
|
||||
$('#pushbullet_apikey, #pushover_apitoken, #scripts_folder, #join_apikey').on('change', function () {
|
||||
// Reload modal to update certain fields
|
||||
doAjaxCall('set_notification_config', $(this), 'tabs', true, reloadModal);
|
||||
doAjaxCall('set_notification_config', $(this), 'tabs', true, false, reloadModal);
|
||||
return false;
|
||||
});
|
||||
|
||||
|
@@ -117,6 +117,12 @@
|
||||
</div>
|
||||
<p class="help-block">Set your preferred time format. <a href="javascript:void(0)" data-target="#dateTimeOptionsModal" data-toggle="modal">Click here</a> to see the parameter list.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="week_start_monday" name="week_start_monday" value="1" ${config['week_start_monday']}> Week Starting on Monday
|
||||
</label>
|
||||
<p class="help-block">Change the "<em>Play by day of week</em>" graph to start on Monday. Default is start on Sunday.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" id="group_history_tables" name="group_history_tables" value="1" ${config['group_history_tables']}> Group Table and Watch Statistics History
|
||||
@@ -129,6 +135,16 @@
|
||||
</label>
|
||||
<p class="help-block">Include current activity in the history tables. Statistics will not be counted until the stream has ended.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notify_watched_percent">Watched Percent</label>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<input type="text" class="form-control" data-parsley-type="integer" id="notify_watched_percent" name="notify_watched_percent" value="${config['notify_watched_percent']}" size="5" data-parsley-range="[50,95]" data-parsley-trigger="change" data-parsley-errors-container="#notify_watched_percent_error" required>
|
||||
</div>
|
||||
<div id="notify_watched_percent_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
<p class="help-block">Set the percentage for a media item to be considered as watched. Minimum 50, Maximum 95.</p>
|
||||
</div>
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Backup</h3>
|
||||
@@ -143,6 +159,19 @@
|
||||
</div>
|
||||
<p class="help-block">The interval (in hours) PlexPy will backup the database and configuration file. Minimum 1, maximum 24, default 6.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="backup_interval">Backup Days</label>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<input type="text" class="form-control" data-parsley-type="integer" id="backup_days" name="backup_days" value="${config['backup_days']}" size="5" data-parsley-min="1" data-parsley-trigger="change" data-parsley-errors-container="#backup_days_error" required>
|
||||
</div>
|
||||
<div id="backup_days_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
The number of days to keep scheduled backups. Minimum 1, default 3.<br />
|
||||
Note: Manual backups are not removed automatically.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Directories</h3>
|
||||
@@ -518,7 +547,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" name="api_key" id="api_key" value="${config['api_key']}" size="20">
|
||||
<input class="form-control" type="text" name="api_key" id="api_key" value="${config['api_key']}" size="20" readonly>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-form" type="button" id="generate_api">Generate</button>
|
||||
</span>
|
||||
@@ -736,6 +765,20 @@
|
||||
</div>
|
||||
<p class="help-block">Backlink protection via anonymizer service, must end in "?".</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Flush Temporary Sessions</label>
|
||||
<p class="help-block">
|
||||
Attempt to fix history logging by flushing out all of the temporary sessions in the database.<br />
|
||||
Warning: This will reset all currently active sessions. For emergency use only when history logging is stuck!
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-form" type="button" id="delete_temp_sessions">Flush</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="padded-header">
|
||||
<h3>Database Import Tool</h3>
|
||||
@@ -887,16 +930,6 @@
|
||||
<h3>Current Activity Notifications</h3>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="notify_watched_percent">Watched Percent</label>
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<input type="text" class="form-control" data-parsley-type="integer" id="notify_watched_percent" name="notify_watched_percent" value="${config['notify_watched_percent']}" size="5" data-parsley-range="[50,95]" data-parsley-trigger="change" data-parsley-errors-container="#notify_watched_percent_error" required>
|
||||
</div>
|
||||
<div id="notify_watched_percent_error" class="alert alert-danger settings-alert" role="alert"></div>
|
||||
</div>
|
||||
<p class="help-block">Set the progress percentage of when a watched notification should be triggered. Minimum 50, Maximum 95.</p>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="notify_consecutive" id="notify_consecutive" value="1" ${config['notify_consecutive']}> Allow Consecutive Notifications
|
||||
@@ -1533,11 +1566,11 @@
|
||||
<div class="modal-body" id="modal-text">
|
||||
<div>
|
||||
<p class="help-block">
|
||||
This will attempt to fetch your token for you. This will not work on Internet Explorer 9 or lower.
|
||||
PlexPy does not store your username and password.
|
||||
This will attempt to fetch a new Plex.tv token for you. PlexPy does not store your username and password.
|
||||
Note: This will not work on Internet Explorer 9 or lower.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label for="pms_username">PMS Username</label>
|
||||
<label for="pms_username">Plex.tv Username</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control" id="pms_username" name="pms_username" size="30">
|
||||
@@ -1546,7 +1579,7 @@
|
||||
<p class="help-block">Username for Plex.tv authentication.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="pms_password">PMS Password</label>
|
||||
<label for="pms_password">Plex.tv Password</label>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="password" class="form-control" id="pms_password" name="pms_password" size="30">
|
||||
@@ -2190,7 +2223,7 @@ $(document).ready(function() {
|
||||
|
||||
function saveSettings() {
|
||||
if (configForm.parsley().validate()) {
|
||||
doAjaxCall('configUpdate', $(this), 'tabs', true, postSaveChecks);
|
||||
doAjaxCall('configUpdate', $(this), 'tabs', true, true, postSaveChecks);
|
||||
return false;
|
||||
} else {
|
||||
showMsg('<i class="fa fa-exclamation-circle"></i> Please verify your settings.', false, true, 5000, true)
|
||||
@@ -2273,6 +2306,12 @@ $(document).ready(function() {
|
||||
confirmAjaxCall(url, msg);
|
||||
});
|
||||
|
||||
$("#delete_temp_sessions").click(function () {
|
||||
var msg = 'Are you sure you want to flush the temporary sessions?<br /><strong>This will reset all currently active sessions.</strong>';
|
||||
var url = 'delete_temp_sessions';
|
||||
confirmAjaxCall(url, msg);
|
||||
});
|
||||
|
||||
$('#api_key').click(function(){ $('#api_key').select() });
|
||||
$("#generate_api").click(function() {
|
||||
$.get('generateAPI',
|
||||
@@ -2368,21 +2407,24 @@ $(document).ready(function() {
|
||||
if ((pms_username !== '') && (pms_password !== '')) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'get_pms_token',
|
||||
url: 'get_plexpy_pms_token',
|
||||
data: {
|
||||
username: pms_username,
|
||||
password: pms_password
|
||||
password: pms_password,
|
||||
force: true
|
||||
},
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function(xhr, status) {
|
||||
var authToken = $.parseJSON(xhr.responseText);
|
||||
if (authToken) {
|
||||
$("#pms-token-status").html('<i class="fa fa-check"></i> Authentication successful!');
|
||||
var result = $.parseJSON(xhr.responseText);
|
||||
var msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
var authToken = result.token;
|
||||
$("#pms-token-status").html('<i class="fa fa-check"></i> ' + msg);
|
||||
$("#pms_token").val(authToken);
|
||||
$('#pms-auth-modal').modal('hide');
|
||||
} else {
|
||||
$("#pms-token-status").html('<i class="fa fa-exclamation-circle"></i> Invalid username or password.');
|
||||
$("#pms-token-status").html('<i class="fa fa-exclamation-circle"></i> ' + msg);
|
||||
}
|
||||
loadUpdateDistros();
|
||||
}
|
||||
|
@@ -47,7 +47,7 @@
|
||||
|
||||
// Redirect to home page after countdown.
|
||||
function reloadPage() {
|
||||
window.location.href = "index";
|
||||
window.location.href = "${new_http_root}index";
|
||||
}
|
||||
</script>
|
||||
</%def>
|
||||
|
@@ -418,7 +418,7 @@
|
||||
if ((pms_username !== '') && (pms_password !== '')) {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'get_pms_token',
|
||||
url: 'get_plexpy_pms_token',
|
||||
data: {
|
||||
username: pms_username,
|
||||
password: pms_password
|
||||
@@ -426,15 +426,17 @@
|
||||
cache: false,
|
||||
async: true,
|
||||
complete: function (xhr, status) {
|
||||
var authToken = $.parseJSON(xhr.responseText);
|
||||
if (authToken) {
|
||||
$("#pms-token-status").html('<i class="fa fa-check"></i> Authentication successful!');
|
||||
var result = $.parseJSON(xhr.responseText);
|
||||
var msg = result.message;
|
||||
if (result.result == 'success') {
|
||||
var authToken = result.token;
|
||||
$("#pms-token-status").html('<i class="fa fa-check"></i> ' + msg);
|
||||
$('#pms-token-status').fadeIn('fast');
|
||||
$("#pms_token").val(authToken);
|
||||
authenticated = true;
|
||||
getServerOptions(authToken)
|
||||
} else {
|
||||
$("#pms-token-status").html('<i class="fa fa-exclamation-circle"></i> Invalid username or password.');
|
||||
$("#pms-token-status").html('<i class="fa fa-exclamation-circle"></i> ' + msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# PROVIDE: plexpy
|
||||
# REQUIRE: sabnzbd
|
||||
# REQUIRE: plexpy
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
|
||||
@@ -10,7 +10,7 @@
|
||||
# plexpy_enable (bool): Set to NO by default.
|
||||
# Set it to YES to enable it.
|
||||
# plexpy_user: The user account PlexPy daemon runs as what
|
||||
# you want it to be. It uses '_sabnzbd' user by
|
||||
# you want it to be. It uses 'plexpy' user by
|
||||
# default. Do not sets it as empty or it will run
|
||||
# as root.
|
||||
# plexpy_dir: Directory where PlexPy lives.
|
||||
@@ -28,7 +28,7 @@ rcvar=${name}_enable
|
||||
load_rc_config ${name}
|
||||
|
||||
: ${plexpy_enable:="NO"}
|
||||
: ${plexpy_user:="_sabnzbd"}
|
||||
: ${plexpy_user:="plexpy"}
|
||||
: ${plexpy_dir:="/usr/local/plexpy"}
|
||||
: ${plexpy_chdir:="${plexpy_dir}"}
|
||||
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
|
||||
|
@@ -1,66 +0,0 @@
|
||||
# PlexPy - Stats for Plex Media Server usage
|
||||
#
|
||||
# Service Unit file for systemd system manager
|
||||
#
|
||||
# INSTALLATION NOTES
|
||||
#
|
||||
# 1. Rename this file as you want, ensuring that it ends in .service
|
||||
# e.g. 'plexpy.service'
|
||||
#
|
||||
# 2. Adjust configuration settings as required. More details in the
|
||||
# "CONFIGURATION NOTES" section shown below.
|
||||
#
|
||||
# 3. Copy this file into your systemd service unit directory, which is
|
||||
# often '/lib/systemd/system'.
|
||||
#
|
||||
# 4. Create any files/directories that you specified back in step #2.
|
||||
# e.g. '/etc/plexpy/plexpy.ini'
|
||||
# '/home/sabnzbd/.plexpy'
|
||||
#
|
||||
# 5. Enable boot-time autostart with the following commands:
|
||||
# systemctl daemon-reload
|
||||
# systemctl enable plexpy.service
|
||||
#
|
||||
# 6. Start now with the following command:
|
||||
# systemctl start plexpy.service
|
||||
#
|
||||
# 7. If troubleshooting startup-errors, start by checking permissions
|
||||
# and ownership on the files/directories that you created in step #4.
|
||||
#
|
||||
#
|
||||
# CONFIGURATION NOTES
|
||||
#
|
||||
# - The example settings in this file assume that:
|
||||
# 1. You will run PlexPy as user/group: sabnzbd.sabnzbd
|
||||
# 2. You will either have PlexPy installed as a subdirectory
|
||||
# under '~sabnzbd', or that you will have a symlink under
|
||||
# '~/sabnzbd' pointing to your PlexPy install dir.
|
||||
# 3. Your PlexPy data directory and configuration file will be
|
||||
# in separate locations from your PlexPy install dir, to
|
||||
# simplify updates.
|
||||
#
|
||||
# - Option names (e.g. ExecStart=, Type=) appear to be case-sensitive)
|
||||
#
|
||||
# - Adjust ExecStart= to point to:
|
||||
# 1. Your PlexPy executable,
|
||||
# 2. Your config file (recommended is to put it somewhere in /etc)
|
||||
# 3. Your datadir (recommended is to NOT put it in your PlexPy exec dir)
|
||||
#
|
||||
# - Adjust User= and Group= to the user/group you want PlexPy to run as.
|
||||
#
|
||||
# - WantedBy= specifies which target (i.e. runlevel) to start PlexPy for.
|
||||
# multi-user.target equates to runlevel 3 (multi-user text mode)
|
||||
# graphical.target equates to runlevel 5 (multi-user X11 graphical mode)
|
||||
|
||||
[Unit]
|
||||
Description=PlexPy - Stats for Plex Media Server usage
|
||||
|
||||
[Service]
|
||||
ExecStart=/home/sabnzbd/plexpy/PlexPy.py --daemon --config /etc/plexpy/plexpy.ini --datadir /home/sabnzbd/.plexpy --nolaunch --quiet
|
||||
GuessMainPID=no
|
||||
Type=forking
|
||||
User=sabnzbd
|
||||
Group=sabnzbd
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# PROVIDE: plexpy
|
||||
# REQUIRE: DAEMON sabnzbd
|
||||
# REQUIRE: DAEMON plexpy
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
|
||||
@@ -10,7 +10,7 @@
|
||||
# plexpy_enable (bool): Set to NO by default.
|
||||
# Set it to YES to enable it.
|
||||
# plexpy_user: The user account PlexPy daemon runs as what
|
||||
# you want it to be. It uses '_sabnzbd' user by
|
||||
# you want it to be. It uses 'plexpy' user by
|
||||
# default. Do not sets it as empty or it will run
|
||||
# as root.
|
||||
# plexpy_dir: Directory where PlexPy lives.
|
||||
@@ -29,7 +29,7 @@ rcvar=${name}_enable
|
||||
load_rc_config ${name}
|
||||
|
||||
: ${plexpy_enable:="NO"}
|
||||
: ${plexpy_user:="_sabnzbd"}
|
||||
: ${plexpy_user:="plexpy"}
|
||||
: ${plexpy_dir:="/usr/local/share/plexpy"}
|
||||
: ${plexpy_chdir:="${plexpy_dir}"}
|
||||
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# PROVIDE: plexpy
|
||||
# REQUIRE: DAEMON sabnzbd
|
||||
# REQUIRE: DAEMON plexpy
|
||||
# KEYWORD: shutdown
|
||||
#
|
||||
# Add the following lines to /etc/rc.conf.local or /etc/rc.conf
|
||||
@@ -10,7 +10,7 @@
|
||||
# plexpy_enable (bool): Set to NO by default.
|
||||
# Set it to YES to enable it.
|
||||
# plexpy_user: The user account PlexPy daemon runs as what
|
||||
# you want it to be. It uses '_sabnzbd' user by
|
||||
# you want it to be. It uses 'plexpy' user by
|
||||
# default. Do not sets it as empty or it will run
|
||||
# as root.
|
||||
# plexpy_dir: Directory where PlexPy lives.
|
||||
@@ -29,7 +29,7 @@ rcvar=${name}_enable
|
||||
load_rc_config ${name}
|
||||
|
||||
: ${plexpy_enable:="NO"}
|
||||
: ${plexpy_user:="_sabnzbd"}
|
||||
: ${plexpy_user:="plexpy"}
|
||||
: ${plexpy_dir:="/usr/local/share/plexpy"}
|
||||
: ${plexpy_chdir:="${plexpy_dir}"}
|
||||
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
|
||||
|
@@ -1,67 +0,0 @@
|
||||
# PlexPy - Stats for Plex Media Server usage
|
||||
#
|
||||
# Service Unit file for systemd system manager
|
||||
#
|
||||
# INSTALLATION NOTES
|
||||
#
|
||||
# 1. Rename this file as you want, ensuring that it ends in .service
|
||||
# e.g. 'plexpy.service'
|
||||
#
|
||||
# 2. Adjust configuration settings as required. More details in the
|
||||
# "CONFIGURATION NOTES" section shown below.
|
||||
#
|
||||
# 3. Copy this file into your systemd service unit directory, which is
|
||||
# often '/lib/systemd/system'.
|
||||
#
|
||||
# 4. Create any files/directories that you specified back in step #2.
|
||||
# e.g. '/opt/plexpy.ini'
|
||||
# '/opt/plexpy'
|
||||
#
|
||||
# 5. Enable boot-time autostart with the following commands:
|
||||
# systemctl daemon-reload
|
||||
# systemctl enable plexpy.service
|
||||
#
|
||||
# 6. Start now with the following command:
|
||||
# systemctl start plexpy.service
|
||||
#
|
||||
# 7. If troubleshooting startup-errors, start by checking permissions
|
||||
# and ownership on the files/directories that you created in step #4.
|
||||
#
|
||||
#
|
||||
# CONFIGURATION NOTES
|
||||
#
|
||||
# - The example settings in this file assume that:
|
||||
# 1. You will run PlexPy as user/group: plex.users
|
||||
# 2. You will either have PlexPy installed as a subdirectory
|
||||
# under '/opt', or that you will have a symlink under
|
||||
# '/opt' pointing to your PlexPy install dir.
|
||||
# 3. Your PlexPy data directory and configuration file can be
|
||||
# in separate locations from your PlexPy install dir, to
|
||||
# simplify updates. However, in the example below they are in the
|
||||
# PlexPy install dir.
|
||||
#
|
||||
# - Option names (e.g. ExecStart=, Type=) appear to be case-sensitive)
|
||||
#
|
||||
# - Adjust ExecStart= to point to:
|
||||
# 1. Your PlexPy executable,
|
||||
# 2. Your config file (recommended is to put it somewhere in /etc)
|
||||
# 3. Your datadir (recommended is to NOT put it in your PlexPy exec dir)
|
||||
#
|
||||
# - Adjust User= and Group= to the user/group you want PlexPy to run as.
|
||||
#
|
||||
# - WantedBy= specifies which target (i.e. runlevel) to start PlexPy for.
|
||||
# multi-user.target equates to runlevel 3 (multi-user text mode)
|
||||
# graphical.target equates to runlevel 5 (multi-user X11 graphical mode)
|
||||
|
||||
[Unit]
|
||||
Description=PlexPy - Stats for Plex Media Server usage
|
||||
|
||||
[Service]
|
||||
ExecStart=/opt/plexpy/PlexPy.py --daemon --config /opt/plexpy/config.ini --datadir /opt/plexpy --nolaunch --quiet
|
||||
GuessMainPID=no
|
||||
Type=forking
|
||||
User=plex
|
||||
Group=users
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
@@ -19,7 +19,7 @@
|
||||
</dependency>
|
||||
|
||||
<method_context>
|
||||
<method_credential user="sabnzbd" group="sabnzbd"/>
|
||||
<method_credential user="plexpy" group="nogroup"/>
|
||||
</method_context>
|
||||
|
||||
<exec_method type="method" name="start" exec="python /opt/plexpy/PlexPy.py --daemon --quiet --nolaunch" timeout_seconds="60"/>
|
||||
|
@@ -13,32 +13,20 @@
|
||||
# 3. Copy this file into your systemd service unit directory, which is
|
||||
# often '/lib/systemd/system'.
|
||||
#
|
||||
# 4. Create any files/directories that you specified back in step #2.
|
||||
# e.g. '/etc/plexpy/plexpy.ini'
|
||||
# '/home/sabnzbd/.plexpy'
|
||||
#
|
||||
# 5. Enable boot-time autostart with the following commands:
|
||||
# 4. Enable boot-time autostart with the following commands:
|
||||
# systemctl daemon-reload
|
||||
# systemctl enable plexpy.service
|
||||
#
|
||||
# 6. Start now with the following command:
|
||||
# 5. Start now with the following command:
|
||||
# systemctl start plexpy.service
|
||||
#
|
||||
# 7. If troubleshooting startup-errors, start by checking permissions
|
||||
# and ownership on the files/directories that you created in step #4.
|
||||
#
|
||||
#
|
||||
# CONFIGURATION NOTES
|
||||
#
|
||||
# - The example settings in this file assume that:
|
||||
# 1. You will run PlexPy as user/group: sabnzbd.sabnzbd
|
||||
# 2. You will either have PlexPy installed as a subdirectory
|
||||
# under '~sabnzbd', or that you will have a symlink under
|
||||
# '~/sabnzbd' pointing to your PlexPy install dir.
|
||||
# 3. Your PlexPy data directory and configuration file will be
|
||||
# in separate locations from your PlexPy install dir, to
|
||||
# simplify updates.
|
||||
#
|
||||
# - The example settings in this file assume that you will run PlexPy as user: plexpy
|
||||
# - To create this user and give it ownership of the plexpy directory:
|
||||
# sudo adduser --system --no-create-home plexpy
|
||||
# sudo chown plexpy:nogroup -R /opt/plexpy
|
||||
#
|
||||
# - Option names (e.g. ExecStart=, Type=) appear to be case-sensitive)
|
||||
#
|
||||
# - Adjust ExecStart= to point to:
|
||||
@@ -63,4 +51,4 @@ User=plexpy
|
||||
Group=nogroup
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
WantedBy=multi-user.target
|
@@ -8,9 +8,9 @@ See the datetime section of the Python Library Reference for information
|
||||
on how to use these modules.
|
||||
'''
|
||||
|
||||
# The Olson database is updated several times a year.
|
||||
OLSON_VERSION = '2014j'
|
||||
VERSION = '2014.10' # Switching to pip compatible version numbering.
|
||||
# The IANA (nee Olson) database is updated several times a year.
|
||||
OLSON_VERSION = '2016f'
|
||||
VERSION = '2016.6.1' # Switching to pip compatible version numbering.
|
||||
__version__ = VERSION
|
||||
|
||||
OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling
|
||||
@@ -25,11 +25,6 @@ __all__ = [
|
||||
|
||||
import sys, datetime, os.path, gettext
|
||||
|
||||
try:
|
||||
from pkg_resources import resource_stream
|
||||
except ImportError:
|
||||
resource_stream = None
|
||||
|
||||
from pytz.exceptions import AmbiguousTimeError
|
||||
from pytz.exceptions import InvalidTimeError
|
||||
from pytz.exceptions import NonExistentTimeError
|
||||
@@ -57,7 +52,7 @@ except NameError: # Python 3.x
|
||||
...
|
||||
UnicodeEncodeError: ...
|
||||
"""
|
||||
s.encode('US-ASCII') # Raise an exception if not ASCII
|
||||
s.encode('ASCII') # Raise an exception if not ASCII
|
||||
return s # But return the original string - not a byte string.
|
||||
|
||||
else: # Python 2.x
|
||||
@@ -73,7 +68,7 @@ else: # Python 2.x
|
||||
...
|
||||
UnicodeEncodeError: ...
|
||||
"""
|
||||
return s.encode('US-ASCII')
|
||||
return s.encode('ASCII')
|
||||
|
||||
|
||||
def open_resource(name):
|
||||
@@ -88,11 +83,17 @@ def open_resource(name):
|
||||
raise ValueError('Bad path segment: %r' % part)
|
||||
filename = os.path.join(os.path.dirname(__file__),
|
||||
'zoneinfo', *name_parts)
|
||||
if not os.path.exists(filename) and resource_stream is not None:
|
||||
if not os.path.exists(filename):
|
||||
# http://bugs.launchpad.net/bugs/383171 - we avoid using this
|
||||
# unless absolutely necessary to help when a broken version of
|
||||
# pkg_resources is installed.
|
||||
return resource_stream(__name__, 'zoneinfo/' + name)
|
||||
try:
|
||||
from pkg_resources import resource_stream
|
||||
except ImportError:
|
||||
resource_stream = None
|
||||
|
||||
if resource_stream is not None:
|
||||
return resource_stream(__name__, 'zoneinfo/' + name)
|
||||
return open(filename, 'rb')
|
||||
|
||||
|
||||
@@ -110,7 +111,7 @@ def resource_exists(name):
|
||||
# module, as well as the Zope3 i18n package. Perhaps we should just provide
|
||||
# the POT file and translations, and leave it up to callers to make use
|
||||
# of them.
|
||||
#
|
||||
#
|
||||
# t = gettext.translation(
|
||||
# 'pytz', os.path.join(os.path.dirname(__file__), 'locales'),
|
||||
# fallback=True
|
||||
@@ -123,7 +124,7 @@ def resource_exists(name):
|
||||
_tzinfo_cache = {}
|
||||
|
||||
def timezone(zone):
|
||||
r''' Return a datetime.tzinfo implementation for the given timezone
|
||||
r''' Return a datetime.tzinfo implementation for the given timezone
|
||||
|
||||
>>> from datetime import datetime, timedelta
|
||||
>>> utc = timezone('UTC')
|
||||
@@ -241,13 +242,13 @@ class UTC(datetime.tzinfo):
|
||||
return "UTC"
|
||||
|
||||
|
||||
UTC = utc = UTC() # UTC is a singleton
|
||||
UTC = utc = UTC() # UTC is a singleton
|
||||
|
||||
|
||||
def _UTC():
|
||||
"""Factory function for utc unpickling.
|
||||
|
||||
Makes sure that unpickling a utc instance always returns the same
|
||||
Makes sure that unpickling a utc instance always returns the same
|
||||
module global.
|
||||
|
||||
These examples belong in the UTC class above, but it is obscured; or in
|
||||
@@ -329,7 +330,7 @@ class _CountryTimezoneDict(LazyDict):
|
||||
zone_tab = open_resource('zone.tab')
|
||||
try:
|
||||
for line in zone_tab:
|
||||
line = line.decode('US-ASCII')
|
||||
line = line.decode('UTF-8')
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
code, coordinates, zone = line.split(None, 4)[:3]
|
||||
@@ -357,7 +358,7 @@ class _CountryNameDict(LazyDict):
|
||||
zone_tab = open_resource('iso3166.tab')
|
||||
try:
|
||||
for line in zone_tab.readlines():
|
||||
line = line.decode('US-ASCII')
|
||||
line = line.decode('UTF-8')
|
||||
if line.startswith('#'):
|
||||
continue
|
||||
code, name = line.split(None, 1)
|
||||
@@ -404,9 +405,11 @@ class _FixedOffset(datetime.tzinfo):
|
||||
|
||||
def normalize(self, dt, is_dst=False):
|
||||
'''Correct the timezone information on the given datetime'''
|
||||
if dt.tzinfo is self:
|
||||
return dt
|
||||
if dt.tzinfo is None:
|
||||
raise ValueError('Naive time - no tzinfo set')
|
||||
return dt.replace(tzinfo=self)
|
||||
return dt.astimezone(self)
|
||||
|
||||
|
||||
def FixedOffset(offset, _tzinfos = {}):
|
||||
@@ -599,6 +602,7 @@ all_timezones = \
|
||||
'America/Eirunepe',
|
||||
'America/El_Salvador',
|
||||
'America/Ensenada',
|
||||
'America/Fort_Nelson',
|
||||
'America/Fort_Wayne',
|
||||
'America/Fortaleza',
|
||||
'America/Glace_Bay',
|
||||
@@ -731,6 +735,7 @@ all_timezones = \
|
||||
'Asia/Bahrain',
|
||||
'Asia/Baku',
|
||||
'Asia/Bangkok',
|
||||
'Asia/Barnaul',
|
||||
'Asia/Beirut',
|
||||
'Asia/Bishkek',
|
||||
'Asia/Brunei',
|
||||
@@ -802,6 +807,7 @@ all_timezones = \
|
||||
'Asia/Thimbu',
|
||||
'Asia/Thimphu',
|
||||
'Asia/Tokyo',
|
||||
'Asia/Tomsk',
|
||||
'Asia/Ujung_Pandang',
|
||||
'Asia/Ulaanbaatar',
|
||||
'Asia/Ulan_Bator',
|
||||
@@ -907,6 +913,7 @@ all_timezones = \
|
||||
'Etc/Zulu',
|
||||
'Europe/Amsterdam',
|
||||
'Europe/Andorra',
|
||||
'Europe/Astrakhan',
|
||||
'Europe/Athens',
|
||||
'Europe/Belfast',
|
||||
'Europe/Belgrade',
|
||||
@@ -927,6 +934,7 @@ all_timezones = \
|
||||
'Europe/Jersey',
|
||||
'Europe/Kaliningrad',
|
||||
'Europe/Kiev',
|
||||
'Europe/Kirov',
|
||||
'Europe/Lisbon',
|
||||
'Europe/Ljubljana',
|
||||
'Europe/London',
|
||||
@@ -954,6 +962,7 @@ all_timezones = \
|
||||
'Europe/Tallinn',
|
||||
'Europe/Tirane',
|
||||
'Europe/Tiraspol',
|
||||
'Europe/Ulyanovsk',
|
||||
'Europe/Uzhgorod',
|
||||
'Europe/Vaduz',
|
||||
'Europe/Vatican',
|
||||
@@ -1177,6 +1186,7 @@ common_timezones = \
|
||||
'America/Edmonton',
|
||||
'America/Eirunepe',
|
||||
'America/El_Salvador',
|
||||
'America/Fort_Nelson',
|
||||
'America/Fortaleza',
|
||||
'America/Glace_Bay',
|
||||
'America/Godthab',
|
||||
@@ -1224,7 +1234,6 @@ common_timezones = \
|
||||
'America/Moncton',
|
||||
'America/Monterrey',
|
||||
'America/Montevideo',
|
||||
'America/Montreal',
|
||||
'America/Montserrat',
|
||||
'America/Nassau',
|
||||
'America/New_York',
|
||||
@@ -1249,7 +1258,6 @@ common_timezones = \
|
||||
'America/Regina',
|
||||
'America/Resolute',
|
||||
'America/Rio_Branco',
|
||||
'America/Santa_Isabel',
|
||||
'America/Santarem',
|
||||
'America/Santiago',
|
||||
'America/Santo_Domingo',
|
||||
@@ -1297,6 +1305,7 @@ common_timezones = \
|
||||
'Asia/Bahrain',
|
||||
'Asia/Baku',
|
||||
'Asia/Bangkok',
|
||||
'Asia/Barnaul',
|
||||
'Asia/Beirut',
|
||||
'Asia/Bishkek',
|
||||
'Asia/Brunei',
|
||||
@@ -1356,6 +1365,7 @@ common_timezones = \
|
||||
'Asia/Tehran',
|
||||
'Asia/Thimphu',
|
||||
'Asia/Tokyo',
|
||||
'Asia/Tomsk',
|
||||
'Asia/Ulaanbaatar',
|
||||
'Asia/Urumqi',
|
||||
'Asia/Ust-Nera',
|
||||
@@ -1394,6 +1404,7 @@ common_timezones = \
|
||||
'Canada/Pacific',
|
||||
'Europe/Amsterdam',
|
||||
'Europe/Andorra',
|
||||
'Europe/Astrakhan',
|
||||
'Europe/Athens',
|
||||
'Europe/Belgrade',
|
||||
'Europe/Berlin',
|
||||
@@ -1413,6 +1424,7 @@ common_timezones = \
|
||||
'Europe/Jersey',
|
||||
'Europe/Kaliningrad',
|
||||
'Europe/Kiev',
|
||||
'Europe/Kirov',
|
||||
'Europe/Lisbon',
|
||||
'Europe/Ljubljana',
|
||||
'Europe/London',
|
||||
@@ -1438,6 +1450,7 @@ common_timezones = \
|
||||
'Europe/Stockholm',
|
||||
'Europe/Tallinn',
|
||||
'Europe/Tirane',
|
||||
'Europe/Ulyanovsk',
|
||||
'Europe/Uzhgorod',
|
||||
'Europe/Vaduz',
|
||||
'Europe/Vatican',
|
||||
|
@@ -15,13 +15,13 @@ from pytz.tzinfo import memorized_datetime, memorized_timedelta
|
||||
|
||||
def _byte_string(s):
|
||||
"""Cast a string or byte string to an ASCII byte string."""
|
||||
return s.encode('US-ASCII')
|
||||
return s.encode('ASCII')
|
||||
|
||||
_NULL = _byte_string('\0')
|
||||
|
||||
def _std_string(s):
|
||||
"""Cast a string or byte string to an ASCII string."""
|
||||
return str(s.decode('US-ASCII'))
|
||||
return str(s.decode('ASCII'))
|
||||
|
||||
def build_tzinfo(zone, fp):
|
||||
head_fmt = '>4s c 15x 6l'
|
||||
@@ -66,7 +66,7 @@ def build_tzinfo(zone, fp):
|
||||
i += 3
|
||||
|
||||
# Now build the timezone object
|
||||
if len(transitions) == 0:
|
||||
if len(ttinfo) ==1 or len(transitions) == 0:
|
||||
ttinfo[0][0], ttinfo[0][2]
|
||||
cls = type(zone, (StaticTzInfo,), dict(
|
||||
zone=zone,
|
||||
|