Compare commits

...

202 Commits

Author SHA1 Message Date
JonnyWong16
08714436c3 v2.1.34 2019-09-03 21:46:48 -07:00
JonnyWong16
f65f5d07c0 Add product to get_history API command 2019-09-03 19:52:54 -07:00
JonnyWong16
a9b10c4560 Add Product column to history tables 2019-09-03 19:52:33 -07:00
JonnyWong16
589fbd3158 Add TVMaze and TMDB IDs to notification parameters after lookup 2019-08-27 19:54:54 -07:00
JonnyWong16
0ffc8c5d19 Update JWT secret instead of UUID 2019-08-24 22:00:31 -07:00
JonnyWong16
7498617b74 Flag update JWT UUID after restarting 2019-08-24 21:13:39 -07:00
JonnyWong16
f21d505ab8 Force logout all clients when changing the admin password 2019-08-24 20:49:56 -07:00
JonnyWong16
7b16af0585 Fix verifying PMS with unpublished hostnames (Fixes Tautulli/Tautulli-Issues#190) 2019-08-16 21:27:34 -07:00
JonnyWong16
a83108282a Fix add title to searchable media info fields 2019-08-14 22:50:23 -07:00
JonnyWong16
1c4d01d6ec Fix libraries/users table respect grouping setting 2019-08-09 19:15:53 -07:00
JonnyWong16
22e6d4067d Missing space for episode titles on tables 2019-08-09 19:01:27 -07:00
JonnyWong16
1046b29c1a v2.1.33 2019-07-27 08:44:26 -07:00
JonnyWong16
d6127e28f3 Remove email sort 2019-07-22 09:03:20 -07:00
JonnyWong16
25a949356d Allow seraching by email address in dropdown menu 2019-07-21 09:46:17 -07:00
JonnyWong16
72a012b817 Update Plex OAuth headers 2019-07-18 11:39:16 -07:00
JonnyWong16
f439bd639c Make sure config has name and value when masking passwords 2019-07-10 21:37:32 -07:00
JonnyWong16
91476a420a Mask notifier and newsletter config passwords (Fixes Tautulli/Tautulli-Issues#172) 2019-07-08 23:50:48 -07:00
JonnyWong16
96c0f9cad5 Add if Docker container to platform 2019-07-08 22:30:58 -07:00
JonnyWong16
df50559495 Fix overwriting version number with None 2019-07-02 08:58:56 -07:00
JonnyWong16
6d35bd7947 v2.1.32 2019-06-26 19:14:30 -07:00
JonnyWong16
d27356bbba Fix timezone error with newsletter scheduler because QNAP devices use a stupid "local" timezone (Fixes Tautulli/Tautulli-Issues#183) 2019-06-26 19:11:33 -07:00
JonnyWong16
3054a824ce v2.1.31 2019-06-24 21:45:57 -07:00
JonnyWong16
3b22b6a3f7 v2.1.31-beta 2019-06-13 22:14:42 -07:00
JonnyWong16
e4be5a716f Fix unable to view database status when auth is disabled 2019-06-13 21:57:05 -07:00
JonnyWong16
13579b8140 Change default database synchronous mode to normal 2019-06-09 10:48:59 -07:00
JonnyWong16
b11437b86b Fix synced content showing incorrect stream info 2019-05-12 17:30:04 -07:00
JonnyWong16
4b744a5acd v2.1.30-beta 2019-05-11 10:46:45 -07:00
JonnyWong16
db5be89710 Merge branch 'master' into nightly 2019-05-11 10:45:34 -07:00
JonnyWong16
46e77b074a Add database status link in configuration table 2019-05-11 10:28:53 -07:00
JonnyWong16
f1376fb2ca Add database integrity check to status API 2019-05-11 10:28:52 -07:00
JonnyWong16
2635569e76 v2.1.29 2019-05-11 09:10:37 -07:00
JonnyWong16
4743538de8 Add status API command 2019-05-11 09:02:18 -07:00
JonnyWong16
235d6f259e Fix missing comma in creating session_history table 2019-05-10 17:54:47 -07:00
JonnyWong16
049a30adbc Update certifi to 2019.03.09 2019-05-02 20:19:42 -07:00
JonnyWong16
e63606f195 Update pytz to 2019.1 2019-05-02 20:13:52 -07:00
JonnyWong16
eea1cf1d2c Schedule jobs using UTC to prevent tiggers in the past 2019-05-02 20:01:39 -07:00
JonnyWong16
43b350a1cc Improve timezone fallback 2019-05-02 20:00:32 -07:00
JonnyWong16
87a77f0522 Fix queue tables displaying intervals greater than 24 hours 2019-04-27 17:03:04 -07:00
JonnyWong16
ce4bfd56c2 Fix scheduler table displaying intervals greater than 24 hours 2019-04-27 16:58:23 -07:00
JonnyWong16
81d443f34c Reorder config 2019-04-27 16:13:25 -07:00
JonnyWong16
4c5602c77c Merge pull request #1336 from abiacco/pms_notify_interval
Adds ability to change notification/check interval for new plex server version
2019-04-27 16:10:49 -07:00
JonnyWong16
feab4115ee Add secure and relayed to notification parameters 2019-04-27 15:54:46 -07:00
JonnyWong16
eae842b09a Store websocket rating key to compare against when mismatched with sessions XML (Fixes Tautulli/Tautulli-Issues#174) 2019-04-27 15:48:31 -07:00
JonnyWong16
9b42bb240a Add secure and relayed to the database 2019-04-27 15:40:33 -07:00
JonnyWong16
d3a7229c00 Fix removing session metadata cache exception IOError to OSError 2019-04-27 10:32:56 -07:00
JonnyWong16
133335a3fb Check section_id > 0 when returning library details 2019-04-27 10:16:11 -07:00
JonnyWong16
32c05136bd Add PLEX_USER_TOKEN to script environment variables 2019-04-18 10:22:04 -07:00
Anthony Biacco
b8d4bb7d69 remove old notify_interval block 2019-04-15 13:45:32 -06:00
JonnyWong16
fc457585fb Update plexpy/webserve.py
Co-Authored-By: abiacco <abiacco@handll.com>
2019-04-15 13:08:04 -06:00
JonnyWong16
74c07167e1 Update plexpy/webserve.py
Co-Authored-By: abiacco <abiacco@handll.com>
2019-04-15 13:07:46 -06:00
JonnyWong16
beabd7bb0f Update plexpy/__init__.py
Co-Authored-By: abiacco <abiacco@handll.com>
2019-04-15 13:07:34 -06:00
JonnyWong16
9a6106f10e Update plexpy/config.py
Co-Authored-By: abiacco <abiacco@handll.com>
2019-04-15 13:07:14 -06:00
JonnyWong16
75949a8dd2 Update plexpy/__init__.py
Co-Authored-By: abiacco <abiacco@handll.com>
2019-04-15 13:07:07 -06:00
JonnyWong16
b92b057ab9 Update data/interfaces/default/settings.html
Co-Authored-By: abiacco <abiacco@handll.com>
2019-04-15 13:06:02 -06:00
JonnyWong16
7d11e1de2d v2.1.29-beta 2019-04-14 13:43:58 -07:00
JonnyWong16
36aa4a6be3 Update API docs 2019-04-13 23:14:41 -07:00
JonnyWong16
24ed63e07c Add undelete button to edit library/user modal 2019-04-13 22:56:32 -07:00
JonnyWong16
f41ed9953a Improve API response codes 2019-04-13 22:53:56 -07:00
JonnyWong16
6970231687 Log script return status code 2019-04-13 22:01:03 -07:00
JonnyWong16
ea036aa354 Fix typo in notifier config triggers 2019-04-13 18:05:40 -07:00
JonnyWong16
d0a7c2f92c Fix typo in notification parameters list 2019-04-10 18:32:54 -07:00
JonnyWong16
f07acd839b Missing closing div tag in c1fd798 2019-04-07 14:22:49 -07:00
JonnyWong16
c1fd798fe9 Add prefix and suffix text modifiers to notifications 2019-04-04 19:21:26 -07:00
JonnyWong16
2f8d2f23fe Add user IP table column IDs 2019-03-30 13:22:01 -07:00
JonnyWong16
b65a30263e Fix user IP address last seen 2019-03-30 12:10:30 -07:00
JonnyWong16
766e33df0e Add getPlexHeaders function 2019-03-26 09:10:09 -07:00
JonnyWong16
68df0f07c8 Return API result error when unauthenticaed 2019-03-21 10:22:34 -07:00
JonnyWong16
819829554b Use global port and root when getting self URL 2019-03-21 08:50:28 -07:00
JonnyWong16
18a38b16b1 Fix a9169d2 2019-03-20 08:58:30 -07:00
JonnyWong16
a9169d2b53 Fix terminate stream when both session_key and session_id are provided 2019-03-20 08:49:15 -07:00
JonnyWong16
76b9b3e474 Improve terminate stream error messages 2019-03-18 11:57:08 -07:00
JonnyWong16
00405f0b18 Change wording in activity header 2019-03-18 10:42:02 -07:00
JonnyWong16
9dfeccdaed Fix update metadata serach only returning 3 results 2019-03-17 18:19:11 -07:00
JonnyWong16
b6d044fe8f Fix API search not using the limit parameter 2019-03-17 17:59:18 -07:00
JonnyWong16
e949b1486e v2.1.28 2019-03-10 14:47:26 -07:00
JonnyWong16
8e1b6efc51 Reword unable to delete from Cloudinary log message 2019-03-10 14:32:45 -07:00
JonnyWong16
00012ffe09 Merge pull request #1344 from Jabcob/patch-1
Update init.systemd
2019-03-10 14:28:01 -07:00
Jabcob
bcf6b4de77 Update init.systemd
Edited user creation and directory ownership instructions in the configuration notes for clarity. Current version may give novice users the impression that the ownership command is only executed on CentOS/Fedora.
2019-03-09 15:39:26 -06:00
JonnyWong16
b1516e9963 Improve mass delete from Cloudinary 2019-03-08 17:49:28 -08:00
JonnyWong16
231de3a7a5 Merge pull request #1343 from samwiseg0/fix/relayed_int
Fix relayed to be an integer vs string
2019-03-06 18:33:05 -08:00
samwiseg0
b611ea659e Fix relayed to be an integer vs string 2019-03-06 17:02:12 -08:00
JonnyWong16
6e9f299c19 Add secure/insecure icon to activity card 2019-03-05 21:55:13 -08:00
JonnyWong16
61fac10079 v2.1.27-beta 2019-03-03 15:30:42 -08:00
JonnyWong16
536e8add17 Encode email exception to unicode 2019-03-03 14:10:05 -08:00
JonnyWong16
cb81bcac57 Return default blank content type (Fixes Tautulli/Tautulli-Issues#165) 2019-03-03 13:39:45 -08:00
JonnyWong16
5dd7806c0e Combine recently added manually due to change in Plex recently added API 2019-03-02 18:42:43 -08:00
JonnyWong16
2a707fc512 Fix typo in f6f5df3 2019-02-24 14:39:07 -08:00
JonnyWong16
469e54a22c Add current release/version to update_check API 2019-02-24 14:36:21 -08:00
JonnyWong16
f6f5df3d1e Add ability to dismiss browser warning 2019-02-24 14:27:34 -08:00
JonnyWong16
ae0960d2e2 Merge pull request #1341 from Arcanemagus/systemd-restart-note
Add Systemd auto restart policy
2019-02-24 14:00:30 -08:00
Landon Abney
a646cc36a1 Enable by default
With the protections from an infinite restart loop in place this should 
be safe to enable by default.
2019-02-24 13:58:19 -08:00
Landon Abney
b243ac5f5c Add burst failure protection
If the process fails 3 times within 90 seconds of a start attempt 
consider it permanently failed and stop all further attempts to restart 
it automatically.
2019-02-24 13:55:33 -08:00
JonnyWong16
bca7744bc5 Remove unicode from Email notification failed error message 2019-02-24 12:51:27 -08:00
JonnyWong16
2fc826c88f Fix some local variable references (Also Fixes Tautulli/Tautulli-Issues#155) 2019-02-24 12:34:55 -08:00
JonnyWong16
6397b1e5a7 Show tags in notification text preview modal 2019-02-24 11:18:18 -08:00
Landon Abney
85b9a47a0d Add note on how to restart automatically
If Tautulli ever crashes due to a failure of some sort the policies 
mentioned here will automatically restart it, with the caveat that they 
will _always_ restart it, even if it is going to crash right away again!
2019-02-22 23:09:34 -08:00
JonnyWong16
5749ab7c92 Remove execute permision from systemd init script (Fixes Tautulli/Tautulli-Issues#149) 2019-02-22 19:50:06 -08:00
JonnyWong16
dcb56cfd20 Add message to complete setup wizard 2019-02-20 21:38:13 -08:00
JonnyWong16
90849f9196 Remove crypto donation 2019-02-20 20:56:50 -08:00
JonnyWong16
5b77cab575 Update Patreon URL 2019-02-20 20:55:59 -08:00
JonnyWong16
6a21d7690a Improve data sanitation (Fixes Tautulli/Tautulli-Issues#161) 2019-02-20 18:35:04 -08:00
JonnyWong16
037e983350 Change PMS beta update check URL 2019-02-19 21:20:34 -08:00
Anthony Biacco
7f0cdb6f26 add config variable PMS_UPDATE_NOTIFY_INTERVAL and settings interface to be to change how often we notify about a new plex version 2018-12-12 10:18:11 -07:00
JonnyWong16
aa023f0166 v2.1.26 2018-12-01 15:50:04 -08:00
JonnyWong16
571b5461c0 Fix stream info graph modal history table (Fixes Tautulli/Tautulli-Issues#142) 2018-12-01 15:30:16 -08:00
JonnyWong16
a749b71f7f Fix activity resume after buffering 2018-12-01 15:17:33 -08:00
JonnyWong16
ac259214f7 Merge pull request #1333 from samwiseg00/add/user_email
Add user_email parameter for notifications
2018-11-04 11:49:42 -08:00
JonnyWong16
e11803685c Fix API error when missing cmd 2018-11-04 11:49:13 -08:00
samwiseg00
e4c3601312 Add user_email parameter for notifications 2018-11-04 14:18:34 -05:00
JonnyWong16
56a91de2c4 v2.1.25 2018-11-03 16:23:21 -07:00
JonnyWong16
e2d217a981 Merge pull request #1332 from samwiseg00/add/livetv_image
Add live TV images for current activity
2018-11-03 12:12:10 -07:00
samwiseg00
b484f27724 Add logic for live tv in current activity 2018-11-03 14:56:53 -04:00
samwiseg00
eb04a2e579 Fix poster image failback 2018-11-03 14:55:56 -04:00
samwiseg00
c66d8ecd5f Add new images for live tv activity 2018-11-03 14:55:30 -04:00
JonnyWong16
79b5f3c36f Merge pull request #1331 from samwiseg00/fix/pms_video_codec
Override * in video codecs
2018-11-02 19:59:48 -07:00
samwiseg00
4a78424b75 Override * in video codecs 2018-11-01 22:38:15 -04:00
JonnyWong16
4f78d0c98a Missing " in settings alert error 2018-10-31 17:49:33 -07:00
JonnyWong16
91b84b4437 Placeholder "-" for burn subtitle codec 2018-10-29 20:47:02 -07:00
JonnyWong16
8a9b3dc782 Override * in audio codecs 2018-10-29 20:46:37 -07:00
JonnyWong16
944df6cec1 v2.1.24-beta 2018-10-29 18:48:05 -07:00
JonnyWong16
354d46e940 Merge pull request #1330 from Arcanemagus/pytz-2018.6
Update pytz to 2018.6
2018-10-24 17:10:22 -04:00
Landon Abney
71cb2d9c4c Update pytz to 2018.6
Update the pytz library files to those from the 2018.6 release.
2018-10-24 12:48:53 -07:00
JonnyWong16
c08cec40cb Log colon 2018-10-23 22:45:28 -07:00
JonnyWong16
dee544c951 Update tzlocal to v1.5.1 2018-10-23 22:40:49 -07:00
JonnyWong16
f42581a5a6 Merge pull request #1329 from samwiseg00/add/timezone_log
Determine system timezone, log the timezone, and display it in the web ui config table
2018-10-23 22:05:06 -07:00
samwiseg00
fdc9f165a4 Add system timezone to the web configuration table 2018-10-23 21:55:32 -07:00
samwiseg00
8fff796700 Log system timezone on startup 2018-10-23 21:55:03 -07:00
samwiseg00
c64a115d29 Add logic to determine system timezone 2018-10-23 21:54:32 -07:00
JonnyWong16
d731ad851c Add queued tasks modal 2018-10-22 22:01:16 -07:00
JonnyWong16
c098175ba9 Merge pull request #1328 from samwiseg00/fix/on_change
Fix transcode change creating invalid sessions in the DB
2018-10-19 19:48:41 -07:00
JonnyWong16
d728f7a11e Version graphs to bypass cache 2018-10-19 18:58:26 -07:00
samwiseg00
7271e66ab8 Fix transcode change creating invalid sessions in the DB 2018-10-18 22:19:36 -04:00
JonnyWong16
5c54283df7 Merge pull request #1326 from samwiseg00/update/telegram_limit
Update character limit for telegram to reflect API changes.
2018-10-16 18:00:28 -07:00
samwiseg00
86b1d0f51d Update character limit for telegram to reflect API changes. 2018-10-16 16:23:56 -04:00
JonnyWong16
f2fedb182c Make local storage unique to Tautulli instance 2018-10-15 18:20:16 -07:00
JonnyWong16
1bce850765 v2.1.23-beta 2018-10-14 09:23:50 -07:00
JonnyWong16
ebe5c3168f Fix minor jquery expression error 2018-10-14 09:15:55 -07:00
JonnyWong16
6e4fa3ef63 Save state of history media type toggle 2018-10-13 22:12:08 -07:00
JonnyWong16
ec7afcdbc4 Fix default local storage chart visibility 2018-10-13 21:54:22 -07:00
JonnyWong16
0f2e25ba72 Save state of homepage recently added type 2018-10-13 21:51:26 -07:00
JonnyWong16
115b05ee7f Reword buffer threshold setting 2018-10-13 21:33:58 -07:00
JonnyWong16
85b4116491 Force buffer threshold to 10 2018-10-13 21:33:43 -07:00
JonnyWong16
863bb4033c Merge pull request #1325 from samwiseg00/change/buffer_threshhold
Change the default buffer threshold and bump the version number
2018-10-13 21:11:10 -07:00
samwiseg00
92672ddda8 Bump version & change default buffer from 3 to 10 2018-10-14 00:08:02 -04:00
JonnyWong16
018356b85e Save home stats config to local storage instead of server 2018-10-13 20:27:08 -07:00
JonnyWong16
d93390f8ed Change home stats type to 'plays' or 'duration' 2018-10-13 20:26:42 -07:00
JonnyWong16
e36be32b8e Set local storage before loading graphs 2018-10-13 20:22:36 -07:00
JonnyWong16
0e0fb2e2b8 Save graph config to local storage instead of server 2018-10-13 18:07:26 -07:00
JonnyWong16
be0144bbe1 Add button for recently added videos on homepage 2018-10-13 17:36:58 -07:00
JonnyWong16
0d30df6853 Add Other Video libraries to newsletters 2018-10-13 17:24:58 -07:00
JonnyWong16
77460f7617 Change type to media_type 2018-10-13 17:24:42 -07:00
JonnyWong16
c70cc535e5 Add library agent to database 2018-10-13 17:23:36 -07:00
JonnyWong16
16733bbe04 Revert column graph widths 2018-10-13 15:57:02 -07:00
JonnyWong16
1686b70c1c Show remote app device token and id 2018-10-13 15:43:19 -07:00
JonnyWong16
1ef4fd294a Save graph visibility state 2018-10-13 15:42:36 -07:00
JonnyWong16
83a4dfc0de Merge pull request #1324 from Arcanemagus/too-fast-buffer
Don't double notify on fast buffer triggers
2018-10-11 18:12:26 -07:00
samwiseg00
2eb82e8732 Change default buffering threshold for new installs 2018-10-11 16:55:45 -04:00
Landon Abney
67f70fab90 Don't double notify on fast buffer triggers
If two buffer notifications come in at the same second right at the cusp 
of the notification trigger the difference between the current and last 
trigger would be 0, causing it to send two notifications.

Change the initial value to `None` to prevent this from happening.
2018-10-11 13:29:21 -07:00
JonnyWong16
fb2362be24 Merge pull request #1323 from samwiseg00/fix/transcode_change
Fix transcode decision change for some clients
2018-10-10 21:09:05 -07:00
samwiseg00
612bf079de Fix transcode decision change for some clients 2018-10-10 16:46:40 -04:00
JonnyWong16
a88047eb9c Merge pull request #1322 from samwiseg00/fix/buffering_state_activity
Fix client buffering identification in certain scenarios
2018-10-09 20:45:57 -07:00
JonnyWong16
7bdef05a45 Fix download API commands 2018-10-09 08:27:48 -07:00
samwiseg00
1a46e09928 Fix client buffering identification in certain scenarios 2018-10-08 10:54:09 -04:00
JonnyWong16
4302c4bc0d Reverse sorting when retriving old rating key list from database 2018-10-06 20:15:19 -07:00
JonnyWong16
3b0f31c112 Merge pull request #1318 from Sheigutn/view-offset-fix
Don't overwrite view offset when processing session history
2018-10-06 18:59:35 -07:00
JonnyWong16
a976d65e9c Lock down some settings for Docker container 2018-10-06 14:19:01 -07:00
Florian Böhm
40559471cf Remove code to update view offset for every websocket event 2018-10-06 11:01:13 +02:00
JonnyWong16
6bb6e27378 Merge pull request #1321 from samwiseg00/add/notify_state_change
Add the ability to notify on transcode decision state change
2018-10-05 21:07:08 -07:00
JonnyWong16
03751abc0e v2.1.22 2018-10-05 21:04:17 -07:00
samwiseg00
8ab5d88db5 Create transcode decision columns for new DBs 2018-10-05 23:18:33 -04:00
samwiseg00
d80919140b Upgrade existing DB for transcode decision 2018-10-05 23:18:18 -04:00
samwiseg00
1e3a347782 Populate NULL text fields after a DB update 2018-10-05 23:18:00 -04:00
samwiseg00
a6e8372d47 Add transcode decision change to notifiers 2018-10-05 23:17:28 -04:00
samwiseg00
ce59692089 Fix typo in the comments 2018-10-05 23:17:06 -04:00
samwiseg00
df76a02478 Account for changing transcode decisions from websocket events 2018-10-05 23:16:49 -04:00
JonnyWong16
a94207691f Improve OAuth polling 2018-09-30 21:05:12 -07:00
JonnyWong16
dbc53ca710 Fix websocket not connecting after setup wizard 2018-09-29 15:32:13 -07:00
JonnyWong16
4c9ddbd8b7 Fix incorrectly showing 127.0.0.1 server in setup wizard 2018-09-29 15:31:55 -07:00
JonnyWong16
045c69f5d8 Catch exception when retrieiving data for notifier configs 2018-09-28 18:21:04 -07:00
JonnyWong16
71ae314c46 Make sure proxy handler priority is before auth handler (Fixes Tautulli/Tautulli-Issues#123) 2018-09-27 18:05:28 -07:00
JonnyWong16
c8575bbc0f v2.1.21 2018-09-21 18:16:48 -07:00
Florian Böhm
af3944734f Fix for usage of wrong view offset field when serializing to session_history
Also add code to update view offset in sessions table more often
2018-09-20 01:17:39 +02:00
JonnyWong16
f1b3a6f7b6 Merge pull request #1316 from Arcanemagus/fix_content_rating_type
Fix the type of the Content Rating notification parameter (Fixes Tautulli/Tautulli-Issues#122)
2018-09-19 12:49:31 -07:00
Landon Abney
8a94f6d63a Fix the type of the Content Rating notification parameter
The "Content Rating" notification parameter was incorrectly marked as an
integer, leading to all values being cast to the number 0. This made it
so every single content rating was the same value in conditions.
2018-09-19 12:46:28 -07:00
JonnyWong16
9b8fb73a7a Merge pull request #1312 from samwiseg00/add/init_distro
Add chown instructions per major distros
2018-09-19 08:41:05 -07:00
JonnyWong16
67c333e86e Add X-Plex-Token log filter 2018-09-16 10:24:07 -07:00
JonnyWong16
cfa0b20419 Fix music showing as pre-tautulli in stream info (Fixes Tautulli/Tautulli-Issues#120) 2018-09-16 09:56:32 -07:00
samwiseg00
4b2930c890 Add chown instructions per major distros 2018-09-15 16:54:49 -03:00
JonnyWong16
d98565ea12 Merge pull request #1309 from Sheigutn/refresh-image-patch
Move refresh image button to right div for track results
2018-09-15 10:28:08 -07:00
Florian Böhm
471f7c184a Replace album-item with cover-item
Also add missing quotation mark in artist cover div
2018-09-12 21:52:57 +02:00
Florian Böhm
3d4a5e6547 Move refresh image span to right div 2018-09-12 15:45:49 +02:00
JonnyWong16
382322d5e7 Always format notification subject 2018-09-11 18:08:40 -07:00
JonnyWong16
c0ae25611b Merge pull request #1152 from wilmardo/execute-permission-init-scripts
Adds execute permission to fedora.centos and systemd init-scripts
2018-09-11 17:45:45 -07:00
JonnyWong16
f025533582 Merge pull request #1308 from ldumont/fix_systemd_group
Fix typo in systemd group value
2018-09-10 08:29:49 -07:00
Loïc Dumont
fd28e5183a Fix typo in systemd group value 2018-09-10 07:35:45 +02:00
JonnyWong16
185099f183 Check for alternative reverse proxy headers 2018-09-09 10:57:14 -07:00
JonnyWong16
cd6289046e Fallback directories to data dir 2018-09-08 23:16:14 -07:00
JonnyWong16
955dc795ff Update javascript uuidv4 function 2018-09-06 23:23:55 -07:00
JonnyWong16
1b772e60a9 Add browser warning for IE/Edge 2018-09-06 22:51:01 -07:00
JonnyWong16
c6f4c17a81 Remove polling flag 2018-09-05 17:45:51 -07:00
JonnyWong16
1e68a81fe1 Stop polling if OAuth popup closed 2018-09-05 17:44:04 -07:00
Wilmar
634e003bb7 Adds execute permission to fedora.centos and systemd init-scripts 2017-11-21 01:09:54 +01:00
701 changed files with 9117 additions and 7874 deletions

32
API.md
View File

@@ -700,8 +700,9 @@ Returns:
"parent_title": "",
"paused_counter": 0,
"percent_complete": 84,
"platform": "Chrome",
"player": "Plex Web (Chrome)",
"platform": "Windows",
"product": "Plex for Windows",
"player": "Castle-PC",
"rating_key": 4348,
"reference_id": 1123,
"session_key": null,
@@ -733,7 +734,7 @@ Required parameters:
Optional parameters:
grouping (int): 0 or 1
time_range (str): The time range to calculate statistics, '30'
stats_type (int): 0 for plays, 1 for duration
stats_type (str): plays or duration
stats_count (str): The number of top items to list, '5'
Returns:
@@ -833,6 +834,7 @@ Required parameters:
None
Optional parameters:
grouping (int): 0 or 1
order_column (str): "library_thumb", "section_name", "section_type", "count", "parent_count",
"child_count", "last_accessed", "last_played", "plays", "duration"
order_dir (str): "desc" or "asc"
@@ -893,6 +895,7 @@ Returns:
json:
{"child_count": null,
"count": 887,
"deleted_section": 0,
"do_notify": 1,
"do_notify_created": 1,
"keep_history": 1,
@@ -1775,7 +1778,7 @@ Returns:
### get_recently_added
Get all items that where recelty added to plex.
Get all items that where recently added to plex.
```
Required parameters:
@@ -1783,7 +1786,7 @@ Required parameters:
Optional parameters:
start (str): The item number to start at
type (str): The media type: movie, show, artist
media_type (str): The media type: movie, show, artist
section_id (str): The id of the Plex library section
Returns:
@@ -2340,6 +2343,7 @@ Required parameters:
None
Optional parameters:
grouping (int): 0 or 1
order_column (str): "user_thumb", "friendly_name", "last_seen", "ip_address", "platform",
"player", "last_played", "plays", "duration"
order_dir (str): "desc" or "asc"
@@ -2644,6 +2648,24 @@ Returns:
```
### status
Get the current status of Tautulli.
```
Required parameters:
None
Optional parameters:
check (str): database
Returns:
json:
{"result": "success",
"message": "Ok",
}
```
### terminate_session
Stop a streaming session.

View File

@@ -1,7 +1,213 @@
# Changelog
## v2.1.34 (2019-09-03)
* History:
* New: Added Product column to history tables.
* Notifications:
* Fix: IMDB/TMDb/TVDB/TVmaze ID notification parameters showing blank values after lookup.
* UI:
* Fix: Libraries and Users tables did not respect the group history setting.
* API:
* Fix: Title field was not searchable in get_library_media_info command.
* New: Added grouping option to get_libraries_table and get_users_table commands.
* New: Added product value to get_history command.
* Other:
* Fix: Could not verify Plex Media Server with unpublished hostnames.
* Change: Automatically logout all Tautulli instances when changing the admin password.
## v2.1.33 (2019-07-27)
* Notifications:
* Change: Mask notification agent password fields.
* Change: Enable searching by email address in dropdown menu.
* Other:
* Fix: Version number being overwritten with "None" which prevented updating in some instances.
* Change: Update Plex OAuth request headers.
## v2.1.32 (2019-06-26)
* Newsletters:
* Fix: Newsletter scheduler issue for QNAP devices using an invalid "local" timezone preventing Tautulli from starting.
## v2.1.31 (2019-06-24)
* No additional changes from v2.1.31-beta.
## v2.1.31-beta (2019-06-13)
* Monitoring:
* Fix: Synced content showing incorrect stream info.
* Other:
* Fix: Unable to view database status when authentication is enabled.
* Change: Default database synchronous mode changed to prevent database corruption. Database response may be slower.
## v2.1.30-beta (2019-05-11)
* Monitoring:
* Fix: Activity crashing with Plex's Artist TV feature.
* New: Added setting for Plex Media Server Update Check Interval. (Thanks @abiacco)
* Notifications:
* New: Added secure and relayed connection notification parameters.
* New: Added PLEX_USER_TOKEN to script environment variables.
* Change: Schedule notifications using UTC to prevent missing notifications due to misconfigured timezones.
* API:
* New: Added status API command to check the status of Tautulli.
## v2.1.29 (2019-05-11)
* No additional changes from v2.1.29-beta.
## v2.1.29-beta (2019-04-14)
* Monitoring:
* Change: "Required Bandwidth" changed to "Reserved Bandwidth" in order to match the Plex dashboard.
* Notifications:
* New: Added prefix and suffix notification text modifiers. See the "Notification Text Modifiers" help modal for details.
* UI:
* New: Added "Undelete" button to the edit library and edit user modals.
* Fix: User IP address history table showing incorrect "Last Seen" values.
* API:
* Fix: Search API only returning 3 results.
* Fix: Terminate stream API failing when both session_key and session_id were provided.
* Change: Improved API response HTTP status codes and error messages.
## v2.1.28 (2019-03-10)
* Monitoring:
* New: Added secure/insecure connection icon on the activity cards. Requires Plex Media Server v1.15+.
* Other:
* Change: Improved mass deleting of all images from Cloudinary. Requires all previous images on Cloudinary to be manually tagged with "tautulli". New uploads are automatically tagged.
## v2.1.27-beta (2019-03-03)
* Monitoring:
* Fix: Error when playing synced optimized versions.
* Change: Show message to complete the setup wizard instead of error communicating with server message.
* Change: URL changed on Plex.tv for Plex Media Server beta updates.
* Notifications:
* New: Show the media type exclusion tags in the text preview modal.
* Fix: Unicode error in the Email notification failed response message.
* Fix: Error when a notification agent response is missing the "Content-Type" header.
* UI:
* Fix: Usernames were not being sanitized in dropdown selectors.
* Change: Different display of "All" recently added items on the homepage due to change in the Plex Media Server v1.15+ API.
* API:
* New: Added current Tautulli version to update_check API response.
* Change: API no longer returns sanitized HTML response data.
* Other:
* New: Added auto-restart to systemd init script.
* Fix: Patreon donation URL.
* Remove: Crypto donation options.
## v2.1.26 (2018-12-01)
* Monitoring:
* Fix: Resume event not being triggered after buffering.
* Notifications:
* New: Added user email as a notification parameter.
* Graphs:
* Fix: History model showing no results for stream info graph.
* API:
* Fix: API returning error when missing a cmd.
## v2.1.25 (2018-11-03)
* Monitoring:
* Fix: Audio and video codec showing up as * on the activity cards.
* New: Poster and background image on the activity cards for live TV.
* UI:
* Fix: Alert message for invalid Tautulli Public Domain setting.
## v2.1.24-beta (2018-10-29)
* Monitoring:
* Fix: Transcode change events creating invalid sessions in the database.
* Notifications:
* Change: Update Telegram character limit to 1024.
* History:
* Fix: Save history table states separately for multiple Tautulli instances.
* Graphs:
* Fix: Save graphs states separately for multiple Tautulli instances.
* Change: Version graphs to bypass browser cache.
* UI:
* New: Added queued tasks modals to the scheduled tasks table for debugging.
* Other:
* Change: Updated timezone info and display in configuration table.
## v2.1.23-beta (2018-10-14)
* Monitoring:
* Fix: Buffer events not being triggered properly.
* Fix: Watched progress sometimes not saved correctly. (Thanks @Sheigutn)
* Notifications:
* New: Added notification trigger for transcode decision change.
* Fix: Multiple buffer notifications being triggered within the same second.
* Change: Default buffer notification threshold changed to 10 for buffer thresholds less than 10.
* Newsletter:
* New: Added Other Video libraries to the newsletter.
* Homepage:
* New: Added Other Video type to recently added on the homepage.
* Change: Save homepage recently added media type toggle state.
* Change: Save homepage stats config to local storage instead of the server.
* History:
* Change: Save history table media type toggle state.
* Graphs:
* Change: Save series visibility state when toggling the legend.
* Change: Save graph config to local storage instead of the server.
* UI:
* New: Show the remote app device token and id in the edit device modal.
* Change: Lock certain settings if using the Tautulli docker container.
* API:
* Fix: download_config, download_database, download_log, and download_plex_log API commands not working.
* Change: get_recently_added command 'type' parameter renamed to 'media_type'. Backwards compatibility is maintained.
* Change: get_home_stats command 'stats_type' parameter change to string 'plays' or 'duration'. Backwards compatibility is maintained.
## v2.1.22 (2018-10-05)
* Notifications:
* Fix: Notification agent settings not loading when failed to retrieve some data.
* UI:
* Fix: Incorrectly showing localhost server in the setup wizard.
* Other:
* Fix: Incorrect redirect to HTTP when HTTPS proxy header is present.
* Fix: Websocket not connecting automatically after the setup wizard.
## v2.1.21 (2018-09-21)
* Notifications:
* Fix: Content Rating notification condition always evaluating to True. (Thanks @Arcanemagus)
* Fix: Script arguments not showing substituted values in the notification logs.
* UI:
* New: Unsupported browser warning when using IE or Edge.
* Fix: Misaligned refresh image icon in album search results. (Thanks @Sheigutn)
* Fix: Music history showing as pre-Tautulli in stream info modal.
* Other:
* Fix: Typo in Systemd init script group value. (Thanks @ldumont)
* Fix: Execute permissions in Fedora/CentOS and Systemd init scripts. (Thanks @wilmardo)
* Fix: Systemd init script instructions per Linux distro. (Thanks @samwiseg00)
* Change: Fallback to Tautulli data directory if logs/backup/cache/newsletter directories are not writable.
* Change: Check for alternative reverse proxy headers if X-Forwarded-Host is missing.
## v2.1.20 (2018-09-05)
* No changes.
* No additional changes from v2.1.20-beta.
## v2.1.20-beta (2018-09-02)

View File

@@ -28,9 +28,12 @@ import sys
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lib'))
import argparse
import datetime
import locale
import pytz
import signal
import time
import tzlocal
import plexpy
from plexpy import config, database, logger, webstart
@@ -106,6 +109,17 @@ def main():
logger.initLogger(console=not plexpy.QUIET, log_dir=False,
verbose=plexpy.VERBOSE)
try:
plexpy.SYS_TIMEZONE = tzlocal.get_localzone()
except (pytz.UnknownTimeZoneError, LookupError, ValueError) as e:
logger.error("Could not determine system timezone: %s" % e)
plexpy.SYS_TIMEZONE = pytz.UTC
plexpy.SYS_UTC_OFFSET = datetime.datetime.now(plexpy.SYS_TIMEZONE).strftime('%z')
if os.getenv('TAUTULLI_DOCKER', False) == 'True':
plexpy.DOCKER = True
if args.dev:
plexpy.DEV = True
logger.debug(u"Tautulli is running in the dev environment.")

View File

@@ -209,7 +209,7 @@ ${next.modalIncludes()}
</div>
</div>
% else:
<div id="donate-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="crypto-donate-modal">
<div id="donate-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="donate-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
@@ -230,17 +230,13 @@ ${next.modalIncludes()}
<ul id="donation_type" class="nav nav-pills" role="tablist" style="display: flex; justify-content: center; margin: 10px 0;">
<li class="active"><a href="#patreon-donation" role="tab" data-toggle="tab">Patreon</a></li>
<li><a href="#paypal-donation" role="tab" data-toggle="tab">PayPal</a></li>
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="bitcoin" data-name="Bitcoin" data-address="3FdfJAyNWU15Sf11U9FTgPHuP1hPz32eEN">Bitcoin</a></li>
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="bitcoincash" data-name="Bitcoin Cash" data-address="1H2atabxAQGaFAWYQEiLkXKSnK9CZZvt2n">Bitcoin Cash</a></li>
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="ethereum" data-name="Ethereum" data-address="0x77ae4c2b8de1a1ccfa93553db39971da58c873d3">Ethereum</a></li>
<li><a href="#crypto-donation" role="tab" data-toggle="tab" class="crypto-donation" data-coin="litecoin" data-name="Litecoin" data-address="LWpPmUqQYHBhMV83XSCsHzPmKLhJt6r57J">Litecoin</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="patreon-donation" style="text-align: center">
<p>
Click the button below to continue to Patreon.
</p>
<a href="${anon_url('https://www.patreon.com/bePatron?u=10078609')}" target="_blank">
<a href="${anon_url('https://www.patreon.com/join/tautulli')}" target="_blank">
<img src="images/become_a_patron_button.png" alt="Become a Patron" height="40">
</a>
</div>
@@ -252,12 +248,6 @@ ${next.modalIncludes()}
<img src="images/gold-rect-paypal-34px.png" alt="PayPal">
</a>
</div>
<div role="tabpanel" class="tab-pane" id="crypto-donation">
<label>QR Code</label>
<pre id="crypto_qr_code" style="text-align: center"></pre>
<label><span id="crypto_type_label"></span> Address</label>
<pre id="crypto_address" style="text-align: center"></pre>
</div>
</div>
</div>
<div class="modal-footer">
@@ -293,7 +283,6 @@ ${next.modalIncludes()}
<script src="${http_root}js/pnotify.custom.min.js"></script>
<script src="${http_root}js/platform.min.js"></script>
<script src="${http_root}js/script.js${cache_param}"></script>
<script src="${http_root}js/jquery.qrcode.min.js"></script>
<script src="${http_root}js/jquery.tripleclick.min.js"></script>
% if _session['user_group'] == 'admin' and BROWSER_NOTIFIERS:
<script src="${http_root}js/ajaxNotifications.js"></script>
@@ -365,16 +354,6 @@ ${next.modalIncludes()}
checkUpdate(function () { $('#nav-update').html('<i class="fa fa-fw fa-arrow-alt-circle-up"></i> Check for Updates'); });
});
$('#donation_type a.crypto-donation').on('shown.bs.tab', function () {
var crypto_coin = $(this).data('coin');
var crypto_name = $(this).data('name');
var crypto_address = $(this).data('address')
$('#crypto_qr_code').empty().qrcode({
text: crypto_coin + ":" + crypto_address
});
$('#crypto_type_label').html(crypto_name);
$('#crypto_address').html(crypto_address);
});
% endif
$('.dropdown-toggle').click(function (e) {

View File

@@ -35,7 +35,7 @@ DOCUMENTATION :: END
</tr>
<tr>
<td>Database File:</td>
<td><a class="no-highlight" href="download_database" data-toggle="tooltip" data-placement="right" title="Download Database">${plexpy.DB_FILE}</a></td>
<td><a class="no-highlight" href="download_database" data-toggle="tooltip" data-placement="right" title="Download Database">${plexpy.DB_FILE}</a> | <a class="no-highlight" href="status/database">Status</a></td>
</tr>
<tr>
<td>Log File:</td>
@@ -69,7 +69,11 @@ DOCUMENTATION :: END
% endif
<tr>
<td>Platform:</td>
<td>${common.PLATFORM} ${common.PLATFORM_RELEASE} (${common.PLATFORM_VERSION + (' - {}'.format(common.PLATFORM_LINUX_DISTRO) if common.PLATFORM_LINUX_DISTRO else '')})</td>
<td>${'[Docker] ' if plexpy.DOCKER else ''}${common.PLATFORM} ${common.PLATFORM_RELEASE} (${common.PLATFORM_VERSION + (' - {}'.format(common.PLATFORM_LINUX_DISTRO) if common.PLATFORM_LINUX_DISTRO else '')})</td>
</tr>
<tr>
<td>System Timezone:</td>
<td>${plexpy.SYS_TIMEZONE.zone} (${'UTC{}'.format(plexpy.SYS_UTC_OFFSET)})
</tr>
<tr>
<td>Python Version:</td>

View File

@@ -676,7 +676,9 @@ textarea.form-control:focus {
color: #F9AA03;
margin: 5px 40px 5px 0;
}
.form-control[readonly] {
.form-control[disabled],
.form-control[readonly],
fieldset[disabled] .form-control {
background-color: #555;
}
.form-control[readonly]:focus {
@@ -2151,6 +2153,10 @@ div.advanced-setting {
li.advanced-setting {
border-left: 1px solid #cc7b19;
}
.docker-setting {
color: #cc7b19;
margin-left: 10px;
}
.user-info-wrapper {
}
.user-info-poster-face {
@@ -3361,6 +3367,34 @@ pre::-webkit-scrollbar-thumb {
.notification-params tr:nth-child(even) td {
background-color: rgba(255,255,255,0.010);
}
.activity-queue {
width: 100%;
margin-top: 10px;
background-color: #282828;
}
.activity-queue th {
padding-left: 10px;
height: 30px;
}
.activity-queue th:first-child {
width: 268px;
}
.activity-queue th:nth-child(2) {
width: 125px;
}
.activity-queue th:nth-child(3) {
width: 175px;
}
.activity-queue td {
height: 25px;
padding: 5px 10px;
}
.activity-queue tr:nth-child(odd) td {
background-color: rgba(255,255,255,0.035);
}
.activity-queue tr:nth-child(even) td {
background-color: rgba(255,255,255,0.010);
}
#days-selection label,
#months-selection label {
margin-bottom: 0;
@@ -4163,3 +4197,20 @@ a[data-tab-destination] {
.fa-blank {
visibility: hidden;
}
#browser-warning {
height: 25px;
width: 100%;
background: #cc7b19;
text-align: center;
font-weight: bold;
padding: 2px 10px;
position: absolute;
top: 0;
z-index: 9999;
}
.help-block li {
margin-top: 0;
color: #737373;
}

View File

@@ -80,7 +80,9 @@ DOCUMENTATION :: END
data-rating_key="${data['rating_key']}" data-parent_rating_key="${data['parent_rating_key']}" data-grandparent_rating_key="${data['grandparent_rating_key']}">
<div class="dashboard-activity-container">
<%
if data['channel_stream'] == 0:
if data['live'] == 1:
background_url = 'images/art-live.png'
elif data['channel_stream'] == 0:
background_url = 'pms_image_proxy?img=' + data['art'] + '&width=500&height=280&opacity=40&background=282828&blur=3&fallback=art&refresh=true'
else:
if (data['art'] and data['art'].startswith('http')) or (data['thumb'] and data['thumb'].startswith('http')):
@@ -93,7 +95,9 @@ DOCUMENTATION :: END
% if data['media_type'] == 'track':
<div id="poster-${sk}-bg" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['parent_thumb']}&width=300&height=300&opacity=60&background=282828&blur=3&fallback=cover&refresh=true);"></div>
% endif
% if data['channel_stream'] == 0:
% if data['live'] == 1:
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(images/poster-live.png);"></div>
% elif data['channel_stream'] == 0:
% if data['media_type'] == 'movie':
<a id="poster-url-${sk}" href="${href}" title="${data['title']}">
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['thumb']}&width=300&height=450&fallback=poster&refresh=true);"></div>
@@ -113,7 +117,7 @@ DOCUMENTATION :: END
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(pms_image_proxy?img=${data['parent_thumb'] or data['thumb']}&width=300&height=450&fallback=poster&refresh=true);"></div>
% endif
% else:
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(images/art.png);"></div>
<div id="poster-${sk}" class="dashboard-activity-poster" style="background-image: url(images/poster.png);"></div>
% endif
% else:
% if data['channel_icon'].startswith('http'):
@@ -214,7 +218,7 @@ DOCUMENTATION :: END
% if data['stream_container_decision'] == 'transcode':
Transcode (${data['container'].upper()} <i class="fa fa-long-arrow-right"></i> ${data['stream_container'].upper()})
% else:
Direct Play (${data['container'].upper()})
Direct Play (${data['stream_container'].upper()})
% endif
</div>
</li>
@@ -232,7 +236,7 @@ DOCUMENTATION :: END
% elif data['stream_video_decision'] == 'copy':
Direct Stream (${data['stream_video_codec'].upper()} ${VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])})
% else:
Direct Play (${data['video_codec'].upper()} ${VIDEO_RESOLUTION_OVERRIDES.get(data['video_resolution'], data['video_resolution'])})
Direct Play (${data['stream_video_codec'].upper()} ${VIDEO_RESOLUTION_OVERRIDES.get(data['stream_video_resolution'], data['stream_video_resolution'])})
% endif
% elif data['media_type'] == 'photo':
Direct Play (${data['width']}x${data['height']})
@@ -249,7 +253,7 @@ DOCUMENTATION :: END
% elif data['stream_audio_decision'] == 'copy':
Direct Stream (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
% else:
Direct Play (${AUDIO_CODEC_OVERRIDES.get(data['audio_codec'], data['audio_codec'].upper())} ${data['audio_channel_layout'].split('(')[0].capitalize()})
Direct Play (${AUDIO_CODEC_OVERRIDES.get(data['stream_audio_codec'], data['stream_audio_codec'].upper())} ${data['stream_audio_channel_layout'].split('(')[0].capitalize()})
% endif
</div>
</li>
@@ -266,7 +270,7 @@ DOCUMENTATION :: END
% elif data['stream_subtitle_decision'] == 'burn':
Burn (${data['subtitle_codec'].upper()})
% else:
Direct Play (${data['stream_subtitle_codec'].upper() if data['synced_version'] else data['subtitle_codec'].upper()})
Direct Play (${data['subtitle_codec'].upper() if data['synced_version'] else data['stream_subtitle_codec'].upper()})
% endif
% else:
None
@@ -279,10 +283,17 @@ DOCUMENTATION :: END
<li class="dashboard-activity-info-item">
<div class="sub-heading">Location</div>
<div class="sub-value time-right">
% if data['secure'] is not None:
% if data['secure']:
<span data-toggle="tooltip" title="Secure Connection"><i class="fa fa-lock"></i></span>
% else:
<span data-toggle="tooltip" title="Insecure Connection"><i class="fa fa-unlock"></i></span>
% endif
% endif
<span id="location-${sk}">${data['location'].upper()}</span>:
% if data['ip_address'] != 'N/A':
<span class="ip-container"><span class="ip-address">${data['ip_address']}</span></span>
% if data['relay']:
% if data['relayed']:
<span data-toggle="tooltip" title="Plex Relay"><i class="fa fa-exclamation-circle"></i></span>
% else:
<a href="#" class="external_ip-modal" data-toggle="modal" data-target="#ip-info-modal" data-ip="${data['ip_address']}">
@@ -313,7 +324,7 @@ DOCUMENTATION :: END
bw = str(bw) + ' kbps'
%>
<span id="stream-bandwidth-${sk}">${bw}</span>
<span id="streaming-brain-${sk}" data-toggle="tooltip" title="Streaming Brain Estimate (Required Bandwidth)"><i class="fa fa-info-circle"></i></span>
<span id="streaming-brain-${sk}" data-toggle="tooltip" title="Streaming Brain Estimate (Reserved Bandwidth)"><i class="fa fa-info-circle"></i></span>
% elif data['synced_version'] == 1 or data['channel_stream'] == 1:
<span id="stream-bandwidth-${sk}">None</span>
% else:

View File

@@ -21,6 +21,7 @@ parent_count Returns the parent item count for the library.
child_count Returns the child item count for the library.
do_notify Returns bool value for whether to send notifications for the library.
keep_history Returns bool value for whether to keep history for the library.
deleted_section Returns bool value for whether the library is marked as deleted.
DOCUMENTATION :: END
</%doc>
@@ -59,6 +60,12 @@ DOCUMENTATION :: END
<p class="help-block">DANGER ZONE! Click the purge button to remove all history logged for this library. This is permanent!</p>
</div>
% endif
% if data['deleted_section']:
<div class="form-group">
<button class="btn btn-bright" id="undelete-library">Undelete</button>
<p class="help-block">Click to re-add the library to the Tautulli libraries list.</p>
</div>
% endif
</fieldset>
</div>
<div class="modal-footer">
@@ -100,6 +107,12 @@ DOCUMENTATION :: END
confirmAjaxCall(url, msg, { section_id: '${data["section_id"]}' }, null, function () { location.reload(); });
});
$('#undelete-library').click(function () {
var msg = 'Are you sure you want to undelete this user?';
var url = 'undelete_library';
confirmAjaxCall(url, msg, { section_id: '${data["section_id"]}' }, null, function () { location.reload(); });
});
$(document).ready(function() {
// Move #confirm-modal to parent container
if (!($('#edit-library-modal').next().is('#confirm-modal-purge'))) {

View File

@@ -21,6 +21,7 @@ is_restricted Returns bool value for whether the user account is restricte
do_notify Returns bool value for whether to send notifications for the user.
keep_history Returns bool value for whether to keep history for the user.
allow_guest Returns bool value for whether to allow guest access for the user.
deleted_user Returns bool value for whether the user is marked as deleted.
DOCUMENTATION :: END
</%doc>
@@ -74,6 +75,12 @@ DOCUMENTATION :: END
<p class="help-block">DANGER ZONE! Click the purge button to remove all history logged for this user. This is permanent!</p>
</div>
% endif
% if data['deleted_user']:
<div class="form-group">
<button class="btn btn-bright" id="undelete-user">Undelete</button>
<p class="help-block">Click to re-add the user to the Tautulli users list.</p>
</div>
% endif
</fieldset>
</div>
<div class="modal-footer">
@@ -122,6 +129,12 @@ DOCUMENTATION :: END
confirmAjaxCall(url, msg, { user_id: '${data["user_id"]}' }, null, function () { location.reload(); });
});
$('#undelete-user').click(function () {
var msg = 'Are you sure you want to undelete this user?';
var url = 'undelete_user';
confirmAjaxCall(url, msg, { user_id: '${data["user_id"]}' }, null, function () { location.reload(); });
});
$(document).ready(function() {
// Move #confirm-modal-purge to parent container
if (!($('#edit-user-modal').next().is('#confirm-modal-purge'))) {

View File

@@ -21,56 +21,33 @@
</label>
</div>
<div class="btn-group" style="margin-right: 2px;" data-toggle="buttons" id="yaxis-selection">
% if config['graph_type'] == 'duration':
<label class="btn btn-dark">
<input type="radio" name="yaxis-options" id="yaxis-count" value="plays" autocomplete="off"> Play Count
</label>
<label class="btn btn-dark active">
<input type="radio" name="yaxis-options" id="yaxis-duration" value="duration" autocomplete="off" checked> Play Duration
</label>
% else:
<label class="btn btn-dark active">
<input type="radio" name="yaxis-options" id="yaxis-count" value="plays" autocomplete="off" checked> Play Count
<input type="radio" name="yaxis-options" id="yaxis-plays" value="plays" autocomplete="off"> Play Count
</label>
<label class="btn btn-dark">
<input type="radio" name="yaxis-options" id="yaxis-duration" value="duration" autocomplete="off"> Play Duration
</label>
% endif
</div>
<div class="input-group pull-right" style="width: 1px;" id="days-selection">
<span class="input-group-addon btn-dark inactive">Last</span>
<input type="number" class="form-control number-input" name="graph-days" id="graph-days" value="${config['graph_days']}" min="1" data-default="7" data-toggle="tooltip" title="Min: 1 day" />
<input type="number" class="form-control number-input" name="graph-days" id="graph-days" value="30" min="1" data-default="7" data-toggle="tooltip" title="Min: 1 day" />
<span class="input-group-addon btn-dark inactive">days</span>
</div>
<div class="input-group pull-right" style="width: 1px;" id="months-selection">
<span class="input-group-addon btn-dark inactive">Last</span>
<input type="number" class="form-control number-input" name="graph-months" id="graph-months" value="${config['graph_months']}" min="1" data-default="12" data-toggle="tooltip" title="Min: 1 month" />
<input type="number" class="form-control number-input" name="graph-months" id="graph-months" value="12" min="1" data-default="12" data-toggle="tooltip" title="Min: 1 month" />
<span class="input-group-addon btn-dark inactive">months</span>
</div>
</div>
</div>
<div class='table-card-back'>
<ul class="nav nav-pills" role="tablist" id="graph-tabs">
% if config['graph_tab'] == 'tabs-3':
<li role="presentation"><a href="#tabs-1" aria-controls="tabs-1" data-toggle="tab" role="tab">Plays by period</a></li>
<li role="presentation"><a href="#tabs-2" aria-controls="tabs-2" data-toggle="tab" role="tab">Stream Info</a></li>
<li role="presentation" class="active"><a href="#tabs-3" aria-controls="tabs-3" data-toggle="tab" role="tab">Play Totals</a></li>
% elif config['graph_tab'] == 'tabs-2':
<li role="presentation"><a href="#tabs-1" aria-controls="tabs-1" data-toggle="tab" role="tab">Plays by period</a></li>
<li role="presentation" class="active"><a href="#tabs-2" aria-controls="tabs-2" data-toggle="tab" role="tab">Stream Info</a></li>
<li role="presentation"><a href="#tabs-3" aria-controls="tabs-3" data-toggle="tab" role="tab">Play Totals</a></li>
% else:
<li role="presentation" class="active"><a href="#tabs-1" aria-controls="tabs-1" data-toggle="tab" role="tab">Plays by period</a></li>
<li role="presentation"><a href="#tabs-1" aria-controls="tabs-1" data-toggle="tab" role="tab">Plays by Period</a></li>
<li role="presentation"><a href="#tabs-2" aria-controls="tabs-2" data-toggle="tab" role="tab">Stream Info</a></li>
<li role="presentation"><a href="#tabs-3" aria-controls="tabs-3" data-toggle="tab" role="tab">Play Totals</a></li>
% endif
</ul>
<div class="tab-content">
% if config['graph_tab'] != 'tabs-2' and config['graph_tab'] != 'tabs-3':
<div role="tabpanel" class="tab-pane active" id="tabs-1">
% else:
<div role="tabpanel" class="tab-pane" id="tabs-1">
% endif
<div class="row">
<div class="col-md-12">
<h4><i class="fa fa-history"></i> Daily <span class="yaxis-text">Play count</span> <small>Last <span class="days">30</span> days</small></h4>
@@ -78,7 +55,7 @@
The total play count or duration of tv, movies, and music played per day. Click a graph point to open up a list of items played for that specific date.
</p>
<div class="graphs-instance">
<div class="watch-chart" id="chart_div_plays_by_day">
<div class="watch-chart" id="graph_plays_by_day">
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...</div>
<br>
</div>
@@ -92,7 +69,7 @@
The combined total of tv, movies, and music played per day of the week.
</p>
<div class="graphs-instance">
<div class="watch-chart" id="chart_div_plays_by_dayofweek" style="float: left;">
<div class="watch-chart" id="graph_plays_by_dayofweek" style="float: left;">
<div class="graphs-load">
<i class="fa fa-refresh fa-spin"></i> Loading chart...
</div>
@@ -106,7 +83,7 @@
The combined total of tv, movies, and music played per hour of the day.
</p>
<div class="graphs-instance">
<div class="watch-chart" id="chart_div_plays_by_hourofday">
<div class="watch-chart" id="graph_plays_by_hourofday">
<div class="graphs-load">
<i class="fa fa-refresh fa-spin"></i> Loading chart...
</div>
@@ -122,7 +99,7 @@
The combined total of tv, movies, and music played by top 10 most active platforms.
</p>
<div class="graphs-instance">
<div class="watch-chart" id="chart_div_plays_by_platform" style="float: left;">
<div class="watch-chart" id="graph_plays_by_platform" style="float: left;">
<div class="graphs-load">
<i class="fa fa-refresh fa-spin"></i> Loading chart...
</div>
@@ -136,7 +113,7 @@
The combined total of tv, movies, and music played by top 10 most active users.
</p>
<div class="graphs-instance">
<div class="watch-chart" id="chart_div_plays_by_user">
<div class="watch-chart" id="graph_plays_by_user">
<div class="graphs-load">
<i class="fa fa-refresh fa-spin"></i> Loading chart...
</div>
@@ -146,12 +123,7 @@
</div>
</div>
</div>
% if config['graph_tab'] == 'tabs-2':
<div role="tabpanel" class="tab-pane active" id="tabs-2">
% else:
<div role="tabpanel" class="tab-pane" id="tabs-2">
% endif
<div class="row">
<div class="col-md-12">
<h4><i class="fa fa-video-camera"></i> Daily Stream type breakdown <small>Last <span class="days">30</span> days</small></h4>
@@ -159,7 +131,7 @@
The total play count or duration of tv, movies, and music by the transcode decision. Click a graph point to open up a list of items played for that specific date.
</p>
<div class="graphs-instance">
<div class="watch-chart" id="chart_div_plays_by_stream_type">
<div class="watch-chart" id="graph_plays_by_stream_type">
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...</div>
<br>
</div>
@@ -173,7 +145,7 @@
The combined total of tv and movies by their original resolution (pre-transcoding).
</p>
<div class="graphs-instance">
<div class="watch-chart" id="chart_div_plays_by_source_resolution" style="float: left;">
<div class="watch-chart" id="graph_plays_by_source_resolution" style="float: left;">
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...
</div>
<br>
@@ -186,7 +158,7 @@
The combined total of tv and movies by their streamed resolution (post-transcoding).
</p>
<div class="graphs-instance">
<div class="watch-chart" id="chart_div_plays_by_stream_resolution">
<div class="watch-chart" id="graph_plays_by_stream_resolution">
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...
</div>
<br>
@@ -201,7 +173,7 @@
The combined total of tv, movies, and music by platform and stream type.
</p>
<div class="graphs-instance">
<div class="watch-chart" id="chart_div_plays_by_platform_by_stream_type" style="float: left;">
<div class="watch-chart" id="graph_plays_by_platform_by_stream_type" style="float: left;">
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...
</div>
<br>
@@ -214,7 +186,7 @@
The combined total of tv, movies, and music by user and stream type.
</p>
<div class="graphs-instance">
<div class="watch-chart" id="chart_div_plays_by_user_by_stream_type" style="float: left;">
<div class="watch-chart" id="graph_plays_by_user_by_stream_type" style="float: left;">
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...
</div>
<br>
@@ -223,12 +195,7 @@
</div>
</div>
</div>
% if config['graph_tab'] == 'tabs-3':
<div role="tabpanel" class="tab-pane active" id="tabs-3">
% else:
<div role="tabpanel" class="tab-pane" id="tabs-3">
% endif
<div class="row">
<div class="col-md-12">
<h4><i class="fa fa-calendar"></i> Plays by month <small>Last <span class="months">12</span> months</small></h4>
@@ -236,7 +203,7 @@
The combined total of tv, movies, and music by month.
</p>
<div class="graphs-instance">
<div class="watch-chart" id="chart_div_plays_by_month">
<div class="watch-chart" id="graph_plays_by_month">
<div class="graphs-load"><i class="fa fa-refresh fa-spin"></i> Loading chart...
</div>
<br>
@@ -266,7 +233,7 @@
<script src="${http_root}js/dataTables.bootstrap.pagination.js"></script>
<script>
var selected_user_id = null
var selected_user_id = null;
// Modal popup dialog
function selectHandler(selectedDate, selectedSeries) {
@@ -311,26 +278,60 @@
console.log("Failed to retrieve history modal data.");
}
}
function getGraphVisibility(chart_name, data_series) {
var chart_key = 'HighCharts_' + chart_name;
var chart_visibility = JSON.parse(getLocalStorage(chart_key, null)) || [];
chart_visibility = chart_visibility.reduce(function(obj, s) {
obj[s.name] = s.visible;
return obj;
}, {});
return data_series.map(function(s) {
var obj = Object.assign({}, s);
obj.visible = (chart_visibility[s.name] !== false);
return obj
});
}
function setGraphVisibility(chart_name, data_series, series_name) {
var chart_key = 'HighCharts_' + chart_name;
var chart_visibility = data_series.map(function(s) {
return {name: s.name, visible: (s.name === series_name) ? !s.visible : s.visible}
});
setLocalStorage(chart_key, JSON.stringify(chart_visibility));
}
</script>
<script src="${http_root}js/graphs/plays_by_day.js"></script>
<script src="${http_root}js/graphs/plays_by_dayofweek.js"></script>
<script src="${http_root}js/graphs/plays_by_hourofday.js"></script>
<script src="${http_root}js/graphs/plays_by_platform.js"></script>
<script src="${http_root}js/graphs/plays_by_user.js"></script>
<script src="${http_root}js/graphs/plays_by_stream_type.js"></script>
<script src="${http_root}js/graphs/plays_by_source_resolution.js"></script>
<script src="${http_root}js/graphs/plays_by_stream_resolution.js"></script>
<script src="${http_root}js/graphs/plays_by_platform_by_stream_type.js"></script>
<script src="${http_root}js/graphs/plays_by_user_by_stream_type.js"></script>
<script src="${http_root}js/graphs/plays_by_month.js"></script>
<script src="${http_root}js/graphs/plays_by_day.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_dayofweek.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_hourofday.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_platform.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_user.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_stream_type.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_source_resolution.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_stream_resolution.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_platform_by_stream_type.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_user_by_stream_type.js${cache_param}"></script>
<script src="${http_root}js/graphs/plays_by_month.js${cache_param}"></script>
<script>
$(document).ready(function () {
// Initial values for graph from config
var yaxis = "${config['graph_type']}";
var current_day_range = ${config['graph_days']};
var current_month_range = ${config['graph_months']};
var current_tab = "${'#' + config['graph_tab']}";
// Initial values for graph from local storage
var yaxis = getLocalStorage('graph_type', 'plays');
var current_day_range = getLocalStorage('graph_days', 30);
var current_month_range = getLocalStorage('graph_months', 12);
var current_tab = '#' + getLocalStorage('graph_tab', 'tabs-1');
$('#yaxis-' + yaxis).prop('checked', true);
$('#yaxis-' + yaxis).closest('label').addClass('active');
$('#graph-days').val(current_day_range);
$('#graph-months').val(current_month_range);
$('#graph-tabs a[href="' + current_tab + '"]').closest('li').addClass('active');
$(current_tab).addClass('active');
$('.days').html(current_day_range);
$('.months').html(current_month_range);
@@ -352,9 +353,6 @@
}
});
var music_visible = (${config['music_logging_enable']} == 1 ? true : false);
function dataSecondsToHours(data) {
$.each(data.series, function (i, series) {
series.data = $.map(series.data, function (value) {
@@ -379,8 +377,8 @@
$.each(data.categories, function (i, day) {
dateArray.push(moment(day, 'YYYY-MM-DD').valueOf());
// Highlight the weekend
if ((moment(day, 'YYYY-MM-DD').format('ddd') == 'Sat') ||
(moment(day, 'YYYY-MM-DD').format('ddd') == 'Sun')) {
if ((moment(day, 'YYYY-MM-DD').format('ddd') === 'Sat') ||
(moment(day, 'YYYY-MM-DD').format('ddd') === 'Sun')) {
hc_plays_by_day_options.xAxis.plotBands.push({
from: i-0.5,
to: i+0.5,
@@ -391,8 +389,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_day_options.yAxis.min = 0;
hc_plays_by_day_options.xAxis.categories = dateArray;
hc_plays_by_day_options.series = data.series;
hc_plays_by_day_options.series[2].visible = music_visible;
hc_plays_by_day_options.series = getGraphVisibility(hc_plays_by_day_options.chart.renderTo, data.series);
var hc_plays_by_day = new Highcharts.Chart(hc_plays_by_day_options);
}
});
@@ -405,8 +402,7 @@
success: function(data) {
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_dayofweek_options.xAxis.categories = data.categories;
hc_plays_by_dayofweek_options.series = data.series;
hc_plays_by_dayofweek_options.series[2].visible = music_visible;
hc_plays_by_dayofweek_options.series = getGraphVisibility(hc_plays_by_dayofweek_options.chart.renderTo, data.series);
var hc_plays_by_dayofweek = new Highcharts.Chart(hc_plays_by_dayofweek_options);
}
});
@@ -419,8 +415,7 @@
success: function(data) {
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_hourofday_options.xAxis.categories = data.categories;
hc_plays_by_hourofday_options.series = data.series;
hc_plays_by_hourofday_options.series[2].visible = music_visible;
hc_plays_by_hourofday_options.series = getGraphVisibility(hc_plays_by_hourofday_options.chart.renderTo, data.series);
var hc_plays_by_hourofday = new Highcharts.Chart(hc_plays_by_hourofday_options);
}
});
@@ -433,8 +428,7 @@
success: function(data) {
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_platform_options.xAxis.categories = data.categories;
hc_plays_by_platform_options.series = data.series;
hc_plays_by_platform_options.series[2].visible = music_visible;
hc_plays_by_platform_options.series = getGraphVisibility(hc_plays_by_platform_options.chart.renderTo, data.series);
var hc_plays_by_platform = new Highcharts.Chart(hc_plays_by_platform_options);
}
});
@@ -447,11 +441,12 @@
success: function(data) {
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_user_options.xAxis.categories = data.categories;
hc_plays_by_user_options.series = data.series;
hc_plays_by_user_options.series[2].visible = music_visible;
hc_plays_by_user_options.series = getGraphVisibility(hc_plays_by_user_options.chart.renderTo, data.series);
var hc_plays_by_user = new Highcharts.Chart(hc_plays_by_user_options);
}
});
$('#graph-tabs a[href="#tabs-1"]').tab('show')
}
function loadGraphsTab2(time_range, yaxis) {
@@ -482,7 +477,7 @@
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_stream_type_options.yAxis.min = 0;
hc_plays_by_stream_type_options.xAxis.categories = dateArray;
hc_plays_by_stream_type_options.series = data.series;
hc_plays_by_stream_type_options.series = getGraphVisibility(hc_plays_by_stream_type_options.chart.renderTo, data.series);
var hc_plays_by_stream_type = new Highcharts.Chart(hc_plays_by_stream_type_options);
}
});
@@ -495,7 +490,7 @@
success: function(data) {
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_source_resolution_options.xAxis.categories = data.categories;
hc_plays_by_source_resolution_options.series = data.series;
hc_plays_by_source_resolution_options.series = getGraphVisibility(hc_plays_by_source_resolution_options.chart.renderTo, data.series);
var hc_plays_by_source_resolution = new Highcharts.Chart(hc_plays_by_source_resolution_options);
}
});
@@ -508,7 +503,7 @@
success: function(data) {
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_stream_resolution_options.xAxis.categories = data.categories;
hc_plays_by_stream_resolution_options.series = data.series;
hc_plays_by_stream_resolution_options.series = getGraphVisibility(hc_plays_by_stream_resolution_options.chart.renderTo, data.series);
var hc_plays_by_stream_resolution = new Highcharts.Chart(hc_plays_by_stream_resolution_options);
}
});
@@ -521,7 +516,7 @@
success: function(data) {
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_platform_by_stream_type_options.xAxis.categories = data.categories;
hc_plays_by_platform_by_stream_type_options.series = data.series;
hc_plays_by_platform_by_stream_type_options.series = getGraphVisibility(hc_plays_by_platform_by_stream_type_options.chart.renderTo, data.series);
var hc_plays_by_platform_by_stream_type = new Highcharts.Chart(hc_plays_by_platform_by_stream_type_options);
}
});
@@ -534,10 +529,12 @@
success: function(data) {
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_user_by_stream_type_options.xAxis.categories = data.categories;
hc_plays_by_user_by_stream_type_options.series = data.series;
hc_plays_by_user_by_stream_type_options.series = getGraphVisibility(hc_plays_by_user_by_stream_type_options.chart.renderTo, data.series);
var hc_plays_by_user_by_stream_type = new Highcharts.Chart(hc_plays_by_user_by_stream_type_options);
}
});
$('#graph-tabs a[href="#tabs-2"]').tab('show')
}
function loadGraphsTab3(time_range, yaxis) {
@@ -555,51 +552,52 @@
if (yaxis === 'duration') { dataSecondsToHours(data); }
hc_plays_by_month_options.yAxis.min = 0;
hc_plays_by_month_options.xAxis.categories = data.categories;
hc_plays_by_month_options.series = data.series;
hc_plays_by_month_options.series[2].visible = music_visible;
hc_plays_by_month_options.series = getGraphVisibility(hc_plays_by_month_options.chart.renderTo, data.series);
var hc_plays_by_month = new Highcharts.Chart(hc_plays_by_month_options);
}
});
$('#graph-tabs a[href="#tabs-3"]').tab('show')
}
// Set initial state
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); }
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');
setLocalStorage('graph_tab', current_tab.replace('#',''));
loadGraphsTab1(current_day_range, yaxis);
$.post('set_graph_config', { graph_tab: current_tab.replace('#','') });
})
});
// Tab2 opened
$('#graph-tabs a[href="#tabs-2"]').on('shown.bs.tab', function (e) {
e.preventDefault();
current_tab = $(this).attr('href');
setLocalStorage('graph_tab', current_tab.replace('#',''));
loadGraphsTab2(current_day_range, yaxis);
$.post('set_graph_config', { graph_tab: current_tab.replace('#','') });
})
});
// Tab3 opened
$('#graph-tabs a[href="#tabs-3"]').on('shown.bs.tab', function (e) {
e.preventDefault();
current_tab = $(this).attr('href');
setLocalStorage('graph_tab', current_tab.replace('#',''));
loadGraphsTab3(current_month_range, yaxis);
$.post('set_graph_config', { graph_tab: current_tab.replace('#','') });
})
});
// Date range changed
$('#graph-days').tooltip({ container: 'body', placement: 'top', html: true });
$('#graph-days').on('change', function() {
forceMinMax($(this));
current_day_range = $(this).val();
if (current_tab == '#tabs-1') { loadGraphsTab1(current_day_range, yaxis); }
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
setLocalStorage('graph_days', current_day_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);
$.post('set_graph_config', { graph_days: current_day_range });
});
// Month range changed
@@ -607,26 +605,26 @@
$('#graph-months').on('change', function() {
forceMinMax($(this));
current_month_range = $(this).val();
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
setLocalStorage('graph_months', current_month_range);
if (current_tab === '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
$('.months').html(current_month_range);
$.post('set_graph_config', { graph_months: current_month_range });
});
// User changed
$('#graph-user').on('change', function() {
selected_user_id = $(this).val() || null;
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); }
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_day_range, yaxis); }
if (current_tab == '#tabs-2') { loadGraphsTab2(current_day_range, yaxis); }
if (current_tab == '#tabs-3') { loadGraphsTab3(current_month_range, yaxis); }
$.post('set_graph_config', { graph_type: yaxis });
setLocalStorage('graph_type', 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); }
});
function setGraphFormat(type) {

View File

@@ -32,17 +32,17 @@
</div>
% endif
<div class="btn-group" data-toggle="buttons" id="media_type-selection">
<label class="btn btn-dark active">
<input type="radio" name="media_type-filter" id="history-all" value="" autocomplete="off"> All
<label class="btn btn-dark">
<input type="radio" name="media_type-filter" id="history-all" value="all" autocomplete="off"> All
</label>
<label class="btn btn-dark">
<input type="radio" name="media_type-filter" id="history-movies" value="movie" autocomplete="off"> Movies
<input type="radio" name="media_type-filter" id="history-movie" value="movie" autocomplete="off"> Movies
</label>
<label class="btn btn-dark">
<input type="radio" name="media_type-filter" id="history-tv_shows" value="episode" autocomplete="off"> TV Shows
<input type="radio" name="media_type-filter" id="history-episode" value="episode" autocomplete="off"> TV Shows
</label>
<label class="btn btn-dark">
<input type="radio" name="media_type-filter" id="history-music" value="track" autocomplete="off"> Music
<input type="radio" name="media_type-filter" id="history-track" value="track" autocomplete="off"> Music
</label>
</div>
<div class="btn-group">
@@ -60,7 +60,8 @@
<th align="left" id="friendly_name">User</th>
<th align="left" id="ip_address">IP Address</th>
<th align="left" id="platform">Platform</th>
<th align="left" id="device">Player</th>
<th align="left" id="product">Product</th>
<th align="left" id="player">Player</th>
<th align="left" id="title">Title</th>
<th align="left" id="started">Started</th>
<th align="left" id="paused_counter">Paused</th>
@@ -143,7 +144,7 @@
var colvis = new $.fn.dataTable.ColVis(history_table, {
buttonText: '<i class="fa fa-columns"></i> Select columns',
buttonClass: 'btn btn-dark',
exclude: [0, 11]
exclude: [0, 12]
});
$(colvis.button()).appendTo('div.colvis-button-bar');
@@ -154,6 +155,7 @@
selected_filter = $('input[name=media_type-filter]:checked', '#media_type-selection');
$(selected_filter).closest('label').addClass('active');
media_type = $(selected_filter).val();
setLocalStorage('history_media_type', media_type);
history_table.draw();
});
@@ -163,8 +165,12 @@
});
}
var media_type = null;
var media_type = getLocalStorage('history_media_type', 'all');
var selected_user_id = "${_session['user_group']}" == "admin" ? null : "${_session['user_id']}";
$('#history-' + media_type).prop('checked', true);
$('#history-' + media_type).closest('label').addClass('active');
loadHistoryTable(media_type, selected_user_id);
% if _session['user_group'] == 'admin':

View File

@@ -26,6 +26,7 @@
<th align="left" id="friendly_name">User</th>
<th align="left" id="ip_address">IP Address</th>
<th align="left" id="platform">Platform</th>
<th align="left" id="product">Product</th>
<th align="left" id="device">Player</th>
<th align="left" id="title">Title</th>
<th align="left" id="started">Started</th>
@@ -54,14 +55,14 @@
json_data: JSON.stringify(d),
user_id: "${data['user_id']}",
start_date: "${data['start_date']}",
media_type: "${data.get('media_type')}",
media_type: "${data.get('media_type') or 'all'}",
transcode_decision: "${data.get('transcode_decision')}"
};
}
};
history_table = $('#history_table_modal').DataTable(history_table_options);
history_table.columns([0, 3, 4, 8, 10, 11]).visible(false);
history_table.columns([0, 3, 4, 5, 9, 11, 12]).visible(false);
clearSearchButton('history_table_modal', history_table);

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -15,9 +15,9 @@
<h3><span id="sessions-xml">Activity</span> &nbsp;&nbsp;
<small>
<span id="currentActivityHeader" style="display: none;">
Streams: <span id="currentActivityHeader-streams"></span> |
Sessions: <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 id="currentActivityHeader-bandwidth-tooltip" data-toggle="tooltip" title="Streaming Brain Estimate (Reserved Bandwidth)"><i class="fa fa-info-circle"></i></span>
</span>
</small>
</h3>
@@ -27,6 +27,8 @@
<div class="text-muted" id="dashboard-checking-activity"><i class="fa fa-refresh fa-spin"></i> Checking for activity...</div>
% elif config['pms_is_cloud']:
<div id="dashboard-no-activity" class="text-muted">Plex Cloud server is sleeping.</div>
% elif not config['first_run_complete']:
<div id="dashboard-no-activity" class="text-muted">The Tautulli setup wizard has not been completed. Please click <a href="welcome">here</a> to go to the setup wizard.</div>
% else:
<div id="dashboard-no-activity" class="text-muted">There was an error communicating with your Plex Server.
% if _session['user_group'] == 'admin':
@@ -44,25 +46,16 @@
<h3 class="pull-left">Watch Statistics</h3>
<div class="button-bar">
<div class="btn-group pull-left" data-toggle="buttons" id="watch-stats-toggles" style="margin-right: 3px">
% if config['home_stats_type'] == 0:
<label class="btn btn-dark active">
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-plays" value="0" autocomplete="off" checked> Play Count
<label class="btn btn-dark">
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-plays" value="plays" autocomplete="off"> Play Count
</label>
<label class="btn btn-dark">
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-duration" value="1" autocomplete="off"> Play Duration
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-duration" value="duration" autocomplete="off"> Play Duration
</label>
% else:
<label class="btn btn-dark">
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-plays" value="0" autocomplete="off"> Play Count
</label>
<label class="btn btn-dark active">
<input type="radio" class="watched-stats-toggle" name="watched-stats-type" id="watched-stats-duration" value="1" autocomplete="off" checked> Play Duration
</label>
% endif
</div>
<div class="input-group pull-left" style="width: 1px; margin-right: 3px" id="watched-stats-days-selection">
<span class="input-group-addon btn-dark inactive">Last</span>
<input type="number" class="form-control number-input" name="watched-stats-days" id="watched-stats-days" value="${config['home_stats_length']}" min="1" data-default="30" data-toggle="tooltip" title="Min: 1 day" />
<input type="number" class="form-control number-input" name="watched-stats-days" id="watched-stats-days" value="30" min="1" data-default="30" data-toggle="tooltip" title="Min: 1 day" />
<span class="input-group-addon btn-dark inactive">days</span>
</div>
</div>
@@ -111,8 +104,8 @@
</ul>
<div class="button-bar">
<div class="btn-group pull-left" data-toggle="buttons" id="recently-added-toggles" style="margin-right: 3px">
<label class="btn btn-dark active" id="recently-added-label-all">
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-all" value="" autocomplete="off"> All
<label class="btn btn-dark" id="recently-added-label-all">
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-all" value="all" autocomplete="off"> All
</label>
<label class="btn btn-dark" id="recently-added-label-movies">
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-movie" value="movie" autocomplete="off"> Movies
@@ -121,11 +114,14 @@
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-show" value="show" autocomplete="off"> TV Shows
</label>
<label class="btn btn-dark" id="recently-added-label-music">
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-music" value="artist" autocomplete="off"> Music
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-artist" value="artist" autocomplete="off"> Music
</label>
<label class="btn btn-dark" id="recently-added-label-other_video">
<input type="radio" name="recently-added-toggle" id="recently-added-toggle-other_video" value="other_video" autocomplete="off"> Videos
</label>
</div>
<div class="input-group pull-left" style="width: 1px;" id="recently-added-count-selection">
<input type="number" class="form-control number-input" name="recently-added-count" id="recently-added-count" value="${config['home_stats_recently_added_count']}" min="1" max="50" data-default="50" data-toggle="tooltip" title="Min: 1 item<br>Max: 50 items" />
<input type="number" class="form-control number-input" name="recently-added-count" id="recently-added-count" value="50" min="1" max="50" data-default="50" data-toggle="tooltip" title="Min: 1 item<br>Max: 50 items" />
<span class="input-group-addon btn-dark inactive">items</span>
</div>
</div>
@@ -434,7 +430,7 @@
if (s.stream_container_decision === 'transcode') {
transcode_container = 'Transcode (' + s.container.toUpperCase() + ' <i class="fa fa-long-arrow-right"></i> ' + s.stream_container.toUpperCase() + ')';
} else {
transcode_container = 'Direct Play (' + s.container.toUpperCase() + ')';
transcode_container = 'Direct Play (' + s.stream_container.toUpperCase() + ')';
}
$('#transcode_container-' + key).html(transcode_container);
@@ -469,7 +465,7 @@
} else if (s.stream_video_decision === 'copy') {
video_decision = 'Direct Stream (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')';
} else {
video_decision = 'Direct Play (' + s.video_codec.toUpperCase() + ' ' + v_res + ')';
video_decision = 'Direct Play (' + s.stream_video_codec.toUpperCase() + ' ' + sv_res + ')';
}
} else if (s.media_type === 'photo') {
video_decision = 'Direct Play (' + s.width + 'x' + s.height + ')';
@@ -485,7 +481,7 @@
} else if (s.stream_audio_decision === 'copy') {
audio_decision = 'Direct Stream (' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')';
} else {
audio_decision = 'Direct Play (' + a_codec + ' ' + capitalizeFirstLetter(s.audio_channel_layout.split('(')[0]) + ')';
audio_decision = 'Direct Play (' + sa_codec + ' ' + capitalizeFirstLetter(s.stream_audio_channel_layout.split('(')[0]) + ')';
}
}
$('#audio_decision-' + key).html(audio_decision);
@@ -499,7 +495,7 @@
} else if (s.stream_subtitle_decision === 'burn') {
subtitle_decision = 'Burn (' + s.subtitle_codec.toUpperCase() + ')';
} else {
subtitle_decision = 'Direct Play (' + ((s.synced_version === '1') ? s.stream_subtitle_codec.toUpperCase() : s.subtitle_codec.toUpperCase()) + ')';
subtitle_decision = 'Direct Play (' + ((s.synced_version === '1') ? s.subtitle_codec.toUpperCase() : s.stream_subtitle_codec.toUpperCase()) + ')';
}
}
$('#subtitle_decision-' + key).html(subtitle_decision);
@@ -724,20 +720,25 @@
});
}
var time_range = $('#watched-stats-days').val();
var stats_type = $('input[name=watched-stats-type]:checked', '#watch-stats-toggles').val();
var stats_type = getLocalStorage('home_stats_type', 'plays');
var time_range = getLocalStorage('home_stats_days', 30);
$('#watched-stats-' + stats_type).prop('checked', true);
$('#watched-stats-' + stats_type).closest('label').addClass('active');
$('#watched-stats-days').val(time_range);
getHomeStats(time_range, stats_type);
$('input[name=watched-stats-type]').change(function () {
stats_type = $(this).filter(':checked').val();
setLocalStorage('home_stats_type', stats_type);
getHomeStats(time_range, stats_type);
$.post('set_home_stats_config', { stats_type: stats_type });
});
$('#watched-stats-days').change(function () {
forceMinMax($(this));
time_range = $(this).val();
setLocalStorage('home_stats_days', time_range);
getHomeStats(time_range, stats_type);
$.post('set_home_stats_config', { time_range: time_range });
});
$('#watched-stats-days').tooltip({ container: 'body', placement: 'top', html: true });
@@ -771,7 +772,7 @@
async: true,
data: {
count: recently_added_count,
type: recently_added_type
media_type: recently_added_type
},
complete: function (xhr, status) {
$("#recentlyAdded").html(xhr.responseText);
@@ -780,8 +781,14 @@
}
});
}
var recently_added_count = $('#recently-added-count').val();
var recently_added_type = '';
var recently_added_count = getLocalStorage('home_stats_recently_added_count', 50);
var recently_added_type = getLocalStorage('home_stats_recently_added_type', 'all');;
$('#recently-added-toggle-' + recently_added_type).prop('checked', true);
$('#recently-added-toggle-' + recently_added_type).closest('label').addClass('active');
$('#recently-added-count').val(recently_added_count);
recentlyAdded(recently_added_count, recently_added_type);
function highlightAddedScrollerButton() {
@@ -835,6 +842,7 @@
$(selected_filter).closest('label').addClass('active');
recently_added_type = $(selected_filter).val();
resetScroller();
setLocalStorage('home_stats_recently_added_type', recently_added_type);
recentlyAdded(recently_added_count, recently_added_type);
});
@@ -842,8 +850,8 @@
forceMinMax($(this));
recently_added_count = $(this).val();
resetScroller();
setLocalStorage('home_stats_recently_added_count', recently_added_count);
recentlyAdded(recently_added_count, recently_added_type);
$.post('set_home_stats_config', { recently_added_count: recently_added_count });
});
$('#recently-added-count').tooltip({ container: 'body', placement: 'top', html: true });

View File

@@ -451,6 +451,7 @@ DOCUMENTATION :: END
<th align="left" id="friendly_name">User</th>
<th align="left" id="ip_address">IP Address</th>
<th align="left" id="platform">Platform</th>
<th align="left" id="product">Product</th>
<th align="left" id="player">Player</th>
<th align="left" id="title">Title</th>
<th align="left" id="started">Started</th>
@@ -613,7 +614,7 @@ DOCUMENTATION :: END
$(document).ready(function () {
get_history();
history_table = $('#history_table-RK-${data["rating_key"]}').DataTable(history_table_options);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 12] });
$(colvis.button()).appendTo('div.colvis-button-bar');
clearSearchButton('history_table-RK-${data["rating_key"]}', history_table);

View File

@@ -190,12 +190,12 @@ DOCUMENTATION :: END
<li>
<a href="info?rating_key=${child['rating_key']}" id="${child['rating_key']}">
<div class="item-children-poster">
<div class="item-children-poster-face cover-item style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
<div class="item-children-poster-face cover-item" style="background-image: url(pms_image_proxy?img=${child['thumb']}&width=300&height=300&fallback=cover);"></div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
</div>
<div class="item-children-instance-text-wrapper album-item">
<div class="item-children-instance-text-wrapper cover-item">
<h3 title="${child['title']}">${child['title']}</h3>
</div>
</a>
@@ -219,7 +219,7 @@ DOCUMENTATION :: END
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
</div>
<div class="item-children-instance-text-wrapper album-item">
<div class="item-children-instance-text-wrapper cover-item">
<h3 title="${child['parent_title']}">${child['parent_title']}</h3>
<h3 title="${child['title']}">${child['title']}</h3>
</div>
@@ -246,11 +246,11 @@ DOCUMENTATION :: END
</div>
</div>
</div>
</div>
% if _session['user_group'] == 'admin':
<span class="overlay-refresh-image" title="Refresh image"><i class="fa fa-refresh refresh_pms_image"></i></span>
% endif
<div class="item-children-instance-text-wrapper album-item">
</div>
<div class="item-children-instance-text-wrapper cover-item">
<h3 title="${child['original_title'] or child['grandparent_title']}">${child['original_title'] or child['grandparent_title']}</h3>
<h3 title="${child['title']}">${child['title']}</h3>
<h3 title="${child['parent_title']}" class="text-muted">${child['parent_title']}</h3>

View File

@@ -2,7 +2,7 @@ var hc_plays_by_day_options = {
chart: {
type: 'line',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'chart_div_plays_by_day'
renderTo: 'graph_plays_by_day'
},
title: {
text: ''
@@ -32,6 +32,11 @@ var hc_plays_by_day_options = {
selectHandler(this.category, this.series.name);
}
}
},
events: {
legendItemClick: function() {
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},

View File

@@ -2,17 +2,11 @@ var hc_plays_by_dayofweek_options = {
chart: {
type: 'column',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'chart_div_plays_by_dayofweek'
renderTo: 'graph_plays_by_dayofweek'
},
title: {
text: ''
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0
}
},
legend: {
enabled: true,
itemStyle: {
@@ -56,14 +50,21 @@ var hc_plays_by_dayofweek_options = {
},
plotOptions: {
column: {
borderWidth: 0,
stacking: 'normal',
borderWidth: '0',
dataLabels: {
enabled: false,
style: {
color: '#000'
}
}
},
series: {
events: {
legendItemClick: function () {
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},
tooltip: {

View File

@@ -2,17 +2,11 @@ var hc_plays_by_hourofday_options = {
chart: {
type: 'column',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'chart_div_plays_by_hourofday'
renderTo: 'graph_plays_by_hourofday'
},
title: {
text: ''
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0
}
},
legend: {
enabled: true,
itemStyle: {
@@ -56,14 +50,21 @@ var hc_plays_by_hourofday_options = {
},
plotOptions: {
column: {
borderWidth: 0,
stacking: 'normal',
borderWidth: '0',
dataLabels: {
enabled: false,
style: {
color: '#000'
}
}
},
series: {
events: {
legendItemClick: function () {
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},
tooltip: {

View File

@@ -2,7 +2,7 @@ var hc_plays_by_month_options = {
chart: {
type: 'column',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'chart_div_plays_by_month'
renderTo: 'graph_plays_by_month'
},
title: {
text: ''
@@ -50,14 +50,21 @@ var hc_plays_by_month_options = {
},
plotOptions: {
column: {
borderWidth: 0,
stacking: 'normal',
borderWidth: '0',
dataLabels: {
enabled: false,
style: {
color: '#000'
}
}
},
series: {
events: {
legendItemClick: function () {
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},
tooltip: {

View File

@@ -2,17 +2,11 @@ var hc_plays_by_platform_options = {
chart: {
type: 'column',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'chart_div_plays_by_platform'
renderTo: 'graph_plays_by_platform'
},
title: {
text: ''
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0
}
},
legend: {
enabled: true,
itemStyle: {
@@ -56,14 +50,21 @@ var hc_plays_by_platform_options = {
},
plotOptions: {
column: {
borderWidth: 0,
stacking: 'normal',
borderWidth: '0',
dataLabels: {
enabled: false,
style: {
color: '#000'
}
}
},
series: {
events: {
legendItemClick: function () {
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},
tooltip: {

View File

@@ -2,17 +2,11 @@ var hc_plays_by_platform_by_stream_type_options = {
chart: {
type: 'column',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'chart_div_plays_by_platform_by_stream_type'
renderTo: 'graph_plays_by_platform_by_stream_type'
},
title: {
text: ''
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0
}
},
legend: {
enabled: true,
itemStyle: {
@@ -56,14 +50,21 @@ var hc_plays_by_platform_by_stream_type_options = {
},
plotOptions: {
column: {
borderWidth: 0,
stacking: 'normal',
borderWidth: '0',
dataLabels: {
enabled: false,
style: {
color: '#000'
}
}
},
series: {
events: {
legendItemClick: function () {
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},
tooltip: {

View File

@@ -2,17 +2,11 @@ var hc_plays_by_source_resolution_options = {
chart: {
type: 'column',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'chart_div_plays_by_source_resolution'
renderTo: 'graph_plays_by_source_resolution'
},
title: {
text: ''
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0
}
},
legend: {
enabled: true,
itemStyle: {
@@ -56,14 +50,21 @@ var hc_plays_by_source_resolution_options = {
},
plotOptions: {
column: {
borderWidth: 0,
stacking: 'normal',
borderWidth: '0',
dataLabels: {
enabled: false,
style: {
color: '#000'
}
}
},
series: {
events: {
legendItemClick: function () {
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},
tooltip: {

View File

@@ -2,17 +2,11 @@ var hc_plays_by_stream_resolution_options = {
chart: {
type: 'column',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'chart_div_plays_by_stream_resolution'
renderTo: 'graph_plays_by_stream_resolution'
},
title: {
text: ''
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0
}
},
legend: {
enabled: true,
itemStyle: {
@@ -56,14 +50,21 @@ var hc_plays_by_stream_resolution_options = {
},
plotOptions: {
column: {
borderWidth: 0,
stacking: 'normal',
borderWidth: '0',
dataLabels: {
enabled: false,
style: {
color: '#000'
}
}
},
series: {
events: {
legendItemClick: function () {
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},
tooltip: {

View File

@@ -2,7 +2,7 @@ var hc_plays_by_stream_type_options = {
chart: {
type: 'line',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'chart_div_plays_by_stream_type'
renderTo: 'graph_plays_by_stream_type'
},
title: {
text: ''
@@ -32,6 +32,11 @@ var hc_plays_by_stream_type_options = {
selectHandler(this.category, this.series.name);
}
}
},
events: {
legendItemClick: function() {
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},

View File

@@ -2,17 +2,11 @@ var hc_plays_by_user_options = {
chart: {
type: 'column',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'chart_div_plays_by_user'
renderTo: 'graph_plays_by_user'
},
title: {
text: ''
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0
}
},
legend: {
enabled: true,
itemStyle: {
@@ -56,14 +50,21 @@ var hc_plays_by_user_options = {
},
plotOptions: {
column: {
borderWidth: 0,
stacking: 'normal',
borderWidth: '0',
dataLabels: {
enabled: false,
style: {
color: '#000'
}
}
},
series: {
events: {
legendItemClick: function () {
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},
tooltip: {

View File

@@ -2,17 +2,11 @@ var hc_plays_by_user_by_stream_type_options = {
chart: {
type: 'column',
backgroundColor: 'rgba(0,0,0,0)',
renderTo: 'chart_div_plays_by_user_by_stream_type'
renderTo: 'graph_plays_by_user_by_stream_type'
},
title: {
text: ''
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0
}
},
legend: {
enabled: true,
itemStyle: {
@@ -56,14 +50,21 @@ var hc_plays_by_user_by_stream_type_options = {
},
plotOptions: {
column: {
borderWidth: 0,
stacking: 'normal',
borderWidth: '0',
dataLabels: {
enabled: false,
style: {
color: '#000'
}
}
},
series: {
events: {
legendItemClick: function () {
setGraphVisibility(this.chart.renderTo.id, this.chart.series, this.name);
}
}
}
},
tooltip: {

View File

@@ -1,3 +1,45 @@
var p = {
name: 'Unknown',
version: 'Unknown',
os: 'Unknown'
};
if (typeof platform !== 'undefined') {
p.name = platform.name;
p.version = platform.version;
p.os = platform.os.toString();
}
if (['IE', 'Microsoft Edge', 'IE Mobile'].indexOf(p.name) > -1) {
if (!getCookie('browserDismiss')) {
var $browser_warning = $('<div id="browser-warning">' +
'<i class="fa fa-exclamation-circle"></i>&nbsp;' +
'Tautulli does not support Internet Explorer or Microsoft Edge! ' +
'Please use a different browser such as Chrome or Firefox.' +
'<button type="button" class="close"><i class="fa fa-remove"></i></button>' +
'</div>');
$('body').prepend($browser_warning);
var offset = $browser_warning.height();
warningOffset(offset);
$browser_warning.one('click', 'button.close', function () {
$browser_warning.remove();
warningOffset(-offset);
setCookie('browserDismiss', 'true', 7);
});
function warningOffset(offset) {
var navbar = $('.navbar-fixed-top');
if (navbar.length) {
navbar.offset({top: navbar.offset().top + offset});
}
var container = $('.body-container');
if (container.length) {
container.offset({top: container.offset().top + offset});
}
}
}
}
function initConfigCheckbox(elem, toggleElem, reverse) {
toggleElem = (toggleElem === undefined) ? null : toggleElem;
reverse = (reverse === undefined) ? false : reverse;
@@ -491,26 +533,48 @@ function PopupCenter(url, title, w, h) {
return newWindow;
}
if (!localStorage.getItem('Tautulli_ClientId')) {
localStorage.setItem('Tautulli_ClientId', uuidv4());
function setLocalStorage(key, value, path) {
if (path !== false) {
key = key + '_' + window.location.pathname;
}
localStorage.setItem(key, value);
}
function getLocalStorage(key, default_value, path) {
if (path !== false) {
key = key + '_' + window.location.pathname;
}
var value = localStorage.getItem(key);
if (value !== null) {
return value
} else if (default_value !== undefined) {
setLocalStorage(key, default_value, path);
return default_value
}
}
function uuidv4() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
)
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function(c) {
var cryptoObj = window.crypto || window.msCrypto; // for IE 11
return (c ^ cryptoObj.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
});
}
var x_plex_headers = {
function getPlexHeaders() {
return {
'Accept': 'application/json',
'X-Plex-Product': 'Tautulli',
'X-Plex-Version': 'Plex OAuth',
'X-Plex-Client-Identifier': localStorage.getItem('Tautulli_ClientId'),
'X-Plex-Platform': platform.name,
'X-Plex-Platform-Version': platform.version,
'X-Plex-Device': platform.os.toString(),
'X-Plex-Device-Name': platform.name
};
'X-Plex-Client-Identifier': getLocalStorage('Tautulli_ClientID', uuidv4(), false),
'X-Plex-Platform': p.name,
'X-Plex-Platform-Version': p.version,
'X-Plex-Model': 'Plex OAuth',
'X-Plex-Device': p.os,
'X-Plex-Device-Name': p.name,
'X-Plex-Device-Screen-Resolution': window.screen.width + 'x' + window.screen.height,
'X-Plex-Language': 'en'
};
}
var plex_oauth_window = null;
const plex_oauth_loader = '<style>' +
@@ -561,6 +625,7 @@ function closePlexOAuthWindow() {
}
getPlexOAuthPin = function () {
var x_plex_headers = getPlexHeaders();
var deferred = $.Deferred();
$.ajax({
@@ -568,7 +633,6 @@ getPlexOAuthPin = function () {
type: 'POST',
headers: x_plex_headers,
success: function(data) {
plex_oauth_window.location = 'https://app.plex.tv/auth/#!?clientID=' + x_plex_headers['X-Plex-Client-Identifier'] + '&code=' + data.code;
deferred.resolve({pin: data.id, code: data.code});
},
error: function() {
@@ -585,48 +649,60 @@ function PlexOAuth(success, error, pre) {
if (typeof pre === "function") {
pre()
}
clearTimeout(polling);
closePlexOAuthWindow();
plex_oauth_window = PopupCenter('', 'Plex-OAuth', 600, 700);
$(plex_oauth_window.document.body).html(plex_oauth_loader);
getPlexOAuthPin().then(function (data) {
var x_plex_headers = getPlexHeaders();
const pin = data.pin;
const code = data.code;
var keep_polling = true;
var oauth_params = {
'clientID': x_plex_headers['X-Plex-Client-Identifier'],
'context[device][product]': x_plex_headers['X-Plex-Product'],
'context[device][version]': x_plex_headers['X-Plex-Version'],
'context[device][platform]': x_plex_headers['X-Plex-Platform'],
'context[device][platformVersion]': x_plex_headers['X-Plex-Platform-Version'],
'context[device][device]': x_plex_headers['X-Plex-Device'],
'context[device][deviceName]': x_plex_headers['X-Plex-Device-Name'],
'context[device][model]': x_plex_headers['X-Plex-Model'],
'context[device][screenResolution]': x_plex_headers['X-Plex-Device-Screen-Resolution'],
'context[device][layout]': 'desktop',
'code': code
}
plex_oauth_window.location = 'https://app.plex.tv/auth/#!?' + encodeData(oauth_params);
polling = pin;
(function poll() {
polling = setTimeout(function () {
$.ajax({
url: 'https://plex.tv/api/v2/pins/' + pin,
type: 'GET',
headers: x_plex_headers,
success: function (data) {
if (data.authToken){
keep_polling = false;
closePlexOAuthWindow();
if (typeof success === "function") {
success(data.authToken)
}
}
},
error: function () {
keep_polling = false;
error: function (jqXHR, textStatus, errorThrown) {
if (textStatus !== "timeout") {
closePlexOAuthWindow();
if (typeof error === "function") {
error()
}
},
complete: function () {
if (keep_polling){
poll();
} else {
clearTimeout(polling);
}
},
timeout: 1000
complete: function () {
if (!plex_oauth_window.closed && polling === pin){
setTimeout(function() {poll()}, 1000);
}
},
timeout: 10000
});
}, 1000);
})();
}, function () {
closePlexOAuthWindow();
@@ -635,3 +711,9 @@ function PlexOAuth(success, error, pre) {
}
});
}
function encodeData(data) {
return Object.keys(data).map(function(key) {
return [key, data[key]].map(encodeURIComponent).join("=");
}).join("&");
}

View File

@@ -49,7 +49,7 @@ history_table_options = {
},
{
"targets": [1],
"data":"date",
"data": "date",
"createdCell": function (td, cellData, rowData, row, col) {
var date = moment(cellData, "X").format(date_format);
if (rowData['state'] !== null) {
@@ -77,7 +77,7 @@ history_table_options = {
},
{
"targets": [2],
"data":"friendly_name",
"data": "friendly_name",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
if (rowData['user_id']) {
@@ -112,7 +112,18 @@ history_table_options = {
},
{
"targets": [4],
"data":"platform",
"data": "platform",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html(capitalizeFirstLetter(cellData));
}
},
"width": "10%",
"className": "no-wrap"
},
{
"targets": [5],
"data": "product",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html(cellData);
@@ -122,7 +133,7 @@ history_table_options = {
"className": "no-wrap"
},
{
"targets": [5],
"targets": [6],
"data": "player",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
@@ -137,12 +148,12 @@ history_table_options = {
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp;' + cellData + '</div></a></div>');
}
},
"width": "12%",
"width": "10%",
"className": "no-wrap modal-control"
},
{
"targets": [6],
"data":"full_title",
"targets": [7],
"data": "full_title",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
var parent_info = '';
@@ -155,7 +166,7 @@ history_table_options = {
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?' + source + 'rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + '&middot; E' + rowData['media_index'] + ')'; }
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?' + source + 'rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
@@ -171,12 +182,12 @@ history_table_options = {
}
}
},
"width": "33%",
"width": "25%",
"className": "datatable-wrap"
},
{
"targets": [7],
"data":"started",
"targets": [8],
"data": "started",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData === null) {
$(td).html('n/a');
@@ -189,8 +200,8 @@ history_table_options = {
"className": "no-wrap"
},
{
"targets": [8],
"data":"paused_counter",
"targets": [9],
"data": "paused_counter",
"render": function (data, type, full) {
if (data !== null) {
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
@@ -203,8 +214,8 @@ history_table_options = {
"className": "no-wrap"
},
{
"targets": [9],
"data":"stopped",
"targets": [10],
"data": "stopped",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData === null || (rowData['state'] != null && rowData['state'] != "stopped")) {
$(td).html('n/a');
@@ -217,8 +228,8 @@ history_table_options = {
"className": "no-wrap"
},
{
"targets": [10],
"data":"duration",
"targets": [11],
"data": "duration",
"render": function (data, type, full) {
if (data !== null) {
return Math.round(moment.duration(data, 'seconds').as('minutes')) + ' mins';
@@ -231,7 +242,7 @@ history_table_options = {
"className": "no-wrap"
},
{
"targets": [11],
"targets": [12],
"data": "watched_status",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData == 1) {
@@ -489,7 +500,8 @@ function childTableFormat(rowData) {
'<th align="left" id="friendly_name">User</th>' +
'<th align="left" id="ip_address">IP Address</th>' +
'<th align="left" id="platform">Platform</th>' +
'<th align="left" id="platform">Player</th>' +
'<th align="left" id="product">Product</th>' +
'<th align="left" id="player">Player</th>' +
'<th align="left" id="title">Title</th>' +
'<th align="left" id="started">Started</th>' +
'<th align="left" id="paused_counter">Paused</th>' +

View File

@@ -107,7 +107,7 @@ history_table_modal_options = {
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + '&middot; E' + rowData['media_index'] + ')'; }
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');

View File

@@ -153,7 +153,7 @@ libraries_list_table_options = {
} else if (rowData['media_type'] === 'episode') {
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
if (rowData['rating_key']) {
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + '&middot; E' + rowData['media_index'] + ')'; }
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else {

View File

@@ -91,7 +91,7 @@ user_ip_table_options = {
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + '&middot; E' + rowData['media_index'] + ')'; }
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');

View File

@@ -166,7 +166,7 @@ users_list_table_options = {
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;">' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');
} else if (rowData['media_type'] === 'episode') {
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + '&middot; E' + rowData['media_index'] + ')'; }
if (rowData['parent_media_index'] && rowData['media_index']) { parent_info = ' (S' + rowData['parent_media_index'] + ' &middot; E' + rowData['media_index'] + ')'; }
media_type = '<span class="media-type-tooltip" data-toggle="tooltip" title="Episode"><i class="fa fa-television fa-fw"></i></span>';
thumb_popover = '<span class="thumb-tooltip" data-toggle="popover" data-img="pms_image_proxy?img=' + rowData['thumb'] + '&width=300&height=450&fallback=poster" data-height="120" data-width="80">' + cellData + parent_info + '</span>'
$(td).html('<div class="history-title"><a href="info?source=history&rating_key=' + rowData['rating_key'] + '"><div style="float: left;" >' + media_type + '&nbsp;' + thumb_popover + '</div></a></div>');

View File

@@ -205,6 +205,7 @@ DOCUMENTATION :: END
<th align="left" id="friendly_name">User</th>
<th align="left" id="ip_address">IP Address</th>
<th align="left" id="platform">Platform</th>
<th align="left" id="product">Product</th>
<th align="left" id="player">Player</th>
<th align="left" id="title">Title</th>
<th align="left" id="started">Started</th>
@@ -385,7 +386,7 @@ DOCUMENTATION :: END
};
history_table = $('#history_table-SID-${data["section_id"]}').DataTable(history_table_options);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 12] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table-SID-${data["section_id"]}', history_table);

View File

@@ -150,6 +150,7 @@
token: token,
remember_me: remember_me
};
var x_plex_headers = getPlexHeaders();
data = $.extend(data, x_plex_headers);
$.ajax({

View File

@@ -20,6 +20,24 @@
</div>
<p class="help-block">Optional: Enter a friendly name for this device. Leave blank for default.</p>
</div>
<div class="form-group">
<label for="friendly_name">Device Token</label>
<div class="row">
<div class="col-md-8">
<input type="text" class="form-control" id="device_token" value="${device['device_token']}" size="30" readonly>
</div>
</div>
<p class="help-block">Your app device token.</p>
</div>
<div class="form-group">
<label for="friendly_name">OneSignal Device ID</label>
<div class="row">
<div class="col-md-8">
<input type="text" class="form-control" id="device_id" value="${device['device_id']}" size="30" readonly>
</div>
</div>
<p class="help-block">Your OneSignal device ID for notifications.</p>
</div>
</div>
</div>
</form>

View File

@@ -584,6 +584,7 @@
var $email_selectors = $('#newsletter_email_to, #newsletter_email_cc, #newsletter_email_bcc').selectize({
plugins: ['remove_button'],
maxItems: null,
searchField: ['text', 'value'],
render: {
item: function(item, escape) {
return '<div>' +

View File

@@ -148,7 +148,7 @@
<div class="col-md-12">
<label>Notification Triggers</label>
<p class="help-block">
Select items that will trigger a notification for this ${notifier['agent_label']} notifiation agent.
Select items that will trigger a notification for this ${notifier['agent_label']} notification agent.
</p>
% for action in available_notification_actions:
<div class="checkbox">
@@ -566,6 +566,7 @@
var $email_selectors = $('#email_to, #email_cc, #email_bcc').selectize({
plugins: ['remove_button'],
maxItems: null,
searchField: ['text', 'value'],
render: {
item: function(item, escape) {
return '<div>' +

View File

@@ -8,7 +8,12 @@
% if text:
% for item in text:
<div style="padding-bottom: 10px;">
<h4>${item['media_type'].capitalize()}</h4>
<h4>
${item['media_type'].capitalize()}
% if item['media_type'] != 'server':
<span class="inline-pre">&lt;${item['media_type']}&gt;&lt;/${item['media_type']}&gt;</span> tags
% endif
</h4>
% if agent != 'webhook':
<pre>${item['subject']}</pre>
% endif

View File

@@ -0,0 +1,67 @@
<%
import datetime
import plexpy
from plexpy import activity_handler, helpers
if queue == 'active sessions':
filter_key = 'session_key-'
title_format = '{2} / {1} ({0})'
title_key = title_format.format('Session Key', 'Title', 'User')
description = 'Queue to flush stuck active sessions to the database.'
else:
filter_key = 'rating_key-'
title_format = '{1} ({0})'
title_key = title_format.format('Rating Key', 'Title')
description = 'Queue to flush recently added items to the database and send notifications if enabled.'
scheduled_jobs = [j.id for j in activity_handler.ACTIVITY_SCHED.get_jobs() if j.id.startswith(filter_key)]
%>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h4 class="modal-title">${queue.title()} Queue</h4>
</div>
<div class="modal-body">
<p class="help-block">
${description}
</p>
<table class="activity-queue">
<thead>
<tr>
<th>
${title_key}
</th>
<th>
Next Flush In
</th>
<th>
Next Flush Time
</th>
</tr>
</thead>
<tbody>
% if scheduled_jobs:
% for job in scheduled_jobs:
<%
sched_job = activity_handler.ACTIVITY_SCHED.get_job(job)
now = datetime.datetime.now(sched_job.next_run_time.tzinfo)
%>
<tr>
<td><strong>${title_format.format(*sched_job.args)}</strong></td>
<td>${helpers.format_timedelta_Hms(sched_job.next_run_time - now)}</td>
<td>${sched_job.next_run_time.astimezone(plexpy.SYS_TIMEZONE).strftime('%Y-%m-%d %H:%M:%S')}</td>
</tr>
% endfor
% else:
<tr>
<td colspan="3" style="text-align: center;"><i class="fa fa-check"></i>&nbsp; Nothing in the ${queue} queue</td>
</tr>
% endif
</tbody>
</table>
</div>
<div class="modal-footer">
</div>
</div>
</div>

View File

@@ -10,9 +10,9 @@ DOCUMENTATION :: END
</%doc>
<%!
import arrow
import datetime
import plexpy
from plexpy import common
from plexpy import common, helpers
scheduled_jobs = [j.id for j in plexpy.SCHED.get_jobs()]
%>
@@ -32,19 +32,24 @@ DOCUMENTATION :: END
% if job in scheduled_jobs:
<%
sched_job = plexpy.SCHED.get_job(job)
run_interval = arrow.get(str(sched_job.trigger.interval), ['H:mm:ss', 'HH:mm:ss'])
next_run_interval = arrow.get(sched_job.next_run_time).timestamp - arrow.now().timestamp
now = datetime.datetime.now(sched_job.next_run_time.tzinfo)
%>
<tr>
<td>${sched_job.id}</td>
<td><i class="fa fa-sm fa-fw fa-check"></i> Active</td>
<td>${arrow.get(run_interval).format('HH:mm:ss')}</td>
<td>${arrow.get(next_run_interval).format('HH:mm:ss')}</td>
<td>${arrow.get(sched_job.next_run_time).format('YYYY-MM-DD HH:mm:ss')}</td>
<td>${helpers.format_timedelta_Hms(sched_job.trigger.interval)}</td>
<td>${helpers.format_timedelta_Hms(sched_job.next_run_time - now)}</td>
<td>${sched_job.next_run_time.astimezone(plexpy.SYS_TIMEZONE).strftime('%Y-%m-%d %H:%M:%S')}</td>
</tr>
% elif job in ('Check for server response', 'Check for active sessions', 'Check for recently added items') and plexpy.WS_CONNECTED:
<tr>
% if job == 'Check for active sessions':
<td><a class="queue-modal-link" href="#" data-queue="active sessions">${job}</a></td>
% elif job == 'Check for recently added items':
<td><a class="queue-modal-link" href="#" data-queue="recently added">${job}</a></td>
% else:
<td>${job}</td>
% endif
<td><i class="fa fa-sm fa-fw fa-check"></i> Websocket</td>
<td>N/A</td>
<td>N/A</td>
@@ -62,3 +67,20 @@ DOCUMENTATION :: END
% endfor
</tbody>
</table>
<script>
$('.queue-modal-link').on('click', function (e) {
e.preventDefault();
$.ajax({
url: 'get_queue_modal',
data: {
queue: $(this).data('queue')
},
cache: false,
async: true,
complete: function(xhr, status) {
$("#queue-modal").html(xhr.responseText);
$('#queue-modal').modal();
}
});
});
</script>

View File

@@ -7,6 +7,9 @@
from plexpy import common, notifiers, newsletters
from plexpy.helpers import anon_url, checked
docker_setting = 'disabled' if plexpy.DOCKER else ''
docker_msg = '<span class="docker-setting small">(Controlled by Docker Container)</span>' if plexpy.DOCKER else ''
available_notification_agents = sorted(notifiers.available_notification_agents(), key=lambda k: k['label'].lower())
available_newsletter_agents = sorted(newsletters.available_newsletter_agents(), key=lambda k: k['label'].lower())
%>
@@ -230,12 +233,12 @@
% if plexpy.INSTALL_TYPE == 'git':
<div class="form-group advanced-setting">
<label for="git_branch">Git Remote / Branch</label>
<label for="git_branch">Git Remote / Branch</label> ${docker_msg | n}
<div class="row">
<div class="col-md-6">
<div class="input-group git-group">
<input type="text" class="form-control" id="git_remote" name="git_remote" value="${config['git_remote']}" data-parsley-trigger="change">
<select class="form-control" id="git_branch" name="git_branch">
<input type="text" class="form-control" id="git_remote" name="git_remote" value="${config['git_remote']}" data-parsley-trigger="change" ${docker_setting}>
<select class="form-control" id="git_branch" name="git_branch" ${docker_setting}>
<% branches = ('master', 'beta', 'nightly') %>
% for branch in branches:
<option value="${branch}" ${'selected' if config['git_branch'] == branch else ''}>${branch}</option>
@@ -245,7 +248,7 @@
% endif
</select>
<span class="input-group-btn">
<button class="btn btn-form" type="button" id="switch_git_branch">Checkout Branch</button>
<button class="btn btn-form" type="button" id="switch_git_branch" ${docker_setting}>Checkout Branch</button>
</span>
</div>
</div>
@@ -253,10 +256,10 @@
<p class="help-block">The git tracking remote and branch (default "origin/master"). Select to switch the git branch (requires restart).</p>
</div>
<div class="form-group advanced-setting">
<label for="git_path">Git Path</label>
<label for="git_path">Git Path</label> ${docker_msg | n}
<div class="row">
<div class="col-md-4">
<input type="text" class="form-control" id="git_path" name="git_path" value="${config['git_path']}" size="30">
<input type="text" class="form-control" id="git_path" name="git_path" value="${config['git_path']}" size="30" ${docker_setting}>
</div>
</div>
<p class="help-block">Optional: The path to your git environment variable. Leave blank for default.</p>
@@ -445,19 +448,19 @@
<p class="help-block">Launch browser pointed to Tautulli on startup.</p>
</div>
<div class="form-group advanced-setting">
<label for="http_host">HTTP Host</label>
<label for="http_host">HTTP Host</label> ${docker_msg | n}
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control http-settings" id="http_host" name="http_host" value="${config['http_host']}" data-parsley-trigger="change" required>
<input type="text" class="form-control http-settings" id="http_host" name="http_host" value="${config['http_host']}" data-parsley-trigger="change" required ${docker_setting}>
</div>
</div>
<p class="help-block">localhost or an IP address to bind the web server to. Default 0.0.0.0 to bind to all interfaces.</p>
</div>
<div class="form-group">
<label for="http_port">HTTP Port</label>
<label for="http_port">HTTP Port</label> ${docker_msg | n}
<div class="row">
<div class="col-md-2">
<input type="text" class="form-control http-settings" data-parsley-type="integer" id="http_port" name="http_port" value="${config['http_port']}" data-parsley-trigger="change" data-parsley-errors-container="#http_port_error" required>
<input type="text" class="form-control http-settings" data-parsley-type="integer" id="http_port" name="http_port" value="${config['http_port']}" data-parsley-trigger="change" data-parsley-errors-container="#http_port_error" required ${docker_setting}>
</div>
<div id="http_port_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
@@ -469,7 +472,7 @@
<div class="col-md-8">
<input type="text" class="form-control" id="http_base_url" name="http_base_url" value="${config['http_base_url']}" placeholder="http://mydomain.com" data-parsley-trigger="change" data-parsley-pattern="^https?:\/\/\S+$" data-parsley-errors-container="#http_base_url_error" data-parsley-error-message="Invalid URL">
</div>
<div id=http_base_url_error" class="alert alert-danger settings-alert" role="alert"></div>
<div id="http_base_url_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
<p class="help-block">
Set your public Tautulli domain for self-hosted notification images and newsletters. (e.g. http://mydomain.com)
@@ -811,6 +814,18 @@
</div>
</div>
</div>
<div class="form-group">
<label for="pms_update_check_interval">Update Check Interval</label>
<div class="row">
<div class="col-md-2">
<input type="text" class="form-control" data-parsley-type="integer" id="pms_update_check_interval" name="pms_update_check_interval" value="${config['pms_update_check_interval']}" size="3" data-parsley-min="1" data-parsley-trigger="change" data-parsley-errors-container="#pms_update_check_interval_error" required>
</div>
<div id="pms_update_check_interval_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
<p class="help-block">
The interval (in hours) Tautulli will check for a new Plex Media Server update. Minimum 1, default 24.
</p>
</div>
</div>
<div class="checkbox">
<label>
@@ -887,7 +902,6 @@
<h3>Current Activity Notifications</h3>
</div>
<p class="help-block">Note: Buffer warnings only work on certain Plex clients. Android and Plex Web do not report buffer events accurately or at all.</p>
<div class="form-group">
<label for="buffer_threshold">Buffer Threshold</label>
<div class="row">
@@ -896,7 +910,13 @@
</div>
<div id="buffer_threshold_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
<p class="help-block">How many buffer events should we wait before triggering the first warning. Buffer events increment on each monitor ping if play state is buffering. 0 to disable buffer warnings.</p>
<p class="help-block">
The number of buffer events required before triggering the first notification.
Buffer events increment on each incoming websocket message if the play state is buffering.
<br>
Note: Buffer warnings only work on certain Plex clients. Some clients can send excessive buffer messages or no messages at all.
This notification may be unreliable and not indicative of a real problem.
</p>
</div>
<div class="form-group advanced-setting">
<label for="buffer_wait">Buffer Wait</label>
@@ -1034,10 +1054,10 @@
<p class="help-block">Optional: Enter the full path to your custom newsletter templates folder. Leave blank for default.</p>
</div>
<div class="form-group advanced-setting">
<label for="newsletter_dir">Newsletter Output Directory</label>
<label for="newsletter_dir">Newsletter Output Directory</label> ${docker_msg | n}
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control" id="newsletter_dir" name="newsletter_dir" value="${config['newsletter_dir']}">
<input type="text" class="form-control" id="newsletter_dir" name="newsletter_dir" value="${config['newsletter_dir']}" ${docker_setting}>
</div>
</div>
<p class="help-block">Enter the full path to where newsletter files will be saved.</p>
@@ -1233,10 +1253,10 @@
</div>
<div class="form-group">
<label for="log_dir">Log Directory</label>
<label for="log_dir">Log Directory</label> ${docker_msg | n}
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control directory-settings" id="log_dir" name="log_dir" value="${config['log_dir']}">
<input type="text" class="form-control directory-settings" id="log_dir" name="log_dir" value="${config['log_dir']}" ${docker_setting}>
<div class="btn-group">
<button class="btn btn-form" type="button" id="clear_logs">Clear Logs</button>
</div>
@@ -1244,10 +1264,10 @@
</div>
</div>
<div class="form-group">
<label for="backup_dir">Backup Directory</label>
<label for="backup_dir">Backup Directory</label> ${docker_msg | n}
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control directory-settings" id="backup_dir" name="backup_dir" value="${config['backup_dir']}">
<input type="text" class="form-control directory-settings" id="backup_dir" name="backup_dir" value="${config['backup_dir']}" ${docker_setting}>
<div class="btn-group">
<button class="btn btn-form" type="button" id="backup_config">Backup Config</button>
<button class="btn btn-form" type="button" id="backup_database">Backup Database</button>
@@ -1256,10 +1276,10 @@
</div>
</div>
<div class="form-group">
<label for="cache_dir">Cache Directory</label>
<label for="cache_dir">Cache Directory</label> ${docker_msg | n}
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control directory-settings" id="cache_dir" name="cache_dir" value="${config['cache_dir']}">
<input type="text" class="form-control directory-settings" id="cache_dir" name="cache_dir" value="${config['cache_dir']}" ${docker_setting}>
<div class="btn-group">
<button class="btn btn-form" type="button" id="clear_cache">Clear All Cache</button>
<button class="btn btn-form" type="button" id="clear_image_cache">Clear Image Cache</button>
@@ -1308,6 +1328,7 @@
</%def>
<%def name="modalIncludes()">
<div id="queue-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="queue-modal"></div>
<div id="guidelines-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="guidelines-modal">
<div class="modal-dialog" role="document">
<div class="modal-content">
@@ -1523,7 +1544,7 @@
<div style="padding-bottom: 10px;">
<p class="help-block">
All text inside <span class="inline-pre">&lt;show&gt;&lt;/show&gt;</span>/<span class="inline-pre">&lt;season&gt;&lt;/season&gt;</span>/<span class="inline-pre">&lt;episode&gt;&lt;/episode&gt;</span>
tags will only be sent when the media type is show/season/episode.
tags will only be sent when the media type is show, season, or episode, respectively.
</p>
<p><strong style="color: #fff;">Example:</strong></p>
<pre>{show_name}&lt;season&gt; - Season {season_num}&lt;/season&gt;&lt;episode&gt; - S{season_num}E{episode_num} - {episode_name}&lt;/episode&gt; was recently added to Plex.</pre>
@@ -1534,7 +1555,7 @@
<div>
<p class="help-block">
All text inside <span class="inline-pre">&lt;artist&gt;&lt;/artist&gt;</span>/<span class="inline-pre">&lt;album&gt;&lt;/album&gt;</span>/<span class="inline-pre">&lt;track&gt;&lt;/track&gt;</span>
tags will only be sent when the media type is artist/album/track.
tags will only be sent when the media type is artist, album, or track, respectively.
</p>
<p><strong style="color: #fff;">Example:</strong></p>
<pre>{artist_name}&lt;album&gt; - {album_name}&lt;/album&gt;&lt;track&gt; - {album_name} - {track_name}&lt;/track&gt; was recently added to Plex.</pre>
@@ -1579,7 +1600,7 @@
<div>
<h4>List Slicing</h4>
</div>
<div>
<div style="padding-bottom: 10px;">
<p class="help-block">
Notification parameters which have a list of items can be sliced with a slice formatter <span class="inline-pre">:[X:Y]</span> to limit the number of items.
Note: the first item in the list is numbered <span class="inline-pre">0</span>.
@@ -1590,6 +1611,41 @@
{actors:[2:]} --> Only the 3rd to last actors (Actors: 2, 3, 4, ...)
{actors:[1:5]} --> Only the 2nd to 5th actors (Actors: 1, 2, 3, 4)</pre>
</div>
<div>
<h4>Prefix and Suffix</h4>
</div>
<div style="padding-bottom: 10px;">
<p class="help-block">
A prefix or a suffix can be added to the notification parameters using <span class="inline-pre">Prefix&lt;</span> and <span class="inline-pre">&gt;Suffix</span>.
If the notification parameter is unavailable, the prefix or suffix will not be displayed.
</p>
<p><strong style="color: #fff;">Example:</strong></p>
<pre>{rating} --> 8.9
{Rating: &lt;rating} --> Rating: 8.9
{rating&gt;/10} --> 8.9/10
{Rating: &lt;rating&gt;/10} --> Rating: 8.9/10</pre>
<p><strong style="color: #fff;">Example with unavailable parameter:</strong></p>
<pre>{rating} -->
Rating: {rating}/10 --> Rating: /10
{Rating: &lt;rating&gt;/10} --> </pre>
</div>
<div>
<h4>Combined</h4>
</div>
<div>
<p class="help-block">
If combining multiple notification text modifiers, the order of the modifiers must be:
</p>
<ol class="help-block">
<li>Prefix</li>
<li>Parameter</li>
<li>Case Modifier</li>
<li>List Slicing</li>
<li>Suffix</li>
</ol>
<p><strong style="color: #fff;">Example:</strong></p>
<pre>{Starring &lt;actors!c:[0]&gt; as the main character.} --> Starring Arnold Schwarzenegger as the main character.</pre>
</div>
</div>
</div>
<div class="modal-footer">
@@ -1611,7 +1667,7 @@
<div class="modal-body">
<div>
<p class="help-block">
If the value for a selected parameter cannot be provided, it will display as blank.
If the value for a selected parameter is unavailable, it will display as blank.
</p>
% for category in common.NEWSLETTER_PARAMETERS:
<table class="notification-params">
@@ -2084,7 +2140,7 @@ $(document).ready(function() {
if (!item.label) {
$.extend(item,
$(this.revertSettings.$children)
.filter('[value=' + item.value + ']').data()
.filter('[value="' + item.value + '"]').data()
);
}
var label = item.label || item.value;
@@ -2292,6 +2348,7 @@ $(document).ready(function() {
$("#token_verify").html('<i class="fa fa-refresh fa-spin"></i>').fadeIn('fast');
}
function OAuthSuccessCallback(authToken) {
var x_plex_headers = getPlexHeaders();
$("#pms_token").val(authToken);
$("#pms_uuid").val(x_plex_headers['X-Plex-Client-Identifier']);
$("#token_verify").html('<i class="fa fa-check"></i>').fadeIn('fast');
@@ -2521,8 +2578,10 @@ $(document).ready(function() {
.prop('selected', selected));
}
var download_url = 'https://plex.tv/api/downloads/' + (plex_update_channel === 'plexpass' ? '5' : '1') + '.json?channel=' + plex_update_channel;
$.ajax({
url: 'https://plex.tv/api/downloads/1.json?channel=' + plex_update_channel,
url: download_url,
type: 'GET',
dataType: 'json',
beforeSend: function (xhr) {

View File

@@ -230,7 +230,7 @@ DOCUMENTATION :: END
<tbody>
<tr>
<td>Codec</td>
<td>${data['stream_subtitle_codec'].upper()}</td>
<td>${data['stream_subtitle_codec'].upper() or '-'}</td>
<td>${data['subtitle_codec'].upper()}</td>
</tr>
</tbody>

View File

@@ -183,6 +183,7 @@ DOCUMENTATION :: END
async: true,
data: {
query: query_string,
limit: 30,
media_type: '${query["media_type"]}',
season_index: '${query["parent_media_index"]}'
},

View File

@@ -156,17 +156,17 @@ DOCUMENTATION :: END
</div>
% endif
<div class="btn-group" data-toggle="buttons" id="media_type-selection">
<label class="btn btn-dark active">
<input type="radio" name="media_type-filter" id="history-all" value="" autocomplete="off"> All
<label class="btn btn-dark">
<input type="radio" name="media_type-filter" id="history-all" value="all" autocomplete="off"> All
</label>
<label class="btn btn-dark">
<input type="radio" name="media_type-filter" id="history-movies" value="movie" autocomplete="off"> Movies
<input type="radio" name="media_type-filter" id="history-movie" value="movie" autocomplete="off"> Movies
</label>
<label class="btn btn-dark">
<input type="radio" name="media_type-filter" id="history-tv_shows" value="episode" autocomplete="off"> TV Shows
<input type="radio" name="media_type-filter" id="history-episode" value="episode" autocomplete="off"> TV Shows
</label>
<label class="btn btn-dark">
<input type="radio" name="media_type-filter" id="history-music" value="track" autocomplete="off"> Music
<input type="radio" name="media_type-filter" id="history-track" value="track" autocomplete="off"> Music
</label>
</div>
<div class="btn-group">
@@ -184,6 +184,7 @@ DOCUMENTATION :: END
<th align="left" id="friendly_name">User</th>
<th align="left" id="ip_address">IP Address</th>
<th align="left" id="platform">Platform</th>
<th align="left" id="product">Product</th>
<th align="left" id="player">Player</th>
<th align="left" id="title">Title</th>
<th align="left" id="started">Started</th>
@@ -274,12 +275,12 @@ DOCUMENTATION :: END
<table class="display user_ip_table" id="user_ip_table-UID-${data['user_id']}" width="100%">
<thead>
<tr>
<th align="left">Last Seen</th>
<th align="left">IP Address</th>
<th align="left">Last Platform</th>
<th align="left">Last Player</th>
<th align="left">Last Played</th>
<th align="left">Play Count</th>
<th align="left" id="last_seen">Last Seen</th>
<th align="left" id="ip_address">IP Address</th>
<th align="left" id="platform">Last Platform</th>
<th align="left" id="player">Last Player</th>
<th align="left" id="last_played">Last Played</th>
<th align="left" id="play_count">Play Count</th>
</tr>
</thead>
</table>
@@ -425,7 +426,7 @@ DOCUMENTATION :: END
history_table = $('#history_table-UID-${data["user_id"]}').DataTable(history_table_options);
history_table.column(2).visible(false);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 12] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table-UID-${data["user_id"]}', history_table);
@@ -435,6 +436,7 @@ DOCUMENTATION :: END
selected_filter = $('input[name=media_type-filter]:checked', '#media_type-selection');
$(selected_filter).closest('label').addClass('active');
media_type = $(selected_filter).val();
setLocalStorage('user_' + user_id + '-history_media_type', media_type);
history_table.draw();
});
}
@@ -494,7 +496,9 @@ DOCUMENTATION :: END
$('a[href="#tabs-history"]').on('shown.bs.tab', function() {
if (typeof(history_table) === 'undefined') {
var media_type = null;
var media_type = getLocalStorage('user_' + user_id + '-history_media_type', 'all');
$('#history-' + media_type).prop('checked', true);
$('#history-' + media_type).closest('label').addClass('active');
loadHistoryTable(media_type);
}
});

View File

@@ -90,6 +90,7 @@
<div class="row">
<div class="col-xs-12">
<select class="form-control pms-settings selectize-pms-ip" id="pms_ip_selectize">
% if config['pms_identifier']:
<option value="${config['pms_ip']}:${config['pms_port']}"
data-identifier="${config['pms_identifier']}"
data-ip="${config['pms_ip']}"
@@ -99,6 +100,7 @@
data-is_cloud="${config['pms_is_cloud']}"
data-label="${config['pms_name'] or 'Local'}"
selected>${config['pms_ip']}</option>
% endif
</select>
</div>
</div>
@@ -336,7 +338,7 @@ $(document).ready(function() {
if (!item.label) {
$.extend(item,
$(this.revertSettings.$children)
.filter('[value=' + item.value + ']').data()
.filter('[value="' + item.value + '"]').data()
);
}
var label = item.label || item.value;

View File

@@ -368,6 +368,7 @@
line-height: 1.2rem;
font-size: 0.9rem;
padding: 5px;
max-width: 320px;
}
.card-info-title a {
text-decoration: none;
@@ -952,6 +953,124 @@
</td>
</tr>
% endif
% if recently_added.get('other_video'):
<tr>
<td class="wrapper" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;box-sizing: border-box;padding: 5px;overflow: auto;">
<div class="sub-header-bar" style="margin-left: auto;margin-right: auto;font-size: 30px;text-align: center;width: 200px;border-top: 1px solid #E5A00D;margin-top: 15px;margin-bottom: 25px;"></div>
<div class="sub-header-title" style="margin-left: auto;margin-right: auto;font-size: 30px;text-align: center;font-weight: lighter;">
<img src="${(base_url_image + 'images/libraries/video.png') if base_url_image else 'https://tautulli.com/images/libraries/video.png'}" class="sub-header-icon" width="30" height="30" style="border: none;-ms-interpolation-mode: bicubic;max-width: 100%;height: 30px;width: 30px;vertical-align: middle;margin-right: 5px;margin-bottom: 5px;"> Recently Added Videos
</div>
<div class="sub-header-count" style="margin-left: auto;margin-right: auto;font-size: 30px;text-align: center;">
<span class="count" style="color: #E5A00D;">${len(recently_added['other_video'])}</span> <span class="count-units" style="color: #aaaaaa;font-size: 20px;text-transform: uppercase;">video${'s' if len(recently_added['other_video']) > 1 else ''}</span>
</div>
</td>
</tr>
<tr>
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
% for video_a, video_b in grouper(recently_added['other_video'], 2):
<tr>
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
<tr>
% for video in (video_a, video_b):
% if video:
% if not video_b:
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
% endif
<td align="center" valign="top" class="card-instance movie" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 3px;width: 502px;min-width: 502px;max-width: 502px;height: 233px;">
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + video['art_hash']) if base_url_image else video['art_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #282828;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
<tr>
<td class="card-poster-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;width: 152px;min-width: 152px;height: 227px;">
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + video['thumb_hash']) if base_url_image else video['thumb_url']});border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;background-color: #3F4245;background-position: center;background-size: cover;background-repeat: no-repeat;background-clip: padding-box;border: 1px solid rgba(255,255,255,.1);">
<tr>
<td style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;">
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${video['rating_key']}" title="${video['title']}" target="_blank" style="text-decoration: underline;">
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-poster.png' if base_url_image else 'https://tautulli.com/images/newsletter/view-on-plex-poster.png'}" width="150" height="225" style="border: none;-ms-interpolation-mode: bicubic;max-width: 100%;display: block;">
</a>
</td>
</tr>
</table>
</td>
<td class="card-info-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;padding-left: 4px;text-align: left;height: 227px;">
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;height: 100%;">
<tr>
<td class="card-info-title nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.9rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;border-bottom: 1px solid rgba(255, 255, 255, .1);line-height: 1.2rem;padding: 5px;max-width: 320px;">
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${video['rating_key']}" title="${video['title']}" target="_blank" style="text-decoration: none;color: #ffffff;">${video['title']}</a>
</td>
</tr>
<tr>
<td class="card-info-body" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.75rem;vertical-align: top;padding: 5px;height: 100%;">
% if video['tagline']:
<p class="nowrap mb5" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;margin-bottom: 5px;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;max-width: 325px;color: #ffffff;">
<em>${video['tagline']}</em>
</p>
% endif
<p style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-weight: 400;margin: 0;max-width: 325px;color: #ffffff;">
${video['summary'][:450] + (video['summary'][450:] and '...')}
</p>
</td>
</tr>
<tr>
<td class="card-info-footer nowrap" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.6rem;vertical-align: top;white-space: nowrap;text-overflow: ellipsis;overflow: hidden;padding-top: 0px;padding-right: 5px;padding-bottom: 5px;padding-left: 5px;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
<tr>
<td class="badge-container" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;max-width: 260px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
<tr>
% if video['year']:
<td class="badge" title="${video['year']}" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 11px;vertical-align: middle;display: inline-block;min-width: 10px;margin-right: 4px;padding: 3px 7px;line-height: 1;text-align: center;white-space: nowrap;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${video['year']}</td>
% endif
% if video['duration']:
<% duration = int(int(video['duration'])/60000) %>
<td class="badge" title="${duration} mins" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 11px;vertical-align: middle;display: inline-block;min-width: 10px;margin-right: 4px;padding: 3px 7px;line-height: 1;text-align: center;white-space: nowrap;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${duration} mins</td>
% endif
% if video['genres']:
% for genre in video['genres'][:]:
<td class="badge" title="${genre}" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 11px;vertical-align: middle;display: inline-block;min-width: 10px;margin-right: 4px;padding: 3px 7px;line-height: 1;text-align: center;white-space: nowrap;background-color: rgba(0, 0, 0, .25);border-radius: 2px;text-overflow: ellipsis;overflow: hidden;color: #ffffff;">${genre}</td>
% endfor
% endif
</tr>
</table>
</td>
% if video['rating']:
<% rating = int(round(float(video['rating']) / 2)) %>
<td class="star-rating-container" title="${int(float(video['rating'])/0.1)}%" align="right" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 14px;vertical-align: top;width: 65px;">
<table border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate;mso-table-lspace: 0pt;mso-table-rspace: 0pt;width: 100%;">
<tr>
% for _ in range(rating):
<td class="star-rating full" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.8rem;vertical-align: bottom;margin-left: 4px;line-height: 1rem;width: 0.5rem;display: inline-block;color: #E5A00D;">&#9733;</td>
% endfor
% for _ in range(5-rating):
<td class="star-rating empty" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 0.8rem;vertical-align: bottom;margin-left: 4px;line-height: 1rem;width: 0.5rem;display: inline-block;color: #aaaaaa;">&#9734;</td>
% endfor
</tr>
</table>
</td>
% endif
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
% if not video_b:
<td align="center" valign="top" class="card-instance pad" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;overflow: hidden;padding: 0 !important;width: 251px !important;min-width: 251px !important;max-width: 251px !important;"></td>
% endif
% endif
% endfor
</tr>
</table>
</td>
</tr>
% endfor
</table>
</td>
</tr>
% endif
<tr>
<td class="footer" style="font-family: 'Open Sans', Helvetica, Arial, sans-serif;font-size: 12px;vertical-align: top;clear: both;margin-top: 10px;text-align: center;width: 100%;">
<div class="footer-bar" style="margin-left: auto;margin-right: auto;width: 200px;border-top: 1px solid #E5A00D;margin-top: 25px;"></div>

View File

@@ -953,6 +953,124 @@
</td>
</tr>
% endif
% if recently_added.get('other_video'):
<tr>
<td class="wrapper">
<div class="sub-header-bar"></div>
<div class="sub-header-title">
<img src="${(base_url_image + 'images/libraries/video.png') if base_url_image else 'https://tautulli.com/images/libraries/video.png'}" class="sub-header-icon" width="30" height="30"/> Recently Added Videos
</div>
<div class="sub-header-count">
<span class="count">${len(recently_added['other_video'])}</span> <span class="count-units">video${'s' if len(recently_added['other_video']) > 1 else ''}</span>
</div>
</td>
</tr>
<tr>
<td>
<table border="0" cellpadding="0" cellspacing="0">
% for video_a, video_b in grouper(recently_added['other_video'], 2):
<tr>
<td>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
% for video in (video_a, video_b):
% if video:
% if not video_b:
<td align="center" valign="top" class="card-instance pad"></td>
% endif
<td align="center" valign="top" class="card-instance movie">
<table border="0" cellpadding="0" cellspacing="3" width="100%" class="card-background" style="background-image: url(${(base_url_image + video['art_hash']) if base_url_image else video['art_url']});">
<tr>
<td class="card-poster-container">
<table border="0" cellpadding="0" cellspacing="0" class="card-poster" style="background-image: url(${(base_url_image + video['thumb_hash']) if base_url_image else video['thumb_url']})">
<tr>
<td>
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${video['rating_key']}" title="${video['title']}" target="_blank">
<img class="card-poster-overlay" src="${base_url_image + 'images/newsletter/view-on-plex-poster.png' if base_url_image else 'https://tautulli.com/images/newsletter/view-on-plex-poster.png'}" width="150" height="225">
</a>
</td>
</tr>
</table>
</td>
<td class="card-info-container">
<table border="0" cellpadding="0" cellspacing="0" class="card-info-container-table">
<tr>
<td class="card-info-title nowrap">
<a href="${parameters['pms_web_url']}#!/server/${parameters['pms_identifier']}/details?key=%2Flibrary%2Fmetadata%2F${video['rating_key']}" title="${video['title']}" target="_blank">${video['title']}</a>
</td>
</tr>
<tr>
<td class="card-info-body">
% if video['tagline']:
<p class="nowrap mb5">
<em>${video['tagline']}</em>
</p>
% endif
<p>
${video['summary'][:450] + (video['summary'][450:] and '...')}
</p>
</td>
</tr>
<tr>
<td class="card-info-footer nowrap">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td class="badge-container">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
% if video['year']:
<td class="badge" title="${video['year']}">${video['year']}</td>
% endif
% if video['duration']:
<% duration = int(int(video['duration'])/60000) %>
<td class="badge" title="${duration} mins">${duration} mins</td>
% endif
% if video['genres']:
% for genre in video['genres'][:]:
<td class="badge" title="${genre}">${genre}</td>
% endfor
% endif
</tr>
</table>
</td>
% if video['rating']:
<% rating = int(round(float(video['rating']) / 2)) %>
<td class="star-rating-container" title="${int(float(video['rating'])/0.1)}%" align="right">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
% for _ in range(rating):
<td class="star-rating full">&#9733;</td>
% endfor
% for _ in range(5-rating):
<td class="star-rating empty">&#9734;</td>
% endfor
</tr>
</table>
</td>
% endif
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
% if not video_b:
<td align="center" valign="top" class="card-instance pad"></td>
% endif
% endif
% endfor
</tr>
</table>
</td>
</tr>
% endfor
</table>
</td>
</tr>
% endif
<tr>
<td class="footer">
<div class="footer-bar"></div>

0
init-scripts/init.fedora.centos.service Normal file → Executable file
View File

View File

@@ -24,7 +24,10 @@
# - The example settings in this file assume that Tautulli is installed to: /opt/Tautulli
#
# - To create this user and give it ownership of the Tautulli directory:
# sudo adduser --system --no-create-home tautulli
# 1. Create the user:
# Ubuntu/Debian: sudo addgroup tautulli && sudo adduser --system --no-create-home tautulli --ingroup tautulli
# CentOS/Fedora: sudo adduser --system --no-create-home tautulli
# 2. Give the user ownership of the Tautulli directory:
# sudo chown tautulli:tautulli -R /opt/Tautulli
#
# - Adjust ExecStart= to point to:
@@ -51,7 +54,11 @@ ExecStart=/opt/Tautulli/Tautulli.py --config /opt/Tautulli/config.ini --datadir
GuessMainPID=no
Type=forking
User=tautulli
Group=tautlli
Group=tautulli
Restart=on-abnormal
RestartSec=5
StartLimitInterval=90
StartLimitBurst=3
[Install]
WantedBy=multi-user.target

View File

@@ -1,3 +1,3 @@
from .core import where, old_where
from .core import where
__version__ = "2017.11.05"
__version__ = "2019.03.09"

View File

@@ -326,36 +326,6 @@ OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH
QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS
-----END CERTIFICATE-----
# Issuer: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association
# Subject: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association
# Label: "Visa eCommerce Root"
# Serial: 25952180776285836048024890241505565794
# MD5 Fingerprint: fc:11:b8:d8:08:93:30:00:6d:23:f9:7e:eb:52:1e:02
# SHA1 Fingerprint: 70:17:9b:86:8c:00:a4:fa:60:91:52:22:3f:9f:3e:32:bd:e0:05:62
# SHA256 Fingerprint: 69:fa:c9:bd:55:fb:0a:c7:8d:53:bb:ee:5c:f1:d5:97:98:9f:d0:aa:ab:20:a2:51:51:bd:f1:73:3e:e7:d1:22
-----BEGIN CERTIFICATE-----
MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr
MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl
cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv
bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw
CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h
dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l
cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h
2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E
lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV
ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq
299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t
vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL
dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD
AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF
AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR
zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3
LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd
7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw
++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt
398znM/jra6O1I7mT1GvFpLgXPYHDw==
-----END CERTIFICATE-----
# Issuer: CN=AAA Certificate Services O=Comodo CA Limited
# Subject: CN=AAA Certificate Services O=Comodo CA Limited
# Label: "Comodo AAA Services root"
@@ -573,78 +543,6 @@ Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2
ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M
-----END CERTIFICATE-----
# Issuer: CN=Chambers of Commerce Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org
# Subject: CN=Chambers of Commerce Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org
# Label: "Camerfirma Chambers of Commerce Root"
# Serial: 0
# MD5 Fingerprint: b0:01:ee:14:d9:af:29:18:94:76:8e:f1:69:33:2a:84
# SHA1 Fingerprint: 6e:3a:55:a4:19:0c:19:5c:93:84:3c:c0:db:72:2e:31:30:61:f0:b1
# SHA256 Fingerprint: 0c:25:8a:12:a5:67:4a:ef:25:f2:8b:a7:dc:fa:ec:ee:a3:48:e5:41:e6:f5:cc:4e:e6:3b:71:b3:61:60:6a:c3
-----BEGIN CERTIFICATE-----
MIIEvTCCA6WgAwIBAgIBADANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJFVTEn
MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEiMCAGA1UEAxMZQ2hhbWJlcnMg
b2YgQ29tbWVyY2UgUm9vdDAeFw0wMzA5MzAxNjEzNDNaFw0zNzA5MzAxNjEzNDRa
MH8xCzAJBgNVBAYTAkVVMScwJQYDVQQKEx5BQyBDYW1lcmZpcm1hIFNBIENJRiBB
ODI3NDMyODcxIzAhBgNVBAsTGmh0dHA6Ly93d3cuY2hhbWJlcnNpZ24ub3JnMSIw
IAYDVQQDExlDaGFtYmVycyBvZiBDb21tZXJjZSBSb290MIIBIDANBgkqhkiG9w0B
AQEFAAOCAQ0AMIIBCAKCAQEAtzZV5aVdGDDg2olUkfzIx1L4L1DZ77F1c2VHfRtb
unXF/KGIJPov7coISjlUxFF6tdpg6jg8gbLL8bvZkSM/SAFwdakFKq0fcfPJVD0d
BmpAPrMMhe5cG3nCYsS4No41XQEMIwRHNaqbYE6gZj3LJgqcQKH0XZi/caulAGgq
7YN6D6IUtdQis4CwPAxaUWktWBiP7Zme8a7ileb2R6jWDA+wWFjbw2Y3npuRVDM3
0pQcakjJyfKl2qUMI/cjDpwyVV5xnIQFUZot/eZOKjRa3spAN2cMVCFVd9oKDMyX
roDclDZK9D7ONhMeU+SsTjoF7Nuucpw4i9A5O4kKPnf+dQIBA6OCAUQwggFAMBIG
A1UdEwEB/wQIMAYBAf8CAQwwPAYDVR0fBDUwMzAxoC+gLYYraHR0cDovL2NybC5j
aGFtYmVyc2lnbi5vcmcvY2hhbWJlcnNyb290LmNybDAdBgNVHQ4EFgQU45T1sU3p
26EpW1eLTXYGduHRooowDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIA
BzAnBgNVHREEIDAegRxjaGFtYmVyc3Jvb3RAY2hhbWJlcnNpZ24ub3JnMCcGA1Ud
EgQgMB6BHGNoYW1iZXJzcm9vdEBjaGFtYmVyc2lnbi5vcmcwWAYDVR0gBFEwTzBN
BgsrBgEEAYGHLgoDATA+MDwGCCsGAQUFBwIBFjBodHRwOi8vY3BzLmNoYW1iZXJz
aWduLm9yZy9jcHMvY2hhbWJlcnNyb290Lmh0bWwwDQYJKoZIhvcNAQEFBQADggEB
AAxBl8IahsAifJ/7kPMa0QOx7xP5IV8EnNrJpY0nbJaHkb5BkAFyk+cefV/2icZd
p0AJPaxJRUXcLo0waLIJuvvDL8y6C98/d3tGfToSJI6WjzwFCm/SlCgdbQzALogi
1djPHRPH8EjX1wWnz8dHnjs8NMiAT9QUu/wNUPf6s+xCX6ndbcj0dc97wXImsQEc
XCz9ek60AcUFV7nnPKoF2YjpB0ZBzu9Bga5Y34OirsrXdx/nADydb47kMgkdTXg0
eDQ8lJsm7U9xxhl6vSAiSFr+S30Dt+dYvsYyTnQeaN2oaFuzPu5ifdmA6Ap1erfu
tGWaIZDgqtCYvDi1czyL+Nw=
-----END CERTIFICATE-----
# Issuer: CN=Global Chambersign Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org
# Subject: CN=Global Chambersign Root O=AC Camerfirma SA CIF A82743287 OU=http://www.chambersign.org
# Label: "Camerfirma Global Chambersign Root"
# Serial: 0
# MD5 Fingerprint: c5:e6:7b:bf:06:d0:4f:43:ed:c4:7a:65:8a:fb:6b:19
# SHA1 Fingerprint: 33:9b:6b:14:50:24:9b:55:7a:01:87:72:84:d9:e0:2f:c3:d2:d8:e9
# SHA256 Fingerprint: ef:3c:b4:17:fc:8e:bf:6f:97:87:6c:9e:4e:ce:39:de:1e:a5:fe:64:91:41:d1:02:8b:7d:11:c0:b2:29:8c:ed
-----BEGIN CERTIFICATE-----
MIIExTCCA62gAwIBAgIBADANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJFVTEn
MCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgyNzQzMjg3MSMwIQYDVQQL
ExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4GA1UEAxMXR2xvYmFsIENo
YW1iZXJzaWduIFJvb3QwHhcNMDMwOTMwMTYxNDE4WhcNMzcwOTMwMTYxNDE4WjB9
MQswCQYDVQQGEwJFVTEnMCUGA1UEChMeQUMgQ2FtZXJmaXJtYSBTQSBDSUYgQTgy
NzQzMjg3MSMwIQYDVQQLExpodHRwOi8vd3d3LmNoYW1iZXJzaWduLm9yZzEgMB4G
A1UEAxMXR2xvYmFsIENoYW1iZXJzaWduIFJvb3QwggEgMA0GCSqGSIb3DQEBAQUA
A4IBDQAwggEIAoIBAQCicKLQn0KuWxfH2H3PFIP8T8mhtxOviteePgQKkotgVvq0
Mi+ITaFgCPS3CU6gSS9J1tPfnZdan5QEcOw/Wdm3zGaLmFIoCQLfxS+EjXqXd7/s
QJ0lcqu1PzKY+7e3/HKE5TWH+VX6ox8Oby4o3Wmg2UIQxvi1RMLQQ3/bvOSiPGpV
eAp3qdjqGTK3L/5cPxvusZjsyq16aUXjlg9V9ubtdepl6DJWk0aJqCWKZQbua795
B9Dxt6/tLE2Su8CoX6dnfQTyFQhwrJLWfQTSM/tMtgsL+xrJxI0DqX5c8lCrEqWh
z0hQpe/SyBoT+rB/sYIcd2oPX9wLlY/vQ37mRQklAgEDo4IBUDCCAUwwEgYDVR0T
AQH/BAgwBgEB/wIBDDA/BgNVHR8EODA2MDSgMqAwhi5odHRwOi8vY3JsLmNoYW1i
ZXJzaWduLm9yZy9jaGFtYmVyc2lnbnJvb3QuY3JsMB0GA1UdDgQWBBRDnDafsJ4w
TcbOX60Qq+UDpfqpFDAOBgNVHQ8BAf8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAgAH
MCoGA1UdEQQjMCGBH2NoYW1iZXJzaWducm9vdEBjaGFtYmVyc2lnbi5vcmcwKgYD
VR0SBCMwIYEfY2hhbWJlcnNpZ25yb290QGNoYW1iZXJzaWduLm9yZzBbBgNVHSAE
VDBSMFAGCysGAQQBgYcuCgEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly9jcHMuY2hh
bWJlcnNpZ24ub3JnL2Nwcy9jaGFtYmVyc2lnbnJvb3QuaHRtbDANBgkqhkiG9w0B
AQUFAAOCAQEAPDtwkfkEVCeR4e3t/mh/YV3lQWVPMvEYBZRqHN4fcNs+ezICNLUM
bKGKfKX0j//U2K0X1S0E0T9YgOKBWYi+wONGkyT+kL0mojAt6JcmVzWJdJYY9hXi
ryQZVgICsroPFOrGimbBhkVVi76SvpykBMdJPJ7oKXqJ1/6v/2j1pReQvayZzKWG
VwlnRtvWFsJG8eSpUPWP0ZIV018+xgBJOm5YstHRJw0lyDL4IBHNfTIzSJRUTN3c
ecQwn+uOuFW114hcxWokPbLTBQNRxgfvzBRydD1ucs4YKIxKoHflCStFREest2d/
AYoFWpO+ocH/+OcOZ6RHSXZddZAa9SaP8A==
-----END CERTIFICATE-----
# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com
# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com
# Label: "XRamp Global CA Root"
@@ -931,38 +829,6 @@ JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
-----END CERTIFICATE-----
# Issuer: CN=DST ACES CA X6 O=Digital Signature Trust OU=DST ACES
# Subject: CN=DST ACES CA X6 O=Digital Signature Trust OU=DST ACES
# Label: "DST ACES CA X6"
# Serial: 17771143917277623872238992636097467865
# MD5 Fingerprint: 21:d8:4c:82:2b:99:09:33:a2:eb:14:24:8d:8e:5f:e8
# SHA1 Fingerprint: 40:54:da:6f:1c:3f:40:74:ac:ed:0f:ec:cd:db:79:d1:53:fb:90:1d
# SHA256 Fingerprint: 76:7c:95:5a:76:41:2c:89:af:68:8e:90:a1:c7:0f:55:6c:fd:6b:60:25:db:ea:10:41:6d:7e:b6:83:1f:8c:40
-----BEGIN CERTIFICATE-----
MIIECTCCAvGgAwIBAgIQDV6ZCtadt3js2AdWO4YV2TANBgkqhkiG9w0BAQUFADBb
MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3Qx
ETAPBgNVBAsTCERTVCBBQ0VTMRcwFQYDVQQDEw5EU1QgQUNFUyBDQSBYNjAeFw0w
MzExMjAyMTE5NThaFw0xNzExMjAyMTE5NThaMFsxCzAJBgNVBAYTAlVTMSAwHgYD
VQQKExdEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdDERMA8GA1UECxMIRFNUIEFDRVMx
FzAVBgNVBAMTDkRTVCBBQ0VTIENBIFg2MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAuT31LMmU3HWKlV1j6IR3dma5WZFcRt2SPp/5DgO0PWGSvSMmtWPu
ktKe1jzIDZBfZIGxqAgNTNj50wUoUrQBJcWVHAx+PhCEdc/BGZFjz+iokYi5Q1K7
gLFViYsx+tC3dr5BPTCapCIlF3PoHuLTrCq9Wzgh1SpL11V94zpVvddtawJXa+ZH
fAjIgrrep4c9oW24MFbCswKBXy314powGCi4ZtPLAZZv6opFVdbgnf9nKxcCpk4a
ahELfrd755jWjHZvwTvbUJN+5dCOHze4vbrGn2zpfDPyMjwmR/onJALJfh1biEIT
ajV8fTXpLmaRcpPVMibEdPVTo7NdmvYJywIDAQABo4HIMIHFMA8GA1UdEwEB/wQF
MAMBAf8wDgYDVR0PAQH/BAQDAgHGMB8GA1UdEQQYMBaBFHBraS1vcHNAdHJ1c3Rk
c3QuY29tMGIGA1UdIARbMFkwVwYKYIZIAWUDAgEBATBJMEcGCCsGAQUFBwIBFjto
dHRwOi8vd3d3LnRydXN0ZHN0LmNvbS9jZXJ0aWZpY2F0ZXMvcG9saWN5L0FDRVMt
aW5kZXguaHRtbDAdBgNVHQ4EFgQUCXIGThhDD+XWzMNqizF7eI+og7gwDQYJKoZI
hvcNAQEFBQADggEBAKPYjtay284F5zLNAdMEA+V25FYrnJmQ6AgwbN99Pe7lv7Uk
QIRJ4dEorsTCOlMwiPH1d25Ryvr/ma8kXxug/fKshMrfqfBfBC6tFr8hlxCBPeP/
h40y3JTlR4peahPJlJU90u7INJXQgNStMgiAVDzgvVJT11J8smk/f3rPanTK+gQq
nExaBqXpIK1FZg9p8d2/6eMyi/rgwYZNcjwu2JN4Cir42NInPRmJX1p7ijvMDNpR
rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2
9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis=
-----END CERTIFICATE-----
# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG
# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG
# Label: "SwissSign Gold CA - G2"
@@ -1291,35 +1157,6 @@ fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv
GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY=
-----END CERTIFICATE-----
# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication EV RootCA1
# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication EV RootCA1
# Label: "Security Communication EV RootCA1"
# Serial: 0
# MD5 Fingerprint: 22:2d:a6:01:ea:7c:0a:f7:f0:6c:56:43:3f:77:76:d3
# SHA1 Fingerprint: fe:b8:c4:32:dc:f9:76:9a:ce:ae:3d:d8:90:8f:fd:28:86:65:64:7d
# SHA256 Fingerprint: a2:2d:ba:68:1e:97:37:6e:2d:39:7d:72:8a:ae:3a:9b:62:96:b9:fd:ba:60:bc:2e:11:f6:47:f2:c6:75:fb:37
-----BEGIN CERTIFICATE-----
MIIDfTCCAmWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJKUDEl
MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEqMCgGA1UECxMh
U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBFViBSb290Q0ExMB4XDTA3MDYwNjAyMTIz
MloXDTM3MDYwNjAyMTIzMlowYDELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09N
IFRydXN0IFN5c3RlbXMgQ08uLExURC4xKjAoBgNVBAsTIVNlY3VyaXR5IENvbW11
bmljYXRpb24gRVYgUm9vdENBMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBALx/7FebJOD+nLpCeamIivqA4PUHKUPqjgo0No0c+qe1OXj/l3X3L+SqawSE
RMqm4miO/VVQYg+kcQ7OBzgtQoVQrTyWb4vVog7P3kmJPdZkLjjlHmy1V4qe70gO
zXppFodEtZDkBp2uoQSXWHnvIEqCa4wiv+wfD+mEce3xDuS4GBPMVjZd0ZoeUWs5
bmB2iDQL87PRsJ3KYeJkHcFGB7hj3R4zZbOOCVVSPbW9/wfrrWFVGCypaZhKqkDF
MxRldAD5kd6vA0jFQFTcD4SQaCDFkpbcLuUCRarAX1T4bepJz11sS6/vmsJWXMY1
VkJqMF/Cq/biPT+zyRGPMUzXn0kCAwEAAaNCMEAwHQYDVR0OBBYEFDVK9U2vP9eC
OKyrcWUXdYydVZPmMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0G
CSqGSIb3DQEBBQUAA4IBAQCoh+ns+EBnXcPBZsdAS5f8hxOQWsTvoMpfi7ent/HW
tWS3irO4G8za+6xmiEHO6Pzk2x6Ipu0nUBsCMCRGef4Eh3CXQHPRwMFXGZpppSeZ
q51ihPZRwSzJIxXYKLerJRO1RuGGAv8mjMSIkh1W/hln8lXkgKNrnKt34VFxDSDb
EJrbvXZ5B3eZKK2aXtqxT0QsNY6llsf9g/BYxnnWmHyojf6GPgcWkuF75x3sM3Z+
Qi5KhfmRiWiEA4Glm5q+4zfFVKtWOxgtQaQM+ELbmaDgcm+7XeEWT1MKZPlO9L9O
VL14bIjqv5wTJMJwaaJ/D8g8rQjJsJhAoyrniIPtd490
-----END CERTIFICATE-----
# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed
# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed
# Label: "OISTE WISeKey Global Root GA CA"
@@ -2673,45 +2510,6 @@ xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX
KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1
-----END CERTIFICATE-----
# Issuer: CN=CA Disig Root R1 O=Disig a.s.
# Subject: CN=CA Disig Root R1 O=Disig a.s.
# Label: "CA Disig Root R1"
# Serial: 14052245610670616104
# MD5 Fingerprint: be:ec:11:93:9a:f5:69:21:bc:d7:c1:c0:67:89:cc:2a
# SHA1 Fingerprint: 8e:1c:74:f8:a6:20:b9:e5:8a:f4:61:fa:ec:2b:47:56:51:1a:52:c6
# SHA256 Fingerprint: f9:6f:23:f4:c3:e7:9c:07:7a:46:98:8d:5a:f5:90:06:76:a0:f0:39:cb:64:5d:d1:75:49:b2:16:c8:24:40:ce
-----BEGIN CERTIFICATE-----
MIIFaTCCA1GgAwIBAgIJAMMDmu5QkG4oMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV
BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu
MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIxMB4XDTEyMDcxOTA5MDY1NloXDTQy
MDcxOTA5MDY1NlowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx
EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjEw
ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCqw3j33Jijp1pedxiy3QRk
D2P9m5YJgNXoqqXinCaUOuiZc4yd39ffg/N4T0Dhf9Kn0uXKE5Pn7cZ3Xza1lK/o
OI7bm+V8u8yN63Vz4STN5qctGS7Y1oprFOsIYgrY3LMATcMjfF9DCCMyEtztDK3A
fQ+lekLZWnDZv6fXARz2m6uOt0qGeKAeVjGu74IKgEH3G8muqzIm1Cxr7X1r5OJe
IgpFy4QxTaz+29FHuvlglzmxZcfe+5nkCiKxLU3lSCZpq+Kq8/v8kiky6bM+TR8n
oc2OuRf7JT7JbvN32g0S9l3HuzYQ1VTW8+DiR0jm3hTaYVKvJrT1cU/J19IG32PK
/yHoWQbgCNWEFVP3Q+V8xaCJmGtzxmjOZd69fwX3se72V6FglcXM6pM6vpmumwKj
rckWtc7dXpl4fho5frLABaTAgqWjR56M6ly2vGfb5ipN0gTco65F97yLnByn1tUD
3AjLLhbKXEAz6GfDLuemROoRRRw1ZS0eRWEkG4IupZ0zXWX4Qfkuy5Q/H6MMMSRE
7cderVC6xkGbrPAXZcD4XW9boAo0PO7X6oifmPmvTiT6l7Jkdtqr9O3jw2Dv1fkC
yC2fg69naQanMVXVz0tv/wQFx1isXxYb5dKj6zHbHzMVTdDypVP1y+E9Tmgt2BLd
qvLmTZtJ5cUoobqwWsagtQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud
DwEB/wQEAwIBBjAdBgNVHQ4EFgQUiQq0OJMa5qvum5EY+fU8PjXQ04IwDQYJKoZI
hvcNAQEFBQADggIBADKL9p1Kyb4U5YysOMo6CdQbzoaz3evUuii+Eq5FLAR0rBNR
xVgYZk2C2tXck8An4b58n1KeElb21Zyp9HWc+jcSjxyT7Ff+Bw+r1RL3D65hXlaA
SfX8MPWbTx9BLxyE04nH4toCdu0Jz2zBuByDHBb6lM19oMgY0sidbvW9adRtPTXo
HqJPYNcHKfyyo6SdbhWSVhlMCrDpfNIZTUJG7L399ldb3Zh+pE3McgODWF3vkzpB
emOqfDqo9ayk0d2iLbYq/J8BjuIQscTK5GfbVSUZP/3oNn6z4eGBrxEWi1CXYBmC
AMBrTXO40RMHPuq2MU/wQppt4hF05ZSsjYSVPCGvxdpHyN85YmLLW1AL14FABZyb
7bq2ix4Eb5YgOe2kfSnbSM6C3NQCjR0EMVrHS/BsYVLXtFHCgWzN4funodKSds+x
DzdYpPJScWc/DIh4gInByLUfkmO+p3qKViwaqKactV2zY9ATIKHrkWzQjX2v3wvk
F7mGnjixlAxYjOBVqjtjbZqJYLhkKpLGN/R+Q0O3c+gB53+XD9fyexn9GtePyfqF
a3qdnom2piiZk4hA9z7NUaPK6u95RyG1/jLix8NRb76AdPCkwzryT+lf3xkK8jsT
Q6wxpLPn6/wY1gGp8yqPNg7rtLG8t0zJa7+h89n07eLw4+1knj0vllJPgFOL
-----END CERTIFICATE-----
# Issuer: CN=CA Disig Root R2 O=Disig a.s.
# Subject: CN=CA Disig Root R2 O=Disig a.s.
# Label: "CA Disig Root R2"
@@ -3655,39 +3453,6 @@ AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ
5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su
-----END CERTIFICATE-----
# Issuer: CN=T\xdcRKTRUST Elektronik Sertifika Hizmet Sa\u011flay\u0131c\u0131s\u0131 H5 O=T\xdcRKTRUST Bilgi \u0130leti\u015fim ve Bili\u015fim G\xfcvenli\u011fi Hizmetleri A.\u015e.
# Subject: CN=T\xdcRKTRUST Elektronik Sertifika Hizmet Sa\u011flay\u0131c\u0131s\u0131 H5 O=T\xdcRKTRUST Bilgi \u0130leti\u015fim ve Bili\u015fim G\xfcvenli\u011fi Hizmetleri A.\u015e.
# Label: "T\xdcRKTRUST Elektronik Sertifika Hizmet Sa\u011flay\u0131c\u0131s\u0131 H5"
# Serial: 156233699172481
# MD5 Fingerprint: da:70:8e:f0:22:df:93:26:f6:5f:9f:d3:15:06:52:4e
# SHA1 Fingerprint: c4:18:f6:4d:46:d1:df:00:3d:27:30:13:72:43:a9:12:11:c6:75:fb
# SHA256 Fingerprint: 49:35:1b:90:34:44:c1:85:cc:dc:5c:69:3d:24:d8:55:5c:b2:08:d6:a8:14:13:07:69:9f:4a:f0:63:19:9d:78
-----BEGIN CERTIFICATE-----
MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UE
BhMCVFIxDzANBgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxn
aSDEsGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkg
QS7Fni4xQjBABgNVBAMMOVTDnFJLVFJVU1QgRWxla3Ryb25payBTZXJ0aWZpa2Eg
SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBINTAeFw0xMzA0MzAwODA3MDFaFw0yMzA0
MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYD
VQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8
dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBF
bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIB
IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApCUZ4WWe60ghUEoI5RHwWrom
/4NZzkQqL/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537jVJp45wnEFPzpALFp/kR
Gml1bsMdi9GYjZOHp3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1mep5Fimh3
4khon6La8eHBEJ/rPCmBp+EyCNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z
5UNP9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0
hO8EuPbJbKoCPrZV4jI3X/xml1/N1p7HIL9Nxqw/dV8c7TKcfGkAaZHjIxhT6QID
AQABo0IwQDAdBgNVHQ4EFgQUVpkHHtOsDGlktAxQR95DLL4gwPswDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJ5FdnsX
SDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPoBP5yCccLqh0l
VX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwq
URawXs3qZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nf
peYVhDfwwvJllpKQd/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CF
Yv4HAqGEVka+lgqaE9chTLd8B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW
+qtB4Uu2NQvAmxU=
-----END CERTIFICATE-----
# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903
# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903
# Label: "Certinomis - Root CA"
@@ -3897,169 +3662,6 @@ lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof
TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR
-----END CERTIFICATE-----
# Issuer: CN=Certplus Root CA G1 O=Certplus
# Subject: CN=Certplus Root CA G1 O=Certplus
# Label: "Certplus Root CA G1"
# Serial: 1491911565779898356709731176965615564637713
# MD5 Fingerprint: 7f:09:9c:f7:d9:b9:5c:69:69:56:d5:37:3e:14:0d:42
# SHA1 Fingerprint: 22:fd:d0:b7:fd:a2:4e:0d:ac:49:2c:a0:ac:a6:7b:6a:1f:e3:f7:66
# SHA256 Fingerprint: 15:2a:40:2b:fc:df:2c:d5:48:05:4d:22:75:b3:9c:7f:ca:3e:c0:97:80:78:b0:f0:ea:76:e5:61:a6:c7:43:3e
-----BEGIN CERTIFICATE-----
MIIFazCCA1OgAwIBAgISESBVg+QtPlRWhS2DN7cs3EYRMA0GCSqGSIb3DQEBDQUA
MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy
dHBsdXMgUm9vdCBDQSBHMTAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBa
MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy
dHBsdXMgUm9vdCBDQSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
ANpQh7bauKk+nWT6VjOaVj0W5QOVsjQcmm1iBdTYj+eJZJ+622SLZOZ5KmHNr49a
iZFluVj8tANfkT8tEBXgfs+8/H9DZ6itXjYj2JizTfNDnjl8KvzsiNWI7nC9hRYt
6kuJPKNxQv4c/dMcLRC4hlTqQ7jbxofaqK6AJc96Jh2qkbBIb6613p7Y1/oA/caP
0FG7Yn2ksYyy/yARujVjBYZHYEMzkPZHogNPlk2dT8Hq6pyi/jQu3rfKG3akt62f
6ajUeD94/vI4CTYd0hYCyOwqaK/1jpTvLRN6HkJKHRUxrgwEV/xhc/MxVoYxgKDE
EW4wduOU8F8ExKyHcomYxZ3MVwia9Az8fXoFOvpHgDm2z4QTd28n6v+WZxcIbekN
1iNQMLAVdBM+5S//Ds3EC0pd8NgAM0lm66EYfFkuPSi5YXHLtaW6uOrc4nBvCGrc
h2c0798wct3zyT8j/zXhviEpIDCB5BmlIOklynMxdCm+4kLV87ImZsdo/Rmz5yCT
mehd4F6H50boJZwKKSTUzViGUkAksnsPmBIgJPaQbEfIDbsYIC7Z/fyL8inqh3SV
4EJQeIQEQWGw9CEjjy3LKCHyamz0GqbFFLQ3ZU+V/YDI+HLlJWvEYLF7bY5KinPO
WftwenMGE9nTdDckQQoRb5fc5+R+ob0V8rqHDz1oihYHAgMBAAGjYzBhMA4GA1Ud
DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSowcCbkahDFXxd
Bie0KlHYlwuBsTAfBgNVHSMEGDAWgBSowcCbkahDFXxdBie0KlHYlwuBsTANBgkq
hkiG9w0BAQ0FAAOCAgEAnFZvAX7RvUz1isbwJh/k4DgYzDLDKTudQSk0YcbX8ACh
66Ryj5QXvBMsdbRX7gp8CXrc1cqh0DQT+Hern+X+2B50ioUHj3/MeXrKls3N/U/7
/SMNkPX0XtPGYX2eEeAC7gkE2Qfdpoq3DIMku4NQkv5gdRE+2J2winq14J2by5BS
S7CTKtQ+FjPlnsZlFT5kOwQ/2wyPX1wdaR+v8+khjPPvl/aatxm2hHSco1S1cE5j
2FddUyGbQJJD+tZ3VTNPZNX70Cxqjm0lpu+F6ALEUz65noe8zDUa3qHpimOHZR4R
Kttjd5cUvpoUmRGywO6wT/gUITJDT5+rosuoD6o7BlXGEilXCNQ314cnrUlZp5Gr
RHpejXDbl85IULFzk/bwg2D5zfHhMf1bfHEhYxQUqq/F3pN+aLHsIqKqkHWetUNy
6mSjhEv9DKgma3GX7lZjZuhCVPnHHd/Qj1vfyDBviP4NxDMcU6ij/UgQ8uQKTuEV
V/xuZDDCVRHc6qnNSlSsKWNEz0pAoNZoWRsz+e86i9sgktxChL8Bq4fA1SCC28a5
g4VCXA9DO2pJNdWY9BW/+mGBDAkgGNLQFwzLSABQ6XaCjGTXOqAHVcweMcDvOrRl
++O/QmueD6i9a5jc2NvLi6Td11n0bt3+qsOR0C5CB8AMTVPNJLFMWx5R9N/pkvo=
-----END CERTIFICATE-----
# Issuer: CN=Certplus Root CA G2 O=Certplus
# Subject: CN=Certplus Root CA G2 O=Certplus
# Label: "Certplus Root CA G2"
# Serial: 1492087096131536844209563509228951875861589
# MD5 Fingerprint: a7:ee:c4:78:2d:1b:ee:2d:b9:29:ce:d6:a7:96:32:31
# SHA1 Fingerprint: 4f:65:8e:1f:e9:06:d8:28:02:e9:54:47:41:c9:54:25:5d:69:cc:1a
# SHA256 Fingerprint: 6c:c0:50:41:e6:44:5e:74:69:6c:4c:fb:c9:f8:0f:54:3b:7e:ab:bb:44:b4:ce:6f:78:7c:6a:99:71:c4:2f:17
-----BEGIN CERTIFICATE-----
MIICHDCCAaKgAwIBAgISESDZkc6uo+jF5//pAq/Pc7xVMAoGCCqGSM49BAMDMD4x
CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs
dXMgUm9vdCBDQSBHMjAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBaMD4x
CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs
dXMgUm9vdCBDQSBHMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABM0PW1aC3/BFGtat
93nwHcmsltaeTpwftEIRyoa/bfuFo8XlGVzX7qY/aWfYeOKmycTbLXku54uNAm8x
Ik0G42ByRZ0OQneezs/lf4WbGOT8zC5y0xaTTsqZY1yhBSpsBqNjMGEwDgYDVR0P
AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNqDYwJ5jtpMxjwj
FNiPwyCrKGBZMB8GA1UdIwQYMBaAFNqDYwJ5jtpMxjwjFNiPwyCrKGBZMAoGCCqG
SM49BAMDA2gAMGUCMHD+sAvZ94OX7PNVHdTcswYO/jOYnYs5kGuUIe22113WTNch
p+e/IQ8rzfcq3IUHnQIxAIYUFuXcsGXCwI4Un78kFmjlvPl5adytRSv3tjFzzAal
U5ORGpOucGpnutee5WEaXw==
-----END CERTIFICATE-----
# Issuer: CN=OpenTrust Root CA G1 O=OpenTrust
# Subject: CN=OpenTrust Root CA G1 O=OpenTrust
# Label: "OpenTrust Root CA G1"
# Serial: 1492036577811947013770400127034825178844775
# MD5 Fingerprint: 76:00:cc:81:29:cd:55:5e:88:6a:7a:2e:f7:4d:39:da
# SHA1 Fingerprint: 79:91:e8:34:f7:e2:ee:dd:08:95:01:52:e9:55:2d:14:e9:58:d5:7e
# SHA256 Fingerprint: 56:c7:71:28:d9:8c:18:d9:1b:4c:fd:ff:bc:25:ee:91:03:d4:75:8e:a2:ab:ad:82:6a:90:f3:45:7d:46:0e:b4
-----BEGIN CERTIFICATE-----
MIIFbzCCA1egAwIBAgISESCzkFU5fX82bWTCp59rY45nMA0GCSqGSIb3DQEBCwUA
MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w
ZW5UcnVzdCBSb290IENBIEcxMB4XDTE0MDUyNjA4NDU1MFoXDTM4MDExNTAwMDAw
MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU
T3BlblRydXN0IFJvb3QgQ0EgRzEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQD4eUbalsUwXopxAy1wpLuwxQjczeY1wICkES3d5oeuXT2R0odsN7faYp6b
wiTXj/HbpqbfRm9RpnHLPhsxZ2L3EVs0J9V5ToybWL0iEA1cJwzdMOWo010hOHQX
/uMftk87ay3bfWAfjH1MBcLrARYVmBSO0ZB3Ij/swjm4eTrwSSTilZHcYTSSjFR0
77F9jAHiOH3BX2pfJLKOYheteSCtqx234LSWSE9mQxAGFiQD4eCcjsZGT44ameGP
uY4zbGneWK2gDqdkVBFpRGZPTBKnjix9xNRbxQA0MMHZmf4yzgeEtE7NCv82TWLx
p2NX5Ntqp66/K7nJ5rInieV+mhxNaMbBGN4zK1FGSxyO9z0M+Yo0FMT7MzUj8czx
Kselu7Cizv5Ta01BG2Yospb6p64KTrk5M0ScdMGTHPjgniQlQ/GbI4Kq3ywgsNw2
TgOzfALU5nsaqocTvz6hdLubDuHAk5/XpGbKuxs74zD0M1mKB3IDVedzagMxbm+W
G+Oin6+Sx+31QrclTDsTBM8clq8cIqPQqwWyTBIjUtz9GVsnnB47ev1CI9sjgBPw
vFEVVJSmdz7QdFG9URQIOTfLHzSpMJ1ShC5VkLG631UAC9hWLbFJSXKAqWLXwPYY
EQRVzXR7z2FwefR7LFxckvzluFqrTJOVoSfupb7PcSNCupt2LQIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUl0YhVyE1
2jZVx/PxN3DlCPaTKbYwHwYDVR0jBBgwFoAUl0YhVyE12jZVx/PxN3DlCPaTKbYw
DQYJKoZIhvcNAQELBQADggIBAB3dAmB84DWn5ph76kTOZ0BP8pNuZtQ5iSas000E
PLuHIT839HEl2ku6q5aCgZG27dmxpGWX4m9kWaSW7mDKHyP7Rbr/jyTwyqkxf3kf
gLMtMrpkZ2CvuVnN35pJ06iCsfmYlIrM4LvgBBuZYLFGZdwIorJGnkSI6pN+VxbS
FXJfLkur1J1juONI5f6ELlgKn0Md/rcYkoZDSw6cMoYsYPXpSOqV7XAp8dUv/TW0
V8/bhUiZucJvbI/NeJWsZCj9VrDDb8O+WVLhX4SPgPL0DTatdrOjteFkdjpY3H1P
XlZs5VVZV6Xf8YpmMIzUUmI4d7S+KNfKNsSbBfD4Fdvb8e80nR14SohWZ25g/4/I
i+GOvUKpMwpZQhISKvqxnUOOBZuZ2mKtVzazHbYNeS2WuOvyDEsMpZTGMKcmGS3t
TAZQMPH9WD25SxdfGbRqhFS0OE85og2WaMMolP3tLR9Ka0OWLpABEPs4poEL0L91
09S5zvE/bw4cHjdx5RiHdRk/ULlepEU0rbDK5uUTdg8xFKmOLZTW1YVNcxVPS/Ky
Pu1svf0OnWZzsD2097+o4BGkxK51CUpjAEggpsadCwmKtODmzj7HPiY46SvepghJ
AwSQiumPv+i2tCqjI40cHLI5kqiPAlxAOXXUc0ECd97N4EOH1uS6SsNsEn/+KuYj
1oxx
-----END CERTIFICATE-----
# Issuer: CN=OpenTrust Root CA G2 O=OpenTrust
# Subject: CN=OpenTrust Root CA G2 O=OpenTrust
# Label: "OpenTrust Root CA G2"
# Serial: 1492012448042702096986875987676935573415441
# MD5 Fingerprint: 57:24:b6:59:24:6b:ae:c8:fe:1c:0c:20:f2:c0:4e:eb
# SHA1 Fingerprint: 79:5f:88:60:c5:ab:7c:3d:92:e6:cb:f4:8d:e1:45:cd:11:ef:60:0b
# SHA256 Fingerprint: 27:99:58:29:fe:6a:75:15:c1:bf:e8:48:f9:c4:76:1d:b1:6c:22:59:29:25:7b:f4:0d:08:94:f2:9e:a8:ba:f2
-----BEGIN CERTIFICATE-----
MIIFbzCCA1egAwIBAgISESChaRu/vbm9UpaPI+hIvyYRMA0GCSqGSIb3DQEBDQUA
MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w
ZW5UcnVzdCBSb290IENBIEcyMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAw
MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU
T3BlblRydXN0IFJvb3QgQ0EgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQDMtlelM5QQgTJT32F+D3Y5z1zCU3UdSXqWON2ic2rxb95eolq5cSG+Ntmh
/LzubKh8NBpxGuga2F8ORAbtp+Dz0mEL4DKiltE48MLaARf85KxP6O6JHnSrT78e
CbY2albz4e6WiWYkBuTNQjpK3eCasMSCRbP+yatcfD7J6xcvDH1urqWPyKwlCm/6
1UWY0jUJ9gNDlP7ZvyCVeYCYitmJNbtRG6Q3ffyZO6v/v6wNj0OxmXsWEH4db0fE
FY8ElggGQgT4hNYdvJGmQr5J1WqIP7wtUdGejeBSzFfdNTVY27SPJIjki9/ca1TS
gSuyzpJLHB9G+h3Ykst2Z7UJmQnlrBcUVXDGPKBWCgOz3GIZ38i1MH/1PCZ1Eb3X
G7OHngevZXHloM8apwkQHZOJZlvoPGIytbU6bumFAYueQ4xncyhZW+vj3CzMpSZy
YhK05pyDRPZRpOLAeiRXyg6lPzq1O4vldu5w5pLeFlwoW5cZJ5L+epJUzpM5ChaH
vGOz9bGTXOBut9Dq+WIyiET7vycotjCVXRIouZW+j1MY5aIYFuJWpLIsEPUdN6b4
t/bQWVyJ98LVtZR00dX+G7bw5tYee9I8y6jj9RjzIR9u701oBnstXW5DiabA+aC/
gh7PU3+06yzbXfZqfUAkBXKJOAGTy3HCOV0GEfZvePg3DTmEJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUajn6QiL3
5okATV59M4PLuG53hq8wHwYDVR0jBBgwFoAUajn6QiL35okATV59M4PLuG53hq8w
DQYJKoZIhvcNAQENBQADggIBAJjLq0A85TMCl38th6aP1F5Kr7ge57tx+4BkJamz
Gj5oXScmp7oq4fBXgwpkTx4idBvpkF/wrM//T2h6OKQQbA2xx6R3gBi2oihEdqc0
nXGEL8pZ0keImUEiyTCYYW49qKgFbdEfwFFEVn8nNQLdXpgKQuswv42hm1GqO+qT
RmTFAHneIWv2V6CG1wZy7HBGS4tz3aAhdT7cHcCP009zHIXZ/n9iyJVvttN7jLpT
wm+bREx50B1ws9efAvSyB7DH5fitIw6mVskpEndI2S9G/Tvw/HRwkqWOOAgfZDC2
t0v7NqwQjqBSM2OdAzVWxWm9xiNaJ5T2pBL4LTM8oValX9YZ6e18CL13zSdkzJTa
TkZQh+D5wVOAHrut+0dSixv9ovneDiK3PTNZbNTe9ZUGMg1RGUFcPk8G97krgCf2
o6p6fAbhQ8MTOWIaNr3gKC6UAuQpLmBVrkA9sHSSXvAgZJY/X0VdiLWK2gKgW0VU
3jg9CcCoSmVGFvyqv1ROTVu+OEO3KMqLM6oaJbolXCkvW0pujOotnCr2BXbgd5eA
iN1nE28daCSLT7d0geX0YJ96Vdc+N9oWaz53rK4YcJUIeSkDiv7BO7M/Gg+kO14f
WKGVyasvc0rQLW6aWQ9VGHgtPFGml4vmu7JwqkwR3v98KzfUetF3NI/n+UL3PIEM
S1IK
-----END CERTIFICATE-----
# Issuer: CN=OpenTrust Root CA G3 O=OpenTrust
# Subject: CN=OpenTrust Root CA G3 O=OpenTrust
# Label: "OpenTrust Root CA G3"
# Serial: 1492104908271485653071219941864171170455615
# MD5 Fingerprint: 21:37:b4:17:16:92:7b:67:46:70:a9:96:d7:a8:13:24
# SHA1 Fingerprint: 6e:26:64:f3:56:bf:34:55:bf:d1:93:3f:7c:01:de:d8:13:da:8a:a6
# SHA256 Fingerprint: b7:c3:62:31:70:6e:81:07:8c:36:7c:b8:96:19:8f:1e:32:08:dd:92:69:49:dd:8f:57:09:a4:10:f7:5b:62:92
-----BEGIN CERTIFICATE-----
MIICITCCAaagAwIBAgISESDm+Ez8JLC+BUCs2oMbNGA/MAoGCCqGSM49BAMDMEAx
CzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5U
cnVzdCBSb290IENBIEczMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAwMFow
QDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwUT3Bl
blRydXN0IFJvb3QgQ0EgRzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARK7liuTcpm
3gY6oxH84Bjwbhy6LTAMidnW7ptzg6kjFYwvWYpa3RTqnVkrQ7cG7DK2uu5Bta1d
oYXM6h0UZqNnfkbilPPntlahFVmhTzeXuSIevRHr9LIfXsMUmuXZl5mjYzBhMA4G
A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRHd8MUi2I5
DMlv4VBN0BBY3JWIbTAfBgNVHSMEGDAWgBRHd8MUi2I5DMlv4VBN0BBY3JWIbTAK
BggqhkjOPQQDAwNpADBmAjEAj6jcnboMBBf6Fek9LykBl7+BFjNAk2z8+e2AcG+q
j9uEwov1NcoG3GRvaBbhj5G5AjEA2Euly8LQCGzpGPta3U1fJAuwACEl74+nBCZx
4nxp5V2a+EEfOzmTk51V6s2N8fvB
-----END CERTIFICATE-----
# Issuer: CN=ISRG Root X1 O=Internet Security Research Group
# Subject: CN=ISRG Root X1 O=Internet Security Research Group
# Label: "ISRG Root X1"
@@ -4603,3 +4205,454 @@ MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX
ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg
h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg==
-----END CERTIFICATE-----
# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6
# Label: "GlobalSign Root CA - R6"
# Serial: 1417766617973444989252670301619537
# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae
# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1
# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69
-----BEGIN CERTIFICATE-----
MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg
MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh
bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx
MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET
MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ
KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI
xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k
ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD
aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw
LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw
1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX
k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2
SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h
bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n
WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY
rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce
MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD
AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu
bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN
nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt
Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61
55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj
vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf
cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz
oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp
nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs
pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v
JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R
8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4
5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA=
-----END CERTIFICATE-----
# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed
# Label: "OISTE WISeKey Global Root GC CA"
# Serial: 44084345621038548146064804565436152554
# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23
# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31
# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d
-----BEGIN CERTIFICATE-----
MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw
CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91
bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg
Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ
BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu
ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS
b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni
eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W
p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T
rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV
57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg
Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R1 O=Google Trust Services LLC
# Subject: CN=GTS Root R1 O=Google Trust Services LLC
# Label: "GTS Root R1"
# Serial: 146587175971765017618439757810265552097
# MD5 Fingerprint: 82:1a:ef:d4:d2:4a:f2:9f:e2:3d:97:06:14:70:72:85
# SHA1 Fingerprint: e1:c9:50:e6:ef:22:f8:4c:56:45:72:8b:92:20:60:d7:d5:a7:a3:e8
# SHA256 Fingerprint: 2a:57:54:71:e3:13:40:bc:21:58:1c:bd:2c:f1:3e:15:84:63:20:3e:ce:94:bc:f9:d3:cc:19:6b:f0:9a:54:72
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM
f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX
mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7
zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P
fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc
vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4
Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp
zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO
Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW
k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+
DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF
lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW
Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1
d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z
XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR
gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3
d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv
J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg
DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM
+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy
F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9
SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws
E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R2 O=Google Trust Services LLC
# Subject: CN=GTS Root R2 O=Google Trust Services LLC
# Label: "GTS Root R2"
# Serial: 146587176055767053814479386953112547951
# MD5 Fingerprint: 44:ed:9a:0e:a4:09:3b:00:f2:ae:4c:a3:c6:61:b0:8b
# SHA1 Fingerprint: d2:73:96:2a:2a:5e:39:9f:73:3f:e1:c7:1e:64:3f:03:38:34:fc:4d
# SHA256 Fingerprint: c4:5d:7b:b0:8e:6d:67:e6:2e:42:35:11:0b:56:4e:5f:78:fd:92:ef:05:8c:84:0a:ea:4e:64:55:d7:58:5c:60
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH
MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy
MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl
cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv
CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg
GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu
XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd
re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu
PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1
mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K
8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj
x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR
nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0
kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok
twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp
8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT
vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT
z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA
pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb
pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB
R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R
RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk
0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC
5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF
izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn
yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R3 O=Google Trust Services LLC
# Subject: CN=GTS Root R3 O=Google Trust Services LLC
# Label: "GTS Root R3"
# Serial: 146587176140553309517047991083707763997
# MD5 Fingerprint: 1a:79:5b:6b:04:52:9c:5d:c7:74:33:1b:25:9a:f9:25
# SHA1 Fingerprint: 30:d4:24:6f:07:ff:db:91:89:8a:0b:e9:49:66:11:eb:8c:5e:46:e5
# SHA256 Fingerprint: 15:d5:b8:77:46:19:ea:7d:54:ce:1c:a6:d0:b0:c4:03:e0:37:a9:17:f1:31:e8:a0:4e:1e:6b:7a:71:ba:bc:e5
-----BEGIN CERTIFICATE-----
MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout
736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A
DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk
fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA
njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd
-----END CERTIFICATE-----
# Issuer: CN=GTS Root R4 O=Google Trust Services LLC
# Subject: CN=GTS Root R4 O=Google Trust Services LLC
# Label: "GTS Root R4"
# Serial: 146587176229350439916519468929765261721
# MD5 Fingerprint: 5d:b6:6a:c4:60:17:24:6a:1a:99:a8:4b:ee:5e:b4:26
# SHA1 Fingerprint: 2a:1d:60:27:d9:4a:b1:0a:1c:4d:91:5c:cd:33:a0:cb:3e:2d:54:cb
# SHA256 Fingerprint: 71:cc:a5:39:1f:9e:79:4b:04:80:25:30:b3:63:e1:21:da:8a:30:43:bb:26:66:2f:ea:4d:ca:7f:c9:51:a4:bd
-----BEGIN CERTIFICATE-----
MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw
CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw
MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp
Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA
IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu
hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l
xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0
CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx
sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w==
-----END CERTIFICATE-----
# Issuer: CN=UCA Global G2 Root O=UniTrust
# Subject: CN=UCA Global G2 Root O=UniTrust
# Label: "UCA Global G2 Root"
# Serial: 124779693093741543919145257850076631279
# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8
# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a
# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c
-----BEGIN CERTIFICATE-----
MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9
MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH
bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x
CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds
b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr
b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9
kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm
VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R
VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc
C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj
tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY
D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv
j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl
NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6
iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP
O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/
BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV
ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj
L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5
1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl
1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU
b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV
PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj
y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb
EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg
DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI
+Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy
YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX
UB+K+wb1whnw0A==
-----END CERTIFICATE-----
# Issuer: CN=UCA Extended Validation Root O=UniTrust
# Subject: CN=UCA Extended Validation Root O=UniTrust
# Label: "UCA Extended Validation Root"
# Serial: 106100277556486529736699587978573607008
# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2
# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a
# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24
-----BEGIN CERTIFICATE-----
MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH
MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF
eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx
MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV
BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB
AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog
D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS
sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop
O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk
sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi
c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj
VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz
KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/
TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G
sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs
1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD
fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T
AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN
l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR
ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ
VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5
c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp
4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s
t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj
2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO
vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C
xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx
cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM
fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax
-----END CERTIFICATE-----
# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036
# Label: "Certigna Root CA"
# Serial: 269714418870597844693661054334862075617
# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77
# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43
# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68
-----BEGIN CERTIFICATE-----
MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw
WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw
MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x
MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD
VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX
BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO
ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M
CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu
I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm
TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh
C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf
ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz
IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT
Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k
JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5
hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB
GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE
FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of
1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov
L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo
dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr
aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq
hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L
6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG
HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6
0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB
lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi
o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1
gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v
faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63
Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh
jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw
3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0=
-----END CERTIFICATE-----
# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI
# Label: "emSign Root CA - G1"
# Serial: 235931866688319308814040
# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac
# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c
# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67
-----BEGIN CERTIFICATE-----
MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD
VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU
ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH
MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO
MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv
Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz
f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO
8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq
d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM
tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt
Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB
o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD
AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x
PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM
wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d
GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH
6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby
RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx
iN66zB+Afko=
-----END CERTIFICATE-----
# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI
# Label: "emSign ECC Root CA - G3"
# Serial: 287880440101571086945156
# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40
# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1
# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b
-----BEGIN CERTIFICATE-----
MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG
EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo
bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g
RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ
TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s
b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw
djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0
WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS
fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB
zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq
hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB
CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD
+JbNR6iC8hZVdyR+EhCVBCyj
-----END CERTIFICATE-----
# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI
# Label: "emSign Root CA - C1"
# Serial: 825510296613316004955058
# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68
# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01
# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f
-----BEGIN CERTIFICATE-----
MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG
A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg
SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw
MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v
dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ
BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ
HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH
3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH
GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c
xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1
aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq
TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87
/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4
kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG
YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT
+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo
WXzhriKi4gp6D/piq1JM4fHfyr6DDUI=
-----END CERTIFICATE-----
# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI
# Label: "emSign ECC Root CA - C3"
# Serial: 582948710642506000014504
# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5
# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66
# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3
-----BEGIN CERTIFICATE-----
MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG
EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx
IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw
MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln
biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND
IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci
MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti
sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O
BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB
Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c
3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J
0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ==
-----END CERTIFICATE-----
# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post
# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post
# Label: "Hongkong Post Root CA 3"
# Serial: 46170865288971385588281144162979347873371282084
# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0
# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02
# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6
-----BEGIN CERTIFICATE-----
MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL
BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ
SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n
a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5
NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT
CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u
Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO
dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI
VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV
9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY
2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY
vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt
bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb
x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+
l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK
TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj
Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP
BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e
i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw
DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG
7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk
MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr
gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk
GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS
3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm
Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+
l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c
JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP
L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa
LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG
mpv0
-----END CERTIFICATE-----

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@@ -8,30 +7,9 @@ certifi.py
This module returns the installation location of cacert.pem.
"""
import os
import warnings
class DeprecatedBundleWarning(DeprecationWarning):
"""
The weak security bundle is being deprecated. Please bother your service
provider to get them to stop using cross-signed roots.
"""
def where():
f = os.path.dirname(__file__)
return os.path.join(f, 'cacert.pem')
def old_where():
warnings.warn(
"The weak security bundle is being deprecated. It will be removed in "
"2018.",
DeprecatedBundleWarning
)
f = os.path.dirname(__file__)
return os.path.join(f, 'weak.pem')
if __name__ == '__main__':
print(where())

View File

@@ -1,414 +0,0 @@
# Issuer: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
# Subject: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited
# Label: "Entrust.net Secure Server CA"
# Serial: 927650371
# MD5 Fingerprint: df:f2:80:73:cc:f1:e6:61:73:fc:f5:42:e9:c5:7c:ee
# SHA1 Fingerprint: 99:a6:9b:e6:1a:fe:88:6b:4d:2b:82:00:7c:b8:54:fc:31:7e:15:39
# SHA256 Fingerprint: 62:f2:40:27:8c:56:4c:4d:d8:bf:7d:9d:4f:6f:36:6e:a8:94:d2:2f:5f:34:d9:89:a9:83:ac:ec:2f:ff:ed:50
-----BEGIN CERTIFICATE-----
MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC
VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u
ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc
KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u
ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1
MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE
ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j
b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF
bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg
U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA
A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/
I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3
wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC
AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb
oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5
BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p
dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk
MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp
b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu
dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0
MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi
E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa
MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI
hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN
95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd
2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI=
-----END CERTIFICATE-----
# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority
# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority
# Label: "ValiCert Class 2 VA"
# Serial: 1
# MD5 Fingerprint: a9:23:75:9b:ba:49:36:6e:31:c2:db:f2:e7:66:ba:87
# SHA1 Fingerprint: 31:7a:2a:d0:7f:2b:33:5e:f5:a1:c3:4e:4b:57:e8:b7:d8:f1:fc:a6
# SHA256 Fingerprint: 58:d0:17:27:9c:d4:dc:63:ab:dd:b1:96:a6:c9:90:6c:30:c4:e0:87:83:ea:e8:c1:60:99:54:d6:93:55:59:6b
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy
NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY
dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9
WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS
v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v
UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu
IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC
W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd
-----END CERTIFICATE-----
# Issuer: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok
# Subject: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok
# Label: "NetLock Express (Class C) Root"
# Serial: 104
# MD5 Fingerprint: 4f:eb:f1:f0:70:c2:80:63:5d:58:9f:da:12:3c:a9:c4
# SHA1 Fingerprint: e3:92:51:2f:0a:cf:f5:05:df:f6:de:06:7f:75:37:e1:65:ea:57:4b
# SHA256 Fingerprint: 0b:5e:ed:4e:84:64:03:cf:55:e0:65:84:84:40:ed:2a:82:75:8b:f5:b9:aa:1f:25:3d:46:13:cf:a0:80:ff:3f
-----BEGIN CERTIFICATE-----
MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx
ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD
EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X
DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw
DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u
c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr
TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA
OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC
2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW
RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P
AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW
ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0
YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz
b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO
ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB
IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs
b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs
ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s
YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg
a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g
SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0
aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg
YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg
Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY
ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g
pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4
Fp1hBWeAyNDYpQcCNJgEjTME1A==
-----END CERTIFICATE-----
# Issuer: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok
# Subject: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok
# Label: "NetLock Business (Class B) Root"
# Serial: 105
# MD5 Fingerprint: 39:16:aa:b9:6a:41:e1:14:69:df:9e:6c:3b:72:dc:b6
# SHA1 Fingerprint: 87:9f:4b:ee:05:df:98:58:3b:e3:60:d6:33:e7:0d:3f:fe:98:71:af
# SHA256 Fingerprint: 39:df:7b:68:2b:7b:93:8f:84:71:54:81:cc:de:8d:60:d8:f2:2e:c5:98:87:7d:0a:aa:c1:2b:59:18:2b:03:12
-----BEGIN CERTIFICATE-----
MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx
ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0
b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD
EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05
OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G
A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh
Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l
dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK
gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX
iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc
Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E
BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G
SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu
b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh
bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv
Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln
aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0
IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh
c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph
biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo
ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP
UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj
YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo
dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA
bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06
sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa
n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS
NitjrFgBazMpUIaD8QFI
-----END CERTIFICATE-----
# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority
# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority
# Label: "RSA Root Certificate 1"
# Serial: 1
# MD5 Fingerprint: a2:6f:53:b7:ee:40:db:4a:68:e7:fa:18:d9:10:4b:72
# SHA1 Fingerprint: 69:bd:8c:f4:9c:d3:00:fb:59:2e:17:93:ca:55:6a:f3:ec:aa:35:fb
# SHA256 Fingerprint: bc:23:f9:8a:31:3c:b9:2d:e3:bb:fc:3a:5a:9f:44:61:ac:39:49:4c:4a:e1:5a:9e:9d:f1:31:e9:9b:73:01:9a
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy
NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD
cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs
2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY
JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE
Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ
n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A
PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu
-----END CERTIFICATE-----
# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority
# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority
# Label: "ValiCert Class 1 VA"
# Serial: 1
# MD5 Fingerprint: 65:58:ab:15:ad:57:6c:1e:a8:a7:b5:69:ac:bf:ff:eb
# SHA1 Fingerprint: e5:df:74:3c:b6:01:c4:9b:98:43:dc:ab:8c:e8:6a:81:10:9f:e4:8e
# SHA256 Fingerprint: f4:c1:49:55:1a:30:13:a3:5b:c7:bf:fe:17:a7:f3:44:9b:c1:ab:5b:5a:0a:e7:4b:06:c2:3b:90:00:4c:01:04
-----BEGIN CERTIFICATE-----
MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0
IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz
BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y
aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG
9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy
NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y
azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs
YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw
Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl
cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y
LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+
TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y
TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0
LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW
I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw
nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI
-----END CERTIFICATE-----
# Issuer: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc.
# Subject: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc.
# Label: "Equifax Secure eBusiness CA 1"
# Serial: 4
# MD5 Fingerprint: 64:9c:ef:2e:44:fc:c6:8f:52:07:d0:51:73:8f:cb:3d
# SHA1 Fingerprint: da:40:18:8b:91:89:a3:ed:ee:ae:da:97:fe:2f:9d:f5:b7:d1:8a:41
# SHA256 Fingerprint: cf:56:ff:46:a4:a1:86:10:9d:d9:65:84:b5:ee:b5:8a:51:0c:42:75:b0:e5:f9:4f:40:bb:ae:86:5e:19:f6:73
-----BEGIN CERTIFICATE-----
MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT
ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw
MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j
LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ
KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo
RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu
WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw
Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD
AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK
eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM
zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+
WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN
/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ==
-----END CERTIFICATE-----
# Issuer: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc.
# Subject: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc.
# Label: "Equifax Secure Global eBusiness CA"
# Serial: 1
# MD5 Fingerprint: 8f:5d:77:06:27:c4:98:3c:5b:93:78:e7:d7:7d:9b:cc
# SHA1 Fingerprint: 7e:78:4a:10:1c:82:65:cc:2d:e1:f1:6d:47:b4:40:ca:d9:0a:19:45
# SHA256 Fingerprint: 5f:0b:62:ea:b5:e3:53:ea:65:21:65:16:58:fb:b6:53:59:f4:43:28:0a:4a:fb:d1:04:d7:7d:10:f9:f0:4c:07
-----BEGIN CERTIFICATE-----
MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc
MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT
ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw
MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj
dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l
c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC
UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc
58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/
o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH
MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr
aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA
A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA
Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv
8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV
-----END CERTIFICATE-----
# Issuer: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division
# Subject: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division
# Label: "Thawte Premium Server CA"
# Serial: 1
# MD5 Fingerprint: 06:9f:69:79:16:66:90:02:1b:8c:8c:a2:c3:07:6f:3a
# SHA1 Fingerprint: 62:7f:8d:78:27:65:63:99:d2:7d:7f:90:44:c9:fe:b3:f3:3e:fa:9a
# SHA256 Fingerprint: ab:70:36:36:5c:71:54:aa:29:c2:c2:9f:5d:41:91:16:3b:16:2a:22:25:01:13:57:d5:6d:07:ff:a7:bc:1f:72
-----BEGIN CERTIFICATE-----
MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy
dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t
MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB
MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG
A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp
b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl
cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv
bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE
VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ
ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR
uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG
9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI
hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM
pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg==
-----END CERTIFICATE-----
# Issuer: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division
# Subject: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division
# Label: "Thawte Server CA"
# Serial: 1
# MD5 Fingerprint: c5:70:c4:a2:ed:53:78:0c:c8:10:53:81:64:cb:d0:1d
# SHA1 Fingerprint: 23:e5:94:94:51:95:f2:41:48:03:b4:d5:64:d2:a3:a3:f5:d8:8b:8c
# SHA256 Fingerprint: b4:41:0b:73:e2:e6:ea:ca:47:fb:c4:2f:8f:a4:01:8a:f4:38:1d:c5:4c:fa:a8:44:50:46:1e:ed:09:45:4d:e9
-----BEGIN CERTIFICATE-----
MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx
FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD
VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv
biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm
MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx
MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT
DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3
dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl
cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3
DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91
yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX
L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj
EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG
7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e
QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ
qdq5snUb9kLy78fyGPmJvKP/iiMucEc=
-----END CERTIFICATE-----
# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority
# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority
# Label: "Verisign Class 3 Public Primary Certification Authority"
# Serial: 149843929435818692848040365716851702463
# MD5 Fingerprint: 10:fc:63:5d:f6:26:3e:0d:f3:25:be:5f:79:cd:67:67
# SHA1 Fingerprint: 74:2c:31:92:e6:07:e4:24:eb:45:49:54:2b:e1:bb:c5:3e:61:74:e2
# SHA256 Fingerprint: e7:68:56:34:ef:ac:f6:9a:ce:93:9a:6b:25:5b:7b:4f:ab:ef:42:93:5b:50:a2:65:ac:b5:cb:60:27:e4:4e:70
-----BEGIN CERTIFICATE-----
MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
-----END CERTIFICATE-----
# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority
# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority
# Label: "Verisign Class 3 Public Primary Certification Authority"
# Serial: 80507572722862485515306429940691309246
# MD5 Fingerprint: ef:5a:f1:33:ef:f1:cd:bb:51:02:ee:12:14:4b:96:c4
# SHA1 Fingerprint: a1:db:63:93:91:6f:17:e4:18:55:09:40:04:15:c7:02:40:b0:ae:6b
# SHA256 Fingerprint: a4:b6:b3:99:6f:c2:f3:06:b3:fd:86:81:bd:63:41:3d:8c:50:09:cc:4f:a3:29:c2:cc:f0:e2:fa:1b:14:03:05
-----BEGIN CERTIFICATE-----
MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG
A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i
2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ
2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ
-----END CERTIFICATE-----
# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network
# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network
# Label: "Verisign Class 3 Public Primary Certification Authority - G2"
# Serial: 167285380242319648451154478808036881606
# MD5 Fingerprint: a2:33:9b:4c:74:78:73:d4:6c:e7:c1:f3:8d:cb:5c:e9
# SHA1 Fingerprint: 85:37:1c:a6:e5:50:14:3d:ce:28:03:47:1b:de:3a:09:e8:f8:77:0f
# SHA256 Fingerprint: 83:ce:3c:12:29:68:8a:59:3d:48:5f:81:97:3c:0f:91:95:43:1e:da:37:cc:5e:36:43:0e:79:c7:a8:88:63:8b
-----BEGIN CERTIFICATE-----
MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ
BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh
c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy
MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp
emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X
DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw
FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg
UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo
YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5
MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB
AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4
pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0
13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID
AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk
U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i
F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY
oJ2daZH9
-----END CERTIFICATE-----
# Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc.
# Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc.
# Label: "GTE CyberTrust Global Root"
# Serial: 421
# MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db
# SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74
# SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36
-----BEGIN CERTIFICATE-----
MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD
VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv
bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv
b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV
UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU
cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds
b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH
iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS
r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4
04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r
GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9
3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P
lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/
-----END CERTIFICATE-----
# Issuer: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
# Subject: C=US, O=Equifax, OU=Equifax Secure Certificate Authority
# Label: "Equifax Secure Certificate Authority"
# Serial: 903804111
# MD5 Fingerprint: 67:cb:9d:c0:13:24:8a:82:9b:b2:17:1e:d1:1b:ec:d4
# SHA1 Fingerprint: d2:32:09:ad:23:d3:14:23:21:74:e4:0d:7f:9d:62:13:97:86:63:3a
# SHA256 Fingerprint: 08:29:7a:40:47:db:a2:36:80:c7:31:db:6e:31:76:53:ca:78:48:e1:be:bd:3a:0b:01:79:a7:07:f9:2c:f1:78
-----BEGIN CERTIFICATE-----
MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy
dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1
MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx
dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B
AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f
BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A
cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC
AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ
MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm
aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw
ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj
IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF
MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA
A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y
7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh
1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4
-----END CERTIFICATE-----

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +0,0 @@
Copyright (c) 2003-2009 Stuart Bishop <stuart@stuartbishop.net>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@@ -1,575 +0,0 @@
pytz - World Timezone Definitions for Python
============================================
:Author: Stuart Bishop <stuart@stuartbishop.net>
Introduction
~~~~~~~~~~~~
pytz brings the Olson tz database into Python. This library allows
accurate and cross platform timezone calculations using Python 2.4
or higher. It also solves the issue of ambiguous times at the end
of daylight saving time, which you can read more about in the Python
Library Reference (``datetime.tzinfo``).
Almost all of the Olson timezones are supported.
.. note::
This library differs from the documented Python API for
tzinfo implementations; if you want to create local wallclock
times you need to use the ``localize()`` method documented in this
document. In addition, if you perform date arithmetic on local
times that cross DST boundaries, the result may be in an incorrect
timezone (ie. subtract 1 minute from 2002-10-27 1:00 EST and you get
2002-10-27 0:59 EST instead of the correct 2002-10-27 1:59 EDT). A
``normalize()`` method is provided to correct this. Unfortunately these
issues cannot be resolved without modifying the Python datetime
implementation (see PEP-431).
Installation
~~~~~~~~~~~~
This package can either be installed from a .egg file using setuptools,
or from the tarball using the standard Python distutils.
If you are installing from a tarball, run the following command as an
administrative user::
python setup.py install
If you are installing using setuptools, you don't even need to download
anything as the latest version will be downloaded for you
from the Python package index::
easy_install --upgrade pytz
If you already have the .egg file, you can use that too::
easy_install pytz-2008g-py2.6.egg
Example & Usage
~~~~~~~~~~~~~~~
Localized times and date arithmetic
-----------------------------------
>>> from datetime import datetime, timedelta
>>> from pytz import timezone
>>> import pytz
>>> utc = pytz.utc
>>> utc.zone
'UTC'
>>> eastern = timezone('US/Eastern')
>>> eastern.zone
'US/Eastern'
>>> amsterdam = timezone('Europe/Amsterdam')
>>> fmt = '%Y-%m-%d %H:%M:%S %Z%z'
This library only supports two ways of building a localized time. The
first is to use the ``localize()`` method provided by the pytz library.
This is used to localize a naive datetime (datetime with no timezone
information):
>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 6, 0, 0))
>>> print(loc_dt.strftime(fmt))
2002-10-27 06:00:00 EST-0500
The second way of building a localized time is by converting an existing
localized time using the standard ``astimezone()`` method:
>>> ams_dt = loc_dt.astimezone(amsterdam)
>>> ams_dt.strftime(fmt)
'2002-10-27 12:00:00 CET+0100'
Unfortunately using the tzinfo argument of the standard datetime
constructors ''does not work'' with pytz for many timezones.
>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=amsterdam).strftime(fmt)
'2002-10-27 12:00:00 LMT+0020'
It is safe for timezones without daylight saving transitions though, such
as UTC:
>>> datetime(2002, 10, 27, 12, 0, 0, tzinfo=pytz.utc).strftime(fmt)
'2002-10-27 12:00:00 UTC+0000'
The preferred way of dealing with times is to always work in UTC,
converting to localtime only when generating output to be read
by humans.
>>> utc_dt = datetime(2002, 10, 27, 6, 0, 0, tzinfo=utc)
>>> loc_dt = utc_dt.astimezone(eastern)
>>> loc_dt.strftime(fmt)
'2002-10-27 01:00:00 EST-0500'
This library also allows you to do date arithmetic using local
times, although it is more complicated than working in UTC as you
need to use the ``normalize()`` method to handle daylight saving time
and other timezone transitions. In this example, ``loc_dt`` is set
to the instant when daylight saving time ends in the US/Eastern
timezone.
>>> before = loc_dt - timedelta(minutes=10)
>>> before.strftime(fmt)
'2002-10-27 00:50:00 EST-0500'
>>> eastern.normalize(before).strftime(fmt)
'2002-10-27 01:50:00 EDT-0400'
>>> after = eastern.normalize(before + timedelta(minutes=20))
>>> after.strftime(fmt)
'2002-10-27 01:10:00 EST-0500'
Creating local times is also tricky, and the reason why working with
local times is not recommended. Unfortunately, you cannot just pass
a ``tzinfo`` argument when constructing a datetime (see the next
section for more details)
>>> dt = datetime(2002, 10, 27, 1, 30, 0)
>>> dt1 = eastern.localize(dt, is_dst=True)
>>> dt1.strftime(fmt)
'2002-10-27 01:30:00 EDT-0400'
>>> dt2 = eastern.localize(dt, is_dst=False)
>>> dt2.strftime(fmt)
'2002-10-27 01:30:00 EST-0500'
Converting between timezones also needs special attention. We also need
to use the ``normalize()`` method to ensure the conversion is correct.
>>> utc_dt = utc.localize(datetime.utcfromtimestamp(1143408899))
>>> utc_dt.strftime(fmt)
'2006-03-26 21:34:59 UTC+0000'
>>> au_tz = timezone('Australia/Sydney')
>>> au_dt = au_tz.normalize(utc_dt.astimezone(au_tz))
>>> au_dt.strftime(fmt)
'2006-03-27 08:34:59 AEDT+1100'
>>> utc_dt2 = utc.normalize(au_dt.astimezone(utc))
>>> utc_dt2.strftime(fmt)
'2006-03-26 21:34:59 UTC+0000'
You can take shortcuts when dealing with the UTC side of timezone
conversions. ``normalize()`` and ``localize()`` are not really
necessary when there are no daylight saving time transitions to
deal with.
>>> utc_dt = datetime.utcfromtimestamp(1143408899).replace(tzinfo=utc)
>>> utc_dt.strftime(fmt)
'2006-03-26 21:34:59 UTC+0000'
>>> au_tz = timezone('Australia/Sydney')
>>> au_dt = au_tz.normalize(utc_dt.astimezone(au_tz))
>>> au_dt.strftime(fmt)
'2006-03-27 08:34:59 AEDT+1100'
>>> utc_dt2 = au_dt.astimezone(utc)
>>> utc_dt2.strftime(fmt)
'2006-03-26 21:34:59 UTC+0000'
``tzinfo`` API
--------------
The ``tzinfo`` instances returned by the ``timezone()`` function have
been extended to cope with ambiguous times by adding an ``is_dst``
parameter to the ``utcoffset()``, ``dst()`` && ``tzname()`` methods.
>>> tz = timezone('America/St_Johns')
>>> normal = datetime(2009, 9, 1)
>>> ambiguous = datetime(2009, 10, 31, 23, 30)
The ``is_dst`` parameter is ignored for most timestamps. It is only used
during DST transition ambiguous periods to resulve that ambiguity.
>>> tz.utcoffset(normal, is_dst=True)
datetime.timedelta(-1, 77400)
>>> tz.dst(normal, is_dst=True)
datetime.timedelta(0, 3600)
>>> tz.tzname(normal, is_dst=True)
'NDT'
>>> tz.utcoffset(ambiguous, is_dst=True)
datetime.timedelta(-1, 77400)
>>> tz.dst(ambiguous, is_dst=True)
datetime.timedelta(0, 3600)
>>> tz.tzname(ambiguous, is_dst=True)
'NDT'
>>> tz.utcoffset(normal, is_dst=False)
datetime.timedelta(-1, 77400)
>>> tz.dst(normal, is_dst=False)
datetime.timedelta(0, 3600)
>>> tz.tzname(normal, is_dst=False)
'NDT'
>>> tz.utcoffset(ambiguous, is_dst=False)
datetime.timedelta(-1, 73800)
>>> tz.dst(ambiguous, is_dst=False)
datetime.timedelta(0)
>>> tz.tzname(ambiguous, is_dst=False)
'NST'
If ``is_dst`` is not specified, ambiguous timestamps will raise
an ``pytz.exceptions.AmbiguousTimeError`` exception.
>>> tz.utcoffset(normal)
datetime.timedelta(-1, 77400)
>>> tz.dst(normal)
datetime.timedelta(0, 3600)
>>> tz.tzname(normal)
'NDT'
>>> import pytz.exceptions
>>> try:
... tz.utcoffset(ambiguous)
... except pytz.exceptions.AmbiguousTimeError:
... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous)
pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00
>>> try:
... tz.dst(ambiguous)
... except pytz.exceptions.AmbiguousTimeError:
... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous)
pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00
>>> try:
... tz.tzname(ambiguous)
... except pytz.exceptions.AmbiguousTimeError:
... print('pytz.exceptions.AmbiguousTimeError: %s' % ambiguous)
pytz.exceptions.AmbiguousTimeError: 2009-10-31 23:30:00
Problems with Localtime
~~~~~~~~~~~~~~~~~~~~~~~
The major problem we have to deal with is that certain datetimes
may occur twice in a year. For example, in the US/Eastern timezone
on the last Sunday morning in October, the following sequence
happens:
- 01:00 EDT occurs
- 1 hour later, instead of 2:00am the clock is turned back 1 hour
and 01:00 happens again (this time 01:00 EST)
In fact, every instant between 01:00 and 02:00 occurs twice. This means
that if you try and create a time in the 'US/Eastern' timezone
the standard datetime syntax, there is no way to specify if you meant
before of after the end-of-daylight-saving-time transition. Using the
pytz custom syntax, the best you can do is make an educated guess:
>>> loc_dt = eastern.localize(datetime(2002, 10, 27, 1, 30, 00))
>>> loc_dt.strftime(fmt)
'2002-10-27 01:30:00 EST-0500'
As you can see, the system has chosen one for you and there is a 50%
chance of it being out by one hour. For some applications, this does
not matter. However, if you are trying to schedule meetings with people
in different timezones or analyze log files it is not acceptable.
The best and simplest solution is to stick with using UTC. The pytz
package encourages using UTC for internal timezone representation by
including a special UTC implementation based on the standard Python
reference implementation in the Python documentation.
The UTC timezone unpickles to be the same instance, and pickles to a
smaller size than other pytz tzinfo instances. The UTC implementation
can be obtained as pytz.utc, pytz.UTC, or pytz.timezone('UTC').
>>> import pickle, pytz
>>> dt = datetime(2005, 3, 1, 14, 13, 21, tzinfo=utc)
>>> naive = dt.replace(tzinfo=None)
>>> p = pickle.dumps(dt, 1)
>>> naive_p = pickle.dumps(naive, 1)
>>> len(p) - len(naive_p)
17
>>> new = pickle.loads(p)
>>> new == dt
True
>>> new is dt
False
>>> new.tzinfo is dt.tzinfo
True
>>> pytz.utc is pytz.UTC is pytz.timezone('UTC')
True
Note that some other timezones are commonly thought of as the same (GMT,
Greenwich, Universal, etc.). The definition of UTC is distinct from these
other timezones, and they are not equivalent. For this reason, they will
not compare the same in Python.
>>> utc == pytz.timezone('GMT')
False
See the section `What is UTC`_, below.
If you insist on working with local times, this library provides a
facility for constructing them unambiguously:
>>> loc_dt = datetime(2002, 10, 27, 1, 30, 00)
>>> est_dt = eastern.localize(loc_dt, is_dst=True)
>>> edt_dt = eastern.localize(loc_dt, is_dst=False)
>>> print(est_dt.strftime(fmt) + ' / ' + edt_dt.strftime(fmt))
2002-10-27 01:30:00 EDT-0400 / 2002-10-27 01:30:00 EST-0500
If you pass None as the is_dst flag to localize(), pytz will refuse to
guess and raise exceptions if you try to build ambiguous or non-existent
times.
For example, 1:30am on 27th Oct 2002 happened twice in the US/Eastern
timezone when the clocks where put back at the end of Daylight Saving
Time:
>>> dt = datetime(2002, 10, 27, 1, 30, 00)
>>> try:
... eastern.localize(dt, is_dst=None)
... except pytz.exceptions.AmbiguousTimeError:
... print('pytz.exceptions.AmbiguousTimeError: %s' % dt)
pytz.exceptions.AmbiguousTimeError: 2002-10-27 01:30:00
Similarly, 2:30am on 7th April 2002 never happened at all in the
US/Eastern timezone, as the clocks where put forward at 2:00am skipping
the entire hour:
>>> dt = datetime(2002, 4, 7, 2, 30, 00)
>>> try:
... eastern.localize(dt, is_dst=None)
... except pytz.exceptions.NonExistentTimeError:
... print('pytz.exceptions.NonExistentTimeError: %s' % dt)
pytz.exceptions.NonExistentTimeError: 2002-04-07 02:30:00
Both of these exceptions share a common base class to make error handling
easier:
>>> isinstance(pytz.AmbiguousTimeError(), pytz.InvalidTimeError)
True
>>> isinstance(pytz.NonExistentTimeError(), pytz.InvalidTimeError)
True
A special case is where countries change their timezone definitions
with no daylight savings time switch. For example, in 1915 Warsaw
switched from Warsaw time to Central European time with no daylight savings
transition. So at the stroke of midnight on August 5th 1915 the clocks
were wound back 24 minutes creating an ambiguous time period that cannot
be specified without referring to the timezone abbreviation or the
actual UTC offset. In this case midnight happened twice, neither time
during a daylight saving time period. pytz handles this transition by
treating the ambiguous period before the switch as daylight savings
time, and the ambiguous period after as standard time.
>>> warsaw = pytz.timezone('Europe/Warsaw')
>>> amb_dt1 = warsaw.localize(datetime(1915, 8, 4, 23, 59, 59), is_dst=True)
>>> amb_dt1.strftime(fmt)
'1915-08-04 23:59:59 WMT+0124'
>>> amb_dt2 = warsaw.localize(datetime(1915, 8, 4, 23, 59, 59), is_dst=False)
>>> amb_dt2.strftime(fmt)
'1915-08-04 23:59:59 CET+0100'
>>> switch_dt = warsaw.localize(datetime(1915, 8, 5, 00, 00, 00), is_dst=False)
>>> switch_dt.strftime(fmt)
'1915-08-05 00:00:00 CET+0100'
>>> str(switch_dt - amb_dt1)
'0:24:01'
>>> str(switch_dt - amb_dt2)
'0:00:01'
The best way of creating a time during an ambiguous time period is
by converting from another timezone such as UTC:
>>> utc_dt = datetime(1915, 8, 4, 22, 36, tzinfo=pytz.utc)
>>> utc_dt.astimezone(warsaw).strftime(fmt)
'1915-08-04 23:36:00 CET+0100'
The standard Python way of handling all these ambiguities is not to
handle them, such as demonstrated in this example using the US/Eastern
timezone definition from the Python documentation (Note that this
implementation only works for dates between 1987 and 2006 - it is
included for tests only!):
>>> from pytz.reference import Eastern # pytz.reference only for tests
>>> dt = datetime(2002, 10, 27, 0, 30, tzinfo=Eastern)
>>> str(dt)
'2002-10-27 00:30:00-04:00'
>>> str(dt + timedelta(hours=1))
'2002-10-27 01:30:00-05:00'
>>> str(dt + timedelta(hours=2))
'2002-10-27 02:30:00-05:00'
>>> str(dt + timedelta(hours=3))
'2002-10-27 03:30:00-05:00'
Notice the first two results? At first glance you might think they are
correct, but taking the UTC offset into account you find that they are
actually two hours appart instead of the 1 hour we asked for.
>>> from pytz.reference import UTC # pytz.reference only for tests
>>> str(dt.astimezone(UTC))
'2002-10-27 04:30:00+00:00'
>>> str((dt + timedelta(hours=1)).astimezone(UTC))
'2002-10-27 06:30:00+00:00'
Country Information
~~~~~~~~~~~~~~~~~~~
A mechanism is provided to access the timezones commonly in use
for a particular country, looked up using the ISO 3166 country code.
It returns a list of strings that can be used to retrieve the relevant
tzinfo instance using ``pytz.timezone()``:
>>> print(' '.join(pytz.country_timezones['nz']))
Pacific/Auckland Pacific/Chatham
The Olson database comes with a ISO 3166 country code to English country
name mapping that pytz exposes as a dictionary:
>>> print(pytz.country_names['nz'])
New Zealand
What is UTC
~~~~~~~~~~~
'UTC' is `Coordinated Universal Time`_. It is a successor to, but distinct
from, Greenwich Mean Time (GMT) and the various definitions of Universal
Time. UTC is now the worldwide standard for regulating clocks and time
measurement.
All other timezones are defined relative to UTC, and include offsets like
UTC+0800 - hours to add or subtract from UTC to derive the local time. No
daylight saving time occurs in UTC, making it a useful timezone to perform
date arithmetic without worrying about the confusion and ambiguities caused
by daylight saving time transitions, your country changing its timezone, or
mobile computers that roam through multiple timezones.
.. _Coordinated Universal Time: https://en.wikipedia.org/wiki/Coordinated_Universal_Time
Helpers
~~~~~~~
There are two lists of timezones provided.
``all_timezones`` is the exhaustive list of the timezone names that can
be used.
>>> from pytz import all_timezones
>>> len(all_timezones) >= 500
True
>>> 'Etc/Greenwich' in all_timezones
True
``common_timezones`` is a list of useful, current timezones. It doesn't
contain deprecated zones or historical zones, except for a few I've
deemed in common usage, such as US/Eastern (open a bug report if you
think other timezones are deserving of being included here). It is also
a sequence of strings.
>>> from pytz import common_timezones
>>> len(common_timezones) < len(all_timezones)
True
>>> 'Etc/Greenwich' in common_timezones
False
>>> 'Australia/Melbourne' in common_timezones
True
>>> 'US/Eastern' in common_timezones
True
>>> 'Canada/Eastern' in common_timezones
True
>>> 'US/Pacific-New' in all_timezones
True
>>> 'US/Pacific-New' in common_timezones
False
Both ``common_timezones`` and ``all_timezones`` are alphabetically
sorted:
>>> common_timezones_dupe = common_timezones[:]
>>> common_timezones_dupe.sort()
>>> common_timezones == common_timezones_dupe
True
>>> all_timezones_dupe = all_timezones[:]
>>> all_timezones_dupe.sort()
>>> all_timezones == all_timezones_dupe
True
``all_timezones`` and ``common_timezones`` are also available as sets.
>>> from pytz import all_timezones_set, common_timezones_set
>>> 'US/Eastern' in all_timezones_set
True
>>> 'US/Eastern' in common_timezones_set
True
>>> 'Australia/Victoria' in common_timezones_set
False
You can also retrieve lists of timezones used by particular countries
using the ``country_timezones()`` function. It requires an ISO-3166
two letter country code.
>>> from pytz import country_timezones
>>> print(' '.join(country_timezones('ch')))
Europe/Zurich
>>> print(' '.join(country_timezones('CH')))
Europe/Zurich
License
~~~~~~~
MIT license.
This code is also available as part of Zope 3 under the Zope Public
License, Version 2.1 (ZPL).
I'm happy to relicense this code if necessary for inclusion in other
open source projects.
Latest Versions
~~~~~~~~~~~~~~~
This package will be updated after releases of the Olson timezone
database. The latest version can be downloaded from the `Python Package
Index <http://pypi.python.org/pypi/pytz/>`_. The code that is used
to generate this distribution is hosted on launchpad.net and available
using the `Bazaar version control system <http://bazaar-vcs.org>`_
using::
bzr branch lp:pytz
Announcements of new releases are made on
`Launchpad <https://launchpad.net/pytz>`_, and the
`Atom feed <http://feeds.launchpad.net/pytz/announcements.atom>`_
hosted there.
Bugs, Feature Requests & Patches
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Bugs can be reported using `Launchpad <https://bugs.launchpad.net/pytz>`_.
Issues & Limitations
~~~~~~~~~~~~~~~~~~~~
- Offsets from UTC are rounded to the nearest whole minute, so timezones
such as Europe/Amsterdam pre 1937 will be up to 30 seconds out. This
is a limitation of the Python datetime library.
- If you think a timezone definition is incorrect, I probably can't fix
it. pytz is a direct translation of the Olson timezone database, and
changes to the timezone definitions need to be made to this source.
If you find errors they should be reported to the time zone mailing
list, linked from http://www.iana.org/time-zones.
Further Reading
~~~~~~~~~~~~~~~
More info than you want to know about timezones:
http://www.twinsun.com/tz/tz-link.htm
Contact
~~~~~~~
Stuart Bishop <stuart@stuartbishop.net>

View File

@@ -8,9 +8,22 @@ See the datetime section of the Python Library Reference for information
on how to use these modules.
'''
import sys
import datetime
import os.path
from pytz.exceptions import AmbiguousTimeError
from pytz.exceptions import InvalidTimeError
from pytz.exceptions import NonExistentTimeError
from pytz.exceptions import UnknownTimeZoneError
from pytz.lazy import LazyDict, LazyList, LazySet # noqa
from pytz.tzinfo import unpickler, BaseTzInfo
from pytz.tzfile import build_tzinfo
# The IANA (nee Olson) database is updated several times a year.
OLSON_VERSION = '2016f'
VERSION = '2016.6.1' # Switching to pip compatible version numbering.
OLSON_VERSION = '2019a'
VERSION = '2019.1' # pip compatible version number.
__version__ = VERSION
OLSEN_VERSION = OLSON_VERSION # Old releases had this misspelling
@@ -21,23 +34,11 @@ __all__ = [
'NonExistentTimeError', 'UnknownTimeZoneError',
'all_timezones', 'all_timezones_set',
'common_timezones', 'common_timezones_set',
]
import sys, datetime, os.path, gettext
from pytz.exceptions import AmbiguousTimeError
from pytz.exceptions import InvalidTimeError
from pytz.exceptions import NonExistentTimeError
from pytz.exceptions import UnknownTimeZoneError
from pytz.lazy import LazyDict, LazyList, LazySet
from pytz.tzinfo import unpickler
from pytz.tzfile import build_tzinfo, _byte_string
'BaseTzInfo',
]
try:
unicode
except NameError: # Python 3.x
if sys.version_info[0] > 2: # Python 3.x
# Python 3.x doesn't have unicode(), making writing code
# for Python 2.3 and Python 3.x a pain.
@@ -52,8 +53,11 @@ except NameError: # Python 3.x
...
UnicodeEncodeError: ...
"""
if type(s) == bytes:
s = s.decode('ASCII')
else:
s.encode('ASCII') # Raise an exception if not ASCII
return s # But return the original string - not a byte string.
return s # But the string - not a byte string.
else: # Python 2.x
@@ -76,11 +80,18 @@ def open_resource(name):
Uses the pkg_resources module if available and no standard file
found at the calculated location.
It is possible to specify different location for zoneinfo
subdir by using the PYTZ_TZDATADIR environment variable.
"""
name_parts = name.lstrip('/').split('/')
for part in name_parts:
if part == os.path.pardir or os.path.sep in part:
raise ValueError('Bad path segment: %r' % part)
zoneinfo_dir = os.environ.get('PYTZ_TZDATADIR', None)
if zoneinfo_dir is not None:
filename = os.path.join(zoneinfo_dir, *name_parts)
else:
filename = os.path.join(os.path.dirname(__file__),
'zoneinfo', *name_parts)
if not os.path.exists(filename):
@@ -106,23 +117,9 @@ def resource_exists(name):
return False
# Enable this when we get some translations?
# We want an i18n API that is useful to programs using Python's gettext
# 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
# )
# def _(timezone_name):
# """Translate a timezone name using the current locale, returning Unicode"""
# return t.ugettext(timezone_name)
_tzinfo_cache = {}
def timezone(zone):
r''' Return a datetime.tzinfo implementation for the given timezone
@@ -160,6 +157,9 @@ def timezone(zone):
Unknown
'''
if zone is None:
raise UnknownTimeZoneError(None)
if zone.upper() == 'UTC':
return utc
@@ -169,9 +169,9 @@ def timezone(zone):
# All valid timezones are ASCII
raise UnknownTimeZoneError(zone)
zone = _unmunge_zone(zone)
zone = _case_insensitive_zone_lookup(_unmunge_zone(zone))
if zone not in _tzinfo_cache:
if zone in all_timezones_set:
if zone in all_timezones_set: # noqa
fp = open_resource(zone)
try:
_tzinfo_cache[zone] = build_tzinfo(zone, fp)
@@ -188,11 +188,16 @@ def _unmunge_zone(zone):
return zone.replace('_plus_', '+').replace('_minus_', '-')
def _case_insensitive_zone_lookup(zone):
"""case-insensitively matching timezone, else return zone unchanged"""
return _all_timezones_lower_to_standard.get(zone.lower()) or zone # noqa
ZERO = datetime.timedelta(0)
HOUR = datetime.timedelta(hours=1)
class UTC(datetime.tzinfo):
class UTC(BaseTzInfo):
"""UTC
Optimized UTC implementation. It unpickles using the single module global
@@ -275,6 +280,8 @@ def _UTC():
False
"""
return utc
_UTC.__safe_for_unpickling__ = True
@@ -285,9 +292,10 @@ def _p(*args):
by shortening the path.
"""
return unpickler(*args)
_p.__safe_for_unpickling__ = True
_p.__safe_for_unpickling__ = True
class _CountryTimezoneDict(LazyDict):
"""Map ISO 3166 country code to a list of timezone names commonly used
@@ -334,7 +342,7 @@ class _CountryTimezoneDict(LazyDict):
if line.startswith('#'):
continue
code, coordinates, zone = line.split(None, 4)[:3]
if zone not in all_timezones_set:
if zone not in all_timezones_set: # noqa
continue
try:
data[code].append(zone)
@@ -344,6 +352,7 @@ class _CountryTimezoneDict(LazyDict):
finally:
zone_tab.close()
country_timezones = _CountryTimezoneDict()
@@ -367,6 +376,7 @@ class _CountryNameDict(LazyDict):
finally:
zone_tab.close()
country_names = _CountryNameDict()
@@ -412,24 +422,24 @@ class _FixedOffset(datetime.tzinfo):
return dt.astimezone(self)
def FixedOffset(offset, _tzinfos = {}):
def FixedOffset(offset, _tzinfos={}):
"""return a fixed-offset timezone based off a number of minutes.
>>> one = FixedOffset(-330)
>>> one
pytz.FixedOffset(-330)
>>> one.utcoffset(datetime.datetime.now())
datetime.timedelta(-1, 66600)
>>> one.dst(datetime.datetime.now())
datetime.timedelta(0)
>>> str(one.utcoffset(datetime.datetime.now()))
'-1 day, 18:30:00'
>>> str(one.dst(datetime.datetime.now()))
'0:00:00'
>>> two = FixedOffset(1380)
>>> two
pytz.FixedOffset(1380)
>>> two.utcoffset(datetime.datetime.now())
datetime.timedelta(0, 82800)
>>> two.dst(datetime.datetime.now())
datetime.timedelta(0)
>>> str(two.utcoffset(datetime.datetime.now()))
'23:00:00'
>>> str(two.dst(datetime.datetime.now()))
'0:00:00'
The datetime.timedelta must be between the range of -1 and 1 day,
non-inclusive.
@@ -478,18 +488,19 @@ def FixedOffset(offset, _tzinfos = {}):
return info
FixedOffset.__safe_for_unpickling__ = True
def _test():
import doctest, os, sys
import doctest
sys.path.insert(0, os.pardir)
import pytz
return doctest.testmod(pytz)
if __name__ == '__main__':
_test()
all_timezones = \
['Africa/Abidjan',
'Africa/Accra',
@@ -676,6 +687,7 @@ all_timezones = \
'America/Porto_Acre',
'America/Porto_Velho',
'America/Puerto_Rico',
'America/Punta_Arenas',
'America/Rainy_River',
'America/Rankin_Inlet',
'America/Recife',
@@ -731,6 +743,7 @@ all_timezones = \
'Asia/Aqtobe',
'Asia/Ashgabat',
'Asia/Ashkhabad',
'Asia/Atyrau',
'Asia/Baghdad',
'Asia/Bahrain',
'Asia/Baku',
@@ -751,6 +764,7 @@ all_timezones = \
'Asia/Dili',
'Asia/Dubai',
'Asia/Dushanbe',
'Asia/Famagusta',
'Asia/Gaza',
'Asia/Harbin',
'Asia/Hebron',
@@ -789,6 +803,7 @@ all_timezones = \
'Asia/Pontianak',
'Asia/Pyongyang',
'Asia/Qatar',
'Asia/Qostanay',
'Asia/Qyzylorda',
'Asia/Rangoon',
'Asia/Riyadh',
@@ -816,6 +831,7 @@ all_timezones = \
'Asia/Vientiane',
'Asia/Vladivostok',
'Asia/Yakutsk',
'Asia/Yangon',
'Asia/Yekaterinburg',
'Asia/Yerevan',
'Atlantic/Azores',
@@ -861,7 +877,6 @@ all_timezones = \
'CST6CDT',
'Canada/Atlantic',
'Canada/Central',
'Canada/East-Saskatchewan',
'Canada/Eastern',
'Canada/Mountain',
'Canada/Newfoundland',
@@ -955,6 +970,7 @@ all_timezones = \
'Europe/Samara',
'Europe/San_Marino',
'Europe/Sarajevo',
'Europe/Saratov',
'Europe/Simferopol',
'Europe/Skopje',
'Europe/Sofia',
@@ -1072,7 +1088,6 @@ all_timezones = \
'US/Michigan',
'US/Mountain',
'US/Pacific',
'US/Pacific-New',
'US/Samoa',
'UTC',
'Universal',
@@ -1083,6 +1098,7 @@ all_timezones = LazyList(
tz for tz in all_timezones if resource_exists(tz))
all_timezones_set = LazySet(all_timezones)
_all_timezones_lower_to_standard = dict((tz.lower(), tz) for tz in all_timezones)
common_timezones = \
['Africa/Abidjan',
'Africa/Accra',
@@ -1252,6 +1268,7 @@ common_timezones = \
'America/Port_of_Spain',
'America/Porto_Velho',
'America/Puerto_Rico',
'America/Punta_Arenas',
'America/Rainy_River',
'America/Rankin_Inlet',
'America/Recife',
@@ -1301,6 +1318,7 @@ common_timezones = \
'Asia/Aqtau',
'Asia/Aqtobe',
'Asia/Ashgabat',
'Asia/Atyrau',
'Asia/Baghdad',
'Asia/Bahrain',
'Asia/Baku',
@@ -1317,6 +1335,7 @@ common_timezones = \
'Asia/Dili',
'Asia/Dubai',
'Asia/Dushanbe',
'Asia/Famagusta',
'Asia/Gaza',
'Asia/Hebron',
'Asia/Ho_Chi_Minh',
@@ -1350,8 +1369,8 @@ common_timezones = \
'Asia/Pontianak',
'Asia/Pyongyang',
'Asia/Qatar',
'Asia/Qostanay',
'Asia/Qyzylorda',
'Asia/Rangoon',
'Asia/Riyadh',
'Asia/Sakhalin',
'Asia/Samarkand',
@@ -1372,6 +1391,7 @@ common_timezones = \
'Asia/Vientiane',
'Asia/Vladivostok',
'Asia/Yakutsk',
'Asia/Yangon',
'Asia/Yekaterinburg',
'Asia/Yerevan',
'Atlantic/Azores',
@@ -1444,6 +1464,7 @@ common_timezones = \
'Europe/Samara',
'Europe/San_Marino',
'Europe/Sarajevo',
'Europe/Saratov',
'Europe/Simferopol',
'Europe/Skopje',
'Europe/Sofia',
@@ -1489,7 +1510,6 @@ common_timezones = \
'Pacific/Guadalcanal',
'Pacific/Guam',
'Pacific/Honolulu',
'Pacific/Johnston',
'Pacific/Kiritimati',
'Pacific/Kosrae',
'Pacific/Kwajalein',

View File

@@ -5,7 +5,7 @@ Custom exceptions raised by pytz.
__all__ = [
'UnknownTimeZoneError', 'InvalidTimeError', 'AmbiguousTimeError',
'NonExistentTimeError',
]
]
class UnknownTimeZoneError(KeyError):

View File

@@ -1,7 +1,10 @@
from threading import RLock
try:
from UserDict import DictMixin
except ImportError:
from collections.abc import Mapping as DictMixin
except ImportError: # Python < 3.3
try:
from UserDict import DictMixin # Python 2
except ImportError: # Python 3.0-3.3
from collections import Mapping as DictMixin
@@ -13,6 +16,7 @@ _fill_lock = RLock()
class LazyDict(DictMixin):
"""Dictionary populated on first use."""
data = None
def __getitem__(self, key):
if self.data is None:
_fill_lock.acquire()

View File

@@ -5,17 +5,28 @@ Used for testing against as they are only correct for the years
'''
from datetime import tzinfo, timedelta, datetime
from pytz import utc, UTC, HOUR, ZERO
from pytz import HOUR, ZERO, UTC
__all__ = [
'FixedOffset',
'LocalTimezone',
'USTimeZone',
'Eastern',
'Central',
'Mountain',
'Pacific',
'UTC'
]
# A class building tzinfo objects for fixed-offset time zones.
# Note that FixedOffset(0, "UTC") is a different way to build a
# UTC tzinfo object.
class FixedOffset(tzinfo):
"""Fixed offset in minutes east from UTC."""
def __init__(self, offset, name):
self.__offset = timedelta(minutes = offset)
self.__offset = timedelta(minutes=offset)
self.__name = name
def utcoffset(self, dt):
@@ -27,18 +38,19 @@ class FixedOffset(tzinfo):
def dst(self, dt):
return ZERO
# A class capturing the platform's idea of local time.
import time as _time
STDOFFSET = timedelta(seconds = -_time.timezone)
STDOFFSET = timedelta(seconds=-_time.timezone)
if _time.daylight:
DSTOFFSET = timedelta(seconds = -_time.altzone)
DSTOFFSET = timedelta(seconds=-_time.altzone)
else:
DSTOFFSET = STDOFFSET
DSTDIFF = DSTOFFSET - STDOFFSET
# A class capturing the platform's idea of local time.
class LocalTimezone(tzinfo):
def utcoffset(self, dt):
@@ -66,7 +78,6 @@ class LocalTimezone(tzinfo):
Local = LocalTimezone()
# A complete implementation of current DST rules for major US time zones.
def first_sunday_on_or_after(dt):
days_to_go = 6 - dt.weekday()
@@ -74,12 +85,15 @@ def first_sunday_on_or_after(dt):
dt += timedelta(days_to_go)
return dt
# In the US, DST starts at 2am (standard time) on the first Sunday in April.
DSTSTART = datetime(1, 4, 1, 2)
# and ends at 2am (DST time; 1am standard time) on the last Sunday of Oct.
# which is the first Sunday on or after Oct 25.
DSTEND = datetime(1, 10, 25, 1)
# A complete implementation of current DST rules for major US time zones.
class USTimeZone(tzinfo):
def __init__(self, hours, reprname, stdname, dstname):
@@ -124,4 +138,3 @@ Eastern = USTimeZone(-5, "Eastern", "EST", "EDT")
Central = USTimeZone(-6, "Central", "CST", "CDT")
Mountain = USTimeZone(-7, "Mountain", "MST", "MDT")
Pacific = USTimeZone(-8, "Pacific", "PST", "PDT")

View File

@@ -0,0 +1,34 @@
# -*- coding: ascii -*-
from doctest import DocFileSuite
import unittest
import os.path
import sys
THIS_DIR = os.path.dirname(__file__)
README = os.path.join(THIS_DIR, os.pardir, os.pardir, 'README.txt')
class DocumentationTestCase(unittest.TestCase):
def test_readme_encoding(self):
'''Confirm the README.txt is pure ASCII.'''
f = open(README, 'rb')
try:
f.read().decode('ASCII')
finally:
f.close()
def test_suite():
"For the Z3 test runner"
return unittest.TestSuite((
DocumentationTestCase('test_readme_encoding'),
DocFileSuite(os.path.join(os.pardir, os.pardir, 'README.txt'))))
if __name__ == '__main__':
sys.path.insert(
0, os.path.abspath(os.path.join(THIS_DIR, os.pardir, os.pardir))
)
unittest.main(defaultTest='test_suite')

315
lib/pytz/tests/test_lazy.py Normal file
View File

@@ -0,0 +1,315 @@
from operator import (
eq, ge, gt, le, lt, ne, add, concat, not_, sub, and_, or_, xor
)
import os.path
import sys
import unittest
import warnings
if __name__ == '__main__':
# Only munge path if invoked as a script. Testrunners should have setup
# the paths already
sys.path.insert(0, os.path.abspath(os.path.join(os.pardir, os.pardir)))
from pytz.lazy import LazyList, LazySet
class LazyListTestCase(unittest.TestCase):
initial_data = [3, 2, 1]
def setUp(self):
self.base = [3, 2, 1]
self.lesser = [2, 1, 0]
self.greater = [4, 3, 2]
self.lazy = LazyList(iter(list(self.base)))
def test_unary_ops(self):
unary_ops = [str, repr, len, bool, not_]
try:
unary_ops.append(unicode)
except NameError:
pass # unicode no longer exists in Python 3.
for op in unary_ops:
self.assertEqual(
op(self.lazy),
op(self.base), str(op))
def test_binary_ops(self):
binary_ops = [eq, ge, gt, le, lt, ne, add, concat]
try:
binary_ops.append(cmp)
except NameError:
pass # cmp no longer exists in Python 3.
for op in binary_ops:
self.assertEqual(
op(self.lazy, self.lazy),
op(self.base, self.base), str(op))
for other in [self.base, self.lesser, self.greater]:
self.assertEqual(
op(self.lazy, other),
op(self.base, other), '%s %s' % (op, other))
self.assertEqual(
op(other, self.lazy),
op(other, self.base), '%s %s' % (op, other))
# Multiplication
self.assertEqual(self.lazy * 3, self.base * 3)
self.assertEqual(3 * self.lazy, 3 * self.base)
# Contains
self.assertTrue(2 in self.lazy)
self.assertFalse(42 in self.lazy)
def test_iadd(self):
self.lazy += [1]
self.base += [1]
self.assertEqual(self.lazy, self.base)
def test_bool(self):
self.assertTrue(bool(self.lazy))
self.assertFalse(bool(LazyList()))
self.assertFalse(bool(LazyList(iter([]))))
def test_hash(self):
self.assertRaises(TypeError, hash, self.lazy)
def test_isinstance(self):
self.assertTrue(isinstance(self.lazy, list))
self.assertFalse(isinstance(self.lazy, tuple))
def test_callable(self):
try:
callable
except NameError:
return # No longer exists with Python 3.
self.assertFalse(callable(self.lazy))
def test_append(self):
self.base.append('extra')
self.lazy.append('extra')
self.assertEqual(self.lazy, self.base)
def test_count(self):
self.assertEqual(self.lazy.count(2), 1)
def test_index(self):
self.assertEqual(self.lazy.index(2), 1)
def test_extend(self):
self.base.extend([6, 7])
self.lazy.extend([6, 7])
self.assertEqual(self.lazy, self.base)
def test_insert(self):
self.base.insert(0, 'ping')
self.lazy.insert(0, 'ping')
self.assertEqual(self.lazy, self.base)
def test_pop(self):
self.assertEqual(self.lazy.pop(), self.base.pop())
self.assertEqual(self.lazy, self.base)
def test_remove(self):
self.base.remove(2)
self.lazy.remove(2)
self.assertEqual(self.lazy, self.base)
def test_reverse(self):
self.base.reverse()
self.lazy.reverse()
self.assertEqual(self.lazy, self.base)
def test_reversed(self):
self.assertEqual(list(reversed(self.lazy)), list(reversed(self.base)))
def test_sort(self):
self.base.sort()
self.assertNotEqual(self.lazy, self.base, 'Test data already sorted')
self.lazy.sort()
self.assertEqual(self.lazy, self.base)
def test_sorted(self):
self.assertEqual(sorted(self.lazy), sorted(self.base))
def test_getitem(self):
for idx in range(-len(self.base), len(self.base)):
self.assertEqual(self.lazy[idx], self.base[idx])
def test_setitem(self):
for idx in range(-len(self.base), len(self.base)):
self.base[idx] = idx + 1000
self.assertNotEqual(self.lazy, self.base)
self.lazy[idx] = idx + 1000
self.assertEqual(self.lazy, self.base)
def test_delitem(self):
del self.base[0]
self.assertNotEqual(self.lazy, self.base)
del self.lazy[0]
self.assertEqual(self.lazy, self.base)
del self.base[-2]
self.assertNotEqual(self.lazy, self.base)
del self.lazy[-2]
self.assertEqual(self.lazy, self.base)
def test_iter(self):
self.assertEqual(list(iter(self.lazy)), list(iter(self.base)))
def test_getslice(self):
for i in range(-len(self.base), len(self.base)):
for j in range(-len(self.base), len(self.base)):
for step in [-1, 1]:
self.assertEqual(self.lazy[i:j:step], self.base[i:j:step])
def test_setslice(self):
for i in range(-len(self.base), len(self.base)):
for j in range(-len(self.base), len(self.base)):
for step in [-1, 1]:
replacement = range(0, len(self.base[i:j:step]))
self.base[i:j:step] = replacement
self.lazy[i:j:step] = replacement
self.assertEqual(self.lazy, self.base)
def test_delslice(self):
del self.base[0:1]
del self.lazy[0:1]
self.assertEqual(self.lazy, self.base)
del self.base[-1:1:-1]
del self.lazy[-1:1:-1]
self.assertEqual(self.lazy, self.base)
class LazySetTestCase(unittest.TestCase):
initial_data = set([3, 2, 1])
def setUp(self):
self.base = set([3, 2, 1])
self.lazy = LazySet(iter(set(self.base)))
def test_unary_ops(self):
# These ops just need to work.
unary_ops = [str, repr]
try:
unary_ops.append(unicode)
except NameError:
pass # unicode no longer exists in Python 3.
for op in unary_ops:
op(self.lazy) # These ops just need to work.
# These ops should return identical values as a real set.
unary_ops = [len, bool, not_]
for op in unary_ops:
self.assertEqual(
op(self.lazy),
op(self.base), '%s(lazy) == %r' % (op, op(self.lazy)))
def test_binary_ops(self):
binary_ops = [eq, ge, gt, le, lt, ne, sub, and_, or_, xor]
try:
binary_ops.append(cmp)
except NameError:
pass # cmp no longer exists in Python 3.
for op in binary_ops:
self.assertEqual(
op(self.lazy, self.lazy),
op(self.base, self.base), str(op))
self.assertEqual(
op(self.lazy, self.base),
op(self.base, self.base), str(op))
self.assertEqual(
op(self.base, self.lazy),
op(self.base, self.base), str(op))
# Contains
self.assertTrue(2 in self.lazy)
self.assertFalse(42 in self.lazy)
def test_iops(self):
try:
iops = [isub, iand, ior, ixor]
except NameError:
return # Don't exist in older Python versions.
for op in iops:
# Mutating operators, so make fresh copies.
lazy = LazySet(self.base)
base = self.base.copy()
op(lazy, set([1]))
op(base, set([1]))
self.assertEqual(lazy, base, str(op))
def test_bool(self):
self.assertTrue(bool(self.lazy))
self.assertFalse(bool(LazySet()))
self.assertFalse(bool(LazySet(iter([]))))
def test_hash(self):
self.assertRaises(TypeError, hash, self.lazy)
def test_isinstance(self):
self.assertTrue(isinstance(self.lazy, set))
def test_callable(self):
try:
callable
except NameError:
return # No longer exists with Python 3.
self.assertFalse(callable(self.lazy))
def test_add(self):
self.base.add('extra')
self.lazy.add('extra')
self.assertEqual(self.lazy, self.base)
def test_copy(self):
self.assertEqual(self.lazy.copy(), self.base)
def test_method_ops(self):
ops = [
'difference', 'intersection', 'isdisjoint',
'issubset', 'issuperset', 'symmetric_difference', 'union',
'difference_update', 'intersection_update',
'symmetric_difference_update', 'update']
for op in ops:
if not hasattr(set, op):
continue # Not in this version of Python.
# Make a copy, as some of the ops are mutating.
lazy = LazySet(set(self.base))
base = set(self.base)
self.assertEqual(
getattr(lazy, op)(set([1])),
getattr(base, op)(set([1])), op)
self.assertEqual(lazy, base, op)
def test_discard(self):
self.base.discard(1)
self.assertNotEqual(self.lazy, self.base)
self.lazy.discard(1)
self.assertEqual(self.lazy, self.base)
def test_pop(self):
self.assertEqual(self.lazy.pop(), self.base.pop())
self.assertEqual(self.lazy, self.base)
def test_remove(self):
self.base.remove(2)
self.lazy.remove(2)
self.assertEqual(self.lazy, self.base)
def test_clear(self):
self.lazy.clear()
self.assertEqual(self.lazy, set())
if __name__ == '__main__':
warnings.simplefilter("error") # Warnings should be fatal in tests.
unittest.main()

View File

@@ -0,0 +1,863 @@
# -*- coding: ascii -*-
import doctest
import sys
import os
import os.path
import unittest
try:
import cPickle as pickle
except ImportError:
import pickle
from datetime import (
datetime,
timedelta
)
import warnings
if __name__ == '__main__':
# Only munge path if invoked as a script. Testrunners should have setup
# the paths already
sys.path.insert(0, os.path.abspath(os.path.join(os.pardir, os.pardir)))
import pytz # noqa
from pytz import reference # noqa
from pytz.tzfile import _byte_string # noqa
from pytz.tzinfo import DstTzInfo, StaticTzInfo # noqa
# I test for expected version to ensure the correct version of pytz is
# actually being tested.
EXPECTED_VERSION = '2019.1'
EXPECTED_OLSON_VERSION = '2019a'
fmt = '%Y-%m-%d %H:%M:%S %Z%z'
NOTIME = timedelta(0)
# GMT is a tzinfo.StaticTzInfo--the class we primarily want to test--while
# UTC is reference implementation. They both have the same timezone meaning.
UTC = pytz.timezone('UTC')
GMT = pytz.timezone('GMT')
assert isinstance(GMT, StaticTzInfo), 'GMT is no longer a StaticTzInfo'
def prettydt(dt):
"""datetime as a string using a known format.
We don't use strftime as it doesn't handle years earlier than 1900
per http://bugs.python.org/issue1777412
"""
if dt.utcoffset() >= timedelta(0):
offset = '+%s' % (dt.utcoffset(),)
else:
offset = '-%s' % (-1 * dt.utcoffset(),)
return '%04d-%02d-%02d %02d:%02d:%02d %s %s' % (
dt.year, dt.month, dt.day,
dt.hour, dt.minute, dt.second,
dt.tzname(), offset)
if sys.version_info[0] > 2:
# Python 3.x doesn't have unicode(), making writing code
# for Python 2.3 and Python 3.x a pain.
unicode = str
class BasicTest(unittest.TestCase):
def testVersion(self):
# Ensuring the correct version of pytz has been loaded
self.assertEqual(
EXPECTED_VERSION, pytz.__version__,
'Incorrect pytz version loaded. Import path is stuffed '
'or this test needs updating. (Wanted %s, got %s)'
% (EXPECTED_VERSION, pytz.__version__)
)
self.assertEqual(
EXPECTED_OLSON_VERSION, pytz.OLSON_VERSION,
'Incorrect pytz version loaded. Import path is stuffed '
'or this test needs updating. (Wanted %s, got %s)'
% (EXPECTED_OLSON_VERSION, pytz.OLSON_VERSION)
)
def testGMT(self):
now = datetime.now(tz=GMT)
self.assertTrue(now.utcoffset() == NOTIME)
self.assertTrue(now.dst() == NOTIME)
self.assertTrue(now.timetuple() == now.utctimetuple())
self.assertTrue(now == now.replace(tzinfo=UTC))
def testReferenceUTC(self):
now = datetime.now(tz=UTC)
self.assertTrue(now.utcoffset() == NOTIME)
self.assertTrue(now.dst() == NOTIME)
self.assertTrue(now.timetuple() == now.utctimetuple())
def testUnknownOffsets(self):
# This tzinfo behavior is required to make
# datetime.time.{utcoffset, dst, tzname} work as documented.
dst_tz = pytz.timezone('US/Eastern')
# This information is not known when we don't have a date,
# so return None per API.
self.assertTrue(dst_tz.utcoffset(None) is None)
self.assertTrue(dst_tz.dst(None) is None)
# We don't know the abbreviation, but this is still a valid
# tzname per the Python documentation.
self.assertEqual(dst_tz.tzname(None), 'US/Eastern')
def clearCache(self):
pytz._tzinfo_cache.clear()
def testUnicodeTimezone(self):
# We need to ensure that cold lookups work for both Unicode
# and traditional strings, and that the desired singleton is
# returned.
self.clearCache()
eastern = pytz.timezone(unicode('US/Eastern'))
self.assertTrue(eastern is pytz.timezone('US/Eastern'))
self.clearCache()
eastern = pytz.timezone('US/Eastern')
self.assertTrue(eastern is pytz.timezone(unicode('US/Eastern')))
def testStaticTzInfo(self):
# Ensure that static timezones are correctly detected,
# per lp:1602807
static = pytz.timezone('Etc/GMT-4')
self.assertTrue(isinstance(static, StaticTzInfo))
class PicklingTest(unittest.TestCase):
def _roundtrip_tzinfo(self, tz):
p = pickle.dumps(tz)
unpickled_tz = pickle.loads(p)
self.assertTrue(tz is unpickled_tz, '%s did not roundtrip' % tz.zone)
def _roundtrip_datetime(self, dt):
# Ensure that the tzinfo attached to a datetime instance
# is identical to the one returned. This is important for
# DST timezones, as some state is stored in the tzinfo.
tz = dt.tzinfo
p = pickle.dumps(dt)
unpickled_dt = pickle.loads(p)
unpickled_tz = unpickled_dt.tzinfo
self.assertTrue(tz is unpickled_tz, '%s did not roundtrip' % tz.zone)
def testDst(self):
tz = pytz.timezone('Europe/Amsterdam')
dt = datetime(2004, 2, 1, 0, 0, 0)
for localized_tz in tz._tzinfos.values():
self._roundtrip_tzinfo(localized_tz)
self._roundtrip_datetime(dt.replace(tzinfo=localized_tz))
def testRoundtrip(self):
for zone in pytz.all_timezones:
tz = pytz.timezone(zone)
self._roundtrip_tzinfo(tz)
def testDatabaseFixes(self):
# Hack the pickle to make it refer to a timezone abbreviation
# that does not match anything. The unpickler should be able
# to repair this case
tz = pytz.timezone('Australia/Melbourne')
p = pickle.dumps(tz)
tzname = tz._tzname
hacked_p = p.replace(
_byte_string(tzname),
_byte_string('?' * len(tzname))
)
self.assertNotEqual(p, hacked_p)
unpickled_tz = pickle.loads(hacked_p)
self.assertTrue(tz is unpickled_tz)
# Simulate a database correction. In this case, the incorrect
# data will continue to be used.
p = pickle.dumps(tz)
new_utcoffset = tz._utcoffset.seconds + 42
# Python 3 introduced a new pickle protocol where numbers are stored in
# hexadecimal representation. Here we extract the pickle
# representation of the number for the current Python version.
old_pickle_pattern = pickle.dumps(tz._utcoffset.seconds)[3:-1]
new_pickle_pattern = pickle.dumps(new_utcoffset)[3:-1]
hacked_p = p.replace(old_pickle_pattern, new_pickle_pattern)
self.assertNotEqual(p, hacked_p)
unpickled_tz = pickle.loads(hacked_p)
self.assertEqual(unpickled_tz._utcoffset.seconds, new_utcoffset)
self.assertTrue(tz is not unpickled_tz)
def testOldPickles(self):
# Ensure that applications serializing pytz instances as pickles
# have no troubles upgrading to a new pytz release. These pickles
# where created with pytz2006j
east1 = pickle.loads(
_byte_string(
"cpytz\n_p\np1\n(S'US/Eastern'\np2\nI-18000\n"
"I0\nS'EST'\np3\ntRp4\n."
)
)
east2 = pytz.timezone('US/Eastern').localize(
datetime(2006, 1, 1)).tzinfo
self.assertTrue(east1 is east2)
# Confirm changes in name munging between 2006j and 2007c cause
# no problems.
pap1 = pickle.loads(_byte_string(
"cpytz\n_p\np1\n(S'America/Port_minus_au_minus_Prince'"
"\np2\nI-17340\nI0\nS'PPMT'\np3\ntRp4\n."))
pap2 = pytz.timezone('America/Port-au-Prince').localize(
datetime(1910, 1, 1)).tzinfo
self.assertTrue(pap1 is pap2)
gmt1 = pickle.loads(_byte_string(
"cpytz\n_p\np1\n(S'Etc/GMT_plus_10'\np2\ntRp3\n."))
gmt2 = pytz.timezone('Etc/GMT+10')
self.assertTrue(gmt1 is gmt2)
class USEasternDSTStartTestCase(unittest.TestCase):
tzinfo = pytz.timezone('US/Eastern')
# 24 hours before DST changeover
transition_time = datetime(2002, 4, 7, 7, 0, 0, tzinfo=UTC)
# Increase for 'flexible' DST transitions due to 1 minute granularity
# of Python's datetime library
instant = timedelta(seconds=1)
# before transition
before = {
'tzname': 'EST',
'utcoffset': timedelta(hours=-5),
'dst': timedelta(hours=0),
}
# after transition
after = {
'tzname': 'EDT',
'utcoffset': timedelta(hours=-4),
'dst': timedelta(hours=1),
}
def _test_tzname(self, utc_dt, wanted):
tzname = wanted['tzname']
dt = utc_dt.astimezone(self.tzinfo)
self.assertEqual(
dt.tzname(), tzname,
'Expected %s as tzname for %s. Got %s' % (
tzname, str(utc_dt), dt.tzname()
)
)
def _test_utcoffset(self, utc_dt, wanted):
utcoffset = wanted['utcoffset']
dt = utc_dt.astimezone(self.tzinfo)
self.assertEqual(
dt.utcoffset(), wanted['utcoffset'],
'Expected %s as utcoffset for %s. Got %s' % (
utcoffset, utc_dt, dt.utcoffset()
)
)
def _test_dst(self, utc_dt, wanted):
dst = wanted['dst']
dt = utc_dt.astimezone(self.tzinfo)
self.assertEqual(
dt.dst(), dst,
'Expected %s as dst for %s. Got %s' % (dst, utc_dt, dt.dst())
)
def test_arithmetic(self):
utc_dt = self.transition_time
for days in range(-420, 720, 20):
delta = timedelta(days=days)
# Make sure we can get back where we started
dt = utc_dt.astimezone(self.tzinfo)
dt2 = dt + delta
dt2 = dt2 - delta
self.assertEqual(dt, dt2)
# Make sure arithmetic crossing DST boundaries ends
# up in the correct timezone after normalization
utc_plus_delta = (utc_dt + delta).astimezone(self.tzinfo)
local_plus_delta = self.tzinfo.normalize(dt + delta)
self.assertEqual(
prettydt(utc_plus_delta), prettydt(local_plus_delta),
'Incorrect result for delta==%d days. Wanted %r. Got %r' % (
days, prettydt(utc_plus_delta), prettydt(local_plus_delta),
)
)
def _test_all(self, utc_dt, wanted):
self._test_utcoffset(utc_dt, wanted)
self._test_tzname(utc_dt, wanted)
self._test_dst(utc_dt, wanted)
def testDayBefore(self):
self._test_all(
self.transition_time - timedelta(days=1), self.before
)
def testTwoHoursBefore(self):
self._test_all(
self.transition_time - timedelta(hours=2), self.before
)
def testHourBefore(self):
self._test_all(
self.transition_time - timedelta(hours=1), self.before
)
def testInstantBefore(self):
self._test_all(
self.transition_time - self.instant, self.before
)
def testTransition(self):
self._test_all(
self.transition_time, self.after
)
def testInstantAfter(self):
self._test_all(
self.transition_time + self.instant, self.after
)
def testHourAfter(self):
self._test_all(
self.transition_time + timedelta(hours=1), self.after
)
def testTwoHoursAfter(self):
self._test_all(
self.transition_time + timedelta(hours=1), self.after
)
def testDayAfter(self):
self._test_all(
self.transition_time + timedelta(days=1), self.after
)
class USEasternDSTEndTestCase(USEasternDSTStartTestCase):
tzinfo = pytz.timezone('US/Eastern')
transition_time = datetime(2002, 10, 27, 6, 0, 0, tzinfo=UTC)
before = {
'tzname': 'EDT',
'utcoffset': timedelta(hours=-4),
'dst': timedelta(hours=1),
}
after = {
'tzname': 'EST',
'utcoffset': timedelta(hours=-5),
'dst': timedelta(hours=0),
}
class USEasternEPTStartTestCase(USEasternDSTStartTestCase):
transition_time = datetime(1945, 8, 14, 23, 0, 0, tzinfo=UTC)
before = {
'tzname': 'EWT',
'utcoffset': timedelta(hours=-4),
'dst': timedelta(hours=1),
}
after = {
'tzname': 'EPT',
'utcoffset': timedelta(hours=-4),
'dst': timedelta(hours=1),
}
class USEasternEPTEndTestCase(USEasternDSTStartTestCase):
transition_time = datetime(1945, 9, 30, 6, 0, 0, tzinfo=UTC)
before = {
'tzname': 'EPT',
'utcoffset': timedelta(hours=-4),
'dst': timedelta(hours=1),
}
after = {
'tzname': 'EST',
'utcoffset': timedelta(hours=-5),
'dst': timedelta(hours=0),
}
class WarsawWMTEndTestCase(USEasternDSTStartTestCase):
# In 1915, Warsaw changed from Warsaw to Central European time.
# This involved the clocks being set backwards, causing a end-of-DST
# like situation without DST being involved.
tzinfo = pytz.timezone('Europe/Warsaw')
transition_time = datetime(1915, 8, 4, 22, 36, 0, tzinfo=UTC)
before = {
'tzname': 'WMT',
'utcoffset': timedelta(hours=1, minutes=24),
'dst': timedelta(0),
}
after = {
'tzname': 'CET',
'utcoffset': timedelta(hours=1),
'dst': timedelta(0),
}
class VilniusWMTEndTestCase(USEasternDSTStartTestCase):
# At the end of 1916, Vilnius changed timezones putting its clock
# forward by 11 minutes 35 seconds. Neither timezone was in DST mode.
tzinfo = pytz.timezone('Europe/Vilnius')
instant = timedelta(seconds=31)
transition_time = datetime(1916, 12, 31, 22, 36, 00, tzinfo=UTC)
before = {
'tzname': 'WMT',
'utcoffset': timedelta(hours=1, minutes=24),
'dst': timedelta(0),
}
after = {
'tzname': 'KMT',
'utcoffset': timedelta(hours=1, minutes=36), # Really 1:35:36
'dst': timedelta(0),
}
class VilniusCESTStartTestCase(USEasternDSTStartTestCase):
# In 1941, Vilnius changed from MSG to CEST, switching to summer
# time while simultaneously reducing its UTC offset by two hours,
# causing the clocks to go backwards for this summer time
# switchover.
tzinfo = pytz.timezone('Europe/Vilnius')
transition_time = datetime(1941, 6, 23, 21, 00, 00, tzinfo=UTC)
before = {
'tzname': 'MSK',
'utcoffset': timedelta(hours=3),
'dst': timedelta(0),
}
after = {
'tzname': 'CEST',
'utcoffset': timedelta(hours=2),
'dst': timedelta(hours=1),
}
class LondonHistoryStartTestCase(USEasternDSTStartTestCase):
# The first known timezone transition in London was in 1847 when
# clocks where synchronized to GMT. However, we currently only
# understand v1 format tzfile(5) files which does handle years
# this far in the past, so our earliest known transition is in
# 1916.
tzinfo = pytz.timezone('Europe/London')
# transition_time = datetime(1847, 12, 1, 1, 15, 00, tzinfo=UTC)
# before = {
# 'tzname': 'LMT',
# 'utcoffset': timedelta(minutes=-75),
# 'dst': timedelta(0),
# }
# after = {
# 'tzname': 'GMT',
# 'utcoffset': timedelta(0),
# 'dst': timedelta(0),
# }
transition_time = datetime(1916, 5, 21, 2, 00, 00, tzinfo=UTC)
before = {
'tzname': 'GMT',
'utcoffset': timedelta(0),
'dst': timedelta(0),
}
after = {
'tzname': 'BST',
'utcoffset': timedelta(hours=1),
'dst': timedelta(hours=1),
}
class LondonHistoryEndTestCase(USEasternDSTStartTestCase):
# Timezone switchovers are projected into the future, even
# though no official statements exist or could be believed even
# if they did exist. We currently only check the last known
# transition in 2037, as we are still using v1 format tzfile(5)
# files.
tzinfo = pytz.timezone('Europe/London')
# transition_time = datetime(2499, 10, 25, 1, 0, 0, tzinfo=UTC)
transition_time = datetime(2037, 10, 25, 1, 0, 0, tzinfo=UTC)
before = {
'tzname': 'BST',
'utcoffset': timedelta(hours=1),
'dst': timedelta(hours=1),
}
after = {
'tzname': 'GMT',
'utcoffset': timedelta(0),
'dst': timedelta(0),
}
class NoumeaHistoryStartTestCase(USEasternDSTStartTestCase):
# Noumea adopted a whole hour offset in 1912. Previously
# it was 11 hours, 5 minutes and 48 seconds off UTC. However,
# due to limitations of the Python datetime library, we need
# to round that to 11 hours 6 minutes.
tzinfo = pytz.timezone('Pacific/Noumea')
transition_time = datetime(1912, 1, 12, 12, 54, 12, tzinfo=UTC)
before = {
'tzname': 'LMT',
'utcoffset': timedelta(hours=11, minutes=6),
'dst': timedelta(0),
}
after = {
'tzname': '+11', # pre-2017a, NCT
'utcoffset': timedelta(hours=11),
'dst': timedelta(0),
}
class NoumeaDSTEndTestCase(USEasternDSTStartTestCase):
# Noumea dropped DST in 1997.
tzinfo = pytz.timezone('Pacific/Noumea')
transition_time = datetime(1997, 3, 1, 15, 00, 00, tzinfo=UTC)
before = {
'tzname': '+12', # pre-2017a, NCST
'utcoffset': timedelta(hours=12),
'dst': timedelta(hours=1),
}
after = {
'tzname': '+11', # pre-2017a, NCT
'utcoffset': timedelta(hours=11),
'dst': timedelta(0),
}
class NoumeaNoMoreDSTTestCase(NoumeaDSTEndTestCase):
# Noumea dropped DST in 1997. Here we test that it stops occuring.
transition_time = (
NoumeaDSTEndTestCase.transition_time + timedelta(days=365 * 10))
before = NoumeaDSTEndTestCase.after
after = NoumeaDSTEndTestCase.after
class TahitiTestCase(USEasternDSTStartTestCase):
# Tahiti has had a single transition in its history.
tzinfo = pytz.timezone('Pacific/Tahiti')
transition_time = datetime(1912, 10, 1, 9, 58, 16, tzinfo=UTC)
before = {
'tzname': 'LMT',
'utcoffset': timedelta(hours=-9, minutes=-58),
'dst': timedelta(0),
}
after = {
'tzname': '-10', # pre-2017a, TAHT
'utcoffset': timedelta(hours=-10),
'dst': timedelta(0),
}
class SamoaInternationalDateLineChange(USEasternDSTStartTestCase):
# At the end of 2011, Samoa will switch from being east of the
# international dateline to the west. There will be no Dec 30th
# 2011 and it will switch from UTC-10 to UTC+14.
tzinfo = pytz.timezone('Pacific/Apia')
transition_time = datetime(2011, 12, 30, 10, 0, 0, tzinfo=UTC)
before = {
'tzname': '-10', # pre-2017a, SDT
'utcoffset': timedelta(hours=-10),
'dst': timedelta(hours=1),
}
after = {
'tzname': '+14', # pre-2017a, WSDT
'utcoffset': timedelta(hours=14),
'dst': timedelta(hours=1),
}
class ReferenceUSEasternDSTStartTestCase(USEasternDSTStartTestCase):
tzinfo = reference.Eastern
def test_arithmetic(self):
# Reference implementation cannot handle this
pass
class ReferenceUSEasternDSTEndTestCase(USEasternDSTEndTestCase):
tzinfo = reference.Eastern
def testHourBefore(self):
# Python's datetime library has a bug, where the hour before
# a daylight saving transition is one hour out. For example,
# at the end of US/Eastern daylight saving time, 01:00 EST
# occurs twice (once at 05:00 UTC and once at 06:00 UTC),
# whereas the first should actually be 01:00 EDT.
# Note that this bug is by design - by accepting this ambiguity
# for one hour one hour per year, an is_dst flag on datetime.time
# became unnecessary.
self._test_all(self.transition_time - timedelta(hours=1), self.after)
def testInstantBefore(self):
self._test_all(self.transition_time - timedelta(seconds=1), self.after)
def test_arithmetic(self):
# Reference implementation cannot handle this
pass
class LocalTestCase(unittest.TestCase):
def testLocalize(self):
loc_tz = pytz.timezone('Europe/Amsterdam')
loc_time = loc_tz.localize(datetime(1930, 5, 10, 0, 0, 0))
# Actually +00:19:32, but Python datetime rounds this
self.assertEqual(loc_time.strftime('%Z%z'), 'AMT+0020')
loc_time = loc_tz.localize(datetime(1930, 5, 20, 0, 0, 0))
# Actually +00:19:32, but Python datetime rounds this
self.assertEqual(loc_time.strftime('%Z%z'), 'NST+0120')
loc_time = loc_tz.localize(datetime(1940, 5, 10, 0, 0, 0))
# pre-2017a, abbreviation was NCT
self.assertEqual(loc_time.strftime('%Z%z'), '+0020+0020')
loc_time = loc_tz.localize(datetime(1940, 5, 20, 0, 0, 0))
self.assertEqual(loc_time.strftime('%Z%z'), 'CEST+0200')
loc_time = loc_tz.localize(datetime(2004, 2, 1, 0, 0, 0))
self.assertEqual(loc_time.strftime('%Z%z'), 'CET+0100')
loc_time = loc_tz.localize(datetime(2004, 4, 1, 0, 0, 0))
self.assertEqual(loc_time.strftime('%Z%z'), 'CEST+0200')
loc_time = loc_tz.localize(datetime(1943, 3, 29, 1, 59, 59))
self.assertEqual(loc_time.strftime('%Z%z'), 'CET+0100')
# Switch to US
loc_tz = pytz.timezone('US/Eastern')
# End of DST ambiguity check
loc_time = loc_tz.localize(datetime(1918, 10, 27, 1, 59, 59), is_dst=1)
self.assertEqual(loc_time.strftime('%Z%z'), 'EDT-0400')
loc_time = loc_tz.localize(datetime(1918, 10, 27, 1, 59, 59), is_dst=0)
self.assertEqual(loc_time.strftime('%Z%z'), 'EST-0500')
self.assertRaises(
pytz.AmbiguousTimeError,
loc_tz.localize, datetime(1918, 10, 27, 1, 59, 59), is_dst=None
)
# Start of DST non-existent times
loc_time = loc_tz.localize(datetime(1918, 3, 31, 2, 0, 0), is_dst=0)
self.assertEqual(loc_time.strftime('%Z%z'), 'EST-0500')
loc_time = loc_tz.localize(datetime(1918, 3, 31, 2, 0, 0), is_dst=1)
self.assertEqual(loc_time.strftime('%Z%z'), 'EDT-0400')
self.assertRaises(
pytz.NonExistentTimeError,
loc_tz.localize, datetime(1918, 3, 31, 2, 0, 0), is_dst=None
)
# Weird changes - war time and peace time both is_dst==True
loc_time = loc_tz.localize(datetime(1942, 2, 9, 3, 0, 0))
self.assertEqual(loc_time.strftime('%Z%z'), 'EWT-0400')
loc_time = loc_tz.localize(datetime(1945, 8, 14, 19, 0, 0))
self.assertEqual(loc_time.strftime('%Z%z'), 'EPT-0400')
loc_time = loc_tz.localize(datetime(1945, 9, 30, 1, 0, 0), is_dst=1)
self.assertEqual(loc_time.strftime('%Z%z'), 'EPT-0400')
loc_time = loc_tz.localize(datetime(1945, 9, 30, 1, 0, 0), is_dst=0)
self.assertEqual(loc_time.strftime('%Z%z'), 'EST-0500')
# Weird changes - ambiguous time (end-of-DST like) but is_dst==False
for zonename, ambiguous_naive, expected in [
('Europe/Warsaw', datetime(1915, 8, 4, 23, 59, 59),
['1915-08-04 23:59:59 WMT+0124',
'1915-08-04 23:59:59 CET+0100']),
('Europe/Moscow', datetime(2014, 10, 26, 1, 30),
['2014-10-26 01:30:00 MSK+0400',
'2014-10-26 01:30:00 MSK+0300'])]:
loc_tz = pytz.timezone(zonename)
self.assertRaises(
pytz.AmbiguousTimeError,
loc_tz.localize, ambiguous_naive, is_dst=None
)
# Also test non-boolean is_dst in the weird case
for dst in [True, timedelta(1), False, timedelta(0)]:
loc_time = loc_tz.localize(ambiguous_naive, is_dst=dst)
self.assertEqual(loc_time.strftime(fmt), expected[not dst])
def testNormalize(self):
tz = pytz.timezone('US/Eastern')
dt = datetime(2004, 4, 4, 7, 0, 0, tzinfo=UTC).astimezone(tz)
dt2 = dt - timedelta(minutes=10)
self.assertEqual(
dt2.strftime('%Y-%m-%d %H:%M:%S %Z%z'),
'2004-04-04 02:50:00 EDT-0400'
)
dt2 = tz.normalize(dt2)
self.assertEqual(
dt2.strftime('%Y-%m-%d %H:%M:%S %Z%z'),
'2004-04-04 01:50:00 EST-0500'
)
def testPartialMinuteOffsets(self):
# utcoffset in Amsterdam was not a whole minute until 1937
# However, we fudge this by rounding them, as the Python
# datetime library
tz = pytz.timezone('Europe/Amsterdam')
utc_dt = datetime(1914, 1, 1, 13, 40, 28, tzinfo=UTC) # correct
utc_dt = utc_dt.replace(second=0) # But we need to fudge it
loc_dt = utc_dt.astimezone(tz)
self.assertEqual(
loc_dt.strftime('%Y-%m-%d %H:%M:%S %Z%z'),
'1914-01-01 14:00:00 AMT+0020'
)
# And get back...
utc_dt = loc_dt.astimezone(UTC)
self.assertEqual(
utc_dt.strftime('%Y-%m-%d %H:%M:%S %Z%z'),
'1914-01-01 13:40:00 UTC+0000'
)
def no_testCreateLocaltime(self):
# It would be nice if this worked, but it doesn't.
tz = pytz.timezone('Europe/Amsterdam')
dt = datetime(2004, 10, 31, 2, 0, 0, tzinfo=tz)
self.assertEqual(
dt.strftime(fmt),
'2004-10-31 02:00:00 CET+0100'
)
class CommonTimezonesTestCase(unittest.TestCase):
def test_bratislava(self):
# Bratislava is the default timezone for Slovakia, but our
# heuristics where not adding it to common_timezones. Ideally,
# common_timezones should be populated from zone.tab at runtime,
# but I'm hesitant to pay the startup cost as loading the list
# on demand whilst remaining backwards compatible seems
# difficult.
self.assertTrue('Europe/Bratislava' in pytz.common_timezones)
self.assertTrue('Europe/Bratislava' in pytz.common_timezones_set)
def test_us_eastern(self):
self.assertTrue('US/Eastern' in pytz.common_timezones)
self.assertTrue('US/Eastern' in pytz.common_timezones_set)
def test_belfast(self):
# Belfast uses London time.
self.assertTrue('Europe/Belfast' in pytz.all_timezones_set)
self.assertFalse('Europe/Belfast' in pytz.common_timezones)
self.assertFalse('Europe/Belfast' in pytz.common_timezones_set)
class ZoneCaseInsensitivityTestCase(unittest.TestCase):
def test_lower_case_timezone_constructor_arg(self):
for tz in pytz.all_timezones_set:
from_lower = pytz.timezone(tz.lower())
from_passed = pytz.timezone(tz)
self.assertEqual(from_lower,
from_passed,
"arg '%s' and arg '%s' produce different "
"timezone objects" % (
from_lower, from_passed))
class BaseTzInfoTestCase:
'''Ensure UTC, StaticTzInfo and DstTzInfo work consistently.
These tests are run for each type of tzinfo.
'''
tz = None # override
tz_class = None # override
def test_expectedclass(self):
self.assertTrue(isinstance(self.tz, self.tz_class))
def test_fromutc(self):
# naive datetime.
dt1 = datetime(2011, 10, 31)
# localized datetime, same timezone.
dt2 = self.tz.localize(dt1)
# Both should give the same results. Note that the standard
# Python tzinfo.fromutc() only supports the second.
for dt in [dt1, dt2]:
loc_dt = self.tz.fromutc(dt)
loc_dt2 = pytz.utc.localize(dt1).astimezone(self.tz)
self.assertEqual(loc_dt, loc_dt2)
# localized datetime, different timezone.
new_tz = pytz.timezone('Europe/Paris')
self.assertTrue(self.tz is not new_tz)
dt3 = new_tz.localize(dt1)
self.assertRaises(ValueError, self.tz.fromutc, dt3)
def test_normalize(self):
other_tz = pytz.timezone('Europe/Paris')
self.assertTrue(self.tz is not other_tz)
dt = datetime(2012, 3, 26, 12, 0)
other_dt = other_tz.localize(dt)
local_dt = self.tz.normalize(other_dt)
self.assertTrue(local_dt.tzinfo is not other_dt.tzinfo)
self.assertNotEqual(
local_dt.replace(tzinfo=None), other_dt.replace(tzinfo=None))
def test_astimezone(self):
other_tz = pytz.timezone('Europe/Paris')
self.assertTrue(self.tz is not other_tz)
dt = datetime(2012, 3, 26, 12, 0)
other_dt = other_tz.localize(dt)
local_dt = other_dt.astimezone(self.tz)
self.assertTrue(local_dt.tzinfo is not other_dt.tzinfo)
self.assertNotEqual(
local_dt.replace(tzinfo=None), other_dt.replace(tzinfo=None))
class OptimizedUTCTestCase(unittest.TestCase, BaseTzInfoTestCase):
tz = pytz.utc
tz_class = tz.__class__
class LegacyUTCTestCase(unittest.TestCase, BaseTzInfoTestCase):
# Deprecated timezone, but useful for comparison tests.
tz = pytz.timezone('Etc/UTC')
tz_class = StaticTzInfo
class StaticTzInfoTestCase(unittest.TestCase, BaseTzInfoTestCase):
tz = pytz.timezone('GMT')
tz_class = StaticTzInfo
class DstTzInfoTestCase(unittest.TestCase, BaseTzInfoTestCase):
tz = pytz.timezone('Australia/Melbourne')
tz_class = DstTzInfo
def test_suite():
suite = unittest.TestSuite()
suite.addTest(doctest.DocTestSuite('pytz'))
suite.addTest(doctest.DocTestSuite('pytz.tzinfo'))
import test_tzinfo
suite.addTest(unittest.defaultTestLoader.loadTestsFromModule(test_tzinfo))
return suite
if __name__ == '__main__':
warnings.simplefilter("error") # Warnings should be fatal in tests.
unittest.main(defaultTest='test_suite')

View File

@@ -3,30 +3,29 @@
$Id: tzfile.py,v 1.8 2004/06/03 00:15:24 zenzen Exp $
'''
try:
from cStringIO import StringIO
except ImportError:
from io import StringIO
from datetime import datetime, timedelta
from datetime import datetime
from struct import unpack, calcsize
from pytz.tzinfo import StaticTzInfo, DstTzInfo, memorized_ttinfo
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('ASCII')
_NULL = _byte_string('\0')
def _std_string(s):
"""Cast a string or byte string to an ASCII string."""
return str(s.decode('ASCII'))
def build_tzinfo(zone, fp):
head_fmt = '>4s c 15x 6l'
head_size = calcsize(head_fmt)
(magic, format, ttisgmtcnt, ttisstdcnt,leapcnt, timecnt,
(magic, format, ttisgmtcnt, ttisstdcnt, leapcnt, timecnt,
typecnt, charcnt) = unpack(head_fmt, fp.read(head_size))
# Make sure it is a tzfile(5) file
@@ -34,7 +33,7 @@ def build_tzinfo(zone, fp):
# Read out the transition times, localtime indices and ttinfo structures.
data_fmt = '>%(timecnt)dl %(timecnt)dB %(ttinfo)s %(charcnt)ds' % dict(
timecnt=timecnt, ttinfo='lBB'*typecnt, charcnt=charcnt)
timecnt=timecnt, ttinfo='lBB' * typecnt, charcnt=charcnt)
data_size = calcsize(data_fmt)
data = unpack(data_fmt, fp.read(data_size))
@@ -53,7 +52,7 @@ def build_tzinfo(zone, fp):
i = 0
while i < len(ttinfo_raw):
# have we looked up this timezone name yet?
tzname_offset = ttinfo_raw[i+2]
tzname_offset = ttinfo_raw[i + 2]
if tzname_offset not in tznames:
nul = tznames_raw.find(_NULL, tzname_offset)
if nul < 0:
@@ -61,12 +60,12 @@ def build_tzinfo(zone, fp):
tznames[tzname_offset] = _std_string(
tznames_raw[tzname_offset:nul])
ttinfo.append((ttinfo_raw[i],
bool(ttinfo_raw[i+1]),
bool(ttinfo_raw[i + 1]),
tznames[tzname_offset]))
i += 3
# Now build the timezone object
if len(ttinfo) ==1 or len(transitions) == 0:
if len(ttinfo) == 1 or len(transitions) == 0:
ttinfo[0][0], ttinfo[0][2]
cls = type(zone, (StaticTzInfo,), dict(
zone=zone,
@@ -91,7 +90,7 @@ def build_tzinfo(zone, fp):
if not inf[1]:
dst = 0
else:
for j in range(i-1, -1, -1):
for j in range(i - 1, -1, -1):
prev_inf = ttinfo[lindexes[j]]
if not prev_inf[1]:
break
@@ -99,8 +98,8 @@ def build_tzinfo(zone, fp):
# Bad dst? Look further. DST > 24 hours happens when
# a timzone has moved across the international dateline.
if dst <= 0 or dst > 3600*3:
for j in range(i+1, len(transitions)):
if dst <= 0 or dst > 3600 * 3:
for j in range(i + 1, len(transitions)):
stdinf = ttinfo[lindexes[j]]
if not stdinf[1]:
dst = inf[0] - stdinf[0]
@@ -129,9 +128,7 @@ if __name__ == '__main__':
from pprint import pprint
base = os.path.join(os.path.dirname(__file__), 'zoneinfo')
tz = build_tzinfo('Australia/Melbourne',
open(os.path.join(base,'Australia','Melbourne'), 'rb'))
open(os.path.join(base, 'Australia', 'Melbourne'), 'rb'))
tz = build_tzinfo('US/Eastern',
open(os.path.join(base,'US','Eastern'), 'rb'))
open(os.path.join(base, 'US', 'Eastern'), 'rb'))
pprint(tz._utc_transition_times)
#print tz.asPython(4)
#print tz.transitions_mapping

View File

@@ -13,6 +13,8 @@ from pytz.exceptions import AmbiguousTimeError, NonExistentTimeError
__all__ = []
_timedelta_cache = {}
def memorized_timedelta(seconds):
'''Create only one instance of each distinct timedelta'''
try:
@@ -24,6 +26,8 @@ def memorized_timedelta(seconds):
_epoch = datetime.utcfromtimestamp(0)
_datetime_cache = {0: _epoch}
def memorized_datetime(seconds):
'''Create only one instance of each distinct datetime'''
try:
@@ -36,6 +40,8 @@ def memorized_datetime(seconds):
return dt
_ttinfo_cache = {}
def memorized_ttinfo(*args):
'''Create only one instance of each distinct tuple'''
try:
@@ -51,6 +57,7 @@ def memorized_ttinfo(*args):
_notime = memorized_timedelta(0)
def _to_seconds(td):
'''Convert a timedelta to seconds'''
return td.seconds + td.days * 24 * 60 * 60
@@ -154,12 +161,18 @@ class DstTzInfo(BaseTzInfo):
timezone definition.
'''
# Overridden in subclass
_utc_transition_times = None # Sorted list of DST transition times in UTC
_transition_info = None # [(utcoffset, dstoffset, tzname)] corresponding
# to _utc_transition_times entries
# Sorted list of DST transition times, UTC
_utc_transition_times = None
# [(utcoffset, dstoffset, tzname)] corresponding to
# _utc_transition_times entries
_transition_info = None
zone = None
# Set in __init__
_tzinfos = None
_dst = None # DST offset
@@ -170,7 +183,8 @@ class DstTzInfo(BaseTzInfo):
else:
_tzinfos = {}
self._tzinfos = _tzinfos
self._utcoffset, self._dst, self._tzname = self._transition_info[0]
self._utcoffset, self._dst, self._tzname = (
self._transition_info[0])
_tzinfos[self._transition_info[0]] = self
for inf in self._transition_info[1:]:
if inf not in _tzinfos:
@@ -178,8 +192,8 @@ class DstTzInfo(BaseTzInfo):
def fromutc(self, dt):
'''See datetime.tzinfo.fromutc'''
if (dt.tzinfo is not None
and getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos):
if (dt.tzinfo is not None and
getattr(dt.tzinfo, '_tzinfos', None) is not self._tzinfos):
raise ValueError('fromutc: dt.tzinfo is not self')
dt = dt.replace(tzinfo=None)
idx = max(0, bisect_right(self._utc_transition_times, dt) - 1)
@@ -337,8 +351,8 @@ class DstTzInfo(BaseTzInfo):
# obtain the correct timezone by winding the clock back.
else:
return self.localize(
dt - timedelta(hours=6), is_dst=False) + timedelta(hours=6)
dt - timedelta(hours=6),
is_dst=False) + timedelta(hours=6)
# If we get this far, we have multiple possible timezones - this
# is an ambiguous case occuring during the end-of-DST transition.
@@ -351,8 +365,7 @@ class DstTzInfo(BaseTzInfo):
# Filter out the possiblilities that don't match the requested
# is_dst
filtered_possible_loc_dt = [
p for p in possible_loc_dt
if bool(p.tzinfo._dst) == is_dst
p for p in possible_loc_dt if bool(p.tzinfo._dst) == is_dst
]
# Hopefully we only have one possibility left. Return it.
@@ -374,7 +387,8 @@ class DstTzInfo(BaseTzInfo):
# i.e., behave like end-of-DST transition
dates = {} # utc -> local
for local_dt in filtered_possible_loc_dt:
utc_time = local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset
utc_time = (
local_dt.replace(tzinfo=None) - local_dt.tzinfo._utcoffset)
assert utc_time not in dates
dates[utc_time] = local_dt
return dates[[min, max][not is_dst](dates)]
@@ -389,11 +403,11 @@ class DstTzInfo(BaseTzInfo):
>>> tz = timezone('America/St_Johns')
>>> ambiguous = datetime(2009, 10, 31, 23, 30)
>>> tz.utcoffset(ambiguous, is_dst=False)
datetime.timedelta(-1, 73800)
>>> str(tz.utcoffset(ambiguous, is_dst=False))
'-1 day, 20:30:00'
>>> tz.utcoffset(ambiguous, is_dst=True)
datetime.timedelta(-1, 77400)
>>> str(tz.utcoffset(ambiguous, is_dst=True))
'-1 day, 21:30:00'
>>> try:
... tz.utcoffset(ambiguous)
@@ -421,19 +435,19 @@ class DstTzInfo(BaseTzInfo):
>>> normal = datetime(2009, 9, 1)
>>> tz.dst(normal)
datetime.timedelta(0, 3600)
>>> tz.dst(normal, is_dst=False)
datetime.timedelta(0, 3600)
>>> tz.dst(normal, is_dst=True)
datetime.timedelta(0, 3600)
>>> str(tz.dst(normal))
'1:00:00'
>>> str(tz.dst(normal, is_dst=False))
'1:00:00'
>>> str(tz.dst(normal, is_dst=True))
'1:00:00'
>>> ambiguous = datetime(2009, 10, 31, 23, 30)
>>> tz.dst(ambiguous, is_dst=False)
datetime.timedelta(0)
>>> tz.dst(ambiguous, is_dst=True)
datetime.timedelta(0, 3600)
>>> str(tz.dst(ambiguous, is_dst=False))
'0:00:00'
>>> str(tz.dst(ambiguous, is_dst=True))
'1:00:00'
>>> try:
... tz.dst(ambiguous)
... except AmbiguousTimeError:
@@ -512,7 +526,6 @@ class DstTzInfo(BaseTzInfo):
)
def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None):
"""Factory function for unpickling pytz tzinfo instances.
@@ -549,8 +562,8 @@ def unpickler(zone, utcoffset=None, dstoffset=None, tzname=None):
# get changed from the initial guess by the database maintainers to
# match reality when this information is discovered.
for localized_tz in tz._tzinfos.values():
if (localized_tz._utcoffset == utcoffset
and localized_tz._dst == dstoffset):
if (localized_tz._utcoffset == utcoffset and
localized_tz._dst == dstoffset):
return localized_tz
# This (utcoffset, dstoffset) information has been removed from the

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More