Compare commits

...

440 Commits

Author SHA1 Message Date
SABnzbd Automation
f13339d64d Automatic translation update 2017-09-05 19:53:42 +00:00
Safihre
fa5b44be99 Update text files for 2.3.0Alpha2 2017-09-05 16:36:12 +02:00
Safihre
08bc7c5b9d Use NZO_LOCK to limit possible dir conflicts when adding NZBs
Hopefully resolving problems with possible overlapping directory names and import errors.
2017-09-05 16:07:43 +02:00
Safihre
d9b5dd549a Revert "Only allow 1 NZB to be added at the same time"
Could result in deadlock for some reason.
This reverts commit e64df8ed60.
2017-09-05 16:05:48 +02:00
Safihre
8ec53a3bce Giving up on elegant solutions to prevent stalling, just use a check
This stupid nightmare still wasn't fixed.
The proper solution creates slowdown on many systems because it's not efficient enough. Instead just do a check every 90 seconds if there's stalled stuff.
2017-09-05 16:05:19 +02:00
Safihre
0aac9a5e5c Correct display of download percentage
No need to calculate in javascript, we get it from API
2017-09-05 16:03:58 +02:00
Safihre
11a880d040 MultiPar shows 'PAR File(s) Incomplete' on verification success
When there are Par2-files with very similar filenames in the folder.
2017-09-05 13:21:48 +02:00
Safihre
67b66beb13 Only count really extra files during Multipar 2017-09-04 22:43:59 +02:00
Safihre
1da633442b Correct byte counts when retrying 2017-09-04 22:43:01 +02:00
Safihre
13de40881e Correct display of Forced items 2017-09-04 21:44:00 +02:00
Safihre
1c6419ea65 Revert "Paused status for individual download trumps Force priority"
Oops, I was wrong. This reverts commit d06c11673f.
2017-09-04 20:51:05 +02:00
Safihre
a2adeffc1a Correctly count all bytes and drop 'missing' use 'mbmissing'
Reporting number of missing articles makes 0 sense, it's the MB that matters.
2017-09-04 20:39:15 +02:00
Safihre
71fa3c544a Also shjow scanning of extra files for par2cmdline/tbb 2017-09-04 14:54:41 +02:00
Safihre
b739fb7f07 Do not count overhead-bytes for NZO statistics 2017-09-04 14:18:55 +02:00
Safihre
860728beae Show counter when Multipar is scanning other files in the directory 2017-09-04 13:58:50 +02:00
Safihre
1bdbf1c6a8 Show different icon when priority is Force 2017-09-04 10:59:48 +02:00
Safihre
abbed4cd77 Show missing articles starting at 2% 2017-09-04 10:59:18 +02:00
Safihre
d06c11673f Paused status for individual download trumps Force priority 2017-09-04 10:57:02 +02:00
Safihre
67d67f5ff6 Correct typo in reject_duplicate_files
Closes #1021
2017-09-03 17:07:41 +02:00
Safihre
2386d65b84 Extrapars could be empty if not +Repair set for job 2017-09-03 16:54:07 +02:00
SABnzbd Automation
be638ecca1 Automatic translation update 2017-09-03 08:23:45 +00:00
Safihre
c32bcea3f6 Re-update text files for 2.3.0 Alpha 1 2017-09-03 10:16:08 +02:00
Safihre
bf0aa6569b Need to do comparison to true number of servers
Previous solution was too simple and could still fail.
2017-09-03 10:12:52 +02:00
Safihre
e64df8ed60 Only allow 1 NZB to be added at the same time 2017-09-02 19:18:13 +02:00
Safihre
f7c3a4381d More efficient to compare TryList sizes
Only less is more.
2017-09-02 17:45:32 +02:00
Safihre
55efb34f03 Allow duplicate filenames by default 2017-09-02 15:39:55 +02:00
Safihre
ae8e9d83f1 Less CPU intensive anti-stall fix 2017-09-02 15:27:31 +02:00
SABnzbd Automation
c4406df73f Automatic translation update 2017-09-02 09:23:23 +00:00
Safihre
12004802b6 Allow users to set custom basepath
Closes #904
https://forums.sabnzbd.org/viewtopic.php?f=11&t=22511
2017-09-02 11:10:13 +02:00
Safihre
6068ca6376 Disable CherryPy timeout monitor
Besides throwing errors, it doesn't really help anything. The actions still get performed. 
See also: https://github.com/cherrypy/cherrypy/issues/1625
2017-09-02 09:50:55 +02:00
Safihre
613ba49165 Only really count passwords that came from the passwords file 2017-09-01 21:21:48 +02:00
Safihre
9cd21d84ee Only reset trylist once when reset of nw
Also happens within the decode()
2017-09-01 13:36:04 +02:00
SABnzbd Automation
8d813f125e Automatic translation update 2017-08-31 14:44:35 +00:00
Safihre
cb66bc28ab create_default_context only availble on Python 2.7.9+ 2017-08-31 13:14:29 +02:00
Safihre
30b13b1856 Small tweaks to startup logging 2017-08-30 23:59:27 +02:00
Safihre
67a133068c Move logging of number of certificates to Debug only
It's a bit slow, can take up to 1.5 seconds of startup time.
2017-08-30 23:48:07 +02:00
Safihre
731a3bcb22 Move Bonjour/ZeroConfig to after SABnzbd start
Slow to mount or to fail (1-2 seconds)
2017-08-30 23:25:49 +02:00
Safihre
724ec8ca9f ZeroConfig could crash due to UTF8 hostname 2017-08-30 23:21:54 +02:00
Safihre
d28f775c71 Remove information about SSL/TSL Protocols because it is inccorect
There is no way to get the actually enabled SSL/TLS protcols on a system, let along from Python. It's not even possible from the `openssl` command line.
See also #994
And: https://stackoverflow.com/questions/45924030/get-available-ssl-tls-protocols-in-python-2-7
2017-08-30 23:15:19 +02:00
Safihre
a834c1c7a7 Add Refresh button to Glitter when Refresh Rate > 2
Linked #842
2017-08-30 15:28:17 +02:00
Safihre
04e595e706 Update text files for 2.3.0Alpha1 (2) 2017-08-29 23:53:36 +02:00
Safihre
46c28dbf68 Do not show dropdown for URL-grabs that are paused
Setting changes are not propegated to the actual downloading, could be optimized.
2017-08-29 23:50:54 +02:00
Safihre
8594bfe817 Timing of try-list check could cause stalling
My god I hate this stalling problem, it haunts me in my dreams.
2017-08-29 23:42:27 +02:00
SABnzbd Automation
e61a01512b Automatic translation update 2017-08-29 16:04:48 +00:00
Safihre
657e3bb594 Update text files for 2.3.0Alpha1 2017-08-29 16:42:34 +02:00
Safihre
bbbdca6a00 Update INSTALL.txt 2017-08-29 16:01:59 +02:00
Safihre
a95c705e4c Auto-disconnect if the last item was deleted from the Queue 2017-08-29 10:02:25 +02:00
Safihre
446c5ba80f Block for more reasons so that try-lists get reset 2017-08-29 09:06:38 +02:00
Safihre
7641c0e1cc Correct Retry function after par2-changes 2017-08-27 23:56:24 +02:00
Safihre
42fdd9c890 Flexible block-search
Also add blocks from setnames that are a lot a like. Especially in those cases with multi-sets where the sets have the same filenames.
2017-08-27 22:48:30 +02:00
Safihre
dca63878db Force priority should be listed as 'Downloading' even if Queue paused
Closes #1012
2017-08-27 22:37:13 +02:00
Safihre
dc67fc414c Do not rely on par2/Multipar parsing to detect par2 files of the parset
We know much bettter now we do full parsing of each par2 file that comes in.
2017-08-27 20:09:40 +02:00
Safihre
90be3cc5a0 Only remove par2's that really belong to the set 2017-08-27 18:49:03 +02:00
Safihre
42cdba5ce3 Improve Prospective Par2 to handle multisets better
Now we get too many blocks, but before the second set wouldn't get enough blocks
2017-08-27 18:32:13 +02:00
Safihre
9c069cfb2c Let par2/MultiPar only remove used par2-files
In case of duplicae filenames we don't want "base.1.par2" to be removed when checking "base.par2".
2017-08-27 17:41:44 +02:00
Safihre
544b420baa Correct get_files API-call to only show really queued files 2017-08-27 16:18:48 +02:00
Safihre
094c96f270 Improve obfuscated and broken par2 handeling
Linked #998.
- In case of full obfuscation we detect the files directly after assembling and rename it such that par2 will pick it up
- Turns out we do need 'partable' to keep track what is the main par2 file
- Handle missing of first par2 file, automatically fetch next one
2017-08-27 14:49:01 +02:00
Safihre
aa7bad56f0 Fix get_unique_filename function 2017-08-27 14:42:08 +02:00
Safihre
977f4e1036 Rework par2-handeling
See #998
- Seperate par2 functions in new file
- Always check for par2-signature if file is not a rar-file
- Always parse par2 contents for signatures (hash -> filename)
- Use the signatures to keep track of par2-packs, not basenames
- Remove seperate 'extrapars' for NZF's, all in the NZO
- Remove 'partable' for NZO, it's all in it's 'extrapars'
2017-08-27 12:23:17 +02:00
Safihre
e05a98d22b Do not show 'Apply filters' from the start
Nothing more we can do to speed up the RSS evaluation.
Closes #679
2017-08-26 18:57:47 +02:00
Safihre
452e955a1e Disable extra features of feedparser
NZB-feeds do not contain the properties that feedparser has fancy parsing for.
The reduces the time spent in feedparser by 40%. 
Linked #679
2017-08-25 22:59:40 +02:00
Safihre
a724f6a979 Revert "Remove locks from ArticleCache"
This reverts commit 5e7558ce4a.
2017-08-25 21:59:11 +02:00
Safihre
715b25b52f More logging when adding NZB's 2017-08-25 21:59:07 +02:00
SABnzbd Automation
4d3f370b3a Automatic translation update 2017-08-25 16:04:37 +00:00
Safihre
07f6717728 Duplicate files in NZB could result in broken unpack after repair
Because par2 would detect them, but not use them. So ".1" files would later be used for unpack, even though it's not a real set.
2017-08-25 16:59:01 +02:00
Safihre
35b4aa6b7a Ignore unpack errors in duplicate rarsets
Multipar and par2tbb will detect and log them so we can remove them, but par2cmdline will not.
2017-08-25 16:58:56 +02:00
Safihre
a7a04d912c Use fileobj to prevent having to chdir, which can crash on macOS
If something is "wrong" with the current directory, for example when SABnzbd is started after downloading in a sandbox by macOS security, this function can error and break the adding of NZB's.
Have to use a fileobj, otherwise GZip will put the whole path inside there.
2017-08-25 10:51:52 +02:00
Safihre
ce558b0850 Original files would be deleted after a MultiPar rename 2017-08-25 10:51:25 +02:00
Safihre
8ab7c294ee Do not fail a job if recursive unpack fails
The user can handle it, we did our part.
2017-08-25 09:19:29 +02:00
Safihre
306228462e Use existing texts for wizard ad translations
Yeah, my fault. I deleted them in a clean-up of the skintext during 1.0.0 development. Very stupid.
2017-08-25 09:19:15 +02:00
SABnzbd Automation
9e7a8468e2 Automatic translation update 2017-08-24 15:24:59 +00:00
SABnzbd Automation
4c2445485a Automatic translation update 2017-08-23 23:13:38 +00:00
Safihre
103c46e2b4 Translation fix for unpack warning 2017-08-23 22:54:40 +02:00
Safihre
b4922d69a2 Pause between unpacks on Windows, otherwise subprocess_fix overloads
Strange but true, but on jobs with many small files to unpack, it would just fail.
2017-08-23 22:50:40 +02:00
Safihre
110a06a3cd Handle '482 Download limt exceeded'
Closes #1009
2017-08-23 22:50:27 +02:00
Safihre
6f0f67110f Only auto-disconnect after first run of verification 2017-08-23 22:48:53 +02:00
Safihre
848721da84 Clean last functions from the real anti-stalling fix 2017-08-23 22:10:06 +02:00
Safihre
127d7ab40c The real anti-stalling fix
Woohoo!
For each NZF (file) make sure all articles have tried a server before marking it as tried. Before if articles were still in transit they could be marked as tried on NZF level before the server could get to them,
2017-08-23 15:43:54 +02:00
Safihre
4fb7246082 TryList reset at NZO level also nessecary
Timing issue between when a new server is selected and when a job is added to the NZO-level try-list. Locks were tried, but failed.
2017-08-23 09:11:34 +02:00
Safihre
8c42237d51 Correct handeling of TryList when server has timeout 2017-08-23 09:11:29 +02:00
Safihre
6a87f0c4e4 Correctly remove + from INFO label in all languages 2017-08-23 09:11:24 +02:00
Safihre
f8630a878c Correct redirect after ports change 2017-08-22 10:11:41 +02:00
Safihre
7f6ef5e204 Only iterate over RSS feeds when there are feeds 2017-08-22 09:52:25 +02:00
Safihre
547d4dbf0a Only discard really non-unique hashes from md5of16k 2017-08-22 09:25:34 +02:00
Safihre
65e70a431c Show hover-title that the compress icon is Direct Unpack 2017-08-22 09:13:40 +02:00
Safihre
f85f4de5ff Error when applying changes to RSS-feeds
Closes #1005
2017-08-21 20:25:34 +02:00
Safihre
97644dea16 Show more clear error message when UnRar of Par2 is missing 2017-08-21 16:27:04 +02:00
SABnzbd Automation
0a6105ebc1 Automatic translation update 2017-08-21 07:18:33 +00:00
Safihre
de3d4f8d14 UnRar is required to read some RAR files 2017-08-21 08:26:12 +02:00
Safihre
f337053aea Add error when NZO creation fails 2017-08-20 20:45:55 +02:00
Safihre
2a4b49a679 Warning "Invalid par2 files" can also be due to bad Par2-parameters 2017-08-20 16:41:18 +02:00
Safihre
ebe526f8cf Also hide email-accounts in logging 2017-08-20 10:33:24 +02:00
Safihre
926cd7b132 Take the risk of allowing up to 5 bad articles in jobs without Par2 2017-08-20 01:35:06 +02:00
Safihre
99667aa410 Remove obsolete code from RSS 2017-08-20 01:20:56 +02:00
Safihre
67cab3465e Remove extra checks from RSS file-store loading 2017-08-20 00:31:22 +02:00
Safihre
ce68a0654b Scheduler Priority/Category Pause/Resume also affects URL-grabbing
Linked #988
2017-08-19 23:59:14 +02:00
Safihre
dffdc3ae1f Extend Scheduler to Pause/Resume based on category
Closes #549
2017-08-19 23:56:51 +02:00
Safihre
28e5311c6c Change logging line of SSL-protocols
Linked #994
2017-08-19 22:52:33 +02:00
Safihre
ebf0526420 Do not show NZB-age when it is nonsense
For example with a grabbing NZB.
2017-08-19 21:49:03 +02:00
Safihre
a2f73ca1f0 Add feed name to URL-grabbing title
Closes #988
2017-08-19 21:40:43 +02:00
Safihre
8ca150c48d Allow pausing of URL-grabbing
Linked #988
2017-08-19 21:20:28 +02:00
Safihre
d765fa09f1 Change certificate-file location for distributions 2017-08-19 20:33:46 +02:00
Safihre
878d68d343 Warn in case the password file has too many passwords 2017-08-19 16:06:06 +02:00
Safihre
fc7bd78dfa Translation changes 2017-08-19 13:28:21 +02:00
SABnzbd Automation
aca6ed360c Automatic translation update 2017-08-19 09:17:50 +00:00
Safihre
53672d1d73 Reset all NZO TryList when doing Prospective Add
I thought in c14b3ed82a that this was enough, but clearly it is not.
2017-08-18 19:36:20 +02:00
SABnzbd Automation
07d316ed4f Automatic translation update 2017-08-18 15:44:05 +00:00
Safihre
8aa57bf406 Update Config jQuery to 3.2.1 and Peity to 3.2.1 2017-08-17 11:00:47 +02:00
Safihre
d369097573 Update KnockoutJS to 3.4.2 2017-08-17 11:00:47 +02:00
Safihre
f4960715fa Do not run get_new_id forever in case of problems
#984
2017-08-17 11:00:47 +02:00
Safihre
83ba676c43 Correct loading of extra certificate files
Also include logging of number of found certificates
2017-08-17 11:00:47 +02:00
Safihre
12cca9dea1 Provide certificates with macOS and Windows build
We have to bring our own certficates on Homebrew Python 
The certifi package brings the latest certificates on build 
This will cause the create_default_context to load it automatically
We also use this on Windows so users are always up-to-date
2017-08-17 11:00:47 +02:00
Safihre
5f52535c44 Faster look-ups when restoring queue 2017-08-17 11:00:47 +02:00
Safihre
2f95410ab4 Allow for older queue-files 2017-08-17 11:00:47 +02:00
Safihre
5749c0c008 Keep TryList state
Part 1 of #973
2017-08-17 11:00:47 +02:00
Safihre
73c71ef4bf Allow filenames with also 1-char file extension 2017-08-17 11:00:47 +02:00
Safihre
6bc1c51013 Remove server specific categories
Finally. This feature has only caused problems.
2017-08-17 11:00:47 +02:00
Safihre
bcdd3302a6 Stall prevention by checking TryList 2017-08-17 11:00:47 +02:00
Jonathon Saine
0fc3b60054 Add propercheck to allow PROPER/REAL/REPACK releases to bypass the series episode dupe check. 2017-08-17 10:54:57 +02:00
Safihre
e9b0d4d691 On to 2.3.0 we go 2017-08-17 10:53:32 +02:00
SABnzbd Automation
0dc2c6687d Automatic translation update 2017-08-17 08:43:38 +00:00
Safihre
b061e582b6 Update text files for 2.2.0 2017-08-16 22:56:10 +02:00
Safihre
690731fd79 Update wizard Ad URL 2017-08-16 13:34:01 +02:00
Safihre
068b7ed7f5 Disk-speed test for Direct Unpack would cause restart 2017-08-15 21:53:24 +02:00
Safihre
aae2fdcd32 Update Unrar to 5.5.0 for Windows and macOS
Right on time.
Closes #935
2017-08-14 20:37:11 +02:00
Safihre
d3628a1eb7 CherryPy 8.1.2 - Catch the [Errno 0] Error
Untill a fix is found.
2017-08-13 16:58:20 +02:00
Safihre
9cc8176d87 Server-tests were broken due to deprecation warning
Linked #996
2017-08-12 14:52:34 +02:00
Safihre
27f83f21be Update text files for 2.2.0RC3 2017-08-12 11:39:28 +02:00
Safihre
5e31a31a21 Make Server charts timezone-proof
Closes #997
2017-08-12 21:23:18 +12:00
Safihre
a077012478 Windows fix for subprocess would break when options were not specified 2017-08-12 09:42:47 +02:00
Safihre
fed0e0f765 Use win32api for power-options on Windows 2017-08-12 09:39:25 +02:00
Safihre
fbdbf7ab22 Improve par2 handeling by always parsing md5of16k and checking new sets
- We postpone only par2-files with actual blocks, in case of duplicate named par2 files that are of different sets we want all base-par2 files.
- The md5of16k is now calculated for every par2 file we get so we can rename everything.
- We also check during assembly if maybe a md5of16k is now available, in case the par2 file came in later.
- If a par2 file comes in, we double check if maybe this pack was not known yet. The setname might not be unique. This way we make sure that everything gets verified at the end.

Still need obfuscation improvements, but that's for later.
Linked #998
2017-08-12 00:33:32 +02:00
SABnzbd Automation
f013d38d00 Automatic translation update 2017-08-10 11:45:54 +00:00
Safihre
93b9c8a6da Correctly escape the values in EN.po 2017-08-10 13:33:11 +02:00
SABnzbd Automation
e3a779bbc6 Automatic translation update 2017-08-09 21:44:13 +00:00
Safihre
adfce8c8b6 Update text files for 2.2.0RC2 2017-08-09 23:03:44 +02:00
Safihre
a49d68c0db Double check that we have pynotify version we can work with
Closes #992
2017-08-09 22:56:07 +02:00
Safihre
e4156e76d1 Disable auto-zoom on mobile for adaptive pages 2017-08-09 22:50:53 +02:00
Safihre
35b66eea0e Add more space to Config header just in case 2017-08-09 18:45:01 +03:00
Safihre
4d0cf8d45f Correct naming and small style fix 2017-08-09 18:36:20 +03:00
Safihre
ad9fef5f41 Prevent SQL injection via category-argument with ' in them 2017-08-09 17:43:17 +03:00
Safihre
6235174995 CherryPy 8.1.2 - Correct f6c163b: macOS "Protocol wrong type for socket"
https://github.com/cherrypy/cheroot/pull/31
https://github.com/cherrypy/cheroot/pull/44
2017-08-09 09:55:04 +03:00
Safihre
4b9ca989c4 Correctly log the rar-files used by DirectUnpack
Not just assume we used them all.
2017-08-08 13:39:03 +03:00
Safihre
4d54aecceb Check the workdir after extraction if we got everything
Sometimes there might be a file left that we did not extract. For example there are NZBs that have the main download in abc.part01.rar etc but also a abc.rar file with the NFO or other things. 
SABnzbd would detect this as 1 set and not unpack both.
2017-08-08 13:37:09 +03:00
Safihre
11eeb6f2e9 Glitter filelist would not show 100% for very small files
Plus some cleanup of move-to-top/bottom code
2017-08-08 12:19:07 +03:00
Safihre
00364b1317 Only update RSS URI if it was modified
See #993
2017-08-08 12:04:54 +03:00
shypike
6666663f78 Fix typo in text about "allow duplicate files" 2017-08-08 09:17:25 +02:00
Safihre
3d6dfec47a Disk-space check in Assembler and check if space to write current file 2017-08-06 19:01:59 +03:00
Safihre
0f3d44aa4b safe_fnmatch should only do the matching
Oops, globber and globber_full are not the same!
2017-08-06 12:57:23 +03:00
SABnzbd Automation
d2d2471950 Automatic translation update 2017-08-06 09:42:54 +00:00
Safihre
b71343e8ab Safe fnmatch everywhere, just to be sure
Linked #990
2017-08-06 11:46:00 +03:00
Safihre
489f3f4ba0 Catch special chars like "[]!*" to break fnmatch and thus repair
Closes #990
2017-08-06 11:25:27 +03:00
Safihre
3765e8c350 Add warning when many duplicate files were discarded
Linked #531, Closes #986
2017-08-02 12:50:26 +02:00
Safihre
28d4f527b8 Fix the server-graphs
They did not display anything if it was the first of the month. Plus some style-fixes.
2017-08-01 13:31:18 +02:00
Safihre
1d8af8f97d Correct counts if one_folder is enabled 2017-08-01 11:28:34 +02:00
Safihre
829ef4bee8 Only one_folder is a reason not to delete the folder in DircetUnpack 2017-08-01 11:06:40 +02:00
Safihre
7e40c12e47 Get rarfiles to delete from both RarFile and parsing Unrar output
RarFile will fail to list all volumes when the job is encrypted, it will only list the first volume. But parsing the output of Unrar will fail on special-char filenames (probably limited to Windows). So now only jobs that are encrypted *and* have many special-chars will not have all rars deleted correctly,
2017-07-31 23:45:56 +02:00
Safihre
37d8d659f5 Show deprication warning for Server Categories 2017-07-31 22:26:21 +02:00
Safihre
0a29291be2 Abort all direct unpackers when disk-full 2017-07-31 22:26:04 +02:00
Safihre
7f3a5f309b Don't postpone if all par2 are desired and should be kept
Prevents downloading of all par2 in case people just wanted to keep some par2, but not all. If people really want all and no-cleanup, they should enable 'All par2'
2017-07-31 16:51:48 +02:00
Safihre
60ec5f9191 Less aggresive cache-busting 2017-07-31 10:38:45 +02:00
Safihre
d03e801e74 Only show import warnings when not gone
Can be removed if #952 is resolved
2017-07-30 20:29:28 +02:00
Safihre
56bf484e77 Also show Verifying Repair status for par2cmdline 2017-07-30 16:51:28 +02:00
Safihre
66674469d5 Return of the DIR_LOCK
Yeah, turns out we probably do need that. In case automation adds a new job while the old one is still getting deleted. Hopefully solving #952
2017-07-30 15:57:34 +02:00
Safihre
09a86683e5 Retry icon was too red in Glitter Night 2017-07-30 15:56:49 +02:00
Safihre
fc9a13879e CSS Tweaks to Night theme 2017-07-30 11:32:35 +02:00
Safihre
73f0885566 Schedule to deprecate Categories setting for Servers in next release
This option only gives headaches because users do not use it correctly or edge cases where downloads stall. 
If users report that they really want to keep this option, we can of course consider it.
2017-07-29 13:29:10 +02:00
Safihre
090b22f193 Revert "CherryPy 8.1.2 - Catch error of Python 2 in combination with new OpenSSL"
Not catching the right error because it's of the general Error class, not SSLError.
2017-07-28 18:46:46 +02:00
Safihre
f9c092ae8f Move Notification Script up on the Config page and add Wiki-link
So they can see other services
2017-07-28 09:52:25 +02:00
Safihre
4246bc2aea Move enable_meta to more logical spot 2017-07-27 16:00:40 +02:00
Safihre
c4fa047393 Automatic redirect to login page in Glitter 2017-07-27 14:56:34 +02:00
SABnzbd Automation
22e4d24a71 Automatic translation update 2017-07-27 12:29:06 +00:00
Safihre
96fccff63b Update text files for 2.2.0RC1 2017-07-27 13:50:41 +02:00
Safihre
6b46a15b49 Include indexer-hostname in the API-key missing warning 2017-07-27 11:50:12 +02:00
Safihre
98cc0dad55 API-key was always required for Indexer-feedback
Remove oznzb specific links for realy now.
Indexers should provide their rating-url in the headers/meta-data. Keeping the Specials setting for backwards compatibility for current OZnzb users.
2017-07-27 11:48:00 +02:00
Safihre
6fdeab6948 Permanent notice in Plush to upgrade to Glitter 2017-07-27 10:35:26 +02:00
Safihre
553dd04cea Restore 'Use tags from indexer' switch
https://forums.sabnzbd.org/viewtopic.php?f=11&t=22864
2017-07-27 09:52:59 +02:00
Safihre
0baa316a72 Revert "Remove enable_meta"
This reverts commit ff3c46fe1f.
2017-07-27 08:35:06 +02:00
Safihre
3ac209f9a9 CherryPy 8.1.2 - Catch error of Python 2 in combination with new OpenSSL
Closes #853
2017-07-26 16:34:45 +02:00
Safihre
ec55f64a8a Correctly remove files when flat_unpack or no-job-folders set
Results could be pretty bad otherwise: https://forums.sabnzbd.org/viewtopic.php?f=11&p=112774
2017-07-26 09:30:39 +02:00
Safihre
932e1e577b Do not double check 'part' in RAR filename
Already caught by the regex, otherwise filenames that have "part" somwhere in the name will not be used for direct-unpack.
2017-07-26 08:59:26 +02:00
Hellowlol
56d5b1d9f8 Make bonjour easier to setup. 2017-07-25 15:52:30 +02:00
Hellowlol
28bdebb147 Let zeroconf know if we are using https. 2017-07-25 15:52:30 +02:00
Safihre
827fc7b64e Direct Unpack would block forever if file was missing 2017-07-25 11:58:29 +02:00
Safihre
f5dde93644 Change History Retention label to fit
Closes #978
2017-07-24 19:21:43 +02:00
Safihre
60c574828e Sort Servers in Download Report
Closes #979
2017-07-24 19:14:31 +02:00
Safihre
14ca8342f9 Update wiki URL to 2.2 2017-07-24 13:36:16 +02:00
Travis
5d5b1bf053 Automatic translation update 2017-07-24 08:16:18 +00:00
Safihre
ea4cdba3eb Update text files for 2.2.0Beta2 2017-07-24 10:08:21 +02:00
Safihre
d6ecebc75a Use existing texts for Tag duplicate 2017-07-24 10:07:15 +02:00
Jonathon Saine
b0af6a1761 User requested a way to track dupes but not have sab block/discard/pause the items (as he doesnt want to have to manually unpause). Figured we could add a 'Tag' switch for the Dupe detection. Works same way as Pause (shows duplicate tag in queue/adds warning/internally tags it) but does not pause the item.
Once the file is done downloading and makes it to the queue, you have no idea it was a duplicate.
You would have to use the 'Warning' and search for that.. and then assume the newer one was the dupe... we really should expose in the History that an item was a duplicate. Right now the 'search' box only looks at the name.. and I dont think we should be messing with the name to add duplicate there. I'd rather us just add a tag/flag whatever to notate this.. and can filter/sort on it. We also should do the same to the Queue as well.. since changing the name of the item to be `DUPLICATE / XXXXX` is a little jarring.

Now if we just exposed the dupe flag in the history, then the whole 'tag' option really isnt needed as it was just a means to an end.
2017-07-24 10:00:15 +02:00
Safihre
6e350f30fc Add midnight auto history-purge and modify texts 2017-07-24 09:47:05 +02:00
Safihre
169137c631 Implement History Retention setting
Closes #678
2017-07-24 09:47:05 +02:00
Safihre
6393dc0dca Remove history_limit from Specials 2017-07-24 09:47:05 +02:00
Safihre
1a27b4824b Style improvements to Queue and Server Graphs
Closes #977
2017-07-24 09:36:48 +02:00
Safihre
2b59a383cf Change label in History-status to 'Direct Unpack' 2017-07-24 09:05:02 +02:00
Safihre
efbaaade22 Correct server graph timezone effects and only show months that we have 2017-07-23 23:14:10 +02:00
Safihre
cf7e7b1f62 Only show usage data for days that have passed 2017-07-23 23:14:10 +02:00
Safihre
2c1746a92d Show Montly usage graphs per server in Config 2017-07-23 23:14:10 +02:00
Travis
a2e57fd3d8 Automatic translation update 2017-07-22 17:27:02 +00:00
Safihre
932f8d9176 Wizard access was not limited by login and external access rules
Bad bad 
Closes #972
2017-07-22 18:54:33 +02:00
Safihre
5ffd82da89 Show vote up/down instead of Video/Audio score 2017-07-22 00:19:40 +02:00
Safihre
8b3de191d9 Add Retry All Failed button to Glitter 2017-07-22 00:11:13 +02:00
Travis
83d8a23e2c Automatic translation update 2017-07-20 22:13:49 +00:00
Safihre
58b107a4b5 Allow up to 5 missing/CRC'ed errors before cancelling Direct Unpack
Sometimes a CRC error is not so bad it turns out
2017-07-20 23:55:36 +02:00
Safihre
a40609b39d Direct Unpack was started before whole file was written to disk
In case part 2 came in before part 1.
2017-07-20 23:55:36 +02:00
Sander Jo
0faa5d3dff Print applied permissions in octal 2017-07-20 21:04:00 +02:00
Travis
374239777e Automatic translation update 2017-07-19 07:55:50 +00:00
Safihre
9a7701d7e6 Update text files for 2.2.0Beta1 2017-07-19 09:33:00 +02:00
Safihre
01ff04f338 Allow Aborting of Direct Unpack during PP and add Completed label 2017-07-19 09:27:24 +02:00
Safihre
eac39767dd Renames on Retry only when defined
Otherwise if it's None, later this will happen:
original_filename = self.renames.get(nzf.filename, '')
AttributeError: 'NoneType' object has no attribute 'get'
2017-07-19 09:23:58 +02:00
Safihre
0d0adf99fa Proper counting of bad articles for DirectUnpack & Prospective Par2 2017-07-18 22:07:56 +02:00
Safihre
16905ce34f Show filename for Unzip instead of Path and show start of Verification 2017-07-18 21:16:05 +02:00
Safihre
5287fa8a0c Stability improvements for Direct Unpack
Now shows the time spent in unpacking and many other bugs squased.
2017-07-18 21:15:30 +02:00
Safihre
b72ab4fb8e Allow concurrent unpacking 2017-07-18 15:14:28 +02:00
Safihre
81054c675c Mimimum speed for Direct Unpack lowered to 40MB/s
It is tested during downloading, so if 40MB/s is still possible then we should be good to go.
2017-07-18 13:51:13 +02:00
Safihre
7362be8748 Group cfg settings by Config section
It was a big mess. 
Now they still could be sorted within each section.. next time.
2017-07-17 20:42:54 +02:00
Travis
b4ba2b3463 Automatic translation update 2017-07-17 18:33:42 +00:00
Safihre
8bed6938c1 Change text in DirectUnpack Enabled message
See also #966
2017-07-17 20:11:50 +02:00
Safihre
ecf16f6201 Show DirectUnpack progress the same as Unpack progress: xx/xx 2017-07-17 17:07:44 +02:00
Safihre
bf240357df Regressions in preparation of extraction path
Thanks @Cpuroast
2017-07-17 16:45:58 +02:00
Safihre
ddcf447957 Add missing save_config after modifying settings
Closes #966
2017-07-17 10:10:20 +02:00
Safihre
d9642611e2 Correct error in missing notify options
#966
2017-07-16 20:28:15 +02:00
Safihre
0018c6f263 Move regex to top and increase save-timeout 2017-07-16 19:26:35 +02:00
Safihre
6398bfa12f Use speed from download-log instead of re-calculating
Closes #829
2017-07-16 19:22:52 +02:00
Safihre
01dfb7538d Correct FileList Move to Top/Bottom CSS for Firefox 2017-07-16 14:28:04 +02:00
Safihre
3f0d4675b6 Fix CSS for Direct Unpack and Move to Top/Bottom 2017-07-16 14:18:50 +02:00
Safihre
f23c5caf80 Fix typo in DirectRenamer for non-Windows 2017-07-16 13:55:36 +02:00
Safihre
bd22430b26 Update text files for 2.2.0Alpha3 2017-07-16 11:04:17 +02:00
Safihre
1189a7fdbc Use tuple in endswith for Direct Unpack
Thanks @hellowlol
2017-07-16 10:59:00 +02:00
Safihre
f3aa4f84fc Remove waiting-time between URLGrab's
Other newsreaders grab multiple URL's at once, so no need for us to wait.
2017-07-16 10:40:39 +02:00
Safihre
ea26ce4700 Remove non-seperator RSS-url commas by detecting if they are valid URLs
Closes #965
2017-07-16 10:30:09 +02:00
Safihre
a1e649b7e2 Correct error in PAR_Verify with renames 2017-07-15 23:43:32 +02:00
Safihre
3b9f2b2cf0 Remove par2classic/cmdline for Windows and macOS 2017-07-15 23:33:20 +02:00
Safihre
7333d19e1c Notifications selection based on Categories
Closes #716
2017-07-15 22:22:20 +02:00
Safihre
232d537d23 Correct Direct Unpack locking behavior for multisets 2017-07-15 17:02:20 +02:00
Safihre
c6e17e7bcb Duplicate par2-16k values need force-remove 2017-07-15 17:02:20 +02:00
Safihre
54c6fd55dd Detection of forbidden-Windows names altered
Now we already sanatize the name during Assembler and when we have to make decisions for Unrar/Par2 we need to know if they might create something unsafe.
2017-07-15 17:02:20 +02:00
Safihre
0625aa1ca8 Make sure all Par2-16k signatures are unique, also in multisets 2017-07-15 17:02:20 +02:00
Safihre
83643f3298 Remove allow_streaming
Bit redundant now we have DirectUnpack
2017-07-15 17:02:20 +02:00
Safihre
ff3c46fe1f Remove enable_meta 2017-07-15 17:02:20 +02:00
Safihre
0930f0dcee Test disk-speed first time DirectUnpack is called 2017-07-15 17:02:20 +02:00
Safihre
3221257310 UnRar's ERROR is also an error
And add starting file to log.
2017-07-15 17:02:20 +02:00
Safihre
8048a73156 Handle active DirectUnpacker in postproc better 2017-07-15 17:02:20 +02:00
Safihre
ea552cd402 Cancel DirectUnpack when the final name changes 2017-07-15 17:02:20 +02:00
Safihre
dcb925f621 Case insensitive matching for DirectUnpack sets 2017-07-15 17:02:20 +02:00
Safihre
cce91e1985 DirectUnpacker should stay to listen to new sets 2017-07-15 17:02:20 +02:00
Safihre
e17d417c2e Re-introduce locks for TryList
After studying everything, it really needs it. Closes #738
2017-07-15 17:02:20 +02:00
Safihre
a69f5bd2df Prevent DirectUnpack locking the PostProcessing 2017-07-15 17:02:20 +02:00
Safihre
97e53eb4d3 Better DirectUnpack percentage counter 2017-07-15 17:02:20 +02:00
Safihre
a6da2b7bee Prevent possible crash in par2_repair 2017-07-15 17:02:20 +02:00
Safihre
4a21e7c217 Show percentage of DirectUnpack, when available 2017-07-15 17:02:20 +02:00
Safihre
9bd3c7be44 Increase maximum number of unpackers
Unrar takes almost no memory anyway
2017-07-15 17:02:20 +02:00
Safihre
434f5c4b2d Remove Audio/Video quality rating icons from Queue 2017-07-15 17:02:20 +02:00
Safihre
d3cc4f9f07 Direct Unpack indicator for Queue 2017-07-15 17:02:20 +02:00
Safihre
a16aa17c17 Don't start when not set to +Unpack and abort if Category changed 2017-07-15 17:02:20 +02:00
Safihre
68445d0409 Full working implementation of DirectUnpack with multi-sets 2017-07-15 17:02:20 +02:00
Safihre
32b68a45cc Integrate with PostProc 2017-07-15 17:02:20 +02:00
Safihre
345f8359cc Unpack to the right directory (with Sorter support) 2017-07-15 17:02:20 +02:00
Safihre
81f9886584 Add Direct Unpack to Config 2017-07-15 17:02:20 +02:00
Safihre
adbc618808 Improvements to detection of volumes 2017-07-15 17:02:20 +02:00
Safihre
41eafc6b4b Become set-specific 2017-07-15 17:02:20 +02:00
Safihre
9f18d8e8c1 Basic working Direct Unpack
Lots to do
2017-07-15 17:02:20 +02:00
Safihre
8c2c853166 Make sure to always have lowest part number 2017-07-15 17:02:20 +02:00
Safihre
97914906a0 Also handle GNTP errors during sending 2017-07-14 14:43:39 +02:00
Safihre
f1ce4ed19b Correctly handle new GNTP errors 2017-07-14 14:41:03 +02:00
Safihre
99185d8151 Update GNTP to 1.0.3
Closes #334
2017-07-14 14:25:07 +02:00
Safihre
385b6b7ade Remove QCHECK_FILE again 2017-07-14 14:25:07 +02:00
gwyden
81ea513f8c Added buttons and logic to move to top and bottom of download queue (#962)
* added buttons and logic to move to top and bottom of queue
* allowed for a larger control box for the new buttons
* Cleanup of unnecessary code
* Simple top and bottom of queue using existing queue data
2017-07-13 23:52:43 +02:00
Safihre
336b1ddba3 Always remove forbidden Win-devices from filenames
This breaks support for par2cmdline on Windows with forbidden names. Assuming no users that have disabled both Multipar *and* par2_multicore
2017-07-12 18:38:19 +02:00
Safihre
7274973322 Shorten par_cleanup code 2017-07-12 18:38:19 +02:00
Safihre
af132965de Revert "Remove QCHECK_FILE, not needed"
This reverts commit 4f8cc3f697.
2017-07-12 18:38:19 +02:00
Safihre
5586742886 Use RarFile.volumelist to get list of used rar-volumes 2017-07-12 18:38:19 +02:00
Safihre
5868b51490 Use fix to allow unicode arguments to POpen on Windows 2017-07-12 18:38:19 +02:00
Travis
7f17a38b9b Automatic translation update 2017-07-12 15:10:14 +00:00
Safihre
415e843ebb Remove 'WARNING:' label from Assembler warnings
It was inconsistent with other messages
2017-07-11 13:33:50 +02:00
Safihre
7ffc1192bb Only par2-rename when actually different 2017-07-11 12:00:36 +02:00
Safihre
945e769a03 Also performe prospective-par2 on renamed files 2017-07-10 23:06:05 +02:00
Safihre
86c7fb86cc Ignore first-16k par2 info if it's not unique 2017-07-10 22:51:17 +02:00
Safihre
ff20f3f620 Fix possible unicode error in tvsort and typo in newsunpack
Closes #950
2017-07-10 21:56:07 +02:00
Safihre
e8bef94706 Correctly handle renames on (multiple) retries 2017-07-10 21:03:37 +02:00
Safihre
d05fe2d680 More uniform handeling of renames 2017-07-10 20:53:31 +02:00
Safihre
4f8cc3f697 Remove QCHECK_FILE, not needed 2017-07-10 19:54:59 +02:00
Safihre
6fa619fa37 More robust renaming based on par2 first-16k info
Also when the correct name is
2017-07-10 17:40:39 +02:00
Safihre
a43f5369ea Do not rename .par2 filenames from NZB
They are usually correct, if mentioned at all
2017-07-10 17:29:34 +02:00
Safihre
2040173dc2 Rename parts of Assembler to be more coherent 2017-07-10 17:20:03 +02:00
Safihre
a15b7ec7ac Remove Windows utf8 detection using par2
Obsolute now we have Multipar
2017-07-10 17:17:12 +02:00
Safihre
6adcf2ce10 Stylistic changes from previous commits 2017-07-10 17:11:32 +02:00
Safihre
e756b9b5c1 Correct filenames while downloading using first-16kb par2 info
Maybe we can also do DirectUnpack!
2017-07-10 17:07:16 +02:00
Safihre
b3de745849 Do not use article-filename if it looks obfuscated 2017-07-10 15:54:17 +02:00
Safihre
77f3dc18b5 Corrections of Move To Top for filelists 2017-07-09 19:51:18 +03:00
gwyden
6b2f15f82e Move To Top/Move To Bottom buttons for filelists (#959)
* Control creation

* JQuery to make the buttons work

* minor text fixes

* tab to spaces cleanup

* style additions and removed hard text from code

* Moved button control to modal finish render event, gave file details a little more room

* Moved control to replace age and size on mouseover

* Added margins and color corrected for the night theme

* resolved night theme readability

* move to working top and bottom

* controls would lose event bindings after the append.  Detach first then insert

* Move to Top and Bottom buttons for files in each NZB
2017-07-09 18:34:33 +02:00
Safihre
570e58611d Repair would fail if extrapars were deleted by previous run
Closes #961
2017-07-06 18:30:31 +03:00
Safihre
6b69010aec Add logging for missing NZF database to debug #952 2017-06-28 11:35:52 +02:00
Travis
e3e2fb7057 Automatic translation update 2017-06-27 09:23:17 +00:00
Safihre
ece04909e7 Add latest changes to changelog 2017-06-27 00:13:24 +02:00
Safihre
963920eb88 Semi-correct missing MB counter for Pre-check
It's still off (for Precheck only), but not sure why
2017-06-26 22:03:50 +02:00
Safihre
cf5fa542b6 Don't show import errors when NZO is gone 2017-06-26 21:36:49 +02:00
Safihre
1be7e99754 Remove last hashlib workaround 2017-06-26 21:00:17 +02:00
Safihre
14e3334682 Correctly handle disk-space calculations
No more glitches in the interface during downloading.
2017-06-26 16:25:03 +02:00
Safihre
b1e033dd55 Update text files for 2.2.0Alpa2 2017-06-26 14:28:22 +02:00
Safihre
111feb1b57 Show missing articles as MB instead of number of articles 2017-06-26 13:46:54 +02:00
Safihre
886b23d034 Update translatable texts 2017-06-26 10:40:02 +02:00
Safihre
f2590792b3 Download all par2 always when enable_par_cleanup is disabled
https://forums.sabnzbd.org/viewtopic.php?f=2&t=22744
2017-06-26 09:19:05 +02:00
Safihre
02a497ed74 Only set Post-processing Completed/Failed at the very end
To prevent race-issues
2017-06-25 20:32:45 +02:00
Safihre
48df0eed84 Add logging for user-actions
We were missing way too many things
2017-06-24 23:18:14 +02:00
Travis
0f58cbb671 Automatic translation update 2017-06-24 18:51:32 +00:00
Safihre
9d71670f59 Full hearted 2017-06-24 20:33:16 +02:00
Safihre
7f838ebb38 Move Donate-link in Glitter 2017-06-24 13:51:19 +02:00
Safihre
ef1cb05bc8 Store result of MultiPar verification
Just in case prospective par2 didn't catch them all
2017-06-24 10:47:33 +02:00
Safihre
c14b3ed82a Prospective Par2 correct TryList reset
I think
2017-06-24 00:34:57 +02:00
Safihre
792e337936 Rename increase_last_history_update to history_updated 2017-06-23 22:58:29 +02:00
Safihre
6cd2e66052 Use actual counter for LAST_HISTORY_UPDATE
Would otherwise miss some updates
2017-06-23 12:49:40 +02:00
Safihre
728022b86d Show Verifying Repair stage for MultiPar 2017-06-23 11:10:47 +02:00
Safihre
7718446313 Convert HTML to text in warning messages
Related: #952
2017-06-23 08:30:54 +02:00
Travis
66dea54053 Automatic translation update 2017-06-22 20:34:50 +00:00
Safihre
f19b60bd41 Also don't list line numbers for NSIS pot file 2017-06-22 21:46:55 +02:00
Safihre
09f1c92856 Move enable_multipar to Specials
Moving forward to make MultiPar the only used par2-solution on Windows.
2017-06-22 11:03:53 +02:00
Safihre
589715901d Correct counting during Checking/Verification in MultiPar 2017-06-22 10:50:00 +02:00
Safihre
3f1a5ff5e0 Fix typo in extract_pot.py 2017-06-22 09:32:59 +02:00
Safihre
49cd956d4c Do not list line-number for POT files
To avoid commit-overhead when updating texts
2017-06-21 22:17:49 +02:00
Safihre
f9acde862f Correct counting in MultiPar Checking 2017-06-21 21:40:24 +02:00
Safihre
503e1dd899 Re-work NZO_LOCK to actually lock when saving 2017-06-21 20:54:00 +02:00
Safihre
c8e12b948d Mixed up application of use_pickle 2017-06-21 17:18:16 +02:00
Safihre
18949d68c0 Fix wrong addition to en.po
Thx @thezoggy!
2017-06-21 17:12:19 +02:00
Safihre
0c51b6c016 Add Donate links to main Config page and Glitter help modal 2017-06-21 09:57:56 +02:00
Safihre
63a5c22c1f Don't continue when fetching failed
Possibly: #914
2017-06-21 09:19:25 +02:00
Safihre
f76e2a7b56 All links to sabnzbd.org should be HTTPS 2017-06-20 23:15:15 +02:00
Safihre
bab151d6f5 Properly fix redirect after enabeling/disabeling HTTPS 2017-06-20 22:46:48 +02:00
Safihre
d43fec088b Fix typo in Correct redirect when enabeling HTTPS 2017-06-20 19:48:18 +02:00
Safihre
a8ca1cbcd7 Correct redirect when enabeling HTTPS 2017-06-20 19:47:45 +02:00
Safihre
ada3494483 Fix typo in Config JavaScript 2017-06-20 19:04:38 +02:00
Safihre
43c238b7f1 Update translations 2017-06-17 11:23:50 +02:00
Safihre
128d10c51e Restart-text was always shown in English 2017-06-17 11:19:59 +02:00
Safihre
1a1e01f9f6 Correct upgrade-notice 2017-06-17 11:13:53 +02:00
Safihre
8483e4ab8a Add last minor change to changelog 2017-06-17 09:01:53 +02:00
Safihre
f6c163b505 CherryPy 8.1.2 - Catch OSX "Protocol wrong type for socket" 2017-06-17 08:40:14 +02:00
Safihre
8f30173db0 Update text files for 2.2.0Alpha1 2017-06-16 15:38:17 +02:00
Brendan Ball
0372ff95bb Added support for systemd power controls
added systemd support to powersup.linux_shutdown, linux_hibernate, linux_standby
2017-06-16 15:19:32 +02:00
Safihre
6fa29c7877 Update translatable texts 2017-06-15 20:58:48 +02:00
Safihre
d4c9121593 Remove NZB_LOCK
Not required
2017-06-15 20:54:13 +02:00
Safihre
76a8df0282 Make it more clear that Hostname verification is a server problem 2017-06-15 20:51:27 +02:00
Safihre
0b6d8309a0 Format the SSL certificate messages more for humans 2017-06-15 20:51:21 +02:00
Safihre
10a9bc0817 Don't show Advanded on Config>General if HTTPS disabled 2017-06-15 16:09:01 +02:00
Safihre
2a14af4ffa Firefox doesn't suck at animations anymore 2017-06-15 14:47:04 +02:00
Safihre
d1a4a292e3 Prevent log-flooding when job is too old for server 2017-06-13 21:40:45 +02:00
Safihre
14c0efa151 Fix error in MultiPar repair when first .par2-file was broken 2017-06-13 21:40:45 +02:00
Safihre
4fc03f2581 Discard all articles at once when too old for server 2017-06-13 21:40:45 +02:00
Safihre
3205b9fda9 Don't fill anything for bandwith limit if nothing is set
Now it would just fill "M" when nothing was set.
2017-06-13 21:40:45 +02:00
Safihre
953e0d6c22 Remove DIR_LOCK
The operations it was locking were always performed from 1 thread anyway.
2017-06-13 21:40:45 +02:00
Safihre
b50ce54ca9 Reformat IO_LOCK to really only protect against NZO-saving collisions
The main intent was not to read/write to same file, but this can (as far as I can see) never happen anyway. 
Before this change 2 threads could not be writing data at the same time, even if they were writing to completly different directories.
2017-06-13 21:40:45 +02:00
Safihre
5e7558ce4a Remove locks from ArticleCache
All operations on the list are atomic or modify objects in place that can't be read at the same time.
2017-06-13 21:40:45 +02:00
Safihre
8aa6362432 Remove NZBQueue wrapper functions
Direct-access!
2017-06-13 21:40:45 +02:00
Safihre
02ebb97a8b Remove NZBQUEUE_LOCK and only use synced wrappers when needed
Less locks = Less waiting
2017-06-13 21:40:45 +02:00
Safihre
b36063403d Remove legacy asserts and work-a-rounds 2017-06-13 21:40:45 +02:00
Safihre
526ffa2afb Add new translation to Changelog 2017-06-13 17:26:37 +02:00
Safihre
5b3fd812d8 Don't break MO-creation on missing Email templates 2017-06-13 14:40:47 +02:00
Safihre
af6dac9cdc Refresh Config > General when submitting language change 2017-06-13 14:14:26 +02:00
Safihre
bc25d936bb Show Hebrew in language menu
Correction of previous commit: I intended to write that it's an experiment because Hebrew is RTL. It seems to work!
2017-06-13 14:14:01 +02:00
Safihre
b497fe1444 Add Hebrew as language
This is an experiment, since Hebrew is LTR language. But a translator translated almost all the texts, so we want to use his efforts!
2017-06-13 11:43:18 +02:00
Safihre
3f456cce05 Move max_art_opt to Specials
Only for special cases (don't know which ones, but I can imagine it could be usefull..)
Deprecate later!
2017-06-13 00:40:02 +02:00
Safihre
4dd2f089ec Move replace_illegal to Specials
Who doesn't want that
2017-06-13 00:37:20 +02:00
Safihre
b1b1bc248d Reformat startswith() to use tuples when testing multiple options 2017-06-11 22:01:45 +02:00
Safihre
d9e675469c Don't throw errors when silent-saving fails 2017-06-11 22:01:45 +02:00
Safihre
ede0ca1772 Catch new way of par2 reporting bad parameters
See: https://forums.sabnzbd.org/viewtopic.php?f=2&t=22713&p=112209#p112209
2017-06-11 22:01:45 +02:00
Safihre
2d098a1477 Defend against possible NTFS crash
Closes #930
2017-06-11 22:01:45 +02:00
Safihre
e5f014b68e Replace spaces/dots in the order written in the Config 2017-06-11 22:01:45 +02:00
Safihre
b3a9dc9eeb Fix old code throughout 2017-06-11 22:01:45 +02:00
Safihre
2a06cec27c Seperate compatibility-check logic in NZBQueue 2017-06-11 22:01:45 +02:00
Safihre
19230c889d Remove unused functions and constants (vulture) 2017-06-11 22:01:45 +02:00
Safihre
c969ce552c Remove unused constants (vulture) 2017-06-11 22:01:45 +02:00
Safihre
2def600d21 Remove unused imports and functions (pyflakes) 2017-06-11 22:01:45 +02:00
Safihre
02aa8f18c8 Trylist doesn't need locks, all atomic operations 2017-06-11 22:01:45 +02:00
Safihre
fcd9522dae Remove unused import and define NzbQueue as proper class 2017-06-11 22:01:45 +02:00
Safihre
72d3ce885e Remove un-used version definitions from __init__ 2017-06-11 22:01:45 +02:00
Safihre
b428996eb7 Convert pickles and keep queue order
Also restore future jobs


dewd
2017-06-11 22:01:45 +02:00
Safihre
2b4eb58fad Correctly show message about old Queue-version
Now it's also upgrade-proof, and not just works for 1 version.
2017-06-11 22:01:45 +02:00
Safihre
240e8dff60 Bump Queue-Version to force Queue-Repair 2017-06-11 22:01:45 +02:00
Safihre
1c286afde6 Implement __slots__ to conserve memory
Objects such as Article() get created a lot. By using the __slots__ property, python will only reserve space for the give keywords instead of a whole diectonary. 

Testing showed that (on WinX64) 1 job now takes between 2-3MB of memory when loaded, compared to 4MB before.
2017-06-11 22:01:45 +02:00
Safihre
2eeb908540 Revert "Remove enable_par_cleanup"
This reverts commit f5ab4a2253.
2017-06-11 16:10:25 +02:00
Safihre
562e6ecce9 Fix Untill typos in texts and comments
Closes #943
2017-06-11 11:51:50 +02:00
Safihre
4bd0d32508 Bump version to 2.2.0-develop 2017-06-11 11:46:36 +02:00
Safihre
6f2ccbef80 Always show Par2-Multicore status on first Config page on Linux 2017-06-09 16:04:30 +02:00
Safihre
61a6cb6d96 Update translations 2017-06-09 11:27:46 +02:00
Safihre
443efb5eda Update text files for 2.1.0 2017-06-09 11:10:48 +02:00
Safihre
ba3c731fee Correctly switch HTTPS port if occupied on first start 2017-06-08 10:50:26 +02:00
Safihre
e55f72dd1d Limit the maxium extension of the bps_list with zeros
If the last bps measurment was very long ago, this could cause thousands of zeros to be pre-loaded. This could cause MemoryErrors on low-spec devices: 
https://forums.sabnzbd.org/viewtopic.php?f=3&t=22709&p=112149
2017-06-07 18:36:36 +02:00
Safihre
b28c0a60a1 Remove option to disable Par2 Multicore on macOS
In the hope to remove par2-classic from the macOS packages in later stage.
2017-06-04 20:30:54 +02:00
Safihre
b7a80bf026 Remove nr_decoders
2 seems to be perfect for now, it was always intended as a temporary setting.
2017-06-04 00:14:52 +02:00
Safihre
fe7218e64b Remove log_new
Not used anywhere..
2017-06-04 00:11:15 +02:00
Safihre
0857a9046d Remove login_realm
Never used nor shown to users to be able to modify it
2017-06-04 00:08:02 +02:00
Safihre
354131b78a Remove prio_sort_list
Nobody seems to care about it
2017-06-04 00:05:06 +02:00
Fish2
e3ae91a4f8 lossless compression of images saved 40KB (87%) 2017-06-04 00:00:25 +02:00
Safihre
52cc5e2e4f Remove create_group_folder NZO attribute 2017-06-03 23:53:48 +02:00
Safihre
0c04451442 Remove create_group_folder
Latest forum topic about this was in 2008
2017-06-03 23:52:54 +02:00
Safihre
f5ab4a2253 Remove enable_par_cleanup 2017-06-03 23:48:51 +02:00
Safihre
1303dfe17a Remove allow_64bit_tools
None of the tools have ever shown problems on their intended platform. We can opt to remove them from their respective builds for the releases, to reduce download-size.
2017-06-03 23:38:57 +02:00
Safihre
55d80f26fa Update wiki links for 2.1
Only tiny changes, but still different from 2.0.
2017-06-03 23:30:52 +02:00
Safihre
7aee585748 Remove depricated functionality to remove samples when adding NZB
Was removed long ago because removing Sample files before downloading could give verification problems.
2017-06-03 14:07:47 +02:00
Safihre
196858409c Update text files for 2.1.0RC1 2017-06-01 22:44:34 +02:00
Safihre
21467dd62f Only download all par2 when there was a problem 2017-06-01 22:43:24 +02:00
Safihre
bb30eb7d11 Always enable QuickCheck 2017-06-01 22:35:54 +02:00
Safihre
fb9f4a7373 User Par2 Parameters for Multipar 2017-06-01 22:35:29 +02:00
Safihre
ccd5c1c75e Show par2 Extra Parameter problems as Error 2017-06-01 22:02:00 +02:00
Safihre
015c578cdd Add par2_multicore to Specials (just in case)
#902
2017-05-30 23:04:18 +02:00
Safihre
8eb4ce2914 On Windows, only show MultiPar 2017-05-30 22:45:26 +02:00
Safihre
a6b8108ee6 Support macOS >= 10.9 (removes 32bit support) 2017-05-30 14:16:34 +02:00
Safihre
181a56218a Support macOS >= 10.8 2017-05-30 14:01:34 +02:00
Safihre
24feaaebd6 Remove macOS PPC support 2017-05-30 13:56:13 +02:00
Safihre
e1945e7a35 Add license file and README for MultiPar 2017-05-28 10:15:12 +02:00
Safihre
072f65dd9c Show history status if par2 was aborted due to Disk full 2017-05-27 19:04:22 +02:00
Safihre
bde03ecc63 False-positive showing disk-full in Glitter 2017-05-27 17:10:00 +02:00
Safihre
73c2e23da4 Remove 'never_repair'
Should just use only Download feature
2017-05-27 14:32:17 +02:00
Safihre
e6233831d1 Fix typo in Multipar text 2017-05-27 01:15:20 +02:00
Safihre
82ccbdaa7b Make human-readable dates the default date-format 2017-05-25 12:24:34 +02:00
Safihre
bf5212a81c Allow aborting running PP-script
Closes #313
2017-05-25 12:23:46 +02:00
Safihre
5c6cc932cf Update text files for 2.1.0Beta1 2017-05-25 11:02:03 +02:00
Safihre
ed4430a7e0 Update translatable texts 2017-05-24 12:39:29 +02:00
Safihre
eb73f78b1f Remove unused try/except and allow cancelling of MultiPar 2017-05-24 12:39:29 +02:00
Safihre
314aad0009 Bring back unique-unpack staging 2017-05-24 12:39:29 +02:00
Safihre
98d0c5c52f Update versioning 2017-05-24 12:39:29 +02:00
Safihre
4d4da889ec Clear text in Config > Switches about Multipar 2017-05-24 12:39:29 +02:00
Safihre
4a622f59ba Updates to Multipar code and unpack-info saving 2017-05-24 12:39:29 +02:00
Safihre
913b92088a Update Multipar to v1.2.9 2017-05-24 12:39:29 +02:00
Safihre
80f4690df8 Enable and notify users about Multipar 2017-05-24 12:39:29 +02:00
Safihre
f552531703 Adding MultiPar interpreter 2017-05-24 12:39:29 +02:00
Safihre
707d4a7a0c Adding Multipar executables 2017-05-24 12:39:29 +02:00
Safihre
be5bebb574 Update text files 2.0.1 - Nr2 2017-05-23 13:12:02 +02:00
Safihre
4ca2a7a65e Also match 'proof' when ignoring samples 2017-05-22 22:07:33 +02:00
Safihre
eed8c9bf50 Update text files for 2.0.1 2017-05-22 21:14:17 +02:00
Safihre
11d5855430 Always show Script box on Categories config page
Otherwise people might not be aware of it
2017-05-22 21:10:17 +02:00
Safihre
b13413b1e5 Only perform end-of-queue action when one is set 2017-05-22 10:38:52 +02:00
Safihre
9809474615 Remove cache from first Config page
It's a dynamic thing, not static like the Config. It can be observed live from the Status and Interface window.
2017-05-21 21:23:10 +02:00
Safihre
9ee3a61ae9 Convert HTML " in Repair confirmation 2017-05-21 21:05:12 +02:00
Safihre
c71ffa02f8 Remove ending \ in extraction path after long-path unrar retry
So, turns out that when not using the \\?\ notation, Unrar does not like the \ at the end of the path (which it does need otherwise). Linked #771
2017-05-21 13:53:38 +02:00
Safihre
2e862da292 Remember all unpack/repair info, no more overwrite
Keeps forgetting all the previous stages, no idea why that's usefull
2017-05-21 13:00:20 +02:00
Safihre
08d762c6c9 No longer try to verify using SFV and/or RAR-check when Par2 failed 2017-05-21 12:23:06 +02:00
Safihre
2ef6f9e0e7 Repair would be skipped if first par2-file was not downloaded 2017-05-20 19:14:04 +02:00
Safihre
b8f84cf18d Renames of QuickCheck were not saved if following files failed
Now we do as much as possible in QuickCheck before moving on, much faster then letting par2 do it
2017-05-20 15:52:36 +02:00
Safihre
7e88af7047 Small cleanup of par2repair code 2017-05-20 14:52:18 +02:00
225 changed files with 36716 additions and 28864 deletions

View File

@@ -1,5 +1,5 @@
*******************************************
*** This is SABnzbd 2.0.0 ***
*** This is SABnzbd 2.2.0 ***
*******************************************
SABnzbd is an open-source cross-platform binary newsreader.
It simplifies the process of downloading from Usenet dramatically,

View File

@@ -1,4 +1,4 @@
SABnzbd 2.0.0
SABnzbd 2.3.0
-------------------------------------------------------------------------------
0) LICENSE
@@ -27,58 +27,56 @@ Just run the downloaded EXE file and the installer will start.
It's just a simple standard installer.
After installation, find the SABnzbd program in the Start menu and start it.
Within 5-10 seconds your web browser will start and show the user interface.
Within a few seconds your web browser will start and show the user interface.
Use the "Help" button in the web-interface to be directed to the Help Wiki.
-------------------------------------------------------------------------------
2) INSTALL pre-built Windows binaries
-------------------------------------------------------------------------------
Unzip pre-built version to any folder of your liking.
Start the SABnzbd.exe program.
Within 5-10 seconds your web browser will start and show the user interface.
Within a few seconds your web browser will start and show the user interface.
Use the "Help" button in the web-interface to be directed to the Help Wiki.
-------------------------------------------------------------------------------
3) INSTALL pre-built macOS binaries
-------------------------------------------------------------------------------
Download the DMG file, mount and drag the SABnzbd icon to Programs.
Just like you do with so many apps.
Make sure you pick the right folder, depending on your macOS version.
-------------------------------------------------------------------------------
4) INSTALL with only sources
-------------------------------------------------------------------------------
Specific guides to install from source are available for Windows and macOS:
https://sabnzbd.org/wiki/installation/install-macos
https://sabnzbd.org/wiki/installation/install-from-source-windows
You need to have Python installed plus some non-standard Python modules
and a few tools.
Unix/Linux/macOS
All platforms
Python-2.7.latest http://www.python.org (2.7.9+ recommended)
Windows
Python-2.7.latest http://www.python.org (2.7.9+ recommended)
PyWin32 use "pip install pypiwin32"
Essential modules
cheetah-2.0.1+ use "pip install cheetah"
par2cmdline >= 0.4 http://parchive.sourceforge.net/
Note: https://sabnzbd.org/wiki/configuration/2.0/switches#par2cmdline
And: https://sabnzbd.org/wiki/installation/multicore-par2
par2cmdline >= 0.4 https://github.com/Parchive/par2cmdline/releases
See also: https://sabnzbd.org/wiki/installation/multicore-par2
unrar >= 5.00+ http://www.rarlab.com/rar_add.htm
openssl >= 1.0.0 http://www.openssl.org/
Optional modules
unzip >= 6.00 http://www.info-zip.org/
7zip >= 9.20 http://www.7zip.org/
sabyenc == 3.0.2 use "pip install sabyenc" - https://sabnzbd.org/sabyenc
openssl >= 1.0.0 http://www.openssl.org/
v0.9.8 will work, but limits certificate validation
sabyenc == 3.0.2 use "pip install sabyenc"
More information: https://sabnzbd.org/sabyenc
cryptography >= 1.0 use "pip install cryptography"
Enables certificate generation and detection of encrypted RAR-files
Optional modules Unix/Linux/macOS
Optional modules Linux
pynotify Should be part of GTK for Python support on Debian/Ubuntu
If not, you cannot use the NotifyOSD feature.
python-dbus Enable option to Shutdown/Restart/Standby PC on queue finish.
@@ -88,6 +86,7 @@ Embedded modules (preferably use the included version)
Unpack the ZIP-file containing the SABnzbd sources to any folder of your liking.
If you want multiple languages, you need to compile the translations.
Start this from a shell terminal (or command prompt):
python tools/make_mo.py
@@ -95,7 +94,7 @@ Start this from a shell terminal (or command prompt):
Start this from a shell terminal (or command prompt):
python SABnzbd.py
Within 5-10 seconds your web browser will start and show the user interface.
Within a few seconds your web browser will start and show the user interface.
Use the "Help" button in the web-interface to be directed to the Help Wiki.
@@ -113,7 +112,7 @@ or
You may of course try other port numbers too.
For troubleshooting you can use the program SABnzbd-console.exe.
For troubleshooting on Windows you can use the program SABnzbd-console.exe.
This will show a black window where logging information will be shown. This
may help you solve problems easier.
@@ -121,7 +120,7 @@ may help you solve problems easier.
6) MORE INFORMATION
-------------------------------------------------------------------------------
Visit the WIKI site:
Visit our wiki:
https://sabnzbd.org/wiki/
@@ -131,4 +130,4 @@ Visit the WIKI site:
Several parts of SABnzbd were built by other people, illustrating the
wonderful world of Free Open Source Software.
See the licenses folder of the main program and of the skin folders.
See the licenses folder of the main program and of the skin folders.

View File

@@ -24,13 +24,13 @@
For these the server blocking method is not very favourable.
There is an INI-only option that will limit blocks to 1 minute.
no_penalties = 1
See: https://sabnzbd.org/wiki/configuration/2.0/special
See: https://sabnzbd.org/wiki/configuration/2.2/special
- Some third-party utilties try to probe SABnzbd API in such a way that you will
often see warnings about unauthenticated access.
If you are sure these probes are harmless, you can suppress the warnings by
setting the option "api_warnings" to 0.
See: https://sabnzbd.org/wiki/configuration/2.0/special
See: https://sabnzbd.org/wiki/configuration/2.2/special
- On OSX you may encounter downloaded files with foreign characters.
The par2 repair may fail when the files were created on a Windows system.
@@ -41,7 +41,7 @@
You will see this only when downloaded files contain accented characters.
You need to fix it yourself by running the convmv utility (available for most Linux platforms).
Possible the file system override setting 'fsys_type' might be solve things:
See: https://sabnzbd.org/wiki/configuration/2.0/special
See: https://sabnzbd.org/wiki/configuration/2.2/special
- The "Watched Folder" sometimes fails to delete the NZB files it has
processed. This happens when other software still accesses these files.
@@ -81,4 +81,4 @@
- Squeeze Linux
There is a "special" option that will allow you to select an alternative library.
use_pickle = 1
See: https://sabnzbd.org/wiki/configuration/2.0/special
See: https://sabnzbd.org/wiki/configuration/2.2/special

View File

@@ -1,8 +1,8 @@
Metadata-Version: 1.0
Name: SABnzbd
Version: 2.0.1RC2
Summary: SABnzbd-2.0.1RC2
Home-page: http://sabnzbd.org
Version: 2.3.0Alpha2
Summary: SABnzbd-2.3.0Alpha1
Home-page: https://sabnzbd.org
Author: The SABnzbd Team
Author-email: team@sabnzbd.org
License: GNU General Public License 2 (GPL2 or later)

View File

@@ -5,7 +5,7 @@ SABnzbd is an Open Source Binary Newsreader written in Python.
It's totally free, incredibly easy to use, and works practically everywhere.
SABnzbd makes Usenet as simple and streamlined as possible by automating everything we can. All you have to do is add an `.nzb`. SABnzbd takes over from there, where it will be automatically downloaded, verified, repaired, extracted and filed away with zero human interaction.
If you want to know more you can head over to our website: http://sabnzbd.org.
If you want to know more you can head over to our website: https://sabnzbd.org.
## Resolving Dependencies

View File

@@ -1,47 +1,62 @@
Release Notes - SABnzbd 2.0.1 RC 2
Release Notes - SABnzbd 2.3.0 Alpha 2
=========================================================
## Bugfixes since 2.0.0RC1
- Queue-finish-action picker in Glitter more stable.
- Custom Pause interpreter in Glitter more reliable.
- Handle occupied port on startup better.
- Pre-check would fail if download was on SMBv3 share.
- Handle more URLGrabber exceptions.
- Command-line parameters were not listed correctly.
## Changes and bugfixes since 2.3.0 Alpha 1
- Specials Config page could not be loaded
- Crash when adding new jobs
- Further stalling-detection improvements
- Crash when a job was set to only Download
- Display of download progress and missing data improved
- Retried jobs would show incorrect download progress
- Different icon for downloads with Force priority
- Show progress during verification of extra files
- API: 'missing' field removed, use 'mbmissing'
## Bugfixes since 2.0.0
- Some users experienced slower download speeds.
- Revert changes to handling of missing articles introduced in 2.0.0 that
turned out to be slow for some users.
- Log X-Forwarded-For of API calls and logins.
- Better handling of 7zip unpacking.
- No longer change ports if the configured port is not available during startup.
- Windows: remove option to start SABnzbd from installer, it would start the
program as administrator.
- Windows: NZB-icon association lost sometimes.
## Upgrade notices
- Windows: When starting the Post-Processing script, the path to the job folder
is no longer in short-path notation but includes the full path. To support
long paths (>255), you might need to alter them to long-path notation (\\?\).
- Schedule items are converted when upgrading to 2.x.x and will break when
reverted back to pre-2.x.x releases.
- The organization of the download queue is different from 0.7.x releases.
So 2.x.x will not see the existing queue, but you can go to Status->QueueRepair
and "Repair" the old queue.
## Changes since 2.2.1
- Option to limit Servers to specific Categories removed
- Improved par2 handling and obfuscated files detection
- Duplicate filenames in NZB's no longer rejected by default
- Set custom URL instead of /sabnzbd/ (in Config > Specials)
- Article-state (which servers are tried) is preserved after restart
- Auto disconnect (if enabled) only after verification of last item
- Slight performance improvement when fetching RSS-feeds
- RSS-feed title is shown for URLs being grabbed
- URL grabbing can now be individually paused
- Scheduler can pause/unpause jobs in specific category
- Series Duplicate Checker can allow PROPER/REAL/REPACK versions
- Refresh-icon in Glitter when refresh rate is above 2 seconds
- macOS: Bundled new OpenSSL version with support for TLSv1.2
- macOS: No longer linked to system certificate store
- macOS and Windows: Installers include Mozilla CA certificates
## Upgrading from 0.7.x and older
## Bugfixes since 2.2.1
- Reduce CPU usage with multiple servers
- Fix yet another potential stalling issue
- Remove Timeout tracebacks
- Only warn if number of actual passwords is larger than 30
- Unexpected behavior when diskspace becomes critically low
- MacOS: Direct Unpack could hang in case of special charters in names
- API: Correct listing of downloaded and queued files in get_files
- API: Jobs with Force priority should always have status 'Downloading'
## Upgrading from 2.1.x and older
- Finish queue
- Stop SABnzbd
- Install new version
- Start SABnzbd
## Upgrade notices
- When upgrading from 2.1.0 or older the queue will be converted. Job order,
settings and data will be preserved, but all jobs will be unpaused and
URL's that did not finish fetching before the upgrade will be lost.
- The organization of the download queue is different from 0.7.x releases.
This version will not see the 0.7.x queue, but you can restore the jobs
by going to Status page and using Queue Repair.
## Known problems and solutions
- Read the file "ISSUES.txt"
## Translations
- Numerous translations updated, thanks to our translators!
## About
SABnzbd is an open-source cross-platform binary newsreader.
It simplifies the process of downloading from Usenet dramatically, thanks

View File

@@ -37,6 +37,7 @@ import getopt
import signal
import socket
import platform
import ssl
import time
import re
@@ -56,8 +57,6 @@ if [int(n) for n in cherrypy.__version__.split('.')] < [8, 1, 2]:
print 'Sorry, requires Python module Cherrypy 8.1.2+ (use the included version)'
sys.exit(1)
from cherrypy import _cpserver
SQLITE_DLL = True
try:
from sqlite3 import version as sqlite3_version
@@ -90,7 +89,7 @@ from sabnzbd.misc import real_path, \
check_latest_version, exit_sab, \
split_host, get_ext, create_https_certificates, \
windows_variant, ip_extract, set_serv_parms, get_serv_parms, globber_full
from sabnzbd.panic import panic_tmpl, panic_port, panic_host, panic_fwall, \
from sabnzbd.panic import panic_tmpl, panic_port, panic_host, \
panic_sqlite, panic, launch_a_browser
import sabnzbd.scheduler as scheduler
import sabnzbd.config as config
@@ -99,7 +98,6 @@ import sabnzbd.downloader
from sabnzbd.encoding import unicoder, deunicode
import sabnzbd.notifier as notifier
import sabnzbd.zconfig
import sabnzbd.utils.sslinfo
from threading import Thread
@@ -428,10 +426,12 @@ def print_modules():
if sabnzbd.newsunpack.PAR2_COMMAND:
logging.info("par2 binary... found (%s)", sabnzbd.newsunpack.PAR2_COMMAND)
else:
logging.error(T('par2 binary... NOT found!'))
logging.error('%s %s' % (T('par2 binary... NOT found!'), T('Verification and repair will not be possible.')))
if sabnzbd.newsunpack.PAR2C_COMMAND:
logging.info("par2cmdline binary... found (%s)", sabnzbd.newsunpack.PAR2C_COMMAND)
if sabnzbd.newsunpack.MULTIPAR_COMMAND:
logging.info("MultiPar binary... found (%s)", sabnzbd.newsunpack.MULTIPAR_COMMAND)
elif sabnzbd.WIN32:
logging.error('%s %s' % (T('MultiPar binary... NOT found!'), T('Verification and repair will not be possible.')))
if sabnzbd.newsunpack.RAR_COMMAND:
logging.info("UNRAR binary... found (%s)", sabnzbd.newsunpack.RAR_COMMAND)
@@ -442,9 +442,9 @@ def print_modules():
want_str = '%.2f' % (float(sabnzbd.constants.REC_RAR_VERSION) / 100)
logging.warning(T('Your UNRAR version is %s, we recommend version %s or higher.<br />') % (have_str, want_str))
elif not (sabnzbd.WIN32 or sabnzbd.DARWIN):
logging.debug('UNRAR binary version %.2f', (float(sabnzbd.newsunpack.RAR_VERSION) / 100))
logging.info('UNRAR binary version %.2f', (float(sabnzbd.newsunpack.RAR_VERSION) / 100))
else:
logging.error(T('unrar binary... NOT found'))
logging.error('%s %s' % (T('unrar binary... NOT found'), T('Downloads will not unpacked.')))
if sabnzbd.newsunpack.ZIP_COMMAND:
logging.info("unzip binary... found (%s)", sabnzbd.newsunpack.ZIP_COMMAND)
@@ -724,24 +724,6 @@ def evaluate_inipath(path):
return path
def cherrypy_logging(log_path, log_handler):
""" Setup CherryPy logging """
log = cherrypy.log
log.access_file = ''
log.error_file = ''
# Max size of 512KB
maxBytes = getattr(log, "rot_maxBytes", 524288)
# cherrypy.log cherrypy.log.1 cherrypy.log.2
backupCount = getattr(log, "rot_backupCount", 3)
# Make a new RotatingFileHandler for the error log.
fname = getattr(log, "rot_error_file", log_path)
h = log_handler(fname, 'a', maxBytes, backupCount)
h.setLevel(logging.DEBUG)
h.setFormatter(cherrypy._cplogging.logfmt)
log.error_log.addHandler(h)
def commandline_handler(frozen=True):
""" Split win32-service commands are true parameters
Returns:
@@ -840,7 +822,7 @@ def main():
logging_level = None
web_dir = None
vista_plus = False
vista64 = False
win64 = False
repair = 0
api_url = None
no_login = False
@@ -965,8 +947,8 @@ def main():
# Detect Windows variant
if sabnzbd.WIN32:
vista_plus, vista64 = windows_variant()
sabnzbd.WIN64 = vista64
vista_plus, win64 = windows_variant()
sabnzbd.WIN64 = win64
if not SQLITE_DLL:
panic_sqlite(sabnzbd.MY_FULLNAME)
@@ -1045,7 +1027,7 @@ def main():
pass
else:
if not url:
url = 'https://%s:%s/sabnzbd/api?' % (browserhost, port)
url = 'https://%s:%s%s/api?' % (browserhost, port, sabnzbd.cfg.url_base())
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
# Bail out if we have fixed our ports after first start-up
if sabnzbd.cfg.fixed_ports():
@@ -1059,7 +1041,7 @@ def main():
sabnzbd.cfg.https_port.set(newport)
else:
# In case HTTPS == HTTP port
http_port = newport
cherryport = newport
sabnzbd.cfg.port.set(newport)
except:
# Something else wrong, probably badly specified host
@@ -1074,7 +1056,7 @@ def main():
pass
else:
if not url:
url = 'http://%s:%s/sabnzbd/api?' % (browserhost, cherryport)
url = 'http://%s:%s%s/api?' % (browserhost, cherryport, sabnzbd.cfg.url_base())
if new_instance or not check_for_sabnzbd(url, upload_nzbs, autobrowser):
# Bail out if we have fixed our ports after first start-up
if sabnzbd.cfg.fixed_ports():
@@ -1169,14 +1151,12 @@ def main():
logging.info('Full executable path = %s', sabnzbd.MY_FULLNAME)
if sabnzbd.WIN32:
suffix = ''
if vista_plus:
suffix = ' (=Vista+)'
if vista64:
suffix = ' (=Vista+ x64)'
if win64:
suffix = '(win64)'
try:
logging.info('Platform=%s%s Class=%s', platform.platform(), suffix, os.name)
logging.info('Platform = %s %s', platform.platform(), suffix)
except:
logging.info('Platform=%s <unknown> Class=%s', suffix, os.name)
logging.info('Platform = %s <unknown>', suffix)
else:
logging.info('Platform = %s', os.name)
logging.info('Python-version = %s', sys.version)
@@ -1194,7 +1174,24 @@ def main():
if not sabnzbd.WIN32 and not sabnzbd.DARWIN and not ('utf' in preferredencoding.lower() and '8' in preferredencoding.lower()):
logging.warning(T("SABnzbd was started with encoding %s, this should be UTF-8. Expect problems with Unicoded file and directory names in downloads.") % preferredencoding)
# SSL Information
logging.info("SSL version = %s", ssl.OPENSSL_VERSION)
# Load (extra) certificates in the binary distributions
if hasattr(sys, "frozen") and (sabnzbd.WIN32 or sabnzbd.DARWIN):
# The certifi package brings the latest certificates on build
# This will cause the create_default_context to load it automatically
os.environ["SSL_CERT_FILE"] = os.path.join(sabnzbd.DIR_PROG, 'cacert.pem')
logging.info('Loaded additional certificates from %s', os.environ["SSL_CERT_FILE"])
# Extra startup info
if sabnzbd.cfg.log_level() > 1:
# List the number of certificates available (can take up to 1.5 seconds)
if sabnzbd.HAVE_SSL_CONTEXT:
ctx = ssl.create_default_context()
logging.debug('Available certificates: %s', repr(ctx.cert_store_stats()))
# Show IPv4/IPv6 address
from sabnzbd.getipaddress import localipv4, publicipv4, ipv6
mylocalipv4 = localipv4()
@@ -1219,12 +1216,12 @@ def main():
from sabnzbd.utils.getperformance import getpystone, getcpu
pystoneperf = getpystone()
if pystoneperf:
logging.debug('CPU Pystone available performance is %s', pystoneperf)
logging.debug('CPU Pystone available performance = %s', pystoneperf)
else:
logging.debug('CPU Pystone available performance could not be calculated')
cpumodel = getcpu() # Linux only
if cpumodel:
logging.debug('CPU model name is %s', cpumodel)
logging.debug('CPU model = %s', cpumodel)
logging.info('Read INI file %s', inifile)
@@ -1268,12 +1265,9 @@ def main():
# Find external programs
sabnzbd.newsunpack.find_programs(sabnzbd.DIR_PROG)
print_modules()
logging.info("SSL version %s", sabnzbd.utils.sslinfo.ssl_version())
logging.info("SSL supported protocols %s", str(sabnzbd.utils.sslinfo.ssl_protocols_labels()))
# HTTPS certificate generation
https_cert = sabnzbd.cfg.https_cert.get_path()
https_key = sabnzbd.cfg.https_key.get_path()
https_chain = sabnzbd.cfg.https_chain.get_path()
@@ -1289,6 +1283,7 @@ def main():
logging.warning(T('Disabled HTTPS because of missing CERT and KEY files'))
enable_https = False
# Starting of the webserver
# Determine if this system has multiple definitions for 'localhost'
hosts = all_localhosts()
multilocal = len(hosts) > 1 and cherryhost in ('localhost', '0.0.0.0')
@@ -1341,6 +1336,7 @@ def main():
'server.socket_port': cherryport,
'server.shutdown_timeout': 0,
'log.screen': False,
'engine.timeout_monitor.on': False,
'engine.autoreload.on': False,
'tools.encode.on': True,
'tools.gzip.on': True,
@@ -1350,7 +1346,6 @@ def main():
'error_page.404': sabnzbd.panic.error_page_404
})
# Do we want CherryPy Logging? Cannot be done via the config
if cherrypylogging:
sabnzbd.WEBLOGFILE = os.path.join(logdir, DEF_LOG_CHERRY)
@@ -1378,7 +1373,7 @@ def main():
# Make available from both URLs
main_page = sabnzbd.interface.MainPage()
cherrypy.tree.mount(main_page, '/', config=appconfig)
cherrypy.tree.mount(main_page, '/sabnzbd/', config=appconfig)
cherrypy.tree.mount(main_page, sabnzbd.cfg.url_base(), config=appconfig)
# Set authentication for CherryPy
sabnzbd.interface.set_auth(cherrypy.config)
@@ -1395,15 +1390,15 @@ def main():
# Wait for server to become ready
cherrypy.engine.wait(cherrypy.process.wspbus.states.STARTED)
sabnzbd.zconfig.set_bonjour(cherryhost, cherryport)
# Window Service support
mail = None
if sabnzbd.WIN32:
if enable_https:
mode = 's'
else:
mode = ''
api_url = 'http%s://%s:%s/sabnzbd/api?apikey=%s' % (mode, browserhost, cherryport, sabnzbd.cfg.api_key())
api_url = 'http%s://%s:%s%s/api?apikey=%s' % (mode, browserhost, cherryport, sabnzbd.cfg.url_base(), sabnzbd.cfg.api_key())
if sabnzbd.WIN_SERVICE:
mail = MailSlot()
@@ -1436,9 +1431,9 @@ def main():
# Set URL for browser
if enable_https:
browser_url = "https://%s:%s/sabnzbd" % (browserhost, cherryport)
browser_url = "https://%s:%s%s" % (browserhost, cherryport, sabnzbd.cfg.url_base())
else:
browser_url = "http://%s:%s/sabnzbd" % (browserhost, cherryport)
browser_url = "http://%s:%s%s" % (browserhost, cherryport, sabnzbd.cfg.url_base())
sabnzbd.BROWSER_URL = browser_url
if not autorestarted:
@@ -1452,6 +1447,13 @@ def main():
check_latest_version()
autorestarted = False
# ZeroConfig/Bonjour needs a ip. Lets try to find it.
try:
z_host = socket.gethostbyname(socket.gethostname())
except socket.gaierror:
z_host = cherryhost
sabnzbd.zconfig.set_bonjour(z_host, cherryport)
# Have to keep this running, otherwise logging will terminate
timer = 0
while not sabnzbd.SABSTOP:
@@ -1700,9 +1702,8 @@ if __name__ == '__main__':
main()
elif getattr(sys, 'frozen', None) == 'macosx_app':
# OSX binary
try:
# OSX binary runner
from PyObjCTools import AppHelper
from sabnzbd.osxmenu import SABnzbdDelegate
@@ -1727,9 +1728,7 @@ if __name__ == '__main__':
sabApp = startApp()
sabApp.start()
AppHelper.runEventLoop()
except:
main()
else:
main()

View File

@@ -196,12 +196,13 @@ socket_errors_to_ignore = plat_specific_errors(
)
socket_errors_to_ignore.append('timed out')
socket_errors_to_ignore.append('The read operation timed out')
if sys.platform == 'darwin':
socket_errors_to_ignore.append(plat_specific_errors('EPROTOTYPE'))
socket_errors_nonblocking = plat_specific_errors(
'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
if sys.platform == 'darwin':
socket_errors_to_ignore.extend(plat_specific_errors('EPROTOTYPE'))
socket_errors_nonblocking.extend(plat_specific_errors('EPROTOTYPE'))
comma_separated_headers = [
ntob(h) for h in
['Accept', 'Accept-Charset', 'Accept-Encoding',

View File

@@ -98,6 +98,12 @@ class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
# The connection can safely be dropped.
return None, {}
raise
except:
# Temporary fix for https://github.com/cherrypy/cherrypy/issues/1618
e = sys.exc_info()[1]
if e.args == (0, 'Error'):
return None, {}
raise
return s, self.get_environ(s)
# TODO: fill this out more with mod ssl env

View File

@@ -1,509 +0,0 @@
import re
import hashlib
import time
import StringIO
__version__ = '0.8'
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
GNTP_INFO_LINE = re.compile(
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)' +
' (?P<encryptionAlgorithmID>[A-Z0-9]+(:(?P<ivValue>[A-F0-9]+))?) ?' +
'((?P<keyHashAlgorithmID>[A-Z0-9]+):(?P<keyHash>[A-F0-9]+).(?P<salt>[A-F0-9]+))?\r\n',
re.IGNORECASE
)
GNTP_INFO_LINE_SHORT = re.compile(
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)',
re.IGNORECASE
)
GNTP_HEADER = re.compile('([\w-]+):(.+)')
GNTP_EOL = '\r\n'
class BaseError(Exception):
def gntp_error(self):
error = GNTPError(self.errorcode, self.errordesc)
return error.encode()
class ParseError(BaseError):
errorcode = 500
errordesc = 'Error parsing the message'
class AuthError(BaseError):
errorcode = 400
errordesc = 'Error with authorization'
class UnsupportedError(BaseError):
errorcode = 500
errordesc = 'Currently unsupported by gntp.py'
class _GNTPBuffer(StringIO.StringIO):
"""GNTP Buffer class"""
def writefmt(self, message="", *args):
"""Shortcut function for writing GNTP Headers"""
self.write((message % args).encode('utf8', 'replace'))
self.write(GNTP_EOL)
class _GNTPBase(object):
"""Base initilization
:param string messagetype: GNTP Message type
:param string version: GNTP Protocol version
:param string encription: Encryption protocol
"""
def __init__(self, messagetype=None, version='1.0', encryption=None):
self.info = {
'version': version,
'messagetype': messagetype,
'encryptionAlgorithmID': encryption
}
self.headers = {}
self.resources = {}
def __str__(self):
return self.encode()
def _parse_info(self, data):
"""Parse the first line of a GNTP message to get security and other info values
:param string data: GNTP Message
:return dict: Parsed GNTP Info line
"""
match = GNTP_INFO_LINE.match(data)
if not match:
raise ParseError('ERROR_PARSING_INFO_LINE')
info = match.groupdict()
if info['encryptionAlgorithmID'] == 'NONE':
info['encryptionAlgorithmID'] = None
return info
def set_password(self, password, encryptAlgo='MD5'):
"""Set a password for a GNTP Message
:param string password: Null to clear password
:param string encryptAlgo: Supports MD5, SHA1, SHA256, SHA512
"""
hash = {
'MD5': hashlib.md5,
'SHA1': hashlib.sha1,
'SHA256': hashlib.sha256,
'SHA512': hashlib.sha512,
}
self.password = password
self.encryptAlgo = encryptAlgo.upper()
if not password:
self.info['encryptionAlgorithmID'] = None
self.info['keyHashAlgorithm'] = None
return
if not self.encryptAlgo in hash.keys():
raise UnsupportedError('INVALID HASH "%s"' % self.encryptAlgo)
hashfunction = hash.get(self.encryptAlgo)
password = password.encode('utf8')
seed = time.ctime()
salt = hashfunction(seed).hexdigest()
saltHash = hashfunction(seed).digest()
keyBasis = password + saltHash
key = hashfunction(keyBasis).digest()
keyHash = hashfunction(key).hexdigest()
self.info['keyHashAlgorithmID'] = self.encryptAlgo
self.info['keyHash'] = keyHash.upper()
self.info['salt'] = salt.upper()
def _decode_hex(self, value):
"""Helper function to decode hex string to `proper` hex string
:param string value: Human readable hex string
:return string: Hex string
"""
result = ''
for i in range(0, len(value), 2):
tmp = int(value[i:i + 2], 16)
result += chr(tmp)
return result
def _decode_binary(self, rawIdentifier, identifier):
rawIdentifier += '\r\n\r\n'
dataLength = int(identifier['Length'])
pointerStart = self.raw.find(rawIdentifier) + len(rawIdentifier)
pointerEnd = pointerStart + dataLength
data = self.raw[pointerStart:pointerEnd]
if not len(data) == dataLength:
raise ParseError('INVALID_DATA_LENGTH Expected: %s Recieved %s' % (dataLength, len(data)))
return data
def _validate_password(self, password):
"""Validate GNTP Message against stored password"""
self.password = password
if password == None:
raise AuthError('Missing password')
keyHash = self.info.get('keyHash', None)
if keyHash is None and self.password is None:
return True
if keyHash is None:
raise AuthError('Invalid keyHash')
if self.password is None:
raise AuthError('Missing password')
password = self.password.encode('utf8')
saltHash = self._decode_hex(self.info['salt'])
keyBasis = password + saltHash
key = hashlib.md5(keyBasis).digest()
keyHash = hashlib.md5(key).hexdigest()
if not keyHash.upper() == self.info['keyHash'].upper():
raise AuthError('Invalid Hash')
return True
def validate(self):
"""Verify required headers"""
for header in self._requiredHeaders:
if not self.headers.get(header, False):
raise ParseError('Missing Notification Header: ' + header)
def _format_info(self):
"""Generate info line for GNTP Message
:return string:
"""
info = u'GNTP/%s %s' % (
self.info.get('version'),
self.info.get('messagetype'),
)
if self.info.get('encryptionAlgorithmID', None):
info += ' %s:%s' % (
self.info.get('encryptionAlgorithmID'),
self.info.get('ivValue'),
)
else:
info += ' NONE'
if self.info.get('keyHashAlgorithmID', None):
info += ' %s:%s.%s' % (
self.info.get('keyHashAlgorithmID'),
self.info.get('keyHash'),
self.info.get('salt')
)
return info
def _parse_dict(self, data):
"""Helper function to parse blocks of GNTP headers into a dictionary
:param string data:
:return dict:
"""
dict = {}
for line in data.split('\r\n'):
match = GNTP_HEADER.match(line)
if not match:
continue
key = unicode(match.group(1).strip(), 'utf8', 'replace')
val = unicode(match.group(2).strip(), 'utf8', 'replace')
dict[key] = val
return dict
def add_header(self, key, value):
if isinstance(value, unicode):
self.headers[key] = value
else:
self.headers[key] = unicode('%s' % value, 'utf8', 'replace')
def add_resource(self, data):
"""Add binary resource
:param string data: Binary Data
"""
identifier = hashlib.md5(data).hexdigest()
self.resources[identifier] = data
return 'x-growl-resource://%s' % identifier
def decode(self, data, password=None):
"""Decode GNTP Message
:param string data:
"""
self.password = password
self.raw = data
parts = self.raw.split('\r\n\r\n')
self.info = self._parse_info(data)
self.headers = self._parse_dict(parts[0])
def encode(self):
"""Encode a generic GNTP Message
:return string: GNTP Message ready to be sent
"""
buffer = _GNTPBuffer()
buffer.writefmt(self._format_info())
#Headers
for k, v in self.headers.iteritems():
buffer.writefmt('%s: %s', k, v)
buffer.writefmt()
#Resources
for resource, data in self.resources.iteritems():
buffer.writefmt('Identifier: %s', resource)
buffer.writefmt('Length: %d', len(data))
buffer.writefmt()
buffer.write(data)
buffer.writefmt()
buffer.writefmt()
return buffer.getvalue()
class GNTPRegister(_GNTPBase):
"""Represents a GNTP Registration Command
:param string data: (Optional) See decode()
:param string password: (Optional) Password to use while encoding/decoding messages
"""
_requiredHeaders = [
'Application-Name',
'Notifications-Count'
]
_requiredNotificationHeaders = ['Notification-Name']
def __init__(self, data=None, password=None):
_GNTPBase.__init__(self, 'REGISTER')
self.notifications = []
if data:
self.decode(data, password)
else:
self.set_password(password)
self.add_header('Application-Name', 'pygntp')
self.add_header('Notifications-Count', 0)
def validate(self):
'''Validate required headers and validate notification headers'''
for header in self._requiredHeaders:
if not self.headers.get(header, False):
raise ParseError('Missing Registration Header: ' + header)
for notice in self.notifications:
for header in self._requiredNotificationHeaders:
if not notice.get(header, False):
raise ParseError('Missing Notification Header: ' + header)
def decode(self, data, password):
"""Decode existing GNTP Registration message
:param string data: Message to decode
"""
self.raw = data
parts = self.raw.split('\r\n\r\n')
self.info = self._parse_info(data)
self._validate_password(password)
self.headers = self._parse_dict(parts[0])
for i, part in enumerate(parts):
if i == 0:
continue # Skip Header
if part.strip() == '':
continue
notice = self._parse_dict(part)
if notice.get('Notification-Name', False):
self.notifications.append(notice)
elif notice.get('Identifier', False):
notice['Data'] = self._decode_binary(part, notice)
#open('register.png','wblol').write(notice['Data'])
self.resources[notice.get('Identifier')] = notice
def add_notification(self, name, enabled=True):
"""Add new Notification to Registration message
:param string name: Notification Name
:param boolean enabled: Enable this notification by default
"""
notice = {}
notice['Notification-Name'] = u'%s' % name
notice['Notification-Enabled'] = u'%s' % enabled
self.notifications.append(notice)
self.add_header('Notifications-Count', len(self.notifications))
def encode(self):
"""Encode a GNTP Registration Message
:return string: Encoded GNTP Registration message
"""
buffer = _GNTPBuffer()
buffer.writefmt(self._format_info())
#Headers
for k, v in self.headers.iteritems():
buffer.writefmt('%s: %s', k, v)
buffer.writefmt()
#Notifications
if len(self.notifications) > 0:
for notice in self.notifications:
for k, v in notice.iteritems():
buffer.writefmt('%s: %s', k, v)
buffer.writefmt()
#Resources
for resource, data in self.resources.iteritems():
buffer.writefmt('Identifier: %s', resource)
buffer.writefmt('Length: %d', len(data))
buffer.writefmt()
buffer.write(data)
buffer.writefmt()
buffer.writefmt()
return buffer.getvalue()
class GNTPNotice(_GNTPBase):
"""Represents a GNTP Notification Command
:param string data: (Optional) See decode()
:param string app: (Optional) Set Application-Name
:param string name: (Optional) Set Notification-Name
:param string title: (Optional) Set Notification Title
:param string password: (Optional) Password to use while encoding/decoding messages
"""
_requiredHeaders = [
'Application-Name',
'Notification-Name',
'Notification-Title'
]
def __init__(self, data=None, app=None, name=None, title=None, password=None):
_GNTPBase.__init__(self, 'NOTIFY')
if data:
self.decode(data, password)
else:
self.set_password(password)
if app:
self.add_header('Application-Name', app)
if name:
self.add_header('Notification-Name', name)
if title:
self.add_header('Notification-Title', title)
def decode(self, data, password):
"""Decode existing GNTP Notification message
:param string data: Message to decode.
"""
self.raw = data
parts = self.raw.split('\r\n\r\n')
self.info = self._parse_info(data)
self._validate_password(password)
self.headers = self._parse_dict(parts[0])
for i, part in enumerate(parts):
if i == 0:
continue # Skip Header
if part.strip() == '':
continue
notice = self._parse_dict(part)
if notice.get('Identifier', False):
notice['Data'] = self._decode_binary(part, notice)
#open('notice.png','wblol').write(notice['Data'])
self.resources[notice.get('Identifier')] = notice
class GNTPSubscribe(_GNTPBase):
"""Represents a GNTP Subscribe Command
:param string data: (Optional) See decode()
:param string password: (Optional) Password to use while encoding/decoding messages
"""
_requiredHeaders = [
'Subscriber-ID',
'Subscriber-Name',
]
def __init__(self, data=None, password=None):
_GNTPBase.__init__(self, 'SUBSCRIBE')
if data:
self.decode(data, password)
else:
self.set_password(password)
class GNTPOK(_GNTPBase):
"""Represents a GNTP OK Response
:param string data: (Optional) See _GNTPResponse.decode()
:param string action: (Optional) Set type of action the OK Response is for
"""
_requiredHeaders = ['Response-Action']
def __init__(self, data=None, action=None):
_GNTPBase.__init__(self, '-OK')
if data:
self.decode(data)
if action:
self.add_header('Response-Action', action)
class GNTPError(_GNTPBase):
"""Represents a GNTP Error response
:param string data: (Optional) See _GNTPResponse.decode()
:param string errorcode: (Optional) Error code
:param string errordesc: (Optional) Error Description
"""
_requiredHeaders = ['Error-Code', 'Error-Description']
def __init__(self, data=None, errorcode=None, errordesc=None):
_GNTPBase.__init__(self, '-ERROR')
if data:
self.decode(data)
if errorcode:
self.add_header('Error-Code', errorcode)
self.add_header('Error-Description', errordesc)
def error(self):
return (self.headers.get('Error-Code', None),
self.headers.get('Error-Description', None))
def parse_gntp(data, password=None):
"""Attempt to parse a message as a GNTP message
:param string data: Message to be parsed
:param string password: Optional password to be used to verify the message
"""
match = GNTP_INFO_LINE_SHORT.match(data)
if not match:
raise ParseError('INVALID_GNTP_INFO')
info = match.groupdict()
if info['messagetype'] == 'REGISTER':
return GNTPRegister(data, password=password)
elif info['messagetype'] == 'NOTIFY':
return GNTPNotice(data, password=password)
elif info['messagetype'] == 'SUBSCRIBE':
return GNTPSubscribe(data, password=password)
elif info['messagetype'] == '-OK':
return GNTPOK(data)
elif info['messagetype'] == '-ERROR':
return GNTPError(data)
raise ParseError('INVALID_GNTP_MESSAGE')

141
gntp/cli.py Normal file
View File

@@ -0,0 +1,141 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
import logging
import os
import sys
from optparse import OptionParser, OptionGroup
from gntp.notifier import GrowlNotifier
from gntp.shim import RawConfigParser
from gntp.version import __version__
DEFAULT_CONFIG = os.path.expanduser('~/.gntp')
config = RawConfigParser({
'hostname': 'localhost',
'password': None,
'port': 23053,
})
config.read([DEFAULT_CONFIG])
if not config.has_section('gntp'):
config.add_section('gntp')
class ClientParser(OptionParser):
def __init__(self):
OptionParser.__init__(self, version="%%prog %s" % __version__)
group = OptionGroup(self, "Network Options")
group.add_option("-H", "--host",
dest="host", default=config.get('gntp', 'hostname'),
help="Specify a hostname to which to send a remote notification. [%default]")
group.add_option("--port",
dest="port", default=config.getint('gntp', 'port'), type="int",
help="port to listen on [%default]")
group.add_option("-P", "--password",
dest='password', default=config.get('gntp', 'password'),
help="Network password")
self.add_option_group(group)
group = OptionGroup(self, "Notification Options")
group.add_option("-n", "--name",
dest="app", default='Python GNTP Test Client',
help="Set the name of the application [%default]")
group.add_option("-s", "--sticky",
dest='sticky', default=False, action="store_true",
help="Make the notification sticky [%default]")
group.add_option("--image",
dest="icon", default=None,
help="Icon for notification (URL or /path/to/file)")
group.add_option("-m", "--message",
dest="message", default=None,
help="Sets the message instead of using stdin")
group.add_option("-p", "--priority",
dest="priority", default=0, type="int",
help="-2 to 2 [%default]")
group.add_option("-d", "--identifier",
dest="identifier",
help="Identifier for coalescing")
group.add_option("-t", "--title",
dest="title", default=None,
help="Set the title of the notification [%default]")
group.add_option("-N", "--notification",
dest="name", default='Notification',
help="Set the notification name [%default]")
group.add_option("--callback",
dest="callback",
help="URL callback")
self.add_option_group(group)
# Extra Options
self.add_option('-v', '--verbose',
dest='verbose', default=0, action='count',
help="Verbosity levels")
def parse_args(self, args=None, values=None):
values, args = OptionParser.parse_args(self, args, values)
if values.message is None:
print('Enter a message followed by Ctrl-D')
try:
message = sys.stdin.read()
except KeyboardInterrupt:
exit()
else:
message = values.message
if values.title is None:
values.title = ' '.join(args)
# If we still have an empty title, use the
# first bit of the message as the title
if values.title == '':
values.title = message[:20]
values.verbose = logging.WARNING - values.verbose * 10
return values, message
def main():
(options, message) = ClientParser().parse_args()
logging.basicConfig(level=options.verbose)
if not os.path.exists(DEFAULT_CONFIG):
logging.info('No config read found at %s', DEFAULT_CONFIG)
growl = GrowlNotifier(
applicationName=options.app,
notifications=[options.name],
defaultNotifications=[options.name],
hostname=options.host,
password=options.password,
port=options.port,
)
result = growl.register()
if result is not True:
exit(result)
# This would likely be better placed within the growl notifier
# class but until I make _checkIcon smarter this is "easier"
if options.icon and growl._checkIcon(options.icon) is False:
logging.info('Loading image %s', options.icon)
f = open(options.icon, 'rb')
options.icon = f.read()
f.close()
result = growl.notify(
noteType=options.name,
title=options.title,
description=message,
icon=options.icon,
sticky=options.sticky,
priority=options.priority,
callback=options.callback,
identifier=options.identifier,
)
if result is not True:
exit(result)
if __name__ == "__main__":
main()

77
gntp/config.py Normal file
View File

@@ -0,0 +1,77 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
"""
The gntp.config module is provided as an extended GrowlNotifier object that takes
advantage of the ConfigParser module to allow us to setup some default values
(such as hostname, password, and port) in a more global way to be shared among
programs using gntp
"""
import logging
import os
import gntp.notifier
import gntp.shim
__all__ = [
'mini',
'GrowlNotifier'
]
logger = logging.getLogger(__name__)
class GrowlNotifier(gntp.notifier.GrowlNotifier):
"""
ConfigParser enhanced GrowlNotifier object
For right now, we are only interested in letting users overide certain
values from ~/.gntp
::
[gntp]
hostname = ?
password = ?
port = ?
"""
def __init__(self, *args, **kwargs):
config = gntp.shim.RawConfigParser({
'hostname': kwargs.get('hostname', 'localhost'),
'password': kwargs.get('password'),
'port': kwargs.get('port', 23053),
})
config.read([os.path.expanduser('~/.gntp')])
# If the file does not exist, then there will be no gntp section defined
# and the config.get() lines below will get confused. Since we are not
# saving the config, it should be safe to just add it here so the
# code below doesn't complain
if not config.has_section('gntp'):
logger.info('Error reading ~/.gntp config file')
config.add_section('gntp')
kwargs['password'] = config.get('gntp', 'password')
kwargs['hostname'] = config.get('gntp', 'hostname')
kwargs['port'] = config.getint('gntp', 'port')
super(GrowlNotifier, self).__init__(*args, **kwargs)
def mini(description, **kwargs):
"""Single notification function
Simple notification function in one line. Has only one required parameter
and attempts to use reasonable defaults for everything else
:param string description: Notification message
"""
kwargs['notifierFactory'] = GrowlNotifier
gntp.notifier.mini(description, **kwargs)
if __name__ == '__main__':
# If we're running this module directly we're likely running it as a test
# so extra debugging is useful
logging.basicConfig(level=logging.INFO)
mini('Testing mini notification')

518
gntp/core.py Normal file
View File

@@ -0,0 +1,518 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
import hashlib
import re
import time
import gntp.shim
import gntp.errors as errors
__all__ = [
'GNTPRegister',
'GNTPNotice',
'GNTPSubscribe',
'GNTPOK',
'GNTPError',
'parse_gntp',
]
#GNTP/<version> <messagetype> <encryptionAlgorithmID>[:<ivValue>][ <keyHashAlgorithmID>:<keyHash>.<salt>]
GNTP_INFO_LINE = re.compile(
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)' +
' (?P<encryptionAlgorithmID>[A-Z0-9]+(:(?P<ivValue>[A-F0-9]+))?) ?' +
'((?P<keyHashAlgorithmID>[A-Z0-9]+):(?P<keyHash>[A-F0-9]+).(?P<salt>[A-F0-9]+))?\r\n',
re.IGNORECASE
)
GNTP_INFO_LINE_SHORT = re.compile(
'GNTP/(?P<version>\d+\.\d+) (?P<messagetype>REGISTER|NOTIFY|SUBSCRIBE|\-OK|\-ERROR)',
re.IGNORECASE
)
GNTP_HEADER = re.compile('([\w-]+):(.+)')
GNTP_EOL = gntp.shim.b('\r\n')
GNTP_SEP = gntp.shim.b(': ')
class _GNTPBuffer(gntp.shim.StringIO):
"""GNTP Buffer class"""
def writeln(self, value=None):
if value:
self.write(gntp.shim.b(value))
self.write(GNTP_EOL)
def writeheader(self, key, value):
if not isinstance(value, str):
value = str(value)
self.write(gntp.shim.b(key))
self.write(GNTP_SEP)
self.write(gntp.shim.b(value))
self.write(GNTP_EOL)
class _GNTPBase(object):
"""Base initilization
:param string messagetype: GNTP Message type
:param string version: GNTP Protocol version
:param string encription: Encryption protocol
"""
def __init__(self, messagetype=None, version='1.0', encryption=None):
self.info = {
'version': version,
'messagetype': messagetype,
'encryptionAlgorithmID': encryption
}
self.hash_algo = {
'MD5': hashlib.md5,
'SHA1': hashlib.sha1,
'SHA256': hashlib.sha256,
'SHA512': hashlib.sha512,
}
self.headers = {}
self.resources = {}
# For Python2 we can just return the bytes as is without worry
# but on Python3 we want to make sure we return the packet as
# a unicode string so that things like logging won't get confused
if gntp.shim.PY2:
def __str__(self):
return self.encode()
else:
def __str__(self):
return gntp.shim.u(self.encode())
def _parse_info(self, data):
"""Parse the first line of a GNTP message to get security and other info values
:param string data: GNTP Message
:return dict: Parsed GNTP Info line
"""
match = GNTP_INFO_LINE.match(data)
if not match:
raise errors.ParseError('ERROR_PARSING_INFO_LINE')
info = match.groupdict()
if info['encryptionAlgorithmID'] == 'NONE':
info['encryptionAlgorithmID'] = None
return info
def set_password(self, password, encryptAlgo='MD5'):
"""Set a password for a GNTP Message
:param string password: Null to clear password
:param string encryptAlgo: Supports MD5, SHA1, SHA256, SHA512
"""
if not password:
self.info['encryptionAlgorithmID'] = None
self.info['keyHashAlgorithm'] = None
return
self.password = gntp.shim.b(password)
self.encryptAlgo = encryptAlgo.upper()
if not self.encryptAlgo in self.hash_algo:
raise errors.UnsupportedError('INVALID HASH "%s"' % self.encryptAlgo)
hashfunction = self.hash_algo.get(self.encryptAlgo)
password = password.encode('utf8')
seed = time.ctime().encode('utf8')
salt = hashfunction(seed).hexdigest()
saltHash = hashfunction(seed).digest()
keyBasis = password + saltHash
key = hashfunction(keyBasis).digest()
keyHash = hashfunction(key).hexdigest()
self.info['keyHashAlgorithmID'] = self.encryptAlgo
self.info['keyHash'] = keyHash.upper()
self.info['salt'] = salt.upper()
def _decode_hex(self, value):
"""Helper function to decode hex string to `proper` hex string
:param string value: Human readable hex string
:return string: Hex string
"""
result = ''
for i in range(0, len(value), 2):
tmp = int(value[i:i + 2], 16)
result += chr(tmp)
return result
def _decode_binary(self, rawIdentifier, identifier):
rawIdentifier += '\r\n\r\n'
dataLength = int(identifier['Length'])
pointerStart = self.raw.find(rawIdentifier) + len(rawIdentifier)
pointerEnd = pointerStart + dataLength
data = self.raw[pointerStart:pointerEnd]
if not len(data) == dataLength:
raise errors.ParseError('INVALID_DATA_LENGTH Expected: %s Recieved %s' % (dataLength, len(data)))
return data
def _validate_password(self, password):
"""Validate GNTP Message against stored password"""
self.password = password
if password is None:
raise errors.AuthError('Missing password')
keyHash = self.info.get('keyHash', None)
if keyHash is None and self.password is None:
return True
if keyHash is None:
raise errors.AuthError('Invalid keyHash')
if self.password is None:
raise errors.AuthError('Missing password')
keyHashAlgorithmID = self.info.get('keyHashAlgorithmID','MD5')
password = self.password.encode('utf8')
saltHash = self._decode_hex(self.info['salt'])
keyBasis = password + saltHash
self.key = self.hash_algo[keyHashAlgorithmID](keyBasis).digest()
keyHash = self.hash_algo[keyHashAlgorithmID](self.key).hexdigest()
if not keyHash.upper() == self.info['keyHash'].upper():
raise errors.AuthError('Invalid Hash')
return True
def validate(self):
"""Verify required headers"""
for header in self._requiredHeaders:
if not self.headers.get(header, False):
raise errors.ParseError('Missing Notification Header: ' + header)
def _format_info(self):
"""Generate info line for GNTP Message
:return string:
"""
info = 'GNTP/%s %s' % (
self.info.get('version'),
self.info.get('messagetype'),
)
if self.info.get('encryptionAlgorithmID', None):
info += ' %s:%s' % (
self.info.get('encryptionAlgorithmID'),
self.info.get('ivValue'),
)
else:
info += ' NONE'
if self.info.get('keyHashAlgorithmID', None):
info += ' %s:%s.%s' % (
self.info.get('keyHashAlgorithmID'),
self.info.get('keyHash'),
self.info.get('salt')
)
return info
def _parse_dict(self, data):
"""Helper function to parse blocks of GNTP headers into a dictionary
:param string data:
:return dict: Dictionary of parsed GNTP Headers
"""
d = {}
for line in data.split('\r\n'):
match = GNTP_HEADER.match(line)
if not match:
continue
key = match.group(1).strip()
val = match.group(2).strip()
d[key] = val
return d
def add_header(self, key, value):
self.headers[key] = value
def add_resource(self, data):
"""Add binary resource
:param string data: Binary Data
"""
data = gntp.shim.b(data)
identifier = hashlib.md5(data).hexdigest()
self.resources[identifier] = data
return 'x-growl-resource://%s' % identifier
def decode(self, data, password=None):
"""Decode GNTP Message
:param string data:
"""
self.password = password
self.raw = gntp.shim.u(data)
parts = self.raw.split('\r\n\r\n')
self.info = self._parse_info(self.raw)
self.headers = self._parse_dict(parts[0])
def encode(self):
"""Encode a generic GNTP Message
:return string: GNTP Message ready to be sent. Returned as a byte string
"""
buff = _GNTPBuffer()
buff.writeln(self._format_info())
#Headers
for k, v in self.headers.items():
buff.writeheader(k, v)
buff.writeln()
#Resources
for resource, data in self.resources.items():
buff.writeheader('Identifier', resource)
buff.writeheader('Length', len(data))
buff.writeln()
buff.write(data)
buff.writeln()
buff.writeln()
return buff.getvalue()
class GNTPRegister(_GNTPBase):
"""Represents a GNTP Registration Command
:param string data: (Optional) See decode()
:param string password: (Optional) Password to use while encoding/decoding messages
"""
_requiredHeaders = [
'Application-Name',
'Notifications-Count'
]
_requiredNotificationHeaders = ['Notification-Name']
def __init__(self, data=None, password=None):
_GNTPBase.__init__(self, 'REGISTER')
self.notifications = []
if data:
self.decode(data, password)
else:
self.set_password(password)
self.add_header('Application-Name', 'pygntp')
self.add_header('Notifications-Count', 0)
def validate(self):
'''Validate required headers and validate notification headers'''
for header in self._requiredHeaders:
if not self.headers.get(header, False):
raise errors.ParseError('Missing Registration Header: ' + header)
for notice in self.notifications:
for header in self._requiredNotificationHeaders:
if not notice.get(header, False):
raise errors.ParseError('Missing Notification Header: ' + header)
def decode(self, data, password):
"""Decode existing GNTP Registration message
:param string data: Message to decode
"""
self.raw = gntp.shim.u(data)
parts = self.raw.split('\r\n\r\n')
self.info = self._parse_info(self.raw)
self._validate_password(password)
self.headers = self._parse_dict(parts[0])
for i, part in enumerate(parts):
if i == 0:
continue # Skip Header
if part.strip() == '':
continue
notice = self._parse_dict(part)
if notice.get('Notification-Name', False):
self.notifications.append(notice)
elif notice.get('Identifier', False):
notice['Data'] = self._decode_binary(part, notice)
#open('register.png','wblol').write(notice['Data'])
self.resources[notice.get('Identifier')] = notice
def add_notification(self, name, enabled=True):
"""Add new Notification to Registration message
:param string name: Notification Name
:param boolean enabled: Enable this notification by default
"""
notice = {}
notice['Notification-Name'] = name
notice['Notification-Enabled'] = enabled
self.notifications.append(notice)
self.add_header('Notifications-Count', len(self.notifications))
def encode(self):
"""Encode a GNTP Registration Message
:return string: Encoded GNTP Registration message. Returned as a byte string
"""
buff = _GNTPBuffer()
buff.writeln(self._format_info())
#Headers
for k, v in self.headers.items():
buff.writeheader(k, v)
buff.writeln()
#Notifications
if len(self.notifications) > 0:
for notice in self.notifications:
for k, v in notice.items():
buff.writeheader(k, v)
buff.writeln()
#Resources
for resource, data in self.resources.items():
buff.writeheader('Identifier', resource)
buff.writeheader('Length', len(data))
buff.writeln()
buff.write(data)
buff.writeln()
buff.writeln()
return buff.getvalue()
class GNTPNotice(_GNTPBase):
"""Represents a GNTP Notification Command
:param string data: (Optional) See decode()
:param string app: (Optional) Set Application-Name
:param string name: (Optional) Set Notification-Name
:param string title: (Optional) Set Notification Title
:param string password: (Optional) Password to use while encoding/decoding messages
"""
_requiredHeaders = [
'Application-Name',
'Notification-Name',
'Notification-Title'
]
def __init__(self, data=None, app=None, name=None, title=None, password=None):
_GNTPBase.__init__(self, 'NOTIFY')
if data:
self.decode(data, password)
else:
self.set_password(password)
if app:
self.add_header('Application-Name', app)
if name:
self.add_header('Notification-Name', name)
if title:
self.add_header('Notification-Title', title)
def decode(self, data, password):
"""Decode existing GNTP Notification message
:param string data: Message to decode.
"""
self.raw = gntp.shim.u(data)
parts = self.raw.split('\r\n\r\n')
self.info = self._parse_info(self.raw)
self._validate_password(password)
self.headers = self._parse_dict(parts[0])
for i, part in enumerate(parts):
if i == 0:
continue # Skip Header
if part.strip() == '':
continue
notice = self._parse_dict(part)
if notice.get('Identifier', False):
notice['Data'] = self._decode_binary(part, notice)
#open('notice.png','wblol').write(notice['Data'])
self.resources[notice.get('Identifier')] = notice
class GNTPSubscribe(_GNTPBase):
"""Represents a GNTP Subscribe Command
:param string data: (Optional) See decode()
:param string password: (Optional) Password to use while encoding/decoding messages
"""
_requiredHeaders = [
'Subscriber-ID',
'Subscriber-Name',
]
def __init__(self, data=None, password=None):
_GNTPBase.__init__(self, 'SUBSCRIBE')
if data:
self.decode(data, password)
else:
self.set_password(password)
class GNTPOK(_GNTPBase):
"""Represents a GNTP OK Response
:param string data: (Optional) See _GNTPResponse.decode()
:param string action: (Optional) Set type of action the OK Response is for
"""
_requiredHeaders = ['Response-Action']
def __init__(self, data=None, action=None):
_GNTPBase.__init__(self, '-OK')
if data:
self.decode(data)
if action:
self.add_header('Response-Action', action)
class GNTPError(_GNTPBase):
"""Represents a GNTP Error response
:param string data: (Optional) See _GNTPResponse.decode()
:param string errorcode: (Optional) Error code
:param string errordesc: (Optional) Error Description
"""
_requiredHeaders = ['Error-Code', 'Error-Description']
def __init__(self, data=None, errorcode=None, errordesc=None):
_GNTPBase.__init__(self, '-ERROR')
if data:
self.decode(data)
if errorcode:
self.add_header('Error-Code', errorcode)
self.add_header('Error-Description', errordesc)
def error(self):
return (self.headers.get('Error-Code', None),
self.headers.get('Error-Description', None))
def parse_gntp(data, password=None):
"""Attempt to parse a message as a GNTP message
:param string data: Message to be parsed
:param string password: Optional password to be used to verify the message
"""
data = gntp.shim.u(data)
match = GNTP_INFO_LINE_SHORT.match(data)
if not match:
raise errors.ParseError('INVALID_GNTP_INFO')
info = match.groupdict()
if info['messagetype'] == 'REGISTER':
return GNTPRegister(data, password=password)
elif info['messagetype'] == 'NOTIFY':
return GNTPNotice(data, password=password)
elif info['messagetype'] == 'SUBSCRIBE':
return GNTPSubscribe(data, password=password)
elif info['messagetype'] == '-OK':
return GNTPOK(data)
elif info['messagetype'] == '-ERROR':
return GNTPError(data)
raise errors.ParseError('INVALID_GNTP_MESSAGE')

25
gntp/errors.py Normal file
View File

@@ -0,0 +1,25 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
class BaseError(Exception):
pass
class ParseError(BaseError):
errorcode = 500
errordesc = 'Error parsing the message'
class AuthError(BaseError):
errorcode = 400
errordesc = 'Error with authorization'
class UnsupportedError(BaseError):
errorcode = 500
errordesc = 'Currently unsupported by gntp.py'
class NetworkError(BaseError):
errorcode = 500
errordesc = "Error connecting to growl server"

View File

@@ -1,3 +1,6 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
"""
The gntp.notifier module is provided as a simple way to send notifications
using GNTP
@@ -9,10 +12,15 @@ using GNTP
`Original Python bindings <http://code.google.com/p/growl/source/browse/Bindings/python/Growl.py>`_
"""
import gntp
import socket
import logging
import platform
import socket
import sys
from gntp.version import __version__
import gntp.core
import gntp.errors as errors
import gntp.shim
__all__ = [
'mini',
@@ -22,45 +30,6 @@ __all__ = [
logger = logging.getLogger(__name__)
def mini(description, applicationName='PythonMini', noteType="Message",
title="Mini Message", applicationIcon=None, hostname='localhost',
password=None, port=23053, sticky=False, priority=None,
callback=None, notificationIcon=None, identifier=None):
"""Single notification function
Simple notification function in one line. Has only one required parameter
and attempts to use reasonable defaults for everything else
:param string description: Notification message
.. warning::
For now, only URL callbacks are supported. In the future, the
callback argument will also support a function
"""
growl = GrowlNotifier(
applicationName=applicationName,
notifications=[noteType],
defaultNotifications=[noteType],
applicationIcon=applicationIcon,
hostname=hostname,
password=password,
port=port,
)
result = growl.register()
if result is not True:
return result
return growl.notify(
noteType=noteType,
title=title,
description=description,
icon=notificationIcon,
sticky=sticky,
priority=priority,
callback=callback,
identifier=identifier,
)
class GrowlNotifier(object):
"""Helper class to simplfy sending Growl messages
@@ -99,8 +68,9 @@ class GrowlNotifier(object):
If it's a simple URL icon, then we return True. If it's a data icon
then we return False
'''
logger.debug('Checking icon')
return data.startswith('http')
logger.info('Checking icon')
return gntp.shim.u(data)[:4] in ['http', 'file']
def register(self):
"""Send GNTP Registration
@@ -109,8 +79,8 @@ class GrowlNotifier(object):
Before sending notifications to Growl, you need to have
sent a registration message at least once
"""
logger.debug('Sending registration to %s:%s', self.hostname, self.port)
register = gntp.GNTPRegister()
logger.info('Sending registration to %s:%s', self.hostname, self.port)
register = gntp.core.GNTPRegister()
register.add_header('Application-Name', self.applicationName)
for notification in self.notifications:
enabled = notification in self.defaultNotifications
@@ -119,8 +89,8 @@ class GrowlNotifier(object):
if self._checkIcon(self.applicationIcon):
register.add_header('Application-Icon', self.applicationIcon)
else:
id = register.add_resource(self.applicationIcon)
register.add_header('Application-Icon', id)
resource = register.add_resource(self.applicationIcon)
register.add_header('Application-Icon', resource)
if self.password:
register.set_password(self.password, self.passwordHash)
self.add_origin_info(register)
@@ -128,7 +98,7 @@ class GrowlNotifier(object):
return self._send('register', register)
def notify(self, noteType, title, description, icon=None, sticky=False,
priority=None, callback=None, identifier=None):
priority=None, callback=None, identifier=None, custom={}):
"""Send a GNTP notifications
.. warning::
@@ -141,14 +111,16 @@ class GrowlNotifier(object):
:param boolean sticky: Sticky notification
:param integer priority: Message priority level from -2 to 2
:param string callback: URL callback
:param dict custom: Custom attributes. Key names should be prefixed with X-
according to the spec but this is not enforced by this class
.. warning::
For now, only URL callbacks are supported. In the future, the
callback argument will also support a function
"""
logger.debug('Sending notification [%s] to %s:%s', noteType, self.hostname, self.port)
logger.info('Sending notification [%s] to %s:%s', noteType, self.hostname, self.port)
assert noteType in self.notifications
notice = gntp.GNTPNotice()
notice = gntp.core.GNTPNotice()
notice.add_header('Application-Name', self.applicationName)
notice.add_header('Notification-Name', noteType)
notice.add_header('Notification-Title', title)
@@ -162,8 +134,8 @@ class GrowlNotifier(object):
if self._checkIcon(icon):
notice.add_header('Notification-Icon', icon)
else:
id = notice.add_resource(icon)
notice.add_header('Notification-Icon', id)
resource = notice.add_resource(icon)
notice.add_header('Notification-Icon', resource)
if description:
notice.add_header('Notification-Text', description)
@@ -172,6 +144,9 @@ class GrowlNotifier(object):
if identifier:
notice.add_header('Notification-Coalescing-ID', identifier)
for key in custom:
notice.add_header(key, custom[key])
self.add_origin_info(notice)
self.notify_hook(notice)
@@ -179,7 +154,7 @@ class GrowlNotifier(object):
def subscribe(self, id, name, port):
"""Send a Subscribe request to a remote machine"""
sub = gntp.GNTPSubscribe()
sub = gntp.core.GNTPSubscribe()
sub.add_header('Subscriber-ID', id)
sub.add_header('Subscriber-Name', name)
sub.add_header('Subscriber-Port', port)
@@ -195,7 +170,7 @@ class GrowlNotifier(object):
"""Add optional Origin headers to message"""
packet.add_header('Origin-Machine-Name', platform.node())
packet.add_header('Origin-Software-Name', 'gntp.py')
packet.add_header('Origin-Software-Version', gntp.__version__)
packet.add_header('Origin-Software-Version', __version__)
packet.add_header('Origin-Platform-Name', platform.system())
packet.add_header('Origin-Platform-Version', platform.platform())
@@ -214,34 +189,78 @@ class GrowlNotifier(object):
packet.validate()
data = packet.encode()
#logger.debug('To : %s:%s <%s>\n%s', self.hostname, self.port, packet.__class__, data)
#Less verbose
logger.debug('To : %s:%s <%s>', self.hostname, self.port, packet.__class__)
logger.debug('To : %s:%s <%s>\n%s', self.hostname, self.port, packet.__class__, data)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(self.socketTimeout)
s.connect((self.hostname, self.port))
s.send(data)
recv_data = s.recv(1024)
while not recv_data.endswith("\r\n\r\n"):
recv_data += s.recv(1024)
response = gntp.parse_gntp(recv_data)
try:
s.connect((self.hostname, self.port))
s.send(data)
recv_data = s.recv(1024)
while not recv_data.endswith(gntp.shim.b("\r\n\r\n")):
recv_data += s.recv(1024)
except socket.error:
# Python2.5 and Python3 compatibile exception
exc = sys.exc_info()[1]
raise errors.NetworkError(exc)
response = gntp.core.parse_gntp(recv_data)
s.close()
#logger.debug('From : %s:%s <%s>\n%s', self.hostname, self.port, response.__class__, response)
#Less verbose
logger.debug('From : %s:%s <%s>', self.hostname, self.port, response.__class__)
logger.debug('From : %s:%s <%s>\n%s', self.hostname, self.port, response.__class__, response)
if type(response) == gntp.GNTPOK:
return True
if response.error()[0] == '404' and 'disabled' in response.error()[1]:
# Ignore message saying that user has disabled this class
if type(response) == gntp.core.GNTPOK:
return True
logger.error('Invalid response: %s', response.error())
return response.error()
def mini(description, applicationName='PythonMini', noteType="Message",
title="Mini Message", applicationIcon=None, hostname='localhost',
password=None, port=23053, sticky=False, priority=None,
callback=None, notificationIcon=None, identifier=None,
notifierFactory=GrowlNotifier):
"""Single notification function
Simple notification function in one line. Has only one required parameter
and attempts to use reasonable defaults for everything else
:param string description: Notification message
.. warning::
For now, only URL callbacks are supported. In the future, the
callback argument will also support a function
"""
try:
growl = notifierFactory(
applicationName=applicationName,
notifications=[noteType],
defaultNotifications=[noteType],
applicationIcon=applicationIcon,
hostname=hostname,
password=password,
port=port,
)
result = growl.register()
if result is not True:
return result
return growl.notify(
noteType=noteType,
title=title,
description=description,
icon=notificationIcon,
sticky=sticky,
priority=priority,
callback=callback,
identifier=identifier,
)
except Exception:
# We want the "mini" function to be simple and swallow Exceptions
# in order to be less invasive
logger.exception("Growl error")
if __name__ == '__main__':
# If we're running this module directly we're likely running it as a test
# so extra debugging is useful
logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(level=logging.INFO)
mini('Testing mini notification')

46
gntp/shim.py Normal file
View File

@@ -0,0 +1,46 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
"""
Python2.5 and Python3.3 compatibility shim
Heavily inspirted by the "six" library.
https://pypi.python.org/pypi/six
"""
import sys
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
if PY3:
def b(s):
if isinstance(s, bytes):
return s
return s.encode('utf8', 'replace')
def u(s):
if isinstance(s, bytes):
return s.decode('utf8', 'replace')
return s
from io import BytesIO as StringIO
from configparser import RawConfigParser
else:
def b(s):
if isinstance(s, unicode):
return s.encode('utf8', 'replace')
return s
def u(s):
if isinstance(s, unicode):
return s
if isinstance(s, int):
s = str(s)
return unicode(s, "utf8", "replace")
from StringIO import StringIO
from ConfigParser import RawConfigParser
b.__doc__ = "Ensure we have a byte string"
u.__doc__ = "Ensure we have a unicode string"

4
gntp/version.py Normal file
View File

@@ -0,0 +1,4 @@
# Copyright: 2013 Paul Traylor
# These sources are released under the terms of the MIT license: see LICENSE
__version__ = '1.0.3'

View File

@@ -22,7 +22,7 @@
</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1" />
<meta name="apple-mobile-web-app-title" content="SABnzbd" />
<link rel="apple-touch-icon" sizes="76x76" href="${root}staticcfg/ico/apple-touch-icon-76x76-precomposed.png" />
@@ -32,7 +32,9 @@
<link rel="apple-touch-icon" sizes="192x192" href="${root}staticcfg/ico/android-192x192.png" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/bootstrap/css/bootstrap.min.css?v=$version" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/style.css?p=$pid" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/chartist.min.css" />
<link rel="stylesheet" type="text/css" href="${root}staticcfg/css/style.css?v=$version" />
<link rel="shortcut icon" href="${root}staticcfg/ico/favicon.ico?v=$version" />
<script type="text/javascript">
@@ -43,6 +45,7 @@
// Information we need
var sabSession = '$session';
var rootURL = '${root}'
var urlBase = '${url_base}'
var folderBrowseUrl = '${root}tapi?mode=browse&output=json&apikey=$session';
var folderSeperator = '#if $os.sep == '\\' then '\\\\' else '/'#'
@@ -59,7 +62,7 @@
configTranslate.confirmLeave = "$T('confirmWithoutSavingPrompt')";
configTranslate.searchPages = ['$T('cmenu-general')', '$T('cmenu-folders')', '$T('cmenu-switches')', '$T('cmenu-sorting')', '$T('cmenu-notif')', '$T('cmenu-special')']
</script>
<script type="text/javascript" src="${root}staticcfg/js/jquery-3.1.1.min.js?v=$version"></script>
<script type="text/javascript" src="${root}staticcfg/js/jquery-3.2.1.min.js?v=$version"></script>
<script type="text/javascript" src="${root}staticcfg/bootstrap/js/bootstrap.min.js?v=$version"></script>
<script type="text/javascript" src="${root}staticcfg/js/script.js?v=$version"></script>
<script type="text/javascript">

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Config"#-->
<!--#set global $help_uri="configuration/2.0/configure"#-->
<!--#set global $help_uri="configuration/2.2/configure"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<!--#from locale import getpreferredencoding#-->
@@ -19,10 +19,6 @@
<th scope="row">$T('confgFile'): </th>
<td>$configfn</td>
</tr>
<tr>
<th scope="row">$T('cache').capitalize(): </th>
<td><!--#set $msg=$T('ft-buffer@2')%($cache_art, $cache_size)#-->$msg</td>
</tr>
<tr>
<th scope="row">$T('parameters'): </th>
<td>$cmdline</td>
@@ -34,7 +30,7 @@
<tr>
<th scope="row">OpenSSL:</th>
<td>
$ssl_version &nbsp; [$ssl_protocols]
$ssl_version
</td>
</tr>
<!--#if not $have_ssl_context#-->
@@ -45,12 +41,16 @@
</td>
</tr>
<!--#end if#-->
<!--#if not $have_mt_par2#-->
<!--#if not $nt and not $darwin#-->
<tr>
<th scope="row">Multicore Par2</th>
<th scope="row">$T('opt-multicore-par2')</th>
<td>
<!--#if $have_mt_par2#-->
<span class="glyphicon glyphicon-ok"></span>
<!--#else#-->
<span class="label label-warning">$T('notAvailable')</span> $T('explain-getpar2mt')
<a href="${helpuri}installation/multicore-par2" target="_blank">${helpuri}installation/multicore-par2</a>
<!--#end if#-->
</td>
</tr>
<!--#end if#-->
@@ -109,7 +109,7 @@
<tbody>
<tr>
<th scope="row">$T('homePage') </th>
<td><a href="http://sabnzbd.org/" target="_blank">http://sabnzbd.org/</a></td>
<td><a href="https://sabnzbd.org/" target="_blank">https://sabnzbd.org/</a></td>
</tr>
<tr>
<th scope="row">$T('menu-wiki') </th>
@@ -117,7 +117,7 @@
</tr>
<tr>
<th scope="row">$T('menu-forums') </th>
<td><a href="http://forums.sabnzbd.org/" target="_blank">http://forums.sabnzbd.org/</a></td>
<td><a href="https://forums.sabnzbd.org/" target="_blank">https://forums.sabnzbd.org/</a></td>
</tr>
<tr>
<th scope="row">$T('source') </th>
@@ -131,6 +131,10 @@
<th scope="row">$T('menu-issues') </th>
<td><a href="https://sabnzbd.org/wiki/introduction/known-issues" target="_blank">https://sabnzbd.org/wiki/introduction/known-issues</a></td>
</tr>
<tr>
<th scope="row">$T('menu-donate') </th>
<td><a href="https://sabnzbd.org/donate" target="_blank">https://sabnzbd.org/donate</a></td>
</tr>
</tbody>
</table>
</div>

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Categories"#-->
<!--#set global $help_uri="configuration/2.0/categories"#-->
<!--#set global $help_uri="configuration/2.2/categories"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
<div class="section">
@@ -17,9 +17,7 @@
<th>$T('category')</th>
<th>$T('priority')</th>
<th>$T('mode')</th>
<!--#if $scripts#-->
<th>$T('script')</th>
<!--#end if#-->
<th>$T('catFolderPath')</th>
<th colspan="2">$T('catTags')</th>
</tr>
@@ -61,8 +59,8 @@
<option value="3" <!--#if $slot.pp=="3" then 'selected="selected"' else ""#-->>$T('pp-delete')</option>
</select>
</td>
<!--#if $scripts#-->
<td>
<!--#if $scripts#-->
<select name="script">
<!--#for $sc in $scripts#-->
<!--#if not ($sc == 'Default' and $slot.name == '*')#-->
@@ -70,8 +68,12 @@
<!--#end if#-->
<!--#end for#-->
</select>
<!--#else#-->
<select name="script" disabled>
</select>
<!--#end if#-->
</td>
<!--#end if#-->
<td class="nowrap">
<input type="text" name="dir" class="fileBrowserSmall" value="$slot.dir" size="20" data-initialdir="$defdir" data-title="$T('catFolderPath')" title="$T('explain-catTags2')" />
</td>

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Folders"#-->
<!--#set global $help_uri="configuration/2.0/folders"#-->
<!--#set global $help_uri="configuration/2.2/folders"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="General"#-->
<!--#set global $help_uri="configuration/2.0/general"#-->
<!--#set global $help_uri="configuration/2.2/general"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
@@ -25,7 +25,7 @@
</div>
<div class="field-pair">
<label class="config" for="enable_https">$T('opt-enable_https')</label>
<input type="checkbox" name="enable_https" id="enable_https" value="1" <!--#if int($enable_https) > 0 then 'checked="checked"' else ""#-->/>
<input type="checkbox" name="enable_https" id="enable_https" value="1" <!--#if int($enable_https) > 0 then 'checked="checked" data-original="1"' else ""#-->/>
<span class="desc">$T('explain-enable_https')</span>
</div>
<div class="field-pair">
@@ -240,7 +240,17 @@
\$('.alert-translate').show()
}
}
\$('#language').on('change', hideOrShowTranslate)
\$('#language').on('change', function() {
// Show message
hideOrShowTranslate()
// Re-load page on submit
\$('.fullform').submit(function() {
// Skip the fancy stuff, just submit
this.submit()
})
// No JSON reponse
\$('#ajax').val('')
})
hideOrShowTranslate()
\$('#apikey, #nzbkey').click(function () { \$(this).select() });
@@ -314,14 +324,20 @@
if(bandwidthLimit) {
var bandwithLimitNumber = parseFloat(bandwidthLimit)
var bandwithLimitText = bandwidthLimit.replace(/[^a-zA-Z]+/g, '');
\$('#bandwidth_max_value').val(bandwithLimitNumber)
\$('#bandwidth_max_dropdown').val(bandwithLimitText)
if(bandwithLimitNumber) {
\$('#bandwidth_max_value').val(bandwithLimitNumber)
\$('#bandwidth_max_dropdown').val(bandwithLimitText)
}
}
// Update the value
\$('#bandwidth_max_value, #bandwidth_max_dropdown').on('change', function() {
\$('#bandwidth_max').val(\$('#bandwidth_max_value').val() + \$('#bandwidth_max_dropdown').val())
if(\$('#bandwidth_max_value').val()) {
\$('#bandwidth_max').val(\$('#bandwidth_max_value').val() + \$('#bandwidth_max_dropdown').val())
} else {
\$('#bandwidth_max').val('')
}
})
});

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Email"#-->
<!--#set global $help_uri="configuration/2.0/notifications"#-->
<!--#set global $help_uri="configuration/2.2/notifications"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<!--#def show_notify_checkboxes($section_label)#-->
@@ -13,6 +13,18 @@
<!--#end for#-->
<!--#end def#-->
<!--#def show_cat_box($section_label)#-->
<div class="col2-cats" <!--#if int($getVar($section_label + '_enable')) > 0 then '' else 'style="display:none"'#-->>
<hr>
<b>$T('affectedCat')</b><br/>
<select name="${section_label}_cats" multiple="multiple" class="multiple_cats">
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $ct in $getVar($section_label + '_cats') then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
<!--#end for#-->
</select>
</div>
<!--#end def#-->
<div class="colmask">
<form action="saveEmail" method="post" name="fullform" class="fullform" autocomplete="off" novalidate>
<input type="hidden" id="session" name="session" value="$session" />
@@ -20,7 +32,15 @@
<div class="section" id="email">
<div class="col2">
<h3>$T('cmenu-email') <a href="$helpuri$help_uri#toc0" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
</div><!-- /col2 -->
<div class="col2-cats" <!--#if int($email_endjob) > 0 then '' else 'style="display:none"'#-->>
<b>$T('affectedCat')</b><br/>
<select name="email_cats" multiple="multiple" class="multiple_cats">
<!--#for $ct in $categories#-->
<option value="$ct" <!--#if $ct in $email_cats then 'selected="selected"' else ""#-->>$Tspec($ct)</option>
<!--#end for#-->
</select>
</div>
</div>
<div class="col1">
<fieldset>
<div class="field-pair">
@@ -79,8 +99,8 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<!--#if $have_ncenter#-->
<div class="section">
<div class="col2">
@@ -91,7 +111,7 @@
<td><label for="ncenter_enable"> $T('opt-ncenter_enable')</label></td>
</tr>
</table>
</div><!-- /col2 -->
</div>
<div class="col1" <!--#if int($ncenter_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
$show_notify_checkboxes('ncenter')
@@ -103,8 +123,8 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<!--#end if#-->
<!--#if $nt#-->
<div class="section">
@@ -116,7 +136,8 @@
<td><label for="acenter_enable"> $T('opt-acenter_enable')</label></td>
</tr>
</table>
</div><!-- /col2 -->
$show_cat_box('acenter')
</div>
<div class="col1" <!--#if int($acenter_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
$show_notify_checkboxes('acenter')
@@ -128,8 +149,8 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<!--#end if#-->
<!--#if $have_ntfosd#-->
<div class="section">
@@ -141,7 +162,8 @@
<td><label for="ntfosd_enable"> $T('opt-ntfosd_enable')</label></td>
</tr>
</table>
</div><!-- /col2 -->
$show_cat_box('ntfosd')
</div>
<div class="col1" <!--#if int($ntfosd_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
$show_notify_checkboxes('ntfosd')
@@ -153,9 +175,48 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<!--#end if#-->
<div class="section" id="nscript">
<div class="col2">
<h3>$T('section-NScript')</h3>
<table>
<tr>
<td><input type="checkbox" name="nscript_enable" id="nscript_enable" value="1" <!--#if int($nscript_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="nscript_enable"> $T('opt-nscript_enable')</label></td>
</tr>
</table>
<em>$T('explain-nscript_enable')</em><br><a href="$helpuri$help_uri#nscript" target="_blank">$T('readwiki')</a>
$show_cat_box('nscript')
</div>
<div class="col1" <!--#if int($nscript_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
<label class="config" for="nscript_script">$T('opt-nscript_script')</label>
<select name="nscript_script">
<!--#for $sc in $scripts#-->
<option value="$sc" <!--#if $nscript_script == $sc then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
<!--#end for#-->
</select>
<span class="desc">$T('explain-nscript_script')</span>
</div>
<div class="field-pair">
<label class="config" for="nscript_parameters">$T('opt-nscript_parameters')</label>
<input type="text" name="nscript_parameters" id="nscript_parameters" value="$nscript_parameters" />
<span class="desc">$T('Optional') - $T('explain-nscript_parameters')</span>
</div>
$show_notify_checkboxes('nscript')
<div class="field-pair">
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
<button class="btn btn-default" type="button" id="test_nscript"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
</div>
</fieldset>
</div>
</div>
<div class="section" id="growl">
<div class="col2">
<h3>$T('growlSettings') <a href="$helpuri$help_uri#toc3" target="_blank"><span class="glyphicon glyphicon-question-sign"></span></a></h3>
@@ -165,7 +226,8 @@
<td><label for="growl_enable"> $T('opt-growl_enable')</label></td>
</tr>
</table>
</div><!-- /col2 -->
$show_cat_box('growl')
</div>
<div class="col1" <!--#if int($growl_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
@@ -187,8 +249,8 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<div class="section" id="prowl">
<div class="col2">
<h3>$T('section-Prowl')</h3>
@@ -199,7 +261,8 @@
</tr>
</table>
<em>$T('explain-prowl_enable')</em>
</div><!-- /col2 -->
$show_cat_box('prowl')
</div>
<div class="col1" <!--#if int($prowl_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
@@ -231,8 +294,8 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<div class="section" id="pushover">
<div class="col2">
@@ -244,7 +307,8 @@
</tr>
</table>
<em>$T('explain-pushover_enable')</em>
</div><!-- /col2 -->
$show_cat_box('pushover')
</div>
<div class="col1" <!--#if int($pushover_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
@@ -286,8 +350,8 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
<div class="section" id="pushbullet">
<div class="col2">
<h3>$T('section-Pushbullet')</h3>
@@ -298,7 +362,8 @@
</tr>
</table>
<em>$T('explain-pushbullet_enable')</em>
</div><!-- /col2 -->
$show_cat_box('pushbullet')
</div>
<div class="col1" <!--#if int($pushbullet_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
@@ -322,46 +387,8 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
<div class="section" id="nscript">
<div class="col2">
<h3>$T('section-NScript')</h3>
<table>
<tr>
<td><input type="checkbox" name="nscript_enable" id="nscript_enable" value="1" <!--#if int($nscript_enable) > 0 then 'checked="checked"' else ""#--> /></td>
<td><label for="nscript_enable"> $T('opt-nscript_enable')</label></td>
</tr>
</table>
<em>$T('explain-nscript_enable')</em>
</div><!-- /col2 -->
<div class="col1" <!--#if int($nscript_enable) > 0 then '' else 'style="display:none"'#-->>
<fieldset>
<div class="field-pair">
<label class="config" for="nscript_script">$T('opt-nscript_script')</label>
<select name="nscript_script">
<!--#for $sc in $scripts#-->
<option value="$sc" <!--#if $nscript_script == $sc then 'selected="selected"' else ""#-->>$Tspec($sc)</option>
<!--#end for#-->
</select>
<span class="desc">$T('explain-nscript_script')</span>
</div>
<div class="field-pair">
<label class="config" for="nscript_parameters">$T('opt-nscript_parameters')</label>
<input type="text" name="nscript_parameters" id="nscript_parameters" value="$nscript_parameters" />
<span class="desc">$T('Optional') - $T('explain-nscript_parameters')</span>
</div>
$show_notify_checkboxes('nscript')
<div class="field-pair">
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
<button class="btn btn-default" type="button" id="test_nscript"><span class="glyphicon glyphicon-comment"></span> $T('testNotify')</button>
</div>
<div class="field-pair result-box">
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
</div><!-- /section -->
</div>
</div>
</form>
</div><!-- /colmask -->
@@ -374,11 +401,20 @@
\$('.col2 input[name$="enable"]').change(function() {
if(this.checked) {
\$(this).parents('.section').find('.col1').show()
\$(this).parents('.col2').find('.col2-cats').show()
} else {
\$(this).parents('.section').find('.col1').hide()
\$(this).parents('.col2').find('.col2-cats').hide()
}
\$('form').submit()
})
\$('#email_endjob').change(function() {
if(\$(this).val() > 0) {
\$(this).parents('.section').find('.col2-cats').show()
} else {
\$(this).parents('.section').find('.col2-cats').hide()
}
})
/**
Testing functions

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="RSS"#-->
<!--#set global $help_uri="configuration/2.0/rss"#-->
<!--#set global $help_uri="configuration/2.2/rss"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
<!--#if not $active_feed#-->

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Scheduling"#-->
<!--#set global $help_uri="configuration/2.0/scheduling"#-->
<!--#set global $help_uri="configuration/2.2/scheduling"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<%
@@ -59,6 +59,13 @@ else:
<option value="$server" data-action="0" data-noarg="1">$T('sch-disable_server') "$actions_servers[$server]"</option>
<!--#end for#-->
</optgroup>
<optgroup label="$T('cmenu-cat')">
<!--#for $cat in $categories#-->
<!--#set $cat_text = $T('Default') if $cat == '*' else $cat#-->
<option value="pause_cat" data-action="$cat" data-noarg="1">$T('sch-pause_cat') "$cat_text"</option>
<option value="resume_cat" data-action="$cat" data-noarg="1">$T('sch-resume_cat') "$cat_text"</option>
<!--#end for#-->
</optgroup>
</select>
</div>
<div class="field-pair" id="hidden_arguments" style="display: none">

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Servers"#-->
<!--#set global $help_uri="configuration/2.0/servers"#-->
<!--#set global $help_uri="configuration/2.2/servers"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
@@ -78,17 +78,6 @@
<input type="checkbox" name="optional" id="optional" value="1" />
<span class="desc">$T('explain-optional')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="categories">$T('srv-categories')</label>
<select name="categories" id="categories" multiple>
<!--#for $cat in $cats#-->
<option value="$cat" <!--#if $cat == "Default"#-->selected<!--#end if#-->>
<!--#if $cat == "Default" then $T('Default') else $cat#-->
</option>
<!--#end for#-->
</select>
<span class="desc">$T('srv-explain-categories')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="displayname">$T('srv-displayname')</label>
<input type="text" name="displayname" id="displayname" />
@@ -110,6 +99,59 @@
</div><!-- /section -->
</form>
<script type="text/javascript" src="${root}staticcfg/js/chartist.min.js"></script>
<script type="text/javascript">
// Define variables needed for the server-plots
var serverData = {}
var chartOptions = {
fullWidth: true,
showArea: true,
axisX: {
labelOffset: {
x: -5
},
showGrid: false
},
axisY: {
labelOffset: {
y: 7
},
scaleMinSpace: 30
},
chartPadding: {
top: 9,
bottom: 0,
left: 30,
right: 20
}
}
</script>
<!--
We need to find how many months we have recorded so far, so we
loop over all the dates to find the lowest value and then use
this to calculate the date-selector
-->
<!--#import json#-->
<!--#import datetime#-->
<!--#import sabnzbd.misc#-->
<!--#def show_date_selector($server, $id)#-->
<!--#set month_names = [$T('January'), $T('February'), $T('March'), $T('April'), $T('May'), $T('June'), $T('July'), $T('August'), $T('September'), $T('October'), $T('November'), $T('December')] #-->
<!--#set min_date = datetime.date.today()#-->
<!--#for date in $server['amounts'][4]#-->
<!--#set split_date = $date.split('-')#-->
<!--#set min_date = min(min_date, datetime.date(int(split_date[0]), int(split_date[1]), 1))#-->
<!--#end for#-->
<!--#set months_recorded = list(sabnzbd.misc.monthrange(min_date, datetime.date.today()))#-->
<!--#$months_recorded.reverse()#-->
<select class="chart-selector" name="chart-selector-${id}" id="chart-selector-${id}" data-id="${id}">
<!--#for $cur_date in months_recorded#-->
<option value="<!--#echo '%d-%02d' % ($cur_date.year, $cur_date.month)#-->">$month_names[$cur_date.month-1] $cur_date.year</option>
<!--#end for#-->
</select>
<!--#end def#-->
<!--#set $prio_colors = ["#59cc33", "#3366cc","#7f33cc", "#cc33a6", "#cc3333"] #-->
<!--#set $cur_prio_color = -1 #-->
<!--#set $last_prio = -1 #-->
@@ -199,21 +241,6 @@
<input type="checkbox" name="send_group" id="send_group$cur" value="1" <!--#if int($server['send_group']) != 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('srv-explain-send_group')</span>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="categories$cur">$T('srv-categories')</label>
<select name="categories" id="categories$cur" multiple>
<!--#for $cat in $cats#-->
<option value="$cat" <!--#if $cat in $server['categories'] then 'selected' else ""#-->>
<!--#if $cat == "Default" then $T('Default') else $cat#-->
</option>
<!--#end for#-->
</select>
<span class="desc">$T('srv-explain-categories')</span>
<div class="alert alert-info alert-no-category">
<span class="glyphicon glyphicon-info-sign"></span>
$T('srv-explain-no-categories')
</div>
</div>
<div class="field-pair advanced-settings">
<label class="config" for="displayname$cur">$T('srv-displayname')</label>
<input type="text" name="displayname" id="displayname$cur" value="$server['displayname']" />
@@ -232,165 +259,252 @@
<div class="alert"></div>
</div>
</fieldset>
</div><!-- /col1 -->
<div class="col2" style="display:block;">
<!--#if 'amounts' in $server#-->
<b>$T('srv-bandwidth'):</b><br/>
$T('total'): $(server['amounts'][0])B<br/>
$T('today'): $(server['amounts'][3])B<br/>
$T('thisWeek'): $(server['amounts'][2])B<br/>
$T('thisMonth'): $(server['amounts'][1])B
<!--#end if#-->
</div>
</div><!-- /section -->
<div class="col1" style="display:block;">
<!--#if 'amounts' in $server#-->
<div class="server-amounts-text">
<b>$T('srv-bandwidth'):</b><br/>
$T('total'): $(server['amounts'][0])B<br/>
$T('today'): $(server['amounts'][3])B<br/>
$T('thisWeek'): $(server['amounts'][2])B<br/>
$T('thisMonth'): $(server['amounts'][1])B
</div>
<div class="server-chart">
$show_date_selector($server, $cur)
<div id="server-chart-${cur}" class="ct-chart"></div>
</div>
<script type="text/javascript">
// Server data
serverData[${cur}] = <!--#echo json.dumps($server['amounts'][4])#-->
\$(document).ready(function() {
showChart(${cur}, \$('#chart-selector-${cur}').val())
})
</script>
<!--#end if#-->
</div>
</div>
</form>
<!--#end for#-->
</div><!-- /colmask -->
<script type="text/javascript">
\$(document).ready(function(){
// Exception when change of priority, reload
\$('input[name="priority"], input[name="displayname"]').on('change', function() {
\$('.fullform').submit(function() {
// Skip the fancy stuff, just submit
this.submit()
})
})
function showChart(server_id, month) {
// This month
var thisDay = new Date()
/**
Message on no Default category selected
**/
function checkServerCats() {
// Now we check all of them
var hasDefault = false;
// Only check the active servers, not the add-server one
\$('.section:not(#addServerContent) select[name="categories"]').each(function() {
// See if this server is enabled
if(!\$(this).parents('.section').find('.col2').hasClass('server-disabled') ) {
// Is there Default?
if(\$(this).val() && \$(this).val().indexOf('Default') > -1) {
// Hide
\$('.alert-no-category').hide()
hasDefault = true
// All good!
return true
}
// What month are we doing?
var inputDate = new Date(month+'-01')
var baseDate = new Date(inputDate.getUTCFullYear(), inputDate.getUTCMonth(), 1)
var maxDaysInMonth = new Date(baseDate.getFullYear(), baseDate.getMonth()+1, 0).getDate()
// Fill the data array
var data = {
labels: [],
series: [[]]
};
var largestVal = 0
for(var i = 1; i < maxDaysInMonth+1; i++) {
// Add X-label
if(i % 3 == 1) {
data['labels'].push(i)
} else {
data['labels'].push(NaN)
}
// Get formatted date
baseDate.setDate(i)
var dateCheck = toFormattedDate(baseDate)
// Add data if we have it
if(dateCheck in serverData[server_id]) {
data['series'][0].push(serverData[server_id][dateCheck])
largestVal = Math.max(largestVal, serverData[server_id][dateCheck])
} else if(thisDay.getYear() == baseDate.getYear() && thisDay.getMonth() == baseDate.getMonth() && thisDay.getDate() < i) {
data['series'][0].push(NaN)
} else {
data['series'][0].push(0)
}
}
// Check if we should shrink the Y-axis values
var devideBy = 1024
var axisLabel = 'KB'
if(largestVal > 1024*1024) {
devideBy = 1024*1024
axisLabel = 'MB'
}
if(largestVal > 1024*1024*1024) {
devideBy = 1024*1024*1024
axisLabel = 'GB'
}
if(largestVal > 1024*1024*1024*1024) {
devideBy = 1024*1024*1024*1024
axisLabel = 'TB'
}
// Shrink the value
data['series'][0] = data['series'][0].map(function(num) {
return num / devideBy;
})
// We found nothing.. Let's show a warning
if(!hasDefault) \$('.alert-no-category').show()
// Show the chart
chart = new Chartist.Line('#server-chart-'+server_id, data, chartOptions);
chart.on('created', function(context) {
// Make sure to add this as the first child so it's at the bottom
context.svg.elem('rect', {
x: context.chartRect.x1,
y: context.chartRect.y2-1,
width: context.chartRect.width(),
height: context.chartRect.height()+2,
fill: 'none',
stroke: '#B9B9B9',
'stroke-width': '1px'
}, '', context.svg, true)
\$('#server-chart-'+server_id+' .ct-label.ct-vertical').each(function(index, elmn) {
elmn.innerHTML += axisLabel
})
});
}
\$('select[name="categories"]').on('change', checkServerCats)
checkServerCats()
// Need to mitigate timezone effects!
function toFormattedDate(date) {
var local = new Date(date);
local.setMinutes(date.getMinutes() - date.getTimezoneOffset());
return local.toJSON().slice(0, 10);
}
/**
Click events
When finished loading
**/
\$('.showserver').click(function () {
if(\$(this).parent().hasClass('server-disabled')) {
\$(this).parent().parent().toggleClass('server-disabled')
}
\$(this).parent().next().toggle();
\$(this).parent().next().next().toggle();
if (\$(this).attr("value") == "$T('showDetails')") {
\$(this).attr("value", "$T('hideDetails')");
} else {
\$(this).attr("value", "$T('showDetails')");
}
});
\$(document).ready(function(){
// Exception when change of priority, reload
\$('input[name="priority"], input[name="displayname"]').on('change', function() {
\$('.fullform').submit(function() {
// Skip the fancy stuff, just submit
this.submit()
})
})
\$('#addServerButton').click(function(){
\$('#addServer').hide();
\$('#addServerContent').show();
});
/**
Update charts when changed
**/
\$('.chart-selector').on('change', function(elemn) {
showChart(\$(elemn.target).data('id'), \$(elemn.target).val())
// Lets us leave (needs to be called after the change event)
setTimeout(function() {
formWasSubmitted = true;
formHasChanged = false;
}, 100)
})
\$('[name="ssl"]').click(function() {
// Use CSS transitions to do some highlighting
var portBox = \$(this).parent().parent().find('[name="port"]')
if(this.checked) {
// Enabled SSL change port when not already a custom port
if(portBox.val() == '119') {
portBox.val('563')
portBox.addClass('port-highlight')
/**
Click events
**/
\$('.showserver').click(function () {
if(\$(this).parent().hasClass('server-disabled')) {
\$(this).parent().parent().toggleClass('server-disabled')
}
} else {
// Remove SSL port
if(portBox.val() == '563') {
portBox.val('119')
portBox.addClass('port-highlight')
}
}
setTimeout(function() { portBox.removeClass('port-highlight') }, 2000)
})
\$('.testServer').click(function(event){
removeObfuscation()
var theButton = \$(this)
var resultBox = theButton.parents('.col1').find('.result-box .alert');
theButton.attr("disabled", "disabled")
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
\$.ajax({
type: "POST",
url: "../../tapi",
data: "mode=config&output=json&name=test_server&" + \$(this).parents('form:first').serialize()
}).then(function(data) {
// Let's replace the link
msg = data.value.message.replace('https://sabnzbd.org/certificate-errors', '<a href="https://sabnzbd.org/certificate-errors" class="alert-link" target="_blank">https://sabnzbd.org/certificate-errors</a>')
// Fill the box and enable the button
resultBox.removeClass('alert-success alert-danger').show()
resultBox.html(msg)
theButton.removeAttr("disabled")
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
// Succes or not?
if(data.value.result) {
resultBox.addClass('alert-success')
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign"></span> ')
\$(this).parent().next().toggle();
\$(this).parent().next().next().toggle();
if (\$(this).attr("value") == "$T('showDetails')") {
\$(this).attr("value", "$T('hideDetails')");
} else {
resultBox.addClass('alert-danger')
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign"></span> ')
\$(this).attr("value", "$T('showDetails')");
}
});
});
\$('.delServer').click(function(){
if( confirm("$T('Plush-confirm')") ) {
\$(this).parents('form:first').attr('action','delServer').submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 500)
}
return false;
});
\$('#addServerButton').click(function(){
\$('#addServer').hide();
\$('#addServerContent').show();
});
\$('.clrServer').click(function(){
if( confirm("$T('Plush-confirm')") ) {
\$(this).parents('form:first').attr('action','clrServer').submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 500)
}
return false;
});
\$('[name="ssl"]').click(function() {
// Use CSS transitions to do some highlighting
var portBox = \$(this).parent().parent().find('[name="port"]')
if(this.checked) {
// Enabled SSL change port when not already a custom port
if(portBox.val() == '119') {
portBox.val('563')
portBox.addClass('port-highlight')
}
} else {
// Remove SSL port
if(portBox.val() == '563') {
portBox.val('119')
portBox.addClass('port-highlight')
}
}
setTimeout(function() { portBox.removeClass('port-highlight') }, 2000)
})
\$('.toggleServerCheckbox').click(function(){
var whichServer = \$(this).attr("name");
\$.ajax({
type: "POST",
url: "toggleServer",
data: {server: whichServer, session: "$session" }
}).done(function() {
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 100)
\$('.testServer').click(function(event){
removeObfuscation()
var theButton = \$(this)
var resultBox = theButton.parents('.col1').find('.result-box .alert');
theButton.attr("disabled", "disabled")
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
\$.ajax({
type: "POST",
url: "../../tapi",
data: "mode=config&output=json&name=test_server&" + \$(this).parents('form:first').serialize()
}).then(function(data) {
// Let's replace the link
msg = data.value.message.replace('https://sabnzbd.org/certificate-errors', '<a href="https://sabnzbd.org/certificate-errors" class="alert-link" target="_blank">https://sabnzbd.org/certificate-errors</a>')
msg = msg.replace('-', '<br>')
// Fill the box and enable the button
resultBox.removeClass('alert-success alert-danger').show()
resultBox.html(msg)
theButton.removeAttr("disabled")
theButton.find('span').toggleClass('glyphicon-sort glyphicon-refresh spin-glyphicon')
// Succes or not?
if(data.value.result) {
resultBox.addClass('alert-success')
resultBox.prepend('<span class="glyphicon glyphicon-ok-sign"></span> ')
} else {
resultBox.addClass('alert-danger')
resultBox.prepend('<span class="glyphicon glyphicon-exclamation-sign"></span> ')
}
});
});
\$('.delServer').click(function(){
if( confirm("$T('Plush-confirm')") ) {
\$(this).parents('form:first').attr('action','delServer').submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 500)
}
return false;
});
\$('.clrServer').click(function(){
if( confirm("$T('Plush-confirm')") ) {
\$(this).parents('form:first').attr('action','clrServer').submit();
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 500)
}
return false;
});
\$('.toggleServerCheckbox').click(function(){
var whichServer = \$(this).attr("name");
\$.ajax({
type: "POST",
url: "toggleServer",
data: {server: whichServer, session: "$session" }
}).done(function() {
// Let us leave!
formWasSubmitted = true;
formHasChanged = false;
setTimeout(function() { location.reload(); }, 100)
});
});
});
});
</script>
<!--#include $webdir + "/_inc_footer_uc.tmpl"#-->

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Sorting"#-->
<!--#set global $help_uri="configuration/2.0/sorting"#-->
<!--#set global $help_uri="configuration/2.2/sorting"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
@@ -198,7 +198,7 @@
<div class="presets float-left">
<input type="button" class="btn btn-default" onclick="movieSet('%title (%y)/%title (%y).%ext',' CD%1');movieExtraFolder(false)" value="$T('button-inFolders')" />
<input type="button" class="btn btn-default" onclick="movieSet('%title (%y).%ext',' CD%1');movieExtraFolder(true)" value="$T('button-noFolders')" />
<input type="button" class="btn btn-default" onclick="movieSet('%0decade/%title (%y).%ext',' CD%1');movieExtraFolder(true)" value="Decades 1" />
<input type="button" class="btn btn-default" onclick="movieSet('%0decade/%title (%y).%ext',' CD%1');movieExtraFolder(true)" value="$T('decade')" />
</div>
</div>
<div id="previewmovie" class="example">

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Special"#-->
<!--#set global $help_uri="configuration/2.0/special"#-->
<!--#set global $help_uri="configuration/2.2/special"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">

View File

@@ -1,5 +1,5 @@
<!--#set global $pane="Switches"#-->
<!--#set global $help_uri="configuration/2.0/switches"#-->
<!--#set global $help_uri="configuration/2.2/switches"#-->
<!--#include $webdir + "/_inc_header_uc.tmpl"#-->
<div class="colmask">
@@ -31,12 +31,6 @@
<input type="number" name="max_art_tries" id="max_art_tries" value="$max_art_tries" min="2" max="2000" />
<span class="desc">$T('explain-max_art_tries')</span>
</div>
<div class="field-pair">
<label class="config" for="max_art_opt">$T('opt-max_art_opt')</label>
<input type="checkbox" name="max_art_opt" id="max_art_opt" value="1" <!--#if int($max_art_opt) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-max_art_opt')</span>
</div>
<div class="field-pair">
<label class="config" for="auto_disconnect">$T('opt-auto_disconnect')</label>
<input type="checkbox" name="auto_disconnect" id="auto_disconnect" value="1" <!--#if int($auto_disconnect) > 0 then 'checked="checked"' else ""#--> />
@@ -92,6 +86,7 @@
<label class="config" for="no_dupes">$T('opt-no_dupes')</label>
<select name="no_dupes" id="no_dupes">
<option value="0" <!--#if int($no_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="4" <!--#if int($no_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
<option value="2" <!--#if int($no_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="3" <!--#if int($no_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
<option value="1" <!--#if int($no_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
@@ -102,12 +97,18 @@
<label class="config" for="no_series_dupes">$T('opt-no_series_dupes')</label>
<select name="no_series_dupes" id="no_series_dupes">
<option value="0" <!--#if int($no_series_dupes) == 0 then 'selected="selected"' else ""#--> >$T('nodupes-off')</option>
<option value="4" <!--#if int($no_series_dupes) == 4 then 'selected="selected"' else ""#--> >$T('nodupes-tag')</option>
<option value="2" <!--#if int($no_series_dupes) == 2 then 'selected="selected"' else ""#--> >$T('nodupes-pause')</option>
<option value="3" <!--#if int($no_series_dupes) == 3 then 'selected="selected"' else ""#--> >$T('nodupes-fail')</option>
<option value="1" <!--#if int($no_series_dupes) == 1 then 'selected="selected"' else ""#--> >$T('nodupes-ignore')</option>
</select>
<span class="desc">$T('explain-no_series_dupes')</span>
</div>
<div class="field-pair">
<label class="config" for="series_propercheck">$T('opt-series_propercheck')</label>
<input type="checkbox" name="series_propercheck" id="series_propercheck" value="1" <!--#if int($series_propercheck) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-series_propercheck')</span>
</div>
<div class="field-pair">
<label class="config" for="pause_on_pwrar">$T('opt-pause_on_pwrar')</label>
<select name="pause_on_pwrar" id="pause_on_pwrar">
@@ -136,6 +137,11 @@
<input type="checkbox" name="auto_sort" id="auto_sort" value="1" <!--#if int($auto_sort) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-auto_sort')</span>
</div>
<div class="field-pair">
<label class="config" for="direct_unpack">$T('opt-direct_unpack')</label>
<input type="checkbox" name="direct_unpack" id="direct_unpack" value="1" <!--#if int($direct_unpack) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-direct_unpack').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair">
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
@@ -159,13 +165,6 @@
<input type="checkbox" name="enable_all_par" id="enable_all_par" value="1" <!--#if int($enable_all_par) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-enable_all_par').replace('. ', '.<br/>')</span>
</div>
<!--#if $have_multicore#-->
<div class="field-pair">
<label class="config" for="par2_multicore">$T('opt-par2_multicore')</label>
<input type="checkbox" name="par2_multicore" id="par2_multicore" value="1" <!--#if int($par2_multicore) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-par2_multicore')</span>
</div>
<!--#end if#-->
<div class="field-pair">
<label class="config" for="par_option">$T('opt-par_option')</label>
<input type="text" name="par_option" id="par_option" value="$par_option" />
@@ -218,11 +217,28 @@
<input type="checkbox" name="ignore_samples" id="ignore_samples" value="1" <!--#if int($ignore_samples) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-ignore_samples') $T('igsam-del').</span>
</div>
<div class="field-pair">
<label class="config" for="enable_meta">$T('opt-enable_meta')</label>
<input type="checkbox" name="enable_meta" id="enable_meta" value="1" <!--#if int($enable_meta) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-enable_meta').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair">
<label class="config" for="cleanup_list">$T('opt-cleanup_list')</label>
<input type="text" name="cleanup_list" id="cleanup_list" value="$cleanup_list"/>
<span class="desc">$T('explain-cleanup_list')</span>
</div>
<div class="field-pair">
<label class="config" for="history_retention_select">$T('opt-history_retention')</label>
<input type="hidden" name="history_retention" id="history_retention" value="$history_retention">
<select name="history_retention_select" id="history_retention_select">
<option value="0">$T('history_retention-all')</option>
<option value="n">$T('history_retention-number')</option>
<option value="d">$T('history_retention-days')</option>
<option value="-1">$T('history_retention-none')</option>
</select>
<input type="number" id="history_retention_number" name="history_retention_number" min="1">
<span class="desc">$T('explain-history_retention').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair">
<button class="btn btn-default saveButton"><span class="glyphicon glyphicon-ok"></span> $T('button-saveChanges')</button>
<button class="btn btn-default restoreDefaults"><span class="glyphicon glyphicon-asterisk"></span> $T('button-restoreDefaults')</button>
@@ -239,7 +255,7 @@
<div class="field-pair">
<label class="config" for="folder_rename">$T('opt-folder_rename')</label>
<input type="checkbox" name="folder_rename" id="folder_rename" value="1" <!--#if int($folder_rename) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-folder_rename')</span>
<span class="desc">$T('explain-folder_rename').replace('. ', '.<br/>')</span>
</div>
<div class="field-pair">
<label class="config" for="replace_spaces">$T('opt-replace_spaces')</label>
@@ -251,11 +267,6 @@
<input type="checkbox" name="replace_dots" id="replace_dots" value="1" <!--#if int($replace_dots) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-replace_dots')</span>
</div>
<div class="field-pair">
<label class="config" for="replace_illegal">$T('opt-replace_illegal')</label>
<input type="checkbox" name="replace_illegal" id="replace_illegal" value="1" <!--#if int($replace_illegal) > 0 then 'checked="checked"' else ""#--> />
<span class="desc">$T('explain-replace_illegal')</span>
</div>
<!--#if not $nt#-->
<div class="field-pair">
<label class="config" for="sanitize_safe">$T('opt-sanitize_safe')</label>
@@ -450,6 +461,53 @@
}
});
\$('#history_retention_select, #history_retention_number').on('change', updateHistoryRetention)
function updateHistoryRetention() {
var retention_setting = \$('#history_retention')
var retention_select = \$('#history_retention_select').val()
var retention_number = \$('#history_retention_number')
// Keep all or keep none
if(retention_select == "0" || retention_select == "-1") {
retention_number.hide()
retention_number.val('')
retention_number.attr('placeholder', '')
retention_setting.val(retention_select)
} else {
retention_number.show()
// Days or number?
if(retention_select.indexOf("d") !== -1) {
retention_number.attr('placeholder', '$T('days').capitalize()')
if(retention_number.val()) {
retention_setting.val(retention_number.val() + 'd')
} else if(parseInt(retention_setting.val()) > 0) {
retention_number.val(parseInt(retention_setting.val()))
}
} else {
retention_number.attr('placeholder', '$T('history_retention-limit')')
if(retention_number.val()) {
retention_setting.val(retention_number.val())
} else if(parseInt(retention_setting.val()) > 0) {
retention_number.val(parseInt(retention_setting.val()))
}
}
}
}
// Set the history-retention settig
var retention_setting_value = \$('#history_retention').val()
if(parseInt(retention_setting_value) > 0) {
// Days or number?
if(retention_setting_value.indexOf("d") !== -1) {
\$('#history_retention_select').val("d")
} else {
\$('#history_retention_select').val("n")
}
\$('#history_retention_number').val(parseInt(retention_setting_value))
} else {
// Keep all or keep none
\$('#history_retention_select').val(retention_setting_value)
\$('#history_retention_number').hide()
}
\$('.restoreDefaults').click(function(e) {
// Get section name
var sectionName = \$(this).parents('.section').find('.col2 h3').text().trim()

View File

@@ -1,9 +1,9 @@
<html lang="$active_lang">
<head>
<title>SABnzbd</title>
<title>SABnzbd - $T('login')</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1" />
<meta name="apple-mobile-web-app-title" content="SABnzbd" />
<link rel="apple-touch-icon" sizes="76x76" href="../staticcfg/ico/apple-touch-icon-76x76-precomposed.png" />
@@ -16,7 +16,7 @@
<link rel="stylesheet" type="text/css" href="../staticcfg/bootstrap/css/bootstrap.min.css?v=$version" />
<link rel="stylesheet" type="text/css" href="../staticcfg/css/login.css?v=$version" />
<script type="text/javascript" src="../staticcfg/js/jquery-3.1.1.min.js?v=$version"></script>
<script type="text/javascript" src="../staticcfg/js/jquery-3.2.1.min.js?v=$version"></script>
<script type="text/javascript" src="../staticcfg/bootstrap/js/bootstrap.min.js?v=$version"></script>
</head>
<html>

View File

File diff suppressed because one or more lines are too long

View File

@@ -137,7 +137,8 @@ input[type="checkbox"]+.desc {
font-style: italic;
padding: 0 1px;
}
.col2 p {
.col2 p,
.col2-cats {
font-size: 12px;
color: #666;
margin: 1em 0;
@@ -618,7 +619,6 @@ h2.activeRSS {
padding: 0 0 .5em;
}
.feed-row div {
padding-right: 10px;
overflow:hidden;
white-space: nowrap;
text-overflow: ellipsis;
@@ -993,6 +993,55 @@ input[type="checkbox"] {
opacity: 0.7;
}
.Servers .server-amounts-text {
width: 20%;
float: left;
}
.Servers .server-chart {
float: right;
width: calc(100% - 250px - 25%);
text-align: center;
position: relative;
}
.Servers .ct-series-a .ct-line {
stroke: #666666;
}
.Servers .ct-series-a .ct-point {
stroke: #666666;
stroke-width: 4px;
}
.Servers .ct-series-a .ct-area {
fill: #666666
}
.Servers .ct-label {
font-size: 1em;
color: black;
}
.Servers .chart-selector {
position: absolute;
display: block;
top: -7px;
left: 50%;
width: 120px;
margin-left: -40px;
min-width: initial;
opacity: 0.8;
}
.Servers .chart-selector:hover {
opacity: 1;
}
.Servers .ct-grid.ct-vertical:first-of-type {
display: none;
}
.advanced-settings {
display: none;
}
@@ -1018,7 +1067,6 @@ input[type="checkbox"] {
display: none;
}
.alert-no-category,
.alert-translate {
display: none;
margin: 5px 0px 0px;
@@ -1118,7 +1166,7 @@ input[type="checkbox"] {
.navbar-nav {
/* For extra wide languages like Polish */
margin-right: -50px;
margin-right: -150px;
}
}
@@ -1170,6 +1218,15 @@ input[type="checkbox"] {
.Servers .col2 button:first-of-type {
margin-bottom: 0;
}
.Servers .server-chart {
display: none;
}
.Servers .server-amounts-text {
padding: 0px 15px 10px;
width: inherit;
}
}
@media screen and (max-width: 768px) {

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 982 B

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@@ -226,18 +226,13 @@ function do_restart() {
$('.main-restarting').show()
// What template
var arrPath = window.location.pathname.split('/');
var urlPath = (arrPath[1] == "m" || arrPath[2] == "m") ? '/sabnzbd/m/' : '/sabnzbd/';
var switchedHTTPS = !$('#enable_https').is(':checked') && window.location.protocol == 'https:'
var switchedHTTPS = ($('#enable_https').is(':checked') == ($('#enable_https').data('original') === undefined))
var portsUnchanged = ($('#port').val() == $('#port').data('original')) && ($('#https_port').val() == $('#https_port').data('original'))
// Are we on settings page?
if(!$('body').hasClass('General')) {
// Same as before, with fall-back in case location.origin is not supported (<IE9)
var urlTotal = window.location.origin ? (window.location.origin + urlPath) : window.location;
} else if (!switchedHTTPS && ($('#port').val() == $('#port').data('original')) && ($('#https_port').val() == $('#https_port').data('original'))) {
// If the http/https port config didn't change, don't try and guess the URL/port to redirect to
// This solves some incorrect behavior if running behind a reverse proxy
var urlTotal = window.location.origin ? (window.location.origin + urlPath) : window.location;
// Are we on settings page or did nothing change?
if(!$('body').hasClass('General') || (!switchedHTTPS && portsUnchanged)) {
// Same as before
var urlTotal = window.location.origin + urlBase
} else {
// Protocol and port depend on http(s) setting
if($('#enable_https').is(':checked') && (window.location.protocol == 'https:' || !$('#https_port').val())) {
@@ -251,7 +246,7 @@ function do_restart() {
}
// We cannot make a good guess for the IP, so at least we assume that stays the same
var urlTotal = urlProtocol + '//' + window.location.hostname + ':' + urlPort + urlPath;
var urlTotal = urlProtocol + '//' + window.location.hostname + ':' + urlPort + urlBase;
}
// Show where we are going to connect
@@ -263,7 +258,7 @@ function do_restart() {
// Keep counter of failures
var failureCounter = 0;
// Now we try untill we can connect
// Now we try until we can connect
var refreshInterval = setInterval(function() {
// We skip the first one
if(failureCounter == 0) {
@@ -409,6 +404,9 @@ $(document).ready(function () {
$('#enable_https').on('change', function() {
$('.enable_https_options').toggle()
})
if(!$('#enable_https').is(':checked')) {
$('.enable_https_options').hide()
}
$('.advancedButton').click(function(event){
$('.advanced-settings').toggle()

View File

@@ -47,9 +47,9 @@
<!-- ko if: historyStatus.has_rating -->
<div class="dropdown history-ratings">
<a href="#" class="name-ratings hover-button" data-toggle="dropdown" onclick="keepOpen(this)">
<span class="glyphicon glyphicon-facetime-video"></span> <span data-bind="text: historyStatus.rating_avg_video"></span>
<span class="glyphicon glyphicon-volume-up"></span> <span data-bind="text: historyStatus.rating_avg_audio"></span>
<a href="#" class="name-icons hover-button" data-toggle="dropdown" onclick="keepOpen(this)">
<span class="glyphicon glyphicon-thumbs-up"></span> <span data-bind="text: historyStatus.rating_avg_vote_up"></span>
<span class="glyphicon glyphicon-thumbs-down"></span> <span data-bind="text: historyStatus.rating_avg_vote_down"></span>
</a>
<ul class="dropdown-menu history-ratings-menu">
<li>
@@ -206,6 +206,7 @@
</ul>
<div class="multioperations-selector" id="history-options">
<a href="#" class="hover-button" title="$T('link-retryAll')" data-tooltip="true" data-placement="left" data-bind="click: history.retryAllFailed"><span class="glyphicon glyphicon-repeat"></span></a>
<a href="#" class="hover-button" title="$T('showAllHis') / $T('showFailedHis')" data-tooltip="true" data-placement="left" data-bind="click: history.toggleShowFailed, css: { 'history-options-show-failed': history.showFailed }"><span class="glyphicon glyphicon-exclamation-sign"></span></a>
<a href="#modal-purge-history" class="hover-button" title="$T('purgeHist')" data-toggle="modal" data-tooltip="true" data-placement="left"><span class="glyphicon glyphicon-trash"></span></a>
</div>

View File

@@ -89,6 +89,7 @@
</a>
<ul class="dropdown-menu menu-options">
<li><a href="#modal-help" data-toggle="modal"><span class="glyphicon glyphicon-question-sign"></span> $T('menu-help')</a></li>
<li><a href="https://sabnzbd.org/donate" target="_blank"><span class="glyphicon glyphicon-heart"></span> $T('menu-donate')</a></li>
<!--#if $have_logout or $have_quota or $have_rss_defined or $have_watched_dir or $pp_pause_event#--><li class="divider"></li><!--#end if#-->
<!--#if $have_logout#--><li><a href="./login/?logout=1"><span class="glyphicon glyphicon-log-out"></span> $T('logout')</a></li><!--#end if#-->
<!--#if $have_quota#--><li><a href="#" data-bind="click: doQueueAction" data-mode="reset_quota">$T('link-resetQuota')</a></li><!--#end if#-->

View File

@@ -60,13 +60,13 @@
<p><strong>If anything is not working as expected, or could be improved, let us know!</strong></p>
<p><strong>If you encounter an error, please include the log file (click on <span class="glyphicon glyphicon-wrench"></span> ) when contacting us.</strong></p>
<h4>General</h4>
<span class="glyphicon glyphicon-home"></span> <a href="http://forums.sabnzbd.org/" target="_blank">SABnzbd Forum</a><br />
<span class="glyphicon glyphicon-home"></span> <a href="https://forums.sabnzbd.org/" target="_blank">SABnzbd Forum</a><br />
<span class="glyphicon glyphicon-plane"></span> <a href="https://github.com/sabnzbd/sabnzbd/" target="_blank">SABnzbd on Github</a><br />
<span class="glyphicon glyphicon-globe"></span> <a href="https://translations.launchpad.net/sabnzbd" target="_blank">Translations of SABnzbd</a><br />
<span class="glyphicon glyphicon-envelope"></span> <a href="mailto:bugs@sabnzbd.org?body=Version:%20$version%20Skin:%20Glitter">Email bugs@sabnzbd.org</a>
<h4>Interface (Glitter)</h4>
<span class="glyphicon glyphicon-home"></span> <a href="http://forums.sabnzbd.org/viewtopic.php?f=5&amp;t=18880" target="_blank">Glitter at SABnzbd Forum</a><br />
<span class="glyphicon glyphicon-home"></span> <a href="https://forums.sabnzbd.org/viewtopic.php?f=5&amp;t=18880" target="_blank">Glitter at SABnzbd Forum</a><br />
<span class="glyphicon glyphicon-envelope"></span> <a href="mailto:safihre@sabnzbd.org?body=Version:%20$version">Email safihre@sabnzbd.org</a>
</div>
</div>
@@ -525,10 +525,14 @@
<div class="progress-bar progress-bar-info" data-bind="attr: { 'style': 'width: '+percentage()+'; background-color: ' + \$parent.filelist.currentItem.progressColor() + ';' }">
<input type="checkbox" data-bind="attr: { 'name' : nzf_id }, disable: !canselect(), click : \$parent.filelist.checkSelectRange" title="$T('Glitter-multiSelect')" />
<strong data-bind="text: percentage"></strong>
<span>
<div class="fileDetails">
<span data-bind="truncatedTextCenter: filename"></span>
<div class="fileControls">
<a href="#" data-bind="click: \$parent.filelist.moveButton" class="hover-button buttonMoveToTop" title="$T('Glitter-top')"><span class="glyphicon glyphicon-chevron-up"></span></a>
<a href="#" data-bind="click: \$parent.filelist.moveButton" class="hover-button buttonMoveToBottom" title="$T('Glitter-bottom')"><span class="glyphicon glyphicon-chevron-down"></span></a>
</div>
<small>(<span data-bind="text: file_age"></span> - <span data-bind="text: mb"></span> MB)</small>
</span>
</div>
</div>
</div>
</td>
@@ -621,7 +625,7 @@
</tr>
<tr>
<td><strong>$T('menu-forums'):</strong></td>
<td><a href="http://forums.sabnzbd.org/" target="_blank">http://forums.sabnzbd.org/</a></td>
<td><a href="https://forums.sabnzbd.org/" target="_blank">https://forums.sabnzbd.org/</a></td>
</tr>
<tr>
<td><strong>GitHub:</strong></td>
@@ -629,7 +633,7 @@
</tr>
<tr>
<td><strong>$T('menu-irc'):</strong></td>
<td><a href="http://www.sabnzbd.org/live-chat/" target="_blank">http://www.sabnzbd.org/live-chat/</a></td>
<td><a href="https://sabnzbd.org/live-chat" target="_blank">https://sabnzbd.org/live-chat</a></td>
</tr>
</tbody>
</table>

View File

@@ -39,6 +39,11 @@
<span class="glyphicon glyphicon-hdd"></span> <span data-bind="text: diskSpaceLeft2"></span>B $T('Glitter-free')
</div>
<!-- /ko -->
<div class="info-container-box-sorting" style="display: none" data-bind="visible: refreshRate() > 2">
<a href="#" data-bind="click: refresh">
<span class="glyphicon glyphicon-repeat" data-tooltip="true" data-placement="left" title="$T('Glitter-refresh')"></span>
</a>
</div>
<div class="info-container-box-sorting dropdown" data-bind="visible: hasQueue()">
<a href="#" data-toggle="dropdown">
<span class="glyphicon glyphicon-sort-by-alphabet" data-tooltip="true" data-placement="left" title="$T('cmenu-sorting')"></span>
@@ -83,7 +88,7 @@
<tr class="queue-item">
<td>
<a href="#" data-bind="click: pauseToggle, attr: { 'title': pausedStatus() ? '$T('link-resume')' : '$T('link-pause')' }">
<span class="hover-button glyphicon" data-bind="css: { 'glyphicon-play': pausedStatus(), 'glyphicon-pause': !pausedStatus() }"></span>
<span class="hover-button glyphicon" data-bind="css: queueIcon"></span>
</a>
</td>
<td class="name">
@@ -95,19 +100,18 @@
<span data-bind="text: password"></span>
</small>
<!-- /ko -->
<!-- ko if: (rating_avg_video() !== false) -->
<div class="name-ratings hover-button">
<span class="glyphicon glyphicon-facetime-video"></span> <span data-bind="text: rating_avg_video"></span>
<span class="glyphicon glyphicon-volume-up"></span> <span data-bind="text: rating_avg_audio"></span>
<div class="name-icons direct-unpack hover-button" data-bind="visible: direct_unpack" title="$T('opt-direct_unpack')">
<span class="glyphicon glyphicon-compressed"></span> <span data-bind="text: direct_unpack"></span>
</div>
<!-- /ko -->
</div>
<form data-bind="submit: editingNameSubmit">
<input type="text" data-bind="value: nameForEdit, visible: editingName(), hasfocus: editingName" />
</form>
<div class="name-options" data-bind="visible: !editingName()">
<a href="#" data-bind="click: editName, css: { disabled: isGrabbing() }" class="hover-button"><span class="glyphicon glyphicon-pencil"></span></a>
<a href="#" data-bind="click: showFiles, css: { disabled: isGrabbing() }" class="hover-button" title="$T('nzoDetails') - $T('srv-password')"><span class="glyphicon glyphicon-folder-open"></span></a>
<div class="name-options" data-bind="visible: !editingName(), css: { disabled: isGrabbing() }">
<a href="#" data-bind="click: \$parent.queue.moveButton" class="hover-button buttonMoveToTop" title="$T('Glitter-top')"><span class="glyphicon glyphicon-chevron-up"></span></a>
<a href="#" data-bind="click: \$parent.queue.moveButton" class="hover-button buttonMoveToBottom" title="$T('Glitter-bottom')"><span class="glyphicon glyphicon-chevron-down"></span></a>
<a href="#" data-bind="click: editName" class="hover-button" title="$T('Glitter-rename')"><span class="glyphicon glyphicon-pencil"></span></a>
<a href="#" data-bind="click: showFiles" class="hover-button" title="$T('nzoDetails') - $T('srv-password')"><span class="glyphicon glyphicon-folder-open"></span></a>
<small data-bind="text: avg_age"></small>
</div>
</td>
@@ -117,8 +121,8 @@
</td>
<td class="progress-indicator">
<div class="progress">
<div class="progress-bar progress-bar-info" data-bind="attr: { 'style': 'width: ' + percentageRounded() + '; background-color: ' + progressColor() + ';' }">
<strong data-bind="text: percentageRounded"></strong>
<div class="progress-bar progress-bar-info" data-bind="attr: { 'style': 'width: ' + percentage() + '%; background-color: ' + progressColor() + ';' }">
<strong data-bind="text: percentage() + '%'"></strong>
<i data-bind="text: missingText"></i>
</div>
<span data-bind="text: progressText"></span>

View File

@@ -16,7 +16,7 @@
<title data-bind="text: title">SABnzbd</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="application-name" content="SABnzbd">
<meta name="apple-mobile-web-app-capable" content="yes" />
@@ -30,13 +30,13 @@
<link rel="apple-touch-icon" sizes="152x152" href="./staticcfg/ico/apple-touch-icon-152x152-precomposed.png" />
<link rel="apple-touch-icon" sizes="180x180" href="./staticcfg/ico/apple-touch-icon-180x180-precomposed.png" />
<link rel="apple-touch-icon" sizes="192x192" href="./staticcfg/ico/android-192x192.png" />
<link rel="shortcut icon" type="image/ico" href="./staticcfg/ico/favicon.ico?v=1.0.0" data-bind="attr: { 'href': SABIcon }" />
<link rel="shortcut icon" type="image/ico" href="./staticcfg/ico/favicon.ico?v=$version" data-bind="attr: { 'href': SABIcon }" />
<link rel="stylesheet" type="text/css" href="./static/bootstrap/css/bootstrap.min.css?p=$pid" />
<link rel="stylesheet" type="text/css" href="./static/stylesheets/glitter.css?p=$pid" />
<link rel="stylesheet" type="text/css" href="./static/stylesheets/glitter.mobile.css?p=$pid" media="all and (max-width: 768px)" />
<link rel="stylesheet" type="text/css" href="./static/bootstrap/css/bootstrap.min.css?v=$version" />
<link rel="stylesheet" type="text/css" href="./static/stylesheets/glitter.css?v=$version" />
<link rel="stylesheet" type="text/css" href="./static/stylesheets/glitter.mobile.css?v=$version" media="all and (max-width: 768px)" />
<!--#if $color_scheme not in ('Default', '') #-->
<link rel="stylesheet" type="text/css" href="./static/stylesheets/colorschemes/${color_scheme}.css?p=$pid"/>
<link rel="stylesheet" type="text/css" href="./static/stylesheets/colorschemes/${color_scheme}.css?v=$version"/>
<!--#end if#-->
<!-- Make translations available in scripts -->
@@ -58,10 +58,11 @@
glitterTranslate.pauseFor = "$T('pauseFor')"
glitterTranslate.minutes = "$T('mins')"
glitterTranslate.shutdown = "$T('shutdownOK?')";
glitterTranslate.restart = "$T('explain-Restart')".replace(/<br \/>/g, "\n");
glitterTranslate.repair = "$T('explain-Repair')".replace(/<br \/>/g, "\n");
glitterTranslate.restart = "$T('explain-Restart') $T('explain-needNewLogin')".replace(/\<br(\s*\/|)\>/g, '\n');
glitterTranslate.repair = "$T('explain-Repair')".replace(/<br \/>/g, "\n").replace(/&quot;/g,'"');
glitterTranslate.removeDown = "$T('Glitter-confirmClearDownloads')";
glitterTranslate.removeDow1 = "$T('Glitter-confirmClear1Download')";
glitterTranslate.retryAll = "$T('link-retryAll')?";
glitterTranslate.encrypted = "$T('Glitter-encrypted')";
glitterTranslate.duplicate = "$T('Glitter-duplicate')";
glitterTranslate.tooLarge = "$T('Glitter-tooLarge')";
@@ -102,7 +103,7 @@
glitterTranslate.status['Script'] = "$T('stage-script')";
glitterTranslate.status['Source'] = "$T('stage-source')";
glitterTranslate.status['Servers'] = "$T('stage-servers')";
glitterTranslate.status['INFO'] = "$T('log-info')".replace('+ ', '').toUpperCase();
glitterTranslate.status['INFO'] = "$T('log-info')".replace('+', '').toUpperCase();
glitterTranslate.status['WARNING'] = "$T('Glitter-warning')";
glitterTranslate.status['ERROR'] = "$T('Glitter-error')";

View File

@@ -35,6 +35,30 @@ function Fileslisting(parent) {
})
}
// Move to top and bottom buttons
self.moveButton = function (item,event) {
var targetRow, sourceRow, tbody;
sourceRow = $(event.currentTarget).parents("tr").filter(":first");
tbody = sourceRow.parents("tbody").filter(":first");
ko.utils.domData.set(sourceRow[0], "ko_sourceIndex", ko.utils.arrayIndexOf(sourceRow.parent().children(), sourceRow[0]));
sourceRow = sourceRow.detach();
if ($(event.currentTarget).is(".buttonMoveToTop")) {
// we are moving to the top
targetRow = tbody.children(".files-done").filter(":last");
} else {
//we are moving to the bottom
targetRow = tbody.children(".files-sortable").filter(":last");
}
if(targetRow.length < 1 ){
// we found an edge case and need to do something special
targetRow = tbody.children(".files-sortable").filter(":first");
sourceRow.insertBefore(targetRow[0]);
} else {
sourceRow.insertAfter($(targetRow[0]));
}
tbody.sortable('option', 'update').call(tbody[0],null, { item: sourceRow });
};
// Trigger update
self.triggerUpdate = function() {
// Call API
@@ -197,9 +221,9 @@ function FileslistingModel(parent, data) {
self.nzf_id = ko.observable(data.nzf_id);
self.file_age = ko.observable(data.age);
self.mb = ko.observable(data.mb);
self.percentage = ko.observable(fixPercentages((100 - (data.mbleft / data.mb * 100)).toFixed(0)));
self.canselect = ko.observable(data.status != "finished" && data.status != "queued");
self.isdone = ko.observable(data.status == "finished");
self.percentage = ko.observable(self.isdone() ? fixPercentages(100) : fixPercentages((100 - (data.mbleft / data.mb * 100)).toFixed(0)));
// Update internally
self.updateFromData = function(data) {
@@ -207,9 +231,10 @@ function FileslistingModel(parent, data) {
self.nzf_id(data.nzf_id)
self.file_age(data.age)
self.mb(data.mb)
self.percentage(fixPercentages((100 - (data.mbleft / data.mb * 100)).toFixed(0)));
self.canselect(data.status != "finished" && data.status != "queued")
self.isdone(data.status == "finished")
// Data is given in MB, would always show 0% for small files even if completed
self.percentage(self.isdone() ? fixPercentages(100) : fixPercentages((100 - (data.mbleft / data.mb * 100)).toFixed(0)))
}
}

View File

@@ -169,7 +169,6 @@ function HistoryListModel(parent) {
// Toggle showing failed
self.toggleShowFailed = function(data, event) {
// Set the loader so it doesn't flicker and then switch
self.isLoading(true)
self.showFailed(!self.showFailed())
@@ -177,7 +176,20 @@ function HistoryListModel(parent) {
$('#history-options a').tooltip('hide')
// Force refresh
self.parent.refresh(true)
}
// Retry all failed
self.retryAllFailed = function(data, event) {
// Ask to be sure
if(confirm(glitterTranslate.retryAll)) {
// Send the command
callAPI({
mode: 'retry_all'
}).then(function() {
// Force refresh
self.parent.refresh(true)
})
}
}
// Empty history options
@@ -297,11 +309,11 @@ function HistoryModel(parent, data) {
self.processingDownload = ko.pureComputed(function() {
var status = self.status();
// When we can cancel
if (status === 'Extracting' || status === 'Verifying' || status == 'Repairing') {
if (status === 'Extracting' || status === 'Verifying' || status == 'Repairing' || status === 'Running') {
return 2
}
// These cannot be cancelled
if(status === 'Moving' || status === 'Running') {
if(status === 'Moving') {
return 1
}
return false;
@@ -328,16 +340,14 @@ function HistoryModel(parent, data) {
case 'speed':
// Anything to calculate?
if(self.historyStatus.bytes() > 0 && self.historyStatus.download_time() > 0) {
var theSpeed = self.historyStatus.bytes()/self.historyStatus.download_time();
theSpeed = theSpeed/1024;
// MB/s or KB/s
if(theSpeed > 1024) {
theSpeed = theSpeed/1024;
return theSpeed.toFixed(1) + ' MB/s'
} else {
return Math.round(theSpeed) + ' KB/s'
}
try {
// Extract the Download section
var downloadLog = ko.utils.arrayFirst(self.historyStatus.stage_log(), function(item) {
return item.name() == 'Download'
});
// Extract the speed
return downloadLog.actions()[0].match(/(\S*\s\S+)(?=<br\/>)/)[0]
} catch(err) { }
}
return;
case 'category':

View File

@@ -12,7 +12,7 @@ function ViewModel() {
self.isRestarting = ko.observable(false);
self.useGlobalOptions = ko.observable(true).extend({ persist: 'useGlobalOptions' });
self.refreshRate = ko.observable(1).extend({ persist: 'pageRefreshRate' });
self.dateFormat = ko.observable('DD/MM/YYYY HH:mm').extend({ persist: 'pageDateFormat' });
self.dateFormat = ko.observable('fromNow').extend({ persist: 'pageDateFormat' });
self.displayTabbed = ko.observable().extend({ persist: 'displayTabbed' });
self.displayCompact = ko.observable(false).extend({ persist: 'displayCompact' });
self.confirmDeleteQueue = ko.observable(true).extend({ persist: 'confirmDeleteQueue' });
@@ -173,8 +173,8 @@ function ViewModel() {
}
// Did we exceed the space?
self.diskSpaceExceeded1(parseInt(response.queue.mbleft)/1024 > parseInt(response.queue.diskspace1))
self.diskSpaceExceeded2(parseInt(response.queue.mbleft)/1024 > parseInt(response.queue.diskspace2))
self.diskSpaceExceeded1(parseInt(response.queue.mbleft)/1024 > parseFloat(response.queue.diskspace1))
self.diskSpaceExceeded2(parseInt(response.queue.mbleft)/1024 > parseFloat(response.queue.diskspace2))
// Quota
self.quotaLimit(response.queue.quota)
@@ -331,6 +331,13 @@ function ViewModel() {
// Split title & speed
var dataSplit = data.split('|||');
// Maybe the result is actually the login page?
if(dataSplit[0].substring(0, 11) === '<html lang=') {
// Redirect
document.location = document.location
return
}
// Set title
self.title(dataSplit[0]);
@@ -538,7 +545,7 @@ function ViewModel() {
// Go over all warnings and add
$.each(response.warnings, function(index, warning) {
// Split warning into parts
var warningSplit = warning.split(/\n/);
var warningSplit = convertHTMLtoText(warning).split(/\n/);
// Reformat CSS label and date
var warningData = {

View File

@@ -148,7 +148,6 @@ function QueueListModel(parent) {
// See what the actual index is of the queue-object
// This way we can see how we move up and down independent of pagination
var itemReplaced = self.queueItems()[event.targetIndex+corTerm];
callAPI({
mode: "switch",
value: itemMoved.id,
@@ -156,6 +155,25 @@ function QueueListModel(parent) {
}).then(self.parent.refresh);
};
// Move button clicked
self.moveButton = function(event,ui) {
var itemMoved = event;
var targetIndex;
if($(ui.currentTarget).is(".buttonMoveToTop")){
//we want to move to the top
targetIndex = 0;
} else {
// we want to move to the bottom
targetIndex = self.totalItems() - 1;
}
callAPI({
mode: "switch",
value: itemMoved.id,
value2: targetIndex
}).then(self.parent.refresh);
}
// Save pagination state
self.paginationLimit.subscribe(function(newValue) {
// Save in config if global
@@ -460,11 +478,13 @@ function QueueModel(parent, data) {
self.password = ko.observable(data.password);
self.index = ko.observable(data.index);
self.status = ko.observable(data.status);
self.isGrabbing = ko.observable(data.status == 'Grabbing')
self.isGrabbing = ko.observable(data.status == 'Grabbing' || data.avg_age == '-')
self.totalMB = ko.observable(parseFloat(data.mb));
self.remainingMB = ko.observable(parseFloat(data.mbleft));
self.remainingMB = ko.observable(parseFloat(data.mbleft))
self.missingMB = ko.observable(parseFloat(data.mbmissing))
self.percentage = ko.observable(parseInt(data.percentage))
self.avg_age = ko.observable(data.avg_age)
self.missing = ko.observable(data.missing)
self.direct_unpack = ko.observable(data.direct_unpack)
self.category = ko.observable(data.cat);
self.priority = ko.observable(parent.priorityName[data.priority]);
self.script = ko.observable(data.script);
@@ -476,8 +496,6 @@ function QueueModel(parent, data) {
self.nameForEdit = ko.observable();
self.editingName = ko.observable(false);
self.hasDropdown = ko.observable(false);
self.rating_avg_video = ko.observable(false)
self.rating_avg_audio = ko.observable(false)
// Color of the progress bar
self.progressColor = ko.computed(function() {
@@ -485,8 +503,8 @@ function QueueModel(parent, data) {
if(self.status() == 'Checking') {
return '#58A9FA'
}
// Check for missing data, the value is arbitrary!
if(self.missing() > 50) {
// Check for missing data, the value is arbitrary! (2%)
if(self.missingMB()/self.totalMB() > 0.02) {
return '#F8A34E'
}
// Set to grey, only when not Force download
@@ -497,22 +515,16 @@ function QueueModel(parent, data) {
return '';
});
// MB's and percentages
self.downloadedMB = ko.computed(function() {
return(self.totalMB() - self.remainingMB()).toFixed(0);
});
self.percentageRounded = ko.pureComputed(function() {
return fixPercentages(((self.downloadedMB() / self.totalMB()) * 100).toFixed(2))
})
// MB's
self.progressText = ko.pureComputed(function() {
return self.downloadedMB() + " MB / " + (self.totalMB() * 1).toFixed(0) + " MB";
return (self.totalMB() - self.remainingMB()).toFixed(0) + " MB / " + (self.totalMB() * 1).toFixed(0) + " MB";
})
// Texts
self.missingText= ko.pureComputed(function() {
// Check for missing data, the value is arbitrary!
if(self.missing() > 50) {
return self.missing() + ' ' + glitterTranslate.misingArt
// Check for missing data, the value is arbitrary! (1%)
if(self.missingMB()/self.totalMB() > 0.01) {
return self.missingMB().toFixed(0) + ' MB ' + glitterTranslate.misingArt
}
return;
})
@@ -529,6 +541,18 @@ function QueueModel(parent, data) {
return rewriteTime(self.timeLeft());
});
// Icon to better show force-priority
self.queueIcon = ko.computed(function() {
// Force comes first
if(self.priority() == 2) {
return 'glyphicon-forward'
}
if(self.pausedStatus()) {
return 'glyphicon-play'
}
return 'glyphicon-pause'
})
// Extra queue column
self.extraText = ko.pureComputed(function() {
// Picked anything?
@@ -561,23 +585,19 @@ function QueueModel(parent, data) {
self.password(data.password);
self.index(data.index);
self.status(data.status)
self.isGrabbing(data.status == 'Grabbing')
self.isGrabbing(data.status == 'Grabbing' || data.avg_age == '-')
self.totalMB(parseFloat(data.mb));
self.remainingMB(parseFloat(data.mbleft));
self.missingMB(parseFloat(data.mbmissing))
self.percentage(parseInt(data.percentage))
self.avg_age(data.avg_age)
self.missing(data.missing)
self.direct_unpack(data.direct_unpack)
self.category(data.cat);
self.priority(parent.priorityName[data.priority]);
self.script(data.script);
self.unpackopts(parseInt(data.unpackopts)) // UnpackOpts fails if not parseInt'd!
self.pausedStatus(data.status == 'Paused');
self.timeLeft(data.timeleft);
// If exists, otherwise false
if(data.rating_avg_video !== undefined) {
self.rating_avg_video(data.rating_avg_video === 0 ? '-' : data.rating_avg_video);
self.rating_avg_audio(data.rating_avg_audio === 0 ? '-' : data.rating_avg_audio);
}
};
// Pause individual download

View File

@@ -1,7 +1,13 @@
// Peity jQuery plugin version 3.2.0
// (c) 2015 Ben Pickles
// Peity jQuery plugin version 3.2.1
// (c) 2016 Ben Pickles
//
// http://benpickles.github.io/peity
//
// Released under MIT license.
!function(t,i,e,n){var r=t.fn.peity=function(i,e){return l&&this.each(function(){var n=t(this),s=n.data("_peity");s?(i&&(s.type=i),t.extend(s.opts,e)):(s=new a(n,i,t.extend({},r.defaults[i],n.data("peity"),e)),n.change(function(){s.draw()}).data("_peity",s)),s.draw()}),this},a=function(t,i,e){this.$el=t,this.type=i,this.opts=e},s=a.prototype,h=s.svgElement=function(e,n){return t(i.createElementNS("http://www.w3.org/2000/svg",e)).attr(n)},l="createElementNS"in i&&h("svg",{})[0].createSVGRect;s.draw=function(){var t=this.opts;r.graphers[this.type].call(this,t),t.after&&t.after.call(this,t)},s.fill=function(){var i=this.opts.fill;return t.isFunction(i)?i:function(t,e){return i[e%i.length]}},s.prepare=function(t,i){return this.$svg||this.$el.hide().after(this.$svg=h("svg",{"class":"peity"})),this.$svg.empty().data("peity",this).attr({height:i,width:t})},s.values=function(){if(this.opts.values){var i=this.opts.values;return this.opts.values="",i}return t.map(this.$el.text().split(this.opts.delimiter),function(t){return parseFloat(t)})},r.defaults={},r.graphers={},r.register=function(t,i,e){this.defaults[t]=i,this.graphers[t]=e},r.register("pie",{fill:["#ff9900","#fff4dd","#ffc66e"],radius:8},function(i){if(!i.delimiter){var n=this.$el.text().match(/[^0-9\.]/);i.delimiter=n?n[0]:","}var r=t.map(this.values(),function(t){return t>0?t:0});if("/"==i.delimiter){var a=r[0],s=r[1];r=[a,e.max(0,s-a)]}for(var l=0,p=r.length,o=0;p>l;l++)o+=r[l];o||(p=2,o=1,r=[0,1]);var f=2*i.radius,c=this.prepare(i.width||f,i.height||f),u=c.width(),d=c.height(),g=u/2,v=d/2,m=e.min(g,v),y=i.innerRadius;"donut"!=this.type||y||(y=.5*m);var w=e.PI,x=this.fill(),k=this.scale=function(t,i){var n=t/o*w*2-w/2;return[i*e.cos(n)+g,i*e.sin(n)+v]},$=0;for(l=0;p>l;l++){var j,A=r[l],E=A/o;if(0!=E){if(1==E)if(y){var F=g-.01,M=v-m,S=v-y;j=h("path",{d:["M",g,M,"A",m,m,0,1,1,F,M,"L",F,S,"A",y,y,0,1,0,g,S].join(" ")})}else j=h("circle",{cx:g,cy:v,r:m});else{var L=$+A,N=["M"].concat(k($,m),"A",m,m,0,E>.5?1:0,1,k(L,m),"L");y?N=N.concat(k(L,y),"A",y,y,0,E>.5?1:0,0,k($,y)):N.push(g,v),$+=A,j=h("path",{d:N.join(" ")})}j.attr("fill",x.call(this,A,l,r)),c.append(j)}}}),r.register("donut",t.extend(!0,{},r.defaults.pie),function(t){r.graphers.pie.call(this,t)}),r.register("line",{delimiter:",",fill:"#c6d9fd",height:16,min:0,stroke:"#4d89f9",strokeWidth:1,width:32},function(t){var i=this.values();1==i.length&&i.push(i[0]);for(var r=e.max.apply(e,t.max==n?i:i.concat(t.max)),a=e.min.apply(e,t.min==n?i:i.concat(t.min)),s=this.prepare(t.width,t.height),l=t.strokeWidth,p=s.width(),o=s.height()-l,f=r-a,c=this.x=function(t){return t*(p/(i.length-1))},u=this.y=function(t){var i=o;return f&&(i-=(t-a)/f*o),i+l/2},d=u(e.max(a,0)),g=[0,d],v=0;v<i.length;v++)g.push(c(v),u(i[v]));g.push(p,d),t.fill&&s.append(h("polygon",{fill:t.fill,points:g.join(" ")})),l&&s.append(h("polyline",{fill:"none",points:g.slice(2,g.length-2).join(" "),stroke:t.stroke,"stroke-width":l,"stroke-linecap":"square"}))}),r.register("bar",{delimiter:",",fill:["#4D89F9"],height:16,min:0,padding:.1,width:32},function(t){for(var i=this.values(),r=e.max.apply(e,t.max==n?i:i.concat(t.max)),a=e.min.apply(e,t.min==n?i:i.concat(t.min)),s=this.prepare(t.width,t.height),l=s.width(),p=s.height(),o=r-a,f=t.padding,c=this.fill(),u=this.x=function(t){return t*l/i.length},d=this.y=function(t){return p-(o?(t-a)/o*p:1)},g=0;g<i.length;g++){var v,m=u(g+f),y=u(g+1-f)-m,w=i[g],x=d(w),k=x,$=x;o?0>w?k=d(e.min(r,0)):$=d(e.max(a,0)):v=1,v=$-k,0==v&&(v=1,r>0&&o&&k--),s.append(h("rect",{fill:c.call(this,w,g,i),x:m,y:k,width:y,height:v}))}})}(jQuery,document,Math);
(function(k,w,h,v){var d=k.fn.peity=function(a,b){y&&this.each(function(){var e=k(this),c=e.data("_peity");c?(a&&(c.type=a),k.extend(c.opts,b)):(c=new x(e,a,k.extend({},d.defaults[a],e.data("peity"),b)),e.change(function(){c.draw()}).data("_peity",c));c.draw()});return this},x=function(a,b,e){this.$el=a;this.type=b;this.opts=e},o=x.prototype,q=o.svgElement=function(a,b){return k(w.createElementNS("http://www.w3.org/2000/svg",a)).attr(b)},y="createElementNS"in w&&q("svg",{})[0].createSVGRect;o.draw=
function(){var a=this.opts;d.graphers[this.type].call(this,a);a.after&&a.after.call(this,a)};o.fill=function(){var a=this.opts.fill;return k.isFunction(a)?a:function(b,e){return a[e%a.length]}};o.prepare=function(a,b){this.$svg||this.$el.hide().after(this.$svg=q("svg",{"class":"peity"}));return this.$svg.empty().data("peity",this).attr({height:b,width:a})};o.values=function(){return k.map(this.$el.text().split(this.opts.delimiter),function(a){return parseFloat(a)})};d.defaults={};d.graphers={};d.register=
function(a,b,e){this.defaults[a]=b;this.graphers[a]=e};d.register("pie",{fill:["#ff9900","#fff4dd","#ffc66e"],radius:8},function(a){if(!a.delimiter){var b=this.$el.text().match(/[^0-9\.]/);a.delimiter=b?b[0]:","}b=k.map(this.values(),function(a){return 0<a?a:0});if("/"==a.delimiter)var e=b[0],b=[e,h.max(0,b[1]-e)];for(var c=0,e=b.length,t=0;c<e;c++)t+=b[c];t||(e=2,t=1,b=[0,1]);var l=2*a.radius,l=this.prepare(a.width||l,a.height||l),c=l.width(),f=l.height(),j=c/2,d=f/2,f=h.min(j,d),a=a.innerRadius;
"donut"==this.type&&!a&&(a=0.5*f);for(var r=h.PI,s=this.fill(),g=this.scale=function(a,b){var c=a/t*r*2-r/2;return[b*h.cos(c)+j,b*h.sin(c)+d]},m=0,c=0;c<e;c++){var u=b[c],i=u/t;if(0!=i){if(1==i)if(a)var i=j-0.01,p=d-f,n=d-a,i=q("path",{d:["M",j,p,"A",f,f,0,1,1,i,p,"L",i,n,"A",a,a,0,1,0,j,n].join(" ")});else i=q("circle",{cx:j,cy:d,r:f});else p=m+u,n=["M"].concat(g(m,f),"A",f,f,0,0.5<i?1:0,1,g(p,f),"L"),a?n=n.concat(g(p,a),"A",a,a,0,0.5<i?1:0,0,g(m,a)):n.push(j,d),m+=u,i=q("path",{d:n.join(" ")});
i.attr("fill",s.call(this,u,c,b));l.append(i)}}});d.register("donut",k.extend(!0,{},d.defaults.pie),function(a){d.graphers.pie.call(this,a)});d.register("line",{delimiter:",",fill:"#c6d9fd",height:16,min:0,stroke:"#4d89f9",strokeWidth:1,width:32},function(a){var b=this.values();1==b.length&&b.push(b[0]);for(var e=h.max.apply(h,a.max==v?b:b.concat(a.max)),c=h.min.apply(h,a.min==v?b:b.concat(a.min)),d=this.prepare(a.width,a.height),l=a.strokeWidth,f=d.width(),j=d.height()-l,k=e-c,e=this.x=function(a){return a*
(f/(b.length-1))},r=this.y=function(a){var b=j;k&&(b-=(a-c)/k*j);return b+l/2},s=r(h.max(c,0)),g=[0,s],m=0;m<b.length;m++)g.push(e(m),r(b[m]));g.push(f,s);a.fill&&d.append(q("polygon",{fill:a.fill,points:g.join(" ")}));l&&d.append(q("polyline",{fill:"none",points:g.slice(2,g.length-2).join(" "),stroke:a.stroke,"stroke-width":l,"stroke-linecap":"square"}))});d.register("bar",{delimiter:",",fill:["#4D89F9"],height:16,min:0,padding:0.1,width:32},function(a){for(var b=this.values(),e=h.max.apply(h,a.max==
v?b:b.concat(a.max)),c=h.min.apply(h,a.min==v?b:b.concat(a.min)),d=this.prepare(a.width,a.height),l=d.width(),f=d.height(),j=e-c,a=a.padding,k=this.fill(),r=this.x=function(a){return a*l/b.length},s=this.y=function(a){return f-(j?(a-c)/j*f:1)},g=0;g<b.length;g++){var m=r(g+a),u=r(g+1-a)-m,i=b[g],p=s(i),n=p,o;j?0>i?n=s(h.min(e,0)):p=s(h.max(c,0)):o=1;o=p-n;0==o&&(o=1,0<e&&j&&n--);d.append(q("rect",{fill:k.call(this,i,g,b),x:m,y:n,width:u,height:o}))}})})(jQuery,document,Math);

View File

@@ -1,123 +1,124 @@
/*!
* Knockout JavaScript library v3.4.0
* (c) Steven Sanderson - http://knockoutjs.com/
* Knockout JavaScript library v3.4.2
* (c) The Knockout.js team - http://knockoutjs.com/
* License: MIT (http://www.opensource.org/licenses/mit-license.php)
*/
(function() {(function(n){var x=this||(0,eval)("this"),u=x.document,M=x.navigator,v=x.jQuery,F=x.JSON;(function(n){"function"===typeof define&&define.amd?define(["exports","require"],n):"object"===typeof exports&&"object"===typeof module?n(module.exports||exports):n(x.ko={})})(function(N,O){function J(a,c){return null===a||typeof a in T?a===c:!1}function U(b,c){var d;return function(){d||(d=a.a.setTimeout(function(){d=n;b()},c))}}function V(b,c){var d;return function(){clearTimeout(d);d=a.a.setTimeout(b,c)}}function W(a,
c){c&&c!==I?"beforeChange"===c?this.Kb(a):this.Ha(a,c):this.Lb(a)}function X(a,c){null!==c&&c.k&&c.k()}function Y(a,c){var d=this.Hc,e=d[s];e.R||(this.lb&&this.Ma[c]?(d.Pb(c,a,this.Ma[c]),this.Ma[c]=null,--this.lb):e.r[c]||d.Pb(c,a,e.s?{ia:a}:d.uc(a)))}function K(b,c,d,e){a.d[b]={init:function(b,g,k,l,m){var h,r;a.m(function(){var q=a.a.c(g()),p=!d!==!q,A=!r;if(A||c||p!==h)A&&a.va.Aa()&&(r=a.a.ua(a.f.childNodes(b),!0)),p?(A||a.f.da(b,a.a.ua(r)),a.eb(e?e(m,q):m,b)):a.f.xa(b),h=p},null,{i:b});return{controlsDescendantBindings:!0}}};
a.h.ta[b]=!1;a.f.Z[b]=!0}var a="undefined"!==typeof N?N:{};a.b=function(b,c){for(var d=b.split("."),e=a,f=0;f<d.length-1;f++)e=e[d[f]];e[d[d.length-1]]=c};a.G=function(a,c,d){a[c]=d};a.version="3.4.0";a.b("version",a.version);a.options={deferUpdates:!1,useOnlyNativeEvents:!1};a.a=function(){function b(a,b){for(var c in a)a.hasOwnProperty(c)&&b(c,a[c])}function c(a,b){if(b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function d(a,b){a.__proto__=b;return a}function e(b,c,d,e){var h=b[c].match(r)||
[];a.a.q(d.match(r),function(b){a.a.pa(h,b,e)});b[c]=h.join(" ")}var f={__proto__:[]}instanceof Array,g="function"===typeof Symbol,k={},l={};k[M&&/Firefox\/2/i.test(M.userAgent)?"KeyboardEvent":"UIEvents"]=["keyup","keydown","keypress"];k.MouseEvents="click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave".split(" ");b(k,function(a,b){if(b.length)for(var c=0,d=b.length;c<d;c++)l[b[c]]=a});var m={propertychange:!0},h=u&&function(){for(var a=3,b=u.createElement("div"),c=
b.getElementsByTagName("i");b.innerHTML="\x3c!--[if gt IE "+ ++a+"]><i></i><![endif]--\x3e",c[0];);return 4<a?a:n}(),r=/\S+/g;return{cc:["authenticity_token",/^__RequestVerificationToken(_.*)?$/],q:function(a,b){for(var c=0,d=a.length;c<d;c++)b(a[c],c)},o:function(a,b){if("function"==typeof Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b);for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},Sb:function(a,b,c){for(var d=0,e=a.length;d<e;d++)if(b.call(c,a[d],d))return a[d];
return null},La:function(b,c){var d=a.a.o(b,c);0<d?b.splice(d,1):0===d&&b.shift()},Tb:function(b){b=b||[];for(var c=[],d=0,e=b.length;d<e;d++)0>a.a.o(c,b[d])&&c.push(b[d]);return c},fb:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)c.push(b(a[d],d));return c},Ka:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)b(a[d],d)&&c.push(a[d]);return c},ra:function(a,b){if(b instanceof Array)a.push.apply(a,b);else for(var c=0,d=b.length;c<d;c++)a.push(b[c]);return a},pa:function(b,c,d){var e=
a.a.o(a.a.zb(b),c);0>e?d&&b.push(c):d||b.splice(e,1)},ka:f,extend:c,Xa:d,Ya:f?d:c,D:b,Ca:function(a,b){if(!a)return a;var c={},d;for(d in a)a.hasOwnProperty(d)&&(c[d]=b(a[d],d,a));return c},ob:function(b){for(;b.firstChild;)a.removeNode(b.firstChild)},jc:function(b){b=a.a.V(b);for(var c=(b[0]&&b[0].ownerDocument||u).createElement("div"),d=0,e=b.length;d<e;d++)c.appendChild(a.$(b[d]));return c},ua:function(b,c){for(var d=0,e=b.length,h=[];d<e;d++){var m=b[d].cloneNode(!0);h.push(c?a.$(m):m)}return h},
da:function(b,c){a.a.ob(b);if(c)for(var d=0,e=c.length;d<e;d++)b.appendChild(c[d])},qc:function(b,c){var d=b.nodeType?[b]:b;if(0<d.length){for(var e=d[0],h=e.parentNode,m=0,l=c.length;m<l;m++)h.insertBefore(c[m],e);m=0;for(l=d.length;m<l;m++)a.removeNode(d[m])}},za:function(a,b){if(a.length){for(b=8===b.nodeType&&b.parentNode||b;a.length&&a[0].parentNode!==b;)a.splice(0,1);for(;1<a.length&&a[a.length-1].parentNode!==b;)a.length--;if(1<a.length){var c=a[0],d=a[a.length-1];for(a.length=0;c!==d;)a.push(c),
c=c.nextSibling;a.push(d)}}return a},sc:function(a,b){7>h?a.setAttribute("selected",b):a.selected=b},$a:function(a){return null===a||a===n?"":a.trim?a.trim():a.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},nd:function(a,b){a=a||"";return b.length>a.length?!1:a.substring(0,b.length)===b},Mc:function(a,b){if(a===b)return!0;if(11===a.nodeType)return!1;if(b.contains)return b.contains(3===a.nodeType?a.parentNode:a);if(b.compareDocumentPosition)return 16==(b.compareDocumentPosition(a)&16);for(;a&&a!=
b;)a=a.parentNode;return!!a},nb:function(b){return a.a.Mc(b,b.ownerDocument.documentElement)},Qb:function(b){return!!a.a.Sb(b,a.a.nb)},A:function(a){return a&&a.tagName&&a.tagName.toLowerCase()},Wb:function(b){return a.onError?function(){try{return b.apply(this,arguments)}catch(c){throw a.onError&&a.onError(c),c;}}:b},setTimeout:function(b,c){return setTimeout(a.a.Wb(b),c)},$b:function(b){setTimeout(function(){a.onError&&a.onError(b);throw b;},0)},p:function(b,c,d){var e=a.a.Wb(d);d=h&&m[c];if(a.options.useOnlyNativeEvents||
d||!v)if(d||"function"!=typeof b.addEventListener)if("undefined"!=typeof b.attachEvent){var l=function(a){e.call(b,a)},f="on"+c;b.attachEvent(f,l);a.a.F.oa(b,function(){b.detachEvent(f,l)})}else throw Error("Browser doesn't support addEventListener or attachEvent");else b.addEventListener(c,e,!1);else v(b).bind(c,e)},Da:function(b,c){if(!b||!b.nodeType)throw Error("element must be a DOM node when calling triggerEvent");var d;"input"===a.a.A(b)&&b.type&&"click"==c.toLowerCase()?(d=b.type,d="checkbox"==
d||"radio"==d):d=!1;if(a.options.useOnlyNativeEvents||!v||d)if("function"==typeof u.createEvent)if("function"==typeof b.dispatchEvent)d=u.createEvent(l[c]||"HTMLEvents"),d.initEvent(c,!0,!0,x,0,0,0,0,0,!1,!1,!1,!1,0,b),b.dispatchEvent(d);else throw Error("The supplied element doesn't support dispatchEvent");else if(d&&b.click)b.click();else if("undefined"!=typeof b.fireEvent)b.fireEvent("on"+c);else throw Error("Browser doesn't support triggering events");else v(b).trigger(c)},c:function(b){return a.H(b)?
b():b},zb:function(b){return a.H(b)?b.t():b},bb:function(b,c,d){var h;c&&("object"===typeof b.classList?(h=b.classList[d?"add":"remove"],a.a.q(c.match(r),function(a){h.call(b.classList,a)})):"string"===typeof b.className.baseVal?e(b.className,"baseVal",c,d):e(b,"className",c,d))},Za:function(b,c){var d=a.a.c(c);if(null===d||d===n)d="";var e=a.f.firstChild(b);!e||3!=e.nodeType||a.f.nextSibling(e)?a.f.da(b,[b.ownerDocument.createTextNode(d)]):e.data=d;a.a.Rc(b)},rc:function(a,b){a.name=b;if(7>=h)try{a.mergeAttributes(u.createElement("<input name='"+
a.name+"'/>"),!1)}catch(c){}},Rc:function(a){9<=h&&(a=1==a.nodeType?a:a.parentNode,a.style&&(a.style.zoom=a.style.zoom))},Nc:function(a){if(h){var b=a.style.width;a.style.width=0;a.style.width=b}},hd:function(b,c){b=a.a.c(b);c=a.a.c(c);for(var d=[],e=b;e<=c;e++)d.push(e);return d},V:function(a){for(var b=[],c=0,d=a.length;c<d;c++)b.push(a[c]);return b},Yb:function(a){return g?Symbol(a):a},rd:6===h,sd:7===h,C:h,ec:function(b,c){for(var d=a.a.V(b.getElementsByTagName("input")).concat(a.a.V(b.getElementsByTagName("textarea"))),
e="string"==typeof c?function(a){return a.name===c}:function(a){return c.test(a.name)},h=[],m=d.length-1;0<=m;m--)e(d[m])&&h.push(d[m]);return h},ed:function(b){return"string"==typeof b&&(b=a.a.$a(b))?F&&F.parse?F.parse(b):(new Function("return "+b))():null},Eb:function(b,c,d){if(!F||!F.stringify)throw Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
return F.stringify(a.a.c(b),c,d)},fd:function(c,d,e){e=e||{};var h=e.params||{},m=e.includeFields||this.cc,l=c;if("object"==typeof c&&"form"===a.a.A(c))for(var l=c.action,f=m.length-1;0<=f;f--)for(var g=a.a.ec(c,m[f]),k=g.length-1;0<=k;k--)h[g[k].name]=g[k].value;d=a.a.c(d);var r=u.createElement("form");r.style.display="none";r.action=l;r.method="post";for(var n in d)c=u.createElement("input"),c.type="hidden",c.name=n,c.value=a.a.Eb(a.a.c(d[n])),r.appendChild(c);b(h,function(a,b){var c=u.createElement("input");
c.type="hidden";c.name=a;c.value=b;r.appendChild(c)});u.body.appendChild(r);e.submitter?e.submitter(r):r.submit();setTimeout(function(){r.parentNode.removeChild(r)},0)}}}();a.b("utils",a.a);a.b("utils.arrayForEach",a.a.q);a.b("utils.arrayFirst",a.a.Sb);a.b("utils.arrayFilter",a.a.Ka);a.b("utils.arrayGetDistinctValues",a.a.Tb);a.b("utils.arrayIndexOf",a.a.o);a.b("utils.arrayMap",a.a.fb);a.b("utils.arrayPushAll",a.a.ra);a.b("utils.arrayRemoveItem",a.a.La);a.b("utils.extend",a.a.extend);a.b("utils.fieldsIncludedWithJsonPost",
a.a.cc);a.b("utils.getFormFields",a.a.ec);a.b("utils.peekObservable",a.a.zb);a.b("utils.postJson",a.a.fd);a.b("utils.parseJson",a.a.ed);a.b("utils.registerEventHandler",a.a.p);a.b("utils.stringifyJson",a.a.Eb);a.b("utils.range",a.a.hd);a.b("utils.toggleDomNodeCssClass",a.a.bb);a.b("utils.triggerEvent",a.a.Da);a.b("utils.unwrapObservable",a.a.c);a.b("utils.objectForEach",a.a.D);a.b("utils.addOrRemoveItem",a.a.pa);a.b("utils.setTextContent",a.a.Za);a.b("unwrap",a.a.c);Function.prototype.bind||(Function.prototype.bind=
function(a){var c=this;if(1===arguments.length)return function(){return c.apply(a,arguments)};var d=Array.prototype.slice.call(arguments,1);return function(){var e=d.slice(0);e.push.apply(e,arguments);return c.apply(a,e)}});a.a.e=new function(){function a(b,g){var k=b[d];if(!k||"null"===k||!e[k]){if(!g)return n;k=b[d]="ko"+c++;e[k]={}}return e[k]}var c=0,d="__ko__"+(new Date).getTime(),e={};return{get:function(c,d){var e=a(c,!1);return e===n?n:e[d]},set:function(c,d,e){if(e!==n||a(c,!1)!==n)a(c,!0)[d]=
e},clear:function(a){var b=a[d];return b?(delete e[b],a[d]=null,!0):!1},I:function(){return c++ +d}}};a.b("utils.domData",a.a.e);a.b("utils.domData.clear",a.a.e.clear);a.a.F=new function(){function b(b,c){var e=a.a.e.get(b,d);e===n&&c&&(e=[],a.a.e.set(b,d,e));return e}function c(d){var e=b(d,!1);if(e)for(var e=e.slice(0),l=0;l<e.length;l++)e[l](d);a.a.e.clear(d);a.a.F.cleanExternalData(d);if(f[d.nodeType])for(e=d.firstChild;d=e;)e=d.nextSibling,8===d.nodeType&&c(d)}var d=a.a.e.I(),e={1:!0,8:!0,9:!0},
f={1:!0,9:!0};return{oa:function(a,c){if("function"!=typeof c)throw Error("Callback must be a function");b(a,!0).push(c)},pc:function(c,e){var l=b(c,!1);l&&(a.a.La(l,e),0==l.length&&a.a.e.set(c,d,n))},$:function(b){if(e[b.nodeType]&&(c(b),f[b.nodeType])){var d=[];a.a.ra(d,b.getElementsByTagName("*"));for(var l=0,m=d.length;l<m;l++)c(d[l])}return b},removeNode:function(b){a.$(b);b.parentNode&&b.parentNode.removeChild(b)},cleanExternalData:function(a){v&&"function"==typeof v.cleanData&&v.cleanData([a])}}};
a.$=a.a.F.$;a.removeNode=a.a.F.removeNode;a.b("cleanNode",a.$);a.b("removeNode",a.removeNode);a.b("utils.domNodeDisposal",a.a.F);a.b("utils.domNodeDisposal.addDisposeCallback",a.a.F.oa);a.b("utils.domNodeDisposal.removeDisposeCallback",a.a.F.pc);(function(){var b=[0,"",""],c=[1,"<table>","</table>"],d=[3,"<table><tbody><tr>","</tr></tbody></table>"],e=[1,"<select multiple='multiple'>","</select>"],f={thead:c,tbody:c,tfoot:c,tr:[2,"<table><tbody>","</tbody></table>"],td:d,th:d,option:e,optgroup:e},
g=8>=a.a.C;a.a.ma=function(c,d){var e;if(v)if(v.parseHTML)e=v.parseHTML(c,d)||[];else{if((e=v.clean([c],d))&&e[0]){for(var h=e[0];h.parentNode&&11!==h.parentNode.nodeType;)h=h.parentNode;h.parentNode&&h.parentNode.removeChild(h)}}else{(e=d)||(e=u);var h=e.parentWindow||e.defaultView||x,r=a.a.$a(c).toLowerCase(),q=e.createElement("div"),p;p=(r=r.match(/^<([a-z]+)[ >]/))&&f[r[1]]||b;r=p[0];p="ignored<div>"+p[1]+c+p[2]+"</div>";"function"==typeof h.innerShiv?q.appendChild(h.innerShiv(p)):(g&&e.appendChild(q),
q.innerHTML=p,g&&q.parentNode.removeChild(q));for(;r--;)q=q.lastChild;e=a.a.V(q.lastChild.childNodes)}return e};a.a.Cb=function(b,c){a.a.ob(b);c=a.a.c(c);if(null!==c&&c!==n)if("string"!=typeof c&&(c=c.toString()),v)v(b).html(c);else for(var d=a.a.ma(c,b.ownerDocument),e=0;e<d.length;e++)b.appendChild(d[e])}})();a.b("utils.parseHtmlFragment",a.a.ma);a.b("utils.setHtml",a.a.Cb);a.M=function(){function b(c,e){if(c)if(8==c.nodeType){var f=a.M.lc(c.nodeValue);null!=f&&e.push({Lc:c,cd:f})}else if(1==c.nodeType)for(var f=
0,g=c.childNodes,k=g.length;f<k;f++)b(g[f],e)}var c={};return{wb:function(a){if("function"!=typeof a)throw Error("You can only pass a function to ko.memoization.memoize()");var b=(4294967296*(1+Math.random())|0).toString(16).substring(1)+(4294967296*(1+Math.random())|0).toString(16).substring(1);c[b]=a;return"\x3c!--[ko_memo:"+b+"]--\x3e"},xc:function(a,b){var f=c[a];if(f===n)throw Error("Couldn't find any memo with ID "+a+". Perhaps it's already been unmemoized.");try{return f.apply(null,b||[]),
!0}finally{delete c[a]}},yc:function(c,e){var f=[];b(c,f);for(var g=0,k=f.length;g<k;g++){var l=f[g].Lc,m=[l];e&&a.a.ra(m,e);a.M.xc(f[g].cd,m);l.nodeValue="";l.parentNode&&l.parentNode.removeChild(l)}},lc:function(a){return(a=a.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:null}}}();a.b("memoization",a.M);a.b("memoization.memoize",a.M.wb);a.b("memoization.unmemoize",a.M.xc);a.b("memoization.parseMemoText",a.M.lc);a.b("memoization.unmemoizeDomNodeAndDescendants",a.M.yc);a.Y=function(){function b(){if(e)for(var b=
e,c=0,m;g<e;)if(m=d[g++]){if(g>b){if(5E3<=++c){g=e;a.a.$b(Error("'Too much recursion' after processing "+c+" task groups."));break}b=e}try{m()}catch(h){a.a.$b(h)}}}function c(){b();g=e=d.length=0}var d=[],e=0,f=1,g=0;return{scheduler:x.MutationObserver?function(a){var b=u.createElement("div");(new MutationObserver(a)).observe(b,{attributes:!0});return function(){b.classList.toggle("foo")}}(c):u&&"onreadystatechange"in u.createElement("script")?function(a){var b=u.createElement("script");b.onreadystatechange=
function(){b.onreadystatechange=null;u.documentElement.removeChild(b);b=null;a()};u.documentElement.appendChild(b)}:function(a){setTimeout(a,0)},Wa:function(b){e||a.Y.scheduler(c);d[e++]=b;return f++},cancel:function(a){a-=f-e;a>=g&&a<e&&(d[a]=null)},resetForTesting:function(){var a=e-g;g=e=d.length=0;return a},md:b}}();a.b("tasks",a.Y);a.b("tasks.schedule",a.Y.Wa);a.b("tasks.runEarly",a.Y.md);a.ya={throttle:function(b,c){b.throttleEvaluation=c;var d=null;return a.B({read:b,write:function(e){clearTimeout(d);
d=a.a.setTimeout(function(){b(e)},c)}})},rateLimit:function(a,c){var d,e,f;"number"==typeof c?d=c:(d=c.timeout,e=c.method);a.cb=!1;f="notifyWhenChangesStop"==e?V:U;a.Ta(function(a){return f(a,d)})},deferred:function(b,c){if(!0!==c)throw Error("The 'deferred' extender only accepts the value 'true', because it is not supported to turn deferral off once enabled.");b.cb||(b.cb=!0,b.Ta(function(c){var e;return function(){a.Y.cancel(e);e=a.Y.Wa(c);b.notifySubscribers(n,"dirty")}}))},notify:function(a,c){a.equalityComparer=
"always"==c?null:J}};var T={undefined:1,"boolean":1,number:1,string:1};a.b("extenders",a.ya);a.vc=function(b,c,d){this.ia=b;this.gb=c;this.Kc=d;this.R=!1;a.G(this,"dispose",this.k)};a.vc.prototype.k=function(){this.R=!0;this.Kc()};a.J=function(){a.a.Ya(this,D);D.rb(this)};var I="change",D={rb:function(a){a.K={};a.Nb=1},X:function(b,c,d){var e=this;d=d||I;var f=new a.vc(e,c?b.bind(c):b,function(){a.a.La(e.K[d],f);e.Ia&&e.Ia(d)});e.sa&&e.sa(d);e.K[d]||(e.K[d]=[]);e.K[d].push(f);return f},notifySubscribers:function(b,
c){c=c||I;c===I&&this.zc();if(this.Pa(c))try{a.l.Ub();for(var d=this.K[c].slice(0),e=0,f;f=d[e];++e)f.R||f.gb(b)}finally{a.l.end()}},Na:function(){return this.Nb},Uc:function(a){return this.Na()!==a},zc:function(){++this.Nb},Ta:function(b){var c=this,d=a.H(c),e,f,g;c.Ha||(c.Ha=c.notifySubscribers,c.notifySubscribers=W);var k=b(function(){c.Mb=!1;d&&g===c&&(g=c());e=!1;c.tb(f,g)&&c.Ha(f=g)});c.Lb=function(a){c.Mb=e=!0;g=a;k()};c.Kb=function(a){e||(f=a,c.Ha(a,"beforeChange"))}},Pa:function(a){return this.K[a]&&
this.K[a].length},Sc:function(b){if(b)return this.K[b]&&this.K[b].length||0;var c=0;a.a.D(this.K,function(a,b){"dirty"!==a&&(c+=b.length)});return c},tb:function(a,c){return!this.equalityComparer||!this.equalityComparer(a,c)},extend:function(b){var c=this;b&&a.a.D(b,function(b,e){var f=a.ya[b];"function"==typeof f&&(c=f(c,e)||c)});return c}};a.G(D,"subscribe",D.X);a.G(D,"extend",D.extend);a.G(D,"getSubscriptionsCount",D.Sc);a.a.ka&&a.a.Xa(D,Function.prototype);a.J.fn=D;a.hc=function(a){return null!=
a&&"function"==typeof a.X&&"function"==typeof a.notifySubscribers};a.b("subscribable",a.J);a.b("isSubscribable",a.hc);a.va=a.l=function(){function b(a){d.push(e);e=a}function c(){e=d.pop()}var d=[],e,f=0;return{Ub:b,end:c,oc:function(b){if(e){if(!a.hc(b))throw Error("Only subscribable things can act as dependencies");e.gb.call(e.Gc,b,b.Cc||(b.Cc=++f))}},w:function(a,d,e){try{return b(),a.apply(d,e||[])}finally{c()}},Aa:function(){if(e)return e.m.Aa()},Sa:function(){if(e)return e.Sa}}}();a.b("computedContext",
a.va);a.b("computedContext.getDependenciesCount",a.va.Aa);a.b("computedContext.isInitial",a.va.Sa);a.b("ignoreDependencies",a.qd=a.l.w);var E=a.a.Yb("_latestValue");a.N=function(b){function c(){if(0<arguments.length)return c.tb(c[E],arguments[0])&&(c.ga(),c[E]=arguments[0],c.fa()),this;a.l.oc(c);return c[E]}c[E]=b;a.a.ka||a.a.extend(c,a.J.fn);a.J.fn.rb(c);a.a.Ya(c,B);a.options.deferUpdates&&a.ya.deferred(c,!0);return c};var B={equalityComparer:J,t:function(){return this[E]},fa:function(){this.notifySubscribers(this[E])},
ga:function(){this.notifySubscribers(this[E],"beforeChange")}};a.a.ka&&a.a.Xa(B,a.J.fn);var H=a.N.gd="__ko_proto__";B[H]=a.N;a.Oa=function(b,c){return null===b||b===n||b[H]===n?!1:b[H]===c?!0:a.Oa(b[H],c)};a.H=function(b){return a.Oa(b,a.N)};a.Ba=function(b){return"function"==typeof b&&b[H]===a.N||"function"==typeof b&&b[H]===a.B&&b.Vc?!0:!1};a.b("observable",a.N);a.b("isObservable",a.H);a.b("isWriteableObservable",a.Ba);a.b("isWritableObservable",a.Ba);a.b("observable.fn",B);a.G(B,"peek",B.t);a.G(B,
"valueHasMutated",B.fa);a.G(B,"valueWillMutate",B.ga);a.la=function(b){b=b||[];if("object"!=typeof b||!("length"in b))throw Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");b=a.N(b);a.a.Ya(b,a.la.fn);return b.extend({trackArrayChanges:!0})};a.la.fn={remove:function(b){for(var c=this.t(),d=[],e="function"!=typeof b||a.H(b)?function(a){return a===b}:b,f=0;f<c.length;f++){var g=c[f];e(g)&&(0===d.length&&this.ga(),d.push(g),c.splice(f,1),f--)}d.length&&
this.fa();return d},removeAll:function(b){if(b===n){var c=this.t(),d=c.slice(0);this.ga();c.splice(0,c.length);this.fa();return d}return b?this.remove(function(c){return 0<=a.a.o(b,c)}):[]},destroy:function(b){var c=this.t(),d="function"!=typeof b||a.H(b)?function(a){return a===b}:b;this.ga();for(var e=c.length-1;0<=e;e--)d(c[e])&&(c[e]._destroy=!0);this.fa()},destroyAll:function(b){return b===n?this.destroy(function(){return!0}):b?this.destroy(function(c){return 0<=a.a.o(b,c)}):[]},indexOf:function(b){var c=
this();return a.a.o(c,b)},replace:function(a,c){var d=this.indexOf(a);0<=d&&(this.ga(),this.t()[d]=c,this.fa())}};a.a.ka&&a.a.Xa(a.la.fn,a.N.fn);a.a.q("pop push reverse shift sort splice unshift".split(" "),function(b){a.la.fn[b]=function(){var a=this.t();this.ga();this.Vb(a,b,arguments);var d=a[b].apply(a,arguments);this.fa();return d===a?this:d}});a.a.q(["slice"],function(b){a.la.fn[b]=function(){var a=this();return a[b].apply(a,arguments)}});a.b("observableArray",a.la);a.ya.trackArrayChanges=function(b,
c){function d(){if(!e){e=!0;var c=b.notifySubscribers;b.notifySubscribers=function(a,b){b&&b!==I||++k;return c.apply(this,arguments)};var d=[].concat(b.t()||[]);f=null;g=b.X(function(c){c=[].concat(c||[]);if(b.Pa("arrayChange")){var e;if(!f||1<k)f=a.a.ib(d,c,b.hb);e=f}d=c;f=null;k=0;e&&e.length&&b.notifySubscribers(e,"arrayChange")})}}b.hb={};c&&"object"==typeof c&&a.a.extend(b.hb,c);b.hb.sparse=!0;if(!b.Vb){var e=!1,f=null,g,k=0,l=b.sa,m=b.Ia;b.sa=function(a){l&&l.call(b,a);"arrayChange"===a&&d()};
b.Ia=function(a){m&&m.call(b,a);"arrayChange"!==a||b.Pa("arrayChange")||(g.k(),e=!1)};b.Vb=function(b,c,d){function m(a,b,c){return l[l.length]={status:a,value:b,index:c}}if(e&&!k){var l=[],g=b.length,t=d.length,G=0;switch(c){case "push":G=g;case "unshift":for(c=0;c<t;c++)m("added",d[c],G+c);break;case "pop":G=g-1;case "shift":g&&m("deleted",b[G],G);break;case "splice":c=Math.min(Math.max(0,0>d[0]?g+d[0]:d[0]),g);for(var g=1===t?g:Math.min(c+(d[1]||0),g),t=c+t-2,G=Math.max(g,t),P=[],n=[],Q=2;c<G;++c,
++Q)c<g&&n.push(m("deleted",b[c],c)),c<t&&P.push(m("added",d[Q],c));a.a.dc(n,P);break;default:return}f=l}}}};var s=a.a.Yb("_state");a.m=a.B=function(b,c,d){function e(){if(0<arguments.length){if("function"===typeof f)f.apply(g.pb,arguments);else throw Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");return this}a.l.oc(e);(g.S||g.s&&e.Qa())&&e.aa();return g.T}"object"===typeof b?d=b:(d=d||{},b&&(d.read=
b));if("function"!=typeof d.read)throw Error("Pass a function that returns the value of the ko.computed");var f=d.write,g={T:n,S:!0,Ra:!1,Fb:!1,R:!1,Va:!1,s:!1,jd:d.read,pb:c||d.owner,i:d.disposeWhenNodeIsRemoved||d.i||null,wa:d.disposeWhen||d.wa,mb:null,r:{},L:0,bc:null};e[s]=g;e.Vc="function"===typeof f;a.a.ka||a.a.extend(e,a.J.fn);a.J.fn.rb(e);a.a.Ya(e,z);d.pure?(g.Va=!0,g.s=!0,a.a.extend(e,$)):d.deferEvaluation&&a.a.extend(e,aa);a.options.deferUpdates&&a.ya.deferred(e,!0);g.i&&(g.Fb=!0,g.i.nodeType||
(g.i=null));g.s||d.deferEvaluation||e.aa();g.i&&e.ba()&&a.a.F.oa(g.i,g.mb=function(){e.k()});return e};var z={equalityComparer:J,Aa:function(){return this[s].L},Pb:function(a,c,d){if(this[s].Va&&c===this)throw Error("A 'pure' computed must not be called recursively");this[s].r[a]=d;d.Ga=this[s].L++;d.na=c.Na()},Qa:function(){var a,c,d=this[s].r;for(a in d)if(d.hasOwnProperty(a)&&(c=d[a],c.ia.Uc(c.na)))return!0},bd:function(){this.Fa&&!this[s].Ra&&this.Fa()},ba:function(){return this[s].S||0<this[s].L},
ld:function(){this.Mb||this.ac()},uc:function(a){if(a.cb&&!this[s].i){var c=a.X(this.bd,this,"dirty"),d=a.X(this.ld,this);return{ia:a,k:function(){c.k();d.k()}}}return a.X(this.ac,this)},ac:function(){var b=this,c=b.throttleEvaluation;c&&0<=c?(clearTimeout(this[s].bc),this[s].bc=a.a.setTimeout(function(){b.aa(!0)},c)):b.Fa?b.Fa():b.aa(!0)},aa:function(b){var c=this[s],d=c.wa;if(!c.Ra&&!c.R){if(c.i&&!a.a.nb(c.i)||d&&d()){if(!c.Fb){this.k();return}}else c.Fb=!1;c.Ra=!0;try{this.Qc(b)}finally{c.Ra=!1}c.L||
this.k()}},Qc:function(b){var c=this[s],d=c.Va?n:!c.L,e={Hc:this,Ma:c.r,lb:c.L};a.l.Ub({Gc:e,gb:Y,m:this,Sa:d});c.r={};c.L=0;e=this.Pc(c,e);this.tb(c.T,e)&&(c.s||this.notifySubscribers(c.T,"beforeChange"),c.T=e,c.s?this.zc():b&&this.notifySubscribers(c.T));d&&this.notifySubscribers(c.T,"awake")},Pc:function(b,c){try{var d=b.jd;return b.pb?d.call(b.pb):d()}finally{a.l.end(),c.lb&&!b.s&&a.a.D(c.Ma,X),b.S=!1}},t:function(){var a=this[s];(a.S&&!a.L||a.s&&this.Qa())&&this.aa();return a.T},Ta:function(b){a.J.fn.Ta.call(this,
b);this.Fa=function(){this.Kb(this[s].T);this[s].S=!0;this.Lb(this)}},k:function(){var b=this[s];!b.s&&b.r&&a.a.D(b.r,function(a,b){b.k&&b.k()});b.i&&b.mb&&a.a.F.pc(b.i,b.mb);b.r=null;b.L=0;b.R=!0;b.S=!1;b.s=!1;b.i=null}},$={sa:function(b){var c=this,d=c[s];if(!d.R&&d.s&&"change"==b){d.s=!1;if(d.S||c.Qa())d.r=null,d.L=0,d.S=!0,c.aa();else{var e=[];a.a.D(d.r,function(a,b){e[b.Ga]=a});a.a.q(e,function(a,b){var e=d.r[a],l=c.uc(e.ia);l.Ga=b;l.na=e.na;d.r[a]=l})}d.R||c.notifySubscribers(d.T,"awake")}},
Ia:function(b){var c=this[s];c.R||"change"!=b||this.Pa("change")||(a.a.D(c.r,function(a,b){b.k&&(c.r[a]={ia:b.ia,Ga:b.Ga,na:b.na},b.k())}),c.s=!0,this.notifySubscribers(n,"asleep"))},Na:function(){var b=this[s];b.s&&(b.S||this.Qa())&&this.aa();return a.J.fn.Na.call(this)}},aa={sa:function(a){"change"!=a&&"beforeChange"!=a||this.t()}};a.a.ka&&a.a.Xa(z,a.J.fn);var R=a.N.gd;a.m[R]=a.N;z[R]=a.m;a.Xc=function(b){return a.Oa(b,a.m)};a.Yc=function(b){return a.Oa(b,a.m)&&b[s]&&b[s].Va};a.b("computed",a.m);
a.b("dependentObservable",a.m);a.b("isComputed",a.Xc);a.b("isPureComputed",a.Yc);a.b("computed.fn",z);a.G(z,"peek",z.t);a.G(z,"dispose",z.k);a.G(z,"isActive",z.ba);a.G(z,"getDependenciesCount",z.Aa);a.nc=function(b,c){if("function"===typeof b)return a.m(b,c,{pure:!0});b=a.a.extend({},b);b.pure=!0;return a.m(b,c)};a.b("pureComputed",a.nc);(function(){function b(a,f,g){g=g||new d;a=f(a);if("object"!=typeof a||null===a||a===n||a instanceof RegExp||a instanceof Date||a instanceof String||a instanceof
Number||a instanceof Boolean)return a;var k=a instanceof Array?[]:{};g.save(a,k);c(a,function(c){var d=f(a[c]);switch(typeof d){case "boolean":case "number":case "string":case "function":k[c]=d;break;case "object":case "undefined":var h=g.get(d);k[c]=h!==n?h:b(d,f,g)}});return k}function c(a,b){if(a instanceof Array){for(var c=0;c<a.length;c++)b(c);"function"==typeof a.toJSON&&b("toJSON")}else for(c in a)b(c)}function d(){this.keys=[];this.Ib=[]}a.wc=function(c){if(0==arguments.length)throw Error("When calling ko.toJS, pass the object you want to convert.");
return b(c,function(b){for(var c=0;a.H(b)&&10>c;c++)b=b();return b})};a.toJSON=function(b,c,d){b=a.wc(b);return a.a.Eb(b,c,d)};d.prototype={save:function(b,c){var d=a.a.o(this.keys,b);0<=d?this.Ib[d]=c:(this.keys.push(b),this.Ib.push(c))},get:function(b){b=a.a.o(this.keys,b);return 0<=b?this.Ib[b]:n}}})();a.b("toJS",a.wc);a.b("toJSON",a.toJSON);(function(){a.j={u:function(b){switch(a.a.A(b)){case "option":return!0===b.__ko__hasDomDataOptionValue__?a.a.e.get(b,a.d.options.xb):7>=a.a.C?b.getAttributeNode("value")&&
b.getAttributeNode("value").specified?b.value:b.text:b.value;case "select":return 0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex]):n;default:return b.value}},ha:function(b,c,d){switch(a.a.A(b)){case "option":switch(typeof c){case "string":a.a.e.set(b,a.d.options.xb,n);"__ko__hasDomDataOptionValue__"in b&&delete b.__ko__hasDomDataOptionValue__;b.value=c;break;default:a.a.e.set(b,a.d.options.xb,c),b.__ko__hasDomDataOptionValue__=!0,b.value="number"===typeof c?c:""}break;case "select":if(""===c||
null===c)c=n;for(var e=-1,f=0,g=b.options.length,k;f<g;++f)if(k=a.j.u(b.options[f]),k==c||""==k&&c===n){e=f;break}if(d||0<=e||c===n&&1<b.size)b.selectedIndex=e;break;default:if(null===c||c===n)c="";b.value=c}}}})();a.b("selectExtensions",a.j);a.b("selectExtensions.readValue",a.j.u);a.b("selectExtensions.writeValue",a.j.ha);a.h=function(){function b(b){b=a.a.$a(b);123===b.charCodeAt(0)&&(b=b.slice(1,-1));var c=[],d=b.match(e),r,k=[],p=0;if(d){d.push(",");for(var A=0,y;y=d[A];++A){var t=y.charCodeAt(0);
if(44===t){if(0>=p){c.push(r&&k.length?{key:r,value:k.join("")}:{unknown:r||k.join("")});r=p=0;k=[];continue}}else if(58===t){if(!p&&!r&&1===k.length){r=k.pop();continue}}else 47===t&&A&&1<y.length?(t=d[A-1].match(f))&&!g[t[0]]&&(b=b.substr(b.indexOf(y)+1),d=b.match(e),d.push(","),A=-1,y="/"):40===t||123===t||91===t?++p:41===t||125===t||93===t?--p:r||k.length||34!==t&&39!==t||(y=y.slice(1,-1));k.push(y)}}return c}var c=["true","false","null","undefined"],d=/^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i,
e=RegExp("\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|/(?:[^/\\\\]|\\\\.)*/w*|[^\\s:,/][^,\"'{}()/:[\\]]*[^\\s,\"'{}()/:[\\]]|[^\\s]","g"),f=/[\])"'A-Za-z0-9_$]+$/,g={"in":1,"return":1,"typeof":1},k={};return{ta:[],ea:k,yb:b,Ua:function(e,m){function h(b,e){var m;if(!A){var l=a.getBindingHandler(b);if(l&&l.preprocess&&!(e=l.preprocess(e,b,h)))return;if(l=k[b])m=e,0<=a.a.o(c,m)?m=!1:(l=m.match(d),m=null===l?!1:l[1]?"Object("+l[1]+")"+l[2]:m),l=m;l&&g.push("'"+b+"':function(_z){"+m+"=_z}")}p&&(e=
"function(){return "+e+" }");f.push("'"+b+"':"+e)}m=m||{};var f=[],g=[],p=m.valueAccessors,A=m.bindingParams,y="string"===typeof e?b(e):e;a.a.q(y,function(a){h(a.key||a.unknown,a.value)});g.length&&h("_ko_property_writers","{"+g.join(",")+" }");return f.join(",")},ad:function(a,b){for(var c=0;c<a.length;c++)if(a[c].key==b)return!0;return!1},Ea:function(b,c,d,e,f){if(b&&a.H(b))!a.Ba(b)||f&&b.t()===e||b(e);else if((b=c.get("_ko_property_writers"))&&b[d])b[d](e)}}}();a.b("expressionRewriting",a.h);a.b("expressionRewriting.bindingRewriteValidators",
a.h.ta);a.b("expressionRewriting.parseObjectLiteral",a.h.yb);a.b("expressionRewriting.preProcessBindings",a.h.Ua);a.b("expressionRewriting._twoWayBindings",a.h.ea);a.b("jsonExpressionRewriting",a.h);a.b("jsonExpressionRewriting.insertPropertyAccessorsIntoJson",a.h.Ua);(function(){function b(a){return 8==a.nodeType&&g.test(f?a.text:a.nodeValue)}function c(a){return 8==a.nodeType&&k.test(f?a.text:a.nodeValue)}function d(a,d){for(var e=a,f=1,l=[];e=e.nextSibling;){if(c(e)&&(f--,0===f))return l;l.push(e);
b(e)&&f++}if(!d)throw Error("Cannot find closing comment tag to match: "+a.nodeValue);return null}function e(a,b){var c=d(a,b);return c?0<c.length?c[c.length-1].nextSibling:a.nextSibling:null}var f=u&&"\x3c!--test--\x3e"===u.createComment("test").text,g=f?/^\x3c!--\s*ko(?:\s+([\s\S]+))?\s*--\x3e$/:/^\s*ko(?:\s+([\s\S]+))?\s*$/,k=f?/^\x3c!--\s*\/ko\s*--\x3e$/:/^\s*\/ko\s*$/,l={ul:!0,ol:!0};a.f={Z:{},childNodes:function(a){return b(a)?d(a):a.childNodes},xa:function(c){if(b(c)){c=a.f.childNodes(c);for(var d=
0,e=c.length;d<e;d++)a.removeNode(c[d])}else a.a.ob(c)},da:function(c,d){if(b(c)){a.f.xa(c);for(var e=c.nextSibling,f=0,l=d.length;f<l;f++)e.parentNode.insertBefore(d[f],e)}else a.a.da(c,d)},mc:function(a,c){b(a)?a.parentNode.insertBefore(c,a.nextSibling):a.firstChild?a.insertBefore(c,a.firstChild):a.appendChild(c)},gc:function(c,d,e){e?b(c)?c.parentNode.insertBefore(d,e.nextSibling):e.nextSibling?c.insertBefore(d,e.nextSibling):c.appendChild(d):a.f.mc(c,d)},firstChild:function(a){return b(a)?!a.nextSibling||
c(a.nextSibling)?null:a.nextSibling:a.firstChild},nextSibling:function(a){b(a)&&(a=e(a));return a.nextSibling&&c(a.nextSibling)?null:a.nextSibling},Tc:b,pd:function(a){return(a=(f?a.text:a.nodeValue).match(g))?a[1]:null},kc:function(d){if(l[a.a.A(d)]){var h=d.firstChild;if(h){do if(1===h.nodeType){var f;f=h.firstChild;var g=null;if(f){do if(g)g.push(f);else if(b(f)){var k=e(f,!0);k?f=k:g=[f]}else c(f)&&(g=[f]);while(f=f.nextSibling)}if(f=g)for(g=h.nextSibling,k=0;k<f.length;k++)g?d.insertBefore(f[k],
g):d.appendChild(f[k])}while(h=h.nextSibling)}}}}})();a.b("virtualElements",a.f);a.b("virtualElements.allowedBindings",a.f.Z);a.b("virtualElements.emptyNode",a.f.xa);a.b("virtualElements.insertAfter",a.f.gc);a.b("virtualElements.prepend",a.f.mc);a.b("virtualElements.setDomNodeChildren",a.f.da);(function(){a.Q=function(){this.Fc={}};a.a.extend(a.Q.prototype,{nodeHasBindings:function(b){switch(b.nodeType){case 1:return null!=b.getAttribute("data-bind")||a.g.getComponentNameForNode(b);case 8:return a.f.Tc(b);
default:return!1}},getBindings:function(b,c){var d=this.getBindingsString(b,c),d=d?this.parseBindingsString(d,c,b):null;return a.g.Ob(d,b,c,!1)},getBindingAccessors:function(b,c){var d=this.getBindingsString(b,c),d=d?this.parseBindingsString(d,c,b,{valueAccessors:!0}):null;return a.g.Ob(d,b,c,!0)},getBindingsString:function(b){switch(b.nodeType){case 1:return b.getAttribute("data-bind");case 8:return a.f.pd(b);default:return null}},parseBindingsString:function(b,c,d,e){try{var f=this.Fc,g=b+(e&&e.valueAccessors||
""),k;if(!(k=f[g])){var l,m="with($context){with($data||{}){return{"+a.h.Ua(b,e)+"}}}";l=new Function("$context","$element",m);k=f[g]=l}return k(c,d)}catch(h){throw h.message="Unable to parse bindings.\nBindings value: "+b+"\nMessage: "+h.message,h;}}});a.Q.instance=new a.Q})();a.b("bindingProvider",a.Q);(function(){function b(a){return function(){return a}}function c(a){return a()}function d(b){return a.a.Ca(a.l.w(b),function(a,c){return function(){return b()[c]}})}function e(c,e,h){return"function"===
typeof c?d(c.bind(null,e,h)):a.a.Ca(c,b)}function f(a,b){return d(this.getBindings.bind(this,a,b))}function g(b,c,d){var e,h=a.f.firstChild(c),f=a.Q.instance,m=f.preprocessNode;if(m){for(;e=h;)h=a.f.nextSibling(e),m.call(f,e);h=a.f.firstChild(c)}for(;e=h;)h=a.f.nextSibling(e),k(b,e,d)}function k(b,c,d){var e=!0,h=1===c.nodeType;h&&a.f.kc(c);if(h&&d||a.Q.instance.nodeHasBindings(c))e=m(c,null,b,d).shouldBindDescendants;e&&!r[a.a.A(c)]&&g(b,c,!h)}function l(b){var c=[],d={},e=[];a.a.D(b,function Z(h){if(!d[h]){var f=
a.getBindingHandler(h);f&&(f.after&&(e.push(h),a.a.q(f.after,function(c){if(b[c]){if(-1!==a.a.o(e,c))throw Error("Cannot combine the following bindings, because they have a cyclic dependency: "+e.join(", "));Z(c)}}),e.length--),c.push({key:h,fc:f}));d[h]=!0}});return c}function m(b,d,e,h){var m=a.a.e.get(b,q);if(!d){if(m)throw Error("You cannot apply bindings multiple times to the same element.");a.a.e.set(b,q,!0)}!m&&h&&a.tc(b,e);var g;if(d&&"function"!==typeof d)g=d;else{var k=a.Q.instance,r=k.getBindingAccessors||
f,p=a.B(function(){(g=d?d(e,b):r.call(k,b,e))&&e.P&&e.P();return g},null,{i:b});g&&p.ba()||(p=null)}var u;if(g){var v=p?function(a){return function(){return c(p()[a])}}:function(a){return g[a]},s=function(){return a.a.Ca(p?p():g,c)};s.get=function(a){return g[a]&&c(v(a))};s.has=function(a){return a in g};h=l(g);a.a.q(h,function(c){var d=c.fc.init,h=c.fc.update,f=c.key;if(8===b.nodeType&&!a.f.Z[f])throw Error("The binding '"+f+"' cannot be used with virtual elements");try{"function"==typeof d&&a.l.w(function(){var a=
d(b,v(f),s,e.$data,e);if(a&&a.controlsDescendantBindings){if(u!==n)throw Error("Multiple bindings ("+u+" and "+f+") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");u=f}}),"function"==typeof h&&a.B(function(){h(b,v(f),s,e.$data,e)},null,{i:b})}catch(m){throw m.message='Unable to process binding "'+f+": "+g[f]+'"\nMessage: '+m.message,m;}})}return{shouldBindDescendants:u===n}}function h(b){return b&&b instanceof a.U?b:new a.U(b)}
a.d={};var r={script:!0,textarea:!0,template:!0};a.getBindingHandler=function(b){return a.d[b]};a.U=function(b,c,d,e){var h=this,f="function"==typeof b&&!a.H(b),m,g=a.B(function(){var m=f?b():b,l=a.a.c(m);c?(c.P&&c.P(),a.a.extend(h,c),g&&(h.P=g)):(h.$parents=[],h.$root=l,h.ko=a);h.$rawData=m;h.$data=l;d&&(h[d]=l);e&&e(h,c,l);return h.$data},null,{wa:function(){return m&&!a.a.Qb(m)},i:!0});g.ba()&&(h.P=g,g.equalityComparer=null,m=[],g.Ac=function(b){m.push(b);a.a.F.oa(b,function(b){a.a.La(m,b);m.length||
(g.k(),h.P=g=n)})})};a.U.prototype.createChildContext=function(b,c,d){return new a.U(b,this,c,function(a,b){a.$parentContext=b;a.$parent=b.$data;a.$parents=(b.$parents||[]).slice(0);a.$parents.unshift(a.$parent);d&&d(a)})};a.U.prototype.extend=function(b){return new a.U(this.P||this.$data,this,null,function(c,d){c.$rawData=d.$rawData;a.a.extend(c,"function"==typeof b?b():b)})};var q=a.a.e.I(),p=a.a.e.I();a.tc=function(b,c){if(2==arguments.length)a.a.e.set(b,p,c),c.P&&c.P.Ac(b);else return a.a.e.get(b,
p)};a.Ja=function(b,c,d){1===b.nodeType&&a.f.kc(b);return m(b,c,h(d),!0)};a.Dc=function(b,c,d){d=h(d);return a.Ja(b,e(c,d,b),d)};a.eb=function(a,b){1!==b.nodeType&&8!==b.nodeType||g(h(a),b,!0)};a.Rb=function(a,b){!v&&x.jQuery&&(v=x.jQuery);if(b&&1!==b.nodeType&&8!==b.nodeType)throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");b=b||x.document.body;k(h(a),b,!0)};a.kb=function(b){switch(b.nodeType){case 1:case 8:var c=a.tc(b);if(c)return c;
if(b.parentNode)return a.kb(b.parentNode)}return n};a.Jc=function(b){return(b=a.kb(b))?b.$data:n};a.b("bindingHandlers",a.d);a.b("applyBindings",a.Rb);a.b("applyBindingsToDescendants",a.eb);a.b("applyBindingAccessorsToNode",a.Ja);a.b("applyBindingsToNode",a.Dc);a.b("contextFor",a.kb);a.b("dataFor",a.Jc)})();(function(b){function c(c,e){var m=f.hasOwnProperty(c)?f[c]:b,h;m?m.X(e):(m=f[c]=new a.J,m.X(e),d(c,function(b,d){var e=!(!d||!d.synchronous);g[c]={definition:b,Zc:e};delete f[c];h||e?m.notifySubscribers(b):
a.Y.Wa(function(){m.notifySubscribers(b)})}),h=!0)}function d(a,b){e("getConfig",[a],function(c){c?e("loadComponent",[a,c],function(a){b(a,c)}):b(null,null)})}function e(c,d,f,h){h||(h=a.g.loaders.slice(0));var g=h.shift();if(g){var q=g[c];if(q){var p=!1;if(q.apply(g,d.concat(function(a){p?f(null):null!==a?f(a):e(c,d,f,h)}))!==b&&(p=!0,!g.suppressLoaderExceptions))throw Error("Component loaders must supply values by invoking the callback, not by returning values synchronously.");}else e(c,d,f,h)}else f(null)}
var f={},g={};a.g={get:function(d,e){var f=g.hasOwnProperty(d)?g[d]:b;f?f.Zc?a.l.w(function(){e(f.definition)}):a.Y.Wa(function(){e(f.definition)}):c(d,e)},Xb:function(a){delete g[a]},Jb:e};a.g.loaders=[];a.b("components",a.g);a.b("components.get",a.g.get);a.b("components.clearCachedDefinition",a.g.Xb)})();(function(){function b(b,c,d,e){function g(){0===--y&&e(k)}var k={},y=2,t=d.template;d=d.viewModel;t?f(c,t,function(c){a.g.Jb("loadTemplate",[b,c],function(a){k.template=a;g()})}):g();d?f(c,d,function(c){a.g.Jb("loadViewModel",
[b,c],function(a){k[l]=a;g()})}):g()}function c(a,b,d){if("function"===typeof b)d(function(a){return new b(a)});else if("function"===typeof b[l])d(b[l]);else if("instance"in b){var e=b.instance;d(function(){return e})}else"viewModel"in b?c(a,b.viewModel,d):a("Unknown viewModel value: "+b)}function d(b){switch(a.a.A(b)){case "script":return a.a.ma(b.text);case "textarea":return a.a.ma(b.value);case "template":if(e(b.content))return a.a.ua(b.content.childNodes)}return a.a.ua(b.childNodes)}function e(a){return x.DocumentFragment?
a instanceof DocumentFragment:a&&11===a.nodeType}function f(a,b,c){"string"===typeof b.require?O||x.require?(O||x.require)([b.require],c):a("Uses require, but no AMD loader is present"):c(b)}function g(a){return function(b){throw Error("Component '"+a+"': "+b);}}var k={};a.g.register=function(b,c){if(!c)throw Error("Invalid configuration for "+b);if(a.g.ub(b))throw Error("Component "+b+" is already registered");k[b]=c};a.g.ub=function(a){return k.hasOwnProperty(a)};a.g.od=function(b){delete k[b];
a.g.Xb(b)};a.g.Zb={getConfig:function(a,b){b(k.hasOwnProperty(a)?k[a]:null)},loadComponent:function(a,c,d){var e=g(a);f(e,c,function(c){b(a,e,c,d)})},loadTemplate:function(b,c,f){b=g(b);if("string"===typeof c)f(a.a.ma(c));else if(c instanceof Array)f(c);else if(e(c))f(a.a.V(c.childNodes));else if(c.element)if(c=c.element,x.HTMLElement?c instanceof HTMLElement:c&&c.tagName&&1===c.nodeType)f(d(c));else if("string"===typeof c){var l=u.getElementById(c);l?f(d(l)):b("Cannot find element with ID "+c)}else b("Unknown element type: "+
c);else b("Unknown template value: "+c)},loadViewModel:function(a,b,d){c(g(a),b,d)}};var l="createViewModel";a.b("components.register",a.g.register);a.b("components.isRegistered",a.g.ub);a.b("components.unregister",a.g.od);a.b("components.defaultLoader",a.g.Zb);a.g.loaders.push(a.g.Zb);a.g.Bc=k})();(function(){function b(b,e){var f=b.getAttribute("params");if(f){var f=c.parseBindingsString(f,e,b,{valueAccessors:!0,bindingParams:!0}),f=a.a.Ca(f,function(c){return a.m(c,null,{i:b})}),g=a.a.Ca(f,function(c){var e=
c.t();return c.ba()?a.m({read:function(){return a.a.c(c())},write:a.Ba(e)&&function(a){c()(a)},i:b}):e});g.hasOwnProperty("$raw")||(g.$raw=f);return g}return{$raw:{}}}a.g.getComponentNameForNode=function(b){var c=a.a.A(b);if(a.g.ub(c)&&(-1!=c.indexOf("-")||"[object HTMLUnknownElement]"==""+b||8>=a.a.C&&b.tagName===c))return c};a.g.Ob=function(c,e,f,g){if(1===e.nodeType){var k=a.g.getComponentNameForNode(e);if(k){c=c||{};if(c.component)throw Error('Cannot use the "component" binding on a custom element matching a component');
var l={name:k,params:b(e,f)};c.component=g?function(){return l}:l}}return c};var c=new a.Q;9>a.a.C&&(a.g.register=function(a){return function(b){u.createElement(b);return a.apply(this,arguments)}}(a.g.register),u.createDocumentFragment=function(b){return function(){var c=b(),f=a.g.Bc,g;for(g in f)f.hasOwnProperty(g)&&c.createElement(g);return c}}(u.createDocumentFragment))})();(function(b){function c(b,c,d){c=c.template;if(!c)throw Error("Component '"+b+"' has no template");b=a.a.ua(c);a.f.da(d,b)}
function d(a,b,c,d){var e=a.createViewModel;return e?e.call(a,d,{element:b,templateNodes:c}):d}var e=0;a.d.component={init:function(f,g,k,l,m){function h(){var a=r&&r.dispose;"function"===typeof a&&a.call(r);q=r=null}var r,q,p=a.a.V(a.f.childNodes(f));a.a.F.oa(f,h);a.m(function(){var l=a.a.c(g()),k,t;"string"===typeof l?k=l:(k=a.a.c(l.name),t=a.a.c(l.params));if(!k)throw Error("No component name specified");var n=q=++e;a.g.get(k,function(e){if(q===n){h();if(!e)throw Error("Unknown component '"+k+
"'");c(k,e,f);var g=d(e,f,p,t);e=m.createChildContext(g,b,function(a){a.$component=g;a.$componentTemplateNodes=p});r=g;a.eb(e,f)}})},null,{i:f});return{controlsDescendantBindings:!0}}};a.f.Z.component=!0})();var S={"class":"className","for":"htmlFor"};a.d.attr={update:function(b,c){var d=a.a.c(c())||{};a.a.D(d,function(c,d){d=a.a.c(d);var g=!1===d||null===d||d===n;g&&b.removeAttribute(c);8>=a.a.C&&c in S?(c=S[c],g?b.removeAttribute(c):b[c]=d):g||b.setAttribute(c,d.toString());"name"===c&&a.a.rc(b,
g?"":d.toString())})}};(function(){a.d.checked={after:["value","attr"],init:function(b,c,d){function e(){var e=b.checked,f=p?g():e;if(!a.va.Sa()&&(!l||e)){var m=a.l.w(c);if(h){var k=r?m.t():m;q!==f?(e&&(a.a.pa(k,f,!0),a.a.pa(k,q,!1)),q=f):a.a.pa(k,f,e);r&&a.Ba(m)&&m(k)}else a.h.Ea(m,d,"checked",f,!0)}}function f(){var d=a.a.c(c());b.checked=h?0<=a.a.o(d,g()):k?d:g()===d}var g=a.nc(function(){return d.has("checkedValue")?a.a.c(d.get("checkedValue")):d.has("value")?a.a.c(d.get("value")):b.value}),k=
"checkbox"==b.type,l="radio"==b.type;if(k||l){var m=c(),h=k&&a.a.c(m)instanceof Array,r=!(h&&m.push&&m.splice),q=h?g():n,p=l||h;l&&!b.name&&a.d.uniqueName.init(b,function(){return!0});a.m(e,null,{i:b});a.a.p(b,"click",e);a.m(f,null,{i:b});m=n}}};a.h.ea.checked=!0;a.d.checkedValue={update:function(b,c){b.value=a.a.c(c())}}})();a.d.css={update:function(b,c){var d=a.a.c(c());null!==d&&"object"==typeof d?a.a.D(d,function(c,d){d=a.a.c(d);a.a.bb(b,c,d)}):(d=a.a.$a(String(d||"")),a.a.bb(b,b.__ko__cssValue,
!1),b.__ko__cssValue=d,a.a.bb(b,d,!0))}};a.d.enable={update:function(b,c){var d=a.a.c(c());d&&b.disabled?b.removeAttribute("disabled"):d||b.disabled||(b.disabled=!0)}};a.d.disable={update:function(b,c){a.d.enable.update(b,function(){return!a.a.c(c())})}};a.d.event={init:function(b,c,d,e,f){var g=c()||{};a.a.D(g,function(g){"string"==typeof g&&a.a.p(b,g,function(b){var m,h=c()[g];if(h){try{var r=a.a.V(arguments);e=f.$data;r.unshift(e);m=h.apply(e,r)}finally{!0!==m&&(b.preventDefault?b.preventDefault():
b.returnValue=!1)}!1===d.get(g+"Bubble")&&(b.cancelBubble=!0,b.stopPropagation&&b.stopPropagation())}})})}};a.d.foreach={ic:function(b){return function(){var c=b(),d=a.a.zb(c);if(!d||"number"==typeof d.length)return{foreach:c,templateEngine:a.W.sb};a.a.c(c);return{foreach:d.data,as:d.as,includeDestroyed:d.includeDestroyed,afterAdd:d.afterAdd,beforeRemove:d.beforeRemove,afterRender:d.afterRender,beforeMove:d.beforeMove,afterMove:d.afterMove,templateEngine:a.W.sb}}},init:function(b,c){return a.d.template.init(b,
a.d.foreach.ic(c))},update:function(b,c,d,e,f){return a.d.template.update(b,a.d.foreach.ic(c),d,e,f)}};a.h.ta.foreach=!1;a.f.Z.foreach=!0;a.d.hasfocus={init:function(b,c,d){function e(e){b.__ko_hasfocusUpdating=!0;var f=b.ownerDocument;if("activeElement"in f){var g;try{g=f.activeElement}catch(h){g=f.body}e=g===b}f=c();a.h.Ea(f,d,"hasfocus",e,!0);b.__ko_hasfocusLastValue=e;b.__ko_hasfocusUpdating=!1}var f=e.bind(null,!0),g=e.bind(null,!1);a.a.p(b,"focus",f);a.a.p(b,"focusin",f);a.a.p(b,"blur",g);a.a.p(b,
"focusout",g)},update:function(b,c){var d=!!a.a.c(c());b.__ko_hasfocusUpdating||b.__ko_hasfocusLastValue===d||(d?b.focus():b.blur(),!d&&b.__ko_hasfocusLastValue&&b.ownerDocument.body.focus(),a.l.w(a.a.Da,null,[b,d?"focusin":"focusout"]))}};a.h.ea.hasfocus=!0;a.d.hasFocus=a.d.hasfocus;a.h.ea.hasFocus=!0;a.d.html={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Cb(b,c())}};K("if");K("ifnot",!1,!0);K("with",!0,!1,function(a,c){return a.createChildContext(c)});var L={};
a.d.options={init:function(b){if("select"!==a.a.A(b))throw Error("options binding applies only to SELECT elements");for(;0<b.length;)b.remove(0);return{controlsDescendantBindings:!0}},update:function(b,c,d){function e(){return a.a.Ka(b.options,function(a){return a.selected})}function f(a,b,c){var d=typeof b;return"function"==d?b(a):"string"==d?a[b]:c}function g(c,e){if(A&&h)a.j.ha(b,a.a.c(d.get("value")),!0);else if(p.length){var f=0<=a.a.o(p,a.j.u(e[0]));a.a.sc(e[0],f);A&&!f&&a.l.w(a.a.Da,null,[b,
"change"])}}var k=b.multiple,l=0!=b.length&&k?b.scrollTop:null,m=a.a.c(c()),h=d.get("valueAllowUnset")&&d.has("value"),r=d.get("optionsIncludeDestroyed");c={};var q,p=[];h||(k?p=a.a.fb(e(),a.j.u):0<=b.selectedIndex&&p.push(a.j.u(b.options[b.selectedIndex])));m&&("undefined"==typeof m.length&&(m=[m]),q=a.a.Ka(m,function(b){return r||b===n||null===b||!a.a.c(b._destroy)}),d.has("optionsCaption")&&(m=a.a.c(d.get("optionsCaption")),null!==m&&m!==n&&q.unshift(L)));var A=!1;c.beforeRemove=function(a){b.removeChild(a)};
m=g;d.has("optionsAfterRender")&&"function"==typeof d.get("optionsAfterRender")&&(m=function(b,c){g(0,c);a.l.w(d.get("optionsAfterRender"),null,[c[0],b!==L?b:n])});a.a.Bb(b,q,function(c,e,g){g.length&&(p=!h&&g[0].selected?[a.j.u(g[0])]:[],A=!0);e=b.ownerDocument.createElement("option");c===L?(a.a.Za(e,d.get("optionsCaption")),a.j.ha(e,n)):(g=f(c,d.get("optionsValue"),c),a.j.ha(e,a.a.c(g)),c=f(c,d.get("optionsText"),g),a.a.Za(e,c));return[e]},c,m);a.l.w(function(){h?a.j.ha(b,a.a.c(d.get("value")),
!0):(k?p.length&&e().length<p.length:p.length&&0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex])!==p[0]:p.length||0<=b.selectedIndex)&&a.a.Da(b,"change")});a.a.Nc(b);l&&20<Math.abs(l-b.scrollTop)&&(b.scrollTop=l)}};a.d.options.xb=a.a.e.I();a.d.selectedOptions={after:["options","foreach"],init:function(b,c,d){a.a.p(b,"change",function(){var e=c(),f=[];a.a.q(b.getElementsByTagName("option"),function(b){b.selected&&f.push(a.j.u(b))});a.h.Ea(e,d,"selectedOptions",f)})},update:function(b,c){if("select"!=
a.a.A(b))throw Error("values binding applies only to SELECT elements");var d=a.a.c(c()),e=b.scrollTop;d&&"number"==typeof d.length&&a.a.q(b.getElementsByTagName("option"),function(b){var c=0<=a.a.o(d,a.j.u(b));b.selected!=c&&a.a.sc(b,c)});b.scrollTop=e}};a.h.ea.selectedOptions=!0;a.d.style={update:function(b,c){var d=a.a.c(c()||{});a.a.D(d,function(c,d){d=a.a.c(d);if(null===d||d===n||!1===d)d="";b.style[c]=d})}};a.d.submit={init:function(b,c,d,e,f){if("function"!=typeof c())throw Error("The value for a submit binding must be a function");
a.a.p(b,"submit",function(a){var d,e=c();try{d=e.call(f.$data,b)}finally{!0!==d&&(a.preventDefault?a.preventDefault():a.returnValue=!1)}})}};a.d.text={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Za(b,c())}};a.f.Z.text=!0;(function(){if(x&&x.navigator)var b=function(a){if(a)return parseFloat(a[1])},c=x.opera&&x.opera.version&&parseInt(x.opera.version()),d=x.navigator.userAgent,e=b(d.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),f=b(d.match(/Firefox\/([^ ]*)/));
if(10>a.a.C)var g=a.a.e.I(),k=a.a.e.I(),l=function(b){var c=this.activeElement;(c=c&&a.a.e.get(c,k))&&c(b)},m=function(b,c){var d=b.ownerDocument;a.a.e.get(d,g)||(a.a.e.set(d,g,!0),a.a.p(d,"selectionchange",l));a.a.e.set(b,k,c)};a.d.textInput={init:function(b,d,g){function l(c,d){a.a.p(b,c,d)}function k(){var c=a.a.c(d());if(null===c||c===n)c="";v!==n&&c===v?a.a.setTimeout(k,4):b.value!==c&&(u=c,b.value=c)}function y(){s||(v=b.value,s=a.a.setTimeout(t,4))}function t(){clearTimeout(s);v=s=n;var c=
b.value;u!==c&&(u=c,a.h.Ea(d(),g,"textInput",c))}var u=b.value,s,v,x=9==a.a.C?y:t;10>a.a.C?(l("propertychange",function(a){"value"===a.propertyName&&x(a)}),8==a.a.C&&(l("keyup",t),l("keydown",t)),8<=a.a.C&&(m(b,x),l("dragend",y))):(l("input",t),5>e&&"textarea"===a.a.A(b)?(l("keydown",y),l("paste",y),l("cut",y)):11>c?l("keydown",y):4>f&&(l("DOMAutoComplete",t),l("dragdrop",t),l("drop",t)));l("change",t);a.m(k,null,{i:b})}};a.h.ea.textInput=!0;a.d.textinput={preprocess:function(a,b,c){c("textInput",
a)}}})();a.d.uniqueName={init:function(b,c){if(c()){var d="ko_unique_"+ ++a.d.uniqueName.Ic;a.a.rc(b,d)}}};a.d.uniqueName.Ic=0;a.d.value={after:["options","foreach"],init:function(b,c,d){if("input"!=b.tagName.toLowerCase()||"checkbox"!=b.type&&"radio"!=b.type){var e=["change"],f=d.get("valueUpdate"),g=!1,k=null;f&&("string"==typeof f&&(f=[f]),a.a.ra(e,f),e=a.a.Tb(e));var l=function(){k=null;g=!1;var e=c(),f=a.j.u(b);a.h.Ea(e,d,"value",f)};!a.a.C||"input"!=b.tagName.toLowerCase()||"text"!=b.type||
"off"==b.autocomplete||b.form&&"off"==b.form.autocomplete||-1!=a.a.o(e,"propertychange")||(a.a.p(b,"propertychange",function(){g=!0}),a.a.p(b,"focus",function(){g=!1}),a.a.p(b,"blur",function(){g&&l()}));a.a.q(e,function(c){var d=l;a.a.nd(c,"after")&&(d=function(){k=a.j.u(b);a.a.setTimeout(l,0)},c=c.substring(5));a.a.p(b,c,d)});var m=function(){var e=a.a.c(c()),f=a.j.u(b);if(null!==k&&e===k)a.a.setTimeout(m,0);else if(e!==f)if("select"===a.a.A(b)){var g=d.get("valueAllowUnset"),f=function(){a.j.ha(b,
e,g)};f();g||e===a.j.u(b)?a.a.setTimeout(f,0):a.l.w(a.a.Da,null,[b,"change"])}else a.j.ha(b,e)};a.m(m,null,{i:b})}else a.Ja(b,{checkedValue:c})},update:function(){}};a.h.ea.value=!0;a.d.visible={update:function(b,c){var d=a.a.c(c()),e="none"!=b.style.display;d&&!e?b.style.display="":!d&&e&&(b.style.display="none")}};(function(b){a.d[b]={init:function(c,d,e,f,g){return a.d.event.init.call(this,c,function(){var a={};a[b]=d();return a},e,f,g)}}})("click");a.O=function(){};a.O.prototype.renderTemplateSource=
function(){throw Error("Override renderTemplateSource");};a.O.prototype.createJavaScriptEvaluatorBlock=function(){throw Error("Override createJavaScriptEvaluatorBlock");};a.O.prototype.makeTemplateSource=function(b,c){if("string"==typeof b){c=c||u;var d=c.getElementById(b);if(!d)throw Error("Cannot find template with ID "+b);return new a.v.n(d)}if(1==b.nodeType||8==b.nodeType)return new a.v.qa(b);throw Error("Unknown template type: "+b);};a.O.prototype.renderTemplate=function(a,c,d,e){a=this.makeTemplateSource(a,
e);return this.renderTemplateSource(a,c,d,e)};a.O.prototype.isTemplateRewritten=function(a,c){return!1===this.allowTemplateRewriting?!0:this.makeTemplateSource(a,c).data("isRewritten")};a.O.prototype.rewriteTemplate=function(a,c,d){a=this.makeTemplateSource(a,d);c=c(a.text());a.text(c);a.data("isRewritten",!0)};a.b("templateEngine",a.O);a.Gb=function(){function b(b,c,d,k){b=a.h.yb(b);for(var l=a.h.ta,m=0;m<b.length;m++){var h=b[m].key;if(l.hasOwnProperty(h)){var r=l[h];if("function"===typeof r){if(h=
r(b[m].value))throw Error(h);}else if(!r)throw Error("This template engine does not support the '"+h+"' binding within its templates");}}d="ko.__tr_ambtns(function($context,$element){return(function(){return{ "+a.h.Ua(b,{valueAccessors:!0})+" } })()},'"+d.toLowerCase()+"')";return k.createJavaScriptEvaluatorBlock(d)+c}var c=/(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'|[^>]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi,d=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;return{Oc:function(b,
c,d){c.isTemplateRewritten(b,d)||c.rewriteTemplate(b,function(b){return a.Gb.dd(b,c)},d)},dd:function(a,f){return a.replace(c,function(a,c,d,e,h){return b(h,c,d,f)}).replace(d,function(a,c){return b(c,"\x3c!-- ko --\x3e","#comment",f)})},Ec:function(b,c){return a.M.wb(function(d,k){var l=d.nextSibling;l&&l.nodeName.toLowerCase()===c&&a.Ja(l,b,k)})}}}();a.b("__tr_ambtns",a.Gb.Ec);(function(){a.v={};a.v.n=function(b){if(this.n=b){var c=a.a.A(b);this.ab="script"===c?1:"textarea"===c?2:"template"==c&&
b.content&&11===b.content.nodeType?3:4}};a.v.n.prototype.text=function(){var b=1===this.ab?"text":2===this.ab?"value":"innerHTML";if(0==arguments.length)return this.n[b];var c=arguments[0];"innerHTML"===b?a.a.Cb(this.n,c):this.n[b]=c};var b=a.a.e.I()+"_";a.v.n.prototype.data=function(c){if(1===arguments.length)return a.a.e.get(this.n,b+c);a.a.e.set(this.n,b+c,arguments[1])};var c=a.a.e.I();a.v.n.prototype.nodes=function(){var b=this.n;if(0==arguments.length)return(a.a.e.get(b,c)||{}).jb||(3===this.ab?
b.content:4===this.ab?b:n);a.a.e.set(b,c,{jb:arguments[0]})};a.v.qa=function(a){this.n=a};a.v.qa.prototype=new a.v.n;a.v.qa.prototype.text=function(){if(0==arguments.length){var b=a.a.e.get(this.n,c)||{};b.Hb===n&&b.jb&&(b.Hb=b.jb.innerHTML);return b.Hb}a.a.e.set(this.n,c,{Hb:arguments[0]})};a.b("templateSources",a.v);a.b("templateSources.domElement",a.v.n);a.b("templateSources.anonymousTemplate",a.v.qa)})();(function(){function b(b,c,d){var e;for(c=a.f.nextSibling(c);b&&(e=b)!==c;)b=a.f.nextSibling(e),
d(e,b)}function c(c,d){if(c.length){var e=c[0],f=c[c.length-1],g=e.parentNode,k=a.Q.instance,n=k.preprocessNode;if(n){b(e,f,function(a,b){var c=a.previousSibling,d=n.call(k,a);d&&(a===e&&(e=d[0]||b),a===f&&(f=d[d.length-1]||c))});c.length=0;if(!e)return;e===f?c.push(e):(c.push(e,f),a.a.za(c,g))}b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.Rb(d,b)});b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.M.yc(b,[d])});a.a.za(c,g)}}function d(a){return a.nodeType?a:0<a.length?a[0]:null}function e(b,
e,f,k,q){q=q||{};var p=(b&&d(b)||f||{}).ownerDocument,n=q.templateEngine||g;a.Gb.Oc(f,n,p);f=n.renderTemplate(f,k,q,p);if("number"!=typeof f.length||0<f.length&&"number"!=typeof f[0].nodeType)throw Error("Template engine must return an array of DOM nodes");p=!1;switch(e){case "replaceChildren":a.f.da(b,f);p=!0;break;case "replaceNode":a.a.qc(b,f);p=!0;break;case "ignoreTargetNode":break;default:throw Error("Unknown renderMode: "+e);}p&&(c(f,k),q.afterRender&&a.l.w(q.afterRender,null,[f,k.$data]));
return f}function f(b,c,d){return a.H(b)?b():"function"===typeof b?b(c,d):b}var g;a.Db=function(b){if(b!=n&&!(b instanceof a.O))throw Error("templateEngine must inherit from ko.templateEngine");g=b};a.Ab=function(b,c,h,k,q){h=h||{};if((h.templateEngine||g)==n)throw Error("Set a template engine before calling renderTemplate");q=q||"replaceChildren";if(k){var p=d(k);return a.B(function(){var g=c&&c instanceof a.U?c:new a.U(a.a.c(c)),n=f(b,g.$data,g),g=e(k,q,n,g,h);"replaceNode"==q&&(k=g,p=d(k))},null,
{wa:function(){return!p||!a.a.nb(p)},i:p&&"replaceNode"==q?p.parentNode:p})}return a.M.wb(function(d){a.Ab(b,c,h,d,"replaceNode")})};a.kd=function(b,d,g,k,q){function p(a,b){c(b,s);g.afterRender&&g.afterRender(b,a);s=null}function u(a,c){s=q.createChildContext(a,g.as,function(a){a.$index=c});var d=f(b,a,s);return e(null,"ignoreTargetNode",d,s,g)}var s;return a.B(function(){var b=a.a.c(d)||[];"undefined"==typeof b.length&&(b=[b]);b=a.a.Ka(b,function(b){return g.includeDestroyed||b===n||null===b||!a.a.c(b._destroy)});
a.l.w(a.a.Bb,null,[k,b,u,g,p])},null,{i:k})};var k=a.a.e.I();a.d.template={init:function(b,c){var d=a.a.c(c());if("string"==typeof d||d.name)a.f.xa(b);else{if("nodes"in d){if(d=d.nodes||[],a.H(d))throw Error('The "nodes" option must be a plain, non-observable array.');}else d=a.f.childNodes(b);d=a.a.jc(d);(new a.v.qa(b)).nodes(d)}return{controlsDescendantBindings:!0}},update:function(b,c,d,e,f){var g=c(),s;c=a.a.c(g);d=!0;e=null;"string"==typeof c?c={}:(g=c.name,"if"in c&&(d=a.a.c(c["if"])),d&&"ifnot"in
c&&(d=!a.a.c(c.ifnot)),s=a.a.c(c.data));"foreach"in c?e=a.kd(g||b,d&&c.foreach||[],c,b,f):d?(f="data"in c?f.createChildContext(s,c.as):f,e=a.Ab(g||b,f,c,b)):a.f.xa(b);f=e;(s=a.a.e.get(b,k))&&"function"==typeof s.k&&s.k();a.a.e.set(b,k,f&&f.ba()?f:n)}};a.h.ta.template=function(b){b=a.h.yb(b);return 1==b.length&&b[0].unknown||a.h.ad(b,"name")?null:"This template engine does not support anonymous templates nested within its templates"};a.f.Z.template=!0})();a.b("setTemplateEngine",a.Db);a.b("renderTemplate",
a.Ab);a.a.dc=function(a,c,d){if(a.length&&c.length){var e,f,g,k,l;for(e=f=0;(!d||e<d)&&(k=a[f]);++f){for(g=0;l=c[g];++g)if(k.value===l.value){k.moved=l.index;l.moved=k.index;c.splice(g,1);e=g=0;break}e+=g}}};a.a.ib=function(){function b(b,d,e,f,g){var k=Math.min,l=Math.max,m=[],h,n=b.length,q,p=d.length,s=p-n||1,u=n+p+1,t,v,x;for(h=0;h<=n;h++)for(v=t,m.push(t=[]),x=k(p,h+s),q=l(0,h-1);q<=x;q++)t[q]=q?h?b[h-1]===d[q-1]?v[q-1]:k(v[q]||u,t[q-1]||u)+1:q+1:h+1;k=[];l=[];s=[];h=n;for(q=p;h||q;)p=m[h][q]-
1,q&&p===m[h][q-1]?l.push(k[k.length]={status:e,value:d[--q],index:q}):h&&p===m[h-1][q]?s.push(k[k.length]={status:f,value:b[--h],index:h}):(--q,--h,g.sparse||k.push({status:"retained",value:d[q]}));a.a.dc(s,l,!g.dontLimitMoves&&10*n);return k.reverse()}return function(a,d,e){e="boolean"===typeof e?{dontLimitMoves:e}:e||{};a=a||[];d=d||[];return a.length<d.length?b(a,d,"added","deleted",e):b(d,a,"deleted","added",e)}}();a.b("utils.compareArrays",a.a.ib);(function(){function b(b,c,d,k,l){var m=[],
h=a.B(function(){var h=c(d,l,a.a.za(m,b))||[];0<m.length&&(a.a.qc(m,h),k&&a.l.w(k,null,[d,h,l]));m.length=0;a.a.ra(m,h)},null,{i:b,wa:function(){return!a.a.Qb(m)}});return{ca:m,B:h.ba()?h:n}}var c=a.a.e.I(),d=a.a.e.I();a.a.Bb=function(e,f,g,k,l){function m(b,c){w=q[c];v!==c&&(D[b]=w);w.qb(v++);a.a.za(w.ca,e);u.push(w);z.push(w)}function h(b,c){if(b)for(var d=0,e=c.length;d<e;d++)c[d]&&a.a.q(c[d].ca,function(a){b(a,d,c[d].ja)})}f=f||[];k=k||{};var r=a.a.e.get(e,c)===n,q=a.a.e.get(e,c)||[],p=a.a.fb(q,
function(a){return a.ja}),s=a.a.ib(p,f,k.dontLimitMoves),u=[],t=0,v=0,x=[],z=[];f=[];for(var D=[],p=[],w,C=0,B,E;B=s[C];C++)switch(E=B.moved,B.status){case "deleted":E===n&&(w=q[t],w.B&&(w.B.k(),w.B=n),a.a.za(w.ca,e).length&&(k.beforeRemove&&(u.push(w),z.push(w),w.ja===d?w=null:f[C]=w),w&&x.push.apply(x,w.ca)));t++;break;case "retained":m(C,t++);break;case "added":E!==n?m(C,E):(w={ja:B.value,qb:a.N(v++)},u.push(w),z.push(w),r||(p[C]=w))}a.a.e.set(e,c,u);h(k.beforeMove,D);a.a.q(x,k.beforeRemove?a.$:
a.removeNode);for(var C=0,r=a.f.firstChild(e),F;w=z[C];C++){w.ca||a.a.extend(w,b(e,g,w.ja,l,w.qb));for(t=0;s=w.ca[t];r=s.nextSibling,F=s,t++)s!==r&&a.f.gc(e,s,F);!w.Wc&&l&&(l(w.ja,w.ca,w.qb),w.Wc=!0)}h(k.beforeRemove,f);for(C=0;C<f.length;++C)f[C]&&(f[C].ja=d);h(k.afterMove,D);h(k.afterAdd,p)}})();a.b("utils.setDomNodeChildrenFromArrayMapping",a.a.Bb);a.W=function(){this.allowTemplateRewriting=!1};a.W.prototype=new a.O;a.W.prototype.renderTemplateSource=function(b,c,d,e){if(c=(9>a.a.C?0:b.nodes)?
b.nodes():null)return a.a.V(c.cloneNode(!0).childNodes);b=b.text();return a.a.ma(b,e)};a.W.sb=new a.W;a.Db(a.W.sb);a.b("nativeTemplateEngine",a.W);(function(){a.vb=function(){var a=this.$c=function(){if(!v||!v.tmpl)return 0;try{if(0<=v.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(a){}return 1}();this.renderTemplateSource=function(b,e,f,g){g=g||u;f=f||{};if(2>a)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var k=b.data("precompiled");
k||(k=b.text()||"",k=v.template(null,"{{ko_with $item.koBindingContext}}"+k+"{{/ko_with}}"),b.data("precompiled",k));b=[e.$data];e=v.extend({koBindingContext:e},f.templateOptions);e=v.tmpl(k,b,e);e.appendTo(g.createElement("div"));v.fragments={};return e};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+a+" })()) }}"};this.addTemplate=function(a,b){u.write("<script type='text/html' id='"+a+"'>"+b+"\x3c/script>")};0<a&&(v.tmpl.tag.ko_code={open:"__.push($1 || '');"},
v.tmpl.tag.ko_with={open:"with($1) {",close:"} "})};a.vb.prototype=new a.O;var b=new a.vb;0<b.$c&&a.Db(b);a.b("jqueryTmplTemplateEngine",a.vb)})()})})();})();
(function() {(function(n){var x=this||(0,eval)("this"),t=x.document,M=x.navigator,u=x.jQuery,H=x.JSON;(function(n){"function"===typeof define&&define.amd?define(["exports","require"],n):"object"===typeof exports&&"object"===typeof module?n(module.exports||exports):n(x.ko={})})(function(N,O){function J(a,c){return null===a||typeof a in R?a===c:!1}function S(b,c){var d;return function(){d||(d=a.a.setTimeout(function(){d=n;b()},c))}}function T(b,c){var d;return function(){clearTimeout(d);d=a.a.setTimeout(b,c)}}function U(a,
c){c&&c!==E?"beforeChange"===c?this.Ob(a):this.Ja(a,c):this.Pb(a)}function V(a,c){null!==c&&c.k&&c.k()}function W(a,c){var d=this.Mc,e=d[s];e.T||(this.ob&&this.Oa[c]?(d.Sb(c,a,this.Oa[c]),this.Oa[c]=null,--this.ob):e.s[c]||d.Sb(c,a,e.t?{$:a}:d.yc(a)),a.Ha&&a.Hc())}function K(b,c,d,e){a.d[b]={init:function(b,g,h,l,m){var k,r;a.m(function(){var q=g(),p=a.a.c(q),p=!d!==!p,A=!r;if(A||c||p!==k)A&&a.xa.Ca()&&(r=a.a.wa(a.f.childNodes(b),!0)),p?(A||a.f.fa(b,a.a.wa(r)),a.hb(e?e(m,q):m,b)):a.f.za(b),k=p},null,
{i:b});return{controlsDescendantBindings:!0}}};a.h.va[b]=!1;a.f.aa[b]=!0}var a="undefined"!==typeof N?N:{};a.b=function(b,c){for(var d=b.split("."),e=a,f=0;f<d.length-1;f++)e=e[d[f]];e[d[d.length-1]]=c};a.H=function(a,c,d){a[c]=d};a.version="3.4.2";a.b("version",a.version);a.options={deferUpdates:!1,useOnlyNativeEvents:!1};a.a=function(){function b(a,b){for(var c in a)a.hasOwnProperty(c)&&b(c,a[c])}function c(a,b){if(b)for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a}function d(a,b){a.__proto__=
b;return a}function e(b,c,d,e){var m=b[c].match(r)||[];a.a.r(d.match(r),function(b){a.a.ra(m,b,e)});b[c]=m.join(" ")}var f={__proto__:[]}instanceof Array,g="function"===typeof Symbol,h={},l={};h[M&&/Firefox\/2/i.test(M.userAgent)?"KeyboardEvent":"UIEvents"]=["keyup","keydown","keypress"];h.MouseEvents="click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave".split(" ");b(h,function(a,b){if(b.length)for(var c=0,d=b.length;c<d;c++)l[b[c]]=a});var m={propertychange:!0},k=
t&&function(){for(var a=3,b=t.createElement("div"),c=b.getElementsByTagName("i");b.innerHTML="\x3c!--[if gt IE "+ ++a+"]><i></i><![endif]--\x3e",c[0];);return 4<a?a:n}(),r=/\S+/g;return{gc:["authenticity_token",/^__RequestVerificationToken(_.*)?$/],r:function(a,b){for(var c=0,d=a.length;c<d;c++)b(a[c],c)},o:function(a,b){if("function"==typeof Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b);for(var c=0,d=a.length;c<d;c++)if(a[c]===b)return c;return-1},Vb:function(a,b,c){for(var d=
0,e=a.length;d<e;d++)if(b.call(c,a[d],d))return a[d];return null},Na:function(b,c){var d=a.a.o(b,c);0<d?b.splice(d,1):0===d&&b.shift()},Wb:function(b){b=b||[];for(var c=[],d=0,e=b.length;d<e;d++)0>a.a.o(c,b[d])&&c.push(b[d]);return c},ib:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)c.push(b(a[d],d));return c},Ma:function(a,b){a=a||[];for(var c=[],d=0,e=a.length;d<e;d++)b(a[d],d)&&c.push(a[d]);return c},ta:function(a,b){if(b instanceof Array)a.push.apply(a,b);else for(var c=0,d=b.length;c<
d;c++)a.push(b[c]);return a},ra:function(b,c,d){var e=a.a.o(a.a.Bb(b),c);0>e?d&&b.push(c):d||b.splice(e,1)},la:f,extend:c,$a:d,ab:f?d:c,D:b,Ea:function(a,b){if(!a)return a;var c={},d;for(d in a)a.hasOwnProperty(d)&&(c[d]=b(a[d],d,a));return c},rb:function(b){for(;b.firstChild;)a.removeNode(b.firstChild)},nc:function(b){b=a.a.W(b);for(var c=(b[0]&&b[0].ownerDocument||t).createElement("div"),d=0,e=b.length;d<e;d++)c.appendChild(a.ba(b[d]));return c},wa:function(b,c){for(var d=0,e=b.length,m=[];d<e;d++){var k=
b[d].cloneNode(!0);m.push(c?a.ba(k):k)}return m},fa:function(b,c){a.a.rb(b);if(c)for(var d=0,e=c.length;d<e;d++)b.appendChild(c[d])},uc:function(b,c){var d=b.nodeType?[b]:b;if(0<d.length){for(var e=d[0],m=e.parentNode,k=0,f=c.length;k<f;k++)m.insertBefore(c[k],e);k=0;for(f=d.length;k<f;k++)a.removeNode(d[k])}},Ba:function(a,b){if(a.length){for(b=8===b.nodeType&&b.parentNode||b;a.length&&a[0].parentNode!==b;)a.splice(0,1);for(;1<a.length&&a[a.length-1].parentNode!==b;)a.length--;if(1<a.length){var c=
a[0],d=a[a.length-1];for(a.length=0;c!==d;)a.push(c),c=c.nextSibling;a.push(d)}}return a},wc:function(a,b){7>k?a.setAttribute("selected",b):a.selected=b},cb:function(a){return null===a||a===n?"":a.trim?a.trim():a.toString().replace(/^[\s\xa0]+|[\s\xa0]+$/g,"")},sd:function(a,b){a=a||"";return b.length>a.length?!1:a.substring(0,b.length)===b},Rc:function(a,b){if(a===b)return!0;if(11===a.nodeType)return!1;if(b.contains)return b.contains(3===a.nodeType?a.parentNode:a);if(b.compareDocumentPosition)return 16==
(b.compareDocumentPosition(a)&16);for(;a&&a!=b;)a=a.parentNode;return!!a},qb:function(b){return a.a.Rc(b,b.ownerDocument.documentElement)},Tb:function(b){return!!a.a.Vb(b,a.a.qb)},A:function(a){return a&&a.tagName&&a.tagName.toLowerCase()},Zb:function(b){return a.onError?function(){try{return b.apply(this,arguments)}catch(c){throw a.onError&&a.onError(c),c;}}:b},setTimeout:function(b,c){return setTimeout(a.a.Zb(b),c)},dc:function(b){setTimeout(function(){a.onError&&a.onError(b);throw b;},0)},q:function(b,
c,d){var e=a.a.Zb(d);d=k&&m[c];if(a.options.useOnlyNativeEvents||d||!u)if(d||"function"!=typeof b.addEventListener)if("undefined"!=typeof b.attachEvent){var f=function(a){e.call(b,a)},l="on"+c;b.attachEvent(l,f);a.a.G.qa(b,function(){b.detachEvent(l,f)})}else throw Error("Browser doesn't support addEventListener or attachEvent");else b.addEventListener(c,e,!1);else u(b).bind(c,e)},Fa:function(b,c){if(!b||!b.nodeType)throw Error("element must be a DOM node when calling triggerEvent");var d;"input"===
a.a.A(b)&&b.type&&"click"==c.toLowerCase()?(d=b.type,d="checkbox"==d||"radio"==d):d=!1;if(a.options.useOnlyNativeEvents||!u||d)if("function"==typeof t.createEvent)if("function"==typeof b.dispatchEvent)d=t.createEvent(l[c]||"HTMLEvents"),d.initEvent(c,!0,!0,x,0,0,0,0,0,!1,!1,!1,!1,0,b),b.dispatchEvent(d);else throw Error("The supplied element doesn't support dispatchEvent");else if(d&&b.click)b.click();else if("undefined"!=typeof b.fireEvent)b.fireEvent("on"+c);else throw Error("Browser doesn't support triggering events");
else u(b).trigger(c)},c:function(b){return a.I(b)?b():b},Bb:function(b){return a.I(b)?b.p():b},fb:function(b,c,d){var k;c&&("object"===typeof b.classList?(k=b.classList[d?"add":"remove"],a.a.r(c.match(r),function(a){k.call(b.classList,a)})):"string"===typeof b.className.baseVal?e(b.className,"baseVal",c,d):e(b,"className",c,d))},bb:function(b,c){var d=a.a.c(c);if(null===d||d===n)d="";var e=a.f.firstChild(b);!e||3!=e.nodeType||a.f.nextSibling(e)?a.f.fa(b,[b.ownerDocument.createTextNode(d)]):e.data=
d;a.a.Wc(b)},vc:function(a,b){a.name=b;if(7>=k)try{a.mergeAttributes(t.createElement("<input name='"+a.name+"'/>"),!1)}catch(c){}},Wc:function(a){9<=k&&(a=1==a.nodeType?a:a.parentNode,a.style&&(a.style.zoom=a.style.zoom))},Sc:function(a){if(k){var b=a.style.width;a.style.width=0;a.style.width=b}},nd:function(b,c){b=a.a.c(b);c=a.a.c(c);for(var d=[],e=b;e<=c;e++)d.push(e);return d},W:function(a){for(var b=[],c=0,d=a.length;c<d;c++)b.push(a[c]);return b},bc:function(a){return g?Symbol(a):a},xd:6===k,
yd:7===k,C:k,ic:function(b,c){for(var d=a.a.W(b.getElementsByTagName("input")).concat(a.a.W(b.getElementsByTagName("textarea"))),e="string"==typeof c?function(a){return a.name===c}:function(a){return c.test(a.name)},k=[],m=d.length-1;0<=m;m--)e(d[m])&&k.push(d[m]);return k},kd:function(b){return"string"==typeof b&&(b=a.a.cb(b))?H&&H.parse?H.parse(b):(new Function("return "+b))():null},Gb:function(b,c,d){if(!H||!H.stringify)throw Error("Cannot find JSON.stringify(). Some browsers (e.g., IE < 8) don't support it natively, but you can overcome this by adding a script reference to json2.js, downloadable from http://www.json.org/json2.js");
return H.stringify(a.a.c(b),c,d)},ld:function(c,d,e){e=e||{};var k=e.params||{},m=e.includeFields||this.gc,f=c;if("object"==typeof c&&"form"===a.a.A(c))for(var f=c.action,l=m.length-1;0<=l;l--)for(var g=a.a.ic(c,m[l]),h=g.length-1;0<=h;h--)k[g[h].name]=g[h].value;d=a.a.c(d);var r=t.createElement("form");r.style.display="none";r.action=f;r.method="post";for(var n in d)c=t.createElement("input"),c.type="hidden",c.name=n,c.value=a.a.Gb(a.a.c(d[n])),r.appendChild(c);b(k,function(a,b){var c=t.createElement("input");
c.type="hidden";c.name=a;c.value=b;r.appendChild(c)});t.body.appendChild(r);e.submitter?e.submitter(r):r.submit();setTimeout(function(){r.parentNode.removeChild(r)},0)}}}();a.b("utils",a.a);a.b("utils.arrayForEach",a.a.r);a.b("utils.arrayFirst",a.a.Vb);a.b("utils.arrayFilter",a.a.Ma);a.b("utils.arrayGetDistinctValues",a.a.Wb);a.b("utils.arrayIndexOf",a.a.o);a.b("utils.arrayMap",a.a.ib);a.b("utils.arrayPushAll",a.a.ta);a.b("utils.arrayRemoveItem",a.a.Na);a.b("utils.extend",a.a.extend);a.b("utils.fieldsIncludedWithJsonPost",
a.a.gc);a.b("utils.getFormFields",a.a.ic);a.b("utils.peekObservable",a.a.Bb);a.b("utils.postJson",a.a.ld);a.b("utils.parseJson",a.a.kd);a.b("utils.registerEventHandler",a.a.q);a.b("utils.stringifyJson",a.a.Gb);a.b("utils.range",a.a.nd);a.b("utils.toggleDomNodeCssClass",a.a.fb);a.b("utils.triggerEvent",a.a.Fa);a.b("utils.unwrapObservable",a.a.c);a.b("utils.objectForEach",a.a.D);a.b("utils.addOrRemoveItem",a.a.ra);a.b("utils.setTextContent",a.a.bb);a.b("unwrap",a.a.c);Function.prototype.bind||(Function.prototype.bind=
function(a){var c=this;if(1===arguments.length)return function(){return c.apply(a,arguments)};var d=Array.prototype.slice.call(arguments,1);return function(){var e=d.slice(0);e.push.apply(e,arguments);return c.apply(a,e)}});a.a.e=new function(){function a(b,g){var h=b[d];if(!h||"null"===h||!e[h]){if(!g)return n;h=b[d]="ko"+c++;e[h]={}}return e[h]}var c=0,d="__ko__"+(new Date).getTime(),e={};return{get:function(c,d){var e=a(c,!1);return e===n?n:e[d]},set:function(c,d,e){if(e!==n||a(c,!1)!==n)a(c,!0)[d]=
e},clear:function(a){var b=a[d];return b?(delete e[b],a[d]=null,!0):!1},J:function(){return c++ +d}}};a.b("utils.domData",a.a.e);a.b("utils.domData.clear",a.a.e.clear);a.a.G=new function(){function b(b,c){var e=a.a.e.get(b,d);e===n&&c&&(e=[],a.a.e.set(b,d,e));return e}function c(d){var e=b(d,!1);if(e)for(var e=e.slice(0),l=0;l<e.length;l++)e[l](d);a.a.e.clear(d);a.a.G.cleanExternalData(d);if(f[d.nodeType])for(e=d.firstChild;d=e;)e=d.nextSibling,8===d.nodeType&&c(d)}var d=a.a.e.J(),e={1:!0,8:!0,9:!0},
f={1:!0,9:!0};return{qa:function(a,c){if("function"!=typeof c)throw Error("Callback must be a function");b(a,!0).push(c)},tc:function(c,e){var f=b(c,!1);f&&(a.a.Na(f,e),0==f.length&&a.a.e.set(c,d,n))},ba:function(b){if(e[b.nodeType]&&(c(b),f[b.nodeType])){var d=[];a.a.ta(d,b.getElementsByTagName("*"));for(var l=0,m=d.length;l<m;l++)c(d[l])}return b},removeNode:function(b){a.ba(b);b.parentNode&&b.parentNode.removeChild(b)},cleanExternalData:function(a){u&&"function"==typeof u.cleanData&&u.cleanData([a])}}};
a.ba=a.a.G.ba;a.removeNode=a.a.G.removeNode;a.b("cleanNode",a.ba);a.b("removeNode",a.removeNode);a.b("utils.domNodeDisposal",a.a.G);a.b("utils.domNodeDisposal.addDisposeCallback",a.a.G.qa);a.b("utils.domNodeDisposal.removeDisposeCallback",a.a.G.tc);(function(){var b=[0,"",""],c=[1,"<table>","</table>"],d=[3,"<table><tbody><tr>","</tr></tbody></table>"],e=[1,"<select multiple='multiple'>","</select>"],f={thead:c,tbody:c,tfoot:c,tr:[2,"<table><tbody>","</tbody></table>"],td:d,th:d,option:e,optgroup:e},
g=8>=a.a.C;a.a.na=function(c,d){var e;if(u)if(u.parseHTML)e=u.parseHTML(c,d)||[];else{if((e=u.clean([c],d))&&e[0]){for(var k=e[0];k.parentNode&&11!==k.parentNode.nodeType;)k=k.parentNode;k.parentNode&&k.parentNode.removeChild(k)}}else{(e=d)||(e=t);var k=e.parentWindow||e.defaultView||x,r=a.a.cb(c).toLowerCase(),q=e.createElement("div"),p;p=(r=r.match(/^<([a-z]+)[ >]/))&&f[r[1]]||b;r=p[0];p="ignored<div>"+p[1]+c+p[2]+"</div>";"function"==typeof k.innerShiv?q.appendChild(k.innerShiv(p)):(g&&e.appendChild(q),
q.innerHTML=p,g&&q.parentNode.removeChild(q));for(;r--;)q=q.lastChild;e=a.a.W(q.lastChild.childNodes)}return e};a.a.Eb=function(b,c){a.a.rb(b);c=a.a.c(c);if(null!==c&&c!==n)if("string"!=typeof c&&(c=c.toString()),u)u(b).html(c);else for(var d=a.a.na(c,b.ownerDocument),e=0;e<d.length;e++)b.appendChild(d[e])}})();a.b("utils.parseHtmlFragment",a.a.na);a.b("utils.setHtml",a.a.Eb);a.N=function(){function b(c,e){if(c)if(8==c.nodeType){var f=a.N.pc(c.nodeValue);null!=f&&e.push({Qc:c,hd:f})}else if(1==c.nodeType)for(var f=
0,g=c.childNodes,h=g.length;f<h;f++)b(g[f],e)}var c={};return{yb:function(a){if("function"!=typeof a)throw Error("You can only pass a function to ko.memoization.memoize()");var b=(4294967296*(1+Math.random())|0).toString(16).substring(1)+(4294967296*(1+Math.random())|0).toString(16).substring(1);c[b]=a;return"\x3c!--[ko_memo:"+b+"]--\x3e"},Bc:function(a,b){var f=c[a];if(f===n)throw Error("Couldn't find any memo with ID "+a+". Perhaps it's already been unmemoized.");try{return f.apply(null,b||[]),
!0}finally{delete c[a]}},Cc:function(c,e){var f=[];b(c,f);for(var g=0,h=f.length;g<h;g++){var l=f[g].Qc,m=[l];e&&a.a.ta(m,e);a.N.Bc(f[g].hd,m);l.nodeValue="";l.parentNode&&l.parentNode.removeChild(l)}},pc:function(a){return(a=a.match(/^\[ko_memo\:(.*?)\]$/))?a[1]:null}}}();a.b("memoization",a.N);a.b("memoization.memoize",a.N.yb);a.b("memoization.unmemoize",a.N.Bc);a.b("memoization.parseMemoText",a.N.pc);a.b("memoization.unmemoizeDomNodeAndDescendants",a.N.Cc);a.Z=function(){function b(){if(e)for(var b=
e,c=0,m;g<e;)if(m=d[g++]){if(g>b){if(5E3<=++c){g=e;a.a.dc(Error("'Too much recursion' after processing "+c+" task groups."));break}b=e}try{m()}catch(k){a.a.dc(k)}}}function c(){b();g=e=d.length=0}var d=[],e=0,f=1,g=0;return{scheduler:x.MutationObserver?function(a){var b=t.createElement("div");(new MutationObserver(a)).observe(b,{attributes:!0});return function(){b.classList.toggle("foo")}}(c):t&&"onreadystatechange"in t.createElement("script")?function(a){var b=t.createElement("script");b.onreadystatechange=
function(){b.onreadystatechange=null;t.documentElement.removeChild(b);b=null;a()};t.documentElement.appendChild(b)}:function(a){setTimeout(a,0)},Za:function(b){e||a.Z.scheduler(c);d[e++]=b;return f++},cancel:function(a){a-=f-e;a>=g&&a<e&&(d[a]=null)},resetForTesting:function(){var a=e-g;g=e=d.length=0;return a},rd:b}}();a.b("tasks",a.Z);a.b("tasks.schedule",a.Z.Za);a.b("tasks.runEarly",a.Z.rd);a.Aa={throttle:function(b,c){b.throttleEvaluation=c;var d=null;return a.B({read:b,write:function(e){clearTimeout(d);
d=a.a.setTimeout(function(){b(e)},c)}})},rateLimit:function(a,c){var d,e,f;"number"==typeof c?d=c:(d=c.timeout,e=c.method);a.gb=!1;f="notifyWhenChangesStop"==e?T:S;a.Wa(function(a){return f(a,d)})},deferred:function(b,c){if(!0!==c)throw Error("The 'deferred' extender only accepts the value 'true', because it is not supported to turn deferral off once enabled.");b.gb||(b.gb=!0,b.Wa(function(c){var e,f=!1;return function(){if(!f){a.Z.cancel(e);e=a.Z.Za(c);try{f=!0,b.notifySubscribers(n,"dirty")}finally{f=
!1}}}}))},notify:function(a,c){a.equalityComparer="always"==c?null:J}};var R={undefined:1,"boolean":1,number:1,string:1};a.b("extenders",a.Aa);a.zc=function(b,c,d){this.$=b;this.jb=c;this.Pc=d;this.T=!1;a.H(this,"dispose",this.k)};a.zc.prototype.k=function(){this.T=!0;this.Pc()};a.K=function(){a.a.ab(this,D);D.ub(this)};var E="change",D={ub:function(a){a.F={change:[]};a.Qb=1},Y:function(b,c,d){var e=this;d=d||E;var f=new a.zc(e,c?b.bind(c):b,function(){a.a.Na(e.F[d],f);e.Ka&&e.Ka(d)});e.ua&&e.ua(d);
e.F[d]||(e.F[d]=[]);e.F[d].push(f);return f},notifySubscribers:function(b,c){c=c||E;c===E&&this.Kb();if(this.Ra(c)){var d=c===E&&this.Fc||this.F[c].slice(0);try{a.l.Xb();for(var e=0,f;f=d[e];++e)f.T||f.jb(b)}finally{a.l.end()}}},Pa:function(){return this.Qb},Zc:function(a){return this.Pa()!==a},Kb:function(){++this.Qb},Wa:function(b){var c=this,d=a.I(c),e,f,g,h;c.Ja||(c.Ja=c.notifySubscribers,c.notifySubscribers=U);var l=b(function(){c.Ha=!1;d&&h===c&&(h=c.Mb?c.Mb():c());var a=f||c.Ua(g,h);f=e=!1;
a&&c.Ja(g=h)});c.Pb=function(a){c.Fc=c.F[E].slice(0);c.Ha=e=!0;h=a;l()};c.Ob=function(a){e||(g=a,c.Ja(a,"beforeChange"))};c.Hc=function(){c.Ua(g,c.p(!0))&&(f=!0)}},Ra:function(a){return this.F[a]&&this.F[a].length},Xc:function(b){if(b)return this.F[b]&&this.F[b].length||0;var c=0;a.a.D(this.F,function(a,b){"dirty"!==a&&(c+=b.length)});return c},Ua:function(a,c){return!this.equalityComparer||!this.equalityComparer(a,c)},extend:function(b){var c=this;b&&a.a.D(b,function(b,e){var f=a.Aa[b];"function"==
typeof f&&(c=f(c,e)||c)});return c}};a.H(D,"subscribe",D.Y);a.H(D,"extend",D.extend);a.H(D,"getSubscriptionsCount",D.Xc);a.a.la&&a.a.$a(D,Function.prototype);a.K.fn=D;a.lc=function(a){return null!=a&&"function"==typeof a.Y&&"function"==typeof a.notifySubscribers};a.b("subscribable",a.K);a.b("isSubscribable",a.lc);a.xa=a.l=function(){function b(a){d.push(e);e=a}function c(){e=d.pop()}var d=[],e,f=0;return{Xb:b,end:c,sc:function(b){if(e){if(!a.lc(b))throw Error("Only subscribable things can act as dependencies");
e.jb.call(e.Lc,b,b.Gc||(b.Gc=++f))}},w:function(a,d,e){try{return b(),a.apply(d,e||[])}finally{c()}},Ca:function(){if(e)return e.m.Ca()},Va:function(){if(e)return e.Va}}}();a.b("computedContext",a.xa);a.b("computedContext.getDependenciesCount",a.xa.Ca);a.b("computedContext.isInitial",a.xa.Va);a.b("ignoreDependencies",a.wd=a.l.w);var F=a.a.bc("_latestValue");a.O=function(b){function c(){if(0<arguments.length)return c.Ua(c[F],arguments[0])&&(c.ia(),c[F]=arguments[0],c.ha()),this;a.l.sc(c);return c[F]}
c[F]=b;a.a.la||a.a.extend(c,a.K.fn);a.K.fn.ub(c);a.a.ab(c,B);a.options.deferUpdates&&a.Aa.deferred(c,!0);return c};var B={equalityComparer:J,p:function(){return this[F]},ha:function(){this.notifySubscribers(this[F])},ia:function(){this.notifySubscribers(this[F],"beforeChange")}};a.a.la&&a.a.$a(B,a.K.fn);var I=a.O.md="__ko_proto__";B[I]=a.O;a.Qa=function(b,c){return null===b||b===n||b[I]===n?!1:b[I]===c?!0:a.Qa(b[I],c)};a.I=function(b){return a.Qa(b,a.O)};a.Da=function(b){return"function"==typeof b&&
b[I]===a.O||"function"==typeof b&&b[I]===a.B&&b.$c?!0:!1};a.b("observable",a.O);a.b("isObservable",a.I);a.b("isWriteableObservable",a.Da);a.b("isWritableObservable",a.Da);a.b("observable.fn",B);a.H(B,"peek",B.p);a.H(B,"valueHasMutated",B.ha);a.H(B,"valueWillMutate",B.ia);a.ma=function(b){b=b||[];if("object"!=typeof b||!("length"in b))throw Error("The argument passed when initializing an observable array must be an array, or null, or undefined.");b=a.O(b);a.a.ab(b,a.ma.fn);return b.extend({trackArrayChanges:!0})};
a.ma.fn={remove:function(b){for(var c=this.p(),d=[],e="function"!=typeof b||a.I(b)?function(a){return a===b}:b,f=0;f<c.length;f++){var g=c[f];e(g)&&(0===d.length&&this.ia(),d.push(g),c.splice(f,1),f--)}d.length&&this.ha();return d},removeAll:function(b){if(b===n){var c=this.p(),d=c.slice(0);this.ia();c.splice(0,c.length);this.ha();return d}return b?this.remove(function(c){return 0<=a.a.o(b,c)}):[]},destroy:function(b){var c=this.p(),d="function"!=typeof b||a.I(b)?function(a){return a===b}:b;this.ia();
for(var e=c.length-1;0<=e;e--)d(c[e])&&(c[e]._destroy=!0);this.ha()},destroyAll:function(b){return b===n?this.destroy(function(){return!0}):b?this.destroy(function(c){return 0<=a.a.o(b,c)}):[]},indexOf:function(b){var c=this();return a.a.o(c,b)},replace:function(a,c){var d=this.indexOf(a);0<=d&&(this.ia(),this.p()[d]=c,this.ha())}};a.a.la&&a.a.$a(a.ma.fn,a.O.fn);a.a.r("pop push reverse shift sort splice unshift".split(" "),function(b){a.ma.fn[b]=function(){var a=this.p();this.ia();this.Yb(a,b,arguments);
var d=a[b].apply(a,arguments);this.ha();return d===a?this:d}});a.a.r(["slice"],function(b){a.ma.fn[b]=function(){var a=this();return a[b].apply(a,arguments)}});a.b("observableArray",a.ma);a.Aa.trackArrayChanges=function(b,c){function d(){if(!e){e=!0;l=b.notifySubscribers;b.notifySubscribers=function(a,b){b&&b!==E||++h;return l.apply(this,arguments)};var c=[].concat(b.p()||[]);f=null;g=b.Y(function(d){d=[].concat(d||[]);if(b.Ra("arrayChange")){var e;if(!f||1<h)f=a.a.lb(c,d,b.kb);e=f}c=d;f=null;h=0;
e&&e.length&&b.notifySubscribers(e,"arrayChange")})}}b.kb={};c&&"object"==typeof c&&a.a.extend(b.kb,c);b.kb.sparse=!0;if(!b.Yb){var e=!1,f=null,g,h=0,l,m=b.ua,k=b.Ka;b.ua=function(a){m&&m.call(b,a);"arrayChange"===a&&d()};b.Ka=function(a){k&&k.call(b,a);"arrayChange"!==a||b.Ra("arrayChange")||(l&&(b.notifySubscribers=l,l=n),g.k(),e=!1)};b.Yb=function(b,c,d){function k(a,b,c){return m[m.length]={status:a,value:b,index:c}}if(e&&!h){var m=[],l=b.length,g=d.length,G=0;switch(c){case "push":G=l;case "unshift":for(c=
0;c<g;c++)k("added",d[c],G+c);break;case "pop":G=l-1;case "shift":l&&k("deleted",b[G],G);break;case "splice":c=Math.min(Math.max(0,0>d[0]?l+d[0]:d[0]),l);for(var l=1===g?l:Math.min(c+(d[1]||0),l),g=c+g-2,G=Math.max(l,g),n=[],s=[],w=2;c<G;++c,++w)c<l&&s.push(k("deleted",b[c],c)),c<g&&n.push(k("added",d[w],c));a.a.hc(s,n);break;default:return}f=m}}}};var s=a.a.bc("_state");a.m=a.B=function(b,c,d){function e(){if(0<arguments.length){if("function"===typeof f)f.apply(g.sb,arguments);else throw Error("Cannot write a value to a ko.computed unless you specify a 'write' option. If you wish to read the current value, don't pass any parameters.");
return this}a.l.sc(e);(g.V||g.t&&e.Sa())&&e.U();return g.M}"object"===typeof b?d=b:(d=d||{},b&&(d.read=b));if("function"!=typeof d.read)throw Error("Pass a function that returns the value of the ko.computed");var f=d.write,g={M:n,da:!0,V:!0,Ta:!1,Hb:!1,T:!1,Ya:!1,t:!1,od:d.read,sb:c||d.owner,i:d.disposeWhenNodeIsRemoved||d.i||null,ya:d.disposeWhen||d.ya,pb:null,s:{},L:0,fc:null};e[s]=g;e.$c="function"===typeof f;a.a.la||a.a.extend(e,a.K.fn);a.K.fn.ub(e);a.a.ab(e,z);d.pure?(g.Ya=!0,g.t=!0,a.a.extend(e,
Y)):d.deferEvaluation&&a.a.extend(e,Z);a.options.deferUpdates&&a.Aa.deferred(e,!0);g.i&&(g.Hb=!0,g.i.nodeType||(g.i=null));g.t||d.deferEvaluation||e.U();g.i&&e.ca()&&a.a.G.qa(g.i,g.pb=function(){e.k()});return e};var z={equalityComparer:J,Ca:function(){return this[s].L},Sb:function(a,c,d){if(this[s].Ya&&c===this)throw Error("A 'pure' computed must not be called recursively");this[s].s[a]=d;d.Ia=this[s].L++;d.pa=c.Pa()},Sa:function(){var a,c,d=this[s].s;for(a in d)if(d.hasOwnProperty(a)&&(c=d[a],this.oa&&
c.$.Ha||c.$.Zc(c.pa)))return!0},gd:function(){this.oa&&!this[s].Ta&&this.oa(!1)},ca:function(){var a=this[s];return a.V||0<a.L},qd:function(){this.Ha?this[s].V&&(this[s].da=!0):this.ec()},yc:function(a){if(a.gb&&!this[s].i){var c=a.Y(this.gd,this,"dirty"),d=a.Y(this.qd,this);return{$:a,k:function(){c.k();d.k()}}}return a.Y(this.ec,this)},ec:function(){var b=this,c=b.throttleEvaluation;c&&0<=c?(clearTimeout(this[s].fc),this[s].fc=a.a.setTimeout(function(){b.U(!0)},c)):b.oa?b.oa(!0):b.U(!0)},U:function(b){var c=
this[s],d=c.ya,e=!1;if(!c.Ta&&!c.T){if(c.i&&!a.a.qb(c.i)||d&&d()){if(!c.Hb){this.k();return}}else c.Hb=!1;c.Ta=!0;try{e=this.Vc(b)}finally{c.Ta=!1}c.L||this.k();return e}},Vc:function(b){var c=this[s],d=!1,e=c.Ya?n:!c.L,f={Mc:this,Oa:c.s,ob:c.L};a.l.Xb({Lc:f,jb:W,m:this,Va:e});c.s={};c.L=0;f=this.Uc(c,f);this.Ua(c.M,f)&&(c.t||this.notifySubscribers(c.M,"beforeChange"),c.M=f,c.t?this.Kb():b&&this.notifySubscribers(c.M),d=!0);e&&this.notifySubscribers(c.M,"awake");return d},Uc:function(b,c){try{var d=
b.od;return b.sb?d.call(b.sb):d()}finally{a.l.end(),c.ob&&!b.t&&a.a.D(c.Oa,V),b.da=b.V=!1}},p:function(a){var c=this[s];(c.V&&(a||!c.L)||c.t&&this.Sa())&&this.U();return c.M},Wa:function(b){a.K.fn.Wa.call(this,b);this.Mb=function(){this[s].da?this.U():this[s].V=!1;return this[s].M};this.oa=function(a){this.Ob(this[s].M);this[s].V=!0;a&&(this[s].da=!0);this.Pb(this)}},k:function(){var b=this[s];!b.t&&b.s&&a.a.D(b.s,function(a,b){b.k&&b.k()});b.i&&b.pb&&a.a.G.tc(b.i,b.pb);b.s=null;b.L=0;b.T=!0;b.da=
!1;b.V=!1;b.t=!1;b.i=null}},Y={ua:function(b){var c=this,d=c[s];if(!d.T&&d.t&&"change"==b){d.t=!1;if(d.da||c.Sa())d.s=null,d.L=0,c.U()&&c.Kb();else{var e=[];a.a.D(d.s,function(a,b){e[b.Ia]=a});a.a.r(e,function(a,b){var e=d.s[a],l=c.yc(e.$);l.Ia=b;l.pa=e.pa;d.s[a]=l})}d.T||c.notifySubscribers(d.M,"awake")}},Ka:function(b){var c=this[s];c.T||"change"!=b||this.Ra("change")||(a.a.D(c.s,function(a,b){b.k&&(c.s[a]={$:b.$,Ia:b.Ia,pa:b.pa},b.k())}),c.t=!0,this.notifySubscribers(n,"asleep"))},Pa:function(){var b=
this[s];b.t&&(b.da||this.Sa())&&this.U();return a.K.fn.Pa.call(this)}},Z={ua:function(a){"change"!=a&&"beforeChange"!=a||this.p()}};a.a.la&&a.a.$a(z,a.K.fn);var P=a.O.md;a.m[P]=a.O;z[P]=a.m;a.bd=function(b){return a.Qa(b,a.m)};a.cd=function(b){return a.Qa(b,a.m)&&b[s]&&b[s].Ya};a.b("computed",a.m);a.b("dependentObservable",a.m);a.b("isComputed",a.bd);a.b("isPureComputed",a.cd);a.b("computed.fn",z);a.H(z,"peek",z.p);a.H(z,"dispose",z.k);a.H(z,"isActive",z.ca);a.H(z,"getDependenciesCount",z.Ca);a.rc=
function(b,c){if("function"===typeof b)return a.m(b,c,{pure:!0});b=a.a.extend({},b);b.pure=!0;return a.m(b,c)};a.b("pureComputed",a.rc);(function(){function b(a,f,g){g=g||new d;a=f(a);if("object"!=typeof a||null===a||a===n||a instanceof RegExp||a instanceof Date||a instanceof String||a instanceof Number||a instanceof Boolean)return a;var h=a instanceof Array?[]:{};g.save(a,h);c(a,function(c){var d=f(a[c]);switch(typeof d){case "boolean":case "number":case "string":case "function":h[c]=d;break;case "object":case "undefined":var k=
g.get(d);h[c]=k!==n?k:b(d,f,g)}});return h}function c(a,b){if(a instanceof Array){for(var c=0;c<a.length;c++)b(c);"function"==typeof a.toJSON&&b("toJSON")}else for(c in a)b(c)}function d(){this.keys=[];this.Lb=[]}a.Ac=function(c){if(0==arguments.length)throw Error("When calling ko.toJS, pass the object you want to convert.");return b(c,function(b){for(var c=0;a.I(b)&&10>c;c++)b=b();return b})};a.toJSON=function(b,c,d){b=a.Ac(b);return a.a.Gb(b,c,d)};d.prototype={save:function(b,c){var d=a.a.o(this.keys,
b);0<=d?this.Lb[d]=c:(this.keys.push(b),this.Lb.push(c))},get:function(b){b=a.a.o(this.keys,b);return 0<=b?this.Lb[b]:n}}})();a.b("toJS",a.Ac);a.b("toJSON",a.toJSON);(function(){a.j={u:function(b){switch(a.a.A(b)){case "option":return!0===b.__ko__hasDomDataOptionValue__?a.a.e.get(b,a.d.options.zb):7>=a.a.C?b.getAttributeNode("value")&&b.getAttributeNode("value").specified?b.value:b.text:b.value;case "select":return 0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex]):n;default:return b.value}},ja:function(b,
c,d){switch(a.a.A(b)){case "option":switch(typeof c){case "string":a.a.e.set(b,a.d.options.zb,n);"__ko__hasDomDataOptionValue__"in b&&delete b.__ko__hasDomDataOptionValue__;b.value=c;break;default:a.a.e.set(b,a.d.options.zb,c),b.__ko__hasDomDataOptionValue__=!0,b.value="number"===typeof c?c:""}break;case "select":if(""===c||null===c)c=n;for(var e=-1,f=0,g=b.options.length,h;f<g;++f)if(h=a.j.u(b.options[f]),h==c||""==h&&c===n){e=f;break}if(d||0<=e||c===n&&1<b.size)b.selectedIndex=e;break;default:if(null===
c||c===n)c="";b.value=c}}}})();a.b("selectExtensions",a.j);a.b("selectExtensions.readValue",a.j.u);a.b("selectExtensions.writeValue",a.j.ja);a.h=function(){function b(b){b=a.a.cb(b);123===b.charCodeAt(0)&&(b=b.slice(1,-1));var c=[],d=b.match(e),r,h=[],p=0;if(d){d.push(",");for(var A=0,y;y=d[A];++A){var v=y.charCodeAt(0);if(44===v){if(0>=p){c.push(r&&h.length?{key:r,value:h.join("")}:{unknown:r||h.join("")});r=p=0;h=[];continue}}else if(58===v){if(!p&&!r&&1===h.length){r=h.pop();continue}}else 47===
v&&A&&1<y.length?(v=d[A-1].match(f))&&!g[v[0]]&&(b=b.substr(b.indexOf(y)+1),d=b.match(e),d.push(","),A=-1,y="/"):40===v||123===v||91===v?++p:41===v||125===v||93===v?--p:r||h.length||34!==v&&39!==v||(y=y.slice(1,-1));h.push(y)}}return c}var c=["true","false","null","undefined"],d=/^(?:[$_a-z][$\w]*|(.+)(\.\s*[$_a-z][$\w]*|\[.+\]))$/i,e=RegExp("\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|/(?:[^/\\\\]|\\\\.)*/w*|[^\\s:,/][^,\"'{}()/:[\\]]*[^\\s,\"'{}()/:[\\]]|[^\\s]","g"),f=/[\])"'A-Za-z0-9_$]+$/,
g={"in":1,"return":1,"typeof":1},h={};return{va:[],ga:h,Ab:b,Xa:function(e,m){function k(b,e){var m;if(!A){var l=a.getBindingHandler(b);if(l&&l.preprocess&&!(e=l.preprocess(e,b,k)))return;if(l=h[b])m=e,0<=a.a.o(c,m)?m=!1:(l=m.match(d),m=null===l?!1:l[1]?"Object("+l[1]+")"+l[2]:m),l=m;l&&g.push("'"+b+"':function(_z){"+m+"=_z}")}p&&(e="function(){return "+e+" }");f.push("'"+b+"':"+e)}m=m||{};var f=[],g=[],p=m.valueAccessors,A=m.bindingParams,y="string"===typeof e?b(e):e;a.a.r(y,function(a){k(a.key||
a.unknown,a.value)});g.length&&k("_ko_property_writers","{"+g.join(",")+" }");return f.join(",")},fd:function(a,b){for(var c=0;c<a.length;c++)if(a[c].key==b)return!0;return!1},Ga:function(b,c,d,e,f){if(b&&a.I(b))!a.Da(b)||f&&b.p()===e||b(e);else if((b=c.get("_ko_property_writers"))&&b[d])b[d](e)}}}();a.b("expressionRewriting",a.h);a.b("expressionRewriting.bindingRewriteValidators",a.h.va);a.b("expressionRewriting.parseObjectLiteral",a.h.Ab);a.b("expressionRewriting.preProcessBindings",a.h.Xa);a.b("expressionRewriting._twoWayBindings",
a.h.ga);a.b("jsonExpressionRewriting",a.h);a.b("jsonExpressionRewriting.insertPropertyAccessorsIntoJson",a.h.Xa);(function(){function b(a){return 8==a.nodeType&&g.test(f?a.text:a.nodeValue)}function c(a){return 8==a.nodeType&&h.test(f?a.text:a.nodeValue)}function d(a,d){for(var e=a,f=1,l=[];e=e.nextSibling;){if(c(e)&&(f--,0===f))return l;l.push(e);b(e)&&f++}if(!d)throw Error("Cannot find closing comment tag to match: "+a.nodeValue);return null}function e(a,b){var c=d(a,b);return c?0<c.length?c[c.length-
1].nextSibling:a.nextSibling:null}var f=t&&"\x3c!--test--\x3e"===t.createComment("test").text,g=f?/^\x3c!--\s*ko(?:\s+([\s\S]+))?\s*--\x3e$/:/^\s*ko(?:\s+([\s\S]+))?\s*$/,h=f?/^\x3c!--\s*\/ko\s*--\x3e$/:/^\s*\/ko\s*$/,l={ul:!0,ol:!0};a.f={aa:{},childNodes:function(a){return b(a)?d(a):a.childNodes},za:function(c){if(b(c)){c=a.f.childNodes(c);for(var d=0,e=c.length;d<e;d++)a.removeNode(c[d])}else a.a.rb(c)},fa:function(c,d){if(b(c)){a.f.za(c);for(var e=c.nextSibling,f=0,l=d.length;f<l;f++)e.parentNode.insertBefore(d[f],
e)}else a.a.fa(c,d)},qc:function(a,c){b(a)?a.parentNode.insertBefore(c,a.nextSibling):a.firstChild?a.insertBefore(c,a.firstChild):a.appendChild(c)},kc:function(c,d,e){e?b(c)?c.parentNode.insertBefore(d,e.nextSibling):e.nextSibling?c.insertBefore(d,e.nextSibling):c.appendChild(d):a.f.qc(c,d)},firstChild:function(a){return b(a)?!a.nextSibling||c(a.nextSibling)?null:a.nextSibling:a.firstChild},nextSibling:function(a){b(a)&&(a=e(a));return a.nextSibling&&c(a.nextSibling)?null:a.nextSibling},Yc:b,vd:function(a){return(a=
(f?a.text:a.nodeValue).match(g))?a[1]:null},oc:function(d){if(l[a.a.A(d)]){var k=d.firstChild;if(k){do if(1===k.nodeType){var f;f=k.firstChild;var g=null;if(f){do if(g)g.push(f);else if(b(f)){var h=e(f,!0);h?f=h:g=[f]}else c(f)&&(g=[f]);while(f=f.nextSibling)}if(f=g)for(g=k.nextSibling,h=0;h<f.length;h++)g?d.insertBefore(f[h],g):d.appendChild(f[h])}while(k=k.nextSibling)}}}}})();a.b("virtualElements",a.f);a.b("virtualElements.allowedBindings",a.f.aa);a.b("virtualElements.emptyNode",a.f.za);a.b("virtualElements.insertAfter",
a.f.kc);a.b("virtualElements.prepend",a.f.qc);a.b("virtualElements.setDomNodeChildren",a.f.fa);(function(){a.S=function(){this.Kc={}};a.a.extend(a.S.prototype,{nodeHasBindings:function(b){switch(b.nodeType){case 1:return null!=b.getAttribute("data-bind")||a.g.getComponentNameForNode(b);case 8:return a.f.Yc(b);default:return!1}},getBindings:function(b,c){var d=this.getBindingsString(b,c),d=d?this.parseBindingsString(d,c,b):null;return a.g.Rb(d,b,c,!1)},getBindingAccessors:function(b,c){var d=this.getBindingsString(b,
c),d=d?this.parseBindingsString(d,c,b,{valueAccessors:!0}):null;return a.g.Rb(d,b,c,!0)},getBindingsString:function(b){switch(b.nodeType){case 1:return b.getAttribute("data-bind");case 8:return a.f.vd(b);default:return null}},parseBindingsString:function(b,c,d,e){try{var f=this.Kc,g=b+(e&&e.valueAccessors||""),h;if(!(h=f[g])){var l,m="with($context){with($data||{}){return{"+a.h.Xa(b,e)+"}}}";l=new Function("$context","$element",m);h=f[g]=l}return h(c,d)}catch(k){throw k.message="Unable to parse bindings.\nBindings value: "+
b+"\nMessage: "+k.message,k;}}});a.S.instance=new a.S})();a.b("bindingProvider",a.S);(function(){function b(a){return function(){return a}}function c(a){return a()}function d(b){return a.a.Ea(a.l.w(b),function(a,c){return function(){return b()[c]}})}function e(c,e,k){return"function"===typeof c?d(c.bind(null,e,k)):a.a.Ea(c,b)}function f(a,b){return d(this.getBindings.bind(this,a,b))}function g(b,c,d){var e,k=a.f.firstChild(c),f=a.S.instance,m=f.preprocessNode;if(m){for(;e=k;)k=a.f.nextSibling(e),
m.call(f,e);k=a.f.firstChild(c)}for(;e=k;)k=a.f.nextSibling(e),h(b,e,d)}function h(b,c,d){var e=!0,k=1===c.nodeType;k&&a.f.oc(c);if(k&&d||a.S.instance.nodeHasBindings(c))e=m(c,null,b,d).shouldBindDescendants;e&&!r[a.a.A(c)]&&g(b,c,!k)}function l(b){var c=[],d={},e=[];a.a.D(b,function X(k){if(!d[k]){var f=a.getBindingHandler(k);f&&(f.after&&(e.push(k),a.a.r(f.after,function(c){if(b[c]){if(-1!==a.a.o(e,c))throw Error("Cannot combine the following bindings, because they have a cyclic dependency: "+e.join(", "));
X(c)}}),e.length--),c.push({key:k,jc:f}));d[k]=!0}});return c}function m(b,d,e,k){var m=a.a.e.get(b,q);if(!d){if(m)throw Error("You cannot apply bindings multiple times to the same element.");a.a.e.set(b,q,!0)}!m&&k&&a.xc(b,e);var g;if(d&&"function"!==typeof d)g=d;else{var h=a.S.instance,r=h.getBindingAccessors||f,p=a.B(function(){(g=d?d(e,b):r.call(h,b,e))&&e.Q&&e.Q();return g},null,{i:b});g&&p.ca()||(p=null)}var s;if(g){var t=p?function(a){return function(){return c(p()[a])}}:function(a){return g[a]},
u=function(){return a.a.Ea(p?p():g,c)};u.get=function(a){return g[a]&&c(t(a))};u.has=function(a){return a in g};k=l(g);a.a.r(k,function(c){var d=c.jc.init,k=c.jc.update,f=c.key;if(8===b.nodeType&&!a.f.aa[f])throw Error("The binding '"+f+"' cannot be used with virtual elements");try{"function"==typeof d&&a.l.w(function(){var a=d(b,t(f),u,e.$data,e);if(a&&a.controlsDescendantBindings){if(s!==n)throw Error("Multiple bindings ("+s+" and "+f+") are trying to control descendant bindings of the same element. You cannot use these bindings together on the same element.");
s=f}}),"function"==typeof k&&a.B(function(){k(b,t(f),u,e.$data,e)},null,{i:b})}catch(m){throw m.message='Unable to process binding "'+f+": "+g[f]+'"\nMessage: '+m.message,m;}})}return{shouldBindDescendants:s===n}}function k(b){return b&&b instanceof a.R?b:new a.R(b)}a.d={};var r={script:!0,textarea:!0,template:!0};a.getBindingHandler=function(b){return a.d[b]};a.R=function(b,c,d,e,k){function f(){var k=g?b():b,m=a.a.c(k);c?(c.Q&&c.Q(),a.a.extend(l,c),l.Q=r):(l.$parents=[],l.$root=m,l.ko=a);l.$rawData=
k;l.$data=m;d&&(l[d]=m);e&&e(l,c,m);return l.$data}function m(){return h&&!a.a.Tb(h)}var l=this,g="function"==typeof b&&!a.I(b),h,r;k&&k.exportDependencies?f():(r=a.B(f,null,{ya:m,i:!0}),r.ca()&&(l.Q=r,r.equalityComparer=null,h=[],r.Dc=function(b){h.push(b);a.a.G.qa(b,function(b){a.a.Na(h,b);h.length||(r.k(),l.Q=r=n)})}))};a.R.prototype.createChildContext=function(b,c,d,e){return new a.R(b,this,c,function(a,b){a.$parentContext=b;a.$parent=b.$data;a.$parents=(b.$parents||[]).slice(0);a.$parents.unshift(a.$parent);
d&&d(a)},e)};a.R.prototype.extend=function(b){return new a.R(this.Q||this.$data,this,null,function(c,d){c.$rawData=d.$rawData;a.a.extend(c,"function"==typeof b?b():b)})};a.R.prototype.ac=function(a,b){return this.createChildContext(a,b,null,{exportDependencies:!0})};var q=a.a.e.J(),p=a.a.e.J();a.xc=function(b,c){if(2==arguments.length)a.a.e.set(b,p,c),c.Q&&c.Q.Dc(b);else return a.a.e.get(b,p)};a.La=function(b,c,d){1===b.nodeType&&a.f.oc(b);return m(b,c,k(d),!0)};a.Ic=function(b,c,d){d=k(d);return a.La(b,
e(c,d,b),d)};a.hb=function(a,b){1!==b.nodeType&&8!==b.nodeType||g(k(a),b,!0)};a.Ub=function(a,b){!u&&x.jQuery&&(u=x.jQuery);if(b&&1!==b.nodeType&&8!==b.nodeType)throw Error("ko.applyBindings: first parameter should be your view model; second parameter should be a DOM node");b=b||x.document.body;h(k(a),b,!0)};a.nb=function(b){switch(b.nodeType){case 1:case 8:var c=a.xc(b);if(c)return c;if(b.parentNode)return a.nb(b.parentNode)}return n};a.Oc=function(b){return(b=a.nb(b))?b.$data:n};a.b("bindingHandlers",
a.d);a.b("applyBindings",a.Ub);a.b("applyBindingsToDescendants",a.hb);a.b("applyBindingAccessorsToNode",a.La);a.b("applyBindingsToNode",a.Ic);a.b("contextFor",a.nb);a.b("dataFor",a.Oc)})();(function(b){function c(c,e){var m=f.hasOwnProperty(c)?f[c]:b,k;m?m.Y(e):(m=f[c]=new a.K,m.Y(e),d(c,function(b,d){var e=!(!d||!d.synchronous);g[c]={definition:b,dd:e};delete f[c];k||e?m.notifySubscribers(b):a.Z.Za(function(){m.notifySubscribers(b)})}),k=!0)}function d(a,b){e("getConfig",[a],function(c){c?e("loadComponent",
[a,c],function(a){b(a,c)}):b(null,null)})}function e(c,d,f,k){k||(k=a.g.loaders.slice(0));var g=k.shift();if(g){var q=g[c];if(q){var p=!1;if(q.apply(g,d.concat(function(a){p?f(null):null!==a?f(a):e(c,d,f,k)}))!==b&&(p=!0,!g.suppressLoaderExceptions))throw Error("Component loaders must supply values by invoking the callback, not by returning values synchronously.");}else e(c,d,f,k)}else f(null)}var f={},g={};a.g={get:function(d,e){var f=g.hasOwnProperty(d)?g[d]:b;f?f.dd?a.l.w(function(){e(f.definition)}):
a.Z.Za(function(){e(f.definition)}):c(d,e)},$b:function(a){delete g[a]},Nb:e};a.g.loaders=[];a.b("components",a.g);a.b("components.get",a.g.get);a.b("components.clearCachedDefinition",a.g.$b)})();(function(){function b(b,c,d,e){function g(){0===--y&&e(h)}var h={},y=2,v=d.template;d=d.viewModel;v?f(c,v,function(c){a.g.Nb("loadTemplate",[b,c],function(a){h.template=a;g()})}):g();d?f(c,d,function(c){a.g.Nb("loadViewModel",[b,c],function(a){h[l]=a;g()})}):g()}function c(a,b,d){if("function"===typeof b)d(function(a){return new b(a)});
else if("function"===typeof b[l])d(b[l]);else if("instance"in b){var e=b.instance;d(function(){return e})}else"viewModel"in b?c(a,b.viewModel,d):a("Unknown viewModel value: "+b)}function d(b){switch(a.a.A(b)){case "script":return a.a.na(b.text);case "textarea":return a.a.na(b.value);case "template":if(e(b.content))return a.a.wa(b.content.childNodes)}return a.a.wa(b.childNodes)}function e(a){return x.DocumentFragment?a instanceof DocumentFragment:a&&11===a.nodeType}function f(a,b,c){"string"===typeof b.require?
O||x.require?(O||x.require)([b.require],c):a("Uses require, but no AMD loader is present"):c(b)}function g(a){return function(b){throw Error("Component '"+a+"': "+b);}}var h={};a.g.register=function(b,c){if(!c)throw Error("Invalid configuration for "+b);if(a.g.wb(b))throw Error("Component "+b+" is already registered");h[b]=c};a.g.wb=function(a){return h.hasOwnProperty(a)};a.g.ud=function(b){delete h[b];a.g.$b(b)};a.g.cc={getConfig:function(a,b){b(h.hasOwnProperty(a)?h[a]:null)},loadComponent:function(a,
c,d){var e=g(a);f(e,c,function(c){b(a,e,c,d)})},loadTemplate:function(b,c,f){b=g(b);if("string"===typeof c)f(a.a.na(c));else if(c instanceof Array)f(c);else if(e(c))f(a.a.W(c.childNodes));else if(c.element)if(c=c.element,x.HTMLElement?c instanceof HTMLElement:c&&c.tagName&&1===c.nodeType)f(d(c));else if("string"===typeof c){var l=t.getElementById(c);l?f(d(l)):b("Cannot find element with ID "+c)}else b("Unknown element type: "+c);else b("Unknown template value: "+c)},loadViewModel:function(a,b,d){c(g(a),
b,d)}};var l="createViewModel";a.b("components.register",a.g.register);a.b("components.isRegistered",a.g.wb);a.b("components.unregister",a.g.ud);a.b("components.defaultLoader",a.g.cc);a.g.loaders.push(a.g.cc);a.g.Ec=h})();(function(){function b(b,e){var f=b.getAttribute("params");if(f){var f=c.parseBindingsString(f,e,b,{valueAccessors:!0,bindingParams:!0}),f=a.a.Ea(f,function(c){return a.m(c,null,{i:b})}),g=a.a.Ea(f,function(c){var e=c.p();return c.ca()?a.m({read:function(){return a.a.c(c())},write:a.Da(e)&&
function(a){c()(a)},i:b}):e});g.hasOwnProperty("$raw")||(g.$raw=f);return g}return{$raw:{}}}a.g.getComponentNameForNode=function(b){var c=a.a.A(b);if(a.g.wb(c)&&(-1!=c.indexOf("-")||"[object HTMLUnknownElement]"==""+b||8>=a.a.C&&b.tagName===c))return c};a.g.Rb=function(c,e,f,g){if(1===e.nodeType){var h=a.g.getComponentNameForNode(e);if(h){c=c||{};if(c.component)throw Error('Cannot use the "component" binding on a custom element matching a component');var l={name:h,params:b(e,f)};c.component=g?function(){return l}:
l}}return c};var c=new a.S;9>a.a.C&&(a.g.register=function(a){return function(b){t.createElement(b);return a.apply(this,arguments)}}(a.g.register),t.createDocumentFragment=function(b){return function(){var c=b(),f=a.g.Ec,g;for(g in f)f.hasOwnProperty(g)&&c.createElement(g);return c}}(t.createDocumentFragment))})();(function(b){function c(b,c,d){c=c.template;if(!c)throw Error("Component '"+b+"' has no template");b=a.a.wa(c);a.f.fa(d,b)}function d(a,b,c,d){var e=a.createViewModel;return e?e.call(a,
d,{element:b,templateNodes:c}):d}var e=0;a.d.component={init:function(f,g,h,l,m){function k(){var a=r&&r.dispose;"function"===typeof a&&a.call(r);q=r=null}var r,q,p=a.a.W(a.f.childNodes(f));a.a.G.qa(f,k);a.m(function(){var l=a.a.c(g()),h,v;"string"===typeof l?h=l:(h=a.a.c(l.name),v=a.a.c(l.params));if(!h)throw Error("No component name specified");var n=q=++e;a.g.get(h,function(e){if(q===n){k();if(!e)throw Error("Unknown component '"+h+"'");c(h,e,f);var l=d(e,f,p,v);e=m.createChildContext(l,b,function(a){a.$component=
l;a.$componentTemplateNodes=p});r=l;a.hb(e,f)}})},null,{i:f});return{controlsDescendantBindings:!0}}};a.f.aa.component=!0})();var Q={"class":"className","for":"htmlFor"};a.d.attr={update:function(b,c){var d=a.a.c(c())||{};a.a.D(d,function(c,d){d=a.a.c(d);var g=!1===d||null===d||d===n;g&&b.removeAttribute(c);8>=a.a.C&&c in Q?(c=Q[c],g?b.removeAttribute(c):b[c]=d):g||b.setAttribute(c,d.toString());"name"===c&&a.a.vc(b,g?"":d.toString())})}};(function(){a.d.checked={after:["value","attr"],init:function(b,
c,d){function e(){var e=b.checked,f=p?g():e;if(!a.xa.Va()&&(!l||e)){var h=a.l.w(c);if(k){var m=r?h.p():h;q!==f?(e&&(a.a.ra(m,f,!0),a.a.ra(m,q,!1)),q=f):a.a.ra(m,f,e);r&&a.Da(h)&&h(m)}else a.h.Ga(h,d,"checked",f,!0)}}function f(){var d=a.a.c(c());b.checked=k?0<=a.a.o(d,g()):h?d:g()===d}var g=a.rc(function(){return d.has("checkedValue")?a.a.c(d.get("checkedValue")):d.has("value")?a.a.c(d.get("value")):b.value}),h="checkbox"==b.type,l="radio"==b.type;if(h||l){var m=c(),k=h&&a.a.c(m)instanceof Array,
r=!(k&&m.push&&m.splice),q=k?g():n,p=l||k;l&&!b.name&&a.d.uniqueName.init(b,function(){return!0});a.m(e,null,{i:b});a.a.q(b,"click",e);a.m(f,null,{i:b});m=n}}};a.h.ga.checked=!0;a.d.checkedValue={update:function(b,c){b.value=a.a.c(c())}}})();a.d.css={update:function(b,c){var d=a.a.c(c());null!==d&&"object"==typeof d?a.a.D(d,function(c,d){d=a.a.c(d);a.a.fb(b,c,d)}):(d=a.a.cb(String(d||"")),a.a.fb(b,b.__ko__cssValue,!1),b.__ko__cssValue=d,a.a.fb(b,d,!0))}};a.d.enable={update:function(b,c){var d=a.a.c(c());
d&&b.disabled?b.removeAttribute("disabled"):d||b.disabled||(b.disabled=!0)}};a.d.disable={update:function(b,c){a.d.enable.update(b,function(){return!a.a.c(c())})}};a.d.event={init:function(b,c,d,e,f){var g=c()||{};a.a.D(g,function(g){"string"==typeof g&&a.a.q(b,g,function(b){var m,k=c()[g];if(k){try{var r=a.a.W(arguments);e=f.$data;r.unshift(e);m=k.apply(e,r)}finally{!0!==m&&(b.preventDefault?b.preventDefault():b.returnValue=!1)}!1===d.get(g+"Bubble")&&(b.cancelBubble=!0,b.stopPropagation&&b.stopPropagation())}})})}};
a.d.foreach={mc:function(b){return function(){var c=b(),d=a.a.Bb(c);if(!d||"number"==typeof d.length)return{foreach:c,templateEngine:a.X.vb};a.a.c(c);return{foreach:d.data,as:d.as,includeDestroyed:d.includeDestroyed,afterAdd:d.afterAdd,beforeRemove:d.beforeRemove,afterRender:d.afterRender,beforeMove:d.beforeMove,afterMove:d.afterMove,templateEngine:a.X.vb}}},init:function(b,c){return a.d.template.init(b,a.d.foreach.mc(c))},update:function(b,c,d,e,f){return a.d.template.update(b,a.d.foreach.mc(c),
d,e,f)}};a.h.va.foreach=!1;a.f.aa.foreach=!0;a.d.hasfocus={init:function(b,c,d){function e(e){b.__ko_hasfocusUpdating=!0;var f=b.ownerDocument;if("activeElement"in f){var g;try{g=f.activeElement}catch(k){g=f.body}e=g===b}f=c();a.h.Ga(f,d,"hasfocus",e,!0);b.__ko_hasfocusLastValue=e;b.__ko_hasfocusUpdating=!1}var f=e.bind(null,!0),g=e.bind(null,!1);a.a.q(b,"focus",f);a.a.q(b,"focusin",f);a.a.q(b,"blur",g);a.a.q(b,"focusout",g)},update:function(b,c){var d=!!a.a.c(c());b.__ko_hasfocusUpdating||b.__ko_hasfocusLastValue===
d||(d?b.focus():b.blur(),!d&&b.__ko_hasfocusLastValue&&b.ownerDocument.body.focus(),a.l.w(a.a.Fa,null,[b,d?"focusin":"focusout"]))}};a.h.ga.hasfocus=!0;a.d.hasFocus=a.d.hasfocus;a.h.ga.hasFocus=!0;a.d.html={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.Eb(b,c())}};K("if");K("ifnot",!1,!0);K("with",!0,!1,function(a,c){return a.ac(c)});var L={};a.d.options={init:function(b){if("select"!==a.a.A(b))throw Error("options binding applies only to SELECT elements");for(;0<
b.length;)b.remove(0);return{controlsDescendantBindings:!0}},update:function(b,c,d){function e(){return a.a.Ma(b.options,function(a){return a.selected})}function f(a,b,c){var d=typeof b;return"function"==d?b(a):"string"==d?a[b]:c}function g(c,e){if(A&&k)a.j.ja(b,a.a.c(d.get("value")),!0);else if(p.length){var f=0<=a.a.o(p,a.j.u(e[0]));a.a.wc(e[0],f);A&&!f&&a.l.w(a.a.Fa,null,[b,"change"])}}var h=b.multiple,l=0!=b.length&&h?b.scrollTop:null,m=a.a.c(c()),k=d.get("valueAllowUnset")&&d.has("value"),r=
d.get("optionsIncludeDestroyed");c={};var q,p=[];k||(h?p=a.a.ib(e(),a.j.u):0<=b.selectedIndex&&p.push(a.j.u(b.options[b.selectedIndex])));m&&("undefined"==typeof m.length&&(m=[m]),q=a.a.Ma(m,function(b){return r||b===n||null===b||!a.a.c(b._destroy)}),d.has("optionsCaption")&&(m=a.a.c(d.get("optionsCaption")),null!==m&&m!==n&&q.unshift(L)));var A=!1;c.beforeRemove=function(a){b.removeChild(a)};m=g;d.has("optionsAfterRender")&&"function"==typeof d.get("optionsAfterRender")&&(m=function(b,c){g(0,c);
a.l.w(d.get("optionsAfterRender"),null,[c[0],b!==L?b:n])});a.a.Db(b,q,function(c,e,g){g.length&&(p=!k&&g[0].selected?[a.j.u(g[0])]:[],A=!0);e=b.ownerDocument.createElement("option");c===L?(a.a.bb(e,d.get("optionsCaption")),a.j.ja(e,n)):(g=f(c,d.get("optionsValue"),c),a.j.ja(e,a.a.c(g)),c=f(c,d.get("optionsText"),g),a.a.bb(e,c));return[e]},c,m);a.l.w(function(){k?a.j.ja(b,a.a.c(d.get("value")),!0):(h?p.length&&e().length<p.length:p.length&&0<=b.selectedIndex?a.j.u(b.options[b.selectedIndex])!==p[0]:
p.length||0<=b.selectedIndex)&&a.a.Fa(b,"change")});a.a.Sc(b);l&&20<Math.abs(l-b.scrollTop)&&(b.scrollTop=l)}};a.d.options.zb=a.a.e.J();a.d.selectedOptions={after:["options","foreach"],init:function(b,c,d){a.a.q(b,"change",function(){var e=c(),f=[];a.a.r(b.getElementsByTagName("option"),function(b){b.selected&&f.push(a.j.u(b))});a.h.Ga(e,d,"selectedOptions",f)})},update:function(b,c){if("select"!=a.a.A(b))throw Error("values binding applies only to SELECT elements");var d=a.a.c(c()),e=b.scrollTop;
d&&"number"==typeof d.length&&a.a.r(b.getElementsByTagName("option"),function(b){var c=0<=a.a.o(d,a.j.u(b));b.selected!=c&&a.a.wc(b,c)});b.scrollTop=e}};a.h.ga.selectedOptions=!0;a.d.style={update:function(b,c){var d=a.a.c(c()||{});a.a.D(d,function(c,d){d=a.a.c(d);if(null===d||d===n||!1===d)d="";b.style[c]=d})}};a.d.submit={init:function(b,c,d,e,f){if("function"!=typeof c())throw Error("The value for a submit binding must be a function");a.a.q(b,"submit",function(a){var d,e=c();try{d=e.call(f.$data,
b)}finally{!0!==d&&(a.preventDefault?a.preventDefault():a.returnValue=!1)}})}};a.d.text={init:function(){return{controlsDescendantBindings:!0}},update:function(b,c){a.a.bb(b,c())}};a.f.aa.text=!0;(function(){if(x&&x.navigator)var b=function(a){if(a)return parseFloat(a[1])},c=x.opera&&x.opera.version&&parseInt(x.opera.version()),d=x.navigator.userAgent,e=b(d.match(/^(?:(?!chrome).)*version\/([^ ]*) safari/i)),f=b(d.match(/Firefox\/([^ ]*)/));if(10>a.a.C)var g=a.a.e.J(),h=a.a.e.J(),l=function(b){var c=
this.activeElement;(c=c&&a.a.e.get(c,h))&&c(b)},m=function(b,c){var d=b.ownerDocument;a.a.e.get(d,g)||(a.a.e.set(d,g,!0),a.a.q(d,"selectionchange",l));a.a.e.set(b,h,c)};a.d.textInput={init:function(b,d,g){function l(c,d){a.a.q(b,c,d)}function h(){var c=a.a.c(d());if(null===c||c===n)c="";u!==n&&c===u?a.a.setTimeout(h,4):b.value!==c&&(s=c,b.value=c)}function y(){t||(u=b.value,t=a.a.setTimeout(v,4))}function v(){clearTimeout(t);u=t=n;var c=b.value;s!==c&&(s=c,a.h.Ga(d(),g,"textInput",c))}var s=b.value,
t,u,x=9==a.a.C?y:v;10>a.a.C?(l("propertychange",function(a){"value"===a.propertyName&&x(a)}),8==a.a.C&&(l("keyup",v),l("keydown",v)),8<=a.a.C&&(m(b,x),l("dragend",y))):(l("input",v),5>e&&"textarea"===a.a.A(b)?(l("keydown",y),l("paste",y),l("cut",y)):11>c?l("keydown",y):4>f&&(l("DOMAutoComplete",v),l("dragdrop",v),l("drop",v)));l("change",v);a.m(h,null,{i:b})}};a.h.ga.textInput=!0;a.d.textinput={preprocess:function(a,b,c){c("textInput",a)}}})();a.d.uniqueName={init:function(b,c){if(c()){var d="ko_unique_"+
++a.d.uniqueName.Nc;a.a.vc(b,d)}}};a.d.uniqueName.Nc=0;a.d.value={after:["options","foreach"],init:function(b,c,d){if("input"!=b.tagName.toLowerCase()||"checkbox"!=b.type&&"radio"!=b.type){var e=["change"],f=d.get("valueUpdate"),g=!1,h=null;f&&("string"==typeof f&&(f=[f]),a.a.ta(e,f),e=a.a.Wb(e));var l=function(){h=null;g=!1;var e=c(),f=a.j.u(b);a.h.Ga(e,d,"value",f)};!a.a.C||"input"!=b.tagName.toLowerCase()||"text"!=b.type||"off"==b.autocomplete||b.form&&"off"==b.form.autocomplete||-1!=a.a.o(e,"propertychange")||
(a.a.q(b,"propertychange",function(){g=!0}),a.a.q(b,"focus",function(){g=!1}),a.a.q(b,"blur",function(){g&&l()}));a.a.r(e,function(c){var d=l;a.a.sd(c,"after")&&(d=function(){h=a.j.u(b);a.a.setTimeout(l,0)},c=c.substring(5));a.a.q(b,c,d)});var m=function(){var e=a.a.c(c()),f=a.j.u(b);if(null!==h&&e===h)a.a.setTimeout(m,0);else if(e!==f)if("select"===a.a.A(b)){var g=d.get("valueAllowUnset"),f=function(){a.j.ja(b,e,g)};f();g||e===a.j.u(b)?a.a.setTimeout(f,0):a.l.w(a.a.Fa,null,[b,"change"])}else a.j.ja(b,
e)};a.m(m,null,{i:b})}else a.La(b,{checkedValue:c})},update:function(){}};a.h.ga.value=!0;a.d.visible={update:function(b,c){var d=a.a.c(c()),e="none"!=b.style.display;d&&!e?b.style.display="":!d&&e&&(b.style.display="none")}};(function(b){a.d[b]={init:function(c,d,e,f,g){return a.d.event.init.call(this,c,function(){var a={};a[b]=d();return a},e,f,g)}}})("click");a.P=function(){};a.P.prototype.renderTemplateSource=function(){throw Error("Override renderTemplateSource");};a.P.prototype.createJavaScriptEvaluatorBlock=
function(){throw Error("Override createJavaScriptEvaluatorBlock");};a.P.prototype.makeTemplateSource=function(b,c){if("string"==typeof b){c=c||t;var d=c.getElementById(b);if(!d)throw Error("Cannot find template with ID "+b);return new a.v.n(d)}if(1==b.nodeType||8==b.nodeType)return new a.v.sa(b);throw Error("Unknown template type: "+b);};a.P.prototype.renderTemplate=function(a,c,d,e){a=this.makeTemplateSource(a,e);return this.renderTemplateSource(a,c,d,e)};a.P.prototype.isTemplateRewritten=function(a,
c){return!1===this.allowTemplateRewriting?!0:this.makeTemplateSource(a,c).data("isRewritten")};a.P.prototype.rewriteTemplate=function(a,c,d){a=this.makeTemplateSource(a,d);c=c(a.text());a.text(c);a.data("isRewritten",!0)};a.b("templateEngine",a.P);a.Ib=function(){function b(b,c,d,h){b=a.h.Ab(b);for(var l=a.h.va,m=0;m<b.length;m++){var k=b[m].key;if(l.hasOwnProperty(k)){var r=l[k];if("function"===typeof r){if(k=r(b[m].value))throw Error(k);}else if(!r)throw Error("This template engine does not support the '"+
k+"' binding within its templates");}}d="ko.__tr_ambtns(function($context,$element){return(function(){return{ "+a.h.Xa(b,{valueAccessors:!0})+" } })()},'"+d.toLowerCase()+"')";return h.createJavaScriptEvaluatorBlock(d)+c}var c=/(<([a-z]+\d*)(?:\s+(?!data-bind\s*=\s*)[a-z0-9\-]+(?:=(?:\"[^\"]*\"|\'[^\']*\'|[^>]*))?)*\s+)data-bind\s*=\s*(["'])([\s\S]*?)\3/gi,d=/\x3c!--\s*ko\b\s*([\s\S]*?)\s*--\x3e/g;return{Tc:function(b,c,d){c.isTemplateRewritten(b,d)||c.rewriteTemplate(b,function(b){return a.Ib.jd(b,
c)},d)},jd:function(a,f){return a.replace(c,function(a,c,d,e,k){return b(k,c,d,f)}).replace(d,function(a,c){return b(c,"\x3c!-- ko --\x3e","#comment",f)})},Jc:function(b,c){return a.N.yb(function(d,h){var l=d.nextSibling;l&&l.nodeName.toLowerCase()===c&&a.La(l,b,h)})}}}();a.b("__tr_ambtns",a.Ib.Jc);(function(){a.v={};a.v.n=function(b){if(this.n=b){var c=a.a.A(b);this.eb="script"===c?1:"textarea"===c?2:"template"==c&&b.content&&11===b.content.nodeType?3:4}};a.v.n.prototype.text=function(){var b=1===
this.eb?"text":2===this.eb?"value":"innerHTML";if(0==arguments.length)return this.n[b];var c=arguments[0];"innerHTML"===b?a.a.Eb(this.n,c):this.n[b]=c};var b=a.a.e.J()+"_";a.v.n.prototype.data=function(c){if(1===arguments.length)return a.a.e.get(this.n,b+c);a.a.e.set(this.n,b+c,arguments[1])};var c=a.a.e.J();a.v.n.prototype.nodes=function(){var b=this.n;if(0==arguments.length)return(a.a.e.get(b,c)||{}).mb||(3===this.eb?b.content:4===this.eb?b:n);a.a.e.set(b,c,{mb:arguments[0]})};a.v.sa=function(a){this.n=
a};a.v.sa.prototype=new a.v.n;a.v.sa.prototype.text=function(){if(0==arguments.length){var b=a.a.e.get(this.n,c)||{};b.Jb===n&&b.mb&&(b.Jb=b.mb.innerHTML);return b.Jb}a.a.e.set(this.n,c,{Jb:arguments[0]})};a.b("templateSources",a.v);a.b("templateSources.domElement",a.v.n);a.b("templateSources.anonymousTemplate",a.v.sa)})();(function(){function b(b,c,d){var e;for(c=a.f.nextSibling(c);b&&(e=b)!==c;)b=a.f.nextSibling(e),d(e,b)}function c(c,d){if(c.length){var e=c[0],f=c[c.length-1],g=e.parentNode,h=
a.S.instance,n=h.preprocessNode;if(n){b(e,f,function(a,b){var c=a.previousSibling,d=n.call(h,a);d&&(a===e&&(e=d[0]||b),a===f&&(f=d[d.length-1]||c))});c.length=0;if(!e)return;e===f?c.push(e):(c.push(e,f),a.a.Ba(c,g))}b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.Ub(d,b)});b(e,f,function(b){1!==b.nodeType&&8!==b.nodeType||a.N.Cc(b,[d])});a.a.Ba(c,g)}}function d(a){return a.nodeType?a:0<a.length?a[0]:null}function e(b,e,f,h,q){q=q||{};var p=(b&&d(b)||f||{}).ownerDocument,n=q.templateEngine||g;
a.Ib.Tc(f,n,p);f=n.renderTemplate(f,h,q,p);if("number"!=typeof f.length||0<f.length&&"number"!=typeof f[0].nodeType)throw Error("Template engine must return an array of DOM nodes");p=!1;switch(e){case "replaceChildren":a.f.fa(b,f);p=!0;break;case "replaceNode":a.a.uc(b,f);p=!0;break;case "ignoreTargetNode":break;default:throw Error("Unknown renderMode: "+e);}p&&(c(f,h),q.afterRender&&a.l.w(q.afterRender,null,[f,h.$data]));return f}function f(b,c,d){return a.I(b)?b():"function"===typeof b?b(c,d):b}
var g;a.Fb=function(b){if(b!=n&&!(b instanceof a.P))throw Error("templateEngine must inherit from ko.templateEngine");g=b};a.Cb=function(b,c,k,h,q){k=k||{};if((k.templateEngine||g)==n)throw Error("Set a template engine before calling renderTemplate");q=q||"replaceChildren";if(h){var p=d(h);return a.B(function(){var g=c&&c instanceof a.R?c:new a.R(c,null,null,null,{exportDependencies:!0}),n=f(b,g.$data,g),g=e(h,q,n,g,k);"replaceNode"==q&&(h=g,p=d(h))},null,{ya:function(){return!p||!a.a.qb(p)},i:p&&
"replaceNode"==q?p.parentNode:p})}return a.N.yb(function(d){a.Cb(b,c,k,d,"replaceNode")})};a.pd=function(b,d,g,h,q){function p(a,b){c(b,t);g.afterRender&&g.afterRender(b,a);t=null}function s(a,c){t=q.createChildContext(a,g.as,function(a){a.$index=c});var d=f(b,a,t);return e(null,"ignoreTargetNode",d,t,g)}var t;return a.B(function(){var b=a.a.c(d)||[];"undefined"==typeof b.length&&(b=[b]);b=a.a.Ma(b,function(b){return g.includeDestroyed||b===n||null===b||!a.a.c(b._destroy)});a.l.w(a.a.Db,null,[h,b,
s,g,p])},null,{i:h})};var h=a.a.e.J();a.d.template={init:function(b,c){var d=a.a.c(c());if("string"==typeof d||d.name)a.f.za(b);else{if("nodes"in d){if(d=d.nodes||[],a.I(d))throw Error('The "nodes" option must be a plain, non-observable array.');}else d=a.f.childNodes(b);d=a.a.nc(d);(new a.v.sa(b)).nodes(d)}return{controlsDescendantBindings:!0}},update:function(b,c,d,e,f){var g=c();c=a.a.c(g);d=!0;e=null;"string"==typeof c?c={}:(g=c.name,"if"in c&&(d=a.a.c(c["if"])),d&&"ifnot"in c&&(d=!a.a.c(c.ifnot)));
"foreach"in c?e=a.pd(g||b,d&&c.foreach||[],c,b,f):d?(f="data"in c?f.ac(c.data,c.as):f,e=a.Cb(g||b,f,c,b)):a.f.za(b);f=e;(c=a.a.e.get(b,h))&&"function"==typeof c.k&&c.k();a.a.e.set(b,h,f&&f.ca()?f:n)}};a.h.va.template=function(b){b=a.h.Ab(b);return 1==b.length&&b[0].unknown||a.h.fd(b,"name")?null:"This template engine does not support anonymous templates nested within its templates"};a.f.aa.template=!0})();a.b("setTemplateEngine",a.Fb);a.b("renderTemplate",a.Cb);a.a.hc=function(a,c,d){if(a.length&&
c.length){var e,f,g,h,l;for(e=f=0;(!d||e<d)&&(h=a[f]);++f){for(g=0;l=c[g];++g)if(h.value===l.value){h.moved=l.index;l.moved=h.index;c.splice(g,1);e=g=0;break}e+=g}}};a.a.lb=function(){function b(b,d,e,f,g){var h=Math.min,l=Math.max,m=[],k,n=b.length,q,p=d.length,s=p-n||1,t=n+p+1,v,u,x;for(k=0;k<=n;k++)for(u=v,m.push(v=[]),x=h(p,k+s),q=l(0,k-1);q<=x;q++)v[q]=q?k?b[k-1]===d[q-1]?u[q-1]:h(u[q]||t,v[q-1]||t)+1:q+1:k+1;h=[];l=[];s=[];k=n;for(q=p;k||q;)p=m[k][q]-1,q&&p===m[k][q-1]?l.push(h[h.length]={status:e,
value:d[--q],index:q}):k&&p===m[k-1][q]?s.push(h[h.length]={status:f,value:b[--k],index:k}):(--q,--k,g.sparse||h.push({status:"retained",value:d[q]}));a.a.hc(s,l,!g.dontLimitMoves&&10*n);return h.reverse()}return function(a,d,e){e="boolean"===typeof e?{dontLimitMoves:e}:e||{};a=a||[];d=d||[];return a.length<d.length?b(a,d,"added","deleted",e):b(d,a,"deleted","added",e)}}();a.b("utils.compareArrays",a.a.lb);(function(){function b(b,c,d,h,l){var m=[],k=a.B(function(){var k=c(d,l,a.a.Ba(m,b))||[];0<
m.length&&(a.a.uc(m,k),h&&a.l.w(h,null,[d,k,l]));m.length=0;a.a.ta(m,k)},null,{i:b,ya:function(){return!a.a.Tb(m)}});return{ea:m,B:k.ca()?k:n}}var c=a.a.e.J(),d=a.a.e.J();a.a.Db=function(e,f,g,h,l){function m(b,c){w=q[c];u!==c&&(D[b]=w);w.tb(u++);a.a.Ba(w.ea,e);t.push(w);z.push(w)}function k(b,c){if(b)for(var d=0,e=c.length;d<e;d++)c[d]&&a.a.r(c[d].ea,function(a){b(a,d,c[d].ka)})}f=f||[];h=h||{};var r=a.a.e.get(e,c)===n,q=a.a.e.get(e,c)||[],p=a.a.ib(q,function(a){return a.ka}),s=a.a.lb(p,f,h.dontLimitMoves),
t=[],v=0,u=0,x=[],z=[];f=[];for(var D=[],p=[],w,C=0,B,E;B=s[C];C++)switch(E=B.moved,B.status){case "deleted":E===n&&(w=q[v],w.B&&(w.B.k(),w.B=n),a.a.Ba(w.ea,e).length&&(h.beforeRemove&&(t.push(w),z.push(w),w.ka===d?w=null:f[C]=w),w&&x.push.apply(x,w.ea)));v++;break;case "retained":m(C,v++);break;case "added":E!==n?m(C,E):(w={ka:B.value,tb:a.O(u++)},t.push(w),z.push(w),r||(p[C]=w))}a.a.e.set(e,c,t);k(h.beforeMove,D);a.a.r(x,h.beforeRemove?a.ba:a.removeNode);for(var C=0,r=a.f.firstChild(e),F;w=z[C];C++){w.ea||
a.a.extend(w,b(e,g,w.ka,l,w.tb));for(v=0;s=w.ea[v];r=s.nextSibling,F=s,v++)s!==r&&a.f.kc(e,s,F);!w.ad&&l&&(l(w.ka,w.ea,w.tb),w.ad=!0)}k(h.beforeRemove,f);for(C=0;C<f.length;++C)f[C]&&(f[C].ka=d);k(h.afterMove,D);k(h.afterAdd,p)}})();a.b("utils.setDomNodeChildrenFromArrayMapping",a.a.Db);a.X=function(){this.allowTemplateRewriting=!1};a.X.prototype=new a.P;a.X.prototype.renderTemplateSource=function(b,c,d,e){if(c=(9>a.a.C?0:b.nodes)?b.nodes():null)return a.a.W(c.cloneNode(!0).childNodes);b=b.text();
return a.a.na(b,e)};a.X.vb=new a.X;a.Fb(a.X.vb);a.b("nativeTemplateEngine",a.X);(function(){a.xb=function(){var a=this.ed=function(){if(!u||!u.tmpl)return 0;try{if(0<=u.tmpl.tag.tmpl.open.toString().indexOf("__"))return 2}catch(a){}return 1}();this.renderTemplateSource=function(b,e,f,g){g=g||t;f=f||{};if(2>a)throw Error("Your version of jQuery.tmpl is too old. Please upgrade to jQuery.tmpl 1.0.0pre or later.");var h=b.data("precompiled");h||(h=b.text()||"",h=u.template(null,"{{ko_with $item.koBindingContext}}"+
h+"{{/ko_with}}"),b.data("precompiled",h));b=[e.$data];e=u.extend({koBindingContext:e},f.templateOptions);e=u.tmpl(h,b,e);e.appendTo(g.createElement("div"));u.fragments={};return e};this.createJavaScriptEvaluatorBlock=function(a){return"{{ko_code ((function() { return "+a+" })()) }}"};this.addTemplate=function(a,b){t.write("<script type='text/html' id='"+a+"'>"+b+"\x3c/script>")};0<a&&(u.tmpl.tag.ko_code={open:"__.push($1 || '');"},u.tmpl.tag.ko_with={open:"with($1) {",close:"} "})};a.xb.prototype=
new a.P;var b=new a.xb;0<b.ed&&a.Fb(b);a.b("jqueryTmplTemplateEngine",a.xb)})()})})();})();

View File

@@ -50,7 +50,8 @@ legend,
color: white !important;
}
.hover-button {
.hover-button,
.fileControls a:hover {
opacity: 0.7;
}
@@ -75,13 +76,14 @@ legend,
background-color: #666;
}
.navbar-collapse.in .dropdown-menu {
.navbar-collapse.in .dropdown-menu, {
border: none;
}
.max-speed-input-clear,
.max-speed-input-clear:hover,
.nav-tabs>li>a:hover {
.nav-tabs>li>a:hover,
.fileControls a {
color: black;
}
@@ -109,6 +111,7 @@ legend,
color: #EBEBEB;
}
table,
.table-striped>tbody>tr:nth-child(even)>td,
.table>tbody>tr:nth-child(even)>td,
.table th,
@@ -156,7 +159,9 @@ select.form-control,
.retry-button, .retry-button-inactive,
.history-options-show-failed,
.queue-error-info,
.options-bad-status {
.options-bad-status,
.history-failed-download:hover .retry-button .glyphicon:before,
.retry-button:hover .glyphicon:before {
color: #F95151 !important;
}
@@ -175,7 +180,7 @@ tbody .caret {
color: #D6D6D6;
}
td.name .name-ratings span,
td.name .name-icons span,
.navbar-nav .open .dropdown-menu>li>a,
.dropdown-header,
#modal-help small,
@@ -203,6 +208,10 @@ hr {
border-bottom: none !important;
}
#modal-item-files .item-files-table {
border-bottom: 1px solid black;
}
.history-queue-swicher .nav-tabs>li>a,
.history-queue-swicher .nav-tabs>li.active>a {
border-bottom: none;
@@ -220,6 +229,11 @@ hr {
box-shadow: inset 1px 0px 1px rgba(0, 0, 0, 1);
}
.modal-content {
border-color: #727272;
border-top: none;
}
/* Placeholders - Will not work if grouped! */
::-webkit-input-placeholder {
color: #EBEBEB !important;

View File

@@ -52,8 +52,8 @@ h2 {
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
background-color: #ffffff;
border: 1px solid #cccccc;
margin-left: -150px;
margin-top: 4px;
margin-left: -220px;
margin-top: 5px;
}
.navbar-collapse.in .dropdown-menu a,
@@ -527,15 +527,23 @@ tbody>tr>td:last-child {
}
.hover-button.glyphicon-play,
.hover-button.glyphicon-forward,
.hover-button.glyphicon-stop {
opacity: 1;
color: #474747;
}
.hover-button.disabled,
.hover-button.disabled:hover {
.hover-button.disabled:hover,
.name-options.disabled .hover-button,
.name-options.disabled .hover-button:hover {
cursor: not-allowed !important;
opacity: 0.1 !important;
pointer-events: none;
}
.name-options.disabled {
cursor: not-allowed !important;
}
.info-container {
@@ -617,6 +625,7 @@ td.name .row-wrap-text {
}
.queue-table td.name .name-options small,
.queue-table td.name .direct-unpack,
.queue-item-password {
opacity: 0.5;
}
@@ -626,7 +635,7 @@ td.name .row-wrap-text {
}
.queue-table td.name:hover .row-wrap-text {
max-width: calc(100% - 85px);
max-width: calc(100% - 125px);
/* Change for each size! */
}
@@ -648,19 +657,23 @@ td.name .row-wrap-text {
border: 1px solid #ccc;
}
td.name .name-ratings {
td.name .name-icons {
display: inline;
margin-left: 5px;
color: black !important;
text-decoration: none !important;
}
.queue-table td.name:hover .name-ratings {
display: none;
td.name .name-icons .glyphicon {
margin-left: 2px;
top: 2px;
font-weight: bold;
}
td.name .name-ratings .glyphicon {
margin-left: 2px;
.glyphicon-chevron-down,
.glyphicon-chevron-up {
top: 2px;
font-weight: bold;
}
tbody.no-downloads tr td {
@@ -769,6 +782,35 @@ tr.queue-item>td:first-child>a {
padding-right: 10px;
}
.item-files-table tr .fileControls{
float:right;
display:none;
}
.item-files-table tr.files-sortable:hover .fileControls{
float:right;
display:block;
margin-left:5px;
}
.progress .progress-bar .fileDetails {
display:inline;
text-align: left;
margin-left: 70px;
line-height: 25px;
position: absolute;
top: 0;
left: 0;
z-index: 2;
font-size: 12px;
color: #404040;
padding-right: 0px;
}
.progress .progress-bar .fileDetails>span {
float: left;
}
.progress strong {
font-size: 13px;
}
@@ -1035,7 +1077,7 @@ tr.queue-item>td:first-child>a {
opacity: 1;
}
.history-ratings .name-ratings {
.history-ratings .name-icons {
float: none !important;
}
@@ -1623,6 +1665,11 @@ input[name="nzbURL"] {
#modal-item-files .item-files-table .progress small {
color: #727272 !important;
margin-left: 5px;
}
#modal-item-files .item-files-table tr.files-sortable:hover .progress small {
display:none;
}
#modal-item-files .item-files-table td {
@@ -1810,7 +1857,7 @@ input[name="nzbURL"] {
}
@media screen and (max-width: 1200px) {
td.name .name-ratings {
td.name .name-icons {
margin-left: 0px;
margin-right: -5px;
display: block;
@@ -1857,6 +1904,11 @@ input[name="nzbURL"] {
.queue .sortable-placeholder td {
padding: 9px 0px 8px !important;
}
.queue-table .buttonMoveToBottom,
.queue-table .buttonMoveToTop {
display: inline;
}
}
@media screen and (min-height: 800px) {
@@ -1921,49 +1973,6 @@ input[name="nzbURL"] {
}
}
/***
SPECIAL FOR FIREFOX
It uses very high CPU for anything animated (Sep 2015)
Disable animations on progress-bar and make the History-'processing' a block animation
Can be removed if it's performance gets better in the future..
***/
@supports (-moz-transform: translate(0, 0)) {
.progress-bar {
transform: none !important;
animation: none !important;
transition: none !important;
}
@keyframes stretchdelay {
0%, 60% {
transform: scaleY(0.4);
}
61%, 100% {
transform: scaleY(1.0);
}
}
.processing-download > div {
animation: stretchdelay 2s infinite linear;
}
.processing-download .loader-bar-two {
animation-delay: 0.2s;
}
.processing-download .loader-bar-three {
animation-delay: 0.4s;
}
.processing-download .loader-bar-four {
animation-delay: 0.6s;
}
.queue-table td.name input {
margin-left: -2px;
}
}
/***
Bootstrap overwrites

View File

@@ -132,6 +132,11 @@ h2 {
max-width: calc(100% - 45px);
}
.queue-table .buttonMoveToBottom,
.queue-table .buttonMoveToTop {
display: none;
}
tr.queue-item>td:first-child>a {
margin-top: 3px;
}

View File

@@ -1,7 +1,7 @@
Plush for SABnzbd 0.6.x | Feb. 21 2010
assembled by pairofdimes - see LICENSE-CC.txt
http://forums.sabnzbd.org contributions welcome
https://forums.sabnzbd.org contributions welcome
======================
THANKS TO CONTRIBUTORS

View File

@@ -101,6 +101,7 @@
<div id="nav_text_left">
<span id="warning_box"><b><a href="${path}status/#tabs-warnings" id="last_warning" title="#echo $last_warning.replace("\n"," ").replace('"',"'") #"><span id="have_warnings">$have_warnings</span> $T('warnings')</a></b></span>
#if $pane=="Main"#
&sdot; <a href="${path}config/general#web_dir">#echo $T('useGlitter').split('.')[0]#.</a>
#if $new_release#&sdot; <a href="$new_rel_url" id="new_release" target="_blank">$T('Plush-updateAvailable').replace(' ','&nbsp;')</a>#end if#
#end if#
</div>

View File

@@ -23,8 +23,8 @@
<div id="help_modal">
<table>
<tr><td><strong>$T('menu-wiki'):</strong></td><td><a href="$helpuri$help_uri" target="_blank">$helpuri$help_uri</a></td></tr>
<tr><td><strong>$T('menu-forums'):</strong></td><td><a href="http://forums.sabnzbd.org/" target="_blank">http://forums.sabnzbd.org/</a></td></tr>
<tr><td><strong>$T('menu-irc'):</strong></td><td><a href="http://www.sabnzbd.org/live-chat/" target="_blank">http://www.sabnzbd.org/live-chat/</a></td></tr>
<tr><td><strong>$T('menu-forums'):</strong></td><td><a href="https://forums.sabnzbd.org/" target="_blank">https://forums.sabnzbd.org/</a></td></tr>
<tr><td><strong>$T('menu-irc'):</strong></td><td><a href="https://sabnzbd.org/live-chat.html" target="_blank">https://sabnzbd.org/live-chat.html</a></td></tr>
</table>
<div class="sabnzbd_logo main_sprite_container sprite_sabnzbdplus_logo"></div>
<p><strong>SABnzbd $T('version'):</strong> $version</p>

View File

@@ -56,7 +56,7 @@
</td>
<td class="download-title" <!--#if $rating_enable#-->style="width:35%"<!--#end if#-->>
<a href="nzb/$slot.nzo_id/" title="$T('status'): $T('post-'+$slot.status)<br/>$T('nzo-age'): $slot.avg_age<br/><!--#if $slot.missing#-->$T('missingArt'): $slot.missing<!--#end if#-->">$slot.filename.replace('.', '.&#8203;').replace('_', '_&#8203;')<!--#if $slot.password#--> / $slot.password<!--#end if#--></a>
<a href="nzb/$slot.nzo_id/" title="$T('status'): $T('post-'+$slot.status)<br/>$T('nzo-age'): $slot.avg_age<br/><!--#if $slot.mbmissing!="0.00"#-->$T('missingArt'): $slot.mbmissing $T('MB')<!--#end if#-->">$slot.filename.replace('.', '.&#8203;').replace('_', '_&#8203;')<!--#if $slot.password#--> / $slot.password<!--#end if#--></a>
</td>
<!--#if $rating_enable#-->

View File

@@ -109,11 +109,6 @@ body {
font-weight: bold;
}
#new_release {
color: green;
font-weight: bold;
}
#livetip {
position: absolute;
background-color: #cfc;
@@ -343,6 +338,7 @@ body {
clear:left;
padding: 0 20px 0 25px;
font-size:90%;
font-weight: bold;
}
#nav_text_left a, #nav_text_right a {
color:#000;
@@ -1083,7 +1079,7 @@ tr:hover .history_added { color: black; }
color: red;
margin-left: 10px;
}
.rating_icon_vision {
width: 16px;
height: 16px;

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 B

After

Width:  |  Height:  |  Size: 78 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 729 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 932 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 256 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 508 B

After

Width:  |  Height:  |  Size: 405 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 980 B

After

Width:  |  Height:  |  Size: 618 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 454 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 854 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 276 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 698 B

After

Width:  |  Height:  |  Size: 524 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 446 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 809 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 811 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 479 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 710 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 363 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 764 B

After

Width:  |  Height:  |  Size: 617 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 B

After

Width:  |  Height:  |  Size: 195 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 29 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 610 B

After

Width:  |  Height:  |  Size: 472 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 B

After

Width:  |  Height:  |  Size: 138 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 B

After

Width:  |  Height:  |  Size: 181 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 218 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 221 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 B

After

Width:  |  Height:  |  Size: 86 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 B

After

Width:  |  Height:  |  Size: 86 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 B

After

Width:  |  Height:  |  Size: 86 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 B

After

Width:  |  Height:  |  Size: 86 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 B

After

Width:  |  Height:  |  Size: 99 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 B

After

Width:  |  Height:  |  Size: 88 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 B

After

Width:  |  Height:  |  Size: 87 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 B

After

Width:  |  Height:  |  Size: 90 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 B

After

Width:  |  Height:  |  Size: 83 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 B

After

Width:  |  Height:  |  Size: 748 B

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